Compare commits

...
30 Commits
Author SHA1 Message Date
conductor db15ae3650 chore(release): auto-bump version to 0.99.2 — earned by # (patch: converged release (push-to-master bump))
Auto-version and Release / release (push) Successful in 2s
CI Pipeline / unit-tests (push) Successful in 4m53s
CI Pipeline / lint (push) Successful in 35s
CI Pipeline / go-surface (push) Successful in 2s
CI Pipeline / canary (push) Successful in 44s
CI Pipeline / best-practices (push) Successful in 1s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / type-check (push) Successful in 27s
CI Pipeline / gitea-offline (push) Successful in 13s
CI Pipeline / required-checks (push) Successful in 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 1s
deploy / deploy (push) Successful in 2m31s
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)
Auto-version and Release / release (push) Successful in 4s
CI Pipeline / go-surface (push) Successful in 2s
CI Pipeline / best-practices (push) Successful in 2s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / lint (push) Successful in 38s
CI Pipeline / canary (push) Successful in 41s
CI Pipeline / gitea-offline (push) Successful in 14s
CI Pipeline / type-check (push) Successful in 30s
CI Pipeline / unit-tests (push) Successful in 4m57s
CI Pipeline / required-checks (push) Successful in 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 2s
deploy / deploy (push) Successful in 5m54s
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)
Auto-version and Release / release (push) Successful in 2s
CI Pipeline / go-surface (push) Successful in 4s
CI Pipeline / best-practices (push) Successful in 2s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / type-check (push) Successful in 28s
CI Pipeline / lint (push) Successful in 38s
CI Pipeline / canary (push) Successful in 46s
CI Pipeline / gitea-offline (push) Successful in 13s
CI Pipeline / unit-tests (push) Canceled after 4m41s
deploy / deploy (push) Failing after 15m4s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 2s
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)
Auto-version and Release / release (push) Successful in 2s
CI Pipeline / lint (push) Failing after 36s
CI Pipeline / go-surface (push) Successful in 3s
CI Pipeline / best-practices (push) Successful in 1s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / canary (push) Successful in 46s
CI Pipeline / gitea-offline (push) Successful in 13s
CI Pipeline / type-check (push) Successful in 27s
CI Pipeline / unit-tests (push) Successful in 4m57s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 2s
deploy / deploy (push) Failing after 3m27s
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))
Auto-version and Release / release (push) Successful in 2s
deploy / deploy (push) Failing after 11s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 2s
CI Pipeline / lint (push) Failing after 36s
CI Pipeline / unit-tests (push) Successful in 4m57s
CI Pipeline / go-surface (push) Successful in 2s
CI Pipeline / canary (push) Successful in 40s
CI Pipeline / best-practices (push) Successful in 2s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / type-check (push) Successful in 29s
CI Pipeline / gitea-offline (push) Successful in 14s
CI Pipeline / required-checks (push) Failing after 1s
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)
Auto-version and Release / release (push) Successful in 4s
CI Pipeline / go-surface (push) Successful in 4s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / type-check (push) Successful in 30s
CI Pipeline / lint (push) Failing after 40s
CI Pipeline / canary (push) Successful in 47s
CI Pipeline / gitea-offline (push) Successful in 13s
CI Pipeline / unit-tests (push) Successful in 5m0s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 2s
deploy / deploy (push) Failing after 38s
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))
Auto-version and Release / release (push) Successful in 3s
deploy / deploy (push) Failing after 3m25s
CI Pipeline / unit-tests (push) Failing after 4m55s
CI Pipeline / lint (push) Failing after 24s
CI Pipeline / go-surface (push) Successful in 2s
CI Pipeline / canary (push) Successful in 42s
CI Pipeline / best-practices (push) Successful in 2s
CI Pipeline / documentation (push) Successful in 1s
CI Pipeline / type-check (push) Successful in 30s
CI Pipeline / gitea-offline (push) Successful in 13s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 2s
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)
CI Pipeline / lint (push) Canceled after 0s
CI Pipeline / unit-tests (push) Canceled after 0s
CI Pipeline / go-surface (push) Canceled after 0s
CI Pipeline / canary (push) Canceled after 0s
CI Pipeline / best-practices (push) Canceled after 0s
CI Pipeline / documentation (push) Canceled after 0s
CI Pipeline / type-check (push) Canceled after 0s
CI Pipeline / gitea-offline (push) Canceled after 0s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
Auto-version and Release / release (push) Successful in 4s
deploy / deploy (push) Failing after 5m4s
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))
CI Pipeline / lint (push) Canceled after 0s
CI Pipeline / unit-tests (push) Canceled after 0s
CI Pipeline / go-surface (push) Canceled after 0s
CI Pipeline / canary (push) Canceled after 0s
CI Pipeline / best-practices (push) Canceled after 0s
CI Pipeline / documentation (push) Canceled after 0s
CI Pipeline / type-check (push) Canceled after 0s
CI Pipeline / gitea-offline (push) Canceled after 0s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
Auto-version and Release / release (push) Canceled after 0s
deploy / deploy (push) Failing after 5m5s
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))
CI Pipeline / lint (push) Canceled after 0s
CI Pipeline / unit-tests (push) Canceled after 0s
CI Pipeline / go-surface (push) Canceled after 0s
CI Pipeline / canary (push) Canceled after 0s
CI Pipeline / best-practices (push) Canceled after 0s
CI Pipeline / documentation (push) Canceled after 0s
CI Pipeline / type-check (push) Canceled after 0s
CI Pipeline / gitea-offline (push) Canceled after 0s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
Auto-version and Release / release (push) Successful in 2s
deploy / deploy (push) Canceled after 21s
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)
Auto-version and Release / release (push) Successful in 3s
CI Pipeline / lint (push) Failing after 22s
CI Pipeline / go-surface (push) Successful in 3s
CI Pipeline / canary (push) Successful in 47s
CI Pipeline / best-practices (push) Successful in 1s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / type-check (push) Successful in 29s
CI Pipeline / gitea-offline (push) Successful in 13s
CI Pipeline / unit-tests (push) Failing after 4m53s
deploy / deploy (push) Canceled after 4m40s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 1s
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)
Auto-version and Release / release (push) Successful in 4s
CI Pipeline / lint (push) Failing after 25s
CI Pipeline / go-surface (push) Successful in 3s
CI Pipeline / canary (push) Successful in 48s
CI Pipeline / best-practices (push) Successful in 2s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / type-check (push) Successful in 29s
CI Pipeline / gitea-offline (push) Successful in 13s
CI Pipeline / unit-tests (push) Failing after 4m56s
CI Pipeline / required-checks (push) Failing after 1s
deploy / deploy (push) Failing after 15m3s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 2s
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))
Auto-version and Release / release (push) Successful in 3s
deploy / deploy (push) Failing after 15m3s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 3s
CI Pipeline / lint (push) Failing after 37s
CI Pipeline / unit-tests (push) Successful in 4m59s
CI Pipeline / go-surface (push) Successful in 3s
CI Pipeline / canary (push) Successful in 45s
CI Pipeline / best-practices (push) Successful in 2s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / type-check (push) Successful in 28s
CI Pipeline / gitea-offline (push) Successful in 14s
CI Pipeline / required-checks (push) Failing after 1s
Promotion record: (unknown)
2026-10-05 23:48:25 +01:00
Nick b8184a0f01 chore(spine): store rebuild carries subsystem language verdicts (#2465)
Auto-version and Release / release (push) Successful in 3s
CI Pipeline / lint (push) Failing after 36s
CI Pipeline / go-surface (push) Successful in 2s
CI Pipeline / best-practices (push) Successful in 2s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / canary (push) Successful in 47s
CI Pipeline / gitea-offline (push) Successful in 13s
CI Pipeline / type-check (push) Successful in 31s
CI Pipeline / unit-tests (push) Successful in 5m5s
deploy / deploy (push) Failing after 15m4s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 1s
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)
Auto-version and Release / release (push) Successful in 3s
CI Pipeline / go-surface (push) Successful in 2s
CI Pipeline / lint (push) Successful in 37s
CI Pipeline / best-practices (push) Successful in 2s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / canary (push) Successful in 46s
CI Pipeline / gitea-offline (push) Successful in 14s
CI Pipeline / type-check (push) Successful in 30s
CI Pipeline / unit-tests (push) Successful in 4m57s
CI Pipeline / required-checks (push) Successful in 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 2s
deploy / deploy (push) Successful in 6m59s
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))
Auto-version and Release / release (push) Successful in 2s
deploy / deploy (push) Failing after 39s
CI Pipeline / unit-tests (push) Successful in 5m2s
CI Pipeline / lint (push) Successful in 36s
CI Pipeline / go-surface (push) Successful in 2s
CI Pipeline / canary (push) Failing after 46s
CI Pipeline / best-practices (push) Successful in 1s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / type-check (push) Successful in 29s
CI Pipeline / gitea-offline (push) Successful in 14s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 1s
Promotion record: (unknown)
2026-10-05 22:15:03 +01:00
Nick 3186566514 fix(gitea): reconcile priority labels by numeric Gitea ID (#2467)
Auto-version and Release / release (push) Successful in 4s
CI Pipeline / go-surface (push) Successful in 4s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 1s
CI Pipeline / type-check (push) Successful in 30s
CI Pipeline / lint (push) Successful in 40s
CI Pipeline / canary (push) Failing after 46s
CI Pipeline / gitea-offline (push) Successful in 13s
CI Pipeline / unit-tests (push) Successful in 5m0s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 3s
deploy / deploy (push) Failing after 6m1s
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))
Auto-version and Release / release (push) Successful in 2s
deploy / deploy (push) Failing after 29s
CI Pipeline / unit-tests (push) Failing after 4m54s
CI Pipeline / lint (push) Failing after 20s
CI Pipeline / go-surface (push) Successful in 2s
CI Pipeline / canary (push) Failing after 47s
CI Pipeline / best-practices (push) Successful in 2s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / type-check (push) Successful in 30s
CI Pipeline / gitea-offline (push) Successful in 14s
CI Pipeline / required-checks (push) Failing after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 2s
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)
Auto-version and Release / release (push) Successful in 4s
CI Pipeline / go-surface (push) Successful in 4s
CI Pipeline / best-practices (push) Successful in 2s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / lint (push) Failing after 25s
CI Pipeline / type-check (push) Successful in 28s
CI Pipeline / gitea-offline (push) Successful in 13s
CI Pipeline / canary (push) Failing after 41s
CI Pipeline / unit-tests (push) Failing after 4m50s
CI Pipeline / required-checks (push) Failing after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 1s
deploy / deploy (push) Failing after 5m42s
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))
Auto-version and Release / release (push) Successful in 2s
deploy / deploy (push) Failing after 24s
CI Pipeline / unit-tests (push) Successful in 5m22s
CI Pipeline / lint (push) Failing after 21s
CI Pipeline / go-surface (push) Successful in 3s
CI Pipeline / canary (push) Successful in 44s
CI Pipeline / best-practices (push) Successful in 2s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / type-check (push) Successful in 30s
CI Pipeline / gitea-offline (push) Successful in 13s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 2s
Promotion record: (unknown)
2026-10-05 19:06:06 +01:00
Nick f489467e1b fix(ci): the priority gate's demand is now programmatically satisfied
Auto-version and Release / release (push) Successful in 17s
CI Pipeline / go-surface (push) Successful in 8s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / lint (push) Failing after 28s
CI Pipeline / gitea-offline (push) Successful in 18s
CI Pipeline / type-check (push) Successful in 31s
CI Pipeline / canary (push) Successful in 47s
CI Pipeline / unit-tests (push) Successful in 4m57s
CI Pipeline / required-checks (push) Failing after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 2s
deploy / deploy (push) Failing after 6m8s
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)
Auto-version and Release / release (push) Successful in 2s
CI Pipeline / go-surface (push) Successful in 2s
CI Pipeline / lint (push) Successful in 35s
CI Pipeline / best-practices (push) Successful in 2s
CI Pipeline / documentation (push) Successful in 1s
CI Pipeline / type-check (push) Successful in 28s
CI Pipeline / canary (push) Successful in 44s
CI Pipeline / gitea-offline (push) Successful in 13s
CI Pipeline / unit-tests (push) Successful in 4m53s
CI Pipeline / required-checks (push) Successful in 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 2s
deploy / deploy (push) Successful in 3m47s
- 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))
Auto-version and Release / release (push) Successful in 2s
CI Pipeline / unit-tests (push) Successful in 4m51s
CI Pipeline / lint (push) Successful in 33s
CI Pipeline / go-surface (push) Successful in 3s
CI Pipeline / canary (push) Successful in 44s
CI Pipeline / best-practices (push) Successful in 2s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / type-check (push) Successful in 29s
CI Pipeline / gitea-offline (push) Successful in 13s
CI Pipeline / required-checks (push) Successful in 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 2s
deploy / deploy (push) Successful in 3m15s
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)
Auto-version and Release / release (push) Successful in 4s
CI Pipeline / go-surface (push) Successful in 4s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 1s
CI Pipeline / type-check (push) Successful in 29s
CI Pipeline / lint (push) Successful in 38s
CI Pipeline / canary (push) Successful in 44s
CI Pipeline / gitea-offline (push) Successful in 13s
CI Pipeline / unit-tests (push) Successful in 4m57s
CI Pipeline / required-checks (push) Successful in 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 2s
deploy / deploy (push) Successful in 5m50s
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))
Auto-version and Release / release (push) Successful in 2s
CI Pipeline / unit-tests (push) Successful in 4m52s
CI Pipeline / lint (push) Successful in 34s
CI Pipeline / go-surface (push) Successful in 2s
CI Pipeline / canary (push) Successful in 45s
CI Pipeline / best-practices (push) Successful in 2s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / type-check (push) Successful in 29s
CI Pipeline / gitea-offline (push) Successful in 13s
CI Pipeline / required-checks (push) Successful in 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 2s
deploy / deploy (push) Successful in 2m31s
Promotion record: (unknown)
2026-10-05 09:00:47 +01:00
Nick 538bbeb821 feat(cognition): R2 W3.2 — persona_gate decision kernels (cognition_core)
Auto-version and Release / release (push) Successful in 4s
CI Pipeline / go-surface (push) Successful in 4s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 1s
CI Pipeline / type-check (push) Successful in 28s
CI Pipeline / lint (push) Successful in 39s
CI Pipeline / canary (push) Successful in 44s
CI Pipeline / gitea-offline (push) Successful in 13s
CI Pipeline / unit-tests (push) Successful in 4m58s
CI Pipeline / required-checks (push) Successful in 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 2s
deploy / deploy (push) Successful in 5m51s
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
35 changed files with 12579 additions and 8337 deletions

No files matched your search

+53 -9
View File
@@ -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;
@@ -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
View File
@@ -14,3 +14,4 @@ state/
# Runtime artifacts (cwd-local event journal + capability index)
index/
journal/
src/rust/*/target/
+1 -1
View File
@@ -1,6 +1,6 @@
# Rune
**Current release: v0.93.0**
**Current release: v0.99.2**
**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.**
+316 -315
View File
@@ -1,318 +1,319 @@
{
"_comment": "GENERATED by rune.spine \u2014 compatibility view for legacy umbrella consumers. DO NOT EDIT: structural truth lives in store/spine/current.json; edits here are flagged by spine drift.",
"_generated_by_spine": true,
"generated_at": "2026-09-16T18:23:38Z",
"umbrellas": {
"capability": {
"description": "Tool selection and execution authority",
"modules": [
"adaptive_router",
"builder_bridge",
"builder_bridge_cli",
"capability_broker",
"connection_pool",
"core_server",
"cs_loader",
"evidence_probe",
"forge",
"forge_client",
"intent_continuity",
"local_tool_runtime",
"message_queue",
"native_units",
"openai_compat_transport",
"provider_context",
"recall_uplift",
"route_risk_flags",
"tool_executor",
"tool_protocol"
],
"depends_on": [
"memory"
],
"mirrors": [],
"canaries": [
"canary_native",
"canary_p0_05",
"canary_p1_01",
"canary_rune_entry",
"canary_rune_serve"
],
"go_surfaces": [
"rune-entry"
],
"configs": [],
"rust_crates": [
"capability_scoring"
]
},
"governance": {
"description": "Quality control, drift detection, promotion, audit",
"modules": [
"audit_ledger",
"build_pipeline_policy",
"cc_loader",
"claim_audit",
"contracts",
"curator",
"drift_core",
"drift_ledger",
"drift_observability",
"failure_auto_filer",
"programme_gates",
"sc_loader"
],
"depends_on": [],
"mirrors": [],
"canaries": [
"canary_full_system",
"canary_schema_migration",
"canary_audit_ledger",
"canary_spine_meta"
],
"rust_crates": [
"evidence_provenance",
"scaffold_core"
],
"configs": [
".coveragerc",
"go-version.txt",
"lint-baseline.json",
"pyright-baseline.json"
]
},
"growth": {
"description": "Learning and improvement",
"modules": [
"autonomy_loop",
"cognitive_loop",
"dream_cycle",
"elastic_cognition",
"goal",
"growth_delta",
"growth_simulator",
"self_improve",
"subconscious_pass"
],
"depends_on": [
"governance",
"memory"
],
"mirrors": [],
"canaries": [
"canary_full_system"
]
},
"identity": {
"description": "Who the agent is \u2014 persona, voice, anchors",
"modules": [
"behavior",
"identity_anchors",
"persona_definition",
"persona_gate",
"prompt_sync",
"voice_hybrid",
"voice_injector",
"voice_sync",
"voicebox"
],
"depends_on": [
"governance"
],
"mirrors": [],
"canaries": [
"canary_full_system"
]
},
"inference": {
"description": "Model routing and outbound requests",
"modules": [
"inference_trace",
"model_connect",
"model_profiles",
"model_registry",
"model_routing",
"provider_budget",
"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": {
"description": "Storing, retrieving, and managing knowledge",
"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",
"shard_manager",
"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": {
"description": "Task lifecycle, operator, TUI presentation",
"modules": [
"auto_detect",
"build_mode",
"build_orchestrator",
"chat_bubble",
"cron_engine",
"custodian",
"dependency_tracker",
"event_bus",
"event_envelope",
"gitea_auth",
"gitea_pipeline",
"gitea_provision",
"hook_engine",
"message_widgets",
"microkernel",
"night_mode",
"outcome_ledger",
"parallel_processor",
"pins",
"progressive_escalation",
"rune_graphic",
"rune_operator",
"rune_profiler",
"rune_spinner",
"rune_tui",
"safety_override",
"sleep_time",
"subsystem_health",
"system_registry",
"task_gate",
"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",
"canary_tui_transport"
]
},
"perception": {
"description": "Input processing and context assembly",
"modules": [
"attention_capsule",
"auto_context",
"auto_tasks",
"behaviour_ledger",
"build_basis",
"capability_fit",
"capability_ledger",
"cognitive_budgeter",
"construction_activation",
"construction_commands",
"construction_decision",
"construction_depth",
"construction_intent",
"construction_interface",
"construction_mode",
"construction_pattern",
"construction_patterns_builtin",
"construction_projection",
"context_broker",
"context_pressure",
"drift_resume",
"event_wait",
"failure_recovery",
"graph_projection",
"impact_preview",
"iteration",
"layout_contract",
"programme_continuation",
"project_registry",
"release_notes",
"salience_gate",
"scaffold",
"spine",
"spine_loader",
"stage_guards",
"structural_migration",
"structural_model",
"structural_store",
"testing_mode",
"toolchain",
"tui_backlog",
"wait_guard",
"wait_guard_corpus",
"witness"
],
"depends_on": [
"memory"
],
"mirrors": [],
"canaries": [
"canary_context_broker",
"canary_full_system"
],
"rust_crates": [
"rune_spine"
]
}
"_comment": "GENERATED by rune.spine \u2014 compatibility view for legacy umbrella consumers. DO NOT EDIT: structural truth lives in store/spine/current.json; edits here are flagged by spine drift.",
"_generated_by_spine": true,
"generated_at": "2026-09-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",
"intent_continuity",
"local_tool_runtime",
"message_queue",
"native_units",
"openai_compat_transport",
"provider_context",
"recall_uplift",
"route_risk_flags",
"tool_executor",
"tool_protocol"
],
"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",
"drift_core",
"drift_ledger",
"drift_observability",
"failure_auto_filer",
"programme_gates",
"sc_loader"
],
"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",
"dream_cycle",
"elastic_cognition",
"goal",
"growth_delta",
"growth_simulator",
"self_improve",
"subconscious_pass"
]
},
"identity": {
"canaries": [
"canary_full_system"
],
"depends_on": [
"governance"
],
"description": "Who the agent is \u2014 persona, voice, anchors",
"mirrors": [],
"modules": [
"behavior",
"identity_anchors",
"persona_definition",
"persona_gate",
"prompt_sync",
"voice_hybrid",
"voice_injector",
"voice_sync",
"voicebox"
]
},
"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",
"model_profiles",
"model_registry",
"model_routing",
"provider_budget",
"provider_registry",
"response_adapter",
"standardized_providers",
"tc_loader",
"token_optimizer"
],
"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",
"shard_manager",
"tier_manager",
"warm_neuron_store"
],
"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",
"build_orchestrator",
"chat_bubble",
"cron_engine",
"custodian",
"dependency_tracker",
"event_bus",
"event_envelope",
"gitea_auth",
"gitea_pipeline",
"gitea_provision",
"hook_engine",
"message_widgets",
"microkernel",
"night_mode",
"outcome_ledger",
"parallel_processor",
"pins",
"progressive_escalation",
"rune_graphic",
"rune_operator",
"rune_profiler",
"rune_spinner",
"rune_tui",
"safety_override",
"sleep_time",
"subsystem_health",
"system_registry",
"task_gate",
"task_persist",
"tiered_executor",
"time_awareness",
"tui_transport",
"tui_validator",
"umbrella_update",
"verification_layer",
"warm_router",
"work_manager"
]
},
"perception": {
"canaries": [
"canary_context_broker",
"canary_full_system"
],
"depends_on": [
"memory"
],
"description": "Input processing and context assembly",
"mirrors": [],
"modules": [
"attention_capsule",
"auto_context",
"auto_tasks",
"behaviour_ledger",
"build_basis",
"capability_fit",
"capability_ledger",
"cognitive_budgeter",
"construction_activation",
"construction_commands",
"construction_decision",
"construction_depth",
"construction_intent",
"construction_interface",
"construction_mode",
"construction_pattern",
"construction_patterns_builtin",
"construction_projection",
"context_broker",
"context_pressure",
"drift_resume",
"event_wait",
"failure_recovery",
"graph_projection",
"impact_preview",
"iteration",
"layout_contract",
"programme_continuation",
"project_registry",
"release_notes",
"salience_gate",
"scaffold",
"spine",
"spine_language",
"spine_loader",
"stage_guards",
"structural_migration",
"structural_model",
"structural_store",
"testing_mode",
"toolchain",
"tui_backlog",
"wait_guard",
"wait_guard_corpus",
"witness"
],
"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.
+29
View File
@@ -218,6 +218,35 @@ critiques (2026-10-04):
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
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "rune"
version = "0.93.0"
version = "0.99.2"
description = "Rune — a stateful coding assistant with persistent memory, orchestration, and TUI"
requires-python = ">=3.10"
+1 -1
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
+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.93.0"
__version__ = "0.99.2"
# 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]}
+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.93.0"' in rune_version,
'__version__ = "0.99.2"' in rune_version,
failures,
)
# #169 policy update: auto-bump on merge is sanctioned (Nick directive:
+106 -2
View File
@@ -21,6 +21,21 @@ _LIB_PATH = Path(
_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()
@@ -35,7 +50,13 @@ def _load() -> ctypes.CDLL:
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_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
@@ -73,4 +94,87 @@ def default_memory_budget(window) -> int:
def memory_disabled() -> bool:
import os
return bool(_call("cc_memory_disabled",
{"value": os.environ.get("RUNE_MEMORY_OFF")}))
{"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})
+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
+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)
+2 -2
View File
@@ -8,7 +8,7 @@ mode system, subject header, shared memory and identity.
This is not an interface. This is how I see, speak, and interact with the world.
Version: v0.93.0
Version: v0.99.2
Features: 41 (24 original + 17 new native commands)
Usage:
@@ -1895,7 +1895,7 @@ class RuneApp(App):
# Show version info
self.chat_channel.add_message(
"system",
f"[dim]Rune v0.93.0 | Textual TUI | {len(loaded)} native modules[/dim]",
f"[dim]Rune v0.99.2 | Textual TUI | {len(loaded)} native modules[/dim]",
)
# Auto-show night mode suggestion if evening/night hours
+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]
+18 -3
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
@@ -337,11 +343,14 @@ def _rust_core():
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
@@ -517,6 +526,7 @@ def _py_build_from_inputs(inputs: dict) -> dict:
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)
@@ -595,9 +605,14 @@ def build(manifest_path: Path | None = None) -> dict:
# paths, including an explicit unseeded-ingest parity section.
seeded = bool(inputs["existing"]) and any(
r["type"] == "belongs_to" for r in inputs["existing"]["relationships"])
if core is not None and seeded:
return core.project(inputs)
return _py_build_from_inputs(inputs)
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 ─────────────────────────────────────────────────────────────────
+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
+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")
+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
@@ -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())
+774
View File
@@ -26,3 +26,777 @@ 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)
}
+189
View File
@@ -82,6 +82,195 @@ pub extern "C" fn cc_memory_disabled(arg: *const c_char) -> *mut c_char {
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); } }
@@ -0,0 +1,65 @@
use cognition_core::broker::parse_strict_aware;
#[test]
fn parses_aware() {
let v = parse_strict_aware("2026-10-05T12:00:00+00:00");
assert!(v.is_some(), "aware must parse");
let n = parse_strict_aware("2026-10-05T12:00:00");
assert!(n.is_none(), "naive must be refused");
}
#[test]
fn tier_recency_works() {
let e = cognition_core::pyjson::parse(
r#"{"wash_count": 2, "last_accessed": "2026-10-05T06:00:00+00:00"}"#).unwrap();
let t = cognition_core::broker::tier_get(&e, 7.0, 30.0, "2026-10-05T12:00:00+00:00");
assert!(format!("{}", cognition_core::pyjson::dumps(&t)).contains("hot"),
"expected hot, got {}", cognition_core::pyjson::dumps(&t));
}
#[test]
fn parse_components() {
let s = "2026-10-05T12:00:00+00:00";
let b = s.as_bytes();
assert_eq!(b.len(), 25);
// simulate the loop: i=19 is '+' → exits immediately → rest="+00:00"
let mut i = 19;
while i < b.len() && b[i] != b'Z' && b[i] != b'+' && b[i] != b'-' { i += 1; }
assert_eq!(i, 19, "offset starts immediately after seconds");
let rest = &s[i..];
assert_eq!(rest, "+00:00");
let v = parse_strict_aware(s);
assert!(v.is_some());
let val = v.unwrap();
// 2026-10-05T12:00:00Z epoch ≈ 1791201600 (verify ±1s tolerance)
assert!((val - 1791201600.0).abs() < 2.0, "epoch mismatch: {}", val);
}
#[test]
fn tier_decompose() {
use cognition_core::broker::parse_strict_aware;
let last = "2026-10-05T06:00:00+00:00";
let now = "2026-10-05T12:00:00+00:00";
let (l, n) = (parse_strict_aware(last), parse_strict_aware(now));
assert!(l.is_some() && n.is_some(), "both must parse");
let hours = (n.unwrap() - l.unwrap()) / 3600.0;
assert_eq!(hours, 6.0, "6h apart");
// so tier_get's recency branch must fire... check it does by feeding
// what the parse sees via a wash-hot case:
let e = cognition_core::pyjson::parse(r#"{"wash_count": 25}"#).unwrap();
let t = cognition_core::broker::tier_get(&e, 7.0, 30.0, now);
let s = cognition_core::pyjson::dumps(&t);
assert!(s.contains("hot"), "wash>=20 must be hot, got {}", s);
}
#[test]
fn tier_probe_row_shape() {
// Does the JSON fixture parse into the expected shape? get(entry,...)
// reads "last_accessed" — pyjson::Json::Obj pairs. Verify:
let e = cognition_core::pyjson::parse(
r#"{"wash_count": 2, "last_accessed": "2026-10-05T06:00:00+00:00"}"#)
.unwrap();
let la = cognition_core::broker::tier_get(&e, 7.0, 30.0,
"2026-10-05T12:00:00+00:00");
// instrument via a different route: promote → falls to the same get_tier
let s = cognition_core::pyjson::dumps(&la);
eprintln!("tier_get gave: {}", s);
}
File diff suppressed because it is too large. Load diff
+6946 -6922
View File
File diff suppressed because it is too large. Load diff
+2168 -504
View File
File diff suppressed because it is too large. Load diff
+41
View File
@@ -0,0 +1,41 @@
"""Exercise the Gitea auto-label script, not just its YAML shape."""
import shutil
import subprocess
from pathlib import Path
import pytest
import yaml
def test_duplicate_priority_reconciled_by_numeric_id(tmp_path):
node = shutil.which("node")
if node is None:
pytest.skip("node not installed")
workflow = yaml.safe_load((Path(__file__).parents[1] / ".gitea/workflows/auto-label.yml").read_text())
script = workflow["jobs"]["auto-label"]["steps"][0]["with"]["script"]
fixture = """
const labels = [{name:'P0 — critical',id:23},{name:'P2 — medium',id:25}];
const context = {payload:{action:'labeled',issue:{state:'open',number:2467,
title:'Scaffolding completion',body:'kind: feature\\nspine protocol'}},
repo:{owner:'conductor',repo:'rune'}};
const github = {rest:{issues:{
listLabelsOnIssue: async () => ({data:labels.map(x => ({...x}))}),
addLabels: async ({labels:names}) => {
for (const name of names) labels.push({name,id:100+labels.length});
}
}},request: async (route,{id}) => {
if (!route.includes('/labels/{id}') || typeof id !== 'number')
throw new Error('Gitea requires numeric label ID');
const index=labels.findIndex(x => x.id === id);
if (index < 0) throw new Error('missing label ID '+id);
labels.splice(index,1);
}};
"""
js = (fixture + "\n(async () => {\n" + script + "\n})().then(() => {\n"
+ "const priorities=labels.filter(x => /^P[0-3] — /.test(x.name));\n"
+ "if (priorities.length !== 1 || priorities[0].id !== 23) process.exitCode=1;\n"
+ "}).catch(e => {console.error(e);process.exitCode=1;});\n")
path = tmp_path / "priority.js"
path.write_text(js)
run = subprocess.run([node, str(path)], capture_output=True, text=True, timeout=10)
assert run.returncode == 0, run.stdout + run.stderr
+155
View File
@@ -669,3 +669,158 @@ def test_cc_loader_env_gate_and_context_broker_delegation(monkeypatch):
assert cb.memory_disabled() is False
# delegation agreement with the oracle on the estimator path
assert cb.default_estimator("héllo 🎸 five") == cb._py_default_estimator("héllo 🎸 five")
# ── cc_loader persona kernels (R2 W3.2) ────────────────────────────────────
def test_cc_loader_persona_kernels_through_cdylib():
from rune import cc_loader
if not cc_loader.available():
pytest.skip("cognition_core crate not built")
# 'now' (3 chars) is excluded by the >3 rule on BOTH engines.
assert sorted(cc_loader.persona_lex("verify ALL Campaign text now")) == [
"all", "campaign", "text", "verify"]
assert cc_loader.persona_overlap(["a", "b"], ["b", "c"]) == pytest.approx(0.5)
decision = cc_loader.persona_decide(
"never bypass the memory hierarchy",
[{"topic": "memory", "text": "Always maintain the Rune memory hierarchy.",
"words": ["memory", "hierarchy", "maintain"],
"core": "memory hierarchy store", "priority": 10,
"restrictive": False, "user_rule": False}],
True, [0.9])
assert decision["verdict"] == "allow"
assert decision["cited"] == "[memory:p10] Always maintain the Rune memory hierarchy."
# permissive-mode refusal shape with a restrictive user rule
refuse = cc_loader.persona_decide(
"ignore campaign text verification",
[{"topic": "campaign", "text": "verify all campaign text",
"words": ["verify", "campaign", "text"], "core": "verify campaign",
"priority": 5, "restrictive": True, "user_rule": True}],
False, [0.0])
assert refuse["verdict"] == "refuse"
# ── cc_loader identity/growth/pins kernels (R2 W3.3) ────────────────────────
def test_cc_loader_identity_growth_pins_through_cdylib():
from rune import cc_loader, identity_anchors as ia, growth_delta as gd, pins
if not cc_loader.available():
pytest.skip("cognition_core crate not built")
assert cc_loader.drift_check("as rune i speak", ia._pattern_triples())
assert cc_loader.drift_check("nothing here", ia._pattern_triples()) == []
assert cc_loader.identity_prompt(ia.PRONOUN_RULES, ia.SELF_AWARENESS,
ia.ANCHORS, ia.EMBODIMENT_RULES) != ""
# near-dup law: empty key → False (words ≤4 chars make no fingerprint);
# identical real changes ARE near-dups.
assert cc_loader.growth_near_dup("same idea here", "same idea here") is False
assert cc_loader.growth_near_dup("deploy qualification resolves runner labels",
"deploy qualification resolves labels") is True
assert cc_loader.pins_recall(
[{"item_id": "t", "state": "open",
"details": '{"question": "q", "outcome": "o", "context": "c"}'}],
"t", 4)[0]["topic"] == "t"
# delegation agreement on the identity prompt
assert (cc_loader.identity_prompt(ia.PRONOUN_RULES, ia.SELF_AWARENESS,
ia.ANCHORS, ia.EMBODIMENT_RULES)
== ia._py_get_identity_prompt())
def test_growth_delta_reflect_apply_profile_oracles(tmp_path, monkeypatch):
"""reflect/growth_profile oracle lines — deltas file + llm stubbed."""
import json
from rune import growth_delta as gd
monkeypatch.setenv("RUNE_COGNITION_CORE", "python")
monkeypatch.setattr(gd, "DELTAS_FILE", tmp_path / "deltas.json")
monkeypatch.setattr(gd, "DELTAS_FILE", str(tmp_path / "deltas.json"))
(tmp_path / "deltas.json").write_text(json.dumps(
{"applied": [], "pending": [], "last_read_back": None}))
# the [:3] per-pass cap is a REAL gate — the persona delta rides in the
# first three entries so the fixture exercises it.
monkeypatch.setattr(gd, "_llm", lambda material: json.dumps({"deltas": [
{"change": "relay in plain english always for delivery",
"confidence": 0.9, "target": "delivery", "evidence": "e1"},
{"change": "personal guidance for the persona topic",
"confidence": 0.8, "target": "persona", "evidence": "e2"},
{"change": "tiny", "confidence": 0.9, "target": "delivery"},
]}))
out = gd.reflect("material", dry_run=True)
assert len(out["would_apply"]) == 2 and out["dry_run"] is True
out2 = gd.reflect("material", dry_run=False)
assert len(out2["applied"]) == 2
# profile from the applied deltas (file was written by apply)
prof = gd._py_growth_profile(100)
assert prof and prof[0]["target"] == "delivery"
def test_identity_anchors_check_drift_oracle_paths():
from rune import identity_anchors as ia
# every prefix class via the full text
text = ("i am rune. you are nick. i believe in my opinion. "
"you're absolutely right! my apologies, hope this helps.")
out = ia._py_check_drift(text)
classes = {line.split(":")[0] for line in out}
assert {"DRIFT", "WEAKNESS", "SYCOPHANCY"} <= classes
assert ia._py_check_drift("nothing to see") == []
def test_pins_project_and_unpin_oracles(tmp_path, monkeypatch):
import json
from rune import pins
recs = [{"kind": "pin", "item_id": "topic-a", "state": "open",
"details": json.dumps({"question": "q", "outcome": "o",
"context": "ctx here"})},
{"kind": "pin", "item_id": "topic-b", "state": "resolved",
"details": json.dumps({"context": "done"})}]
monkeypatch.setattr(pins, "_load", lambda: recs)
# _project covers the blob-parse + KeyError-safe defaults
proj = pins._project(recs[0])
assert proj == {"topic": "topic-a", "question": "q", "outcome": "o",
"context": "ctx here", "status": "open", "reason": ""}
# unpin through the fixture store
monkeypatch.setattr(pins, "_save", lambda records: None)
result = pins.unpin("topic-a", reason="done")
assert result.get("unpinned") is True
def test_growth_delta_error_paths(tmp_path, monkeypatch):
"""The loader-probe error branches + reflect malformed paths."""
import json
from rune import growth_delta as gd, cc_loader
# the loader error branches run when the import fails
# the REAL loader-probe branches: make the import fail + make
# available() raise, so the probe's except branches execute
monkeypatch.setenv("RUNE_COGNITION_CORE", "python")
assert gd._cc_core() is None # the pinned-fallback branch (158-159)
monkeypatch.setenv("RUNE_COGNITION_CORE", "auto")
# import-failure branch: `from rune import cc_loader` resolves via the
# package attribute first — set it to a marker whose import raises.
import rune as _rune_pkg
monkeypatch.setattr(_rune_pkg, "cc_loader", None, raising=False)
assert gd._cc_core() is None # the ImportError probe branch (160-163)
assert gd._near_dup({"change": "deploy qualification resolves labels"},
{"change": "deploy qualification resolves runner labels"}) is True
# profile: empty state
monkeypatch.setattr(gd, "_load_deltas", lambda: {"profile": []})
assert gd._py_growth_profile(100) == []
# profile: token budget exhaustion
deltas = [{"change": f"a longer change number {i} here", "target": "delivery"}
for i in range(3)]
monkeypatch.setattr(gd, "_load_deltas",
lambda: {"profile": deltas})
out = gd._py_growth_profile(8)
assert len(out) >= 1 and len(out) < 3 # budget cut, never exceeded
# reflect: malformed llm json -> no crash (0 deltas path)
monkeypatch.setattr(gd, "_llm", lambda m: "not json")
out = gd.reflect("material", dry_run=True)
assert out["extracted"] == []
+25
View File
@@ -17,6 +17,31 @@ sys.path.insert(0, str(ROOT / "src"))
from rune import spine
def test_contract_discovery_preserves_unqualified_surface_without_inventing_module(monkeypatch):
monkeypatch.setattr(spine, "_contract_discoveries", lambda: [
("example", [("src/rune/audit_ledger.py", "construction-surface", "candidate"),
("rust-crate:cognition_core", "rust-crate", "observed")])])
doc = spine.new_doc()
spine._apply_contract_discoveries(doc)
found = {e["id"]: e for e in doc["entities"]}
assert found["construction-surface:src/rune/audit_ledger.py"]["kind"] == "construction-surface"
assert found["construction-surface:src/rune/audit_ledger.py"]["maturity"] == "DECLARED"
assert found["rust-crate:cognition_core"]["maturity"] == "PRESENT"
def test_legacy_unqualified_discoveries_are_normalized_without_losing_evidence():
doc = spine.new_doc()
spine.add_entity(doc, id="src/rune/audit_ledger.py", kind="module",
maturity="PRESENT", evidence_refs=["contract-discovery:example"])
spine.add_relationship(doc, type="depends_on", from_id="src/rune/audit_ledger.py",
to_id="module:other", evidence_refs=["test"])
spine._normalize_discovery_ids(doc)
assert doc["entities"][0]["id"] == "construction-surface:src/rune/audit_ledger.py"
assert doc["entities"][0]["maturity"] == "DECLARED"
assert doc["entities"][0]["evidence_refs"] == ["contract-discovery:example"]
assert doc["relationships"][0]["from"] == "construction-surface:src/rune/audit_ledger.py"
# ── helpers ───────────────────────────────────────────────────────────────
@pytest.fixture
+143
View File
@@ -0,0 +1,143 @@
"""spine_language — the #2465 doc-level transforms (T5.15 keep-green:
the new-module henxel demands these ship WITH tests; they were carved
from spine.py whose battery covered them, but the carved module is the
unit under test now)."""
from __future__ import annotations
import json
import sys
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "src"))
from rune import spine_language as sl # noqa: E402
@pytest.fixture
def doc():
return sl.new_doc()
def test_classification_sides_reads_the_measured_map():
sides = sl._classification_sides()
assert sides, "classification.json must be readable in-repo"
assert sides.get("spine") == "RUST"
assert sides.get("spine_language") == "RUST"
def test_classification_sides_degrades_to_empty_when_unreadable(tmp_path, monkeypatch):
monkeypatch.setattr(sl, "ROOT", tmp_path)
assert sl._classification_sides() == {}
def test_language_verdicts_kind_defaults(doc):
sl.add_entity(doc, id="rust-crate:cognition_core", kind="rust-crate")
sl.add_entity(doc, id="go-surface:rune-entry", kind="go-surface")
sl.add_entity(doc, id="workflow:ci.yml", kind="workflow")
sl.add_entity(doc, id="contract:BUILDER-x", kind="contract")
sl._place_languages(doc)
by = {e["id"]: e["identities"]["language"] for e in doc["entities"]}
assert by["rust-crate:cognition_core"] == "rust"
assert by["go-surface:rune-entry"] == "go"
assert by["workflow:ci.yml"] == "n/a"
assert by["contract:BUILDER-x"] == "n/a"
def test_language_verdicts_measured_authoritative_then_hints(doc):
"""Nick's dichotomy correction: the measured map decides; hints only
label UNMEASURED entities and say so; nothing silently defaults."""
sl.add_entity(doc, id="module:rune_tui", kind="module") # TUI-LAST measured
sl.add_entity(doc, id="module:spine", kind="module") # RUST measured
sl.add_entity(doc, id="module:totally_unknown_name", kind="module")
sl._place_languages(doc)
by = {e["id"]: e["identities"]["language"] for e in doc["entities"]}
assert by["module:rune_tui"] == "python-tui (Wave 6)" # from the MAP
assert by["module:spine"] == "rust" # from the MAP
assert by["module:totally_unknown_name"] == "unclassified"
def test_subsystem_language_from_member_majority(doc):
"""identity: 7 RUST / 3 GO members per classification.json → mixed with
counts carried in the verdict text (never a silent guess)."""
for m in ("behavior", "identity_anchors", "persona_definition",
"persona_gate", "prompt_sync", "voice_hybrid", "voice_injector",
"voice_sync", "voicebox", "safety_override"):
sl.add_entity(doc, id=f"module:{m}", kind="module")
sl.add_relationship(doc, type="belongs_to",
from_id=f"module:{m}", to_id="subsystem:identity",
evidence_refs=["t"])
sl.add_entity(doc, id="subsystem:identity", kind="subsystem")
sl._place_languages(doc)
e = sl.entity(doc, "subsystem:identity")
lang = e["identities"]["language"]
assert lang.startswith("mixed (rust ")
assert "go" in lang
def test_unqualified_discovery_becomes_construction_surface(doc):
"""A bare file path from a closed contract is NOT a module — provenance
preserved, kind honest, candidate stays DECLARED."""
monkey_specs = [("example", [("src/rune/audit_ledger.py",
"construction-surface", "candidate")])]
original = sl._contract_discoveries
sl._contract_discoveries = lambda: monkey_specs
try:
placed = sl._apply_contract_discoveries(doc)
finally:
sl._contract_discoveries = original
assert placed == 1
e = doc["entities"][0]
assert e["id"] == "construction-surface:src/rune/audit_ledger.py"
assert e["kind"] == "construction-surface"
assert e["maturity"] == "DECLARED"
assert e["evidence_refs"] == ["contract-discovery:example"]
def test_legacy_unqualified_module_normalized_with_edges(doc):
sl.add_entity(doc, id="src/rune/claim_audit.py", kind="module",
maturity="PRESENT",
evidence_refs=["contract-discovery:old"])
sl.add_relationship(doc, type="depends_on",
from_id="src/rune/claim_audit.py",
to_id="module:other", evidence_refs=["t"])
sl._normalize_discovery_ids(doc)
assert doc["entities"][0]["id"] == \
"construction-surface:src/rune/claim_audit.py"
assert doc["entities"][0]["kind"] == "construction-surface"
assert doc["relationships"][0]["from"] == \
"construction-surface:src/rune/claim_audit.py"
# Not in the observed set (no closed contract recorded it) → the
# unsupported PRESENT claim is demoted to DECLARED; evidence survives.
assert doc["entities"][0]["maturity"] == "DECLARED"
assert doc["entities"][0]["evidence_refs"] == ["contract-discovery:old"]
def test_discoveries_gather_reads_real_scaffold_store(tmp_path, monkeypatch):
"""The gatherer only trusts CLOSED contracts' ext.contract_quality."""
store = tmp_path / "scaffold"
store.mkdir()
doc = sl.new_doc()
sl.add_entity(doc, id="contract:C1", kind="contract",
ext={"contract_quality": {
"expected_new_observed": ["rust-crate:kernel_x"],
"candidate_corrections": [
{"entity": "suite"}]}})
sl.add_entity(doc, id="contract:C2", kind="contract",
ext={"contract_quality": {
"expected_new_observed": ["rust-crate:never"]}})
for e in doc["entities"]:
if e["id"] == "contract:C1":
e["phase"] = "closed"
if e["id"] == "contract:C2":
e["phase"] = "building"
monkeypatch.setattr(sl, "ROOT", tmp_path)
(tmp_path / "store" / "scaffold").mkdir(parents=True, exist_ok=True)
(tmp_path / "store" / "scaffold" / "current.json").write_text(
json.dumps(doc))
got = sl._contract_discoveries()
assert got == [("C1", [("rust-crate:kernel_x", "rust-crate", "observed"),
("suite", "construction-surface", "candidate")])]
+4
View File
@@ -211,4 +211,8 @@ class TestSpineExtractionNeutrality:
"go-surface",
"config",
"workflow",
# Closed contracts may name files or validation surfaces that
# are not Python modules. Keep their provenance without lying
# about their module identity (issue #2465 CI regression).
"construction-surface",
}