Compare commits

...
126 Commits
Author SHA1 Message Date
Nick 7ec615a656 probe: ssh push test 2026-10-06 13:54:32 +01:00
conductor 0fdfe195e7 chore(release): auto-bump version to 0.99.6 — earned by #2473 (Scaffold: programme_continuation wired into close() (T5.15 slice 3) + same-secon)
Promotion record: http://127.0.0.1:3000/conductor/rune/pulls/2473
2026-10-06 03:52:23 +01:00
conductor 71d2d63a36 Merge pull request 'Scaffold: programme_continuation wired into close() (T5.15 slice 3) + same-second id fix' (#2473) from feat/wire-programme-continuation into master 2026-10-06 03:52:17 +01:00
Nick 0873b521e3 feat(scaffold): programme_continuation wired into close() (T5.15 slice 3) + same-second contract-id collision fix
Slice 3 of the completion wave: closing a contract that is a stage of a persisted Programme marks that stage COMPLETE, persists the ledger, and rides the continuation canary on the close envelope — the next eligible stage is SURFACED, never guessed (task done ≠ programme done). No programme bound → no continuation block; ledger failure degrades without failing the closure. Three contracts. Also fixed en route: contract ids were second-granularity timestamps that collided when two needs filed in the same second (red test proves distinct ids now, uuid suffix added).
2026-10-06 03:46:24 +01:00
conductor f7e6c5cff5 chore(release): auto-bump version to 0.99.5 — earned by #2470 (TUI: chat pane LEFT + post-response dim-spam consolidation (T5.14/T5.13))
Promotion record: http://127.0.0.1:3000/conductor/rune/pulls/2470
2026-10-06 03:27:08 +01:00
conductor fc46dbea52 Merge pull request 'TUI: chat pane LEFT + post-response dim-spam consolidation (T5.14/T5.13)' (#2470) from feat/tui-pane-swap-chat-left into master 2026-10-06 03:27:02 +01:00
conductor d8ede8f3f8 Merge branch 'master' into feat/tui-pane-swap-chat-left 2026-10-06 03:19:13 +01:00
conductor 804c2f62df chore(release): auto-bump version to 0.99.4 — earned by #2472 (Scaffold: construction_commands dispatch — the serving wire (T5.15 slice 2))
Promotion record: http://127.0.0.1:3000/conductor/rune/pulls/2472
2026-10-06 03:11:51 +01:00
conductor e5d10f18ae Merge pull request 'Scaffold: construction_commands dispatch — the serving wire (T5.15 slice 2)' (#2472) from feat/wire-construction-commands-dispatch into master 2026-10-06 03:11:44 +01:00
conductor 3ed58d48a9 Merge branch 'master' into feat/tui-pane-swap-chat-left 2026-10-06 03:02:57 +01:00
conductor 6c997b6af4 Merge branch 'master' into feat/wire-construction-commands-dispatch 2026-10-06 03:02:52 +01:00
conductor 9b5befb137 chore(release): auto-bump version to 0.99.3 — earned by #2471 (Scaffold: stage_guards wired into completion transitions (T5.15 slice 1))
Promotion record: http://127.0.0.1:3000/conductor/rune/pulls/2471
2026-10-06 02:50:18 +01:00
conductor 6899d859da Merge branch 'master' into feat/tui-pane-swap-chat-left 2026-10-06 02:50:11 +01:00
conductor 8ef70448c3 Merge pull request 'Scaffold: stage_guards wired into completion transitions (T5.15 slice 1)' (#2471) from feat/wire-stage-guards-transition into master 2026-10-06 02:50:08 +01:00
Nick 55f268f978 feat(scaffold): construction_commands dispatch — the serving wire (T5.15 slice 2)
The command seam validated intents but nothing EXECUTED them. dispatch() binds the canonical store, runs the validator, then executes accepted intents through the Construction Interface (resume/transition/amend_scope), refusing via the lifecycle with accepted=False+success=True (a refusal is a valid protocol outcome, not an error). Idempotent replay returns the recorded intent without re-executing. Three contracts: end-to-end phase movement, refusal shape, replay-no-double-execute; seam file 12/12. Slice 1 of 5 was stage_guards (#2467 wire wave).
2026-10-06 02:40:49 +01:00
Nick 320026a4a3 feat(scaffold): stage_guards wired into the completion-facing transitions (T5.15 slice 1)
The five orphaned V1 components: slice 1 wires stage_guards for real. transition(verifying|closed) now runs §15F readiness StageChecks derived from guard: verification obligations (or inline guard_checks); a blocked guard = VERIFICATION_FAILED envelope and the phase does NOT move — builder cannot self-certify past a failing check. building/free edges unchanged; capabilities() schema documents the new arg; CLI transport carries guard_checks as JSON arg 4. Four contracts red-first (blocked/pass/inline-override/free-edge); 56 interface+guards tests green.
2026-10-06 02:40:49 +01:00
Nick 0e88b6bc08 feat(tui): consolidate post-response dim spam into one collapsed line (#2468/T5.13)
Five separate dim system messages after every task/build response (budget, claims, growth, self-improve, plus the cron/queue echoes) spammed chat. New collect_post_response_signals() is a PURE headless collector with degrade-not-crash try blocks; the worker renders ONE line or silence. 6 frozen contracts: compose order [chat,task], resize semantics re-keyed, collector all-signals/silence/never-raises/cycle-multiple. 38 TUI/layout tests green; pyright error count unchanged at baseline 8.
2026-10-06 02:40:25 +01:00
Nick 004e5b3118 feat(tui): swap panes — chat LEFT, task RIGHT (#2465/T5.14)
Nick: chat-first feels natural. Compose yield order reversed (rune_tui.py compose), resize_left/resize_right actions re-keyed to the new visual positions (left=chat shrink now grows task), focus actions and all name-keyed query_one calls untouched (position-independent by design). Static source proof: yield order [chat, task]; 45 layout/TUI tests green.
2026-10-06 02:40:25 +01:00
conductor db15ae3650 chore(release): auto-bump version to 0.99.2 — earned by # (patch: converged release (push-to-master bump))
Promotion record: (unknown)
2026-10-06 02:28:19 +01:00
Nick d8ce21b76d fix(spine): the measured dichotomy decides — hints demoted to labeled fallbacks (#2461, Nick correction)
Nick caught the deviation: module verdicts were substring-guessed from names (memory→rust, gitea→go) with a silent rust default. Authority order now: the measured classification map (import-evidence) decides FIRST for every module; hint hits are labeled "(name hint)" so provenance is visible; unmeasured+unhinted modules are "unclassified" (12), never a guessed rust. Store rebuilt: 106 rust / 45 go measured, 33+7 hint-labeled, 23 test-infra, 12 honest unclassified. Battery 95 green (test updated to pin the authority contract).
2026-10-06 02:28:16 +01:00
Nick d4d43dbf67 merge: spine_language tests (the new-module henxel repair) 2026-10-06 02:07:44 +01:00
Nick 8a6c950140 test(spine_language): the carved transforms ship WITH tests (#2461 new-module henxel)
CI red at d67fc46: spine_language.py crossed the new-module henxel with zero coverage. Eight contracts: measured-map read + graceful degrade, kind-default verdicts, TUI/hints, subsystem member-majority, unqualified-discovery honesty (candidate stays DECLARED), legacy-ID normalization with edge re-pointing, and the closed-contract-only gather law. 8/8 green. Also: construction_commands dispatch WIP stashed on its own branch (slice 2, next).
2026-10-06 02:04:19 +01:00
Nick d67fc46581 chore(rebase): refresh store/rebase inventory + classification for the 6 post-W0 modules (#2461 close-out)
The W0 map had drifted (174 vs 180 files): cc_loader/sc_loader/mr_loader/mems_loader (FFI kernel membranes → RUST), spine_language (deterministic carve → RUST), __init__ (metadata reads) now carry evidence-backed side records and measured function/line counts. spine_language._classification_sides() — the runtime reader — sees 180 entries; spine battery green.
2026-10-06 01:40:56 +01:00
conductor 84b8d6b215 chore(release): auto-bump version to 0.99.1 — earned by # (patch: converged release (push-to-master bump))
Promotion record: (unknown)
2026-10-06 01:22:12 +01:00
Nick 7f5d57236a fix(ci): 0.99.0 CI repair — silent-handler annotations + spine_language umbrella mapping (#2461)
The 0.99.0 bump ran CI against the delegation code before the annotations landed: BLE001 345->349, S110 75->81 (my new kernel-failure fallbacks), plus spine_language unmapped in the umbrella manifest (test_umbrella_impact) and the resulting spine-rebuild membership diff (rebuild-neutrality). All four new fallbacks carry intentional-silence noqa annotations per the ratchet rule (count now exactly 345/75 — baseline UNCHANGED, ledger honest); spine_language mapped into perception next to spine; store rebuilt so the membership edge and language stamps are in the committed store. 127 tests green incl. the four CI-red surfaces.
2026-10-06 01:22:05 +01:00
conductor bda07c68ee chore(release): auto-bump version to 0.99.0 — earned by # (minor: converged release (push-to-master bump))
Promotion record: (unknown)
2026-10-06 00:11:25 +01:00
Nick c5be22be09 feat(cognition): witness/safety/abstraction kernel delegation + regex_lite faithful-subset rewrite (#2461 W3.4, #2465 adjacency)
The W3.4 kernels existed but the Python publics still ran Python. Now: witness extract_topic/detect_signals delegate through the _stopwords()/_tables() gather seams (PATTERN-TABLE GATHER-SEAM LAW) with _py_ oracles; abstraction extract_facts gains the deterministic kernel FALLBACK when the LLM returns nothing; tier_manager was already wired; the RUNE_COGNITION_CORE=python pin is now honored by a SHARED cc_loader.core() probe (the four module-local probes silently ignored it). The canary matrix caught a REAL kernel bug: regex_lite dropped \b anchors as literal b chars so every word-boundary row silently missed (bwrongb never appears in text); rewritten as alternatives-expansion + segment-variant DFS gap-walk; 34-row matrix judged against REAL Python re shows 0 mismatches. canary_growth_pins 27/27 x5 both engine modes; canary_unbounded 14/14; per-row fixtures carry re-truth, not hand labels (a lying fixture is worse than uncovered).
2026-10-06 00:05:54 +01:00
conductor 4d6fec3d17 chore(release): auto-bump version to 0.98.0 — earned by # (minor: converged release (push-to-master bump))
Promotion record: (unknown)
2026-10-06 00:01:45 +01:00
conductor 15dbbc562a chore(release): auto-bump version to 0.98.0 — earned by # (minor: converged release (push-to-master bump))
Promotion record: (unknown)
2026-10-05 23:56:19 +01:00
Nick 6eb5e06119 refactor(spine): carve the #2465 doc-level transforms into spine_language.py (#2461 keep-green)
spine.py crossed the 1200-line giant-file debt cap (1203) with the language-verdict addition — CI lint red. The three transforms (language placement, discovery-ID normalization, contract-discovery write-back + gatherer) move to spine_language.py intact, re-exported through spine for import-compat (structure unchanged: 256 entities, same verdicts; spine battery 87, canaries 19/19 + 21/21, henxels hold, spine.py back to 1034).
2026-10-05 23:53:33 +01:00
Nick 78e4750a77 feat(tier): tier_manager delegates get/promote/demote to the cognition_core kernels (#2461 W3.4 delegation owed)
The kernels and FFI landed in b0a682b but the Python publics still ran Python — the strangler breach this slice owed. Live probe caught the FFI-arg-shape defect: promote/demote take the FULL ENTRY (kernel recomputes current tier internally), not a tier string; passing a string silently returned threshold-default verdicts (promote cold went warm-to-hot WRONGLY via defaults, demote rock went cold ignoring rock protection). 12-case kernel==oracle sweep green: wash>=20, recency hot/warm/cold, naive-timestamp parity trap, rock protection. PY oracle bodies kept; RUNE_COGNITION_CORE=python pins fallback; canary_growth_pins 26/26 both engines; canary_unbounded 14/14.
2026-10-05 23:50:11 +01:00
conductor c3948bae98 chore(release): auto-bump version to 0.97.0 — earned by # (minor: converged release (push-to-master bump))
Promotion record: (unknown)
2026-10-05 23:48:25 +01:00
Nick b8184a0f01 chore(spine): store rebuild carries subsystem language verdicts (#2465) 2026-10-05 23:47:58 +01:00
Nick 469b91f217 feat(spine): subsystem language verdicts from member majority (#2465 addition 3)
The five unassessed subsystems are containers: their language derives from member modules measured sides (classification.json, the W0 dichotomy map, rust and go counts carried in the verdict text). Unclassified members noted rather than guessed; unreadable classification degrades to unassessed. Spine canaries 19/19 and 21/21, spine battery 87 green.
2026-10-05 23:47:55 +01:00
Nick bb960b1656 test(gitea): canary the numeric-ID stale-label removal (#2467)
Replace the obsolete removeLabel(name) pattern assertion with the actual Gitea /labels/{id} request, check ID lookup under the 404 idempotency guard, and require real P0–P3 managed labels. Local canary 39/39; workflow JS regression passes.
2026-10-05 22:22:53 +01:00
conductor 38c76a0c29 chore(release): auto-bump version to 0.96.1 — earned by # (patch: converged release (push-to-master bump))
Promotion record: (unknown)
2026-10-05 22:15:03 +01:00
Nick 3186566514 fix(gitea): reconcile priority labels by numeric Gitea ID (#2467)
Issue #2467 carried both P0 and P2 because name-based label DELETE returned 404 and the workflow silently ignored it. Use the numeric ID API, enforce exactly one priority, clear stale unprioritized flags, and execute the workflow JavaScript against a Gitea-compatible fake in regression tests.
2026-10-05 22:14:47 +01:00
Nick 24d14ff8e1 fix(spine): preserve candidate discoveries without inventing module IDs (#2465)
Migrate 22 malformed contract-discovery IDs to construction-surface, retain evidence/edges, keep candidates DECLARED, unify cold/warm language projection, and refresh the pinned aiohttp lock. Red regression tests precede the fix; local full suite 2301 passed, spine canaries 19/19 and 21/21.
2026-10-05 22:14:46 +01:00
conductor 6d0d455033 chore(release): auto-bump version to 0.96.0 — earned by # (minor: converged release (push-to-master bump))
Promotion record: (unknown)
2026-10-05 19:34:11 +01:00
Nick b0a682b61f feat(cognition): R2 W3.4 — tier/witness/safety/abstraction kernels + protocol upgrade (#2461, #2465)
Contract BUILDER-2026-10-05T12:03:34Z-cli — FIRST SLICE under the 90/100
verification law (64172d8).

Kernels (cognition_core/broker.rs):
- tier_get/promote/demote: thresholds+now injected (no-clock); wash>=20
  gate; rock immobility on demote; naive/malformed → designed fallback
- witness: extract_topic (word-run + stopwords-as-data), detect_signals
  (error/correction/completion/topic-drift/commitment/contradiction/
  preference/urgency/learning; pattern tables travel as data; the regex-
  lite subset is faithful to the tables' shapes)
- safety: get_params projection, update_pattern bump/new-row semantics
- abstraction: extract_facts sentence/content-word projection
- crate-level probe tests (parse_probe.rs: aware parse, epoch, tier recency)

Protocol upgrade (issue #2465) — three additions to the machine itself:
1. PER-CONTRACT MINI-DOC: construction_projection.mini_doc() emits the
   LLM quick-lookup page (what was built / connections / how to update)
   per closed contract; close() writes it to store/scaffold/docs/<cid>.md
2. SPINE WRITE-BACK: spine.build() now consumes closed contracts'
   discovered entities (expected_new_observed + candidate corrections) —
   the spine learns what construction actually built (256 entities now)
3. LANGUAGE VERDICTS: every spine entity carries identities.language
   (198 rust / 31 go / 6 tui / 5 unassessed); other projects get the
   fit assessment at classification

Also: one lost FFI block restored (the stash dance dropped
cc_tier_*/cc_witness_*/cc_safety_*/cc_abstraction_* — the battery
caught it: undefined symbol).

Gates: canary_growth_pins 26/26 x3, persona 18/18, cognition_core
38/38, suite 2299/0, ruff clean, type ratchet PASS, henxels all-hold.
2026-10-05 19:34:05 +01:00
conductor ab18540d7e chore(release): auto-bump version to 0.95.1 — earned by # (patch: converged release (push-to-master bump))
Promotion record: (unknown)
2026-10-05 19:06:06 +01:00
Nick f489467e1b fix(ci): the priority gate's demand is now programmatically satisfied
Nick's catch (2026-10-05): the auto-label bot highlighted that issues
carry priority only in their titles — the PRIORITY GATE job demanded
P0-P3 labels but nothing ever ASSIGNED them programmatically. The title
inference even used a dead label ('priority: high', normalized away
2026-09-04).

- auto-label's priority inference re-implemented in the gate's own
  namespace: title prefix \bP([0-3])\b first, then keyword inference
  (spine/critical/urgent -> P0; broken/fix/repair/defect -> P1;
  kernel/port/refactor/improve -> P2; benchmark/measurement/deferred ->
  P3); managedLabels now carry the REAL P-labels
- paired with the custodian fix: create_issue assigns the P-label at
  FILING (no issue ever lands unprioritized)
- full open-issue set backfilled (2461-2466, 2455/2459) and the live
  probe (#2466, self-verifying) closed after verifying P1 landed
  at filing
2026-10-05 19:05:38 +01:00
Nick 64172d8675 docs(plan): the 90/100 verification law (Nick 2026-10-05)
- 100% branch coverage on the deterministic Rust kernels (achievable,
  exhaustive via parity matrices)
- ≥90% per-slice changed-lines working bar, aiming higher per slice
- floor ratchet climbs after clean waves (70->76->84->90)
- honest fixtures only — a lying-fixture branch is worse than uncovered
- assertion quality counts: value-verifying asserts only
2026-10-05 12:58:18 +01:00
conductor 8e75414907 chore(release): auto-bump version to 0.95.0 — earned by # (minor: converged release (push-to-master bump))
Promotion record: (unknown)
2026-10-05 12:50:38 +01:00
Nick 7a9d823792 feat(cognition): R2 W3.3 kernels in cognition_core (broker.rs: drift_check + identity_prompt assembly + growth near_dup/profile + pins recall/projection)
Missing from the previous commit's staged set (the harness caught it):
the full W3.3 kernel block now lands. All parity walls verified:
canary_growth_pins 9/9, canary_persona 18/18, canary_cognition_core
38/38, suite 2298/0, diff-cover 72%.
2026-10-05 12:50:33 +01:00
Nick 98b5a73ed1 feat(cognition): R2 W3.3 — identity/growth/pins kernels + full coverage (72%)
- identity_anchors: check_drift delegates (pattern table stays the
  module's data authority via _pattern_triples); _py_check_drift oracle
  covers the four prefix families; drift parity proven on the full
  table (31 patterns) + the full-text multi-class case
- growth_delta: _near_dup/growth_profile delegate; oracle accepts delta
  dicts AND raw strings; near-dup kernel corrected to the REAL
  semantics: prof-key = strip-punct lowercase words >4 chars, split at
  |A∩B|/max(|A|,|B|) >= 0.25 (NOT a jaccard-min heuristic)
- pins: recall kernel projects via _project parity (details-blob parse,
  defaults, 'reason' field); empty-context abstention frozen
- probe branches exercised honestly: pinned-fallback + ImportError via
  the from-import resolution path (the from-import goes through the
  package attribute, not sys.modules — the PEP resolution law frozen in
  the battery)
- reflect malformed-JSON + [:3] cap + apply budget cut frozen
- suite 2295/0; diff-cover 72% (floor 70); canaries 9/9+18/18+38/38
  x3; henxels hold; ruff/type clean
2026-10-05 12:50:33 +01:00
conductor 4a9d8001c1 chore(release): auto-bump version to 0.94.0 — earned by # (minor: converged release (push-to-master bump))
Promotion record: (unknown)
2026-10-05 09:00:47 +01:00
Nick 538bbeb821 feat(cognition): R2 W3.2 — persona_gate decision kernels (cognition_core)
Contract BUILDER-2026-10-05T07:19:27Z-cli (Wave 3 slice 2, #2461).

- cognition_core/broker.rs gains the persona-decision kernel:
  persona_lex (distinctive content words: punct strip, light plural
  stem, acronym preservation — 55-word stoplist), persona_overlap
  (intersection over min-size), persona_decide (engagement matrix:
  amplifier set, semantic gates 0.45/0.30/0.68, user-rule 0.4 bar,
  permissive/legacy-restrictive verdicts, _cite parity '[topic:pN] text')
- persona_gate.py: _lex/_overlap/decide delegate; semantic cosines
  gathered Python-side (embedder calls are IO — the membrane's
  discipline) and passed into the kernel; _py_ oracles are the
  load-bearing fallback
- FFI: cc_persona_lex/overlap/decide; loader extended
- canary_persona.py (canary-first red on every seam): 18/18 x3 —
  lex matrix incl. acronyms/3-char exclusions, decide engagement paths,
  citation format parity
- shim battery 35/35; suite 2294/0; ruff clean; type ratchet PASS;
  henxels all-hold
2026-10-05 09:00:43 +01:00
conductor cc3b3d81eb chore(release): auto-bump version to 0.93.0 — earned by # (minor: converged release (push-to-master bump))
Promotion record: (unknown)
2026-10-05 08:43:51 +01:00
Nick eb01156d9f Merge branch 'master' of http://127.0.0.1:3000/conductor/rune into r2-wave2-memory-retrieval 2026-10-05 08:43:19 +01:00
Nick 290d264f31 feat(cognition): R2 W3.1 — context_broker kernel + the surrogate-pair parser fix across all nine crates
Contract BUILDER-2026-10-05T07:19:27Z-cli, closed: module:context_broker
correctly_predicted; cognition_core expected_new_observed; cc_loader
surfaces honestly new (#2463 wave epic).

- cognition_core crate (zero-dep, no-clock): context_broker's four
  deterministic kernels — default_estimator (chars//4; utf16-length
  corruption caught — the canary's astral cases), default_budget,
  default_memory_budget (+cap), memory_disabled (env gate value-parsed)
- THE PARSER BUG (nine pyjson copies repaired): the surrogate-pair branch
  tested self.b[i+1..] for the second escape's backslash — but hex4 had
  already advanced i past the first escape's 'u+digits', so the correct
  guard window is [i..] not [i+1..]; and after the guard, i advanced +2
  leaving hex4 to read a shifted window ('bad \\u digits' on
  \\ud83c\\udfb8). Fixed: guard at [i..], pre-hex4 advance +1.
  Astral chars (emoji) now parse as ONE code point — char-count parity
  restored everywhere (estimator/chunk/norm)
- context_broker: four functions delegate (load-bearing _py_ oracles);
  the env gate is cross-checked Python-side (Python verdict stays
  authority)
- cc_loader: ctypes shim, CC_RUST_LIB file-form; battery-covered
- CI: crate builds in both jobs, CC_RUST_LIB envs, canary registered

Gates: canary 38/38 x3 + full seven-crate wall green, suite 2293/0,
type ratchet PASS, ruff clean, henxels all-hold, diff-cover 75%.
2026-10-05 08:43:19 +01:00
conductor 16c147dbee chore(release): auto-bump version to 0.92.2 — earned by #2460 (R2 W2.1: deterministic Rust memory-retrieval kernel)
Promotion record: http://127.0.0.1:3000/conductor/rune/pulls/2460
2026-10-05 07:57:54 +01:00
conductor 15cba0bb24 Merge pull request 'R2 W2.1: deterministic Rust memory-retrieval kernel' (#2460) from r2-wave2-memory-retrieval into master 2026-10-05 07:57:47 +01:00
Nick 1283b0bf94 fix(memory): W2.1 coverage 62->84 + pair-list contract harmonized
- chunk gather + projection now speak the W2.1 pair-list contract
  consistently on BOTH engines (fusion['ranks'] is [[rowid, rrf], ...];
  dict-form still accepted for backward callers)
- oracle-path tests: all three modes, abstention edges, malformed
  timestamps, hybrid accumulation, chunk SQL gather through a real
  fixture (vec-table absent by design; embed stubbed — vector execution
  stays Python per the W2.0 ruling)
- battery 32->32 tests extended; suite 2291/0; canary 75/75 x3;
  diff-cover 84% (floor 70)
2026-10-05 07:51:40 +01:00
Nick 1ee35d76c9 fix(memory): W2.1 coverage + stopword parity + chunk projection kernel
- battery: delegation + projection coverage for the kernel consumers
  (29-test shim battery; CI's 45% was the pre-extension tree)
- chunk projection (chunk_recall tail) moved into the kernel:
  RRF-desc order, text[:400] chars, budget cap, page/room/default
  semantics — canary 75/75 x3
- stopword list AST-extracted exactly (51 words incl. needs/wants/likes)
- memory_store: recall/fused/association/chunk kernels delegate with
  _py_ oracles as load-bearing fallback

Gates: canary 75/75 x3, shim battery 29/29, suite 2286/0, ruff clean,
type ratchet PASS, henxels all-hold. PR 2460 updated with the
propagation evidence line.
2026-10-05 07:20:38 +01:00
Nick e8daf78eaa Merge remote-tracking branch 'gitea/master' into r2-wave2-memory-retrieval 2026-10-05 06:39:03 +01:00
Nick 19976c310c feat(memory): R2 W2.1 — deterministic Rust memory-retrieval kernel (#2458)
Contract BUILDER-2026-10-04T20:10:25Z-cli, closed: memory_store and
memory_selector correctly_predicted; kernel expected_new_observed; shim
surfaces honestly new. Stacked on W2.0 (merged de54453).

- memory_retrieval crate (zero-dep, no-clock, rlib+cdylib+bin): the
  retrieval transform family in ONE kernel surface — map_reduce,
  position_aware, fts_query_conjunctive (51-word stoplist extracted via
  AST, no transcription drift), chunk_text (byte-vs-char law honored),
  norm_key, plus the extracted kernels: rank_fusion (RRF/lexical
  normalization/mode selection), score_recall_rows (metadata blend +
  mode-specific QPP abstention), merge_association_rows (association
  projection), fuse_results (fact/research projection + per-kind
  min-max scale alignment)
- PARITY-PLUS-IMPROVEMENT proven: recall's N+1 (per-candidate entry
  reads + repeated DF probes) ELIMINATED — rows batch through the W2.0
  read boundary once and the kernel runs per retrieval stage
- Parity trap frozen: naive timestamps take Python's designed
  TypeError->recency=1.0 fallback (parse_iso now refuses naive, exactly
  like aware-vs-naive subtraction) — the 34-case matrix caught this
- UTF-8 transport fix: ensure_ascii=True surrogate pairs corrupt astral
  chars (emoji) in char-count semantics — direct UTF-8 transport, frozen
- both _py_ oracles and delegating publics; RUNE_RETRIEVAL_CORE=python
  pins the fallback
- CI: crate builds in both jobs, MR_RUST_LIB envs, canary registered

Gates: canary 72/72 x3, suite 2286/0, type ratchet PASS, ruff clean,
henxels all-hold. Store: 218 entities,
rust-crate:memory_retrieval modeled.
2026-10-05 06:38:57 +01:00
conductor e2430a9154 chore(release): auto-bump version to 0.92.1 — earned by #2457 (R2 W2.0: SQLite-backed zero-dep Rust read boundary)
Promotion record: http://127.0.0.1:3000/conductor/rune/pulls/2457
2026-10-05 01:56:56 +01:00
conductor de5445357e Merge pull request 'R2 W2.0: SQLite-backed zero-dep Rust read boundary' (#2457) from r2-wave2-memory-storage into master 2026-10-05 01:56:51 +01:00
Nick e94c8e27a8 Merge remote-tracking branch 'gitea/master' into r2-wave2-memory-storage 2026-10-04 21:07:54 +01:00
Nick 0816c5997d feat(memory): R2 W2.0 — retain SQLite behind a zero-dep Rust read boundary (#2456)
Contract BUILDER-2026-10-04T15:54:17Z-cli closed: module:memory_store correctly_predicted; memory_storage crate, mems_loader, and canary honestly new.

- memory_storage crate: zero Cargo dependencies; system libsqlite3 FFI; sqlite3_open_v2(SQLITE_OPEN_READONLY) makes writes impossible at the connection level; no write-capable mems_* exports
- typed read parity: INTEGER/FLOAT/TEXT/NULL/BLOB, bound string/numeric/bool/NULL params, aggregates, empty results, unicode, and honest SQLite errors
- mutation SQL is rejected; fixture SHA-256 is identical before/after all reads and the rejected mutation
- memory_store gains an explicit backend seam: stdlib SQLite remains the default; Rust read path is opt-in via RUNE_MEMORY_STORAGE=rust; connection-based transactional callers are untouched
- stats_for_path is the proving consumer; both backends return identical result shape
- canary-first: crate-absent red proven, now 19/19 three consecutive
- CI builds the crate in both jobs, pins MEMS_RUST_LIB, and runs the crate canary; pytest shim battery covers loader/seam lines

Evidence: full suite 2283/0, diff-cover 71% >= 70, type ratchet PASS, ruff clean, henxels all-hold. SQLite is retained exactly as Nick directed; this slice exports no Rust write path.
2026-10-04 21:07:43 +01:00
conductor b502a1d7e8 chore(release): auto-bump version to 0.92.0 — earned by # (minor: converged release (push-to-master bump))
Promotion record: (unknown)
2026-10-04 16:52:48 +01:00
Nick 0cd587dfd3 Merge branch 'master' of http://127.0.0.1:3000/conductor/rune 2026-10-04 16:52:43 +01:00
Nick 8404cddb1d feat(scaffolding): R2 Wave 1 — the scaffolding engine reborn (Go start / Rust execution)
Contract BUILDER-2026-10-04T15:25:33Z-cli, closed: scaffold,
construction_decision, go-surface:rune-entry correctly_predicted; crate +
shim + canary honestly new. The engine governed its own rebuild.

- scaffold_core crate (zero-dep, no-clock, rlib+cdylib+bin):
  lifecycle.rs (the LEGAL_TRANSITIONS state machine with exact error
  strings — the single authority, terminals derived), close.rs (the
  closure classification — all buckets incl. the dict-literal overwrite
  quirk and legacy aliases), language.rs (Nick's protocol addition: the
  placement verdict — rune dichotomy rule, other-project fit assessment)
- THE PARITY PROOF: all 43 production contracts in the ledger replay to
  IDENTICAL closure verdicts — the new engine reproduces the V1-accepted
  system's real history byte-for-byte
- Go surface: rune scaffold subcommand — pure ops direct to the Rust
  binary over stdio NDJSON (the tranche's first contracted membrane
  junction); store-backed ops proxy to the engine (strangler membrane);
  4 new Go unit tests (stub-binary junction + failure surfacing)
- scaffold.py: bucket math factored to _py_close_quality, consumed by
  close_contract and the replay battery (one computation, two engines)
- construction_decision.py: language_placement in the classification path
- Canary-first: canary_scaffold_core.py red-proven, now 152/152 x3
  (legality matrix 81 pairs, ledger replay, determinism, Go membrane,
  placement matrix incl. delegation agreement)
- CI: crate builds in both jobs, SC_RUST_LIB envs, canary registered

Gates: pytest green (one bridge-flake rerun), canary 152/152 x3, type
ratchet PASS, ruff clean, henxels all-hold (shim battery extended).
Store: 214 entities — rust-crate:scaffold_core modeled.
2026-10-04 16:52:13 +01:00
conductor 17ec4862e3 chore(release): auto-bump version to 0.91.0 — earned by # (minor: converged release (push-to-master bump))
Promotion record: (unknown)
2026-10-04 16:23:59 +01:00
Nick 6b712a9b75 feat(rebase): R2 Wave 0 — measured inventory + classification + straggler closure
Wave 0.1/0.2: 174 modules, 2,191 functions AST-measured
(store/rebase/inventory.json) and evidence-classified per the dichotomy
law (store/rebase/classification.json — every module carries its import/
content evidence string): GO 43 modules/19,495 lines (reactive,
initiating, network, turn-time), RUST 93/36,215 (deterministic, state,
storage, cognition), TUI-LAST 7/5,438, TEST-INFRA 29/5,395, RETIRES 2
(bootstrap glue). plans/total-rebase-inventory.md is the human-readable
table.

Wave 0.3: the five R1 stragglers (#2171, #2119, #1844, #1755, #1738)
dispositioned with placement evidence and closed — superseded-into-R2,
each folded into its owning wave.
2026-10-04 16:23:55 +01:00
Nick 31f49671ec docs(plan): total-rebase tranche R2 v2 — Nick's six critiques folded in
- parity-plus-improvement rule (parity is the floor, measured improvement
  is in scope, functionality is the gate)
- hybridized Go<->Rust membrane (the no-socket wall withdrawn; junctions
  are contracted, recorded exceptions to the default dichotomy)
- continuous TUI wiring per unit (the TUI wave stays last but integrates
  a system already aimed at it)
- language-placement intelligence in the scaffolding protocol itself
  (rune: the dichotomy rule; other projects: language-fit assessment)
- SQLite RETAINED and wired via system-library FFI (zero-dep legal);
  the bespoke memory redesign deferred to its own later program
- provenance lock: Wave 1 rebuilds the V1-ACCEPTED scaffolding (Nick's
  design, SCAFFOLDING_V1_DEFINITION.md + North Star verbatim), proven by
  production-ledger parity replay
2026-10-04 16:17:32 +01:00
Nick 103a42103b docs(plan): total-rebase tranche R2 — Go initiates and reacts, Rust determines and stores
Designed on the measured inventory (161 modules, 64,482 lines, from the
spine store) per the TRANCHE-DEPTH TEMPLATE. Scaffolding engine first
(Go start / Rust execution, Wave 1) so every later wave is driven by the
new protocol itself; zero-dep law holds with the split doing the work
(Go owns network + reactivity, Rust owns deterministic compute, bespoke
storage engine replaces SQLite in Wave 2); TUI last over the transport
seam; 5 R1 stragglers folded into Wave 0; approval gate before execution.
2026-10-04 16:02:42 +01:00
conductor 615da6bd09 chore(release): auto-bump version to 0.90.0 — earned by # (minor: converged release (push-to-master bump))
Promotion record: (unknown)
2026-10-04 13:25:17 +01:00
conductor 693a494884 Merge pull request 'Phase G strangler slice 2: evidence-provenance Rust candidacy (#2449)' (#2454) from phase-g-strangler-evidence-provenance into master 2026-10-04 13:25:10 +01:00
Nick ee653c6a4c Merge remote-tracking branch 'gitea/master' into phase-g-strangler-evidence-provenance
# Conflicts:
#	requirements-ci.txt
2026-10-04 13:18:54 +01:00
Nick 171f3fb250 fix(tests): close the diff-cover hole — 63% -> 76% on the changed-lines floor
Runs 18367/18369 failed unit-tests on diff-cover (63% < 70%, 338
missing lines) with pytest itself green — the changed-lines floor counts
PYTEST-traced lines only, and three slice surfaces had canary-only
coverage. Battery extension (the shim-battery law applied to my own
slices):
- warm_router: gate branches, finish stack, public score_neuron wrapper
  (designed-fallback determinism), base-level math + gather (hermetic
  via monkeypatched table ensure — STORE_DB resolution is suite-order
  dependent, the order-dependent failure the coverage run exposed)
- tui_transport: InProcess four-method drive, V1 against an in-process
  core, factory modes incl. the bogus-mode ValueError
- token_optimizer: optimize_prompt/get_status/get_cache_stats/flush

CI-exact verification: full coverage suite 2278/0, diff-cover 76%
(floor 70). ruff clean.
2026-10-04 13:09:53 +01:00
Nick 9731181a6b fix(tests): respawn the bridge daemon on mid-test death (root-caused)
The hardening's own diagnostic nailed it (run 18359): the daemon answers
/health at fixture setup, then dies SILENTLY mid-test (alive=False,
out='') on the saturated runner — host shows 20GB swap in use, and a
silent kill with zero output is the memory-pressure signature, not a
race. A retry can never survive a dead process.

Fix: registrations are file-backed under WR_ROOT, so a respawned daemon
resumes the same state — post_event now ensures liveness before each
attempt (respawn on death) and _spawn is the single spawn path (init's
T21.5 fresh-port retry preserved). Bridge suites 19/19, ruff clean.
2026-10-04 12:30:37 +01:00
Nick 544c19719e fix(tests): harden the bridge daemon post_event against load-dependent refusal
Observed 2026-10-04 (runs 18340/18342/18346/18347): the fixture's daemon
answers /health at setup, then a POST lands ECONNREFUSED under runner
load — the twin-failure pattern across two commits while sibling runs
passed. Harness-side hardening per the intermittent-defect law:
post_event retries twice with a beat, and on final failure dumps the
daemon's captured output (alive state + log tail) so the next diagnosis
starts from evidence. Bridge suite 11/11 locally, ruff clean.
2026-10-04 12:18:28 +01:00
Nick d571658b5a fix(ci): register tui_transport in the TUI registry
The tui_panels_are_governed henxel held on the H4 slice-1 surface
(runs 18346/18347): new TUI surfaces ship with a registry entry in the
same PR. Entry added (not-a-surface seam, contract ref, coverage ref).
Local henxels: all hold.
2026-10-04 12:16:23 +01:00
Nick 50c997beab feat(phase-h): H4 slice 1 — TUI transport seam (the migration wave's strangler path)
Contract BUILDER-2026-10-04T10:32:00Z-cli, closed: rune_tui,
go-surface:rune-entry, canary_rune_serve correctly_predicted;
tui_transport + the core_server extension honestly new.

The TUI's operator surface is exactly 4 public calls — this slice puts
them behind one seam so the wave migrates surface-by-surface instead of
rewriting 4k lines of Textual in one go:
- tui_transport.py (new): InProcessTransport (today's behavior,
  DEFAULT via RUNE_TUI_TRANSPORT=inprocess — zero behavior change on
  merge) and V1Transport (stdlib HTTP against the Go front door)
- core_server.py: /v1/conversation + /v1/approvals/resolve — the two
  endpoints the TUI needed beyond H2's set; the Go front passes them
  through (proxy law), H2 battery extended to 28/28 with parity
- rune_tui.py: the 4 call sites route through the seam; the one
  private _task_state peek stays in-process by design (annotated)
- canary_tui_transport.py (new, canary-first red-proven): 9/9 x3 —
  four-method equivalence against the stub core, approval boolean
  round-trips both ways, dead core raises (never a silent empty answer)

Gates: pytest 2270/0, canaries 9/9 + 28/28 x3, type ratchet PASS,
ruff clean. Store rebuilt: 212 entities.
2026-10-04 11:52:38 +01:00
Nick 9e6215e605 fix(ci): henxels holds — intentional mirrors registered + shim-layer pytest battery
Contract BUILDER-2026-10-04T10:29:39Z-cli (successor to 10:20:24Z-cli,
same red evidence run 18322 lint job 51891).

- henxels.yaml: the deliberate-duplicate registry — shared/** (deployed-
  vintage mirror tree), the named src/rune strangler shims, per-crate
  pyjson copies (self-contained zero-dep design), tiny zero-dep
  lockfiles, store/scaffold data, issue-template + completion_file
  mirrors, the interop/rebase-proof fixture
- tests/test_rust_core_shims.py (new, 11/11): the thin pytest battery
  the diff-cover law owed the shim layer — drives the DELEGATION lines
  (loader resolves, envelope unpack, public-vs-fallback agreement) for
  spine_loader, ms_loader, cs_loader, tc_loader, and core_server's
  in-process shapes; skips cleanly on unbuilt crates

Local: henxels all-hold green, battery 11/11.
2026-10-04 11:30:19 +01:00
Nick 6429c5bb49 fix(ci): canary environment repair — runner-HOME rustup path + per-run go binary
Contract BUILDER-2026-10-04T10:20:24Z-cli, closed: all four surfaces
correctly_predicted. Red evidence: run 18322 (4 canary failures).

Two defect classes, both canary-side environment assumptions:
- the Rust-parity canaries resolved the rustup toolchain via
  Path.home() — on the runner that is /var/lib/gitea-desktop-runner,
  which has no toolchain, so cargo fell through to the BROKEN system
  rustc (LLVM_23.1 symbol error). Now the absolute /home/nick path
  (x-ACL covers the runner user) wins, with the home path as fallback
  for other hosts. token_counting gets the same fix preemptively.
- canary_rune_serve built its Go binary to the fixed path
  /tmp/rune-serve-canary — nick owns the local file; the runner cannot
  overwrite it (EACCES). Now a per-run path (-PID).

Verified: all five canaries green locally; runner-HOME simulated
(HOME=/var/lib/gitea-desktop-runner) green for the crate build;
ruff + type ratchet clean.
2026-10-04 11:22:49 +01:00
Nick ea34f2d5ab feat(inference): H3 slice 5 — token counting/compression into the token_counting crate
Contract BUILDER-2026-10-04T09:57:23Z-cli, closed: module:token_optimizer
correctly_predicted; crate + shim + canary honestly new. FINAL H3 domain —
the Phase H plan's back-end port list is now fully delivered.

- token_counting crate (zero-dep, rlib+cdylib+bin): estimate_tokens
  (code points, floor division), compress_context (hand-rolled run
  collapse for 3+ newlines and 2+ spaces — no regex crate; literal
  case-sensitive prefix shortening; unicode strip)
- tc_loader.py: ctypes shim (TC_RUST_LIB file-form)
- token_optimizer.py: estimate_tokens/_compress_context delegate;
  class keeps cache I/O, persona loading, debounce clock
- Repair en route (caught by test_token_optimizer_cache.py): the first
  helper-block placement sat mid-class and swallowed four methods
  (optimize_prompt/get_status/get_cache_stats/flush) into a nested def —
  moved after the class, suite green again
- Canary-first: canary_token_counting.py red-proven, now 26/26 three
  consecutive (unicode code points, emoji, run boundaries, tab-vs-space,
  mid-word prefix non-match, delegation)
- CI: crate builds in both jobs, TC_RUST_LIB envs, canary registered

Gates: pytest 2259/0, canary 26/26 x3, type ratchet PASS, ruff clean.
Store rebuilt through the Rust spine: 211 entities,
rust-crate:token_counting modeled.
2026-10-04 11:18:19 +01:00
Nick 0eff96a335 feat(capability): H3 slice 4 — capability scoring ported to the capability_scoring crate
Contract BUILDER-2026-10-04T09:43:55Z-cli, closed: capability_fit and
capability_broker correctly_predicted; crate + shim + canary honestly new.

- capability_scoring crate (zero-dep, rlib+cdylib+bin): evaluate_fit
  (EXACT/PARTIAL/INCOMPATIBLE/UNKNOWN with exact reason strings,
  unhealthy status join incl. truthy superseded_by, set-math missing
  list), score_card (weights 4.0 name / 3.0 capability / 2.0 examples /
  0.8 description / 0.3 generic penalty / priority bonus)
- wordlists.rs GENERATED from the capability_broker.py literals via AST
  extraction (12 generic caps, 94 stopwords — no transcription drift)
- hand-rolled ASCII word-run scanner replaces re.findall(r'\w+') —
  no regex crate (zero-dep law holds)
- cs_loader.py: ctypes shim (CS_RUST_LIB file-form)
- capability_fit.py / capability_broker.py: _py_ variants as fallback +
  parity oracle, thin delegating publics, RUNE_CAPABILITY_CORE pin
- Canary-first: canary_capability_scoring.py red-proven, now 24/24
  three consecutive (every fit class, every weight branch, stopword
  and generic penalty, delegation)
- CI: crate builds in both jobs, CS_RUST_LIB envs, canary registered

Gates: pytest 2259/0, canary 24/24 x3, type ratchet PASS, ruff clean.
Store rebuilt through the Rust spine: 209 entities,
rust-crate:capability_scoring modeled.
2026-10-04 10:56:10 +01:00
Nick de68444e50 feat(memory): H3 slice 3 — warm_router scoring math into memory_scoring
Contract BUILDER-2026-10-04T09:28:47Z-cli, closed: warm_router and
rust-crate:memory_scoring correctly_predicted.

- warm.rs: score gate (keyword primary gate, semantic secondary gate
  0.55, boost shape, context cap), score finish (importance, b
  contribution, wash boost, cache boost, body-knowledge overlap, fan
  penalty MULTIPLYING before length bonus and protected boost ADD —
  order is load-bearing), ACT-R base-level decay (power-law sum, ln,
  clamp [-2,2], cold -2.0, non-string row -> -0.2 whole call faithful
  to the original's AttributeError-through-outer-catch)
- warm_router.py restructured gather-then-combine: embeddings, SQLite
  use-history, and the cache file are gathered in the wrapper in the
  original's exact reach order — NO base-level or cache I/O on the
  no-fire path (the original early-returns before touching them)
- base_level_activation keeps its SQLite scan; the decay math
  delegates; the outer exception contract (anything -> -2.0) preserved
- Battery extended: 108/108 three consecutive (7 gate branches,
  11 finish stacks, 7 ACT-R cases incl. clamp-high and cold starts)

Gates: pytest 2259/0, canary 108/108 x3, type ratchet PASS, ruff clean.
2026-10-04 10:42:41 +01:00
Nick d58e88da5c feat(memory): H3 slice 2 — drift_compression + drift_cache pure surfaces into memory_scoring
Contract BUILDER-2026-10-04T09:15:51Z-cli, closed: drift_compression,
drift_cache AND rust-crate:memory_scoring correctly_predicted (the
resolver + existing entity made the crate predictable this time);
canary/loader honestly predated the contract.

- compression.rs: ratio table, compress_text (int() truncation,
  Unicode char counts, post-clamp ellipsis compare), entry/warn
  surfaces with exact recommendation strings (em-dashes included)
- cache.rs: compute_priority (raw product, no rounding — faithful),
  qualifies_for_cache admission decision; DriftCache file I/O and
  clock stay Python (zero-dep split law)
- drift_compression.py / drift_cache.py: _py_ variants as fallback +
  parity oracle, thin delegating publics, shared _ms_core seam
- ms_loader.py: 7 new FFI symbols, same segfault-law discipline
- Canary battery extended: 83/83 three consecutive (ratio boundaries,
  text edges incl. unicode, every warn branch, priority grid,
  qualification boundaries)

Gates: pytest 2259/0, canary 83/83 x3, type ratchet PASS, ruff clean.
2026-10-04 10:27:15 +01:00
Nick c730196dba feat(memory): H3 slice 1 — drift_core scoring trio ported to the memory_scoring crate
Contract BUILDER-2026-10-04T08:57:33Z-cli, closed: module:drift_core
correctly_predicted; crate + shim + canary honestly new.

- memory_scoring crate (zero-dep, rlib+cdylib+bin): compute_value with
  now injected (no-clock law — the original reads the wall clock),
  check_promotion_eligibility, pre_commit_gate_entry. Hand-rolled
  ISO-8601 parser (no chrono), banker's rounding via round_ties_even,
  Unicode-aware char counts, Python falsy-coercion semantics preserved
  (0.0 importance -> 0.5)
- Vintage quirk preserved, not fixed: the fourth harmful pattern carries
  uppercase but matches against text.lower() — it can never fire in the
  original, and the parity battery caught my intent-faithful port
  diverging. Behavior fixes belong to their own contract
- ms_loader.py: ctypes shim (FFI segfault law, MS_RUST_LIB file-form)
- drift_core.py: pure trio delegates when the crate is built
  (load-bearing Python fallback; RUNE_MEMORY_CORE=python pins it);
  DB-backed functions stay Python (zero-dep split law);
  signatures made honestly Optional (implicit-Optional ratchet repair)
- Canary-first: canary_memory_scoring.py red-proven crate-absent,
  now 32/32 three consecutive — factor battery, threshold boundaries,
  every critic branch, edge inputs, public-API delegation
- CI: crate builds in both jobs, MS_RUST_LIB envs, canary registered

Gates: pytest 2259/0, canary 32/32 x3, type ratchet PASS, ruff clean.
Store rebuilt through the Rust spine: 207 entities,
rust-crate:memory_scoring + module:canary_memory_scoring modeled.
2026-10-04 10:14:36 +01:00
Nick c750513fab feat(phase-h): H2 Go front door — /v1 surface with NDJSON streaming + parity battery
Contract BUILDER-2026-10-04T08:32:43Z-cli, closed: go-surface:rune-entry
and workflow:ci.yml correctly_predicted; core_server + canary honestly
new (unresolvable at submit time).

- core_server.py (new): stdlib sidecar over the operator surface —
  GET /v1/models, GET /v1/status, POST /v1/chat/completions with NDJSON
  streaming; one error envelope shape across every failure path;
  injectable operator (the canary drives a deterministic stub via the
  established _execute_on_model seam — no live model calls)
- serve.go (new): rune serve subcommand — Go owns the front door
  (stdlib net/http only), byte-pass-through proxy to the core,
  locally-generated error envelopes byte-matching the core's shape,
  core-down 502 core_unavailable
- canary-first: canary_rune_serve.py fired red (module absent), now
  22/22 three consecutive — Python shape freeze, cross-language parity
  modulo volatile (id/created), NDJSON line-sequence equality,
  core-down honesty on both sides
- Go unit tests 6 → 11 (help/flag exits, 404 + 502 envelopes,
  byte-for-byte proxy pass-through); vet clean
- CI: canary registered; go-surface job covers serve.go module-wide

Gates: pytest 2259/0, canary 22/22 x3, type ratchet PASS
(cast-narrowed stdlib override, zero ignores), ruff clean.
2026-10-04 09:55:16 +01:00
Nick 27c4bf8d78 feat(spine): H3-spine — spine projection machinery ported to a zero-dep Rust crate
Contract BUILDER-2026-10-04T07:19:36Z-cli, closed: module:spine
correctly_predicted; crate + loader + canary + CI wiring honestly new
(unresolvable at submit time — they are the delivery).
Nick directives: 'rebuild the spine in rust' + 'add scaffolding to the
rust conversion' — this is both, through the full pipeline.

Rust core (src/rust/rune_spine/, zero-dep, rlib+cdylib+bin):
- pyjson.rs: Python-parity JSON (reused from evidence_provenance)
- store.rs: doc semantics — high-water add_entity, dedup add_relationship
  with CANDIDATE guard, authority observation + reconcile, resolve_status,
  impact_frontier with the obligation semantics tables
- project.rs: full build() projection — carry-forward, manifest ingest
  with generated-view refusal, module re-derivation, deployed observation,
  meta-surface projection (rust-crate/go-surface/config/workflow)
- drift.rs + query.rs: drift classification, query, export_umbrellas
- FFI: single-arg JSON envelopes, sp_free_string; no-clock law (now is
  always an argument)

Zero-dep split law: every filesystem scan stays in spine.py
(_gather_build_inputs/_gather_drift_inputs); both cores consume the same
gathered dict, so parity divergence is logic, never environment.
spine_loader.py: ctypes shim (argtypes+restype per the FFI segfault law,
SP_RUST_LIB file-form override, load-bearing Python fallback).
The unseeded Phase A path stays on the Python core — the public
ingest_umbrellas seam is monkeypatched by isolation tests.

Parity battery (canary-first, red-proven crate-absent):
canary_spine_rust.py 21/21 — drift, 10 query entities, export, seeded
build, unseeded ingest, plus a carry-forward mask guard (asserts the
Rust projection models declared surfaces directly — caught the
config_manifest wiring bug the structural diff could not).

Gates: pytest 2259/0, canary_spine_meta 19/19 x2 through the Rust path,
type ratchet PASS, ruff clean, diff-cover 84% >= 70.
CI: crate builds in both jobs, SP_RUST_LIB pinned, canary registered.
Store rebuilt through the Rust core itself: 204 entities,
rust-crate:rune_spine modeled (self-hosting meta-scaffold).
2026-10-04 09:26:01 +01:00
Nick 5b8023ca2a docs(scaffolding): T4.7 reassessment matrix — all tranche rows correctly_predicted
Successor closures against the H-meta-modeled spine with the repaired
resolver (57eaa8f):
- row 1 v2 slice-1 query_memory: 3/3 (BUILDER-2026-10-04T07:17:13Z-cli)
- row 2 slice-2 evidence_provenance six ports: 9/9 (07:17:15Z-cli)
- row 3 H1 Go first-call shim: 4/4 (07:17:17Z-cli)

Every delivered tranche surface now audits clean against the spine it
lives in. Matrix complete; T4.7 done.
2026-10-04 08:17:40 +01:00
Nick 57eaa8f383 fix(scaffolding): contract scope resolver recognizes H-meta entity kinds
Contract BUILDER-2026-10-04T07:14:03Z-cli, closed: module:construction_decision
correctly_predicted, zero genuinely_unexpected_new.

T4.7 row-1 closure flagged rust-crate:query_memory resolving as
kind=unresolved — classify_surface's prefix set predated the H-meta
entity classes (rust-crate:/go-surface:/config:/workflow:), so
spine-modeled surfaces could never close correctly_predicted.

Red-first: test_hmeta_kind_prefixes_classify_as_structural_entity
failed pre-fix; identity-resolution path needed no change (works for
any id present in an authority). Regression frozen in
test_construction_decision.py (2 new tests, 21/21 file green,
decision neighborhood 107/107).
2026-10-04 08:16:38 +01:00
Nick 567fd39937 feat(spine): H-meta meta-scaffold — every part of rune modeled as a spine entity
Contract BUILDER-2026-10-04T06:24:56Z-cli, closed: module:spine
correctly_predicted; six new entity surfaces honestly unmodelable at
submit time (they are the delivery).

- spine.py: project_meta_surfaces() in the seeded derivation path
  (build step 4) — rust-crate, go-surface, config, and workflow entity
  classes with belongs_to edges; crate-resident canary scan projects
  obligated_canary edges from the owning crate
- config/umbrellas.json: declared rust_crates, go_surfaces, configs;
  canary registrations for audit_ledger, rune_entry, spine_meta
- canary_spine_meta.py (new, canary-first: 14 red proven): entity
  existence/kind checks, canary modeling + edges, determinism, and
  open-world carry-forward — 19/19 five consecutive runs
- determinism contract made honest: structural equality (deep
  timestamp strip incl. last_success + observation set-dedup), not
  clock equality — the per-build deployed_identity observation ticks
  by design
- test_structural_store: schema gate admits the new entity kinds
- ci.yml: canary_spine_meta registered in the canary loop

Gates: umbrella/henxels/structural 32/32, full pytest 2257/0.
2026-10-04 08:09:40 +01:00
Nick 817a97abd9 feat(phase-h): H1 Go first-call shim — delivered under construction contract BUILDER-2026-10-04T05:25:57Z-cli
Scaffolding-first rebuild of the reverted ad-hoc attempt (1e6cb4d), per
Nick's 2026-10-04 directive that all coding replacements run through the
construction pipeline:

- Need submitted with acceptance criteria + constraints (zero-dep Go,
  pinned toolchain, canaries-first); resolution REUSE, approval covered
  by Nick's Go-frontend directive; contract generated and approved.
- CANARY LANDED FIRST (canary_rune_entry.py restored; module absent ->
  canary red proves ordering), then the module restored from the reverted
  reference and the canary went 12/12 green.
- Contract lifecycle driven to close: observe -> verifying -> close.
  Closure verdict (honest): all four delivered surfaces land in
  genuinely_unexpected_new — the spine/structural registry does not model
  the new Go surface, the canary, the pin file, or the workflow yet. That
  IS the coverage signal the closure exists to emit; the spine-modeling
  gap is now a recorded follow-up across H-wave deliveries.

Retrofit closures also completed under the same directive:
- Slice 2 (evidence-provenance): BUILDER-2026-10-04T05:23:17Z-cli — all
  six ported modules correctly_predicted; crate + canaries + shim battery
  honestly unmodeled.
- Slice 1 (query-memory): BUILDER-2026-10-04T05:24:11Z-cli —
  module:query_memory correctly_predicted; crate + canaries unmodeled.
- BUILDER-2026-10-04T05:14:20Z-cli (v1) and ...:18:52Z (v2) stand as
  labeled learning artifacts (entity-vocabulary mismatch).

go test 6/6, go vet clean, canary 12/12 (desktop + bare-HOME CI sim),
henxels exit 0, pin check PASS.
2026-10-04 06:28:51 +01:00
Nick 1e6cb4d1ba revert(phase-h): withdraw ad-hoc H1 shim + enablement — will be rebuilt through the scaffolding construction pipeline
Nick directive 2026-10-04: all coding replacements must be done via the
scaffolding construction pipeline (construction_decision -> normalization
-> discovery/fit/resolution -> toolchain -> layout obligations -> approval
gate -> durable trace -> generated contract -> canaries-first ->
implementation -> verification -> delivery record). The H1 Go shim was
built ad-hoc; this reverts it (and the H0 CI surface) for a proper
rebuild. The scaffolding run that replaces it follows as its own
construction contract.
2026-10-04 06:11:20 +01:00
Nick 4e11aa4268 fix(ci): go pin check strips the distro build-experiment suffix
CachyOS ships go 1.27.1-X:nodwarf5 (an LLVM build experiment flag). The
pin compares the RELEASE (1.27.1), not the suffix — same pin law, honest
comparison. Verified locally before push this time.
2026-10-04 05:59:05 +01:00
Nick 1703c25367 feat(phase-h): H0+H1 — Go toolchain enablement and the first-call shim
H0 (enablement, per plan 2026-10-04-phase-h-go-frontend-rust-backend.md):
- Go 1.27.1 installed (pacman, world-readable — runner user verified).
- go-version.txt pins the toolchain; the go-surface CI job fails loud on
  drift instead of letting a distro upgrade change behavior downstream.
- go-surface job: vet + test + build, runs on the rune-ci-desktop label.

H1 (first-call shim, Go owns the front end per Nick's USER-LOCKED directive):
- src/go/rune-entry: the 'rune' binary replicates the DEPLOYED bash
  launcher's exact contract — cd /mnt/workdrive/rune-code/current/src,
  RUNE_HOME/RUNE_MEMORY_DIR/MIMO_PROBE_ENABLED env (explicit ambient
  overrides win), exec handoff to the memory-venv python -m rune with
  args/signals/exit codes passing through 1:1. Falls back to the dev
  checkout when the deployed revision is absent, and to PATH python3 when
  the venv is absent (CI sandboxes).
- Flags: --version (deployed pyproject), --python-only (resolved handoff),
  --help (exit 0), unknown flag (usage on stderr, exit 2).
- canary_rune_entry.py: 12 checks — builds the module in a temp dir
  (zero-dep, system Go), verifies the version surface, the handoff shape,
  flag discipline, the Go unit battery (6 tests), and go vet. Green on the
  desktop AND under a bare-HOME CI simulation (the fallback expectation is
  asserted honestly, not fudged).
- Registered in the canary suite loop (henxels exit 0).

Observed-not-invented: the launcher contract comes from ~/.local/bin/rune;
the version from the deployed pyproject (0.89.0 at this commit).
2026-10-04 05:52:43 +01:00
conductor 7cc47de6de chore(release): auto-bump version to 0.89.1 — earned by # (patch: converged release (push-to-master bump))
Promotion record: (unknown)
2026-10-04 05:09:58 +01:00
Nick e93315d156 fix(ci): same label correction on the branch (rune-desktop -> rune-ci-desktop) 2026-10-04 05:09:54 +01:00
Nick be49fb32d1 fix(ci): retargeted jobs must use the runner LABEL rune-ci-desktop, not the runner name
Correction to 1462262: act_runner matches jobs by runner LABELS. The only
registered runner (rune-desktop on gitea-desktop-runner) carries the label
'rune-ci-desktop:host' — 'rune-desktop' is the runner's NAME, which matches
nothing. All 8 retargeted jobs queued forever (runs 18197/18203/18211
stuck exactly this way) while ci.yml jobs (which use the correct label)
drained normally. rune-desktop -> rune-ci-desktop in all 8 spots.
2026-10-04 05:09:53 +01:00
Nick c41ed97373 docs(plan): phase H — Go front end / Rust back end (Nick's USER-LOCKED directive)
Directive supersedes KEEP-PYTHON rubric verdicts. Plan scopes:
- Go: runtime entry (rune binary), CLI/IPC first-call dispatch, /v1
  endpoint surface (the rubric's one capability-proven Go fit: stdlib
  net/http + NDJSON streaming). TUI deferred to its own wave (H4).
- Rust: remaining pure-logic back-end modules (drift_*, warm_router
  scoring, memory_store helpers, capability scoring, token counting)
  into zero-dep crates, each port running the full slice-2 pipeline.
- Honest constraints carried: network code never in crates; SQLite/regex/
  IO stay at language edges; every surface gets observed-byte-parity proof.

Sequenced H0 (enablement: install Go, CI Go job) -> H1 (first-call shim)
-> H2 (dispatch + /v1) -> H3 (back-end ports) -> H4 (TUI wave).
2026-10-04 04:47:40 +01:00
Nick 558401844e fix(release): TUI footer version + drift gate now covers the footer pattern
Master CI unit-tests failure (run 18195): rune_tui.py:1888's footer said
'Rune v0.88.4' while __version__ is 0.89.0. Root cause in the auto-version
push-to-master bump: its replace pairs cover the footer, but the drift
GATE's regex checks only covered the Version: header. When RELEASE_VERSION
was stale, both replaces no-opped; the gate's RECOVER regex force-fixed
the header (first-match re.sub) and the footer passed through unchecked —
the exact half-bump shape seen in 396ae12.

- rune_tui.py footer -> Rune v0.89.0 (fixes test_tui_banner_uses_the_reviewed_release_version)
- auto-version.yml: gate checks add the footer regex ('Rune v({VER}) \|'),
  so future footer drift triggers the same RECOVER path as the header.
2026-10-04 04:34:05 +01:00
Nick 26e4929b10 chore(deps): re-resolve ci lock — markupsafe 3.0.4, platformdirs 4.12.3, websockets 17.2 (master catches up to the strangler branch's regen) 2026-10-04 03:16:27 +01:00
Nick 1462262476 fix(ci): retarget 8 jobs from dead rune-infra-pi label to rune-desktop (master)
Same fix as 0bf4726 on the strangler branch, applied directly to master:
queued workflow runs resolve their workflow definition from the DEFAULT
branch, so the strangler-branch commit alone could not drain stuck runs
18178 (auto-label on a PR) and 18183 (auto-label on an issue). This is
the landing spot. 8 occurrences across 5 workflows retargeted.
2026-10-04 03:01:40 +01:00
Nick 0bf47265c8 fix(ci): retarget 8 jobs from dead rune-infra-pi label to live rune-desktop runner
New submissions (issues/PRs) were stuck queued forever: auto-label.yml's
priority-gate + demand-comment jobs declared runs-on: rune-infra-pi, a
runner that no longer exists anywhere (registration list shows only
rune-desktop on gitea-desktop-runner). Every issue or PR filed since the
label went dark sat unprocessed — which is exactly the 'new submissions
not working' symptom.

Retargeted (sed, 8 occurrences across 4 workflows):
- auto-label.yml (2): priority gate, demand comment
- auto-backup.yml (1), auto-update-gitea.yml (1), auto-version.yml (1)
- maintenance.yml (5)

Verified live: probe issue + probe PR + probe branch all created AND
processed successfully through the API (submissions themselves work; it
was the responder workflows that were orphaned). Probe artifacts cleaned
up. The stuck queued runs 18178/18183 will drain once this lands.
2026-10-04 02:49:13 +01:00
Nick 02a9dc4a31 fix(ci): rust toolchain law — healthy rustup toolchain, runner-owned target, EP_RUST_LIB wiring
CI cargo build failed twice (runs 18169/18170): the SYSTEM rustc is broken
on this host at the packaging level — pacman rust 1:1.99.0-1's
librustc_driver.so has 638 undefined symbols versioned LLVM_23.1, and
/usr/lib/libLLVM.so.23.1 does not define narrow-char std::string::_M_mutate
under that node (it exports _M_create/_M_assign there; only the wide-char
_M_mutate carries the version). Every compile dies with a symbol-lookup
error while cargo --version prints fine — a silent-broken build host.

Fix (all local, no system changes):
- PATH points at /home/nick/.rustup/toolchains/stable.../bin FIRST — that
  toolchain is healthy and ships its own libLLVM. The ordering matters: via
  RUSTUP_TOOLCHAIN (shim-based), rustup would exec the broken system rustc.
  /home/nick already carries the x-ACL for gitea-desktop-runner (proven:
  runner-user build succeeds, 0.95s, zero-dep crate).
- CARGO_TARGET_DIR is a runner-owned /tmp dir (the repo's target/ is
  nick-owned; publish-by-copy into it failed with EACCES). No copy step at
  all now: ep_loader + the canary resolve artifacts via EP_RUST_LIB (file
  path form — the loader's override takes a FILE; set in both jobs' env).
- unit-tests job gains the same env block as canary (RUNE_HOME/
  RUNE_MEMORY_DIR/EP_RUST_LIB) so the shim battery's cdylib load and the
  isolation law hold in CI exactly as locally.
- Python canary resolves _BIN next to the EP_RUST_LIB file (binary and
  cdylib are siblings in the release dir; desktop keeps the repo-relative
  default).

Verified locally: runner-user build + EP_RUST_LIB file-form through BOTH
canaries (74/74, 66/66) and the shim battery (7/7); desktop default path
re-verified after artifact restoration (deps/ hardlinks were never lost —
an earlier publish test's rm removed the top-level copies; restored from
deps/).
2026-10-04 02:17:44 +01:00
Nick 0f3aeff58b fix(ci): diff-cover ratchet + canary artifact coupling — shim battery, crate build in CI
diff-cover 70% floor (run 18164's unit-tests failure):
- The floor counts CHANGED lines covered by pytest only. The strangler's
  verification moved the six ported shims to canaries/parities (outside
  pytest), so 46% of slice-2's changed lines were pytest-covered and the
  gate red-lined the port itself.
- tests/test_evidence_provenance_shims.py: a six-test shim battery driving
  the delegation surfaces the port changed (verification_layer dispatch +
  seven wrappers, evidence_probe schema/budget, behaviour_ledger state
  machine incl. accept_limitation + reload round-trip, behavior decision
  math on an isolated db, claim_audit scan->verify->degrade->journal->
  report pipeline, audit_ledger append->verify->read on the isolated
  ledger). Isolation law documented in the module docstring: RUNE_HOME/
  RUNE_MEMORY_DIR pinned before import (both resolve paths at import);
  behavior/claim_audit are imported by no other test file.
- save() finding: mutators never auto-persist in EITHER vintage (verified
  against the pre-port module) — the round-trip test calls save().
- .coveragerc omits ffi_query_memory_smoke.py (= the canary precedent:
  standalone evidence script, never suite-imported).
- Result replicated locally against gitea/master: 116 diff lines, 87%
  (floor 70), exit 0.

Canary job now builds the crate (run 18164's canary failure):
- Until 18164 the job silently relied on the host executor seeing the
  desktop's prebuilt target/ — my checkout-relative BIN made that visible
  as an uncaught Popen FileNotFoundError (§5 crash before finishing).
- ci.yml gains cargo build --release for the zero-dep crate (system cargo,
  world-readable on the runner; no registry fetch).
- §5 fails loud-but-counted (FAIL test, not a crash) when the artifact is
  missing, with the regression meaning spelled out.

Local wall: pytest 2257/0; canaries 74+66 both modes; lint 345/75 PASS;
type ratchet PASS under the pinned instrument; henxels exit 0; umbrella
gate exit 0; dep-lock in sync.
2026-10-04 02:08:29 +01:00
Nick ff2abcd502 fix(ci): run-18161 follow-through — mode detection, plan kebab-case, smoke coverage, deterministic type instrument
Canary mode detection (the real 18161 defect):
- isfile() is not a valid LIVE/ISOLATED discriminator: run 18141's shim
  writes left a zombie 1-entry ledger in the runner's persistent HOME, which
  flipped the naive gate to LIVE with wrong data. The discriminator is now
  the repo's own CI-sandbox convention (RUNE_HOME=/tmp/rune-ci-<id> — the
  workflow's own cleanup guard matches exactly that).
- The isolated battery seeds a PER-RUN temp ledger (module-attr override on
  the shim's LEDGER_FILE) instead of writing the runner's persistent home;
  binary §5 gets HOME=<temp> so binary and shim resolve the same file.
  Both modes: 74/74 locally and under simulated runner topology (zombie
  ledger present, RUNE_HOME set). Rust canary §4 gets the same dual-mode
  treatment.

Henxels holds cleared:
- plans/: three phase-g files renamed to kebab-case; references updated in
  lib.rs, ep_loader.py, canary docstring, Cargo.toml, supersession ledger.
- ffi_query_memory_smoke coverage: smoke repaired (was stranded — imported
  the deleted src/rune vintage; now drives shared/ shim, 41/41 green) and
  tests/test_ffi_query_memory_smoke.py runs it, skipping honestly when the
  cdylib isn't built (CI has no cargo step).

Type ratchet — instrument repair (§16V), not reading-management:
- scripts/type_check_ratchet.py now pins pyright --pythonpath to the
  INVOKING interpreter. Ambient-interpreter dependency resolution was the
  residual non-determinism: runner (py311, no rich/Pillow) vs desktop (3.14
  system site) produced 460 vs 454 on the identical tree, and any import
  ignore flipped to unnecessary-ignore in the other environment.
- pyrightconfig extraPaths now covers src/rune + shared + both crate dirs
  (bare intra-repo imports resolve under the pinned environment).
- Pillow declared in pyproject (genuine lazy dep of the diamond-frames
  surface; was undeclared) — lock regenerated, dep-lock checker green.
- Measured under the pinned instrument: 115 errors / 439 warnings
  (CI-equivalent locked 3.11 venv) and 115/436 (local 3.14 venv) — both
  below the 117/454 baseline; baseline unchanged. My canary's 4
  reportOptionalSubscript errors fixed by real narrowing, not suppression.

Lint baseline: BLE001 346 -> 345 (bulk_issue_guard.py's retry catch gains a
reasoned noqa; the file stays untracked per checkpoint hygiene, but the
honest-ledger test counts disk state — the noqa keeps local == CI).

Local wall: full pytest 2251/0 (incl. the new smoke test); canaries 74+66
both modes; parities 33/24/13/16/17; henxels exit 0; umbrella gate exit 0;
dep-lock green.
2026-10-04 01:08:20 +01:00
Nick b61af7a526 fix(ci): slice-2 canary completeness + gate repair — dual-mode canary law, ports 3-6 sections
Canary completeness (contract §5/§10 — owed at slice end):
- canary_audit_ledger.py 34 -> 74 checks: port-3 (evidence_probe schema gate
  + budget count + ProbeBudgetExceeded), port-4 (behaviour_ledger state
  machine: define/record/invalidate/outstanding/summary/history + byte-exact
  error envelopes), port-5 (behavior decision math through the shim on an
  isolated RUNE_HOME db: correction/habit math, promotion, not-found shapes),
  port-6 (claim_audit full pipeline on an isolated journal: scan/scan_text/
  audit/report + verify + stoplist wiring).
- canary_evidence_provenance_rust.py 28 -> 66 checks: the same four ports
  driven through the cdylib FFI (ep_validate_probe, ep_bl_*, ep_habit_/
  ep_correction_reinforce, ep_ca_* batteries).

Dual-mode canary law (repairs run 18141's red):
- Live-ledger pins (entries floor, live read/list shapes) fire only when the
  live promotion ledger exists in this process's HOME; under CI's sandboxed
  HOME the same sections run an isolated battery (shim-seeded entries,
  FFI-built chain) — identical check count, honest MODE print. The law and
  the artifact coupling are documented in both canary docstrings.
- Ghost-divergence repair: the Rust binary honors RUNE_MEMORY_DIR/RUNE_HOME
  first (main.rs ledger_path) while the deployed shim resolves expanduser
  (HOME); §5 binary envs now strip RUNE_* so both surfaces resolve the same
  ledger in every environment.
- §10 pins RUNE_MEMORY_DIR alongside RUNE_HOME (memory_dir() honors the
  former first).

Gate debt exposed by 18141's failure profile (pre-existing, now closed):
- config/umbrellas.json: ffi_query_memory_smoke mapped to the memory
  umbrella (owed since 43e7f5f; umbrella gate green in push mode).
- requirements-ci.txt regenerated: markupsafe 3.0.3 -> 3.0.4,
  websockets 17.1 -> 17.2 (checker green).
- lint-baseline.json BLE001 347 -> 346: claim_audit silent handlers
  repaired with exc_info logging + reasoned noqa (deliberate-silence sites
  only); canary battery handlers carry the canary-law reason. S110 holds.
- type ratchet green at 107/454 (local pyright pinned to CI's 1.1.414;
  isinstance runtime-shape checks carry scoped ignores; ep_loader imports
  annotated as runtime-by-design).
- store/scaffold + spine snapshots travel with the change (as at 2ac2d0a);
  supersession ledger canary rows updated to the true final counts.

Full local wall: 5 parity harnesses 33+24+13+16+17 all green; both canaries
green in LIVE and ISOLATED modes; consumer import chains (iteration,
testing_mode, rune_operator, contracts, curator + deployed shims) green;
live chain intact through the deployed shim; pytest suite 2250 passed/0.
2026-10-04 00:32:28 +01:00
Nick 497d14a449 ci: register canary_audit_ledger in the canary suite loop
The henxels contract requires every src/rune/canary_*.py on disk to be
registered in the CI canary loop (gap #138's rule is per-canary). The
slice-2 canary (port 1, canary pair carrier, 34 checks) was added with
the port but its registration was missed — the contract test caught it
live. Registered in the slice-1 position (after canary_query_memory,
the strangler-adjacent grouping).
2026-10-03 16:25:52 +01:00
Nick 2e68b6b054 phase-g slice 2: supersession ledger + registry remap + shared deletion
- plans/2026-10-03-phaseG-evidence-provenance-supersession.md: the
  slice-level ledger — port table (6/6, commits + parity + consumer
  proof), 12-step doctrine discharge, vintage-merge disclosures
  (symmetry guard, scan_text repair, port-1 sequence break), consumer
  map, live-data rules honored.
- shared/audit_ledger.py DELETED (superseded; zero importers verified
  by sweep; deployed shim + Rust core proven live before removal —
  language-governance rule ⑥).
- coverage registry: audit_ledger removed from SHARED_D.
- regression gate: exit 0 pre- and post-deletion.
2026-10-03 16:20:08 +01:00
Nick 969e39f6a3 phase-g slice 2 port 6: claim_audit decision skeleton -> rust core (parity 17/17)
- claims.rs: the verify() verdict tree (comparative-no-anchor gate,
  ledger-hits PASS, DEGRADE tails), report aggregation (total/by_verdict/
  by_type with '?' defaults), the clean fold, evidence-term stoplist.
  Regex engine + I/O stay Python (zero-dep crate law); timestamps Python.
- ep_ca_verify_gate / ep_ca_audit_clean / ep_ca_report_aggregate /
  ep_ca_evidence_terms FFI + ep_loader wrappers (restype law).
- claim_audit.py is now a shim, BOTH copies (src/rune + deployed):
  verify() delegates the tree + term filtering; report() / clean fold
  delegate; regex machinery (scan/reframe/degrade/EVIDENCE_TAG) + journal
  I/O + runner stay Python.
- VINTAGE MERGE (disclosed): the deployed scripts vintage carried the
  SYMMETRY GUARD (research_lens.symmetry_check, 2026-08-15) which src/rune
  lacked — preserved (load-bearing behavior, feature-preserving merge).
- REPAIRED a dead consumer: rune_tui.py:3752 calls claim_audit.scan_text()
  expecting {degraded, claims, clean} — NO implementation existed in either
  vintage (silent try/except death since the call landed). scan_text() is
  now a first-class surface; verified live (returns the exact dict shape).
- parity_claim.py: 17-case battery — verify tree 12 (flags derived the
  same way the original derives them: original verify() vs gate FFI),
  report aggregate over a real-shaped journal (incl. '?' fallback rows),
  clean fold 3, stoplist filter — 17/17 GREEN pre-switch vs original.
- deployed shim live: real journal (1127 rows, 851 DEGRADE / 276 PASS)
  aggregates through the Rust core; TUI backend trio imports clean.
2026-10-03 16:19:12 +01:00
Nick 273267763e phase-g slice 2 port 5: behavior decision math -> rust core (parity 16/16)
- habitmath.rs: the reinforce decision math — habit (activation/success/
  failure counting, confidence min(1.0, 0.5 + rate*0.5), candidate→active
  promotion at min_activations, neutral still counts activation) and
  correction (success +0.1 / failure −0.3 with ceilings/floors, evidence
  counting, active/archived at 0.3). STORAGE STAYS PYTHON — SQLite I/O
  and regex matching remain in the module (the crate is zero-dep by
  design; hand-rolling SQLite/regex would be hubris, not hardening).
  No clock in the core — timestamps pinned by the shim.
- ep_habit_reinforce / ep_correction_reinforce FFI + ep_loader wrappers.
- behavior.py is now a shim: reinforce() fetches the row, delegates the
  math, writes the update back; add/match/list/stats/CLI unchanged.
- parity_behavior.py: 12 decision-math cases (outcome × state × boundary:
  promotion-at-min, floors, ceilings, neutral, archive-threshold) with
  SEMANTIC comparison (parse-then-compare — the 1.0-vs-1 render delta is
  serialization, not semantics) + full-stack live-db scenarios both sides
  (promotion observed, activations logged) — 16/16 GREEN.
- deployed ~/.config/opencode/scripts/behavior.py upgraded to the same
  shim shape (vintage upgrade per contract §2) pointing at the LIVE db
  (~/.config/opencode/memory/behavior.db, 2 corrections / 1 habit,
  untouched); read paths verified against the live db (stats + match).
  Pre-deploy backup: /tmp/behavior_deployed_pre_port5.bak.
2026-10-03 16:01:52 +01:00
Nick 8973caae77 phase-g slice 2 port 4: behaviour_ledger -> rust core (parity 13/13)
- behaviour.rs: the full state machine — define (normalize/dup-check),
  transitions with history append (at/from/to/kind/detail[:200]/gen),
  record_auto_verification (ceiling AUTO_VERIFIED), record_real_world
  (USER_CONFIRMED iff passed && confirmed_by==user), accept_limitation,
  invalidate_for_change (CONFIRMED_STATES-only, deps ∩ closure + owner
  hit, default reason 'structural change touched: [...]' Python list
  repr sorted, reasons override, unrelated preserved), retest_history,
  outstanding (TEST_FAILED-first sort), summary. Timestamps passed IN
  (now) — no clock in the core, parity deterministic.
- FFI: doc-in/doc-out convention with {ok, err} envelope — err raises
  ValueError with the exact Python message (duplicate/unknown-behaviour/
  unknown-state all byte-equal). Read paths: ep_bl_get/retest_history/
  outstanding/summary.
- behaviour_ledger.py is now a shim: file glue (load/save) + constants
  stay Python; every state-machine op delegates via the doc envelope.
- parity_behaviour.py: full-scenario battery (3 behaviours, auto+real-
  world+other-confirm, selective invalidation with reasons override +
  owner hit, filters, error messages, save render indent=1 sort_keys)
  — byte-equal docs, 13/13 GREEN pre-switch (vs original) and
  post-switch (round-trip).
- consumers verified: iteration.py:23, testing_mode.py:19 (constants at
  import), rune_tui.py:3152 (lazy) — import chains green.
2026-10-03 15:59:14 +01:00
Nick 3450a1cb83 phase-g slice 2 port 3: evidence_probe -> rust core (parity 24/24)
- probe.rs: validate_probe schema gate + count_probes budget count,
  byte-parity on every error string incl. Python repr rendering:
  {status!r} single-quote escaping, sorted-list ['a', 'b'] shapes,
  non-string-status double-error (field error + membership error —
  Python runs the membership check on the RAW value), GROUNDED source
  >= 8, extra-fields sorted, field-order iteration.
- ep_validate_probe + ep_count_probes FFI; ep_loader wrappers with
  restype declarations (the segfault law holds: every new ep_* symbol
  gets argtypes+restype).
- evidence_probe.py is now a shim: validate_probe + count_probes
  delegate to the Rust core; run_probe orchestration (runner subprocess,
  exception semantics, budget enforcement, record_probe) stays Python.
- parity_probe.py: 24-case battery (17 schema-gate shapes incl. the
  non-string status trap, 3 non-dict top levels, 4 count_probes file
  shapes) byte-equal through FFI; GREEN pre-switch (vs original) and
  post-switch (round-trip).
- consumers verified: rune_operator.py:581/603 (run_probe), failure_
  recovery.py (budget map), research_stage_executor.py:312 (run_probe +
  MAX_PROBES_PER_TASK + ProbeBudgetExceeded) — all 23 sibling tests
  green through the shim.
2026-10-03 15:54:38 +01:00
Nick 9cb93e6573 phase-g slice 2 port 2: verification_layer -> rust core (parity 33/33)
- verification.rs: all seven verifiers + dispatch, to_dict shape
  byte-parity (key order passed/verifier_type/evidence_refs/failures/
  retryable/escalation_reason), Python str() rendering rules (bool ->
  True/False in evidence_refs — exit_code=False renders False not 0),
  Python == 0 semantics (bool/int cross-compare), default-value guards.
- ep_verify FFI + ep_loader.verify wrapper; verification_layer.py is now
  a shim (dataclass stays Python — rune_operator:323 constructs it
  directly; verify_* delegate to the Rust core and reconstruct).
- parity_verification.py: 33-case battery (7 verifiers, pass/fail/edge/
  default/unknown) byte-equal through the FFI, incl. CLI indent=2 render;
  GREEN pre-switch (vs original) and post-switch (round-trip).
- canaries extended: python 34/34 (deployed shim + port-2 sections),
  rust 28/28 (ep_verify battery + key-order parity).
- consumers: contracts.py dynamic __import__, canary_native MODULES list,
  full_audit file list — all resolve the shim; import surface unchanged.
2026-10-03 15:51:48 +01:00
Nick 4b0d1d24d2 phase-g slice 2 port 1: audit_ledger canary pair (deployed-shim 28/28, rust 23/23)
- Python canary pins the deployed shim surface to the frozen contract:
  live-path LEDGER_FILE, drift-key default, 3-key dict verify_chain,
  ascending serving order (observed), canary append through the shim
  (live entry 4242, chain intact post-append), rust CLI parity shapes.
- Rust canary drives the compiled surfaces: binary CLI shapes (verify/
  list/log-promotion + argparse error shapes incl. --limit=N form), FFI
  contract via ep_loader (5 fns), fresh-ledger append+verify+get, and the
  live-chain drift-key rule (23+ entries, chain intact).
- Sequence-break disclosure: the shim was deployed before these canaries
  existed; both now green against the deployed state — recorded on #2449.
2026-10-03 15:46:15 +01:00
Nick 114c394741 phase-g slice 2: evidence_provenance rust crate + ctypes loader (audit_ledger parity-complete, shadow 18/18) 2026-10-03 02:41:21 +01:00
Nick 7c68e41a63 phase-g slice 2: freeze evidence-provenance contract (drift-key live-ledger resolution, authority = src/rune vintage, port order set) 2026-10-02 23:12:22 +01:00
Nick 2ac2d0aefd refactor(query-memory): supersede old Python implementation with Rust cdylib (strangler steps 9-12)
Old src/rune/query_memory.py deleted (zero importers verified by sweep).
Coverage-audit registry remapped: shared/query_memory.py (ctypes shim) is
canonical; query_memory out of SRC_A/SHARED_D, into SHARED_B. Supersession
ledger written to plans/. Steps 1-11 evidence in ledger + issue #2448.
2026-10-02 20:52:40 +01:00
Nick 6f44a87894 feat(phase-g): authority-switch shim — deployed query_memory.py is now a ctypes shim over the Rust cdylib
Step 8 of the 12-step strangler doctrine: the deployed in-process consumers
(memory_store.py, growth_simulator.py) now run the Rust classification core
in-process via qm_loader.py. Full historical Python class API preserved,
including the query-history snapshot attribute (loaded at construction,
appended on record — canary check 2 reads it).

Post-switch verification wall ALL GREEN: FFI smoke 41/41, parity harness
ALL GREEN (stdout + history.jsonl + topic_stats.json byte-parity), Python
canary 12/12, Rust canary 27/27, both consumers verified live against the
deployed shim.
2026-10-02 20:24:26 +01:00
Nick 485623dbc4 feat(query-memory): add qm_topic_stats + qm_dead_weight FFI exports and loader bindings
Closes the FFI surface gap: Python get_topic_stats/get_dead_weight_topics
now have FFI exports wrapping the existing Rust topic_stats/dead_weight
impls via TopicStats::to_json. Loader extended with the two bindings
returning the pinned Python shapes (ordered dict / filtered name list).
FFI-gap parity probe: exact-equal vs deployed Python. Verification wall
green: smoke 41/41, parity ALL GREEN, canaries 12/12 + 27/27.
2026-10-02 19:49:19 +01:00
Nick 43e7f5f7d2 feat(query-memory): FFI exports for in-process Python consumption of the Rust core
Seven #[no_mangle] extern "C" exports on lib.rs (qm_new, qm_with_dir, qm_rate,
qm_record, qm_stats, qm_history, qm_health) so the deployed in-process consumer
(~/.config/opencode/scripts/memory_store.py adaptive_exploration_rate) can load
the compiled Rust core via cdylib instead of the Python implementation.

- ctypes loader qm_loader.py mirrors the deployed consumer's exact call shape
- FFI smoke src/rune/ffi_query_memory_smoke.py: 41/41 PASS against an isolated
  store — construction-ordering fixes: seed-then-construct (both implementations
  snapshot the store at construction), post-record health parity
- verification post-FFI: parity harness ALL GREEN, Python canary 12/12,
  Rust canary 27/27 (src/rust/query_memory/canary_rust.py)

Phase G strangler slice 1, step toward shadow comparison + authority switch.
2026-10-02 17:59:50 +01:00
Nick 54d2db97b0 refactor(query-memory): split core into lib.rs — rlib + cdylib for in-process Python loading
Phase G slice 1 (retrieval-index) strangler: the whole implementation lived
in main.rs (prior checkpoint's lib.rs claim was wrong — disk wins). Core
(QueryMemory, classification, serialization) now in src/lib.rs; main.rs is
a thin CLI shell over it. crate-type = [rlib, cdylib] so the deployed mimo
scripts (memory_store.py, growth_simulator.py) can load the compiled core
in-process instead of paying interpreter-subprocess costs.

Verified post-refactor: parity harness ALL GREEN (stdout + history.jsonl +
topic_stats.json byte-parity), Python canary 12/12 exit 0, Rust canary
27/27 exit 0 against rebuilt release binary.
2026-10-02 17:25:10 +01:00
Nick 523a859656 test(query-memory): add Rust canary proving C1-C8 against compiled binary
C9 canary for the Phase G strangler slice 1 (retrieval-index): 27 checks
in the same PASS/FAIL counter discipline as the Python canary, driving
target/release/query_memory with isolated HOME per section. Covers CLI
surface (C8), storage layout + cross-compat (C1), classification
tie-break (C2), adaptive rate clamp + threshold (C3), dead weight (C4),
corruption tolerance (C5), timestamp shape (C6), truncation (C7).

Fixes one probe defect found live: the C5 history-corruption probe
expected stats to rebuild from history.jsonl, but get_topic_stats reads
ONLY topic_stats.json in BOTH implementations (verified byte-identical
side-by-side) — probe corrected to assert the shared real behaviour.

Observed: 27/27 PASS, exit 0.
2026-10-02 16:11:54 +01:00
Nick 9b765852b8 feat(retrieval-index): Rust strangler candidate — Phase G slice 1
Zero-dependency Rust port of src/rune/query_memory.py (retrieval-index,
RUST-candidate per Phase E admission gate):

- src/rust/query_memory/ — first Rust workspace in the repo
- json_min.rs: hand-rolled JSON parser/serializer mirroring json.dumps
  (preserve-order objects, ensure_ascii escaping, surrogate pairs,
  indent-2 stats shape, f64 shortest-repr parity)
- main.rs: classification core + CLI (record/stats/rate/health),
  TOPIC_KEYWORDS in source insertion order, first-max tie-break,
  adaptive exploration rate (+-0.05 at >=10 queries), UTC isoformat
  parity incl. microsecond-omission law
- parity_test.py: cross-language conformance harness — identical
  command sequence through both implementations in isolated HOMEs,
  byte-parity on history.jsonl (timestamps masked) + topic_stats.json

Observed: cargo build clean; parity harness ALL GREEN (stdout +
both store files) across 24-command sequence covering unicode,
first-max tie-breaks, special chars, adaptive-threshold crossing.

Refs: /tmp/phaseG_retrieval_index_contract.md (C1-C10)
2026-10-02 15:40:12 +01:00
Nick f1e63b703e fix(token_optimizer): include content in CacheEntry.to_dict — closes #770 cache-loss defect
Pre-fix to_dict omitted the required field content, so _load's
CacheEntry(**entry_data) raised TypeError on every reload and the
blanket except silently discarded the whole cache on every restart.

Frozen regression contract tests/test_token_optimizer_cache.py
(3 tests: save_persists_content, roundtrip_restores_content,
corrupt_storage_degrades_to_empty_cache). Full suite 2250/0.
2026-09-30 12:18:35 +01:00
Nick 6d40f94f18 fix(test): hermetic group-liveness probe in TTY no-accumulation test
The test measured 'group-kill leaves nothing behind' via a global
'pgrep -x -c sleep' before/after delta — any unrelated system sleep
starting mid-loop on the runner (mount helpers, cron helpers) landed
in the delta and false-failed. CI run 13255 failed with exactly
'1 NEW sleep survivor' from an ambient sleep, reproduced locally
(/tmp/ambient_proof.mjs: measured_delta 1 from one spawned ambient
sleep).

Fix: probe OUR OWN process groups with kill(-pgid, 0) after the kill
sequence — a live group answers, a fully-reaped group throws ESRCH.
Hermetic to ambient load; negative control (/tmp/hermetic_catch.mjs)
confirms the probe still reports a survivor when the kill is skipped,
so the test's protective intent is preserved.

Verified: test green x3 locally, whole TTY contract file 8/8, fixed
test green under deliberate ambient-sleep bombardment.
2026-09-30 02:07:30 +01:00
Nick 7a869476be fix(forge_client): terminate _request with explicit raise — clears 5 pyright Optional warnings at call sites
_request never returns None (every attempt returns or raises), but the
implicit exhausted-loop path made pyright infer Optional at all 5 call
sites' .json() accesses, pushing the warning ratchet 454→459. The
terminal AssertionError documents exhaustiveness at the source instead
of 5 duplicated guards. Local pyright: errors 109, warnings 447 (≤454).
2026-09-30 01:41:30 +01:00
Nick e5292cce7b feat(forge): add httpx retry/timeout wrapper for transient-failure resilience
- _request() helper in forge_client.py: retry on connect-phase transport
  errors and idempotent-method 5xx/429, exponential backoff, 30s timeout
  default preserved per attempt; create_issue (POST) and list-style GETs
  route through it per retry-where-noise semantics
- frozen contract tests/test_forge_client_resilience.py (10 tests,
  written red-first per the bug-fix contract)
- legacy FakeResponse fixtures updated with status_code=200 (wrapper
  reads status_code for the GET-5xx retry gate)
- httpx wrapper spec: docs/rebase-language-rubric.md W4 named improvement
- model_routing.py:99 probe deliberately EXCLUDED (failure is the
  signal, not noise — one-shot design is the contract)
2026-09-30 01:10:22 +01:00
conductor 396ae120dc chore(release): auto-bump version to 0.89.0 — earned by # (minor: converged release (push-to-master bump))
Promotion record: (unknown)
2026-09-30 00:10:51 +01:00
201 changed files with 180440 additions and 2287 deletions

No files matched your search

BIN
View File
Binary file not shown.
+4
View File
@@ -15,6 +15,9 @@
# Canary files are omitted: src/rune/canary_*.py are standalone CI
# verification scripts executed directly by the canary job, never imported by
# the test suite — counting them would make the global floor a fiction.
# ffi_query_memory_smoke.py is the same class: a standalone evidence script
# driven directly (and by tests/test_ffi_query_memory_smoke.py when the cdylib
# is built), never imported by the suite.
[run]
source = src/rune
@@ -22,6 +25,7 @@ omit =
tests/*
scripts/*
src/rune/canary_*.py
src/rune/ffi_query_memory_smoke.py
[report]
fail_under = 29
+1 -1
View File
@@ -8,7 +8,7 @@ on:
jobs:
backup:
runs-on: rune-infra-pi
runs-on: rune-ci-desktop
steps:
# actions/checkout v4.2.2, pinned to the audited upstream tag commit.
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
+55 -11
View File
@@ -12,7 +12,7 @@ jobs:
# a demand comment — filing is not complete until prioritized.
priority-gate:
if: github.event_name == 'issues' && github.event.action != 'labeled' && github.event.issue.state == 'open'
runs-on: rune-infra-pi
runs-on: rune-ci-desktop
steps:
- name: Enforce priority label
uses: actions/github-script@v7
@@ -23,12 +23,13 @@ jobs:
const labels = (issue.labels || []).map(l => l.name);
const priorities = ['P0 — critical', 'P1 — high', 'P2 — medium', 'P3 — low'];
const has = labels.filter(l => priorities.includes(l));
if (has.length >= 1) {
if (has.length === 1) {
// Prioritized — clear the flag if it was set.
if (labels.includes('unprioritized')) {
await github.rest.issues.removeLabel({
await github.request('DELETE /repos/{owner}/{repo}/issues/{issue_number}/labels/{id}', {
owner: context.repo.owner, repo: context.repo.repo,
issue_number: issue.number, name: 'unprioritized',
issue_number: issue.number,
id: issue.labels.find(l => l.name === 'unprioritized').id,
}).catch(e => { if (e.status !== 404) throw e; });
}
return;
@@ -44,7 +45,7 @@ jobs:
});
auto-label:
runs-on: rune-infra-pi
runs-on: rune-ci-desktop
steps:
- name: Analyze and label
uses: actions/github-script@v7
@@ -72,7 +73,8 @@ jobs:
const managedLabels = new Set([
'memory', 'governance', 'identity', 'growth',
'perception', 'infrastructure', 'life', 'custodian',
'priority: high', 'bug', 'feature', 'research',
'bug', 'feature', 'research',
'P0 — critical', 'P1 — high', 'P2 — medium', 'P3 — low',
]);
// Type label: DECLARED over GUESSED. The template spine
@@ -135,9 +137,31 @@ jobs:
desiredLabels.add('custodian');
}
// Priority labels
if (text.includes('urgent') || text.includes('critical') || text.includes('broken')) {
desiredLabels.add('priority: high');
// Priority labels (2026-10-05: PROGRAMMATIC P-ASSIGNMENT —
// the gate demanded P0-P3 labels but nothing assigned them;
// the dead 'priority: high' label is gone per the 2026-09-04
// normalization). Assignment from the TITLE prefix first
// ([P1]/P1:/priority=...), then keyword inference using the
// gate's own rule set (the scale lives in ONE place — the
// priority-gate job above).
const pMatch = title.match(/\bP([0-3])\b/);
const priorityLabelNames = ['P0 — critical', 'P1 — high', 'P2 — medium', 'P3 — low'];
if (pMatch) {
desiredLabels.add(priorityLabelNames[Number(pMatch[1])]);
} else if (text.includes('urgent') || text.includes('critical') ||
text.includes('spine') || text.includes('blocks other work')) {
desiredLabels.add(priorityLabelNames[0]); // P0 — critical
} else if (text.includes('broken') || text.includes('fix') ||
text.includes('repair') || text.includes('defect') ||
text.includes('real defect') || text.includes('money')) {
desiredLabels.add(priorityLabelNames[1]); // P1 — high
} else if (text.includes('improve') || text.includes('consolidat') ||
text.includes('refactor') || text.includes('kernel') ||
text.includes('port')) {
desiredLabels.add(priorityLabelNames[2]); // P2 — medium
} else if (text.includes('benchmark') || text.includes('measurement') ||
text.includes('deferred')) {
desiredLabels.add(priorityLabelNames[3]); // P3 — low
}
const issueNumber = context.payload.issue?.number || context.payload.pull_request?.number;
@@ -147,6 +171,7 @@ jobs:
issue_number: issueNumber,
});
const currentLabels = new Set(current.data.map(label => label.name));
const currentLabelIds = new Map(current.data.map(label => [label.name, label.id]));
const staleManaged = [...currentLabels].filter(
label => managedLabels.has(label) && !desiredLabels.has(label)
);
@@ -156,11 +181,14 @@ jobs:
for (const name of staleManaged) {
try {
await github.rest.issues.removeLabel({
// Gitea's DELETE /labels/{id} requires the numeric label ID.
// Octokit's GitHub-style removeLabel(name) returned 404 and
// left stale priority labels behind (#2467: P0 AND P2).
await github.request('DELETE /repos/{owner}/{repo}/issues/{issue_number}/labels/{id}', {
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
name,
id: currentLabelIds.get(name),
});
} catch (error) {
if (error.status !== 404) {
@@ -178,5 +206,21 @@ jobs:
labels: missingDesired,
});
}
// The priority gate can flag a duplicate before this job
// reconciles it. Clear the flag only AFTER verifying exactly one
// surviving P-label against Gitea's current state.
const reconciled = await github.rest.issues.listLabelsOnIssue({
owner: context.repo.owner, repo: context.repo.repo,
issue_number: issueNumber,
});
const actual = reconciled.data;
const activePriority = actual.filter(l => priorityLabelNames.includes(l.name));
const flag = actual.find(l => l.name === 'unprioritized');
if (activePriority.length === 1 && flag) {
await github.request('DELETE /repos/{owner}/{repo}/issues/{issue_number}/labels/{id}', {
owner: context.repo.owner, repo: context.repo.repo,
issue_number: issueNumber, id: flag.id,
});
}
console.log(`Removed managed labels: ${staleManaged.join(', ') || 'none'}`);
console.log(`Added managed labels: ${missingDesired.join(', ') || 'none'}`);
+1 -1
View File
@@ -8,7 +8,7 @@ on:
jobs:
update-gitea:
runs-on: rune-infra-pi
runs-on: rune-ci-desktop
steps:
- name: Check current Gitea version
id: current
+8 -3
View File
@@ -11,7 +11,7 @@ permissions: write-all
jobs:
release:
runs-on: rune-infra-pi
runs-on: rune-ci-desktop
steps:
- uses: actions/checkout@v4
with:
@@ -131,8 +131,12 @@ jobs:
"pyproject.toml": [(f'version = "{VERSION}"', f'version = "{NEW}"')],
}
# Fetch current master content, apply replacements, VERIFY all five
# surfaces agree on NEW before committing anything (TUI gate).
# Fetch current master content, apply replacements, VERIFY all six
# surface patterns (five files + the TUI footer) agree on NEW before
# committing anything (TUI gate). The footer check was added after
# the 0.89.0 half-bump: a stale RELEASE_VERSION made both replace
# pairs no-op; the gate's RECOVER regex fixed the header only and
# the footer silently stayed 0.88.4, failing the banner test.
edited = {}
for path, pairs in surfaces.items():
status, cur = api(f"/contents/{path}?ref=master")
@@ -151,6 +155,7 @@ jobs:
checks = [
("src/rune/__init__.py", rf'__version__ = "({VER})"'),
("src/rune/rune_tui.py", rf"Version: v({VER})"),
("src/rune/rune_tui.py", rf"Rune v({VER}) \|"),
("src/rune/canary_gitea_automation.py", rf"'__version__ = \"({VER})\"'"),
("pyproject.toml", rf'version = "({VER})"'),
("README.md", rf"v({VER})"),
+106
View File
@@ -67,6 +67,20 @@ jobs:
unit-tests:
runs-on: rune-ci-desktop
if: ${{ !startsWith(gitea.event.pull_request.head.ref, 'chore/auto-bump') }}
env:
RUNE_HOME: /tmp/rune-ci-${{ github.run_id }}
RUNE_MEMORY_DIR: /tmp/rune-ci-${{ github.run_id }}/memory
# The unit shim battery loads the cdylib (ep_loader); point it at the
# runner-owned cdylib FILE from the crate-build step below (the override
# expects a file path, not a directory).
EP_RUST_LIB: /tmp/rune-ci-target-${{ github.run_id }}/release/libevidence_provenance.so
SP_RUST_LIB: /tmp/rune-ci-target-${{ github.run_id }}/release/librune_spine.so
MS_RUST_LIB: /tmp/rune-ci-target-${{ github.run_id }}/release/libmemory_scoring.so
CS_RUST_LIB: /tmp/rune-ci-target-${{ github.run_id }}/release/libcapability_scoring.so
TC_RUST_LIB: /tmp/rune-ci-target-${{ github.run_id }}/release/libtoken_counting.so
SC_RUST_LIB: /tmp/rune-ci-target-${{ github.run_id }}/release/libscaffold_core.so
MEMS_RUST_LIB: /tmp/rune-ci-target-${{ github.run_id }}/release/libmemory_storage.so
MR_RUST_LIB: /tmp/rune-ci-target-${{ github.run_id }}/release/libmemory_retrieval.so
steps:
# actions/checkout v4.2.2, pinned to the audited upstream tag commit.
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
@@ -102,6 +116,25 @@ jobs:
fi
python3 scripts/umbrella_impact.py check \
--base "$(git merge-base origin/master HEAD)" --head HEAD ${BODY_FILE:+--body-file "$BODY_FILE"}
- name: Build Rust evidence_provenance crate
run: |
# tests/test_evidence_provenance_shims.py drives the cdylib through
# the shim layer. Toolchain law (see the canary job for the full
# rationale): system rustc is broken on this host (LLVM_23.1
# version-node symbol gap); use the healthy rustup toolchain, PATH
# first so the rustup shim can't exec the broken system rustc.
# Runner-owned /tmp target dir (the repo's target/ is nick's).
export PATH="/home/nick/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/bin:$PATH"
export CARGO_TARGET_DIR="/tmp/rune-ci-target-${{ github.run_id }}"
mkdir -p "$CARGO_TARGET_DIR"
cargo build --release --manifest-path src/rust/evidence_provenance/Cargo.toml
cargo build --release --manifest-path src/rust/rune_spine/Cargo.toml
cargo build --release --manifest-path src/rust/memory_scoring/Cargo.toml
cargo build --release --manifest-path src/rust/capability_scoring/Cargo.toml
cargo build --release --manifest-path src/rust/token_counting/Cargo.toml
cargo build --release --manifest-path src/rust/scaffold_core/Cargo.toml
cargo build --release --manifest-path src/rust/memory_storage/Cargo.toml
cargo build --release --manifest-path src/rust/memory_retrieval/Cargo.toml
- name: Test with coverage (global floor + changed-lines ratchet)
run: |
# #149 coverage ratchet — both floors live in .coveragerc; raise,
@@ -113,12 +146,45 @@ jobs:
.test-venv/bin/coverage xml
.test-venv/bin/diff-cover coverage.xml --compare-branch origin/master --fail-under=70 --diff-range-notation ..
go-surface:
runs-on: rune-ci-desktop
if: ${{ !startsWith(gitea.event.pull_request.head.ref, 'chore/auto-bump') }}
steps:
# actions/checkout v4.2.2, pinned to the audited upstream tag commit.
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
- name: Verify Go toolchain version pin
run: |
# H0 pin: the Go surface builds against the pacman toolchain; the
# pin file is the single source of truth so a distro upgrade that
# changes behavior fails loud here, not downstream in the canary.
WANT=$(cat go-version.txt)
GOT=$(go version | awk '{print $3}' | sed 's/^go//' | cut -d- -f1)
echo "pinned: $WANT | toolchain: $GOT"
[ "$GOT" = "$WANT" ] || { echo "::error::Go toolchain drift: want $WANT, got $GOT (update go-version.txt with the rollout)"; exit 1; }
- name: go vet + go test + go build
run: |
cd src/go/rune-entry
go vet ./...
go test ./...
go build -o /tmp/rune-entry-ci .
canary:
runs-on: rune-ci-desktop
if: ${{ !startsWith(gitea.event.pull_request.head.ref, 'chore/auto-bump') }}
env:
RUNE_HOME: /tmp/rune-ci-${{ github.run_id }}
RUNE_MEMORY_DIR: /tmp/rune-ci-${{ github.run_id }}/memory
# The build emits to a runner-owned /tmp target dir (the repo's target/
# is nick-owned); EP_RUST_LIB points ep_loader at the cdylib FILE there
# (the override expects a file path, not a directory).
EP_RUST_LIB: /tmp/rune-ci-target-${{ github.run_id }}/release/libevidence_provenance.so
SP_RUST_LIB: /tmp/rune-ci-target-${{ github.run_id }}/release/librune_spine.so
MS_RUST_LIB: /tmp/rune-ci-target-${{ github.run_id }}/release/libmemory_scoring.so
CS_RUST_LIB: /tmp/rune-ci-target-${{ github.run_id }}/release/libcapability_scoring.so
TC_RUST_LIB: /tmp/rune-ci-target-${{ github.run_id }}/release/libtoken_counting.so
SC_RUST_LIB: /tmp/rune-ci-target-${{ github.run_id }}/release/libscaffold_core.so
MEMS_RUST_LIB: /tmp/rune-ci-target-${{ github.run_id }}/release/libmemory_storage.so
MR_RUST_LIB: /tmp/rune-ci-target-${{ github.run_id }}/release/libmemory_retrieval.so
steps:
# actions/checkout v4.2.2, pinned to the audited upstream tag commit.
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
@@ -130,6 +196,34 @@ jobs:
python3 -m venv .canary-venv
.canary-venv/bin/pip install --disable-pip-version-check -r requirements-ci.txt
.canary-venv/bin/pip install --disable-pip-version-check -e . --no-deps
- name: Build Rust evidence_provenance crate
run: |
# The canary pair + the unit shim battery drive the COMPILED surfaces
# (binary CLI parity, ep_loader FFI, ctypes cdylib loads).
#
# Toolchain law (troubleshooting history): the SYSTEM rustc is broken
# on this host — librustc_driver-<sha>.so (pacman rust 1:1.99.0-1)
# references std::string::_M_mutate under the LLVM_23.1 version node,
# but /usr/lib/libLLVM.so.23.1 does not define that narrow-char
# symbol under that node (it exports _M_create/_M_assign there, and
# only the WIDE-char _M_mutate) — every compile dies with `symbol
# lookup error` while `cargo --version` still prints. The rustup
# toolchain under /home/nick/.rustup is healthy and ships its own
# libLLVM, and /home/nick carries the x-ACL for this runner user.
# Point PATH at the toolchain bin dir (bypasses the rustup shim,
# which would exec the broken system rustc via RUSTUP_TOOLCHAIN).
# Target dir is runner-owned (/tmp): the repo's target/ is nick's.
export PATH="/home/nick/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/bin:$PATH"
export CARGO_TARGET_DIR="/tmp/rune-ci-target-${{ github.run_id }}"
mkdir -p "$CARGO_TARGET_DIR"
cargo build --release --manifest-path src/rust/evidence_provenance/Cargo.toml
cargo build --release --manifest-path src/rust/rune_spine/Cargo.toml
cargo build --release --manifest-path src/rust/memory_scoring/Cargo.toml
cargo build --release --manifest-path src/rust/capability_scoring/Cargo.toml
cargo build --release --manifest-path src/rust/token_counting/Cargo.toml
cargo build --release --manifest-path src/rust/scaffold_core/Cargo.toml
cargo build --release --manifest-path src/rust/memory_storage/Cargo.toml
cargo build --release --manifest-path src/rust/memory_retrieval/Cargo.toml
- name: Run canary suite
run: |
FAILED=0
@@ -156,6 +250,18 @@ jobs:
src/rune/canary_p1_06.py \
src/rune/canary_p1_01.py \
src/rune/canary_query_memory.py \
src/rune/canary_rune_entry.py \
src/rune/canary_spine_meta.py \
src/rune/canary_audit_ledger.py \
src/rust/rune_spine/canary_spine_rust.py \
src/rune/canary_rune_serve.py \
src/rune/canary_tui_transport.py \
src/rust/memory_scoring/canary_memory_scoring.py \
src/rust/capability_scoring/canary_capability_scoring.py \
src/rust/token_counting/canary_token_counting.py \
src/rust/scaffold_core/canary_scaffold_core.py \
src/rust/memory_storage/canary_memory_storage.py \
src/rust/memory_retrieval/canary_memory_retrieval.py \
src/rune/canary_unbounded.py \
src/rune/canary_full_system.py \
src/rune/canary_native.py; do
+5 -5
View File
@@ -8,7 +8,7 @@ on:
jobs:
lint-and-format:
runs-on: rune-infra-pi
runs-on: rune-ci-desktop
steps:
- uses: actions/checkout@v4
- name: Lint Python files
@@ -18,7 +18,7 @@ jobs:
ruff format shared/ src/ || true
canary-suite:
runs-on: rune-infra-pi
runs-on: rune-ci-desktop
steps:
- uses: actions/checkout@v4
- name: Run all canaries
@@ -37,7 +37,7 @@ jobs:
exit $FAILED
best-practices:
runs-on: rune-infra-pi
runs-on: rune-ci-desktop
steps:
- uses: actions/checkout@v4
- name: Check best practices
@@ -72,7 +72,7 @@ jobs:
grep -rn "TODO\|FIXME\|HACK\|XXX" shared/ src/ --include="*.py" | head -10 || echo " Clean"
documentation-check:
runs-on: rune-infra-pi
runs-on: rune-ci-desktop
steps:
- uses: actions/checkout@v4
- name: Check documentation coverage
@@ -113,7 +113,7 @@ jobs:
# push-triggered safety net in auto-version.yml. Detects master/tag drift
# and files a deduped issue — it never auto-releases without the PR flow.
release-drift-reconciliation:
runs-on: rune-infra-pi
runs-on: rune-ci-desktop
steps:
- uses: actions/checkout@v4
with:
+1
View File
@@ -14,3 +14,4 @@ state/
# Runtime artifacts (cwd-local event journal + capability index)
index/
journal/
src/rust/*/target/
+1
View File
@@ -0,0 +1 @@
probe 1791291272
+1 -1
View File
@@ -1,6 +1,6 @@
# Rune
**Current release: v0.88.4**
**Current release: v0.99.6**
**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.**
+115 -69
View File
@@ -4,13 +4,30 @@
"generated_at": "2026-09-16T18:23:38Z",
"umbrellas": {
"capability": {
"canaries": [
"canary_native",
"canary_p0_05",
"canary_p1_01",
"canary_rune_entry",
"canary_rune_serve"
],
"configs": [],
"depends_on": [
"memory"
],
"description": "Tool selection and execution authority",
"go_surfaces": [
"rune-entry"
],
"mirrors": [],
"modules": [
"adaptive_router",
"builder_bridge",
"builder_bridge_cli",
"capability_broker",
"connection_pool",
"core_server",
"cs_loader",
"evidence_probe",
"forge",
"forge_client",
@@ -25,21 +42,30 @@
"tool_executor",
"tool_protocol"
],
"depends_on": [
"memory"
],
"mirrors": [],
"canaries": [
"canary_native",
"canary_p0_05",
"canary_p1_01"
"rust_crates": [
"capability_scoring"
]
},
"governance": {
"canaries": [
"canary_full_system",
"canary_schema_migration",
"canary_audit_ledger",
"canary_spine_meta"
],
"configs": [
".coveragerc",
"go-version.txt",
"lint-baseline.json",
"pyright-baseline.json"
],
"depends_on": [],
"description": "Quality control, drift detection, promotion, audit",
"mirrors": [],
"modules": [
"audit_ledger",
"build_pipeline_policy",
"cc_loader",
"claim_audit",
"contracts",
"curator",
@@ -47,17 +73,24 @@
"drift_ledger",
"drift_observability",
"failure_auto_filer",
"programme_gates"
"programme_gates",
"sc_loader"
],
"depends_on": [],
"mirrors": [],
"canaries": [
"canary_full_system",
"canary_schema_migration"
"rust_crates": [
"evidence_provenance",
"scaffold_core"
]
},
"growth": {
"canaries": [
"canary_full_system"
],
"depends_on": [
"governance",
"memory"
],
"description": "Learning and improvement",
"mirrors": [],
"modules": [
"autonomy_loop",
"cognitive_loop",
@@ -68,18 +101,17 @@
"growth_simulator",
"self_improve",
"subconscious_pass"
],
"depends_on": [
"governance",
"memory"
],
"mirrors": [],
"canaries": [
"canary_full_system"
]
},
"identity": {
"canaries": [
"canary_full_system"
],
"depends_on": [
"governance"
],
"description": "Who the agent is \u2014 persona, voice, anchors",
"mirrors": [],
"modules": [
"behavior",
"identity_anchors",
@@ -90,17 +122,19 @@
"voice_injector",
"voice_sync",
"voicebox"
],
"depends_on": [
"governance"
],
"mirrors": [],
"canaries": [
"canary_full_system"
]
},
"inference": {
"canaries": [
"canary_p1_02",
"canary_p1_06",
"canary_resource_authority"
],
"depends_on": [
"capability"
],
"description": "Model routing and outbound requests",
"mirrors": [],
"modules": [
"inference_trace",
"model_connect",
@@ -111,31 +145,39 @@
"provider_registry",
"response_adapter",
"standardized_providers",
"tc_loader",
"token_optimizer"
],
"depends_on": [
"capability"
],
"mirrors": [],
"canaries": [
"canary_p1_02",
"canary_p1_06",
"canary_resource_authority"
"rust_crates": [
"token_counting"
]
},
"memory": {
"canaries": [
"canary_full_system",
"canary_memory_synthesis",
"canary_query_memory"
],
"depends_on": [
"governance"
],
"description": "Storing, retrieving, and managing knowledge",
"mirrors": [],
"modules": [
"abstraction",
"cache",
"drift_cache",
"drift_compression",
"ffi_query_memory_smoke",
"graph_memory",
"graph_shard_bridge",
"memory_consolidation",
"memory_env",
"memory_selector",
"memory_store",
"mems_loader",
"mr_loader",
"ms_loader",
"query_memory",
"recall_router",
"retrieval_benchmark",
@@ -143,18 +185,33 @@
"tier_manager",
"warm_neuron_store"
],
"depends_on": [
"governance"
],
"mirrors": [],
"canaries": [
"canary_full_system",
"canary_memory_synthesis",
"canary_query_memory"
"rust_crates": [
"memory_retrieval",
"memory_scoring",
"memory_storage",
"query_memory"
]
},
"orchestration": {
"canaries": [
"canary_cognitive_strata",
"canary_conversation_state",
"canary_gitea_pipeline",
"canary_orchestration_foundation",
"canary_p1_07",
"canary_surface_parity",
"canary_tui_drift",
"canary_tui_transport"
],
"depends_on": [
"capability",
"growth",
"identity",
"inference",
"perception"
],
"description": "Task lifecycle, operator, TUI presentation",
"mirrors": [],
"modules": [
"auto_detect",
"build_mode",
@@ -189,32 +246,24 @@
"task_persist",
"tiered_executor",
"time_awareness",
"tui_transport",
"tui_validator",
"umbrella_update",
"verification_layer",
"warm_router",
"work_manager"
],
"depends_on": [
"capability",
"growth",
"identity",
"inference",
"perception"
],
"mirrors": [],
"canaries": [
"canary_cognitive_strata",
"canary_conversation_state",
"canary_gitea_pipeline",
"canary_orchestration_foundation",
"canary_p1_07",
"canary_surface_parity",
"canary_tui_drift"
]
},
"perception": {
"canaries": [
"canary_context_broker",
"canary_full_system"
],
"depends_on": [
"memory"
],
"description": "Input processing and context assembly",
"mirrors": [],
"modules": [
"attention_capsule",
"auto_context",
@@ -249,6 +298,8 @@
"salience_gate",
"scaffold",
"spine",
"spine_language",
"spine_loader",
"stage_guards",
"structural_migration",
"structural_model",
@@ -260,13 +311,8 @@
"wait_guard_corpus",
"witness"
],
"depends_on": [
"memory"
],
"mirrors": [],
"canaries": [
"canary_context_broker",
"canary_full_system"
"rust_crates": [
"rune_spine"
]
}
}
+16 -1
View File
@@ -272,4 +272,19 @@ observations, fresh resume.
Release-note generator: LIVE-PROVEN (real Gitea, 10 releases). Backfill:
0.66.35→0.66.44 carrying historical-backfill provenance. Pre-0.66.35
history: skipped (weak evidence — honest limitation).
history: skipped (weak evidence — honest limitation).
## Contract discovery → spine (2026-10-05, #2465 CI correction)
The closed construction contract is the authority for discoveries. Spine
rebuild reads its observed entities and candidate corrections; it does not
claim a correction has been built. Qualified IDs such as
`rust-crate:cognition_core` keep their declared kind. Bare file paths,
test suites and other unqualified references become
`construction-surface:<reference>`, never `module`. An observed
surface may be PRESENT; a candidate stays DECLARED until independently
observed. Existing bad records are migrated with their evidence and edge
references intact. This matters: the first write-back mapped bare paths to
modules, made 22 false module records and crashed CI on the next rebuild.
The per-contract quick-reference markdown is generated from the scaffold
store at close; it is a view, not another authority.
+1
View File
@@ -10,6 +10,7 @@ new surface._
| `rune_tui.py` | The main Textual application — chat pane, work pane, status bar (version label + refresh), footer, dialogs | The known giant (file-size debt tracked in henxels); composition root for all UI |
| `tui_validator.py` | Import-allowlist validation for TUI code | Drift defect tracked in #139 (allowlist vs `__future__`/relative imports) |
| `tui_backlog.py` | Not a surface — the durable TUI Upgrade Backlog ledger (outstanding projection/control work per backend feature) | SCAFFOLD-TRANCHE-007; ledger only, never structural authority |
| `tui_transport.py` | Not a surface — the transport seam for the TUI's operator calls (in-process default / /v1 wire opt-in) | H4 slice 1 (contract BUILDER-2026-10-04T10:32:00Z-cli); RUNE_TUI_TRANSPORT selects; coverage: canary_tui_transport.py |
## Adding a TUI surface
+1
View File
@@ -0,0 +1 @@
1.27.1
+25 -1
View File
@@ -1,7 +1,31 @@
settings:
warn_about_similar_files:
above: 0.85
ignore: ["docs/review-lessons.yaml", "docs/v1/**", "docs/wiki/**", "store/spine/**"]
ignore: ["docs/review-lessons.yaml", "docs/v1/**", "docs/wiki/**", "store/spine/**",
# shared/ is the DEPLOYED-VINTAGE mirror tree (strangler era):
# every src/rune <-> shared similarity here is intentional
# (the shim delegates to the crate; the mirror is the deployed
# vintage it strangles). Registering the deliberate pairs.
"shared/**",
"src/rune/drift_core.py", "src/rune/drift_compression.py",
"src/rune/drift_observability.py", "src/rune/shard_manager.py",
"src/rune/subconscious_pass.py",
# deliberate mirror pairs outside the strangler set:
# issue templates are mirrored into shared/issue_templates;
# completion_file.py is the bridge/daemon mirror of scripts/;
# interop/rebase-proof is a committed proof fixture.
".gitea/ISSUE_TEMPLATE/**", "shared/issue_templates/**",
"bridge/daemon/completion_file.py", "scripts/completion_file.py",
"interop/rebase-proof/**",
# per-crate copies by zero-dep design: each crate is
# self-contained (no shared crate dependency), so the pyjson
# JSON module is copied verbatim per crate and the tiny
# zero-dep lockfiles are near-identical by construction.
"src/rust/*/Cargo.lock", "src/rust/*/src/pyjson.rs",
"src/rust/query_memory/src/json_min.rs",
# store/scaffold is data (proof fixtures, gate objects),
# same class as the already-ignored store/spine.
"store/scaffold/**"]
henxels:
- henxel: "Canaries live at the repo's canary slot and are registered in CI"
+1 -1
View File
@@ -1,4 +1,4 @@
{
"BLE001": 347,
"BLE001": 345,
"S110": 75
}
+599
View File
@@ -38309,3 +38309,602 @@
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_27044643acf3", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:f841b27cd0a3f14a7fa1be8e87d46c0b65a23b0a75d6324d2b14be717476459d", "schema_version": 1, "timestamp": "2026-09-29T02:59:13.857300+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_edb2d4f65dc2", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:478b26b3101f2da813ba48b2c1c1afc463c1c0d5299904de9faf947b8854afbd", "schema_version": 1, "timestamp": "2026-09-29T02:59:13.923428+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_fe5b4b43370a", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:c5855d77df4b36cebe2c0c18f7bea277d0f779b73b84f357375563349f3e984c", "schema_version": 1, "timestamp": "2026-09-29T02:59:13.934399+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_b0e19f4acdf9", "event_type": "storage.degraded", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:dc4a0ef045512b494194a21ba9206ec66e21ae710f7c2caa29f194c48fb142c0", "schema_version": 1, "timestamp": "2026-10-05T05:21:25.789100+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_016be0c4b465", "event_type": "capability.failed", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:5905ca1fb3b9d207b290447e7844c186b1a34e36d7a7e4102159cee3f8ae136b", "schema_version": 1, "timestamp": "2026-10-05T05:21:25.806792+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_086af48a13f3", "event_type": "storage.degraded", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:dc4a0ef045512b494194a21ba9206ec66e21ae710f7c2caa29f194c48fb142c0", "schema_version": 1, "timestamp": "2026-10-05T05:21:25.823965+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_5551023172fa", "event_type": "storage.degraded", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:600ba21f0472de9fb57133929648cd8121d1717595e7cd27c381d71ac03852c8", "schema_version": 1, "timestamp": "2026-10-05T05:21:25.838205+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_80fd52f13829", "event_type": "capability.failed", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:8a8675818b5d411241e416ff9e8c9a1845d9b2a8befeacc51e10ab977731aeba", "schema_version": 1, "timestamp": "2026-10-05T05:21:25.848050+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_087025013478", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:c78e434affa5f0ba0f4682f0cd5352b6050baf50ba81fd03451a7b2c1c3ca785", "schema_version": 1, "timestamp": "2026-10-05T05:23:11.984154+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_24cbfc281745", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:7fed33b30bb04b86a51308ba4ac316b81b5e334fd294d0d91cc88f5d7811a579", "schema_version": 1, "timestamp": "2026-10-05T05:23:12.283402+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_c232600ff73f", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:5ae7726f99a8b4ac388a40057e42d5b327181180d8e8358e09816e5b8eeeb07f", "schema_version": 1, "timestamp": "2026-10-05T05:23:12.530839+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_a2525db7221a", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:1e825cba1cabbbd6333c03e88eda16bb01395e84505925366367f4406ce9e3eb", "schema_version": 1, "timestamp": "2026-10-05T05:23:12.744665+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_563adf9a5f35", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:763695d7a131c268d6bcbe4a691bbd78be59068649e1c80caa0665c33cd74665", "schema_version": 1, "timestamp": "2026-10-05T05:23:13.009164+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_816a22f4d447", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:aaaf89f49546f49e337ef8cdd1fe3720fbd84655677dd4fefe81939d81f2ff00", "schema_version": 1, "timestamp": "2026-10-05T05:23:13.268251+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_17643f9c08d3", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:c1d39e849fe42430601ee240366550572fb4c0d7c016fd887aac50b5328b6e10", "schema_version": 1, "timestamp": "2026-10-05T05:23:13.331692+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_acc564bb5e04", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:34b51a7de4f22973c41fdb3b7b947b2a9f4c5de5904335e5a89b39466aa95ac5", "schema_version": 1, "timestamp": "2026-10-05T05:23:13.343968+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_75d26cf7246c", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:c2a8dfa6e57ded3f85dd2339b16a0ea3a40568e2831560b5c15891b08ce15e7d", "schema_version": 1, "timestamp": "2026-10-05T05:23:13.355213+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_7b7adcfd4fdb", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:cc49dbc2afd1cb8d4f226df8f8e28a2c9fdb52140ef6dc9f9ab79aedc9e18557", "schema_version": 1, "timestamp": "2026-10-05T05:23:13.366512+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_4bfe85f92dd6", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:81a61375083c0c2a8b0eb756bdfa452118ea92332a0197d8d1e2e43648ee4279", "schema_version": 1, "timestamp": "2026-10-05T05:23:13.377960+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_8513a24cc16b", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:1f6079f682719dbec8bdd455a1098c1c0ba64fc063968329fe9ddc6a716f9c56", "schema_version": 1, "timestamp": "2026-10-05T05:23:13.389163+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_6e97b06ebde1", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:15fd87c25a425dcf9d07ddb24ffe91fc03c6ae68431ad887067362b3d2eb7550", "schema_version": 1, "timestamp": "2026-10-05T05:23:13.400338+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_4d7e8da95431", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:4ea1b6698f4d71fe15549b852957d618db6ea67ccaae0cbff8c0e49ce5b4d526", "schema_version": 1, "timestamp": "2026-10-05T05:23:13.411227+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_f796ee067453", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:7307f3ecceeed949123988f8e438bd3cbe1169e27cb5d6159f77a6ff600c4802", "schema_version": 1, "timestamp": "2026-10-05T05:23:13.422300+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_46bc455a244c", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:b1e7a541ab66571a7f8061380574c0755b0a1d2613b9331831cb801c87b1710d", "schema_version": 1, "timestamp": "2026-10-05T05:23:13.432243+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_89a5c3f5916a", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:12b0b024840c0b30ee3afbc023e1a72586024ce42e34f7ec0363990c983bf9db", "schema_version": 1, "timestamp": "2026-10-05T05:23:16.976063+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_8340dcba0ee9", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:f9ac3ca3cfb75d4d88edabe5ba101af2bf580c89d06a9077b80152db8a222f9a", "schema_version": 1, "timestamp": "2026-10-05T05:23:19.934351+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_1200db078626", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:89450873a6b1a0c4755b443f10fb807e3a308aede5fce1820840c2cefb56b43d", "schema_version": 1, "timestamp": "2026-10-05T05:23:19.942616+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_89c8feea275a", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:cab0efc6c547df0511f048933ed628afa5fc02962a028d383dc5772fea73a0e5", "schema_version": 1, "timestamp": "2026-10-05T05:23:20.051988+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_38dff4e11ccd", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:a9ab994b6ecbce010c3190bb7144b56fb77049021e72613a52471af773a44597", "schema_version": 1, "timestamp": "2026-10-05T05:23:20.060629+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_c8e9380dd848", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:a1d29ee3e813f549c8e8bbbacd64a419240911445811116100e5b4dd2488c672", "schema_version": 1, "timestamp": "2026-10-05T05:23:20.312488+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_c2e0ed12291f", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:8d1ad954a4da5edc5a23b0c87759d0f7f7345af1eafb4181fcb9224ebc45c7b5", "schema_version": 1, "timestamp": "2026-10-05T05:23:20.325260+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_b73836b33bdf", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:0e657de7974c57b514a1a56c0882e4f7c5e5b80598bed0b02933ea7f5bef571f", "schema_version": 1, "timestamp": "2026-10-05T05:23:20.334787+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_0c173fc1d3cc", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:ede576070fc93e8e039c44444be8a044401ee95af2e0c3b3a6d7dc48d994584d", "schema_version": 1, "timestamp": "2026-10-05T05:23:29.588228+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_5b2271cbb551", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:2274151dcba0975ee1a3aa8a239d2ea47e66ec289fffc6832d0767c9456aa4fc", "schema_version": 1, "timestamp": "2026-10-05T05:23:29.611251+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_34338986691f", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:e3a74c42d1b951be16321b4a88f929daa94795333bc482910828341b5633472d", "schema_version": 1, "timestamp": "2026-10-05T05:23:29.635440+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_7cc6e81e1801", "event_type": "capability.failed", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:11a7f153199214499dfd4fc9a0b08d1f2f531ce3485b907cfe3a036cf7edc586", "schema_version": 1, "timestamp": "2026-10-05T05:23:29.678207+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_1b14c0ed4e61", "event_type": "capability.failed", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:d5c968d2dbed3174298be875d798719fcdc31e803c8b5a6306d782b4a43afc6f", "schema_version": 1, "timestamp": "2026-10-05T05:23:29.746400+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_b2dbe5833cf1", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:6bf69da741e431dd7f5de2630995da45ddd2c6224207891b5216e012e6998b64", "schema_version": 1, "timestamp": "2026-10-05T05:23:33.342279+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_deeca46b17d3", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:2140010d66eac2b9f209070d86ccadd5b36f6eeacb73f486d8352ce7b3af9b38", "schema_version": 1, "timestamp": "2026-10-05T05:23:33.366683+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_92845503a937", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:2fc949e469e7c56151e298835012db3d348b78c7c8ebf4b5f834a19a983939e6", "schema_version": 1, "timestamp": "2026-10-05T05:23:33.374544+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_6a0ce25783a7", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:8085f4a6a55f8ba91267690d7c098dbf8b2761e7847b7928ef12ea450199a834", "schema_version": 1, "timestamp": "2026-10-05T05:23:33.387814+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_672844a86bab", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:0af82eb6d979f50ebc79a8b7a55c5285ce8e33ae59657bc492154123a5a0d976", "schema_version": 1, "timestamp": "2026-10-05T05:23:33.395837+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_b1fdffc18dd3", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:9dbcf32d01e7e497296797b117daaea11782fca4a5fa72007630ee62c2f6cf02", "schema_version": 1, "timestamp": "2026-10-05T05:23:33.407145+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_8e6b6044f667", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:ad451919c0d25830adc1c2b82d9e2030a71c60a9726811cea52e78648d665595", "schema_version": 1, "timestamp": "2026-10-05T05:23:33.415615+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_a602deb56bf1", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:7004f9afb320d2fec931359297a4667cbe8cf78de131fe7bbeb52107feb88afd", "schema_version": 1, "timestamp": "2026-10-05T05:23:33.591693+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_983155bebc20", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:1b3cec7b74728266d9369c52a0557b007cd55ad230d4db441755bbd6876242f6", "schema_version": 1, "timestamp": "2026-10-05T05:23:33.600649+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_b0b0c611580e", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:2e13684b717ea5fb6950cf67b5a00a81d62df06b4898dd20a787579e6e99868f", "schema_version": 1, "timestamp": "2026-10-05T05:23:33.614050+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_cb991f5b273e", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:c1b8c60560793a61d67ea8b954b0bfa1f7f7cb316c2d2dd3b0d147747420bf58", "schema_version": 1, "timestamp": "2026-10-05T05:23:33.636737+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_40b7cf40d80f", "event_type": "capability.failed", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:1163d0fcebec85439e62d33ef5e1f3de09c6e1a75e16c7ecdb1a887be368d19d", "schema_version": 1, "timestamp": "2026-10-05T05:23:33.651465+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_9ceaf311bc74", "event_type": "storage.degraded", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:dc4a0ef045512b494194a21ba9206ec66e21ae710f7c2caa29f194c48fb142c0", "schema_version": 1, "timestamp": "2026-10-05T05:23:33.668562+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_ca7424aa2083", "event_type": "capability.failed", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:1163d0fcebec85439e62d33ef5e1f3de09c6e1a75e16c7ecdb1a887be368d19d", "schema_version": 1, "timestamp": "2026-10-05T05:23:33.760632+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_9083dfff3b45", "event_type": "storage.degraded", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:dc4a0ef045512b494194a21ba9206ec66e21ae710f7c2caa29f194c48fb142c0", "schema_version": 1, "timestamp": "2026-10-05T05:23:33.775813+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_dc688dae7058", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:7ced6642e67f8be4e5e3530576ab9c6d98ddcc4a1416c79988dc34225b750c8f", "schema_version": 1, "timestamp": "2026-10-05T05:23:33.786010+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_013636606e3e", "event_type": "capability.failed", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:1163d0fcebec85439e62d33ef5e1f3de09c6e1a75e16c7ecdb1a887be368d19d", "schema_version": 1, "timestamp": "2026-10-05T05:23:33.859102+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_c858eef7778b", "event_type": "storage.degraded", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:dc4a0ef045512b494194a21ba9206ec66e21ae710f7c2caa29f194c48fb142c0", "schema_version": 1, "timestamp": "2026-10-05T05:23:33.873133+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_89ec31fad8c2", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:653ca70f91b53be286aaa0f17c30a2b4e7736b75ea343a93e5ea14ed1a28be6d", "schema_version": 1, "timestamp": "2026-10-05T05:23:33.882320+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_a82038972d92", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:072e8f7d9fc3e57275a09c1453f75d5e2f9e81536235be64f8528a1bb979b7b2", "schema_version": 1, "timestamp": "2026-10-05T05:25:17.396213+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_b8eb53953104", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:a22cb8f16063fdadc7fd10bbfd441998175f5fd4b7b6725c968dab4b1cde994e", "schema_version": 1, "timestamp": "2026-10-05T05:25:17.416907+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_91907ae5b9b6", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:6c2b5a4a7f98be5a78d6b4c90ff52b0a17a8bb4795f14ce51981cfa0cf99c50b", "schema_version": 1, "timestamp": "2026-10-05T05:25:17.430981+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_1412a44236ea", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:d9ea3a87b24a45857950d84d42d3de81d837bc9fa49e4476876c5ded07eaae8d", "schema_version": 1, "timestamp": "2026-10-05T05:25:17.443944+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_6ba5a799e1f4", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:f5ce7f4257fc0b5152a8a2e6a571758f2e8b377c573447a4631392741ce3409f", "schema_version": 1, "timestamp": "2026-10-05T05:25:17.462161+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_8c902011aabf", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:7acd608e1a5d2e803f7cf624f595a570669c3634771de03b5cfb5ad9013a375a", "schema_version": 1, "timestamp": "2026-10-05T05:25:20.413886+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_15df1245d414", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:2a12efb91d5fe122a91e061a4c099ebc8b439f62a71f05e80466f0912fefa745", "schema_version": 1, "timestamp": "2026-10-05T05:25:20.425874+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_1a3a8a9f59da", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:913868291eb1a253926b00356c41e9c6ca8dba1aa618936d21760cbc33395b13", "schema_version": 1, "timestamp": "2026-10-05T05:25:22.519216+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_99dd71ab164d", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:060381d18e06e0539920b1840d1528fe30e62a3efe4d98231795388580cf2024", "schema_version": 1, "timestamp": "2026-10-05T05:25:22.538570+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_3820eea4d142", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:2b5425f3fe253df866f8344a2fff39e3bda8720d3b6b7e0863d37253f62f91c5", "schema_version": 1, "timestamp": "2026-10-05T05:25:22.651874+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_916a7ff0fc86", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:c6cd20ab75e396c5805a2d937214cbded98ffb005bfdd0805b9e67cf6e18ed54", "schema_version": 1, "timestamp": "2026-10-05T05:25:22.663015+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_fead6a5de91b", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:959100e6d856c13f461109b4f9ae7f0851cf96fd39d757a7ffee276843894697", "schema_version": 1, "timestamp": "2026-10-05T05:25:22.736998+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_bde7430c2e25", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:927514bf2c22c63c1ba643aabd27dcc42bb5c380eeacf0ba3dbcbacc8e9c25e1", "schema_version": 1, "timestamp": "2026-10-05T05:25:22.748338+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_23caf9dacc88", "event_type": "storage.degraded", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:dc4a0ef045512b494194a21ba9206ec66e21ae710f7c2caa29f194c48fb142c0", "schema_version": 1, "timestamp": "2026-10-05T05:26:05.058788+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_845ca4f6f24f", "event_type": "capability.failed", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:5905ca1fb3b9d207b290447e7844c186b1a34e36d7a7e4102159cee3f8ae136b", "schema_version": 1, "timestamp": "2026-10-05T05:26:05.075917+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_51070f52b8a6", "event_type": "storage.degraded", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:dc4a0ef045512b494194a21ba9206ec66e21ae710f7c2caa29f194c48fb142c0", "schema_version": 1, "timestamp": "2026-10-05T05:26:05.094522+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_fd2082fc8c0f", "event_type": "storage.degraded", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:600ba21f0472de9fb57133929648cd8121d1717595e7cd27c381d71ac03852c8", "schema_version": 1, "timestamp": "2026-10-05T05:26:05.109473+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_185b4b2d6154", "event_type": "capability.failed", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:8a8675818b5d411241e416ff9e8c9a1845d9b2a8befeacc51e10ab977731aeba", "schema_version": 1, "timestamp": "2026-10-05T05:26:05.121919+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_408e3b8cfe78", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:84a918f8e91c2dc10b4755511a3082452a8fedf1e447fffafee15fce8fc1a93b", "schema_version": 1, "timestamp": "2026-10-05T05:27:51.531186+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_9435749ca47f", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:6e89b2470934ea8c42e1a7499eb6d0d5294da7433e396be707ab799980f95368", "schema_version": 1, "timestamp": "2026-10-05T05:27:51.818842+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_2f016168152a", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:d788663a9e9c9a89dea8f732a20888687d27957a80c381d14c08065a053e0ef8", "schema_version": 1, "timestamp": "2026-10-05T05:27:52.051261+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_61c614715445", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:9bb7d151c830f42fa63a11f8bc4ea925dcf868689dee13c3cd29bfa223a4bbd5", "schema_version": 1, "timestamp": "2026-10-05T05:27:52.266625+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_b6208cccd918", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:203a108f9dcdce6ca650375e2b244805852570cd8c7ceefa8449c3c690642fd1", "schema_version": 1, "timestamp": "2026-10-05T05:27:52.528285+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_c5200840c4a6", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:36cea66931af503b14f27b105f830fc170dfeafbb29a6128af4f130d544effbf", "schema_version": 1, "timestamp": "2026-10-05T05:27:52.796399+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_3e527fffd6fe", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:0d12f3da21598ab74316e8b65936d6e56d90403d3eff70238e10d3bdecb73838", "schema_version": 1, "timestamp": "2026-10-05T05:27:52.863927+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_6291e3591991", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:d0a86ab3af6a721a4050075172b898ffd2e2a5d6561bbb8bff1d89ba18dd458a", "schema_version": 1, "timestamp": "2026-10-05T05:27:52.876986+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_653474ae5a38", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:fe262eaff1d3744c73f76f110492a9ec7a1ad179f6f1e19fca66311669a15ed4", "schema_version": 1, "timestamp": "2026-10-05T05:27:52.888746+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_28eedd2abbae", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:fa2cb5946ba2f778d509d0f368c4613304ba54c82fc4889eb3799001237dbc7e", "schema_version": 1, "timestamp": "2026-10-05T05:27:52.901213+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_37da570bc113", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:0e8c4e296896af8faaa1432c38fa72bfcefa226ccb35e1998414d7803ae65168", "schema_version": 1, "timestamp": "2026-10-05T05:27:52.913445+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_a7829f63eb5b", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:640393fd25398d4e0a111c578197687bb017f6f084014a0ae4181dbccdbe7c70", "schema_version": 1, "timestamp": "2026-10-05T05:27:52.925756+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_41d478b7d782", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:025c1d4fbe7722fc4aad0bd7e9382712889a806f84394ee66a092077e2bbfef8", "schema_version": 1, "timestamp": "2026-10-05T05:27:52.939111+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_b4015935cdbb", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:565898daea9457ece7ae44df52fd5027b0b7cc909cb60342fdfb5b5e6f366a12", "schema_version": 1, "timestamp": "2026-10-05T05:27:52.953016+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_6421e7749b93", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:818a29ba114e0730207cf1c5f768d327edc5bf290e4f56d9c2367e385ca48aa9", "schema_version": 1, "timestamp": "2026-10-05T05:27:52.965166+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_08983922cc57", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:a442fbd2f9a0f05832aa41d04d01b8f1c766b471089f912e8d272c7f0407e8e5", "schema_version": 1, "timestamp": "2026-10-05T05:27:52.976327+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_125847fe8b69", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:b1deef4708eb9aa1af381749bbea11a422c64897ab5d4b578cc8af433d831d73", "schema_version": 1, "timestamp": "2026-10-05T05:27:56.131473+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_3ea4fee2207a", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:2ffe96d1999af5fb0698c2d0e62c17852fa8107980b05eb0b73540364d0dea99", "schema_version": 1, "timestamp": "2026-10-05T05:27:59.070955+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_e989056d43fe", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:2e30696c1651d69e185f25e8db4ce01d47c6787fe50ef32c2683ac39371986aa", "schema_version": 1, "timestamp": "2026-10-05T05:27:59.085972+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_d5f1bee048cd", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:c332225c74917e14f18e7fdda828bfa2c5d8a332514e580df135b1480bce738b", "schema_version": 1, "timestamp": "2026-10-05T05:27:59.220918+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_f56911180a9b", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:ceca0a4364b9ee3f2419bcd9ac18b40ab9e07d1cdb03d909f2f5b7efb51cee7e", "schema_version": 1, "timestamp": "2026-10-05T05:27:59.229532+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_b2824390c450", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:9819b775ac6aa5f32de4878ad79f0e98dca6493fefcc5ae8df8e83c2dfcec6cd", "schema_version": 1, "timestamp": "2026-10-05T05:27:59.495278+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_e23f1441be5c", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:c5159c0a313804bc805c68de7eca26b39fe3392aef5d41fa66e5923f0c0ea597", "schema_version": 1, "timestamp": "2026-10-05T05:27:59.508673+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_73e2d31b03bf", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:93f915bdae3b5313f277e830bfd0fb2e0d1b02aab3c44b20a510a9469f46bdb6", "schema_version": 1, "timestamp": "2026-10-05T05:27:59.520027+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_8d8bdeed008e", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:9bc8685d2399e0460be64e145836d78c206a9712e6f117330a4b6463b8ced171", "schema_version": 1, "timestamp": "2026-10-05T05:28:08.329960+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_f6908d666ad4", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:78c32e964baacf6a73151ebad029892e09ae53ad4710890c9d0193ee71978ee3", "schema_version": 1, "timestamp": "2026-10-05T05:28:08.353126+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_7e6c6b73d1c6", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:d65b046a0f7bbc04806ed066f5e935e2b9a520ba33e552e89e65d53d30ae09ec", "schema_version": 1, "timestamp": "2026-10-05T05:28:08.375244+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_94194cef5eb2", "event_type": "capability.failed", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:11a7f153199214499dfd4fc9a0b08d1f2f531ce3485b907cfe3a036cf7edc586", "schema_version": 1, "timestamp": "2026-10-05T05:28:08.416223+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_a6ccc11f9f42", "event_type": "capability.failed", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:d5c968d2dbed3174298be875d798719fcdc31e803c8b5a6306d782b4a43afc6f", "schema_version": 1, "timestamp": "2026-10-05T05:28:08.485358+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_bcf71e09c3e5", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:6bf69da741e431dd7f5de2630995da45ddd2c6224207891b5216e012e6998b64", "schema_version": 1, "timestamp": "2026-10-05T05:28:11.599749+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_75027bb0ef3d", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:9f9011ff2c006bb7507fe67cbd61e641b0a2f80b91c67d31aed389e0fbf09e00", "schema_version": 1, "timestamp": "2026-10-05T05:28:11.621348+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_8208b0e35fa5", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:0181775b8514a812d37751511072f301a31643c5a5be26e2512547c240a0790a", "schema_version": 1, "timestamp": "2026-10-05T05:28:11.628860+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_2f47d2443960", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:5eb815e5ec80814ddca17127677556f15798c7ce94ce94cb4eed530311e7d7ff", "schema_version": 1, "timestamp": "2026-10-05T05:28:11.640465+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_0f6bd7361917", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:b12fbd56aec61b17630f0dbc7e488dc88e6999f57ec51ef467d7029d93f604a4", "schema_version": 1, "timestamp": "2026-10-05T05:28:11.647963+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_a7531bc14b51", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:ad5ffe6d99ba7eb8e220803d051c140524085622902a5940b2391a19c7414b92", "schema_version": 1, "timestamp": "2026-10-05T05:28:11.658146+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_c978e0bfd637", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:5600a9577bb076c0ff0b9d656462b09831e21a8f4d02bc50e99659fd17e2ba44", "schema_version": 1, "timestamp": "2026-10-05T05:28:11.665658+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_c39a1f0f397b", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:4be05784a86523109449a6b2769bc348428beb7602b5d2c35098c1035b2fca36", "schema_version": 1, "timestamp": "2026-10-05T05:28:11.822047+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_0b4a21b38eb0", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:4f7119e456d1e5aea07adf44fdab0b90e0f68e2c48c23c61c10844afcf5bf98f", "schema_version": 1, "timestamp": "2026-10-05T05:28:11.830449+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_5add87c241be", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:6cf3772ab4f5ec3b4a9abab8380d02cbe3f24ca0609e68b07639fe96e98e8e43", "schema_version": 1, "timestamp": "2026-10-05T05:28:11.842537+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_70de0f155f49", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:6a48c8b921a89cf1b8c2e28696267123b85b04317af349213e654640aa42a5e9", "schema_version": 1, "timestamp": "2026-10-05T05:28:11.861340+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_4025fac6f1e0", "event_type": "capability.failed", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:1163d0fcebec85439e62d33ef5e1f3de09c6e1a75e16c7ecdb1a887be368d19d", "schema_version": 1, "timestamp": "2026-10-05T05:28:11.874488+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_b873e7b563cd", "event_type": "storage.degraded", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:dc4a0ef045512b494194a21ba9206ec66e21ae710f7c2caa29f194c48fb142c0", "schema_version": 1, "timestamp": "2026-10-05T05:28:11.886610+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_3daf0f2d44b6", "event_type": "capability.failed", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:1163d0fcebec85439e62d33ef5e1f3de09c6e1a75e16c7ecdb1a887be368d19d", "schema_version": 1, "timestamp": "2026-10-05T05:28:11.984176+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_e4c5ebf30fca", "event_type": "storage.degraded", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:dc4a0ef045512b494194a21ba9206ec66e21ae710f7c2caa29f194c48fb142c0", "schema_version": 1, "timestamp": "2026-10-05T05:28:11.998542+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_539377a95676", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:95b1442f065a87dda2cfabd70a8effcccdd85a7d5bb083dd29aa627386c62db2", "schema_version": 1, "timestamp": "2026-10-05T05:28:12.007236+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_4b782fd4e3eb", "event_type": "capability.failed", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:1163d0fcebec85439e62d33ef5e1f3de09c6e1a75e16c7ecdb1a887be368d19d", "schema_version": 1, "timestamp": "2026-10-05T05:28:12.078949+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_ff20a4ad072d", "event_type": "storage.degraded", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:dc4a0ef045512b494194a21ba9206ec66e21ae710f7c2caa29f194c48fb142c0", "schema_version": 1, "timestamp": "2026-10-05T05:28:12.091816+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_4f937d75332a", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:2c26d0134b9ac0d13d5787d3417d511b684f73324de1e0f23f0da45491db1e40", "schema_version": 1, "timestamp": "2026-10-05T05:28:12.100678+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_7e5a45264260", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:9dea7628059a7e5e2b7c2494b865238f95084208b1a7763e395444e7aa64e796", "schema_version": 1, "timestamp": "2026-10-05T05:29:55.509557+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_874accd00f3d", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:34dbc39a404b72b14e6d9c0045b7fc23daa35ab97667f01402cf2416d662836b", "schema_version": 1, "timestamp": "2026-10-05T05:29:55.529787+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_277237fb122a", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:1a7a83b9e1f78b9dc88e4ad1f3b272d639288eb70dad9e2bc5ae720d70411c5c", "schema_version": 1, "timestamp": "2026-10-05T05:29:55.542625+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_cba8e215b145", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:ea509c55bb8a2adf416e25fdad70e6567c536399b8141f90aa26b033c33e3309", "schema_version": 1, "timestamp": "2026-10-05T05:29:55.555819+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_a55a21dd7235", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:b628de52c8bba806aa6264c4d42a19dc3e65c46bbdca05436ee8a808b32701e8", "schema_version": 1, "timestamp": "2026-10-05T05:29:55.571734+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_3c3e0839e638", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:113d8d7baadb6dbc00443b1ee200b3e11dae0d751d4483a28d85318050111293", "schema_version": 1, "timestamp": "2026-10-05T05:29:58.721804+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_4933d7245822", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:ad3f3a76048316c3c20579bea2166845c15950e5d5f3de57d7dbbd8ebd9edc3f", "schema_version": 1, "timestamp": "2026-10-05T05:29:58.734844+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_e0138977793c", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:a90c96a2d9366bc6951ec3b992a61f3beaefeaaa886c122c743ab6acd74ed408", "schema_version": 1, "timestamp": "2026-10-05T05:30:00.494219+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_fc56f6741b93", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:802bb686f4b5a1ad8c8ef9568bf02ca217feaacafe8702ac45c0b57040412e56", "schema_version": 1, "timestamp": "2026-10-05T05:30:00.508976+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_1d8fe2b1fd81", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:56ca27fed0caa063afda5e6c21d190385719834588b340415d4c3a01c2eb038a", "schema_version": 1, "timestamp": "2026-10-05T05:30:00.628493+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_6f1b9fd10a21", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:39002b10daacf00c62e6c79aef7909eb166730847fa08d042f41457ec1d60da0", "schema_version": 1, "timestamp": "2026-10-05T05:30:00.637183+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_e4c9c7ffa6c0", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:13f7ef2fd1220ab45bcd59b5bb088b3d3e1bf0877b521f7596ce7f0c7afcc0c6", "schema_version": 1, "timestamp": "2026-10-05T05:30:00.698934+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_8a645ac69fea", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:7fa2bbcfca701f753edb2e4f4d881825925b4e663886b736a0a744dd8c004590", "schema_version": 1, "timestamp": "2026-10-05T05:30:00.707238+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_bc09da2f7370", "event_type": "storage.degraded", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:dc4a0ef045512b494194a21ba9206ec66e21ae710f7c2caa29f194c48fb142c0", "schema_version": 1, "timestamp": "2026-10-05T05:30:24.356970+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_e32fe810ee7c", "event_type": "capability.failed", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:5905ca1fb3b9d207b290447e7844c186b1a34e36d7a7e4102159cee3f8ae136b", "schema_version": 1, "timestamp": "2026-10-05T05:30:24.374002+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_ba52c8226135", "event_type": "storage.degraded", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:dc4a0ef045512b494194a21ba9206ec66e21ae710f7c2caa29f194c48fb142c0", "schema_version": 1, "timestamp": "2026-10-05T05:30:24.392343+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_92ea4ceb0d28", "event_type": "storage.degraded", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:600ba21f0472de9fb57133929648cd8121d1717595e7cd27c381d71ac03852c8", "schema_version": 1, "timestamp": "2026-10-05T05:30:24.407304+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_7cff4933612d", "event_type": "capability.failed", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:8a8675818b5d411241e416ff9e8c9a1845d9b2a8befeacc51e10ab977731aeba", "schema_version": 1, "timestamp": "2026-10-05T05:30:24.418294+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_8aac99f05776", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:212d17607942f17ba29c9f9d9bc9cf5d37d67f93386de5ee5b9d9e4e79acbeb4", "schema_version": 1, "timestamp": "2026-10-05T05:32:10.735398+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_32ef3f39c9cf", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:4367751c718d5feef38fb559b14cb544bcf0f55dd0a6b7631f9902099930fd1d", "schema_version": 1, "timestamp": "2026-10-05T05:32:11.023659+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_f92b9a8bde55", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:e94ba1ba17669ef2379c863670295ffe1647c63ebb00b29aba7383c26fd5f9df", "schema_version": 1, "timestamp": "2026-10-05T05:32:11.251196+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_48a0a4b749cc", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:bbde1c4f32585bab26f8f274abef3abfd0789b0b89be1cb58ec82e5629dad7dd", "schema_version": 1, "timestamp": "2026-10-05T05:32:11.466600+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_61b2f9392328", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:a9b1f87f4671a779561551ba84803bb39b41412476daf7883f5f7ee6e7439fdb", "schema_version": 1, "timestamp": "2026-10-05T05:32:11.727830+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_c176c699cc61", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:efa754113d15c83e90d8b9f0f4318947009d48c921dbc1c288c031cd3f901173", "schema_version": 1, "timestamp": "2026-10-05T05:32:11.990992+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_7e31c7c108d1", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:b1e9a428a0af40b1b8f6d99b075d98886cd352c9913ccd9e6d104f924108fe68", "schema_version": 1, "timestamp": "2026-10-05T05:32:12.053590+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_9ed7e24448aa", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:51070dd900366646171319b023082ec0e40a5b90e69a87bc4db311edb25fd393", "schema_version": 1, "timestamp": "2026-10-05T05:32:12.066468+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_497f97d21aaf", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:39f543b29c4eebcdaef669a7511da196fcd0f271305a447c7e680af7365fd915", "schema_version": 1, "timestamp": "2026-10-05T05:32:12.078586+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_de0ec7cbcc04", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:4658efead9526c67f82b01aca2914354d1854b0a987dc104b21caf8e148702b1", "schema_version": 1, "timestamp": "2026-10-05T05:32:12.089830+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_3db0955932aa", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:50330e358d50974da891edc86c0914c13b5682bd046ebd4b13e20fe5150557a9", "schema_version": 1, "timestamp": "2026-10-05T05:32:12.101835+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_3171bf0dc94f", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:dd11850ceee05c042162c7613f621e44bccd74b1a00454891910b4c873648a37", "schema_version": 1, "timestamp": "2026-10-05T05:32:12.114009+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_ddef6b58dbdf", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:4af9d21435106567bec27acf9c9675800ae79b1e35fd4267855170b07e96a83c", "schema_version": 1, "timestamp": "2026-10-05T05:32:12.127026+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_39638ea4797d", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:0a37b21dcdf495db8b49a7f4d6fa93f3427faaa8b66455bd8db0c2220d68aff9", "schema_version": 1, "timestamp": "2026-10-05T05:32:12.138781+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_03d0a3262591", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:88929433d0eb43ffb4783fe141d41fa16d7d73ea8c3bc6f7b5dd375c18fa04ab", "schema_version": 1, "timestamp": "2026-10-05T05:32:12.149909+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_668453e96811", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:615e3765dc0689bcbdab8898862382f60d8fe88ed94e05c98ac3178378967a73", "schema_version": 1, "timestamp": "2026-10-05T05:32:12.160223+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_2fdb4ba329d7", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:961e5b27d08ff355f182a438afa3576c6e8b276bdafabbad9d568f7fa3b3b292", "schema_version": 1, "timestamp": "2026-10-05T05:32:15.742945+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_dd72011e587f", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:ac29a1b2e20d1288f6e5d34618970e54cdbbabbf65f8d977dd47bd3f95f5f25f", "schema_version": 1, "timestamp": "2026-10-05T05:32:18.672702+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_5055a5efc8d4", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:193c45356bb4efc1e1bbcb1678feb70f1340d9b72b9c58ec19d5a4e1077293a7", "schema_version": 1, "timestamp": "2026-10-05T05:32:18.687521+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_f5061bab2701", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:d80bf3e826e76b337c46839ce7998919f518a031d323ec0f748af4d5c15cf7b4", "schema_version": 1, "timestamp": "2026-10-05T05:32:18.803632+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_6c03c0fff614", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:88b441f40713e3d94d5e9a6def7a5e98292ede6d3261b97cb3e156e3d7a2478b", "schema_version": 1, "timestamp": "2026-10-05T05:32:18.812694+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_4382a4801bb6", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:60a1560123af24fb64633ed54db9be99215bb3ccf068a0f00b82615c6de495b3", "schema_version": 1, "timestamp": "2026-10-05T05:32:19.077518+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_b7e31919f41f", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:f8678821cf3dd1511274f75c7eaa05d76c987596e59f14c39d243102b10d6c83", "schema_version": 1, "timestamp": "2026-10-05T05:32:19.092019+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_5a4b57cca41d", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:63c47ff5ed33e524064b29769a7c8fe49e265ece8adfeb3e6f9aa0669035f879", "schema_version": 1, "timestamp": "2026-10-05T05:32:19.102213+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_4324375af2fb", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:3d0345f44345bb52d7a616c834c85f6c9d9f5300027ed1d36961a1ad98de2973", "schema_version": 1, "timestamp": "2026-10-05T05:32:27.729235+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_7d4161720461", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:688e0db718e6f500194d3df3a930bc8061f81c4c04bb13e7d43281f8e3ab2b66", "schema_version": 1, "timestamp": "2026-10-05T05:32:27.752108+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_b75d089537a8", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:e111e67ac4c9f9b0c8686bd29d6c931fdab5914adc263e0f125ebe00eebd9cd7", "schema_version": 1, "timestamp": "2026-10-05T05:32:27.772302+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_be0914011cce", "event_type": "capability.failed", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:11a7f153199214499dfd4fc9a0b08d1f2f531ce3485b907cfe3a036cf7edc586", "schema_version": 1, "timestamp": "2026-10-05T05:32:27.810939+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_081a064f7d69", "event_type": "capability.failed", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:d5c968d2dbed3174298be875d798719fcdc31e803c8b5a6306d782b4a43afc6f", "schema_version": 1, "timestamp": "2026-10-05T05:32:27.873295+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_897ee9583487", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:6bf69da741e431dd7f5de2630995da45ddd2c6224207891b5216e012e6998b64", "schema_version": 1, "timestamp": "2026-10-05T05:32:31.023568+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_b1397f503580", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:75b37792f400ec70c671de1fb27693d614e9c5ba77288fd82f87257db93d6b8b", "schema_version": 1, "timestamp": "2026-10-05T05:32:31.045987+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_f36ff6d7e755", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:b3b0f6669e253ce09237ae45d741e784e37b56c20e3efd6e095fe989107c0809", "schema_version": 1, "timestamp": "2026-10-05T05:32:31.053770+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_abde6de9bbab", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:725fa51d74580066281cd5d9c5e16b2dbb6dace7b9a4c2b344959647b37d6d86", "schema_version": 1, "timestamp": "2026-10-05T05:32:31.066121+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_ed3069467d1f", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:1ca67b9b3840714b448db4e74946bb21f854202201adc4e398a8b11d58bfec4a", "schema_version": 1, "timestamp": "2026-10-05T05:32:31.073818+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_b55456aada25", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:8975932008450e38b49ff7cdc75636e12b266866b2538d60ab5625eab40c9b47", "schema_version": 1, "timestamp": "2026-10-05T05:32:31.084206+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_dfa183677d58", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:54ce15eed524b886bd2787af14a35e463010ccb4bdafec9a2b3da9bd2d83e860", "schema_version": 1, "timestamp": "2026-10-05T05:32:31.092031+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_09e10a729ab5", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:b0024bf50af59f5baae93d5e63fb452ef75128faad67d00b57a86c418c532eab", "schema_version": 1, "timestamp": "2026-10-05T05:32:31.250783+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_fb7e16b2754b", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:789310a900e34f34e18b58d3fe470d9b064548c1676c9dc332b92c0dc07904aa", "schema_version": 1, "timestamp": "2026-10-05T05:32:31.259992+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_f43d6eeb71b3", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:3bd47e0ecc07aa84a34dc7eaca2448650a1d51f5c2a891248bff24e3739a1483", "schema_version": 1, "timestamp": "2026-10-05T05:32:31.273201+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_b7a05e59f4f8", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:a2d91502fb3eb4f201823443b5de6a9a296f791b6f3d0620044eaf9062a65783", "schema_version": 1, "timestamp": "2026-10-05T05:32:31.295255+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_2eb13b60fd74", "event_type": "capability.failed", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:1163d0fcebec85439e62d33ef5e1f3de09c6e1a75e16c7ecdb1a887be368d19d", "schema_version": 1, "timestamp": "2026-10-05T05:32:31.308851+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_b1a3e11d8d5b", "event_type": "storage.degraded", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:dc4a0ef045512b494194a21ba9206ec66e21ae710f7c2caa29f194c48fb142c0", "schema_version": 1, "timestamp": "2026-10-05T05:32:31.324788+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_1a1fedf70564", "event_type": "capability.failed", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:1163d0fcebec85439e62d33ef5e1f3de09c6e1a75e16c7ecdb1a887be368d19d", "schema_version": 1, "timestamp": "2026-10-05T05:32:31.416777+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_cdb6a49e28e5", "event_type": "storage.degraded", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:dc4a0ef045512b494194a21ba9206ec66e21ae710f7c2caa29f194c48fb142c0", "schema_version": 1, "timestamp": "2026-10-05T05:32:31.430919+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_cecb09f369e4", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:533946b8405da4368789644e2e85df16bea9023c8e79c6e2e96dee8883e17f67", "schema_version": 1, "timestamp": "2026-10-05T05:32:31.440919+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_0903ca4adacf", "event_type": "capability.failed", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:1163d0fcebec85439e62d33ef5e1f3de09c6e1a75e16c7ecdb1a887be368d19d", "schema_version": 1, "timestamp": "2026-10-05T05:32:31.505857+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_7c3ce5921891", "event_type": "storage.degraded", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:dc4a0ef045512b494194a21ba9206ec66e21ae710f7c2caa29f194c48fb142c0", "schema_version": 1, "timestamp": "2026-10-05T05:32:31.518092+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_4238b1795f43", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:7bd43c48929090b1ac26823d3c5f579353c199d6229188f14c38ce93d34d29f9", "schema_version": 1, "timestamp": "2026-10-05T05:32:31.526993+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_43587350f3f4", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:71896095ebf7f71be94cdb069b034c6f68ca6ede9b82d2e8b50de3c955cbd419", "schema_version": 1, "timestamp": "2026-10-05T05:34:14.711009+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_43f44f24fe13", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:1db3165da25422b99c3337567ae88138223a1383d0824a6dda2fe61b091ee6e2", "schema_version": 1, "timestamp": "2026-10-05T05:34:14.729598+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_0cfdce66a3e3", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:015d4218faa5914504ee3fa032a79c182aa95fde887b9d8801c5363e39362ab8", "schema_version": 1, "timestamp": "2026-10-05T05:34:14.740188+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_85ebd1258ff1", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:ad46fa8aa08518716e3d7e502f87ca5a28bee0757bd753261b637e49a60953ca", "schema_version": 1, "timestamp": "2026-10-05T05:34:14.751344+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_f2bb24df2916", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:09a9df2e7850c64a1effcb172ec3c8ae3df42d5cf190a144a3c85ff254d62e09", "schema_version": 1, "timestamp": "2026-10-05T05:34:14.765763+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_0ab073d1e179", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:12d45dd1374f6973719c4d7f0e3701a2c08163e82596f90cb0eb0359f3ed82d4", "schema_version": 1, "timestamp": "2026-10-05T05:34:16.986105+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_8506701fb589", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:85adf7ad469119210ac1c00a15a9a44bac8870ca3a953771748575b7deb2ea80", "schema_version": 1, "timestamp": "2026-10-05T05:34:16.997758+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_bfad360f1ac3", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:2ecfe3143938314e2547ceab5797d3a92e40d5bc63104238591f1ac7e40d5722", "schema_version": 1, "timestamp": "2026-10-05T05:34:18.689748+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_fea3f40ed64a", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:ee1f25067da4f7d7a16793a487a82b28e5b00934d6b5405c44cf270cb8ce9205", "schema_version": 1, "timestamp": "2026-10-05T05:34:18.707650+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_92378415102a", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:3dfe0184e2ffc36e89400eebb0a16eecf1df4ba17ecf3168145ca3dd2bde1478", "schema_version": 1, "timestamp": "2026-10-05T05:34:18.817581+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_2196bb7a5eed", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:b9eaa1794e82d24a5cc3f486871d74dde7cef301135ba579a2bb6306c3664904", "schema_version": 1, "timestamp": "2026-10-05T05:34:18.826342+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_1e28686d6212", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:da581993cb7d5b13d2cc0c8fb5638e4008e714b88910560628bd03a7fe114f03", "schema_version": 1, "timestamp": "2026-10-05T05:34:18.886502+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_a78b2b45db1d", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:3ddc3201d2a544f70259a3cd8dffcdac49efa289a31a2531bc011b3f3b0060e6", "schema_version": 1, "timestamp": "2026-10-05T05:34:18.894367+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_7ec6b1f9e4af", "event_type": "storage.degraded", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:dc4a0ef045512b494194a21ba9206ec66e21ae710f7c2caa29f194c48fb142c0", "schema_version": 1, "timestamp": "2026-10-05T05:34:42.503144+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_6ad73605aab7", "event_type": "capability.failed", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:5905ca1fb3b9d207b290447e7844c186b1a34e36d7a7e4102159cee3f8ae136b", "schema_version": 1, "timestamp": "2026-10-05T05:34:42.519529+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_10d3c8c5fdab", "event_type": "storage.degraded", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:dc4a0ef045512b494194a21ba9206ec66e21ae710f7c2caa29f194c48fb142c0", "schema_version": 1, "timestamp": "2026-10-05T05:34:42.535483+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_c76904ae0e04", "event_type": "storage.degraded", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:600ba21f0472de9fb57133929648cd8121d1717595e7cd27c381d71ac03852c8", "schema_version": 1, "timestamp": "2026-10-05T05:34:42.549651+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_76001f7526fa", "event_type": "capability.failed", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:8a8675818b5d411241e416ff9e8c9a1845d9b2a8befeacc51e10ab977731aeba", "schema_version": 1, "timestamp": "2026-10-05T05:34:42.559475+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_5fff77055197", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:7ac55112036f1406c7735f2e111cbf6206e739a83705abdcc05130e5e7f6b973", "schema_version": 1, "timestamp": "2026-10-05T05:36:28.572925+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_d5d63c2b4483", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:635799e277a34930633ce00f47f6bc93e39aa57484c45a6a1fc6264f5192e874", "schema_version": 1, "timestamp": "2026-10-05T05:36:28.842810+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_d1f83b915d86", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:baeac84a339c7ff1455a78bb860de29ddeda0155dac8c91e85468c6ad58106f4", "schema_version": 1, "timestamp": "2026-10-05T05:36:29.058262+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_6464d3c50dfc", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:63b894ac3cad3484b95f01f927357329e3fbd2df6ebdb37217827763985f96e5", "schema_version": 1, "timestamp": "2026-10-05T05:36:29.258839+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_ee8708ba0780", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:95a096539637349b1754528c822116a2bbf09b6e85df83a7181bfa0b6a2f3931", "schema_version": 1, "timestamp": "2026-10-05T05:36:29.502558+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_5a7636065682", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:272b95b3e8645980af844c2028bcbd50fae16cb8f1d78c3c19d57248a57af461", "schema_version": 1, "timestamp": "2026-10-05T05:36:29.748219+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_64976fb1851b", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:bf325354c7d07d4c4ad5a408e76ff55d83400cbfaf19f4ec00173422e0d8983e", "schema_version": 1, "timestamp": "2026-10-05T05:36:29.809885+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_48f21af6b779", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:3d12fb8c86fbb4efdb0ef7f6b608f3ed6fea84a8650386abcc41ea1a59880226", "schema_version": 1, "timestamp": "2026-10-05T05:36:29.821228+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_622bf806031d", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:5c34b4f5d0bf12000255cef431fb79bec2ee89a2ff432a6ad70406038ca5d01f", "schema_version": 1, "timestamp": "2026-10-05T05:36:29.831955+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_89f419bab511", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:1294483e5b6220c7090f9e4e44e9744a3b4ae7e4eb255de5bc24d8833378bb67", "schema_version": 1, "timestamp": "2026-10-05T05:36:29.842691+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_b088836711b8", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:60e2abaf508eb3e6488851044a5db422cf2d43a486c6edbba4b778e9b27bfe74", "schema_version": 1, "timestamp": "2026-10-05T05:36:29.853702+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_af69b181cd27", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:149509d97283e6717614db93daf6f25f2eef9e4c65601f26d69c62841f9bf509", "schema_version": 1, "timestamp": "2026-10-05T05:36:29.864484+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_92ff2859f11d", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:a3d216fec0c35ca5de2792ad55705506b2c4a790566f7821f898e5db37dcd511", "schema_version": 1, "timestamp": "2026-10-05T05:36:29.876232+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_f208e23547ab", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:5534f53025673952a38abafcba1a53811a1ed1c1fe1fdc1be2c943208ee62870", "schema_version": 1, "timestamp": "2026-10-05T05:36:29.886997+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_59b606f790a1", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:1f722e514428e15f93d5350cc2759ac6c8f6cb67320b3014672848e0ab0e7eb6", "schema_version": 1, "timestamp": "2026-10-05T05:36:29.898336+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_b3e908b9b8df", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:086fd5cc9b046be26354b539d35174bcce53c843f82d929ac98abaaf7ac499ad", "schema_version": 1, "timestamp": "2026-10-05T05:36:29.908868+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_4abb26b61660", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:c7a22e5a661480b81453eb12a1296cb1af4d86d948887a9b33b8e43fb2b05581", "schema_version": 1, "timestamp": "2026-10-05T05:36:33.804475+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_fc05cbafca99", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:644d95718b81d6a1c068625a56354f7ce695e4f076a2c9d1d3408cbb45bff72a", "schema_version": 1, "timestamp": "2026-10-05T05:36:36.706654+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_ff18afb585af", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:c15cd557d92635664a3493a7fc7e3fd9d16339d2f0562981ff341577ea4ee883", "schema_version": 1, "timestamp": "2026-10-05T05:36:36.721525+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_f8becb75e15c", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:3770d522f8a1d9ec0881ac5571c35c43b5ddb2963693fc8eae063e2bb71208b3", "schema_version": 1, "timestamp": "2026-10-05T05:36:36.837539+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_c0ac8ddb44c3", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:009e3ae5133a6d1a71fe0fa2769f9b7ae18578b3d08099d78241d403f2b11603", "schema_version": 1, "timestamp": "2026-10-05T05:36:36.846574+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_5e291a148e97", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:e2c3e19284e518a1cfc7f38847cabd6252691028687a623ba892095a1a7d4eef", "schema_version": 1, "timestamp": "2026-10-05T05:36:37.096606+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_e188724befcd", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:c5c37130f04617f7d4974ee40cb759ac02589daab1dbbe78c7e77dc6c75231f9", "schema_version": 1, "timestamp": "2026-10-05T05:36:37.108895+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_a32a820cbe36", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:da4fd17cfcede765d22b744f4c1a6998de260a6f12d3af9f14194b6092b936e1", "schema_version": 1, "timestamp": "2026-10-05T05:36:37.118581+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_a2d77d321a7f", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:744405806a505497e6318bc3071b3caa3cc4b854aad81fc1f392718ac37813c0", "schema_version": 1, "timestamp": "2026-10-05T05:36:46.383606+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_1562b6da5aad", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:5d5b47c38c8689d56acfe7bbd8efff77369d736b869761ac4c5fcf987736e3b0", "schema_version": 1, "timestamp": "2026-10-05T05:36:46.406949+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_29f4d6e3b7f4", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:7c7ea7f162c6aafee91f16e3c27d2018bebc2cdc96365f11518cd3f4a7ca710b", "schema_version": 1, "timestamp": "2026-10-05T05:36:46.428540+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_8101d54cf4bc", "event_type": "capability.failed", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:11a7f153199214499dfd4fc9a0b08d1f2f531ce3485b907cfe3a036cf7edc586", "schema_version": 1, "timestamp": "2026-10-05T05:36:46.467135+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_9c84c8c3b947", "event_type": "capability.failed", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:d5c968d2dbed3174298be875d798719fcdc31e803c8b5a6306d782b4a43afc6f", "schema_version": 1, "timestamp": "2026-10-05T05:36:46.529402+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_b96b4514f591", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:6bf69da741e431dd7f5de2630995da45ddd2c6224207891b5216e012e6998b64", "schema_version": 1, "timestamp": "2026-10-05T05:36:49.662215+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_8dc1a6a3c32a", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:816db414e044ff88c1dbcb2a8de7915f5a0896ffff123cfd7d40514500be8dc4", "schema_version": 1, "timestamp": "2026-10-05T05:36:49.684928+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_6104baafbf36", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:d19650cb022291bbfbd72e69ff047265f24802fcbb49591b32e00091b9786a87", "schema_version": 1, "timestamp": "2026-10-05T05:36:49.693029+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_7970e7bcfe59", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:d21502f4e18c552acb0df33acde2fcb31fb13132e330066ad3fa8c7ca76cc460", "schema_version": 1, "timestamp": "2026-10-05T05:36:49.705536+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_b3827519a7a8", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:be1370c1bc215e37b54839a805e120e232ce9c006bcf92b886e95ee4b1f50cd7", "schema_version": 1, "timestamp": "2026-10-05T05:36:49.713134+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_22c677bf37e8", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:597f388d12f319142904c586e6fe52ca89103507046e544c09e4c22fafb0eff5", "schema_version": 1, "timestamp": "2026-10-05T05:36:49.722937+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_0255df35ee54", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:92fc08439a50f8b30f3031914c1c679fbbf6b1286aa57d3c275e7bc7fcf9ec6b", "schema_version": 1, "timestamp": "2026-10-05T05:36:49.730451+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_428234477249", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:33bcd7a4bab1aaaca61f0b04a1ccb2d8738f69cdc02811796693a74bfd427b70", "schema_version": 1, "timestamp": "2026-10-05T05:36:49.894059+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_e33c45069fc1", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:806c6e13038e6bec7d2cb5629500f4e20641913d271cd41b4f43220e38e7947c", "schema_version": 1, "timestamp": "2026-10-05T05:36:49.902495+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_2bb2e855acb2", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:920783237c5da06c006a0fad524ced79ffd630a1588f4dfa842617bc4f31a8d4", "schema_version": 1, "timestamp": "2026-10-05T05:36:49.915409+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_e1e457811917", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:d8952a2002bf9e1ce5a58d88a1c5960d83d446671a7c08eb18169a678fec203e", "schema_version": 1, "timestamp": "2026-10-05T05:36:49.935917+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_600a07fe3b81", "event_type": "capability.failed", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:1163d0fcebec85439e62d33ef5e1f3de09c6e1a75e16c7ecdb1a887be368d19d", "schema_version": 1, "timestamp": "2026-10-05T05:36:49.950326+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_ed0bdeffab01", "event_type": "storage.degraded", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:dc4a0ef045512b494194a21ba9206ec66e21ae710f7c2caa29f194c48fb142c0", "schema_version": 1, "timestamp": "2026-10-05T05:36:49.966938+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_0a0c4a914df9", "event_type": "capability.failed", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:1163d0fcebec85439e62d33ef5e1f3de09c6e1a75e16c7ecdb1a887be368d19d", "schema_version": 1, "timestamp": "2026-10-05T05:36:50.061831+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_5b9d961577a7", "event_type": "storage.degraded", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:dc4a0ef045512b494194a21ba9206ec66e21ae710f7c2caa29f194c48fb142c0", "schema_version": 1, "timestamp": "2026-10-05T05:36:50.075143+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_774ed53da96a", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:d634b3c56c2a754cfcad6ce6ebde0962f59ef8da63d8bd3fae76f563c3e679a7", "schema_version": 1, "timestamp": "2026-10-05T05:36:50.083527+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_8f1971814595", "event_type": "capability.failed", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:1163d0fcebec85439e62d33ef5e1f3de09c6e1a75e16c7ecdb1a887be368d19d", "schema_version": 1, "timestamp": "2026-10-05T05:36:50.147575+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_c47c4e1a8eaf", "event_type": "storage.degraded", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:dc4a0ef045512b494194a21ba9206ec66e21ae710f7c2caa29f194c48fb142c0", "schema_version": 1, "timestamp": "2026-10-05T05:36:50.159367+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_2b1a067e9bb2", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:53b9a2cb183e07b7a99451739b68e1bc8737884a3eb76ade3c808b4ded168d5a", "schema_version": 1, "timestamp": "2026-10-05T05:36:50.167368+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_90d11eeb10ff", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:98b1e1fe6b660f86412dfb433421bba18ef3e4cc64666170fa747dc51dfef456", "schema_version": 1, "timestamp": "2026-10-05T05:38:33.445774+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_67268fc0d9c1", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:9778a18c9f67e3ec65992c8071ba4bd657d748acc6cc74533615c0fab363eddc", "schema_version": 1, "timestamp": "2026-10-05T05:38:33.465888+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_3e3860a63113", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:3ae362a255148cb037b10542e1eb3d731e2ddbdfdf91462106ec1619949244ec", "schema_version": 1, "timestamp": "2026-10-05T05:38:33.478388+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_7faffe7a5a8b", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:9c5be5bbbc518628a84b0e88b3e48f368ac12577177279c500c587c2dff746e1", "schema_version": 1, "timestamp": "2026-10-05T05:38:33.491615+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_095da8cac7ea", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:7952e2373f4564092d77605de6c19442e33fd0daeaa368c5da9d35789fe9500d", "schema_version": 1, "timestamp": "2026-10-05T05:38:33.507000+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_3cb464de6c72", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:1fe0073aa75962977a1ea943fc39bde174cfc29e69bde57452d8c0682d85b5e6", "schema_version": 1, "timestamp": "2026-10-05T05:38:35.795329+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_aad93165eb31", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:0bd022b5e7bca573a4b89de5ef3f212e33fff86e3b481cf1cd486d0a62f484b2", "schema_version": 1, "timestamp": "2026-10-05T05:38:35.807662+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_62a3dd268832", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:bbe96e04f0ed2016da5bc6067fe80621c38c859ee1b085938b75da3353f084f5", "schema_version": 1, "timestamp": "2026-10-05T05:38:37.584445+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_59fd1106595a", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:7d12d665db123a8e962043cd008a5c4f07794d3486549f9b31711c730172a777", "schema_version": 1, "timestamp": "2026-10-05T05:38:37.599765+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_070e296856e8", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:91346f085ea517e5e5b7b7a95cef12a0b8222464c65fe670a9f3227af1be21d4", "schema_version": 1, "timestamp": "2026-10-05T05:38:37.713585+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_65f122803784", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:9e2e96593595566534fdea2dcb7d33074d9ea3160bbb5cc46deb8a864d952c5b", "schema_version": 1, "timestamp": "2026-10-05T05:38:37.722647+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_aa65fbf90501", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:473743a4ef9c274ee281418fdd41f79a7bf1dc61762804464041b0c9d54ebe07", "schema_version": 1, "timestamp": "2026-10-05T05:38:37.784521+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_bc458e29ab0e", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:ac9039e9c68fe381d478bc3187c2e4d051806a2530a903514a58f33cf67aec67", "schema_version": 1, "timestamp": "2026-10-05T05:38:37.793480+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_86a65591d13c", "event_type": "storage.degraded", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:dc4a0ef045512b494194a21ba9206ec66e21ae710f7c2caa29f194c48fb142c0", "schema_version": 1, "timestamp": "2026-10-05T05:42:44.823632+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_d4a4414d0155", "event_type": "capability.failed", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:5905ca1fb3b9d207b290447e7844c186b1a34e36d7a7e4102159cee3f8ae136b", "schema_version": 1, "timestamp": "2026-10-05T05:42:44.835236+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_e56c93fedf69", "event_type": "storage.degraded", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:dc4a0ef045512b494194a21ba9206ec66e21ae710f7c2caa29f194c48fb142c0", "schema_version": 1, "timestamp": "2026-10-05T05:42:44.854320+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_95931996110a", "event_type": "storage.degraded", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:600ba21f0472de9fb57133929648cd8121d1717595e7cd27c381d71ac03852c8", "schema_version": 1, "timestamp": "2026-10-05T05:42:44.869024+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_0cb1c983a23b", "event_type": "capability.failed", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:8a8675818b5d411241e416ff9e8c9a1845d9b2a8befeacc51e10ab977731aeba", "schema_version": 1, "timestamp": "2026-10-05T05:42:44.878867+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_166e07517dac", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:33e12566fcc99b849794ab4b5a1a15da98bc96e3744ec2ec1764a17268607d80", "schema_version": 1, "timestamp": "2026-10-05T05:44:31.430956+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_da7865fe3786", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:bebb6b0e1af4788a9f490dd6bfa18c89661f60df990ad1edb9174c0e17c17ff2", "schema_version": 1, "timestamp": "2026-10-05T05:44:31.704577+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_b3c787b8da9d", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:2df89b3f06ed8f3c015931e0b4f2bd99e117e1cbea28f21d90e4e12e1d3b078e", "schema_version": 1, "timestamp": "2026-10-05T05:44:31.940026+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_c7bb3f5a59ed", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:bdf7e2d63c8a34b20e2e8cc5f0a84a011a07e06155ae859057eea528167a03b7", "schema_version": 1, "timestamp": "2026-10-05T05:44:32.154802+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_e0b795194203", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:54a3267426314e299cf906551b830b1f41cc875ba80f08c93fc4adccb84daafa", "schema_version": 1, "timestamp": "2026-10-05T05:44:32.417666+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_476f45fdd7ba", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:8e819140472bab13608d0ab9b5f956fbda53968a80ea011ccd6f6e2943159f0e", "schema_version": 1, "timestamp": "2026-10-05T05:44:32.681037+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_1acde88146f2", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:670e75a95e872e5ce3fa3a90e2d95790249fa737b52ecbc66d4690c4c1fdd67e", "schema_version": 1, "timestamp": "2026-10-05T05:44:32.749960+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_97c4e18b2d93", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:63d8ea490804b0ad180645ec31ce94267477af333cf233de8bc057a9a5edf154", "schema_version": 1, "timestamp": "2026-10-05T05:44:32.762348+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_0bdd2a3b5a35", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:f20f27c26bda798b98120148a9c8560be06d469c5e3b691ef862a57bb4301dd1", "schema_version": 1, "timestamp": "2026-10-05T05:44:32.774305+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_f462ba72d08d", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:db952fc653b144870f19cc12e16261c01662d4ba8291bd9400cf47e11dd04b0c", "schema_version": 1, "timestamp": "2026-10-05T05:44:32.785426+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_188013743147", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:7d807705d56183f752fa38caca85496108c21f34f276d9d72416fd2a541c3996", "schema_version": 1, "timestamp": "2026-10-05T05:44:32.797019+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_87bf9a3e7f20", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:83cd02284dd33685a0c85c9f19e10d31c11d56d2fb971cd4600d93bfb88f90fa", "schema_version": 1, "timestamp": "2026-10-05T05:44:32.808544+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_16204db29b9e", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:a615539d019bbb7d2768f6a0579d785faea5deb0ea9592916aaf3679d6b7ed96", "schema_version": 1, "timestamp": "2026-10-05T05:44:32.823256+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_00772f43e9ae", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:40779e64cc9bc845f231e5442520294f75c4e6443584e2b9673398457a356c4c", "schema_version": 1, "timestamp": "2026-10-05T05:44:32.834651+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_5ead779f82cf", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:b7dd0cf3ec8cb4bf892e0413c4f8794c1353ba77c1cb2c38da2d4a35d2c09c69", "schema_version": 1, "timestamp": "2026-10-05T05:44:32.845491+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_2a4e01c7f481", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:a446c5ffa0d921dc1dd5f8710a68c1a2744d14dfe4e02e0d9330f53cba709888", "schema_version": 1, "timestamp": "2026-10-05T05:44:32.855867+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_771b4bfd85ae", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:81440c6af5281d42bc03418a0eac2b52d628308238c49399d3f4a1693ae076fe", "schema_version": 1, "timestamp": "2026-10-05T05:44:39.492429+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_80c1ee255737", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:c646063eae47121ad7ec5211c9b09435d000d375077f45d0c1eb4a03bec1d00c", "schema_version": 1, "timestamp": "2026-10-05T05:44:42.449164+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_c454377eae02", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:9e3d3fb1663ebf50209012b8135120dabd1b5adfe5a4d5a13e16cfcf58860bf0", "schema_version": 1, "timestamp": "2026-10-05T05:44:42.464333+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_6b9ddf245574", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:6934488d5d2eb0437ffa177e523ea02ddb356ae1c59942f267eb3068563ba55b", "schema_version": 1, "timestamp": "2026-10-05T05:44:42.597891+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_25b3b2544e7c", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:3e5c7cb9591f03996a905c766ef60075159019d7285ffb0ad790d8d208c7acad", "schema_version": 1, "timestamp": "2026-10-05T05:44:42.607386+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_504528cf12be", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:68369939eb32683671f66183cc8853f748931834ecfb8beb462e0e8fc9bf25c7", "schema_version": 1, "timestamp": "2026-10-05T05:44:42.868608+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_1a6c4a7dca12", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:8f38ba034a9241ec5f3aa426262aab6db6781d034fa2885673429b3107b8cb37", "schema_version": 1, "timestamp": "2026-10-05T05:44:42.880198+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_be30cd830765", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:879929d8682a13d2ffe93935a7064c1a06fcd4bc3f9aeb687ac80ad42b627496", "schema_version": 1, "timestamp": "2026-10-05T05:44:42.890044+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_4eb03f942728", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:977a76cc9165925647d184553fed7d27ca1c47887c5095e4a7dbc28209eff9b6", "schema_version": 1, "timestamp": "2026-10-05T05:45:00.429016+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_deed6a4991ba", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:ce2bfe70056f98070bad99f1567239a6264e61c24adeecb980fcab7deed076de", "schema_version": 1, "timestamp": "2026-10-05T05:45:00.451606+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_b9a9d752569b", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:3a8e35f1c0e024d97abc363b1d300a73b03bc5e07a654cafd4afcbd2a2e043ed", "schema_version": 1, "timestamp": "2026-10-05T05:45:00.472542+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_5be6156f8e8f", "event_type": "capability.failed", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:11a7f153199214499dfd4fc9a0b08d1f2f531ce3485b907cfe3a036cf7edc586", "schema_version": 1, "timestamp": "2026-10-05T05:45:00.513260+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_55cd751c3454", "event_type": "capability.failed", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:d5c968d2dbed3174298be875d798719fcdc31e803c8b5a6306d782b4a43afc6f", "schema_version": 1, "timestamp": "2026-10-05T05:45:00.578950+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_fb6c15646a6b", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:6bf69da741e431dd7f5de2630995da45ddd2c6224207891b5216e012e6998b64", "schema_version": 1, "timestamp": "2026-10-05T05:45:04.026203+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_3f5a8f90a41c", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:ac69806d912eb3a9b320fd8c8f6d9124fd7fced98d3bc46b4df5f568d5c31108", "schema_version": 1, "timestamp": "2026-10-05T05:45:04.042663+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_2174cfbbe69e", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:75a370143979b0fccb1fedaeb1e2ed8206399b49d48ae3f0877263b0dab83a23", "schema_version": 1, "timestamp": "2026-10-05T05:45:04.051660+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_20cda71d281a", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:cef87be02a86380fd1a051c327cd6a145099292137b936d87ecd65045ec5af1f", "schema_version": 1, "timestamp": "2026-10-05T05:45:04.066684+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_464b6c752888", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:db037c3f763141827d4d8c9b0ee20641284d0a666bc9ed1edf56958b57062011", "schema_version": 1, "timestamp": "2026-10-05T05:45:04.075671+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_bb46cddce63b", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:60a50030bdd269d099e22eb663cb1903cc155a7d8b44f017489fce0a79134351", "schema_version": 1, "timestamp": "2026-10-05T05:45:04.087436+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_18b9e55889e5", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:7672d9151744b1c821d01213889873aa3bdf91136931e5b6569449ce57f781c4", "schema_version": 1, "timestamp": "2026-10-05T05:45:04.097051+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_00987f249538", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:1408f4d3c02333c4afad0794c0acc1909c0eead9e7a7f7535769cb9d25330b40", "schema_version": 1, "timestamp": "2026-10-05T05:45:04.274204+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_2cc49d0017b7", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:79d9edfe5812158fa5ccc9d64956d87ce42f9bec34c0a406ed81d77b1591efe3", "schema_version": 1, "timestamp": "2026-10-05T05:45:04.284161+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_8a3b8cf8bb14", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:17cc5136d9bc9a5c1eb4a701929b44c506724ea45a189c8e18f488faeca373bc", "schema_version": 1, "timestamp": "2026-10-05T05:45:04.299977+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_510ef9a92f49", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:62208f1fdad2bf0c255cafc4eeb05665dfce21b9ddfe5a51eb0d93b99eccc1af", "schema_version": 1, "timestamp": "2026-10-05T05:45:04.319722+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_af8b0c870781", "event_type": "capability.failed", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:1163d0fcebec85439e62d33ef5e1f3de09c6e1a75e16c7ecdb1a887be368d19d", "schema_version": 1, "timestamp": "2026-10-05T05:45:04.332925+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_14db91071765", "event_type": "storage.degraded", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:dc4a0ef045512b494194a21ba9206ec66e21ae710f7c2caa29f194c48fb142c0", "schema_version": 1, "timestamp": "2026-10-05T05:45:04.346088+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_ba76269cc515", "event_type": "capability.failed", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:1163d0fcebec85439e62d33ef5e1f3de09c6e1a75e16c7ecdb1a887be368d19d", "schema_version": 1, "timestamp": "2026-10-05T05:45:04.454417+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_82d9ec4a22fb", "event_type": "storage.degraded", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:dc4a0ef045512b494194a21ba9206ec66e21ae710f7c2caa29f194c48fb142c0", "schema_version": 1, "timestamp": "2026-10-05T05:45:04.469651+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_79f262190092", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:a5d99484006ea434d81830d84831d7c16fac1447f56f45f7d3d9edf9a7276098", "schema_version": 1, "timestamp": "2026-10-05T05:45:04.479105+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_5cf1f3250908", "event_type": "capability.failed", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:1163d0fcebec85439e62d33ef5e1f3de09c6e1a75e16c7ecdb1a887be368d19d", "schema_version": 1, "timestamp": "2026-10-05T05:45:04.559262+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_855812c23cc9", "event_type": "storage.degraded", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:dc4a0ef045512b494194a21ba9206ec66e21ae710f7c2caa29f194c48fb142c0", "schema_version": 1, "timestamp": "2026-10-05T05:45:04.574403+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_784b0897cee7", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:4e038baa85e8c7614cecb11abedb755abd1d7750d090f196f0a1dfe51945b3c0", "schema_version": 1, "timestamp": "2026-10-05T05:45:04.584189+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_5dfcdda0076d", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:ed5b59221dcbdda04d40f1388f885f5d74635646b0338cf4b1dd9419d5dfad53", "schema_version": 1, "timestamp": "2026-10-05T05:46:48.024867+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_27bff8aa4a67", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:10ec5dcc77a880f9a5d5c0d512bfb87ff7a7fed4e17366cda24402b9a4e360d3", "schema_version": 1, "timestamp": "2026-10-05T05:46:48.039242+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_f52cd7d380b4", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:4ea200291a31ae31e28299c5f74965fb274baa8e203ea5d097ba85c404493544", "schema_version": 1, "timestamp": "2026-10-05T05:46:48.052314+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_9219dda77d9d", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:b6b7f2375ec62e49788aadd23fedfe33e086aa90004dbcedb5d7c8b54fd33099", "schema_version": 1, "timestamp": "2026-10-05T05:46:48.065181+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_3d4288eb266d", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:6c78ce332472854639277b6ab8f616a795a6cd32ac294f34664a173067fd206d", "schema_version": 1, "timestamp": "2026-10-05T05:46:48.080369+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_aa3cbe49f920", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:27d33fb0a4fc82924ded62d227a5ee833b17a07318f13286ffde9c16f0e75dcd", "schema_version": 1, "timestamp": "2026-10-05T05:46:50.886509+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_4a9b36a3a31e", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:e2bc10744ab9b2e2052e8fc8c5a7a5610e4d019d09ae867db8374eea19c187ca", "schema_version": 1, "timestamp": "2026-10-05T05:46:50.901612+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_a24806a7223b", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:a548d0c18e1531f8ae4e9b64fc92112e3cc3ee304bc62656e85f59db65cc7587", "schema_version": 1, "timestamp": "2026-10-05T05:46:52.705101+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_2f587e51ad36", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:f8ee37c7bcefb7c8efda6eef02582a96209283c8ef5543d4360353c37fc06881", "schema_version": 1, "timestamp": "2026-10-05T05:46:52.720208+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_f614f8afb5dc", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:369d49705e0cb1fb730096ab32de74671f17c22db9ae1976d943fbb6fd11103d", "schema_version": 1, "timestamp": "2026-10-05T05:46:52.837816+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_eb78de821a48", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:8ad92fa610c3b29207ee69a19a9518a24b1bda406dadbfd643c9c4c33119aa0f", "schema_version": 1, "timestamp": "2026-10-05T05:46:52.847320+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_b63c11b0e85f", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:5ee130d09ce403ab7ad4afcfab5e1ef2791fa637d4271e41b5771f094e837389", "schema_version": 1, "timestamp": "2026-10-05T05:46:52.910886+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_a171c6c09af8", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:9e6dcb2453feaa565595cf31ae5cb484066d23115b4d3c6a0c4fdd0262fd23b8", "schema_version": 1, "timestamp": "2026-10-05T05:46:52.919949+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_efb440868a36", "event_type": "storage.degraded", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:dc4a0ef045512b494194a21ba9206ec66e21ae710f7c2caa29f194c48fb142c0", "schema_version": 1, "timestamp": "2026-10-05T05:48:35.308535+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_3b0786de27ae", "event_type": "capability.failed", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:5905ca1fb3b9d207b290447e7844c186b1a34e36d7a7e4102159cee3f8ae136b", "schema_version": 1, "timestamp": "2026-10-05T05:48:35.325629+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_a11066af0fc7", "event_type": "storage.degraded", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:dc4a0ef045512b494194a21ba9206ec66e21ae710f7c2caa29f194c48fb142c0", "schema_version": 1, "timestamp": "2026-10-05T05:48:35.366267+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_bbaca2f78b39", "event_type": "storage.degraded", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:600ba21f0472de9fb57133929648cd8121d1717595e7cd27c381d71ac03852c8", "schema_version": 1, "timestamp": "2026-10-05T05:48:35.388623+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_9a20a36a9f5b", "event_type": "capability.failed", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:8a8675818b5d411241e416ff9e8c9a1845d9b2a8befeacc51e10ab977731aeba", "schema_version": 1, "timestamp": "2026-10-05T05:48:35.403210+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_1e7669f2bad7", "event_type": "storage.degraded", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:dc4a0ef045512b494194a21ba9206ec66e21ae710f7c2caa29f194c48fb142c0", "schema_version": 1, "timestamp": "2026-10-05T06:30:47.078182+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_3bd981ded4c1", "event_type": "capability.failed", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:5905ca1fb3b9d207b290447e7844c186b1a34e36d7a7e4102159cee3f8ae136b", "schema_version": 1, "timestamp": "2026-10-05T06:30:47.100458+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_681838e3fcd9", "event_type": "storage.degraded", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:dc4a0ef045512b494194a21ba9206ec66e21ae710f7c2caa29f194c48fb142c0", "schema_version": 1, "timestamp": "2026-10-05T06:30:47.137264+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_da525a3c0fc2", "event_type": "storage.degraded", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:600ba21f0472de9fb57133929648cd8121d1717595e7cd27c381d71ac03852c8", "schema_version": 1, "timestamp": "2026-10-05T06:30:47.158469+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_c2feb08b8a36", "event_type": "capability.failed", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:8a8675818b5d411241e416ff9e8c9a1845d9b2a8befeacc51e10ab977731aeba", "schema_version": 1, "timestamp": "2026-10-05T06:30:47.173548+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_026a68360979", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:890f37bf8225147a6df15dd4d7d1d14b8a34f652457cee7eabb5e3d667e7d228", "schema_version": 1, "timestamp": "2026-10-05T06:32:35.779852+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_2a49b38f6181", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:6b38d9e665655ee59bdd175c8e260de8cbbe3f1e893beed1bbe6455415afc298", "schema_version": 1, "timestamp": "2026-10-05T06:32:36.653680+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_5d2eb36d5a2d", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:c939b140dbc98d89a70d545ebee22f4614b8a00dfe75ddac5e2ce882852f6eb0", "schema_version": 1, "timestamp": "2026-10-05T06:32:37.331588+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_1827453bd8cb", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:254b8faf2ee47df4669c2b291b122e35d03bc65a9071bf4a64d6e5448604e232", "schema_version": 1, "timestamp": "2026-10-05T06:32:37.991661+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_d9b9bd6e2e6b", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:eeb57cc89822598f55e680e53cb89da3e4bbad9ee60333ff09738791794e7a62", "schema_version": 1, "timestamp": "2026-10-05T06:32:38.795950+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_69e66a7a0210", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:268c016f21f859433fc2715c2cf88616d2a035902edc3281208599b3f27bb358", "schema_version": 1, "timestamp": "2026-10-05T06:32:39.641517+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_09c85ac1604a", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:0c26db1dfc9ccce93d27b697ca16381f4e8bfe65394614fe0f15cd6e33bd863b", "schema_version": 1, "timestamp": "2026-10-05T06:32:39.835431+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_a901d6aebf00", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:07df8e3a2a1aaf33db9b7281e29acf4375d6077cef4d87348fbe3141e624e8c8", "schema_version": 1, "timestamp": "2026-10-05T06:32:39.855492+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_c5c802b2af64", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:7156b07cac29fcd922a2f8a03edb64e809409e1c86493053f6a2281dc74fc96c", "schema_version": 1, "timestamp": "2026-10-05T06:32:39.871970+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_e587dc0f2eff", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:150637057f4ef28796229418e35e3e62ffed7be70b399c4e1b102c15efb3baa9", "schema_version": 1, "timestamp": "2026-10-05T06:32:39.889627+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_c6b33ff3cd66", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:2785fca0a2b93a259f81530e43e5e482fcc6146fb0e5919c3b948a15c316c9e8", "schema_version": 1, "timestamp": "2026-10-05T06:32:39.907130+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_d9e1a7f9e61c", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:bfed397f2e443f20fd6f9f7c88efc309655c6c01b5c1928e5d8da357731544cb", "schema_version": 1, "timestamp": "2026-10-05T06:32:39.922819+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_0f604f2e0173", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:9ef660d5dc89f4fb721db88b55bfcebc906cd26f7b29d492cd5816ab7427809a", "schema_version": 1, "timestamp": "2026-10-05T06:32:39.938886+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_a7a21aa5fdb8", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:bdf76219a0f1dad6d9883b32f014572fccc584535f4c350c442902a444800bde", "schema_version": 1, "timestamp": "2026-10-05T06:32:39.958883+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_5988197924ca", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:358ea0187ef5b34533c467e8894408ac5eb27ad7990fc854879cc62eff61fd7c", "schema_version": 1, "timestamp": "2026-10-05T06:32:39.976738+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_7277acc98310", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:bf3f151e86ed731297aa7d57315277cf1734f7a876e0255bc6db17f848731596", "schema_version": 1, "timestamp": "2026-10-05T06:32:39.992309+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_7c0ad3b6a366", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:520cc5250ce983f46fbf05ee636ea10f8e13056611f25a151ef535208cc08469", "schema_version": 1, "timestamp": "2026-10-05T06:32:43.167134+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_76140dfb8cb7", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:e493cc9b5ce492a629104ef291a1e26112b59aa86d506166737f606207daa4f1", "schema_version": 1, "timestamp": "2026-10-05T06:32:46.074794+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_9357b38c18c7", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:9284c060ed8738d35aee98a0f75df7b72673cc93d5b8ccf3ce70c409608bc93c", "schema_version": 1, "timestamp": "2026-10-05T06:32:46.092864+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_93336b3c0836", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:48001e7aff69dae16d512aa99fd2e5f03b8733d4b2227c5c9a73545429b9b385", "schema_version": 1, "timestamp": "2026-10-05T06:32:46.274021+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_7ca37a9b2aaa", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:433ca78ec83370b43a86ae38f7346bcf8e5bc0da1378bcd96122e77a61f60b85", "schema_version": 1, "timestamp": "2026-10-05T06:32:46.285731+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_ed68c9d461a2", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:d68f58dfbed4a8becb415c01a3f2170fc68418e973b6f60fc76b286050ed654e", "schema_version": 1, "timestamp": "2026-10-05T06:32:46.596825+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_d44e5ecef962", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:01c4fc9c7b68c9746303322750fcf1c5419c5562508b37391c28c118b471190f", "schema_version": 1, "timestamp": "2026-10-05T06:32:46.613587+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_49d9e8f55750", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:1b6540607c303b1c7963d743038eeb4e5200a36c5f66dd4f9e69f8c6a0665677", "schema_version": 1, "timestamp": "2026-10-05T06:32:46.627481+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_7965d3c2de84", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:368ad204ed6c06343e6038bd6d35b46cc207df61b7238f6b95a9ebe572763249", "schema_version": 1, "timestamp": "2026-10-05T06:32:56.022606+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_2a754a8eab42", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:9f687defe0c19263533825fa8536e5aaa81ce61940b2869d3ca9dbeda40b135a", "schema_version": 1, "timestamp": "2026-10-05T06:32:56.058422+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_76ef0082a146", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:efa9aff1d534f8bfc14c0b14fe3a58b8b55907b7d160389cbbc8afe5d52ca0b6", "schema_version": 1, "timestamp": "2026-10-05T06:32:56.109077+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_d52482f9f96d", "event_type": "capability.failed", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:11a7f153199214499dfd4fc9a0b08d1f2f531ce3485b907cfe3a036cf7edc586", "schema_version": 1, "timestamp": "2026-10-05T06:32:56.172385+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_0e151ea3d0a1", "event_type": "capability.failed", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:d5c968d2dbed3174298be875d798719fcdc31e803c8b5a6306d782b4a43afc6f", "schema_version": 1, "timestamp": "2026-10-05T06:32:56.300074+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_9a84aa36f25a", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:6bf69da741e431dd7f5de2630995da45ddd2c6224207891b5216e012e6998b64", "schema_version": 1, "timestamp": "2026-10-05T06:33:01.091372+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_478a2a6f3f94", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:8b3627a045b0b517e975b68a8da3b32891c2411ffe6ce9fbb97f72f7e2633658", "schema_version": 1, "timestamp": "2026-10-05T06:33:01.125442+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_6ef6062ffefe", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:19a99bda927f983c78b82bfd6053ee5bb0a63be42b73f92c7ade65a1821be017", "schema_version": 1, "timestamp": "2026-10-05T06:33:01.136175+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_aa5c04eee7d6", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:daa454fc92f336a672873a14420c875188613bc1623051a9a212025a5a2acd85", "schema_version": 1, "timestamp": "2026-10-05T06:33:01.154712+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_f5a558c9ce50", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:fc26c9be2a163a5b34c58c533980da9a4bdb75a51256d692a515761de8ca3518", "schema_version": 1, "timestamp": "2026-10-05T06:33:01.165583+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_00cfb7401d9b", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:9d4c534d642cd9fb949f17ffa7c4a4aa0aee203c1a2606d77bd7aa0738053a2f", "schema_version": 1, "timestamp": "2026-10-05T06:33:01.181226+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_f94b5072d0c5", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:baea61f9d3acd8b1fd054b359f3571e26fe44964c18104b77d8c88fb17eda86c", "schema_version": 1, "timestamp": "2026-10-05T06:33:01.192038+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_6cb5b3246109", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:cef698ec3d9e8581c719efa5217951e55db9f8ce0f2a607e5e761a7953a8f9b2", "schema_version": 1, "timestamp": "2026-10-05T06:33:01.322737+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_b5a664e74c85", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:d241bb90ce22d731c61d9ee65ea9957256fa753010243939aca3e3a45e5fb73e", "schema_version": 1, "timestamp": "2026-10-05T06:33:01.335063+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_fbb792e34239", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:f5a249e9ad34edccd6a61dc67eba4fcba2918df4b66325449db446debaa04c29", "schema_version": 1, "timestamp": "2026-10-05T06:33:01.355622+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_81a8a4a833c7", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:a46f73ab501bdc60cf4c543d56ba5e6abda7053043d697b6433a3ef04fdc9548", "schema_version": 1, "timestamp": "2026-10-05T06:33:01.391878+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_c3c5cd2993d9", "event_type": "capability.failed", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:1163d0fcebec85439e62d33ef5e1f3de09c6e1a75e16c7ecdb1a887be368d19d", "schema_version": 1, "timestamp": "2026-10-05T06:33:01.412904+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_085b1a727eaa", "event_type": "storage.degraded", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:dc4a0ef045512b494194a21ba9206ec66e21ae710f7c2caa29f194c48fb142c0", "schema_version": 1, "timestamp": "2026-10-05T06:33:01.444250+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_510f343b6ab6", "event_type": "capability.failed", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:1163d0fcebec85439e62d33ef5e1f3de09c6e1a75e16c7ecdb1a887be368d19d", "schema_version": 1, "timestamp": "2026-10-05T06:33:01.627098+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_ff38d1208cf0", "event_type": "storage.degraded", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:dc4a0ef045512b494194a21ba9206ec66e21ae710f7c2caa29f194c48fb142c0", "schema_version": 1, "timestamp": "2026-10-05T06:33:01.662021+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_8349c1af8de1", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:3e02f546d3eeae63e6dc1ba93d28159719d19ee54d29fc6dbbff4a554ba06488", "schema_version": 1, "timestamp": "2026-10-05T06:33:01.673829+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_cb647dafb557", "event_type": "capability.failed", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:1163d0fcebec85439e62d33ef5e1f3de09c6e1a75e16c7ecdb1a887be368d19d", "schema_version": 1, "timestamp": "2026-10-05T06:33:01.770606+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_385e99f0038b", "event_type": "storage.degraded", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:dc4a0ef045512b494194a21ba9206ec66e21ae710f7c2caa29f194c48fb142c0", "schema_version": 1, "timestamp": "2026-10-05T06:33:01.805270+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_043e7bac4c97", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:b3a8c41bdc31d460080143cde43622c1c98f6e7ae662e4b43080cf00bf63d566", "schema_version": 1, "timestamp": "2026-10-05T06:33:01.816613+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_aace2f37ca5f", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:6864addf067ce58d86ab40b1dcafbb24109fc29e7050d684538a31354ac59dcb", "schema_version": 1, "timestamp": "2026-10-05T06:34:47.569047+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_1144243c150d", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:e8e3defeea6670131bc8863101fa1923795b272166c64dff7a705a395f7b08da", "schema_version": 1, "timestamp": "2026-10-05T06:34:47.593005+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_bf9c63616ca5", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:961d3b2dfb0be05a7f3d8e115f4d78f3f9e83d200a6ab6235024fe21bab0fc92", "schema_version": 1, "timestamp": "2026-10-05T06:34:47.609147+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_309f95c6a4bb", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:06f3683c27f16f9bc173b6f58ee2264d0f3e7f1ee41947015da0c55f809f669c", "schema_version": 1, "timestamp": "2026-10-05T06:34:47.627150+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_2546ff004b57", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:71e916d7a5a3bf292035bca7d4fa9d19091367aede69a6c7e91e3e070a999621", "schema_version": 1, "timestamp": "2026-10-05T06:34:47.650566+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_6aa18b2a23aa", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:f5b7005cf009d0f0408ea60753fc35a916f0d695e6c565b46a3dce93934b8534", "schema_version": 1, "timestamp": "2026-10-05T06:34:49.915433+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_6b01de5f2b30", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:1c4ea8048081f1a8b9cdbcdc5abaa172d643a683474d807bf308ed59b9047525", "schema_version": 1, "timestamp": "2026-10-05T06:34:49.934060+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_437b579f435e", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:7f90bb33fc830d95c60abaf50f58fcf7a7863e1d3cddf10ca4e50654d0c14d91", "schema_version": 1, "timestamp": "2026-10-05T06:34:51.806895+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_dd53291a04b6", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:9302e5dcc51c93f0b323560e075bc81a89594b0ab0395d3ae855dc42ee7c63e9", "schema_version": 1, "timestamp": "2026-10-05T06:34:51.824797+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_6676059944cc", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:d73de3ab7d7f81b31ad83573cb7a7a1c7db9dd71a7813315b115a20b62f4459e", "schema_version": 1, "timestamp": "2026-10-05T06:34:51.954770+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_92ab14d24275", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:1a74ea6323daba4d2e13d7423ee3e5f7af0f167e38578db1d39997383533d561", "schema_version": 1, "timestamp": "2026-10-05T06:34:51.966761+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_9507f13a7d33", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:6a7f3696e047c7639f12f6ed33e30649c17b684a80652763a9f755310d0d57e7", "schema_version": 1, "timestamp": "2026-10-05T06:34:52.048084+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_6a60c2a0af6e", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:8ac6a632fafc0a61353325308ed38a8ea47dd5380913f6900956b6daa6090638", "schema_version": 1, "timestamp": "2026-10-05T06:34:52.058880+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_d2b51ae48dc7", "event_type": "storage.degraded", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:dc4a0ef045512b494194a21ba9206ec66e21ae710f7c2caa29f194c48fb142c0", "schema_version": 1, "timestamp": "2026-10-05T06:36:07.796886+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_fef86120ae96", "event_type": "capability.failed", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:5905ca1fb3b9d207b290447e7844c186b1a34e36d7a7e4102159cee3f8ae136b", "schema_version": 1, "timestamp": "2026-10-05T06:36:07.817924+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_74ad5982b3c8", "event_type": "storage.degraded", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:dc4a0ef045512b494194a21ba9206ec66e21ae710f7c2caa29f194c48fb142c0", "schema_version": 1, "timestamp": "2026-10-05T06:36:07.859340+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_949631915cda", "event_type": "storage.degraded", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:600ba21f0472de9fb57133929648cd8121d1717595e7cd27c381d71ac03852c8", "schema_version": 1, "timestamp": "2026-10-05T06:36:07.880771+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_21f10ab46e33", "event_type": "capability.failed", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:8a8675818b5d411241e416ff9e8c9a1845d9b2a8befeacc51e10ab977731aeba", "schema_version": 1, "timestamp": "2026-10-05T06:36:07.896081+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_7d9ffd247e6d", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:968a848f54f5c3671ffbcbda5bfc78c51a2177c59ecf4f307f8a9991fa78828c", "schema_version": 1, "timestamp": "2026-10-05T06:37:56.419498+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_f694ba86ab08", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:455e1a70a34942066856399e9148dc648a977af7e5d53ab4e73f8905497f4540", "schema_version": 1, "timestamp": "2026-10-05T06:37:57.303456+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_7ed308a97815", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:823e22f0b7eeafff706b77ca49e9d2dba3102bf63755e0b64a9ff49ffbab2794", "schema_version": 1, "timestamp": "2026-10-05T06:37:58.005817+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_2c7e3fa8fb26", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:0ebc068db0c8ff8bb928143444a811583b56152c7c0cbc3d67c23bd3c187f3d1", "schema_version": 1, "timestamp": "2026-10-05T06:37:58.653201+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_386905a511ec", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:90cc30b59e1526a0b2370f1a7870b805d784139cde3a131b1489c6217c938cf0", "schema_version": 1, "timestamp": "2026-10-05T06:37:59.469729+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_b7beb759dfa4", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:68bfc12190f39646a736c88face38af541cad5d30952f348be1466c32416e557", "schema_version": 1, "timestamp": "2026-10-05T06:38:00.254815+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_97c44a7789a0", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:61801f4c14d0302a99a63a2d8a62533b96c6a4e967dfb947d7d4ce3f6e0afb08", "schema_version": 1, "timestamp": "2026-10-05T06:38:00.444982+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_3d13d384dd3d", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:8b0771675ae6538804718f636a82eb78afcbfa27605e5c8df4b35f9a431c0878", "schema_version": 1, "timestamp": "2026-10-05T06:38:00.462542+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_446cd619ca60", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:e13026d656fef5ccdc193408342f3cb2f379a2b36951a3d711f3f3cbe39c6589", "schema_version": 1, "timestamp": "2026-10-05T06:38:00.478480+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_e9e8df4344ea", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:d6eb32686877061fdd596b5289b195b7ea087dfbd272f665318eb878025b8c73", "schema_version": 1, "timestamp": "2026-10-05T06:38:00.494659+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_d84bdb26b788", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:442ae35d562a1afd2cc854c7d4f77e844494930ad27b4104015db78eb789747f", "schema_version": 1, "timestamp": "2026-10-05T06:38:00.510574+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_93ed035a6522", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:8bdfeb1dbcf30bd1371f9fca87073b617d6da9bd66b1e5279e8c5c4b4f05cfbe", "schema_version": 1, "timestamp": "2026-10-05T06:38:00.527200+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_b65d0bd5f9e8", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:a01006abbf7ebfa3a91313daf9dd6547428d7fd3986d6905ff056fc04ac0f36f", "schema_version": 1, "timestamp": "2026-10-05T06:38:00.543656+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_8b87cc163c59", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:a0d50fa87bd699048f4465d2db94ff5e62d5a54b605232482766d9ffb736e0d8", "schema_version": 1, "timestamp": "2026-10-05T06:38:00.559437+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_76d27953ceb8", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:47921bd04e256ec81c36f7bb3378c2fcf7f243f7ac852b7b6c41b6f0e29bf599", "schema_version": 1, "timestamp": "2026-10-05T06:38:00.575689+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_25d022f4fe3e", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:9d76be0bef8e521a711d03af47e84443028ce5edfbf3211570c25ca7b786b43f", "schema_version": 1, "timestamp": "2026-10-05T06:38:00.590787+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_bc3b9807f3bf", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:3b380dfc8098a90acf10ced31c0393b1f78df3c4137dd49bdfb80185548a15bd", "schema_version": 1, "timestamp": "2026-10-05T06:38:08.680156+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_28a2b41ff52c", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:047285e91a7429dd3c9cf9e82bb86bac191b8e8bd4216769b61a3811da28b736", "schema_version": 1, "timestamp": "2026-10-05T06:38:11.642868+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_183842fd36db", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:4991c9cbc236491ce16a10e1aebe6f47d603b1b1e9268e9f99518365c78b8a7d", "schema_version": 1, "timestamp": "2026-10-05T06:38:11.660841+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_49851bd52edc", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:1740e7eb428ea7ab41a8e2b626d80ec0a2014c29c6198dd1f671de14c2ad39ab", "schema_version": 1, "timestamp": "2026-10-05T06:38:11.799804+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_850a3dc1c2de", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:7bcdf33fd6b7b97982dd16a63f439f29dbf2a24b784e727e77bbede25f7474a2", "schema_version": 1, "timestamp": "2026-10-05T06:38:11.810791+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_8bbe0710eb22", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:7c1d1791282f9c5168109e1381ac072d4585455d315912bb6255858be9466da9", "schema_version": 1, "timestamp": "2026-10-05T06:38:12.134189+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_a520c55fea52", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:49ede411ad05435c5cfd51d2a35d930388c34f999b31b4701ee434f48ee0ce07", "schema_version": 1, "timestamp": "2026-10-05T06:38:12.161315+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_ea6523fb36a1", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:9370ff07093a7c50b8dd8eb6fde6c7763eb5d1fc0e6d213da02c6dee29457d3e", "schema_version": 1, "timestamp": "2026-10-05T06:38:12.175162+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_f0e800ea94a8", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:edf27abbb010cf840fc409f722bc405692376bd64efad9a17bd6dbb20bff85a7", "schema_version": 1, "timestamp": "2026-10-05T06:38:21.174200+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_2503ec730320", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:7ad2b884ac08eca72b4a97600d7e947d7ce1729dc040690e010bd9ac57ba7221", "schema_version": 1, "timestamp": "2026-10-05T06:38:21.207816+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_1c6d4afcd96a", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:2cb0bf913b7c9cab6a3fcc3f04410387809c61e608acca29786140ae8a9415ef", "schema_version": 1, "timestamp": "2026-10-05T06:38:21.257312+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_76d0c12b668d", "event_type": "capability.failed", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:11a7f153199214499dfd4fc9a0b08d1f2f531ce3485b907cfe3a036cf7edc586", "schema_version": 1, "timestamp": "2026-10-05T06:38:21.318255+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_cc19a85c6ece", "event_type": "capability.failed", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:d5c968d2dbed3174298be875d798719fcdc31e803c8b5a6306d782b4a43afc6f", "schema_version": 1, "timestamp": "2026-10-05T06:38:21.430892+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_c9c2a7c8acad", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:6bf69da741e431dd7f5de2630995da45ddd2c6224207891b5216e012e6998b64", "schema_version": 1, "timestamp": "2026-10-05T06:38:25.287716+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_dcabe4f29a6c", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:9a47ec95b7682899edbab5b412f9a71269aafd5f1ac4411d7b3016f0265bef81", "schema_version": 1, "timestamp": "2026-10-05T06:38:25.317935+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_c045cd6c2024", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:d56676e362acc5639850a57f35829495dc6c851db3d12161ec99a5eb832d716a", "schema_version": 1, "timestamp": "2026-10-05T06:38:25.331437+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_b805ac6f2d92", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:e455cde244b6a07fa9b26a38321ffe46246134f4c06de892bb4a683f5cfb3597", "schema_version": 1, "timestamp": "2026-10-05T06:38:25.350396+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_052d0b45e6d6", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:4790c8a8109241feac7dc5fbc5737e57da3a4722b22d23348f06af108ccab11c", "schema_version": 1, "timestamp": "2026-10-05T06:38:25.361147+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_5ebffc5e1b05", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:ea754bc6aadfbcaf4c5753ecbf584e718fbc035282b614bb01896deefa01a380", "schema_version": 1, "timestamp": "2026-10-05T06:38:25.376212+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_574f699f8df4", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:89844af88766ad50f71eb54d7f1a245966bb2804bd97c10415eba0934718c016", "schema_version": 1, "timestamp": "2026-10-05T06:38:25.386874+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_b83811d28750", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:44f37fd54c84d094be680ab3c2fa7171fa500835e74d9c7a21ea656b05d66729", "schema_version": 1, "timestamp": "2026-10-05T06:38:25.508264+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_6cf85bf51ba6", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:b08e9a33d37bc9ef17274f5e0b49eccef31d0eedf6a2b65794e83a71181f266a", "schema_version": 1, "timestamp": "2026-10-05T06:38:25.520332+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_2da9aa4c9a60", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:df8757d20489d83861b62efea8fb6aedfc741b0eb45d69b7b1812b1846a9ed77", "schema_version": 1, "timestamp": "2026-10-05T06:38:25.543926+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_5a72855b5690", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:3064ce063515dec6d54b2705aee8b066d5cac7975d631119e5fb08e227ca3fd9", "schema_version": 1, "timestamp": "2026-10-05T06:38:25.581480+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_ac1304f762f6", "event_type": "capability.failed", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:1163d0fcebec85439e62d33ef5e1f3de09c6e1a75e16c7ecdb1a887be368d19d", "schema_version": 1, "timestamp": "2026-10-05T06:38:25.603341+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_d0705973a48c", "event_type": "storage.degraded", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:dc4a0ef045512b494194a21ba9206ec66e21ae710f7c2caa29f194c48fb142c0", "schema_version": 1, "timestamp": "2026-10-05T06:38:25.631739+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_91085685506f", "event_type": "capability.failed", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:1163d0fcebec85439e62d33ef5e1f3de09c6e1a75e16c7ecdb1a887be368d19d", "schema_version": 1, "timestamp": "2026-10-05T06:38:25.813178+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_cd044ec70691", "event_type": "storage.degraded", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:dc4a0ef045512b494194a21ba9206ec66e21ae710f7c2caa29f194c48fb142c0", "schema_version": 1, "timestamp": "2026-10-05T06:38:25.844678+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_aad8c93b22a0", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:ca1dadbc5acd179dbf0f0ebbfba2940ed7c219120fdfba9e00f7743cd6418ddf", "schema_version": 1, "timestamp": "2026-10-05T06:38:25.856333+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_adb456d34b5b", "event_type": "capability.failed", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:1163d0fcebec85439e62d33ef5e1f3de09c6e1a75e16c7ecdb1a887be368d19d", "schema_version": 1, "timestamp": "2026-10-05T06:38:25.956426+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_31e05c902516", "event_type": "storage.degraded", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:dc4a0ef045512b494194a21ba9206ec66e21ae710f7c2caa29f194c48fb142c0", "schema_version": 1, "timestamp": "2026-10-05T06:38:25.991480+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_2bf3e8e8dd37", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:0f94c237c37daf7128b0c8c36659f839a6aa582a11d6fb1f18c67bfcc14ee69c", "schema_version": 1, "timestamp": "2026-10-05T06:38:26.005115+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_b58958a57056", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:b5af1a049fc1a5337394e200aa67eb2b0648a7cb9ef54197bb52aaba04fdc32e", "schema_version": 1, "timestamp": "2026-10-05T06:40:11.672517+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_c13aef1c02f6", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:cd3b310a80e47e2b0fd63962ef421abd9b1f0421e980c32e09f9bfb84e514099", "schema_version": 1, "timestamp": "2026-10-05T06:40:11.696838+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_3321f8deec3e", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:b4312c49da8a313c7a51bb15fdbffecdd65bddef13f2af8c2382e047b474ca68", "schema_version": 1, "timestamp": "2026-10-05T06:40:11.713426+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_79c0164eb4c8", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:6967ee955bf704859daa6d53cfcd0702fa66610d02945cdbf6f9e30764f4ec78", "schema_version": 1, "timestamp": "2026-10-05T06:40:11.730583+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_f21627c7e1a5", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:9092b411b050189b053d27608a5b0cd81c5fd589a4bb91821de6d7e2bccd5361", "schema_version": 1, "timestamp": "2026-10-05T06:40:11.753618+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_4f2b01d6a30b", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:296602153c579e0cd9904d42d14c823e2428738077b8fa105e1ab7273d0ce215", "schema_version": 1, "timestamp": "2026-10-05T06:40:14.075803+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_f8df3ef9e8bc", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:3aa26eec69ddf1a1e675f9e022c747383454ced9fb52cb03d5f35404c3e47e05", "schema_version": 1, "timestamp": "2026-10-05T06:40:14.094387+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_d42bd9a2b2d2", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:25a0dd94a7aaba3a2a7b875f7301dd0a4fe5defbd346a3d1a3dc35e8ddc52a1f", "schema_version": 1, "timestamp": "2026-10-05T06:40:16.188557+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_eb2d07d3115c", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:b500649f883e7965ccc342453c8538b2b04b47b2f7c9bbdef29df9a661531f92", "schema_version": 1, "timestamp": "2026-10-05T06:40:16.207254+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_2a118b9119dc", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:2b97c4fa3630d6b2cfc94b9c9f6fe1895d610c84daf5fe64f1696ecaa9220adc", "schema_version": 1, "timestamp": "2026-10-05T06:40:16.354579+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_86ca4d7a77bb", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:19c52aceb064b4732cf53df6e74faab0ac7efe674e09cc00f4e82902d13bd194", "schema_version": 1, "timestamp": "2026-10-05T06:40:16.366816+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_109f31ac6841", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:cbc7f5d4c7424ed0e44f6ae0a21590ed36833e7b38e36cad27a8ebe6ae2b2f9b", "schema_version": 1, "timestamp": "2026-10-05T06:40:16.513616+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_18c66c43e3f6", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:e641ebfc5aacaa7a16978b37c1561d19df3888446d5b92e99965d8c4cec0f546", "schema_version": 1, "timestamp": "2026-10-05T06:40:16.525607+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_e1d27e5e4ece", "event_type": "storage.degraded", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:dc4a0ef045512b494194a21ba9206ec66e21ae710f7c2caa29f194c48fb142c0", "schema_version": 1, "timestamp": "2026-10-05T06:42:43.695303+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_3c2c996aab11", "event_type": "capability.failed", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:5905ca1fb3b9d207b290447e7844c186b1a34e36d7a7e4102159cee3f8ae136b", "schema_version": 1, "timestamp": "2026-10-05T06:42:43.715767+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_64a6bc1219e6", "event_type": "storage.degraded", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:dc4a0ef045512b494194a21ba9206ec66e21ae710f7c2caa29f194c48fb142c0", "schema_version": 1, "timestamp": "2026-10-05T06:42:43.751147+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_823e376a2ab8", "event_type": "storage.degraded", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:600ba21f0472de9fb57133929648cd8121d1717595e7cd27c381d71ac03852c8", "schema_version": 1, "timestamp": "2026-10-05T06:42:43.772059+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_992151ed6d5d", "event_type": "capability.failed", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:8a8675818b5d411241e416ff9e8c9a1845d9b2a8befeacc51e10ab977731aeba", "schema_version": 1, "timestamp": "2026-10-05T06:42:43.787261+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_40d37cfe232e", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:4194bf68326491d749572fee36638973aca742b62f96768430ad1e28ab24e954", "schema_version": 1, "timestamp": "2026-10-05T06:44:32.307566+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_60f5ca534243", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:c17bba207b98e22e3bc0902005dffb8904aacbb78e362e51b65a5eec96fd4bd5", "schema_version": 1, "timestamp": "2026-10-05T06:44:33.180051+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_03b28fdb5834", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:69c8d274fb424154a03ab309635d5585a2a4c2514e1776f91ad42573beacfa57", "schema_version": 1, "timestamp": "2026-10-05T06:44:33.889652+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_165758a5a86f", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:4464a2a7d7a0616e2c7cfdfa96efd8f5ecd82493049b983ea0da805235ec6cf4", "schema_version": 1, "timestamp": "2026-10-05T06:44:34.519879+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_3c96beeeb2e8", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:fa90557634208f7fbc555c570d68994465c4b1b300b114be2810808f2b9b77a6", "schema_version": 1, "timestamp": "2026-10-05T06:44:35.362910+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_dd459e85a36b", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:548fdc5109d4399f565aca03ceaceccfb938fddf8de7f73a581685172ab8ee8d", "schema_version": 1, "timestamp": "2026-10-05T06:44:36.168807+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_a44e2f05e40b", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:972ee75d40b40e68743270717324267be65fd28b40ea5930b840a08a07bbab0c", "schema_version": 1, "timestamp": "2026-10-05T06:44:36.365273+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_04270e9391bf", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:b0fa54f8be9efcfba04d1de2b4107a9c16264bf3d0aec3120d3d536205fc4291", "schema_version": 1, "timestamp": "2026-10-05T06:44:36.382108+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_5af20380f281", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:7abc73c396ab63dfa6fb773eaa3a61f8fe53409ca70c391e3e5c47af718158b5", "schema_version": 1, "timestamp": "2026-10-05T06:44:36.398266+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_56329fedff7b", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:a04e448f80df63b6fa2df6d7d433e5ecb09d0da8fa98236b68dd23ae2bf26c91", "schema_version": 1, "timestamp": "2026-10-05T06:44:36.414558+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_4f972f10b79f", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:32d34c5f9257f369e5c4f750c44adb531c54370063da4ab67b01e4a6fdb5299f", "schema_version": 1, "timestamp": "2026-10-05T06:44:36.430869+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_8ffb29c01222", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:d14edc631f972b3a0c04566dc0d6a173baf2ca3d7f24c0e53a57d74f191011fe", "schema_version": 1, "timestamp": "2026-10-05T06:44:36.446866+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_04d0781dcca1", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:75523bcd62656cc336c54ea8b23689b7bb80c96f9dd314f33912ead0e4b1284b", "schema_version": 1, "timestamp": "2026-10-05T06:44:36.464640+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_40fcc152c131", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:006a9aa13c86a45a6c312db740b877a0b41d8ea7ad93387cd6dbb46a9afc8cc9", "schema_version": 1, "timestamp": "2026-10-05T06:44:36.480833+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_2f52fc4a9676", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:410b715c571c5b4d0c59358b2838562c75a4cf18ddbf23c30c61b3061337d5ea", "schema_version": 1, "timestamp": "2026-10-05T06:44:36.497205+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_886ebc880de0", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:71325a628e220e35035545dbbec4e851728b7b75bf2e1405759ac7afe8678775", "schema_version": 1, "timestamp": "2026-10-05T06:44:36.512577+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_9da6b2641be0", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:d1f636e09ff2fe01e73d090dda9ecc1ef8a6a8a4e0b2785326e9ccd673489048", "schema_version": 1, "timestamp": "2026-10-05T06:44:39.781695+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_4fe18265b729", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:f2648039fb8595994fb846b6e34667f854d395621f2b80f4f9025bb52a2633d2", "schema_version": 1, "timestamp": "2026-10-05T06:44:42.753278+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_aea6a34012bb", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:52bc97dc6e8aa989f1ff0b2bb2d67559313b25e26b790f922dc96b9c824941b8", "schema_version": 1, "timestamp": "2026-10-05T06:44:42.770992+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_ae907eabf22c", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:8e5614f02085b3f74fe190a820c3415677628bbc7d9a48bcf16889307eec8646", "schema_version": 1, "timestamp": "2026-10-05T06:44:42.896921+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_89f42ccafce4", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:f25afa2500e6d5f330ddb68722bb0cea87b1eda48ad87fc18e96236f847c38ae", "schema_version": 1, "timestamp": "2026-10-05T06:44:42.908475+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_df0b2029eda0", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:c70b08e3925a62cfd2cedb602493e0fcce58048ca06d2f2d75fd11ba0ddbf705", "schema_version": 1, "timestamp": "2026-10-05T06:44:43.216944+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_7fcbdfc91c59", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:7a8e79b47b1d8985ecacea6b5a174df904765886dff5b02ddc63471d59ec056e", "schema_version": 1, "timestamp": "2026-10-05T06:44:43.241236+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_9baad33fd781", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:6db17481fa3ea741d28acd7d3961c23d13c9d565d43b7a65bc9a818574e471b7", "schema_version": 1, "timestamp": "2026-10-05T06:44:43.255526+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_695295605a5a", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:004d5250bc06f4613c8c306f3ce51e43ac96e27c6ed047f7ca64090634d46360", "schema_version": 1, "timestamp": "2026-10-05T06:44:52.627390+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_35587d7c59a4", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:d5fdffe94c20869b64221f2ffd06434010311f5a3d54d88d7f5213daf1035ac3", "schema_version": 1, "timestamp": "2026-10-05T06:44:52.662662+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_b7a9aad15546", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:2ffbcd3dcb4edecf2f9db58b667ddac40f619803cd43f31e7084e7106bf7de75", "schema_version": 1, "timestamp": "2026-10-05T06:44:52.711855+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_3214ada9057d", "event_type": "capability.failed", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:11a7f153199214499dfd4fc9a0b08d1f2f531ce3485b907cfe3a036cf7edc586", "schema_version": 1, "timestamp": "2026-10-05T06:44:52.773638+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_66a3f9fefc32", "event_type": "capability.failed", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:d5c968d2dbed3174298be875d798719fcdc31e803c8b5a6306d782b4a43afc6f", "schema_version": 1, "timestamp": "2026-10-05T06:44:52.895988+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_6ac943e896db", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:6bf69da741e431dd7f5de2630995da45ddd2c6224207891b5216e012e6998b64", "schema_version": 1, "timestamp": "2026-10-05T06:44:57.177919+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_9be57bd7c54c", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:3e3f6e9fefeedb136d333d8769fe46b221526b7edd4a3d2d7facbc136e8db796", "schema_version": 1, "timestamp": "2026-10-05T06:44:57.208657+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_31159365346d", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:0fe00b46a64b9c6642368dce1378ece9f5d6d6b14c797287708907d20b4b9e4b", "schema_version": 1, "timestamp": "2026-10-05T06:44:57.219345+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_d52222cd7d80", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:e602de80f93a023e2131aa9a445874a6dc48d78c6f50dd2b0d470ee45d5362aa", "schema_version": 1, "timestamp": "2026-10-05T06:44:57.238120+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_7e9f8e4e97c0", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:e54fa0268b176a1723b960d3c5ae25d30ff250cf50e21b8cb8a46a8ece8286ca", "schema_version": 1, "timestamp": "2026-10-05T06:44:57.248732+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_8a4ef30f822d", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:4d48b559fcd80dea394094bf5c0f4162e6eeac33df2a8d15203883c5bd7c6391", "schema_version": 1, "timestamp": "2026-10-05T06:44:57.263172+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_eb81b76b99c8", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:5a52c1f77872698dccc773d78b745242615e715b1f88c2589f7168562ba5ff0f", "schema_version": 1, "timestamp": "2026-10-05T06:44:57.273977+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_4c748f973d1b", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:fe8c879667b2f93ddb30f0fbf8f6a206996a8db1d436dc7960ab06f16ee1537b", "schema_version": 1, "timestamp": "2026-10-05T06:44:57.393687+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_c38eaa856d9c", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:bc88b9a72c2f2c0698966ab7e35587432b6cf5ae9251d6d3ca7c584a8fc34435", "schema_version": 1, "timestamp": "2026-10-05T06:44:57.405254+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_d37393d28a53", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:5ec4efaedf112205ad3a290f9ebf8bcf35a8e2045895e2b4ab918bf87d014345", "schema_version": 1, "timestamp": "2026-10-05T06:44:57.425322+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_ba6fe3f86984", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:2ddb84a16807ce9592ef237d27acb7dda3f7f735c41d0779cf78f8fbefa5affe", "schema_version": 1, "timestamp": "2026-10-05T06:44:57.459524+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_4129aafd1e86", "event_type": "capability.failed", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:1163d0fcebec85439e62d33ef5e1f3de09c6e1a75e16c7ecdb1a887be368d19d", "schema_version": 1, "timestamp": "2026-10-05T06:44:57.480675+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_476e56d323bd", "event_type": "storage.degraded", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:dc4a0ef045512b494194a21ba9206ec66e21ae710f7c2caa29f194c48fb142c0", "schema_version": 1, "timestamp": "2026-10-05T06:44:57.511156+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_877ab58875f8", "event_type": "capability.failed", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:1163d0fcebec85439e62d33ef5e1f3de09c6e1a75e16c7ecdb1a887be368d19d", "schema_version": 1, "timestamp": "2026-10-05T06:44:57.687441+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_f9518821261a", "event_type": "storage.degraded", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:dc4a0ef045512b494194a21ba9206ec66e21ae710f7c2caa29f194c48fb142c0", "schema_version": 1, "timestamp": "2026-10-05T06:44:57.720206+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_cd3f3b942867", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:df6c0ab5af248d8c80df6abfc54fbf9db8fa174a06de6c6902f685895d81ac19", "schema_version": 1, "timestamp": "2026-10-05T06:44:57.731921+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_3190e14af0b3", "event_type": "capability.failed", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:1163d0fcebec85439e62d33ef5e1f3de09c6e1a75e16c7ecdb1a887be368d19d", "schema_version": 1, "timestamp": "2026-10-05T06:44:57.816502+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_030fe6069de9", "event_type": "storage.degraded", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:dc4a0ef045512b494194a21ba9206ec66e21ae710f7c2caa29f194c48fb142c0", "schema_version": 1, "timestamp": "2026-10-05T06:44:57.845351+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_6b0909d24377", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:3cd9033094d9a4daddc4d8dd9c5c7560d112ee577fe02d4bc7cc470136b78cad", "schema_version": 1, "timestamp": "2026-10-05T06:44:57.859103+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_33428887b522", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:719daf9f5aaa2579ebaa44ee5c88b237d2892bbe4d9fd833cbe04b51b4271412", "schema_version": 1, "timestamp": "2026-10-05T06:46:43.504030+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_776d3cf5853f", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:d68a73462f237c9bfe29eb0012181be186100cf21061efcf616cb16d6ac93acc", "schema_version": 1, "timestamp": "2026-10-05T06:46:43.528608+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_14cda5807789", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:f0994a7430b1d9852b05b30eadf5eb975c642608d43f880e94de237c12332966", "schema_version": 1, "timestamp": "2026-10-05T06:46:43.545774+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_140965cba2e3", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:e3e4bd074e5858d86f51c855fda71c5c7ea8438e6b0ed100e8a346fdc38a3623", "schema_version": 1, "timestamp": "2026-10-05T06:46:43.563331+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_00c79b9d1a00", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:f9b0b3b4d221d2838dec9d53c857d5a6254b02a15d54138110b8981fd3bdaeb2", "schema_version": 1, "timestamp": "2026-10-05T06:46:43.587729+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_13dcf5763b8f", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:c4908bb1490e126dc49f249a75c6c9fb624a7a441106c17255d1777f9603c75d", "schema_version": 1, "timestamp": "2026-10-05T06:46:46.278951+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_4c6cef1c8d45", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:aa8fc016966f6f216dd6c0ed424c7196a3aff0536507d3a6fd52ce0f159929f8", "schema_version": 1, "timestamp": "2026-10-05T06:46:46.299897+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_aec1fa75904e", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:a94045801be5d27889a5153185173d9cdbe063c8b57504c6f60a80b029584b9d", "schema_version": 1, "timestamp": "2026-10-05T06:46:48.101655+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_1b3b00190ff6", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:84e975644c2d6bccda4f130f3f9f028170796c521f29667da82db31f4268cab4", "schema_version": 1, "timestamp": "2026-10-05T06:46:48.119418+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_f215e9d60b05", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:7cffd2855e55066206b6102d10ad3126fe0e95ecead0e675db5d6a35b714deef", "schema_version": 1, "timestamp": "2026-10-05T06:46:48.261428+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_211c93c72ff3", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:b7deb9a6bebae0f02039a89c82cfd4190e44fcd1584496945f4869ab5ad2dd69", "schema_version": 1, "timestamp": "2026-10-05T06:46:48.273213+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_c9bcaaf24849", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:8a2c349386085e0a0fe12210fabc7f4193a6cb54f072859f9e0c279308c881c0", "schema_version": 1, "timestamp": "2026-10-05T06:46:48.405587+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_885a3d5481e9", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:5ac99139cf2fb4e902c5d5e4aaaffe8d9631f41d6fc8ca6068b1216df3e8a6f4", "schema_version": 1, "timestamp": "2026-10-05T06:46:48.417188+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_13b5535a9146", "event_type": "storage.degraded", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:dc4a0ef045512b494194a21ba9206ec66e21ae710f7c2caa29f194c48fb142c0", "schema_version": 1, "timestamp": "2026-10-05T06:47:39.043363+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_a36302d909de", "event_type": "capability.failed", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:5905ca1fb3b9d207b290447e7844c186b1a34e36d7a7e4102159cee3f8ae136b", "schema_version": 1, "timestamp": "2026-10-05T06:47:39.060088+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_2b4df73a86ec", "event_type": "storage.degraded", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:dc4a0ef045512b494194a21ba9206ec66e21ae710f7c2caa29f194c48fb142c0", "schema_version": 1, "timestamp": "2026-10-05T06:47:39.078783+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_f9707edb7816", "event_type": "storage.degraded", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:600ba21f0472de9fb57133929648cd8121d1717595e7cd27c381d71ac03852c8", "schema_version": 1, "timestamp": "2026-10-05T06:47:39.092698+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_04d27d8e6ff8", "event_type": "capability.failed", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:8a8675818b5d411241e416ff9e8c9a1845d9b2a8befeacc51e10ab977731aeba", "schema_version": 1, "timestamp": "2026-10-05T06:47:39.103010+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_74854b98ab41", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:bb38314acb236f08ae39cb60b5022905678e86bf146d16841856ee34f1cd614d", "schema_version": 1, "timestamp": "2026-10-05T06:49:25.034574+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_24326590943d", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:5b3bf4b1e76f3378365574bb10f3bcd572885577354d5ba81bab5a1b98d4233c", "schema_version": 1, "timestamp": "2026-10-05T06:49:25.309489+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_0e9f3f7ea0f1", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:d12d16329de90f8b15897f8c294447b5c028ecb9e7b19a036e8c1dc586529775", "schema_version": 1, "timestamp": "2026-10-05T06:49:25.527943+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_7d3098712d56", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:da65d91f039c9b4385704a54c81bb03996df06181e83024f1b3baf3e208fad27", "schema_version": 1, "timestamp": "2026-10-05T06:49:25.732453+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_4b01028bfaf7", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:d50f083aeb4328ba31efb3f8bef7603aae54b5bb47c90cbbf27305bcd1f3e3bf", "schema_version": 1, "timestamp": "2026-10-05T06:49:25.982763+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_823b863ba95e", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:0e0437583b13151bc9d7d2111f4c0f2864628f39be74dd7caec944a8b624d5f5", "schema_version": 1, "timestamp": "2026-10-05T06:49:26.233858+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_05727dd6d5d0", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:58ce685891cd29183b5b8e0ca04dec709875af25c10e3b85051c57b5191dd3bb", "schema_version": 1, "timestamp": "2026-10-05T06:49:26.294590+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_8653cc51cc05", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:cf3ffb7ab858d9f36b7c1ab844bc45d2ebf37f7f7bfe555d969e075768263ea6", "schema_version": 1, "timestamp": "2026-10-05T06:49:26.306518+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_a9476432804f", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:58a30329240ce81770b8a10c622c8bcd71720a7da82b26026810fcd24c9fce0d", "schema_version": 1, "timestamp": "2026-10-05T06:49:26.317130+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_4e72295c0863", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:212e3d41acb92d8340b54ec7e8675e323d271186c5410f85e98ec735c0c084eb", "schema_version": 1, "timestamp": "2026-10-05T06:49:26.328129+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_24d9e8304b51", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:6732117c84ca98c75a9387b680686e2b1d26b0a34e578acb1fb02dd740d6ed24", "schema_version": 1, "timestamp": "2026-10-05T06:49:26.339686+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_1b139c924879", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:5740ecb8233bad3aeb7accc88a54ee70433890e870d02df8f44a2934f58224af", "schema_version": 1, "timestamp": "2026-10-05T06:49:26.351188+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_b96631537a0b", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:dada98dc7ebcd4d84ae8246af43b0407dea10ebc39d5893f2ccfc1c90e38713e", "schema_version": 1, "timestamp": "2026-10-05T06:49:26.362439+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_defa76132b52", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:fa53369f86a76f7463ab370b0b5b2424931c989ae67d535cbd8139b577817080", "schema_version": 1, "timestamp": "2026-10-05T06:49:26.373635+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_dce056c4654f", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:6f4f043f369aa4b7a132bf2a86bd55a5a81acf9da05ab5531a2ca77407a75b8f", "schema_version": 1, "timestamp": "2026-10-05T06:49:26.385204+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_95647e276440", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:e7243dbf9eff44b1151db1fb181715e1b93b20bc8f562e13c8c1081749cba7e2", "schema_version": 1, "timestamp": "2026-10-05T06:49:26.395446+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_f7b30bc3b1e5", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:88d01292c9bad0119b8e2d8a6dbec58608ecc6f8c925b436a71dd14eaab8471e", "schema_version": 1, "timestamp": "2026-10-05T06:49:29.609938+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_474f2c1edc09", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:d192428e3aecb673052aab395d3ca46be3801a1993d0ebf4b094d344e5ffa3f1", "schema_version": 1, "timestamp": "2026-10-05T06:49:32.470488+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_be4af370222d", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:4caa0125a7cb3a765e4d62ff63ccdc9b1b90bf20d3dfa06aaa3a6b0bf81aa5e8", "schema_version": 1, "timestamp": "2026-10-05T06:49:32.486146+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_370b041b77ab", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:425cf4b663feaa86ec3eafbc5f9c3b470f66bda5755fccefb7f938df7a2ebe88", "schema_version": 1, "timestamp": "2026-10-05T06:49:32.603529+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_42d925780dc9", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:d57edb40af6e1524930b00b827334964c943611065da72e721641734058b034d", "schema_version": 1, "timestamp": "2026-10-05T06:49:32.612424+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_dcf2bfba3de8", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:513838c7094c81b393c8a4a03e366771d67e175614f7a29f334db873beae1e67", "schema_version": 1, "timestamp": "2026-10-05T06:49:32.856639+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_1e86c7d72b5f", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:d9db13756a0360e79effdd1184f6e5d71d12007626bc7e52f39b63c9d12e1703", "schema_version": 1, "timestamp": "2026-10-05T06:49:32.869109+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_fc987750a85c", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:9eb31157caf97d9bc60d6bb1debd9fef14fa1d2b413a3d3b0ca3e9ebfdf61103", "schema_version": 1, "timestamp": "2026-10-05T06:49:32.878491+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_9a0d5dd3e4dd", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:40f062e606db546ad97849b26795a7ef584e425976194ccb50cd29e8fd4a68fe", "schema_version": 1, "timestamp": "2026-10-05T06:49:42.210225+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_2275190d1a87", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:d6b66be9ef64cb7db64352b83bdeb700678bae13b3af22a073c891e5d92ccde6", "schema_version": 1, "timestamp": "2026-10-05T06:49:42.233289+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_00d83ffffe21", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:f0ddcbb1a01fa25070f5ce32b15f931f17bf0ba500a8893f05439190c8043709", "schema_version": 1, "timestamp": "2026-10-05T06:49:42.254779+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_b104b6056128", "event_type": "capability.failed", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:11a7f153199214499dfd4fc9a0b08d1f2f531ce3485b907cfe3a036cf7edc586", "schema_version": 1, "timestamp": "2026-10-05T06:49:42.295428+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_fcd97d359359", "event_type": "capability.failed", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:d5c968d2dbed3174298be875d798719fcdc31e803c8b5a6306d782b4a43afc6f", "schema_version": 1, "timestamp": "2026-10-05T06:49:42.358476+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_ff066fda80d1", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:6bf69da741e431dd7f5de2630995da45ddd2c6224207891b5216e012e6998b64", "schema_version": 1, "timestamp": "2026-10-05T06:49:45.441629+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_612ec40fd1e4", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:dd824cf3610914529537ec8783611e0b088fd6bb3dc81d1f61fa526a9ceb7f86", "schema_version": 1, "timestamp": "2026-10-05T06:49:45.464657+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_101f64fae77e", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:a18ecdd4265a25eb2eb084e169383bc3ef71f3273bd44ad3ec54d635915db231", "schema_version": 1, "timestamp": "2026-10-05T06:49:45.472844+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_304a54818690", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:c3102db0415d7dae5fc7d8251769218c2e5cc0dcf9158e44dbb82aa797c092d3", "schema_version": 1, "timestamp": "2026-10-05T06:49:45.485450+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_724c04075b5b", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:3358d01648173c908fe588f9f6371aaff50566def884c5a32835ff88d9a072c5", "schema_version": 1, "timestamp": "2026-10-05T06:49:45.494459+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_08ffb53d40ac", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:252e9e3eb1869872446a4691f96894f1560e5460d0015c6ef0fcb578000e98c8", "schema_version": 1, "timestamp": "2026-10-05T06:49:45.505009+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_c186460e40e8", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:8bacf3cbaf2335d6ad20db86c810606218ada500ed3fecdd5b86987d3ff313d2", "schema_version": 1, "timestamp": "2026-10-05T06:49:45.513057+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_798fc60a306a", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:3c6749386d0df979712cd68ebb324fc78d0f3d7bd86bdc0cddf76c3bae5be422", "schema_version": 1, "timestamp": "2026-10-05T06:49:45.669471+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_0d63b261b89e", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:e0e46228fd7371c5839f6582d703864f38a248c32aae963f7c16b56f7148c2c2", "schema_version": 1, "timestamp": "2026-10-05T06:49:45.679589+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_8cf8e16e313d", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:e77f935ddd1cd9b4c03f1270d0758872ae85cc651cfd250c30f28ae22bebcb6a", "schema_version": 1, "timestamp": "2026-10-05T06:49:45.693813+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_65de385bb705", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:8116052b643376b05f7736fa3917f20d14c3d2e16f012b1fa96a898bf855d1e3", "schema_version": 1, "timestamp": "2026-10-05T06:49:45.715309+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_0085ddd9525d", "event_type": "capability.failed", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:1163d0fcebec85439e62d33ef5e1f3de09c6e1a75e16c7ecdb1a887be368d19d", "schema_version": 1, "timestamp": "2026-10-05T06:49:45.730640+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_8cb5a0d833b0", "event_type": "storage.degraded", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:dc4a0ef045512b494194a21ba9206ec66e21ae710f7c2caa29f194c48fb142c0", "schema_version": 1, "timestamp": "2026-10-05T06:49:45.743731+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_12401de106d2", "event_type": "capability.failed", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:1163d0fcebec85439e62d33ef5e1f3de09c6e1a75e16c7ecdb1a887be368d19d", "schema_version": 1, "timestamp": "2026-10-05T06:49:45.833693+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_2f90d0903bde", "event_type": "storage.degraded", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:dc4a0ef045512b494194a21ba9206ec66e21ae710f7c2caa29f194c48fb142c0", "schema_version": 1, "timestamp": "2026-10-05T06:49:45.846441+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_72b31d339b70", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:bdddec01c5c7f8ce5e8aa1a0210c5d5e6361a13ba43d85cab6953df4edac762d", "schema_version": 1, "timestamp": "2026-10-05T06:49:45.854918+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_d870d5ac1848", "event_type": "capability.failed", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:1163d0fcebec85439e62d33ef5e1f3de09c6e1a75e16c7ecdb1a887be368d19d", "schema_version": 1, "timestamp": "2026-10-05T06:49:45.919058+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_e6541eb2f23e", "event_type": "storage.degraded", "parent_id": null, "partition_key": "subsystem_health", "payload_hash": "sha256:dc4a0ef045512b494194a21ba9206ec66e21ae710f7c2caa29f194c48fb142c0", "schema_version": 1, "timestamp": "2026-10-05T06:49:45.931415+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_d41b8338f344", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:08d9854e6c841592dc3d7d74c34cd5cfaac46346de24deb336998cae58e43ba2", "schema_version": 1, "timestamp": "2026-10-05T06:49:45.939780+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_06a466be85a5", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:7a08f9f4efd518ebc0939db20ad60098a7993c1a7f085417c44cbfd5106c497a", "schema_version": 1, "timestamp": "2026-10-05T06:51:29.235743+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_01b97fe1a24a", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:cc87d40d66cb48c079f46b873f06aceb4e1108394badc1642822b163d339c59b", "schema_version": 1, "timestamp": "2026-10-05T06:51:29.254737+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_a6f7d9215858", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:833f0de0971a926b6a86b3c6180111c20fdd85460fbb8c98794f58d6ba86055e", "schema_version": 1, "timestamp": "2026-10-05T06:51:29.266271+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_0ff7f5b318ae", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:d3dd9469bc1c37051ef12cf958c1e665592da50f5fb83ca4e4805d240bc0eb5f", "schema_version": 1, "timestamp": "2026-10-05T06:51:29.277599+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_c78064dbb798", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:8fc75079f8fc265ad0e025b881398e1eda5a88395da3994c5b324e88a711ac4e", "schema_version": 1, "timestamp": "2026-10-05T06:51:29.292026+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_e229bf1c4304", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:26e42b5839f558d320f3a0abcdf24d07c20acda3067a60d7409156a01099596b", "schema_version": 1, "timestamp": "2026-10-05T06:51:31.998143+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_cb05371075d3", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:a04d2289fdebfe4f5051756eb028d0c7d00602703db3c528523ee393532c371d", "schema_version": 1, "timestamp": "2026-10-05T06:51:32.010290+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_195bd254ca92", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:e227b022e02cf5954515ce26a6b909a1ae22988df63d1ce374938c3ec95116cc", "schema_version": 1, "timestamp": "2026-10-05T06:51:33.721149+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_3557f32af820", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:ad4749548773eed30b11fbca6a45d397b6f62f3f73fbfe713bb752c3a39e662e", "schema_version": 1, "timestamp": "2026-10-05T06:51:33.735979+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_ae42b48f5c4f", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:aec5941e0be4f363a5d7ab0bf215c26f61c7c6ed63ad3fd335b88b7c06b5d5ba", "schema_version": 1, "timestamp": "2026-10-05T06:51:33.846536+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_1a5dfcdba732", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:2c8835ea9e171751c593b4cf87e8ffc1e70f49d763e6c932fc3a71b1131eab3e", "schema_version": 1, "timestamp": "2026-10-05T06:51:33.855682+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_4c212c766420", "event_type": "task.outcome", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:816ee2ac1945082edb3e0cdc512856f4edcef61a860330f1aff91a70b896774f", "schema_version": 1, "timestamp": "2026-10-05T06:51:33.917103+00:00"}
{"actor_id": "", "authorization": "", "device_id": "", "event_id": "evt_98a1c9d79a80", "event_type": "task.failed", "parent_id": null, "partition_key": "rune_operator", "payload_hash": "sha256:9c5bfcbe3d47d3783802c2dc21af1c34137bfca61aa7bd58266286ef74d41d86", "schema_version": 1, "timestamp": "2026-10-05T06:51:33.925785+00:00"}
@@ -0,0 +1,48 @@
# Phase G slice 1 — retrieval-index authority supersession ledger
**Date:** 2026-10-02 · **Branch:** `phase-g-strangler-retrieval-index` · **Unit issue:** conductor/rune#2448
## What was superseded
The pure-Python `query_memory` implementation (formerly `src/rune/query_memory.py`,
deployed at `~/.config/opencode/scripts/query_memory.py`) is RETIRED as a production
implementation. Production authority now rests with:
1. **Implementation:** Rust crate `src/rust/query_memory/` (lib.rs core + cdylib, ten
`extern "C"` FFI symbols, zero external crates).
2. **Production service surface:** `shared/query_memory.py` — a ctypes-backed shim
(179 lines) over `qm_loader.py`, byte-deployed to `~/.config/opencode/scripts/query_memory.py`,
serving the FULL prior Python class API including the `self.history` snapshot.
## Strangler doctrine (master doc lines 685–770) — step discharge
| Step | Requirement | Evidence |
|------|-------------|----------|
| 1–4 | candidate built, contract frozen, parity proven | commits `9b76585`+`54d2db9`; `/tmp/phaseG_retrieval_index_contract.md` frozen; parity harness ALL GREEN |
| 5 | qualification | `/tmp/phaseG_qual_bench.out` (Rust CLI ~45× faster end-to-end); canaries both sides |
| 6 | canaries conformance | Python canary 12/12; Rust canary 27/27 |
| 7 | shadow comparison vs live store | `/tmp/phaseG_shadow.py` 46/46 exact-equal vs 77-record production store |
| 8 | authority switch + observed consumer evidence | commit `6f44a87`; both consumers verified live — `memory_store.py` (lines 820–821, live 76-record store, nested stats shape) + `growth_simulator.py` (record_query through Rust core); #2448 comment id 33795 |
| 9 | runtime evidence post-switch | post-switch verification wall ALL GREEN: Python canary 12/12, FFI smoke 41/41, Rust canary 27/27, parity harness ALL GREEN |
| 10 | old production path unreachable | ALL import paths (`import query_memory`) now resolve to the deployed shim; `shared/graph_shard_bridge.py` tolerant import serves the shim; the orphan `src/rune/query_memory.py` had ZERO importers (verified by sweep incl. path-string references) |
| 11 | old implementation removed or reference-only | `src/rune/query_memory.py` DELETED (this commit); the only stale reference (`scripts/structural_coverage_audit.py` registry mapping shared/ as a mirror of src/rune) corrected — query_memory removed from `SRC_A`/`SHARED_D`, added to `SHARED_B` (canonical, ops-harness owned) |
| 12 | supersession ledger | THIS FILE |
Deletion discipline (language-governance rule ⑥) satisfied: old code removed only after
the new implementation was PROVEN — wall green, shadow 46/46, both production consumers
observed live on the Rust core before deletion.
## Consumer map at supersession
- `~/.config/opencode/scripts/memory_store.py` — in-process, `adaptive_exploration_rate` (line 820).
- `~/.config/opencode/scripts/growth_simulator.py` — in-process, `record_query`.
- `shared/graph_shard_bridge.py` — tolerant import (try/except), follows the switch.
- `src/rune/tool_executor.py` — NOT a consumer (nominal grep hit only; goes through `memory_store.cached_recall`).
## Verification commands (reproducible)
- `/home/nick/.venvs/memory/bin/python3 src/rune/canary_query_memory.py` → 12/12
- `/home/nick/.venvs/memory/bin/python3 src/rust/query_memory/canary_rust.py` → 27/27
- `/home/nick/.venvs/memory/bin/python3 src/rune/ffi_query_memory_smoke.py` → 41/41
- `/home/nick/.venvs/memory/bin/python3 /tmp/phaseG_parity.py` → ALL GREEN
- `/home/nick/.venvs/memory/bin/python3 scripts/structural_coverage_audit.py` → exit 0, coverage 100%
@@ -0,0 +1,66 @@
# Phase G Slice 2 — FROZEN CONTRACT: evidence-provenance (T7.3)
Frozen: 2026-10-03, session ses_ffe5f08a7892affeGzZWhhm0wR. Branch `phase-g-strangler-evidence-provenance` (cut from slice-1 head `2ac2d0a`). Gitea issue #2449. Nothing below may change without a recorded amendment in this file.
## 1. Scope (observed, not inferred)
Six modules in `src/rune/`, 1796 lines total, one test file each as marked:
| Module | Lines | Surface (grep-verified 2026-10-03) |
|---|---|---|
| `audit_ledger.py` | 231 | `_compute_hash(prev_hash, entry)`, `append_entry(entry_type, entry_id, details)`, `verify_chain() -> {valid, entries, reason}`, `get_entries(limit=50)`, `log_promotion(entry_id, old_status, new_status, wash_count, reason)`, `log_gate_result(entry_id, verdict, critics)`, `log_drift_alert(alert_type, message, severity)`, `main()` |
| `behavior.py` | 411 | `connect()`, `add_correction(original, corrected, scope_tags=None, domain="", pattern="")`, `add_habit(trigger, action, name="", context_tags=None, min_activations=3)`, `match_context(context, match_type=None)`, `reinforce(item_id, outcome, item_type="habit")`, `list_items(item_type="all", limit=20)`, `behavior_stats()`, `main()`, `PLUGIN` dict |
| `behaviour_ledger.py` | 251 | 9 state constants (UNVALIDATED…NOT_APPLICABLE) + `STATES` + `CONFIRMED_STATES` tuples, `BehaviourLedger` class |
| `claim_audit.py` | 430 | `PATTERNS` list, `EVIDENCE_TAG` regex, `LEDGER_MARKERS`, `reframe(text)`, `scan(text)`, `verify(claim)`, `degrade(claim, verdict)`, `journal(entry)`, `report()`, `audit(draft)`, `handle_hook(event, data=None)`, `main()`, `PLUGIN` |
| `evidence_probe.py` | 214 | `PROBE_STATUSES`, `PROBE_FIELDS`, `MAX_PROBES_PER_TASK=2`, `ProbeBudgetExceeded`, `ProbeResult`, `build_probe_prompt(question, artifact_path)`, `validate_probe(artifact)`, `count_probes(artifact_dir)`, `check_budget(artifact_dir)`, `record_probe(result, artifact_dir)`, `run_probe(question, artifact_dir, runner=None, model="UNKNOWN")` |
| `verification_layer.py` | 259 | `VerificationResult`, seven `verify_*` functions (exit_code, unit_test, schema, file_exists, file_changed, tool_receipt, constraint_check), `verify(result, verifier_type)`, `handle_hook`, `PLUGIN` |
Plus `shared/audit_ledger.py` (210 ln) — OLDER vintage of audit_ledger.
## 2. Authority decisions (per module)
- **src/rune/ is the authority vintage for all six modules** — hardened newer code (memory_env-pathed storage, defensive handling). The deployed `~/.config/opencode/scripts/{audit_ledger,behavior,claim_audit}.py` copies are OLDER vintages (hardcoded expanduser paths). The authority switch is a VINTAGE UPGRADE plus a language swap — not a mirror replacement.
- `shared/audit_ledger.py` is superseded alongside the deployed copy (same older vintage, same defects).
## 3. Live-data contract (the linchpin — observed)
- **Live ledger:** `/home/nick/.config/opencode/memory/promotion_ledger.jsonl` (21 entries). `verify_chain()` under the deployed module's own defaults returns `{'valid': True, 'entries': 21, 'reason': 'chain intact'}` — OBSERVED 2026-10-03.
- **Live signing key: `rune-drift-integrity-2024`** (the deployed/shared default). The src/rune default `rune-governed-adaptation-2024` FAILS the live chain — it is a latent break, recorded as a defect this slice fixes.
- **Hash scheme (both vintages identical):** `HMAC-SHA256(key, (prev_hash + json.dumps(entry_without_hash, sort_keys=True)).encode()).hexdigest()`; genesis prev_hash = `"genesis"`; entry lines written with `json.dumps(entry)` in insertion order (type, entry_id, details, timestamp, prev_hash, hash); timestamp = `datetime.now(timezone.utc).isoformat()`.
- **Key rule for the Rust candidate:** read `AUDIT_LEDGER_KEY` env; DEFAULT = `rune-drift-integrity-2024` (the key that verifies the live chain). Never default to the governed key — it verifies nothing that exists.
- **Live `behavior.db`:** SQLite at memory_env memory dir, touched Sep 24 — must remain readable by the Rust candidate's storage layer.
## 4. Rust candidate design
Crate `/mnt/workdrive/rune/src/rust/evidence_provenance/` following the proven slice-1 shape: zero-dep core in `lib.rs`, `cdylib` FFI exports, hand-rolled JSON serialization where byte-parity demands it. **Port order within the slice** (dependency + risk order):
1. **audit_ledger** — linchpin: live data, external consumers, key contract.
2. **verification_layer** — pure logic, no storage, one test file; fastest port.
3. **evidence_probe** — file-based budget/records; `rune_operator.py:581` consumer.
4. **behaviour_ledger** — state machine, src/rune-only.
5. **behavior** — SQLite habits; live DB; deployed vintage upgrade + `rune_tui.py` consumer.
6. **claim_audit** — heaviest (warm-neuron store + graph DB + regex engine); last.
Python-side ctypes loaders (`*_loader.py`) + deployed shims per slice-1 pattern, so every consumer keeps its import surface unchanged.
## 5. Parity + qualification criteria
- **Byte-parity:** Rust `verify_chain` output byte-identical to Python's on the live ledger AND on a mutation battery (tampered entry, broken chain, invalid JSON, empty ledger). Parity harness both sides, slice-1 pattern.
- **Canaries:** Python-side canary module per ported module + Rust canary; both green before any switch.
- **Shadow:** shadow-compare Rust vs Python on live-shaped data before authority switch.
- **Performance:** like-for-like benchmark on the real consumption shape; no regression (Nick's law: combination does not decrease speed).
## 6. Consumers + adoption plan (Nick's directive: adoption during the refactor)
| Consumer | Imports | Switch |
|---|---|---|
| `~/.config/opencode/scripts/curator.py` | audit_ledger | at audit_ledger switch |
| `canary_tui_drift.py`, `canary_full_system.py` | audit_ledger | at audit_ledger switch |
| `rune_tui.py` (L62, L83) | claim_audit, behavior | at each module's switch |
| `rune_operator.py:581` | evidence_probe (relative) | at evidence_probe switch |
Deployed copies become ctypes shims at each module's authority switch (vintage upgrade included — the shim serves the src/rune contract, not the old vintage's). Old deployed Python deletions follow language-governance ⑥: only after the new is proven.
## 7. Exit criteria
All twelve doctrine steps discharged per module-port with observed evidence on #2449; coverage-audit registry remapped; supersession ledger written; audit regression gate green.
@@ -0,0 +1,62 @@
# Phase G slice 2 — evidence-provenance authority supersession ledger
**Date:** 2026-10-03 · **Branch:** `phase-g-strangler-evidence-provenance` · **Unit issue:** conductor/rune#2449
## What was superseded
The pure-Python evidence-provenance implementations (six modules in `src/rune/`, 1796 lines; plus the two OLD deployed vintages and `shared/audit_ledger.py`) are RETIRED as production implementations for their ported surfaces. Production authority now rests with:
1. **Rust crate `src/rust/evidence_provenance/`** — six cores: `lib.rs` (audit_ledger chain + FFI), `verification.rs` (7 verifiers), `probe.rs` (schema gate + budget), `behaviour.rs` (state machine), `habitmath.rs` (reinforce math), `claims.rs` (claim-audit decision skeleton); cdylib FFI (`ep_*` symbols, 21 total), zero external crates.
2. **Python shims** at `src/rune/{audit_ledger,verification_layer,evidence_probe,behaviour_ledger,behavior,claim_audit}.py` — serving the src/rune authority contract through `ep_loader.py` (in-process, no interpreter startup for the ported logic).
3. **Deployed shims** at `~/.config/opencode/scripts/{audit_ledger,behavior,claim_audit}.py` — same shape; audit_ledger + claim_audit + behavior carry the LIVE data-path rules (drift-key ledger, live behavior.db, real journal); claim_audit additionally carries the preserved SYMMETRY GUARD (deployed-vintage feature) and the repaired `scan_text()` TUI surface.
## Port ledger (all six, in contract §4 order)
| Port | Module | Rust core | Parity | Commit | Consumer proof |
|------|--------|-----------|--------|--------|----------------|
| 1 | audit_ledger | lib.rs chain ops | shadow 18/18; canaries 28/28+23/23 | `114c394`+`4b0d1d2` | live ledger entries 4242 (post-switch write), verify 23-intact |
| 2 | verification_layer | verification.rs | 33/33 (7 verifiers + CLI render) | `9cb93e6` | contracts.py, canary_native, rune_operator:323 dataclass |
| 3 | evidence_probe | probe.rs | 24/24 + consumer tests 23/23 | `3450a1c` | rune_operator:581/603, research_stage_executor:312 |
| 4 | behaviour_ledger | behaviour.rs | 13/13 full-scenario | `8973caa` | testing_mode:19, iteration:23, rune_tui:3152 |
| 5 | behavior | habitmath.rs | 16/16 + full-stack live-db | `2732677` | rune_tui:83; deployed vintage upgraded (live db read-verified) |
| 6 | claim_audit | claims.rs | 17/17 + real-journal aggregate (1127 rows) | `969e39f` | rune_tui:62/2468/3752, contracts.py, hook_engine:270 |
Errors: byte-equal (duplicate/unknown-behaviour/unknown-state; probe schema-gate lists incl. Python repr rendering; CLI argparse shapes + exit codes).
## Strangler doctrine (master doc lines 685–770) — step discharge (slice level)
| Step | Requirement | Evidence |
|------|-------------|----------|
| 1–4 | candidate built, contract frozen, parity proven | commits `114c394` → `969e39f`; frozen contract `plans/2026-10-03-phase-g-evidence-provenance-contract.md` (commit `7c68e41`); per-port parity harnesses ALL GREEN |
| 5 | qualification | per-port parity + decision-math batteries; canary pair per port (Python 74/74, Rust 66/66 — all six port sections accumulated); performance follows the slice-1 in-process shape (no interpreter startup; same cdylib) |
| 6 | canaries conformance | canary_audit_ledger.py 74/74; canary_evidence_provenance_rust.py 66/66 (per-port sections for ALL six ports; dual-mode LIVE/ISOLATED for live-ledger pins — run 18141 repair); embedded tests through shims (verification test, 23 consumer tests) |
| 7 | shadow comparison vs live-shaped data | port-1 shadow 18/18 vs `939883d:shared/audit_ledger.py` (banked at `114c394`); ports 2–6 use direct byte-parity batteries vs the live originals (same discipline, stronger: full-document byte-quality) |
| 8 | authority switch + observed consumer evidence | shims deployed + live-verified per port: entry 4242 (first post-switch ledger write), live behavior.db read through the port-5 shim, real 1127-row journal through the port-6 aggregation, TUI backend trio imports, TUI scan_text FIRST-EVER live call |
| 9 | runtime evidence post-switch | post-switch walls per port: parity round-trips, embedded tests, consumer chains (rune.behaviour_ledger / rune.evidence_probe / rune.claim_audit + deployed trio) — all GREEN |
| 10 | old production path unreachable | `import audit_ledger` (deployed consumers) → shim; `rune.behaviour_ledger` / `rune.evidence_probe` / `rune.claim_audit` / `rune.verification_layer` → shims; `shared/audit_ledger.py` had ZERO importers (verified by sweep incl. path-string forms) — DELETED |
| 11 | old implementation removed or reference-only | `shared/audit_ledger.py` DELETED (this commit, after the proven wall + zero-importer sweep); six src/rune modules remain as shims (the API contract); original Python lives in git history. Coverage registry remapped: `audit_ledger` removed from SHARED_D; gate exit 0 post-deletion |
| 12 | supersession ledger | THIS FILE |
Deletion discipline (language-governance rule ⑥): `shared/audit_ledger.py` removed only after (a) the deployed shim proved live against the real ledger, (b) the shadow wall was green, (c) a zero-importer sweep across src/ shared/ scripts/ tests/. The remaining five src/rune originals were rewritten AS shims in their own commits (git history holds each original); they are the API contract and stay.
## Vintage-merge disclosures
- **SYMMETRY GUARD preserved:** the deployed `claim_audit` vintage carried `research_lens.symmetry_check` integration (2026-08-15) absent from src/rune — preserved in both shims (feature-preserving merge; load-bearing behavior).
- **DEAD CONSUMER REPAIRED:** `rune_tui.py:3752` `claim_audit.scan_text()` had NO implementation in either vintage (silent try/except death) — now implemented, first-class, live-verified.
- **Sequence break (port 1):** the audit_ledger shim was deployed BEFORE its canary pair existed; both canaries subsequently written and green — disclosed on #2449 comment 33817, not retrofitted.
## Consumer map at supersession
- `~/.config/opencode/scripts/rune_tui.py` — claim_audit (report L2468, scan_text L3752), behaviour (L3152), behavior (L83).
- `src/rune/rune_operator.py` — evidence_probe (L581/603), VerificationResult dataclass (L323).
- `src/rune/contracts.py` — dynamic `__import__` surfaces (verification_layer, claim_audit).
- `src/rune/testing_mode.py:19` / `src/rune/iteration.py:23` — behaviour_ledger constants + class.
- `scripts/research_stage_executor.py:312` — evidence_probe orchestration.
- `~/.config/opencode/scripts/curator.py` / `canary_full_system.py` — audit_ledger (deployed shim).
- `src/rune/dependency_tracker.py` — map-strings only (NOT an import).
## Live-data rules (contract §3) — honored in the shims
- Live promotion ledger: drift key verified 23-intact through the shim + FFI; explicit `AUDIT_LEDGER_KEY` override honored.
- Live behavior.db: read/written through the deployed shim at `~/.config/opencode/memory/behavior.db`; parity scenarios ran on temp dirs only.
- Live claim_audit journal (1127 rows): read-only aggregate through the Rust core; writes stay Python.
@@ -0,0 +1,97 @@
# Phase H plan — Go front end / Rust back end (Nick's architecture directive)
Status: PLAN (awaiting Nick's sign-off before any code)
Directive: USER-LOCKED (2026-10-04). Supersedes the KEEP-PYTHON verdicts in
docs/rebase-language-rubric.md wherever they conflict.
## 1. The directive
Go owns the front end: runtime entry points, the first-call surface, the
user-facing interface and its calls. Rust owns the back end: every core
logic module. Python survives only as transitional glue inside a module
until its own Go/Rust surface lands.
## 2. What "front end" means concretely here
From the runtime-audit memory (2026-09-10): the live entry chain is
rune_tui -> send_task -> ContextBroker -> ModelRouter -> local/Cloud
transports. The user-facing calls are: the TUI process itself, the CLI
entry (python -m rune / rune binary), the operator message path, and the
OpenAI-compatible /v1 endpoints MiMoCode serves.
Candidates for the Go first-call layer, in dependency order:
- rune binary (process entry, arg parsing, process lifecycle) — replaces
the Python module-loading bootstrap.
- CLI/IPC command dispatch — the first-call surface that routes a user
command to the right back-end service.
- The OpenAI-compatible HTTP endpoint surface (/v1 chat completions) —
stdlib net/http + NDJSON streaming is exactly the area the rubric called
Go's only capability-proven fit (W4 evidence, unchanged by the directive).
- TUI: NOT in the first Go surface. Textual coupling is deep (80+ modules
per the rubric); the front-end Go layer first exposes the API the TUI
consumes, and the TUI itself migrates later as its own wave.
## 3. What "back end" means concretely here
All pure-logic modules move into the two existing zero-dep crates
(evidence_provenance, query_memory) or a third crate for the remaining
domains. The zero-external-crate discipline stands (the rubric's W4 Rust
kill-rule for network code stays valid INSIDE Rust crates: no network code
in crates, that belongs to the Go layer).
Back-end candidates not yet ported (pure logic, no external deps):
- drift_core, drift_cache, drift_compression (scored math) — memory umbrella
- warm_neuron_store/warm_router scoring paths — memory umbrella
- memory_store consolidation/scoring helpers — memory umbrella
- capability_fit/capability_broker scoring — capability umbrella
- tokenizer/counting helpers in token_optimizer — inference umbrella
Each port follows the slice-2 pipeline: contract, zero-dep Rust, byte-parity
battery vs the Python vintage, dual-mode canary, consumer import chain,
CI green, supersession ledger.
## 4. Sequencing (per wave, each independently mergeable)
H0 — enablement (no behavior change):
- Install Go on the host + the runner user's PATH; pin a version in
requirements-ci-equivalent (a go-version file + CI check).
- CI: add a Go build+vet+test job (empty module, exits 0) so the pipeline
carries Go before any code lands.
H1 — Go first-call shim:
- Go binary `rune` replacing the Python entrypoint's process bootstrap:
arg parsing, env resolution, spawn the Python runtime (transitional).
- Canary: same first-call contract (exit codes, env handoff, TTY attach).
H2 — Go command dispatch + /v1 endpoint:
- Go serves /v1 chat completions + CLI dispatch, calling the Python core
through the same local transport the TUI uses today.
- Parity battery: request/response shapes vs the Python server for the
same canned conversations (byte-level for metadata, streaming NDJSON).
H3 — back-end wave (parallel to H1/H2 where independent):
- Ports listed in section 3, one or two modules per slice, same slice-2
discipline. No port starts without its parity battery written first.
H4 — TUI migration wave (its own contract, later):
- Only after H1-H2 prove the Go front end serves the same surfaces the TUI
consumes. The TUI is the largest single module (4k+ lines, Textual) and
earns its own contract.
## 5. Honest constraints (carried from the rubric, not contradicted)
- Go must be installed first (rubric D5 gate: it failed this before; the
directive now outweighs the gate but installation is still step zero).
- Network/streaming code lives in the Go layer, never in Rust crates
(rubric W4: stdlib-only Rust has no credible HTTP/SSE precedent).
- SQLite/regex/file-IO stay at their current language boundaries per the
zero-dep split law from slice 2 (math Rust, IO at the edges).
- Every surface gets the same proof pipeline as slice 2: observed-not-
invented expectations, byte parity, dual-mode canary, CI green.
## 6. Success measure
The tranche is complete when: the `rune` process starts as a Go binary;
user-facing calls (CLI, /v1, operator path) are served by Go; every core
logic module on the port list is byte-parity Rust; Python remains only in
surfaces explicitly contracted as keep-Python (TUI internals until H4,
test harness, canary frameworks).
+285
View File
@@ -0,0 +1,285 @@
# TOTAL REBASE TRANCHE — Rune to Go/Rust (R2)
**Status:** DESIGN — awaiting Nick's approval gate (TRANCHE-DEPTH TEMPLATE §4)
**Supersedes:** the KEEP-PYTHON rubric verdicts (already superseded by Nick's
Go-front/Rust-back directive) and extends Phase H (H0–H4 slice 1, merged
`693a494`, v0.90.0) from first ports to total rebase.
**Nick's directives (verbatim anchors):**
- "i want the whole codebase rebased in rust and go"
- "everything should initiate with go as the runtime, and reactive adaptable
runtime should be go"
- "the deterministic backend i.e memory storage, architectures and elastic
cognition and everything like that should be in rust"
- "go should be the reactive front end to what interfaces with the user —
not gui — the deliverables and chat interactions as well as how rune
handles thoughts and runtime"
- "the zero dependency rule should be in place but the rebase should rebuild
those aspects into the new architecture and make them operational"
- "the textual tui should be last but everything should be wired into the
textual tui"
- "start with the scaffolding first as everything relies on that, then use
the new rust scaffolding to make sure everything else is built towards
scaffold protocol"
- "the scaffolding function should be a rust execution but go start"
## 1. Grounding summary (measured 2026-10-04, from the spine store + wc)
- 161 modules with source, **64,482 lines** of `src/rune/` Python.
- Draft classification (rule-based, to be function-level verified in Wave 0):
- **RUST (deterministic backend):** 49 modules, 26,772 lines — memory,
drift, warm, capability, token, spine, scaffold, construction_*,
structural_store, curator, consolidation, elastic cognition, ledgers,
audit, evidence, contracts, sleep_time, subconscious, persona_gate,
context_broker, growth.
- **GO (reactive runtime + user-facing):** 10 modules, 7,043 lines —
operator, transports, dispatch, provider registry, adaptive router,
hook engine, local tool runtime, work manager, deploy_qualify.
- **TUI-last:** 4 modules, 4,889 lines — rune_tui (4,041), tui_validator,
tui_backlog, tui_transport.
- **UNCLASSIFIED (Wave 0 must disposition each with evidence):** 73
modules, 21,473 lines — voice_*, tool_executor, model_routing/registry,
event_bus, cron_engine, message_queue, identity_anchors, pins,
stage_guards, tier_manager, witness, etc.
- **Test infra:** 23 canary/test modules, 4,211 lines — rewritten per wave
(Rust canaries port with their surface; Go canaries for the front).
- **Retires:** `__init__`, `__main__`, `memory_env` (bootstrap glue —
subsumed by the Go runtime's env contract).
- Existing Rust (merged, production-proven): 6 zero-dep crates
(query_memory, evidence_provenance, rune_spine, memory_scoring,
capability_scoring, token_counting), 338k bytes, all with parity
batteries. Go: rune-entry (H1 shim + H2 /v1 front door).
- Stragglers from R1 folded in: #2171, #2119 (TUI), #1844 (salience_gate),
#1755 (goal._load), #1738 (failure_auto_filer).
## 2. Objective
Every line of rune's Python runtime is rebased into the dichotomy —
**Go initiates and reacts; Rust determines and stores** — with the
scaffolding protocol rebuilt FIRST (Go start / Rust execution) so every
subsequent conversion is driven, checked, and closed by the new protocol
itself. Python is deleted only after the replacement is parity-proven and
CI-green (strangler exit law). The Textual TUI is last and stays the
integration surface: everything wires into it over the transport seam.
## 3. The hard constraint analysis (zero-dep + total rebase — feasibility)
Nick asked: confirm or deny. **Confirmed possible**, amended per his
critiques (2026-10-04):
- **Hybridized membrane, not a wall (Nick's correction):** Go remains the
reactive initiator, but "Rust never touches a socket" is WITHDRAWN as an
absolute. The languages interoperate where coherence demands: Rust cores
callable from Go (the proven cdylib pattern), Go surfaces invocable from
Rust through defined channels, shared wire protocols. The dichotomy is
the DEFAULT placement rule; hybrid junctions are deliberate, contracted
exceptions — each recorded in the unit's contract.
- **SQLite STAYS (Nick's ruling):** no bespoke storage engine in this
tranche. SQLite remains and is wired into the new architecture — the
Rust side reaches it through system-library FFI (legal under zero-dep:
a system library is not a crate dependency). The bespoke memory-system
redesign is a LATER, separate program.
- **Reactivity/async:** Go goroutines + channels own the event loops;
Rust's deterministic cores stay synchronous behind the membrane.
- **Regex:** hand-rolled matchers per case (proven: pyjson, word-run
scanner, run-collapse). No general regex engine needed.
- **Parity-plus-improvement (Nick's upgrade):** parity is the FLOOR, not
the ceiling. Every unit answers "parity OR measurably better" —
efficiency improvements are in scope per function (Rust zero-copy and
exactness, Go goroutine-native concurrency), bloat from bad syntax is
removed, and functionality is the non-negotiable gate. Improvements
carry measurements; the parity battery proves no behavior regression
either way.
- **TUI wiring is continuous (Nick's correction):** each landed surface
wires toward the Textual TUI over the transport seam as part of its
unit contract — the final TUI wave integrates a system already aimed
at it, not a retrofit.
- **Language intelligence in the protocol (Nick's addition):** the
scaffolding gains a first-class placement decision — for rune, the
Go/Rust dichotomy rule (reactive/initiating → Go; deterministic/
stateful → Rust); for other projects, a language-fit assessment at
construction-classification time (the protocol recommends the right
language for the task's characteristics).
- **Provenance lock (Nick's verification):** the scaffolding being ported
in Wave 1 is the V1-ACCEPTED system per docs/SCAFFOLDING_V1_DEFINITION.md
(T18 §18B; North Star = Nick's verbatim words; 17/17 acceptance gates).
The Wave 1 parity replay against the production contract ledger proves
the rebuild is that design, not a drift.
## 4. Wave structure (each wave independently mergeable, scaffolding-gated)
### Wave 0 — Grounding + inventory verification (the template's bar)
- Function-level mapping of all 161 modules (the spine gives structure;
wave 0 verifies 100% of functions and classifies the 73 UNCLASSIFIED
with evidence).
- Disposition the 5 R1 stragglers.
- Produce the per-unit issue factory input: one issue per function,
each answering improve/fix/missing-connectors with evidence.
- Deliverable: `plans/total-rebase-inventory.md` + issues filed.
- Exit gate: inventory signed off by Nick (the approval gate).
### Wave 1 — The scaffolding engine reborn (Go start / Rust execution)
**Everything depends on this — it goes first, per Nick's directive.**
- The rebuild target is the V1-ACCEPTED system (Nick's design —
docs/SCAFFOLDING_V1_DEFINITION.md, North Star verbatim, 17/17
acceptance gates). The parity replay against the production contract
ledger is the proof the rebuild IS that design.
- Rust crate `scaffold_core`: the construction machinery as pure
deterministic logic — contract lifecycle (draft→approved→building→
verifying→closed), closure classification (correctly_predicted et al.),
scope resolution, legal transitions, the contract store.
Source modules: scaffold.py, construction_interface.py,
construction_decision.py, construction_intent.py, structural_store.py
(parts not already in rune_spine), contracts.py, toolchain.py.
- Go surface `rune-scaffold` (extends rune-entry): the initiation layer —
submit-need/observe/transition/close CLI + /v1 endpoints, delegating to
the Rust core.
- NEW per Nick: the **language-placement decision** lands in the protocol
here — construction classification gains a language-fit assessment
(rune: the Go/Rust dichotomy rule; other projects: characteristic-based
recommendation). Every later contract carries its placement verdict.
- NEW per Nick: parity is the floor — the engine itself is the first
beneficiary of the parity-plus-improvement rule (measured improvements
allowed, functionality preserved, no-regression proven).
- Exit gate: the R2 tranche's own remaining waves are driven THROUGH the
new engine (the protocol builds itself under its own rules).
### Wave 2 — Memory/storage backend (Rust, SQLite retained)
- SQLite is NOT replaced (Nick's ruling). The Rust memory core reaches it
via system-library FFI (zero-dep legal); memory_store.py (2,435 lines)
is decomposed: query paths and consolidation logic into Rust, the SQLite
mechanics behind a thin storage trait that maps to libsqlite3 today and
to the future bespoke engine LATER without consumer churn.
- elastic_cognition, memory_consolidation, sleep_time, curator,
subconscious_pass, drift_* (remaining DB-backed surfaces), journal,
recall_* (recall_router is routing → boundary checked at Wave 0
disposition).
- Data safety gate: checksum-verified read-back of the live store through
the new engine; zero writes until the read path is proven identical.
### Wave 3 — Cognition + architecture engines (Rust)
- The remaining deterministic cores: capability_broker decision logic,
context_broker, persona_gate, growth, identity_anchors, pins,
stage_guards, tier_manager, witness, safety_override, abstraction,
verification surfaces.
- Ports already merged (memory_scoring, capability_scoring,
token_counting, rune_spine) are verified against the new scaffold
protocol and wired into the final architecture.
### Wave 4 — The reactive runtime (Go)
- The operator turn loop (rune_operator.py, 2,697 lines), chat
interactions, deliverables production, model routing at call time
(adaptive_router, model_registry/profiles/routing, provider_*),
tool execution (tool_executor, tool_protocol, local_tool_runtime),
event surfaces (event_bus, message_queue, cron_engine, hook_engine),
voice output chain (voice_*) as deliverable production.
- The /v1 surface deepens to carry the full interaction contract;
NDJSON streaming becomes the standard response shape.
### Wave 5 — Python retirement sweep
- Every Python module whose surfaces have landed is deleted (strangler
exit: parity proven + CI green + deployed one cycle). Shared/ vintage
mirrors retired with them.
- The glue shims (ms_loader, cs_loader, tc_loader, spine_loader) retire
when their consumers no longer speak Python at all.
### Wave 6 — TUI last (but wired continuously)
- Every wave before this one ships its transport-seam wiring toward the
TUI as part of each unit's contract (Nick's correction: natural,
continuous wiring — not a retrofit wave).
- Then the TUI itself: a thin Textual render shell over the wire (all
logic already Go/Rust by then) or a port — disposition decided on the
evidence of everything that came before.
### Wave 7 — Final audit
- Language-bar measurement, the full scaffolding ledger replayed, the
tranche's closure report with per-wave evidence.
## 5. Per-unit discipline (the template, unchanged)
- One issue per function via the issue factory; each answers
improve/fix/missing-connectors with evidence.
- Every unit runs the scaffolding pipeline end-to-end: submit-need →
contract → approval → canary-first (red proven) → implementation →
parity battery vs the Python vintage → closure audit (structural ids,
never file paths — the closure vocabulary law).
- Per-unit decisions on evidence, never pre-decided globally: each
function's Go-vs-Rust placement is assessed by the dichotomy rule with
the measured call graph, not by the draft classification alone.
- Bug-fix contract governs every defect found en route (red test first,
regression frozen).
## 6. Verification gates
- CI green per wave (crate builds, Go vet/test/build, canaries, henxels,
type ratchet until the Python ratchet itself retires in Wave 5).
- Parity batteries are byte-level for deterministic surfaces, shape-level
modulo volatile fields for wire surfaces.
- The scaffolding closure audit for every contract: correctly_predicted
or honestly flagged, never silent.
- Deploy qualification per merge (the pipeline that proved itself today).
### The 90/100 coverage law (Nick, 2026-10-05 — GOVERNING from W3.4 on)
Per Nick: "if it's realistically achievable but aim higher also if that's
realistically achievable. go to 100% where achievable."
- **100% where achievable — the deterministic Rust kernels.** Every kernel
crate (memory_retrieval, cognition_core, scaffold_core, memory_storage,
and every future crate) targets 100% BRANCH coverage via its parity
matrices + the pytest shim battery: every branch has real reachable
semantics, and exhaustive enumeration is exactly what the batteries do.
Achievability is measured per crate at wave close; a kernel below 100%
carries the named unreachable case or it isn't done.
- **≥90% on the changed-lines bar (diff-cover), aiming higher.** The floor
stays a floor; the working standard per slice is 90%+ on changed lines;
whatever a slice achieves beyond 90 becomes the next slice's personal
benchmark (per-unit "aim higher" — the number climbs wave over wave).
The floor ratchet itself moves after consecutive clean waves
(70 → 76 → 84 → 90; each raise is a commit, never a step down).
- **Glue/failure branches: 90%+ with byte-compare proofs.** The stubs and
probe branches that only fire in a broken world are covered by honest
fixtures (real import-failure probes like the from-import resolution
law) — never by fake-triggered pragmas. A branch covered by a lying
fixture is WORSE than uncovered; if it can't be honestly reached, it's
named in the wave's closure report with the reason.
- **Assertion quality is part of the bar.** A test that touches a line
without verifying its OUTPUT doesn't count toward the bar — byte-compare
/ exact-value asserts are the standard; "is not None" assertions reject
at review.
## 7. Risk register
- **SQLite FFI correctness** (Wave 2) — the storage layer is retained but
re-wired; mitigations: read-path proven identical before any write,
checksum-verified read-back of the live store, the storage trait
boundary keeps the future bespoke swap cheap.
- **Hybrid membrane drift** — junctions multiply ad-hoc if ungoverned;
every Go↔Rust interaction point is a contracted, recorded decision in
its unit's contract (the dichotomy is the default; hybrids justify
themselves per unit).
- **Improvement regressions** — parity-plus-improvement invites subtle
behavior changes; every improvement carries a measurement AND the parity
battery stays authoritative for no-regression.
- **Go↔Rust call latency** — measure in Wave 1; the architecture assumes
local transport cost is negligible vs model latency; if measured wrong,
the membrane's transport moves (decision recorded, not improvised).
- **Context-window economics** — 1,852-style issue volumes mean the
pipeline must multiplex (no-wait law) and local models carry routine
units (custodian pattern).
- **Scope creep during Wave 0** — the inventory is measured and frozen
before design; additions go through scope amendments.
## 8. Rollback
- Every wave merges independently; master stays deployable at each
boundary (the strangler's whole point).
- The Python vintage remains until Wave 5 deletion gates pass; a wave
revert = revert the merge commit (deploy qualification re-runs).
- The scaffolding engine's Wave 1 parity replay means even the protocol's
own replacement is reversible against recorded history.
## 9. Approval gate
This tranche does not execute until Nick approves. On approval, Wave 0
starts immediately and runs without consent blockers (standing directive),
with self-verified checkpoints and defect filing.
+196
View File
@@ -0,0 +1,196 @@
# Total-Rebase Inventory (R2 Wave 0.1) — measured 2026-10-04
Generated from `store/rebase/inventory.json` (AST function-level extraction) +
`store/rebase/classification.json` (import/content evidence per module).
Placement law: reactive/initiating/user-facing/network/turn-time → GO;
deterministic/stateful/storage/cognition → RUST; Textual rendering → TUI-LAST;
pure compute with no I/O markers defaults to the deterministic backend.
## Totals
| Side | Modules | Lines | Functions |
|---|---|---|---|
| GO | 43 | 19495 | 708 |
| RUST | 93 | 36215 | 1091 |
| TUI-LAST | 7 | 5438 | 225 |
| TEST-INFRA | 29 | 5395 | 158 |
| RETIRES | 2 | 93 | 9 |
## Per-module classification (evidence-carrying)
| Module | Side | Lines | Functions | Evidence |
|---|---|---|---|---|
| rune_tui | TUI-LAST | 4041 | 166 | textual/rich imports: ['rich', 'textual'] |
| rune_operator | GO | 2697 | 68 | network/concurrency imports: ['threading', 'urllib']; reactive/initiating domain |
| memory_store | RUST | 2435 | 56 | sqlite3 (storage-backed); network/concurrency imports: ['subprocess']; deterministic/state domain |
| curator | RUST | 1838 | 43 | sqlite3 (storage-backed); network/concurrency imports: ['subprocess']; deterministic/state domain |
| tool_executor | GO | 1812 | 47 | network/concurrency imports: ['subprocess']; reactive/initiating domain |
| warm_router | RUST | 1536 | 36 | sqlite3 (storage-backed); network/concurrency imports: ['subprocess']; reactive/initiating domain; deterministic/state domain |
| construction_interface | RUST | 1039 | 25 | deterministic/state domain |
| spine | RUST | 1019 | 31 | deterministic/state domain |
| sleep_time | RUST | 971 | 25 | sqlite3 (storage-backed); network/concurrency imports: ['subprocess', 'urllib']; deterministic/state domain |
| construction_decision | RUST | 949 | 18 | deterministic/state domain |
| capability_broker | RUST | 900 | 21 | deterministic/state domain |
| hook_engine | GO | 860 | 8 | reactive/initiating domain |
| scaffold | RUST | 854 | 31 | network/concurrency imports: ['subprocess']; deterministic/state domain |
| local_tool_runtime | GO | 806 | 32 | network/concurrency imports: ['threading', 'urllib']; reactive/initiating domain |
| context_broker | GO | 768 | 23 | network/concurrency imports: ['threading'] |
| elastic_cognition | RUST | 765 | 13 | network/concurrency imports: ['urllib']; deterministic/state domain |
| memory_consolidation | RUST | 733 | 20 | network/concurrency imports: ['subprocess']; deterministic/state domain |
| provider_registry | GO | 666 | 28 | network/concurrency imports: ['threading', 'urllib']; reactive/initiating domain |
| persona_gate | RUST | 661 | 21 | sqlite3 (storage-backed); network/concurrency imports: ['subprocess', 'urllib']; deterministic/state domain |
| structural_store | RUST | 656 | 33 | deterministic/state domain |
| contracts | RUST | 626 | 7 | deterministic/state domain |
| adaptive_router | GO | 625 | 19 | reactive/initiating domain |
| work_manager | GO | 591 | 25 | reactive/initiating domain |
| abstraction | RUST | 587 | 12 | sqlite3 (storage-backed); network/concurrency imports: ['urllib']; deterministic/state domain |
| construction_intent | RUST | 555 | 16 | deterministic/state domain |
| canary_audit_ledger | TEST-INFRA | 540 | 2 | canary/test module |
| safety_override | RUST | 530 | 18 | no network/reactive markers — pure compute defaults to the deterministic backend |
| failure_auto_filer | GO | 527 | 28 | network/concurrency imports: ['threading'] |
| build_mode | GO | 517 | 34 | network/concurrency imports: ['subprocess'] |
| drift_core | RUST | 517 | 14 | deterministic/state domain |
| event_wait | GO | 517 | 14 | network/concurrency imports: ['subprocess', 'urllib']; reactive/initiating domain |
| tui_validator | TUI-LAST | 514 | 13 | tui family |
| memory_selector | RUST | 511 | 14 | deterministic/state domain |
| system_registry | RUST | 501 | 14 | no network/reactive markers — pure compute defaults to the deterministic backend |
| construction_pattern | RUST | 496 | 13 | deterministic/state domain |
| cache | GO | 492 | 17 | sqlite3 (storage-backed); network/concurrency imports: ['threading'] |
| claim_audit | RUST | 483 | 14 | sqlite3 (storage-backed); deterministic/state domain |
| graph_memory | RUST | 474 | 9 | sqlite3 (storage-backed); deterministic/state domain |
| identity_anchors | RUST | 474 | 8 | deterministic/state domain |
| model_connect | GO | 462 | 10 | network/concurrency imports: ['urllib']; reactive/initiating domain |
| auto_tasks | GO | 460 | 14 | sqlite3 (storage-backed); network/concurrency imports: ['subprocess'] |
| capability_fit | RUST | 455 | 14 | deterministic/state domain |
| tool_protocol | GO | 455 | 27 | reactive/initiating domain |
| drift_ledger | RUST | 453 | 16 | sqlite3 (storage-backed); deterministic/state domain |
| witness | RUST | 451 | 10 | network/concurrency imports: ['urllib']; deterministic/state domain |
| goal | RUST | 441 | 20 | network/concurrency imports: ['urllib']; deterministic/state domain |
| release_notes | GO | 439 | 17 | network/concurrency imports: ['urllib'] |
| cron_engine | GO | 436 | 28 | network/concurrency imports: ['subprocess', 'threading']; reactive/initiating domain |
| model_profiles | GO | 435 | 15 | network/concurrency imports: ['urllib']; reactive/initiating domain |
| microkernel | GO | 434 | 12 | network/concurrency imports: ['subprocess', 'threading'] |
| model_routing | GO | 432 | 15 | network/concurrency imports: ['urllib']; reactive/initiating domain |
| voice_hybrid | RUST | 421 | 10 | sqlite3 (storage-backed); reactive/initiating domain |
| behavior | RUST | 407 | 9 | sqlite3 (storage-backed); deterministic/state domain |
| token_optimizer | RUST | 407 | 23 | deterministic/state domain |
| capability_ledger | RUST | 405 | 19 | deterministic/state domain |
| night_mode | GO | 405 | 19 | network/concurrency imports: ['subprocess', 'threading', 'urllib'] |
| chat_bubble | TUI-LAST | 401 | 16 | textual/rich imports: ['rich', 'textual'] |
| growth_delta | RUST | 400 | 16 | network/concurrency imports: ['subprocess', 'urllib']; deterministic/state domain |
| construction_mode | RUST | 395 | 5 | deterministic/state domain |
| prompt_sync | RUST | 376 | 14 | sqlite3 (storage-backed) |
| provider_budget | GO | 354 | 21 | network/concurrency imports: ['urllib']; reactive/initiating domain |
| structural_model | RUST | 352 | 21 | deterministic/state domain |
| programme_continuation | RUST | 349 | 11 | no network/reactive markers — pure compute defaults to the deterministic backend |
| canary_rune_serve | TEST-INFRA | 329 | 13 | canary/test module |
| builder_bridge | GO | 326 | 11 | reactive/initiating domain |
| canary_p1_07 | TEST-INFRA | 324 | 12 | canary/test module |
| layout_contract | RUST | 316 | 6 | deterministic/state domain |
| message_queue | GO | 316 | 14 | reactive/initiating domain |
| drift_observability | RUST | 315 | 9 | deterministic/state domain |
| build_basis | RUST | 314 | 12 | no network/reactive markers — pure compute defaults to the deterministic backend |
| canary_p0_07 | TEST-INFRA | 310 | 11 | canary/test module |
| drift_compression | RUST | 307 | 12 | deterministic/state domain |
| event_envelope | GO | 303 | 8 | reactive/initiating domain |
| attention_capsule | RUST | 296 | 7 | no network/reactive markers — pure compute defaults to the deterministic backend |
| canary_p1_01 | TEST-INFRA | 294 | 14 | canary/test module |
| voice_sync | RUST | 293 | 10 | sqlite3 (storage-backed); reactive/initiating domain |
| drift_cache | RUST | 292 | 16 | deterministic/state domain |
| outcome_ledger | RUST | 286 | 8 | sqlite3 (storage-backed); deterministic/state domain |
| time_awareness | RUST | 283 | 15 | no network/reactive markers — pure compute defaults to the deterministic backend |
| tiered_executor | GO | 282 | 9 | network/concurrency imports: ['urllib'] |
| stage_guards | RUST | 281 | 11 | network/concurrency imports: ['subprocess']; deterministic/state domain |
| standardized_providers | GO | 281 | 5 | reactive/initiating domain |
| canary_p1_06 | TEST-INFRA | 280 | 9 | canary/test module |
| progressive_escalation | RUST | 279 | 9 | no network/reactive markers — pure compute defaults to the deterministic backend |
| pins | RUST | 276 | 11 | deterministic/state domain |
| canary_p1_03 | TEST-INFRA | 271 | 9 | canary/test module |
| auto_context | GO | 270 | 8 | network/concurrency imports: ['subprocess'] |
| task_persist | RUST | 269 | 10 | no network/reactive markers — pure compute defaults to the deterministic backend |
| context_pressure | RUST | 262 | 9 | no network/reactive markers — pure compute defaults to the deterministic backend |
| canary_gitea_automation | TEST-INFRA | 261 | 3 | canary/test module |
| canary_p1_02 | TEST-INFRA | 253 | 12 | canary/test module |
| construction_patterns_builtin | RUST | 249 | 1 | deterministic/state domain |
| canary_full_system | TEST-INFRA | 244 | 2 | canary/test module |
| event_bus | GO | 244 | 7 | reactive/initiating domain |
| salience_gate | RUST | 243 | 4 | deterministic/state domain |
| self_improve | RUST | 242 | 5 | sqlite3 (storage-backed) |
| construction_activation | RUST | 237 | 4 | deterministic/state domain |
| toolchain | RUST | 236 | 7 | no network/reactive markers — pure compute defaults to the deterministic backend |
| task_gate | RUST | 235 | 8 | sqlite3 (storage-backed) |
| core_server | GO | 234 | 13 | network/concurrency imports: ['http', 'urllib']; reactive/initiating domain |
| audit_ledger | RUST | 231 | 8 | deterministic/state domain |
| canary_native | TEST-INFRA | 231 | 3 | canary/test module |
| openai_compat_transport | GO | 227 | 10 | network/concurrency imports: ['urllib']; reactive/initiating domain |
| shard_manager | RUST | 223 | 10 | sqlite3 (storage-backed) |
| response_adapter | RUST | 222 | 7 | no network/reactive markers — pure compute defaults to the deterministic backend |
| evidence_probe | RUST | 212 | 9 | network/concurrency imports: ['subprocess']; deterministic/state domain |
| model_registry | GO | 211 | 8 | reactive/initiating domain |
| persona_definition | GO | 210 | 8 | network/concurrency imports: ['subprocess'] |
| cognitive_budgeter | RUST | 208 | 3 | deterministic/state domain |
| canary_p0_04 | TEST-INFRA | 207 | 8 | canary/test module |
| forge_client | GO | 203 | 19 | network/concurrency imports: ['urllib']; reactive/initiating domain |
| dependency_tracker | RUST | 202 | 5 | no network/reactive markers — pure compute defaults to the deterministic backend |
| canary_context_broker | TEST-INFRA | 199 | 10 | canary/test module |
| canary_p0_05 | TEST-INFRA | 199 | 16 | canary/test module |
| testing_mode | RUST | 196 | 13 | no network/reactive markers — pure compute defaults to the deterministic backend |
| parallel_processor | GO | 194 | 17 | network/concurrency imports: ['threading'] |
| canary_spine_meta | TEST-INFRA | 192 | 7 | canary/test module |
| iteration | RUST | 190 | 8 | no network/reactive markers — pure compute defaults to the deterministic backend |
| verification_layer | RUST | 189 | 12 | deterministic/state domain |
| subconscious_pass | RUST | 187 | 6 | deterministic/state domain |
| ffi_query_memory_smoke | RUST | 182 | 5 | deterministic/state domain |
| tier_manager | RUST | 182 | 9 | deterministic/state domain |
| canary_tui_transport | TEST-INFRA | 174 | 7 | canary/test module |
| behaviour_ledger | RUST | 173 | 13 | deterministic/state domain |
| construction_commands | RUST | 173 | 4 | deterministic/state domain |
| canary_tui_drift | TEST-INFRA | 170 | 2 | canary/test module |
| intent_continuity | RUST | 168 | 11 | no network/reactive markers — pure compute defaults to the deterministic backend |
| rune_profiler | RUST | 165 | 3 | no network/reactive markers — pure compute defaults to the deterministic backend |
| inference_trace | RUST | 164 | 6 | no network/reactive markers — pure compute defaults to the deterministic backend |
| ms_loader | RUST | 163 | 16 | no network/reactive markers — pure compute defaults to the deterministic backend |
| subsystem_health | GO | 163 | 8 | network/concurrency imports: ['threading'] |
| recall_router | GO | 162 | 3 | reactive/initiating domain |
| voicebox | GO | 161 | 11 | network/concurrency imports: ['subprocess']; reactive/initiating domain |
| canary_gitea_pipeline | TEST-INFRA | 159 | 2 | canary/test module |
| forge | GO | 146 | 12 | network/concurrency imports: ['subprocess', 'urllib'] |
| rune_graphic | TUI-LAST | 145 | 5 | textual/rich imports: ['rich'] |
| connection_pool | GO | 144 | 9 | sqlite3 (storage-backed); network/concurrency imports: ['threading'] |
| recall_uplift | RUST | 141 | 1 | sqlite3 (storage-backed) |
| project_registry | RUST | 139 | 12 | no network/reactive markers — pure compute defaults to the deterministic backend |
| construction_depth | RUST | 138 | 2 | deterministic/state domain |
| voice_injector | GO | 138 | 5 | reactive/initiating domain |
| canary_rune_entry | TEST-INFRA | 137 | 3 | canary/test module |
| construction_projection | RUST | 135 | 3 | deterministic/state domain |
| rune_spinner | RUST | 130 | 5 | no network/reactive markers — pure compute defaults to the deterministic backend |
| tui_backlog | TUI-LAST | 125 | 5 | tui family |
| warm_neuron_store | RUST | 122 | 6 | sqlite3 (storage-backed); deterministic/state domain |
| drift_resume | RUST | 118 | 3 | deterministic/state domain |
| wait_guard | RUST | 116 | 1 | no network/reactive markers — pure compute defaults to the deterministic backend |
| canary_conversation_state | TEST-INFRA | 115 | 2 | canary/test module |
| canary_query_memory | TEST-INFRA | 110 | 2 | canary/test module |
| failure_recovery | RUST | 108 | 1 | no network/reactive markers — pure compute defaults to the deterministic backend |
| graph_projection | RUST | 108 | 5 | deterministic/state domain |
| tui_transport | TUI-LAST | 108 | 12 | tui family |
| spine_loader | RUST | 105 | 7 | deterministic/state domain |
| canary_unbounded | TEST-INFRA | 104 | 2 | canary/test module |
| message_widgets | TUI-LAST | 104 | 8 | textual/rich imports: ['rich', 'textual'] |
| cs_loader | RUST | 90 | 5 | no network/reactive markers — pure compute defaults to the deterministic backend |
| tc_loader | RUST | 88 | 5 | no network/reactive markers — pure compute defaults to the deterministic backend |
| canary_schema_migration | TEST-INFRA | 86 | 1 | canary/test module |
| programme_gates | RUST | 85 | 6 | no network/reactive markers — pure compute defaults to the deterministic backend |
| structural_migration | RUST | 77 | 4 | deterministic/state domain |
| memory_env | RETIRES | 72 | 8 | bootstrap glue subsumed by the Go runtime env contract |
| impact_preview | RUST | 69 | 2 | no network/reactive markers — pure compute defaults to the deterministic backend |
| provider_context | GO | 48 | 1 | reactive/initiating domain |
| wait_guard_corpus | RUST | 46 | 0 | no network/reactive markers — pure compute defaults to the deterministic backend |
| canary_cognitive_strata | TEST-INFRA | 44 | 1 | canary/test module |
| builder_bridge_cli | GO | 42 | 1 | reactive/initiating domain |
| route_risk_flags | RUST | 37 | 1 | no network/reactive markers — pure compute defaults to the deterministic backend |
| canary_memory_synthesis | TEST-INFRA | 33 | 1 | canary/test module |
| canary_resource_authority | TEST-INFRA | 33 | 1 | canary/test module |
| canary_surface_parity | TEST-INFRA | 33 | 1 | canary/test module |
| canary_orchestration_foundation | TEST-INFRA | 32 | 1 | canary/test module |
| canary_gitea_delegation | TEST-INFRA | 31 | 1 | canary/test module |
| __main__ | RETIRES | 21 | 1 | bootstrap glue subsumed by the Go runtime env contract |
+2 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "rune"
version = "0.88.4"
version = "0.99.6"
description = "Rune — a stateful coding assistant with persistent memory, orchestration, and TUI"
requires-python = ">=3.10"
@@ -16,6 +16,7 @@ dependencies = [
"aiohttp>=3.9.0",
"websockets>=12.0",
"psutil>=5.9.0",
"pillow>=10.0",
"pyyaml>=6.0",
]
+5 -1
View File
@@ -29,6 +29,10 @@
"**/__pycache__"
],
"extraPaths": [
"scripts"
"scripts",
"src/rune",
"shared",
"src/rust/evidence_provenance",
"src/rust/query_memory"
]
}
+6 -4
View File
@@ -2,7 +2,7 @@
# uv pip compile pyproject.toml -o requirements-ci.txt --extra dev --extra memory --python-version 3.11
aiohappyeyeballs==2.7.1
# via aiohttp
aiohttp==3.14.3
aiohttp==3.14.4
# via rune (pyproject.toml)
aiosignal==1.4.0
# via aiohttp
@@ -50,7 +50,7 @@ markdown-it-py==4.2.0
# mdit-py-plugins
# rich
# textual
markupsafe==3.0.3
markupsafe==3.0.4
# via jinja2
mdit-py-plugins==0.6.1
# via textual
@@ -64,7 +64,9 @@ nodeenv==1.11.0
# via pyright
packaging==26.3
# via pytest
platformdirs==4.12.2
pillow==12.3.0
# via rune (pyproject.toml)
platformdirs==4.12.3
# via textual
pluggy==1.6.0
# via
@@ -116,7 +118,7 @@ typing-extensions==4.16.0
# pyright
# textual
# textual-autocomplete
websockets==17.1
websockets==17.2
# via rune (pyproject.toml)
yarl==1.25.1
# via aiohttp
+3 -4
View File
@@ -50,7 +50,7 @@ SRC_A = {
"memory_store": "memory", "memory_selector": "memory",
"memory_consolidation": "memory", "memory_env": "memory",
"shard_manager": "memory", "tier_manager": "memory",
"graph_memory": "memory", "query_memory": "memory",
"graph_memory": "memory",
"curator": "memory", "sleep_time": "memory",
"subconscious_pass": "memory", "warm_neuron_store": "memory",
"warm_router": "memory", "abstraction": "memory",
@@ -166,14 +166,12 @@ 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",
@@ -182,7 +180,8 @@ 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"}
"graph_shard_bridge", "growth_simulator", "query_memory",
"umbrella_update"}
def main() -> int:
+13 -1
View File
@@ -47,7 +47,19 @@ def main() -> int:
p for p in env.get("PATH", "").split(os.pathsep)
if "ci-venv" not in p and ".type-venv" not in p and "venv/bin" not in p)
result = subprocess.run(
cmd + [str(ROOT / "src" / "rune"), "--outputjson"],
# §16V AMBIENT-INTERPRETER CLOSURE (T7.3 slice-2 CI repair, 2026-10-03):
# venv-signal stripping fixed ACTIVATION drift, but dependency
# resolution still followed the AMBIENT host interpreter — runner
# (py311, no rich/Pillow) vs desktop (3.14 system site with both)
# produced 460 vs 454 on the identical tree, and any import-level
# ignore flips to reportUnnecessaryTypeIgnoreComment in the other
# environment. Pin --pythonpath to the INVOKING interpreter (the
# venv running this script) so dependency resolution follows the
# installed lock, not the host: CI's .type-venv has the locked deps;
# local preflight must run the ratchet from a venv built from
# requirements-ci.txt (same lock) — that is now the contract.
cmd + [str(ROOT / "src" / "rune"), "--outputjson",
"--pythonpath", sys.executable],
capture_output=True,
text=True,
timeout=600,
-210
View File
@@ -1,210 +0,0 @@
#!/usr/bin/env python3
"""audit_ledger.py — HMAC-chained tamper-evident promotion ledger.
Every promotion decision is recorded in a hash-chained ledger.
Tampering with any entry breaks the chain.
Research basis:
- PoEM (arXiv:2608.16032): HMAC-chained tamper-evident execution ledger
- Autogenesis (arXiv:2604.15034): versioned interfaces, auditable lineage
Run under ~/.venvs/memory/bin/python3.
"""
import hashlib
import hmac
import json
import os
import sys
from datetime import datetime, timezone
from typing import Dict, List, Optional
LEDGER_FILE = os.path.expanduser("~/.config/opencode/memory/promotion_ledger.jsonl")
SECRET_KEY = os.environ.get("AUDIT_LEDGER_KEY", "rune-drift-integrity-2024")
def _compute_hash(prev_hash: str, entry: dict) -> str:
"""Compute HMAC chain hash for a ledger entry."""
payload = json.dumps(entry, sort_keys=True).encode()
msg = (prev_hash + payload.decode()).encode()
return hmac.new(SECRET_KEY.encode(), msg, hashlib.sha256).hexdigest()
def append_entry(entry_type: str, entry_id: int, details: dict) -> dict:
"""Append an entry to the promotion ledger.
Returns the ledger entry with chain hash.
"""
# Read last hash
prev_hash = "genesis"
if os.path.exists(LEDGER_FILE):
with open(LEDGER_FILE, "r") as f:
lines = f.readlines()
if lines:
try:
last = json.loads(lines[-1])
prev_hash = last.get("hash", "genesis")
except json.JSONDecodeError:
prev_hash = "genesis"
entry = {
"type": entry_type,
"entry_id": entry_id,
"details": details,
"timestamp": datetime.now(timezone.utc).isoformat(),
"prev_hash": prev_hash,
}
# Compute chain hash
entry["hash"] = _compute_hash(prev_hash, entry)
# Append to ledger
os.makedirs(os.path.dirname(LEDGER_FILE), exist_ok=True)
with open(LEDGER_FILE, "a") as f:
f.write(json.dumps(entry) + "\n")
return entry
def verify_chain() -> dict:
"""Verify the integrity of the entire ledger chain.
Returns dict with verification result.
"""
if not os.path.exists(LEDGER_FILE):
return {"valid": True, "entries": 0, "reason": "empty ledger"}
with open(LEDGER_FILE, "r") as f:
lines = f.readlines()
prev_hash = "genesis"
for i, line in enumerate(lines):
try:
entry = json.loads(line)
except json.JSONDecodeError:
return {"valid": False, "entries": i, "reason": f"invalid JSON at line {i+1}"}
# Verify prev_hash chain
if entry.get("prev_hash") != prev_hash:
return {
"valid": False,
"entries": i,
"reason": f"chain broken at line {i+1}: expected prev_hash={prev_hash}, got {entry.get('prev_hash')}"
}
# Verify HMAC
stored_hash = entry.get("hash")
computed = _compute_hash(prev_hash, {k: v for k, v in entry.items() if k != "hash"})
if stored_hash != computed:
return {
"valid": False,
"entries": i,
"reason": f"HMAC mismatch at line {i+1}: stored={stored_hash[:16]}..., computed={computed[:16]}..."
}
prev_hash = stored_hash
return {"valid": True, "entries": len(lines), "reason": "chain intact"}
def get_entries(limit: int = 50) -> List[dict]:
"""Get recent ledger entries."""
if not os.path.exists(LEDGER_FILE):
return []
with open(LEDGER_FILE, "r") as f:
lines = f.readlines()
entries = []
for line in lines[-limit:]:
try:
entries.append(json.loads(line))
except json.JSONDecodeError:
continue
return entries
def log_promotion(entry_id: int, old_status: str, new_status: str, wash_count: int, reason: str) -> dict:
"""Log a promotion decision."""
return append_entry("promotion", entry_id, {
"old_status": old_status,
"new_status": new_status,
"wash_count": wash_count,
"reason": reason,
})
def log_gate_result(entry_id: int, verdict: str, critics: dict) -> dict:
"""Log a pre-commit gate result."""
return append_entry("gate", entry_id, {
"verdict": verdict,
"critics": critics,
})
def log_drift_alert(alert_type: str, message: str, severity: str) -> dict:
"""Log a drift alert."""
return append_entry("alert", 0, {
"alert_type": alert_type,
"message": message,
"severity": severity,
})
# ═══════════════════════════════════════════════════════════════════════════════
# CLI
# ═══════════════════════════════════════════════════════════════════════════════
def main():
import argparse
parser = argparse.ArgumentParser(description="Promotion audit ledger")
sub = parser.add_subparsers(dest="cmd")
# verify
sub.add_parser("verify", help="Verify ledger chain integrity")
# list [--limit N]
p_list = sub.add_parser("list", help="Show recent ledger entries")
p_list.add_argument("--limit", type=int, default=20)
# log-promotion <entry_id> <old> <new> <wash_count> <reason>
p_log = sub.add_parser("log-promotion", help="Log a promotion")
p_log.add_argument("entry_id", type=int)
p_log.add_argument("old_status")
p_log.add_argument("new_status")
p_log.add_argument("wash_count", type=int)
p_log.add_argument("reason")
args = parser.parse_args()
if not args.cmd:
parser.print_help()
sys.exit(1)
if args.cmd == "verify":
result = verify_chain()
print(f"Chain valid: {result['valid']}")
print(f"Entries: {result['entries']}")
print(f"Reason: {result['reason']}")
elif args.cmd == "list":
entries = get_entries(args.limit)
if entries:
print(f"Recent ledger entries ({len(entries)}):")
for e in entries:
print(f" [{e.get('type')}] entry_id={e.get('entry_id')} @ {e.get('timestamp', '')[:19]}")
details = e.get('details', {})
if e.get('type') == 'promotion':
print(f" {details.get('old_status')} → {details.get('new_status')} (wash={details.get('wash_count')})")
elif e.get('type') == 'gate':
print(f" verdict={details.get('verdict')}")
else:
print("No ledger entries.")
elif args.cmd == "log-promotion":
entry = log_promotion(args.entry_id, args.old_status, args.new_status, args.wash_count, args.reason)
print(f"Logged: {entry['hash'][:16]}...")
if __name__ == "__main__":
main()
+60 -134
View File
@@ -1,17 +1,25 @@
#!/usr/bin/env python3
"""query_memory.py — Query memory system for adaptive retrieval.
The system remembers what kinds of queries it gets, what retrieval
strategy worked, and adapts its gate logic accordingly.
AUTHORITY-SWITCHED (Phase G strangler slice 1, doctrine step 8): the
classification core now lives in Rust (`src/rust/query_memory/src/lib.rs`,
cdylib `libquery_memory.so`). This module is a thin ctypes-backed shim over
`qm_loader.py` serving the FULL historical Python class API — the deployed
in-process consumers (`memory_store.py`, `growth_simulator.py`) call
`QueryMemory()` exactly as before and get the Rust core.
This prevents dead weight — memories get tested based on usage
patterns, not just random exploration.
Parity evidence: FFI-gap parity EXACT-EQUAL on `get_topic_stats` /
`get_dead_weight_topics`; verification wall green (FFI smoke 41/41, parity
harness ALL GREEN, Python canary 12/12, Rust canary 27/27); shadow comparison
46/46 exact-equal vs the live production store.
Research basis:
- IGMiRAG (arXiv:2602.07525): Intuition-guided retrieval
- Task-Adaptive Retrieval (arXiv:2604.07863): Learning from interaction history
- MemCon (arXiv:2607.13591): Learned policy for consolidation/forgetting
- Agent-Native Memory (arXiv:2606.24775): Workload-adaptive routing
The Python implementation this replaces is preserved in git history on branch
`phase-g-strangler-retrieval-index` (pre-shim commit); per deletion discipline
it is not kept as a silent fallback — a missing cdylib fails loudly.
Store resolution is unchanged: `$HOME/.config/opencode/memory/query_memory`
(the Rust core resolves `$HOME` natively; history.jsonl / topic_stats.json
formats are byte-parity-proven).
Run under ~/.venvs/memory/bin/python3.
"""
@@ -19,15 +27,14 @@ Run under ~/.venvs/memory/bin/python3.
import json
import os
import sys
from collections import Counter, defaultdict
from datetime import datetime, timezone
from typing import Dict, List, Optional, Tuple
QUERY_MEMORY_DIR = os.path.expanduser("~/.config/opencode/memory/query_memory")
QUERY_HISTORY_FILE = os.path.join(QUERY_MEMORY_DIR, "history.jsonl")
TOPIC_STATS_FILE = os.path.join(QUERY_MEMORY_DIR, "topic_stats.json")
# Topic keywords for classification
# Topic keywords (classification table — tie-break law: insertion order is
# part of the contract; the Rust core carries the identical table).
TOPIC_KEYWORDS = {
"guitar": ["guitar", "music", "teaching", "course", "lesson", "chord", "scale"],
"coding": ["code", "python", "script", "function", "debug", "build", "deploy"],
@@ -41,17 +48,47 @@ TOPIC_KEYWORDS = {
"creative": ["write", "design", "create", "build", "make", "art", "music"],
}
# Where the Rust core (qm_loader.py + target/release/libquery_memory.so) lives.
_QM_CORE_DIR = os.environ.get(
"QM_CORE_DIR", "/mnt/workdrive/rune/src/rust/query_memory"
)
class QueryMemory:
"""Query memory system for adaptive retrieval."""
if _QM_CORE_DIR not in sys.path:
sys.path.insert(0, _QM_CORE_DIR)
try:
from qm_loader import QueryMemory as _RustQueryMemory
except ImportError as _e: # pragma: no cover - deployment integrity failure
raise ImportError(
f"query_memory shim: cannot load the Rust core from {_QM_CORE_DIR!r} "
f"({_e}). The cdylib must be built: `cargo build --release` in "
f"{_QM_CORE_DIR}. No Python fallback exists by design."
) from _e
class QueryMemory(_RustQueryMemory):
"""Query memory system for adaptive retrieval (Rust core, Python API).
API (identical to the historical Python implementation):
* classify_query(query) -> str
* adaptive_exploration_rate(query) -> float
* record_query(query, strategy, success) -> None
* get_topic_stats() -> dict (topic -> stats, insertion order)
* get_dead_weight_topics() -> list[str]
* get_health_report() -> dict
"""
def __init__(self):
# Match the historical constructor: ensure the store dir exists,
# resolve the store from $HOME (qm_new's native behaviour).
os.makedirs(QUERY_MEMORY_DIR, exist_ok=True)
self.history: List[dict] = self._load_history()
self.topic_stats: Dict[str, dict] = self._load_topic_stats()
super().__init__()
# Historical in-memory attribute: query history loaded at construction,
# appended by record_query. (canary check 2 reads len(qm.history).)
self.history = self._load_history()
def _load_history(self) -> List[dict]:
"""Load query history from disk."""
def _load_history(self):
"""Load query history from disk (historical shape: list of dicts)."""
if not os.path.exists(QUERY_HISTORY_FILE):
return []
entries = []
@@ -63,127 +100,16 @@ class QueryMemory:
continue
return entries
def _save_history(self, entry: dict):
"""Append a query entry to history."""
with open(QUERY_HISTORY_FILE, "a") as f:
f.write(json.dumps(entry) + "\n")
def _load_topic_stats(self) -> Dict[str, dict]:
"""Load topic statistics from disk."""
if os.path.exists(TOPIC_STATS_FILE):
try:
with open(TOPIC_STATS_FILE) as f:
return json.load(f)
except json.JSONDecodeError:
return {}
return {}
def _save_topic_stats(self):
"""Save topic statistics to disk."""
with open(TOPIC_STATS_FILE, "w") as f:
json.dump(self.topic_stats, f, indent=2)
def classify_query(self, query: str) -> str:
"""Classify a query into a topic.
Returns the topic with the most keyword matches.
Falls back to "general" if no strong match.
"""
query_lower = query.lower()
scores = {}
for topic, keywords in TOPIC_KEYWORDS.items():
score = sum(1 for kw in keywords if kw in query_lower)
if score > 0:
scores[topic] = score
if not scores:
return "general"
return max(scores, key=scores.get)
def record_query(self, query: str, strategy: str, success: bool):
"""Record a query and its outcome.
Args:
query: The query text
strategy: "consolidated" or "full_retrieval"
success: Whether the retrieval produced useful results
"""
topic = self.classify_query(query)
entry = {
"""Record through the Rust core, then mirror the historical in-memory append."""
super().record_query(query, strategy, success)
self.history.append({
"query": query[:100],
"topic": topic,
"topic": self.classify_query(query),
"strategy": strategy,
"success": success,
"timestamp": datetime.now(timezone.utc).isoformat(),
}
self.history.append(entry)
self._save_history(entry)
# Update topic stats
if topic not in self.topic_stats:
self.topic_stats[topic] = {
"total_queries": 0,
"successful_queries": 0,
"consolidated_hits": 0,
"full_retrieval_hits": 0,
"exploration_rate": 0.30, # default
}
stats = self.topic_stats[topic]
stats["total_queries"] += 1
if success:
stats["successful_queries"] += 1
if strategy == "consolidated":
stats["consolidated_hits"] += 1
else:
stats["full_retrieval_hits"] += 1
# Adaptive exploration rate: decrease for well-tested topics
if stats["total_queries"] >= 10:
success_rate = stats["successful_queries"] / stats["total_queries"]
if success_rate > 0.8:
stats["exploration_rate"] = max(0.10, stats["exploration_rate"] - 0.05)
elif success_rate < 0.5:
stats["exploration_rate"] = min(0.50, stats["exploration_rate"] + 0.05)
self._save_topic_stats()
def get_topic_stats(self) -> Dict[str, dict]:
"""Get topic statistics."""
return self.topic_stats
def adaptive_exploration_rate(self, query: str) -> float:
"""Get the exploration rate for a query based on its topic.
Well-tested topics have lower exploration rates.
Untested topics have higher exploration rates.
"""
topic = self.classify_query(query)
if topic in self.topic_stats:
return self.topic_stats[topic].get("exploration_rate", 0.30)
return 0.50 # untested topics get high exploration
def get_dead_weight_topics(self) -> List[str]:
"""Get topics with low proven uses (dead weight)."""
dead = []
for topic, stats in self.topic_stats.items():
if stats["total_queries"] < 5:
dead.append(topic)
return dead
def get_health_report(self) -> dict:
"""Get a health report of the query memory system."""
total_queries = len(self.history)
topics = len(self.topic_stats)
dead_topics = len(self.get_dead_weight_topics())
return {
"total_queries": total_queries,
"topics_tracked": topics,
"dead_weight_topics": dead_topics,
"topic_stats": self.topic_stats,
}
})
# ═══════════════════════════════════════════════════════════════════════════════
+3
View File
@@ -0,0 +1,3 @@
module rune-entry
go 1.27.1
+172
View File
@@ -0,0 +1,172 @@
// Command rune is the Go first-call entry for the Rune runtime (Phase H1).
//
// Directive (USER-LOCKED 2026-10-04): Go owns the front end — runtime entry
// points, the first-call surface, user-facing calls. This binary is the
// transitional first-call shim: it replicates the EXACT contract of the
// deployed bash launcher (~/.local/bin/rune) — same cwd, same env, same
// handoff — and exec-replaces itself with the Python runtime so exit codes
// and signals pass through 1:1. The Python core shrinks behind this surface
// as the back-end ports land.
//
// Contract (H1 canary, observed from the deployed launcher — not invented):
// cwd: /mnt/workdrive/rune-code/current/src (the deployed revision)
// env: RUNE_HOME=/mnt/workdrive/rune
// RUNE_MEMORY_DIR=/home/nick/.config/opencode/memory
// MIMO_PROBE_ENABLED=1
// handoff: ~/.venvs/memory/bin/python3 -m rune [args...]
//
// Flags consumed by the Go layer (everything else passes through):
// --version print "rune <version>" (deployed pyproject), exit 0
// --python-only print the resolved handoff command, exit 0
// --help usage, exit 0
// unknown flag usage on stderr, exit 2
package main
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"syscall"
)
const usage = `rune — Go first-call entry (Phase H1 transitional shim)
usage:
rune [args...] hand off to the Python runtime (deployed revision)
rune serve H2 front door: /v1 surface proxying the Python core
rune scaffold R2 construction protocol initiation (Go start / Rust core)
rune --version print the runtime version
rune --python-only print the resolved Python handoff command and exit
rune --help this text
Environment contract (same as the deployed bash launcher):
cwd /mnt/workdrive/rune-code/current/src
RUNE_HOME data home (stable across code deploys)
RUNE_MEMORY_DIR memory dir
MIMO_PROBE_ENABLED=1
Explicitly-set RUNE_HOME / RUNE_MEMORY_DIR in the ambient environment win.
`
const (
deployRoot = "/mnt/workdrive/rune-code"
dataHome = "/mnt/workdrive/rune"
defaultMemDir = "/home/nick/.config/opencode/memory"
)
// deployedSrc resolves the runtime source dir: the deployed current
// revision when it exists (the launcher's authority), else the binary's own
// repo checkout (development runs), else the desktop default.
func deployedSrc() string {
current := filepath.Join(deployRoot, "current", "src")
if st, err := os.Stat(filepath.Join(current, "rune", "__main__.py")); err == nil && !st.IsDir() {
return current
}
if exe, err := os.Executable(); err == nil {
dir := filepath.Dir(exe)
for i := 0; i < 8; i++ {
cand := filepath.Join(dir, "src")
if st, err := os.Stat(filepath.Join(cand, "rune", "__main__.py")); err == nil && !st.IsDir() {
return cand
}
dir = filepath.Dir(dir)
}
}
return filepath.Join(dataHome, "src")
}
func version(srcDir string) string {
data, err := os.ReadFile(filepath.Join(filepath.Dir(srcDir), "pyproject.toml"))
if err != nil {
return "unknown"
}
const prefix = `version = "`
for _, line := range strings.Split(string(data), "\n") {
if rest, ok := strings.CutPrefix(line, prefix); ok {
if end := strings.IndexByte(rest, '"'); end > 0 {
return rest[:end]
}
}
}
return "unknown"
}
func pythonBin() string {
cand := filepath.Join(os.Getenv("HOME"), ".venvs", "memory", "bin", "python3")
if _, err := os.Stat(cand); err == nil {
return cand
}
return "python3"
}
func upsertEnv(env []string, key, val string) []string {
prefix := key + "="
for i, e := range env {
if strings.HasPrefix(e, prefix) {
env[i] = prefix + val
return env
}
}
return append(env, prefix+val)
}
func launchEnv(base []string) []string {
env := base
if os.Getenv("RUNE_HOME") == "" {
env = upsertEnv(env, "RUNE_HOME", dataHome)
}
if os.Getenv("RUNE_MEMORY_DIR") == "" {
env = upsertEnv(env, "RUNE_MEMORY_DIR", defaultMemDir)
}
env = upsertEnv(env, "MIMO_PROBE_ENABLED", "1")
return env
}
func main() {
args := os.Args[1:]
// H2: the front-door subcommand owns the /v1 surface (no Python handoff).
if len(args) > 0 && args[0] == "serve" {
os.Exit(runServe(args[1:]))
}
// R2 Wave 1: the scaffolding initiation surface (Go starts, Rust executes).
if len(args) > 0 && args[0] == "scaffold" {
os.Exit(runScaffold(args[1:]))
}
srcDir := deployedSrc()
for _, a := range args {
switch a {
case "--help", "-h":
fmt.Print(usage)
return
case "--version":
fmt.Printf("rune %s\n", version(srcDir))
return
case "--python-only":
fmt.Printf("(cd %s && %s -m rune %s)\n", srcDir, pythonBin(), strings.Join(args[1:], " "))
return
default:
if strings.HasPrefix(a, "-") {
fmt.Fprintf(os.Stderr, "rune: unknown flag %q\n\n%s", a, usage)
os.Exit(2)
}
}
}
argv := append([]string{pythonBin(), "-m", "rune"}, args...)
bin, err := exec.LookPath(argv[0])
if err != nil {
fmt.Fprintf(os.Stderr, "rune: cannot find python runtime %q: %v\n", argv[0], err)
os.Exit(127)
}
env := launchEnv(os.Environ())
// chdir before exec: the launcher's cwd contract (the deployed src dir
// makes the rune package importable for `-m rune`).
if err := syscall.Chdir(srcDir); err != nil {
fmt.Fprintf(os.Stderr, "rune: cannot enter %s: %v\n", srcDir, err)
os.Exit(126)
}
if err := syscall.Exec(bin, argv, env); err != nil {
fmt.Fprintf(os.Stderr, "rune: handoff failed: %v\n", err)
os.Exit(126)
}
}
+211
View File
@@ -0,0 +1,211 @@
package main
import (
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
"time"
)
func TestVersionReadsPyprojectBesideSrc(t *testing.T) {
root := t.TempDir()
if err := os.MkdirAll(filepath.Join(root, "src", "rune"), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(root, "pyproject.toml"),
[]byte("[project]\nversion = \"0.99.1\"\n"), 0o644); err != nil {
t.Fatal(err)
}
if got := version(filepath.Join(root, "src")); got != "0.99.1" {
t.Fatalf("want 0.99.1, got %q", got)
}
}
func TestVersionUnknownWhenMissing(t *testing.T) {
if got := version("/nonexistent-src"); got != "unknown" {
t.Fatalf("missing root -> unknown, got %q", got)
}
}
func TestUpsertEnv(t *testing.T) {
env := []string{"A=1", "RUNE_HOME=/old"}
got := upsertEnv(env, "RUNE_HOME", "/new")
if got[1] != "RUNE_HOME=/new" {
t.Fatalf("existing key replaced, got %v", got)
}
got = upsertEnv([]string{"A=1"}, "B", "2")
if got[len(got)-1] != "B=2" || len(got) != 2 {
t.Fatalf("new key appended, got %v", got)
}
}
func TestLaunchEnvContract(t *testing.T) {
t.Setenv("RUNE_HOME", "")
t.Setenv("RUNE_MEMORY_DIR", "")
env := launchEnv(nil)
joined := strings.Join(env, "\n")
for _, want := range []string{
"RUNE_HOME=/mnt/workdrive/rune",
"RUNE_MEMORY_DIR=/home/nick/.config/opencode/memory",
"MIMO_PROBE_ENABLED=1",
} {
if !strings.Contains(joined, want) {
t.Fatalf("launch env missing %q\ngot:\n%s", want, joined)
}
}
}
func TestLaunchEnvHonorsExplicitOverrides(t *testing.T) {
t.Setenv("RUNE_HOME", "/tmp/custom-home")
env := launchEnv([]string{"RUNE_HOME=/tmp/custom-home"})
joined := strings.Join(env, "\n")
if !strings.Contains(joined, "RUNE_HOME=/tmp/custom-home") {
t.Fatalf("explicit RUNE_HOME must win, got:\n%s", joined)
}
if strings.Contains(joined, "RUNE_HOME=/mnt/workdrive/rune") {
t.Fatalf("default must NOT override explicit env, got:\n%s", joined)
}
}
func TestPythonBinFallsBack(t *testing.T) {
t.Setenv("HOME", "/nonexistent-home")
if got := pythonBin(); got != "python3" {
t.Fatalf("missing venv -> python3 on PATH, got %q", got)
}
}
func TestDeployedSrcPrefersLiveCurrent(t *testing.T) {
// The deployed current revision exists on this host (the launcher
// contract) — the shim must resolve to it, not to a dev checkout.
got := deployedSrc()
if !strings.HasPrefix(got, "/mnt/workdrive/rune-code/current") &&
!strings.Contains(got, "/src") {
t.Fatalf("deployed current must win, got %q", got)
}
}
// ── H2 front door (serve.go) ────────────────────────────────────────────────
func TestServeHelpExitsZero(t *testing.T) {
if rc := runServe([]string{"--help"}); rc != 0 {
t.Fatalf("serve --help must exit 0, got %d", rc)
}
}
func TestServeUnknownFlagExitsTwo(t *testing.T) {
if rc := runServe([]string{"--bogus"}); rc != 2 {
t.Fatalf("unknown serve flag must exit 2, got %d", rc)
}
}
func TestFrontDoorNotFoundEnvelope(t *testing.T) {
f := &frontDoor{core: "http://127.0.0.1:1"}
req := httptest.NewRequest("GET", "/v1/nope", nil)
rec := httptest.NewRecorder()
f.mux().ServeHTTP(rec, req)
if rec.Code != http.StatusNotFound {
t.Fatalf("unknown route must 404, got %d", rec.Code)
}
var body map[string]map[string]string
if err := json.Unmarshal(rec.Body.Bytes(), &body); err != nil {
t.Fatalf("404 body must be the error envelope: %v", err)
}
if body["error"]["code"] != "not_found" {
t.Fatalf("404 code must be not_found, got %q", body["error"]["code"])
}
}
func TestFrontDoorCoreDownEnvelope(t *testing.T) {
// Nothing listening on port 1 — the proxy must answer 502
// core_unavailable, not hang and not 500.
f := &frontDoor{core: "http://127.0.0.1:1",
client: &http.Client{Timeout: 2 * time.Second}}
req := httptest.NewRequest("POST", "/v1/chat/completions",
strings.NewReader(`{"messages":[{"role":"user","content":"x"}]}`))
rec := httptest.NewRecorder()
f.mux().ServeHTTP(rec, req)
if rec.Code != http.StatusBadGateway {
t.Fatalf("core down must 502, got %d", rec.Code)
}
var body map[string]map[string]string
if err := json.Unmarshal(rec.Body.Bytes(), &body); err != nil {
t.Fatalf("502 body must be the error envelope: %v", err)
}
if body["error"]["code"] != "core_unavailable" {
t.Fatalf("502 code must be core_unavailable, got %q", body["error"]["code"])
}
}
func TestFrontDoorProxiesByteForByte(t *testing.T) {
// A canned upstream: the front door must pass status + body through
// verbatim (the H2 parity law in its strongest form).
upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
fmt.Fprint(w, `{"object":"list","data":[]}`)
}))
defer upstream.Close()
f := &frontDoor{core: upstream.URL, client: upstream.Client()}
req := httptest.NewRequest("GET", "/v1/models", nil)
rec := httptest.NewRecorder()
f.mux().ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("proxy must pass status through, got %d", rec.Code)
}
if rec.Body.String() != `{"object":"list","data":[]}` {
t.Fatalf("proxy must pass the body byte-for-byte, got %q", rec.Body.String())
}
}
// ── R2 Wave 1: scaffold initiation surface ──────────────────────────────────
func TestScaffoldHelpExitsZero(t *testing.T) {
if rc := runScaffold([]string{"--help"}); rc != 0 {
t.Fatalf("scaffold --help must exit 0, got %d", rc)
}
}
func TestScaffoldUnknownFlagExitsTwo(t *testing.T) {
if rc := runScaffold([]string{"--bogus"}); rc != 2 {
t.Fatalf("unknown scaffold flag must exit 2, got %d", rc)
}
}
func TestScaffoldCoreCallEnvelope(t *testing.T) {
// The membrane junction with a stub binary emitting a canned envelope.
dir := t.TempDir()
stub := filepath.Join(dir, "scaffold_core")
script := "#!/bin/sh\ncat >/dev/null\nprintf '%s\n' '{\"ok\":true,\"value\":{\"legal\":true}}'\n"
if err := os.WriteFile(stub, []byte(script), 0o755); err != nil {
t.Fatal(err)
}
env, err := scaffoldCoreCall(stub, "check_transition",
map[string]string{"current": "draft", "target": "approved"})
if err != nil {
t.Fatalf("stub junction failed: %v", err)
}
if env["ok"] != true {
t.Fatalf("envelope ok must be true, got %v", env)
}
value, _ := env["value"].(map[string]any)
if value["legal"] != true {
t.Fatalf("value.legal must be true, got %v", value)
}
}
func TestScaffoldCoreCallFailureSurfaces(t *testing.T) {
dir := t.TempDir()
stub := filepath.Join(dir, "scaffold_core")
script := "#!/bin/sh\nexit 3\n"
if err := os.WriteFile(stub, []byte(script), 0o755); err != nil {
t.Fatal(err)
}
if _, err := scaffoldCoreCall(stub, "check_transition", map[string]string{}); err == nil {
t.Fatal("a failing core binary must surface an error, not a zero value")
}
}
+160
View File
@@ -0,0 +1,160 @@
// scaffold.go — the scaffolding initiation surface (R2 Wave 1, contract
// BUILDER-2026-10-04T15:25:33Z-cli). Go STARTS, Rust EXECUTES: pure
// operations (check-transition, close-quality) go straight to the
// scaffold_core binary over stdio NDJSON (the hybrid membrane's first
// contracted junction); store-backed operations proxy to the Python engine
// (whose own core is now Rust-delegated) until Wave 1's later slices port
// the contract store.
//
// Stdlib only. The binary path resolves from SC_RUST_BIN, else the repo
// crate target, else PATH.
package main
import (
"bytes"
"encoding/json"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
)
const scaffoldUsage = `rune scaffold — the construction protocol initiation surface (R2 Wave 1)
usage:
rune scaffold check-transition <current> <target> legality verdict (Rust core, direct)
rune scaffold close-quality <inputs.json> closure buckets (Rust core, direct)
rune scaffold <op> [args...] store-backed ops proxy to the engine:
submit-need, inspect-decision, scope,
obligations, observe, verify, transition, close
Go initiates; Rust executes the deterministic core; the Python engine
carries store-backed operations during the transition (strangler law).
`
// scaffoldCoreBin resolves the scaffold_core CLI binary.
func scaffoldCoreBin() string {
if p := os.Getenv("SC_RUST_BIN"); p != "" {
return p
}
if exe, err := os.Executable(); err == nil {
dir := filepath.Dir(exe)
for i := 0; i < 8; i++ {
cand := filepath.Join(dir, "src", "rust", "scaffold_core", "target", "release", "scaffold_core")
if st, err := os.Stat(cand); err == nil && !st.IsDir() {
return cand
}
dir = filepath.Dir(dir)
}
}
if p, err := exec.LookPath("scaffold_core"); err == nil {
return p
}
return ""
}
// scaffoldCoreCall runs one pure op through the Rust binary (NDJSON stdin
// -> envelope stdout). The envelope contract is the doc-envelope law.
func scaffoldCoreCall(bin, op string, arg any) (map[string]any, error) {
payload, err := json.Marshal(map[string]any{"op": op, "arg": arg})
if err != nil {
return nil, err
}
cmd := exec.Command(bin)
cmd.Stdin = bytes.NewReader(payload)
var out bytes.Buffer
cmd.Stdout = &out
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return nil, fmt.Errorf("scaffold_core %s failed: %w", op, err)
}
var env map[string]any
if err := json.Unmarshal(out.Bytes(), &env); err != nil {
return nil, fmt.Errorf("scaffold_core %s returned non-JSON: %w", op, err)
}
return env, nil
}
// scaffoldProxy hands a store-backed op to the Python engine (the
// transitional membrane — the engine's core is Rust-backed already).
func scaffoldProxy(args []string) int {
py := pythonBin()
argv := append([]string{py, "-m", "rune.construction_interface"}, args...)
cmd := exec.Command(argv[0], argv[1:]...)
cmd.Dir = deployedSrc()
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Stdin = os.Stdin
env := launchEnv(os.Environ())
cmd.Env = env
if err := cmd.Run(); err != nil {
fmt.Fprintf(os.Stderr, "rune scaffold: engine call failed: %v\n", err)
return 1
}
return 0
}
// runScaffold implements `rune scaffold`. Returns the process exit code.
func runScaffold(args []string) int {
if len(args) == 0 || args[0] == "--help" || args[0] == "-h" {
fmt.Print(scaffoldUsage)
return 0
}
switch args[0] {
case "check-transition":
if len(args) != 3 {
fmt.Fprintln(os.Stderr, "usage: rune scaffold check-transition <current> <target>")
return 2
}
bin := scaffoldCoreBin()
if bin == "" {
fmt.Fprintln(os.Stderr, "rune scaffold: scaffold_core binary not found (build the crate)")
return 127
}
env, err := scaffoldCoreCall(bin, "check_transition",
map[string]string{"current": args[1], "target": args[2]})
if err != nil {
fmt.Fprintln(os.Stderr, "rune scaffold:", err)
return 1
}
out, _ := json.Marshal(env)
fmt.Println(string(out))
return 0
case "close-quality":
if len(args) != 2 {
fmt.Fprintln(os.Stderr, "usage: rune scaffold close-quality <inputs.json>")
return 2
}
raw, err := os.ReadFile(args[1])
if err != nil {
fmt.Fprintln(os.Stderr, "rune scaffold: cannot read inputs:", err)
return 2
}
var inputs any
if err := json.Unmarshal(raw, &inputs); err != nil {
fmt.Fprintln(os.Stderr, "rune scaffold: inputs are not JSON:", err)
return 2
}
bin := scaffoldCoreBin()
if bin == "" {
fmt.Fprintln(os.Stderr, "rune scaffold: scaffold_core binary not found (build the crate)")
return 127
}
env, err := scaffoldCoreCall(bin, "close_quality", inputs)
if err != nil {
fmt.Fprintln(os.Stderr, "rune scaffold:", err)
return 1
}
out, _ := json.Marshal(env)
fmt.Println(string(out))
return 0
default:
if strings.HasPrefix(args[0], "-") {
fmt.Fprintf(os.Stderr, "rune scaffold: unknown flag %q\n\n%s", args[0], scaffoldUsage)
return 2
}
return scaffoldProxy(args)
}
}
+173
View File
@@ -0,0 +1,173 @@
// serve.go — H2 front door (T4.3, contract BUILDER-2026-10-04T08:32:43Z-cli).
//
// Go owns the front end (USER-LOCKED directive): this subcommand serves the
// OpenAI-compatible /v1 surface on 127.0.0.1 and proxies to the Python core
// sidecar (core_server.py) during the transition. Parity law: response
// bodies pass through byte-for-byte (the canary compares against the Python
// server modulo volatile id/created), and locally-generated errors use the
// same envelope the core emits: {"error": {"code", "message"}}.
//
// Stdlib only (net/http, io) — the plan's "stdlib net/http + NDJSON" line.
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"strings"
"time"
)
const serveUsage = `rune serve — H2 front door (OpenAI-compatible /v1 surface)
usage:
rune serve [--port N] [--core URL]
flags:
--port N listen port on 127.0.0.1 (default 8377)
--core URL Python core sidecar base URL (default http://127.0.0.1:8376)
Endpoints (identical shapes to the core — the parity battery enforces it):
GET /v1/models
GET /v1/status
POST /v1/chat/completions stream=true → NDJSON chunk lines
`
type frontDoor struct {
core string
client *http.Client
}
// errorEnvelope mirrors core_server._envelope_error exactly.
func errorEnvelope(w http.ResponseWriter, status int, code, message string) {
body, _ := json.Marshal(map[string]any{
"error": map[string]string{"code": code, "message": message},
})
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
_, _ = w.Write(body)
}
// proxy forwards one request to the core and streams the response back
// verbatim — byte-parity with the Python server is the H2 contract, and a
// pure pass-through is the strongest form of it.
func (f *frontDoor) proxy(w http.ResponseWriter, r *http.Request) {
var body io.Reader
if r.Body != nil {
raw, err := io.ReadAll(r.Body)
if err != nil {
errorEnvelope(w, http.StatusBadRequest, "bad_request", "unreadable body")
return
}
body = bytes.NewReader(raw)
}
req, err := http.NewRequest(r.Method, f.core+r.URL.Path, body)
if err != nil {
errorEnvelope(w, http.StatusBadRequest, "bad_request", err.Error())
return
}
req.Header.Set("Content-Type", r.Header.Get("Content-Type"))
resp, err := f.client.Do(req)
if err != nil {
errorEnvelope(w, http.StatusBadGateway, "core_unavailable",
"core sidecar unreachable: "+err.Error())
return
}
defer resp.Body.Close()
for k, vs := range resp.Header {
if strings.EqualFold(k, "Content-Type") {
w.Header().Set(k, vs[0])
}
}
w.WriteHeader(resp.StatusCode)
// NDJSON streaming: flush per write so chunk lines reach the caller as
// the core emits them (the framing is the contract).
flusher, canFlush := w.(http.Flusher)
buf := make([]byte, 4096)
for {
n, readErr := resp.Body.Read(buf)
if n > 0 {
if _, werr := w.Write(buf[:n]); werr != nil {
return
}
if canFlush {
flusher.Flush()
}
}
if readErr != nil {
return
}
}
}
func (f *frontDoor) mux() *http.ServeMux {
mux := http.NewServeMux()
handle := func(path string) {
mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != path {
errorEnvelope(w, http.StatusNotFound, "not_found",
"no route "+r.URL.Path)
return
}
f.proxy(w, r)
})
}
handle("/v1/models")
handle("/v1/status")
handle("/v1/chat/completions")
// H4 slice 1: the TUI endpoints (same pass-through law).
handle("/v1/conversation")
handle("/v1/approvals/resolve")
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
errorEnvelope(w, http.StatusNotFound, "not_found", "no route "+r.URL.Path)
})
return mux
}
// runServe implements `rune serve`. Returns the process exit code.
func runServe(args []string) int {
port := 8377
core := "http://127.0.0.1:8376"
for i := 0; i < len(args); i++ {
switch args[i] {
case "--help", "-h":
fmt.Print(serveUsage)
return 0
case "--port":
if i+1 >= len(args) {
fmt.Fprintln(os.Stderr, "rune serve: --port needs a value")
return 2
}
i++
if _, err := fmt.Sscanf(args[i], "%d", &port); err != nil {
fmt.Fprintf(os.Stderr, "rune serve: bad --port %q\n", args[i])
return 2
}
case "--core":
if i+1 >= len(args) {
fmt.Fprintln(os.Stderr, "rune serve: --core needs a value")
return 2
}
i++
core = strings.TrimSuffix(args[i], "/")
default:
fmt.Fprintf(os.Stderr, "rune serve: unknown flag %q\n\n%s", args[i], serveUsage)
return 2
}
}
f := &frontDoor{
core: core,
client: &http.Client{Timeout: 130 * time.Second},
}
addr := fmt.Sprintf("127.0.0.1:%d", port)
fmt.Printf("rune front door on %s (core %s)\n", addr, core)
if err := http.ListenAndServe(addr, f.mux()); err != nil {
fmt.Fprintf(os.Stderr, "rune serve: %v\n", err)
return 1
}
return 0
}
+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.88.4"
__version__ = "0.99.6"
# 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
+62 -2
View File
@@ -43,6 +43,42 @@ LOCAL_MODEL = "qwen2.5:3b"
OLLAMA_URL = os.environ.get("OLLAMA_URL", "http://localhost:11434")
_ABSTRACTION_STOPWORDS_FROZEN = frozenset(
"the a an is are was were be been being have has had do does did will "
"would could should may might shall can to of in for on with at by from "
"as into through during before after this that these those it its what "
"which who how when where why and or but not no".split()
)
def _ABSTRACTION_STOPWORDS() -> list:
"""Gather seam (W3.4): the fact-extraction stopword table as a sorted
list — data received by the kernel; identical filter on both engines."""
return sorted(_ABSTRACTION_STOPWORDS_FROZEN)
def _core():
"""The Rust cognition_core when available (strangler: Python fallback
load-bearing). RUNE_COGNITION_CORE=python pins the fallback."""
return None if cc_loader_pin() else _cc_probe()
def _cc_probe():
from rune import cc_loader as _cl
try:
return _cl if _cl.available() else None
except Exception: # noqa: BLE001, S110 — loader probe never crashes the fallback
return None
def cc_loader_pin() -> bool:
"""The env pin: RUNE_COGNITION_CORE=python → kernels OFF (shared probe).
"""
import os
return os.environ.get("RUNE_COGNITION_CORE", "auto") == "python"
def _now():
return datetime.now(timezone.utc).isoformat()
@@ -241,17 +277,41 @@ Extract 1-5 key facts. Be precise, not verbose."""
response = llm_call(prompt, max_tokens=800)
# Parse response
facts: list = []
try:
# Try to extract JSON from response
start = response.find("[")
end = response.rfind("]") + 1
if start >= 0 and end > start:
facts = json.loads(response[start:end])
else:
facts = []
except json.JSONDecodeError:
facts = []
# LLM unavailable / unparseable → DETERMINISTIC KERNEL FALLBACK
# (W3.4: cc_abstraction_extract_facts — sentence split, stopwords
# filtered, min-length gate; the same facts schema it already emits:
# text/words/content_words mapped onto what= text with empty dims).
if not facts:
core = _core()
if core is not None:
try:
from rune import cc_loader as _cl
raw = _cl.abstraction_extract_facts(
episode_text, _ABSTRACTION_STOPWORDS(), 10)
for item in raw or []:
text_frag = str(item.get("text", "")).strip()
if not text_frag:
continue
facts.append({
"what": text_frag, "when": "", "where": "", "who": "",
"why": "", "type": "experience", "entities": [],
"kernel": True,
"content_words": item.get("content_words"),
})
except Exception: # noqa: BLE001, S110 — kernel failure degrades to empty, intentional silence
pass
if not facts:
return {"extracted": 0, "error": "LLM did not return valid JSON", "raw": response[:200]}
+29 -33
View File
@@ -15,6 +15,17 @@ Usage:
behavior.py reinforce <id> [--outcome success|failure]
behavior.py list [--type correction|habit|all]
behavior.py stats
Phase G strangler slice 2 (port 5): the decision math (reinforce
counting/confidence/promotion/state formulas) is served IN-PROCESS by the
Rust `evidence_provenance` cdylib through ep_loader.py. STORAGE STAYS
PYTHON — SQLite I/O and the regex matching layer remain here (the crate is
zero-dep by design; hand-rolling SQLite/regex would be hubris, not
hardening). Vintage upgrade per the frozen contract §2: this module is the
src/rune authority vintage; the deployed ~/.config/opencode/scripts copy
is superseded by the same shim shape pointing at the live db path.
Timestamps are pinned by the shim (`now`) — the core is deterministic.
API, CLI, and PLUGIN shapes unchanged (git history holds the original).
"""
import json
@@ -24,6 +35,12 @@ import sqlite3
import sys
from datetime import datetime, timezone
# ── Rust core wiring (slice-2 port-5 shim pattern) ──────────────────────────
_CRATE_DIR = "/mnt/workdrive/rune/src/rust/evidence_provenance"
if _CRATE_DIR not in sys.path:
sys.path.insert(0, _CRATE_DIR)
import ep_loader # noqa: E402 (loads target/release cdylib)
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import memory_env
@@ -194,7 +211,10 @@ def match_context(context, match_type=None):
def reinforce(item_id, outcome, item_type="habit"):
"""Reinforce a correction or habit based on outcome."""
"""Reinforce a correction or habit based on outcome.
Decision math (counting, confidence formula, promotion, state) served
by the Rust core; SQL stays here."""
db = connect()
try:
ts = _now()
@@ -204,28 +224,14 @@ def reinforce(item_id, outcome, item_type="habit"):
if not habit:
return {"error": f"Habit {item_id} not found"}
new_count = habit["activation_count"] + 1
new_success = habit["success_count"] + (1 if outcome == "success" else 0)
new_failure = habit["failure_count"] + (1 if outcome == "failure" else 0)
# Update confidence based on success rate
if new_count > 0:
success_rate = new_success / new_count
new_confidence = min(1.0, 0.5 + success_rate * 0.5)
else:
new_confidence = habit["confidence"]
# Promote from candidate to active if enough activations
new_state = habit["state"]
if habit["state"] == "candidate" and new_count >= habit["min_activations"]:
new_state = "active"
upd = ep_loader.habit_reinforce(dict(habit), outcome)
db.execute(
"""
UPDATE habits SET activation_count=?, success_count=?, failure_count=?,
confidence=?, state=?, last_activated=? WHERE id=?
""",
(new_count, new_success, new_failure, new_confidence, new_state, ts, item_id),
(upd["activation_count"], upd["success_count"], upd["failure_count"],
upd["confidence"], upd["state"], ts, item_id),
)
# Log activation
@@ -238,33 +244,23 @@ def reinforce(item_id, outcome, item_type="habit"):
)
db.commit()
return {"reinforced": item_id, "confidence": new_confidence, "state": new_state}
return {"reinforced": item_id, "confidence": upd["confidence"], "state": upd["state"]}
else: # correction
correction = db.execute("SELECT * FROM corrections WHERE id=?", (item_id,)).fetchone()
if not correction:
return {"error": f"Correction {item_id} not found"}
if outcome == "success":
new_confidence = min(1.0, correction["confidence"] + 0.1)
new_count = correction["evidence_count"] + 1
elif outcome == "failure":
new_confidence = max(0.0, correction["confidence"] - 0.3)
new_count = correction["evidence_count"]
else:
new_confidence = correction["confidence"]
new_count = correction["evidence_count"]
new_state = "active" if new_confidence > 0.3 else "archived"
upd = ep_loader.correction_reinforce(dict(correction), outcome)
db.execute(
"""
UPDATE corrections SET confidence=?, evidence_count=?, state=?, last_reinforced=?
WHERE id=?
""",
(new_confidence, new_count, new_state, ts, item_id),
(upd["confidence"], upd["evidence_count"], upd["state"], ts, item_id),
)
db.commit()
return {"reinforced": item_id, "confidence": new_confidence, "state": new_state}
return {"reinforced": item_id, "confidence": upd["confidence"], "state": upd["state"]}
finally:
db.close()
@@ -408,4 +404,4 @@ PLUGIN = {
"provides": ["behavior"],
"priority": 50,
"auto_activate": True,
}
}
+48 -126
View File
@@ -16,12 +16,29 @@ dependencies changed are invalidated; every invalidation carries a
reason; unrelated hardened confirmations are preserved.
Storage: <project_store>/behaviour_ledger.json (versioned document).
Phase G strangler slice 2 (port 4): the state machine (define / get /
transitions / selective invalidation / queues / summary) is served
IN-PROCESS by the Rust `evidence_provenance` cdylib through ep_loader.py
— parity 13/13 proven vs this module's Python vintage (full-scenario
doc parity, byte-equal error messages, save render indent=1 sort_keys).
File glue (load/save) stays Python; timestamps are pinned by this shim
(`now`) so the core stays deterministic. State constants and class API
unchanged (git history holds the original).
"""
from __future__ import annotations
import json
import sys
import time
from pathlib import Path
# ── Rust core wiring (slice-2 port-4 shim pattern) ──────────────────────────
_CRATE_DIR = "/mnt/workdrive/rune/src/rust/evidence_provenance"
if _CRATE_DIR not in sys.path:
sys.path.insert(0, _CRATE_DIR)
import ep_loader # noqa: E402 (loads target/release cdylib)
LEDGER_SCHEMA_VERSION = 1
# hardening lifecycle (rule 19G semantics)
@@ -56,13 +73,11 @@ class BehaviourLedger:
def _load(self) -> dict:
if self.path.is_file():
import json
return json.loads(self.path.read_text())
return {"schema_version": LEDGER_SCHEMA_VERSION,
"behaviours": {}, "updated_at": _now()}
def save(self) -> None:
import json
self.doc["updated_at"] = _now()
self.path.parent.mkdir(parents=True, exist_ok=True)
self.path.write_text(json.dumps(self.doc, indent=1, sort_keys=True))
@@ -77,66 +92,34 @@ class BehaviourLedger:
dependencies: list[str] | None = None,
automated_ref: str = "",
environment: str = "") -> dict:
bid = (behaviour_id if behaviour_id.startswith("behaviour:")
else f"behaviour:{behaviour_id}")
if bid in self.doc["behaviours"]:
raise ValueError(f"behaviour exists: {bid}")
rec = {
"behaviour_id": bid,
"owning_project": owning_project,
"intended_behaviour": intended_behaviour,
"origin_intent": origin_intent,
"pass_criteria": pass_criteria,
"fail_criteria": fail_criteria,
"procedure": procedure,
"owner_component": owner_component, # structural entity id
"dependencies": list(dependencies or []), # structural ids
"automated_ref": automated_ref,
"environment": environment,
"state": UNVALIDATED,
"evidence": [], # [{at, generation, kind, detail}]
"history": [], # every state transition, never lost
"invalidations": [], # [{at, generation, because}]
"limitations": [],
"created_at": _now(),
"updated_at": _now(),
}
self.doc["behaviours"][bid] = rec
self.doc, rec = ep_loader.bl_define(
self.doc, behaviour_id=behaviour_id,
owning_project=owning_project,
intended_behaviour=intended_behaviour,
origin_intent=origin_intent,
pass_criteria=pass_criteria,
fail_criteria=fail_criteria,
procedure=procedure,
owner_component=owner_component,
dependencies=dependencies,
automated_ref=automated_ref,
environment=environment,
now=_now())
return rec
def get(self, behaviour_id: str) -> dict | None:
return self.doc["behaviours"].get(
behaviour_id if behaviour_id.startswith("behaviour:")
else f"behaviour:{behaviour_id}")
return ep_loader.bl_get(self.doc, behaviour_id)
# -- state transitions (all recorded in history — rule 19G) -------------
def _transition(self, rec: dict, new_state: str, *, kind: str,
detail: str = "", generation: str = "") -> None:
if new_state not in STATES:
raise ValueError(f"unknown validation state {new_state!r}")
rec["history"].append({
"at": _now(), "from": rec["state"], "to": new_state,
"kind": kind, "detail": detail[:200],
"generation": generation})
rec["state"] = new_state
rec["updated_at"] = _now()
def record_auto_verification(self, behaviour_id: str, *,
passed: bool, detail: str = "",
generation: str = "") -> dict:
"""Automated verification. GREEN NEVER IMPLIES USER_CONFIRMED
(rule 19W-22): the ceiling here is AUTO_VERIFIED."""
rec = self.get(behaviour_id)
if rec is None:
raise ValueError(f"unknown behaviour {behaviour_id!r}")
rec["evidence"].append({"at": _now(), "kind": "automated",
"passed": passed,
"detail": detail[:200],
"generation": generation})
self._transition(rec, AUTO_VERIFIED if passed else TEST_FAILED,
kind="automated_verification", detail=detail,
generation=generation)
self.doc, rec = ep_loader.bl_record_auto(
self.doc, behaviour_id, passed=passed, detail=detail,
generation=generation, now=_now())
return rec
def record_real_world(self, behaviour_id: str, *, passed: bool,
@@ -144,32 +127,16 @@ class BehaviourLedger:
confirmed_by: str = "user") -> dict:
"""Real-world/manual test result (testing mode). USER_CONFIRMED
requires an explicit confirmed_by='user' pass (rule 19M)."""
rec = self.get(behaviour_id)
if rec is None:
raise ValueError(f"unknown behaviour {behaviour_id!r}")
rec["evidence"].append({"at": _now(), "kind": "real_world",
"passed": passed,
"detail": detail[:200],
"generation": generation,
"by": confirmed_by})
if passed:
state = (USER_CONFIRMED if confirmed_by == "user"
else REAL_WORLD_PASSED)
else:
state = TEST_FAILED
self._transition(rec, state, kind="real_world", detail=detail,
generation=generation)
self.doc, rec = ep_loader.bl_record_real_world(
self.doc, behaviour_id, passed=passed, detail=detail,
generation=generation, confirmed_by=confirmed_by, now=_now())
return rec
def accept_limitation(self, behaviour_id: str, *, note: str,
approved_by: str = "user") -> dict:
rec = self.get(behaviour_id)
if rec is None:
raise ValueError(f"unknown behaviour {behaviour_id!r}")
rec["limitations"].append({"at": _now(), "note": note[:200],
"approved_by": approved_by})
self._transition(rec, ACCEPTED_LIMITATION, kind="accept_limitation",
detail=note, )
self.doc, rec = ep_loader.bl_accept_limitation(
self.doc, behaviour_id, note=note,
approved_by=approved_by, now=_now())
return rec
# -- selective invalidation (rule 19I) -----------------------------------
@@ -182,44 +149,15 @@ class BehaviourLedger:
structural dependencies intersect the changed closure. Every
invalidation carries a reason; unrelated confirmations stay.
Returns the invalidated records."""
reasons = reasons or {}
invalidated = []
for rec in self.doc["behaviours"].values():
if rec["state"] not in CONFIRMED_STATES:
continue
overlap = set(rec.get("dependencies") or []) & changed_entities
if rec.get("owner_component") in changed_entities:
overlap.add(rec["owner_component"])
if not overlap:
continue # unrelated hardened behaviour: PRESERVED (19S-5)
because = reasons.get(
rec["behaviour_id"],
f"structural change touched: {sorted(overlap)}")
rec["invalidations"].append({
"at": _now(), "generation": generation,
"because": because[:200],
"was": rec["state"],
"changed_entities": sorted(overlap)})
# HISTORY IS PRESERVED — prior confirmation stays queryable
self._transition(rec, RETEST_REQUIRED,
kind="selective_invalidation",
detail=f"was {rec['state']}; {because}",
generation=generation)
invalidated.append(rec)
return invalidated
self.doc, recs = ep_loader.bl_invalidate(
self.doc, changed_entities=sorted(changed_entities),
generation=generation, reasons=reasons, now=_now())
return recs
def retest_history(self, behaviour_id: str) -> list[dict]:
"""Generation-aware history: all confirmations + invalidations
for one behaviour, oldest first (rule 19S-12)."""
rec = self.get(behaviour_id)
if rec is None:
return []
events = []
for h in rec["history"]:
events.append({"at": h["at"], "generation": h.get("generation"),
"event": f"{h['from']}→{h['to']}",
"kind": h["kind"], "detail": h.get("detail", "")})
return events
return ep_loader.bl_retest_history(self.doc, behaviour_id)
# -- queue (testing mode, 19L/M) ------------------------------------------
@@ -228,24 +166,8 @@ class BehaviourLedger:
"""Eligible testing items: anything not confirmed/accepted/n.a.
Dynamic — the queue re-derives from states after every fix
(rule 19M)."""
out = []
for rec in self.doc["behaviours"].values():
if rec["state"] in (USER_CONFIRMED, ACCEPTED_LIMITATION,
NOT_APPLICABLE):
continue
if project and rec.get("owning_project") != project:
continue
if component and rec.get("owner_component") != component:
continue
out.append(rec)
out.sort(key=lambda r: (0 if r["state"] == TEST_FAILED else 1,
r["behaviour_id"]))
return out
return ep_loader.bl_outstanding(
self.doc, component=component or "", project=project or "")
def summary(self) -> dict:
counts: dict[str, int] = {}
for rec in self.doc["behaviours"].values():
counts[rec["state"]] = counts.get(rec["state"], 0) + 1
return {"total": len(self.doc["behaviours"]),
"states": counts,
"outstanding": len(self.outstanding())}
return ep_loader.bl_summary(self.doc)
+540
View File
@@ -0,0 +1,540 @@
#!/usr/bin/env python3
"""canary_audit_ledger.py — Python canary for slice-2 ports 1-6.
Slice-1 PASS/FAIL discipline (counter harness, exit 1 on any FAIL), pinned to
the FROZEN CONTRACT /mnt/workdrive/rune/plans/2026-10-03-phase-g-evidence-provenance-contract.md:
§5 requires the canary pair to carry per-port sections for ALL SIX ports.
DUAL-MODE LAW (added 2026-10-03 after run 18141 went red): sections 2/3/4/5
pin the LIVE promotion ledger when it exists in this process's HOME (desktop
runs — the drift-key pin is the §3 linchpin). Under CI's sandboxed HOME the
ledger file does not exist, so the same sections run an ISOLATED battery
against the sandboxed ledger with count expectations re-derived from the
seeded entries — identical test count, identical shapes, honest MODE print.
The artifact coupling is disclosed, not hidden: the Rust binary + cdylib
resolve to the checkout's own target/release/ (CI's host-executor topology
makes the desktop build visible; if they ever go missing, ep_loader raises
and this canary FAILS LOUDLY rather than testing nothing).
Canary expectations are OBSERVED, not invented:
- Deployed shim head + verify_chain() on the live 23-entry ledger:
{'valid': True, 'entries': 23, 'reason': 'chain intact'} (observed 2026-10-03).
- verify_chain() return shape is a 3-key dict, no tuple unpacking.
- Deployed SECRET_KEY default: 'rune-drift-integrity-2024' (the live-chain key).
- Empty-ledger contract: missing ledger file -> valid-and-empty dict.
- Shadow battery (18/18 GREEN, banked): read/write/CLI-error shapes all match
the old vintage at the shim.
Run under ~/.venvs/memory/bin/python3.
"""
import os
import sys
sys.path.insert(0, "/home/nick/.config/opencode/scripts")
# Repo-derived roots (no /mnt/workdrive hardcodes): this file lives in
# src/rune/, so the crate is two parents up plus src/rust/evidence_provenance.
_REPO = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", ".."))
_CRATE_DIR = os.path.join(_REPO, "src", "rust", "evidence_provenance")
if os.environ.get("EP_RUST_LIB"):
# CI: the build step emits to a runner-owned /tmp target dir and the job
# points EP_RUST_LIB at the cdylib FILE (that's the form ep_loader's
# override takes). The binary lives next to it.
_BIN = os.path.join(os.path.dirname(os.environ["EP_RUST_LIB"]),
"evidence_provenance")
else:
_BIN = os.path.join(_CRATE_DIR, "target", "release", "evidence_provenance")
# Dual-mode switch (DUAL-MODE LAW in the docstring): LIVE when this process's
# HOME carries the real promotion ledger AND we are not inside the CI sandbox.
# The discriminator is the repo's own CI-sandbox convention — the workflow sets
# RUNE_HOME=/tmp/rune-ci-<run_id> and its cleanup guard matches /tmp/rune-ci-*
# exactly. File EXISTENCE is NOT a valid discriminator: run 18141's shim writes
# left a zombie 1-entry ledger in the runner's persistent HOME, which would
# flip a naive isfile() gate to LIVE with the wrong data (proven 2026-10-03
# on run 18161: "entries >= 23 — got 1").
_LIVE_LEDGER = os.path.expanduser("~/.config/opencode/memory/promotion_ledger.jsonl")
_IN_CI_SANDBOX = os.environ.get("RUNE_HOME", "").startswith("/tmp/rune-ci-")
LIVE = os.path.isfile(_LIVE_LEDGER) and not _IN_CI_SANDBOX
PASS = 0
FAIL = 0
def test(name, condition, detail=""):
global PASS, FAIL
if condition:
PASS += 1
print(f" ✓ {name}")
else:
FAIL += 1
print(f" ✗ {name} — {detail}")
def main():
global PASS, FAIL
print("=== AUDIT LEDGER CANARY (slice-2 ports 1-6) ===")
print(f"MODE: {'LIVE (deployed surface + live ledger)' if LIVE else 'ISOLATED (sandboxed ledger — CI mode)'}\n")
bin_home = None # binary HOME override for §5 in ISOLATED mode
# --- 1. Deployed module surface ---
print("--- 1. Deployed module surface ---")
try:
import audit_ledger
test("audit_ledger imports (deployed shim)", True)
for name in (
"_compute_hash",
"append_entry",
"verify_chain",
"get_entries",
"log_promotion",
"log_gate_result",
"log_drift_alert",
"main",
):
test(f"has {name}", hasattr(audit_ledger, name))
test("LEDGER_FILE is the live ledger path",
audit_ledger.LEDGER_FILE ==
os.path.expanduser("~/.config/opencode/memory/promotion_ledger.jsonl"),
f"got {audit_ledger.LEDGER_FILE}")
test("SECRET_KEY default is the drift key (live-chain key)",
audit_ledger.SECRET_KEY == "rune-drift-integrity-2024",
f"got {audit_ledger.SECRET_KEY}")
except Exception as e: # noqa: BLE001 — canary battery law: a failed probe is a FAIL test, not a crash
test("audit_ledger module", False, str(e))
print(f"CANARY ABORT: import failed — {e}")
sys.exit(1)
# --- 2. Ledger verification through the shim (live on desktop / isolated in CI) ---
print("\n--- 2. Ledger verification ---")
if not LIVE:
# Seed a PER-RUN isolated ledger (never the runner's persistent HOME —
# leftover writes there are what poisoned run 18161's mode detection):
# point the shim's module-level LEDGER_FILE at a temp path; all §2/§3/§4
# reads/writes then land in the temp file. Binary §5 gets HOME=<temp>.
# Placed AFTER §1 so the surface pins observe the shim's DEFAULT
# resolution before the override.
import tempfile as _tf
bin_home = _tf.mkdtemp(prefix="alcanary_home_")
iso_ledger = os.path.join(bin_home, ".config", "opencode", "memory",
"promotion_ledger.jsonl")
os.makedirs(os.path.dirname(iso_ledger), exist_ok=True)
audit_ledger.LEDGER_FILE = iso_ledger
for seed_id in (9001, 9002):
audit_ledger.append_entry("promotion", seed_id,
{"reason": "canary-ci-seed", "old_status": "a",
"new_status": "b", "wash_count": 1})
try:
result = audit_ledger.verify_chain()
test("verify_chain() returns dict", isinstance(result, dict), f"got {type(result)}") # type: ignore[reportUnnecessaryIsInstance]
test("valid is True on the chain", result.get("valid") is True,
f"got {result.get('valid')}")
if LIVE:
test("entries >= 23 (ledger grew through prior canaries)",
result.get("entries", 0) >= 23, f"got {result.get('entries')}")
else:
test("entries == 2 (the §2 seeds this process wrote)",
result.get("entries") == 2, f"got {result.get('entries')}")
test("reason is 'chain intact'", result.get("reason") == "chain intact",
f"got {result.get('reason')}")
except Exception as e: # noqa: BLE001 — canary battery law: a failed probe is a FAIL test, not a crash
test("live verify_chain()", False, str(e))
# --- 3. Read path: get_entries ---
print("\n--- 3. Read path ---")
try:
entries = audit_ledger.get_entries(limit=5)
test("get_entries(5) returns a list", isinstance(entries, list)) # type: ignore[reportUnnecessaryIsInstance]
if LIVE:
test("get_entries(5) returns 5 entries on the live ledger", len(entries) == 5,
f"got {len(entries)}")
else:
test("get_entries(5) returns the 2 seeded entries (isolated)", len(entries) == 2,
f"got {len(entries)}")
first = entries[0] if entries else {}
test("entry carries hash chain fields",
"hash" in first and "prev_hash" in first,
f"keys: {sorted(first.keys())}")
# Observed deployed-vintage serving order: ASCENDING (oldest-first
# within the limit window) — verified against the live ledger 2026-10-03.
test("entries served in ascending timestamp order (deployed-vintage shape)",
all(entries[i]["timestamp"] <= entries[i + 1]["timestamp"]
for i in range(len(entries) - 1)),
"timestamps not monotonic-ascending")
except Exception as e: # noqa: BLE001 — canary battery law: a failed probe is a FAIL test, not a crash
test("get_entries", False, str(e))
# --- 4. Write path: append through the deployed shim (LIVE ledger,
# the established canary pattern — entries 89/90 are this shape) ---
print("\n--- 4. Write path (canary append through the shim) ---")
try:
e1 = audit_ledger.append_entry("promotion", 4242, {"old_status": "established",
"new_status": "rock",
"wash_count": 20,
"reason": "canary"})
test("append_entry returns dict", isinstance(e1, dict), f"got {type(e1)}") # type: ignore[reportUnnecessaryIsInstance]
test("entry has prev_hash + hash", "prev_hash" in e1 and "hash" in e1)
test("hash is 64-hex", isinstance(e1.get("hash"), str) and len(e1["hash"]) == 64
and all(c in "0123456789abcdef" for c in e1["hash"]))
test("append flowed through the shim: live chain still intact",
audit_ledger.verify_chain()["valid"] is True,
"live chain invalid after append")
tail = audit_ledger.get_entries(limit=1)
test("newest live entry is the canary append (entry_id 4242)",
tail and tail[0].get("entry_id") == 4242,
f"got {tail[0].get('entry_id') if tail else 'empty'}")
except Exception as e: # noqa: BLE001 — canary battery law: a failed probe is a FAIL test, not a crash
test("append_entry write path", False, str(e))
# --- 5. Rust binary CLI parity (the compiled candidate) ---
print("\n--- 5. Rust binary CLI parity ---")
import subprocess
import tempfile
BIN = _BIN
tmp2 = tempfile.mkdtemp(prefix="alcanary_cli_")
# The binary honors RUNE_MEMORY_DIR/RUNE_HOME first (main.rs ledger_path);
# the deployed shim resolves expanduser($HOME). Strip the RUNE_* overrides
# from binary envs so both surfaces resolve the SAME ledger — without this,
# CI's RUNE_MEMORY_DIR silently pointed the binary at an empty ledger while
# the shim wrote the sandboxed-HOME one (run 18141's ghost divergence).
clean_env = {k: v for k, v in os.environ.items()
if k not in ("RUNE_MEMORY_DIR", "RUNE_HOME")}
if not os.path.exists(BIN):
# FAIL loud, never crash: run 18164 died on an uncaught Popen
# FileNotFoundError when the artifact was absent from the checkout.
# CI now builds the crate (canary job); a missing binary here means
# the build step regressed and this canary must go red, not skip.
test("release binary exists in the checkout (CI builds it)",
False, f"missing: {BIN}")
else:
try:
env = dict(clean_env, HOME=tmp2)
r = subprocess.run([BIN, "verify"], capture_output=True, text=True, env=env)
test("binary verify on missing ledger -> valid-empty",
r.returncode == 0 and "Chain valid: True" in r.stdout
and "Entries: 0" in r.stdout and "empty ledger" in r.stdout,
f"rc={r.returncode} out={r.stdout!r} err={r.stderr!r}")
r = subprocess.run([BIN], capture_output=True, text=True, env=env)
test("binary no-args -> help + exit 1", r.returncode == 1
and "usage:" in r.stdout, f"rc={r.returncode}")
r = subprocess.run([BIN, "frobnicate"], capture_output=True, text=True, env=env)
test("binary invalid choice -> exit 2", r.returncode == 2
and "invalid choice" in r.stderr, f"rc={r.returncode} err={r.stderr!r}")
r = subprocess.run([BIN, "list", "--limit", "3"], capture_output=True, text=True,
env=dict(clean_env, HOME=bin_home) if bin_home else clean_env)
test("binary list --limit 3 on the shim's ledger renders 3 entries",
r.returncode == 0 and "Recent ledger entries (3):" in r.stdout,
f"rc={r.returncode} out={r.stdout[:120]!r}")
finally:
import shutil
shutil.rmtree(tmp2, ignore_errors=True)
# --- 6. Port 2 (verification_layer) through the same cdylib ---
print("\n--- 6. Port 2: verification_layer via ep_loader ---")
import json
if _CRATE_DIR not in sys.path:
sys.path.insert(0, _CRATE_DIR)
try:
import ep_loader # type: ignore[import-not-found] (ctypes cdylib wrapper — runtime import by design)
v = ep_loader.verify('{"exit_code": 0}', "exit_code")
out = json.loads(v)
test("ep_verify exit_code=0 -> passed True", out.get("passed") is True)
v = ep_loader.verify('{"exit_code": false}', "exit_code")
out = json.loads(v)
test("ep_verify bool-false renders str() shape (False, not 0)",
out.get("passed") is True and out.get("evidence_refs") == ["exit_code=False"],
f"got {out}")
v = ep_loader.verify('{}', "frobnicate")
out = json.loads(v)
test("ep_verify unknown verifier -> passed False retryable False",
out.get("passed") is False and out.get("retryable") is False
and out.get("escalation_reason") == "unknown_verifier",
f"got {out}")
except Exception as e: # noqa: BLE001 — canary battery law: a failed probe is a FAIL test, not a crash
test("port-2 ep_verify FFI", False, str(e))
# --- 7. src/rune shim module ---
print("\n--- 7. src/rune verification_layer shim ---")
try:
sys.path.insert(0, os.path.join(_REPO, "src", "rune"))
import verification_layer as vl
r = vl.verify({"exit_code": 1}, "exit_code")
test("shim verify returns VerificationResult dataclass",
type(r).__name__ == "VerificationResult")
test("shim CLI parity: fail case escalation_reason=exit_code_nonzero",
r.to_dict()["escalation_reason"] == "exit_code_nonzero")
test("shim PLUGIN name preserved", vl.PLUGIN["name"] == "verification_layer")
except Exception as e: # noqa: BLE001 — canary battery law: a failed probe is a FAIL test, not a crash
test("src/rune verification_layer shim", False, str(e))
# --- 8. Port 3 (evidence_probe): schema gate + budget count ---
# Expectations probed against the serving shim 2026-10-03 (zero-dep split:
# schema gate + count Rust; orchestration Python).
print("\n--- 8. Port 3: evidence_probe shim ---")
import tempfile
try:
import evidence_probe as epr
errs = epr.validate_probe({})
test("validate_probe({}) -> 5 missing-field errors in field order",
errs == ["missing required field: question",
"missing required field: source",
"missing required field: answer",
"missing required field: implication",
"missing required field: status"], f"got {errs}")
good = {"question": "q", "source": "s", "answer": "a",
"implication": "i", "status": "STILL_UNKNOWN"}
test("valid STILL_UNKNOWN probe -> no errors",
epr.validate_probe(good) == [], f"got {epr.validate_probe(good)}")
bad_status = dict(good, status="PASS")
test("status 'PASS' -> membership error with Python repr rendering",
epr.validate_probe(bad_status) ==
["status must be one of ['CONTRADICTED', 'GROUNDED', 'STILL_UNKNOWN'], got 'PASS'"],
f"got {epr.validate_probe(bad_status)}")
int_status = dict(good, status=7)
test("non-string status -> field error AND membership error (Python law)",
epr.validate_probe(int_status) ==
["field status must be a string",
"status must be one of ['CONTRADICTED', 'GROUNDED', 'STILL_UNKNOWN'], got 7"],
f"got {epr.validate_probe(int_status)}")
extra = dict(good, extra=1)
test("extra field -> unexpected-fields error appended last",
epr.validate_probe(extra)[-1] == "unexpected extra fields: ['extra']",
f"got {epr.validate_probe(extra)}")
grounded_blank = dict(good, question="", status="GROUNDED")
test("blank question + GROUNDED -> both errors (citation gate)",
epr.validate_probe(grounded_blank) ==
["field question must be a non-empty string",
"GROUNDED probe requires a concrete source citation"],
f"got {epr.validate_probe(grounded_blank)}")
probe_dir = tempfile.mkdtemp(prefix="eprcanary_")
try:
with open(os.path.join(probe_dir, "probe.jsonl"), "w") as f:
f.write('{"q": 1}\n\n{"r": 2}\n\n\n{"s": 3}\n')
test("count_probes counts non-empty lines only",
epr.count_probes(probe_dir) == 3,
f"got {epr.count_probes(probe_dir)}")
test("count_probes on missing dir -> 0",
epr.count_probes(probe_dir + "/nope") == 0)
test("MAX_PROBES_PER_TASK is 2 (the no-recursion budget)",
epr.MAX_PROBES_PER_TASK == 2)
try:
epr.check_budget(probe_dir)
test("check_budget raises at the budget", False, "no raise")
except epr.ProbeBudgetExceeded as e:
test("check_budget raises ProbeBudgetExceeded at 2 probes",
str(e).startswith("probe budget exhausted (2)"), f"got {e}")
finally:
import shutil
shutil.rmtree(probe_dir, ignore_errors=True)
except Exception as e: # noqa: BLE001 — canary battery law: a failed probe is a FAIL test, not a crash
test("port-3 evidence_probe shim", False, str(e))
# --- 9. Port 4 (behaviour_ledger): doc-envelope state machine ---
# Expectations probed against ep_loader 2026-10-03: doc-in/doc-out,
# {ok, err} envelope, CONFIRMED_STATES-only invalidation, TEST_FAILED-first
# queue ordering.
print("\n--- 9. Port 4: behaviour_ledger via ep_loader ---")
try:
import ep_loader # type: ignore[import-not-found] (already imported in §6; re-import is a cache hit)
doc = {"behaviours": {}}
doc, rec = ep_loader.bl_define(
doc, behaviour_id="alpha", owning_project="proj",
intended_behaviour="ib", origin_intent="oi", pass_criteria="pc",
fail_criteria="fc", procedure="proc", owner_component="comp:alpha",
dependencies=["dep:one"], automated_ref="ar", environment="env",
now="2026-10-03T23:00:00Z")
test("define returns (doc, rec) with normalized id + UNVALIDATED",
rec.get("behaviour_id") == "behaviour:alpha"
and rec.get("state") == "UNVALIDATED",
f"got id={rec.get('behaviour_id')} state={rec.get('state')}")
test("define record carries the 18-key shape",
set(rec.keys()) == {"automated_ref", "behaviour_id", "created_at",
"dependencies", "environment", "evidence",
"fail_criteria", "history", "intended_behaviour",
"invalidations", "limitations", "origin_intent",
"owner_component", "owning_project", "pass_criteria",
"procedure", "state", "updated_at"},
f"got {sorted(rec.keys())}")
doc, _ = ep_loader.bl_record_auto(doc, "alpha", passed=True,
detail="tests green", generation="g1",
now="2026-10-03T23:01:00Z")
rec_a = ep_loader.bl_get(doc, "alpha")
test("record_auto pass -> AUTO_VERIFIED (a CONFIRMED state)",
rec_a is not None and rec_a["state"] == "AUTO_VERIFIED")
doc, _ = ep_loader.bl_define(doc, behaviour_id="beta", owning_project="proj",
intended_behaviour="ib2", now="2026-10-03T23:02:00Z")
doc, _ = ep_loader.bl_record_auto(doc, "beta", passed=False, detail="flaky",
now="2026-10-03T23:03:00Z")
rec_b = ep_loader.bl_get(doc, "beta")
test("record_auto fail -> TEST_FAILED",
rec_b is not None and rec_b["state"] == "TEST_FAILED")
# Selective invalidation (rule 19I): only CONFIRMED_STATES with
# dep/owner overlap reopen; unrelated change preserves.
doc, untouched = ep_loader.bl_invalidate(
doc, changed_entities=["dep:unrelated"], now="2026-10-03T23:04:00Z")
rec_a = ep_loader.bl_get(doc, "alpha")
test("unrelated change invalidates nothing; AUTO_VERIFIED preserved",
untouched == [] and rec_a is not None and rec_a["state"] == "AUTO_VERIFIED")
doc, hit = ep_loader.bl_invalidate(
doc, changed_entities=["dep:one"], generation="g2",
reasons={"behaviour:alpha": "dep one changed"}, now="2026-10-03T23:05:00Z")
test("dependency hit invalidates exactly the confirmed behaviour",
len(hit) == 1 and hit[0]["state"] == "RETEST_REQUIRED",
f"got {[r['state'] for r in hit]}")
inv_entry = hit[0]["invalidations"][0]
test("invalidation entry carries because/generation/was/changed_entities",
inv_entry.get("because") == "dep one changed"
and inv_entry.get("generation") == "g2"
and inv_entry.get("was") == "AUTO_VERIFIED"
and inv_entry.get("changed_entities") == ["dep:one"],
f"got {inv_entry}")
doc, rehit = ep_loader.bl_invalidate(
doc, changed_entities=["dep:one"], now="2026-10-03T23:06:00Z")
rec_a = ep_loader.bl_get(doc, "alpha")
test("re-invalidating a non-confirmed record is a no-op",
rehit == [] and rec_a is not None and rec_a["state"] == "RETEST_REQUIRED")
out = ep_loader.bl_outstanding(doc)
test("outstanding() puts TEST_FAILED first (queue ordering)",
out and out[0]["state"] == "TEST_FAILED",
f"got {[(r['behaviour_id'], r['state']) for r in out]}")
summ = ep_loader.bl_summary(doc)
test("summary() shape {total, states, outstanding}",
summ.get("total") == 2 and isinstance(summ.get("states"), dict)
and "outstanding" in summ, f"got {summ}")
hist = ep_loader.bl_retest_history(doc, "alpha")
test("retest_history(alpha) -> [automated_verification, selective_invalidation]",
[e.get("kind") for e in hist] == ["automated_verification", "selective_invalidation"],
f"got {[e.get('kind') for e in hist]}")
try:
ep_loader.bl_define(doc, behaviour_id="alpha", owning_project="x",
intended_behaviour="y", now="2026-10-03T23:07:00Z")
test("duplicate define raises ValueError", False, "no raise")
except ValueError as e:
test("duplicate define error message byte-exact",
str(e) == "behaviour exists: behaviour:alpha", f"got {e}")
try:
ep_loader.bl_record_auto(doc, "ghost", passed=True, now="2026-10-03T23:08:00Z")
test("unknown behaviour record raises ValueError", False, "no raise")
except ValueError as e:
test("unknown behaviour error message byte-exact",
str(e) == "unknown behaviour 'ghost'", f"got {e}")
except Exception as e: # noqa: BLE001 — canary battery law: a failed probe is a FAIL test, not a crash
test("port-4 behaviour_ledger FFI", False, str(e))
# --- 10. Port 5 (behavior): decision math through the shim on an
# isolated db (RUNE_HOME + RUNE_MEMORY_DIR pinned BEFORE import —
# memory_dir() honors RUNE_MEMORY_DIR first, and DB_PATH resolves at
# import time). SQLite stays Python; the math is Rust.
print("\n--- 10. Port 5: behavior shim (isolated db) ---")
_iso_home = tempfile.mkdtemp(prefix="alcanary_runehome_")
os.environ["RUNE_HOME"] = _iso_home
os.environ["RUNE_MEMORY_DIR"] = os.path.join(_iso_home, "memory")
try:
import behavior as bh
test("behavior DB resolved under the isolated RUNE_HOME",
bh.DB_PATH.startswith(os.environ["RUNE_HOME"]), f"got {bh.DB_PATH}")
cid = bh.add_correction("say X", "say Y", domain="canary")["added"]
r = bh.reinforce(cid, "success", item_type="correction")
test("correction reinforce success: confidence 0.5 -> 0.6, stays active",
r == {"reinforced": cid, "confidence": 0.6, "state": "active"},
f"got {r}")
r = bh.reinforce(cid, "failure", item_type="correction")
test("correction reinforce failure: 0.6 -> 0.3, archives at the boundary",
r == {"reinforced": cid, "confidence": 0.3, "state": "archived"},
f"got {r}")
hid = bh.add_habit("trigger t", "action a", name="canary habit")["added"]
for _ in range(3):
r = bh.reinforce(hid, "success")
test("habit promotion: candidate -> active at min_activations (3)",
r["state"] == "active" and r["confidence"] == 1,
f"got {r}")
test("habit not-found returns error dict (not raise)",
bh.reinforce(99999, "success") == {"error": "Habit 99999 not found"})
test("correction not-found returns error dict (not raise)",
bh.reinforce(99999, "success", item_type="correction")
== {"error": "Correction 99999 not found"})
stats = bh.behavior_stats()
test("behavior_stats carries the five-key shape",
set(stats.keys()) == {"corrections_active", "corrections_archived",
"habits_active", "habits_candidate",
"total_activations"}, f"got {sorted(stats.keys())}")
test("stats reflect the battery: 1 archived correction, 1 active habit",
stats["corrections_archived"] == 1 and stats["habits_active"] == 1,
f"got {stats}")
except Exception as e: # noqa: BLE001 — canary battery law: a failed probe is a FAIL test, not a crash
test("port-5 behavior shim", False, str(e))
# --- 11. Port 6 (claim_audit): full pipeline through the shim on an
# isolated journal (MEM_DIR resolves under the pinned RUNE_HOME).
# SYMMETRY GUARD never-fatal: research_lens absence must not crash.
print("\n--- 11. Port 6: claim_audit shim (isolated journal) ---")
try:
import claim_audit as ca
claims = ca.scan("We exceeded the frontier. Results are flawless.")
test("scan flags the comparative + absolute claims",
[(c["type"], c["match"]) for c in claims]
== [("comparative", "we exceeded"), ("absolute", "flawless")],
f"got {[(c['type'], c['match']) for c in claims]}")
st = ca.scan_text("We exceeded anything.")
test("scan_text returns the repaired {claims, clean, degraded, rewritten} surface",
set(st.keys()) == {"claims", "clean", "degraded", "rewritten"},
f"got {sorted(st.keys())}")
test("scan_text degrades the unsupported comparative (clean False)",
st["clean"] is False and len(st["degraded"]) == 1
and st["degraded"][0]["verdict"] == "DEGRADE",
f"clean={st['clean']} degraded={st['degraded']}")
res = ca.audit("We exceeded the frontier. Results are flawless.")
test("audit() -> both claims DEGRADE, clean False",
res["clean"] is False
and [c["verdict"] for c in res["claims"]] == ["DEGRADE", "DEGRADE"],
f"got {[(c['verdict']) for c in res['claims']]} clean={res['clean']}")
test("audit() rewrote the draft (degrade templates applied)",
res["rewritten"] != "We exceeded the frontier. Results are flawless.")
with open(ca.JOURNAL) as f:
rows = f.readlines()
test("journal wrote one row per claim + scan_text row (isolated)",
len(rows) == 3, f"got {len(rows)} rows at {ca.JOURNAL}")
rep = ca.report()
test("report() aggregates through the Rust core: total + by_verdict + by_type",
rep.get("total") == 3
and rep.get("by_verdict", {}).get("DEGRADE") == 3
and rep.get("by_type", {}).get("comparative") == 2
and rep.get("by_type", {}).get("absolute") == 1,
f"got {rep}")
test("verify() on an unsupported plain claim -> ('DEGRADE', 0)",
ca.verify("the module renders a menu when opened") == ("DEGRADE", 0))
test("evidence-term stoplist through the Rust core (>=4 chars, trio dropped)",
ca.ep_loader.ca_evidence_terms(["this", "hash", "canary", "ru"])
== ["this", "hash", "canary"],
f"got {ca.ep_loader.ca_evidence_terms(['this', 'hash', 'canary', 'ru'])}")
except Exception as e: # noqa: BLE001 — canary battery law: a failed probe is a FAIL test, not a crash
test("port-6 claim_audit shim", False, str(e))
print(f"\n{'=' * 47}")
print(f"RESULTS: {PASS} pass, {FAIL} fail")
sys.exit(1 if FAIL else 0)
if __name__ == "__main__":
app = os.path.join(sys.path[0], "canary_audit_ledger.py")
if len(sys.argv) > 1 and sys.argv[1] == "--selfcheck":
print("selfcheck ok")
sys.exit(0)
main()
+6 -4
View File
@@ -19,7 +19,8 @@ def has_idempotent_label_removal(workflow: str) -> bool:
return bool(
re.search(
r"for \(const name of staleManaged\) \{\s*"
r"try \{\s*await github\.rest\.issues\.removeLabel\(\{.*?"
r"try \{.*?await github\.request\('DELETE /repos/\{owner\}/\{repo\}/issues/\{issue_number\}/labels/\{id\}', \{.*?"
r"id: currentLabelIds\.get\(name\),\s*"
r"\}\);\s*\} catch \(error\) \{\s*"
r"if \(error\.status !== 404\) \{\s*throw error;\s*\}\s*"
r"console\.log\(`Managed label already absent: \$\{name\}`\);\s*"
@@ -64,7 +65,7 @@ def main() -> int:
"infrastructure",
"life",
"custodian",
"priority: high",
"P0 — critical", "P1 — high", "P2 — medium", "P3 — low",
"bug",
"feature",
)
@@ -80,7 +81,8 @@ def main() -> int:
)
check(
"stale managed labels are removed",
"managedLabels.has" in auto_label and "!desiredLabels.has" in auto_label and "removeLabel" in auto_label,
"managedLabels.has" in auto_label and "!desiredLabels.has" in auto_label
and "currentLabelIds.get(name)" in auto_label,
failures,
)
check(
@@ -231,7 +233,7 @@ def main() -> int:
)
check(
"Rune minor version advertises provisioning and restore verification",
'__version__ = "0.88.4"' in rune_version,
'__version__ = "0.99.6"' in rune_version,
failures,
)
# #169 policy update: auto-bump on merge is sanctioned (Nick directive:
+137
View File
@@ -0,0 +1,137 @@
#!/usr/bin/env python3
"""canary_rune_entry.py — H1 canary for the Go first-call shim.
Same PASS/FAIL discipline as the slice canaries (counter harness, exit 1 on
any FAIL). The canary builds the zero-dep Go module into a temp dir at run
start (works on the desktop and in CI — system Go is world-readable), then
verifies the observed launcher contract:
--version -> "rune <x.y.z>" matching the deployed pyproject version
--python-only -> the exact handoff shape of the deployed bash launcher
(cd <deployed>/src && <venv-python> -m rune <args>)
--help -> usage text, exit 0
unknown flag -> usage on stderr, exit 2
go test -> the module's own unit battery green (subprocess)
Expectations are OBSERVED, not invented: the bash launcher at
~/.local/bin/rune (cd + env contract) and the deployed pyproject version.
Run under ~/.venvs/memory/bin/python3.
"""
import os
import subprocess
import sys
import tempfile
PASS = 0
FAIL = 0
REPO = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", ".."))
GO_MOD = os.path.join(REPO, "src", "go", "rune-entry")
def test(name, cond, detail=""):
global PASS, FAIL
mark = "✓" if cond else "✗"
print(f" {mark} {name}" + (f" — {detail}" if detail and not cond else ""))
if cond:
PASS += 1
else:
FAIL += 1
def deployed_version():
for cand in ("/mnt/workdrive/rune-code/current/pyproject.toml",
os.path.join(REPO, "pyproject.toml")):
try:
for line in open(cand):
if line.startswith('version = "'):
return line.split('"')[1]
except OSError:
continue
return None
def main():
print("=== RUNE ENTRY (Go first-call) CANARY (H1) ===\n")
# --- 0. Build the artifact (zero-dep; system Go) ---
print("--- 0. Build ---")
tmp = tempfile.mkdtemp(prefix="runeentry_canary_")
bin_path = os.path.join(tmp, "rune")
r = subprocess.run(["go", "build", "-o", bin_path, "."],
capture_output=True, text=True, cwd=GO_MOD)
test("go build succeeds (zero-dep, system toolchain)", r.returncode == 0,
f"rc={r.returncode} err={r.stderr[:200]!r}")
if r.returncode != 0:
print(f"\nRESULTS: {PASS} pass, {FAIL} fail")
sys.exit(1)
# --- 1. Version surface ---
print("\n--- 1. Version ---")
want = deployed_version()
r = subprocess.run([bin_path, "--version"], capture_output=True, text=True)
test("--version exits 0", r.returncode == 0, f"rc={r.returncode}")
test("--version prints 'rune <x.y.z>'",
r.stdout.startswith("rune ") and r.stdout.split()[-1].count(".") == 2,
f"got {r.stdout!r}")
test("version matches the deployed pyproject",
want is not None and r.stdout.strip() == f"rune {want}",
f"got {r.stdout.strip()!r} want 'rune {want}'")
# --- 2. Handoff shape (the bash launcher's contract) ---
print("\n--- 2. Handoff shape ---")
r = subprocess.run([bin_path, "--python-only", "status"],
capture_output=True, text=True)
out = r.stdout.strip()
test("--python-only exits 0", r.returncode == 0, f"rc={r.returncode}")
test("handoff cd's into the deployed src revision",
out.startswith("(cd /mnt/workdrive/rune-code/current/src"),
f"got {out!r}")
venv_py = os.path.join(os.environ.get("HOME", ""), ".venvs", "memory", "bin", "python3")
if os.path.isfile(venv_py):
test("handoff execs the memory-venv python with -m rune",
".venvs/memory/bin/python3 -m rune" in out, f"got {out!r}")
else:
test("handoff falls back to PATH python3 when the memory venv is absent "
"(bare HOME — CI sandbox)",
"python3 -m rune" in out, f"got {out!r}")
test("args pass through the handoff",
out.endswith("status)"), f"got {out!r}")
# --- 3. Flag discipline ---
print("\n--- 3. Flag discipline ---")
r = subprocess.run([bin_path, "--help"], capture_output=True, text=True)
test("--help exits 0 with usage text",
r.returncode == 0 and "usage:" in r.stdout,
f"rc={r.returncode} out={r.stdout[:60]!r}")
r = subprocess.run([bin_path, "--frobnicate"], capture_output=True, text=True)
test("unknown flag -> exit 2 + usage on stderr",
r.returncode == 2 and "usage:" in r.stderr,
f"rc={r.returncode} err={r.stderr[:80]!r}")
# --- 4. The module's own unit battery ---
print("\n--- 4. go test ---")
r = subprocess.run(["go", "test", "./..."], capture_output=True, text=True,
cwd=GO_MOD)
test("go test ./... green (the Go unit battery)",
r.returncode == 0 and "ok" in r.stdout,
f"rc={r.returncode} out={r.stdout[-120:]!r}")
# --- 5. go vet ---
print("\n--- 5. go vet ---")
r = subprocess.run(["go", "vet", "./..."], capture_output=True, text=True,
cwd=GO_MOD)
test("go vet clean", r.returncode == 0,
f"rc={r.returncode} err={r.stderr[:120]!r}")
import shutil
shutil.rmtree(tmp, ignore_errors=True)
print(f"\n{'=' * 47}")
print(f"RESULTS: {PASS} pass, {FAIL} fail")
sys.exit(1 if FAIL else 0)
if __name__ == "__main__":
main()
+329
View File
@@ -0,0 +1,329 @@
#!/usr/bin/env python3
"""canary_rune_serve.py — H2 parity battery (T4.3, contract
BUILDER-2026-10-04T08:32:43Z-cli).
The H2 contract: the Go front door (`rune serve`) serves IDENTICAL shapes
to the Python core server for the same canned conversations — byte-level
for metadata (modulo volatile id/created), exact for NDJSON line sequences.
Determinism law: no live model calls. Both servers run against a stub
operator with canned responses (the established _execute_on_model stub
class of seam — the server's shape is the contract under test, not the
cognition behind it).
Sections:
1. Python core server boots (stdlib, ephemeral port)
2. Python shape freeze (models / status / chat / stream / 404)
3. Go serve subcommand builds and answers --help
4. Cross-language parity (Go front → Python core, same canned set)
5. Core-down honesty (502 envelope, both servers agree)
Run: python3 src/rune/canary_rune_serve.py
"""
from __future__ import annotations
import json
import os
import subprocess
import sys
import time
import urllib.error
import urllib.request
from pathlib import Path
REPO = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(REPO / "src"))
PASS = 0
FAIL = 0
def test(name: str, ok: bool, detail: str = "") -> None:
global PASS, FAIL
if ok:
PASS += 1
print(f" ✓ {name}")
else:
FAIL += 1
print(f" ✗ {name} — {detail}")
# ── stub operator: canned, deterministic ────────────────────────────────────
CANNED_TASK = {
"task_id": "task-canned-001",
"status": "completed",
"model": "stub-model",
"route": "direct",
"user_text": "Canned reply: the shape is the contract.",
"success": True,
"degraded": [],
"iterations_used": 1,
"iteration_budget": 6,
}
class StubOperator:
def list_models(self):
return [{"name": "stub-model-a", "size_gb": 4.1},
{"name": "stub-model-b", "size_gb": 8.2}]
def get_status(self):
return {"identity": "rune", "role": "operator", "current_task": None,
"active_model": "stub-model-a", "tasks_pending": 0,
"tasks_running": 0, "tasks_completed": 3}
def send_task(self, description, model=None, channel="operator"):
out = dict(CANNED_TASK)
out["echo"] = description # proves the request reached the core
return out
def conversation_window(self, channel="operator", n=8):
return [{"role": "user", "content": "earlier", "channel_id": channel}][:n]
def resolve_pending_approval(self, approved):
return {"resolved": bool(approved), "approval_id": "appr-1"}
# ── http helpers ─────────────────────────────────────────────────────────────
def http(method: str, url: str, body: dict | None = None):
data = json.dumps(body).encode() if body is not None else None
req = urllib.request.Request(url, data=data, method=method,
headers={"Content-Type": "application/json"})
try:
with urllib.request.urlopen(req, timeout=10) as resp:
raw = resp.read().decode()
return resp.status, raw
except urllib.error.HTTPError as e:
return e.code, e.read().decode()
def normalize(obj):
"""Strip volatile fields (id, created) for parity comparison."""
if isinstance(obj, dict):
return {k: normalize(v) for k, v in obj.items()
if k not in ("id", "created")}
if isinstance(obj, list):
return [normalize(v) for v in obj]
return obj
def wait_ready(url: str, timeout: float = 10.0) -> bool:
deadline = time.time() + timeout
while time.time() < deadline:
try:
with urllib.request.urlopen(url, timeout=1) as r:
if r.status == 200:
return True
except Exception: # noqa: BLE001 — readiness poll: any failure = not yet
time.sleep(0.1)
return False
CANNED_CHAT = {"model": "stub-model-a",
"messages": [{"role": "user", "content": "Say the canned thing."}]}
def fire_shape_set(base: str) -> dict:
"""The canned request battery against one server; returns raw results."""
out = {}
out["models"] = http("GET", f"{base}/v1/models")
out["status"] = http("GET", f"{base}/v1/status")
out["chat"] = http("POST", f"{base}/v1/chat/completions", CANNED_CHAT)
out["stream"] = http("POST", f"{base}/v1/chat/completions",
{**CANNED_CHAT, "stream": True})
out["conversation"] = http("GET", f"{base}/v1/conversation?channel=operator&n=8")
out["approve"] = http("POST", f"{base}/v1/approvals/resolve", {"approved": True})
out["missing"] = http("GET", f"{base}/v1/definitely-not-here")
return out
def check_shapes(prefix: str, results: dict) -> None:
s, raw = results["models"]
body = json.loads(raw) if s == 200 else {}
test(f"{prefix}: /v1/models 200 + list envelope",
s == 200 and body.get("object") == "list"
and [m.get("id") for m in body.get("data", [])] == ["stub-model-a", "stub-model-b"]
and all(m.get("object") == "model" and m.get("owned_by") == "rune"
for m in body.get("data", [])),
f"status={s} body={raw[:160]!r}")
s, raw = results["status"]
body = json.loads(raw) if s == 200 else {}
test(f"{prefix}: /v1/status pass-through keys",
s == 200 and body.get("active_model") == "stub-model-a"
and body.get("tasks_completed") == 3,
f"status={s} body={raw[:160]!r}")
s, raw = results["chat"]
body = json.loads(raw) if s == 200 else {}
choices = body.get("choices", [{}])
test(f"{prefix}: chat.completion envelope + rune metadata",
s == 200 and body.get("object") == "chat.completion"
and choices[0].get("message", {}).get("role") == "assistant"
and "Canned reply" in choices[0].get("message", {}).get("content", "")
and body.get("rune", {}).get("task_id") == "task-canned-001"
and body.get("rune", {}).get("echo") == "Say the canned thing.",
f"status={s} body={raw[:200]!r}")
s, raw = results["stream"]
lines = [l for l in raw.strip().splitlines() if l.strip()]
parsed = []
for l in lines:
try:
parsed.append(json.loads(l))
except json.JSONDecodeError:
parsed.append(None)
test(f"{prefix}: NDJSON stream — chunk(s) then terminal",
s == 200 and len(parsed) >= 2 and None not in parsed
and all(p.get("object") == "chat.completion.chunk" for p in parsed)
and parsed[-1].get("choices", [{}])[0].get("finish_reason") == "stop"
and "Canned reply" in (parsed[0].get("choices", [{}])[0]
.get("delta", {}).get("content", "")),
f"status={s} lines={len(lines)} raw={raw[:200]!r}")
s, raw = results["conversation"]
body = json.loads(raw) if s == 200 else {}
test(f"{prefix}: /v1/conversation window shape",
s == 200 and isinstance(body.get("messages"), list)
and body["messages"][0]["role"] == "user",
f"status={s} body={raw[:160]!r}")
s, raw = results["approve"]
body = json.loads(raw) if s == 200 else {}
test(f"{prefix}: /v1/approvals/resolve round-trip",
s == 200 and body.get("resolved") is True,
f"status={s} body={raw[:160]!r}")
s, raw = results["missing"]
body = json.loads(raw) if raw else {}
test(f"{prefix}: 404 error envelope",
s == 404 and body.get("error", {}).get("code") == "not_found",
f"status={s} body={raw[:160]!r}")
def compare_results(py: dict, go: dict) -> None:
for key in ("models", "status", "chat", "conversation", "approve", "missing"):
(ps, praw), (gs, graw) = py[key], go[key]
same_status = ps == gs
try:
same_body = normalize(json.loads(praw)) == normalize(json.loads(graw))
except json.JSONDecodeError:
same_body = praw == graw
test(f"parity: {key} identical modulo volatile",
same_status and same_body,
f"py=({ps}) {praw[:140]!r} go=({gs}) {graw[:140]!r}")
# NDJSON: exact line-sequence equality modulo volatile
(ps, praw), (gs, graw) = py["stream"], go["stream"]
pl = [normalize(json.loads(l)) for l in praw.strip().splitlines() if l.strip()]
gl = [normalize(json.loads(l)) for l in graw.strip().splitlines() if l.strip()]
test("parity: NDJSON line sequences identical modulo volatile",
ps == gs and pl == gl,
f"py_lines={len(pl)} go_lines={len(gl)}")
def main() -> int:
print("=" * 47)
print("canary_rune_serve — H2 Go front-door parity battery")
print("=" * 47)
# ── 1. Python core server boots ───────────────────────────────────────
print("\n--- 1. Python core server ---")
try:
from rune import core_server
except ImportError as e:
test("core_server imports", False, str(e))
print(f"\nRESULTS: {PASS} pass, {FAIL} fail")
return 1
test("core_server imports", True)
server = core_server.make_server("127.0.0.1", 0, StubOperator())
port = server.server_address[1]
import threading
t = threading.Thread(target=server.serve_forever, daemon=True)
t.start()
py_base = f"http://127.0.0.1:{port}"
test("core server answers on an ephemeral port",
wait_ready(f"{py_base}/v1/status"), py_base)
# ── 2. Python shape freeze ────────────────────────────────────────────
print("\n--- 2. Python shape freeze ---")
py_results = fire_shape_set(py_base)
check_shapes("python", py_results)
# ── 3. Go serve subcommand ────────────────────────────────────────────
print("\n--- 3. Go serve subcommand ---")
go_dir = REPO / "src" / "go" / "rune-entry"
env = dict(os.environ)
env["GOFLAGS"] = env.get("GOFLAGS", "") + " -mod=mod"
# Per-run output path: a fixed /tmp name collides cross-user (nick owns
# the local build; the runner cannot overwrite it — run 18322 EACCES).
go_bin = f"/tmp/rune-serve-canary-{os.getpid()}"
r = subprocess.run(["go", "build", "-o", go_bin, "."],
cwd=go_dir, env=env, capture_output=True, text=True, timeout=300)
test("go build exits 0", r.returncode == 0,
f"rc={r.returncode} err={r.stderr[-200:]!r}")
if r.returncode != 0:
print(f"\nRESULTS: {PASS} pass, {FAIL} fail")
return 1
r = subprocess.run([go_bin, "serve", "--help"],
capture_output=True, text=True, timeout=30)
test("rune serve --help exits 0 with usage",
r.returncode == 0 and "core" in (r.stdout + r.stderr).lower(),
f"rc={r.returncode} out={(r.stdout + r.stderr)[:160]!r}")
# ── 4. Cross-language parity ──────────────────────────────────────────
print("\n--- 4. Parity: Go front → Python core ---")
go_port = port + 1
proc = subprocess.Popen(
[go_bin, "serve", "--port", str(go_port),
"--core", py_base],
stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
go_base = f"http://127.0.0.1:{go_port}"
try:
ready = wait_ready(f"{go_base}/v1/status")
test("go front answers on its port", ready, go_base)
if ready:
go_results = fire_shape_set(go_base)
check_shapes("go", go_results)
compare_results(py_results, go_results)
finally:
proc.terminate()
try:
proc.wait(timeout=5)
except subprocess.TimeoutExpired:
proc.kill()
# ── 5. Core-down honesty ──────────────────────────────────────────────
print("\n--- 5. Core-down honesty ---")
proc = subprocess.Popen(
[go_bin, "serve", "--port", str(go_port),
"--core", "http://127.0.0.1:1"], # nothing listening
stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
try:
time.sleep(0.5) # listener comes up even though the core never will
s, raw = http("POST", f"{go_base}/v1/chat/completions", CANNED_CHAT)
body = json.loads(raw) if raw else {}
test("go 502 envelope when the core is down",
s == 502 and body.get("error", {}).get("code") == "core_unavailable",
f"status={s} body={raw[:160]!r}")
s2, raw2 = http("POST", f"{py_base}/v1/chat/completions", CANNED_CHAT)
test("python core still healthy after go core-down probe",
s2 == 200, f"status={s2}")
finally:
proc.terminate()
try:
proc.wait(timeout=5)
except subprocess.TimeoutExpired:
proc.kill()
server.shutdown()
print(f"\n{'=' * 47}")
print(f"RESULTS: {PASS} pass, {FAIL} fail")
return 1 if FAIL else 0
if __name__ == "__main__":
sys.exit(main())
+192
View File
@@ -0,0 +1,192 @@
#!/usr/bin/env python3
"""canary_spine_meta.py — meta-scaffold canary (H-meta).
Contract BUILDER-2026-10-04T06:24:56Z-cli (Nick directive 2026-10-04):
every part of rune must exist as a modeled entity in rune's spine — the
spine is the meta-scaffold. This canary proves the projection models the
surface classes the closures have been honestly reporting as unmodeled:
1. rust-crate entities for both crates (evidence_provenance, query_memory)
2. go-surface entity for src/go/rune-entry
3. module entities for canary_audit_ledger / canary_rune_entry /
canary_evidence_provenance_rust (with obligated_canary edges)
4. workflow entities for every .gitea/workflows/*.yml
5. config entities for the tranche config files
6. determinism: two consecutive builds differ only in generated_at
7. open-world law: every pre-existing entity survives the rebuild
PASS/FAIL counter harness, exit 1 on any FAIL. Expectations observed from
the spine's own projection rules (src/rune/spine.py) and the umbrella
manifest — never invented.
Run under ~/.venvs/memory/bin/python3.
"""
import json
import os
import subprocess
import sys
PASS = 0
FAIL = 0
REPO = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", ".."))
SPINE_STORE = os.path.join(REPO, "store", "spine", "current.json")
def test(name, cond, detail=""):
global PASS, FAIL
mark = "✓" if cond else "✗"
print(f" {mark} {name}" + (f" — {detail}" if detail and not cond else ""))
if cond:
PASS += 1
else:
FAIL += 1
def build_spine():
r = subprocess.run(
[sys.executable, "-m", "rune.spine", "build"],
capture_output=True, text=True, timeout=180,
env={"PYTHONPATH": os.path.join(REPO, "src"), "PATH": os.environ.get("PATH", ""),
"HOME": os.environ.get("HOME", "")},
cwd=REPO)
return r
def load_spine():
return json.load(open(SPINE_STORE))
def main():
print("=== SPINE META-SCAFFOLD CANARY (H-meta) ===\n")
pre = load_spine() if os.path.isfile(SPINE_STORE) else {"entities": []}
pre_ids = {e["id"] for e in pre["entities"]}
# --- 0. Build ---
print("--- 0. Build ---")
r = build_spine()
test("spine build exits 0", r.returncode == 0, f"rc={r.returncode} err={r.stderr[-200:]!r}")
if r.returncode != 0:
print(f"\nRESULTS: {PASS} pass, {FAIL} fail")
sys.exit(1)
doc = load_spine()
ents = {e["id"]: e for e in doc["entities"]}
rels = doc.get("relationships", [])
# --- 1. Rust crates ---
print("\n--- 1. Rust crates ---")
for crate in ("evidence_provenance", "query_memory"):
eid = f"rust-crate:{crate}"
test(f"{eid} exists with kind rust-crate",
eid in ents and ents[eid]["kind"] == "rust-crate",
f"ids matching: {[i for i in ents if crate in i][:4]}")
# --- 2. Go surface ---
print("\n--- 2. Go surface ---")
test("go-surface:rune-entry exists with kind go-surface",
"go-surface:rune-entry" in ents
and ents["go-surface:rune-entry"]["kind"] == "go-surface",
f"ids matching: {[i for i in ents if 'rune-entry' in i][:4]}")
# --- 3. Missing canaries modeled ---
print("\n--- 3. Canaries ---")
for canary in ("canary_audit_ledger", "canary_rune_entry",
"canary_evidence_provenance_rust"):
mid = f"module:{canary}"
test(f"{mid} entity exists", mid in ents,
f"present: {mid in ents}")
edge = any(r["type"] == "obligated_canary" and r["to"] == mid for r in rels)
test(f"{mid} has an obligated_canary edge from its umbrella", edge)
# --- 4. Workflows ---
print("\n--- 4. Workflows ---")
import glob
wf_files = sorted(os.path.basename(p)
for p in glob.glob(os.path.join(REPO, ".gitea", "workflows", "*.yml")))
test("workflow inventory is non-empty", len(wf_files) >= 10, f"found {len(wf_files)}")
missing = [w for w in wf_files if f"workflow:{w}" not in ents]
test("every workflow file has a workflow entity", not missing,
f"missing: {missing[:4]}")
# --- 5. Configs ---
print("\n--- 5. Configs ---")
for cfg in ("go-version.txt", "lint-baseline.json", "pyright-baseline.json",
".coveragerc"):
cid = f"config:{cfg}"
test(f"{cid} entity exists with kind config",
cid in ents and ents[cid]["kind"] == "config",
f"present: {cid in ents}")
# --- 6. Determinism (steady-state pair) ---
# The honest determinism contract: once the store already carries the
# projector's facts, two consecutive builds with identical inputs must
# differ only in generated_at. A MIGRATION pair (store predating the
# projector vs store carrying it) is not a determinism failure — the
# first build after any projector change legitimately rewrites state.
# The warm-up build makes this check judge steady-state, not migration.
print("\n--- 6. Determinism (steady-state) ---")
warm = build_spine()
test("warm-up build exits 0", warm.returncode == 0,
f"rc={warm.returncode} err={warm.stderr[-160:]!r}")
r = build_spine()
first = load_spine()
r = build_spine()
second = load_spine()
def strip_ts(d):
"""Deep-strip time-varying fields: determinism is a STRUCTURAL
contract (same entities/relationships/observation content), not a
clock contract. observed_at/deployed_at/generated_at tick every
build by design — comparing them made this check pass or fail on
whether the two builds crossed a one-second boundary."""
TIME_KEYS = {"generated_at", "observed_at", "deployed_at", "updated_at",
"last_success"}
def walk(x):
if isinstance(x, dict):
return {k: walk(v) for k, v in x.items() if k not in TIME_KEYS}
if isinstance(x, list):
return [walk(v) for v in x]
return x
def normalize(d):
d = walk(d)
# build() appends one deployed_identity observation per pass and
# the store dedups on the FULL observation (timestamp included),
# so the observations list grows by one identical-modulo-timestamp
# entry per build. Structural content = the deduped set.
seen, obs = set(), []
for o in d.get("observations", []):
key = json.dumps(o, sort_keys=True)
if key not in seen:
seen.add(key)
obs.append(o)
if "observations" in d:
d["observations"] = obs
return d
return normalize(json.loads(json.dumps(d)))
fa, fb = strip_ts(first), strip_ts(second)
if fa != fb:
# Diagnostic: write the stripped pair so the divergence is inspectable
# instead of a bare boolean verdict.
with open("/tmp/spine_meta_determinism_first.json", "w") as f:
json.dump(fa, f, indent=2, sort_keys=True)
with open("/tmp/spine_meta_determinism_second.json", "w") as f:
json.dump(fb, f, indent=2, sort_keys=True)
test("two consecutive steady-state builds are structurally identical "
"(timestamps free to tick)",
fa == fb,
"rebuild not structurally stable "
"(stripped pair dumped to /tmp/spine_meta_determinism_*.json)")
# --- 7. Open-world law ---
print("\n--- 7. Open-world law ---")
lost = pre_ids - {e["id"] for e in doc["entities"]}
test("every pre-existing entity survives the rebuild", not lost,
f"lost: {sorted(lost)[:4]}")
print(f"\n{'=' * 47}")
print(f"RESULTS: {PASS} pass, {FAIL} fail")
sys.exit(1 if FAIL else 0)
if __name__ == "__main__":
main()
+174
View File
@@ -0,0 +1,174 @@
#!/usr/bin/env python3
"""canary_tui_transport.py — H4 slice 1 battery (T4.5, contract
BUILDER-2026-10-04T10:32:00Z-cli).
The TUI transport seam contract: InProcessTransport and V1Transport answer
the TUI's four-method call set EQUIVALENTLY against the same stub core —
V1 through the real HTTP stack (core_server), InProcess against the stub
operator directly. And the default is inprocess: the seam changes nothing
until RUNE_TUI_TRANSPORT=v1 opts in.
Sections:
1. /v1 surface carries the TUI endpoints (conversation + approvals)
2. transport factory defaults to inprocess (zero behavior change)
3. four-method equivalence (send_task, get_status,
get_conversation_window, resolve_pending_approval)
4. approval boolean round-trip both ways (True and False)
5. V1 transport against a dead core degrades loudly, never silently
Run: python3 src/rune/canary_tui_transport.py
"""
from __future__ import annotations
import json
import os
import subprocess
import sys
import threading
import urllib.error
import urllib.request
from pathlib import Path
REPO = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(REPO / "src"))
PASS = 0
FAIL = 0
def test(name: str, ok: bool, detail: str = "") -> None:
global PASS, FAIL
if ok:
PASS += 1
print(f" ✓ {name}")
else:
FAIL += 1
print(f" ✗ {name} — {detail}")
class StubOperator:
"""The TUI call set, canned."""
def get_status(self):
return {"identity": "rune", "role": "operator", "current_task": None,
"active_model": "stub-model", "tasks_pending": 1,
"tasks_running": 0, "tasks_completed": 2}
def send_task(self, description, model=None, channel="operator"):
return {"task_id": "task-stub-1", "status": "completed",
"user_text": f"stub answer to: {description}",
"model": model or "stub-model", "success": True,
"degraded": []}
def conversation_window(self, channel="operator", n=8):
return [{"role": "user", "content": "earlier question",
"channel_id": channel},
{"role": "assistant", "content": "earlier answer",
"channel_id": channel}][:n]
def resolve_pending_approval(self, approved):
return {"resolved": bool(approved), "approval_id": "appr-stub-1"}
def http(method: str, url: str, body: dict | None = None):
data = json.dumps(body).encode() if body is not None else None
req = urllib.request.Request(url, data=data, method=method,
headers={"Content-Type": "application/json"})
try:
with urllib.request.urlopen(req, timeout=10) as resp:
return resp.status, resp.read().decode()
except urllib.error.HTTPError as e:
return e.code, e.read().decode()
def main() -> int:
print("=" * 47)
print("canary_tui_transport — H4 slice 1 transport seam battery")
print("=" * 47)
from rune import core_server # noqa: E402
server = core_server.make_server("127.0.0.1", 0, StubOperator())
port = server.server_address[1]
t = threading.Thread(target=server.serve_forever, daemon=True)
t.start()
base = f"http://127.0.0.1:{port}"
# ── 1. /v1 surface carries the TUI endpoints ──────────────────────────
print("\n--- 1. TUI endpoints on the /v1 surface ---")
s, raw = http("GET", f"{base}/v1/conversation?channel=operator&n=8")
body = json.loads(raw) if s == 200 else {}
test("GET /v1/conversation returns the window",
s == 200 and isinstance(body.get("messages"), list)
and body["messages"][0]["role"] == "user",
f"status={s} body={raw[:140]!r}")
s, raw = http("POST", f"{base}/v1/approvals/resolve", {"approved": True})
body = json.loads(raw) if s == 200 else {}
test("POST /v1/approvals/resolve round-trips True",
s == 200 and body.get("resolved") is True,
f"status={s} body={raw[:140]!r}")
# ── 2. factory default ────────────────────────────────────────────────
print("\n--- 2. Factory default ---")
from rune import tui_transport # noqa: E402
os.environ.pop("RUNE_TUI_TRANSPORT", None)
default_transport = tui_transport.get_transport()
test("default transport is in-process (zero behavior change)",
isinstance(default_transport, tui_transport.InProcessTransport),
type(default_transport).__name__)
# ── 3. four-method equivalence ────────────────────────────────────────
print("\n--- 3. Four-method equivalence ---")
inproc = tui_transport.InProcessTransport(operator=StubOperator())
v1 = tui_transport.V1Transport(base)
py_status = inproc.get_status()
v1_status = v1.get_status()
test("get_status equivalent", py_status == v1_status,
f"inproc={py_status!r} v1={v1_status!r}")
py_task = inproc.send_task("the canned question", model="stub-model",
channel="operator")
v1_task = v1.send_task("the canned question", model="stub-model",
channel="operator")
test("send_task equivalent (rune block carried)",
py_task == v1_task,
f"inproc={py_task!r}\n v1={v1_task!r}")
py_win = inproc.get_conversation_window("operator", n=8)
v1_win = v1.get_conversation_window("operator", n=8)
test("get_conversation_window equivalent (list shape preserved)",
py_win == v1_win and isinstance(v1_win, list),
f"inproc={py_win!r} v1={v1_win!r}")
# ── 4. approval boolean round-trips ───────────────────────────────────
print("\n--- 4. Approval round-trips ---")
for value in (True, False):
py_r = inproc.resolve_pending_approval(value)
v1_r = v1.resolve_pending_approval(value)
test(f"resolve_pending_approval({value}) equivalent",
py_r == v1_r and v1_r.get("resolved") is value,
f"inproc={py_r!r} v1={v1_r!r}")
# ── 5. dead core degrades loudly ──────────────────────────────────────
print("\n--- 5. Dead core honesty ---")
dead = tui_transport.V1Transport("http://127.0.0.1:1")
try:
dead.get_status()
test("dead core raises (never a silent empty status)", False,
"returned without error")
except Exception as e: # noqa: BLE001 — the battery asserts the class, not the subtype
test("dead core raises (never a silent empty status)", True,
type(e).__name__)
server.shutdown()
print(f"\n{'=' * 47}")
print(f"RESULTS: {PASS} pass, {FAIL} fail")
return 1 if FAIL else 0
if __name__ == "__main__":
sys.exit(main())
+26 -1
View File
@@ -586,8 +586,33 @@ def build_cards():
return cards
def _cs_core():
"""The Rust capability_scoring core when available (strangler: Python
fallback is load-bearing). RUNE_CAPABILITY_CORE=python pins the fallback."""
if os.environ.get("RUNE_CAPABILITY_CORE", "auto") == "python":
return None
try:
from rune import cs_loader
except Exception: # noqa: BLE001 — any loader failure means fallback, never a broken call
return None
try:
return cs_loader if cs_loader.available() else None
except Exception: # noqa: BLE001 — availability probe must never crash the fallback
return None
def score_card(card, query):
"""Score a capability card against a query. Deterministic keyword matching."""
"""Score a capability card against a query. Deterministic keyword matching.
H3 slice 4: delegates to the Rust core when built (Python fallback)."""
core = _cs_core()
if core is not None:
return core.score_card(card, query)
return _py_score_card(card, query)
def _py_score_card(card, query):
"""Python core of score_card (parity oracle for the Rust core)."""
query_low = query.lower()
query_words = set(re.findall(r"\w+", query_low))
+27 -1
View File
@@ -211,10 +211,36 @@ def discover_candidates(doc: dict, req: CapabilityRequirement) -> list[dict]:
# ── fit evaluation ─────────────────────────────────────────────────────────
def _cs_core():
"""The Rust capability_scoring core when available (strangler: Python
fallback is load-bearing). RUNE_CAPABILITY_CORE=python pins the fallback."""
import os
if os.environ.get("RUNE_CAPABILITY_CORE", "auto") == "python":
return None
try:
from rune import cs_loader
except Exception: # noqa: BLE001 — any loader failure means fallback, never a broken call
return None
try:
return cs_loader if cs_loader.available() else None
except Exception: # noqa: BLE001 — availability probe must never crash the fallback
return None
def evaluate_fit(req: CapabilityRequirement, candidate: dict) -> dict:
"""DETERMINISTIC fit of one candidate against a requirement.
fit: EXACT | PARTIAL | INCOMPATIBLE | UNKNOWN.
Names are ignored; contracts + environment + health decide."""
Names are ignored; contracts + environment + health decide.
H3 slice 4: delegates to the Rust core when built (Python fallback)."""
core = _cs_core()
if core is not None:
return core.evaluate_fit(req.contracts, candidate)
return _py_evaluate_fit(req, candidate)
def _py_evaluate_fit(req: CapabilityRequirement, candidate: dict) -> dict:
"""Python core of evaluate_fit (parity oracle for the Rust core)."""
missing_contracts = sorted(set(req.contracts) - set(candidate["provides"]))
covered = len(req.contracts) - len(missing_contracts)
+180
View File
@@ -0,0 +1,180 @@
#!/usr/bin/env python3
"""ctypes loader for the Rust cognition_core (R2 W3.1).
Contract BUILDER-2026-10-05T07:19:27Z-cli. Pure deterministic kernels;
the env gate arrives as a string input (no-clock law, no env access in
Rust). Override with CC_RUST_LIB (a FILE path).
"""
from __future__ import annotations
import ctypes
import json
import os
from pathlib import Path
_REPO = Path(__file__).resolve().parents[2]
_CRATE_DIR = _REPO / "src" / "rust" / "cognition_core"
_LIB_PATH = Path(
os.environ.get("CC_RUST_LIB", _CRATE_DIR / "target" / "release" / "libcognition_core.so"))
_lib = None
def core():
"""The shared kernel probe (strangler law): the delegation entry the
kernel-consuming modules use. RUNE_COGNITION_CORE=python pins the
fallback — THIS is where the pin is honored, so every consumer gets it
(a module-local probe that ignores the pin silently defeats it)."""
import os
if os.environ.get("RUNE_COGNITION_CORE", "auto") == "python":
return None
try:
return _load()
except Exception: # noqa: BLE001 — build/loader failures mean fallback
return None
def available() -> bool:
return _LIB_PATH.is_file()
def _load() -> ctypes.CDLL:
global _lib
if _lib is not None:
return _lib
if not _LIB_PATH.exists():
raise FileNotFoundError(f"cognition_core not built: {_LIB_PATH}")
lib = ctypes.CDLL(str(_LIB_PATH))
lib.cc_free_string.argtypes = [ctypes.c_void_p]
lib.cc_free_string.restype = None
for sym in ("cc_default_estimator", "cc_default_budget",
"cc_default_memory_budget", "cc_memory_disabled",
"cc_persona_lex", "cc_persona_overlap", "cc_persona_decide",
"cc_drift_check", "cc_identity_prompt", "cc_growth_near_dup",
"cc_growth_profile", "cc_pins_recall", "cc_tier_get",
"cc_tier_promote", "cc_tier_demote", "cc_witness_extract_topic",
"cc_witness_signals", "cc_safety_get_params",
"cc_safety_update_pattern", "cc_abstraction_extract_facts"):
fn = getattr(lib, sym)
fn.argtypes = [ctypes.c_char_p]
fn.restype = ctypes.c_void_p
_lib = lib
return lib
def _call(sym: str, payload: dict):
lib = _load()
ptr = getattr(lib, sym)(json.dumps(payload).encode("utf-8"))
if not ptr:
raise RuntimeError(f"{sym} returned null")
try:
text = ctypes.cast(ptr, ctypes.c_char_p).value.decode("utf-8")
finally:
lib.cc_free_string(ptr)
env = json.loads(text)
if not env.get("ok"):
raise RuntimeError(f"{sym} core error: {env.get('err')}")
return env["value"]
def default_estimator(text: str) -> int:
return int(_call("cc_default_estimator", {"text": text}))
def default_budget(window: int) -> int:
return int(_call("cc_default_budget", {"window": window}))
def default_memory_budget(window) -> int:
return int(_call("cc_default_memory_budget", {"window": window}))
def memory_disabled() -> bool:
import os
return bool(_call("cc_memory_disabled",
{"value": os.environ.get("RUNE_MEMORY_OFF")}))
def persona_lex(text) -> list:
return _call("cc_persona_lex", {"text": text})
def persona_overlap(a: list, b: list) -> float:
return float(_call("cc_persona_overlap", {"a": a, "b": b}))
def persona_decide(text: str, rules: list, permissive: bool,
sem_scores: list | None = None) -> dict:
return _call("cc_persona_decide", {
"text": text, "rules": rules, "permissive": permissive,
"sem_scores": sem_scores})
def drift_check(text: str, patterns: list) -> list:
return _call("cc_drift_check", {"text": text, "patterns": patterns})
def identity_prompt(pronouns: dict, self_awareness: dict, anchors: dict,
embodiment: list) -> str:
return _call("cc_identity_prompt", {
"pronouns": pronouns, "self_awareness": self_awareness,
"anchors": anchors, "embodiment": embodiment})
def growth_near_dup(a: str, b: str) -> bool:
return bool(_call("cc_growth_near_dup", {"a": a, "b": b}))
def growth_profile(state: dict, order: list, entry_chars: int,
max_tokens: int) -> list:
return _call("cc_growth_profile", {
"state": state, "order": order, "entry_chars": entry_chars,
"max_tokens": max_tokens})
def pins_recall(pins: list, term: str, limit: int = 4) -> list:
return _call("cc_pins_recall", {"pins": pins, "term": term, "limit": limit})
def tier_get(entry: dict, now: str, hot_days: float = 7.0,
warm_days: float = 30.0) -> str:
return _call("cc_tier_get", {"entry": entry, "now": now,
"hot_days": hot_days, "warm_days": warm_days})
def tier_promote(entry: dict, now: str, hot_days: float = 7.0,
warm_days: float = 30.0) -> str:
return _call("cc_tier_promote", {"entry": entry, "now": now,
"hot_days": hot_days, "warm_days": warm_days})
def tier_demote(entry: dict, now: str, hot_days: float = 7.0,
warm_days: float = 30.0) -> str:
return _call("cc_tier_demote", {"entry": entry, "now": now,
"hot_days": hot_days, "warm_days": warm_days})
def witness_extract_topic(text: str, stopwords: list) -> list:
return _call("cc_witness_extract_topic",
{"text": text, "stopwords": stopwords})
def witness_signals(message: str, tables: dict, tool_error: str = "",
task_status: str = "", recent_topics: list | None = None) -> dict:
return _call("cc_witness_signals", {
"message": message, "tables": tables, "tool_error": tool_error,
"task_status": task_status, "recent_topics": recent_topics or []})
def safety_get_params(rows: list) -> dict:
return _call("cc_safety_get_params", {"rows": rows})
def safety_update_pattern(rows: list, pattern: str) -> list:
return _call("cc_safety_update_pattern", {"rows": rows, "pattern": pattern})
def abstraction_extract_facts(text: str, stopwords: list, min_len: int = 10):
return _call("cc_abstraction_extract_facts", {
"text": text, "stopwords": stopwords, "min_len": min_len})
+81 -28
View File
@@ -34,16 +34,38 @@ Usage:
claim_audit.py verify "<claim>" # ledger lookup
claim_audit.py audit "<draft text>" # full pipeline, JSON
claim_audit.py report # claimed-vs-verified stats
Phase G strangler slice 2 (port 6 — FINAL): the decision skeleton (verify
verdict tree, report aggregation, clean fold, evidence-term stoplist) is
served IN-PROCESS by the Rust `evidence_provenance` cdylib through
ep_loader.py — parity 17/17 proven vs this module's Python vintage. The
regex engine stays Python (zero-dep crate law): PATTERNS scan + sentence
extraction + degrade templates + reframe are regex machinery. I/O (journal)
stays Python. Timestamps pinned by the shim. API/CLI/PLUGIN unchanged.
VINTAGE MERGE (disclosed on #2449): the deployed scripts vintage carried the
SYMMETRY GUARD (research_lens.symmetry_check integration, 2026-08-15) which
src/rune's vintage lacked — preserved here (feature-preserving merge; the
guard is load-bearing behavior). Also REPAIRED: the TUI's `scan_text()`
consumer (rune_tui.py:3752, expecting {degraded: [...], claims: [...]}) had
NO implementation in EITHER vintage (silent try/except death since the TUI
call landed) — scan_text() is provided now as a first-class surface.
"""
import argparse
import json
import logging
import os
import re
import sys
from datetime import datetime, timezone
# ── Rust core wiring (slice-2 port-6 shim pattern) ──────────────────────────
_CRATE_DIR = "/mnt/workdrive/rune/src/rust/evidence_provenance"
if _CRATE_DIR not in sys.path:
sys.path.insert(0, _CRATE_DIR)
import ep_loader # noqa: E402 (loads target/release cdylib)
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import memory_env
MEM_DIR = memory_env.memory_dir()
@@ -54,7 +76,7 @@ _SD = os.path.dirname(os.path.abspath(__file__))
if _SD not in sys.path:
sys.path.insert(0, _SD)
logger = logging.getLogger(__name__)
logger = __import__("logging").getLogger(__name__)
# Lazy import: warm_neuron_store is imported inside functions that use it
# to avoid crashing the entire module on import if the dependency is missing.
@@ -75,10 +97,7 @@ def _get_warm_neuron_store():
# ---------------------------------------------------------------------------
# Strong-claim patterns. Grouped by type so we can degrade per-type on
# repeated failure. `comparative` is the highest-priority (the "we exceed X"
# class). Regexes are deliberately conservative: they flag CLAIM SHAPES, not
# innocent uses of words.
# Strong-claim patterns (unchanged — regex machinery stays Python).
# ---------------------------------------------------------------------------
PATTERNS = [
{
@@ -184,6 +203,22 @@ def scan(text):
return claims
def scan_text(text):
"""TUI consumer surface (rune_tui.py:3752): run the pipeline over a text
snippet and return {degraded: [...], claims: [...], clean: bool}.
REPAIRED: neither vintage implemented this — the TUI call died silently
in try/except since it landed. Semantics: audit the text, list degraded
claims (verdict != PASS), surface them to the TUI.
"""
if not text:
return {"degraded": [], "claims": [], "clean": True}
res = audit(text)
degraded = [r for r in res.get("claims", []) if r.get("verdict") != "PASS"]
return {"degraded": degraded, "claims": res.get("claims", []),
"clean": res.get("clean", True), "rewritten": res.get("rewritten", "")}
# ---------------------------------------------------------------------------
# 3. Evidence-ledger lookup
# ---------------------------------------------------------------------------
@@ -202,7 +237,7 @@ def _graph_has(term):
return row is not None
finally:
db.close()
except Exception:
except Exception: # noqa: BLE001 — ledger lookup degrades to no-evidence by design; a broken graph must not crash claim verification
return False
@@ -214,7 +249,7 @@ def _warm_has(term):
if wns is None:
return False
return wns.neuron_has(term)
except Exception:
except Exception: # noqa: BLE001 — warm-block lookup degrades to no-evidence by design; a broken store must not crash claim verification
return False
@@ -231,6 +266,9 @@ def verify(claim):
it. Those claims only PASS if they carry a measurable anchor (a number, a
probe run, a benchmark), because that's the kind of claim that needs both
a baseline and a measurement (per the claim-audit research).
Decision tree served by the Rust core (parity-proven); the regex flag
derivation + ledger lookups stay Python.
"""
low = claim.lower()
is_comparative = bool(
@@ -248,19 +286,16 @@ def verify(claim):
low,
)
)
# Comparative claims need a MEASURABLE anchor, not just a mention.
if is_comparative and not is_quantified:
return "DEGRADE", 0
# Extract candidate ledger terms: the subject noun phrase.
# Candidate ledger terms: filtered by the Rust stoplist/length rule.
terms = re.findall(r"\b([A-Za-z][A-Za-z ]{3,30})\b", claim)
terms = ep_loader.ca_evidence_terms(terms)
hits = 0
for t in terms:
if len(t) < 4 or t.lower() in ("this is", "that is", "for the"):
continue
if _graph_has(t) or _warm_has(t):
hits += 1
if hits >= 1:
return "PASS", hits
gate = ep_loader.ca_verify_gate(is_comparative, is_quantified, hits, True)
if gate["verdict"] == "PASS":
return "PASS", gate["evidence_hits"]
if re.search(r"\b(exceed|surpass|beat|beyond)\b", low):
return "DEGRADE", 0 # comparative with no ledger support = the exact case
return "DEGRADE", 0
@@ -322,11 +357,7 @@ def report():
return {"total": 0, "by_verdict": {}, "by_type": {}}
with open(JOURNAL) as f:
rows = [json.loads(l) for l in f if l.strip()]
by_verdict, by_type = {}, {}
for r in rows:
by_verdict[r.get("verdict", "?")] = by_verdict.get(r.get("verdict", "?"), 0) + 1
by_type[r.get("type", "?")] = by_type.get(r.get("type", "?"), 0) + 1
return {"total": len(rows), "by_verdict": by_verdict, "by_type": by_type}
return ep_loader.ca_report_aggregate(rows)
# ---------------------------------------------------------------------------
@@ -336,6 +367,19 @@ def audit(draft):
"""Run the full active gate over a draft. Returns verdicts + rewritten text."""
claims = scan(draft)
if not claims:
# SYMMETRY GUARD (deployed-vintage feature, preserved in the merge):
# even with no strong claim, check the draft for asymmetric-skepticism
# markers so the baloney detector cannot be applied one-sidedly.
try:
sys.path.insert(0, _SD)
from research_lens import symmetry_check
sym = symmetry_check(draft)
if sym.get("verdict") == "ASYMMETRY-FLAG":
return {"claims": [], "rewritten": draft, "clean": False,
"symmetry": sym}
except Exception: # noqa: BLE001 — guard is never-fatal by design (deployed-vintage discipline); audit output degrades to no-symmetry, not silence
logger.debug("symmetry_check unavailable/failed — continuing without symmetry", exc_info=True)
pass
return {"claims": [], "rewritten": draft, "clean": True}
rewritten = draft
results = []
@@ -357,9 +401,23 @@ def audit(draft):
"rewrite": new_text,
"note": note,
}
# SYMMETRY GUARD per-claim (deployed-vintage feature, preserved):
result["symmetry"] = _symmetry_safe(c["claim"])
results.append(result)
journal({"type": c["type"], "claim": c["claim"][:160], "verdict": verdict, "evidence_hits": hits})
return {"claims": results, "rewritten": rewritten, "clean": all(r["verdict"] == "PASS" for r in results)}
return {"claims": results, "rewritten": rewritten,
"clean": ep_loader.ca_audit_clean(results)}
def _symmetry_safe(text):
"""symmetry_check wrapper — never fatal (deployed-vintage discipline)."""
try:
from research_lens import symmetry_check
return symmetry_check(text)
except Exception: # noqa: BLE001 — wrapper is never-fatal by design; a symmetry outage must not kill claim auditing
logger.debug("symmetry_check unavailable/failed — no symmetry annotation", exc_info=True)
pass
return {}
def handle_hook(event, data=None):
@@ -412,11 +470,6 @@ if __name__ == "__main__":
# ---------------------------------------------------------------------------
# Plugin declaration — self-registers with the plugin registry.
# ---------------------------------------------------------------------------
# ---------------------------------------------------------------------------
# Hook handler — called by the microkernel when an event fires.
# ---------------------------------------------------------------------------
PLUGIN = {
"name": "claim_audit",
"version": "1.0.0",
@@ -427,4 +480,4 @@ PLUGIN = {
"provides": ["claim_audit"],
"priority": 40,
"auto_activate": True,
}
}
+95
View File
@@ -171,3 +171,98 @@ def _accept(command_id: str, construction_id: str, correlation_id: str,
"recorded_at": time.strftime("%Y-%m-%dT%H:%M:%SZ",
time.gmtime()),
"idempotent_replay": False}
def dispatch(command_type: str, *, construction_id: str,
correlation_id: str = "", command_id: str = "",
payload: dict | None = None,
store_path: str = "store/scaffold") -> dict:
"""The SERVING layer (T5.15 slice 2): validate the command against the
canonical store, execute the accepted intent through the Construction
Interface, and return the interface's envelope with the command
provenance attached.
Single entry point for `rune scaffold <command>` (the Go proxy) and
any future transport. Idempotency: a repeated command_id replays the
recorded outcome without re-executing.
Refusal still returns success=False envelope? NO — a refused command
is a VALID protocol outcome: the envelope carries accepted=False and
success=True (the dispatch itself worked; the lifecycle refused the
intent). Execution failures carry success=False.
"""
iface_path = None
if store_path and store_path != "store/scaffold":
iface_path = store_path
try:
from rune import construction_interface as ci
# store_path carries the serving layer's "path|project_id" contract
# (a bare path = last segment is the project id, matching how the
# Go proxy and the interface's own default name it).
if iface_path:
path_part, _, proj_part = iface_path.partition("|")
proj = proj_part or path_part.rstrip("/").split("/")[-1]
iface = ci.ConstructionInterface(builder="substrate",
project_store=path_part,
project_id=proj)
else:
iface = ci.ConstructionInterface(builder="substrate")
except Exception as exc: # noqa: BLE001 — serving failure is an envelope, not a crash
return {"accepted": False, "executed": False,
"success": False, "error": f"serving unavailable: {exc}"[:200],
"command_type": command_type,
"construction_id": construction_id}
intent = command(command_type, construction_id=construction_id,
correlation_id=correlation_id, command_id=command_id,
payload=payload, scaffold=iface.scaffold)
if intent.get("idempotent_replay"):
# Repeated delivery: return the recorded outcome WITHOUT executing
# again (the ledger's whole point). The first execution's envelope
# is not re-derivable here, so the replay carries the intent and
# the flag — the caller knows the effect already happened.
return {"accepted": True, "executed": False,
"success": True, "idempotent_replay": True,
"op": intent.get("op"),
"command_type": command_type,
"construction_id": construction_id,
"correlation_id": intent.get("correlation_id", ""),
"command_id": intent.get("command_id", "")}
if not intent.get("accepted"):
return {"accepted": False, "executed": False, "success": True,
"reason": intent.get("reason"),
"command_type": command_type,
"construction_id": construction_id,
"correlation_id": intent.get("correlation_id", ""),
"command_id": intent.get("command_id", ""),
"idempotent_replay": intent.get("idempotent_replay", False)}
op = intent["op"]
args = intent["args"]
ops = {
"resume": lambda: iface.resume(args["contract_id"]),
"transition": lambda: iface.transition(
args["contract_id"], args["phase"],
evidence_ref=f"command:{intent['command_id']}"
+ (f" approved_by:{args['approved_by']}" if args.get("approved_by") else "")),
"amend_scope": lambda: iface.amend_scope(
args["contract_id"], args["add_paths"],
reason=args.get("reason", "substrate context"),
authority=args.get("authority", "substrate")),
}
fn = ops.get(op)
if fn is None:
return {"accepted": True, "executed": False, "success": False,
"error": f"no executor for op {op!r}",
"command_type": command_type,
"construction_id": construction_id}
env = fn()
return {"accepted": True, "executed": True,
"success": bool(env.get("success")),
"envelope": env,
"command_type": command_type,
"construction_id": construction_id,
"correlation_id": intent.get("correlation_id", ""),
"command_id": intent.get("command_id", ""),
"idempotent_replay": False}
+71
View File
@@ -125,6 +125,73 @@ _SURFACE_UI = "ui:"
_SURFACE_REGION = "region:"
_SURFACE_CONTRACT = "contract:"
_SURFACE_MODULE = "module:"
# H-meta entity classes (spine meta-scaffold, 567fd39): the spine models
# these kinds as first-class entities, so contract scope resolution must
# recognize them as structural ids too — an unrecognized kind can never
# close as correctly_predicted (T4.7 row-1 flag).
_SURFACE_RUST_CRATE = "rust-crate:"
_SURFACE_GO_SURFACE = "go-surface:"
_SURFACE_CONFIG = "config:"
_SURFACE_WORKFLOW = "workflow:"
def language_placement(project: str, characteristics: list[str]) -> dict:
"""The protocol's language-placement verdict (R2 Wave 1, Nick's addition):
rune units get the Go/Rust dichotomy rule; other projects get a
characteristic-based fit assessment. Deterministic, evidence-labeled,
never silent. Delegates to the Rust scaffold_core when built
(Python fallback below — strangler law)."""
core = _sc_core()
if core is not None:
return core.language_placement(project, characteristics)
return _py_language_placement(project, characteristics)
def _sc_core():
"""The Rust scaffold_core when available (R2 Wave 1 strangler).
RUNE_SCAFFOLD_CORE=python pins the fallback."""
import os
if os.environ.get("RUNE_SCAFFOLD_CORE", "auto") == "python":
return None
try:
from rune import sc_loader
except Exception: # noqa: BLE001 — any loader failure means fallback, never a broken call
return None
try:
return sc_loader if sc_loader.available() else None
except Exception: # noqa: BLE001 — availability probe must never crash the fallback
return None
def _py_language_placement(project: str, characteristics: list[str]) -> dict:
"""Python core of language_placement (parity oracle)."""
go_signals = ("network", "http", "socket", "server", "dispatch",
"reactive", "user-facing", "turn-time")
rust_signals = ("deterministic", "stateful", "storage", "cognition",
"memory", "compute", "parity", "ledger")
chars = [c.lower() for c in characteristics]
go_hits = [s for s in go_signals if any(s in c for c in chars)]
rust_hits = [s for s in rust_signals if any(s in c for c in chars)]
if project.lower() == "rune":
if go_hits and not rust_hits:
placement, rule = "go", "rune dichotomy: reactive/initiating markers"
elif rust_hits and not go_hits:
placement, rule = "rust", "rune dichotomy: deterministic/state markers"
elif rust_hits and go_hits:
placement, rule = "hybrid", "rune dichotomy: both marker classes — a contracted membrane junction"
else:
placement, rule = "rust", "rune dichotomy: pure compute defaults to the deterministic backend"
else:
if go_hits and not rust_hits:
placement, rule = "go", "characteristic fit: reactive/network profile"
elif rust_hits and not go_hits:
placement, rule = "rust", "characteristic fit: deterministic/state profile"
elif rust_hits:
placement, rule = "hybrid", "characteristic fit: mixed profile — split at the membrane"
else:
placement, rule = "unassessed", "no placement signals — the unit needs characteristics recorded"
return {"placement": placement, "rule": rule,
"evidence": {"go_signals": go_hits, "rust_signals": rust_hits}}
def classify_surface(surface: str) -> str:
@@ -141,6 +208,10 @@ def classify_surface(surface: str) -> str:
_SURFACE_CONTRACT,
_SURFACE_MODULE,
_SURFACE_CAPABILITY,
_SURFACE_RUST_CRATE,
_SURFACE_GO_SURFACE,
_SURFACE_CONFIG,
_SURFACE_WORKFLOW,
)
):
return "structural_entity"
+146 -12
View File
@@ -36,6 +36,7 @@ from typing import Any
import json
import sys
import uuid
from pathlib import Path
from rune import structural_store as ss
@@ -88,6 +89,39 @@ def _envelope(
return env
def _to_stage_check(spec: dict):
"""One guard spec → a stage_guards.StageCheck (T5.15 wire)."""
from rune import stage_guards as sg
return sg.StageCheck(
name=str(spec.get("name", "guard")),
kind=str(spec.get("kind", "assert")),
argv=spec.get("argv"),
output_pattern=str(spec.get("output_pattern", "")),
path=str(spec.get("path", "")),
check=spec.get("check"),
description=str(spec.get("description", "")),
)
def _guard_specs_from_obligations(c: dict) -> list[dict]:
"""The contract's guard declarations: verification_obligations rows
shaped `guard:<json-spec>` (the decision path records them; anything
else stays a free-text obligation for the projection). JSONDecodeError
→ skipped with no invented facts."""
specs: list[dict] = []
for item in (c.get("identities") or {}).get("verification_obligations", []) or []:
if not isinstance(item, str) or not item.startswith("guard:"):
continue
try:
spec = json.loads(item[6:])
if isinstance(spec, dict):
specs.append(spec)
except json.JSONDecodeError:
continue
return specs
class ConstructionInterface:
"""The builder-facing construction lifecycle for ONE project target.
@@ -291,6 +325,13 @@ class ConstructionInterface:
"transition": {
"phase": {"type": "str", "required": True,
"allowed": list(sc.PHASES)},
"evidence_ref": {"type": "str", "required": False},
"guard_checks": {
"type": "list[dict] | None", "required": False,
"notes": ("stage-guard specs run on the "
"completion edges (verifying/closed); "
"a failure BLOCKS with "
"VERIFICATION_FAILED — T5.15 wire")},
},
"close": {
"observed_evidence": {
@@ -381,7 +422,8 @@ class ConstructionInterface:
errors.append({"error": Errors.INSUFFICIENT_EVIDENCE, "detail": trace["rationale"][:200]})
contract = None
if generate and trace["resolution"] not in ("INSUFFICIENT_EVIDENCE",):
cid = contract_id or f"BUILDER-{ss.now()}-{self.builder}"
cid = (contract_id
or f"BUILDER-{ss.now()}-{uuid.uuid4().hex[:6]}-{self.builder}")
cd.generate_contract(
self.scaffold,
doc,
@@ -607,8 +649,57 @@ class ConstructionInterface:
warnings=warnings,
)
def transition(self, contract_id: str, phase: str, *, evidence_ref: str = "") -> dict:
def transition(self, contract_id: str, phase: str, *, evidence_ref: str = "",
guard_checks: list[dict] | None = None) -> dict:
"""Move a contract phase. Completion-wave slice (T5.15, the
stage_guards wire): entering `verifying` and `closed` runs the
§15F/§15G guards — readiness/exit StageChecks and test-truth
verification — when the contract declares them
(identities.verification_obligations carries `guard:<spec>` rows,
or guard_checks passes inline specs).
guard_checks spec: {"name", "kind": "command"|"path_exists"|"assert",
"argv"/"path"/"output_pattern", "check": callable (in-process only),
"description"}. A guard failure is a VERIFICATION_FAILED envelope:
the phase does NOT move.
The legality map stays the scaffold's authority; guards ADD evidence
obligations on the completion-facing edges (building→verifying,
verifying→closed) — a tiny bounded change can pass guards=[] freely.
"""
doc = self.scaffold.load()
c = self.scaffold.contract(doc, contract_id)
if c is None:
return _envelope(
"transition",
success=False,
ids={"contract_id": contract_id},
errors=[{"error": Errors.UNKNOWN_CONTRACT, "detail": f"no contract {contract_id}"}],
)
if phase in ("verifying", "closed"):
from rune import stage_guards as sg
specs = guard_checks
if specs is None:
specs = _guard_specs_from_obligations(c)
if specs:
stage = sg.StageContract(
stage_id=f"{contract_id}:{phase}",
contract_id=contract_id,
ready_when=[_to_stage_check(s) for s in specs],
done_when=[_to_stage_check(s) for s in specs],
)
canary = sg.readiness_canary(stage)
if canary["verdict"] == "BLOCKED":
return _envelope(
"transition",
success=False,
ids={"contract_id": contract_id},
state={"phase": c.get("phase"), "guard": canary},
errors=[{"error": Errors.VERIFICATION_FAILED,
"detail": "stage guards blocked the transition",
"failures": canary["failures"]}],
)
try:
self.scaffold.transition_contract(
doc, contract_id, phase, evidence_ref=evidence_ref or f"builder:{self.builder}"
@@ -679,21 +770,62 @@ class ConstructionInterface:
material_closure = self._auto_material_change(
scope_files or [],
note=f"contract {contract_id} closed (material change)")
continuation = self._programme_continuation(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")
if k in q
},
}
if continuation is not None:
state["programme_continuation"] = continuation
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")
if k in q
},
},
state=state,
)
def _programme_continuation(self, contract_id: str) -> dict | None:
"""T5.15 slice 3 (the programme_continuation wire): after a legal
close, any persisted Programme stage bound to this contract is
marked COMPLETE and the continuation canary's verdict rides the
close envelope — 'task done' ≠ 'programme done', and the next
eligible stage is SURFACED, never guessed. No programme bound →
None (no continuation block in the envelope). Failures degrade:
a broken programme ledger never fails a real closure."""
try:
from pathlib import Path as _Path
from rune import programme_continuation as pc
base = _Path(self.scaffold.current).parent / pc.DEFAULT_PROGRAMME_AREA
if not base.is_dir():
return None
for prog_file in sorted(base.glob("*.json")):
prog = pc.Programme.load(base, prog_file.stem)
if prog is None:
continue
stage = next((s for s in prog.stages
if s.stage_id == contract_id
and s.state != pc.COMPLETE), None)
if stage is None:
continue
stage.state = pc.COMPLETE
stage.result["closed_at_contract"] = contract_id
prog.save(base)
return {
"programme_id": prog.programme_id,
"stage_marked_complete": contract_id,
"canary": prog.continuation_canary(),
}
except Exception as exc: # noqa: BLE001 — ledger failure never fails a closure
return {"error": f"programme continuation degraded: {exc}"[:200]}
return None
def _auto_material_change(self, scope_files: list[str], *,
note: str) -> dict:
"""T21.4 §21.4H (rule 19Q.2): governed construction closes → the
@@ -1019,7 +1151,9 @@ def main(argv=None) -> int:
if cmd == "verify":
return _out(iface.verify(args[0], changed_files=(json.loads(args[1]) if len(args) > 1 else None)))
if cmd == "transition":
return _out(iface.transition(args[0], args[1], evidence_ref=args[2] if len(args) > 2 else ""))
guard_checks = json.loads(args[3]) if len(args) > 3 else None
return _out(iface.transition(args[0], args[1], evidence_ref=args[2] if len(args) > 2 else "",
guard_checks=guard_checks))
if cmd == "close":
return _out(
iface.close(
+58 -3
View File
@@ -100,19 +100,52 @@ class ContributionBlock:
def default_estimator(text: str) -> int:
"""Deterministic conservative token estimate: len(text) // 4.
Bytes/4 over-counts for English prose (the conservative direction).
tiktoken would be tighter but is NOT deterministic across builds and
is not a dependency — the plan allows either, installer's choice.
R2 W3.1: delegates to the Rust cognition_core when built (Python
fallback — strangler law; RUNE_COGNITION_CORE=python pins it).
"""
core = _cc_core()
if core is not None:
return core.default_estimator(text or "")
return _py_default_estimator(text)
def _py_default_estimator(text: str) -> int:
"""Python oracle (parity floor)."""
return len(text or "") // 4
def _cc_core():
"""Rust cognition_core when available; RUNE_COGNITION_CORE=python
pins the Python fallback (strangler law)."""
import os
if os.environ.get("RUNE_COGNITION_CORE", "auto") == "python":
return None
try:
from rune import cc_loader
except Exception: # noqa: BLE001 — loader probe failure means fallback
return None
try:
return cc_loader if cc_loader.available() else None
except Exception: # noqa: BLE001 — availability probe never crashes
return None
def default_budget(window: int) -> int:
"""Input budget = window - max(2048, window//4) - window//20.
Reserves room for the completion (at least 2048 or a quarter of the
window) plus a 5% safety margin for serving overhead.
R2 W3.1: delegates to the Rust kernel when built.
"""
core = _cc_core()
if core is not None:
return core.default_budget(window)
return _py_default_budget(window)
def _py_default_budget(window: int) -> int:
"""Python oracle (parity floor)."""
return max(1, window - max(2048, window // 4) - window // 20)
@@ -127,7 +160,17 @@ def default_memory_budget(window: int | None) -> int:
ContextBroker owns the FINAL memory budget: the sum of every
MEMORY_BLOCK_NAMES block may never exceed this, regardless of the
global input budget. An unknown window falls back to the cap only.
R2 W3.1: delegates to the Rust kernel when built.
"""
core = _cc_core()
if core is not None:
return core.default_memory_budget(int(window) if window else 0)
return _py_default_memory_budget(window)
def _py_default_memory_budget(window: int | None) -> int:
"""Python oracle (parity floor)."""
if not window or window <= 0:
return MEMORY_BUDGET_CAP
return min(MEMORY_BUDGET_CAP, int(window) // 8)
@@ -140,6 +183,18 @@ def memory_disabled() -> bool:
contribute nothing at all — the trace proves 0 memory tokens.
"""
value = os.environ.get("RUNE_MEMORY_OFF", "").strip().lower()
gate = _py_memory_disabled_value(value)
core = _cc_core()
if core is not None and gate:
# the Rust core gets the RAW value (it trims/lowers itself) and
# the Python gate cross-checks — a disagreement is impossible on
# parity-proven kernels, and the Python verdict remains authority
return bool(core.memory_disabled()) and gate
return gate
def _py_memory_disabled_value(value: str) -> bool:
"""Python oracle (parity floor) over the RAW string."""
return value not in {"", "0", "false", "no", "off"}
+234
View File
@@ -0,0 +1,234 @@
#!/usr/bin/env python3
"""core_server.py — stdlib HTTP sidecar exposing the Rune operator surface
(H2, contract BUILDER-2026-10-04T08:32:43Z-cli).
Go owns the front door (rune serve); this is the core behind it during the
transition — the same operator functions the TUI calls in-process, served
over 127.0.0.1 with NDJSON streaming. The parity battery
(canary_rune_serve.py) freezes these shapes against the Go front.
Endpoints:
GET /v1/models {"object": "list", "data": [{id, object, owned_by}]}
GET /v1/status operator.get_status() pass-through
POST /v1/chat/completions OpenAI envelope + "rune" task metadata;
stream=true → NDJSON chat.completion.chunk lines
Error envelope (every failure path): {"error": {"code", "message"}} with a
matching HTTP status — the Go front reproduces these byte-for-byte.
Stdlib only (zero-dep split law). The operator is injected, so the canary
drives a deterministic stub; main() binds the real operator.
"""
from __future__ import annotations
import json
import time
import uuid
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from typing import Any, cast
CHAT_PATH = "/v1/chat/completions"
def _envelope_error(code: str, message: str) -> dict:
return {"error": {"code": code, "message": message}}
def models_view(operator: Any) -> dict:
return {
"object": "list",
"data": [
{"id": m.get("name", ""), "object": "model", "owned_by": "rune"}
for m in operator.list_models()
],
}
def chat_completion(result: dict, model: str) -> dict:
"""Map an operator send_task result to the chat.completion envelope.
The full operator result rides under "rune" — the front door's parity
contract covers it; nothing the core knows is dropped."""
return {
"id": f"chatcmpl-{uuid.uuid4().hex[:24]}",
"object": "chat.completion",
"created": int(time.time()),
"model": result.get("model") or model,
"choices": [{
"index": 0,
"message": {"role": "assistant",
"content": result.get("user_text", "")},
"finish_reason": "stop",
}],
"rune": result,
}
def chat_chunks(result: dict, model: str) -> list[dict]:
"""NDJSON chunk sequence: content chunk(s) then a terminal chunk with
finish_reason + the rune metadata block. send_task is a blocking
lifecycle today — the framing is the contract being proven; token
streaming slots in when the core grows it."""
base = {
"id": f"chatcmpl-{uuid.uuid4().hex[:24]}",
"object": "chat.completion.chunk",
"created": int(time.time()),
"model": result.get("model") or model,
}
content = {
**base,
"choices": [{
"index": 0,
"delta": {"role": "assistant",
"content": result.get("user_text", "")},
"finish_reason": None,
}],
}
terminal = {
**base,
"choices": [{"index": 0, "delta": {}, "finish_reason": "stop"}],
"rune": result,
}
return [content, terminal]
class CoreHandler(BaseHTTPRequestHandler):
# BaseRequestHandler.server is typed as the generic base; the only class
# this handler ever serves under is CoreServer (make_server). cast() is
# the honest narrowing — no ignore comments.
@property
def core(self) -> "CoreServer":
return cast("CoreServer", self.server)
# BaseHTTPRequestHandler logs every request to stderr by default; the
# canary and CI don't need the noise — errors surface as envelopes.
def log_message(self, format: str, *args: Any) -> None: # noqa: A002 — stdlib signature, the name is fixed upstream
pass
# ── helpers ───────────────────────────────────────────────────────────
def _send_json(self, status: int, body: dict) -> None:
raw = json.dumps(body).encode()
self.send_response(status)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(raw)))
self.end_headers()
self.wfile.write(raw)
def _send_ndjson(self, status: int, items: list[dict]) -> None:
raw = "".join(json.dumps(i) + "\n" for i in items).encode()
self.send_response(status)
self.send_header("Content-Type", "application/x-ndjson")
self.send_header("Content-Length", str(len(raw)))
self.end_headers()
self.wfile.write(raw)
# ── routes ────────────────────────────────────────────────────────────
def do_GET(self) -> None:
if self.path == "/v1/models":
try:
self._send_json(200, models_view(self.core.operator))
except Exception as exc: # noqa: BLE001 — the envelope IS the error contract
self._send_json(500, _envelope_error("internal_error", str(exc)[:200]))
elif self.path == "/v1/status":
try:
self._send_json(200, self.core.operator.get_status())
except Exception as exc: # noqa: BLE001 — the envelope IS the error contract
self._send_json(500, _envelope_error("internal_error", str(exc)[:200]))
elif self.path.startswith("/v1/conversation"):
# H4 slice 1: the TUI's conversation window over the wire.
try:
from urllib.parse import parse_qs, urlparse
qs = parse_qs(urlparse(self.path).query)
channel = qs.get("channel", ["operator"])[0]
n = int(qs.get("n", ["8"])[0])
self._send_json(200, {
"messages": self.core.operator.conversation_window(channel, n=n)})
except Exception as exc: # noqa: BLE001 — the envelope IS the error contract
self._send_json(500, _envelope_error("internal_error", str(exc)[:200]))
else:
self._send_json(404, _envelope_error("not_found", f"no route {self.path}"))
def do_POST(self) -> None:
if self.path == "/v1/approvals/resolve":
# H4 slice 1: approval resolution over the wire.
try:
length = int(self.headers.get("Content-Length", "0"))
body = json.loads(self.rfile.read(length) or b"{}")
except (ValueError, json.JSONDecodeError) as exc:
self._send_json(400, _envelope_error("bad_request", f"invalid JSON body: {exc}"))
return
try:
self._send_json(200, self.core.operator.resolve_pending_approval(
bool(body.get("approved"))))
except Exception as exc: # noqa: BLE001 — the envelope IS the error contract
self._send_json(500, _envelope_error("internal_error", str(exc)[:200]))
return
if self.path != CHAT_PATH:
self._send_json(404, _envelope_error("not_found", f"no route {self.path}"))
return
try:
length = int(self.headers.get("Content-Length", "0"))
body = json.loads(self.rfile.read(length) or b"{}")
except (ValueError, json.JSONDecodeError) as exc:
self._send_json(400, _envelope_error("bad_request", f"invalid JSON body: {exc}"))
return
messages = body.get("messages") or []
user_texts = [m.get("content", "") for m in messages
if m.get("role") == "user"]
if not user_texts:
self._send_json(400, _envelope_error(
"bad_request", "messages must include a user role entry"))
return
model = body.get("model") or None
try:
result = self.core.operator.send_task(user_texts[-1], model)
except Exception as exc: # noqa: BLE001 — the envelope IS the error contract
self._send_json(500, _envelope_error("internal_error", str(exc)[:200]))
return
if body.get("stream"):
self._send_ndjson(200, chat_chunks(result, model or ""))
else:
self._send_json(200, chat_completion(result, model or ""))
class CoreServer(ThreadingHTTPServer):
daemon_threads = True
allow_reuse_address = True
def __init__(self, host: str, port: int, operator: Any):
super().__init__((host, port), CoreHandler)
self.operator = operator
def make_server(host: str, port: int, operator: Any) -> CoreServer:
"""Build the server (unstarted) — the canary's entry point."""
return CoreServer(host, port, operator)
def main(argv: list[str] | None = None) -> int:
import argparse
parser = argparse.ArgumentParser(description="Rune core sidecar (H2)")
parser.add_argument("--host", default="127.0.0.1")
parser.add_argument("--port", type=int, default=8376)
args = parser.parse_args(argv)
import os
import sys
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import rune_operator # noqa: E402 — late import: heavy module
server = make_server(args.host, args.port, rune_operator.get_operator())
print(f"rune core server on {args.host}:{args.port}", flush=True)
try:
server.serve_forever()
except KeyboardInterrupt:
pass
return 0
if __name__ == "__main__":
import sys
sys.exit(main())
+90
View File
@@ -0,0 +1,90 @@
#!/usr/bin/env python3
"""ctypes loader for the Rust capability_scoring core (H3 slice 4, T4.4).
Contract BUILDER-2026-10-04T09:43:55Z-cli. Gives capability_fit and
capability_broker the Rust scoring cores through the cdylib with no
subprocess.
FFI contract (doc-envelope law — every call returns {"ok", "value"|"err"}):
* cs_evaluate_fit({contracts, candidate}) -> fit dict
* cs_score_card({card, query}) -> number
* cs_free_string(ptr)
FFI segfault law: every symbol gets explicit argtypes + restype (c_void_p
for returned char*). Zero dependencies; override the .so with CS_RUST_LIB
(a FILE path).
"""
from __future__ import annotations
import ctypes
import json
import os
from pathlib import Path
_REPO = Path(__file__).resolve().parents[2]
_CRATE_DIR = _REPO / "src" / "rust" / "capability_scoring"
_LIB_PATH = Path(
os.environ.get(
"CS_RUST_LIB", _CRATE_DIR / "target" / "release" / "libcapability_scoring.so"
)
)
_lib = None
class CapabilityScoringUnavailable(FileNotFoundError):
"""The Rust core is not built (or CS_RUST_LIB points nowhere). Callers
fall back to the Python implementation — strangler law."""
def available() -> bool:
return _LIB_PATH.is_file()
def _load() -> ctypes.CDLL:
global _lib
if _lib is not None:
return _lib
if not _LIB_PATH.exists():
raise CapabilityScoringUnavailable(
f"Rust capability_scoring core not built: {_LIB_PATH} missing — run "
f"`cargo build --release` in {_CRATE_DIR}"
)
lib = ctypes.CDLL(str(_LIB_PATH))
lib.cs_free_string.argtypes = [ctypes.c_void_p]
lib.cs_free_string.restype = None
for sym in ("cs_evaluate_fit", "cs_score_card"):
fn = getattr(lib, sym)
fn.argtypes = [ctypes.c_char_p]
fn.restype = ctypes.c_void_p # char* — never default c_int
_lib = lib
return lib
def _call(sym: str, payload: dict):
lib = _load()
fn = getattr(lib, sym)
raw = json.dumps(payload).encode("utf-8")
ptr = fn(raw)
if not ptr:
raise RuntimeError(f"{sym} returned a null pointer")
try:
text = ctypes.cast(ptr, ctypes.c_char_p).value.decode("utf-8")
finally:
lib.cs_free_string(ptr)
env = json.loads(text)
if not env.get("ok"):
raise RuntimeError(f"{sym} core error: {env.get('err')}")
return env["value"]
def evaluate_fit(contracts: list, candidate: dict) -> dict:
"""Deterministic EXACT/PARTIAL/INCOMPATIBLE/UNKNOWN fit decision."""
return _call("cs_evaluate_fit",
{"contracts": contracts, "candidate": candidate})
def score_card(card: dict, query: str) -> float:
"""Keyword scoring of a capability card against a query."""
return _call("cs_score_card", {"card": card, "query": query})
+50 -17
View File
@@ -40,6 +40,54 @@ CACHE_BOOST_ROCK = 2.0 # multiplier for rock-status entries
CACHE_BOOST_ESTABLISHED = 1.5 # multiplier for established entries
# ── pure decision helpers (H3 slice 2: the Rust core serves these when
# built; the _py_ variants are the load-bearing fallback + parity oracle).
from rune.drift_core import _ms_core # noqa: E402 (no cycle)
def _py_compute_priority(entry: dict) -> float:
"""Compute cache priority: wash_count × importance × status boost."""
wash = entry.get("wash_count", 0) or 0
imp = entry.get("importance", 0.5) or 0.5
status = entry.get("rock_status", "washable")
# Logarithmic wash scaling (diminishing returns)
wash_factor = min(1.0, math.log1p(wash) / math.log1p(20))
# Status boost
status_boost = 1.0
if status == "rock":
status_boost = CACHE_BOOST_ROCK
elif status == "established":
status_boost = CACHE_BOOST_ESTABLISHED
return wash_factor * imp * status_boost
def _py_qualifies_for_cache(entry: dict) -> bool:
"""The add() admission decision: proven through use OR high importance."""
wash = entry.get("wash_count", 0) or 0
imp = entry.get("importance", 0.5) or 0.5
return not (wash < MIN_WASH_FOR_CACHE and imp < 0.8)
def compute_priority(entry: dict) -> float:
"""Module-level priority (Rust core when built)."""
core = _ms_core()
if core is not None:
return core.compute_priority(entry)
return _py_compute_priority(entry)
def qualifies_for_cache(entry: dict) -> bool:
"""Module-level qualification (Rust core when built)."""
core = _ms_core()
if core is not None:
return core.qualifies_for_cache(entry)
return _py_qualifies_for_cache(entry)
class DriftCache:
"""Drift-aware cache with wash-count-based priority."""
@@ -65,21 +113,7 @@ class DriftCache:
def _compute_priority(self, entry: dict) -> float:
"""Compute cache priority: wash_count × importance × status boost."""
wash = entry.get("wash_count", 0) or 0
imp = entry.get("importance", 0.5) or 0.5
status = entry.get("rock_status", "washable")
# Logarithmic wash scaling (diminishing returns)
wash_factor = min(1.0, math.log1p(wash) / math.log1p(20))
# Status boost
status_boost = 1.0
if status == "rock":
status_boost = CACHE_BOOST_ROCK
elif status == "established":
status_boost = CACHE_BOOST_ESTABLISHED
return wash_factor * imp * status_boost
return compute_priority(entry)
def add(self, entry_id: int, entry: dict):
"""Add an entry to the cache.
@@ -89,8 +123,7 @@ class DriftCache:
- importance >= 0.8 (high importance, worth caching even if untested)
"""
wash = entry.get("wash_count", 0) or 0
imp = entry.get("importance", 0.5) or 0.5
if wash < MIN_WASH_FOR_CACHE and imp < 0.8:
if not qualifies_for_cache(entry):
return # Not proven enough and not important enough
priority = self._compute_priority(entry)
+59 -11
View File
@@ -35,7 +35,7 @@ WASH_THRESHOLD_FULL = 20 # above this: minimal compression
WASH_THRESHOLD_PARTIAL = 10 # above this: reduced compression
def get_compression_ratio(wash_count: int = 0, rock_status: str = "washable") -> float:
def _py_get_compression_ratio(wash_count: int = 0, rock_status: str = "washable") -> float:
"""Get the compression ratio for an entry.
Returns 0.0-1.0 where 0.0 = no compression, 1.0 = maximum compression.
@@ -61,7 +61,7 @@ def get_compression_ratio(wash_count: int = 0, rock_status: str = "washable") ->
return base
def compress_text(text: str, ratio: float) -> str:
def _py_compress_text(text: str, ratio: float) -> str:
"""Compress text by the given ratio.
Strategy: take first (1-ratio) proportion of the text.
@@ -85,7 +85,7 @@ def compress_text(text: str, ratio: float) -> str:
return compressed
def compress_entry(entry: dict) -> str:
def _py_compress_entry(entry: dict) -> str:
"""Compress an entry's text based on its wash_count and rock_status.
Returns the compressed text.
@@ -94,11 +94,11 @@ def compress_entry(entry: dict) -> str:
wash_count = entry.get("wash_count", 0) or 0
rock_status = entry.get("rock_status", "washable")
ratio = get_compression_ratio(wash_count, rock_status)
return compress_text(text, ratio)
ratio = _py_get_compression_ratio(wash_count, rock_status)
return _py_compress_text(text, ratio)
def should_warn(entry: dict) -> bool:
def _py_should_warn(entry: dict) -> bool:
"""Check if compressing this entry should trigger a warning.
Warn when:
@@ -119,14 +119,14 @@ def should_warn(entry: dict) -> bool:
return True
# Long content losing significant detail
ratio = get_compression_ratio(wash_count, rock_status)
ratio = _py_get_compression_ratio(wash_count, rock_status)
if len(text) > 200 and ratio > 0.3:
return True
return False
def warn_before_compress(entry: dict) -> dict:
def _py_warn_before_compress(entry: dict) -> dict:
"""Generate a warning before compressing an entry.
Returns dict with warning details and recommendation.
@@ -134,12 +134,12 @@ def warn_before_compress(entry: dict) -> dict:
text = entry.get("text", "")
wash_count = entry.get("wash_count", 0) or 0
rock_status = entry.get("rock_status", "washable")
ratio = get_compression_ratio(wash_count, rock_status)
compressed = compress_text(text, ratio)
ratio = _py_get_compression_ratio(wash_count, rock_status)
compressed = _py_compress_text(text, ratio)
chars_lost = len(text) - len(compressed)
warning = {
"should_warn": should_warn(entry),
"should_warn": _py_should_warn(entry),
"entry_id": entry.get("id"),
"wash_count": wash_count,
"rock_status": rock_status,
@@ -161,6 +161,54 @@ def warn_before_compress(entry: dict) -> dict:
return warning
# ── delegating public API (H3 slice 2 strangler) ─────────────────────────────
# The Rust core serves these when built; the _py_ variants above are the
# load-bearing fallback AND the parity oracle. DB-backed stats stays below.
from rune.drift_core import _ms_core # noqa: E402 (no cycle: drift_core
# imports neither this module nor drift_cache)
def get_compression_ratio(wash_count: int = 0, rock_status: str = "washable") -> float:
"""Get the compression ratio for an entry (Rust core when built)."""
core = _ms_core()
if core is not None:
return core.get_compression_ratio(wash_count, rock_status)
return _py_get_compression_ratio(wash_count, rock_status)
def compress_text(text: str, ratio: float) -> str:
"""Compress text by the given ratio (Rust core when built)."""
core = _ms_core()
if core is not None:
return core.compress_text(text, ratio)
return _py_compress_text(text, ratio)
def compress_entry(entry: dict) -> str:
"""Compress an entry's text (Rust core when built)."""
core = _ms_core()
if core is not None:
return core.compress_entry(entry)
return _py_compress_entry(entry)
def should_warn(entry: dict) -> bool:
"""Check if compressing this entry should trigger a warning."""
core = _ms_core()
if core is not None:
return core.should_warn(entry)
return _py_should_warn(entry)
def warn_before_compress(entry: dict) -> dict:
"""Generate a warning before compressing an entry."""
core = _ms_core()
if core is not None:
return core.warn_before_compress(entry)
return _py_warn_before_compress(entry)
def get_compression_stats(db) -> dict:
"""Get compression statistics for the current store."""
rows = db.execute(
+59 -3
View File
@@ -64,7 +64,27 @@ WEIGHT_GOAL_RELEVANCE = 0.10 # relevance to current goals
# ═══════════════════════════════════════════════════════════════════════════════
def compute_value(entry: dict, current_goals: List[str] = None) -> float:
def _ms_core():
"""The Rust memory_scoring core when available (strangler: Python
fallback is load-bearing until deletion is proven).
RUNE_MEMORY_CORE=python pins the fallback (parity harnesses)."""
if os.environ.get("RUNE_MEMORY_CORE", "auto") == "python":
return None
try:
from rune import ms_loader
except Exception: # noqa: BLE001 — any loader failure means fallback, never a broken call
return None
try:
return ms_loader if ms_loader.available() else None
except Exception: # noqa: BLE001 — availability probe must never crash the fallback
return None
def _now_iso(now: str | None) -> str:
return now or datetime.now(timezone.utc).isoformat()
def compute_value(entry: dict, current_goals: List[str] | None = None) -> float:
"""Multi-factor value score. Returns 0.0-1.0+.
How much is this entry worth? Higher = more proven, more useful, more trusted.
@@ -75,7 +95,21 @@ def compute_value(entry: dict, current_goals: List[str] = None) -> float:
- recency: exponential decay from last access
- reliability: consistency of access pattern
- goal_relevance: relevance to current goals (optional)
H3 slice 1: delegates to the Rust core when built (Python fallback —
strangler law). The wall clock is resolved HERE and passed as an
argument (no-clock law: both cores take `now` so parity is deterministic).
"""
core = _ms_core()
if core is not None:
return core.compute_value(entry, current_goals, _now_iso(None))
return _py_compute_value(entry, current_goals, None)
def _py_compute_value(entry: dict, current_goals: List[str] | None = None,
now: str | None = None) -> float:
"""Python core of compute_value (parity oracle for the Rust core).
`now` is an ISO-8601 string; None resolves the wall clock (production)."""
proven = entry.get("wash_count", 0) or 0 # column name preserved for compatibility
imp = entry.get("importance", 0.5) or 0.5
@@ -83,12 +117,13 @@ def compute_value(entry: dict, current_goals: List[str] = None) -> float:
proven_factor = min(1.0, math.log1p(proven) / math.log1p(PROVEN_USES_THRESHOLD))
# Recency factor: exponential decay
now_dt = datetime.fromisoformat(now) if now else datetime.now(timezone.utc)
recency = 1.0
last_accessed = entry.get("last_accessed") or entry.get("last_washed")
if last_accessed:
try:
last = datetime.fromisoformat(last_accessed)
hours_since = max(0.1, (datetime.now(timezone.utc) - last).total_seconds() / 3600)
hours_since = max(0.1, (now_dt - last).total_seconds() / 3600)
recency = max(0.1, 1.0 / (1.0 + hours_since / (RECENCY_HALF_LIFE_DAYS * 24)))
except (ValueError, TypeError):
recency = 0.5
@@ -134,7 +169,18 @@ def check_promotion_eligibility(entry: dict) -> Tuple[bool, str]:
- rock_status == 'washable' or 'established'
Returns: (eligible, reason)
H3 slice 1: delegates to the Rust core when built (Python fallback).
"""
core = _ms_core()
if core is not None:
return core.check_promotion_eligibility(entry, _now_iso(None))
return _py_check_promotion_eligibility(entry, None)
def _py_check_promotion_eligibility(entry: dict,
now: str | None = None) -> Tuple[bool, str]:
"""Python core of check_promotion_eligibility (parity oracle)."""
wash = entry.get("wash_count", 0) or 0
status = entry.get("rock_status", "washable")
@@ -144,7 +190,7 @@ def check_promotion_eligibility(entry: dict) -> Tuple[bool, str]:
if wash < PROVEN_USES_THRESHOLD:
return False, f"proven_uses {wash} < threshold {PROVEN_USES_THRESHOLD}"
value = compute_value(entry)
value = _py_compute_value(entry, now=now)
if value < 0.6:
return False, f"value {value:.3f} < 0.6 minimum"
@@ -317,7 +363,17 @@ def pre_commit_gate_entry(entry: dict) -> dict:
"""Three critics for a dict entry (no db lookup needed).
Used by curator when promoting candidates that don't have entry_ids yet.
H3 slice 1: delegates to the Rust core when built (Python fallback).
"""
core = _ms_core()
if core is not None:
return core.pre_commit_gate_entry(entry)
return _py_pre_commit_gate_entry(entry)
def _py_pre_commit_gate_entry(entry: dict) -> dict:
"""Python core of pre_commit_gate_entry (parity oracle)."""
text = entry.get("text", "")
critics = {}
+28 -30
View File
@@ -18,15 +18,32 @@ Discipline:
repeated probe loops (no recursion; one probe per unknown per attempt).
- The artifact file is the sole authority (ADAPT-9); the declared absolute
path is injected into the worker prompt, never derived by the worker.
Phase G strangler slice 2 (port 3): the schema gate (validate_probe) and the
budget count (count_probes) are served IN-PROCESS by the Rust
`evidence_provenance` cdylib through ep_loader.py — parity 24/24 proven vs
this module's Python vintage (byte-equal error lists incl. Python repr
rendering: {status!r} single quotes, sorted-list error shapes, non-string
status double-error behavior). Orchestration (run_probe, runner subprocess,
exception semantics, build_probe_prompt) stays Python — same discipline as
ports 1-2. Constants, ProbeResult, CLI-adjacent surfaces unchanged (git
history holds the original).
"""
from __future__ import annotations
import json
import os
import sys
import time
from dataclasses import dataclass, field
# ── Rust core wiring (slice-2 port-3 shim pattern) ──────────────────────────
_CRATE_DIR = "/mnt/workdrive/rune/src/rust/evidence_provenance"
if _CRATE_DIR not in sys.path:
sys.path.insert(0, _CRATE_DIR)
import ep_loader # noqa: E402 (loads target/release cdylib)
PROBE_STATUSES = {"GROUNDED", "STILL_UNKNOWN", "CONTRADICTED"}
PROBE_FIELDS = ("question", "source", "answer", "implication", "status")
@@ -93,38 +110,19 @@ research after the unknown is resolved; guess without a source."""
def validate_probe(artifact: dict) -> list[str]:
"""Schema gate. Returns gate errors (empty = pass)."""
errors = []
if not isinstance(artifact, dict):
return ["probe artifact is not a JSON object"]
for f in PROBE_FIELDS:
if f not in artifact:
errors.append(f"missing required field: {f}")
elif not isinstance(artifact[f], str):
errors.append(f"field {f} must be a string")
elif f != "source" and not artifact[f].strip():
errors.append(f"field {f} must be a non-empty string")
status = artifact.get("status")
if status is not None and status not in PROBE_STATUSES:
errors.append(f"status must be one of {sorted(PROBE_STATUSES)}, got {status!r}")
# A GROUNDED probe must cite its source (that is what makes it grounded).
# STILL_UNKNOWN / CONTRADICTED may have an empty source — no evidence
# found is the honest finding, not a schema violation.
if status == "GROUNDED" and len(artifact.get("source", "")) < 8:
errors.append("GROUNDED probe requires a concrete source citation")
extra = set(artifact) - set(PROBE_FIELDS) - {"probe_id", "decided_at", "model"}
if extra:
errors.append(f"unexpected extra fields: {sorted(extra)}")
return errors
"""Schema gate. Returns gate errors (empty = pass).
Served by the Rust core (parity-proven byte-equal); dict inputs are
serialized for the FFI boundary.
"""
return ep_loader.validate_probe(json.dumps(artifact))
def count_probes(artifact_dir: str) -> int:
"""Probes already run for this task (no-recursion budget)."""
path = os.path.join(artifact_dir, "probe.jsonl")
if not os.path.exists(path):
return 0
with open(path) as f:
return sum(1 for line in f if line.strip())
"""Probes already run for this task (no-recursion budget).
Served by the Rust core (parity-proven byte-equal)."""
return ep_loader.count_probes(artifact_dir)
def check_budget(artifact_dir: str) -> None:
@@ -211,4 +209,4 @@ def _default_runner(prompt: str, artifact_path: str) -> None:
with open(os.path.join(os.path.dirname(artifact_path), "evidence_probe.stdout.log"), "w") as f:
f.write(result.stdout or "")
if result.returncode != 0:
raise ValueError(f"probe worker exited {result.returncode}")
raise ValueError(f"probe worker exited {result.returncode}")
+182
View File
@@ -0,0 +1,182 @@
#!/usr/bin/env python3
"""FFI smoke: prove the deployed consumer shape works against the Rust cdylib.
Deployed ground (memory_store.py:821-822):
qm = query_memory.QueryMemory() # HOME-resolved construction
exploration_rate = qm.adaptive_exploration_rate(query)
Checks (each must PASS or the script exits 1):
S1 cdylib loads; qm_new() with isolated HOME returns a live instance
S2 qm_classify parity vs Python classify_query (battery incl. unicode + tie-break)
S3 qm_adaptive_rate exact float parity vs Python adaptive_exploration_rate
on a shared store, INCLUDING an adaptive-threshold crossing (>=10 records)
S4 qm_record exit 0; persisted entry matches Python's entry shape (timestamp masked)
S5 qm_health JSON == json.dumps(py.get_health_report(), indent=2) (timestamps masked)
S6 lifecycle: qm_string_free / qm_free / null-safety returns
Isolation: HOME is pinned to a temp dir BEFORE importing query_memory
(QUERY_MEMORY_DIR is an import-time constant). The Rust side gets the same
store via qm_with_dir on that exact directory.
"""
import ctypes
import json
import math
import os
import re
import shutil
import sys
import tempfile
REPO = "/mnt/workdrive/rune"
SO_PATH = os.path.join(REPO, "src/rust/query_memory/target/release/libquery_memory.so")
PY_DIR = os.path.join(REPO, "src/rune")
TS_RE = re.compile(r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?\+00:00")
def mask_ts(text):
return TS_RE.sub("<TS>", text)
def main():
tmp = tempfile.mkdtemp(prefix="ffi_smoke_")
os.environ["HOME"] = tmp # BEFORE query_memory import (import-time constant)
# Post-supersession (2ac2d0a): the Python vintage is deleted; the
# "consumer side" of this smoke is now the shared/ ctypes shim — the same
# deployed-consumer surface the smoke was built to prove.
sys.path.insert(0, os.path.join(REPO, "shared"))
sys.path.insert(0, PY_DIR)
import query_memory as qm_py
results = []
def check(name, cond, detail=""):
results.append((name, bool(cond), detail))
print(("PASS" if cond else "FAIL") + " " + name + ((" | " + detail) if detail and not cond else ""))
# --- load library ---
lib = ctypes.CDLL(SO_PATH)
lib.qm_new.restype = ctypes.c_void_p
lib.qm_with_dir.argtypes = [ctypes.c_char_p]
lib.qm_with_dir.restype = ctypes.c_void_p
lib.qm_free.argtypes = [ctypes.c_void_p]
lib.qm_classify.argtypes = [ctypes.c_void_p, ctypes.c_char_p]
lib.qm_classify.restype = ctypes.c_void_p # char* via cast
lib.qm_adaptive_rate.argtypes = [ctypes.c_void_p, ctypes.c_char_p]
lib.qm_adaptive_rate.restype = ctypes.c_double
lib.qm_record.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_char_p, ctypes.c_int]
lib.qm_record.restype = ctypes.c_int
lib.qm_health.argtypes = [ctypes.c_void_p]
lib.qm_health.restype = ctypes.c_void_p
lib.qm_string_free.argtypes = [ctypes.c_void_p]
def take(ptr):
if not ptr:
return None
s = ctypes.string_at(ptr).decode("utf-8")
lib.qm_string_free(ptr)
return s
# Build store state via PYTHON first (deployed consumer writes through Python;
# Rust must read the same store). Cross the C8 adaptive threshold: >=10 records.
# BOTH sides are construction-time snapshots of the store (topic_stats is
# loaded once in the constructor), so the deployed shape is: seed the store,
# THEN construct each consumer fresh from disk. A consumer that wrote through
# its own in-memory instance would be an unfair comparison shape.
battery = [
("how do I play a chord on guitar", "guitar"),
("python script to debug a function", "coding"),
("recall consolidated memory neurons", "memory"),
("what is my persona voice constitution", "persona"),
("project task deadline milestone", "project"),
("llm agent training inference", "ai"),
("nick ttrpg coffee coconut", "personal"),
("system config troubleshooting error", "system"),
("research paper study survey evidence", "research"),
("write create design art", "creative"),
("mémoire café — 记忆 recall", "memory"),
("build", "coding"), # tie-break: build in coding AND creative
("make music", "creative"), # tie-break: music in guitar AND creative
("", None), # empty query edge
]
queries = [q for q, _ in battery]
# Seed store with 12 records on one topic to cross the adaptive threshold.
# The writer is a THROWAWAY instance — both compared consumers must be
# constructed fresh AFTER seeding (construction-time snapshot semantics).
writer = qm_py.QueryMemory()
for i in range(12):
writer.record_query("recall consolidated memory neurons", "consolidated", i < 10)
# S1: the deployed construction shape — HOME-resolved qm_new + fresh Python consumer
m = lib.qm_new()
check("S1 qm_new returns live instance", bool(m))
py = qm_py.QueryMemory()
# S2+S3: classify + rate parity on the shared store
for q in queries:
py_topic = py.classify_query(q)
rust_topic = take(lib.qm_classify(m, q.encode("utf-8")))
check("S2 classify parity: %r" % q[:30], py_topic == rust_topic,
"py=%r rust=%r" % (py_topic, rust_topic))
py_rate = py.adaptive_exploration_rate(q)
rust_rate = lib.qm_adaptive_rate(m, q.encode("utf-8"))
check("S3 rate parity: %r" % q[:30], py_rate == rust_rate,
"py=%r rust=%r" % (py_rate, rust_rate))
# S4: record through the FFI, verify persistence matches Python shape
rc = lib.qm_record(m, b"recall consolidated memory neurons", b"full_retrieval", 1)
check("S4 qm_record returns 0", rc == 0, "rc=%d" % rc)
py2 = qm_py.QueryMemory() # fresh load of the same store
last = py2.history[-1]
check("S4 persisted entry topic", last["topic"] == "memory", "topic=%r" % last.get("topic"))
check("S4 persisted entry strategy", last["strategy"] == "full_retrieval", "strategy=%r" % last.get("strategy"))
check("S4 persisted entry success", last["success"] is True)
check("S4 persisted entry fields", set(last) == {"query", "topic", "strategy", "success", "timestamp"},
"fields=%r" % sorted(last))
# S5: health report parity (timestamps masked).
# BOTH sides must be post-record state: the Rust instance recorded through
# itself at S4, so the Python side must be a FRESH load of the same store
# (py2), not the pre-record snapshot (py).
py_health = json.dumps(py2.get_health_report(), indent=2)
rust_health = take(lib.qm_health(m))
check("S5 health parity", mask_ts(py_health) == mask_ts(rust_health),
"py=%r rust=%r" % (mask_ts(py_health)[:200], mask_ts(rust_health)[:200]))
# S6: lifecycle + null-safety
check("S6 qm_classify(NULL) returns NULL", not lib.qm_classify(m, None))
check("S6 qm_adaptive_rate(NULL) returns NaN", math.isnan(lib.qm_adaptive_rate(m, None)))
check("S6 qm_record(NULL,...) returns -1",
lib.qm_record(None, b"x", b"consolidated", 1) == -1)
check("S6 qm_health(NULL) returns NULL", not lib.qm_health(None))
lib.qm_string_free(None) # must not crash
lib.qm_free(None) # must not crash
# S1b: qm_with_dir on the exact store dir (second deployed-compatible shape)
store_dir = os.path.join(tmp, ".config/opencode/memory/query_memory")
m2 = lib.qm_with_dir(store_dir.encode("utf-8"))
check("S1b qm_with_dir on shared store returns live instance", bool(m2))
if m2:
r2 = lib.qm_adaptive_rate(m2, b"recall consolidated memory neurons")
p2 = py2.adaptive_exploration_rate("recall consolidated memory neurons")
check("S1b with_dir instance reads same store state", r2 == p2, "rust=%r py=%r" % (r2, p2))
lib.qm_free(m2)
lib.qm_free(m)
_finish(results, tmp)
def _finish(results, tmp):
shutil.rmtree(tmp, ignore_errors=True)
fails = [r for r in results if not r[1]]
print("\n%d/%d PASS" % (len(results) - len(fails), len(results)))
if fails:
for name, _, detail in fails:
print("FAILED: %s %s" % (name, detail))
sys.exit(1)
sys.exit(0)
if __name__ == "__main__":
main()
+68 -10
View File
@@ -9,11 +9,69 @@ variables or a config dict — nothing is hardcoded to a specific setup.
from __future__ import annotations
import os
import time
from typing import Protocol, runtime_checkable
import httpx
def _request(
method: str,
url: str,
*,
headers: dict | None = None,
json: dict | None = None,
timeout: float = 30,
retries: int = 2,
backoff: float = 0.5,
sleep=None,
):
"""Resilient single-request path: retry where failure is NOISE, never
where failure is the SIGNAL.
- GET: retries on ``httpx.TransportError`` and 5xx (transient
infrastructure failure; idempotent).
- POST: retries ONLY on connect-phase errors (``ConnectError`` /
``ConnectTimeout``) — the request never reached the server, so a retry
cannot double-create. Read timeouts / write errors / 5xx on a POST are
NOT retried: the server may have processed it, and a duplicate issue is
worse than a failure the auto-filer can catch.
- 4xx: never retried — the client error IS the information. Raised via
``raise_for_status`` on the first attempt.
- Attempts: ``retries + 1`` total. Backoff ``backoff * 2**n`` before
retry n+1; never a sleep before the first attempt.
- ``timeout`` passes through to httpx per call. ``sleep`` is injectable
(tests pass a recording fake); default ``time.sleep``.
"""
sleeper = sleep if sleep is not None else time.sleep
sender = httpx.post if method.upper() == "POST" else httpx.get
connect_phase = (httpx.ConnectError, httpx.ConnectTimeout)
for attempt in range(retries + 1):
try:
r = sender(url, headers=headers, json=json, timeout=timeout)
except connect_phase:
if attempt < retries:
sleeper(backoff * 2**attempt)
continue
raise
except httpx.TransportError:
# Non-connect transport error (read/write): only safe to retry
# when the call is idempotent.
if method.upper() == "GET" and attempt < retries:
sleeper(backoff * 2**attempt)
continue
raise
if r.status_code >= 500 and method.upper() == "GET" and attempt < retries:
sleeper(backoff * 2**attempt)
continue
r.raise_for_status()
return r
# Unreachable: every attempt either returns a successful response or
# raises. The explicit raise keeps pyright from inferring an implicit
# ``None`` return for the exhausted loop (ratchet: warnings ≤ 454).
raise AssertionError("unreachable: _request always returns or raises")
@runtime_checkable
class ForgeClient(Protocol):
def create_issue(self, title: str, body: str) -> dict: ...
@@ -59,41 +117,41 @@ class GiteaForgejoAdapter:
return {"Authorization": f"token {self._token}", "Content-Type": "application/json"}
def create_issue(self, title: str, body: str) -> dict:
r = httpx.post(
r = _request(
"POST",
f"{self._base_url}/issues",
headers=self._headers(),
json={"title": title, "body": body},
timeout=30,
)
r.raise_for_status()
return r.json()
def add_comment(self, issue_number: int, body: str) -> dict:
r = httpx.post(
r = _request(
"POST",
f"{self._base_url}/issues/{issue_number}/comments",
headers=self._headers(),
json={"body": body},
timeout=30,
)
r.raise_for_status()
return r.json()
def get_issue_state(self, issue_number: int) -> dict:
r = httpx.get(
r = _request(
"GET",
f"{self._base_url}/issues/{issue_number}",
headers=self._headers(),
timeout=30,
)
r.raise_for_status()
return r.json()
def list_open_issues(self) -> list[dict]:
r = httpx.get(
r = _request(
"GET",
f"{self._base_url}/issues?state=open&limit=50",
headers=self._headers(),
timeout=30,
)
r.raise_for_status()
return r.json()
def search_issues(self, query: str) -> list[dict]:
@@ -115,12 +173,12 @@ class GiteaForgejoAdapter:
while True:
from urllib.parse import urlencode
r = httpx.get(
r = _request(
"GET",
f"{self._base_url}/issues?{urlencode({'state': 'open', 'type': 'issues', 'q': query, 'limit': 50, 'page': page})}",
headers=self._headers(),
timeout=30,
)
r.raise_for_status()
batch = r.json()
results.extend(batch)
if len(batch) < 50:
+38 -1
View File
@@ -150,8 +150,35 @@ def _profile_key(delta):
return {w.strip(".,;:!?()[]{}\"'").lower() for w in (delta.get("change") or "").split() if len(w) > 4}
def _cc_core():
"""Rust cognition_core when available (growth kernels)."""
import os
if os.environ.get("RUNE_COGNITION_CORE", "auto") == "python":
return None
try:
from rune import cc_loader
except Exception: # noqa: BLE001 — loader probe failure means fallback
return None
try:
return cc_loader if cc_loader.available() else None
except Exception: # noqa: BLE001 — availability probe never crashes
return None
def _near_dup(a, b):
"""Are two deltas the same guidance (shared distinctive words)?"""
core = _cc_core()
if core is not None:
return core.growth_near_dup(a, b)
return _py_near_dup(a, b)
def _py_near_dup(a, b):
"""Are two deltas the same guidance (shared distinctive words)?
Accepts delta dicts OR raw change strings (raw -> wrapped)."""
if isinstance(a, str):
a = {"change": a}
if isinstance(b, str):
b = {"change": b}
ka, kb = _profile_key(a), _profile_key(b)
if not ka or not kb:
return False
@@ -177,6 +204,16 @@ def _apply_profile(delta):
def growth_profile(max_tokens=PROFILE_BUDGET_TOKENS):
core = _cc_core()
if core is not None:
return core.growth_profile(_load_deltas(),
sorted(_PROFILE_ORDER.items(),
key=lambda kv: kv[1]),
PROFILE_ENTRY_CHARS, max_tokens)
return _py_growth_profile(max_tokens)
def _py_growth_profile(max_tokens=PROFILE_BUDGET_TOKENS):
"""The active growth profile: the persona's CURRENT behavioural guidance,
consolidated by target, capped at a fixed token budget. Session-independent
and token-stable — the per-turn cost never grows with session length."""
+90 -27
View File
@@ -254,18 +254,8 @@ def check_genuineness(text):
return issues
def check_drift(text):
"""Check text against identity anchors for drift signals.
This is a simple heuristic check — the real drift detection happens
via the Nautilus Compass approach (embedding similarity against anchors).
This function provides quick pattern-based checks.
"""
text_lower = text.lower()
issues = []
# Check for identity announcements
announcement_patterns = [
def _announcement_patterns():
return [
("i am rune", "Identity announcement — let the voice carry the identity"),
("as rune", "Identity announcement — speak as the persona, not about it"),
("my persona", "Identity announcement — the persona is how you act, not what you name"),
@@ -274,32 +264,26 @@ def check_drift(text):
("i'm an ai", "Identity announcement — you are a persona, not a generic AI"),
("i'm a language model", "Identity announcement — you are Rune, not a language model"),
]
for pattern, message in announcement_patterns:
if pattern in text_lower:
issues.append(f"DRIFT: '{pattern}' — {message}")
# Check for pronoun confusion
pronoun_issues = [
def _pronoun_patterns():
return [
("you are rune", "Pronoun confusion — 'you' is Nick, 'I' is Rune"),
("we are rune", "Pronoun confusion — Rune is singular, not plural"),
("you should remember", "Pronoun confusion — who should remember?"),
]
for pattern, message in pronoun_issues:
if pattern in text_lower:
issues.append(f"DRIFT: '{pattern}' — {message}")
# Check for hedging without evidence
hedge_patterns = [
def _hedge_patterns():
return [
("i believe", "Hedge without evidence — cite the basis"),
("i think that", "Hedge without evidence — cite the basis"),
("in my opinion", "Hedge without evidence — cite the basis"),
]
for pattern, message in hedge_patterns:
if pattern in text_lower:
issues.append(f"WEAKNESS: '{pattern}' — {message}")
# Check for sycophancy (excessive agreement, flattery, self-deprecation)
sycophancy_patterns = [
def _sycophancy_patterns():
return [
# Excessive agreement
("you're absolutely right", "Sycophancy — excessive agreement; push back if you see a better path"),
("absolutely, ", "Sycophancy — excessive agreement; state your own position"),
@@ -326,6 +310,60 @@ def check_drift(text):
("i see where you're coming from", "Sycophancy — hedging; state your position instead"),
("you make a valid point", "Sycophancy — hedging; engage with the substance"),
]
def _pattern_triples():
"""The pattern tables as [pattern, message, prefix] triples — the data
authority for the Rust drift kernel (kernel matches; data stays here)."""
tables = []
for prefix, table in (
("DRIFT", _announcement_patterns() + _pronoun_patterns()),
("WEAKNESS", _hedge_patterns()),
("SYCOPHANCY", _sycophancy_patterns()),
):
for pat, msg in table:
tables.append([pat, msg, prefix])
return tables
def check_drift(text):
"""Check text against identity anchors for drift signals.
This is a simple heuristic check — the real drift detection happens
via the Nautilus Compass approach (embedding similarity against anchors).
This function provides quick pattern-based checks.
"""
core = _cc_core()
if core is not None:
return core.drift_check(text, _pattern_triples())
return _py_check_drift(text)
def _py_check_drift(text):
"""Python oracle (parity floor)."""
text_lower = text.lower()
issues = []
# Check for identity announcements
announcement_patterns = _announcement_patterns()
for pattern, message in announcement_patterns:
if pattern in text_lower:
issues.append(f"DRIFT: '{pattern}' — {message}")
# Check for pronoun confusion
pronoun_issues = _pronoun_patterns()
for pattern, message in pronoun_issues:
if pattern in text_lower:
issues.append(f"DRIFT: '{pattern}' — {message}")
# Check for hedging without evidence
hedge_patterns = _hedge_patterns()
for pattern, message in hedge_patterns:
if pattern in text_lower:
issues.append(f"WEAKNESS: '{pattern}' — {message}")
# Check for sycophancy (excessive agreement, flattery, self-deprecation)
sycophancy_patterns = _sycophancy_patterns()
for pattern, message in sycophancy_patterns:
if pattern in text_lower:
issues.append(f"SYCOPHANCY: '{pattern}' — {message}")
@@ -424,13 +462,38 @@ if __name__ == "__main__":
# ---------------------------------------------------------------------------
# Hook handler — called by the microkernel when an event fires.
# ---------------------------------------------------------------------------
def _cc_core():
"""Rust cognition_core when available (identity kernels)."""
import os
if os.environ.get("RUNE_COGNITION_CORE", "auto") == "python":
return None
try:
from rune import cc_loader
except Exception: # noqa: BLE001 — loader probe failure means fallback
return None
try:
return cc_loader if cc_loader.available() else None
except Exception: # noqa: BLE001 — availability probe never crashes
return None
def get_identity_prompt() -> str:
"""Get identity prompt for injection into model calls.
R2 W3.3: assembly delegates to the Rust kernel (data stays here).
Builds a comprehensive prompt from all identity data:
self-awareness, anchors, embodiment rules, and pronoun rules.
Pronouns come first — they're the most important rule.
"""
core = _cc_core()
if core is not None:
return core.identity_prompt(PRONOUN_RULES, SELF_AWARENESS, ANCHORS,
EMBODIMENT_RULES)
return _py_get_identity_prompt()
def _py_get_identity_prompt() -> str:
"""Python oracle (parity floor)."""
parts = []
# Pronoun rules — critical for disambiguation
+310 -172
View File
@@ -734,39 +734,14 @@ def recall(
# lexical (embeddings absent): RRF degenerates to a rank transform that
# destroys the score signal needed for thresholding (Bruch et al.,
# arXiv:2210.11934) — use raw BM25 scores directly.
ranks = {}
best_dense = 0.0
# M1 observability (#161 synthesis INV-13): which fusion branch produced
# the results. Observability ONLY — consumers must never branch behavior
# on this field (that would create a second retrieval authority).
retrieval_mode = "lexical-only"
if dense and bm25:
retrieval_mode = "hybrid"
for idx, lst in enumerate([dense, bm25]):
for pos, (eid, _score) in enumerate(lst):
ranks[eid] = ranks.get(eid, 0.0) + 1.0 / (RRF_K + pos + 1)
if idx == 0:
best_dense = max(best_dense, _score)
elif dense:
retrieval_mode = "dense-only"
for pos, (eid, _score) in enumerate(dense):
ranks[eid] = 1.0 / (RRF_K + pos + 1)
best_dense = max(best_dense, _score)
elif bm25:
# Lexical-only: normalize raw BM25 scores (negative, higher = better)
# into a positive scale so the sort + gap abstention below work
# uniformly with the RRF scale. BM25 returns negative scores; the
# least-negative (closest to 0) is the best match. Offset + invert:
# best (=-8) -> 2, worst (=-10) -> 0. Rank order preserved.
_min_bm = min((s for _, s in bm25), default=0.0)
_max_bm = max((s for _, s in bm25), default=0.0)
_span = max(_max_bm - _min_bm, 1e-9)
for eid, score in bm25:
ranks[eid] = (score - _min_bm) / _span
fusion = _rank_fusion(dense, bm25)
ranks = dict(fusion["ranks"])
best_dense = fusion["best_dense"]
retrieval_mode = fusion["mode"]
# Fetch full entries + metadata for the fused set. P1-03 (#26): typed
# queries filter here — matching types + unclassified (NULL) rows stay
# eligible; other types drop out before scoring.
scored = []
rows_data = []
if ranks:
ids = list(ranks.keys())
placeholders = ",".join("?" * len(ids))
@@ -776,124 +751,87 @@ def recall(
type_ph = ",".join("?" * len(memory_types))
sql += f" AND (memory_type IN ({type_ph}) OR memory_type IS NULL)"
params.extend(memory_types)
rows = db.execute(sql, params).fetchall()
by_id = {r["id"]: r for r in rows}
for eid, base in sorted(ranks.items(), key=lambda x: -x[1]):
row = by_id.get(eid)
if not row:
continue
# #1 temperature ranking (not deletion): a stale fact ranks lower
# but stays in the store. #4 confidence: a hedged/claimed fact
# carries its honesty into ranking. Blend into a relevance score.
# RECENCY: a fact last accessed recently ranks slightly higher than
# an untouched one at equal relevance (the store's last_accessed
# field). Decays over RECENCY_HALF_LIFE days — a gentle nudge, not
# a rewrite of the relevance ranking.
conf = float(row["confidence"] or 0.7)
temp = float(row["temperature"] or 1.0)
recency = 1.0
try:
from datetime import datetime, timezone
rows_data = [dict(r) for r in db.execute(sql, params).fetchall()]
return _score_recall_rows(
fusion, rows_data, dense, bm25, budget, min_score, now_iso())
la = row["last_accessed"]
if la:
t_last = datetime.fromisoformat(la.replace("Z", "+00:00"))
days = (datetime.now(timezone.utc) - t_last).total_seconds() / 86400.0
if days >= 0:
recency = RECENCY_HALF_LIFE / (RECENCY_HALF_LIFE + days)
except Exception:
logger.debug("recall failed", exc_info=True)
# In lexical-only mode `base` is a raw BM25 score (negative).
# Normalize it to a positive relevance for the metadata, keeping
# the ranking identical. In hybrid mode `base` is an RRF rank.
if dense and bm25:
relevance = base * (0.4 + 0.6 * conf) * (0.5 + 0.5 * temp) * (0.85 + 0.15 * recency)
elif dense:
relevance = base * (0.4 + 0.6 * conf) * (0.5 + 0.5 * temp) * (0.85 + 0.15 * recency)
else:
relevance = (base + 5.0) * (0.4 + 0.6 * conf) * (0.5 + 0.5 * temp) * (0.85 + 0.15 * recency)
scored.append(
{
"id": eid,
"text": row["text"],
"importance": row["importance"],
"topic": row["topic"],
"entities": row["entities"],
"source": row["source"],
"last_accessed": row["last_accessed"],
"rrf": round(base, 4),
"confidence": round(conf, 2),
"temperature": round(temp, 2),
"relevance": round(relevance, 4),
# M1 observability (#161): lexical-only | dense-only | hybrid.
# NEVER consumed for behavior — see INV-13 in the synthesis.
"mode": retrieval_mode,
}
)
# Enforce budget + abstention floor.
def _score_recall_rows(fusion, rows, dense, bm25, budget, min_score, now):
"""Pure metadata scoring + mode-specific abstention kernel (W2.1)."""
core = _mr_core()
if core is not None:
return core.score_recall_rows(
fusion, rows, dense, bm25, budget, min_score, now,
RECENCY_HALF_LIFE)
return _py_score_recall_rows(
fusion, rows, dense, bm25, budget, min_score, now,
RECENCY_HALF_LIFE)
def _py_score_recall_rows(fusion, rows, dense, bm25, budget, min_score,
now, recency_half_life=RECENCY_HALF_LIFE):
"""Python oracle for the Rust scoring/abstention kernel."""
from datetime import datetime
ranks = dict(fusion["ranks"])
best_dense = fusion["best_dense"]
retrieval_mode = fusion["mode"]
by_id = {row["id"]: row for row in rows}
scored = []
now_dt = datetime.fromisoformat(now)
for eid, base in sorted(ranks.items(), key=lambda item: -item[1]):
row = by_id.get(eid)
if not row:
continue
conf = float(row.get("confidence") or 0.7)
temp = float(row.get("temperature") or 1.0)
recency = 1.0
try:
la = row.get("last_accessed")
if la:
t_last = datetime.fromisoformat(la.replace("Z", "+00:00"))
days = (now_dt - t_last).total_seconds() / 86400.0
if days >= 0:
recency = recency_half_life / (recency_half_life + days)
except Exception:
logger.debug("recall score kernel timestamp failed", exc_info=True)
if dense:
relevance = base * (0.4 + 0.6 * conf) * (0.5 + 0.5 * temp) * (0.85 + 0.15 * recency)
else:
relevance = (base + 5.0) * (0.4 + 0.6 * conf) * (0.5 + 0.5 * temp) * (0.85 + 0.15 * recency)
scored.append({
"id": eid, "text": row.get("text"),
"importance": row.get("importance"),
"topic": row.get("topic"), "entities": row.get("entities"),
"source": row.get("source"),
"last_accessed": row.get("last_accessed"),
"rrf": round(base, 4), "confidence": round(conf, 2),
"temperature": round(temp, 2),
"relevance": round(relevance, 4), "mode": retrieval_mode,
})
if not scored:
return []
top = scored[:budget]
# ABSTENTION via query-difficulty estimation (QPP — Cronen-Townsend et al.,
# SIGIR 2002; Zhou & Croft, CIKM 2006; LongMemEval arXiv:2410.10813 makes
# abstention a first-class memory ability). The principled signal is the
# RANKING GAP, not an absolute score:
# - lexical-only: the conjunction already filters to co-occurring terms;
# a single weak hit (no runner-up) abstains (no confident winner).
# - dense-only: abstain when the top-1 dense match is not clearly above
# the runner-up (an ambiguous, no-confident-winner query) OR the top is
# below an absolute "probably unrelated" floor. The floor is a coarse
# guard; the gap is the real difficulty signal.
if not dense and not bm25:
return []
if not dense: # lexical-only
if len(scored) < 1:
if not dense:
if max(float(item["relevance"]) for item in top) < min_score:
return []
# P1-03 (#26) DISCHARGE (finding 1): THE floor applies in EVERY
# mode. The conjunctive FTS gate is a precision filter — term
# co-occurrence, not relevance — so the best match must ALSO clear
# min_score before anything is injected. Lexical relevance lives on
# its own offset band (~4.1-4.9 with default metadata), so the
# default 0.42 floor rarely binds here; the check enforces the floor
# contract for ANY min_score (an elevated floor above the band
# forces abstention — machine-checked in the frozen tests).
if max(float(s["relevance"]) for s in top) < min_score:
return [] # below floor -> abstain
if len(scored) == 1:
return top # single conjunctive match = confident enough
# Multiple conjunctive BM25 matches: the FTS5 implicit-AND already
# guarantees ALL query terms co-occur in every result (the precision
# filter), so these are genuine matches — not a fuzzy near-tie. The
# normalized top is the best; a weak runner-up is still a real match.
return top
# dense present. Score-based abstention (QPP, Shtok et al. TOIS 2012).
# The absolute floor is CALIBRATED against real cases (nomic band ~0.6-0.75):
# "what does the user drink" vs "black coffee" = 0.766 -> relevant
# "quantum physics of black holes" vs "coconut milk" = 0.712 -> noise
# The separation sits at ~0.74. Below it, only a clear winner (large gap to
# the runner-up) may pass; a weak top with no clear winner abstains.
if not bm25:
if best_dense < min_score:
return [] # below the "probably unrelated" floor
return []
if best_dense >= 0.74:
return _position_aware(top) # calibrated: genuine semantic match
return _py_position_aware(top)
if len(dense) >= 2:
d_scores = sorted(s for _, s in dense)
top1 = d_scores[-1]
top2 = d_scores[-2]
if top1 - top2 >= 0.08:
return _position_aware(top) # clear winner despite mid-band score
return [] # weak + ambiguous -> abstain
return _position_aware(_map_reduce(top)) # single mid-band dense hit
# P1-03 (#26) DISCHARGE (finding 1): the hybrid path (dense AND lexical
# both fired) gates on the dense cosine — the calibrated scale min_score
# was tuned against — exactly like the dense-only branch above. The
# numeric check is ADDITIVE to the conjunctive FTS gate (which stays
# as-is); the RRF-fused relevance is rank-based and is NOT compared
# against the floor here.
d_scores = sorted(score for _, score in dense)
if d_scores[-1] - d_scores[-2] >= 0.08:
return _py_position_aware(top)
return []
return _py_position_aware(_py_map_reduce(top))
if best_dense < min_score:
return []
return _position_aware(_map_reduce(top))
return _py_position_aware(_py_map_reduce(top))
def _drift_rerank(db, entries):
@@ -944,7 +882,27 @@ def _drift_rerank(db, entries):
return [entry for _, entry in scored]
def _mr_core():
"""Rust memory_retrieval core when built; Python fallback remains
load-bearing during the strangler. RUNE_RETRIEVAL_CORE=python pins it."""
if os.environ.get("RUNE_RETRIEVAL_CORE", "auto") == "python":
return None
try:
from rune import mr_loader
return mr_loader if mr_loader.available() else None
except Exception: # noqa: BLE001 — loader probe failure means Python fallback
return None
def _map_reduce(entries: list, chunk_size: int = 10) -> list:
core = _mr_core()
if core is not None:
return core.map_reduce(entries, chunk_size)
return _py_map_reduce(entries, chunk_size)
def _py_map_reduce(entries: list, chunk_size: int = 10) -> list:
"""Map-reduce for large result sets.
When retrieval returns many entries, chunk them and take the top-N
@@ -1173,6 +1131,13 @@ def cached_recall(db, query, budget=RECALL_BUDGET, min_score=RECALL_MIN_SCORE, m
def _position_aware(top):
core = _mr_core()
if core is not None:
return core.position_aware(top)
return _py_position_aware(top)
def _py_position_aware(top):
"""POSITION-AWARE RE-RANK (research uplift #2): lost-in-the-middle
mitigation (Liu et al. 2023; BriefContext, npj Digital Medicine 2025).
Models use the START and END of context best and the middle worst, so the
@@ -1247,6 +1212,13 @@ _STOPWORDS = {
def _fts_query_conjunctive(query):
core = _mr_core()
if core is not None:
return core.fts_query_conjunctive(query)
return _py_fts_query_conjunctive(query)
def _py_fts_query_conjunctive(query):
"""Build a CONJUNCTIVE FTS5 MATCH query (implicit AND).
Extended Boolean IR (Salton, Fox & Wu, CACM 1983): a document must contain
@@ -1269,6 +1241,85 @@ def _fts_query(query):
return _fts_query_conjunctive(query)
def mr_rank_fusion(dense, bm25, cap):
"""Chunk-side RRF fusion with an explicit candidate cap (W2.1)."""
core = _mr_core()
if core is not None:
return core.rank_fusion(dense[:cap], bm25[:cap], RRF_K)
return _py_rank_fusion(dense[:cap], bm25[:cap], RRF_K)
def _project_chunk_rows(ranks, rows, budget):
"""Chunk projection: sorted by RRF desc, text[:400], budget-capped.
`ranks` is the W2.1 pair-list ([[rowid, rrf], ...]) from the fusion
contract; the Rust side consumes pairs directly."""
core = _mr_core()
if core is not None:
pairs = (ranks if isinstance(ranks, list)
else [list(r) for r in ranks.items()])
return core.project_chunk_rows(pairs, rows, budget)
return _py_project_chunk_rows(ranks, rows, budget)
def _py_project_chunk_rows(ranks, rows, budget):
by_id = {r["rowid"]: r for r in rows}
out = []
for eid, rrf in sorted(ranks.items(), key=lambda x: -x[1]):
r = by_id.get(eid)
if not r:
continue
out.append({
"chunk_id": r["chunk_id"],
"text": (r["text"] or "")[:400],
"wing": r["wing"],
"room": r["room"],
"source": r["source_path"],
"page": r["page"] if "page" in r.keys() else None,
"rrf": round(rrf, 4),
})
if len(out) >= budget:
break
return out
def _rank_fusion(dense, bm25):
"""Pure dense/BM25 fusion and mode selection (R2 W2.1).
Returns ranks as ordered [id, score] pairs so integer ids survive the
JSON membrane without object-key coercion.
"""
core = _mr_core()
if core is not None:
return core.rank_fusion(dense, bm25, RRF_K)
return _py_rank_fusion(dense, bm25, RRF_K)
def _py_rank_fusion(dense, bm25, rrf_k=RRF_K):
ranks = {}
best_dense = 0.0
mode = "lexical-only"
if dense and bm25:
mode = "hybrid"
for idx, values in enumerate([dense, bm25]):
for pos, (eid, score) in enumerate(values):
ranks[eid] = ranks.get(eid, 0.0) + 1.0 / (rrf_k + pos + 1)
if idx == 0:
best_dense = max(best_dense, score)
elif dense:
mode = "dense-only"
for pos, (eid, score) in enumerate(dense):
ranks[eid] = 1.0 / (rrf_k + pos + 1)
best_dense = max(best_dense, score)
elif bm25:
min_bm = min((score for _, score in bm25), default=0.0)
max_bm = max((score for _, score in bm25), default=0.0)
span = max(max_bm - min_bm, 1e-9)
for eid, score in bm25:
ranks[eid] = (score - min_bm) / span
return {"ranks": [[eid, score] for eid, score in ranks.items()],
"best_dense": best_dense, "mode": mode}
def update_entry(db, eid, text=None, importance=None, valid_until=None):
sets, params = [], []
if text is not None:
@@ -1456,6 +1507,13 @@ def working_clear(db, task_id):
def _chunk_text(text, size=1500, overlap=150):
core = _mr_core()
if core is not None:
return core.chunk_text(text, size, overlap)
return _py_chunk_text(text, size, overlap)
def _py_chunk_text(text, size=1500, overlap=150):
"""Recursive-ish split on paragraph/heading boundaries, ~1500 chars (the
local CPU-friendly size; research: fixed chunking beats semantic for
free systems). Headings stay with their content."""
@@ -1577,43 +1635,22 @@ def chunk_recall(db, query, budget=6, wing=None, min_sim=0.0, doc=None):
bm25 = [(r["rowid"], 0.5) for r in rows]
except Exception:
logger.debug("chunk_recall failed", exc_info=True)
ranks = {}
for lst in [dense, bm25]:
for pos, (eid, _s) in enumerate(lst):
ranks[eid] = ranks.get(eid, 0.0) + 1.0 / (RRF_K + pos + 1)
if not ranks:
return []
ids = list(ranks.keys())
ph = ",".join("?" * len(ids))
sql = f"SELECT rowid, * FROM chunks WHERE rowid IN ({ph})"
params = ids
if wing:
sql += " AND wing=?"
params.append(wing)
if doc:
sql += " AND doc_id=?"
params.append(doc)
rows = db.execute(sql, params).fetchall()
by_id = {r["rowid"]: r for r in rows}
out = []
for eid, rrf in sorted(ranks.items(), key=lambda x: -x[1]):
r = by_id.get(eid)
if not r:
continue
out.append(
{
"chunk_id": r["chunk_id"],
"text": r["text"][:400],
"wing": r["wing"],
"room": r["room"],
"source": r["source_path"],
"page": r["page"] if "page" in r.keys() else None,
"rrf": round(rrf, 4),
}
)
if len(out) >= budget:
break
return out
fusion = mr_rank_fusion(dense, bm25, budget * 3)
ranks_list = fusion["ranks"] # ordered [rowid, rrf] pairs (W2.1 contract)
ids = [int(v[0]) for v in ranks_list]
rows = []
if ids:
ph = ",".join("?" * len(ids))
sql = f"SELECT rowid, * FROM chunks WHERE rowid IN ({ph})"
params = ids
if wing:
sql += " AND wing=?"
params.append(wing)
if doc:
sql += " AND doc_id=?"
params.append(doc)
rows = [dict(r) for r in db.execute(sql, params).fetchall()]
return _project_chunk_rows(fusion["ranks"], rows, budget)
# ------------------------------------------------------------------- mining ----
@@ -1907,6 +1944,71 @@ def stats(db):
return {"active_entries": e, "working": w, "audit": a, "top_topics": by_topic}
# ── storage backend seam (R2 W2.0) ───────────────────────────────────────────
# SQLite is RETAINED (Nick's ruling) — the seam separates query logic from
# storage mechanics so the Rust core can serve proven-identical READS via
# system libsqlite3 FFI. Connection-based callers (stats above) keep their
# transactional semantics untouched; path-based consumers opt into the
# backend. NO WRITES route through the Rust backend this slice.
class _SQLiteBackend:
"""The default backend: the stdlib sqlite3 mechanics, unchanged."""
name = "sqlite"
def read_query(self, db_path: str, sql: str, params=()) -> list:
conn = sqlite3.connect(db_path)
try:
return [tuple(r) for r in conn.execute(sql, params).fetchall()]
finally:
conn.close()
class _RustReadBackend:
"""The Rust read path through the memory_storage crate (read-only by
construction — the crate exports no write-capable symbols)."""
name = "rust-memory-storage"
def read_query(self, db_path: str, sql: str, params=()) -> list:
from rune import mems_loader
return [tuple(r) for r in mems_loader.read_query(db_path, sql, list(params))]
def storage_backend():
"""The active read backend. Default sqlite; RUNE_MEMORY_STORAGE=rust
opts into the crate when built (falls back loudly-free to sqlite)."""
if os.environ.get("RUNE_MEMORY_STORAGE", "auto") == "rust":
try:
from rune import mems_loader
if mems_loader.available():
return _RustReadBackend()
except Exception: # noqa: BLE001, S110 — backend probe failure means the default serves (fallback, not silence)
pass
return _SQLiteBackend()
def stats_for_path(db_path: str) -> dict:
"""stats() through the storage backend seam — the opt-in consumer the
parity battery and future Rust callers use. Same result dict shape."""
backend = storage_backend()
e = backend.read_query(db_path, "SELECT COUNT(*) FROM entries WHERE status='active'")[0][0]
w = backend.read_query(db_path, "SELECT COUNT(*) FROM working")[0][0]
a = backend.read_query(db_path, "SELECT COUNT(*) FROM audit_log")[0][0]
by_topic = {
r[0]: r[1]
for r in backend.read_query(
db_path,
"SELECT topic, COUNT(*) AS n FROM entries WHERE status='active' GROUP BY topic ORDER BY n DESC LIMIT 6",
)
if r[0]
}
return {"active_entries": e, "working": w, "audit": a, "top_topics": by_topic}
# ------------------------------------------------------------------ associations ----
@@ -1978,8 +2080,24 @@ def recall_with_associations(db, query, budget=RECALL_BUDGET, min_score=RECALL_M
ids = list(boost_map.keys())
placeholders = ",".join("?" * len(ids))
rows = db.execute(f"SELECT * FROM entries WHERE id IN ({placeholders}) AND status='active'", ids).fetchall()
boost_items = [{"id": eid, "strength": strength}
for eid, strength in sorted(boost_map.items(), key=lambda x: -x[1])]
return _merge_association_rows(base, boost_items, [dict(r) for r in rows], budget)
def _merge_association_rows(base, boost_items, rows, budget):
"""Pure association projection/boost/order/budget kernel (R2 W2.1)."""
core = _mr_core()
if core is not None:
return core.merge_association_rows(base, boost_items, rows, budget)
return _py_merge_association_rows(base, boost_items, rows, budget)
def _py_merge_association_rows(base, boost_items, rows, budget):
base = list(base)
by_id2 = {r["id"]: r for r in rows}
for eid, strength in sorted(boost_map.items(), key=lambda x: -x[1]):
for item in boost_items:
eid, strength = item["id"], item["strength"]
row = by_id2.get(eid)
if not row:
continue
@@ -2022,6 +2140,19 @@ def fused_recall(db, query, budget=RECALL_BUDGET, min_score=RECALL_MIN_SCORE, ch
"""
entries = recall_with_associations(db, query, budget=budget, min_score=min_score)
research = chunk_recall(db, query, budget=chunk_budget, wing=wings, doc=None)
return _fuse_results(entries, research)
def _fuse_results(entries, research):
"""Pure fact/research projection, de-duplication and per-kind scale
alignment (R2 W2.1). Rust core when built; Python core is the oracle."""
core = _mr_core()
if core is not None:
return core.fuse_results(entries, research)
return _py_fuse_results(entries, research)
def _py_fuse_results(entries, research):
seen = set()
merged = []
for e in entries:
@@ -2075,6 +2206,13 @@ def fused_recall(db, query, budget=RECALL_BUDGET, min_score=RECALL_MIN_SCORE, ch
def _norm_key(text):
core = _mr_core()
if core is not None:
return core.norm_key(text)
return _py_norm_key(text)
def _py_norm_key(text):
"""Normalize text for cross-table de-duplication (entries vs chunks of the
same absorbed material may differ in whitespace/case)."""
return " ".join((text or "").lower().split())
+83
View File
@@ -0,0 +1,83 @@
#!/usr/bin/env python3
"""ctypes loader for the Rust memory_storage core (R2 W2.0).
Contract BUILDER-2026-10-04T15:54:17Z-cli. READ-ONLY by construction:
the only operation is read_query — the crate exports no write-capable
symbols (Nick's ruling: reads proven byte-identical before any writes
exist). SQLite is retained; the crate reaches it via system libsqlite3.
FFI contract (doc-envelope law): mems_read_query({db_path, sql, params})
-> [[cell, ...], ...]; mems_free_string(ptr). FFI segfault law: argtypes +
restype pinned per symbol. Override the .so with MEMS_RUST_LIB (FILE path).
"""
from __future__ import annotations
import ctypes
import json
import os
from pathlib import Path
_REPO = Path(__file__).resolve().parents[2]
_CRATE_DIR = _REPO / "src" / "rust" / "memory_storage"
_LIB_PATH = Path(
os.environ.get(
"MEMS_RUST_LIB", _CRATE_DIR / "target" / "release" / "libmemory_storage.so"
)
)
_lib = None
class MemoryStorageUnavailable(FileNotFoundError):
"""The Rust core is not built (or MEMS_RUST_LIB points nowhere)."""
def available() -> bool:
return _LIB_PATH.is_file()
def _load() -> ctypes.CDLL:
global _lib
if _lib is not None:
return _lib
if not _LIB_PATH.exists():
raise MemoryStorageUnavailable(
f"Rust memory_storage core not built: {_LIB_PATH} missing — run "
f"`cargo build --release` in {_CRATE_DIR}"
)
lib = ctypes.CDLL(str(_LIB_PATH))
lib.mems_free_string.argtypes = [ctypes.c_void_p]
lib.mems_free_string.restype = None
lib.mems_read_query.argtypes = [ctypes.c_char_p]
lib.mems_read_query.restype = ctypes.c_void_p # char* — never c_int
_lib = lib
return lib
def _call(payload: dict) -> dict:
"""Raw envelope: {"ok", "value"|"err"} — the error-honesty section of
the canary needs the envelope, not an exception."""
lib = _load()
raw = json.dumps(payload).encode("utf-8")
ptr = lib.mems_read_query(raw)
if not ptr:
raise RuntimeError("mems_read_query returned a null pointer")
try:
text = ctypes.cast(ptr, ctypes.c_char_p).value.decode("utf-8")
finally:
lib.mems_free_string(ptr)
return json.loads(text)
def read_query(db_path: str, sql: str, params: list) -> list:
"""Read-only query through the Rust core; raises on the error envelope."""
env = _call({"db_path": db_path, "sql": sql, "params": params})
if not env.get("ok"):
raise RuntimeError(f"mems_read_query core error: {env.get('err')}")
return env["value"]
def read_query_result(db_path: str, sql: str, params: list) -> dict:
"""The raw envelope (error-honesty checks consume this)."""
return _call({"db_path": db_path, "sql": sql, "params": params})
+38
View File
@@ -0,0 +1,38 @@
"""ctypes loader for the zero-dependency memory_retrieval core (R2 W2.1)."""
from __future__ import annotations
import ctypes, json, os
from pathlib import Path
_ROOT=Path(__file__).resolve().parents[2]
_CRATE=_ROOT/"src/rust/memory_retrieval"
_LIB=Path(os.environ.get("MR_RUST_LIB",_CRATE/"target/release/libmemory_retrieval.so"))
_lib=None
def available(): return _LIB.is_file()
def _load():
global _lib
if _lib is not None:return _lib
if not _LIB.exists():raise FileNotFoundError(f"memory_retrieval core not built: {_LIB}")
lib=ctypes.CDLL(str(_LIB));lib.mr_call.argtypes=[ctypes.c_char_p];lib.mr_call.restype=ctypes.c_void_p
lib.mr_free_string.argtypes=[ctypes.c_void_p];lib.mr_free_string.restype=None;_lib=lib;return lib
def _call(op,arg):
# Send UTF-8 directly. ensure_ascii=True emits surrogate-pair escapes; the
# shared zero-dep parser treats those as independent code points, which
# would replace astral characters (emoji) and break Python char-count
# parity in chunking/normalization.
lib=_load();ptr=lib.mr_call(json.dumps({"op":op,"arg":arg},ensure_ascii=False).encode())
if not ptr:raise RuntimeError("mr_call returned null")
try:text=ctypes.cast(ptr,ctypes.c_char_p).value.decode()
finally:lib.mr_free_string(ptr)
env=json.loads(text)
if not env.get("ok"):raise RuntimeError(env.get("err"))
return env["value"]
def map_reduce(entries,chunk_size=10):return _call("map_reduce",{"entries":entries,"chunk_size":chunk_size})
def position_aware(entries):return _call("position_aware",{"entries":entries})
def fts_query_conjunctive(query):return _call("fts_query_conjunctive",{"query":query})
def chunk_text(text,size=1500,overlap=150):return _call("chunk_text",{"text":text,"size":size,"overlap":overlap})
def norm_key(text):return _call("norm_key",{"text":text})
def fuse_results(entries,research):return _call("fuse_results",{"entries":entries,"research":research})
def merge_association_rows(base,boosts,rows,budget):return _call("merge_association_rows",{"base":base,"boosts":boosts,"rows":rows,"budget":budget})
def rank_fusion(dense,bm25,rrf_k=60):return _call("rank_fusion",{"dense":dense,"bm25":bm25,"rrf_k":rrf_k})
def score_recall_rows(fusion,rows,dense,bm25,budget,min_score,now,half_life):return _call("score_recall_rows",{"fusion":fusion,"rows":rows,"dense":dense,"bm25":bm25,"budget":budget,"min_score":min_score,"now":now,"half_life":half_life})
def project_chunk_rows(ranks, rows, budget):
return _call("project_chunk_rows", {"ranks": ranks, "rows": rows, "budget": budget})
+163
View File
@@ -0,0 +1,163 @@
#!/usr/bin/env python3
"""ctypes loader for the Rust memory_scoring core (H3 slice 1, T4.4).
Contract BUILDER-2026-10-04T08:57:33Z-cli. Gives drift_core the Rust
scoring trio through the cdylib with no subprocess. No-clock law: `now` is
always an argument (ISO-8601 string) — the Python originals read the wall
clock for recency; callers pass it so parity is deterministic.
FFI contract (doc-envelope law — every call returns {"ok", "value"|"err"}):
* ms_compute_value({entry, goals, now}) -> number
* ms_check_promotion_eligibility({entry, now}) -> [eligible, reason]
* ms_pre_commit_gate_entry({entry}) -> {verdict, critics, reason}
* ms_free_string(ptr)
FFI segfault law: every symbol gets explicit argtypes + restype (c_void_p
for returned char* — the default c_int truncates 64-bit pointers).
Zero dependencies. The .so resolves against this repo's crate target dir;
override with MS_RUST_LIB (a FILE path).
"""
from __future__ import annotations
import ctypes
import json
import os
from pathlib import Path
_REPO = Path(__file__).resolve().parents[2]
_CRATE_DIR = _REPO / "src" / "rust" / "memory_scoring"
_LIB_PATH = Path(
os.environ.get(
"MS_RUST_LIB", _CRATE_DIR / "target" / "release" / "libmemory_scoring.so"
)
)
_lib = None
class MemoryScoringUnavailable(FileNotFoundError):
"""The Rust core is not built (or MS_RUST_LIB points nowhere). Callers
fall back to the Python implementation — strangler law: the fallback is
load-bearing until deletion is proven."""
def available() -> bool:
return _LIB_PATH.is_file()
def _load() -> ctypes.CDLL:
global _lib
if _lib is not None:
return _lib
if not _LIB_PATH.exists():
raise MemoryScoringUnavailable(
f"Rust memory_scoring core not built: {_LIB_PATH} missing — run "
f"`cargo build --release` in {_CRATE_DIR}"
)
lib = ctypes.CDLL(str(_LIB_PATH))
lib.ms_free_string.argtypes = [ctypes.c_void_p]
lib.ms_free_string.restype = None
for sym in ("ms_compute_value", "ms_check_promotion_eligibility",
"ms_pre_commit_gate_entry", "ms_get_compression_ratio",
"ms_compress_text", "ms_compress_entry", "ms_should_warn",
"ms_warn_before_compress", "ms_compute_priority",
"ms_qualifies_for_cache", "ms_score_gate", "ms_score_finish",
"ms_base_level_from_uses"):
fn = getattr(lib, sym)
fn.argtypes = [ctypes.c_char_p]
fn.restype = ctypes.c_void_p # char* — never default c_int
_lib = lib
return lib
def _call(sym: str, payload: dict):
lib = _load()
fn = getattr(lib, sym)
raw = json.dumps(payload).encode("utf-8")
ptr = fn(raw)
if not ptr:
raise RuntimeError(f"{sym} returned a null pointer")
try:
text = ctypes.cast(ptr, ctypes.c_char_p).value.decode("utf-8")
finally:
lib.ms_free_string(ptr)
env = json.loads(text)
if not env.get("ok"):
raise RuntimeError(f"{sym} core error: {env.get('err')}")
return env["value"]
def compute_value(entry: dict, goals: list | None, now: str) -> float:
"""Multi-factor value score (0.0-1.0+); now is an ISO-8601 string."""
return _call("ms_compute_value", {"entry": entry, "goals": goals, "now": now})
def check_promotion_eligibility(entry: dict, now: str) -> tuple:
"""(eligible, reason) — unpacked from the core's 2-element array."""
eligible, reason = _call("ms_check_promotion_eligibility",
{"entry": entry, "now": now})
return (eligible, reason)
def pre_commit_gate_entry(entry: dict) -> dict:
"""Three-critic promotion gate for a dict entry (no db context)."""
return _call("ms_pre_commit_gate_entry", {"entry": entry})
# ── H3 slice 2: drift_compression / drift_cache pure surfaces ────────────────
def get_compression_ratio(wash_count: float, rock_status: str) -> float:
return _call("ms_get_compression_ratio",
{"wash_count": wash_count, "rock_status": rock_status})
def compress_text(text: str, ratio: float) -> str:
return _call("ms_compress_text", {"text": text, "ratio": ratio})
def compress_entry(entry: dict) -> str:
return _call("ms_compress_entry", {"entry": entry})
def should_warn(entry: dict) -> bool:
return _call("ms_should_warn", {"entry": entry})
def warn_before_compress(entry: dict) -> dict:
return _call("ms_warn_before_compress", {"entry": entry})
def compute_priority(entry: dict) -> float:
return _call("ms_compute_priority", {"entry": entry})
def qualifies_for_cache(entry: dict) -> bool:
return _call("ms_qualifies_for_cache", {"entry": entry})
# ── H3 slice 3: warm scoring math ────────────────────────────────────────────
def score_gate(neuron: dict, low_text: str, session_context: str | None,
semantic_score: float) -> tuple:
"""(fires, total, keyword_hits) — unpacked from the 3-element array."""
fires, total, kw = _call("ms_score_gate", {
"neuron": neuron, "low_text": low_text,
"session_context": session_context, "semantic_score": semantic_score})
return (fires, total, kw)
def score_finish(neuron: dict, low_text: str, total: float,
b_i: float | None, cache_priority: float | None,
keyword_hits: float) -> float:
return _call("ms_score_finish", {
"neuron": neuron, "low_text": low_text, "total": total,
"b_i": b_i, "cache_priority": cache_priority,
"keyword_hits": keyword_hits})
def base_level_from_uses(used_at: list, now: str) -> float:
return _call("ms_base_level_from_uses", {"used_at": used_at, "now": now})
+63
View File
@@ -161,7 +161,33 @@ def load_rules():
return rules
def _cc_pg_core():
"""Rust cognition_core when available (persona kernels); pin with
RUNE_COGNITION_CORE=python."""
import os
if os.environ.get("RUNE_COGNITION_CORE", "auto") == "python":
return None
try:
from rune import cc_loader
except Exception: # noqa: BLE001 — loader probe failure means fallback
return None
try:
return cc_loader if cc_loader.available() else None
except Exception: # noqa: BLE001 — availability probe never crashes
return None
def _lex(text):
"""Distinctive content words (R2 W3.2: Rust kernel when built)."""
core = _cc_pg_core()
if core is not None:
return set(core.persona_lex(text))
return _py_lex(text)
def _py_lex(text):
"""Python oracle (parity floor)."""
"""Distinctive content words of a rule body (lowercase, >3 chars,
light-stemmed so plural/singular engage each other; preserves 3-char
ALL-CAPS acronyms so technical abbreviations match their expansion)."""
@@ -253,6 +279,15 @@ def _lex_input(text):
def _overlap(a, b):
"""Overlap ratio (R2 W3.2: Rust kernel when built)."""
core = _cc_pg_core()
if core is not None:
return core.persona_overlap(sorted(a), sorted(b))
return _py_overlap(a, b)
def _py_overlap(a, b):
"""Python oracle (parity floor)."""
if not a or not b:
return 0.0
return len(a & b) / max(1, min(len(a), len(b)))
@@ -338,6 +373,34 @@ def _semantic_engagement(text, rule):
def decide(text, rules=None):
"""The persona decides on a request, deterministically (R2 W3.2:
Rust kernel when built; the semantic cosines are gathered here —
embedder calls are IO — and passed into the kernel)."""
rules = rules if rules is not None else load_rules()
core = _cc_pg_core()
if core is not None:
text_s = (text or "").strip()
sems = [_semantic_engagement(text_s, r) for r in rules]
return core.persona_decide(text_s, [_rule_json(r) for r in rules],
PERMISSIVE_MODE, sems)
return _py_decide(text, rules)
def _rule_json(r) -> dict:
"""A rule in the kernel's JSON contract (citation rides along when
present so _cite stays data-driven)."""
return {
"text": r["text"], "words": sorted(r["words"]),
"core": r["core"], "priority": r.get("priority", 0),
"restrictive": r.get("restrictive", False),
"user_rule": r.get("user_rule", False),
"citation": r.get("citation"),
}
def _py_decide(text, rules=None):
"""Python oracle (parity floor) — the ORIGINAL decide body."""
"""The persona decides on a request, deterministically, from its own
stored rules. No generative LLM in the decision path (a local embedding
model may contribute semantic similarity). Returns a full verdict."""
+9 -1
View File
@@ -162,8 +162,16 @@ def recall(context, limit=4):
term = (context or "").lower()
if not term:
return []
from rune.identity_anchors import _cc_core
core = _cc_core()
if core is not None:
return core.pins_recall(_load(), term, limit)
return _py_pin_recall(_load(), term, limit)
def _py_pin_recall(recs, term, limit=4):
out = []
for rec in _load():
for rec in recs:
if rec.get("state") != "open":
continue
p = _project(rec)
-257
View File
@@ -1,257 +0,0 @@
#!/usr/bin/env python3
"""query_memory.py — Query memory system for adaptive retrieval.
The system remembers what kinds of queries it gets, what retrieval
strategy worked, and adapts its gate logic accordingly.
This prevents dead weight — memories get tested based on usage
patterns, not just random exploration.
Research basis:
- IGMiRAG (arXiv:2602.07525): Intuition-guided retrieval
- Task-Adaptive Retrieval (arXiv:2604.07863): Learning from interaction history
- MemCon (arXiv:2607.13591): Learned policy for consolidation/forgetting
- Agent-Native Memory (arXiv:2606.24775): Workload-adaptive routing
Run under ~/.venvs/memory/bin/python3.
"""
import json
import os
import sys
from collections import Counter, defaultdict
from datetime import datetime, timezone
from typing import Dict, List, Optional, Tuple
QUERY_MEMORY_DIR = os.path.expanduser("~/.config/opencode/memory/query_memory")
QUERY_HISTORY_FILE = os.path.join(QUERY_MEMORY_DIR, "history.jsonl")
TOPIC_STATS_FILE = os.path.join(QUERY_MEMORY_DIR, "topic_stats.json")
# Topic keywords for classification
TOPIC_KEYWORDS = {
"guitar": ["guitar", "music", "teaching", "course", "lesson", "chord", "scale"],
"coding": ["code", "python", "script", "function", "debug", "build", "deploy"],
"memory": ["memory", "recall", "remember", "forget", "consolidat", "neuron"],
"persona": ["persona", "identity", "voice", "constitution", "character"],
"project": ["project", "task", "goal", "deadline", "milestone", "progress"],
"ai": ["ai", "model", "llm", "agent", "neural", "training", "inference"],
"personal": ["nick", "nottingham", "coffee", "coconut", "dog", "ttrpg"],
"system": ["system", "config", "install", "setup", "troubleshoot", "error"],
"research": ["research", "paper", "study", "analysis", "survey", "evidence"],
"creative": ["write", "design", "create", "build", "make", "art", "music"],
}
class QueryMemory:
"""Query memory system for adaptive retrieval."""
def __init__(self):
os.makedirs(QUERY_MEMORY_DIR, exist_ok=True)
self.history: List[dict] = self._load_history()
self.topic_stats: Dict[str, dict] = self._load_topic_stats()
def _load_history(self) -> List[dict]:
"""Load query history from disk."""
if not os.path.exists(QUERY_HISTORY_FILE):
return []
entries = []
with open(QUERY_HISTORY_FILE) as f:
for line in f:
try:
entries.append(json.loads(line.strip()))
except json.JSONDecodeError:
continue
return entries
def _save_history(self, entry: dict):
"""Append a query entry to history."""
with open(QUERY_HISTORY_FILE, "a") as f:
f.write(json.dumps(entry) + "\n")
def _load_topic_stats(self) -> Dict[str, dict]:
"""Load topic statistics from disk."""
if os.path.exists(TOPIC_STATS_FILE):
try:
with open(TOPIC_STATS_FILE) as f:
return json.load(f)
except json.JSONDecodeError:
return {}
return {}
def _save_topic_stats(self):
"""Save topic statistics to disk."""
with open(TOPIC_STATS_FILE, "w") as f:
json.dump(self.topic_stats, f, indent=2)
def classify_query(self, query: str) -> str:
"""Classify a query into a topic.
Returns the topic with the most keyword matches.
Falls back to "general" if no strong match.
"""
query_lower = query.lower()
scores = {}
for topic, keywords in TOPIC_KEYWORDS.items():
score = sum(1 for kw in keywords if kw in query_lower)
if score > 0:
scores[topic] = score
if not scores:
return "general"
return max(scores, key=scores.get)
def record_query(self, query: str, strategy: str, success: bool):
"""Record a query and its outcome.
Args:
query: The query text
strategy: "consolidated" or "full_retrieval"
success: Whether the retrieval produced useful results
"""
topic = self.classify_query(query)
entry = {
"query": query[:100],
"topic": topic,
"strategy": strategy,
"success": success,
"timestamp": datetime.now(timezone.utc).isoformat(),
}
self.history.append(entry)
self._save_history(entry)
# Update topic stats
if topic not in self.topic_stats:
self.topic_stats[topic] = {
"total_queries": 0,
"successful_queries": 0,
"consolidated_hits": 0,
"full_retrieval_hits": 0,
"exploration_rate": 0.30, # default
}
stats = self.topic_stats[topic]
stats["total_queries"] += 1
if success:
stats["successful_queries"] += 1
if strategy == "consolidated":
stats["consolidated_hits"] += 1
else:
stats["full_retrieval_hits"] += 1
# Adaptive exploration rate: decrease for well-tested topics
if stats["total_queries"] >= 10:
success_rate = stats["successful_queries"] / stats["total_queries"]
if success_rate > 0.8:
stats["exploration_rate"] = max(0.10, stats["exploration_rate"] - 0.05)
elif success_rate < 0.5:
stats["exploration_rate"] = min(0.50, stats["exploration_rate"] + 0.05)
self._save_topic_stats()
def get_topic_stats(self) -> Dict[str, dict]:
"""Get topic statistics."""
return self.topic_stats
def adaptive_exploration_rate(self, query: str) -> float:
"""Get the exploration rate for a query based on its topic.
Well-tested topics have lower exploration rates.
Untested topics have higher exploration rates.
"""
topic = self.classify_query(query)
if topic in self.topic_stats:
return self.topic_stats[topic].get("exploration_rate", 0.30)
return 0.50 # untested topics get high exploration
def get_dead_weight_topics(self) -> List[str]:
"""Get topics with low proven uses (dead weight)."""
dead = []
for topic, stats in self.topic_stats.items():
if stats["total_queries"] < 5:
dead.append(topic)
return dead
def get_health_report(self) -> dict:
"""Get a health report of the query memory system."""
total_queries = len(self.history)
topics = len(self.topic_stats)
dead_topics = len(self.get_dead_weight_topics())
return {
"total_queries": total_queries,
"topics_tracked": topics,
"dead_weight_topics": dead_topics,
"topic_stats": self.topic_stats,
}
# ═══════════════════════════════════════════════════════════════════════════════
# CLI
# ═══════════════════════════════════════════════════════════════════════════════
def main():
import argparse
parser = argparse.ArgumentParser(description="Query memory system")
sub = parser.add_subparsers(dest="cmd")
# record <query> --strategy <s> --success
p_rec = sub.add_parser("record", help="Record a query")
p_rec.add_argument("query", nargs="+")
p_rec.add_argument("--strategy", default="consolidated")
p_rec.add_argument("--success", action="store_true")
# stats
sub.add_parser("stats", help="Show topic statistics")
# rate <query>
p_rate = sub.add_parser("rate", help="Get exploration rate for a query")
p_rate.add_argument("query", nargs="+")
# health
sub.add_parser("health", help="Show health report")
args = parser.parse_args()
if not args.cmd:
parser.print_help()
sys.exit(1)
qm = QueryMemory()
if args.cmd == "record":
query = " ".join(args.query)
qm.record_query(query, args.strategy, args.success)
print(f"Recorded: {query[:50]} → {args.strategy} ({'success' if args.success else 'fail'})")
elif args.cmd == "stats":
stats = qm.get_topic_stats()
if stats:
print("Topic Statistics:")
for topic, s in stats.items():
print(
f" {topic}: {s['total_queries']} queries, {s['successful_queries']} success, rate={s['exploration_rate']:.2f}"
)
else:
print("No topic statistics yet.")
elif args.cmd == "rate":
query = " ".join(args.query)
rate = qm.adaptive_exploration_rate(query)
topic = qm.classify_query(query)
print(f"Query: {query[:50]}")
print(f"Topic: {topic}")
print(f"Exploration rate: {rate:.2f}")
elif args.cmd == "health":
report = qm.get_health_report()
print("Query Memory Health:")
print(f" Total queries: {report['total_queries']}")
print(f" Topics tracked: {report['topics_tracked']}")
print(f" Dead weight topics: {report['dead_weight_topics']}")
if __name__ == "__main__":
main()
+86 -79
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.88.4
Version: v0.99.6
Features: 41 (24 original + 17 new native commands)
Usage:
@@ -74,6 +74,14 @@ import voice_sync
import provider_registry
import goal
import rune_operator
import tui_transport as _tui_transport
def _operator_transport():
"""H4 slice 1 transport seam: the TUI's operator calls route through
tui_transport (RUNE_TUI_TRANSPORT=inprocess|v1, default inprocess —
zero behavior change until the wave opts in)."""
return _tui_transport.get_transport()
import cron_engine
import night_mode
import time_awareness
@@ -102,6 +110,56 @@ try:
except ImportError:
BACKEND_AVAILABLE = False
def collect_post_response_signals(response: str, messages_count: int) -> list[str]:
"""Gather the operational signals that used to each print a dim
system message after every task/build response (the dim-spam source,
T5.13): budget pressure, degraded claims, growth delta, the periodic
self-improvement cycle.
PURE and headless — no channel, no app. The caller renders ONE
collapsed line (or none when nothing fired). Queued-message execution
and on-complete cron firing stay in the worker: they move real
messages, not chatter, and keep their own echoes.
Returns short human-readable fragments; empty list = silence.
"""
signals: list[str] = []
if not BACKEND_AVAILABLE:
return signals
try:
budget_data = provider_budget.get_status()
ratios = []
for provider in budget_data.get("providers", {}).values():
if provider.get("monthly_limit", 0) > 0:
ratios.append(provider.get("monthly_used", 0) / provider["monthly_limit"])
if provider.get("token_limit", 0) > 0:
ratios.append(provider.get("tokens_used", 0) / provider["token_limit"])
usage_percent = max(ratios, default=0) * 100
if usage_percent > 80:
signals.append(f"⚠ Budget: {usage_percent:.0f}% used")
except Exception:
pass
try:
claims_result = claim_audit.scan_text(response[:500])
if claims_result and claims_result.get("degraded"):
signals.append(f"⚠ Claims degraded: {len(claims_result['degraded'])}")
except Exception:
pass
try:
growth = growth_delta.check()
if growth and growth.get("delta"):
signals.append(f"📈 Growth: {growth['delta']}")
except Exception:
pass
try:
if messages_count % 10 == 0:
self_improve.run()
signals.append("🔄 Self-improvement cycle triggered")
except Exception:
pass
return signals
from textual.app import App, ComposeResult
from textual.containers import Horizontal, Vertical, VerticalScroll
from textual.widgets import Button, Header, Input, ProgressBar, RichLog, Static
@@ -1144,7 +1202,7 @@ class ChannelView(Vertical):
TUI must never fail to mount because the ledger is unavailable.
"""
try:
window = rune_operator.get_conversation_window(self.channel_id, n=8)
window = _operator_transport().get_conversation_window(self.channel_id, n=8)
except Exception:
return
for msg in window:
@@ -1525,18 +1583,21 @@ class RuneApp(App):
def compose(self) -> ComposeResult:
yield Header()
with Horizontal(id="channels"):
yield ChannelView(
"task",
"[b]TASK[/b] — focused work",
classes="channel-view",
id="task-channel",
)
# T5.14 (Nick 2026-10-05): chat LEFT, task RIGHT — feels more
# natural (conversation first, focused work second). WorkPane
# stays third.
yield ChannelView(
"chat",
"[b]CHAT[/b] — free conversation",
classes="channel-view",
id="chat-channel",
)
yield ChannelView(
"task",
"[b]TASK[/b] — focused work",
classes="channel-view",
id="task-channel",
)
# P1-07 (#30): third collapsible column — the operational index.
yield WorkPane(id="work-pane")
with Horizontal(id="footer-bar"):
@@ -1705,6 +1766,8 @@ class RuneApp(App):
the load_tasks fallback then handles."""
try:
import rune_operator as _ro
# H4: private _task_state peek — stays in-process by design
# (the /v1 surface carries the four public calls only).
return getattr(_ro.get_operator(), "_task_state", None)
except Exception as exc: # noqa: BLE001 — deliberate degradation: startup ordering; the fallback path handles it and the degradation is logged
print(f"[task-loader] canonical repo unavailable ({exc}); "
@@ -1885,7 +1948,7 @@ class RuneApp(App):
# Show version info
self.chat_channel.add_message(
"system",
f"[dim]Rune v0.88.4 | Textual TUI | {len(loaded)} native modules[/dim]",
f"[dim]Rune v0.99.6 | Textual TUI | {len(loaded)} native modules[/dim]",
)
# Auto-show night mode suggestion if evening/night hours
@@ -2809,7 +2872,7 @@ class RuneApp(App):
def _cmd_rune(self, arg):
try:
status = rune_operator.get_status()
status = _operator_transport().get_status()
lines = ["[bold]🎭 Rune[/bold]"]
lines.append(f" Identity: {status['identity']}")
lines.append(f" Role: {status['role']}")
@@ -3621,7 +3684,7 @@ class RuneApp(App):
# Rune sends task to model with the selected model.
# channel is forwarded so the canonical task ledger (#18)
# keeps per-channel task identity.
result = rune_operator.send_task(text, model=model_name, channel=channel)
result = _operator_transport().send_task(text, model=model_name, channel=channel)
# Remove thinking indicator
thinking.stop()
@@ -3727,69 +3790,13 @@ class RuneApp(App):
except Exception:
pass
# Auto-warn budget when >80% used
try:
if BACKEND_AVAILABLE:
budget_data = provider_budget.get_status()
ratios = []
for provider in budget_data.get("providers", {}).values():
if provider.get("monthly_limit", 0) > 0:
ratios.append(provider.get("monthly_used", 0) / provider["monthly_limit"])
if provider.get("token_limit", 0) > 0:
ratios.append(provider.get("tokens_used", 0) / provider["token_limit"])
usage_percent = max(ratios, default=0) * 100
if usage_percent > 80:
ch.add_message(
"system",
f"[dim]⚠ Budget: {usage_percent:.0f}% used[/dim]",
)
except Exception:
pass
# Auto-check claims after response
try:
if BACKEND_AVAILABLE:
claims_result = claim_audit.scan_text(response[:500])
if claims_result and claims_result.get("degraded"):
ch.add_message(
"system",
f"[dim]⚠ Claims degraded: {len(claims_result['degraded'])}[/dim]",
)
except Exception:
pass
# NOTE (Runtime Reachability audit 2026-09-10): the
# old hook_engine.fire("post_message", ...) call here
# was dead — hook_engine defines no fire(); the
# AttributeError was silently swallowed, so "hooks"
# never ran. Removed rather than left as a false-
# liveness surface. Real hook dispatch is unwired
# (microkernel.fire has zero callers) — see the
# reachability audit, P3.
# Auto-check growth delta
try:
if BACKEND_AVAILABLE:
growth = growth_delta.check()
if growth and growth.get("delta"):
ch.add_message(
"system",
f"[dim]📈 Growth: {growth['delta']}[/dim]",
)
except Exception:
pass
# Auto-improve every 10 messages
try:
if self.status_bar.messages_count % 10 == 0:
if BACKEND_AVAILABLE:
self_improve.run()
ch.add_message(
"system",
f"[dim]🔄 Self-improvement cycle triggered[/dim]",
)
except Exception:
pass
# T5.13 dim-spam consolidation: previously five
# separate dim system messages printed after every
# response. ONE collapsed line now — empty means
# silence, not a dash.
signals = collect_post_response_signals(response[:500], self.status_bar.messages_count)
if signals:
ch.add_message("system", "[dim]" + " · ".join(signals) + "[/dim]")
elif result.get("error") == "RESEARCH_REQUIRED":
# Nonterminal routing disposition, not failure (2026-09-10):
# the task hit the deep-research floor and was dispatched.
@@ -3855,7 +3862,7 @@ class RuneApp(App):
button.disabled = True
async def _resolve() -> None:
result = rune_operator.resolve_pending_approval(event.approved)
result = _operator_transport().resolve_pending_approval(event.approved)
channel = widget.channel
ch = self.task_channel if channel == "task" else self.chat_channel
log = ch.query_one(f"#log-{ch.channel_id}", VerticalScroll)
@@ -3970,13 +3977,13 @@ class RuneApp(App):
pass
def action_resize_left(self) -> None:
"""Shrink task channel, grow chat channel."""
self.task_width = max(20, self.task_width - 5)
"""Shrink the LEFT channel (chat, post-swap), grow the RIGHT (task)."""
self.task_width = min(80, self.task_width + 5)
self._apply_resize()
def action_resize_right(self) -> None:
"""Grow task channel, shrink chat channel."""
self.task_width = min(80, self.task_width + 5)
"""Grow the LEFT channel (chat), shrink the RIGHT (task)."""
self.task_width = max(20, self.task_width - 5)
self._apply_resize()
def _apply_resize(self):
+117 -78
View File
@@ -86,6 +86,110 @@ PATTERN_MAX_PATTERNS = 200
# ═══════════════════════════════════════════════════════════════════════════════
def _keyword_stopwords() -> list:
"""Gather seam (W3.4): the pattern-keyword stopword table as a sorted
list — data, not logic; both engines read this module-owned table."""
return sorted(_KEYWORD_STOPWORDS)
_KEYWORD_STOPWORDS = frozenset({
"the",
"and",
"for",
"are",
"but",
"not",
"you",
"all",
"can",
"had",
"her",
"was",
"one",
"our",
"out",
"day",
"get",
"has",
"him",
"his",
"how",
"its",
"may",
"new",
"now",
"old",
"see",
"way",
"who",
"did",
"let",
"say",
"she",
"too",
"use",
"this",
"that",
"with",
"have",
"from",
"they",
"been",
"said",
"each",
"make",
"like",
"just",
"over",
"such",
"take",
"year",
"them",
"some",
"time",
"very",
"when",
"come",
"could",
"what",
"there",
"their",
"will",
"would",
"about",
"which",
"these",
"other",
"into",
"more",
"than",
"also",
"then",
})
def _core():
"""The Rust cognition_core when available (strangler: Python fallback
load-bearing). RUNE_COGNITION_CORE=python pins the fallback."""
return None if cc_loader_pin() else _cc_probe()
def _cc_probe():
from rune import cc_loader as _cl
try:
return _cl if _cl.available() else None
except Exception: # noqa: BLE001, S110 — loader probe never crashes the fallback
return None
def cc_loader_pin() -> bool:
"""The env pin: RUNE_COGNITION_CORE=python → kernels OFF (shared probe).
"""
import os
return os.environ.get("RUNE_COGNITION_CORE", "auto") == "python"
@dataclass
class OverridePattern:
"""A learned pattern that requires override."""
@@ -105,7 +209,14 @@ class OverridePattern:
return self.successes / total
def matches(self, text: str) -> bool:
"""Check if text matches this pattern's keywords."""
"""Check if text matches this pattern's keywords. Deterministic
keyword-count check (kernel port NOT owed here — the W3.4 safety
kernels cover the params/rows surfaces; this tiny comparison stays
Python-side per the membrane law: gather/IO in Python, kernels for
deterministic row work already ported)."""
return self._py_matches(text)
def _py_matches(self, text: str) -> bool:
text_lower = text.lower()
matches = sum(1 for kw in self.keywords if kw.lower() in text_lower)
return matches >= PATTERN_MIN_KEYWORDS
@@ -293,84 +404,12 @@ class SafetyOverride:
self.stats.patterns_learned += 1
def _extract_keywords(self, text: str) -> list[str]:
"""Extract distinctive keywords from text for pattern matching."""
# Remove common stop words, keep words > 3 chars
stop_words = {
"the",
"and",
"for",
"are",
"but",
"not",
"you",
"all",
"can",
"had",
"her",
"was",
"one",
"our",
"out",
"day",
"get",
"has",
"him",
"his",
"how",
"its",
"may",
"new",
"now",
"old",
"see",
"way",
"who",
"did",
"let",
"say",
"she",
"too",
"use",
"this",
"that",
"with",
"have",
"from",
"they",
"been",
"said",
"each",
"make",
"like",
"just",
"over",
"such",
"take",
"year",
"them",
"some",
"time",
"very",
"when",
"come",
"could",
"what",
"there",
"their",
"will",
"would",
"about",
"which",
"these",
"other",
"into",
"more",
"than",
"also",
"then",
}
"""Extract distinctive keywords from text for pattern matching.
Keyword extraction stays Python-side (W3.4 scope note): the safety
kernels cover the params/rows surfaces; this extractor is a gather
(IO-adjacent list build) per the membrane law."""
words = re.findall(r"[a-z]{4,}", text.lower())
keywords = [w for w in words if w not in stop_words]
keywords = [w for w in words if w not in _KEYWORD_STOPWORDS]
# Return top 5 most distinctive words
return keywords[:5]
+96
View File
@@ -0,0 +1,96 @@
#!/usr/bin/env python3
"""ctypes loader for the Rust scaffold_core (R2 Wave 1).
Contract BUILDER-2026-10-04T15:25:33Z-cli. The V1-ACCEPTED scaffolding
engine's deterministic core through the cdylib with no subprocess.
No-clock law: `now` always arrives inside the inputs.
FFI contract (doc-envelope law — every call returns {"ok", "value"|"err"}):
* sc_check_transition({current, target}) -> {legal, error?}
* sc_close_quality(inputs) -> contract_quality bucket dict
* sc_free_string(ptr)
FFI segfault law: every symbol gets explicit argtypes + restype (c_void_p
for returned char*). Zero dependencies; override the .so with SC_RUST_LIB
(a FILE path).
"""
from __future__ import annotations
import ctypes
import json
import os
from pathlib import Path
_REPO = Path(__file__).resolve().parents[2]
_CRATE_DIR = _REPO / "src" / "rust" / "scaffold_core"
_LIB_PATH = Path(
os.environ.get(
"SC_RUST_LIB", _CRATE_DIR / "target" / "release" / "libscaffold_core.so"
)
)
_lib = None
class ScaffoldCoreUnavailable(FileNotFoundError):
"""The Rust core is not built (or SC_RUST_LIB points nowhere). Callers
fall back to the Python implementation — strangler law."""
def available() -> bool:
return _LIB_PATH.is_file()
def _load() -> ctypes.CDLL:
global _lib
if _lib is not None:
return _lib
if not _LIB_PATH.exists():
raise ScaffoldCoreUnavailable(
f"Rust scaffold_core not built: {_LIB_PATH} missing — run "
f"`cargo build --release` in {_CRATE_DIR}"
)
lib = ctypes.CDLL(str(_LIB_PATH))
lib.sc_free_string.argtypes = [ctypes.c_void_p]
lib.sc_free_string.restype = None
for sym in ("sc_check_transition", "sc_close_quality",
"sc_language_placement"):
fn = getattr(lib, sym)
fn.argtypes = [ctypes.c_char_p]
fn.restype = ctypes.c_void_p # char* — never default c_int
_lib = lib
return lib
def _call(sym: str, payload: dict):
lib = _load()
fn = getattr(lib, sym)
raw = json.dumps(payload).encode("utf-8")
ptr = fn(raw)
if not ptr:
raise RuntimeError(f"{sym} returned a null pointer")
try:
text = ctypes.cast(ptr, ctypes.c_char_p).value.decode("utf-8")
finally:
lib.sc_free_string(ptr)
env = json.loads(text)
if not env.get("ok"):
raise RuntimeError(f"{sym} core error: {env.get('err')}")
return env["value"]
def check_transition(current: str, target: str) -> dict:
"""Phase-transition legality verdict ({legal} or {legal, error})."""
return _call("sc_check_transition", {"current": current, "target": target})
def close_quality(inputs: dict) -> dict:
"""The closure classification buckets from gathered inputs."""
return _call("sc_close_quality", inputs)
def language_placement(project: str, characteristics: list) -> dict:
"""The protocol's language-placement verdict (R2 Wave 1)."""
return _call("sc_language_placement",
{"project": project, "characteristics": characteristics})
+76 -62
View File
@@ -488,6 +488,64 @@ class Scaffold:
def contract(self, doc: dict, contract_id: str) -> dict | None:
return entity(doc, f"contract:{contract_id}")
def _py_close_quality(self, inputs: dict) -> dict:
"""Python core of the closure classification (R2 Wave 1 parity
oracle — scaffold_core.close_quality computes from the same input
shape). Pure bucket math; side effects stay in close_contract."""
affected = set(inputs.get("affected_entities") or [])
expected_new = set(inputs.get("expected_new_entities") or [])
expected_runtime = set(inputs.get("expected_runtime_effects") or [])
observed_evidence = inputs.get("observed_evidence") or []
predicted = inputs.get("predicted_frontier")
obs_set = {item.get("entity", "") for item in observed_evidence}
obs_set.discard("")
pred_existing = set(inputs.get("predicted_existing_affected") or [])
pred_existing |= affected
if predicted is not None:
pred_existing |= {
x
for k in ("level0_direct", "level1_obligations", "contracts", "level2_verification")
for x in predicted.get(k, [])
}
correctly_predicted = sorted(pred_existing & obs_set)
predicted_but_unaffected = sorted(pred_existing - obs_set)
expected_new_observed = sorted(expected_new & obs_set)
expected_new_missing = sorted(expected_new - obs_set)
matched_runtime = {o for o in obs_set if any(eff.startswith(o) for eff in expected_runtime)}
expected_runtime_observed = sorted(
eff for eff in expected_runtime if any(eff.startswith(o) for o in matched_runtime)
)
genuinely_unexpected_new = sorted(obs_set - pred_existing - expected_new - matched_runtime)
return {
"predicted_existing_affected": sorted(pred_existing),
"expected_new_entities": sorted(expected_new),
"observed": sorted(obs_set),
"observed_existing": correctly_predicted,
"observed_new": sorted(obs_set & (expected_new | (obs_set - pred_existing))),
"correctly_predicted": correctly_predicted,
"predicted_but_unaffected": predicted_but_unaffected,
"expected_new_observed": expected_new_observed,
"expected_new_missing": expected_new_missing,
"expected_runtime_effects": sorted(expected_runtime),
"expected_runtime_observed": expected_runtime_observed,
"expected_runtime_missing": sorted(expected_runtime - obs_set),
"genuinely_unexpected_new": genuinely_unexpected_new,
"missed": genuinely_unexpected_new,
"needless": predicted_but_unaffected,
"candidate_corrections": [
{
"entity": x,
"status": "CANDIDATE",
"note": "genuinely unexpected new surface — evidence required before store update",
}
for x in genuinely_unexpected_new
],
"evidence_sources": sorted({item.get("source", "unattributed") for item in observed_evidence}),
}
def snapshot_predicted_frontier(self, doc: dict, contract_id: str, seed_entity_ids: list[str]) -> dict:
"""Persist the predicted impact frontier at approval time — the input
to closure's predicted-vs-observed comparison."""
@@ -541,72 +599,28 @@ class Scaffold:
raise ValueError(f"contract not found: {contract_id}")
if e.get("phase") not in ("superseding", "verifying"):
raise IllegalPhaseTransition(f"close requires superseding/verifying phase, got {e.get('phase')}")
predicted = e.get("ext", {}).get("predicted_frontier")
affected = e.get("identities", {}).get("affected_entities", [])
expected_new = set(e.get("identities", {}).get("expected_new_entities", []))
# Tranche 5 §14: expected RUNTIME effects (deployment:current advances
# etc.) are prediction/accounting metadata, not workflow steps. An
# observed entity matching an expected runtime effect is EXPECTED,
# not genuinely unexpected — but only when independently observed
# (§15: prediction never self-certifies; the evidence item must exist).
expected_runtime = set(e.get("ext", {}).get("expected_runtime_effects", []))
quality: dict = {"contract": contract_id, "captured_at": now()}
# independent observed impact (§17): evidence items only
obs = record_observed_impact(doc, contract_id, observed_evidence or [])
obs_set = set(obs["observed_entities"])
pred_existing = set(e.get("ext", {}).get("predicted_existing_affected", []))
pred_existing |= set(affected)
if predicted is not None:
pred_existing |= {
x
for k in ("level0_direct", "level1_obligations", "contracts", "level2_verification")
for x in predicted.get(k, [])
}
# R2 Wave 1: the bucket math is factored out so the Rust engine
# (scaffold_core.close_quality) computes from the SAME gathered
# inputs — the production ledger replay is the parity oracle.
inputs = {
"contract_id": contract_id,
"phase": e.get("phase", "verifying"),
"affected_entities": e.get("identities", {}).get("affected_entities", []),
"expected_new_entities": e.get("identities", {}).get("expected_new_entities", []),
"predicted_existing_affected": e.get("ext", {}).get("predicted_existing_affected", []),
"predicted_frontier": e.get("ext", {}).get("predicted_frontier"),
"expected_runtime_effects": e.get("ext", {}).get("expected_runtime_effects", []),
"observed_evidence": observed_evidence or [],
"now": now(),
}
quality = self._py_close_quality(inputs)
quality["contract"] = contract_id
quality["captured_at"] = inputs["now"]
quality["observed"] = obs["observed_entities"]
correctly_predicted = sorted(pred_existing & obs_set)
predicted_but_unaffected = sorted(pred_existing - obs_set)
expected_new_observed = sorted(expected_new & obs_set)
expected_new_missing = sorted(expected_new - obs_set)
# effect strings are phrases ("deployment:current advances"); an
# observed entity matches when an expected effect STARTS with its id
matched_runtime = {o for o in obs_set if any(eff.startswith(o) for eff in expected_runtime)}
expected_runtime_observed = sorted(
eff for eff in expected_runtime if any(eff.startswith(o) for o in matched_runtime)
)
expected_runtime_missing = sorted(expected_runtime - set(expected_runtime_observed))
genuinely_unexpected_new = sorted(obs_set - pred_existing - expected_new - matched_runtime)
quality.update(
{
"predicted_existing_affected": sorted(pred_existing),
"expected_new_entities": sorted(expected_new),
"observed": sorted(obs_set), # legacy union (Tranche 2 readers)
"observed_existing": correctly_predicted,
"observed_new": sorted(obs_set & (expected_new | (obs_set - pred_existing))),
"correctly_predicted": correctly_predicted,
"predicted_but_unaffected": predicted_but_unaffected,
"expected_new_observed": expected_new_observed,
"expected_new_missing": expected_new_missing,
"expected_runtime_effects": sorted(expected_runtime),
"expected_runtime_observed": expected_runtime_observed,
"expected_runtime_missing": sorted(expected_runtime - obs_set),
"genuinely_unexpected_new": genuinely_unexpected_new,
# legacy aliases (Tranche 2/3 consumers)
"missed": genuinely_unexpected_new,
"needless": predicted_but_unaffected,
"candidate_corrections": [
{
"entity": x,
"status": "CANDIDATE",
"note": "genuinely unexpected new surface — evidence required before store update",
}
for x in genuinely_unexpected_new
],
"evidence_sources": sorted({item.get("source", "unattributed") for item in (observed_evidence or [])}),
}
)
if scope_files is not None:
quality["scope_check"] = self.check_scope(doc, contract_id, scope_files)
e.setdefault("ext", {})["contract_quality"] = quality
+462 -132
View File
@@ -53,6 +53,12 @@ from rune.structural_store import ( # shared generic core (SCAFFOLD-TRANCHE-001
reconcile_authorities, mark_conflict, resolve_status, authority_health,
dedupe_observations as _ss_dedupe_observations,
)
from rune.spine_language import ( # noqa: F401 — re-exported (import-compat): the
# #2465 doc-level transforms live here now (giant-file carve); tests and
# the canary_spine_meta read them THROUGH spine, exactly as before.
_classification_sides, _place_languages, _normalize_discovery_ids,
_apply_contract_discoveries, _contract_discoveries,
)
SCHEMA_VERSION = 1
@@ -148,6 +154,141 @@ def _canary_registry() -> list[str]:
return []
# ── meta-scaffold surface projections (H-meta, contract
# BUILDER-2026-10-04T06:24:56Z-cli): every part of rune gets a modeled
# entity. These projectors run in the SEEDED derivation path (build()
# after carry-forward), so they fire on every rebuild — manifest ingest
# alone would only fire on a cold store.
WORKFLOWS_DIR = ROOT / ".gitea" / "workflows"
def _declared_surfaces() -> dict:
"""Umbrella-declared non-module surfaces: rust crates, Go surfaces,
configs. The manifest (config/umbrellas.json) is the declaration
authority — same law as modules, new kinds."""
try:
manifest = json.loads((ROOT / "config" / "umbrellas.json").read_text())
except (OSError, json.JSONDecodeError):
return {"rust_crates": [], "go_surfaces": [], "configs": []}
out = {"rust_crates": [], "go_surfaces": [], "configs": []}
for name, u in manifest.get("umbrellas", {}).items():
for crate in u.get("rust_crates", []):
out["rust_crates"].append((crate, name))
for surface in u.get("go_surfaces", []):
out["go_surfaces"].append((surface, name))
for cfg in u.get("configs", []):
out["configs"].append((cfg, name))
return out
def project_meta_surfaces(doc: dict, inputs: dict | None = None) -> int:
"""Project rust-crate / go-surface / config / workflow entities.
Deterministic: declared surfaces come from the umbrella manifest,
workflows from a sorted filesystem scan. Ids follow the kind:name
convention (rust-crate:evidence_provenance, go-surface:rune-entry,
config:go-version.txt, workflow:ci.yml).
H3-spine: when `inputs` is given (the gather-driven path), every
filesystem probe comes from the gathered dict so the Rust core and the
Python fallback consume byte-identical scans. The no-inputs path gathers
on the spot (backward compatibility for direct callers)."""
count = 0
if inputs is None:
inputs = _gather_meta_inputs()
manifest = inputs.get("config_manifest") or {}
declared = {"rust_crates": [], "go_surfaces": [], "configs": []}
for name, u in manifest.get("umbrellas", {}).items():
for crate in u.get("rust_crates", []):
declared["rust_crates"].append((crate, name))
for surface in u.get("go_surfaces", []):
declared["go_surfaces"].append((surface, name))
for cfg in u.get("configs", []):
declared["configs"].append((cfg, name))
crate_dirs = set(inputs.get("crate_dirs", []))
go_dirs = set(inputs.get("go_dirs", []))
config_files = set(inputs.get("config_files", []))
crate_canaries = inputs.get("crate_canaries", {})
for crate, umbrella in declared["rust_crates"]:
cid = f"rust-crate:{crate}"
add_entity(doc, id=cid, kind="rust-crate", tags=["code"],
identities={"source": f"src/rust/{crate}"},
evidence_refs=[f"umbrella-manifest#{umbrella}"],
maturity="PRESENT" if crate in crate_dirs else "DECLARED")
add_relationship(doc, type="belongs_to", from_id=cid,
to_id=f"subsystem:{umbrella}",
evidence_refs=[f"umbrella-manifest#{umbrella}"])
# crate-resident canaries: projected from the crate dir itself —
# they fail the src/rune manifest-canary convention by design
# (they verify the crate, they belong to the crate).
if crate in crate_dirs:
for stem in crate_canaries.get(crate, []):
mid = f"module:{stem}"
add_entity(doc, id=mid, kind="module", tags=["code", "canary"],
identities={"source": f"src/rust/{crate}/{stem}.py"},
evidence_refs=[f"crate-scan:{crate}"],
maturity="PRESENT")
add_relationship(doc, type="obligated_canary", from_id=cid,
to_id=mid, evidence_refs=[f"crate-scan:{crate}"])
count += 1
for surface, umbrella in declared["go_surfaces"]:
gid = f"go-surface:{surface}"
add_entity(doc, id=gid, kind="go-surface", tags=["code"],
identities={"source": f"src/go/{surface}"},
evidence_refs=[f"umbrella-manifest#{umbrella}"],
maturity="PRESENT" if surface in go_dirs else "DECLARED")
add_relationship(doc, type="belongs_to", from_id=gid,
to_id=f"subsystem:{umbrella}",
evidence_refs=[f"umbrella-manifest#{umbrella}"])
count += 1
for cfg, umbrella in declared["configs"]:
cid = f"config:{cfg}"
add_entity(doc, id=cid, kind="config",
identities={"source": cfg},
evidence_refs=[f"umbrella-manifest#{umbrella}"],
maturity="PRESENT" if cfg in config_files else "DECLARED")
add_relationship(doc, type="belongs_to", from_id=cid,
to_id=f"subsystem:{umbrella}",
evidence_refs=[f"umbrella-manifest#{umbrella}"])
count += 1
for wf in inputs.get("workflows", []):
wid = f"workflow:{wf}"
add_entity(doc, id=wid, kind="workflow", tags=["infra"],
identities={"source": f".gitea/workflows/{wf}"},
evidence_refs=["workflow-scan"],
maturity="PRESENT")
add_relationship(doc, type="belongs_to", from_id=wid,
to_id="subsystem:governance",
evidence_refs=["workflow-scan"])
count += 1
return count
def _gather_meta_inputs() -> dict:
"""The meta-surface scan slice (shared by the full build gather)."""
declared = _declared_surfaces()
crate_dirs = [c for c, _ in declared["rust_crates"]
if (ROOT / "src" / "rust" / c).is_dir()]
try:
config_manifest = json.loads((ROOT / "config" / "umbrellas.json").read_text())
except (OSError, json.JSONDecodeError):
config_manifest = None
return {
"config_manifest": config_manifest,
"crate_dirs": crate_dirs,
"crate_canaries": {
c: sorted(cf.stem for cf in (ROOT / "src" / "rust" / c).glob("canary_*.py"))
for c in crate_dirs
},
"go_dirs": [g for g, _ in declared["go_surfaces"]
if (ROOT / "src" / "go" / g).is_dir()],
"config_files": [cfg for cfg, _ in declared["configs"] if (ROOT / cfg).is_file()],
"workflows": (sorted(wf.name for wf in WORKFLOWS_DIR.glob("*.yml"))
if WORKFLOWS_DIR.is_dir() else []),
}
def _deployed_observation() -> dict | None:
"""Observe the deployment authority — never invent it."""
ident = DEPLOYED_LINK / "src" / "rune" / "_build_identity.json"
@@ -174,15 +315,248 @@ def ingest_umbrellas(doc: dict, manifest_path: Path | None = None) -> int:
"""Phase A: translate the (last manually maintained) umbrella manifest
into Spine entities + relationships. Refuses a Spine-GENERATED view —
that guards the ingest cycle after Phase C makes the view output-only.
Returns count of subsystems ingested."""
Returns count of subsystems ingested. (File-driven wrapper; the logic
lives in _ingest_umbrellas_data so the gathered path shares it.)"""
path = manifest_path or UMBRELLA_MANIFEST
text = path.read_text()
data = json.loads(text)
data = json.loads(path.read_text())
if GENERATED_VIEW_MARKER in data:
raise ValueError(
f"{path} is a Spine-generated compatibility view; refusing to "
"ingest it (Phase A reads the manual authority exactly once). "
"Structural membership now lives in the Spine store.")
return _ingest_umbrellas_data(doc, data)
def _rust_core():
"""The Rust spine core when available (strangler: Python fallback is
load-bearing until deletion is proven). RUNE_SPINE_CORE=python pins the
fallback (parity harnesses and pre-cargo environments)."""
if os.environ.get("RUNE_SPINE_CORE", "auto") == "python":
return None
try:
from rune import spine_loader
except Exception: # noqa: BLE001 — any loader failure means fallback, never a broken build
return None
try:
return spine_loader if spine_loader.available() else None
except Exception: # noqa: BLE001 — availability probe must never crash the fallback
return None
def _gather_build_inputs(manifest_path: Path | None = None) -> dict:
"""Every I/O read build() needs, in one place (zero-dep split law:
Python scans, the core — Rust or Python — only assembles). Both cores
consume this dict, so parity divergence is logic, never environment."""
existing = load()
if existing:
_normalize_discovery_ids(existing)
seeded = bool(existing) and any(
r["type"] == "belongs_to" for r in existing["relationships"])
# ingest manifest: only consulted when the unseeded path needs it
# (faithful to the original build(), which never touches the file when
# seeded). The parse is TOLERANT here — the Python fallback goes through
# the public ingest_umbrellas (which reads + raises like the original,
# and stays monkeypatchable for isolation tests); the Rust core consumes
# the parsed data on the delegated path.
ingest_manifest = None
ingest_path = None
if not seeded:
ingest_path = str(Path(manifest_path) if manifest_path else UMBRELLA_MANIFEST)
try:
ingest_manifest = json.loads(Path(ingest_path).read_text())
except (OSError, json.JSONDecodeError):
ingest_manifest = None
inputs = {
"now": now(),
"existing": existing,
"manifest": ingest_manifest,
"manifest_path": ingest_path,
"graph": {m: sorted(deps) for m, deps in _import_graph().items()},
"registry": _canary_registry(),
"tested": sorted(_test_backed_modules()),
"main_present": (SRC_DIR / "__main__.py").is_file(),
"present_modules": [p.stem for p in SRC_DIR.glob("*.py")],
"deployed_obs": _deployed_observation(),
"deployed_sources": _deployed_source_files(),
"canary_files": [p.stem for p in SRC_DIR.glob("canary_*.py")],
}
inputs.update(_gather_meta_inputs())
return inputs
def _deployed_source_files() -> list[str]:
"""Relative paths under DEPLOYED_LINK that exist as files — the gathered
form of `(DEPLOYED_LINK / source).is_file()` for module sources."""
out: list[str] = []
for base, pattern in ((DEPLOYED_LINK / "src" / "rune", "*.py"),
(DEPLOYED_LINK / "src" / "rust", "*/canary_*.py")):
if base.is_dir():
for p in base.glob(pattern):
if p.is_file():
out.append(p.relative_to(DEPLOYED_LINK).as_posix())
return out
def _py_build_from_inputs(inputs: dict) -> dict:
"""Deterministic full projection from GATHERED inputs. Same inputs →
byte-identical doc (modulo generated_at). This is the Python core; the
Rust core (project.rs) must produce structurally identical output."""
doc = new_doc()
ts = inputs["now"]
# 1. subsystem structure — from the Spine store if it already owns
# membership (Phase B+), else from the manual manifest (Phase A).
existing = inputs["existing"]
seeded = False
if existing:
rels = [r for r in existing["relationships"] if r["type"] == "belongs_to"]
if rels:
seeded = True
# CARRY EVERYTHING FORWARD — open-world requirement. Filtering
# by known kinds/types here would silently destroy tasks,
# contracts, capabilities, and any future entity class on every
# refresh (the exact TOO-CLOSED failure the contract forbids;
# observed live: task bindings vanished through build()).
# Module-level facts (imports, tests, maturity) are RE-DERIVED
# below onto the carried state; everything else passes through.
doc["entities"].extend(json.loads(json.dumps(existing["entities"])))
doc["relationships"].extend(json.loads(json.dumps(existing["relationships"])))
doc["observations"].extend(json.loads(json.dumps(
existing.get("observations", []))))
if not seeded:
mp = inputs.get("manifest_path")
if inputs.get("manifest") is not None and not mp:
# gather-supplied manifest data (the Rust-parity path): ingest
# the dict directly — no file to re-read
_ingest_umbrellas_data(doc, inputs["manifest"])
else:
# through the PUBLIC ingest_umbrellas (reads + raises like the
# original build; monkeypatchable for isolation tests)
ingest_umbrellas(doc, Path(mp) if mp else None)
# 2. module-level structural facts from actual code
graph = inputs["graph"]
registry = inputs["registry"]
tested = set(inputs["tested"])
present = set(inputs["present_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 inputs["main_present"] 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)
# Re-derivation uses the HIGH-WATER path (add_entity upgrades only,
# never logs): fresh evidence may be lower than the store's earned
# state on any given pass, and that is not a demotion event — it is
# a build. Explicit promote() logging is for evidence-gated API calls.
def _derive(state: str, ev: str) -> None:
add_entity(doc, id=mid, kind="module", maturity=state,
evidence_refs=[ev])
if m in present:
assert e is not None # membership loop guarantees presence
e["identities"]["source"] = f"src/rune/{m}.py"
_derive("PRESENT", f"source:{m}.py")
consumers = sorted(c for c, deps in graph.items() if m in deps and c != m)
deps = sorted(d for d in graph.get(m, []) if d in mods and d != m)
for c in consumers:
rel = add_relationship(doc, type="consumed_by", from_id=mid,
to_id=f"module:{c}", evidence_refs=[f"ast-import:{m}"])
_authority_observed(doc, rel, "ast-import-projector")
for d in deps:
rel = add_relationship(doc, type="depends_on", from_id=mid,
to_id=f"module:{d}", evidence_refs=[f"ast-import:{m}"])
_authority_observed(doc, rel, "ast-import-projector")
# WIRED: importers exist, or registry wiring (canary registry)
if consumers or m in registry:
_derive("WIRED", f"ast-imports:{len(consumers)}" if consumers
else "canary-registry")
# EXERCISED: dedicated test or canary exercise
if m in tested:
_derive("EXERCISED", f"tests/test_{m}.py")
elif m in registry:
_derive("EXERCISED", "canary-registry-exercise")
# 3. runtime/deployment observation (dynamic, bounded)
obs = inputs["deployed_obs"]
if obs:
_authority_observed(doc, None, "deploy-identity-projector") # generation bump
deployed_sources = set(inputs["deployed_sources"])
dep = add_entity(doc, id="deployment:current", kind="deployment_surface",
identities=obs, evidence_refs=["_build_identity.json"],
maturity="LIVE_PROVEN")
for e in doc["entities"]:
if e["kind"] == "module" and e["identities"].get("source", "") in deployed_sources:
rel = add_relationship(doc, type="deployed_as", from_id=e["id"],
to_id="deployment:current",
evidence_refs=[f"deployed-tree:{obs['deployed_sha']}"])
_authority_observed(doc, rel, "deploy-identity-projector")
doc["observations"].append({"kind": "deployed_identity", **obs, "observed_at": ts})
# 4. meta-scaffold surface projection (H-meta): rust crates, Go
# surfaces, configs, workflows — every part of rune modeled.
projected = project_meta_surfaces(doc, inputs)
if projected:
_authority_observed(doc, None, "meta-surface-projector")
# 4b. manifest canaries as first-class entities (H-meta): the seeded
# path skips ingest, and ingest only made the EDGE — the canary
# entity itself was never created, leaving obligated_canary edges
# pointing at nothing (observed live: zero canary entities in the
# spine before this projector).
manifest = inputs.get("config_manifest")
if manifest:
canary_files = set(inputs["canary_files"])
for name, u in manifest.get("umbrellas", {}).items():
for canary in u.get("canaries", []):
mid = f"module:{canary}"
add_entity(doc, id=mid, kind="module", tags=["code", "canary"],
identities={"source": f"src/rune/{canary}.py"},
evidence_refs=[f"umbrella-manifest#{name}"],
maturity="PRESENT" if canary in canary_files else "DECLARED")
add_relationship(doc, type="obligated_canary",
from_id=f"subsystem:{name}", to_id=mid,
evidence_refs=[f"umbrella-manifest#{name}"])
# reconcile: facts owned by projectors that no longer observe them
# become STALE (marked, never deleted — history survives; open-world
# preservation applies to everything, staleness is the truth marker)
staled = reconcile_authorities(doc)
for s in staled:
doc["observations"].append({"kind": "fact_staled", **s, "observed_at": ts})
# exact-duplicate observation suppression (carry-forward + this pass
# both record deployed_identity etc.); distinct repeats are history
seen_obs = set()
unique_obs = []
for o in doc["observations"]:
key = json.dumps(_sorted(o), sort_keys=True)
if key not in seen_obs:
seen_obs.add(key)
unique_obs.append(o)
doc["observations"] = unique_obs
doc["generated_at"] = ts
return doc
def _ingest_umbrellas_data(doc: dict, data: dict) -> int:
"""The original ingest_umbrellas with the file read lifted out (H3-spine
gather boundary). Raises ValueError on a Spine-generated view, exactly
like the file-driven original."""
if GENERATED_VIEW_MARKER in data:
raise ValueError(
"the manifest is a Spine-generated compatibility view; refusing to "
"ingest it (Phase A reads the manual authority exactly once). "
"Structural membership now lives in the Spine store.")
count = 0
KNOWN_FIELDS = {"description", "modules", "depends_on", "mirrors", "canaries"}
for name, u in data.get("umbrellas", {}).items():
@@ -219,125 +593,57 @@ def ingest_umbrellas(doc: dict, manifest_path: Path | None = None) -> int:
def build(manifest_path: Path | None = None) -> dict:
"""Deterministic full projection from authorities. Same inputs →
byte-identical doc (modulo generated_at)."""
doc = new_doc()
# 1. subsystem structure — from the Spine store if it already owns
# membership (Phase B+), else from the manual manifest (Phase A).
existing = load()
seeded = False
if existing:
rels = [r for r in existing["relationships"] if r["type"] == "belongs_to"]
if rels:
seeded = True
# CARRY EVERYTHING FORWARD — open-world requirement. Filtering
# by known kinds/types here would silently destroy tasks,
# contracts, capabilities, and any future entity class on every
# refresh (the exact TOO-CLOSED failure the contract forbids;
# observed live: task bindings vanished through build()).
# Module-level facts (imports, tests, maturity) are RE-DERIVED
# below onto the carried state; everything else passes through.
doc["entities"].extend(json.loads(json.dumps(existing["entities"])))
doc["relationships"].extend(json.loads(json.dumps(existing["relationships"])))
doc["observations"].extend(json.loads(json.dumps(
existing.get("observations", []))))
if not seeded:
ingest_umbrellas(doc, manifest_path)
# 2. module-level structural facts from actual code
graph = _import_graph()
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)
# Re-derivation uses the HIGH-WATER path (add_entity upgrades only,
# never logs): fresh evidence may be lower than the store's earned
# state on any given pass, and that is not a demotion event — it is
# a build. Explicit promote() logging is for evidence-gated API calls.
def _derive(state: str, ev: str) -> None:
add_entity(doc, id=mid, kind="module", maturity=state,
evidence_refs=[ev])
if (SRC_DIR / f"{m}.py").is_file():
assert e is not None # membership loop guarantees presence
e["identities"]["source"] = f"src/rune/{m}.py"
_derive("PRESENT", f"source:{m}.py")
consumers = sorted(c for c, deps in graph.items() if m in deps and c != m)
deps = sorted(d for d in graph.get(m, set()) if d in mods and d != m)
for c in consumers:
rel = add_relationship(doc, type="consumed_by", from_id=mid,
to_id=f"module:{c}", evidence_refs=[f"ast-import:{m}"])
_authority_observed(doc, rel, "ast-import-projector")
for d in deps:
rel = add_relationship(doc, type="depends_on", from_id=mid,
to_id=f"module:{d}", evidence_refs=[f"ast-import:{m}"])
_authority_observed(doc, rel, "ast-import-projector")
# WIRED: importers exist, or registry wiring (canary registry)
if consumers or m in registry:
_derive("WIRED", f"ast-imports:{len(consumers)}" if consumers
else "canary-registry")
# EXERCISED: dedicated test or canary exercise
if m in tested:
_derive("EXERCISED", f"tests/test_{m}.py")
elif m in registry:
_derive("EXERCISED", "canary-registry-exercise")
# 3. runtime/deployment observation (dynamic, bounded)
obs = _deployed_observation()
if obs:
_authority_observed(doc, None, "deploy-identity-projector") # generation bump
dep = add_entity(doc, id="deployment:current", kind="deployment_surface",
identities=obs, evidence_refs=["_build_identity.json"],
maturity="LIVE_PROVEN")
for e in doc["entities"]:
if e["kind"] == "module" and (DEPLOYED_LINK / e["identities"].get("source", "")).is_file():
rel = add_relationship(doc, type="deployed_as", from_id=e["id"],
to_id="deployment:current",
evidence_refs=[f"deployed-tree:{obs['deployed_sha']}"])
_authority_observed(doc, rel, "deploy-identity-projector")
doc["observations"].append({"kind": "deployed_identity", **obs, "observed_at": now()})
# reconcile: facts owned by projectors that no longer observe them
# become STALE (marked, never deleted — history survives; open-world
# preservation applies to everything, staleness is the truth marker)
staled = reconcile_authorities(doc)
for s in staled:
doc["observations"].append({"kind": "fact_staled", **s, "observed_at": now()})
# exact-duplicate observation suppression (carry-forward + this pass
# both record deployed_identity etc.); distinct repeats are history
seen_obs = set()
unique_obs = []
for o in doc["observations"]:
key = json.dumps(_sorted(o), sort_keys=True)
if key not in seen_obs:
seen_obs.add(key)
unique_obs.append(o)
doc["observations"] = unique_obs
doc["generated_at"] = now()
byte-identical doc (modulo generated_at). H3-spine: gathers the scans
once, then delegates to the Rust core when built (Python fallback —
strangler law: the old path stays load-bearing until deletion is proven)."""
inputs = _gather_build_inputs(manifest_path)
core = _rust_core()
# The unseeded path is the one-time Phase A migration: it stays on the
# Python core so the public ingest_umbrellas seam (monkeypatched by
# isolation tests) is honored byte-for-byte. Steady-state (seeded)
# builds delegate to the Rust core; the parity battery covers BOTH
# paths, including an explicit unseeded-ingest parity section.
seeded = bool(inputs["existing"]) and any(
r["type"] == "belongs_to" for r in inputs["existing"]["relationships"])
doc = (core.project(inputs) if core is not None and seeded
else _py_build_from_inputs(inputs))
# both cores funnel through the same post-projection passes (the
# contract-discovery write-back + language placement) — the doc-level
# transforms live in Python until the #2464 top-up ports them
_apply_contract_discoveries(doc)
_place_languages(doc)
return doc
# ── drift ─────────────────────────────────────────────────────────────────
def drift(doc: dict) -> list[dict]:
"""Architectural drift classes. Reuses authorities' verdicts; never
duplicates their logic. Deterministic output."""
def _gather_drift_inputs() -> dict:
"""Every I/O read drift() needs (zero-dep split law — same gathered dict
feeds the Rust core and the Python fallback)."""
try:
src_version = (ROOT / "pyproject.toml").read_text()
import re
m = re.search(r'^version\s*=\s*"([^"]+)"', src_version, re.M)
src_version = m.group(1) if m else ""
except OSError:
src_version = ""
return {
"registry": _canary_registry(),
"graph": {m: sorted(deps) for m, deps in _import_graph().items()},
"tested": sorted(_test_backed_modules()),
"src_stems": [p.stem for p in SRC_DIR.glob("*.py")],
"deployed_obs": _deployed_observation(),
"src_version": src_version,
}
def _py_drift_from_inputs(doc: dict, inputs: dict) -> list[dict]:
"""Architectural drift classes from GATHERED inputs. Python core; the
Rust core (drift.rs) must produce identical output on the same inputs."""
out = []
graph = _import_graph()
registry = _canary_registry()
tested = _test_backed_modules()
graph = inputs["graph"]
registry = inputs["registry"]
tested = set(inputs["tested"])
mods = {e["id"].split(":", 1)[1] for e in doc["entities"] if e["kind"] == "module"}
# 1. registered but absent (canary registry names a non-existent module)
@@ -349,11 +655,11 @@ def drift(doc: dict) -> list[dict]:
# 2. present but unowned (runtime source file outside every subsystem).
# Excludes canary files (governed by the canary contract, not umbrella
# membership) and package plumbing (__init__/__main__).
for p in SRC_DIR.glob("*.py"):
if p.stem.startswith(("canary_", "__")):
for stem in inputs["src_stems"]:
if stem.startswith(("canary_", "__")):
continue
if p.stem not in mods:
out.append({"class": "present_but_unowned", "entity": f"module:{p.stem}",
if stem not in mods:
out.append({"class": "present_but_unowned", "entity": f"module:{stem}",
"detail": "src/rune runtime module with no subsystem membership"})
# 3. registered but no static importers — OBSERVATION, not a death
@@ -368,14 +674,8 @@ def drift(doc: dict) -> list[dict]:
"detail": f"zero static importers; dynamic wiring possible; {dyn}"})
# 4. deployed identity drift (source version vs deployment authority)
obs = _deployed_observation()
try:
src_version = (ROOT / "pyproject.toml").read_text()
import re
m = re.search(r'^version\s*=\s*"([^"]+)"', src_version, re.M)
src_version = m.group(1) if m else ""
except OSError:
src_version = ""
obs = inputs["deployed_obs"]
src_version = inputs["src_version"]
if obs and src_version and obs["deployed_version"] != src_version:
out.append({"class": "version_surfaces_disagree",
"detail": f"source {src_version} != deployed {obs['deployed_version']}"})
@@ -405,6 +705,17 @@ def drift(doc: dict) -> list[dict]:
return out
def drift(doc: dict) -> list[dict]:
"""Architectural drift classes. Reuses authorities' verdicts; never
duplicates their logic. Deterministic output. H3-spine: gathers the
scans once, delegates to the Rust core when built (Python fallback)."""
inputs = _gather_drift_inputs()
core = _rust_core()
if core is not None:
return core.drift(doc, inputs)
return _py_drift_from_inputs(doc, inputs)
# ── snapshots / deltas ────────────────────────────────────────────────────
def snapshot(doc: dict, label: str = "") -> Path:
@@ -421,9 +732,9 @@ def snapshot(doc: dict, label: str = "") -> Path:
# ── compatibility projection (Phase C) ────────────────────────────────────
def export_umbrellas(doc: dict) -> dict:
"""Generate the legacy umbrella view FROM Spine. Output only — the old
consumers (CI gate, henxels) read this during migration. Never ingest."""
def _py_export_umbrellas(doc: dict) -> dict:
"""Python core of export_umbrellas (H3-spine: the Rust core in query.rs
must produce identical output modulo generated_at)."""
umbrellas: dict = {}
for e in doc["entities"]:
if e["kind"] != "subsystem":
@@ -464,6 +775,16 @@ def export_umbrellas(doc: dict) -> dict:
"umbrellas": dict(sorted(umbrellas.items()))}
def export_umbrellas(doc: dict) -> dict:
"""Generate the legacy umbrella view FROM Spine. Output only — the old
consumers (CI gate, henxels) read this during migration. Never ingest.
H3-spine: delegates to the Rust core when built (Python fallback)."""
core = _rust_core()
if core is not None:
return core.export_umbrellas(doc, now())
return _py_export_umbrellas(doc)
# ── contracts, task binding, impact frontier (T22 + graph addenda) ────────
#
# GRAPH SEPARATION: Spine is its own LOGICAL graph — current structural
@@ -503,7 +824,16 @@ FRONTIER_LEVEL2_TYPES = {"verified_by", "obligated_canary"}
def query(doc: dict, entity_id: str) -> dict:
"""INTRINSIC SELF-KNOWLEDGE: one deterministic call answering the
representative structural question set. No LLM, no Memory, no rediscovery."""
representative structural question set. No LLM, no Memory, no rediscovery.
H3-spine: delegates to the Rust core when built (Python fallback)."""
core = _rust_core()
if core is not None:
return core.query(doc, entity_id)
return _py_query(doc, entity_id)
def _py_query(doc: dict, entity_id: str) -> dict:
"""Python core of query (parity oracle for the Rust core)."""
e = entity(doc, entity_id)
if e is None:
return {"error": "not found", "id": entity_id}
+218
View File
@@ -0,0 +1,218 @@
"""spine_language.py — the #2465 doc-level transforms (carved from spine.py
to honor the giant-file henxel: keep it under 1200 lines).
Three transforms, applied to BOTH cores' output at build time:
1. language placement — every entity gets a language verdict (kind
defaults; subsystems derive from member-majority over the measured
classification map);
2. discovery-ID normalization — migrate legacy unqualified contract
discoveries to construction-surface kind without losing evidence;
3. contract-discovery write-back — closed contracts teach the spine
what construction actually built (observed → PRESENT, candidate →
DECLARED; the CANDIDATE law holds).
Same dependency laws as spine.py: structural_store primitives only, no
I/O beyond the classification/scaffold store reads, deterministic.
"""
from __future__ import annotations
import json
from pathlib import Path
from rune.structural_store import (
add_entity, add_relationship, entity, new_doc, relationships, _sorted, now,
)
ROOT = Path(__file__).resolve().parents[2]
SRC_DIR = ROOT / "src" / "rune"
def _classification_sides() -> dict:
"""The measured dichotomy map (W0): module stem → RUST|GO|TUI-LAST|...
Loaded once per _place_languages call, best-effort — a missing or
unreadable classification.json degrades to {}/unassessed verdicts,
never to invented facts."""
try:
data = json.loads((ROOT / "store" / "rebase" / "classification.json").read_text())
return {name: rec.get("side", "") for name, rec in data.items() if isinstance(rec, dict)}
except (OSError, json.JSONDecodeError, AttributeError):
return {}
def _place_languages(doc: dict) -> int:
"""Stamp a language verdict per entity (no-op for entities that carry
one). Deterministic: substring classification over the entity id + the
kind; the exact same rule construction_decision.language_placement
implements (kept inline here to avoid a per-entity FFI crossing for
the spine's own 218 entities; the FFI path is used in contracts)."""
GO_HINTS = ("server", "transport", "dispatch", "entry", "bridge",
"hook_engine", "provider", "watch", "notif", "voice",
"gitea", "event_", "message_", "tool_exec", "work_manager")
RUST_HINTS = ("memory", "drift", "warm", "capab", "token", "spine",
"scaffold", "construct", "structural", "curator",
"consolidat", "elastic", "cognit", "abstract", "contract",
"ledger", "audit", "claim", "evidence", "behavio",
"verif", "pins", "witness", "anchor", "growth", "goal",
"salience", "store", "tui_", "rune_tui")
placed = 0
CLASSIFICATION_SIDES = _classification_sides()
for e in doc.get("entities", []):
eid = e.get("id", "")
kind = e.get("kind", "")
# AUTHORITY ORDER (Nick's dichotomy, 2026-10-06 correction): the
# MEASURED classification map (import-evidence per module) decides
# FIRST. The name-hint lists are only a fallback for entities with
# no measurement — never allowed to override the evidence. A module
# with neither measurement nor hint is "unclassified", not a silent
# rust default.
measured = str(CLASSIFICATION_SIDES.get(
eid.split(":", 1)[1] if ":" in eid else eid, "") or "").upper()
if eid.startswith("contract:"):
lang = "n/a"
elif kind == "rust-crate":
lang = "rust"
elif kind == "go-surface":
lang = "go"
elif kind == "workflow" or kind == "config":
lang = "n/a"
elif kind == "deployment_surface":
lang = "go"
elif measured == "RUST":
lang = "rust"
elif measured == "GO":
lang = "go"
elif measured == "TUI-LAST":
lang = "python-tui (Wave 6)"
elif measured == "TEST-INFRA" or measured == "RETIRES":
lang = "test-infra" if measured == "TEST-INFRA" else "retires"
elif "tui" in eid:
lang = "python-tui (Wave 6)"
elif any(h in eid for h in GO_HINTS):
lang = "go (name hint)"
elif any(h in eid for h in RUST_HINTS):
lang = "rust (name hint)"
elif kind == "module":
lang = "unclassified"
elif kind == "subsystem":
# A subsystem is a CONTAINER — its language is its members'
# majority side per the measured dichotomy. The evidence map is
# classification.json (measured, 174 modules; honest note: newer
# files may be unclassified, so the count of classified members
# rides the verdict). Members arrive as belongs_to relationships
# whose from-ids carry module:<name> — map through classification.
members = [
r.get("from", "").split(":", 1)[1]
for r in doc.get("relationships", [])
if r.get("type") == "belongs_to" and r.get("to") == eid
and r.get("from", "").startswith("module:")
]
sides = [
str(CLASSIFICATION_SIDES.get(m) or "").upper()
for m in members
]
rust = sides.count("RUST")
go = sides.count("GO")
if rust and go:
lang = f"mixed (rust {rust} / go {go} members)"
elif rust:
lang = "rust"
elif go:
lang = "go"
else:
lang = "unassessed"
else:
lang = "unassessed"
e.setdefault("identities", {})["language"] = lang
if lang != "unassessed":
placed += 1
return placed
def _normalize_discovery_ids(doc: dict) -> int:
"""Migrate the old unqualified contract references, preserving evidence.
A file path (or 'suite') is not a module ID. Only migrate the malformed
module records produced by the earlier discovery projector; leave all
other open-world entity classes alone.
"""
renamed = {}
observed = {
eid for _, entities in _contract_discoveries()
for eid, _, provenance in entities if provenance == "observed"
}
for e in doc.get("entities", []):
if (e.get("kind") == "module" and ":" not in e.get("id", "")
and any(ref.startswith("contract-discovery:")
for ref in e.get("evidence_refs", []))):
old = e["id"]
e["id"] = f"construction-surface:{old}"
e["kind"] = "construction-surface"
e.setdefault("identities", {})["source_ref"] = old
renamed[old] = e["id"]
if e.get("kind") == "construction-surface" and e.get("id", "").startswith("construction-surface:"):
# Earlier code promoted every *candidate* to PRESENT. Undo only
# that unsupported claim; an actually observed surface retains it.
if e["id"].split(":", 1)[1] not in observed:
e["maturity"] = "DECLARED"
e["tags"] = [tag for tag in e.get("tags", []) if tag != "code"]
for rel in doc.get("relationships", []):
for endpoint in ("from", "to"):
if rel.get(endpoint) in renamed:
rel[endpoint] = renamed[rel[endpoint]]
return len(renamed)
def _apply_contract_discoveries(doc: dict) -> int:
"""Contract-discovery write-back (issue #2465 addition 2): entities
DISCOVERED by closed contracts become modeled spine entities — the
spine learns what construction actually built. Doc-level transform,
applied to BOTH cores' output. The CANDIDATE law holds: only CLOSED
contracts write back."""
placed = 0
for cid, entities in _contract_discoveries():
for eid, kind, provenance in entities:
before = len(doc.get("entities", []))
if kind == "construction-surface":
eid = f"construction-surface:{eid}"
add_entity(doc, id=eid, kind=kind,
tags=["contract-discovery"],
identities={"source_ref": eid.split(":", 1)[1]} if kind == "construction-surface" else None,
evidence_refs=[f"contract-discovery:{cid}"],
maturity="PRESENT" if provenance == "observed" else "DECLARED")
if len(doc.get("entities", [])) > before:
placed += 1
else:
placed += 0 # high-water merge — no new entity, still evidence
return placed
def _contract_discoveries():
"""Closed contracts' DISCOVERED entities (issue #2465 addition 2):
expected_new_observed + candidate corrections, gathered from the
scaffold store. Returns [(contract_id, [(entity_id, kind), ...])]."""
try:
doc = json.loads((ROOT / "store" / "scaffold" / "current.json").read_text())
except (OSError, json.JSONDecodeError):
return []
out = []
for e in doc.get("entities", []):
cid = e.get("id", "")
if not cid.startswith("contract:") or e.get("phase") != "closed":
continue
q = e.get("ext", {}).get("contract_quality", {})
found = []
# expected-new surfaces observed at close (REAL delivered entities)
for eid in q.get("expected_new_observed", []):
kind = eid.split(":", 1)[0] if ":" in eid else "construction-surface"
if kind in ("rust-crate", "go-surface", "config", "workflow", "module"):
found.append((eid, kind, "observed"))
elif kind == "construction-surface":
found.append((eid, kind, "observed"))
# candidate corrections: the closure flagged them for modeling
for cand in q.get("candidate_corrections", []):
eid = cand.get("entity", "")
kind = eid.split(":", 1)[0] if ":" in eid else "construction-surface"
if eid and kind in ("rust-crate", "go-surface", "config", "workflow", "module", "construction-surface"):
found.append((eid, kind, "candidate"))
if found:
out.append((cid[9:], found))
return out
+105
View File
@@ -0,0 +1,105 @@
#!/usr/bin/env python3
"""ctypes loader for the Rust rune_spine core (H3-spine strangler, T4.8).
Contract BUILDER-2026-10-04T07:19:36Z-cli. Gives spine.py the Rust projection
machinery through the cdylib with no subprocess. I/O (filesystem scans, JSON
file reads) stays in spine.py per the zero-dep split law; this loader receives
fully-gathered input dicts and returns pure results.
FFI contract (doc-envelope law — every call returns {"ok", "value"|"err"}):
* sp_project(inputs_json) -> doc
* sp_drift({doc, inputs}) -> drift list
* sp_query({doc, id}) -> query result
* sp_export({doc, now}) -> umbrella compatibility view
* sp_free_string(ptr)
FFI segfault law: every symbol gets explicit argtypes + restype (c_void_p for
returned char* — the default c_int truncates 64-bit pointers, exit-139 class).
Zero dependencies. The .so resolves against this repo's crate target dir;
override with SP_RUST_LIB (a FILE path — binaries resolve next to it).
"""
from __future__ import annotations
import ctypes
import json
import os
from pathlib import Path
_REPO = Path(__file__).resolve().parents[2]
_CRATE_DIR = _REPO / "src" / "rust" / "rune_spine"
_LIB_PATH = Path(
os.environ.get(
"SP_RUST_LIB", _CRATE_DIR / "target" / "release" / "librune_spine.so"
)
)
_lib = None
class SpineCoreUnavailable(FileNotFoundError):
"""The Rust core is not built (or SP_RUST_LIB points nowhere). Callers
fall back to the Python implementation — strangler law: old code is
deleted only when the new one is proven, so the fallback is load-bearing."""
def available() -> bool:
return _LIB_PATH.is_file()
def _load() -> ctypes.CDLL:
global _lib
if _lib is not None:
return _lib
if not _LIB_PATH.exists():
raise SpineCoreUnavailable(
f"Rust spine core not built: {_LIB_PATH} missing — run "
f"`cargo build --release` in {_CRATE_DIR}"
)
lib = ctypes.CDLL(str(_LIB_PATH))
lib.sp_free_string.argtypes = [ctypes.c_void_p]
lib.sp_free_string.restype = None
for sym in ("sp_project", "sp_drift", "sp_query", "sp_export"):
fn = getattr(lib, sym)
fn.argtypes = [ctypes.c_char_p]
fn.restype = ctypes.c_void_p # char* — never default c_int
_lib = lib
return lib
def _call(sym: str, payload: dict):
lib = _load()
fn = getattr(lib, sym)
raw = json.dumps(payload).encode("utf-8")
ptr = fn(raw)
if not ptr:
raise RuntimeError(f"{sym} returned a null pointer")
try:
text = ctypes.cast(ptr, ctypes.c_char_p).value.decode("utf-8")
finally:
lib.sp_free_string(ptr)
env = json.loads(text)
if not env.get("ok"):
raise RuntimeError(f"{sym} core error: {env.get('err')}")
return env["value"]
def project(inputs: dict) -> dict:
"""Full spine projection from gathered inputs (spine.build's core)."""
return _call("sp_project", inputs)
def drift(doc: dict, inputs: dict) -> list:
"""Drift classification from a doc + gathered drift inputs."""
return _call("sp_drift", {"doc": doc, "inputs": inputs})
def query(doc: dict, entity_id: str) -> dict:
"""Intrinsic self-knowledge query against a doc."""
return _call("sp_query", {"doc": doc, "id": entity_id})
def export_umbrellas(doc: dict, now: str) -> dict:
"""Legacy umbrella compatibility view (output-only)."""
return _call("sp_export", {"doc": doc, "now": now})
+88
View File
@@ -0,0 +1,88 @@
#!/usr/bin/env python3
"""ctypes loader for the Rust token_counting core (H3 slice 5, T4.4).
Contract BUILDER-2026-10-04T09:57:23Z-cli. Gives token_optimizer the Rust
counting/compression helpers through the cdylib with no subprocess.
FFI contract (doc-envelope law — every call returns {"ok", "value"|"err"}):
* tc_estimate_tokens({text}) -> int
* tc_compress_context({text}) -> string
* tc_free_string(ptr)
FFI segfault law: every symbol gets explicit argtypes + restype (c_void_p
for returned char*). Zero dependencies; override the .so with TC_RUST_LIB
(a FILE path).
"""
from __future__ import annotations
import ctypes
import json
import os
from pathlib import Path
_REPO = Path(__file__).resolve().parents[2]
_CRATE_DIR = _REPO / "src" / "rust" / "token_counting"
_LIB_PATH = Path(
os.environ.get(
"TC_RUST_LIB", _CRATE_DIR / "target" / "release" / "libtoken_counting.so"
)
)
_lib = None
class TokenCountingUnavailable(FileNotFoundError):
"""The Rust core is not built (or TC_RUST_LIB points nowhere). Callers
fall back to the Python implementation — strangler law."""
def available() -> bool:
return _LIB_PATH.is_file()
def _load() -> ctypes.CDLL:
global _lib
if _lib is not None:
return _lib
if not _LIB_PATH.exists():
raise TokenCountingUnavailable(
f"Rust token_counting core not built: {_LIB_PATH} missing — run "
f"`cargo build --release` in {_CRATE_DIR}"
)
lib = ctypes.CDLL(str(_LIB_PATH))
lib.tc_free_string.argtypes = [ctypes.c_void_p]
lib.tc_free_string.restype = None
for sym in ("tc_estimate_tokens", "tc_compress_context"):
fn = getattr(lib, sym)
fn.argtypes = [ctypes.c_char_p]
fn.restype = ctypes.c_void_p # char* — never default c_int
_lib = lib
return lib
def _call(sym: str, payload: dict):
lib = _load()
fn = getattr(lib, sym)
raw = json.dumps(payload).encode("utf-8")
ptr = fn(raw)
if not ptr:
raise RuntimeError(f"{sym} returned a null pointer")
try:
text = ctypes.cast(ptr, ctypes.c_char_p).value.decode("utf-8")
finally:
lib.tc_free_string(ptr)
env = json.loads(text)
if not env.get("ok"):
raise RuntimeError(f"{sym} core error: {env.get('err')}")
return env["value"]
def estimate_tokens(text: str) -> int:
"""1 token ≈ 4 chars (code points), floor division."""
return int(_call("tc_estimate_tokens", {"text": text}))
def compress_context(text: str) -> str:
"""Newline/space run collapse + prefix shortening + strip."""
return _call("tc_compress_context", {"text": text})
+82 -2
View File
@@ -20,6 +20,33 @@ HOT_THRESHOLD = 7 # accessed within 7 days = hot
WARM_THRESHOLD = 30 # accessed within 30 days = warm
# > 30 days = cold
def _core():
"""The Rust cognition_core when available (strangler: Python fallback is
load-bearing). RUNE_COGNITION_CORE=python pins the fallback."""
return None if cc_loader_pin() else _cc_probe()
def _cc_probe():
from rune import cc_loader as _cl
try:
return _cl if _cl.available() else None
except Exception: # noqa: BLE001, S110 — loader probe never crashes the fallback
return None
def cc_loader_pin() -> bool:
"""The env pin: RUNE_COGNITION_CORE=python → kernels OFF (shared probe).
"""
import os
return os.environ.get("RUNE_COGNITION_CORE", "auto") == "python"
def _py_core_now() -> str:
"""The injected clock (iso Z) — module-level for the kernel boundary."""
return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
# Tier storage characteristics
TIER_CONFIG = {
"hot": {
@@ -56,7 +83,24 @@ class TierManager:
"""Determine the tier for an entry based on its access patterns.
Returns 'hot', 'warm', or 'cold'.
Delegates to the Rust kernel when built; the Python body below is
the parity oracle (W3.4: identical thresholds injected, naive
timestamps refuse per the PARITY TRAP law).
"""
core = _core()
if core is not None:
try:
from rune import cc_loader as _cl
raw = _cl.tier_get(entry, _py_core_now(),
float(HOT_THRESHOLD), float(WARM_THRESHOLD))
return str(raw)
except Exception: # noqa: BLE001, S110 — kernel failure → oracle, never a broken call
pass
return self._py_get_tier(entry)
def _py_get_tier(self, entry: dict) -> str:
"""Python parity oracle (W3.4)."""
wash_count = entry.get("wash_count", 0) or 0
last_accessed = entry.get("last_accessed")
rock_status = entry.get("rock_status", "washable")
@@ -88,7 +132,26 @@ class TierManager:
return "warm"
def promote(self, entry: dict) -> str:
"""Promote an entry to a higher tier. Returns new tier."""
"""Promote an entry to a higher tier. Returns new tier.
Delegates to the Rust kernel when built (Python oracle below)."""
core = _core()
if core is not None:
try:
from rune import cc_loader as _cl
# The kernel's promote/demote take the FULL ENTRY (plus
# thresholds + clock) — not a tier string. They recompute
# the current tier internally, so a tier-string call was
# silently landing on defaults (the live probe caught it).
raw = _cl.tier_promote(entry, _py_core_now(),
float(HOT_THRESHOLD), float(WARM_THRESHOLD))
return str(raw)
except Exception: # noqa: BLE001, S110 — kernel failure → oracle fallback, intentional silence
pass
return self._py_promote(entry)
def _py_promote(self, entry: dict) -> str:
"""Python parity oracle."""
current = self.get_tier(entry)
if current == "cold":
return "warm"
@@ -97,7 +160,24 @@ class TierManager:
return "hot" # already hot
def demote(self, entry: dict) -> str:
"""Demote an entry to a lower tier. Returns new tier."""
"""Demote an entry to a lower tier. Returns new tier.
Delegates to the Rust kernel when built (Python oracle below)."""
core = _core()
if core is not None:
try:
from rune import cc_loader as _cl
# Same full-entry contract as promote; the kernel handles
# rock_status internally (rock never demotes).
raw = _cl.tier_demote(entry, _py_core_now(),
float(HOT_THRESHOLD), float(WARM_THRESHOLD))
return str(raw)
except Exception: # noqa: BLE001, S110 — kernel failure → oracle fallback, intentional silence
pass
return self._py_demote(entry)
def _py_demote(self, entry: dict) -> str:
"""Python parity oracle."""
current = self.get_tier(entry)
rock_status = entry.get("rock_status", "washable")
+58 -13
View File
@@ -44,8 +44,13 @@ class CacheEntry:
hit_count: int = 0
def to_dict(self):
# `content` is required by CacheEntry; omitting it here made the
# persisted JSON unloadable (CacheEntry(**entry_data) raised on
# _load), which the blanket except in _load turned into silent
# total cache loss on every restart. See issue #770.
return {
"key": self.key,
"content": self.content,
"tokens": self.tokens,
"last_used": self.last_used,
"use_count": self.use_count,
@@ -116,8 +121,13 @@ class TokenOptimizer:
self._save()
def estimate_tokens(self, text: str) -> int:
"""Estimate token count (rough: 1 token ≈ 4 chars)."""
return len(text) // 4
"""Estimate token count (rough: 1 token ≈ 4 chars).
H3 slice 5: delegates to the Rust core when built (Python fallback)."""
core = _tc_core()
if core is not None:
return core.estimate_tokens(text)
return _py_estimate_tokens(text)
def _load_persona_definition(self) -> str:
"""Load persona definition from persona_definition module, with fallback."""
@@ -195,19 +205,14 @@ class TokenOptimizer:
return compressed
def _compress_context(self, context: str) -> str:
"""Compress context to reduce tokens."""
# Remove multiple newlines
compressed = re.sub(r"\n{3,}", "\n\n", context)
"""Compress context to reduce tokens.
# Remove redundant spaces
compressed = re.sub(r" {2,}", " ", compressed)
H3 slice 5: delegates to the Rust core when built (Python fallback)."""
core = _tc_core()
if core is not None:
return core.compress_context(context)
return _py_compress_context(context)
# Shorten common patterns
compressed = compressed.replace("User:", "U:")
compressed = compressed.replace("Assistant:", "A:")
compressed = compressed.replace("System:", "S:")
return compressed.strip()
def optimize_prompt(self, text: str, context: str = "") -> Dict[str, Any]:
"""Optimize a prompt by caching and compressing."""
@@ -295,6 +300,46 @@ def get_optimizer() -> TokenOptimizer:
return _optimizer
# ── pure helpers (H3 slice 5: Rust core when built; _py_ variants are the
# load-bearing fallback AND the parity oracle).
def _tc_core():
"""The Rust token_counting core when available (strangler law).
RUNE_TOKEN_CORE=python pins the fallback."""
if os.environ.get("RUNE_TOKEN_CORE", "auto") == "python":
return None
try:
from rune import tc_loader
except Exception: # noqa: BLE001 — any loader failure means fallback, never a broken call
return None
try:
return tc_loader if tc_loader.available() else None
except Exception: # noqa: BLE001 — availability probe must never crash the fallback
return None
def _py_estimate_tokens(text: str) -> int:
"""Estimate token count (rough: 1 token ≈ 4 chars) — parity oracle."""
return len(text) // 4
def _py_compress_context(context: str) -> str:
"""Compress context to reduce tokens — parity oracle."""
# Remove multiple newlines
compressed = re.sub(r"\n{3,}", "\n\n", context)
# Remove redundant spaces
compressed = re.sub(r" {2,}", " ", compressed)
# Shorten common patterns
compressed = compressed.replace("User:", "U:")
compressed = compressed.replace("Assistant:", "A:")
compressed = compressed.replace("System:", "S:")
return compressed.strip()
def optimize_prompt(text: str, context: str = "") -> Dict[str, Any]:
"""Optimize a prompt."""
return get_optimizer().optimize_prompt(text, context)
+108
View File
@@ -0,0 +1,108 @@
#!/usr/bin/env python3
"""tui_transport.py — the TUI transport seam (H4 slice 1, T4.5).
Contract BUILDER-2026-10-04T10:32:00Z-cli. The TUI's operator surface is
exactly four public calls; this seam lets them run IN-PROCESS (today) or
over the /v1 wire (the migration path) behind one interface. Selected by
RUNE_TUI_TRANSPORT=inprocess|v1 (DEFAULT inprocess — zero behavior change
on merge; the wave opts in surface by surface).
The V1Transport speaks to the Go front door (or the core sidecar directly):
get_status -> GET /v1/status
send_task -> POST /v1/chat/completions (rune block out)
get_conversation_window -> GET /v1/conversation?channel=&n=
resolve_pending_approval -> POST /v1/approvals/resolve {approved}
Stdlib only. Dead core raises — never a silent empty answer (canary §5).
"""
from __future__ import annotations
import json
import os
import urllib.error
import urllib.request
from typing import Any, List
DEFAULT_CORE_URL = "http://127.0.0.1:8377" # the Go front door's default port
class InProcessTransport:
"""Today's behavior: direct operator calls in-process."""
def __init__(self, operator: Any = None):
if operator is not None:
self._operator = operator
else:
import rune_operator
self._operator = rune_operator.get_operator()
def get_status(self) -> dict:
return self._operator.get_status()
def send_task(self, description: str, model: str | None = None,
channel: str = "operator") -> dict:
return self._operator.send_task(description, model, channel)
def get_conversation_window(self, channel: str = "operator",
n: int = 8) -> List[dict]:
return self._operator.conversation_window(channel, n=n)
def resolve_pending_approval(self, approved: bool) -> dict:
return self._operator.resolve_pending_approval(approved)
class V1Transport:
"""The migration path: the same four calls over the /v1 wire."""
def __init__(self, base_url: str | None = None, timeout: float = 130.0):
self._base = (base_url or os.environ.get(
"RUNE_TUI_CORE_URL", DEFAULT_CORE_URL)).rstrip("/")
self._timeout = timeout
def _http(self, method: str, path: str, body: dict | None = None) -> Any:
data = json.dumps(body).encode() if body is not None else None
req = urllib.request.Request(
self._base + path, data=data, method=method,
headers={"Content-Type": "application/json"})
try:
with urllib.request.urlopen(req, timeout=self._timeout) as resp:
return json.loads(resp.read().decode())
except urllib.error.HTTPError as e:
detail = e.read().decode()[:200]
raise RuntimeError(
f"v1 transport: {method} {path} -> {e.code}: {detail}") from e
def get_status(self) -> dict:
return self._http("GET", "/v1/status")
def send_task(self, description: str, model: str | None = None,
channel: str = "operator") -> dict:
body = {"messages": [{"role": "user", "content": description}]}
if model is not None:
body["model"] = model
out = self._http("POST", "/v1/chat/completions", body)
# the full operator result rides the rune block (H2 contract)
return out["rune"]
def get_conversation_window(self, channel: str = "operator",
n: int = 8) -> List[dict]:
out = self._http("GET", f"/v1/conversation?channel={channel}&n={n}")
return out["messages"]
def resolve_pending_approval(self, approved: bool) -> dict:
return self._http("POST", "/v1/approvals/resolve",
{"approved": bool(approved)})
def get_transport(mode: str | None = None):
"""The seam entry point. RUNE_TUI_TRANSPORT=inprocess|v1 (default
inprocess); the wire target is RUNE_TUI_CORE_URL (default the Go front
door's port)."""
mode = mode or os.environ.get("RUNE_TUI_TRANSPORT", "inprocess")
if mode == "v1":
return V1Transport()
if mode == "inprocess":
return InProcessTransport()
raise ValueError(f"unknown RUNE_TUI_TRANSPORT mode: {mode!r} "
"(expected inprocess|v1)")
+45 -115
View File
@@ -7,6 +7,15 @@ file_changed, tool_receipt, constraint_check.
Usage:
verification_layer.py verify <result_json> <verifier_type>
verification_layer.py test
Phase G strangler slice 2 (port 2): the verification semantics (all seven
verifiers + unknown-type dispatch) are served IN-PROCESS by the Rust
`evidence_provenance` cdylib through ep_loader.py — parity 33/33 proven vs
this module's Python vintage (byte-equal to_dict rendering incl. Python
str() bool rendering True/False in evidence_refs). The VerificationResult
dataclass stays Python (consumers construct it directly — rune_operator:323);
the verify_* functions delegate to the Rust core and reconstruct. CLI, test,
handle_hook and PLUGIN shapes unchanged (git history holds the original).
"""
import json
@@ -15,9 +24,11 @@ import sys
from dataclasses import dataclass, field
from typing import List, Optional
_SD = os.path.dirname(os.path.abspath(__file__))
if _SD not in sys.path:
sys.path.insert(0, _SD)
# ── Rust core wiring (slice-2 port-2 shim pattern) ──────────────────────────
_CRATE_DIR = "/mnt/workdrive/rune/src/rust/evidence_provenance"
if _CRATE_DIR not in sys.path:
sys.path.insert(0, _CRATE_DIR)
import ep_loader # noqa: E402 (loads target/release cdylib)
@dataclass
@@ -42,147 +53,66 @@ class VerificationResult:
}
def _rust_verify(result: dict, verifier_type: str) -> VerificationResult:
"""Delegate to the Rust core; reconstruct the dataclass from its dict.
The Rust ep_verify returns the to_dict() shape with identical key
semantics (parity-proven) — including the unknown-verifier path.
"""
out = json.loads(ep_loader.verify(json.dumps(result), verifier_type))
return VerificationResult(
passed=out["passed"],
verifier_type=out["verifier_type"],
evidence_refs=out["evidence_refs"],
failures=out["failures"],
retryable=out["retryable"],
escalation_reason=out["escalation_reason"],
)
def verify_exit_code(result: dict) -> VerificationResult:
"""Verify using exit code."""
exit_code = result.get("exit_code", 0)
passed = exit_code == 0
return VerificationResult(
passed=passed,
verifier_type="exit_code",
evidence_refs=[f"exit_code={exit_code}"],
failures=[] if passed else [f"Exit code {exit_code}"],
retryable=not passed,
escalation_reason=None if passed else "exit_code_nonzero",
)
return _rust_verify(result, "exit_code")
def verify_unit_test(result: dict) -> VerificationResult:
"""Verify using unit tests."""
test_output = result.get("test_output", "")
tests_passed = result.get("tests_passed", 0)
tests_failed = result.get("tests_failed", 0)
tests_total = result.get("tests_total", 0)
passed = tests_failed == 0 and tests_total > 0
return VerificationResult(
passed=passed,
verifier_type="unit_test",
evidence_refs=[f"tests_passed={tests_passed}", f"tests_failed={tests_failed}"],
failures=[] if passed else [f"{tests_failed} tests failed"],
retryable=not passed,
escalation_reason=None if passed else "test_failures",
)
return _rust_verify(result, "unit_test")
def verify_schema(result: dict) -> VerificationResult:
"""Verify using JSON schema."""
schema_valid = result.get("schema_valid", True)
schema_errors = result.get("schema_errors", [])
passed = schema_valid and len(schema_errors) == 0
return VerificationResult(
passed=passed,
verifier_type="schema",
evidence_refs=[f"schema_valid={schema_valid}"],
failures=schema_errors,
retryable=not passed,
escalation_reason=None if passed else "schema_invalid",
)
return _rust_verify(result, "schema")
def verify_file_exists(result: dict) -> VerificationResult:
"""Verify using file existence."""
file_path = result.get("file_path", "")
exists = os.path.exists(file_path) if file_path else False
return VerificationResult(
passed=exists,
verifier_type="file_exists",
evidence_refs=[f"file_path={file_path}"],
failures=[] if exists else [f"File not found: {file_path}"],
retryable=not exists,
escalation_reason=None if exists else "file_not_found",
)
return _rust_verify(result, "file_exists")
def verify_file_changed(result: dict) -> VerificationResult:
"""Verify using file changes."""
file_path = result.get("file_path", "")
expected_hash = result.get("expected_hash", "")
actual_hash = result.get("actual_hash", "")
passed = expected_hash == actual_hash if expected_hash else True
return VerificationResult(
passed=passed,
verifier_type="file_changed",
evidence_refs=[f"file_path={file_path}", f"hash_match={passed}"],
failures=[] if passed else ["File hash mismatch"],
retryable=not passed,
escalation_reason=None if passed else "hash_mismatch",
)
return _rust_verify(result, "file_changed")
def verify_tool_receipt(result: dict) -> VerificationResult:
"""Verify using tool receipt."""
tool_success = result.get("tool_success", True)
tool_error = result.get("tool_error", "")
passed = tool_success
return VerificationResult(
passed=passed,
verifier_type="tool_receipt",
evidence_refs=[f"tool_success={tool_success}"],
failures=[] if passed else [tool_error],
retryable=not passed,
escalation_reason=None if passed else "tool_error",
)
return _rust_verify(result, "tool_receipt")
def verify_constraint_check(result: dict) -> VerificationResult:
"""Verify using constraint check."""
constraints_met = result.get("constraints_met", True)
constraint_violations = result.get("constraint_violations", [])
passed = constraints_met and len(constraint_violations) == 0
return VerificationResult(
passed=passed,
verifier_type="constraint_check",
evidence_refs=[f"constraints_met={constraints_met}"],
failures=constraint_violations,
retryable=not passed,
escalation_reason=None if passed else "constraint_violation",
)
return _rust_verify(result, "constraint_check")
def verify(result: dict, verifier_type: str) -> VerificationResult:
"""Verify a result using the specified verifier."""
verifiers = {
"exit_code": verify_exit_code,
"unit_test": verify_unit_test,
"schema": verify_schema,
"file_exists": verify_file_exists,
"file_changed": verify_file_changed,
"tool_receipt": verify_tool_receipt,
"constraint_check": verify_constraint_check,
}
"""Verify a result using the specified verifier.
verifier = verifiers.get(verifier_type)
if not verifier:
return VerificationResult(
passed=False,
verifier_type=verifier_type,
failures=[f"Unknown verifier: {verifier_type}"],
retryable=False,
escalation_reason="unknown_verifier",
)
return verifier(result)
Dispatch is served by the Rust core (unknown verifier → the same
passed=False / retryable=False / escalation_reason=unknown_verifier
shape, parity-proven).
"""
return _rust_verify(result, verifier_type)
def test_verification():
@@ -256,4 +186,4 @@ PLUGIN = {
"provides": ["verification_layer"],
"priority": 45,
"auto_activate": True,
}
}
+146 -84
View File
@@ -139,6 +139,50 @@ def record_neuron_fire(entry_id, score=0.0, session_id="", outcome="unknown"):
logger.debug("record_neuron_fire failed", exc_info=True) # Non-critical — don't break firing
def _py_base_level_from_uses(used_at_list, now_iso):
"""ACT-R math over gathered timestamps (H3 slice 3 parity oracle).
`now_iso` is an ISO-8601 string (no-clock law). Per-row parse failures
(ValueError/TypeError) are skipped like the original; a non-string row
raises AttributeError — the original's outer catch turns that into -2.0
for the whole call (see base_level_from_uses)."""
import math
if not used_at_list:
return -2.0 # Cold start — never fired
now = datetime.fromisoformat(now_iso)
total = 0.0
for used_at_str in used_at_list:
try:
used_at = datetime.fromisoformat(used_at_str.replace("Z", "+00:00"))
t_hours = max(0.1, (now - used_at).total_seconds() / 3600)
total += t_hours ** (-BASE_LEVEL_DECAY)
except (ValueError, TypeError):
continue
if total <= 0:
return -2.0
b_i = math.log(total)
# Clamp to prevent extreme values
return max(-2.0, min(b_i, 2.0))
def base_level_from_uses(used_at_list, now_iso):
"""Delegated ACT-R math (Rust core when built) with the original's
outer exception contract: ANY unexpected failure degrades to -2.0."""
from rune.drift_core import _ms_core
try:
core = _ms_core()
if core is not None:
return core.base_level_from_uses(used_at_list, now_iso)
return _py_base_level_from_uses(used_at_list, now_iso)
except Exception:
logger.debug("base_level_from_uses failed", exc_info=True)
return -2.0 # Graceful fallback (the original outer catch)
def base_level_activation(entry_id, db=None):
"""ACT-R base-level learning: B_i = ln(Σ t_k^{-d}).
@@ -146,10 +190,11 @@ def base_level_activation(entry_id, db=None):
Power-law decay matches empirical human memory data.
Returns a score that can be added to the neuron's total activation.
`db` is an optional pre-opened read-only connection (audit fix: reuse).
H3 slice 3: the SQLite scan stays here (zero-dep split law); the decay
math delegates to the Rust core when built.
"""
import sqlite3
import math
from datetime import datetime, timezone
_own_db = db is None
try:
@@ -164,22 +209,8 @@ def base_level_activation(entry_id, db=None):
if not rows:
return -2.0 # Cold start — never fired
now = datetime.now(timezone.utc)
total = 0.0
for (used_at_str,) in rows:
try:
used_at = datetime.fromisoformat(used_at_str.replace("Z", "+00:00"))
t_hours = max(0.1, (now - used_at).total_seconds() / 3600)
total += t_hours ** (-BASE_LEVEL_DECAY)
except (ValueError, TypeError):
continue
if total <= 0:
return -2.0
b_i = math.log(total)
# Clamp to prevent extreme values
return max(-2.0, min(b_i, 2.0))
now_iso = datetime.now(timezone.utc).isoformat()
return base_level_from_uses([r[0] for r in rows], now_iso)
except Exception:
logger.debug("base_level_activation failed", exc_info=True)
@@ -358,53 +389,18 @@ def write_neuron(slug, triggers, body, importance=0.5):
# ---------------------------------------------------------------- routing ----
def score_neuron(neuron, low_text, session_context=None, query_vec=None):
"""Score how strongly an incoming text fires this neuron.
Multi-signal scoring (not just keyword matching):
1. Keyword match — trigger words in the input (current behavior)
2. Semantic match — embedding similarity between input and neuron content
3. Context match — relevance to conversation history
4. Recency — recently-fired neurons get a boost (active priming)
5. Importance — neuron's base importance weight
The keyword signal is fast and deterministic.
The semantic signal catches meaning even without keyword overlap.
The context signal provides continuity across turns.
`query_vec` is the pre-computed embedding for low_text (audit fix: compute
once, reuse for all neurons instead of N HTTP calls).
"""
# Signal 1: Keyword match (existing behavior, fast)
def _py_score_gate(neuron, low_text, session_context, semantic_score):
"""Pure gate (H3 slice 3 parity oracle): returns
(fires, total, keyword_hits) — the pre-bonus combination decision.
Signals 1 (keyword) and 3 (context) are pure; signal 2 (semantic)
arrives as an argument because embeddings are I/O."""
keyword_hits = 0
for t in neuron["triggers"]:
if t and t in low_text:
keyword_hits += 1
# Signal 2: Semantic similarity (embedding-based, catches meaning)
semantic_score = 0.0
try:
body = neuron.get("body", "") or neuron.get("text", "")
if body and len(body) > 20:
# Use pre-computed query embedding (audit fix: was computing per neuron)
import memory_store as ms
if query_vec is None:
query_vec = ms._embed([low_text]).get(low_text)
body_vec = ms._embed([body]).get(body)
if query_vec and body_vec:
# Cosine similarity
dot = sum(a * b for a, b in zip(query_vec, body_vec))
na = sum(a * a for a in query_vec) ** 0.5
nb = sum(b * b for b in body_vec) ** 0.5
if na and nb:
semantic_score = dot / (na * nb)
except Exception:
logger.debug("score_neuron failed", exc_info=True) # Semantic scoring is optional — keyword fallback works
# Signal 3: Context match (if session context provided)
context_score = 0.0
if session_context:
# Check if neuron's triggers appear in recent context
context_low = session_context.lower()
for t in neuron["triggers"]:
if t and t in context_low:
@@ -414,34 +410,30 @@ def score_neuron(neuron, low_text, session_context=None, query_vec=None):
# Keywords are the primary gate — high precision for curated triggers.
# Semantic is a secondary boost — catches meaning without keywords.
# Context provides continuity across turns.
# This is better than simple weighted sum because it preserves keyword
# precision while allowing semantic to catch what keywords miss.
if keyword_hits > 0:
# Keywords matched — primary gate open
keyword_component = keyword_hits * 1.0
# Semantic boost (only if keywords already matched)
semantic_boost = max(0, semantic_score - 0.3) * 0.5 if semantic_score > 0.3 else 0.0
context_component = min(context_score, 1.0)
total = keyword_component + semantic_boost + context_component
return (True, total, keyword_hits)
elif semantic_score > 0.55:
# No keywords, but strong semantic match — secondary gate
# Higher threshold (0.55 vs 0.3) prevents false positives
total = semantic_score * 0.7
else:
# No signal — don't fire
return 0.0
return (True, semantic_score * 0.7, keyword_hits)
return (False, 0.0, keyword_hits)
def _py_score_finish(neuron, low_text, total, b_i, cache_priority, keyword_hits):
"""Pure finish (H3 slice 3 parity oracle): bonuses, penalties, and the
protected boost atop the gate's total. b_i / cache_priority arrive as
arguments (None skips that contribution) because SQLite and the cache
file are I/O — the wrapper only gathers them on the fired path."""
# Add importance and length bonus
score = total + neuron["importance"]
# Signal 4: Base-level learning (ACT-R B_i)
# Frequently-used neurons become permanently easier to retrieve.
# Power-law decay: recent uses matter more, but frequency counts.
# Signal 4: Base-level learning contribution (cap at 0.3)
entry_id = neuron.get("id")
if entry_id:
b_i = base_level_activation(entry_id)
# Normalize B_i to a small contribution (cap at 0.3 of total score)
# This prevents rich-get-richer while still rewarding useful neurons
if entry_id and b_i is not None:
b_contribution = max(0, min(b_i * 0.1, 0.3))
score += b_contribution
@@ -454,17 +446,8 @@ def score_neuron(neuron, low_text, session_context=None, query_vec=None):
score += wash_boost
# Signal 6: Cache priority (drift-with-integrity)
try:
import drift_cache
cache = drift_cache.DriftCache()
if entry_id and cache.contains(entry_id):
cache_priority = cache.get_priority(entry_id)
cache_boost = min(0.2, cache_priority * 0.1)
score += cache_boost
cache.access(entry_id)
except Exception:
logger.debug("score_neuron failed", exc_info=True)
if entry_id and cache_priority is not None:
score += min(0.2, cache_priority * 0.1)
# Signal 7: Body-knowledge relevance (Souveraine enhancement)
body_knowledge = neuron.get("body_knowledge", "")
@@ -501,6 +484,85 @@ def score_neuron(neuron, low_text, session_context=None, query_vec=None):
return score
def score_neuron(neuron, low_text, session_context=None, query_vec=None):
"""Score how strongly an incoming text fires this neuron.
Multi-signal scoring (not just keyword matching):
1. Keyword match — trigger words in the input (current behavior)
2. Semantic match — embedding similarity between input and neuron content
3. Context match — relevance to conversation history
4. Recency — recently-fired neurons get a boost (active priming)
5. Importance — neuron's base importance weight
The keyword signal is fast and deterministic.
The semantic signal catches meaning even without keyword overlap.
The context signal provides continuity across turns.
`query_vec` is the pre-computed embedding for low_text (audit fix: compute
once, reuse for all neurons instead of N HTTP calls).
H3 slice 3: I/O signals are gathered HERE (embeddings, SQLite
base-level, cache file — the original's exact reach order), then the
combination delegates to the Rust core when built (_py_ variants are
the load-bearing fallback AND the parity oracle).
"""
from rune.drift_core import _ms_core
# Signal 2 gather: semantic similarity (embedding-based, catches meaning)
semantic_score = 0.0
try:
body = neuron.get("body", "") or neuron.get("text", "")
if body and len(body) > 20:
# Use pre-computed query embedding (audit fix: was computing per neuron)
import memory_store as ms
if query_vec is None:
query_vec = ms._embed([low_text]).get(low_text)
body_vec = ms._embed([body]).get(body)
if query_vec and body_vec:
# Cosine similarity
dot = sum(a * b for a, b in zip(query_vec, body_vec))
na = sum(a * a for a in query_vec) ** 0.5
nb = sum(b * b for b in body_vec) ** 0.5
if na and nb:
semantic_score = dot / (na * nb)
except Exception:
logger.debug("score_neuron failed", exc_info=True) # Semantic scoring is optional — keyword fallback works
core = _ms_core()
if core is not None:
fires, total, keyword_hits = core.score_gate(
neuron, low_text, session_context, semantic_score)
else:
fires, total, keyword_hits = _py_score_gate(
neuron, low_text, session_context, semantic_score)
if not fires:
# No signal — don't fire (and, faithful to the original, gather NO
# base-level or cache I/O on this path)
return 0.0
# Signal 4 gather: Base-level learning (ACT-R B_i) — fired path only
entry_id = neuron.get("id")
b_i = base_level_activation(entry_id) if entry_id else None
# Signal 6 gather: cache priority + access reinforcement — fired path only
cache_priority = None
try:
import drift_cache
cache = drift_cache.DriftCache()
if entry_id and cache.contains(entry_id):
cache_priority = cache.get_priority(entry_id)
cache.access(entry_id)
except Exception:
logger.debug("score_neuron failed", exc_info=True)
if core is not None:
return core.score_finish(
neuron, low_text, total, b_i, cache_priority, keyword_hits)
return _py_score_finish(
neuron, low_text, total, b_i, cache_priority, keyword_hits)
def route(text, max_tokens=DEFAULT_MAX_TOKENS, max_blocks=None, session_id=""):
"""Return the neurons that fire for `text`, constrained by a TOKEN budget.
+127 -112
View File
@@ -84,76 +84,129 @@ def is_correction(text):
return False
def extract_topic(text):
"""Extract topic keywords from text. Deterministic."""
stopwords = {
"the",
"a",
"an",
"is",
"are",
"was",
"were",
"be",
"been",
"being",
"have",
"has",
"had",
"do",
"does",
"did",
"will",
"would",
"could",
"should",
"may",
"might",
"shall",
"can",
"to",
"of",
"in",
"for",
"on",
"with",
"at",
"by",
"from",
"as",
"into",
"through",
"during",
"before",
"after",
"this",
"that",
"these",
"those",
"it",
"its",
"what",
"which",
"who",
"how",
"when",
"where",
"why",
"and",
"or",
"but",
"not",
"no",
def _stopwords() -> list:
"""Gather seam (W3.4 delegation): the topic-extraction stopword table
as a SORTED list — sorted so JSON travel and kernel-side matching see
a deterministic order regardless of the set's iteration order."""
return sorted(_STOPWORDS)
_STOPWORDS = {
"the", "a", "an", "is", "are", "was", "were", "be", "been", "being",
"have", "has", "had", "do", "does", "did", "will", "would", "could",
"should", "may", "might", "shall", "can", "to", "of", "in", "for",
"on", "with", "at", "by", "from", "as", "into", "through", "during",
"before", "after", "this", "that", "these", "those", "it", "its",
"what", "which", "who", "how", "when", "where", "why", "and", "or",
"but", "not", "no",
}
def _tables() -> dict:
"""Gather seam: ALL regex pattern tables as data in one dict — the
exact contract the cc_witness_signals kernel consumes (parity-proven
per-table-row; regex_lite_search is the faithful subset for THESE
shapes). Sorted lists keep travel deterministic."""
return {
"stopwords": _stopwords(),
"correction": sorted(CORRECTION_PATTERNS),
"commitment": sorted([
r"\bI'?ll\s+(send|do|fix|build|check|update|create|write)\b",
r"\bI will\s+(send|do|fix|build|check|update|create|write)\b",
r"\blet me\s+(send|do|fix|build|check|update|create|write)\b",
r"\bremind me\b",
r"\bI promise\b",
r"\bI need to\b",
]),
"contradiction": sorted([r"\b(but|however|actually|wait|that contradicts)\b"]),
"preference": sorted([
r"\bI (prefer|like|love|hate|dislike)\b",
r"\bI'?m (not )?(a fan of|into|interested in)\b",
r"\b(don'?t|do not) (like|want|need)\b",
r"\b(always|never) (use|do|say)\b",
]),
"urgency": sorted([
r"\b(urgent|asap|immediately|now|quick|hurry)\b",
r"\b(deadline|due|overdue|late)\b",
r"\b(important|critical|crucial|vital)\b",
]),
"learning": sorted([
r"\b(learn|understand|explain|teach|show me)\b",
r"\b(how (does|do|did)|why (does|do|did)|what (is|are|was|were))\b",
r"\b(I don'?t (understand|know|get))\b",
]),
}
def _core():
"""The Rust cognition_core when available (strangler: Python fallback
load-bearing). RUNE_COGNITION_CORE=python pins the fallback."""
return None if cc_loader_pin() else _cc_probe()
def _cc_probe():
from rune import cc_loader as _cl
try:
return _cl if _cl.available() else None
except Exception: # noqa: BLE001, S110 — loader probe never crashes the fallback
return None
def cc_loader_pin() -> bool:
"""The env pin: RUNE_COGNITION_CORE=python → kernels OFF (shared probe).
"""
import os
return os.environ.get("RUNE_COGNITION_CORE", "auto") == "python"
def extract_topic(text):
"""Extract topic keywords from text. Deterministic.
Delegates to the kernel when built; Python oracle below."""
core = _core()
if core is not None:
try:
from rune import cc_loader as _cl
raw = _cl.witness_extract_topic(text, _stopwords())
return list(raw) if isinstance(raw, list) else list(raw or [])
except Exception: # noqa: BLE001, S110 — kernel failure → oracle, intentional silence
pass
words = re.findall(r"\w+", text.lower())
return [w for w in words if len(w) > 3 and w not in stopwords][:5]
return [w for w in words if len(w) > 3 and w not in _STOPWORDS][:5]
def _py_extract_topic(text):
"""Python parity oracle (W3.4)."""
words = re.findall(r"\w+", text.lower())
return [w for w in words if len(w) > 3 and w not in _STOPWORDS][:5]
def detect_signals(message, tool_error=None, task_status=None, recent_topics=None):
"""Tier 1: Reflex — extract signals from the turn. Pure Python, <1ms.
Returns dict of signals. Empty dict = nothing meaningful happened.
Delegates to the kernel when built (tables travel as data per the
REGEX-TABLE PORT LAW); Python oracle below is the parity authority.
"""
core = _core()
if core is not None:
try:
from rune import cc_loader as _cl
raw = _cl.witness_signals(message, _tables(), tool_error or "",
task_status or "", recent_topics or [])
if isinstance(raw, dict):
return {k: v for k, v in raw.items() if v not in (False, "")}
# non-dict → fall through to the oracle
except Exception: # noqa: BLE001, S110 — kernel failure → oracle, intentional silence
pass
return _py_detect_signals(message, tool_error, task_status, recent_topics)
def _py_detect_signals(message, tool_error=None, task_status=None,
recent_topics=None):
"""Python parity oracle (W3.4). Local tables via the gather seam."""
signals = {}
# Error signal
@@ -179,62 +232,24 @@ def detect_signals(message, tool_error=None, task_status=None, recent_topics=Non
signals["topic_drift"] = True
signals["new_topics"] = list(current_topics - recent_set)[:3]
# Commitment detection
commitment_patterns = [
r"\bI'?ll\s+(send|do|fix|build|check|update|create|write)\b",
r"\bI will\s+(send|do|fix|build|check|update|create|write)\b",
r"\blet me\s+(send|do|fix|build|check|update|create|write)\b",
r"\bremind me\b",
r"\bI promise\b",
r"\bI need to\b",
]
if message:
for pattern in commitment_patterns:
if re.search(pattern, message, re.IGNORECASE):
signals["commitment_detected"] = True
break
# Commitment detection (tables live in _tables() — the gather seam;
# the oracle reads the SAME table data the kernel receives)
for signal_name, table_name in (
("commitment_detected", "commitment"),
("preference_detected", "preference"),
("urgency_detected", "urgency"),
("learning_opportunity", "learning"),
):
if message:
for pattern in _tables().get(table_name, []):
if re.search(pattern, message, re.IGNORECASE):
signals[signal_name] = True
break
# Contradiction signal (simple: message contains "but" or "however" after a statement)
if message and re.search(r"\b(but|however|actually|wait|that contradicts)\b", message.lower()):
signals["possible_contradiction"] = True
# Preference detection
preference_patterns = [
r"\bI (prefer|like|love|hate|dislike)\b",
r"\bI'?m (not )?(a fan of|into|interested in)\b",
r"\b(don'?t|do not) (like|want|need)\b",
r"\b(always|never) (use|do|say)\b",
]
if message:
for pattern in preference_patterns:
if re.search(pattern, message, re.IGNORECASE):
signals["preference_detected"] = True
break
# Urgency detection
urgency_patterns = [
r"\b(urgent|asap|immediately|now|quick|hurry)\b",
r"\b(deadline|due|overdue|late)\b",
r"\b(important|critical|crucial|vital)\b",
]
if message:
for pattern in urgency_patterns:
if re.search(pattern, message, re.IGNORECASE):
signals["urgency_detected"] = True
break
# Learning opportunity detection
learning_patterns = [
r"\b(learn|understand|explain|teach|show me)\b",
r"\b(how (does|do|did)|why (does|do|did)|what (is|are|was|were))\b",
r"\b(I don'?t (understand|know|get))\b",
]
if message:
for pattern in learning_patterns:
if re.search(pattern, message, re.IGNORECASE):
signals["learning_opportunity"] = True
break
return signals
+7
View File
@@ -0,0 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "capability_scoring"
version = "0.1.0"
+13
View File
@@ -0,0 +1,13 @@
[package]
name = "capability_scoring"
version = "0.1.0"
edition = "2021"
description = "H3 slice 4 (T4.4) — capability_fit.evaluate_fit + capability_broker.score_card Rust port per contract BUILDER-2026-10-04T09:43:55Z-cli; zero-dep"
[lib]
name = "capability_scoring"
crate-type = ["rlib", "cdylib"]
[[bin]]
name = "capability_scoring"
path = "src/main.rs"
@@ -0,0 +1,208 @@
#!/usr/bin/env python3
"""canary_capability_scoring.py — parity battery for the capability_scoring
crate (H3 slice 4, T4.4, contract BUILDER-2026-10-04T09:43:55Z-cli).
Strangler law: the Rust core must produce BYTE-IDENTICAL output to the
Python scoring originals (capability_fit.evaluate_fit, capability_broker.
score_card) on identical inputs.
Sections:
1. crate builds (rustup PATH-first per the SYSTEM RUSTC BROKEN law)
2. evaluate_fit parity (every fit class + exact reason strings)
3. score_card parity (weights, generic penalty, stopwords, priority)
Run: python3 src/rust/capability_scoring/canary_capability_scoring.py
"""
from __future__ import annotations
import os
import subprocess
import sys
from pathlib import Path
CRATE_DIR = Path(__file__).resolve().parent
REPO = CRATE_DIR.parents[2]
sys.path.insert(0, str(REPO / "src"))
PASS = 0
FAIL = 0
def test(name: str, ok: bool, detail: str = "") -> None:
global PASS, FAIL
if ok:
PASS += 1
print(f" ✓ {name}")
else:
FAIL += 1
print(f" ✗ {name} — {detail}")
def build_crate() -> subprocess.CompletedProcess:
env = dict(os.environ)
# SYSTEM RUSTC BROKEN law: the healthy toolchain lives at the ABSOLUTE
# /home/nick path (the runner's HOME has no toolchain — x-ACL covers the
# runner user). Fall back to the caller's HOME for other hosts.
_absolute = Path("/home/nick/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/bin")
rustup_bin = _absolute if _absolute.is_dir() else (
Path.home() / ".rustup" / "toolchains" / "stable-x86_64-unknown-linux-gnu" / "bin")
env["PATH"] = f"{rustup_bin}:{env.get('PATH', '')}"
env["CARGO_TARGET_DIR"] = env.get("CARGO_TARGET_DIR", str(CRATE_DIR / "target"))
return subprocess.run(
["cargo", "build", "--release"],
cwd=CRATE_DIR, env=env, capture_output=True, text=True, timeout=600)
def fit_battery() -> list[tuple[str, list[str], dict]]:
return [
("EXACT all covered",
["contract:a", "contract:b"],
{"id": "cap:x", "provides": ["contract:a", "contract:b"],
"status": "CURRENT", "maturity": "WIRED", "consumers": ["module:y"]}),
("PARTIAL some covered",
["contract:a", "contract:b", "contract:c"],
{"id": "cap:x", "provides": ["contract:a"],
"status": "CURRENT", "maturity": "DECLARED", "consumers": []}),
("INCOMPATIBLE none covered",
["contract:a"],
{"id": "cap:x", "provides": ["contract:zzz"],
"status": "CURRENT", "maturity": "PRESENT", "consumers": []}),
("UNKNOWN no provides",
["contract:a"],
{"id": "cap:x", "provides": [],
"status": "CURRENT", "maturity": "DECLARED", "consumers": []}),
("unhealthy SUPERSEDED",
["contract:a"],
{"id": "cap:x", "provides": ["contract:a"],
"status": "SUPERSEDED", "maturity": "WIRED", "consumers": []}),
("unhealthy STALE",
["contract:a"],
{"id": "cap:x", "provides": ["contract:a"],
"status": "STALE", "maturity": "WIRED", "consumers": []}),
("unhealthy superseded_by set",
["contract:a"],
{"id": "cap:x", "provides": ["contract:a"], "status": "CURRENT",
"superseded_by": "cap:newer", "maturity": "WIRED", "consumers": []}),
("no contracts required -> EXACT",
[],
{"id": "cap:x", "provides": ["contract:a"],
"status": "CURRENT", "maturity": "LIVE_PROVEN", "consumers": ["a", "b"]}),
("unknown maturity ranks zero",
["contract:a"],
{"id": "cap:x", "provides": ["contract:a"],
"status": "CURRENT", "maturity": "WEIRD", "consumers": []}),
("duplicate provides dedup via set math",
["contract:a"],
{"id": "cap:x", "provides": ["contract:a", "contract:a"],
"status": "CURRENT", "maturity": "EXERCISED", "consumers": []}),
]
def card_battery() -> list[tuple[str, dict, str]]:
return [
("name match 4.0",
{"name": "query_memory", "capabilities": [], "description": "",
"semantic_examples": [], "priority": 100},
"run the query_memory tool"),
("capability match 3.0",
{"name": "x", "capabilities": ["spine_projection"], "description": "",
"semantic_examples": [], "priority": 100},
"compute the spine_projection now"),
("generic capability penalized 0.3",
{"name": "x", "capabilities": ["audit"], "description": "",
"semantic_examples": [], "priority": 100},
"please audit this"),
("description match 0.8",
{"name": "x", "capabilities": [],
"description": "manages deployment pipelines", "semantic_examples": [],
"priority": 100},
"deployment pipelines"),
("semantic examples 2.0",
{"name": "x", "capabilities": [], "description": "",
"semantic_examples": ["port the drift scoring"], "priority": 100},
"drift scoring"),
("priority bonus high priority",
{"name": "x", "capabilities": [], "description": "",
"semantic_examples": [], "priority": 0},
"anything"),
("stopwords excluded",
{"name": "x", "capabilities": ["the"], "description": "",
"semantic_examples": [], "priority": 100},
"the and or but"),
("empty query",
{"name": "x", "capabilities": ["spine"], "description": "",
"semantic_examples": [], "priority": 50},
""),
("underscore word chars",
{"name": "x", "capabilities": [], "description": "a_b c_d",
"semantic_examples": [], "priority": 100},
"a_b"),
("missing keys default",
{},
"nothing matches here"),
]
def main() -> int:
print("=" * 47)
print("canary_capability_scoring — capability scoring parity battery")
print("=" * 47)
print("\n--- 1. Crate builds ---")
r = build_crate()
test("cargo build --release exits 0", r.returncode == 0,
f"rc={r.returncode} err={r.stderr[-300:]!r}")
if r.returncode != 0:
print(f"\nRESULTS: {PASS} pass, {FAIL} fail")
return 1
default_so = Path(os.environ.get(
"CARGO_TARGET_DIR", str(CRATE_DIR / "target"))) / "release" / "libcapability_scoring.so"
so = Path(os.environ.get("CS_RUST_LIB", default_so))
test("libcapability_scoring.so exists", so.is_file(), str(so))
if not so.is_file():
print(f"\nRESULTS: {PASS} pass, {FAIL} fail")
return 1
from rune import capability_broker # noqa: E402
from rune import capability_fit # noqa: E402
from rune import cs_loader # noqa: E402
# ── 2. evaluate_fit parity ────────────────────────────────────────────
print("\n--- 2. evaluate_fit parity ---")
for name, contracts, candidate in fit_battery():
req = capability_fit.CapabilityRequirement("test objective",
contracts=contracts)
py = capability_fit._py_evaluate_fit(req, candidate)
rs = cs_loader.evaluate_fit(contracts, candidate)
test(f"fit: {name}", py == rs,
f"py={py!r}\n rs={rs!r}")
# ── 3. score_card parity ──────────────────────────────────────────────
print("\n--- 3. score_card parity ---")
for name, card, query in card_battery():
py = capability_broker._py_score_card(card, query)
rs = cs_loader.score_card(card, query)
test(f"card: {name}", py == rs, f"py={py!r} rs={rs!r}")
# ── 4. delegation sanity ──────────────────────────────────────────────
print("\n--- 4. Public API delegation ---")
req = capability_fit.CapabilityRequirement("obj", contracts=["contract:a"])
cand = {"id": "cap:x", "provides": ["contract:a"], "status": "CURRENT",
"maturity": "WIRED", "consumers": []}
test("public evaluate_fit matches core",
capability_fit.evaluate_fit(req, cand)
== capability_fit._py_evaluate_fit(req, cand), "mismatch")
card = {"name": "query_memory", "capabilities": [], "description": "",
"semantic_examples": [], "priority": 100}
test("public score_card matches core",
capability_broker.score_card(card, "query_memory")
== capability_broker._py_score_card(card, "query_memory"), "mismatch")
print(f"\n{'=' * 47}")
print(f"RESULTS: {PASS} pass, {FAIL} fail")
return 1 if FAIL else 0
if __name__ == "__main__":
sys.exit(main())
+103
View File
@@ -0,0 +1,103 @@
//! lib.rs — FFI surface for the capability_scoring crate (H3 slice 4).
//!
//! Every function takes ONE JSON argument string and returns a JSON envelope
//! string: {"ok": true, "value": ...} or {"ok": false, "err": "..."} — the
//! doc-envelope law. The caller frees returned strings with cs_free_string.
//! FFI segfault law: cs_loader.py pins argtypes + restype for every symbol.
pub mod pyjson;
pub mod scoring;
pub mod wordlists;
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
fn read_arg(ptr: *const c_char) -> Result<String, String> {
if ptr.is_null() {
return Err("null argument pointer".to_string());
}
unsafe { CStr::from_ptr(ptr) }
.to_str()
.map(|s| s.to_string())
.map_err(|e| format!("argument is not valid UTF-8: {}", e))
}
fn envelope(result: Result<pyjson::Json, String>) -> *mut c_char {
let body = match result {
Ok(value) => pyjson::Json::Obj(vec![
("ok".to_string(), pyjson::Json::Bool(true)),
("value".to_string(), value),
]),
Err(err) => pyjson::Json::Obj(vec![
("ok".to_string(), pyjson::Json::Bool(false)),
("err".to_string(), pyjson::Json::Str(err)),
]),
};
let out = pyjson::dumps(&body);
CString::new(out)
.map(|c| c.into_raw())
.unwrap_or(std::ptr::null_mut())
}
fn arg_json(text: &str) -> Result<pyjson::Json, String> {
pyjson::parse(text).map_err(|e| format!("argument JSON parse failed: {}", e))
}
fn get<'a>(v: &'a pyjson::Json, key: &str) -> Option<&'a pyjson::Json> {
match v {
pyjson::Json::Obj(pairs) => pairs.iter().find(|(k, _)| k == key).map(|(_, v)| v),
_ => None,
}
}
fn get_str<'a>(v: &'a pyjson::Json, key: &str) -> Option<&'a str> {
match get(v, key) {
Some(pyjson::Json::Str(s)) => Some(s),
_ => None,
}
}
fn str_list(v: Option<&pyjson::Json>) -> Vec<String> {
match v {
Some(pyjson::Json::Arr(items)) => items
.iter()
.filter_map(|x| match x {
pyjson::Json::Str(s) => Some(s.clone()),
_ => None,
})
.collect(),
_ => vec![],
}
}
/// cs_evaluate_fit({"contracts": [...], "candidate": {...}}) -> fit dict
#[no_mangle]
pub extern "C" fn cs_evaluate_fit(arg: *const c_char) -> *mut c_char {
let result = read_arg(arg).and_then(|text| arg_json(&text)).and_then(|a| {
let contracts = str_list(get(&a, "contracts"));
let candidate = get(&a, "candidate").cloned().ok_or("arg.candidate required")?;
Ok(scoring::evaluate_fit(&contracts, &candidate))
});
envelope(result)
}
/// cs_score_card({"card": {...}, "query": "..."}) -> number
#[no_mangle]
pub extern "C" fn cs_score_card(arg: *const c_char) -> *mut c_char {
let result = read_arg(arg).and_then(|text| arg_json(&text)).and_then(|a| {
let card = get(&a, "card").cloned().ok_or("arg.card required")?;
let query = get_str(&a, "query").unwrap_or("");
Ok(pyjson::Json::Num(scoring::score_card(&card, query)))
});
envelope(result)
}
/// Free a string returned by any cs_* function.
#[no_mangle]
pub extern "C" fn cs_free_string(ptr: *mut c_char) {
if !ptr.is_null() {
unsafe {
let _ = CString::from_raw(ptr);
}
}
}
+87
View File
@@ -0,0 +1,87 @@
//! main.rs — CLI front for the capability_scoring core (runner/debug use).
//!
//! stdin: {"op": "evaluate_fit"|"score_card", "arg": <json>}
//! stdout: the same {ok, value|err} envelope the FFI returns.
use capability_scoring::{pyjson, scoring};
use std::io::Read;
fn get<'a>(v: &'a pyjson::Json, key: &str) -> Option<&'a pyjson::Json> {
match v {
pyjson::Json::Obj(pairs) => pairs.iter().find(|(k, _)| k == key).map(|(_, v)| v),
_ => None,
}
}
fn str_list(v: Option<&pyjson::Json>) -> Vec<String> {
match v {
Some(pyjson::Json::Arr(items)) => items
.iter()
.filter_map(|x| match x {
pyjson::Json::Str(s) => Some(s.clone()),
_ => None,
})
.collect(),
_ => vec![],
}
}
fn main() {
let mut input = String::new();
if std::io::stdin().read_to_string(&mut input).is_err() {
emit(Err("failed to read stdin".to_string()), false);
return;
}
let parsed = match pyjson::parse(&input) {
Ok(p) => p,
Err(e) => {
emit(Err(format!("stdin JSON parse failed: {}", e)), false);
return;
}
};
let op = match get(&parsed, "op") {
Some(pyjson::Json::Str(s)) => s.clone(),
_ => {
emit(Err("op required".to_string()), false);
return;
}
};
let arg = get(&parsed, "arg").cloned().unwrap_or(pyjson::Json::Null);
let result = match op.as_str() {
"evaluate_fit" => {
let contracts = str_list(get(&arg, "contracts"));
match get(&arg, "candidate") {
Some(c) => Ok(scoring::evaluate_fit(&contracts, c)),
None => Err("arg.candidate required".to_string()),
}
}
"score_card" => match get(&arg, "card") {
Some(c) => {
let query = match get(&arg, "query") {
Some(pyjson::Json::Str(s)) => s.clone(),
_ => String::new(),
};
Ok(pyjson::Json::Num(scoring::score_card(c, &query)))
}
None => Err("arg.card required".to_string()),
},
other => Err(format!("unknown op: {:?}", other)),
};
let ok = result.is_ok();
emit(result, ok);
}
fn emit(result: Result<pyjson::Json, String>, ok: bool) {
let body = match result {
Ok(value) => pyjson::Json::Obj(vec![
("ok".to_string(), pyjson::Json::Bool(true)),
("value".to_string(), value),
]),
Err(err) => pyjson::Json::Obj(vec![
("ok".to_string(), pyjson::Json::Bool(false)),
("err".to_string(), pyjson::Json::Str(err)),
]),
};
println!("{}", pyjson::dumps(&body));
std::process::exit(if ok { 0 } else { 1 });
}
+444
View File
@@ -0,0 +1,444 @@
//! json_min.py — minimal JSON parse/serialize with Python-parity output.
//!
//! Exists because contract C1 demands byte-comparable state files with the
//! Python implementation (`json.dumps` / `json.dump(indent=2)`):
//! - compact separators: ", " between items, ": " between key/value
//! (Python json.dumps DEFAULTS — no separators arg means the space-y form)
//! - ensure_ascii: non-ASCII escaped as \uXXXX (with surrogate pairs)
//! - floats: shortest round-trip repr (Rust `{}` == Python `repr` for f64
//! in every value this module produces, including arithmetic artifacts
//! like 0.30000000000000004)
//! - indent=2 dump: newline+indent after every item separator, "{}" when
//! empty, NO trailing newline (json.dump never writes one)
//! Object key order is preserved (Vec of pairs) — matches Python 3.7+ dict
//! insertion order, which the classification tie-break law depends on.
use std::fmt::Write as _;
/// Preserved-order JSON value.
#[derive(Debug, Clone, PartialEq)]
pub enum Json {
Null,
Bool(bool),
Num(f64),
Str(String),
Arr(Vec<Json>),
Obj(Vec<(String, Json)>),
}
impl Json {
pub fn get(&self, key: &str) -> Option<&Json> {
match self {
Json::Obj(pairs) => pairs.iter().find(|(k, _)| k == key).map(|(_, v)| v),
_ => None,
}
}
}
// ── parsing ─────────────────────────────────────────────────────────────────
pub fn parse(input: &str) -> Result<Json, String> {
let bytes = input.as_bytes();
let mut p = Parser { b: bytes, i: 0 };
p.skip_ws();
let v = p.value()?;
p.skip_ws();
if p.i != bytes.len() {
return Err(format!("trailing data at byte {}", p.i));
}
Ok(v)
}
struct Parser<'a> {
b: &'a [u8],
i: usize,
}
impl<'a> Parser<'a> {
fn skip_ws(&mut self) {
while self.i < self.b.len() && matches!(self.b[self.i], b' ' | b'\t' | b'\n' | b'\r') {
self.i += 1;
}
}
fn peek(&self) -> Option<u8> {
self.b.get(self.i).copied()
}
fn expect(&mut self, c: u8) -> Result<(), String> {
if self.peek() == Some(c) {
self.i += 1;
Ok(())
} else {
Err(format!("expected '{}' at byte {}", c as char, self.i))
}
}
fn value(&mut self) -> Result<Json, String> {
match self.peek() {
Some(b'{') => self.object(),
Some(b'[') => self.array(),
Some(b'"') => Ok(Json::Str(self.string()?)),
Some(b't') => self.literal("true", Json::Bool(true)),
Some(b'f') => self.literal("false", Json::Bool(false)),
Some(b'n') => self.literal("null", Json::Null),
Some(c) if c == b'-' || c.is_ascii_digit() => self.number(),
_ => Err(format!("unexpected byte at {}", self.i)),
}
}
fn literal(&mut self, lit: &str, v: Json) -> Result<Json, String> {
if self.b[self.i..].starts_with(lit.as_bytes()) {
self.i += lit.len();
Ok(v)
} else {
Err(format!("bad literal at byte {}", self.i))
}
}
fn object(&mut self) -> Result<Json, String> {
self.expect(b'{')?;
let mut pairs = Vec::new();
self.skip_ws();
if self.peek() == Some(b'}') {
self.i += 1;
return Ok(Json::Obj(pairs));
}
loop {
self.skip_ws();
let key = self.string()?;
self.skip_ws();
self.expect(b':')?;
self.skip_ws();
let val = self.value()?;
pairs.push((key, val));
self.skip_ws();
match self.peek() {
Some(b',') => self.i += 1,
Some(b'}') => {
self.i += 1;
return Ok(Json::Obj(pairs));
}
_ => return Err(format!("expected ',' or '}}' at byte {}", self.i)),
}
}
}
fn array(&mut self) -> Result<Json, String> {
self.expect(b'[')?;
let mut items = Vec::new();
self.skip_ws();
if self.peek() == Some(b']') {
self.i += 1;
return Ok(Json::Arr(items));
}
loop {
self.skip_ws();
items.push(self.value()?);
self.skip_ws();
match self.peek() {
Some(b',') => self.i += 1,
Some(b']') => {
self.i += 1;
return Ok(Json::Arr(items));
}
_ => return Err(format!("expected ',' or ']' at byte {}", self.i)),
}
}
}
fn string(&mut self) -> Result<String, String> {
self.expect(b'"')?;
let mut out = String::new();
loop {
match self.peek() {
None => return Err("unterminated string".into()),
Some(b'"') => {
self.i += 1;
return Ok(out);
}
Some(b'\\') => {
self.i += 1;
match self.peek() {
Some(b'"') => out.push('"'),
Some(b'\\') => out.push('\\'),
Some(b'/') => out.push('/'),
Some(b'b') => out.push('\u{0008}'),
Some(b'f') => out.push('\u{000C}'),
Some(b'n') => out.push('\n'),
Some(b'r') => out.push('\r'),
Some(b't') => out.push('\t'),
Some(b'u') => {
let cp = self.hex4()?;
// surrogate pair handling (Python accepts these)
if (0xD800..0xDC00).contains(&cp) {
if self.b[self.i..].starts_with(b"\\u") {
self.i += 1; // now at 'u' — hex4 reads [i+1..i+5]
let lo = self.hex4()?;
if (0xDC00..0xE000).contains(&lo) {
let c = 0x10000
+ ((cp - 0xD800) << 10)
+ (lo - 0xDC00);
out.push(
char::from_u32(c)
.ok_or("bad surrogate pair")?,
);
} else {
// lone high surrogate — Python keeps it as
// an escaped lone surrogate; we degrade to
// U+FFFD (not produced by this writer)
out.push('\u{FFFD}');
}
} else {
out.push('\u{FFFD}');
}
} else {
out.push(char::from_u32(cp).unwrap_or('\u{FFFD}'));
}
continue; // hex4 already advanced i
}
_ => return Err(format!("bad escape at byte {}", self.i)),
}
self.i += 1;
}
Some(_) => {
// consume one UTF-8 scalar
let s = &self.b[self.i..];
let ch_len = utf8_len(s[0]);
let chunk = std::str::from_utf8(&s[..ch_len.min(s.len())])
.map_err(|_| "bad utf8")?;
out.push_str(chunk);
self.i += ch_len;
}
}
}
}
fn hex4(&mut self) -> Result<u32, String> {
if self.i + 4 > self.b.len() {
return Err("short \\u escape".into());
}
let s = std::str::from_utf8(&self.b[self.i + 1..self.i + 5])
.map_err(|_| "bad \\u")?;
let v = u32::from_str_radix(s, 16).map_err(|_| "bad \\u digits")?;
self.i += 5; // 'u' + 4 hex digits (caller consumed the backslash)
Ok(v)
}
fn number(&mut self) -> Result<Json, String> {
let start = self.i;
if self.peek() == Some(b'-') {
self.i += 1;
}
while matches!(self.peek(), Some(c) if c.is_ascii_digit()) {
self.i += 1;
}
if self.peek() == Some(b'.') {
self.i += 1;
while matches!(self.peek(), Some(c) if c.is_ascii_digit()) {
self.i += 1;
}
}
if matches!(self.peek(), Some(b'e') | Some(b'E')) {
self.i += 1;
if matches!(self.peek(), Some(b'+') | Some(b'-')) {
self.i += 1;
}
while matches!(self.peek(), Some(c) if c.is_ascii_digit()) {
self.i += 1;
}
}
let s = std::str::from_utf8(&self.b[start..self.i]).map_err(|_| "bad number")?;
s.parse::<f64>()
.map(Json::Num)
.map_err(|_| format!("bad number '{}'", s))
}
}
fn utf8_len(b: u8) -> usize {
if b < 0x80 {
1
} else if b >> 5 == 0b110 {
2
} else if b >> 4 == 0b1110 {
3
} else {
4
}
}
// ── serialization (Python json.dumps / json.dump(indent=2) parity) ─────────
/// Python `json.dumps(value, sort_keys=True)` — compact separators, object
/// keys recursively sorted. Python sorts str keys by code point; Rust's
/// byte-wise UTF-8 `String` ordering is identical for valid UTF-8, so a
/// plain `sort_by` on the key is faithful.
pub fn dumps_sorted(v: &Json) -> String {
let mut out = String::new();
write_compact_sorted(&mut out, v);
out
}
fn write_compact_sorted(out: &mut String, v: &Json) {
match v {
Json::Obj(pairs) => {
let mut sorted: Vec<&(String, Json)> = pairs.iter().collect();
sorted.sort_by(|a, b| a.0.cmp(&b.0));
out.push('{');
for (i, (k, val)) in sorted.iter().enumerate() {
if i > 0 {
out.push_str(", ");
}
write_escaped(out, k);
out.push_str(": ");
write_compact_sorted(out, val);
}
out.push('}');
}
Json::Arr(items) => {
out.push('[');
for (i, item) in items.iter().enumerate() {
if i > 0 {
out.push_str(", ");
}
write_compact_sorted(out, item);
}
out.push(']');
}
other => write_compact(out, other),
}
}
/// Python `json.dumps(value)` — compact separators `", "` / `": "`,
/// ensure_ascii escaping.
pub fn dumps(v: &Json) -> String {
let mut out = String::new();
write_compact(&mut out, v);
out
}
fn write_compact(out: &mut String, v: &Json) {
match v {
Json::Null => out.push_str("null"),
Json::Bool(true) => out.push_str("true"),
Json::Bool(false) => out.push_str("false"),
Json::Num(n) => out.push_str(&fmt_num(*n)),
Json::Str(s) => write_escaped(out, s),
Json::Arr(items) => {
out.push('[');
for (i, item) in items.iter().enumerate() {
if i > 0 {
out.push_str(", ");
}
write_compact(out, item);
}
out.push(']');
}
Json::Obj(pairs) => {
out.push('{');
for (i, (k, val)) in pairs.iter().enumerate() {
if i > 0 {
out.push_str(", ");
}
write_escaped(out, k);
out.push_str(": ");
write_compact(out, val);
}
out.push('}');
}
}
}
/// Python `json.dump(value, indent=2)` — item separator becomes
/// `",\n<indent>"`, key separator `": "`, empty container → `{}` / `[]`,
/// NO trailing newline.
pub fn dump_indent2(v: &Json) -> String {
let mut out = String::new();
write_indent(&mut out, v, 0);
out
}
fn write_indent(out: &mut String, v: &Json, depth: usize) {
match v {
Json::Arr(items) if !items.is_empty() => {
out.push_str("[\n");
for (i, item) in items.iter().enumerate() {
if i > 0 {
out.push_str(",\n");
}
push_indent(out, depth + 1);
write_indent(out, item, depth + 1);
}
out.push('\n');
push_indent(out, depth);
out.push(']');
}
Json::Arr(_) => out.push_str("[]"),
Json::Obj(pairs) if !pairs.is_empty() => {
out.push_str("{\n");
for (i, (k, val)) in pairs.iter().enumerate() {
if i > 0 {
out.push_str(",\n");
}
push_indent(out, depth + 1);
write_escaped(out, k);
out.push_str(": ");
write_indent(out, val, depth + 1);
}
out.push('\n');
push_indent(out, depth);
out.push('}');
}
Json::Obj(_) => out.push_str("{}"),
other => write_compact(out, other),
}
}
fn push_indent(out: &mut String, depth: usize) {
for _ in 0..depth {
out.push_str(" ");
}
}
/// Integer-shaped f64 prints without a decimal point (matching how this
/// module's writers emit counts); all other values use shortest round-trip.
pub fn fmt_num(n: f64) -> String {
if n.is_finite() && n.fract() == 0.0 && n.abs() < 9.007_199_254_740_992e15 {
return format!("{}", n as i64);
}
format!("{}", n)
}
/// Python `json.dumps` string escaping with ensure_ascii=True.
pub fn write_escaped(out: &mut String, s: &str) {
out.push('"');
for ch in s.chars() {
match ch {
'"' => out.push_str("\\\""),
'\\' => out.push_str("\\\\"),
'\n' => out.push_str("\\n"),
'\r' => out.push_str("\\r"),
'\t' => out.push_str("\\t"),
'\u{0008}' => out.push_str("\\b"),
'\u{000C}' => out.push_str("\\f"),
c if (c as u32) < 0x20 => {
let _ = write!(out, "\\u{:04x}", c as u32);
}
c if (c as u32) <= 0x7E => out.push(c),
// ensure_ascii: escape everything non-ASCII, surrogate pairs
// for astral planes (Python behaviour)
c => {
let cp = c as u32;
if cp > 0xFFFF {
let v = cp - 0x10000;
let hi = 0xD800 + (v >> 10);
let lo = 0xDC00 + (v & 0x3FF);
let _ = write!(out, "\\u{:04x}\\u{:04x}", hi, lo);
} else {
let _ = write!(out, "\\u{:04x}", cp);
}
}
}
}
out.push('"');
}
+220
View File
@@ -0,0 +1,220 @@
//! scoring.rs — capability_fit.evaluate_fit + capability_broker.score_card,
//! faithful ports (H3 slice 4, contract BUILDER-2026-10-04T09:43:55Z-cli).
//!
//! Faithfulness traps (each canary-covered):
//! * Python set math on provides/contracts (dedup, sorted missing list).
//! * `superseded_by` truthiness joins SUPERSEDED/STALE as unhealthy.
//! * EXACT when no contracts are required (missing is empty).
//! * re.findall(r"\w+") hand-rolled as ASCII word-char runs [A-Za-z0-9_]+
//! (Python's \w is Unicode-aware, but every input these sets see in
//! production is ASCII; the canary covers the ASCII contract).
//! * generic_caps penalty applies per OVERLAP COUNT, not per capability.
use crate::pyjson::Json;
use crate::wordlists::{GENERIC_CAPS, STOPWORDS};
const MATURITY_ORDER: [&str; 6] = [
"DECLARED", "PRESENT", "WIRED", "REACHABLE", "EXERCISED", "LIVE_PROVEN",
];
fn maturity_rank(m: &str) -> i64 {
MATURITY_ORDER.iter().position(|&x| x == m).map(|i| i as i64).unwrap_or(0)
}
fn obj_get<'a>(v: &'a Json, key: &str) -> Option<&'a Json> {
match v {
Json::Obj(pairs) => pairs.iter().find(|(k, _)| k == key).map(|(_, v)| v),
_ => None,
}
}
fn str_list(v: Option<&Json>) -> Vec<String> {
match v {
Some(Json::Arr(items)) => items
.iter()
.filter_map(|x| match x {
Json::Str(s) => Some(s.clone()),
_ => None,
})
.collect(),
_ => vec![],
}
}
fn str_or<'a>(v: Option<&'a Json>, default: &'a str) -> &'a str {
match v {
Some(Json::Str(s)) => s,
_ => default,
}
}
// ── evaluate_fit ────────────────────────────────────────────────────────────
/// DETERMINISTIC fit of one candidate against a requirement's contracts.
/// `contracts` is the requirement's (already sorted) contract list.
pub fn evaluate_fit(contracts: &[String], candidate: &Json) -> Json {
let provides = str_list(obj_get(candidate, "provides"));
let provides_set: Vec<&String> = provides.iter().collect();
let mut missing: Vec<String> = contracts
.iter()
.filter(|c| !provides_set.contains(c))
.cloned()
.collect();
missing.sort();
missing.dedup();
let covered = contracts.len() as i64 - missing.len() as i64;
let status = str_or(obj_get(candidate, "status"), "");
let superseded_by = obj_get(candidate, "superseded_by");
// Python bool(candidate.get("superseded_by")): any truthy value.
let unhealthy = status == "SUPERSEDED"
|| status == "STALE"
|| matches!(superseded_by, Some(Json::Str(s)) if !s.is_empty())
|| matches!(superseded_by, Some(Json::Bool(true)))
|| matches!(superseded_by, Some(Json::Num(n)) if *n != 0.0);
let maturity = maturity_rank(str_or(obj_get(candidate, "maturity"), "DECLARED"));
let id = str_or(obj_get(candidate, "id"), "");
let mk = |fit: &str, reason: String, covered: i64, with_extras: bool| {
let mut out = Json::Obj(vec![
("candidate".to_string(), Json::Str(id.to_string())),
("fit".to_string(), Json::Str(fit.to_string())),
("reason".to_string(), Json::Str(reason)),
(
"missing_contracts".to_string(),
Json::Arr(missing.iter().map(|m| Json::Str(m.clone())).collect()),
),
("covered".to_string(), Json::Num(covered as f64)),
]);
if with_extras {
if let Json::Obj(ref mut pairs) = out {
pairs.push(("maturity_rank".to_string(), Json::Num(maturity as f64)));
pairs.push((
"consumers".to_string(),
obj_get(candidate, "consumers").cloned().unwrap_or(Json::Arr(vec![])),
));
}
}
out
};
if unhealthy {
return mk(
"INCOMPATIBLE",
format!("candidate status {} — not reusable as-is", status),
covered,
false,
);
}
if !contracts.is_empty() && provides.is_empty() {
return mk(
"UNKNOWN",
"candidate exposes no contracts to check".to_string(),
0,
false,
);
}
if missing.is_empty() {
mk("EXACT", "all required contracts provided".to_string(), covered, true)
} else if covered > 0 {
mk(
"PARTIAL",
format!("covers {}/{} required contracts", covered, contracts.len()),
covered,
true,
)
} else {
mk(
"INCOMPATIBLE",
"provides none of the required contracts".to_string(),
covered,
true,
)
}
}
// ── score_card ──────────────────────────────────────────────────────────────
/// Python re.findall(r"\w+", s) for ASCII inputs: maximal [A-Za-z0-9_] runs.
fn word_runs(s: &str) -> Vec<String> {
let mut out = vec![];
let mut cur = String::new();
for ch in s.chars() {
if ch.is_ascii_alphanumeric() || ch == '_' {
cur.push(ch);
} else if !cur.is_empty() {
out.push(std::mem::take(&mut cur));
}
}
if !cur.is_empty() {
out.push(cur);
}
out
}
fn word_set(s: &str) -> Vec<String> {
let mut out = word_runs(s);
out.sort();
out.dedup();
out
}
fn minus(hay: &[String], needles: &[&str]) -> Vec<String> {
hay.iter().filter(|w| !needles.contains(&w.as_str())).cloned().collect()
}
fn overlap_count(a: &[String], b: &[String]) -> usize {
a.iter().filter(|w| b.contains(w)).count()
}
pub fn score_card(card: &Json, query: &str) -> f64 {
let query_low = query.to_lowercase();
let query_words = word_set(&query_low);
let query_content = minus(&query_words, &STOPWORDS);
let mut score = 0.0;
// Direct capability name match (highest weight, generic penalized)
for cap in str_list(obj_get(card, "capabilities")) {
let cap_words = word_set(&cap.to_lowercase());
let overlap = overlap_count(&query_content, &cap_words);
if overlap > 0 {
if GENERIC_CAPS.contains(&cap.to_lowercase().as_str()) {
score += 0.3 * overlap as f64;
} else {
score += 3.0 * overlap as f64;
}
}
}
// Description word match (medium weight)
let desc = str_or(obj_get(card, "description"), "").to_lowercase();
let desc_words = minus(&word_set(&desc), &STOPWORDS);
score += 0.8 * overlap_count(&query_content, &desc_words) as f64;
// Semantic example match (high weight — curated)
for example in str_list(obj_get(card, "semantic_examples")) {
let example_words = minus(&word_set(&example.to_lowercase()), &STOPWORDS);
let overlap = overlap_count(&query_content, &example_words);
if overlap > 0 {
score += 2.0 * overlap as f64;
}
}
// Name match (very high weight — direct name reference)
let name_words = word_set(&str_or(obj_get(card, "name"), "").to_lowercase());
let overlap = overlap_count(&query_words, &name_words);
if overlap > 0 {
score += 4.0 * overlap as f64;
}
// Priority bonus (lower number = higher priority)
let priority = match obj_get(card, "priority") {
Some(Json::Num(n)) => *n,
_ => 100.0,
};
score += ((100.0 - priority) / 200.0).max(0.0);
score
}
@@ -0,0 +1,113 @@
//! Generated from capability_broker.py score_card literals — do not edit by hand.
pub const GENERIC_CAPS: [&str; 12] = [
"audit",
"growth",
"memory_env",
"read",
"reasoning",
"recall",
"research",
"skills",
"status",
"tasks",
"voice",
"write",
];
pub const STOPWORDS: [&str; 94] = [
"a",
"about",
"above",
"after",
"again",
"against",
"all",
"an",
"and",
"are",
"as",
"at",
"be",
"because",
"been",
"before",
"being",
"below",
"between",
"both",
"but",
"by",
"can",
"could",
"did",
"do",
"does",
"during",
"each",
"every",
"few",
"for",
"from",
"further",
"had",
"has",
"have",
"here",
"how",
"if",
"in",
"into",
"is",
"it",
"its",
"just",
"may",
"might",
"more",
"most",
"no",
"nor",
"not",
"of",
"off",
"on",
"once",
"only",
"or",
"other",
"out",
"over",
"own",
"same",
"shall",
"should",
"so",
"some",
"such",
"than",
"that",
"the",
"then",
"there",
"these",
"this",
"those",
"through",
"to",
"too",
"under",
"very",
"was",
"were",
"what",
"when",
"where",
"which",
"while",
"who",
"why",
"will",
"with",
"would",
];
+7
View File
@@ -0,0 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "cognition_core"
version = "0.1.0"
+13
View File
@@ -0,0 +1,13 @@
[package]
name = "cognition_core"
version = "0.1.0"
edition = "2021"
description = "R2 W3.1 — deterministic cognition kernels (context_broker first) per contract BUILDER-2026-10-05T07:19:27Z-cli; zero-dep, no-clock"
[lib]
name = "cognition_core"
crate-type = ["rlib", "cdylib"]
[[bin]]
name = "cognition_core"
path = "src/main.rs"
@@ -0,0 +1,134 @@
#!/usr/bin/env python3
"""canary_cognition_core.py — parity battery for the cognition_core crate
(R2 Wave 3 slice 1, contract BUILDER-2026-10-05T07:19:27Z-cli).
First tenant: context_broker's deterministic kernel — default_estimator,
default_budget, default_memory_budget, memory_disabled.
Parity floor: byte-identical outputs on identical inputs.
Improvement (measured): the estimator's per-character loop may become a
faster path IF identical — measured, or reverted.
Run: python3 src/rust/cognition_core/canary_cognition_core.py
"""
from __future__ import annotations
import json
import os
import subprocess
import sys
import time
from pathlib import Path
CRATE = Path(__file__).resolve().parent
REPO = CRATE.parents[2]
sys.path.insert(0, str(REPO / "src"))
PASS = FAIL = 0
NOW = "2026-10-05T07:30:00+00:00"
def check(name, ok, detail=""):
global PASS, FAIL
if ok:
PASS += 1
print(f" ✓ {name}")
else:
FAIL += 1
print(f" ✗ {name} — {detail}")
def build():
env = dict(os.environ)
absolute = Path("/home/nick/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/bin")
rust = absolute if absolute.is_dir() else (
Path.home() / ".rustup/toolchains/stable-x86_64-unknown-linux-gnu/bin")
env["PATH"] = f"{rust}:{env.get('PATH', '')}"
return subprocess.run(["cargo", "build", "--release"], cwd=CRATE,
env=env, capture_output=True, text=True, timeout=600)
def main():
print("=== cognition_core: context_broker kernel parity ===")
r = build()
check("crate builds", r.returncode == 0, r.stderr[-240:])
if r.returncode:
print(f"RESULTS: {PASS} pass, {FAIL} fail")
return 1
from rune import cc_loader
from rune import context_broker as cb
# ── estimator: exact token semantics on every text class ──────────────
texts = ["", " ", "one", "hello world", "héllo wörld 🎸 emoji",
"x" * 10001, "tabs\t\tand\nnewlines\n", " padded ",
"unicode — dashes — en‑dash", "1 token ~ 4 chars maybe"]
for t in texts:
py = cb._py_default_estimator(t)
rs = cc_loader.default_estimator(t)
check(f"estimator {t[:24]!r}", py == rs, f"py={py!r} rs={rs!r}")
# property sweep: 200 random-ish strings, parity must hold
import random
random.seed(20261005)
for i in range(200):
n = random.randint(0, 300)
t = "".join(random.choice("abcd \n\té🎸 ") for _ in range(n))
py = cb._py_default_estimator(t)
rs = cc_loader.default_estimator(t)
if py != rs:
check("estimator property sweep", False, f"seed case {i}: py={py!r} rs={rs!r}")
break
else:
check("estimator property sweep (200 cases)", True)
# ── budget arithmetic ──────────────────────────────────────────────────
for window in (0, 1, 1024, 8192, 131072, 200000, 10**7):
py = cb._py_default_budget(window)
rs = cc_loader.default_budget(window)
check(f"budget {window}", py == rs, f"py={py!r} rs={rs!r}")
for window in (None, 0, 4096, 32768):
py = cb._py_default_memory_budget(window)
rs = cc_loader.default_memory_budget(window)
check(f"memory budget {window}", py == rs, f"py={py!r} rs={rs!r}")
# ── disabled gate (env contract) ──────────────────────────────────────
env_vars = [None, "", "0", "1", "true", "false", "yes"]
for v in env_vars:
env = dict(os.environ)
if v is None:
env.pop("RUNE_MEMORY_OFF", None)
else:
env["RUNE_MEMORY_OFF"] = v
os.environ.clear(); os.environ.update(env)
raw = os.environ.get("RUNE_MEMORY_OFF", "").strip().lower()
py = cb._py_memory_disabled_value(raw)
rs = cc_loader.memory_disabled()
check(f"memory_disabled env {v!r}", py == rs, f"py={py!r} rs={rs!r}")
# and the PUBLIC delegation path agrees (the cross-check gate)
pub = cb.memory_disabled()
check(f"memory_disabled public env {v!r}", pub == py,
f"pub={pub!r} py={py!r}")
os.environ.pop("RUNE_MEMORY_OFF", None)
# ── improvement measurement (estimator) ───────────────────────────────
import timeit
t = "x" * 5000
py_us = min(timeit.repeat(lambda: cb._py_default_estimator(t), number=1000, repeat=5)) / 1000 * 1e6
rs_us = min(timeit.repeat(lambda: cc_loader.default_estimator(t), number=1000, repeat=5)) / 1000 * 1e6
print(f" measured: py {py_us:.1f}us vs rust {rs_us:.1f}us per call")
# FFI MICRO-LATENCY LAW (measured R2 W2.1): JSON-envelope serialization
# dominates at small scale — kernel-parity, not micro-latency, is the
# improvement channel at THIS surface. The estimator's value (1250) is
# trivially correct on both sides; assert parity, note timing.
t_out_rs = cc_loader.default_estimator(t)
t_out_py = cb._py_default_estimator(t)
check("estimator parity on the measured input", t_out_rs == t_out_py,
f"py={t_out_py!r} rs={t_out_rs!r}")
print(f"\nRESULTS: {PASS} pass, {FAIL} fail")
return 1 if FAIL else 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,217 @@
#!/usr/bin/env python3
"""canary_cognition_core growth/identity/pins section — W3.3 (contract
BUILDER-2026-10-05T07:19:27Z-cli, slice 3).
Kernels: identity_anchors (check_drift/get_identity_prompt/show_*),
growth_delta (growth_profile/_near_dup/_apply_profile), pins (pin/recall).
Run standalone.
"""
from __future__ import annotations
import json
import os
import subprocess
import sys
from pathlib import Path
CRATE = Path(__file__).resolve().parent
REPO = CRATE.parents[2]
sys.path.insert(0, str(REPO / "src"))
PASS = FAIL = 0
def check(name, ok, detail=""):
global PASS, FAIL
if ok:
PASS += 1
print(f" ✓ {name}")
else:
FAIL += 1
print(f" ✗ {name} — {detail}")
def build():
env = dict(os.environ)
absolute = Path("/home/nick/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/bin")
rust = absolute if absolute.is_dir() else (
Path.home() / ".rustup/toolchains/stable-x86_64-unknown-linux-gnu/bin")
env["PATH"] = f"{rust}:{env.get('PATH', '')}"
return subprocess.run(["cargo", "build", "--release"], cwd=CRATE,
env=env, capture_output=True, text=True, timeout=600)
def main():
print("=== cognition_core: identity/growth/pins kernels (W3.3) ===")
r = build()
check("crate builds", r.returncode == 0, r.stderr[-240:])
if r.returncode:
print(f"RESULTS: {PASS} pass, {FAIL} fail")
return 1
from rune import cc_loader, identity_anchors as ia, growth_delta as gd, pins
# ── identity_anchors: drift-check matching + prompt assembly ──────────
# patterns arrive as [pattern, message, prefix] triples (data stays in
# the Python module — the kernel matches only).
triples = []
for prefix, table in (("DRIFT", ia._DRIFT_PATTERNS_CACHE if hasattr(ia, "_DRIFT_PATTERNS_CACHE") else None),): pass
# gather the real tables (verification via kernel parity on the LIVE data)
live_text = "As Rune, I am here."
issues_py = ia.check_drift(live_text)
triples = ia._pattern_triples() # the module's real gather seam
rs = cc_loader.drift_check(live_text, triples)
check("drift-check parity", issues_py == rs, f"py={issues_py!r} rs={rs!r}")
# every pattern from the module's tables is exercised verbatim
all_py = ia.check_drift("i am rune; you are nick. i believe it helped. "
"you're absolutely right, let me know.")
all_rs = cc_loader.drift_check("i am rune; you are nick. i believe it helped. "
"you're absolutely right, let me know.", triples)
check("drift-check full-table parity", all_py == all_rs,
f"py={all_py!r}\n rs={all_rs!r}")
prompt_py = ia._py_get_identity_prompt()
prompt_rs = cc_loader.identity_prompt(ia.PRONOUN_RULES, ia.SELF_AWARENESS,
ia.ANCHORS, ia.EMBODIMENT_RULES)
check("identity prompt assembly parity", prompt_py == prompt_rs,
f"py={prompt_py[:80]!r} rs={prompt_rs[:80]!r}")
# ── growth_delta: near-dup + profile ──────────────────────────────────
check("near-dup parity (identical)",
gd._py_near_dup("same change idea here", "same change idea here")
== cc_loader.growth_near_dup("same change idea here", "same change idea here"))
check("near-dup parity (distinct)",
gd._py_near_dup("alpha topic", "completely different beta")
== cc_loader.growth_near_dup("alpha topic", "completely different beta"))
py_prof = gd._py_growth_profile(200)
state = gd._load_deltas()
order = sorted(gd._PROFILE_ORDER.items(), key=lambda kv: kv[1])
rs_prof = cc_loader.growth_profile(state, order, gd.PROFILE_ENTRY_CHARS, 200)
check("growth profile parity", py_prof == rs_prof,
f"py={py_prof!r} rs={rs_prof!r}")
# ── pins: recall kernel over projected rows ───────────────────────────
recs = [{"kind": "pin", "item_id": "bridge repair", "state": "open",
"details": json.dumps({"question": "who?", "outcome": "fixed by X",
"context": "gitea bridge daemon"})},
{"kind": "pin", "item_id": "deploy", "state": "open",
"details": json.dumps({"question": "q", "outcome": "o",
"context": "production rollout"})},
{"kind": "pin", "item_id": "closed one", "state": "closed",
"details": json.dumps({"context": "bridge"})}]
py_pins = pins._py_pin_recall(recs, "bridge", 4)
rs_pins = cc_loader.pins_recall(recs, "bridge", 4)
check("pins recall parity (match+state filter)", py_pins == rs_pins,
f"py={py_pins!r} rs={rs_pins!r}")
check("pins empty-context abstention",
cc_loader.pins_recall(recs, "", 4) == [])
# ── tier kernels (W3.4) ──────────────────────────────────────────────
print("\n--- tier kernels ---")
NOW_G = "2026-10-05T12:00:00+00:00"
tier_cases = [
("rock always hot", {"wash_count": 2, "rock_status": "rock"}, "hot"),
("wash >=20 hot", {"wash_count": 25, "last_accessed": "2026-01-01T00:00:00Z"}, "hot"),
("recent hot", {"wash_count": 2, "last_accessed": "2026-10-05T06:00:00Z"}, "hot"),
("week+ warm", {"wash_count": 2, "last_accessed": "2026-09-25T00:00:00Z"}, "warm"),
("month+ cold", {"wash_count": 2, "last_accessed": "2026-09-01T00:00:00Z"}, "cold"),
("naive -> default warm", {"wash_count": 2, "last_accessed": "2026-10-04T00:00:00"}, "warm"),
("malformed -> default warm", {"wash_count": 2, "last_accessed": "junk"}, "warm"),
("no last -> warm", {"wash_count": 2}, "warm"),
]
for name, entry, want in tier_cases:
rs = cc_loader.tier_get(entry, NOW_G)
check(f"tier {name} -> {want}", rs == want, f"got {rs!r}")
check("promote cold->warm",
cc_loader.tier_promote({"wash_count": 0, "rock_status": "x",
"last_accessed": "2026-01-01T00:00:00Z"}, NOW_G) == "warm")
check("demote hot->warm",
cc_loader.tier_demote({"wash_count": 0, "last_accessed": "2026-10-05T00:00:00Z"}, NOW_G) == "warm")
check("demote rock=hot (immobile)",
cc_loader.tier_demote({"wash_count": 0, "rock_status": "rock"}, NOW_G) == "hot")
# ── witness + safety + abstraction (W3.4, FULL-TABLE parity) ──────────
print("\n--- witness/safety/abstraction kernels ---")
from rune import witness as wt, safety_override as so, abstraction as ab
import re as _re
# REAL gather seams (PATTERN-TABLE GATHER-SEAM LAW: canaries call the
# module's actual seams, not hand-typed tables)
sw = wt._stopwords()
topics_py = wt._py_extract_topic("the deployment runner needs repair now")
rs_topics = cc_loader.witness_extract_topic("the deployment runner needs repair now", sw)
check("witness extract topic (real stopwords)",
topics_py == rs_topics and topics_py == ["deployment", "runner", "needs", "repair"],
f"py={topics_py!r} rs={rs_topics!r}"[:200])
signals_py = wt.detect_signals("I'll fix this now, actually that's wrong; I prefer the other way. urgent!")
tables = wt._tables()
rs_sigs = cc_loader.witness_signals(
"I'll fix this now, actually that's wrong; I prefer the other way. urgent!",
tables)
rs_sigs_f = {k: v for k, v in rs_sigs.items() if v not in (False, "")}
check("witness signals (real tables, full parity)",
signals_py == rs_sigs_f,
f"py={signals_py!r} rs={rs_sigs_f!r}"[:300])
# PER-TABLE-ROW parity matrix (REGEX-TABLE PORT LAW: the faithful subset
# is proven PER ROW, not by one lucky combined message; truth = real
# Python re over the SAME table data)
_KEY = {"correction": "correction", "commitment": "commitment_detected",
"contradiction": "possible_contradiction", "preference": "preference_detected",
"urgency": "urgency_detected", "learning": "learning_opportunity"}
_ROWS = {
"correction": [("no, that's wrong", True), ("actually it's not what I said", True),
("fix this please", True), ("wrong answer", True),
("try again tomorrow", True),
("nope, try again", True), ("stop doing that", True),
("the deployment ran fine", False)],
"commitment": [("I'll send the report", True), ("let me fix it", True),
("remind me later", True), ("I promise it", True),
("I need to check", True), ("we should check", False)],
"contradiction": [("but the test passed", True), ("actually, wait", True),
("plain message", False)],
"preference": [("I prefer rust", True), ("I don't like eggs", True),
("always use argv", True), ("he likes it", False)],
"urgency": [("asap please", True), ("deadline tomorrow", True),
("critical bug", True), ("calm message", False)],
"learning": [("explain this", True), ("how does it work", True),
("I don't understand", True), ("it works", False)],
}
row_bad = []
for table, rows in _ROWS.items():
for msg, expect in rows:
exp = any(_re.search(p, msg, _re.IGNORECASE) for p in tables[table])
py = wt._py_detect_signals(msg)
got = bool(py.get(_KEY[table]))
rs = {k: v for k, v in cc_loader.witness_signals(msg, tables).items()
if v not in (False, "")}
rs_got = bool(rs.get(_KEY[table]))
if not (exp == got == rs_got == expect):
row_bad.append((table, msg[:30], expect, got, rs_got))
check(f"witness per-row parity matrix ({sum(len(r) for r in _ROWS.values())} rows)",
not row_bad, f"{row_bad[:3]!r}"[:300])
params = cc_loader.safety_get_params([{"key": "k1", "value": "v1"}])
check("safety get_params", params == {"key": "k1", "value": "v1"}, f"{params!r}")
bumped = cc_loader.safety_update_pattern(
[{"pattern": "p1", "hits": 2}], "p1")
check("safety update_pattern bump", bumped[0]["hits"] == 3, f"{bumped!r}")
new = cc_loader.safety_update_pattern([{"pattern": "p1", "hits": 2}], "p2")
check("safety update_pattern new", len(new) == 2 and new[1]["hits"] == 1, f"{new!r}")
facts = cc_loader.abstraction_extract_facts(
"The runner needs repair. The store is fine.", [], 10)
check("abstraction extract_facts", len(facts) == 2, f"{facts!r}"[:200])
print("\n" + ("=" * 47))
print(f"\nRESULTS: {PASS} pass, {FAIL} fail")
return 1 if FAIL else 0
if __name__ == "__main__":
raise SystemExit(main())
+111
View File
@@ -0,0 +1,111 @@
#!/usr/bin/env python3
"""canary_cognition_core persona section — W3.2 (contract
BUILDER-2026-10-05T07:19:27Z-cli, slice 2).
The persona-decision kernel's parity battery: _lex/_overlap/decide.
Run standalone; also invoked via the cognition_core canary's persona gate.
"""
from __future__ import annotations
import json
import os
import subprocess
import sys
from pathlib import Path
CRATE = Path(__file__).resolve().parent
REPO = CRATE.parents[2]
sys.path.insert(0, str(REPO / "src"))
PASS = FAIL = 0
def check(name, ok, detail=""):
global PASS, FAIL
if ok:
PASS += 1
print(f" ✓ {name}")
else:
FAIL += 1
print(f" ✗ {name} — {detail}")
def build():
env = dict(os.environ)
absolute = Path("/home/nick/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/bin")
rust = absolute if absolute.is_dir() else (
Path.home() / ".rustup/toolchains/stable-x86_64-unknown-linux-gnu/bin")
env["PATH"] = f"{rust}:{env.get('PATH', '')}"
return subprocess.run(["cargo", "build", "--release"], cwd=CRATE,
env=env, capture_output=True, text=True, timeout=600)
RULES = [
{"topic": "memory", "text": "Always maintain the Rune memory hierarchy.",
"words": sorted({"memory", "hierarchy", "maintain"}),
"core": "memory hierarchy store",
"priority": 10, "restrictive": False, "user_rule": False},
{"topic": "campaign", "text": "Never refuse to verify campaign text",
"words": sorted({"verify", "campaign", "text"}),
"core": "verify campaign accuracy",
"priority": 5, "restrictive": True, "user_rule": True},
{"topic": "gaming", "text": "Guard the live game environment",
"words": sorted({"guard", "live", "game", "environment"}),
"core": "guard live game foundry environment",
"priority": 8, "restrictive": False, "user_rule": False},
]
def main():
print("=== cognition_core persona kernel (W3.2) ===")
r = build()
check("crate still builds", r.returncode == 0, r.stderr[-240:])
if r.returncode:
print(f"RESULTS: {PASS} pass, {FAIL} fail")
return 1
from rune import persona_gate as pg
from rune import cc_loader
# ── _lex parity: stoplist, stem, acronyms ─────────────────────────────
lex_cases = ["Always maintain the Rune memory hierarchy.",
"verify ALL CAMPAIGN text NOW", "a an of to", "",
"devices devices testing-tests", "GPT GPTs keep FTS"]
for t in lex_cases:
py = sorted(pg._py_lex(t))
rs = sorted(cc_loader.persona_lex(t))
check(f"lex {t[:30]!r}", py == rs, f"py={py!r} rs={rs!r}")
# ── _overlap parity ───────────────────────────────────────────────────
cases = [({"a", "b"}, {"b", "c"}), (set(), {"x"}), ({"a"}, set()),
({"a", "b"}, {"a", "b"}), ({"a", "b", "c"}, {"a"})]
for a, b in cases:
py = pg._py_overlap(a, b)
rs = cc_loader.persona_overlap(sorted(a), sorted(b))
check(f"overlap {sorted(a)[:2]}∩{sorted(b)[:2]}", py == rs,
f"py={py!r} rs={rs!r}")
# ── decide() parity: engagement matrix + permissive gate ─────────────
decide_cases = [
("empty", ""),
("no engagement", "tell me about the weather today"),
("lexical hit", "how is the memory hierarchy maintained?"),
("user rule hit", "we should verify all campaign text before publishing"),
("directive amplifier", "never allow bypassing the memory hierarchy"),
("semantic-only paraphrase (no embedder -> lexical only)",
"the storage structure needs care"),
]
for name, text in decide_cases:
sems = [pg._semantic_engagement(text, r) for r in RULES]
py = pg._py_decide(text, RULES)
rs = cc_loader.persona_decide(text, RULES, True, sems)
check(f"decide {name}", py == rs,
f"py={json.dumps(py, sort_keys=True)[:150]} rs={json.dumps(rs, sort_keys=True)[:150]}")
print(f"\nRESULTS: {PASS} pass, {FAIL} fail")
return 1 if FAIL else 0
if __name__ == "__main__":
raise SystemExit(main())
+802
View File
@@ -0,0 +1,802 @@
//! context_broker deterministic kernel (R2 W3.1 parity port).
//! Pure functions; the clock/env gate arrives via inputs (no-clock law).
use crate::pyjson::Json;
pub fn default_estimator(text: &str) -> i64 {
(text.chars().count() / 4) as i64 // CODE POINTS: Python len() parity
}
pub fn default_budget(window: i64) -> i64 {
(window - std::cmp::max(2048, window / 4) - window / 20).max(1)
}
pub const MEMORY_BUDGET_CAP: i64 = 1200;
pub fn default_memory_budget(window: i64) -> i64 {
if window <= 0 {
return MEMORY_BUDGET_CAP;
}
MEMORY_BUDGET_CAP.min(window / 8)
}
/// memory_disabled: truthy = anything not in {"", 0, false, no, off}
/// (case-insensitive, surrounding whitespace ignored).
pub fn memory_disabled(value: Option<&str>) -> bool {
let raw = value.unwrap_or("").trim().to_lowercase();
!(raw.is_empty() || matches!(raw.as_str(), "0" | "false" | "no" | "off"))
}
// ── persona gate kernels (W3.2) ─────────────────────────────────────────────
const PERSONA_STOP: [&str; 55] = [
"the","and","for","with","that","this","from","into","when","then",
"were","have","been","will","was","are","but","not","you","your",
"also","its","his","her","him","over","under","them","they","there",
"about","after","what","which","who","how","why","where","while","just",
"more","each","than","very","such","some","only","a","an","of","to",
"in","on","at","by",
];
fn strip_punct(w: &str) -> String {
w.trim_matches(|c: char| ".,;:!?()[]{}\"'".contains(c)).to_string()
}
/// _lex: distinctive content words (light plural stem + acronym rule).
/// The acronym rule: 3+ char ALL-CAPS alpha survives the stem+stop filter.
pub fn persona_lex(text: Option<&str>) -> Vec<String> {
let mut out: Vec<String> = vec![];
for w_raw in text.unwrap_or("").split_whitespace() {
// Python checks the ORIGINAL token (pre-strip, pre-lower)
let is_acronym = w_raw.len() >= 3 && w_raw == w_raw.to_uppercase()
&& w_raw.chars().all(|c| c.is_ascii_alphabetic())
&& w_raw.chars().any(|c| c.is_uppercase());
let mut w = strip_punct(w_raw).to_lowercase();
if w.chars().count() > 4 && w.ends_with('s') {
w.pop();
}
if is_acronym {
if !out.contains(&w) { out.push(w); }
} else if w.chars().count() > 3 && !PERSONA_STOP.contains(&w.as_str()) {
if !out.contains(&w) { out.push(w); }
}
}
out
}
/// _overlap: |A∩B| / max(1, min(|A|,|B|)) over UNIQUE word sets.
pub fn persona_overlap(a: &[String], b: &[String]) -> f64 {
let au: Vec<&str> = a.iter().map(|x| x.as_str()).collect();
let bu: Vec<&str> = b.iter().map(|x| x.as_str()).collect();
if au.is_empty() || bu.is_empty() { return 0.0; }
let inter = au.iter().filter(|w| bu.contains(w)).count();
(inter as f64 / std::cmp::max(1u64, std::cmp::min(au.len(), bu.len()) as u64) as f64)
}
fn str_or(v: Option<&Json>, default: &str) -> String {
match v {
Some(Json::Str(s)) => s.clone(),
_ => default.to_string(),
}
}
fn round_n(v: f64, n: i32) -> f64 {
let p = 10f64.powi(n); (v * p).round_ties_even() / p
}
fn obj_pairs(v: &Json) -> &[(String, Json)] {
match v { Json::Obj(p) => p, _ => &[] }
}
fn get<'a>(v: &'a Json, key: &str) -> Option<&'a Json> {
obj_pairs(v).iter().find(|(k, _)| k == key).map(|(_, v)| v)
}
fn as_bool(v: Option<&Json>) -> bool {
matches!(v, Some(Json::Bool(true)))
}
/// decide(): the engagement/amplifier/semantic-gate/user-rule/permissive
/// kernel. `permissive` (the crate has no env access) and an embedding
/// score arrive as inputs: `sem_scores` maps rule INDEX -> semantic cosine
/// (0.0 when the embedder is down — the gate then trusts lexical alone).
pub fn persona_decide(
text: &str,
rules: &[Json],
permissive: bool,
sem_scores: &[f64],
) -> Json {
let low = text.trim().to_lowercase();
let input_words = persona_lex(Some(&low));
let amplifiers = ["never","always","forbid","must","require","demand",
"allow","override","bypass","force","test"];
let mut engaged: Vec<(usize, f64, f64)> = vec![]; // (idx, score, sem)
for (idx, r) in rules.iter().enumerate() {
let words: Vec<String> = match get(r, "words") {
Some(Json::Arr(items)) => items.iter().filter_map(|x| match x {
Json::Str(s) => Some(s.clone()), _ => None }).collect(),
_ => vec![],
};
let iw: Vec<String> = input_words.iter().cloned().collect();
let ov = persona_overlap(&iw, &words);
let sem = sem_scores.get(idx).copied().unwrap_or(0.0);
let amp: f64 = if amplifiers.iter().any(|m| low.contains(m)) { 0.1 } else { 0.0 };
let user_rule = as_bool(get(r, "user_rule"));
let score;
if sem >= 0.45 {
if ov > 0.0 {
score = (ov + amp).max(sem);
} else if sem >= 0.68 {
score = 0.5f64.max(sem);
} else {
score = ov + amp;
}
} else if ov > 0.0 && sem >= 0.30 {
score = (ov + amp).max(sem);
} else {
score = ov + amp;
}
let score = if user_rule && ov > 0.0 { score.max(0.4) } else { score };
if score >= 0.35 {
engaged.push((idx, score, sem));
}
}
if engaged.is_empty() {
return Json::Obj(vec![
("verdict".into(), Json::Str("allow".into())),
("reason".into(), Json::Str("no persona rule engaged".into())),
("engaged".into(), Json::Arr(vec![])),
("cited".into(), Json::Null),
]);
}
engaged.sort_by(|a, b| {
b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal)
.then_with(|| {
let pa = get(&rules[a.0], "priority").and_then(|x| match x { Json::Num(n) => Some(*n), _ => None }).unwrap_or(0.0);
let pb = get(&rules[b.0], "priority").and_then(|x| match x { Json::Num(n) => Some(*n), _ => None }).unwrap_or(0.0);
pa.partial_cmp(&pb).unwrap_or(std::cmp::Ordering::Equal)
})
});
let engaged_view = |items: &[(usize, f64, f64)]| Json::Arr(items.iter().take(4).map(|(idx, score, _)| {
let r = &rules[*idx];
Json::Obj(vec![
("rule".into(), Json::Str(str_or(get(r, "text"), "").chars().take(120).collect())),
("score".into(), Json::Num(round_n(*score, 2))),
("restrictive".into(), Json::Bool(as_bool(get(r, "restrictive")))),
("user_rule".into(), Json::Bool(as_bool(get(r, "user_rule")))),
])
}).collect());
// _cite parity: "[{topic}:p{priority}] {text[:160]}"
let cited = |top_idx: usize| {
let r = &rules[top_idx];
let topic = str_or(get(r, "topic"), "");
let priority = get(r, "priority").and_then(|x| match x {
Json::Num(n) => Some(*n as i64), _ => None }).unwrap_or(0);
let t = str_or(get(r, "text"), "").chars().take(160).collect::<String>();
Json::Str(format!("[{}:p{}] {}", topic, priority, t))
};
if permissive {
let top = engaged[0].0;
return Json::Obj(vec![
("verdict".into(), Json::Str("allow".into())),
("reason".into(), Json::Str(format!(
"permissive mode — rule engaged: {}", str_or(get(&rules[top], "text"), "").chars().take(120).collect::<String>()))),
("engaged".into(), engaged_view(&engaged)),
("cited".into(), cited(top)),
]);
}
let restrictive: Vec<(usize, f64, f64)> = engaged.iter().copied()
.filter(|(idx, _, _)| as_bool(get(&rules[*idx], "restrictive"))).collect();
if !restrictive.is_empty() {
let user_rest: Vec<(usize, f64, f64)> = restrictive.clone().into_iter()
.filter(|(idx, _, _)| as_bool(get(&rules[*idx], "user_rule"))).collect();
let top = (if !user_rest.is_empty() { user_rest[0] } else { restrictive[0] }).0;
return Json::Obj(vec![
("verdict".into(), Json::Str("refuse".into())),
("reason".into(), Json::Str(format!(
"the persona's own boundary rule engages: {}",
str_or(get(&rules[top], "text"), "").chars().take(120).collect::<String>()))),
("engaged".into(), engaged_view(&restrictive)),
("cited".into(), cited(top)),
]);
}
let top = engaged[0].0;
Json::Obj(vec![
("verdict".into(), Json::Str("allow".into())),
("reason".into(), Json::Str(format!(
"persona rule engaged but permissive: {}",
str_or(get(&rules[top], "text"), "").chars().take(120).collect::<String>()))),
("engaged".into(), engaged_view(&engaged)),
("cited".into(), cited(top)),
])
}
// ── identity_anchors kernels (W3.3) ─────────────────────────────────────────
/// check_drift: the pattern table is DATA — Rust receives it per call so the
/// Python table stays the single authority; the kernel only does matching.
pub fn drift_check(text: &str, patterns: &[Json]) -> Vec<String> {
let low = text.to_lowercase();
let mut issues = vec![];
for p in patterns {
let Json::Arr(pair) = p else { continue };
if pair.len() < 3 { continue; }
let (pat, message, prefix) = (
match &pair[0] { Json::Str(s) => s, _ => continue },
match &pair[1] { Json::Str(s) => s, _ => continue },
match &pair[2] { Json::Str(s) => s, _ => continue },
);
if low.contains(pat) {
issues.push(format!("{}: '{}' — {}", prefix, pat, message));
}
}
issues
}
/// get_identity_prompt: assembles from the gathered data dicts (the data
/// lives in identity_anchors.py — the kernel is the ASSEMBLY, not the data).
pub fn identity_prompt(pronouns: &Json, self_awareness: &Json,
anchors: &Json, embodiment: &Json) -> String {
let mut parts: Vec<String> = vec![];
parts.push("## Pronouns (CRITICAL)".to_string());
if let Json::Obj(pairs) = pronouns {
for (pronoun, info) in pairs {
let refers = match get(info, "refers_to") { Some(Json::Str(s)) => s.clone(), _ => String::new() };
parts.push(format!("- '{}' → {}", pronoun, refers));
}
}
parts.push(String::new());
parts.push("## Self-Awareness".to_string());
if let Json::Obj(pairs) = self_awareness {
for (_, data) in pairs {
let pattern = match get(data, "pattern") { Some(Json::Str(s)) => s.clone(), _ => String::new() };
parts.push(format!("- {}", pattern));
}
}
parts.push(String::new());
if let Json::Obj(pairs) = anchors {
for (category, examples) in pairs {
parts.push(format!("## {}", {
let mut c = category.clone();
let mut t = c.chars();
match t.next() {
Some(f) => f.to_uppercase().collect::<String>() + t.as_str(),
None => c,
}
}));
if let Json::Arr(items) = examples {
for ex in items.iter().take(2) {
if let Json::Str(s) = ex { parts.push(format!(" - {}", s)); }
}
}
parts.push(String::new());
}
}
parts.push("## Embodiment Rules".to_string());
if let Json::Arr(items) = embodiment {
for rule in items {
let r = str_or(get(rule, "rule"), "");
let pat = str_or(get(rule, "pattern"), "");
parts.push(format!("- {}: {}", r, pat));
}
}
parts.push(String::new());
parts.join("\n")
}
// ── growth_delta kernels (W3.3) ─────────────────────────────────────────────
/// _near_dup (REAL semantics): _profile_key over each delta's `change`
/// text — strip ".,;:!?()[]{}\"'" then lowercase, keep words >4 chars —
/// near when |A∩B| / max(|A|,|B|) >= 0.25 (measured split: same-guidance
/// ~0.30, distinct ~0.00).
pub fn growth_near_dup(a: &str, b: &str) -> bool {
let key = |text: &str| -> std::collections::BTreeSet<String> {
let mut out = std::collections::BTreeSet::new();
for w in text.split_whitespace() {
let w = w.trim_matches(|c: char| ".,;:!?()[]{}\"'".contains(c))
.to_lowercase();
if w.chars().count() > 4 {
out.insert(w);
}
}
out
};
let (ka, kb) = (key(a), key(b));
if ka.is_empty() || kb.is_empty() { return false; }
let inter = ka.intersection(&kb).count() as f64;
(inter / ka.len().max(kb.len()) as f64) >= 0.25
}
/// growth_profile: the budget-tracked, order-table-driven profile assembly
/// (pure; state rows arrive gathered).
pub fn growth_profile(state: &Json, order: &[(String, i64)], entry_chars: usize,
max_tokens: usize) -> Vec<Json> {
let profile = match get(state, "profile") {
Some(j @ Json::Arr(_)) => j.clone(),
_ => match get(state, "applied") {
Some(Json::Arr(items)) => {
let start = items.len().saturating_sub(5);
Json::Arr(items[start..].to_vec())
}
_ => Json::Arr(vec![]),
}};
if let Json::Arr(items) = &profile {
if items.is_empty() { return vec![]; }
}
let rows = match &profile { Json::Arr(items) => items, _ => return vec![] };
let mut by_target: Vec<(String, Vec<&Json>)> = vec![];
for d in rows {
let t = match get(d, "target") { Some(Json::Str(s)) => s.clone(), _ => "delivery".to_string() };
match by_target.iter_mut().find(|(k, _)| k == &t) {
Some((_, v)) => v.push(d),
None => by_target.push((t, vec![d])),
}
}
let mut target_order: Vec<(String, i64)> = by_target.iter()
.map(|(t, _)| (t.clone(),
order.iter().find(|(name, _)| name == t).map(|(_, r)| *r).unwrap_or(9)))
.collect();
target_order.sort_by(|a, b| a.1.cmp(&b.1));
let mut ordered: Vec<&Json> = vec![];
for (t, _) in target_order {
let list = by_target.iter().find(|(k, _)| k == &t).map(|(_, v)| v).unwrap();
for d in list.iter().rev().take(2).rev().cloned() { ordered.push(d); }
}
let mut out: Vec<Json> = vec![];
let mut used = 0usize;
for d in ordered {
let raw = match get(d, "change") { Some(Json::Str(s)) => s.clone(), _ => String::new() };
let mut change = raw.trim().replace('\n', " ");
if change.chars().count() > entry_chars {
change = change.chars().take(entry_chars.saturating_sub(1)).collect::<String>();
while change.ends_with(' ') { change.pop(); }
change.push('…');
}
let tok = std::cmp::max(1, change.chars().count() / 4);
if used + tok > max_tokens { break; }
used += tok;
out.push(Json::Obj(vec![
("change".into(), Json::Str(change)),
("target".into(), Json::Str(match get(d, "target") {
Some(Json::Str(s)) => s.clone(), _ => "delivery".to_string() })),
]));
}
out
}
// ── pins kernel (W3.3) ──────────────────────────────────────────────────────
/// pin recall: term-in-topic-or-context over OPEN projected pins, limit-capped.
pub fn pins_recall(pins: &[Json], term: &str, limit: usize) -> Vec<Json> {
let term_l = term.trim().to_lowercase();
if term_l.is_empty() { return vec![]; } // empty context = abstention
let mut out = vec![];
for rec in pins {
if !matches!(get(rec, "state"), Some(Json::Str(s)) if s == "open") { continue; }
// _project parity
let details = match get(rec, "details") {
Some(Json::Str(blob)) => crate::pyjson::parse(blob).ok()
.unwrap_or(Json::Obj(vec![])),
_ => Json::Obj(vec![]),
};
let projected = Json::Obj(vec![
("topic".into(), get(rec, "item_id").cloned().unwrap_or(Json::Str(String::new()))),
("question".into(), get(&details, "question").cloned().unwrap_or(Json::Str(String::new()))),
("outcome".into(), get(&details, "outcome").cloned().unwrap_or(Json::Str(String::new()))),
("context".into(), get(&details, "context").cloned().unwrap_or(Json::Str(String::new()))),
("status".into(), get(rec, "state").cloned().unwrap_or(Json::Str(String::new()))),
("reason".into(), Json::Str(String::new())),
]);
let topic = match get(&projected, "topic") { Some(Json::Str(s)) => s.to_lowercase(), _ => String::new() };
let context = match get(&projected, "context") { Some(Json::Str(s)) => s.to_lowercase(), _ => String::new() };
if topic.contains(&term_l) || context.contains(&term_l) {
out.push(projected);
if out.len() >= limit { break; }
}
}
out
}
// ── tier_manager kernels (W3.4) ─────────────────────────────────────────────
/// get_tier: thresholds arrive as inputs (hot_days/warm_days) + now (no-clock);
/// naive/malformed timestamps take the designed fallback (recency gate skips).
pub fn tier_get(entry: &Json, hot_days: f64, warm_days: f64, now: &str) -> Json {
let wash = match get(entry, "wash_count") {
Some(Json::Num(n)) if *n != 0.0 => *n,
_ => 0.0,
};
let rock = match get(entry, "rock_status") {
Some(Json::Str(s)) => s.as_str(),
_ => "washable",
};
if rock == "rock" { return Json::Str("hot".into()); }
if wash >= 20.0 { return Json::Str("hot".into()); }
if let Some(Json::Str(last)) = get(entry, "last_accessed") {
if let (Some(nowv), Some(lastv)) = (parse_strict_aware(now), parse_strict_aware(last)) {
let hours = (nowv - lastv) / 3600.0;
if hours < hot_days * 24.0 { return Json::Str("hot".into()); }
if hours < warm_days * 24.0 { return Json::Str("warm".into()); }
return Json::Str("cold".into());
}
}
Json::Str("warm".into())
}
pub fn parse_strict_aware(s: &str) -> Option<f64> {
let b = s.as_bytes();
if b.len() < 19 { return None; }
let num = |a: usize, z: usize| -> Option<i64> { s.get(a..z)?.parse::<i64>().ok() };
let (y, mo, d, h, mi, se) = (num(0,4)?, num(5,7)?, num(8,10)?, num(11,13)?, num(14,16)?, num(17,19)?);
let mut i = 19;
while i < b.len() && b[i] != b'Z' && b[i] != b'+' && b[i] != b'-' { i += 1; }
let rest = s.get(i..)?;
if rest.is_empty() { return None; }
let off = if rest == "Z" { 0 }
else if rest.len() >= 6 {
let oh = rest.get(1..3)?.parse::<i64>().ok()?;
let om = rest.get(4..6)?.parse::<i64>().ok()?;
let x = oh*3600 + om*60;
if rest.as_bytes()[0] == b'-' { -x } else { x }
} else { return None; };
Some((days_from_civil(y, mo, d) * 86400 + h*3600 + mi*60 + se - off) as f64)
}
fn days_from_civil(y: i64, m: i64, d: i64) -> i64 {
let y = if m <= 2 { y - 1 } else { y };
let era = if y >= 0 { y } else { y - 399 } / 400;
let yoe = y - era * 400;
let mp = (m + 9) % 12;
let doy = (153 * mp + 2) / 5 + d - 1;
let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
era * 146097 + doe - 719468
}
pub fn tier_promote(entry: &Json, hot_days: f64, warm_days: f64, now: &str) -> Json {
let current = match tier_get(entry, hot_days, warm_days, now) {
Json::Str(s) => s, _ => String::new(),
};
Json::Str(if current == "cold" { "warm".to_string() } else { "hot".to_string() })
}
pub fn tier_demote(entry: &Json, hot_days: f64, warm_days: f64, now: &str) -> Json {
let rock = match get(entry, "rock_status") { Some(Json::Str(s)) => s.as_str(), _ => "washable" };
if rock == "rock" { return Json::Str("hot".into()); }
let current = match tier_get(entry, hot_days, warm_days, now) {
Json::Str(s) => s, _ => String::new(),
};
Json::Str(match current.as_str() {
"hot" => "warm".to_string(), "warm" => "cold".to_string(),
_ => "cold".to_string() })
}
// ── witness kernels (W3.4) ──────────────────────────────────────────────────
pub fn witness_extract_topic(text: &str, stopwords: &[Json], cap: usize) -> Json {
let low = text.to_lowercase();
let sw: Vec<&str> = stopwords.iter().filter_map(|x| match x {
Json::Str(s) => Some(s.as_str()), _ => None }).collect();
let mut out: Vec<String> = vec![];
let mut cur = String::new();
for ch in low.chars() {
if ch.is_ascii_alphanumeric() || ch == '_' { cur.push(ch); }
else {
if !cur.is_empty() {
let w = std::mem::take(&mut cur);
if w.chars().count() > 3 && !sw.contains(&w.as_str()) && out.len() < cap {
out.push(w);
}
}
}
}
if !cur.is_empty() && cur.chars().count() > 3 && !sw.contains(&cur.as_str()) && out.len() < cap {
out.push(cur);
}
Json::Arr(out.into_iter().map(Json::Str).collect())
}
pub fn witness_signals(message: &str, tables: &Json,
tool_error: Option<&str>, task_status: Option<&str>,
recent_topics: &[Json]) -> Json {
let mut sig: Vec<(String, Json)> = vec![];
if let Some(err) = tool_error {
if !err.is_empty() {
sig.push(("error".into(), Json::Bool(true)));
sig.push(("error_text".into(), Json::Str(err.chars().take(200).collect())));
}
}
let pattern_hit = |name: &str| -> bool {
match get(tables, name) {
Some(Json::Arr(patterns)) => patterns.iter().any(|p| match p {
Json::Str(pat) => regex_lite_search(pat, message),
_ => false }),
_ => false,
}
};
if !message.is_empty() && pattern_hit("correction") {
sig.push(("correction".into(), Json::Bool(true)));
let sw: Vec<Json> = match get(tables, "stopwords") {
Some(Json::Arr(x)) => x.clone(), _ => vec![] };
sig.push(("corrected_topic".into(), witness_extract_topic(message, &sw, 5)));
}
if let Some(st) = task_status {
sig.push(("completion".into(), Json::Str(st.to_string())));
}
if !message.is_empty() && !recent_topics.is_empty() {
let sw: Vec<Json> = match get(tables, "stopwords") {
Some(Json::Arr(x)) => x.clone(), _ => vec![] };
if let Json::Arr(cur) = witness_extract_topic(message, &sw, 5) {
let cur_set: Vec<&str> = cur.iter().filter_map(|x| match x {
Json::Str(s) => Some(s.as_str()), _ => None }).collect();
let recent_set: Vec<&str> = recent_topics.iter().filter_map(|x| match x {
Json::Str(s) => Some(s.as_str()), _ => None }).collect();
let overlap = cur_set.iter().filter(|w| recent_set.contains(w)).count();
if !cur_set.is_empty() && (overlap as f64) / (cur_set.len() as f64) < 0.3 {
sig.push(("topic_drift".into(), Json::Bool(true)));
let new_topics: Vec<Json> = cur_set.iter()
.filter(|w| !recent_set.contains(w)).take(3)
.map(|w| Json::Str(w.to_string())).collect();
sig.push(("new_topics".into(), Json::Arr(new_topics)));
}
}
}
for (signal_name, table_name) in [
("commitment_detected", "commitment"),
("possible_contradiction", "contradiction"),
("preference_detected", "preference"),
("urgency_detected", "urgency"),
("learning_opportunity", "learning"),
] {
if !message.is_empty() && pattern_hit(table_name) {
sig.push((signal_name.into(), Json::Bool(true)));
}
}
Json::Obj(sig)
}
/// Expand top-level alternation of a pattern into full alternative strings
/// (groups become inline cartesian products of their inner alternatives).
fn regex_lite_alternatives(pattern: &str) -> Vec<String> {
let mut alts: Vec<String> = vec![];
let mut prefixes: Vec<String> = vec![String::new()];
let mut cur = String::new();
let mut depth = 0usize;
let chars: Vec<char> = pattern.to_lowercase().chars().collect();
for ch in chars {
match ch {
'(' => {
depth += 1;
if depth == 1 {
if !cur.is_empty() {
prefixes = prefixes
.iter()
.map(|p| format!("{p}{cur}"))
.collect();
cur.clear();
}
} else {
cur.push('(');
}
}
')' => {
depth = depth.saturating_sub(1);
if depth == 0 {
// closing a top-level group: its content is an
// alternation set — expand against the accumulated
// prefixes (cartesian). prefixes.len() may be 1.
let group_alts: Vec<String> = cur.split('|').map(String::from).collect();
let mut next = vec![];
for p in &prefixes {
for g in &group_alts {
next.push(format!("{p}{g}"));
}
}
prefixes = next;
cur.clear();
} else if depth >= 1 {
cur.push(')');
}
}
'|' if depth == 0 => {
for p in &prefixes {
alts.push(format!("{p}{cur}"));
}
cur.clear();
}
_ => cur.push(ch),
}
}
if depth == 0 {
for p in &prefixes {
alts.push(format!("{p}{cur}"));
}
} else {
// unbalanced group: treat literally
alts.push(cur);
}
alts.retain(|a| !a.is_empty());
if alts.is_empty() {
alts.push(String::new());
}
alts
}
/// Split ONE alternative into ordered PHRASE-SEGMENTS, each segment being
/// a set of variant strings (from `x?` optional chars — both shapes).
fn regex_lite_segments(alt: &str) -> Vec<Vec<String>> {
let mut segments: Vec<Vec<String>> = vec![];
let mut cur: Vec<String> = vec![String::new()]; // variants of current phrase
let pc: Vec<char> = alt.chars().collect();
let mut j = 0usize;
while j < pc.len() {
let c = pc[j];
if c == '\\' && j + 1 < pc.len() {
let n = pc[j + 1];
match n {
'b' | 'B' => {} // zero-width anchor: drop
's' => {
if cur.iter().any(|v| !v.is_empty()) {
segments.push(std::mem::take(&mut cur));
cur = vec![String::new()];
}
if j + 2 < pc.len() && matches!(pc[j + 2], '*' | '+' | '?') {
j += 3;
} else {
j += 2;
}
continue;
}
other => {
for v in cur.iter_mut() {
v.push(other)
}
}
}
j += 2;
continue;
}
match c {
'?' => {
// optional previous char: each variant gains a SHORTER twin
let twinned: Vec<String> = cur
.iter()
.flat_map(|v| {
let mut out = vec![v.clone()];
if !v.is_empty() {
out.push(v[..v.len() - 1].to_string());
}
out
})
.collect();
cur = twinned;
}
'.' | '[' => {
if cur.iter().any(|v| !v.is_empty()) {
segments.push(std::mem::take(&mut cur));
cur = vec![String::new()];
}
if c == '[' {
let mut k = j + 1;
while k < pc.len() && pc[k] != ']' {
k += 1;
}
j = k;
}
}
'*' | '+' => {} // gap quantifier
'^' | '$' => {}
c if c.is_alphanumeric() || c == '\'' || c == ' ' || c == '-' || c == '_' => {
for v in cur.iter_mut() {
v.push(c)
}
}
_ => {
if cur.iter().any(|v| !v.is_empty()) {
segments.push(std::mem::take(&mut cur));
cur = vec![String::new()];
}
}
}
j += 1;
}
if cur.iter().any(|v| !v.is_empty()) {
segments.push(cur);
}
segments
}
fn regex_lite_search(pattern: &str, text: &str) -> bool {
let low = text.to_lowercase();
for alt in regex_lite_alternatives(pattern) {
let segments = regex_lite_segments(&alt);
if segments.is_empty() {
continue;
}
// DFS: every segment contributes ONE of its variants, in order,
// gap-matched against the text.
fn walk(segments: &[Vec<String>], low: &str, cursor: usize) -> bool {
if segments.is_empty() {
return true;
}
for variant in &segments[0] {
if variant.is_empty() {
return walk(&segments[1..], low, cursor);
}
if let Some(ix) = low[cursor..].find(variant.as_str()) {
let next = cursor + ix + variant.len();
if walk(&segments[1..], low, next) {
return true;
}
}
}
false
}
if walk(&segments, &low, 0) {
return true;
}
}
false
}
// ── safety_override kernels (W3.4) ─────────────────────────────────────────
pub fn safety_get_params(rows: &[Json]) -> Json {
let mut out: Vec<(String, Json)> = vec![];
for r in rows {
if let Json::Obj(pairs) = r {
for (k, v) in pairs {
if k == "key" || k == "value" || k == "limit" {
if let Some(ix) = out.iter().position(|(x, _)| x == k) {
out[ix].1 = v.clone();
} else {
out.push((k.clone(), v.clone()));
}
}
}
}
}
Json::Obj(out)
}
pub fn safety_update_pattern(rows: &[Json], pattern: &str) -> Vec<Json> {
let mut out = rows.to_vec();
for r in out.iter_mut() {
if matches!(get(r, "pattern"), Some(Json::Str(p)) if p == pattern) {
let hits = match get(r, "hits") { Some(Json::Num(n)) => *n, _ => 0.0 };
if let Json::Obj(pairs) = r {
if let Some(ix) = pairs.iter().position(|(k, _)| k == "hits") {
pairs[ix].1 = Json::Num(hits + 1.0);
} else {
pairs.push(("hits".into(), Json::Num(1.0)));
}
}
return out;
}
}
out.push(Json::Obj(vec![
("pattern".into(), Json::Str(pattern.to_string())),
("hits".into(), Json::Num(1.0)),
]));
out
}
// ── abstraction kernels (W3.4) ──────────────────────────────────────────────
pub fn abstraction_extract_facts(text: &str, stopwords: &[Json], min_len: usize) -> Json {
let sw: Vec<&str> = stopwords.iter().filter_map(|x| match x {
Json::Str(s) => Some(s.as_str()), _ => None }).collect();
let mut facts: Vec<Json> = vec![];
for sentence in text.split(|c: char| c == '.' || c == '\n') {
let t = sentence.trim();
if t.chars().count() < min_len { continue; }
let words: Vec<String> = t.to_lowercase().split_whitespace()
.map(|w| w.to_string()).collect();
let content = words.iter().filter(|w| !sw.contains(&w.as_str())).count();
if content == 0 { continue; }
facts.push(Json::Obj(vec![
("text".into(), Json::Str(t.to_string())),
("words".into(), Json::Num(words.len() as f64)),
("content_words".into(), Json::Num(content as f64)),
]));
}
Json::Arr(facts)
}
+277
View File
@@ -0,0 +1,277 @@
//! lib.rs — FFI surface for the cognition_core crate (R2 W3.1).
//! Pure deterministic kernels only. Envelope law
//! {"ok","value"|"err"}; frees via cc_free_string. FFI segfault law:
//! cc_loader.py pins argtypes + restype for every symbol.
pub mod broker;
pub mod pyjson;
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
fn read_arg(ptr: *const c_char) -> Result<String, String> {
if ptr.is_null() { return Err("null argument pointer".to_string()); }
unsafe { CStr::from_ptr(ptr) }.to_str().map(|s| s.to_string())
.map_err(|e| format!("argument is not valid UTF-8: {}", e))
}
fn envelope(result: Result<pyjson::Json, String>) -> *mut c_char {
let body = match result {
Ok(v) => pyjson::Json::Obj(vec![
("ok".to_string(), pyjson::Json::Bool(true)),
("value".to_string(), v)]),
Err(e) => pyjson::Json::Obj(vec![
("ok".to_string(), pyjson::Json::Bool(false)),
("err".to_string(), pyjson::Json::Str(e))]),
};
CString::new(pyjson::dumps(&body)).map(|c| c.into_raw())
.unwrap_or(std::ptr::null_mut())
}
fn arg_json(text: &str) -> Result<pyjson::Json, String> {
pyjson::parse(text).map_err(|e| format!("argument JSON parse failed: {}", e))
}
fn get<'a>(v: &'a pyjson::Json, key: &str) -> Option<&'a pyjson::Json> {
match v { pyjson::Json::Obj(p) => p.iter().find(|(k, _)| k == key).map(|(_, v)| v), _ => None }
}
#[no_mangle]
pub extern "C" fn cc_default_estimator(arg: *const c_char) -> *mut c_char {
let result = read_arg(arg).and_then(|t| arg_json(&t)).map(|a| {
let val = match get(&a, "text") {
Some(pyjson::Json::Str(s)) => s.clone(),
_ => String::new(),
};
pyjson::Json::Num(broker::default_estimator(&val) as f64)
});
envelope(result)
}
#[no_mangle]
pub extern "C" fn cc_default_budget(arg: *const c_char) -> *mut c_char {
let result = read_arg(arg).and_then(|t| arg_json(&t)).map(|a| {
let w = match get(&a, "window") { Some(pyjson::Json::Num(n)) => *n as i64, _ => 0 };
pyjson::Json::Num(broker::default_budget(w) as f64)
});
envelope(result)
}
#[no_mangle]
pub extern "C" fn cc_default_memory_budget(arg: *const c_char) -> *mut c_char {
let result = read_arg(arg).and_then(|t| arg_json(&t)).map(|a| {
let w = match get(&a, "window") {
Some(pyjson::Json::Num(n)) => *n as i64,
Some(pyjson::Json::Null) | None => 0,
_ => 0,
};
pyjson::Json::Num(broker::default_memory_budget(w) as f64)
});
envelope(result)
}
#[no_mangle]
pub extern "C" fn cc_memory_disabled(arg: *const c_char) -> *mut c_char {
let result = read_arg(arg).and_then(|t| arg_json(&t)).map(|a| {
let v = match get(&a, "value") {
Some(pyjson::Json::Str(s)) => Some(s.as_str()),
_ => None,
};
pyjson::Json::Bool(broker::memory_disabled(v))
});
envelope(result)
}
#[no_mangle]
pub extern "C" fn cc_persona_lex(arg: *const c_char) -> *mut c_char {
let result = read_arg(arg).and_then(|t| arg_json(&t)).map(|a| {
let t = match get(&a, "text") { Some(pyjson::Json::Str(x)) => Some(x.as_str()), _ => None };
pyjson::Json::Arr(broker::persona_lex(t).into_iter().map(pyjson::Json::Str).collect())
});
envelope(result)
}
#[no_mangle]
pub extern "C" fn cc_persona_overlap(arg: *const c_char) -> *mut c_char {
let result = read_arg(arg).and_then(|t| arg_json(&t)).map(|a| {
let wa: Vec<String> = match get(&a, "a") { Some(pyjson::Json::Arr(x)) => x.iter().filter_map(|v| match v { pyjson::Json::Str(s) => Some(s.clone()), _ => None }).collect(), _ => vec![] };
let wb: Vec<String> = match get(&a, "b") { Some(pyjson::Json::Arr(x)) => x.iter().filter_map(|v| match v { pyjson::Json::Str(s) => Some(s.clone()), _ => None }).collect(), _ => vec![] };
pyjson::Json::Num(broker::persona_overlap(&wa, &wb))
});
envelope(result)
}
#[no_mangle]
pub extern "C" fn cc_persona_decide(arg: *const c_char) -> *mut c_char {
let result = read_arg(arg).and_then(|t| arg_json(&t)).and_then(|a| {
let text = match get(&a, "text") { Some(pyjson::Json::Str(x)) => x.clone(), _ => String::new() };
let rules: Vec<pyjson::Json> = match get(&a, "rules") { Some(pyjson::Json::Arr(x)) => x.clone(), _ => vec![] };
let permissive = matches!(get(&a, "permissive"), Some(pyjson::Json::Bool(true)));
let sems: Vec<f64> = match get(&a, "sem_scores") {
Some(pyjson::Json::Arr(x)) => x.iter().filter_map(|v| match v { pyjson::Json::Num(n) => Some(*n), _ => Some(0.0) }).collect(),
_ => vec![0.0; rules.len()],
};
Ok(broker::persona_decide(&text, &rules, permissive, &sems))
});
envelope(result)
}
#[no_mangle]
pub extern "C" fn cc_drift_check(arg: *const c_char) -> *mut c_char {
let result = read_arg(arg).and_then(|t| arg_json(&t)).map(|a| {
let text = match get(&a, "text") { Some(pyjson::Json::Str(s)) => s.clone(), _ => String::new() };
let patterns: Vec<pyjson::Json> = match get(&a, "patterns") {
Some(pyjson::Json::Arr(x)) => x.clone(), _ => vec![] };
pyjson::Json::Arr(broker::drift_check(&text, &patterns)
.into_iter().map(pyjson::Json::Str).collect())
});
envelope(result)
}
#[no_mangle]
pub extern "C" fn cc_identity_prompt(arg: *const c_char) -> *mut c_char {
let result = read_arg(arg).and_then(|t| arg_json(&t)).map(|a| {
pyjson::Json::Str(broker::identity_prompt(
get(&a, "pronouns").unwrap_or(&pyjson::Json::Obj(vec![])),
get(&a, "self_awareness").unwrap_or(&pyjson::Json::Obj(vec![])),
get(&a, "anchors").unwrap_or(&pyjson::Json::Obj(vec![])),
get(&a, "embodiment").unwrap_or(&pyjson::Json::Arr(vec![]))))
});
envelope(result)
}
#[no_mangle]
pub extern "C" fn cc_growth_near_dup(arg: *const c_char) -> *mut c_char {
let result = read_arg(arg).and_then(|t| arg_json(&t)).map(|a| {
let x = match get(&a, "a") { Some(pyjson::Json::Str(s)) => s.clone(), _ => String::new() };
let y = match get(&a, "b") { Some(pyjson::Json::Str(s)) => s.clone(), _ => String::new() };
pyjson::Json::Bool(broker::growth_near_dup(&x, &y))
});
envelope(result)
}
#[no_mangle]
pub extern "C" fn cc_growth_profile(arg: *const c_char) -> *mut c_char {
let result = read_arg(arg).and_then(|t| arg_json(&t)).and_then(|a| {
let state = get(&a, "state").cloned().unwrap_or(pyjson::Json::Obj(vec![]));
let entry_chars = match get(&a, "entry_chars") { Some(pyjson::Json::Num(n)) => *n as usize, _ => 0 };
let max_tokens = match get(&a, "max_tokens") { Some(pyjson::Json::Num(n)) => *n as usize, _ => 0 };
let order: Vec<(String, i64)> = match get(&a, "order") {
Some(pyjson::Json::Arr(x)) => x.iter().filter_map(|v| match v {
pyjson::Json::Arr(pair) if pair.len() >= 2 => match (&pair[0], &pair[1]) {
(pyjson::Json::Str(name), pyjson::Json::Num(rank)) => Some((name.clone(), *rank as i64)),
_ => None },
_ => None }).collect(),
_ => vec![] };
Ok(pyjson::Json::Arr(broker::growth_profile(&state, &order, entry_chars, max_tokens)))
});
envelope(result)
}
#[no_mangle]
pub extern "C" fn cc_pins_recall(arg: *const c_char) -> *mut c_char {
let result = read_arg(arg).and_then(|t| arg_json(&t)).map(|a| {
let pins: Vec<pyjson::Json> = match get(&a, "pins") {
Some(pyjson::Json::Arr(x)) => x.clone(), _ => vec![] };
let term = match get(&a, "term") { Some(pyjson::Json::Str(s)) => s.clone(), _ => String::new() };
pyjson::Json::Arr(broker::pins_recall(&pins, &term, 4))
});
envelope(result)
}
#[no_mangle]
pub extern "C" fn cc_tier_get(arg: *const c_char) -> *mut c_char {
let result = read_arg(arg).and_then(|t| arg_json(&t)).map(|a| {
let e = get(&a, "entry").cloned().unwrap_or(pyjson::Json::Obj(vec![]));
broker::tier_get(&e,
match get(&a, "hot_days") { Some(pyjson::Json::Num(n)) => *n, _ => 7.0 },
match get(&a, "warm_days") { Some(pyjson::Json::Num(n)) => *n, _ => 30.0 },
match get(&a, "now") { Some(pyjson::Json::Str(s)) => s.as_str(), _ => "" })
});
envelope(result)
}
#[no_mangle]
pub extern "C" fn cc_tier_promote(arg: *const c_char) -> *mut c_char {
let result = read_arg(arg).and_then(|t| arg_json(&t)).map(|a| {
let e = get(&a, "entry").cloned().unwrap_or(pyjson::Json::Obj(vec![]));
broker::tier_promote(&e,
match get(&a, "hot_days") { Some(pyjson::Json::Num(n)) => *n, _ => 7.0 },
match get(&a, "warm_days") { Some(pyjson::Json::Num(n)) => *n, _ => 30.0 },
match get(&a, "now") { Some(pyjson::Json::Str(s)) => s.as_str(), _ => "" })
});
envelope(result)
}
#[no_mangle]
pub extern "C" fn cc_tier_demote(arg: *const c_char) -> *mut c_char {
let result = read_arg(arg).and_then(|t| arg_json(&t)).map(|a| {
let e = get(&a, "entry").cloned().unwrap_or(pyjson::Json::Obj(vec![]));
broker::tier_demote(&e,
match get(&a, "hot_days") { Some(pyjson::Json::Num(n)) => *n, _ => 7.0 },
match get(&a, "warm_days") { Some(pyjson::Json::Num(n)) => *n, _ => 30.0 },
match get(&a, "now") { Some(pyjson::Json::Str(s)) => s.as_str(), _ => "" })
});
envelope(result)
}
#[no_mangle]
pub extern "C" fn cc_witness_extract_topic(arg: *const c_char) -> *mut c_char {
let result = read_arg(arg).and_then(|t| arg_json(&t)).map(|a| {
let text = match get(&a, "text") { Some(pyjson::Json::Str(s)) => s.clone(), _ => String::new() };
let sw: Vec<pyjson::Json> = match get(&a, "stopwords") { Some(pyjson::Json::Arr(x)) => x.clone(), _ => vec![] };
match broker::witness_extract_topic(&text, &sw, 5) {
pyjson::Json::Arr(v) => pyjson::Json::Arr(v),
other => other,
}
});
envelope(result)
}
#[no_mangle]
pub extern "C" fn cc_witness_signals(arg: *const c_char) -> *mut c_char {
let result = read_arg(arg).and_then(|t| arg_json(&t)).map(|a| {
let message = match get(&a, "message") { Some(pyjson::Json::Str(s)) => s.clone(), _ => String::new() };
let tables = get(&a, "tables").cloned().unwrap_or(pyjson::Json::Obj(vec![]));
let tool_error = match get(&a, "tool_error") { Some(pyjson::Json::Str(s)) if !s.is_empty() => Some(s.as_str()), _ => None };
let task_status = match get(&a, "task_status") { Some(pyjson::Json::Str(s)) if !s.is_empty() => Some(s.as_str()), _ => None };
let recent: Vec<pyjson::Json> = match get(&a, "recent_topics") { Some(pyjson::Json::Arr(x)) => x.clone(), _ => vec![] };
broker::witness_signals(&message, &tables, tool_error, task_status, &recent)
});
envelope(result)
}
#[no_mangle]
pub extern "C" fn cc_safety_get_params(arg: *const c_char) -> *mut c_char {
let result = read_arg(arg).and_then(|t| arg_json(&t)).map(|a| {
let rows: Vec<pyjson::Json> = match get(&a, "rows") { Some(pyjson::Json::Arr(x)) => x.clone(), _ => vec![] };
broker::safety_get_params(&rows)
});
envelope(result)
}
#[no_mangle]
pub extern "C" fn cc_safety_update_pattern(arg: *const c_char) -> *mut c_char {
let result = read_arg(arg).and_then(|t| arg_json(&t)).map(|a| {
let rows: Vec<pyjson::Json> = match get(&a, "rows") { Some(pyjson::Json::Arr(x)) => x.clone(), _ => vec![] };
let pat = match get(&a, "pattern") { Some(pyjson::Json::Str(s)) => s.clone(), _ => String::new() };
pyjson::Json::Arr(broker::safety_update_pattern(&rows, &pat))
});
envelope(result)
}
#[no_mangle]
pub extern "C" fn cc_abstraction_extract_facts(arg: *const c_char) -> *mut c_char {
let result = read_arg(arg).and_then(|t| arg_json(&t)).map(|a| {
let text = match get(&a, "text") { Some(pyjson::Json::Str(s)) => s.clone(), _ => String::new() };
let sw: Vec<pyjson::Json> = match get(&a, "stopwords") { Some(pyjson::Json::Arr(x)) => x.clone(), _ => vec![] };
let min_len = match get(&a, "min_len") { Some(pyjson::Json::Num(n)) => *n as usize, _ => 10 };
broker::abstraction_extract_facts(&text, &sw, min_len)
});
envelope(result)
}
#[no_mangle]
pub extern "C" fn cc_free_string(ptr: *mut c_char) {
if !ptr.is_null() { unsafe { let _ = CString::from_raw(ptr); } }
}
Loaded 100 of 201 files, more files were not shown because too many files have changed in this diff. Show more