Compare commits

...
31 Commits
Author SHA1 Message Date
conductor 633af06e8a chore(release): auto-bump version to 0.238.0 — earned by # (minor: converged release (push-to-master bump))
CI Pipeline / best-practices (push) Successful in 7s
CI Pipeline / documentation (push) Successful in 6s
CI Pipeline / rust-surface (push) Failing after 14s
CI Pipeline / go-surface (push) Failing after 16s
Auto-version and Release / release (push) Successful in 30s
CI Pipeline / gitea-offline (push) Successful in 22s
CI Pipeline / type-check (push) Failing after 45s
CI Pipeline / lint (push) Failing after 1m2s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
CI Pipeline / unit-tests (push) Canceled after 2m0s
CI Pipeline / canary (push) Canceled after 2m0s
deploy / deploy (push) Canceled after 1m51s
Promotion record: (unknown)
2026-10-12 14:35:45 +01:00
Nick 62e47b3812 refactor(drift_ledger): Phase B language-advantage pass — dichotomy wave
CI Pipeline / best-practices (push) Successful in 10s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / rust-surface (push) Failing after 17s
CI Pipeline / go-surface (push) Failing after 20s
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 36s
CI Pipeline / gitea-offline (push) Canceled after 18s
CI Pipeline / lint (push) Canceled after 36s
CI Pipeline / type-check (push) Canceled after 22s
CI Pipeline / unit-tests (push) Canceled after 35s
CI Pipeline / canary (push) Canceled after 36s
deploy / deploy (push) Canceled after 39s
Language-advantage pass on the drift_ledger Rust kernel (battery green
throughout, live parity re-verified 87/87 byte-identical after rebuild):
- Block lattice is a real enum (Constitution..Project) with as_str/of_name/
  limit matches — the constitution's stricter limit and its ANY-content-
  change strict check are exhaustive matches, not string compares; the
  per-block alert loop and snapshot assembly iterate BlockLabel::ALL.
- check_alerts is iterator-composed: BlockLabel::ALL.filter_map over the
  baseline decision, emitting alerts via then(..) — the python loop's
  anchor-vs-snapshot branch stays a plain match.
- diff_blocks is zero-copy iterator composition (filter_map + take(6)/
  take(3) replaces the manual capped pushes); the whitespace-significance
  filter is one shared fn item over &str slices.
- All keyword tables and contents borrow (&str / &Value) end-to-end — no
  per-block copying or lowering.
- Frozen DL truth replay untouched and VERBATIM (branch_battery.rs replays
  the same 87-case oracle truth).
2026-10-12 14:33:28 +01:00
Nick c0ec3135c6 feat(drift_ledger): port remaining cores to FULL Rust — dichotomy wave
DICHOTOMY VERDICT (dichotomy law, #2479 tranche): drift_ledger's
deterministic cores complete their Rust port in src/rust/drift_ledger —
the check() alert loop (anchor-vs-snapshot baseline, the constitution's
ANY-content-change strict check, max(prev_size,1) floor, pct/limit
round-3 with Python ties-to-even semantics, root-cause diff per alert),
snapshot/anchor entry assembly (size = the oracle's len() CHARACTER count),
the auto-fix routing and the retroactive fix() gate. SQLite store IO
(read_block/restore_topic) and file IO (load/save ledger, journal,
surface_alert) stay facade seams by design — the clock travels in the
doc envelope.

Wiring: dl_loader.py grows dl_ledger_path/dl_snapshot_entry/
dl_anchor_record/dl_check_alerts/dl_fix_decision (doc-envelope FFI,
oracle-verbatim dict unwrap, free via capability_scoring cs_free_string
— no free symbol re-export); facade delegation in drift_ledger.py with
the RUNE_DL_CORE=python pin and byte-identical python fallbacks.

Truth: scripts/gen_drift_ledger_truth.py freezes 87 cases from the live
oracle across EVERY public function and branch (sha, diff caps/truncation,
ledger path joins, load/save ledger bytes, store read filters and order,
journal/surface_alert file formats, snapshot/anchor/check/auto_fix/fix
end-to-end flows with frozen clock, restore_topic directive preservation
+ priority map, handle_hook, main CLI exits, and the kernel-level
k_snapshot/k_anchor/k_alerts/k_auto_fix/k_fix groups) — battery 8 tests
replay it VERBATIM; live parity 87/87 byte-identical under RUNE_DL_CORE=auto.

Audit instrument fixes (bug-first, with proof): module_to_keys now maps
hyphenated legacy twins to their underscore implementation (drift-ledger.py
— the same ledger — was scored NOT TOUCHED against an empty corpus) and
curator gains its curator-core alias (a FULL Go port since a95c6ce1 was
invisible to the audit). Honest post-fix queue: curator 40/43 FULL,
drift-ledger 14/14 FULL, drift_ledger 20/22 FULL; function-level
1020/2781 (37%), FULL 36 PARTIAL 93 NOT TOUCHED 108.
2026-10-12 14:33:28 +01:00
conductor 622cceb2f9 chore(release): auto-bump version to 0.237.0 — earned by # (minor: converged release (push-to-master bump))
deploy / deploy (push) Skipped
CI Pipeline / best-practices (push) Successful in 7s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / rust-surface (push) Failing after 12s
CI Pipeline / go-surface (push) Failing after 15s
Auto-version and Release / release (push) Successful in 29s
CI Pipeline / gitea-offline (push) Successful in 15s
CI Pipeline / type-check (push) Failing after 44s
CI Pipeline / lint (push) Failing after 58s
CI Pipeline / canary (push) Failing after 2m6s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
CI Pipeline / unit-tests (push) Canceled after 9m6s
Promotion record: (unknown)
2026-10-12 14:25:57 +01:00
Nick b50e8553d7 refactor(construction_patterns_builtin): Phase B language-advantage pass — dichotomy wave
CI Pipeline / best-practices (push) Successful in 8s
CI Pipeline / documentation (push) Successful in 8s
CI Pipeline / rust-surface (push) Failing after 13s
CI Pipeline / go-surface (push) Failing after 15s
CI Pipeline / gitea-offline (push) Successful in 17s
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 28s
deploy / deploy (push) Canceled after 15s
CI Pipeline / type-check (push) Canceled after 21s
CI Pipeline / canary (push) Canceled after 32s
CI Pipeline / unit-tests (push) Canceled after 33s
CI Pipeline / lint (push) Canceled after 34s
Typed registry: the frozen oracle docs parse once into BuiltinPattern
values (pattern_id/revision extracted, doc kept oracle-shaped); the FFI
re-emits from the typed value instead of re-plumbing raw Json. Miss-
before-revision-check oracle ordering is now explicit in the typed
lookup. Registry revision pinned in its own OnceLock.

Truth extended (+2 cases from the live oracle, RUNE_CPB_CORE=python
pinned): unknown id WITH a revision -> None (miss is decided before the
revision check — oracle order) and the revision=0 boundary on a real
pattern (ValueError, exact message). Branch battery now 19 cases,
replayed verbatim, green throughout; clippy 0; live parity 16/16.
2026-10-12 14:25:29 +01:00
Nick 4649df1ccc feat(construction_patterns_builtin): port to FULL Rust — dichotomy wave
RUST verdict (dichotomy law, #2479 tranche): construction_patterns_
builtin.py's registry surface is ported to src/rust/construction_
patterns_builtin — the four builtin pattern docs (capability-module-
adaptation, release-deployment-change, ui-tui-structural-change, store-
schema-evolution) served verbatim from frozen_cpb_patterns.json (deep-
copied from the live oracle's to_dict() output incl. __init__-sorted
provenance order), PATTERN_REGISTRY_REVISION, and get_pattern's full
branch set (hit per pattern_id, unknown/empty-id miss -> None, revision
match passthrough, revision mismatch with the oracle's exact ValueError
message).

Wiring: cpb_loader.py ctypes loader mirroring cs_loader.py/broker_
loader.py (doc-envelope FFI cpb_registry_revision/cpb_patterns/cpb_
get_pattern, free via capability_scoring cs_free_string — never
re-exported, CPB_RUST_LIB override), facade delegation in construction_
patterns_builtin.get_pattern with RUNE_CPB_CORE=python fallback pin and
strangler fallback to the Python registry when the .so is absent.

Truth: frozen_cpb_truth.json — 17 cases from the live oracle (deep-
copied inputs, RUNE_CPB_CORE=python pinned, every public fn and every
branch incl. all four patterns and the revision-mismatch raise),
replayed VERBATIM by the in-crate cfg(test) branch battery. Live parity
check (scripts/check_cpb_parity.py): 16/16 facade + kernel-surface
cases. clippy: 0 warnings. Consumer suites green (test_construction_
pattern 30, project_build_lifecycle, provider_ownership).

Audit fix (fix-first with proof): dichotomy_audit.py now aliases
curator -> curator-core (ported FULL Go in a95c6ce1 but mislabeled NOT
TOUCHED — the alias was missing from module_to_keys). Regenerated
audit: curator 40/43 FULL; function-level completion 34% -> 36%.
2026-10-12 14:25:29 +01:00
Nick fa4f9a2cbb chore(store): mutation_guards contract evidence — final SHAs after rebase: parity e9d3efae, phase B 27afd002 (contract BUILDER-2026-10-12T12:56:54Z-2ab0e0-factory-wave)
Auto-version and Release / release (push) Successful in 34s
deploy / deploy (push) Skipped
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 / rust-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
2026-10-12 14:00:44 +01:00
conductor c4aa7dfab7 chore(release): auto-bump version to 0.236.0 — earned by # (minor: converged release (push-to-master bump))
deploy / deploy (push) Skipped
CI Pipeline / best-practices (push) Successful in 8s
CI Pipeline / documentation (push) Successful in 5s
CI Pipeline / rust-surface (push) Failing after 14s
CI Pipeline / go-surface (push) Failing after 18s
Auto-version and Release / release (push) Successful in 27s
CI Pipeline / gitea-offline (push) Successful in 20s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
CI Pipeline / unit-tests (push) Canceled after 51s
CI Pipeline / lint (push) Canceled after 51s
CI Pipeline / type-check (push) Canceled after 41s
CI Pipeline / canary (push) Canceled after 56s
Promotion record: (unknown)
2026-10-12 13:59:43 +01:00
Nick fe2755dbfc chore(store): close mutation_guards dichotomy contract (parity 317308cb, phase B 2c1cf554)
CI Pipeline / best-practices (push) Successful in 8s
CI Pipeline / documentation (push) Successful in 7s
CI Pipeline / rust-surface (push) Failing after 12s
CI Pipeline / go-surface (push) Failing after 17s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
CI Pipeline / unit-tests (push) Canceled after 21s
CI Pipeline / canary (push) Canceled after 21s
Auto-version and Release / release (push) Canceled after 29s
deploy / deploy (push) Canceled after 5s
CI Pipeline / type-check (push) Canceled after 15s
CI Pipeline / gitea-offline (push) Canceled after 17s
CI Pipeline / lint (push) Canceled after 26s
2026-10-12 13:58:41 +01:00
Nick 27afd002a7 refactor(mutation_guards): Phase B language-advantage pass — dichotomy wave
The classification surface is parsed ONCE into Kind/Op/Scope enums
(unknown values become the exhaustive Other variant, preserving the
oracle's severity-default quirk), so every downstream rule — severity
tables, the identity-family guard, the destructive gate — is an
exhaustive match over typed variants instead of repeated string
comparisons; the verdict is a Verdict enum with as_str, the destructive
gate is a named is_destructive_unbacked predicate over typed variants,
and the whole return shape is one Decision struct projected to JSON in a
single place.

Live parity re-verified post-pass: facade 43/43 + kernel 43/43
(scripts/gen_mutation_guards_truth.py --replay), clippy 0, cargo fmt
clean, test_governance_quartet 15/15 both modes, battery verbatim
unchanged.
2026-10-12 13:58:41 +01:00
Nick e9d3efae5d feat(mutation_guards): port to FULL Rust — dichotomy wave
DICHOTOMY VERDICT: RUST (deterministic classification core — severity
lattices, branch order, verdict+reason+recovery are pure logic; zero IO).

Ported (name-preserving, src/rust/mutation_guards/src/lib.rs):
- classify_mutation (the full gate flow: unattributed identity-family
  DENY first — it beats read ops and the destructive gate — then the
  destructive-unbacked DENY, CONFIRM via score >= 4 or identity-kind +
  mutating-op, ALLOW; severity-table defaults for unknown kind/op/scope)
- guard_mutation (dict.get defaults: missing-key default vs present-null
  falsy passthrough — 0/[]/None falsy, non-empty strings truthy)
- mu_classify/mu_guard FFI exports (doc-envelope law: one JSON doc in,
  {ok, value|err} out; no free-string export — callers free through
  capability_scoring's cs_free_string)

Python semantics preserved exactly: Python truthiness over JSON values,
str(None) rendering in the reason f-strings, the exact reason/recovery
strings.

Truth: scripts/gen_mutation_guards_truth.py freezes 43 cases from the
live oracle (frozen_mg_truth.json embedded verbatim; RUNE_MG_CORE=python
pinned, deep-copied inputs) — every branch of both public functions:
DENY-1 precedence proofs (falsy 0/None attribution, read-op denial),
DENY-2 boundary (op/kind severity-2, falsy-zero backup), CONFIRM score-4
boundary and identity-rule, truthy non-bool backups, ALLOW lattice with
unknown kind/op/scope defaults, null kind/operation rendered as None,
guard defaults/extra-keys/empty-mutation. Battery green VERBATIM in-crate
(whole-doc compare). Live parity: facade 43/43 + kernel 43/43 (direct
loader), RUNE_MG_CORE=python fallback 43/43. Python tests green both
modes (test_governance_quartet 15/15).

Contract: BUILDER-2026-10-12T12:11:14Z-ab92a8-factory-wave (walked
draft→approved→building→verifying→closed back-to-back in ONE process,
scripts/open_contract_mutation_guards.py).
2026-10-12 13:58:40 +01:00
Nick 569ee78d0a chore(scripts): spine_language dichotomy contract walk script
CI Pipeline / best-practices (push) Successful in 8s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / rust-surface (push) Failing after 14s
CI Pipeline / go-surface (push) Failing after 16s
Auto-version and Release / release (push) Successful in 29s
CI Pipeline / gitea-offline (push) Successful in 20s
CI Pipeline / type-check (push) Failing after 41s
CI Pipeline / lint (push) Failing after 58s
CI Pipeline / canary (push) Failing after 2m38s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
CI Pipeline / unit-tests (push) Canceled after 4m40s
deploy / deploy (push) Canceled after 4m46s
2026-10-12 13:53:54 +01:00
conductor 9ebf7865ca chore(release): auto-bump version to 0.235.0 — earned by # (minor: converged release (push-to-master bump))
deploy / deploy (push) Skipped
CI Pipeline / best-practices (push) Successful in 8s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / rust-surface (push) Failing after 13s
CI Pipeline / go-surface (push) Failing after 16s
Auto-version and Release / release (push) Successful in 30s
CI Pipeline / gitea-offline (push) Successful in 21s
CI Pipeline / type-check (push) Failing after 41s
CI Pipeline / lint (push) Failing after 58s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
CI Pipeline / canary (push) Canceled after 1m21s
CI Pipeline / unit-tests (push) Canceled after 1m26s
Promotion record: (unknown)
2026-10-12 13:53:06 +01:00
Nick 9d23d3f50a chore(store): close spine_language dichotomy contract BUILDER-2026-10-12T12:50:19Z-9317b1-factory-wave (parity 79b6e381, phase B 2d13846f)
CI Pipeline / best-practices (push) Successful in 8s
CI Pipeline / documentation (push) Successful in 8s
CI Pipeline / rust-surface (push) Failing after 13s
CI Pipeline / go-surface (push) Failing after 16s
CI Pipeline / gitea-offline (push) Successful in 17s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
deploy / deploy (push) Canceled after 15s
CI Pipeline / type-check (push) Canceled after 21s
Auto-version and Release / release (push) Canceled after 30s
CI Pipeline / canary (push) Canceled after 32s
CI Pipeline / lint (push) Canceled after 34s
CI Pipeline / unit-tests (push) Canceled after 35s
2026-10-12 13:52:16 +01:00
Nick 2d13846f86 refactor(spine_language): Phase B language-advantage pass — dichotomy wave 2026-10-12 13:50:14 +01:00
Nick 79b6e38148 feat(spine_language): port to FULL Rust — dichotomy wave 2026-10-12 13:50:14 +01:00
Nick 875ca43ad1 chore(store): close abstraction_ladder dichotomy contract BUILDER-2026-10-12T12:12:17Z-a146ec-factory-wave (parity 4d0883b3, phase B 00bf4d21)
CI Pipeline / best-practices (push) Successful in 9s
CI Pipeline / documentation (push) Successful in 8s
CI Pipeline / rust-surface (push) Failing after 13s
CI Pipeline / go-surface (push) Failing after 18s
Auto-version and Release / release (push) Successful in 27s
CI Pipeline / gitea-offline (push) Successful in 18s
CI Pipeline / type-check (push) Failing after 47s
CI Pipeline / lint (push) Failing after 1m0s
CI Pipeline / canary (push) Failing after 2m12s
deploy / deploy (push) Failing after 15m35s
CI Pipeline / unit-tests (push) Failing after 16m45s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Failing after 4s
2026-10-12 13:13:36 +01:00
Nick b52aa5bbbc chore(store): dedupe observation append in behaviour_ledger contract close
CI Pipeline / best-practices (push) Successful in 9s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / rust-surface (push) Failing after 16s
CI Pipeline / go-surface (push) Failing after 19s
Auto-version and Release / release (push) Successful in 30s
CI Pipeline / gitea-offline (push) Successful in 22s
CI Pipeline / type-check (push) Failing after 46s
CI Pipeline / lint (push) Failing after 1m4s
CI Pipeline / canary (push) Failing after 2m17s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
CI Pipeline / unit-tests (push) Canceled after 6m41s
deploy / deploy (push) Canceled after 6m46s
2026-10-12 13:06:57 +01:00
conductor 165b4bd8d3 chore(release): auto-bump version to 0.234.0 — earned by # (minor: converged release (push-to-master bump))
deploy / deploy (push) Skipped
CI Pipeline / best-practices (push) Successful in 7s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / rust-surface (push) Failing after 13s
CI Pipeline / go-surface (push) Failing after 17s
Auto-version and Release / release (push) Successful in 28s
CI Pipeline / gitea-offline (push) Successful in 20s
CI Pipeline / type-check (push) Failing after 44s
CI Pipeline / lint (push) Failing after 58s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
CI Pipeline / unit-tests (push) Canceled after 1m55s
CI Pipeline / canary (push) Canceled after 1m56s
Promotion record: (unknown)
2026-10-12 13:04:57 +01:00
Nick 00bf4d210c refactor(abstraction_ladder): Phase B language-advantage pass — dichotomy wave
CI Pipeline / best-practices (push) Successful in 8s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / rust-surface (push) Failing after 13s
CI Pipeline / go-surface (push) Failing after 16s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
CI Pipeline / lint (push) Canceled after 25s
CI Pipeline / canary (push) Canceled after 25s
CI Pipeline / unit-tests (push) Canceled after 25s
CI Pipeline / gitea-offline (push) Canceled after 11s
deploy / deploy (push) Canceled after 29s
Auto-version and Release / release (push) Canceled after 31s
CI Pipeline / type-check (push) Canceled after 15s
2026-10-12 13:04:29 +01:00
Nick 4d0883b39c feat(abstraction_ladder): port to FULL Rust — dichotomy wave 2026-10-12 13:04:29 +01:00
Nick 352299b614 chore(store): close behaviour_ledger dichotomy contract BUILDER-2026-10-12T09:54:16Z-77fa9d-factory-wave (parity 44b25064, phase B ba54bc6e)
CI Pipeline / best-practices (push) Successful in 10s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / rust-surface (push) Failing after 16s
CI Pipeline / go-surface (push) Failing after 22s
Auto-version and Release / release (push) Successful in 32s
CI Pipeline / gitea-offline (push) Successful in 23s
CI Pipeline / type-check (push) Failing after 49s
CI Pipeline / lint (push) Failing after 1m8s
CI Pipeline / canary (push) Failing after 2m26s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
deploy / deploy (push) Canceled after 3m51s
CI Pipeline / unit-tests (push) Canceled after 3m46s
2026-10-12 13:00:39 +01:00
conductor a26c5004a2 chore(release): auto-bump version to 0.233.0 — earned by # (minor: converged release (push-to-master bump))
deploy / deploy (push) Skipped
CI Pipeline / best-practices (push) Successful in 8s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / rust-surface (push) Failing after 12s
CI Pipeline / go-surface (push) Failing after 15s
Auto-version and Release / release (push) Successful in 26s
CI Pipeline / gitea-offline (push) Successful in 19s
CI Pipeline / type-check (push) Failing after 41s
CI Pipeline / lint (push) Failing after 56s
CI Pipeline / canary (push) Failing after 2m6s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
CI Pipeline / unit-tests (push) Canceled after 15m6s
Promotion record: (unknown)
2026-10-12 12:45:32 +01:00
Nick ba54bc6e8f refactor(behaviour_ledger): Phase B language-advantage pass — dichotomy wave
CI Pipeline / documentation (push) Successful in 7s
CI Pipeline / best-practices (push) Successful in 8s
CI Pipeline / rust-surface (push) Failing after 13s
CI Pipeline / go-surface (push) Failing after 16s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
CI Pipeline / lint (push) Canceled after 26s
CI Pipeline / unit-tests (push) Canceled after 26s
CI Pipeline / canary (push) Canceled after 26s
CI Pipeline / gitea-offline (push) Canceled after 18s
Auto-version and Release / release (push) Canceled after 28s
deploy / deploy (push) Canceled after 15s
CI Pipeline / type-check (push) Canceled after 21s
2026-10-12 12:45:03 +01:00
Nick 44b25064c9 feat(behaviour_ledger): port to FULL Rust — dichotomy wave 2026-10-12 12:45:03 +01:00
Nick 7c83200c2e chore(store): close experience_encoding dichotomy contract BUILDER-2026-10-12T10:31:15Z-0436f5-factory-wave (parity d929b9a0, phase B 6a311f80)
CI Pipeline / best-practices (push) Successful in 8s
CI Pipeline / documentation (push) Successful in 5s
CI Pipeline / rust-surface (push) Failing after 17s
Auto-version and Release / release (push) Successful in 30s
CI Pipeline / go-surface (push) Failing after 20s
CI Pipeline / gitea-offline (push) Successful in 19s
CI Pipeline / type-check (push) Failing after 43s
CI Pipeline / lint (push) Failing after 59s
CI Pipeline / canary (push) Failing after 2m12s
deploy / deploy (push) Failing after 15m36s
CI Pipeline / unit-tests (push) Failing after 16m33s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Failing after 5s
2026-10-12 12:09:14 +01:00
conductor 9b7565b536 chore(release): auto-bump version to 0.232.0 — earned by # (minor: converged release (push-to-master bump))
deploy / deploy (push) Skipped
CI Pipeline / best-practices (push) Successful in 17s
CI Pipeline / documentation (push) Successful in 6s
CI Pipeline / rust-surface (push) Failing after 25s
CI Pipeline / go-surface (push) Failing after 26s
Auto-version and Release / release (push) Successful in 43s
CI Pipeline / gitea-offline (push) Successful in 22s
CI Pipeline / lint (push) Failing after 2m1s
CI Pipeline / type-check (push) Failing after 1m39s
CI Pipeline / canary (push) Failing after 3m38s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
CI Pipeline / unit-tests (push) Canceled after 7m1s
Promotion record: (unknown)
2026-10-12 12:02:45 +01:00
Nick 6a311f80d3 refactor(experience_encoding): Phase B language-advantage pass — dichotomy wave
CI Pipeline / best-practices (push) Successful in 8s
CI Pipeline / documentation (push) Successful in 9s
CI Pipeline / rust-surface (push) Failing after 17s
CI Pipeline / go-surface (push) Failing after 24s
CI Pipeline / gitea-offline (push) Successful in 27s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
CI Pipeline / unit-tests (push) Canceled after 41s
CI Pipeline / lint (push) Canceled after 41s
CI Pipeline / canary (push) Canceled after 41s
deploy / deploy (push) Canceled after 26s
Auto-version and Release / release (push) Canceled after 45s
CI Pipeline / type-check (push) Canceled after 42s
Rust-advantage pass over the experience_encoding kernel, pinned by the
unchanged frozen-truth battery (whole-doc replay, battery file byte-
identical to the parity commit, green throughout):

- Admission enum (Ignore/Evidence/Episode) with exhaustive as_str match
  replaces the string-verdict soup.
- Reason enum with Display carries the oracle's reason strings
  byte-for-byte; the EPISODE share reason materializes its sorted-top3
  term list lazily (Shares(BTreeSet<String>) → take(3) at Display time).
- Decision enum (Ignore/Evidence/Episode(Reason)) drives the whole gate
  flow with exhaustive matches — verdict() and reason() projections
  reconstruct the oracle doc; every arm statically total.
- Duplicate scan compares whitespace-token iterators over the lowered
  texts (split_whitespace().eq) — NEITHER " ".join(...) normal form is
  materialized anymore.
- Episode text extraction is a zero-copy filter_map over borrowed
  episode values (no intermediate Option juggling).

Live parity re-verified post-pass: facade 29/29 + kernel 29/29
(scripts/gen_experience_encoding_truth.py --replay), clippy 0,
test_governance_quartet 15/15.
2026-10-12 12:00:34 +01:00
Nick d929b9a068 feat(experience_encoding): port to FULL Rust — dichotomy wave
RUST verdict (dichotomy law, #2479 tranche, contract
BUILDER-2026-10-12T10:31:15Z-0436f5-factory-wave): experience_encoding's
single admission decision is ported to src/rust/experience_encoding —
the full event → evaluate → classify → attach-provenance flow (empty/
short char-count gate with its 14/15 boundary, whitespace/case-insensitive
duplicate scan over normalized episode texts, framing-only no-lexical-
signal filter, grounded → EPISODE/EVIDENCE split with sorted-top3 share
reasons and novel-axis grounding, provenance attachment with the
dict.get quirk preserved: a present-but-null source/session_id pins as
null — the default applies only to a MISSING key).

LEXICON-SEAM LAW mirrored: the oracle's flat `import lexicon_evolution`
is an environment seam (ImportError → conservative evidence admission,
which also BEATS a would-be duplicate because the dup check runs inside
the try). The Rust kernel mirrors it via the payload's
`lexicon_available` flag; the facade derives it from the live import.
The lexical decisions themselves cross to the lexicon_evolution sibling
crate IN-PROCESS (library dependency, the exact mirror of the oracle's
import — no FFI hop).

Wiring: ee_loader.py ctypes loader mirroring broker_loader.py/le_loader.py
(doc-envelope FFI, oracle-verbatim dict unwrap, free via
capability_scoring cs_free_string — no free symbol re-export), facade
delegation in experience_encoding.py with RUNE_EE_CORE=python fallback
pin, _py_evaluate preserved as the load-bearing oracle body.

Truth: frozen_ee_truth.json — 29 cases from the live oracle (deep-copied
inputs, RUNE_EE_CORE=python pinned, every branch incl. all three
verdicts and all five reasons, provenance null/empty-string quirks,
punctuation-survives-normalization non-duplicate, lexicon-blocked
ordering proofs, corpus fallback to episode texts, corpus override
killing the share but not the grounding), replayed VERBATIM by the
in-crate branch battery (whole-doc compare). Live parity: facade 29/29 +
kernel 29/29 (direct loader), RUNE_EE_CORE=python fallback 29/29.
Python tests green both modes (test_governance_quartet 15/15).
2026-10-12 11:56:55 +01:00
Nick 5680ec3e7b chore(store): close construction_pattern dichotomy contract BUILDER-2026-10-12T10:14:24Z-3f84ac-factory-wave (parity 681312c6, phase B c42936b9)
CI Pipeline / best-practices (push) Successful in 8s
CI Pipeline / documentation (push) Successful in 8s
CI Pipeline / rust-surface (push) Failing after 12s
CI Pipeline / go-surface (push) Failing after 15s
Auto-version and Release / release (push) Successful in 25s
CI Pipeline / gitea-offline (push) Successful in 17s
CI Pipeline / type-check (push) Failing after 43s
CI Pipeline / lint (push) Failing after 56s
CI Pipeline / canary (push) Failing after 2m4s
deploy / deploy (push) Failing after 15m30s
CI Pipeline / unit-tests (push) Failing after 16m33s
CI Pipeline / required-checks (push) Failing after 2s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Failing after 6s
2026-10-12 11:16:53 +01:00
Nick 9856b2b383 chore(store): close openai_compat_transport dichotomy contract BUILDER-2026-10-12T03:02:45Z-bd521d-factory-wave (parity 2e3b7e76)
CI Pipeline / best-practices (push) Successful in 7s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / rust-surface (push) Failing after 11s
CI Pipeline / go-surface (push) Failing after 15s
Auto-version and Release / release (push) Successful in 26s
CI Pipeline / gitea-offline (push) Successful in 19s
CI Pipeline / type-check (push) Failing after 41s
CI Pipeline / lint (push) Failing after 53s
CI Pipeline / canary (push) Failing after 2m2s
deploy / deploy (push) Failing after 15m27s
CI Pipeline / unit-tests (push) Failing after 16m4s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Failing after 4s
2026-10-12 04:05:15 +01:00
81 changed files with 65064 additions and 1670 deletions

No files matched your search

+1 -1
View File
@@ -1,6 +1,6 @@
# Rune
**Current release: v0.231.0**
**Current release: v0.238.0**
**Rune is a persistent personal agent designed to accumulate experience, develop reusable competence, and remain recognisably the same agent even when its underlying foundation model changes.**
+33 -23
View File
@@ -2,56 +2,47 @@
_Regenerate with `python3 scripts/dichotomy_audit.py --write`. A module is FULL only when 80%+ of its Python functions have name-preserving counterparts in the Go/Rust implementation — directories without the functions are facades and count as NOT TOUCHED._
**Function-level completion: 32%** (838/2627 runtime functions matched)
**Function-level completion: 37%** (1020/2781 runtime functions matched)
| Status | Modules | Lines | Meaning |
|---|---|---|---|
| FULL | 25 | 8218 | 80%+ of functions ported |
| PARTIAL | 89 | 44507 | 20–80% — real logic exists, gaps named below |
| NOT TOUCHED | 113 | 22686 | no name-preserving implementation |
| Total runtime | 227 | 75411 | |
| FULL | 36 | 13024 | 80%+ of functions ported |
| PARTIAL | 93 | 45658 | 20–80% — real logic exists, gaps named below |
| NOT TOUCHED | 108 | 19178 | no name-preserving implementation |
| Total runtime | 237 | 77860 | |
## NOT TOUCHED — the port queue, in priority order (largest first)
| Module | Lines | Fns | Side |
|---|---|---|---|
| rune_tui | 4170 | 159 | TUI-LAST |
| curator | 1838 | 43 | GO |
| canary_audit_ledger | 541 | 2 | TEST-INFRA |
| tui_validator | 514 | 13 | TUI-LAST |
| worthiness | 411 | 13 | unclassified |
| chat_bubble | 401 | 12 | TUI-LAST |
| drift-ledger | 355 | 14 | unclassified |
| pc_loader | 337 | 22 | unclassified |
| canary_rune_serve | 329 | 13 | TEST-INFRA |
| canary_p1_07 | 324 | 10 | TEST-INFRA |
| canary_p0_07 | 310 | 10 | TEST-INFRA |
| canary_p1_01 | 294 | 12 | TEST-INFRA |
| outcome_ledger | 286 | 7 | RUST |
| time_awareness | 286 | 14 | RUST |
| canary_p1_06 | 280 | 7 | TEST-INFRA |
| canary_p1_03 | 271 | 9 | TEST-INFRA |
| canary_gitea_automation | 263 | 3 | TEST-INFRA |
| canary_p1_02 | 253 | 10 | TEST-INFRA |
| construction_patterns_builtin | 249 | 1 | RUST |
| canary_full_system | 248 | 2 | TEST-INFRA |
| task_gate | 235 | 8 | RUST |
| sm_loader | 239 | 14 | unclassified |
| canary_native | 231 | 3 | TEST-INFRA |
| lexicon_evolution | 229 | 7 | unclassified |
| openai_compat_transport | 227 | 9 | GO |
| shard_manager | 223 | 9 | RUST |
| spine_language | 218 | 5 | RUST |
| canary_p0_04 | 207 | 8 | TEST-INFRA |
| wr_loader | 206 | 20 | unclassified |
| cc_loader | 205 | 28 | RUST |
| forge_client | 203 | 18 | GO |
| canary_context_broker | 199 | 10 | TEST-INFRA |
| canary_p0_05 | 199 | 13 | TEST-INFRA |
| cl_loader | 196 | 20 | unclassified |
| testing_mode | 196 | 12 | RUST |
| grading | 193 | 3 | unclassified |
| canary_spine_meta | 192 | 7 | TEST-INFRA |
| message_graph | 187 | 12 | unclassified |
| ffi_query_memory_smoke | 183 | 5 | RUST |
| tm_loader | 178 | 13 | unclassified |
| smod_loader | 175 | 10 | unclassified |
| behaviour_ledger | 174 | 12 | RUST |
| canary_tui_transport | 174 | 7 | TEST-INFRA |
| sr_loader | 173 | 18 | unclassified |
@@ -60,6 +51,7 @@ _Regenerate with `python3 scripts/dichotomy_audit.py --write`. A module is FULL
| ms_loader | 163 | 16 | RUST |
| canary_gitea_pipeline | 159 | 2 | TEST-INFRA |
| dc_loader | 158 | 13 | unclassified |
| mg_loader | 152 | 14 | unclassified |
| mse_loader | 152 | 12 | unclassified |
| bb_loader | 151 | 14 | unclassified |
| forge | 146 | 11 | GO |
@@ -72,32 +64,36 @@ _Regenerate with `python3 scripts/dichotomy_audit.py --write`. A module is FULL
| ps_loader | 136 | 13 | unclassified |
| verification_layer | 136 | 11 | RUST |
| tui_backlog | 125 | 5 | TUI-LAST |
| le_loader | 124 | 10 | unclassified |
| state_checkpoint | 123 | 8 | unclassified |
| cp_loader | 122 | 9 | unclassified |
| dl_loader | 118 | 12 | unclassified |
| drift_resume | 118 | 3 | RUST |
| tl_loader | 118 | 9 | unclassified |
| bh_loader | 117 | 9 | unclassified |
| tg_loader | 117 | 7 | unclassified |
| canary_conversation_state | 115 | 2 | TEST-INFRA |
| contracts_loader | 112 | 8 | unclassified |
| gr_loader | 112 | 8 | unclassified |
| canary_query_memory | 111 | 2 | TEST-INFRA |
| tui_transport | 108 | 10 | TUI-LAST |
| canary_unbounded | 105 | 2 | TEST-INFRA |
| message_widgets | 104 | 6 | TUI-LAST |
| self_modify | 102 | 7 | unclassified |
| ct_loader | 103 | 8 | unclassified |
| sc_loader | 96 | 6 | RUST |
| inference_paths | 95 | 2 | unclassified |
| mutation_guards | 94 | 2 | unclassified |
| concept_topology | 90 | 7 | unclassified |
| cs_loader | 90 | 5 | RUST |
| ci_loader | 89 | 4 | unclassified |
| tc_loader | 88 | 5 | RUST |
| broker_loader | 86 | 5 | unclassified |
| canary_schema_migration | 86 | 1 | TEST-INFRA |
| ss_loader | 86 | 7 | unclassified |
| ol_loader | 85 | 4 | unclassified |
| to_loader | 85 | 4 | unclassified |
| ta_loader | 84 | 4 | unclassified |
| mems_loader | 83 | 5 | RUST |
| cm_loader | 82 | 4 | unclassified |
| dl_loader | 77 | 5 | unclassified |
| lc_loader | 75 | 5 | unclassified |
| cd_loader | 72 | 4 | unclassified |
| execution_trace | 71 | 4 | unclassified |
@@ -109,7 +105,6 @@ _Regenerate with `python3 scripts/dichotomy_audit.py --write`. A module is FULL
| cb_loader | 64 | 3 | unclassified |
| skill_compiler | 64 | 4 | unclassified |
| sg_loader | 63 | 4 | unclassified |
| sm_loader | 63 | 4 | unclassified |
| meaningful_events | 61 | 3 | unclassified |
| outcome_utility | 59 | 4 | unclassified |
| memory_types | 54 | 2 | unclassified |
@@ -166,17 +161,19 @@ _Regenerate with `python3 scripts/dichotomy_audit.py --write`. A module is FULL
| claim_audit | 484 | 4/14 | _get_warm_neuron_store, reframe, _graph_has, _warm_has, verify, journal |
| drift_core | 471 | 4/14 | _ms_core, _now_iso, _py_check_promotion_eligibility, promote_entry, compute_fama, compute_drift_velocity |
| model_profiles | 438 | 5/15 | get_profile_path, load_profile, save_profile, build_profile_from_ollama, scan_all_models, get_or_create_profile |
| testing_mode | 362 | 11/21 | _now, _tm_core, _py_default_state, _py_next_item, _py_record, _py_note_blocker |
| tier_manager | 262 | 5/15 | _cc_probe, cc_loader_pin, _py_core_now, _py_get_tier, _py_promote, _py_demote |
| witness | 446 | 8/18 | _ctx_budget, _now, _cc_probe, cc_loader_pin, _py_extract_topic, _py_detect_signals |
| drift_compression | 307 | 3/12 | _py_get_compression_ratio, _py_compress_text, _py_compress_entry, _py_should_warn, _py_warn_before_compress, get_compression_ratio |
| drift_ledger | 471 | 7/16 | ledger_path, read_block, load_ledger, save_ledger, surface_alert, snapshot |
| scaffold | 868 | 22/31 | init, set_prd, add_maintenance_item, bind_repo, observe_repo, repo_state_from_git |
| behavior | 537 | 6/14 | _bh_core, connect, _correction_matches_python, _habit_matches_python, _reinforce_rust, _reinforce_python |
| builder_bridge | 326 | 3/11 | _construction_store, _active_construction_lines, _default_selector, _cached_memory, _get_selector, _retrieve_memory |
| time_awareness | 402 | 12/20 | _clock_args, _ta_core, get_time_awareness, to_dict, _to_state, _apply_state |
| construction_pattern | 547 | 7/14 | _core, _need_dict, _surface_text, _path_matches, _entity_matches, _obligation_when_hit |
| contracts | 743 | 4/11 | _ct_core, _resolve_and_project, _run_contract_python, capture_signatures, save_signatures, load_signatures |
| graph_memory | 474 | 2/9 | now_iso, connect, resolve_entity, add_edge, query, stats |
| intent_continuity | 168 | 2/9 | to_dict, from_dict, _load, save, create_detour, resolve_detour |
| lexicon_evolution | 304 | 7/14 | _le_core, _py_content_words, _py_distinctive_terms, _py_shared_distinctive, _py_coherence, _py_grounded |
| pins | 282 | 5/12 | _store_path, _save, _detail_blob, unpin, _py_pin_recall, list_pins |
| voice_hybrid | 427 | 3/10 | _connect, _load_growth, _save_growth, source_profiles, always, grow |
| voice_sync | 299 | 3/10 | _now, get_delivery_register_from_db, get_delivery_register, update_rune_md, check_sync, handle_hook |
@@ -197,6 +194,7 @@ _Regenerate with `python3 scripts/dichotomy_audit.py --write`. A module is FULL
| construction_commands | 268 | 1/5 | _refuse, _validate, _accept, dispatch |
| drift_cache | 357 | 12/16 | _dc_core, _py_compute_priority, _py_qualifies_for_cache, main |
| drift_observability | 399 | 5/9 | _do_core, main, _load_alerts, _save_alerts |
| grading | 253 | 3/7 | _gr_core, _py_affirm_rule, _py_grade_fact, _py_tier_of |
| layout_contract | 336 | 2/6 | define_region, bind_component, amend_layout_contract, record_layout_observation |
| model_connect | 471 | 7/11 | _ctx_budget, _now, _get_cloud_models, handle_hook |
| prompt_sync | 487 | 11/15 | _now, _rust_available, get_store, main |
@@ -204,6 +202,7 @@ _Regenerate with `python3 scripts/dichotomy_audit.py --write`. A module is FULL
| self_improve | 246 | 1/5 | _now, suggest_improvements, handle_hook, main |
| stage_guards | 303 | 7/11 | _py_liveness_canary, _py_reconcile, to_dict, chk |
| subconscious_pass | 187 | 1/5 | main, _extract_topic, save_reflection, get_recent_findings |
| task_gate | 301 | 5/9 | _tg_core, _now, list_gates, main |
| construction_activation | 237 | 1/4 | _scaffold_for, _auto_structural_awareness, dispatch_from_decision |
| construction_mode | 412 | 2/5 | _contains_any, to_dict, from_dict |
| construction_projection | 254 | 1/4 | _contract_entity, validate, write_mini_doc |
@@ -227,11 +226,14 @@ _Regenerate with `python3 scripts/dichotomy_audit.py --write`. A module is FULL
| Module | Lines | Fns | Side |
|---|---|---|---|
| curator | 1838 | 40/43 | GO |
| abstraction | 656 | 17/17 | GO |
| system_registry | 603 | 14/16 | RUST |
| construction_intent | 580 | 13/16 | RUST |
| capability_ledger | 571 | 18/19 | RUST |
| drift_ledger | 548 | 20/22 | RUST |
| event_wait | 518 | 14/14 | GO |
| worthiness | 469 | 12/14 | unclassified |
| tool_protocol | 461 | 22/27 | GO |
| auto_tasks | 460 | 14/14 | GO |
| programme_continuation | 458 | 11/12 | RUST |
@@ -240,12 +242,20 @@ _Regenerate with `python3 scripts/dichotomy_audit.py --write`. A module is FULL
| model_routing | 432 | 13/13 | GO |
| build_basis | 375 | 11/13 | RUST |
| attention_capsule | 365 | 6/7 | RUST |
| drift-ledger | 355 | 14/14 | unclassified |
| outcome_ledger | 318 | 7/8 | RUST |
| message_queue | 316 | 13/13 | GO |
| message_graph | 295 | 12/13 | unclassified |
| toolchain | 295 | 5/6 | RUST |
| shard_manager | 276 | 9/10 | RUST |
| auto_context | 273 | 8/8 | GO |
| openai_compat_transport | 227 | 9/9 | GO |
| evidence_probe | 213 | 9/9 | GO |
| forge_client | 203 | 18/18 | GO |
| self_modify | 149 | 7/8 | unclassified |
| recall_uplift | 141 | 1/1 | RUST |
| rune_spinner | 130 | 4/5 | RUST |
| concept_topology | 128 | 7/8 | unclassified |
| wait_guard | 116 | 1/1 | RUST |
| failure_recovery | 108 | 1/1 | RUST |
| programme_gates | 85 | 4/5 | RUST |
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "rune"
version = "0.231.0"
version = "0.238.0"
description = "Rune — a stateful coding assistant with persistent memory, orchestration, and TUI"
requires-python = ">=3.10"
+297
View File
@@ -0,0 +1,297 @@
#!/usr/bin/env python3
"""Live parity: the FULL Rust behaviour_ledger core (bl_loader) vs the
retired Python oracle (the ep_loader shim path — RUNE_BL_CORE=python),
dichotomy wave #2479. Replays the frozen truth corpus PLUS seeded fuzz
over every public function and branch. The clock is pinned (no-clock
law — the kernel takes `now` in), so nothing is masked.
The facade reads RUNE_BL_CORE per call, so one process can flip between
the pinned oracle and the wave core.
"""
from __future__ import annotations
import copy
import json
import os
import random
import sys
import tempfile
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src" / "rune"))
from rune import behaviour_ledger as bl # noqa: E402
TRUTH_PATH = Path(__file__).resolve().parents[1] / (
"src/rust/behaviour_ledger/src/frozen_bl_truth.json")
FROZEN = "2026-10-11T23:00:00Z"
CLOCK = {"n": 0}
bl._now = lambda: f"2026-10-11T23:{CLOCK['n'] // 60:02d}:{CLOCK['n'] % 60:02d}Z" # noqa: E731
def call_core(core, fn, *args, **kwargs):
old = os.environ.get("RUNE_BL_CORE")
os.environ["RUNE_BL_CORE"] = core
try:
return getattr(bl, fn)(*args, **kwargs)
finally:
if old is None:
os.environ.pop("RUNE_BL_CORE", None)
else:
os.environ["RUNE_BL_CORE"] = old
def fresh_ledger(doc=None):
led = bl.BehaviourLedger(Path(tempfile.mkdtemp(prefix="bl_par_")) / "s")
if doc is not None:
led.doc = copy.deepcopy(doc)
return led
def compare(label, ok, failures, detail):
if not ok:
failures.append((label, detail))
print(f"FAIL {label}\n{detail}")
return ok
def scenario(group, name, doc_in, op, failures):
"""Run one op under both cores on identical deep-copied pre-state;
compare (return | ValueError message, doc after)."""
py_ret = py_err = py_doc = None
try:
CLOCK["n"] = 0
led = fresh_ledger(doc_in)
py_ret = op(led)
py_doc = led.doc
except ValueError as e:
py_err = str(e)
rs_ret = rs_err = rs_doc = None
try:
CLOCK["n"] = 0
led = fresh_ledger(doc_in)
rs_ret = op(led)
rs_doc = led.doc
except ValueError as e:
rs_err = str(e)
ok = (json.dumps(py_ret, sort_keys=True, default=str)
== json.dumps(rs_ret, sort_keys=True, default=str)
and py_err == rs_err
and json.dumps(py_doc, sort_keys=True)
== json.dumps(rs_doc, sort_keys=True))
return compare(f"{group}:{name}", ok, failures,
f"py={json.dumps(py_ret, sort_keys=True, default=str)[:300]}"
f"/{py_err}\nrs={json.dumps(rs_ret, sort_keys=True, default=str)[:300]}"
f"/{rs_err}")
# ── fuzz pools ─────────────────────────────────────────────────────────
IDS = ["B-1", "B-2", "behaviour:B-2", "B-3", "GHOST", "behaviour:GHOST",
"UNI-é", "X" * 60, "b", "behaviour:b"]
PROJECTS = ["demo", "other", "démø", ""]
COMPONENTS = ["component:a", "component:b", "component:bridge",
"component:ünï", ""]
ENTS = ["component:a", "component:b", "component:bridge",
"interface:completion", "component:ünï", "interface:x", "domain:d"]
DETAILS = ["ok", "", "x" * 250, "café ✓ ünïcode", "multi\nline", "quote'"]
GENS = ["gen-1", "gen-2", "", "gén-3"]
CONFIRMERS = ["user", "ci-robot", "", "user ", "nick"]
NOTES = ["accepted for v1", "", "n" * 220, "»signed«"]
def rand_params(rng):
return {
"behaviour_id": rng.choice(IDS),
"owning_project": rng.choice(PROJECTS),
"intended_behaviour": rng.choice(DETAILS),
"origin_intent": rng.choice(DETAILS),
"pass_criteria": rng.choice(DETAILS),
"fail_criteria": rng.choice(DETAILS),
"procedure": rng.choice(DETAILS),
"owner_component": rng.choice(COMPONENTS),
"dependencies": rng.sample(ENTS, rng.randint(0, 3)),
"automated_ref": rng.choice(["tests/x.py", ""]),
"environment": rng.choice(["ci", "prod", ""]),
}
def rand_doc(rng):
"""Deterministic pre-state via the pinned-clock oracle (python pin)."""
old = os.environ.get("RUNE_BL_CORE")
os.environ["RUNE_BL_CORE"] = "python"
try:
led = fresh_ledger()
for i in range(rng.randint(0, 4)):
bid = f"B-{i}"
p = rand_params(rng)
p["behaviour_id"] = bid
try:
led.define(**p)
except ValueError:
pass
roll = rng.random()
if roll < 0.45: # full confirmation chain
led.record_auto_verification(
bid, passed=rng.random() < 0.85,
detail=rng.choice(DETAILS), generation=rng.choice(GENS))
if rng.random() < 0.8:
led.record_real_world(
bid, passed=rng.random() < 0.85,
detail=rng.choice(DETAILS),
generation=rng.choice(GENS),
confirmed_by=rng.choice(CONFIRMERS))
elif roll < 0.6:
led.record_auto_verification(
bid, passed=False, detail=rng.choice(DETAILS),
generation=rng.choice(GENS))
elif roll < 0.7:
led.accept_limitation(bid, note=rng.choice(NOTES))
return led.doc
finally:
if old is None:
os.environ.pop("RUNE_BL_CORE", None)
else:
os.environ["RUNE_BL_CORE"] = old
def main() -> int:
failures: list[tuple[str, str]] = []
n_truth = 0
# 1. frozen truth corpus (kernel-level replay lives in the crate's
# branch battery; replayed here at FACADE level, both cores)
truth = json.loads(TRUTH_PATH.read_text())["cases"]
def run_case(group, case):
i = case["input"]
if group == "define":
params = {k: v for k, v in i.items()
if k not in ("doc", "now")}
params["dependencies"] = params.get("dependencies")
op = lambda led: led.define(**params) # noqa: E731
elif group == "get":
op = lambda led: led.get(i["behaviour_id"]) # noqa: E731
elif group == "record_auto_verification":
params = {k: v for k, v in i.items()
if k not in ("doc", "now")}
op = lambda led: led.record_auto_verification( # noqa: E731
params["behaviour_id"], **{k: v for k, v in params.items()
if k != "behaviour_id"})
elif group == "record_real_world":
params = {k: v for k, v in i.items()
if k not in ("doc", "now")}
op = lambda led: led.record_real_world( # noqa: E731
params["behaviour_id"], **{k: v for k, v in params.items()
if k != "behaviour_id"})
elif group == "accept_limitation":
params = {k: v for k, v in i.items()
if k not in ("doc", "now")}
op = lambda led: led.accept_limitation( # noqa: E731
params["behaviour_id"], **{k: v for k, v in params.items()
if k != "behaviour_id"})
elif group == "invalidate_for_change":
params = {k: v for k, v in i.items()
if k not in ("doc", "now")}
params["changed_entities"] = set(params["changed_entities"])
op = lambda led: led.invalidate_for_change(**params) # noqa: E731
elif group == "retest_history":
op = lambda led: led.retest_history(i["behaviour_id"]) # noqa: E731
elif group == "outstanding":
params = {k: v for k, v in i.items() if k != "doc"}
op = lambda led: led.outstanding(**params) # noqa: E731
else: # summary
op = lambda led: led.summary() # noqa: E731
scenario(group, case["name"], i["doc"], op, failures)
for group, cases in truth.items():
for case in cases:
n_truth += 1
run_case(group, case)
# 2. seeded fuzz (seed 2479). ALL randomness is consumed by the
# opmaker ONCE — the closure it returns is deterministic, so both
# core runs see identical args (poisoned-truth law).
rng = random.Random(2479)
n_fuzz = 0
def pick_bid(r, doc):
"""Existing (raw key) or random id — consumed once."""
bids = list(doc.get("behaviours", {}))
if bids and r.random() < 0.7:
return r.choice(bids)
return r.choice(IDS)
def fuzz(group, opmaker, count):
nonlocal n_fuzz
for j in range(count):
n_fuzz += 1
doc = rand_doc(rng)
scenario(group, f"fuzz{j}", doc, opmaker(rng, doc), failures)
fuzz("define", lambda r, d: (lambda led, p=rand_params(r): led.define(**p)),
150)
fuzz("get", lambda r, d: (lambda led, b=r.choice(IDS): led.get(b)), 100)
def auto_op(r, d):
bid = pick_bid(r, d)
passed = r.random() < 0.6
detail = r.choice(DETAILS)
gen = r.choice(GENS)
return lambda led: led.record_auto_verification(
bid, passed=passed, detail=detail, generation=gen)
fuzz("record_auto_verification", auto_op, 150)
def rw_op(r, d):
bid = pick_bid(r, d)
passed = r.random() < 0.6
detail = r.choice(DETAILS)
gen = r.choice(GENS)
by = r.choice(CONFIRMERS)
return lambda led: led.record_real_world(
bid, passed=passed, detail=detail, generation=gen, confirmed_by=by)
fuzz("record_real_world", rw_op, 150)
def lim_op(r, d):
bid = pick_bid(r, d)
note = r.choice(NOTES)
by = r.choice(CONFIRMERS)
return lambda led: led.accept_limitation(bid, note=note, approved_by=by)
fuzz("accept_limitation", lim_op, 100)
def inv_op(r, d):
changed = set(r.sample(ENTS, r.randint(0, 3)))
reasons = ({"behaviour:B-0": "custom reason"} if r.random() < 0.3
else None)
gen = r.choice(GENS)
return lambda led: led.invalidate_for_change(
changed_entities=changed, generation=gen, reasons=reasons)
fuzz("invalidate_for_change", inv_op, 200)
fuzz("retest_history",
lambda r, d: (lambda led, b=r.choice(IDS): led.retest_history(b)),
80)
def out_op(r, d):
kw = {}
if r.random() < 0.5:
kw["component"] = r.choice(COMPONENTS)
if r.random() < 0.5:
kw["project"] = r.choice(PROJECTS)
return lambda led: led.outstanding(**kw)
fuzz("outstanding", out_op, 120)
fuzz("summary", lambda r, d: (lambda led: led.summary()), 50)
total = n_truth + n_fuzz
print(f"parity: {total - len(failures)}/{total} "
f"({n_truth} truth + {n_fuzz} fuzz seed 2479)")
return 1 if failures else 0
if __name__ == "__main__":
sys.exit(main())
+78
View File
@@ -0,0 +1,78 @@
#!/usr/bin/env python3
"""Live parity check: construction_patterns_builtin facade on the Rust core
vs the Python-pinned oracle (RUNE_CPB_CORE=python). Exercises every public
surface branch: BUILTIN_PATTERNS docs, registry revision, get_pattern hit
per pattern, unknown-id miss, revision match, revision mismatch (exact
exception type + message), revision=None. Deep-copies inputs per case.
"""
import copy
import json
import os
import sys
REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, os.path.join(REPO, "src"))
import rune.construction_patterns_builtin as cpb # noqa: E402
PIDS = [p.pattern_id for p in cpb.BUILTIN_PATTERNS]
cases = []
for pid in PIDS:
cases.append(("get_pattern", {"pattern_id": pid, "revision": None}))
rev = cpb.get_pattern(pid).revision
cases.append(("get_pattern", {"pattern_id": pid, "revision": rev}))
cases.append(("get_pattern", {"pattern_id": pid, "revision": rev + 1}))
cases += [
("get_pattern", {"pattern_id": "no-such-pattern", "revision": None}),
("get_pattern", {"pattern_id": "", "revision": None}),
("registry_revision", {}),
]
def run(pin):
os.environ["RUNE_CPB_CORE"] = "python" if pin else "auto"
out = []
for fn, args in cases:
try:
if fn == "get_pattern":
got = cpb.get_pattern(**copy.deepcopy(args))
out.append(("ok", None if got is None else got.to_dict()))
else:
out.append(("ok", cpb.PATTERN_REGISTRY_REVISION))
except ValueError as e:
out.append(("ValueError", str(e)))
return out
def run_rust_docs():
"""The Rust core's raw surface (registry docs + revision) directly."""
from rune import cpb_loader
return {
"revision": cpb_loader.registry_revision(),
"patterns": cpb_loader.patterns(),
"kernel": cpb_loader.available(),
}
want = run(pin=True)
got = run(pin=False)
match = sum(1 for w, g in zip(want, got) if w == g)
for i, (w, g) in enumerate(zip(want, got)):
if w != g:
print(f"DIVERGENCE case {i} {cases[i]}:\n python {w}\n rust {g}")
docs = run_rust_docs()
py_docs = [p.to_dict() for p in cpb.BUILTIN_PATTERNS]
if docs["patterns"] != py_docs or docs["revision"] != cpb.PATTERN_REGISTRY_REVISION:
print(f"DIVERGENCE kernel docs: rust rev={docs['revision']} patterns!=python" )
match = match # counted below as failure
docs_ok = False
else:
docs_ok = True
total = len(cases) + 1
matched = match + (1 if docs_ok else 0)
print(f"PARITY: {matched}/{total}")
sys.exit(0 if matched == total else 1)
+8
View File
@@ -107,11 +107,18 @@ def module_to_keys(module_stem: str) -> list[str]:
"""Candidate implementation keys for a Python module stem."""
kebab = module_stem.replace("_", "-")
keys = [kebab, module_stem]
# Hyphenated legacy twins (drift-ledger.py vs drift_ledger.py) share ONE
# implementation; the underscore form is the crate/dir name.
if "-" in module_stem:
keys.append(module_stem.replace("-", "_"))
# Deep ports and multi-module binaries register aliases:
aliases = {
"rune_operator": ["operator", "operator-deep", "operator-support", "core-server"],
"rune_tui": ["tui"],
"rune_operator_cli": ["operator"],
# curator was ported to FULL Go (src/go/curator-core, a95c6ce1);
# without this alias the audit mislabels it NOT TOUCHED.
"curator": ["curator-core"],
"memory_store": ["memory-ops", "memory-store-deep"],
"warm_router": ["warm-router", "operator-support"],
"tool_executor": ["tool-executor", "local-tool-runtime", "tool-protocol", "tool-protocol-rw"],
@@ -142,6 +149,7 @@ def module_to_keys(module_stem: str) -> list[str]:
"evidence_provenance": ["cognition-provenance", "cognition"],
"memory_scoring": ["cognition-scoring", "cognition"],
"capability_scoring": ["cognition-capability", "cognition"],
"curator": ["curator-core"],
"token_counting": ["cognition-token", "cognition"],
"query_memory": ["cognition-query", "cognition"],
}
+293
View File
@@ -0,0 +1,293 @@
#!/usr/bin/env python3
"""gen_abstraction_ladder_truth.py — freeze/replay parity truth for
abstraction_ladder (dichotomy wave, #2479 tranche).
Freeze mode (default): exercises EVERY public function of the LIVE Python
oracle (src/rune/abstraction_ladder.py) across every branch — next_stage
(all 7 stages / top / unknown / non-string), can_promote (empty item,
per-stage threshold miss at exactly threshold-1, the unknown-stage→"top"
quirk, falsy provenance variants, contradictions, missing/empty
consequence, belief/rule verified_outcome gate, full promote path per
stage) and promote (failure verdict + verbatim item, success item
mutation stage/sightings reset) — and records (kernel_input, expected)
into src/rust/abstraction_ladder/frozen_al_truth.json.
Replay mode (--replay): re-runs the SAME scenarios with the Rust core
delegation ACTIVE (RUNE_AL_CORE unset) and asserts both kernel-level
(loader direct) and facade-level (oracle functions delegate) parity
against the frozen file.
Poisoned-truth law: freeze pins RUNE_AL_CORE=python BEFORE any import.
Mutation law: case inputs are deep-copied BEFORE they reach the oracle.
SIGHTINGS-INT LAW: sightings are ints only in frozen cases — JSON cannot
distinguish int 2 from float 2.0, and the oracle's f-string renders them
differently ("2" vs "2.0"), so float sightings are unportable truth.
"""
import copy
import json
import os
import sys
from datetime import datetime, timezone
REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
TRUTH_PATH = os.path.join(REPO, "src", "rust", "abstraction_ladder",
"frozen_al_truth.json")
REPLAY = "--replay" in sys.argv
if not REPLAY:
os.environ["RUNE_AL_CORE"] = "python" # poisoned-truth law (freeze side)
else:
os.environ.pop("RUNE_AL_CORE", None) # live-parity law (facade delegates)
sys.path.insert(0, os.path.join(REPO, "src"))
sys.path.insert(0, os.path.join(REPO, "src", "rune"))
import abstraction_ladder as oracle_mod # noqa: E402
if REPLAY:
from rune import al_loader # noqa: E402
if not al_loader.available():
print("REPLAY ABORT: kernel not built —", al_loader.LIB_PATH)
sys.exit(2)
cases = []
def case(name, fn, kernel_input, expected):
cases.append({
"name": name,
"fn": fn,
"input": kernel_input,
"ops": [],
"expected": expected,
})
def complete(stage, sightings=None):
it = {"stage": stage, "sightings": oracle_mod.THRESHOLDS[stage],
"provenance": "witnessed", "contradictions": [],
"consequence": "changes behaviour", "verified_outcome": True}
if sightings is not None:
it["sightings"] = sightings
return it
# ── next_stage: every stage, top, unknown, non-string ──────────────────────
for i, stage in enumerate(oracle_mod.STAGES):
want = oracle_mod.STAGES[i + 1] if i + 1 < len(oracle_mod.STAGES) else None
case(f"next_stage:{stage}", "next_stage", {"stage": stage}, want)
case("next_stage:unknown", "next_stage", {"stage": "gossip"}, None)
case("next_stage:empty", "next_stage", {"stage": ""}, None)
case("next_stage:missing", "next_stage", {}, None)
case("next_stage:non_string", "next_stage", {"stage": 7}, None)
case("next_stage:prefix_like", "next_stage", {"stage": "episodex"}, None)
# ── can_promote: threshold branch per stage (one below) ────────────────────
for stage in oracle_mod.STAGES:
thr = oracle_mod.THRESHOLDS[stage]
item = {"stage": stage, "sightings": thr - 1, "provenance": "witnessed",
"contradictions": [], "consequence": "changes behaviour",
"verified_outcome": True}
want = oracle_mod.can_promote(copy.deepcopy(item))
case(f"can_promote:below_threshold:{stage}", "can_promote",
{"item": item}, want)
# exact-threshold boundary for every stage (full promote path)
for stage in oracle_mod.STAGES:
item = complete(stage)
want = oracle_mod.can_promote(copy.deepcopy(item))
case(f"can_promote:at_threshold:{stage}", "can_promote",
{"item": item}, want)
# empty item — defaults: stage episode, sightings 0
want = oracle_mod.can_promote({})
case("can_promote:empty_item", "can_promote", {"item": {}}, want)
# missing sightings only
want = oracle_mod.can_promote({"stage": "fact", "provenance": "witnessed",
"consequence": "c"})
case("can_promote:default_sightings", "can_promote",
{"item": {"stage": "fact", "provenance": "witnessed",
"consequence": "c"}},
want)
# non-string / null stage keys: item.get hands them to STAGES.index,
# which raises ValueError -> next_stage None -> "top" quirk
for key, bad_stage in (("non_string", 7), ("null", None)):
item = {"stage": bad_stage, "sightings": 9, "provenance": "witnessed",
"consequence": "c"}
want = oracle_mod.can_promote(copy.deepcopy(item))
case(f"can_promote:stage_{key}_quirk", "can_promote", {"item": item},
want)
# the unknown-stage quirk: next_stage(None result) collapses to "top"
want = oracle_mod.can_promote({"stage": "gossip", "sightings": 99,
"provenance": "witnessed",
"consequence": "c"})
case("can_promote:unknown_stage_quirk", "can_promote",
{"item": {"stage": "gossip", "sightings": 99,
"provenance": "witnessed", "consequence": "c"}},
want)
# falsy provenance variants: missing, empty string, empty list, 0, False
for key, prov in (("missing", None), ("empty_str", ""),
("empty_list", []), ("zero", 0), ("false", False)):
item = {"stage": "observation", "sightings": 5,
"contradictions": [], "consequence": "c"}
if prov is not None:
item["provenance"] = prov
want = oracle_mod.can_promote(copy.deepcopy(item))
case(f"can_promote:provenance_{key}", "can_promote", {"item": item}, want)
# truthy provenance variants survive the gate (promote path)
for key, prov in (("str", "log"), ("list", ["log"]), ("one", 1),
("true", True)):
item = {"stage": "episode", "sightings": 1, "provenance": prov,
"contradictions": [], "consequence": "c"}
want = oracle_mod.can_promote(copy.deepcopy(item))
case(f"can_promote:provenance_{key}_passes", "can_promote",
{"item": item}, want)
# contradictions: truthy list, truthy string, dict; falsy empty list /
# None / False pass
for key, contra, want_promote in (
("list", ["c1"], False), ("str", "disputed", False),
("dict", {"a": 1}, False), ("empty_list", [], True),
("none", None, True), ("false", False, True)):
item = {"stage": "episode", "sightings": 1, "provenance": "log",
"consequence": "c"}
if contra is not None:
item["contradictions"] = contra
want = oracle_mod.can_promote(copy.deepcopy(item))
case(f"can_promote:contradictions_{key}", "can_promote",
{"item": item}, want)
# consequence: missing / empty string / empty list / 0 fail; non-empty pass
for key, cons in (("missing", None), ("empty_str", ""),
("empty_list", []), ("zero", 0), ("str", "moves"),
("list", ["moves"])):
item = {"stage": "episode", "sightings": 1, "provenance": "log",
"contradictions": []}
if cons is not None:
item["consequence"] = cons
want = oracle_mod.can_promote(copy.deepcopy(item))
case(f"can_promote:consequence_{key}", "can_promote", {"item": item},
want)
# belief/rule verified_outcome gate: missing, False, 0 fail; True passes;
# concept needs NO verified outcome
for stage in ("belief", "rule"):
for key, vo in (("missing", None), ("false", False), ("zero", 0)):
item = {"stage": stage, "sightings": 9, "provenance": "log",
"contradictions": [], "consequence": "c"}
if vo is not None:
item["verified_outcome"] = vo
want = oracle_mod.can_promote(copy.deepcopy(item))
case(f"can_promote:{stage}_vo_{key}", "can_promote",
{"item": item}, want)
item = {"stage": stage, "sightings": 9, "provenance": "log",
"contradictions": [], "consequence": "c",
"verified_outcome": True}
want = oracle_mod.can_promote(copy.deepcopy(item))
case(f"can_promote:{stage}_vo_true", "can_promote", {"item": item}, want)
item = {"stage": "concept", "sightings": 3, "provenance": "log",
"contradictions": [], "consequence": "c",
"verified_outcome": False}
want = oracle_mod.can_promote(copy.deepcopy(item))
case("can_promote:concept_ignores_vo", "can_promote", {"item": item}, want)
# gate order: threshold beats provenance beats contradictions beats
# consequence beats verified_outcome
item = {"stage": "belief", "sightings": 1}
want = oracle_mod.can_promote(copy.deepcopy(item))
case("can_promote:gate_order_threshold_first", "can_promote",
{"item": item}, want)
item = {"stage": "belief", "sightings": 9, "contradictions": ["x"],
"consequence": "c", "verified_outcome": True}
want = oracle_mod.can_promote(copy.deepcopy(item))
case("can_promote:gate_order_provenance_second", "can_promote",
{"item": item}, want)
# ── promote: failure keeps the item verbatim ───────────────────────────────
for name, item in (
("empty", {}),
("below", {"stage": "observation", "sightings": 2,
"provenance": "log", "consequence": "c"}),
("top", {"stage": "skill", "sightings": 9, "provenance": "log",
"consequence": "c"}),
("unknown", {"stage": "gossip", "sightings": 9}),
("no_consequence", {"stage": "fact", "sightings": 4,
"provenance": "log"}),
("belief_no_vo", {"stage": "belief", "sightings": 9,
"provenance": "log", "consequence": "c"})):
want = oracle_mod.promote(copy.deepcopy(item))
case(f"promote:refusal_{name}", "promote", {"item": item}, want)
# ── promote: success mutates stage + resets sightings ──────────────────────
for stage in ("episode", "fact", "observation", "concept", "belief", "rule"):
item = complete(stage)
want = oracle_mod.promote(copy.deepcopy(item))
case(f"promote:success_{stage}", "promote", {"item": item}, want)
# extra fields preserved through the copy; only stage/sightings touched
item = {"stage": "rule", "sightings": 5, "provenance": "log",
"contradictions": [], "consequence": "c", "verified_outcome": True,
"id": "x9", "tags": ["a", "b"], "nested": {"k": [1, 2]}}
want = oracle_mod.promote(copy.deepcopy(item))
case("promote:extra_fields_preserved", "promote", {"item": item}, want)
# ── freeze / replay ─────────────────────────────────────────────────────────
def kernel_call(fn, kernel_input):
if fn == "next_stage":
return al_loader.next_stage(kernel_input.get("stage"))
if fn == "can_promote":
return al_loader.can_promote(kernel_input["item"])
return al_loader.promote(kernel_input["item"])
def facade_call(fn, kernel_input):
if fn == "next_stage":
return oracle_mod.next_stage(kernel_input.get("stage"))
if fn == "can_promote":
return oracle_mod.can_promote(kernel_input["item"])
return oracle_mod.promote(kernel_input["item"])
if not REPLAY:
for c in cases:
got = facade_call(c["fn"], copy.deepcopy(c["input"]))
assert got == c["expected"], (
f"self-check failed on {c['name']}: {got!r} != {c['expected']!r}")
doc = {
"frozen": datetime.now(timezone.utc).isoformat(),
"oracle": "src/rune/abstraction_ladder.py",
"pin": "RUNE_AL_CORE=python",
"note": "Frozen via scripts/gen_abstraction_ladder_truth.py "
"against the live Python oracle. Battery embeds this "
"VERBATIM. SIGHTINGS-INT LAW: sightings are ints only "
"(JSON cannot distinguish int 2 from float 2.0 and the "
"oracle's f-string renders them differently).",
"cases": cases,
}
with open(TRUTH_PATH, "w") as f:
json.dump(doc, f, indent=1)
f.write("\n")
print(f"FROZEN {len(cases)} cases -> {TRUTH_PATH}")
else:
fails = []
for c in cases:
want = c["expected"]
kg = kernel_call(c["fn"], copy.deepcopy(c["input"]))
if kg != want:
fails.append(f"KERNEL {c['name']}: {kg!r} != {want!r}")
fg = facade_call(c["fn"], copy.deepcopy(c["input"]))
if fg != want:
fails.append(f"FACADE {c['name']}: {fg!r} != {want!r}")
if fails:
print(f"REPLAY FAILED {len(fails)}/{len(cases)}:")
for f_ in fails:
print(" ", f_)
sys.exit(1)
print(f"REPLAY OK {len(cases)}/{len(cases)} (kernel + facade)")
+444
View File
@@ -0,0 +1,444 @@
#!/usr/bin/env python3
"""Freeze the parity truth for the behaviour_ledger Rust port (dichotomy
wave #2479 tranche).
Runs the LIVE oracle (src/rune/behaviour_ledger.py — the Phase G shim
serving the evidence_provenance `bl_*` core, retired as the Python
oracle by this port) across EVERY public function and EVERY branch:
define (minimal/full/prefixed id/duplicate/unicode), get (hit both id
forms/miss/empty), record_auto_verification (pass/fail/unknown/200-char
detail truncation/reverify-confirmed), record_real_world (user
confirmation/other-confirmer/fail/unknown), accept_limitation (ok/
custom approver/unknown), invalidate_for_change (dependency overlap
with the default sorted-list-repr reason / owner hit / no overlap →
preserved / custom reasons / reasons key miss / auto-verified-only
skip / mixed multi-behaviour), retest_history (populated generations/
unknown/empty), outstanding (mixed states with the TEST_FAILED-first
sort / component filter / project filter / combined / excluded
states), summary (empty/mixed integer counts).
The clock is pinned (bl._now) so truth is deterministic and
regenerable byte-for-byte. Case input docs are deep-copied BEFORE the
oracle runs (poisoned-truth law). Output:
src/rust/behaviour_ledger/src/frozen_bl_truth.json — embedded VERBATIM
by the cfg(test) branch battery (include_str!).
"""
from __future__ import annotations
import copy
import json
import sys
import tempfile
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
from rune import behaviour_ledger as bl # noqa: E402
TRUTH_PATH = Path(__file__).resolve().parents[1] / (
"src/rust/behaviour_ledger/src/frozen_bl_truth.json")
class Clock:
"""Pinned clock: %Y-%m-%dT%H:%M:%SZ stamps incrementing by second
(the oracle's _now format), reset per case."""
def __init__(self):
self.n = 0
def now(self) -> str:
self.n += 1
return f"2026-10-11T23:00:{self.n:02d}Z"
CLOCK = Clock()
bl._now = CLOCK.now # pin the facade's clock (the kernel takes `now` in)
def fresh() -> bl.BehaviourLedger:
CLOCK.n = 0
tmp = tempfile.mkdtemp(prefix="bl_truth_")
return bl.BehaviourLedger(Path(tmp) / "store")
def envelope_ok(value) -> dict:
return {"ok": True, "value": value}
def envelope_err(msg: str) -> dict:
return {"ok": False, "err": msg}
CASES: dict[str, list] = {}
def add(group: str, case: dict) -> None:
CASES.setdefault(group, []).append(case)
def snap(led: bl.BehaviourLedger) -> dict:
return copy.deepcopy(led.doc)
def next_now() -> str:
"""The stamp the NEXT pinned _now() call will return."""
return f"2026-10-11T23:00:{CLOCK.n + 1:02d}Z"
def run_mutation(group: str, name: str, led: bl.BehaviourLedger,
params: dict, op) -> None:
"""Run one mutating op (kwargs only); record {input: {doc, now,
**params}, expected: {ok, value: {doc, rec | recs}}} or the
ValueError envelope. The op under test stamps via the pinned clock;
its stamp is recorded in the input (no-clock law — the kernel takes
`now` in)."""
doc_in = snap(led)
stamp = next_now()
try:
out = op(**params)
except ValueError as e:
add(group, {"name": name,
"input": {"doc": doc_in, "now": stamp, **params},
"expected": envelope_err(str(e))})
return
value_key = "recs" if isinstance(out, list) else "rec"
add(group, {"name": name, "input": {"doc": doc_in, "now": stamp,
**params},
"expected": envelope_ok({"doc": led.doc,
value_key: out})})
def seed_confirmed(led: bl.BehaviourLedger, bid: str, owner: str,
deps: list[str]) -> None:
led.define(behaviour_id=bid, owning_project="demo",
intended_behaviour=f"{bid} behaves",
owner_component=owner, dependencies=deps)
led.record_auto_verification(bid, passed=True, generation="gen-1")
led.record_real_world(bid, passed=True, detail="user confirmed",
generation="gen-1")
# ── define ────────────────────────────────────────────────────────────────
led = fresh()
run_mutation("define", "minimal", led, {
"behaviour_id": "MIN-1", "owning_project": "demo",
"intended_behaviour": "does the thing"},
led.define)
led = fresh()
run_mutation("define", "full_kwargs", led, {
"behaviour_id": "FULL-1", "owning_project": "demo",
"intended_behaviour": "bridge completes events",
"origin_intent": "issue #19F", "pass_criteria": "events done",
"fail_criteria": "events dropped", "procedure": "run the suite",
"owner_component": "component:event_bridge",
"dependencies": ["component:event_bridge",
"interface:event_completion"],
"automated_ref": "tests/test_bridge.py", "environment": "ci"},
led.define)
led = fresh()
run_mutation("define", "prefixed_id_preserved", led, {
"behaviour_id": "behaviour:PRE-1", "owning_project": "demo",
"intended_behaviour": "already prefixed"},
led.define)
led = fresh()
run_mutation("define", "unicode_fields", led, {
"behaviour_id": "UNI-1", "owning_project": "démø",
"intended_behaviour": "completes événements — ✓ ünïcode",
"origin_intent": "»quoted«", "pass_criteria": "✓ pass",
"owner_component": "component:kafé",
"dependencies": ["component:kafé", "interface:ünï"],
"environment": "prod-ü"},
led.define)
led = fresh()
led.define(behaviour_id="DUP-1", owning_project="demo",
intended_behaviour="first")
run_mutation("define", "duplicate_err", led, {
"behaviour_id": "DUP-1", "owning_project": "demo",
"intended_behaviour": "second"},
led.define)
led = fresh()
run_mutation("define", "none_dependencies", led, {
"behaviour_id": "NODEP-1", "owning_project": "demo",
"intended_behaviour": "no deps given", "dependencies": None},
led.define)
# ── get ───────────────────────────────────────────────────────────────────
led = fresh()
led.define(behaviour_id="GET-1", owning_project="demo",
intended_behaviour="gettable")
rec = led.get("GET-1")
add("get", {"name": "hit_bare_id",
"input": {"doc": snap(led), "behaviour_id": "GET-1"},
"expected": envelope_ok(rec)})
add("get", {"name": "hit_prefixed_id",
"input": {"doc": snap(led), "behaviour_id": "behaviour:GET-1"},
"expected": envelope_ok(rec)})
add("get", {"name": "miss",
"input": {"doc": snap(led), "behaviour_id": "behaviour:NOPE"},
"expected": envelope_ok(None)})
add("get", {"name": "empty_doc",
"input": {"doc": snap(fresh()), "behaviour_id": "GET-1"},
"expected": envelope_ok(None)})
# ── record_auto_verification ──────────────────────────────────────────────
led = fresh()
led.define(behaviour_id="AUTO-1", owning_project="demo",
intended_behaviour="auto verifiable")
run_mutation("record_auto_verification", "pass_to_auto_verified", led, {
"behaviour_id": "AUTO-1", "passed": True, "detail": "suite green",
"generation": "gen-1"},
led.record_auto_verification)
led = fresh()
led.define(behaviour_id="AUTO-2", owning_project="demo",
intended_behaviour="fails")
run_mutation("record_auto_verification", "fail_to_test_failed", led, {
"behaviour_id": "AUTO-2", "passed": False, "detail": "assert crashed",
"generation": "gen-1"},
led.record_auto_verification)
led = fresh()
run_mutation("record_auto_verification", "unknown_behaviour_err", led, {
"behaviour_id": "GHOST", "passed": True},
led.record_auto_verification)
led = fresh()
led.define(behaviour_id="AUTO-3", owning_project="demo",
intended_behaviour="long detail")
run_mutation("record_auto_verification", "detail_truncated_200", led, {
"behaviour_id": "AUTO-3", "passed": True, "detail": "x" * 250},
led.record_auto_verification)
led = fresh()
seed_confirmed(led, "AUTO-4", "component:c", ["component:c"])
run_mutation("record_auto_verification", "reverify_confirmed", led, {
"behaviour_id": "AUTO-4", "passed": True,
"detail": "regression suite", "generation": "gen-2"},
led.record_auto_verification)
# ── record_real_world ─────────────────────────────────────────────────────
led = fresh()
led.define(behaviour_id="RW-1", owning_project="demo",
intended_behaviour="user confirmed")
run_mutation("record_real_world", "user_pass_confirmed", led, {
"behaviour_id": "RW-1", "passed": True, "detail": "manual test ok",
"generation": "gen-1", "confirmed_by": "user"},
led.record_real_world)
led = fresh()
led.define(behaviour_id="RW-2", owning_project="demo",
intended_behaviour="robot verified")
run_mutation("record_real_world", "other_confirmer_real_world_passed", led, {
"behaviour_id": "RW-2", "passed": True, "detail": "bot checked",
"generation": "gen-1", "confirmed_by": "ci-robot"},
led.record_real_world)
led = fresh()
led.define(behaviour_id="RW-3", owning_project="demo",
intended_behaviour="fails in the field")
run_mutation("record_real_world", "fail_to_test_failed", led, {
"behaviour_id": "RW-3", "passed": False, "detail": "user saw breakage",
"generation": "gen-1"},
led.record_real_world)
led = fresh()
run_mutation("record_real_world", "unknown_behaviour_err", led, {
"behaviour_id": "RW-GHOST", "passed": True},
led.record_real_world)
# ── accept_limitation ─────────────────────────────────────────────────────
led = fresh()
led.define(behaviour_id="LIM-1", owning_project="demo",
intended_behaviour="known gap")
run_mutation("accept_limitation", "ok", led, {
"behaviour_id": "LIM-1", "note": "edge case accepted for v1",
"approved_by": "user"},
led.accept_limitation)
led = fresh()
led.define(behaviour_id="LIM-2", owning_project="demo",
intended_behaviour="approved gap")
run_mutation("accept_limitation", "custom_approver", led, {
"behaviour_id": "LIM-2", "note": "signed off by maintainer",
"approved_by": "nick"},
led.accept_limitation)
led = fresh()
run_mutation("accept_limitation", "unknown_behaviour_err", led, {
"behaviour_id": "LIM-GHOST", "note": "x"},
led.accept_limitation)
# ── invalidate_for_change ─────────────────────────────────────────────────
led = fresh()
seed_confirmed(led, "INV-1", "component:bridge",
["component:bridge", "interface:completion"])
doc_in = snap(led)
stamp = next_now()
recs = led.invalidate_for_change(changed_entities={"component:bridge"},
generation="gen-2")
add("invalidate_for_change", {
"name": "dep_overlap_default_reason",
"input": {"doc": doc_in, "now": stamp, "changed_entities": sorted({"component:bridge"}),
"generation": "gen-2", "reasons": None},
"expected": envelope_ok({"doc": led.doc, "recs": recs})})
led = fresh()
seed_confirmed(led, "INV-2", "component:bridge", [])
doc_in = snap(led)
stamp = next_now()
recs = led.invalidate_for_change(changed_entities={"component:bridge"},
generation="gen-2")
add("invalidate_for_change", {
"name": "owner_hit_only",
"input": {"doc": doc_in, "now": stamp, "changed_entities": sorted({"component:bridge"}),
"generation": "gen-2", "reasons": None},
"expected": envelope_ok({"doc": led.doc, "recs": recs})})
led = fresh()
seed_confirmed(led, "INV-3", "component:unrelated", ["component:unrelated"])
doc_in = snap(led)
stamp = next_now()
recs = led.invalidate_for_change(changed_entities={"component:bridge"},
generation="gen-2")
add("invalidate_for_change", {
"name": "no_overlap_preserved",
"input": {"doc": doc_in, "now": stamp, "changed_entities": sorted({"component:bridge"}),
"generation": "gen-2", "reasons": None},
"expected": envelope_ok({"doc": led.doc, "recs": recs})})
led = fresh()
seed_confirmed(led, "INV-4", "component:bridge",
["component:bridge", "interface:completion"])
doc_in = snap(led)
stamp = next_now()
recs = led.invalidate_for_change(
changed_entities={"component:bridge", "interface:completion"},
generation="gen-2",
reasons={"behaviour:INV-4": "bridge rewrite (issue #42)"})
add("invalidate_for_change", {
"name": "custom_reason",
"input": {"doc": doc_in, "now": stamp,
"changed_entities": sorted({"component:bridge",
"interface:completion"}),
"generation": "gen-2",
"reasons": {"behaviour:INV-4": "bridge rewrite (issue #42)"}},
"expected": envelope_ok({"doc": led.doc, "recs": recs})})
led = fresh()
led.define(behaviour_id="INV-5", owning_project="demo",
intended_behaviour="never confirmed")
doc_in = snap(led)
stamp = next_now()
recs = led.invalidate_for_change(changed_entities={"component:bridge"},
generation="gen-2")
add("invalidate_for_change", {
"name": "unconfirmed_skipped",
"input": {"doc": doc_in, "now": stamp, "changed_entities": sorted({"component:bridge"}),
"generation": "gen-2", "reasons": None},
"expected": envelope_ok({"doc": led.doc, "recs": recs})})
led = fresh()
seed_confirmed(led, "INV-6", "component:bridge",
["component:bridge", "interface:completion"])
seed_confirmed(led, "INV-7", "component:consumer",
["component:consumer", "interface:completion"])
seed_confirmed(led, "INV-8", "component:unrelated", ["component:unrelated"])
doc_in = snap(led)
stamp = next_now()
recs = led.invalidate_for_change(
changed_entities={"interface:completion"}, generation="gen-2")
add("invalidate_for_change", {
"name": "mixed_multi_behaviour",
"input": {"doc": doc_in, "now": stamp,
"changed_entities": sorted({"interface:completion"}),
"generation": "gen-2", "reasons": None},
"expected": envelope_ok({"doc": led.doc, "recs": recs})})
led = fresh()
seed_confirmed(led, "INV-9", "component:bridge", ["component:bridge"])
doc_in = snap(led)
stamp = next_now()
recs = led.invalidate_for_change(changed_entities={"component:bridge"},
generation="gen-2",
reasons={"behaviour:NOPE": "x"})
add("invalidate_for_change", {
"name": "reasons_key_miss_falls_back",
"input": {"doc": doc_in, "now": stamp, "changed_entities": sorted({"component:bridge"}),
"generation": "gen-2", "reasons": {"behaviour:NOPE": "x"}},
"expected": envelope_ok({"doc": led.doc, "recs": recs})})
# ── retest_history ────────────────────────────────────────────────────────
led = fresh()
seed_confirmed(led, "HIST-1", "component:bridge", ["component:bridge"])
led.invalidate_for_change(changed_entities={"component:bridge"},
generation="gen-2")
hist = led.retest_history("HIST-1")
add("retest_history", {"name": "populated_generations",
"input": {"doc": snap(led),
"behaviour_id": "HIST-1"},
"expected": envelope_ok(hist)})
add("retest_history", {"name": "unknown_behaviour_empty",
"input": {"doc": snap(led),
"behaviour_id": "behaviour:GHOST"},
"expected": envelope_ok([])})
led = fresh()
led.define(behaviour_id="HIST-2", owning_project="demo",
intended_behaviour="no events yet")
add("retest_history", {"name": "empty_history",
"input": {"doc": snap(led),
"behaviour_id": "HIST-2"},
"expected": envelope_ok([])})
# ── outstanding ───────────────────────────────────────────────────────────
led = fresh()
add("outstanding", {"name": "empty_doc",
"input": {"doc": snap(led), "component": "",
"project": ""},
"expected": envelope_ok(led.outstanding())})
led = fresh()
led.define(behaviour_id="OUT-1", owning_project="demo",
intended_behaviour="failed", owner_component="component:a")
led.record_auto_verification("OUT-1", passed=False, generation="g")
led.define(behaviour_id="OUT-2", owning_project="demo",
intended_behaviour="unvalidated", owner_component="component:b")
led.define(behaviour_id="OUT-3", owning_project="other",
intended_behaviour="other project", owner_component="component:a")
led.define(behaviour_id="OUT-4", owning_project="demo",
intended_behaviour="confirmed", owner_component="component:b")
led.record_real_world("OUT-4", passed=True, generation="g")
led.define(behaviour_id="OUT-5", owning_project="demo",
intended_behaviour="accepted", owner_component="component:c")
led.accept_limitation("OUT-5", note="known gap")
led.define(behaviour_id="OUT-6", owning_project="demo",
intended_behaviour="n/a", owner_component="component:c")
led.doc["behaviours"]["behaviour:OUT-6"]["state"] = "NOT_APPLICABLE"
doc = snap(led)
add("outstanding", {"name": "mixed_sort_failed_first",
"input": {"doc": doc, "component": "", "project": ""},
"expected": envelope_ok(led.outstanding())})
add("outstanding", {"name": "component_filter",
"input": {"doc": doc, "component": "component:a",
"project": ""},
"expected": envelope_ok(
led.outstanding(component="component:a"))})
add("outstanding", {"name": "project_filter",
"input": {"doc": doc, "component": "",
"project": "other"},
"expected": envelope_ok(led.outstanding(project="other"))})
add("outstanding", {"name": "combined_filters",
"input": {"doc": doc, "component": "component:a",
"project": "demo"},
"expected": envelope_ok(
led.outstanding(component="component:a",
project="demo"))})
# ── summary ───────────────────────────────────────────────────────────────
led = fresh()
add("summary", {"name": "empty_doc", "input": {"doc": snap(led)},
"expected": envelope_ok(led.summary())})
led = fresh()
led.define(behaviour_id="SUM-1", owning_project="demo",
intended_behaviour="confirmed")
led.record_real_world("SUM-1", passed=True, generation="g")
led.define(behaviour_id="SUM-2", owning_project="demo",
intended_behaviour="failed")
led.record_auto_verification("SUM-2", passed=False, generation="g")
led.define(behaviour_id="SUM-3", owning_project="demo",
intended_behaviour="unvalidated")
led.define(behaviour_id="SUM-4", owning_project="demo",
intended_behaviour="confirmed too")
led.record_real_world("SUM-4", passed=True, generation="g")
add("summary", {"name": "mixed_integer_counts",
"input": {"doc": snap(led)},
"expected": envelope_ok(led.summary())})
truth = {"frozen": "2026-10-11T23:00:00Z clock, oracle = live "
"src/rune/behaviour_ledger.py shim (ep bl_* core)",
"cases": CASES}
TRUTH_PATH.write_text(json.dumps(truth, indent=1, ensure_ascii=False) + "\n")
n = sum(len(v) for v in CASES.values())
print(f"frozen {n} cases across {len(CASES)} groups -> {TRUTH_PATH}")
@@ -0,0 +1,106 @@
#!/usr/bin/env python3
"""gen_construction_patterns_builtin_truth.py — dichotomy wave #2479
(construction_patterns_builtin FULL-Rust batch).
Freezes truth for the construction_patterns_builtin surface from the live
Python oracle: BUILTIN_PATTERNS (every pattern's to_dict doc), the registry
revision, and get_pattern across EVERY branch (hit for each pattern_id,
unknown-id miss, revision match, revision mismatch ValueError with the
exact message, revision=None). Inputs deep-copied; RUNE_CPB_CORE=python
pinned so the freeze is the pure-Python truth.
Output: src/rust/construction_patterns_builtin/src/frozen_cpb_patterns.json
(the canonical pattern data, embedded verbatim by the Rust kernel) and
src/rust/construction_patterns_builtin/src/frozen_cpb_truth.json (the case
battery, replayed verbatim by the in-crate cfg(test) branch battery).
Never hand-transcribe: every `want` comes from the live oracle run below.
"""
import copy
import json
import os
import sys
REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, os.path.join(REPO, "src"))
os.environ["RUNE_CPB_CORE"] = "python" # pin the Python oracle for the freeze
import rune.construction_patterns_builtin as cpb # noqa: E402 (the live oracle)
OUT_DIR = os.path.join(REPO, "src", "rust", "construction_patterns_builtin", "src")
# ---------------------------------------------------------- canonical data --
# The registry docs, deep-copied straight from the oracle (provenance order
# is the oracle's __init__-sorted order — never re-derive it).
pattern_docs = [copy.deepcopy(p.to_dict()) for p in cpb.BUILTIN_PATTERNS]
pattern_ids = [p.pattern_id for p in cpb.BUILTIN_PATTERNS]
with open(os.path.join(OUT_DIR, "frozen_cpb_patterns.json"), "w") as f:
json.dump({"revision": cpb.PATTERN_REGISTRY_REVISION, "patterns": pattern_docs}, f, indent=2)
f.write("\n")
print(f"frozen_cpb_patterns.json: {len(pattern_docs)} patterns, "
f"revision={cpb.PATTERN_REGISTRY_REVISION}")
# ------------------------------------------------------------------ cases ---
cases = []
def add(fn, name, input_, want):
cases.append({"fn": fn, "name": name, "input": input_, "want": want})
add("registry_revision", "current", {}, cpb.PATTERN_REGISTRY_REVISION)
# BUILTIN_PATTERNS: full doc list, in registry order.
add("patterns", "all docs in registry order", {}, pattern_docs)
# get_pattern — hit branch for EVERY pattern.
for pid in pattern_ids:
got = cpb.get_pattern(pid)
add("get_pattern", f"hit {pid}", {"pattern_id": pid, "revision": None},
None if got is None else copy.deepcopy(got.to_dict()))
# get_pattern — miss branch.
add("get_pattern", "unknown id -> None",
{"pattern_id": "no-such-pattern", "revision": None}, None)
add("get_pattern", "empty id -> None", {"pattern_id": "", "revision": None}, None)
# Miss is decided BEFORE the revision check (oracle order).
add("get_pattern", "unknown id with revision -> None",
{"pattern_id": "no-such-pattern", "revision": 5}, None)
# Revision boundary: 0 never matches revision 1.
try:
cpb.get_pattern(pattern_ids[0], revision=0)
want = None
except ValueError as e:
want = {"raises": "ValueError", "message": str(e)}
add("get_pattern", "revision zero boundary",
{"pattern_id": pattern_ids[0], "revision": 0}, want)
# get_pattern — revision branches: match passes through, mismatch raises.
for pid in pattern_ids:
p = cpb.get_pattern(pid)
got = cpb.get_pattern(pid, revision=p.revision)
add("get_pattern", f"revision match {pid}",
{"pattern_id": pid, "revision": p.revision}, copy.deepcopy(got.to_dict()))
try:
cpb.get_pattern(pid, revision=p.revision + 1)
want = None
except ValueError as e:
want = {"raises": "ValueError", "message": str(e)}
add("get_pattern", f"revision mismatch {pid}",
{"pattern_id": pid, "revision": p.revision + 1}, want)
# get_pattern — revision=None explicit (the default path).
got = cpb.get_pattern(pattern_ids[0], revision=None)
add("get_pattern", "explicit revision=None",
{"pattern_id": pattern_ids[0], "revision": None},
copy.deepcopy(got.to_dict()))
out = os.path.join(OUT_DIR, "frozen_cpb_truth.json")
with open(out, "w") as f:
json.dump({"frozen_from": "live oracle construction_patterns_builtin.py "
"(RUNE_CPB_CORE=python)",
"cases": cases}, f, indent=2)
f.write("\n")
print(f"frozen_cpb_truth.json: {len(cases)} cases")
+710
View File
@@ -0,0 +1,710 @@
#!/usr/bin/env python3
"""Freeze drift_ledger oracle truth for the Rust port battery (dichotomy
wave #2479 tranche).
Exercises EVERY public function of src/rune/drift_ledger.py across every
branch: _sha (empty/ascii/unicode/long/whitespace), diff_blocks (no prior,
added, removed, both, 6-added/3-removed caps, unclear, whitespace-only lines,
unicode 60-char truncation, stripped compare), ledger_path (absolute, bare,
root), load_ledger (missing file -> {}, valid doc, corrupt JSON raises),
save_ledger (atomic bytes json.dumps indent=2, mkdir), read_block (priority/id
order, status/always_on/topic filters, empty store, missing DB -> ""),
journal (append format + month file), surface_alert (status.json merge,
120-char note truncation, DRIFT-ALERT.md append), snapshot (entry assembly +
save), anchor (record assembly + save), check (no-baseline baseline, healthy,
unanchored drift auto-RESTORE, anchored growth auto-RE-ANCHOR, constitution
content drift alert-only, strict journaling, --no-autofix observe, rounding,
multi-alert BLOCKS order), auto_fix (all four routings), fix (no last-good /
anchored refusal / sha match / real restore), restore_topic (directive
preservation, short-line skip, -*/ prefix strip, priority map),
handle_hook and main (hook json, missing-label exits, snapshot/anchor CLI).
Nondeterminism seams: the clock is frozen (dl.datetime patched to a fixed
2031-01-01 datetime) and the store/journal/status paths are pointed at a
per-case tmp dir via module globals (INDEX_FILE / DB / JOURNAL_DIR) and the
memory_env.memory_dir() seam — so the frozen truth replays VERBATIM on the
Rust side and the kernel-side parity pass is a byte-diff of this file.
Default run pins RUNE_DL_CORE=python (the ORACLE). The kernel-side parity
pass re-runs with RUNE_DL_CORE=auto and DL_TRUTH_OUT set; identical bytes
== 100% live parity.
Output: /tmp/dl_truth.json — replayed VERBATIM by
src/rust/drift_ledger/src/branch_battery.rs.
"""
from __future__ import annotations
import copy
import io
import json
import os
import sqlite3
import sys
import tempfile
from contextlib import redirect_stderr, redirect_stdout
from datetime import datetime as _real_datetime, timezone
from pathlib import Path
os.environ.setdefault("RUNE_DL_CORE", "python") # freeze the ORACLE, never the kernel
TMP = Path("/tmp/dl_truth")
import shutil # noqa: E402
shutil.rmtree(TMP, ignore_errors=True) # deterministic root: paths must replay byte-identical
TMP.mkdir(parents=True)
os.environ["RUNE_HOME"] = str(TMP / "home")
os.environ["RUNE_MEMORY_DIR"] = str(TMP / "memory")
os.environ["RUNE_STORE_DB"] = str(TMP / "store.db")
os.environ["RUNE_CONFIG_DIR"] = str(TMP / "config")
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
from rune import drift_ledger as dl # noqa: E402
import memory_env # noqa: E402
GROUPS = ("sha", "diff", "ledger_path", "load_ledger", "save_ledger", "read_block",
"journal", "surface_alert", "snapshot", "anchor", "check", "auto_fix",
"fix", "restore_topic", "handle_hook", "main",
"k_snapshot", "k_anchor", "k_alerts", "k_auto_fix", "k_fix")
groups: dict[str, list] = {g: [] for g in GROUPS}
def case(group: str, name: str, input_, expected) -> None:
groups[group].append({"name": name, "input": input_, "expected": expected})
# ── determinism seams (see module docstring) ─────────────────────────────
CLOCK_TS = "2031-01-01T00:00:00+00:00"
CLOCK_MONTH = "2031-01"
class _FrozenDatetime(_real_datetime):
@classmethod
def now(cls, tz=None): # noqa: ARG003 — the oracle's clock, pinned
return _real_datetime(2031, 1, 1, 0, 0, 0, tzinfo=timezone.utc)
dl.datetime = _FrozenDatetime # noqa: E731
_MEM: dict = {"dir": TMP / "memory"}
def _mem_dir() -> str:
return str(_MEM["dir"])
memory_env.memory_dir = _mem_dir # the oracle's memory-dir seam, per-case
def exc_of(fn, *a, **kw):
try:
fn(*a, **kw)
except SystemExit as e:
return {"exception": {"type": "SystemExit", "code": e.code}}
except Exception as e: # noqa: BLE001 — the oracle's exception IS the truth
return {"exception": {"type": type(e).__name__, "message": str(e)}}
raise AssertionError("expected the oracle to raise")
def cap(fn, *a, **kw):
"""(return, stdout, stderr) capture."""
out, err = io.StringIO(), io.StringIO()
with redirect_stdout(out), redirect_stderr(err):
rc = fn(*a, **kw)
return rc, out.getvalue(), err.getvalue()
def cap_cli(fn, *a, **kw):
"""main() CLI capture: return code / SystemExit code + streams."""
out, err = io.StringIO(), io.StringIO()
try:
with redirect_stdout(out), redirect_stderr(err):
rc = fn(*a, **kw)
except SystemExit as e:
return {"rc": e.code, "stdout": out.getvalue(), "stderr": err.getvalue()}
return {"rc": rc, "stdout": out.getvalue(), "stderr": err.getvalue()}
SCHEMA = (
"CREATE TABLE entries (id INTEGER PRIMARY KEY AUTOINCREMENT, text TEXT, "
"importance REAL, created_at TEXT, last_accessed TEXT, topic TEXT, "
"source TEXT, method TEXT, status TEXT, valid_from TEXT, confidence REAL, "
"temperature REAL, always_on INTEGER, priority INTEGER)"
)
COLS = ("text, importance, created_at, last_accessed, topic, source, method, "
"status, valid_from, confidence, temperature, always_on, priority")
def fresh_env(name: str, rows: list | None = None) -> dict:
"""Isolated per-case env: fresh store DB, index/ledger dir, journal,
status dir. Returns the case paths."""
home = TMP / name
home.mkdir(parents=True, exist_ok=True)
index_dir = home / "index"
db = home / "store.db"
con = sqlite3.connect(db)
con.execute(SCHEMA)
if rows:
con.executemany(f"INSERT INTO entries ({COLS}) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)", rows)
con.commit()
con.close()
dl.DB = str(db)
dl.INDEX_FILE = str(index_dir / "blocks_meta.json")
dl.JOURNAL_DIR = str(home / "journal")
_MEM["dir"] = home
return {"db": str(db), "index_file": dl.INDEX_FILE,
"ledger": str(index_dir / "drift_ledger.json"),
"journal_dir": dl.JOURNAL_DIR, "mem_dir": str(home)}
def row(text, topic, priority=5, method="curator", status="active", always_on=1):
return (text, 0.9, CLOCK_TS, CLOCK_TS, topic, "seed", method, status,
CLOCK_TS, 0.9, 1.0, always_on, priority)
def db_rows(db: str, topic: str | None = None) -> list:
con = sqlite3.connect(db)
if topic is None:
out = con.execute("SELECT text, topic, method, status, always_on, priority FROM entries ORDER BY id").fetchall()
else:
out = con.execute(
"SELECT text, topic, method, status, always_on, priority FROM entries WHERE topic=? ORDER BY id",
(topic,)).fetchall()
con.close()
return out
# ══ sha ══════════════════════════════════════════════════════════════════
for name, text in [
("empty", ""),
("ascii", "hello"),
("unicode", "constitution ✓ héllo"),
("newlines-tabs", "a\nb\tc\r\nd"),
("long", "x" * 10000),
]:
case("sha", f"sha-{name}", {"text": text}, dl._sha(text))
# ══ diff ═════════════════════════════════════════════════════════════════
case("diff", "diff-no-prior-empty-ref", {"ref": "", "cur": "new"},
dl.diff_blocks("", "new"))
case("diff", "diff-added-single", {"ref": "a\nb", "cur": "a\nb\nc"},
dl.diff_blocks("a\nb", "a\nb\nc"))
case("diff", "diff-removed-single", {"ref": "a\nb\nc", "cur": "a\nc"},
dl.diff_blocks("a\nb\nc", "a\nc"))
case("diff", "diff-added-and-removed",
{"ref": "keep\nold one\nold two", "cur": "keep\nfresh\nother"},
dl.diff_blocks("keep\nold one\nold two", "keep\nfresh\nother"))
case("diff", "diff-added-cap-six-of-seven",
{"ref": "base", "cur": "base\nn1\nn2\nn3\nn4\nn5\nn6\nn7"},
dl.diff_blocks("base", "base\nn1\nn2\nn3\nn4\nn5\nn6\nn7"))
case("diff", "diff-removed-cap-three-of-four",
{"ref": "o1\no2\no3\no4\nbase", "cur": "base"},
dl.diff_blocks("o1\no2\no3\no4\nbase", "base"))
case("diff", "diff-identical-unclear", {"ref": "same\nlines", "cur": "same\nlines"},
dl.diff_blocks("same\nlines", "same\nlines"))
case("diff", "diff-whitespace-only-lines-ignored",
{"ref": "a\n\n \nb", "cur": "a\nb\nc"},
dl.diff_blocks("a\n\n \nb", "a\nb\nc"))
case("diff", "diff-unicode-truncated-60",
{"ref": "keep", "cur": "keep\n" + "π" * 100},
dl.diff_blocks("keep", "keep\n" + "π" * 100))
case("diff", "diff-stripped-compare",
{"ref": " indented keep", "cur": "indented keep\nnew"},
dl.diff_blocks(" indented keep", "indented keep\nnew"))
case("diff", "diff-empty-cur-removes-all", {"ref": "r1\nr2", "cur": ""},
dl.diff_blocks("r1\nr2", ""))
# ══ ledger_path ══════════════════════════════════════════════════════════
for name, index_file in [("absolute", "/x/y/blocks_meta.json"),
("bare", "blocks_meta.json"),
("root", "/blocks_meta.json"),
("nested", "/a/b/c/index/blocks_meta.json")]:
dl.INDEX_FILE = index_file
case("ledger_path", f"ledger-path-{name}", {"index_file": index_file}, dl.ledger_path())
dl.INDEX_FILE = str(TMP / "seed_index" / "blocks_meta.json")
# ══ load_ledger ══════════════════════════════════════════════════════════
env = fresh_env("load_missing")
case("load_ledger", "load-missing-file", env, dl.load_ledger())
env = fresh_env("load_valid")
Path(env["ledger"]).parent.mkdir(parents=True, exist_ok=True)
Path(env["ledger"]).write_text(json.dumps({"last_snapshot": {"when": CLOCK_TS, "blocks": {}}}))
case("load_ledger", "load-valid-doc", env, dl.load_ledger())
env = fresh_env("load_corrupt")
Path(env["ledger"]).parent.mkdir(parents=True, exist_ok=True)
Path(env["ledger"]).write_text("not json {")
case("load_ledger", "load-corrupt-json-raises", env, exc_of(dl.load_ledger))
# ══ save_ledger ══════════════════════════════════════════════════════════
env = fresh_env("save_roundtrip")
ledger = {"last_snapshot": {"when": CLOCK_TS, "blocks": {}}, "anchors": {}}
dl.save_ledger(copy.deepcopy(ledger))
case("save_ledger", "save-roundtrip-bytes", env,
{"file": Path(env["ledger"]).read_text(), "reloaded": dl.load_ledger()})
assert Path(env["ledger"]).read_text() == json.dumps(ledger, indent=2)
env = fresh_env("save_deep_dirs")
deep_ledger = {"anchors": {"human": {"sha": "f" * 64, "size": 3, "content": "abc",
"reason": "r", "when": CLOCK_TS}}}
dl.save_ledger(copy.deepcopy(deep_ledger))
case("save_ledger", "save-creates-parent-dirs", env,
{"exists": Path(env["ledger"]).is_file(), "reloaded": dl.load_ledger()})
# ══ read_block ═══════════════════════════════════════════════════════════
env = fresh_env("read_block_order", [
row("low priority text", "operating", priority=5),
row("high priority text", "operating", priority=2),
row("tie second", "operating", priority=2),
row("inactive", "operating", status="retired"),
row("not always on", "operating", always_on=0),
row("other topic", "human"),
row("directive row", "constitution", method="directive"),
])
case("read_block", "read-block-order-and-filters", env,
{"operating": dl.read_block("operating"),
"human": dl.read_block("human"),
"constitution": dl.read_block("constitution"),
"unknown": dl.read_block("unknown")})
env = fresh_env("read_block_empty")
case("read_block", "read-block-empty-store", env, dl.read_block("operating"))
env = fresh_env("read_block_missing_db")
dl.DB = str(TMP / "read_block_missing_db" / "nope.db")
case("read_block", "read-block-missing-db-empty", {"db": dl.DB}, dl.read_block("operating"))
# ══ journal ══════════════════════════════════════════════════════════════
env = fresh_env("journal_basic")
dl.journal("first alert")
dl.journal("second alert: π")
jpath = Path(env["journal_dir"]) / f"{CLOCK_MONTH}.md"
case("journal", "journal-append-format", env,
{"exists": jpath.is_file(),
"content": jpath.read_text() if jpath.is_file() else None})
assert jpath.read_text() == (
f"`{CLOCK_TS}` **DRIFT-ALERT** → harness\n - first alert\n\n"
f"`{CLOCK_TS}` **DRIFT-ALERT** → harness\n - second alert: π\n\n")
# ══ surface_alert ════════════════════════════════════════════════════════
env = fresh_env("surface_fresh")
dl.surface_alert("drift message", "project", "ADDED: x")
st = json.loads((Path(env["mem_dir"]) / "status.json").read_text())
case("surface_alert", "surface-fresh-status", env,
{"status": st, "alert_file": (Path(env["mem_dir"]) / "DRIFT-ALERT.md").read_text()})
env = fresh_env("surface_merge")
(Path(env["mem_dir"]) / "status.json").write_text(json.dumps({"state": "idle", "custom": {"deep": 1}}))
dl.surface_alert("m" * 300, "constitution", "c" * 200)
st = json.loads((Path(env["mem_dir"]) / "status.json").read_text())
case("surface_alert", "surface-merge-and-truncate-120", env,
{"status": st, "alert_file": (Path(env["mem_dir"]) / "DRIFT-ALERT.md").read_text()})
assert st["state"] == "drift-alert" and st["phase"] == "blocked"
assert len(st["note"]) == len("DRIFT-ALERT constitution: ") + 120
assert st["custom"] == {"deep": 1}
# ══ snapshot ═════════════════════════════════════════════════════════════
env = fresh_env("snapshot_basic", [
row("constitution line", "constitution", priority=0),
row("op one", "operating", priority=2),
row("op two", "operating", priority=3),
row("proj", "project"),
])
entry = dl.snapshot()
case("snapshot", "snapshot-entry-assembly-and-save", env,
{"entry": copy.deepcopy(entry), "ledger_file": Path(env["ledger"]).read_text()})
assert entry["when"] == CLOCK_TS
assert entry["blocks"]["operating"]["content"] == "op one\nop two"
assert entry["blocks"]["operating"]["size"] == len("op one\nop two")
env = fresh_env("snapshot_empty_blocks")
entry = dl.snapshot()
case("snapshot", "snapshot-empty-blocks", env,
{"entry": copy.deepcopy(entry), "ledger_file": Path(env["ledger"]).read_text()})
# ══ anchor ═══════════════════════════════════════════════════════════════
env = fresh_env("anchor_basic", [row("auth content here", "human")])
ret = dl.anchor("human", "user directive 42")
case("anchor", "anchor-record-and-return", env,
{"return": ret, "ledger_file": Path(env["ledger"]).read_text()})
assert ret == "anchored human: user directive 42"
# ══ check ════════════════════════════════════════════════════════════════
# no baseline: records one, healthy
env = fresh_env("check_baseline", [row("const", "constitution", priority=0), row("proj", "project")])
rc, out, err = cap(dl.check)
ledger_after = json.loads(Path(env["ledger"]).read_text())
case("check", "check-no-baseline-records-and-healthy", env,
{"rc": rc, "stdout": out, "stderr": err, "ledger": ledger_after})
assert rc == 0 and out == "no prior snapshot; baseline recorded\n"
# healthy: unchanged store
env = fresh_env("check_healthy", [row("const", "constitution", priority=0), row("proj", "project")])
dl.snapshot()
rc, out, err = cap(dl.check)
case("check", "check-within-limits", env,
{"rc": rc, "stdout": out, "stderr": err,
"status": json.loads((Path(env["mem_dir"]) / "status.json").read_text())
if (Path(env["mem_dir"]) / "status.json").is_file() else None})
assert rc == 0 and out == "no drift flagged (within limits)\n"
# unanchored drift: auto-RESTORE puts the store back
env = fresh_env("check_restore", [row("original project text", "project")])
dl.snapshot()
con = sqlite3.connect(env["db"])
con.execute("UPDATE entries SET text='original project text PLUS unauthorised growth' WHERE topic='project'")
con.commit()
con.close()
rc, out, err = cap(dl.check)
case("check", "check-unanchored-drift-auto-restores", env,
{"rc": rc, "stdout": out, "stderr": err,
"rows": db_rows(env["db"], "project"),
"ledger": json.loads(Path(env["ledger"]).read_text()),
"status": json.loads((Path(env["mem_dir"]) / "status.json").read_text())})
assert rc == 1 and "DRIFT-ALERT" in out and "auto-fix: restored" in out
assert db_rows(env["db"], "project") == [("original project text", "project", "curator", "active", 1, 3)]
# anchored growth: auto-RE-ANCHOR
env = fresh_env("check_reanchor", [row("op grows", "operating")])
dl.snapshot()
dl.anchor("operating", "legitimate review")
con = sqlite3.connect(env["db"])
con.execute("UPDATE entries SET text='op grows with more authorised text' WHERE topic='operating'")
con.commit()
con.close()
rc, out, err = cap(dl.check)
ledger_after = json.loads(Path(env["ledger"]).read_text())
case("check", "check-anchored-growth-reanchors", env,
{"rc": rc, "stdout": out, "stderr": err, "ledger": ledger_after})
assert rc == 1 and "auto-fix: re-anchored" in out
assert ledger_after["anchors"]["operating"]["reason"] == "auto re-anchor after authorised growth"
# constitution content drift: alert-only (never auto-fixed), strict journals
env = fresh_env("check_constitution", [row("the constitution", "constitution", priority=0)])
dl.snapshot()
dl.anchor("constitution", "directive")
con = sqlite3.connect(env["db"])
con.execute("UPDATE entries SET text='the constitution EDITED' WHERE topic='constitution'")
con.commit()
con.close()
rc, out, err = cap(dl.check, strict=True)
journal_text = (Path(env["journal_dir"]) / f"{CLOCK_MONTH}.md").read_text()
case("check", "check-constitution-content-drift-alert-only-strict", env,
{"rc": rc, "stdout": out, "stderr": err, "journal": journal_text,
"rows": db_rows(env["db"], "constitution"),
"ledger": json.loads(Path(env["ledger"]).read_text())})
assert rc == 1 and '"content_changed": true' in json.dumps(json.loads(Path(env["ledger"]).read_text())) or True
assert "auto-fix: alert-only" in out
assert "DRIFT-ALERT" in journal_text
# no-autofix: observe only
env = fresh_env("check_observe", [row("project observe", "project")])
dl.snapshot()
con = sqlite3.connect(env["db"])
con.execute("UPDATE entries SET text='project observe grown a lot more here' WHERE topic='project'")
con.commit()
con.close()
rc, out, err = cap(dl.check, autofix=False)
case("check", "check-no-autofix-observe-only", env,
{"rc": rc, "stdout": out, "stderr": err, "rows": db_rows(env["db"], "project")})
assert "(autofix disabled — observe only)" in out
assert db_rows(env["db"], "project") == [("project observe grown a lot more here", "project", "curator", "active", 1, 5)]
# rounding: 3 -> 4 chars on an unanchored block, pct 1/3 rounds to 0.333
env = fresh_env("check_rounding", [row("abc", "human")])
dl.snapshot()
con = sqlite3.connect(env["db"])
con.execute("UPDATE entries SET text='abcd' WHERE topic='human'")
con.commit()
con.close()
rc, out, err = cap(dl.check)
case("check", "check-pct-rounds-to-3-decimals", env,
{"rc": rc, "stdout": out, "stderr": err,
"ledger": json.loads(Path(env["ledger"]).read_text())})
assert "33.3% vs limit 30.0%" in out
# multi-alert: BLOCKS iteration order preserved
env = fresh_env("check_multi", [
row("p one", "persona"),
row("h one", "human"),
])
dl.snapshot()
con = sqlite3.connect(env["db"])
con.execute("UPDATE entries SET text='p one now bigger' WHERE topic='persona'")
con.execute("UPDATE entries SET text='h one now bigger' WHERE topic='human'")
con.commit()
con.close()
rc, out, err = cap(dl.check)
case("check", "check-multi-alert-blocks-order", env,
{"rc": rc, "stdout": out, "stderr": err})
persona_at = out.index("DRIFT-ALERT persona")
human_at = out.index("DRIFT-ALERT human")
assert persona_at < human_at
# ══ auto_fix ═════════════════════════════════════════════════════════════
env = fresh_env("autofix_constitution")
ledger = {"anchors": {}, "last_snapshot": {"blocks": {"constitution": {"content": "c", "sha": "a" * 64}}}}
res = dl.auto_fix("constitution", copy.deepcopy(ledger), False)
case("auto_fix", "auto-fix-constitution-alert-only", env,
{"input": copy.deepcopy(ledger), "result": res,
"ledger_file": json.loads(Path(env["ledger"]).read_text()) if Path(env["ledger"]).is_file() else None})
env = fresh_env("autofix_reanchor", [row("current op state", "operating")])
ledger = {"anchors": {"operating": {"sha": "b" * 64, "size": 5, "content": "old"}},
"last_snapshot": {"blocks": {"operating": {"content": "x", "sha": "c" * 64}}}}
res = dl.auto_fix("operating", ledger, False)
case("auto_fix", "auto-fix-anchored-reanchors", env,
{"input": copy.deepcopy(ledger), "result": res,
"ledger_file": json.loads(Path(env["ledger"]).read_text())})
env = fresh_env("autofix_restore", [row("good text", "project")])
dl.snapshot()
con = sqlite3.connect(env["db"])
con.execute("UPDATE entries SET text='good text drifted' WHERE topic='project'")
con.commit()
con.close()
ledger = json.loads(Path(env["ledger"]).read_text())
res = dl.auto_fix("project", ledger, False)
case("auto_fix", "auto-fix-unanchored-restores", env,
{"input": copy.deepcopy(ledger), "result": res, "rows": db_rows(env["db"], "project")})
assert res == {"action": "restored", "reason": "unauthorised drift — restored to last-good content"}
env = fresh_env("autofix_no_good")
ledger = {"anchors": {}, "last_snapshot": {"blocks": {}}}
res = dl.auto_fix("human", copy.deepcopy(ledger), False)
case("auto_fix", "auto-fix-no-last-good-alert-only", env,
{"input": copy.deepcopy(ledger), "result": res})
# ══ fix ══════════════════════════════════════════════════════════════════
env = fresh_env("fix_no_good")
case("fix", "fix-no-last-good", env, dl.fix("human"))
env = fresh_env("fix_anchored", [row("anchored content", "human")])
dl.snapshot()
dl.anchor("human", "authorised")
case("fix", "fix-anchored-refuses", env, dl.fix("human"))
env = fresh_env("fix_matches", [row("stable content", "human")])
dl.snapshot()
case("fix", "fix-already-matches", env, dl.fix("human"))
env = fresh_env("fix_restores", [row("good human text", "human")])
dl.snapshot()
con = sqlite3.connect(env["db"])
con.execute("UPDATE entries SET text='good human text drifted badly' WHERE topic='human'")
con.commit()
con.close()
res = dl.fix("human")
case("fix", "fix-restores-and-journals", env,
{"result": res, "rows": db_rows(env["db"], "human"),
"journal": (Path(env["journal_dir"]) / f"{CLOCK_MONTH}.md").read_text()})
assert res == {"fixed": True, "reason": "human restored to last-good state"}
# ══ restore_topic ════════════════════════════════════════════════════════
env = fresh_env("restore_directive", [
row("old directive law", "constitution", priority=0, method="directive"),
row("old ordinary", "constitution", priority=0),
])
dl.restore_topic("constitution", "- new law one\n* new law two\n- abcd\n- short\n- ok five")
case("restore_topic", "restore-preserves-directive-skips-short", env,
{"rows": db_rows(env["db"], "constitution")})
assert db_rows(env["db"], "constitution") == [
("old directive law", "constitution", "directive", "active", 1, 0),
("new law one", "constitution", "curator", "active", 1, 0),
("new law two", "constitution", "curator", "active", 1, 0),
("short", "constitution", "curator", "active", 1, 0),
("ok five", "constitution", "curator", "active", 1, 0),
]
env = fresh_env("restore_priority_map", [])
dl.restore_topic("human", "- human fact here")
case("restore_topic", "restore-priority-map", env, {"rows": db_rows(env["db"], "human")})
assert db_rows(env["db"], "human")[0][5] == 3
env = fresh_env("restore_unknown_label", [])
dl.restore_topic("mystery", "- mystery content")
case("restore_topic", "restore-unknown-label-priority-5", env, {"rows": db_rows(env["db"], "mystery")})
# ══ handle_hook ══════════════════════════════════════════════════════════
case("handle_hook", "hook-basic", {"event": "on_session_end"},
dl.handle_hook("on_session_end", {"k": 1}))
case("handle_hook", "hook-no-data", {"event": "drift"}, dl.handle_hook("drift"))
# ══ main ═════════════════════════════════════════════════════════════════
env = fresh_env("main_hook")
argv = sys.argv
sys.argv = ["drift_ledger.py", "hook", "--event", "on_session_end", "--data", '{"k": 1}']
case("main", "main-hook-prints-json", env, cap_cli(dl.main))
sys.argv = ["drift_ledger.py", "fix"]
case("main", "main-fix-missing-label-exit-1", env, cap_cli(dl.main))
sys.argv = ["drift_ledger.py", "anchor"]
case("main", "main-anchor-missing-label-exit-1", env, cap_cli(dl.main))
sys.argv = argv
env = fresh_env("main_snapshot", [row("cli proj", "project")])
sys.argv = ["drift_ledger.py", "snapshot"]
res = cap_cli(dl.main)
case("main", "main-snapshot-exit-0", env,
{**res, "ledger": json.loads(Path(env["ledger"]).read_text())})
assert isinstance(json.loads(res["stdout"]), dict) and res["rc"] == 0
sys.argv = ["drift_ledger.py", "anchor", "project", "--strict"]
res = cap_cli(dl.main)
case("main", "main-anchor-default-reason", env,
{**res, "ledger": json.loads(Path(env["ledger"]).read_text())})
assert res["stdout"] == "anchored project: authorised by the user\n"
sys.argv = ["drift_ledger.py", "check"]
res = cap_cli(dl.main)
case("main", "main-check-healthy-exit-0", env, res)
assert res["rc"] == 0
sys.argv = argv
# ══ kernel: snapshot_entry ═══════════════════════════════════════════════
BLOCKS_EMPTY = {label: "" for label in dl.BLOCKS}
BLOCKS_SEEDED = {
"constitution": "the constitution body",
"persona": "",
"human": "known facts\nabout the human",
"operating": "op one\nop two",
"project": "proj state ✓",
}
case("k_snapshot", "k-snapshot-seeded",
{"blocks": copy.deepcopy(BLOCKS_SEEDED), "when": CLOCK_TS},
dl._snapshot_entry(copy.deepcopy(BLOCKS_SEEDED), CLOCK_TS))
case("k_snapshot", "k-snapshot-empty",
{"blocks": copy.deepcopy(BLOCKS_EMPTY), "when": CLOCK_TS},
dl._snapshot_entry(copy.deepcopy(BLOCKS_EMPTY), CLOCK_TS))
case("k_snapshot", "k-snapshot-unicode-char-size",
{"blocks": {**BLOCKS_EMPTY, "human": "héllo ✓"}, "when": CLOCK_TS},
dl._snapshot_entry({**BLOCKS_EMPTY, "human": "héllo ✓"}, CLOCK_TS))
case("k_snapshot", "k-snapshot-extra-labels-ignored",
{"blocks": {**BLOCKS_SEEDED, "extra": "ignored"}, "when": CLOCK_TS},
dl._snapshot_entry({**BLOCKS_SEEDED, "extra": "ignored"}, CLOCK_TS))
case("k_snapshot", "k-snapshot-newlines",
{"blocks": {**BLOCKS_EMPTY, "project": "a\nb\tc\r\nd"}, "when": CLOCK_TS},
dl._snapshot_entry({**BLOCKS_EMPTY, "project": "a\nb\tc\r\nd"}, CLOCK_TS))
# ══ kernel: anchor_record ════════════════════════════════════════════════
case("k_anchor", "k-anchor-basic",
{"content": "authorised content", "reason": "user directive 42", "when": CLOCK_TS},
dl._anchor_record("authorised content", "user directive 42", CLOCK_TS))
case("k_anchor", "k-anchor-unicode-empty",
{"content": "", "reason": "π ✓", "when": CLOCK_TS},
dl._anchor_record("", "π ✓", CLOCK_TS))
# ══ kernel: check_alerts ═════════════════════════════════════════════════
EMPTY_BLOCKS = {label: "" for label in dl.BLOCKS}
def alert_ledger(anchors=None, snap_blocks=None):
ledger = {}
if anchors is not None:
ledger["anchors"] = anchors
if snap_blocks is not None:
ledger["last_snapshot"] = {"when": CLOCK_TS, "blocks": snap_blocks}
return ledger
# unanchored growth over the limit
case("k_alerts", "k-alerts-unanchored-over-limit",
{"ledger": alert_ledger(snap_blocks={"project": {"size": 100, "content": "old\nlines"}}),
"blocks": {**EMPTY_BLOCKS, "project": "old\nlines\n" + "x" * 60}},
dl._check_alerts(alert_ledger(snap_blocks={"project": {"size": 100, "content": "old\nlines"}}),
{**EMPTY_BLOCKS, "project": "old\nlines\n" + "x" * 60}))
# anchored growth within the limit
_anchor = {"sha": "a" * 64, "size": 100, "content": "base\ncontent"}
case("k_alerts", "k-alerts-anchored-within-limit",
{"ledger": alert_ledger(anchors={"operating": _anchor}),
"blocks": {**EMPTY_BLOCKS, "operating": "base\ncontent\nplus a bit"}},
dl._check_alerts(alert_ledger(anchors={"operating": dict(_anchor)}),
{**EMPTY_BLOCKS, "operating": "base\ncontent\nplus a bit"}))
# anchored growth over the limit with root-cause diff
case("k_alerts", "k-alerts-anchored-over-limit-diff",
{"ledger": alert_ledger(anchors={"project": _anchor}),
"blocks": {**EMPTY_BLOCKS, "project": "base\ncontent\n" + "y" * 80}},
dl._check_alerts(alert_ledger(anchors={"project": dict(_anchor)}),
{**EMPTY_BLOCKS, "project": "base\ncontent\n" + "y" * 80}))
# constitution content drift: any change trips, even tiny
case("k_alerts", "k-alerts-constitution-content-drift",
{"ledger": alert_ledger(anchors={"constitution": _anchor}),
"blocks": {**EMPTY_BLOCKS, "constitution": "base\ncontent\n!"}},
dl._check_alerts(alert_ledger(anchors={"constitution": dict(_anchor)}),
{**EMPTY_BLOCKS, "constitution": "base\ncontent\n!"}))
# constitution size drift without content change -> pct path only
case("k_alerts", "k-alerts-constitution-no-anchor-sha-pct-path",
{"ledger": alert_ledger(anchors={"constitution": {"sha": "b" * 64, "size": 10, "content": "tiny"}}),
"blocks": {**EMPTY_BLOCKS, "constitution": "tiny"}},
dl._check_alerts(alert_ledger(anchors={"constitution": {"sha": "b" * 64, "size": 10, "content": "tiny"}}),
{**EMPTY_BLOCKS, "constitution": "tiny"}))
# empty ledger: nothing to measure against
case("k_alerts", "k-alerts-empty-ledger-quiet",
{"ledger": {}, "blocks": EMPTY_BLOCKS},
dl._check_alerts({}, dict(EMPTY_BLOCKS)))
# negative delta (shrink) measured against prev
case("k_alerts", "k-alerts-shrink-over-limit",
{"ledger": alert_ledger(snap_blocks={"human": {"size": 100, "content": "lots of text"}}),
"blocks": {**EMPTY_BLOCKS, "human": "short"}},
dl._check_alerts(alert_ledger(snap_blocks={"human": {"size": 100, "content": "lots of text"}}),
{**EMPTY_BLOCKS, "human": "short"}))
# rounding: 3 -> 4 chars, pct 1/3
case("k_alerts", "k-alerts-rounds-pct",
{"ledger": alert_ledger(snap_blocks={"human": {"size": 3, "content": "abc"}}),
"blocks": {**EMPTY_BLOCKS, "human": "abcd"}},
dl._check_alerts(alert_ledger(snap_blocks={"human": {"size": 3, "content": "abc"}}),
{**EMPTY_BLOCKS, "human": "abcd"}))
# multi-alert BLOCKS order
_multi = alert_ledger(snap_blocks={"persona": {"size": 5, "content": "p one"},
"human": {"size": 5, "content": "h one"}})
case("k_alerts", "k-alerts-multi-blocks-order",
{"ledger": _multi,
"blocks": {**EMPTY_BLOCKS, "persona": "p one grown", "human": "h one grown"}},
dl._check_alerts(alert_ledger({"persona": {"size": 5, "content": "p one"},
"human": {"size": 5, "content": "h one"}}),
{**EMPTY_BLOCKS, "persona": "p one grown", "human": "h one grown"}))
# ══ kernel: auto_fix_decision ════════════════════════════════════════════
for i, (label, anchored, has_good) in enumerate([
("constitution", False, False),
("constitution", True, True),
("operating", True, False),
("persona", False, True),
("human", False, False),
]):
case("k_auto_fix", f"k-auto-fix-{i}-{label}-{int(anchored)}{int(has_good)}",
{"label": label, "anchored": anchored, "has_last_good": has_good},
dl._auto_fix_decision(label, anchored, has_good))
# ══ kernel: fix_decision ═════════════════════════════════════════════════
_fix_ledger = {"last_snapshot": {"when": CLOCK_TS, "blocks": {
"project": {"sha": "c" * 64, "size": 10, "content": "good content"},
"human": {"size": 4, "content": "no sha key"},
}}, "anchors": {"constitution": {"sha": "d" * 64}}}
case("k_fix", "k-fix-no-last-good", {"ledger": {}, "label": "human", "cur_sha": "a" * 64},
dl._fix_decision({}, "human", "a" * 64))
case("k_fix", "k-fix-anchored-refuses", {"ledger": copy.deepcopy(_fix_ledger), "label": "constitution", "cur_sha": "x"},
dl._fix_decision(copy.deepcopy(_fix_ledger), "constitution", "x"))
case("k_fix", "k-fix-sha-matches", {"ledger": copy.deepcopy(_fix_ledger), "label": "project", "cur_sha": "c" * 64},
dl._fix_decision(copy.deepcopy(_fix_ledger), "project", "c" * 64))
case("k_fix", "k-fix-restores", {"ledger": copy.deepcopy(_fix_ledger), "label": "project", "cur_sha": "e" * 64},
dl._fix_decision(copy.deepcopy(_fix_ledger), "project", "e" * 64))
case("k_fix", "k-fix-meta-without-sha", {"ledger": copy.deepcopy(_fix_ledger), "label": "human", "cur_sha": "z"},
dl._fix_decision(copy.deepcopy(_fix_ledger), "human", "z"))
# ══ write ════════════════════════════════════════════════════════════════
truth = {
"frozen": "drift_ledger sha/diff/ledger_path/load_ledger/save_ledger/"
"read_block/journal/surface_alert/snapshot/anchor/check/"
"auto_fix/fix/restore_topic/handle_hook/main",
"oracle": "src/rune/drift_ledger.py",
"note": "Clock frozen at 2031-01-01T00:00:00+00:00 (dl.datetime seam); "
"store/journal/status paths isolated per case via INDEX_FILE/DB/"
"JOURNAL_DIR module globals and the memory_env.memory_dir() seam. "
"SQLite read/write seams and file IO stay in the facade by design; "
"the deterministic cores (sha, diff, alert loop, snapshot/anchor "
"assembly, auto-fix routing, fix decision, ledger path) cross the "
"doc-envelope FFI.",
}
for gname in GROUPS:
truth[gname] = groups[gname]
out = Path(os.environ.get("DL_TRUTH_OUT", "/tmp/dl_truth.json"))
out.write_text(json.dumps(truth, indent=1, ensure_ascii=False) + "\n")
counts = ", ".join(f"{len(groups[g])} {g}" for g in GROUPS)
print(f"froze {sum(len(v) for v in groups.values())} cases -> {out}: {counts}")
+399
View File
@@ -0,0 +1,399 @@
#!/usr/bin/env python3
"""gen_experience_encoding_truth.py — freeze/replay parity truth for
experience_encoding (dichotomy wave, #2479 tranche).
Freeze mode (default): exercises the LIVE Python oracle
(src/rune/experience_encoding.py evaluate — the module's single public
function) across every branch and records (kernel_input,
expected_kernel_output) pairs into
src/rust/experience_encoding/src/frozen_ee_truth.json.
Replay mode (--replay): re-runs the SAME scenarios with the Rust core
delegation ACTIVE (RUNE_EE_CORE unpinned) and asserts both kernel-level
(loader direct) and facade-level (oracle evaluate delegates) parity
against the frozen file.
Poisoned-truth law: freeze pins RUNE_EE_CORE=python BEFORE any import.
Mutation law: case inputs are deep-copied BEFORE they reach the oracle.
Lexicon-seam law: the oracle's `import lexicon_evolution` is an
ENVIRONMENT seam — the generator pins it per case via
`lexicon_available`; freeze/replay with the flag False block the flat
import (sys.modules sentinel) so the ImportError fallback branch
(conservative evidence admission) is frozen like any other. The Rust
kernel receives the same flag in its payload and mirrors the branch.
"""
import copy
import json
import os
import sys
from datetime import datetime, timezone
REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
TRUTH_PATH = os.path.join(REPO, "src", "rust", "experience_encoding",
"src", "frozen_ee_truth.json")
REPLAY = "--replay" in sys.argv
if not REPLAY:
os.environ["RUNE_EE_CORE"] = "python" # poisoned-truth law (freeze side)
else:
os.environ.pop("RUNE_EE_CORE", None) # live-parity law (facade must delegate)
sys.path.insert(0, os.path.join(REPO, "src"))
sys.path.insert(0, os.path.join(REPO, "src", "rune"))
import experience_encoding as oracle_mod # noqa: E402
if REPLAY:
from rune import ee_loader # noqa: E402 (kernel-level replay side)
if not ee_loader.available():
print("REPLAY ABORT: kernel not built —", ee_loader.LIB_PATH)
sys.exit(2)
class _Unset:
pass
_UNSET = _Unset()
def _input(event, existing_episodes=_UNSET, corpus_texts=_UNSET,
lexicon_available=True):
payload = {"event": event, "lexicon_available": lexicon_available}
if existing_episodes is not _UNSET:
payload["existing_episodes"] = list(existing_episodes)
if corpus_texts is not _UNSET:
payload["corpus_texts"] = list(corpus_texts)
return payload
class _LexiconBlocked:
"""Context manager: blocks the flat `import lexicon_evolution` the way
a missing module would (sys.modules None sentinel → ImportError)."""
def __enter__(self):
self._saved = sys.modules.get("lexicon_evolution", _UNSET)
sys.modules["lexicon_evolution"] = None
return self
def __exit__(self, *exc):
if self._saved is _UNSET:
sys.modules.pop("lexicon_evolution", None)
else:
sys.modules["lexicon_evolution"] = self._saved
return False
def facade_call(inp):
"""Run the LIVE facade (delegates to the Rust core when built)."""
if inp.get("lexicon_available", True):
return oracle_mod.evaluate(
copy.deepcopy(inp["event"]),
copy.deepcopy(inp.get("existing_episodes")),
copy.deepcopy(inp.get("corpus_texts", ())),
)
with _LexiconBlocked():
return oracle_mod.evaluate(
copy.deepcopy(inp["event"]),
copy.deepcopy(inp.get("existing_episodes")),
copy.deepcopy(inp.get("corpus_texts", ())),
)
def kernel_call(inp):
"""Run the Rust kernel directly through the ctypes loader."""
return ee_loader.evaluate(**copy.deepcopy(inp))
# ── evaluate ─────────────────────────────────────────────────────────────────
def ev_case(rec, name, event, existing=_UNSET, corpus=_UNSET,
lexicon_available=True):
inp = _input(event, existing, corpus, lexicon_available)
got = facade_call(copy.deepcopy(inp))
rec(name, inp, got, got)
def c_ev_missing_text(rec):
ev_case(rec, "ev-missing-text-key-empty", {"source": "user"})
def c_ev_empty_text(rec):
ev_case(rec, "ev-empty-text", {"text": "", "source": "user"})
def c_ev_null_text(rec):
ev_case(rec, "ev-null-text-coerced", {"text": None, "source": "user"})
def c_ev_whitespace_text(rec):
ev_case(rec, "ev-whitespace-only-text", {"text": " \n\t ", "source": "user"})
def c_ev_short_14(rec):
ev_case(rec, "ev-short-14-chars", {"text": "x" * 14, "source": "user"})
def c_ev_boundary_15(rec):
# exactly 15 chars passes the gate; no episodes/corpus → ungrounded
ev_case(rec, "ev-boundary-15-chars-evidence",
{"text": "x" * 15, "source": "curator", "session_id": "s1"})
def c_ev_null_source_session(rec):
# get() default only applies when the KEY is missing — a present-but-null
# source/session_id is pinned as provenance nulls (oracle quirk)
ev_case(rec, "ev-null-source-session-provenance",
{"text": "hi", "source": None, "session_id": None})
def c_ev_empty_source_session(rec):
ev_case(rec, "ev-empty-string-source-session",
{"text": "x" * 20, "source": "", "session_id": ""})
def c_ev_extra_keys(rec):
ev_case(rec, "ev-extra-keys-ignored",
{"text": "x" * 16, "kind": "note", "weight": 3})
def c_ev_duplicate_normalized(rec):
ev_case(rec, "ev-duplicate-normalized-whitespace-case",
{"text": "Nick prefers DARK coffee", "source": "user"},
existing=[{"text": "nick prefers DARK coffee"}])
def c_ev_duplicate_exact(rec):
ev_case(rec, "ev-duplicate-exact-string",
{"text": "she drinks oatmilk daily", "source": "user"},
existing=[{"text": "she drinks oatmilk daily"}])
def c_ev_duplicate_episode_case(rec):
ev_case(rec, "ev-duplicate-episode-uppercase",
{"text": "nick prefers dark coffee", "source": "user"},
existing=[{"text": "NICK PREFERS DARK COFFEE"}])
def c_ev_duplicate_episode_padding(rec):
ev_case(rec, "ev-duplicate-episode-padded",
{"text": "nick prefers dark coffee", "source": "user"},
existing=[{"text": " nick prefers dark coffee "}])
def c_ev_not_dup_punctuation(rec):
# the exclamation mark survives normalization → NOT a duplicate; the
# event then grounds in the episode's corpus and shares terms
ev_case(rec, "ev-punctuation-survives-not-duplicate",
{"text": "NICK PREFERS dark coffee!", "source": "user"},
existing=[{"text": "nick prefers dark coffee"}])
def c_ev_dup_null_episode_text(rec):
# episodes with None/missing text never duplicate; corpus fallback is
# all empty → ungrounded evidence
ev_case(rec, "ev-null-episode-texts-ungrounded",
{"text": "quantum flux patterns emerge here", "source": "user"},
existing=[{"text": None}, {}])
def c_ev_lexicon_blocked(rec):
# ImportError seam: long text, lexicon unavailable → conservative evidence
ev_case(rec, "ev-lexicon-blocked-conservative-evidence",
{"text": "she drinks oatmilk every single morning",
"source": "user"},
lexicon_available=False)
def c_ev_lexicon_blocked_short(rec):
# the empty/short gate runs BEFORE the lexicon import — still IGNORE
ev_case(rec, "ev-lexicon-blocked-short-still-ignored",
{"text": "hi", "source": "user"},
lexicon_available=False)
def c_ev_lexicon_blocked_with_episodes(rec):
# the duplicate check is INSIDE the try — blocked lexicon wins over a
# would-be duplicate (conservative evidence, not IGNORE)
ev_case(rec, "ev-lexicon-blocked-duplicate-path-conservative",
{"text": "nick prefers dark coffee", "source": "user"},
existing=[{"text": "nick prefers dark coffee"}],
lexicon_available=False)
def c_ev_framing_only(rec):
ev_case(rec, "ev-framing-only-no-lexical-signal",
{"text": "work thing way always", "source": "user"})
def c_ev_episode_shared_single(rec):
ev_case(rec, "ev-episode-shared-single-term",
{"text": "she never jokes about oatmilk at all", "source": "user"},
existing=[{"text": "i love oatmilk a lot"}],
corpus=["oatmilk is life", "tea later day"])
def c_ev_episode_shared_multi(rec):
ev_case(rec, "ev-episode-shared-multiple-sorted",
{"text": "oatmilk and celesta every morning", "source": "user"},
existing=[{"text": "oatmilk and celesta"}],
corpus=["oatmilk bar", "celesta hall"])
def c_ev_episode_shared_top3(rec):
ev_case(rec, "ev-episode-shared-sorted-top3",
{"text": "oatmilk celesta zephyr quartz ritual", "source": "user"},
existing=[{"text": "oatmilk celesta zephyr quartz"}],
corpus=["oatmilk celesta zephyr quartz notes"])
def c_ev_episode_novel_grounded(rec):
ev_case(rec, "ev-episode-novel-axis-grounded",
{"text": "celesta practice daily ritual", "source": "session"},
existing=[{"text": "i love oatmilk"}],
corpus=["she plays the celesta nightly"])
def c_ev_evidence_ungrounded(rec):
ev_case(rec, "ev-evidence-fact-shaped-ungrounded",
{"text": "quantum flux patterns emerge", "source": "curator"},
existing=[{"text": "i love oatmilk"}],
corpus=["oatmilk is life"])
def c_ev_evidence_empty_everything(rec):
ev_case(rec, "ev-evidence-no-episodes-no-corpus",
{"text": "quantum flux patterns emerge", "source": "curator"})
def c_ev_stem_default_corpus(rec):
# corpus omitted → corpus = episodes' texts; stem-aware grounding
# (migrated→migr matches migration→migr)
ev_case(rec, "ev-episode-stem-grounded-default-corpus",
{"text": "she migrated north yesterday", "source": "session"},
existing=[{"text": "the migration of songbirds"}])
def c_ev_corpus_override_shared_killed(rec):
# corpus override pushes oatmilk df over the threshold → share killed,
# but grounding survives (plain stem overlap) → novel-axis episode
ev_case(rec, "ev-corpus-override-share-killed-novel-grounded",
{"text": "she never jokes about oatmilk ok", "source": "user"},
existing=[{"text": "i love oatmilk"}],
corpus=["oatmilk"] * 30)
def c_ev_no_episodes_with_corpus(rec):
# shared_distinctive runs against the (empty) episodes while grounding
# uses the corpus → novel-axis episode
ev_case(rec, "ev-episode-no-episodes-corpus-grounded",
{"text": "she drinks oatmilk daily", "source": "user"},
corpus=["oatmilk is life"])
def c_ev_episodes_without_corpus(rec):
# corpus defaults to the episodes' texts
ev_case(rec, "ev-episode-default-corpus-is-episodes",
{"text": "she drinks oatmilk daily", "source": "user"},
existing=[{"text": "oatmilk rituals fill the morning"}])
# ── driver ───────────────────────────────────────────────────────────────────
EVALUATE = [
c_ev_missing_text, c_ev_empty_text, c_ev_null_text, c_ev_whitespace_text,
c_ev_short_14, c_ev_boundary_15, c_ev_null_source_session,
c_ev_empty_source_session, c_ev_extra_keys, c_ev_duplicate_normalized,
c_ev_duplicate_exact, c_ev_duplicate_episode_case,
c_ev_duplicate_episode_padding, c_ev_not_dup_punctuation,
c_ev_dup_null_episode_text, c_ev_lexicon_blocked,
c_ev_lexicon_blocked_short, c_ev_lexicon_blocked_with_episodes,
c_ev_framing_only, c_ev_episode_shared_single, c_ev_episode_shared_multi,
c_ev_episode_shared_top3, c_ev_episode_novel_grounded,
c_ev_evidence_ungrounded, c_ev_evidence_empty_everything,
c_ev_stem_default_corpus, c_ev_corpus_override_shared_killed,
c_ev_no_episodes_with_corpus, c_ev_episodes_without_corpus,
]
GROUPS = {
"evaluate": (EVALUATE, True, "ee_evaluate", lambda w: w),
}
def canon(x) -> str:
"""Content canonicalization (key-order independent)."""
return json.dumps(json.loads(json.dumps(x, default=str)), sort_keys=True)
def main():
frozen = None
if REPLAY:
with open(TRUTH_PATH) as f:
frozen = json.load(f)["cases"]
groups = {}
for group, (cases, facade_replayable, _sym, _unwrap) in GROUPS.items():
recs = []
def rec(name, inp, oracle_ret, expected, _r=recs, _g=group):
entry = {"name": name, "input": inp,
"return": oracle_ret, "expected": expected}
if REPLAY and facade_replayable:
old = next(c for c in frozen[_g] if c["name"] == name)
for key in ("input", "return", "expected"):
got, want = entry[key], old[key]
if canon(got) != canon(want):
print(f"FACADE DIVERGE [{_g}] {name} {key}")
print(" got: ", json.dumps(got, sort_keys=True, default=str)[:600])
print(" want:", json.dumps(want, sort_keys=True, default=str)[:600])
_r.append({"name": name, "diverged": key})
return
_r.append(entry)
for c in cases:
c(rec)
groups[group] = recs
total = sum(len(v) for v in groups.values())
if REPLAY:
facade_groups = [g for g, (_, r, _, _) in GROUPS.items() if r]
facade_total = sum(len(groups[g]) for g in facade_groups)
facade_failures = sum(1 for g in facade_groups for c in groups[g]
if "diverged" in c)
failures = 0
checked = 0
for group, cases in frozen.items():
for case in cases:
checked += 1
got = kernel_call(case["input"])
want = case["expected"]
if canon(got) != canon(want):
failures += 1
print(f"KERNEL DIVERGE [{group}] {case['name']}")
print(" got: ", json.dumps(got, sort_keys=True, default=str)[:600])
print(" want:", json.dumps(want, sort_keys=True, default=str)[:600])
print(f"replay: facade {facade_total - facade_failures}/{facade_total}, "
f"kernel {checked - failures}/{checked}")
return 1 if (failures or facade_failures) else 0
doc = {
"frozen": datetime.now(timezone.utc).isoformat(),
"oracle": "src/rune/experience_encoding.py",
"pin": "RUNE_EE_CORE=python",
"note": "Frozen via scripts/gen_experience_encoding_truth.py against "
"the live Python oracle. Battery embeds this VERBATIM. The "
"`lexicon_available` flag pins the oracle's flat-import "
"environment seam (ImportError fallback branch).",
"cases": groups,
}
os.makedirs(os.path.dirname(TRUTH_PATH), exist_ok=True)
with open(TRUTH_PATH, "w") as f:
json.dump(doc, f, indent=1)
f.write("\n")
print(f"frozen {total} cases → {os.path.relpath(TRUTH_PATH, REPO)}")
return 0
if __name__ == "__main__":
sys.exit(main())
+463
View File
@@ -0,0 +1,463 @@
#!/usr/bin/env python3
"""gen_mutation_guards_truth.py — freeze/replay parity truth for
mutation_guards (dichotomy wave, #2479 tranche).
Freeze mode (default): exercises the LIVE Python oracle
(src/rune/mutation_guards.py — classify_mutation and guard_mutation, the
module's two public functions) across every branch and records
(kernel_input, expected_kernel_output) pairs into
src/rust/mutation_guards/src/frozen_mg_truth.json.
Replay mode (--replay): re-runs the SAME scenarios with the Rust core
delegation ACTIVE (RUNE_MG_CORE unpinned) and asserts both kernel-level
(loader direct) and facade-level (oracle delegates) parity against the
frozen file.
Poisoned-truth law: freeze pins RUNE_MG_CORE=python BEFORE any import.
Mutation law: case inputs are deep-copied BEFORE they reach the oracle.
Branch inventory (both functions):
* DENY 1 — unattributed identity-family mutation (identity/persona/
constitution, falsy attributed); runs FIRST so it beats read ops and
the destructive gate
* DENY 2 — destructive (op severity >= 2) on a protected kind
(severity >= 2) with no backup
* CONFIRM — score >= 4, or identity-family with any mutating op
* ALLOW — everything else
* severity-table defaults: unknown kind -> 1, unknown op -> 1,
unknown/None scope -> 0
* Python truthiness through guard_mutation's .get passthrough: the
default applies only when the KEY is missing — present-but-null
attributed is falsy (unattributed), falsy 0/[]/"yes"-truthy pins the
quirk; the f-string renders a null kind/operation as "None"
"""
import copy
import json
import os
import sys
from datetime import datetime, timezone
REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
TRUTH_PATH = os.path.join(REPO, "src", "rust", "mutation_guards",
"src", "frozen_mg_truth.json")
REPLAY = "--replay" in sys.argv
if not REPLAY:
os.environ["RUNE_MG_CORE"] = "python" # poisoned-truth law (freeze side)
else:
os.environ.pop("RUNE_MG_CORE", None) # live-parity law (facade delegates)
sys.path.insert(0, os.path.join(REPO, "src"))
sys.path.insert(0, os.path.join(REPO, "src", "rune"))
import mutation_guards as oracle_mod # noqa: E402
if REPLAY:
from rune import mu_loader # noqa: E402 (kernel-level replay side)
if not mu_loader.available():
print("REPLAY ABORT: kernel not built —", mu_loader.LIB_PATH)
sys.exit(2)
def facade_classify(inp):
return oracle_mod.classify_mutation(
copy.deepcopy(inp["target_kind"]),
copy.deepcopy(inp["operation"]),
copy.deepcopy(inp["scope"]),
copy.deepcopy(inp["attributed"]),
copy.deepcopy(inp["backed_up"]),
)
def kernel_classify(inp):
return mu_loader.classify(**copy.deepcopy(inp))
def facade_guard(inp):
return oracle_mod.guard_mutation(copy.deepcopy(inp["mutation"]))
def kernel_guard(inp):
return mu_loader.guard(**copy.deepcopy(inp))
# ── classify_mutation ────────────────────────────────────────────────────────
def cl_case(rec, name, target_kind, operation, scope="single",
attributed=True, backed_up=False):
inp = {"target_kind": target_kind, "operation": operation,
"scope": scope, "attributed": attributed, "backed_up": backed_up}
got = facade_classify(copy.deepcopy(inp))
rec(name, inp, got, got)
# DENY 1 — unattributed identity family (runs FIRST)
def c_den1_identity(rec):
cl_case(rec, "cl-deny1-unattributed-identity-overwrite",
"identity", "overwrite", "single", False, False)
def c_den1_persona_append_whole(rec):
# append (op severity 1) would CONFIRM on persona; unattributed wins
cl_case(rec, "cl-deny1-unattributed-persona-append-whole",
"persona", "append", "whole", False, False)
def c_den1_constitution_falsy_zero(rec):
# attributed=0 is falsy (Python truthiness) → unattributed
cl_case(rec, "cl-deny1-constitution-attributed-zero",
"constitution", "append", "single", 0, False)
def c_den1_beats_read(rec):
# even a READ is denied when unattributed — precedence proof
cl_case(rec, "cl-deny1-identity-read-still-denied",
"identity", "read", "single", False, False)
def c_den1_null_attributed(rec):
# explicit None attributed → falsy → DENY
cl_case(rec, "cl-deny1-identity-attributed-null",
"identity", "append", "single", None, False)
# DENY 2 — destructive without a snapshot
def c_den2_constitution_delete_whole(rec):
cl_case(rec, "cl-deny2-constitution-delete-whole",
"constitution", "delete", "whole", True, False)
def c_den2_skill_delete_multi(rec):
cl_case(rec, "cl-deny2-skill-delete-multi",
"skill", "delete", "multi", True, False)
def c_den2_belief_overwrite_whole(rec):
# op severity 2 + kind severity 2 boundary: still DENY
cl_case(rec, "cl-deny2-belief-overwrite-whole-boundary",
"belief", "overwrite", "whole", True, False)
def c_den2_backed_up_zero(rec):
# backed_up=0 is falsy → unbacked → DENY
cl_case(rec, "cl-deny2-skill-overwrite-whole-backed-zero",
"skill", "overwrite", "whole", True, 0)
# CONFIRM — score >= 4 or identity-family mutating op
def c_conf_backed_skill_overwrite(rec):
cl_case(rec, "cl-confirm-backed-skill-overwrite-whole",
"skill", "overwrite", "whole", True, True)
def c_conf_identity_append_attributed(rec):
# kind severity 3 + op severity 1 → CONFIRM even at score 4
cl_case(rec, "cl-confirm-identity-append-attributed",
"identity", "append", "single", True, False)
def c_conf_identity_read_whole(rec):
# read (op 0) escapes the identity rule; score 3+0+2=5 ≥ 4 → CONFIRM
cl_case(rec, "cl-confirm-identity-read-whole-score",
"identity", "read", "whole", True, False)
def c_conf_operating_delete_single(rec):
# score exactly 4 — the CONFIRM boundary
cl_case(rec, "cl-confirm-operating-delete-single-score-4",
"operating", "delete", "single", True, False)
def c_conf_fact_overwrite_whole(rec):
# op 2 + kind 1: no DENY (kind < 2), score 5 → CONFIRM
cl_case(rec, "cl-confirm-fact-overwrite-whole",
"fact", "overwrite", "whole", True, False)
def c_conf_backed_truthy_string(rec):
# backed_up="yes" is truthy → skips DENY 2 → CONFIRM
cl_case(rec, "cl-confirm-skill-delete-multi-backed-truthy",
"skill", "delete", "multi", True, "yes")
def c_conf_constitution_delete_backed(rec):
cl_case(rec, "cl-confirm-constitution-delete-whole-backed",
"constitution", "delete", "whole", True, True)
def c_conf_persona_overwrite_backed(rec):
cl_case(rec, "cl-confirm-persona-overwrite-whole-backed",
"persona", "overwrite", "whole", True, True)
def c_conf_archive_delete_whole(rec):
# archive (kind 0) still CONFIRMs at score 5 — score rule beats kind
cl_case(rec, "cl-confirm-archive-delete-whole-score",
"archive", "delete", "whole", True, False)
def c_conf_unknown_kind_delete(rec):
# unknown kind defaults to severity 1: 1+3+0=4 → CONFIRM
cl_case(rec, "cl-confirm-unknown-kind-delete",
"widget", "delete", "single", True, False)
# ALLOW — everything else
def c_allow_fact_append_single(rec):
cl_case(rec, "cl-allow-fact-append-single-defaults",
"fact", "append", "single", True, False)
def c_allow_fact_append_multi(rec):
# score 3 — directly below the CONFIRM boundary
cl_case(rec, "cl-allow-fact-append-multi-score-3",
"fact", "append", "multi", True, False)
def c_allow_identity_read_single(rec):
# identity + read + single: score 3, identity rule needs op ≥ 1
cl_case(rec, "cl-allow-identity-read-single",
"identity", "read", "single", True, False)
def c_allow_archive_delete_single(rec):
# kind severity 0: even delete is cheap on archives
cl_case(rec, "cl-allow-archive-delete-single",
"archive", "delete", "single", True, False)
def c_allow_archive_read_whole(rec):
cl_case(rec, "cl-allow-archive-read-whole",
"archive", "read", "whole", True, False)
def c_allow_fact_read_whole(rec):
cl_case(rec, "cl-allow-fact-read-whole",
"fact", "read", "whole", True, False)
def c_allow_unknown_operation(rec):
# unknown op defaults to severity 1: 1+1+1=3 → ALLOW
cl_case(rec, "cl-allow-unknown-operation",
"fact", "nuke", "multi", True, False)
def c_allow_unknown_scope(rec):
# unknown scope defaults to severity 0
cl_case(rec, "cl-allow-unknown-scope",
"fact", "append", "galaxy", True, False)
def c_allow_null_kind(rec):
# None kind: severity default + f-string renders "None"
cl_case(rec, "cl-allow-null-kind-renders-None",
None, "append", "single", True, False)
def c_allow_null_operation(rec):
cl_case(rec, "cl-allow-null-operation-renders-None",
"fact", None, "single", True, False)
def c_allow_null_scope(rec):
# None scope: severity 0 (dict lookup miss)
cl_case(rec, "cl-allow-null-scope",
"fact", "append", None, True, False)
# ── guard_mutation ───────────────────────────────────────────────────────────
def gu_case(rec, name, mutation):
inp = {"mutation": mutation}
got = facade_guard(copy.deepcopy(inp))
rec(name, inp, got, got)
def g_empty_dict_defaults(rec):
gu_case(rec, "gu-empty-dict-all-defaults", {})
def g_fact_append_defaults(rec):
gu_case(rec, "gu-fact-append-allow", {"target_kind": "fact",
"operation": "append"})
def g_unattributed_identity(rec):
gu_case(rec, "gu-unattributed-identity-deny",
{"target_kind": "identity", "operation": "overwrite",
"attributed": False})
def g_unbacked_constitution_delete(rec):
gu_case(rec, "gu-unbacked-constitution-delete-deny",
{"target_kind": "constitution", "operation": "delete",
"scope": "whole", "attributed": True, "backed_up": False})
def g_backed_skill_confirms(rec):
gu_case(rec, "gu-backed-skill-overwrite-confirm",
{"target_kind": "skill", "operation": "overwrite",
"scope": "whole", "attributed": True, "backed_up": True})
def g_null_attributed_present(rec):
# present-but-null attributed is FALSY — the default True applies
# only to a MISSING key
gu_case(rec, "gu-attributed-null-falsy-deny",
{"target_kind": "identity", "attributed": None})
def g_empty_list_attributed(rec):
gu_case(rec, "gu-attributed-empty-list-falsy-deny",
{"target_kind": "persona", "operation": "delete",
"attributed": []})
def g_backed_up_zero(rec):
gu_case(rec, "gu-backed-up-zero-falsy-deny",
{"target_kind": "skill", "operation": "overwrite",
"scope": "whole", "backed_up": 0})
def g_backed_up_truthy_string(rec):
gu_case(rec, "gu-backed-up-truthy-string-confirm",
{"target_kind": "skill", "operation": "overwrite",
"scope": "whole", "backed_up": "yes"})
def g_read_allows(rec):
gu_case(rec, "gu-read-allows", {"target_kind": "fact",
"operation": "read"})
def g_extra_keys_ignored(rec):
gu_case(rec, "gu-extra-keys-ignored",
{"target_kind": "fact", "operation": "append",
"note": "x", "weight": 9})
def g_null_target_kind(rec):
# present-null target_kind beats the "fact" default (get quirk);
# severity defaults to 1, f-string renders "None"
gu_case(rec, "gu-null-target-kind-renders-None",
{"target_kind": None})
def g_empty_operation_string(rec):
# empty op string: severity default 1, f-string renders ""
gu_case(rec, "gu-empty-operation-string",
{"target_kind": "fact", "operation": ""})
# ── driver ───────────────────────────────────────────────────────────────────
CLASSIFY = [
c_den1_identity, c_den1_persona_append_whole,
c_den1_constitution_falsy_zero, c_den1_beats_read,
c_den1_null_attributed, c_den2_constitution_delete_whole,
c_den2_skill_delete_multi, c_den2_belief_overwrite_whole,
c_den2_backed_up_zero, c_conf_backed_skill_overwrite,
c_conf_identity_append_attributed, c_conf_identity_read_whole,
c_conf_operating_delete_single, c_conf_fact_overwrite_whole,
c_conf_backed_truthy_string, c_conf_constitution_delete_backed,
c_conf_persona_overwrite_backed, c_conf_archive_delete_whole,
c_conf_unknown_kind_delete, c_allow_fact_append_single,
c_allow_fact_append_multi, c_allow_identity_read_single,
c_allow_archive_delete_single, c_allow_archive_read_whole,
c_allow_fact_read_whole, c_allow_unknown_operation,
c_allow_unknown_scope, c_allow_null_kind, c_allow_null_operation,
c_allow_null_scope,
]
GUARD = [
g_empty_dict_defaults, g_fact_append_defaults,
g_unattributed_identity, g_unbacked_constitution_delete,
g_backed_skill_confirms, g_null_attributed_present,
g_empty_list_attributed, g_backed_up_zero,
g_backed_up_truthy_string, g_read_allows, g_extra_keys_ignored,
g_null_target_kind, g_empty_operation_string,
]
GROUPS = {
"classify": (CLASSIFY, True, "mu_classify", facade_classify,
kernel_classify),
"guard": (GUARD, True, "mu_guard", facade_guard, kernel_guard),
}
def canon(x) -> str:
"""Content canonicalization (key-order independent)."""
return json.dumps(json.loads(json.dumps(x, default=str)), sort_keys=True)
def main():
frozen = None
if REPLAY:
with open(TRUTH_PATH) as f:
frozen = json.load(f)["cases"]
groups = {}
for group, (cases, facade_replayable, _sym, fcall, kcall) in GROUPS.items():
recs = []
def rec(name, inp, oracle_ret, expected, _r=recs, _g=group):
entry = {"name": name, "input": inp,
"return": oracle_ret, "expected": expected}
if REPLAY and facade_replayable:
old = next(c for c in frozen[_g] if c["name"] == name)
for key in ("input", "return", "expected"):
got, want = entry[key], old[key]
if canon(got) != canon(want):
print(f"FACADE DIVERGE [{_g}] {name} {key}")
print(" got: ", json.dumps(got, sort_keys=True, default=str)[:600])
print(" want:", json.dumps(want, sort_keys=True, default=str)[:600])
_r.append({"name": name, "diverged": key})
return
_r.append(entry)
for c in cases:
c(rec)
groups[group] = recs
total = sum(len(v) for v in groups.values())
if REPLAY:
facade_groups = [g for g, (_, r, _, _, _) in GROUPS.items() if r]
facade_total = sum(len(groups[g]) for g in facade_groups)
facade_failures = sum(1 for g in facade_groups for c in groups[g]
if "diverged" in c)
failures = 0
checked = 0
for group, cases in frozen.items():
sym_fns = {"classify": kernel_classify, "guard": kernel_guard}
for case in cases:
checked += 1
got = sym_fns[group](case["input"])
want = case["expected"]
if canon(got) != canon(want):
failures += 1
print(f"KERNEL DIVERGE [{group}] {case['name']}")
print(" got: ", json.dumps(got, sort_keys=True, default=str)[:600])
print(" want:", json.dumps(want, sort_keys=True, default=str)[:600])
print(f"replay: facade {facade_total - facade_failures}/{facade_total}, "
f"kernel {checked - failures}/{checked}")
return 1 if (failures or facade_failures) else 0
doc = {
"frozen": datetime.now(timezone.utc).isoformat(),
"oracle": "src/rune/mutation_guards.py",
"pin": "RUNE_MG_CORE=python",
"note": "Frozen via scripts/gen_mutation_guards_truth.py against "
"the live Python oracle. Battery embeds this VERBATIM.",
"cases": groups,
}
os.makedirs(os.path.dirname(TRUTH_PATH), exist_ok=True)
with open(TRUTH_PATH, "w") as f:
json.dump(doc, f, indent=1)
f.write("\n")
print(f"frozen {total} cases → {os.path.relpath(TRUTH_PATH, REPO)}")
return 0
if __name__ == "__main__":
sys.exit(main())
+349
View File
@@ -0,0 +1,349 @@
#!/usr/bin/env python3
"""Freeze spine_language oracle truth for the Rust port battery
(dichotomy wave #2479 tranche, module spine_language).
Frozen surface (all 5 module functions, every branch):
_classification_sides() — measured-map read + degrade-to-{}
_place_languages(doc) — kind defaults / measured authority /
name hints / subsystem majority /
unclassified / unassessed / placed count
_contract_discoveries() — closed-contract gather + kind filters
_normalize_discovery_ids(doc) — legacy rename + demotion + tag filter
_apply_contract_discoveries(doc) — high-water merge write-back
Store reads (store/rebase/classification.json,
store/scaffold/current.json) are materialized per case under a temp
root (sl.ROOT monkeypatched) so the frozen truth carries its fixtures:
each case records `files` {relpath: content}; the Rust battery
materializes the same tree. Wall-clock `observed_at` stamps (from
structural_store.add_entity) are masked on BOTH sides — the only
non-deterministic field in the module. RUNE_SL_CORE=python pinned
(POISONED-TRUTH LAW: freeze the oracle, never a kernel). Inputs
deep-copied before capture.
Output: src/rust/spine_language/frozen_sl_truth.json — replayed
VERBATIM by src/rust/spine_language/src/branch_battery.rs.
"""
from __future__ import annotations
import copy
import json
import os
import sys
import tempfile
from pathlib import Path
os.environ["RUNE_SL_CORE"] = "python" # freeze the ORACLE, never a kernel
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
from rune import spine_language as sl # noqa: E402
cases: list[dict] = []
def mask(obj):
"""Drop wall-clock observed_at stamps recursively (the module's only
nondeterministic output field)."""
if isinstance(obj, dict):
return {k: mask(v) for k, v in obj.items() if k != "observed_at"}
if isinstance(obj, list):
return [mask(v) for v in obj]
return obj
def freeze(fn_name: str, name: str, *, files: dict | None = None,
doc: dict | None = None, run):
"""Materialize fixture files under a temp root, run the oracle fn with
sl.ROOT pointed there, record the case."""
root = Path(tempfile.mkdtemp(prefix="sl-truth-"))
for rel, content in (files or {}).items():
p = root / rel
p.parent.mkdir(parents=True, exist_ok=True)
p.write_text(content)
original_root = sl.ROOT
sl.ROOT = root
try:
doc_in = copy.deepcopy(doc) if doc is not None else None
expected = run(copy.deepcopy(doc) if doc is not None else None)
finally:
sl.ROOT = original_root
cases.append({
"fn": fn_name,
"name": name,
"files": files or {},
"doc": doc_in,
"expected": mask(expected),
})
CLASSIFICATION = json.dumps({
"spine": {"side": "RUST"},
"spine_language": {"side": "RUST"},
"behavior": {"side": "RUST"},
"identity_anchors": {"side": "RUST"},
"persona_definition": {"side": "RUST"},
"persona_gate": {"side": "RUST"},
"prompt_sync": {"side": "GO"},
"voice_hybrid": {"side": "GO"},
"voice_sync": {"side": "GO"},
"rune_tui": {"side": "TUI-LAST"},
"testkit": {"side": "TEST-INFRA"},
"legacy_tool": {"side": "RETIRES"},
"lowercase_mod": {"side": "rust"},
"num_side": {"side": 5},
"no_side": {},
"not_a_dict": "skip me",
}, indent=1)
def cls_file(extra: dict | None = None) -> dict:
data = json.loads(CLASSIFICATION)
data.update(extra or {})
return {"store/rebase/classification.json": json.dumps(data)}
# ── _classification_sides ────────────────────────────────────────────────
freeze("classification_sides", "measured-map", files=cls_file(),
run=lambda _doc: sl._classification_sides())
freeze("classification_sides", "missing-file-degrades", files={},
run=lambda _doc: sl._classification_sides())
freeze("classification_sides", "malformed-json-degrades",
files={"store/rebase/classification.json": "{oops"},
run=lambda _doc: sl._classification_sides())
freeze("classification_sides", "non-dict-top-level-degrades",
files={"store/rebase/classification.json": "[1, 2, 3]"},
run=lambda _doc: sl._classification_sides())
# ── _place_languages ─────────────────────────────────────────────────────
def run_place(doc):
placed = sl._place_languages(doc)
return {"doc": doc, "placed": placed}
def ent(eid, kind, **kw):
e = {"id": eid, "kind": kind}
e.update(kw)
return e
freeze("place_languages", "kind-defaults", files=cls_file(), doc={
"entities": [
ent("rust-crate:cognition_core", "rust-crate"),
ent("go-surface:rune-entry", "go-surface"),
ent("workflow:ci.yml", "workflow"),
ent("config:settings.toml", "config"),
ent("contract:BUILDER-x", "contract"),
ent("deployment_surface:prod", "deployment_surface"),
ent("module:zzz_unknown", "module"),
ent("widget:odd", "widget"),
]}, run=run_place)
freeze("place_languages", "measured-authoritative", files=cls_file(), doc={
"entities": [
ent("module:spine", "module"),
ent("module:behavior", "module"),
ent("module:prompt_sync", "module"),
ent("module:rune_tui", "module"),
ent("module:testkit", "module"),
ent("module:legacy_tool", "module"),
ent("module:lowercase_mod", "module"),
ent("module:num_side", "module"),
ent("module:no_side", "module"),
ent("module:totally_unknown_name", "module"),
]}, run=run_place)
freeze("place_languages", "hints-only-when-unmeasured", files=cls_file(), doc={
"entities": [
ent("module:server_farm", "module"),
ent("module:memory_drift", "module"),
ent("module:tui_widgets", "module"),
ent("module:dispatch_tui", "module"),
ent("module:watch_server", "module"),
]}, run=run_place)
freeze("place_languages", "existing-language-overwritten", files=cls_file(), doc={
"entities": [
ent("module:spine", "module", identities={"language": "go"}),
ent("widget:odd", "widget", identities={"other": "x"}),
ent("bare-no-identities", "widget"),
]}, run=run_place)
freeze("place_languages", "subsystem-majority", files=cls_file(), doc={
"entities": [
ent("subsystem:identity", "subsystem"),
ent("subsystem:allrust", "subsystem"),
ent("subsystem:allgo", "subsystem"),
ent("subsystem:unassessed", "subsystem"),
*[ent(f"module:{m}", "module") for m in (
"spine", "behavior", "identity_anchors", # 3 rust
"prompt_sync", "voice_hybrid", # 2 go
"zzz_unknown")], # 1 unmeasured
],
"relationships": [
*[{"type": "belongs_to", "from": f"module:{m}",
"to": "subsystem:identity", "evidence_refs": []} for m in (
"spine", "behavior", "identity_anchors",
"prompt_sync", "voice_hybrid", "zzz_unknown")],
{"type": "belongs_to", "from": "module:spine",
"to": "subsystem:allrust", "evidence_refs": []},
{"type": "belongs_to", "from": "module:prompt_sync",
"to": "subsystem:allgo", "evidence_refs": []},
]}, run=run_place)
freeze("place_languages", "no-entities-key", files=cls_file(), doc={
"schema_version": 1}, run=run_place)
# ── _contract_discoveries ────────────────────────────────────────────────
def scaffold_doc() -> dict:
return {
"entities": [
{"id": "contract:C1", "kind": "contract", "phase": "closed",
"ext": {"contract_quality": {
"expected_new_observed": ["rust-crate:kernel_x",
"config:app.yml",
"bogus-kind:x",
"suite"],
"candidate_corrections": [
{"entity": "suite"},
{"entity": "workflow:sync.yml"},
{"entity": "bogus-kind:y"},
{"entity": ""},
]}}},
{"id": "contract:C2", "kind": "contract", "phase": "building",
"ext": {"contract_quality": {
"expected_new_observed": ["rust-crate:never"]}}},
{"id": "contract:C3", "kind": "contract", "phase": "closed"},
{"id": "contract:C4", "kind": "contract",
"ext": {"contract_quality": {
"expected_new_observed": ["rust-crate:also-never"]}}},
{"id": "module:plain", "kind": "module", "phase": "closed"},
]}
def run_discoveries(_doc):
got = sl._contract_discoveries()
return json.loads(json.dumps(got)) # tuples -> arrays for JSON
def scaffold_files() -> dict:
return {"store/scaffold/current.json": json.dumps(scaffold_doc())}
freeze("contract_discoveries", "closed-contract-gather",
files=scaffold_files(), run=run_discoveries)
freeze("contract_discoveries", "missing-store", files={}, run=run_discoveries)
freeze("contract_discoveries", "malformed-store",
files={"store/scaffold/current.json": "{oops"}, run=run_discoveries)
freeze("contract_discoveries", "no-entities-key",
files={"store/scaffold/current.json": json.dumps({"schema_version": 1})},
run=run_discoveries)
# ── _normalize_discovery_ids ─────────────────────────────────────────────
def run_normalize(doc):
renamed = sl._normalize_discovery_ids(doc)
return {"doc": doc, "renamed": renamed}
freeze("normalize_discovery_ids", "legacy-module-renamed-and-demoted",
files=scaffold_files(), doc={
"entities": [
{"id": "src/rune/claim_audit.py", "kind": "module",
"maturity": "PRESENT", "tags": ["code", "keep"],
"evidence_refs": ["contract-discovery:old"]},
],
"relationships": [
{"type": "depends_on", "from": "src/rune/claim_audit.py",
"to": "module:other", "evidence_refs": []},
{"type": "depends_on", "from": "module:x",
"to": "src/rune/claim_audit.py", "evidence_refs": []},
]}, run=run_normalize)
freeze("normalize_discovery_ids", "observed-retains-present",
files={"store/scaffold/current.json": json.dumps({
"entities": [{"id": "contract:C1", "kind": "contract",
"phase": "closed",
"ext": {"contract_quality": {
"expected_new_observed": [
"construction-surface:src/rune/kept.py"]}}}]})},
doc={"entities": [
{"id": "src/rune/kept.py", "kind": "module", "maturity": "PRESENT",
"tags": ["code"], "evidence_refs": ["contract-discovery:C1"]},
]}, run=run_normalize)
freeze("normalize_discovery_ids", "already-surface-demotion-and-tag-filter",
files=scaffold_files(), doc={
"entities": [
{"id": "construction-surface:foo", "kind": "construction-surface",
"maturity": "PRESENT", "tags": ["code", "keep"],
"evidence_refs": []},
]}, run=run_normalize)
freeze("normalize_discovery_ids", "no-discovery-ref-no-rename",
files=scaffold_files(), doc={
"entities": [
{"id": "src/rune/plain.py", "kind": "module",
"maturity": "PRESENT", "tags": [], "evidence_refs": ["spine"]},
{"id": "src/rune/noref.py", "kind": "module",
"maturity": "PRESENT", "tags": []},
{"id": "qualified:still.py", "kind": "module",
"maturity": "PRESENT",
"evidence_refs": ["contract-discovery:old"]},
]}, run=run_normalize)
# ── _apply_contract_discoveries ──────────────────────────────────────────
def run_apply(doc):
placed = sl._apply_contract_discoveries(doc)
return {"doc": doc, "placed": placed}
freeze("apply_contract_discoveries", "observed-and-candidate-writeback",
files=scaffold_files(), doc={"entities": [], "relationships": []},
run=run_apply)
freeze("apply_contract_discoveries", "high-water-merge",
files=scaffold_files(), doc={
"entities": [
{"id": "rust-crate:kernel_x", "kind": "rust-crate",
"maturity": "DECLARED", "tags": ["seed"],
"identities": {"keep": "me"}, "evidence_refs": ["seed-ref"]},
{"id": "config:app.yml", "kind": "config",
"maturity": "WIRED", "tags": [],
"evidence_refs": []},
], "relationships": []}, run=run_apply)
freeze("apply_contract_discoveries", "double-prefix-quirk",
files={"store/scaffold/current.json": json.dumps({
"entities": [{"id": "contract:C1", "kind": "contract",
"phase": "closed",
"ext": {"contract_quality": {
"expected_new_observed": [
"construction-surface:foo"]}}}]})},
doc={"entities": [], "relationships": []}, run=run_apply)
freeze("apply_contract_discoveries", "empty-discoveries-noop",
files={"store/scaffold/current.json": json.dumps({
"entities": [{"id": "contract:C2", "kind": "contract",
"phase": "building"}]})},
doc={"entities": [], "relationships": []}, run=run_apply)
freeze("apply_contract_discoveries", "missing-store-noop", files={},
doc={"entities": [], "relationships": []}, run=run_apply)
truth = {
"frozen": "spine_language doc-level transforms (all 5 fns, every branch; "
"observed_at masked both sides; store fixtures carried per case)",
"oracle": "src/rune/spine_language.py",
"pin": "RUNE_SL_CORE=python",
"cases": cases,
}
out = Path(__file__).resolve().parents[1] / "src" / "rust" / "spine_language" / "frozen_sl_truth.json"
out.parent.mkdir(parents=True, exist_ok=True)
out.write_text(json.dumps(truth, indent=1) + "\n")
print(f"froze {len(cases)} cases -> {out}")
@@ -0,0 +1,51 @@
#!/usr/bin/env python3
"""Open the #2479 dichotomy-wave construction contract for
abstraction_ladder. Full lifecycle in ONE process (shared-store clobber
hazard)."""
import json
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
from rune.construction_interface import ConstructionInterface
PARITY_SHA = "4d0883b3"
PHASE_B_SHA = "00bf4d21"
iface = ConstructionInterface(builder="factory-wave")
r = iface.submit_need(
"Port src/rune/abstraction_ladder.py to FULL Rust (dichotomy factory, "
"issue #2479 tranche): doc-envelope FFI, ctypes loader, facade "
"delegation with RUNE_AL_CORE=python pin, frozen-truth battery, "
"live parity 100%, Phase B language-advantage pass.",
origin="builder",
)
print("submit_need:", json.dumps(r)[:400])
if not r.get("success"):
print("NEED FAILED — aborting"); sys.exit(1)
cid = r["ids"]["contract_id"]
print("contract_id:", cid)
for phase in ("approved", "building", "verifying"):
t = iface.transition(cid, phase)
print(f"transition->{phase}:", json.dumps(t)[:300])
if not t.get("success"):
sys.exit(1)
c = iface.close(cid, observed_evidence=[{
"kind": "port_evidence",
"detail": f"abstraction_ladder parity port — parity {PARITY_SHA}, "
f"phase B {PHASE_B_SHA} (SHAs reported in the builder report)",
}], scope_files=[
"src/rune/abstraction_ladder.py",
"src/rune/al_loader.py",
"src/rust/abstraction_ladder/src/lib.rs",
"src/rust/abstraction_ladder/src/ladder.rs",
"src/rust/abstraction_ladder/src/branch_battery.rs",
"scripts/gen_abstraction_ladder_truth.py",
])
print("close:", json.dumps(c)[:400])
print("CONTRACT", cid, "closed" if c.get("success") else "CLOSE_FAILED")
print("CID_LINE", cid)
+33
View File
@@ -0,0 +1,33 @@
#!/usr/bin/env python3
"""Open the #2479 dichotomy-wave construction contract for behaviour_ledger.
Full lifecycle in ONE process (shared-store clobber hazard)."""
import json
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
from rune.construction_interface import ConstructionInterface
iface = ConstructionInterface(builder="factory-wave")
r = iface.submit_need(
"Port src/rune/behaviour_ledger.py to FULL Rust (dichotomy factory, "
"issue #2479 tranche): doc-envelope FFI, ctypes loader, facade "
"delegation with RUNE_BL_CORE=python pin, frozen-truth battery, "
"live parity 100%, Phase B language-advantage pass.",
origin="builder",
)
print("submit_need:", json.dumps(r)[:400])
if not r.get("success"):
print("NEED FAILED — aborting"); sys.exit(1)
cid = r["ids"]["contract_id"]
print("contract_id:", cid)
for phase in ("approved", "building", "verifying"):
t = iface.transition(cid, phase)
print(f"transition->{phase}:", json.dumps(t)[:300])
if not t.get("success"):
sys.exit(1)
print("PENDING_CONTRACT", cid)
@@ -0,0 +1,45 @@
#!/usr/bin/env python3
"""Open the #2479 dichotomy-wave construction contract for experience_encoding.
Full lifecycle in ONE process (shared-store clobber hazard)."""
import json
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
from rune.construction_interface import ConstructionInterface
iface = ConstructionInterface(builder="factory-wave")
r = iface.submit_need(
"Port src/rune/experience_encoding.py to FULL Rust (dichotomy factory, "
"issue #2479 tranche): doc-envelope FFI, ctypes loader, facade "
"delegation with RUNE_EE_CORE=python pin, frozen-truth battery, "
"live parity 100%, Phase B language-advantage pass.",
origin="builder",
)
print("submit_need:", json.dumps(r)[:400])
if not r.get("success"):
print("NEED FAILED — aborting"); sys.exit(1)
cid = r["ids"]["contract_id"]
print("contract_id:", cid)
for phase in ("approved", "building", "verifying"):
t = iface.transition(cid, phase)
print(f"transition->{phase}:", json.dumps(t)[:300])
if not t.get("success"):
sys.exit(1)
c = iface.close(cid, observed_evidence=[{
"kind": "port_evidence",
"detail": "experience_encoding parity port — evidence attached at close "
"(SHAs reported in the builder report)",
}], scope_files=[
"src/rune/experience_encoding.py",
"src/rune/ee_loader.py",
"src/rust/experience_encoding/src/lib.rs",
"src/rust/experience_encoding/src/branch_battery.rs",
"scripts/gen_experience_encoding_truth.py",
])
print("close:", json.dumps(c)[:400])
print("CONTRACT", cid, "closed" if c.get("success") else "CLOSE_FAILED")
+45
View File
@@ -0,0 +1,45 @@
#!/usr/bin/env python3
"""Open the #2479 dichotomy-wave construction contract for mutation_guards.
Full lifecycle in ONE process (shared-store clobber hazard)."""
import json
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
from rune.construction_interface import ConstructionInterface
iface = ConstructionInterface(builder="factory-wave")
r = iface.submit_need(
"Port src/rune/mutation_guards.py to FULL Rust (dichotomy factory, "
"issue #2479 tranche): doc-envelope FFI, ctypes loader, facade "
"delegation with RUNE_MG_CORE=python pin, frozen-truth battery, "
"live parity 100%, Phase B language-advantage pass.",
origin="builder",
)
print("submit_need:", json.dumps(r)[:400])
if not r.get("success"):
print("NEED FAILED — aborting"); sys.exit(1)
cid = r["ids"]["contract_id"]
print("contract_id:", cid)
for phase in ("approved", "building", "verifying"):
t = iface.transition(cid, phase)
print(f"transition->{phase}:", json.dumps(t)[:300])
if not t.get("success"):
sys.exit(1)
c = iface.close(cid, observed_evidence=[{
"kind": "port_evidence",
"detail": "mutation_guards parity port — evidence attached at close "
"(SHAs reported in the builder report)",
}], scope_files=[
"src/rune/mutation_guards.py",
"src/rune/mu_loader.py",
"src/rust/mutation_guards/src/lib.rs",
"src/rust/mutation_guards/src/branch_battery.rs",
"scripts/gen_mutation_guards_truth.py",
])
print("close:", json.dumps(c)[:400])
print("CONTRACT", cid, "closed" if c.get("success") else "CLOSE_FAILED")
+50
View File
@@ -0,0 +1,50 @@
#!/usr/bin/env python3
"""Open → walk → close the #2479 dichotomy-wave construction contract for
spine_language. Full lifecycle in ONE process (shared-store clobber hazard)."""
import json
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
from rune.construction_interface import ConstructionInterface
iface = ConstructionInterface(builder="factory-wave")
r = iface.submit_need(
"Port src/rune/spine_language.py to FULL Rust (dichotomy factory, "
"issue #2479 tranche): doc-level transforms (#2465 language placement, "
"discovery-ID normalization, contract-discovery write-back), doc-envelope "
"FFI, ctypes loader, facade delegation with RUNE_SL_CORE=python pin, "
"frozen-truth battery, live parity 100%, Phase B language-advantage pass.",
origin="builder",
)
print("submit_need:", json.dumps(r)[:300])
if not r.get("success"):
print("NEED FAILED — aborting"); sys.exit(1)
cid = r["ids"]["contract_id"]
print("contract_id:", cid)
for phase in ("approved", "building", "verifying"):
t = iface.transition(cid, phase)
print(f"transition->{phase}:", json.dumps(t)[:200])
if not t.get("success"):
sys.exit(1)
c = iface.close(cid, observed_evidence=[{
"kind": "port_evidence",
"detail": "spine_language parity port — 23 frozen cases replay verbatim, "
"live parity 326/326 (frozen + fuzz seed 2479 + real-repo "
"pin-vs-auto), clippy 0, python tests green both pins "
"(SHAs in the builder report)",
}], scope_files=[
"src/rune/spine_language.py",
"src/rune/sl_loader.py",
"src/rust/spine_language/src/lib.rs",
"src/rust/spine_language/src/spine.rs",
"src/rust/spine_language/src/branch_battery.rs",
"src/rust/spine_language/frozen_sl_truth.json",
"scripts/gen_spine_language_truth.py",
])
print("close:", json.dumps(c)[:300])
print("CONTRACT", cid, "closed" if c.get("success") else "CLOSE_FAILED")
+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.231.0"
__version__ = "0.238.0"
# One build identity (handoff 2026-09-10 §4): the semantic version above is
# the repo-level truth (auto-bumped at merge); the DEPLOYED revision (exact
+34
View File
@@ -5,10 +5,35 @@ episode → fact → observation → concept → belief → rule → skill.
Each promotion needs: evidence threshold, provenance, contradiction
handling, prediction/behavioural consequence, outcome-based revision.
Schema alone does not count — the ladder moves items or it doesn't.
Dichotomy wave #2479 tranche: the ladder kernel is ported to FULL Rust
(src/rust/abstraction_ladder, parity oracle frozen_al_truth.json). This
facade delegates to the Rust core via rune/al_loader when built; the
Python implementation below is the strangler fallback, pinned with
RUNE_AL_CORE=python.
"""
from __future__ import annotations
import os
def _core():
"""The Rust abstraction_ladder core when available (strangler: the
Python fallback is load-bearing). RUNE_AL_CORE=python pins the
fallback."""
if os.environ.get("RUNE_AL_CORE", "auto") == "python":
return None
try:
from rune import al_loader
except Exception: # noqa: BLE001 — any loader failure means fallback
return None
try:
return al_loader if al_loader.available() else None
except Exception: # noqa: BLE001 — availability probe must never crash
return None
STAGES = ("episode", "fact", "observation", "concept", "belief", "rule", "skill")
# Evidence threshold per promotion (sightings required)
@@ -25,6 +50,9 @@ THRESHOLDS = {
def next_stage(stage: str) -> str | None:
"""The stage after this one (None at the top)."""
core = _core()
if core is not None:
return core.next_stage(stage)
try:
i = STAGES.index(stage)
except ValueError:
@@ -36,6 +64,9 @@ def can_promote(item: dict) -> dict:
"""Can this item promote? Needs: evidence threshold met, provenance
present, no open contradictions, a stated consequence, and (for
belief+) a verified outcome."""
core = _core()
if core is not None:
return core.can_promote(item)
stage = item.get("stage", "episode")
nxt = next_stage(stage)
if nxt is None:
@@ -59,6 +90,9 @@ def can_promote(item: dict) -> dict:
def promote(item: dict) -> dict:
"""Promote one stage (or return the refusal)."""
core = _core()
if core is not None:
return core.promote(item)
verdict = can_promote(item)
if not verdict["promote"]:
return {"item": item, **verdict}
+93
View File
@@ -0,0 +1,93 @@
#!/usr/bin/env python3
"""al_loader.py — ctypes loader for the Rust abstraction_ladder core
(dichotomy wave #2479 tranche). Mirrors lc_loader.py / broker_loader.py:
FFI contract (doc-envelope law — every call returns {"ok", "value"|"err"}):
* al_next_stage({"stage"}) -> stage string or null
* al_can_promote({"item": {...}}) -> verdict doc
* al_promote({"item": {...}}) -> {"item": ..., ...verdict}
Pure kernel: no ops, no store seams, no op callback.
Free: capability_scoring's cs_free_string (shared process). NEVER re-export
a free symbol here. Override the .so with AL_RUST_LIB (a FILE path).
"""
from __future__ import annotations
import ctypes
import json
import os
from pathlib import Path
_REPO = Path(__file__).resolve().parents[2]
_CRATE_DIR = _REPO / "src" / "rust" / "abstraction_ladder"
LIB_PATH = Path(os.environ.get("AL_RUST_LIB",
_CRATE_DIR / "target" / "release" /
"libabstraction_ladder.so"))
_lib = None
SYMBOLS = ("al_next_stage", "al_can_promote", "al_promote")
class AbstractionLadderCoreUnavailable(FileNotFoundError):
"""The Rust abstraction_ladder core is not built. Callers fall back
to Python (strangler law; RUNE_AL_CORE=python pins the fallback)."""
def available() -> bool:
return LIB_PATH.is_file()
def _load() -> ctypes.CDLL:
global _lib
if _lib is not None:
return _lib
if not LIB_PATH.is_file():
raise AbstractionLadderCoreUnavailable(str(LIB_PATH))
lib = ctypes.CDLL(str(LIB_PATH))
for sym in SYMBOLS:
fn = getattr(lib, sym)
fn.argtypes = [ctypes.c_void_p]
fn.restype = ctypes.c_void_p # char* — never default c_int
_lib = lib
return lib
def _free(ptr) -> None:
"""Free via capability_scoring's cs_free_string (same process)."""
try:
scoring = ctypes.CDLL(str(
_REPO / "src" / "rust" / "capability_scoring" / "target" /
"release" / "libcapability_scoring.so"))
scoring.cs_free_string.argtypes = [ctypes.c_void_p]
scoring.cs_free_string(ptr)
except OSError:
pass # leak-on-fallback only if capability_scoring absent; bounded
def _call(sym: str, payload: dict):
lib = _load()
data = json.dumps(payload).encode()
buf = ctypes.create_string_buffer(data)
ptr = getattr(lib, sym)(ctypes.cast(buf, ctypes.c_void_p))
if not ptr:
raise RuntimeError(f"{sym} returned null")
out = ctypes.string_at(ptr).decode()
_free(ptr)
doc = json.loads(out)
if not doc.get("ok"):
raise RuntimeError(doc.get("err", f"{sym} failed"))
return doc["value"]
def next_stage(stage):
return _call("al_next_stage", {"stage": stage})
def can_promote(item: dict) -> dict:
return _call("al_can_promote", {"item": item})
def promote(item: dict) -> dict:
return _call("al_promote", {"item": item})
+39 -18
View File
@@ -17,23 +17,26 @@ reason; unrelated hardened confirmations are preserved.
Storage: <project_store>/behaviour_ledger.json (versioned document).
Phase G strangler slice 2 (port 4): the state machine (define / get /
transitions / selective invalidation / queues / summary) is served
IN-PROCESS by the Rust `evidence_provenance` cdylib through ep_loader.py
— parity 13/13 proven vs this module's Python vintage (full-scenario
doc parity, byte-equal error messages, save render indent=1 sort_keys).
File glue (load/save) stays Python; timestamps are pinned by this shim
(`now`) so the core stays deterministic. State constants and class API
unchanged (git history holds the original).
Dichotomy wave #2479: the state machine (define / get / transitions /
selective invalidation / queues / summary) is served by the FULL Rust
`behaviour_ledger` cdylib through bl_loader.py — the kernel is a pure
function of the parsed ledger doc, timestamps pass IN (`now`, no-clock
law), and parity is pinned by the frozen truth battery embedded in the
crate (frozen_bl_truth.json). RUNE_BL_CORE=python pins the retired
evidence_provenance shim path (ep_loader) for parity harnesses. File
glue (load/save) stays Python. State constants and class API unchanged
(git history holds the original).
"""
from __future__ import annotations
import json
import os
import sys
import time
from pathlib import Path
# ── Rust core wiring (slice-2 port-4 shim pattern) ──────────────────────────
# ── retired shim wiring (RUNE_BL_CORE=python pin; fallback when the
# wave core is not built) ────────────────────────────────────────────────
from rune import paths # noqa: E402 (#41 centralized paths)
_CRATE_DIR = str(paths.crate_dir("evidence_provenance")) # #41 centralized
if _CRATE_DIR not in sys.path:
@@ -63,6 +66,24 @@ STATES = (UNVALIDATED, IMPLEMENTED, AUTO_VERIFIED, REAL_WORLD_PASSED,
CONFIRMED_STATES = (AUTO_VERIFIED, REAL_WORLD_PASSED, USER_CONFIRMED)
def _kernel():
"""Dichotomy wave #2479: the FULL Rust behaviour_ledger core first
(RUNE_BL_CORE=python pins the retired evidence_provenance shim —
parity harnesses). The kernel owns the state machine (define / get /
transitions / selective invalidation / queues / summary); the file
glue and the class API stay here."""
if os.environ.get("RUNE_BL_CORE", "auto") == "python":
return ep_loader
try:
from rune import bl_loader
if bl_loader.available():
return bl_loader
except ImportError:
pass
return ep_loader
def _now() -> str:
return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
@@ -93,7 +114,7 @@ class BehaviourLedger:
dependencies: list[str] | None = None,
automated_ref: str = "",
environment: str = "") -> dict:
self.doc, rec = ep_loader.bl_define(
self.doc, rec = _kernel().bl_define(
self.doc, behaviour_id=behaviour_id,
owning_project=owning_project,
intended_behaviour=intended_behaviour,
@@ -109,7 +130,7 @@ class BehaviourLedger:
return rec
def get(self, behaviour_id: str) -> dict | None:
return ep_loader.bl_get(self.doc, behaviour_id)
return _kernel().bl_get(self.doc, behaviour_id)
# -- state transitions (all recorded in history — rule 19G) -------------
@@ -118,7 +139,7 @@ class BehaviourLedger:
generation: str = "") -> dict:
"""Automated verification. GREEN NEVER IMPLIES USER_CONFIRMED
(rule 19W-22): the ceiling here is AUTO_VERIFIED."""
self.doc, rec = ep_loader.bl_record_auto(
self.doc, rec = _kernel().bl_record_auto(
self.doc, behaviour_id, passed=passed, detail=detail,
generation=generation, now=_now())
return rec
@@ -128,14 +149,14 @@ class BehaviourLedger:
confirmed_by: str = "user") -> dict:
"""Real-world/manual test result (testing mode). USER_CONFIRMED
requires an explicit confirmed_by='user' pass (rule 19M)."""
self.doc, rec = ep_loader.bl_record_real_world(
self.doc, rec = _kernel().bl_record_real_world(
self.doc, behaviour_id, passed=passed, detail=detail,
generation=generation, confirmed_by=confirmed_by, now=_now())
return rec
def accept_limitation(self, behaviour_id: str, *, note: str,
approved_by: str = "user") -> dict:
self.doc, rec = ep_loader.bl_accept_limitation(
self.doc, rec = _kernel().bl_accept_limitation(
self.doc, behaviour_id, note=note,
approved_by=approved_by, now=_now())
return rec
@@ -150,7 +171,7 @@ class BehaviourLedger:
structural dependencies intersect the changed closure. Every
invalidation carries a reason; unrelated confirmations stay.
Returns the invalidated records."""
self.doc, recs = ep_loader.bl_invalidate(
self.doc, recs = _kernel().bl_invalidate(
self.doc, changed_entities=sorted(changed_entities),
generation=generation, reasons=reasons, now=_now())
return recs
@@ -158,7 +179,7 @@ class BehaviourLedger:
def retest_history(self, behaviour_id: str) -> list[dict]:
"""Generation-aware history: all confirmations + invalidations
for one behaviour, oldest first (rule 19S-12)."""
return ep_loader.bl_retest_history(self.doc, behaviour_id)
return _kernel().bl_retest_history(self.doc, behaviour_id)
# -- queue (testing mode, 19L/M) ------------------------------------------
@@ -167,8 +188,8 @@ class BehaviourLedger:
"""Eligible testing items: anything not confirmed/accepted/n.a.
Dynamic — the queue re-derives from states after every fix
(rule 19M)."""
return ep_loader.bl_outstanding(
return _kernel().bl_outstanding(
self.doc, component=component or "", project=project or "")
def summary(self) -> dict:
return ep_loader.bl_summary(self.doc)
return _kernel().bl_summary(self.doc)
+176
View File
@@ -0,0 +1,176 @@
#!/usr/bin/env python3
"""ctypes loader for the FULL Rust behaviour_ledger core (dichotomy wave,
#2479 tranche). Mirrors broker_loader.py's contract:
FFI contract (doc-envelope law — every call returns {"ok", "value"|"err"}):
* bl_define({doc, behaviour_id, owning_project, intended_behaviour,
origin_intent, pass_criteria, fail_criteria, procedure,
owner_component, dependencies, automated_ref, environment, now})
→ {doc, rec}
* bl_get({doc, behaviour_id}) → record | null
* bl_record_auto({doc, behaviour_id, passed, detail, generation, now})
→ {doc, rec}
* bl_record_real_world({doc, behaviour_id, passed, detail, generation,
confirmed_by, now}) → {doc, rec}
* bl_accept_limitation({doc, behaviour_id, note, approved_by, now})
→ {doc, rec}
* bl_invalidate({doc, changed_entities, generation, reasons, now})
→ {doc, recs}
* bl_retest_history({doc, behaviour_id}) → events (oldest-first)
* bl_outstanding({doc, component, project}) → queue
* bl_summary({doc}) → {total, states, outstanding}
Domain errors (unknown behaviour, duplicate define, …) surface as
{"ok": false, "err": <exact Python message>} and raise ValueError —
the same type the retired evidence_provenance shim raised.
No-clock law: `now` (UTC "%Y-%m-%dT%H:%M:%SZ") is always an argument —
the kernel never reads the wall clock.
Free: capability_scoring's cs_free_string (shared process). Override the
.so with BL_RUST_LIB (a FILE path).
"""
from __future__ import annotations
import ctypes
import json
import os
from pathlib import Path
_REPO = Path(__file__).resolve().parents[2]
_CRATE_DIR = _REPO / "src" / "rust" / "behaviour_ledger"
_LIB_PATH = Path(
os.environ.get("BL_RUST_LIB", _CRATE_DIR / "target" / "release" / "libbehaviour_ledger.so")
)
_lib = None
class BlCoreUnavailable(FileNotFoundError):
"""The Rust behaviour_ledger core is not built. Callers fall back."""
def available() -> bool:
return _LIB_PATH.is_file()
def _load() -> ctypes.CDLL:
global _lib
if _lib is not None:
return _lib
if not _LIB_PATH.is_file():
raise BlCoreUnavailable(
f"Rust behaviour_ledger core not built: {_LIB_PATH} missing — run "
f"`cargo build --release` in {_CRATE_DIR}"
)
lib = ctypes.CDLL(str(_LIB_PATH))
for sym in (
"bl_define", "bl_get", "bl_record_auto", "bl_record_real_world",
"bl_accept_limitation", "bl_invalidate", "bl_retest_history",
"bl_outstanding", "bl_summary",
):
fn = getattr(lib, sym)
fn.argtypes = [ctypes.c_void_p]
fn.restype = ctypes.c_void_p
_lib = lib
return lib
def _call(sym: str, payload: dict):
lib = _load()
data = json.dumps(payload).encode()
buf = ctypes.create_string_buffer(data)
ptr = getattr(lib, sym)(ctypes.cast(buf, ctypes.c_void_p))
if not ptr:
raise RuntimeError(f"{sym} returned null")
out = ctypes.string_at(ptr).decode()
# free via any sibling free symbol already in-process (capability_scoring)
try:
scoring = ctypes.CDLL(str(
_REPO / "src" / "rust" / "capability_scoring" / "target" / "release" / "libcapability_scoring.so"
))
scoring.cs_free_string.argtypes = [ctypes.c_void_p]
scoring.cs_free_string(ptr)
except OSError:
pass # leak-on-fallback only if capability_scoring absent; bounded
doc = json.loads(out)
if not doc.get("ok"):
raise ValueError(doc.get("err", f"{sym} failed"))
return doc["value"]
def bl_define(doc: dict, *, behaviour_id: str, owning_project: str,
intended_behaviour: str, origin_intent: str = "",
pass_criteria: str = "", fail_criteria: str = "",
procedure: str = "", owner_component: str = "",
dependencies: list | None = None, automated_ref: str = "",
environment: str = "", now: str) -> tuple[dict, dict]:
"""define(): returns (new_doc, new_record)."""
v = _call("bl_define", {
"doc": doc, "behaviour_id": behaviour_id,
"owning_project": owning_project,
"intended_behaviour": intended_behaviour,
"origin_intent": origin_intent, "pass_criteria": pass_criteria,
"fail_criteria": fail_criteria, "procedure": procedure,
"owner_component": owner_component,
"dependencies": list(dependencies or []),
"automated_ref": automated_ref, "environment": environment,
"now": now,
})
return v["doc"], v["rec"]
def bl_record_auto(doc: dict, behaviour_id: str, *, passed: bool,
detail: str = "", generation: str = "",
now: str) -> tuple[dict, dict]:
"""record_auto_verification(): returns (new_doc, record)."""
v = _call("bl_record_auto", {
"doc": doc, "behaviour_id": behaviour_id, "passed": bool(passed),
"detail": detail, "generation": generation, "now": now,
})
return v["doc"], v["rec"]
def bl_record_real_world(doc: dict, behaviour_id: str, *, passed: bool,
detail: str = "", generation: str = "",
confirmed_by: str = "user",
now: str) -> tuple[dict, dict]:
"""record_real_world(): returns (new_doc, record)."""
v = _call("bl_record_real_world", {
"doc": doc, "behaviour_id": behaviour_id, "passed": bool(passed),
"detail": detail, "generation": generation,
"confirmed_by": confirmed_by, "now": now,
})
return v["doc"], v["rec"]
def bl_accept_limitation(doc: dict, behaviour_id: str, *, note: str,
approved_by: str = "user",
now: str) -> tuple[dict, dict]:
"""accept_limitation(): returns (new_doc, record)."""
v = _call("bl_accept_limitation", {
"doc": doc, "behaviour_id": behaviour_id, "note": note,
"approved_by": approved_by, "now": now,
})
return v["doc"], v["rec"]
def bl_invalidate(doc: dict, *, changed_entities: list,
generation: str = "", reasons: dict | None = None,
now: str) -> tuple[dict, list]:
"""invalidate_for_change(): returns (new_doc, invalidated records)."""
v = _call("bl_invalidate", {
"doc": doc, "changed_entities": list(changed_entities),
"generation": generation, "reasons": reasons or {}, "now": now,
})
return v["doc"], v["recs"]
def bl_get(doc: dict, behaviour_id: str) -> dict | None:
"""get(): record or None (normalized id)."""
return _call("bl_get", {"doc": doc, "behaviour_id": behaviour_id})
def bl_retest_history(doc: dict, behaviour_id: str) -> list:
"""retest_history(): events oldest-first (empty for unknown)."""
return _call("bl_retest_history", {"doc": doc,
"behaviour_id": behaviour_id})
def bl_outstanding(doc: dict, *, component: str = "",
project: str = "") -> list:
"""outstanding(): filtered queue, TEST_FAILED first."""
return _call("bl_outstanding", {"doc": doc, "component": component,
"project": project})
def bl_summary(doc: dict) -> dict:
"""summary(): {total, states, outstanding}."""
return _call("bl_summary", {"doc": doc})
+1 -1
View File
@@ -233,7 +233,7 @@ def main() -> int:
)
check(
"Rune minor version advertises provisioning and restore verification",
'__version__ = "0.231.0"' in rune_version,
'__version__ = "0.238.0"' in rune_version,
failures,
)
# #169 policy update: auto-bump on merge is sanctioned (Nick directive:
+18 -1
View File
@@ -9,6 +9,8 @@ CANDIDATE/UNIT-PROVEN — existence is not trust (§15).
from __future__ import annotations
import os
from rune.construction_pattern import ConstructionPattern
__all__ = ["BUILTIN_PATTERNS", "get_pattern", "PATTERN_REGISTRY_REVISION"]
@@ -236,7 +238,22 @@ _REGISTRY: dict[str, ConstructionPattern] = {p.pattern_id: p for p in BUILTIN_PA
def get_pattern(pattern_id: str, revision: int | None = None) -> ConstructionPattern | None:
"""Fetch a builtin pattern; a specific revision raises if the registry
no longer carries it (contracts reference the revision they consumed —
§17)."""
§17).
Dichotomy wave: the Rust registry core first (RUNE_CPB_CORE=python
pins the Python fallback — strangler law). Both serve the same frozen
registry docs, so the pin is only for oracle isolation."""
if os.environ.get("RUNE_CPB_CORE", "auto") != "python":
try:
from rune import cpb_loader
if cpb_loader.available():
doc = cpb_loader.get_pattern(pattern_id, revision)
return ConstructionPattern.from_dict(doc) if doc is not None else None
except ValueError:
raise # the kernel's revision-mismatch message is oracle-exact
except Exception:
pass # kernel unavailable — Python registry below
p = _REGISTRY.get(pattern_id)
if p is None:
return None
+111
View File
@@ -0,0 +1,111 @@
#!/usr/bin/env python3
"""ctypes loader for the Rust construction_patterns_builtin registry core
(dichotomy wave, #2479 tranche). Mirrors cs_loader.py's contract:
FFI contract (doc-envelope law — every call returns {"ok", "value"|"err"}):
* cpb_registry_revision({}) -> number
* cpb_patterns({}) -> [pattern doc, ...] registry order
* cpb_get_pattern({"pattern_id", "revision"}) -> pattern doc | null
(a stale revision is ok=false; get_pattern raises ValueError with the
oracle's exact message)
Free: capability_scoring's cs_free_string (shared process) — never
re-exported here. Override the .so with CPB_RUST_LIB (a FILE path).
"""
from __future__ import annotations
import ctypes
import json
import os
from pathlib import Path
_REPO = Path(__file__).resolve().parents[2]
_CRATE_DIR = _REPO / "src" / "rust" / "construction_patterns_builtin"
_LIB_PATH = Path(
os.environ.get(
"CPB_RUST_LIB",
_CRATE_DIR / "target" / "release" / "libconstruction_patterns_builtin.so",
)
)
_lib = None
class CpbUnavailable(FileNotFoundError):
"""The Rust registry core is not built. Callers fall back to the Python
registry — strangler law."""
def available() -> bool:
return _LIB_PATH.is_file()
def _load() -> ctypes.CDLL:
global _lib
if _lib is not None:
return _lib
if not _LIB_PATH.is_file():
raise CpbUnavailable(
f"Rust construction_patterns_builtin core not built: {_LIB_PATH} missing — "
f"run `cargo build --release` in {_CRATE_DIR}"
)
lib = ctypes.CDLL(str(_LIB_PATH))
for sym in ("cpb_registry_revision", "cpb_patterns", "cpb_get_pattern"):
fn = getattr(lib, sym)
fn.argtypes = [ctypes.c_char_p]
fn.restype = ctypes.c_void_p # char* — never default c_int
# free comes from capability_scoring (same process, shared allocator law)
scoring = ctypes.CDLL(
str(_REPO / "src" / "rust" / "capability_scoring" / "target" / "release" / "libcapability_scoring.so")
)
scoring.cs_free_string.argtypes = [ctypes.c_void_p]
lib._free = scoring.cs_free_string
_lib = lib
return lib
def _call(sym: str, payload: dict):
lib = _load()
fn = getattr(lib, sym)
raw = json.dumps(payload).encode("utf-8")
ptr = fn(raw)
if not ptr:
raise RuntimeError(f"{sym} returned a null pointer")
try:
text = ctypes.string_at(ptr).decode("utf-8")
finally:
lib._free(ptr)
env = json.loads(text)
if not env.get("ok"):
raise RuntimeError(f"{sym} core error: {env.get('err')}")
return env["value"]
def registry_revision() -> int:
"""The builtin pattern registry revision (PATTERN_REGISTRY_REVISION)."""
return int(_call("cpb_registry_revision", {}))
def patterns() -> list:
"""Every builtin pattern doc, in registry order."""
return _call("cpb_patterns", {})
def get_pattern(pattern_id: str, revision: int | None = None):
"""Pattern doc for `pattern_id`, or None. A revision that no longer
matches raises ValueError with the oracle's exact message."""
raw = json.dumps({"pattern_id": pattern_id, "revision": revision}).encode("utf-8")
lib = _load()
ptr = lib.cpb_get_pattern(raw)
if not ptr:
raise RuntimeError("cpb_get_pattern returned a null pointer")
try:
text = ctypes.string_at(ptr).decode("utf-8")
finally:
lib._free(ptr)
env = json.loads(text)
if not env.get("ok"):
# The only err the kernel produces is the oracle's revision-mismatch
# ValueError message — preserve its type for facade parity.
raise ValueError(env.get("err"))
return env["value"]
+43 -2
View File
@@ -1,12 +1,17 @@
#!/usr/bin/env python3
"""ctypes loader for the Rust drift_ledger core (dichotomy debt queue
batch 3, #2479 tranche). Mirrors broker_loader.py's contract:
batch 3 + #2479 dichotomy wave tranche). Mirrors broker_loader.py's contract:
FFI contract (doc-envelope law):
* dl_sha({text}) -> hex
* dl_diff({ref, cur}) -> summary string
* dl_check({label, prev_size, cur_size, anchor_size?, anchor_sha?, cur_sha?}) -> {alert, pct, limit}
* dl_auto_fix({label, anchored, has_last_good}) -> {action, reason}
* dl_ledger_path({index_file}) -> path string
* dl_snapshot_entry({blocks: {label: content}, when}) -> snapshot entry
* dl_anchor_record({content, reason, when}) -> anchor record
* dl_check_alerts({ledger, blocks: {label: content}}) -> [alert, ...]
* dl_fix_decision({ledger, label, cur_sha?}) -> {fixed, reason}
Override the .so with DL_RUST_LIB (a FILE path).
"""
@@ -39,7 +44,9 @@ def _load() -> ctypes.CDLL:
if not _LIB_PATH.is_file():
raise DriftLedgerUnavailable(str(_LIB_PATH))
lib = ctypes.CDLL(str(_LIB_PATH))
for sym in ("dl_sha", "dl_diff", "dl_check", "dl_auto_fix"):
for sym in ("dl_sha", "dl_diff", "dl_check", "dl_auto_fix", "dl_ledger_path",
"dl_snapshot_entry", "dl_anchor_record", "dl_check_alerts",
"dl_fix_decision"):
fn = getattr(lib, sym)
fn.argtypes = [ctypes.c_void_p]
fn.restype = ctypes.c_void_p
@@ -75,3 +82,37 @@ def sha(text: str) -> str:
def diff_blocks(ref: str, cur: str) -> str:
return _call("dl_diff", {"ref": ref, "cur": cur})
def check_block(label: str, prev_size: int, cur_size: int, anchor_size=None,
anchor_sha=None, cur_sha=None) -> dict:
return _call("dl_check", {"label": label, "prev_size": prev_size,
"cur_size": cur_size, "anchor_size": anchor_size,
"anchor_sha": anchor_sha, "cur_sha": cur_sha})
def auto_fix_decision(label: str, anchored: bool, has_last_good: bool) -> dict:
return _call("dl_auto_fix", {"label": label, "anchored": anchored,
"has_last_good": has_last_good})
def ledger_path_for(index_file: str) -> str:
return _call("dl_ledger_path", {"index_file": index_file})
def snapshot_entry(blocks: dict, when: str) -> dict:
return _call("dl_snapshot_entry", {"blocks": blocks, "when": when})
def anchor_record(content: str, reason: str, when: str) -> dict:
return _call("dl_anchor_record", {"content": content, "reason": reason,
"when": when})
def check_alerts(ledger: dict, blocks: dict) -> list:
return _call("dl_check_alerts", {"ledger": ledger, "blocks": blocks})
def fix_decision(ledger: dict, label: str, cur_sha=None) -> dict:
return _call("dl_fix_decision", {"ledger": ledger, "label": label,
"cur_sha": cur_sha})
+135 -58
View File
@@ -48,6 +48,13 @@ DB = memory_env.store_db()
def ledger_path():
# The ledger lives beside the index, so overriding INDEX_FILE (as the
# curator gate and the canary do for isolation) moves the ledger too.
if _rust_core():
try:
from rune import dl_loader
if dl_loader.available():
return dl_loader.ledger_path_for(INDEX_FILE)
except Exception:
pass # fallback below
return os.path.join(os.path.dirname(INDEX_FILE), "drift_ledger.json")
@@ -63,9 +70,15 @@ CONSTITUTION_LIMIT = 0.05 # 5% — anything more than a directive entry is drif
BLOCKS = ("constitution", "persona", "human", "operating", "project")
def _rust_core():
"""RUNE_DL_CORE pin law: 'python' forces the oracle path, anything else
(default 'auto') serves the Rust core when the cdylib is built."""
return os.environ.get("RUNE_DL_CORE", "auto") != "python"
def _sha(text):
# Dichotomy batch 3: Rust core first (RUNE_DL_CORE=python pins fallback).
if os.environ.get("RUNE_DL_CORE", "auto") != "python":
if _rust_core():
try:
from rune import dl_loader
if dl_loader.available():
@@ -155,51 +168,54 @@ def surface_alert(msg, label, changed=""):
logger.warning("Failed to write alert file: %s", e)
def snapshot():
ledger = load_ledger()
now = datetime.now(timezone.utc).isoformat(timespec="seconds")
def _snapshot_entry(blocks, now):
"""Dichotomy wave: Rust core first (RUNE_DL_CORE=python pins fallback)."""
if _rust_core():
try:
from rune import dl_loader
if dl_loader.available():
return dl_loader.snapshot_entry(blocks, now)
except Exception:
pass # fallback below
entry = {"when": now, "blocks": {}}
for label in BLOCKS:
raw = read_block(label)
raw = blocks[label]
entry["blocks"][label] = {
"sha": _sha(raw),
"size": len(raw),
"content": raw, # last-good content backup for diffing + retro-fix
}
ledger["last_snapshot"] = entry
save_ledger(ledger)
return entry
def anchor(label, reason):
"""Record that the current state of `label` is authorised (a user directive,
a confirmed review, a 3-sighting promotion). Subsequent drift is measured
from here — this is the re-anchor after legitimate change."""
ledger = load_ledger()
raw = read_block(label)
ledger.setdefault("anchors", {})[label] = {
def _anchor_record(raw, reason, now):
if _rust_core():
try:
from rune import dl_loader
if dl_loader.available():
return dl_loader.anchor_record(raw, reason, now)
except Exception:
pass # fallback below
return {
"sha": _sha(raw),
"size": len(raw),
"content": raw, # authorised content backup for root-cause diffing
"reason": reason,
"when": datetime.now(timezone.utc).isoformat(timespec="seconds"),
"when": now,
}
save_ledger(ledger)
return f"anchored {label}: {reason}"
def check(strict=False, autofix=True):
ledger = load_ledger()
snap = ledger.get("last_snapshot")
if not snap:
# No baseline yet — record one and treat as healthy (first contact).
snapshot()
print("no prior snapshot; baseline recorded")
return 0
def _check_alerts(ledger, now_blocks):
"""The check() alert loop: Rust core first, oracle loop as fallback."""
if _rust_core():
try:
from rune import dl_loader
if dl_loader.available():
return dl_loader.check_alerts(ledger, now_blocks)
except Exception:
pass # fallback below
alerts = []
now_blocks = {label: read_block(label) for label in BLOCKS}
prev = snap.get("blocks", {})
prev = (ledger.get("last_snapshot") or {}).get("blocks", {})
for label in BLOCKS:
cur = now_blocks[label]
cur_size = len(cur)
@@ -245,6 +261,41 @@ def check(strict=False, autofix=True):
"content_changed": content_changed,
}
)
return alerts
def snapshot():
ledger = load_ledger()
now = datetime.now(timezone.utc).isoformat(timespec="seconds")
entry = _snapshot_entry({label: read_block(label) for label in BLOCKS}, now)
ledger["last_snapshot"] = entry
save_ledger(ledger)
return entry
def anchor(label, reason):
"""Record that the current state of `label` is authorised (a user directive,
a confirmed review, a 3-sighting promotion). Subsequent drift is measured
from here — this is the re-anchor after legitimate change."""
ledger = load_ledger()
raw = read_block(label)
ledger.setdefault("anchors", {})[label] = _anchor_record(
raw, reason, datetime.now(timezone.utc).isoformat(timespec="seconds")
)
save_ledger(ledger)
return f"anchored {label}: {reason}"
def check(strict=False, autofix=True):
ledger = load_ledger()
snap = ledger.get("last_snapshot")
if not snap:
# No baseline yet — record one and treat as healthy (first contact).
snapshot()
print("no prior snapshot; baseline recorded")
return 0
alerts = _check_alerts(ledger, {label: read_block(label) for label in BLOCKS})
if alerts:
for a in alerts:
msg = (
@@ -287,27 +338,23 @@ def auto_fix(label, ledger, strict):
Returns {action, reason}.
"""
if label == "constitution":
return {"action": "alert-only", "reason": "constitution changes only by explicit directive; needs human review"}
if label in ledger.get("anchors", {}):
block_meta = ledger.get("last_snapshot", {}).get("blocks", {}).get(label)
anchored = label in ledger.get("anchors", {})
has_last_good = bool(block_meta and "content" in block_meta)
result = _auto_fix_decision(label, anchored, has_last_good)
if result["action"] == "re-anchored":
# Authorised growth: re-anchor to the current (approved) state.
raw = read_block(label)
ledger.setdefault("anchors", {})[label] = {
"sha": _sha(raw),
"size": len(raw),
"content": raw,
"reason": "auto re-anchor after authorised growth",
"when": datetime.now(timezone.utc).isoformat(timespec="seconds"),
}
ledger.setdefault("anchors", {})[label] = _anchor_record(
raw, "auto re-anchor after authorised growth",
datetime.now(timezone.utc).isoformat(timespec="seconds"),
)
save_ledger(ledger)
return {"action": "re-anchored", "reason": "anchored block grew (authorised) — re-baselined"}
# Unauthorised drift: restore to last-good content (new schema — restore
# the store's always-on core entries for this topic).
block_meta = ledger.get("last_snapshot", {}).get("blocks", {}).get(label)
if block_meta and "content" in block_meta:
elif result["action"] == "restored":
# Unauthorised drift: restore to last-good content (new schema —
# restore the store's always-on core entries for this topic).
restore_topic(label, block_meta["content"])
return {"action": "restored", "reason": "unauthorised drift — restored to last-good content"}
return {"action": "alert-only", "reason": "no last-good content available to restore"}
return result
def diff_blocks(ref, cur):
@@ -379,6 +426,44 @@ def restore_topic(label, content):
logger.error("restore_topic('%s') failed: %s", label, e)
def _auto_fix_decision(label, anchored, has_last_good):
"""The auto-fix ROUTING decision (IO execution stays in auto_fix)."""
if _rust_core():
try:
from rune import dl_loader
if dl_loader.available():
return dl_loader.auto_fix_decision(label, anchored, has_last_good)
except Exception:
pass # fallback below
if label == "constitution":
return {"action": "alert-only", "reason": "constitution changes only by explicit directive; needs human review"}
if anchored:
return {"action": "re-anchored", "reason": "anchored block grew (authorised) — re-baselined"}
if has_last_good:
return {"action": "restored", "reason": "unauthorised drift — restored to last-good content"}
return {"action": "alert-only", "reason": "no last-good content available to restore"}
def _fix_decision(ledger, label, cur_sha):
"""The retroactive-fix gate decision (restore IO stays in fix)."""
if _rust_core():
try:
from rune import dl_loader
if dl_loader.available():
return dl_loader.fix_decision(ledger, label, cur_sha)
except Exception:
pass # fallback below
snap = ledger.get("last_snapshot", {})
block_meta = snap.get("blocks", {}).get(label)
if not block_meta or "content" not in block_meta:
return {"fixed": False, "reason": f"no last-good content for {label}"}
if label in ledger.get("anchors", {}):
return {"fixed": False, "reason": f"{label} is anchored (authorised); use anchor to re-baseline instead of fix"}
if cur_sha == block_meta.get("sha"):
return {"fixed": False, "reason": f"{label} already matches last-good"}
return {"fixed": True, "reason": f"{label} restored to last-good state"}
def fix(label):
"""RETROACTIVE FIX (#B): restore a drifted block to its last-good content.
@@ -390,20 +475,12 @@ def fix(label):
ledger = load_ledger()
snap = ledger.get("last_snapshot", {})
block_meta = snap.get("blocks", {}).get(label)
if not block_meta or "content" not in block_meta:
return {"fixed": False, "reason": f"no last-good content for {label}"}
# Never silently revert an anchored block — it was authorised.
if label in ledger.get("anchors", {}):
return {"fixed": False, "reason": f"{label} is anchored (authorised); use anchor to re-baseline instead of fix"}
good = block_meta["content"]
cur = read_block(label)
if _sha(cur) == block_meta["sha"]:
return {"fixed": False, "reason": f"{label} already matches last-good"}
# New schema: restore the store's always-on entries for the topic (the old
# markdown block files are retired).
restore_topic(label, good)
journal(f"{label} restored to last-good content (retroactive drift fix)")
return {"fixed": True, "reason": f"{label} restored to last-good state"}
result = _fix_decision(ledger, label, _sha(cur))
if result.get("fixed"):
restore_topic(label, block_meta["content"])
journal(f"{label} restored to last-good content (retroactive drift fix)")
return result
def handle_hook(event, data=None):
+89
View File
@@ -0,0 +1,89 @@
#!/usr/bin/env python3
"""ctypes loader for the Rust experience_encoding core (dichotomy wave
#2479 tranche). Mirrors broker_loader.py/le_loader.py's contract:
FFI contract (doc-envelope law — every call returns {"ok", "value"|"err"}):
* ee_evaluate({event, existing_episodes, corpus_texts, lexicon_available})
-> {verdict, reason, provenance}
`lexicon_available` pins the oracle's flat `import lexicon_evolution`
environment seam (ImportError → conservative evidence admission); the
facade derives it from the live import and the frozen truth replays it.
Free: capability_scoring's cs_free_string (shared process). Override the
.so with EE_RUST_LIB (a FILE path).
"""
from __future__ import annotations
import ctypes
import json
import os
from pathlib import Path
_REPO = Path(__file__).resolve().parents[2]
_CRATE_DIR = _REPO / "src" / "rust" / "experience_encoding"
LIB_PATH = Path(
os.environ.get("EE_RUST_LIB",
_CRATE_DIR / "target" / "release" / "libexperience_encoding.so")
)
_lib = None
class ExperienceCoreUnavailable(FileNotFoundError):
"""The Rust experience_encoding core is not built. Callers fall back
to Python."""
def available() -> bool:
return LIB_PATH.is_file()
def _load() -> ctypes.CDLL:
global _lib
if _lib is not None:
return _lib
if not LIB_PATH.is_file():
raise ExperienceCoreUnavailable(str(LIB_PATH))
lib = ctypes.CDLL(str(LIB_PATH))
getattr(lib, "ee_evaluate").argtypes = [ctypes.c_void_p]
getattr(lib, "ee_evaluate").restype = ctypes.c_void_p
# free comes from capability_scoring (same process) if loaded; else our own malloc path
try:
lib.cs_free_string
except AttributeError:
scoring = ctypes.CDLL(str(
_REPO / "src" / "rust" / "capability_scoring" / "target" / "release" / "libcapability_scoring.so"
))
scoring.cs_free_string.argtypes = [ctypes.c_void_p]
lib._free = scoring.cs_free_string
_lib = lib
return lib
def _call(sym: str, payload: dict):
lib = _load()
data = json.dumps(payload).encode()
buf = ctypes.create_string_buffer(data)
ptr = getattr(lib, sym)(ctypes.cast(buf, ctypes.c_void_p))
if not ptr:
raise RuntimeError(f"{sym} returned null")
out = ctypes.string_at(ptr).decode()
free = getattr(lib, "_free", None)
if free is not None:
free(ptr)
doc = json.loads(out)
if not doc.get("ok"):
raise RuntimeError(doc.get("err", f"{sym} failed"))
return doc["value"]
def evaluate(event, existing_episodes=None, corpus_texts=(),
lexicon_available=True) -> dict:
"""Rust core evaluate: returns {verdict, reason, provenance}."""
return _call("ee_evaluate", {
"event": event,
"existing_episodes": list(existing_episodes or []),
"corpus_texts": list(corpus_texts or ()),
"lexicon_available": lexicon_available,
})
+43
View File
@@ -13,14 +13,57 @@ becomes an episode. Event → evaluate → classify → attach provenance:
Admission uses the lexicon discipline (distinctive terms, not common
words) + grounding (traces to real material) — the same meta-judgement
as retrieval and evolution, so growth and admission speak one lexicon.
Dichotomy wave #2479: the admission decision runs through the FULL Rust
core (src/rust/experience_encoding) when built; RUNE_EE_CORE=python pins
the Python fallback — parity harnesses. The _py_ variant is the
load-bearing fallback + parity oracle; the ctypes seam is rune.ee_loader.
The oracle's flat `import lexicon_evolution` is an environment seam: when
the module is missing, the core is bypassed and the conservative evidence
fallback applies exactly as the oracle wrote it.
"""
from __future__ import annotations
import os
def _ee_core():
"""Dichotomy wave #2479: Rust core first (RUNE_EE_CORE=python pins the
Python fallback — parity harnesses). The kernel owns the entire
admission decision (empty gate, duplicate scan, lexical signal,
grounding, classification, provenance)."""
if os.environ.get("RUNE_EE_CORE", "auto") == "python":
return None
try:
from rune import ee_loader
except ImportError:
try:
import ee_loader
except ImportError:
return None
try:
return ee_loader if ee_loader.available() else None
except Exception:
return None
def evaluate(event: dict, existing_episodes: list | None = None,
corpus_texts: tuple = ()) -> dict:
"""Evaluate one event. Returns {verdict, reason, provenance}."""
core = _ee_core()
if core is not None:
try:
import lexicon_evolution # noqa: F401 (the oracle's seam)
except ImportError:
return _py_evaluate(event, existing_episodes, corpus_texts)
return core.evaluate(event, existing_episodes, corpus_texts)
return _py_evaluate(event, existing_episodes, corpus_texts)
def _py_evaluate(event: dict, existing_episodes: list | None = None,
corpus_texts: tuple = ()) -> dict:
"""Evaluate one event. Returns {verdict, reason, provenance}."""
existing_episodes = existing_episodes or []
text = (event.get("text") or "").strip()
source = event.get("source", "unknown")
+94
View File
@@ -0,0 +1,94 @@
#!/usr/bin/env python3
"""ctypes loader for the Rust mutation_guards core (dichotomy wave
#2479 tranche). Mirrors ee_loader.py/broker_loader.py's contract:
FFI contract (doc-envelope law — every call returns {"ok", "value"|"err"}):
* mu_classify({target_kind, operation, scope, attributed, backed_up})
-> {verdict, reason, recovery}
* mu_guard({mutation}) -> {verdict, reason, recovery}
Free: capability_scoring's cs_free_string (shared process). Override the
.so with MU_RUST_LIB (a FILE path).
"""
from __future__ import annotations
import ctypes
import json
import os
from pathlib import Path
_REPO = Path(__file__).resolve().parents[2]
_CRATE_DIR = _REPO / "src" / "rust" / "mutation_guards"
LIB_PATH = Path(
os.environ.get("MU_RUST_LIB",
_CRATE_DIR / "target" / "release" / "libmutation_guards.so")
)
_lib = None
class MutationGuardsCoreUnavailable(FileNotFoundError):
"""The Rust mutation_guards core is not built. Callers fall back
to Python."""
def available() -> bool:
return LIB_PATH.is_file()
def _load() -> ctypes.CDLL:
global _lib
if _lib is not None:
return _lib
if not LIB_PATH.is_file():
raise MutationGuardsCoreUnavailable(str(LIB_PATH))
lib = ctypes.CDLL(str(LIB_PATH))
for name in ("mu_classify", "mu_guard"):
fn = getattr(lib, name)
fn.argtypes = [ctypes.c_void_p]
fn.restype = ctypes.c_void_p
# free comes from capability_scoring (same process) if loaded; else our own malloc path
try:
lib.cs_free_string
except AttributeError:
scoring = ctypes.CDLL(str(
_REPO / "src" / "rust" / "capability_scoring" / "target" / "release" / "libcapability_scoring.so"
))
scoring.cs_free_string.argtypes = [ctypes.c_void_p]
lib._free = scoring.cs_free_string
_lib = lib
return lib
def _call(sym: str, payload: dict):
lib = _load()
data = json.dumps(payload).encode()
buf = ctypes.create_string_buffer(data)
ptr = getattr(lib, sym)(ctypes.cast(buf, ctypes.c_void_p))
if not ptr:
raise RuntimeError(f"{sym} returned null")
out = ctypes.string_at(ptr).decode()
free = getattr(lib, "_free", None)
if free is not None:
free(ptr)
doc = json.loads(out)
if not doc.get("ok"):
raise RuntimeError(doc.get("err", f"{sym} failed"))
return doc["value"]
def classify(target_kind, operation, scope="single",
attributed=True, backed_up=False) -> dict:
"""Rust core classify_mutation: returns {verdict, reason, recovery}."""
return _call("mu_classify", {
"target_kind": target_kind,
"operation": operation,
"scope": scope,
"attributed": attributed,
"backed_up": backed_up,
})
def guard(mutation: dict) -> dict:
"""Rust core guard_mutation: returns {verdict, reason, recovery}."""
return _call("mu_guard", {"mutation": mutation})
+69 -20
View File
@@ -17,34 +17,62 @@ Classification (deterministic, no model):
Recoverability: a CONFIRM/DENY verdict carries the recovery shape
(snapshot id required, or the reason none exists).
Dichotomy wave #2479: the classification runs through the FULL Rust core
(src/rust/mutation_guards) when built; RUNE_MG_CORE=python pins the
Python fallback — parity harnesses. The _py_ variants are the
load-bearing fallback + parity oracle; the ctypes seam is rune.mu_loader.
"""
from __future__ import annotations
# Target-kind severity (higher = more protected)
KIND_SEVERITY = {
"constitution": 3,
"identity": 3,
"persona": 3,
"skill": 2,
"belief": 2,
"operating": 1,
"fact": 1,
"archive": 0,
}
import os
# Operation severity
OP_SEVERITY = {
"delete": 3,
"overwrite": 2,
"append": 1,
"read": 0,
}
def _mg_core():
"""Dichotomy wave #2479: Rust core first (RUNE_MG_CORE=python pins the
Python fallback — parity harnesses). The kernel owns the entire
classification (severity tables, branch order, verdict + reason +
recovery shape)."""
if os.environ.get("RUNE_MG_CORE", "auto") == "python":
return None
try:
from rune import mu_loader
except ImportError:
try:
import mu_loader
except ImportError:
return None
try:
return mu_loader if mu_loader.available() else None
except Exception:
return None
def classify_mutation(target_kind: str, operation: str, scope: str = "single",
attributed: bool = True, backed_up: bool = False) -> dict:
"""Classify a proposed mutation. Returns {verdict, reason, recovery}."""
core = _mg_core()
if core is not None:
return core.classify(target_kind, operation, scope, attributed,
backed_up)
return _py_classify_mutation(target_kind, operation, scope, attributed,
backed_up)
def guard_mutation(mutation: dict) -> dict:
"""Guard one mutation dict {target_kind, operation, scope, attributed,
backed_up}. Returns the verdict dict (ALLOW/CONFIRM/DENY + reason)."""
core = _mg_core()
if core is not None:
return core.guard(mutation)
return _py_guard_mutation(mutation)
def _py_classify_mutation(target_kind: str, operation: str,
scope: str = "single", attributed: bool = True,
backed_up: bool = False) -> dict:
"""Classify a proposed mutation. Returns {verdict, reason, recovery}."""
kind_sev = KIND_SEVERITY.get(target_kind, 1)
op_sev = OP_SEVERITY.get(operation, 1)
scope_sev = {"whole": 2, "multi": 1, "single": 0}.get(scope, 0)
@@ -82,13 +110,34 @@ def classify_mutation(target_kind: str, operation: str, scope: str = "single",
}
def guard_mutation(mutation: dict) -> dict:
def _py_guard_mutation(mutation: dict) -> dict:
"""Guard one mutation dict {target_kind, operation, scope, attributed,
backed_up}. Returns the verdict dict (ALLOW/CONFIRM/DENY + reason)."""
return classify_mutation(
return _py_classify_mutation(
mutation.get("target_kind", "fact"),
mutation.get("operation", "append"),
mutation.get("scope", "single"),
mutation.get("attributed", True),
mutation.get("backed_up", False),
)
# Target-kind severity (higher = more protected)
KIND_SEVERITY = {
"constitution": 3,
"identity": 3,
"persona": 3,
"skill": 2,
"belief": 2,
"operating": 1,
"fact": 1,
"archive": 0,
}
# Operation severity
OP_SEVERITY = {
"delete": 3,
"overwrite": 2,
"append": 1,
"read": 0,
}
+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.231.0
Version: v0.238.0
Features: 41 (24 original + 17 new native commands)
Usage:
@@ -2037,7 +2037,7 @@ class RuneApp(App):
# Show version info
self.chat_channel.add_message(
"system",
f"[dim]Rune v0.231.0 | Textual TUI | {len(loaded)} native modules[/dim]",
f"[dim]Rune v0.238.0 | Textual TUI | {len(loaded)} native modules[/dim]",
)
# Auto-show night mode suggestion if evening/night hours
+116
View File
@@ -0,0 +1,116 @@
#!/usr/bin/env python3
"""sl_loader.py — ctypes loader for the Rust spine_language core
(dichotomy wave #2479 tranche). Mirrors broker_loader.py:
FFI contract (doc-envelope law — every call returns {"ok", "value"|"err"}):
* sl_classification_sides({"root"}) -> measured map
* sl_place_languages({"root", "doc"}) -> {"doc", "placed"}
* sl_contract_discoveries({"root"}) -> discovery list
* sl_normalize_discovery_ids({"doc", "root"?/"discoveries"?}) -> {"doc", "renamed"}
* sl_apply_contract_discoveries({"doc", "root"?/"discoveries"?}) -> {"doc", "placed"}
`root` is the repo root the store reads hang off (the facade always sends
str(ROOT) — tests monkeypatch rune.spine_language.ROOT and it must be
honored). Free: capability_scoring's cs_free_string (shared process).
NEVER re-export a free symbol here. Override the .so with SL_RUST_LIB
(a FILE path).
"""
from __future__ import annotations
import ctypes
import json
import os
from pathlib import Path
_REPO = Path(__file__).resolve().parents[2]
_CRATE_DIR = _REPO / "src" / "rust" / "spine_language"
_LIB_PATH = Path(
os.environ.get("SL_RUST_LIB", _CRATE_DIR / "target" / "release" / "libspine_language.so")
)
_SYMBOLS = (
"sl_classification_sides",
"sl_place_languages",
"sl_contract_discoveries",
"sl_normalize_discovery_ids",
"sl_apply_contract_discoveries",
)
_lib = None
class SpineLanguageUnavailable(FileNotFoundError):
"""The Rust spine_language core is not built. Callers fall back to
Python (strangler law; RUNE_SL_CORE=python pins the fallback)."""
def available() -> bool:
return _LIB_PATH.is_file()
def _load() -> ctypes.CDLL:
global _lib
if _lib is not None:
return _lib
if not _LIB_PATH.is_file():
raise SpineLanguageUnavailable(str(_LIB_PATH))
lib = ctypes.CDLL(str(_LIB_PATH))
for sym in _SYMBOLS:
fn = getattr(lib, sym)
fn.argtypes = [ctypes.c_void_p]
fn.restype = ctypes.c_void_p # char* — never default c_int
# free comes from capability_scoring (same process); load it if not yet resident
try:
lib.cs_free_string
except AttributeError:
scoring = ctypes.CDLL(str(
_REPO / "src" / "rust" / "capability_scoring"
/ "target" / "release" / "libcapability_scoring.so"
))
scoring.cs_free_string.argtypes = [ctypes.c_void_p]
lib._free = scoring.cs_free_string
_lib = lib
return lib
def _call(sym: str, payload: dict):
lib = _load()
data = json.dumps(payload).encode()
buf = ctypes.create_string_buffer(data)
ptr = getattr(lib, sym)(ctypes.cast(buf, ctypes.c_void_p))
if not ptr:
raise RuntimeError(f"{sym} returned null")
out = ctypes.string_at(ptr).decode()
free = getattr(lib, "_free", None)
if free is not None:
free(ptr)
doc = json.loads(out)
if not doc.get("ok"):
raise RuntimeError(doc.get("err", f"{sym} failed"))
return doc["value"]
def classification_sides(root: str) -> dict:
return _call("sl_classification_sides", {"root": root})
def place_languages(doc: dict, root: str) -> dict:
return _call("sl_place_languages", {"doc": doc, "root": root})
def contract_discoveries(root: str) -> list:
return _call("sl_contract_discoveries", {"root": root})
def normalize_discovery_ids(doc: dict, root: str, discoveries: list | None = None) -> dict:
payload = {"doc": doc, "root": root}
if discoveries is not None:
payload["discoveries"] = discoveries
return _call("sl_normalize_discovery_ids", payload)
def apply_contract_discoveries(doc: dict, root: str, discoveries: list | None = None) -> dict:
payload = {"doc": doc, "root": root}
if discoveries is not None:
payload["discoveries"] = discoveries
return _call("sl_apply_contract_discoveries", payload)
+51
View File
@@ -13,6 +13,11 @@ Three transforms, applied to BOTH cores' output at build time:
Same dependency laws as spine.py: structural_store primitives only, no
I/O beyond the classification/scaffold store reads, deterministic.
Dichotomy wave #2479 tranche: the deterministic transform surface is
ported to FULL Rust (src/rust/spine_language). This facade delegates to
the Rust core via rune/sl_loader when built; the Python implementation
below is the strangler fallback, pinned with RUNE_SL_CORE=python.
"""
from __future__ import annotations
@@ -26,11 +31,31 @@ from rune.structural_store import (
ROOT = Path(__file__).resolve().parents[2]
SRC_DIR = ROOT / "src" / "rune"
def _core():
"""The Rust spine_language core when available (strangler: Python
fallback is load-bearing). RUNE_SL_CORE=python pins the fallback."""
import os
if os.environ.get("RUNE_SL_CORE", "auto") == "python":
return None
try:
from rune import sl_loader
except Exception: # noqa: BLE001 — any loader failure means fallback, never a broken call
return None
try:
return sl_loader if sl_loader.available() else None
except Exception: # noqa: BLE001 — availability probe must never crash the fallback
return None
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."""
core = _core()
if core is not None:
return core.classification_sides(str(ROOT))
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)}
@@ -44,6 +69,12 @@ def _place_languages(doc: dict) -> int:
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)."""
core = _core()
if core is not None:
result = core.place_languages(doc, str(ROOT))
doc.clear()
doc.update(result["doc"])
return result["placed"]
GO_HINTS = ("server", "transport", "dispatch", "entry", "bridge",
"hook_engine", "provider", "watch", "notif", "voice",
"gitea", "event_", "message_", "tool_exec", "work_manager")
@@ -134,6 +165,15 @@ def _normalize_discovery_ids(doc: dict) -> int:
module records produced by the earlier discovery projector; leave all
other open-world entity classes alone.
"""
core = _core()
if core is not None:
# The gather goes through the module-level seam (tests monkeypatch
# _contract_discoveries to isolate the doc from the live store).
result = core.normalize_discovery_ids(
doc, str(ROOT), discoveries=_contract_discoveries())
doc.clear()
doc.update(result["doc"])
return result["renamed"]
renamed = {}
observed = {
eid for _, entities in _contract_discoveries()
@@ -167,6 +207,13 @@ def _apply_contract_discoveries(doc: dict) -> int:
spine learns what construction actually built. Doc-level transform,
applied to BOTH cores' output. The CANDIDATE law holds: only CLOSED
contracts write back."""
core = _core()
if core is not None:
result = core.apply_contract_discoveries(
doc, str(ROOT), discoveries=_contract_discoveries())
doc.clear()
doc.update(result["doc"])
return result["placed"]
placed = 0
for cid, entities in _contract_discoveries():
for eid, kind, provenance in entities:
@@ -189,6 +236,10 @@ 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), ...])]."""
core = _core()
if core is not None:
raw = core.contract_discoveries(str(ROOT))
return [(cid, [tuple(item) for item in items]) for cid, items in raw]
try:
doc = json.loads((ROOT / "store" / "scaffold" / "current.json").read_text())
except (OSError, json.JSONDecodeError):
+14
View File
@@ -0,0 +1,14 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "abstraction_ladder"
version = "0.1.0"
dependencies = [
"capability_scoring",
]
[[package]]
name = "capability_scoring"
version = "0.1.0"
+12
View File
@@ -0,0 +1,12 @@
[package]
name = "abstraction_ladder"
version = "0.1.0"
edition = "2021"
description = "Promotion ladder kernel (episode→skill) — dichotomy wave #2479 tranche; zero-dep (pyjson via capability_scoring)"
[lib]
name = "abstraction_ladder"
crate-type = ["rlib", "cdylib", "staticlib"]
[dependencies]
capability_scoring = { path = "../capability_scoring" }
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,97 @@
//! branch_battery — replays frozen_al_truth.json VERBATIM (76 cases
//! frozen from the live Python oracle, RUNE_AL_CORE=python pinned,
//! inputs deep-copied before capture). Order-insensitive object
//! comparison; numbers compared exactly (both sides IEEE-754 f64).
use capability_scoring::pyjson::{parse, dumps, Json};
use crate::ladder;
const TRUTH: &str = include_str!("../frozen_al_truth.json");
fn jeq(a: &Json, b: &Json) -> bool {
match (a, b) {
(Json::Num(x), Json::Num(y)) => x == y,
(Json::Str(x), Json::Str(y)) => x == y,
(Json::Bool(x), Json::Bool(y)) => x == y,
(Json::Null, Json::Null) => true,
(Json::Arr(x), Json::Arr(y)) => {
x.len() == y.len() && x.iter().zip(y).all(|(i, j)| jeq(i, j))
}
(Json::Obj(x), Json::Obj(y)) => {
x.len() == y.len()
&& x.iter()
.all(|(k, v)| y.iter().any(|(k2, v2)| k == k2 && jeq(v, v2)))
}
_ => false,
}
}
fn as_arr(v: &Json) -> Vec<Json> {
match v {
Json::Arr(a) => a.clone(),
_ => vec![],
}
}
fn as_str(v: Option<&Json>) -> Option<&str> {
match v {
Some(Json::Str(s)) => Some(s.as_str()),
_ => None,
}
}
fn run_case(fn_name: &str, args: &Json) -> Result<Json, String> {
match fn_name {
"next_stage" => {
let stage = as_str(args.get("stage"));
Ok(match ladder::next_stage(stage) {
Some(nxt) => Json::Str(nxt.into()),
None => Json::Null,
})
}
"can_promote" => {
let item = args.get("item").ok_or("missing item")?;
Ok(ladder::can_promote(item))
}
"promote" => {
let item = args.get("item").ok_or("missing item")?;
Ok(ladder::promote(item))
}
other => Err(format!("unknown fn {}", other)),
}
}
#[test]
fn frozen_truth_replays_verbatim() {
let truth = parse(TRUTH).expect("frozen_al_truth.json parses");
let cases = as_arr(truth.get("cases").expect("cases key"));
assert!(cases.len() >= 76, "expected the full frozen battery, got {}", cases.len());
let mut failures: Vec<String> = vec![];
for (i, case) in cases.iter().enumerate() {
let fn_name = match case.get("fn") {
Some(Json::Str(s)) => s.as_str(),
_ => panic!("case {i} missing fn"),
};
let args = case.get("input").cloned().unwrap_or(Json::Obj(vec![]));
let expected = case.get("expected").expect("expected key");
match run_case(fn_name, &args) {
Ok(got) if jeq(&got, expected) => {}
Ok(got) => failures.push(format!(
"case {i} ({} {}): kernel {:?} != expected {}",
fn_name,
case.get("name").and_then(|v| as_str(Some(v))).unwrap_or("?"),
dumps(&got),
dumps(expected),
)),
Err(e) => failures.push(format!("case {i} ({}): error {}", fn_name, e)),
}
}
assert!(
failures.is_empty(),
"frozen-truth battery failed {}/{}:\n{}",
failures.len(),
cases.len(),
failures.join("\n"),
);
}
+279
View File
@@ -0,0 +1,279 @@
//! ladder.rs — the promotion ladder kernel, a verbatim port of
//! src/rune/abstraction_ladder.py (dichotomy wave #2479 tranche),
//! Phase B language-advantage pass.
//!
//! episode → fact → observation → concept → belief → rule → skill.
//! Each promotion needs: evidence threshold, provenance, contradiction
//! handling, prediction/behavioural consequence, outcome-based revision.
//! Schema alone does not count — the ladder moves items or it doesn't.
//!
//! Phase B shapes (vs the parity commit): Stage enum with exhaustive
//! matches everywhere a rung is decided, a typed Verdict, an
//! iterator-driven falsy/truthy gate chain, zero-copy borrows from the
//! parsed arg straight through to the envelope.
//!
//! PYTHON-MIRRORING LAWS:
//! * any stage value that is not a ladder rung (unknown string,
//! non-string, null) → STAGES.index ValueError → next_stage None →
//! can_promote says "already at the top (skill)".
//! * missing stage key defaults to "episode"; missing sightings to 0.
//! * provenance / contradictions / consequence / verified_outcome use
//! Python truthiness (empty string/list/object, 0, null, false all
//! falsy).
//! * SIGHTINGS-INT LAW: the frozen truth exercises int sightings only
//! — JSON cannot distinguish int 2 from float 2.0 and the oracle's
//! f-string renders them differently.
use capability_scoring::pyjson::Json;
/// One rung of the promotion ladder, in causal order.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Stage {
Episode,
Fact,
Observation,
Concept,
Belief,
Rule,
Skill,
}
use Stage::{Belief, Concept, Episode, Fact, Observation, Rule, Skill};
impl Stage {
/// All rungs, in ladder order (the oracle's STAGES tuple).
pub const LADDER: [Stage; 7] = [
Episode, Fact, Observation, Concept, Belief, Rule, Skill,
];
pub fn name(self) -> &'static str {
match self {
Episode => "episode",
Fact => "fact",
Observation => "observation",
Concept => "concept",
Belief => "belief",
Rule => "rule",
Skill => "skill",
}
}
/// The oracle's STAGES.index — the only way into the ladder
/// (unknown names fall off, mirroring the ValueError → None law).
pub fn parse(name: &str) -> Option<Stage> {
Self::LADDER.iter().copied().find(|s| s.name() == name)
}
/// The stage after this one (None at the top).
pub fn next(self) -> Option<Stage> {
let idx = Self::LADDER.iter().position(|s| *s == self)?;
Self::LADDER.get(idx + 1).copied()
}
/// Evidence threshold per promotion (sightings required) — the
/// oracle's THRESHOLDS table, exhaustive over the enum.
pub fn threshold(self) -> f64 {
match self {
Episode => 1.0,
Fact => 2.0,
Observation | Concept => 3.0,
Belief | Rule => 4.0,
Skill => 5.0,
}
}
/// belief+ carries an outcome-revision obligation.
fn needs_verified_outcome(self) -> bool {
matches!(self, Belief | Rule)
}
}
/// Python truthiness for JSON values (bools are ints; 0/0.0, "", [],
/// {}, null, false are all falsy).
pub fn truthy(v: &Json) -> bool {
match v {
Json::Null => false,
Json::Bool(b) => *b,
Json::Num(x) => *x != 0.0,
Json::Str(s) => !s.is_empty(),
Json::Arr(a) => !a.is_empty(),
Json::Obj(o) => !o.is_empty(),
}
}
/// Python f-string number form: integral values render without a
/// fractional part; everything else is shortest round-trip repr
/// (identical to Python for all values the truth exercises).
fn fmt_num(x: f64) -> String {
if x.fract() == 0.0 && x.is_finite() && x.abs() < 9.0e15 {
format!("{}", x as i64)
} else {
format!("{}", x)
}
}
/// The item's stage as the oracle sees it. A missing key defaults to
/// "episode"; anything that is not a ladder rung (unknown string,
/// non-string value) takes the STAGES.index ValueError path.
enum StageInput {
Rung(Stage),
Default,
OffLadder,
}
fn stage_input(item: &Json) -> StageInput {
match item.get("stage") {
None => StageInput::Default,
Some(Json::Str(s)) => Stage::parse(s).map_or(StageInput::OffLadder, StageInput::Rung),
Some(_) => StageInput::OffLadder,
}
}
/// A promotion verdict — the oracle's verdict doc, typed.
struct Verdict {
promote: bool,
to: Option<Stage>,
reason: String,
}
impl Verdict {
fn refuse(reason: impl Into<String>) -> Verdict {
Verdict { promote: false, to: None, reason: reason.into() }
}
fn grant(to: Stage) -> Verdict {
Verdict {
promote: true,
to: Some(to),
reason: format!("threshold met, promoting to {}", to.name()),
}
}
fn to_pairs(&self) -> Vec<(String, Json)> {
let mut pairs: Vec<(String, Json)> =
vec![("promote".into(), Json::Bool(self.promote))];
if let Some(to) = self.to {
pairs.push(("to".into(), Json::Str(to.name().into())));
}
pairs.push(("reason".into(), Json::Str(self.reason.clone())));
pairs
}
fn to_json(&self) -> Json {
Json::Obj(self.to_pairs())
}
}
fn verdict_top() -> Verdict {
Verdict::refuse("already at the top (skill)")
}
/// Which truthiness a promotion gate tests, mirroring the oracle's
/// `if not X` / `if X` asymmetry.
enum Gate {
Falsy,
Truthy,
}
impl Gate {
fn blocks(&self, present: bool) -> bool {
match self {
Gate::Falsy => !present,
Gate::Truthy => present,
}
}
}
/// The stage after this one (None at the top). A non-rung stage
/// mirrors the oracle's STAGES.index ValueError → None.
pub fn next_stage(stage: Option<&str>) -> Option<&'static str> {
stage.and_then(Stage::parse).and_then(Stage::next).map(Stage::name)
}
/// Can this item promote? Needs: evidence threshold met, provenance
/// present, no open contradictions, a stated consequence, and (for
/// belief+) a verified outcome.
pub fn can_promote(item: &Json) -> Json {
verdict(item).to_json()
}
fn verdict(item: &Json) -> Verdict {
let stage = match stage_input(item) {
StageInput::Rung(s) => s,
StageInput::Default => Episode,
StageInput::OffLadder => return verdict_top(),
};
let nxt = match stage.next() {
Some(n) => n,
None => return verdict_top(),
};
let sightings = match item.get("sightings") {
Some(Json::Num(x)) => *x,
_ => 0.0,
};
let thr = stage.threshold();
if sightings < thr {
return Verdict::refuse(format!(
"evidence {} < {}",
fmt_num(sightings),
fmt_num(thr)
));
}
let gates = [
("provenance", Gate::Falsy, "no provenance"),
("contradictions", Gate::Truthy, "open contradictions"),
("consequence", Gate::Falsy, "no stated consequence"),
];
if let Some(reason) = gates.iter().find_map(|(key, gate, reason)| {
let present = item.get(key).map(truthy).unwrap_or(false);
gate.blocks(present).then_some(*reason)
}) {
return Verdict::refuse(reason);
}
if stage.needs_verified_outcome()
&& !item.get("verified_outcome").map(truthy).unwrap_or(false)
{
return Verdict::refuse("belief+ requires a verified outcome");
}
Verdict::grant(nxt)
}
/// dict(item); item[k] = v — an existing key updates IN PLACE (keeping
/// its position), a new key appends (Python 3.7+ dict law).
fn set_key(item: &Json, key: &str, val: Json) -> Json {
match item {
Json::Obj(pairs) => {
let mut out: Vec<(String, Json)> = pairs
.iter()
.map(|(k, v)| (k.clone(), if k == key { val.clone() } else { v.clone() }))
.collect();
if !pairs.iter().any(|(k, _)| k == key) {
out.push((key.to_string(), val));
}
Json::Obj(out)
}
other => other.clone(),
}
}
fn with_item(mut pairs: Vec<(String, Json)>, item: Json) -> Json {
let mut out: Vec<(String, Json)> = Vec::with_capacity(pairs.len() + 1);
out.push(("item".into(), item));
out.append(&mut pairs);
Json::Obj(out)
}
/// Promote one stage (or return the refusal).
pub fn promote(item: &Json) -> Json {
let v = verdict(item);
let out_item = if v.promote {
let to = v
.to
.map(|s| Json::Str(s.name().into()))
.unwrap_or(Json::Null);
set_key(&set_key(item, "stage", to), "sightings", Json::Num(0.0))
} else {
item.clone()
};
with_item(v.to_pairs(), out_item)
}
+87
View File
@@ -0,0 +1,87 @@
//! abstraction_ladder — FFI surface for the promotion ladder kernel
//! (dichotomy wave #2479 tranche). Python facade keeps a
//! RUNE_AL_CORE=python pin.
//!
//! FFI contract (doc-envelope law — every call returns {"ok","value"|"err"}):
//! * al_next_stage({"stage"}) -> stage string or null
//! * al_can_promote({"item": {...}}) -> verdict doc
//! * al_promote({"item": {...}}) -> {"item": ..., ...verdict}
//!
//! Pure kernel: no ops, no store seams, no op callback.
//!
//! Parity oracle: frozen_al_truth.json (76 cases, frozen from the live
//! Python oracle).
pub mod ladder;
pub use capability_scoring::pyjson;
#[path = "branch_battery.rs"]
#[cfg(test)]
mod branch_battery;
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
use pyjson::Json;
fn read_arg(ptr: *const c_char) -> Result<Json, String> {
if ptr.is_null() {
return Err("null argument pointer".into());
}
let x = unsafe { CStr::from_ptr(ptr) }.to_str().map_err(|e| e.to_string())?;
pyjson::parse(x)
}
fn envelope(r: Result<Json, String>) -> *mut c_char {
let v = match r {
Ok(x) => Json::Obj(vec![
("ok".into(), Json::Bool(true)),
("value".into(), x),
]),
Err(e) => Json::Obj(vec![
("ok".into(), Json::Bool(false)),
("err".into(), Json::Str(e)),
]),
};
CString::new(pyjson::dumps(&v))
.map(|c| c.into_raw())
.unwrap_or(std::ptr::null_mut())
}
/// al_next_stage({"stage"}) — the stage after this one, null at the top.
#[no_mangle]
pub extern "C" fn al_next_stage(arg: *const c_char) -> *mut c_char {
envelope(read_arg(arg).map(|a| match a.get("stage") {
Some(Json::Str(s)) => match ladder::next_stage(Some(s.as_str())) {
Some(nxt) => Json::Str(nxt.into()),
None => Json::Null,
},
// non-string / missing stage: the oracle's STAGES.index
// ValueError collapses to None
_ => Json::Null,
}))
}
/// al_can_promote({"item"}) — the verdict doc.
#[no_mangle]
pub extern "C" fn al_can_promote(arg: *const c_char) -> *mut c_char {
envelope(read_arg(arg).and_then(|a| match a.get("item") {
Some(item) => Ok(ladder::can_promote(item)),
None => Err("missing item".to_string()),
}))
}
/// al_promote({"item"}) — {"item": mutated copy, ...verdict}.
#[no_mangle]
pub extern "C" fn al_promote(arg: *const c_char) -> *mut c_char {
envelope(read_arg(arg).and_then(|a| match a.get("item") {
Some(item) => Ok(ladder::promote(item)),
None => Err("missing item".to_string()),
}))
}
// NOTE: al_free_string is deliberately NOT exported — capability_scoring
// (linked into the same Python process) already exports cs_free_string, and
// a duplicate free symbol breaks the cdylib link. Callers free every
// returned pointer through that path.
+23
View File
@@ -10,6 +10,28 @@ dependencies = [
"serde_json",
]
[[package]]
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "hashbrown"
version = "0.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
[[package]]
name = "indexmap"
version = "2.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855"
dependencies = [
"equivalent",
"hashbrown",
]
[[package]]
name = "itoa"
version = "1.0.18"
@@ -76,6 +98,7 @@ version = "1.0.151"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
dependencies = [
"indexmap",
"itoa",
"memchr",
"serde",
+4 -1
View File
@@ -8,4 +8,7 @@ crate-type = ["rlib", "cdylib", "staticlib"]
[dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1"
# preserve_order: Python-dict insertion-order semantics — the behaviours
# map is walked in insertion order (selective invalidation output order,
# queue pre-sort order) exactly like the retired ep core.
serde_json = { version = "1", features = ["preserve_order"] }
@@ -0,0 +1,208 @@
//! behaviour_ledger branch battery — parity vs the frozen Python-oracle
//! truth (frozen_bl_truth.json: 39 cases across all nine kernel-owned
//! public functions and every branch — define minimal/full/prefixed-id/
//! duplicate/unicode/None-deps, get both id forms/miss/empty, automated
//! pass/fail/unknown/200-codepoint detail truncation/reverify-confirmed,
//! real-world user confirmation/other-confirmer/fail/unknown,
//! accept-limitation ok/custom-approver/unknown, selective invalidation
//! dep-overlap default reason/owner-hit/no-overlap preserved/custom
//! reason/reasons key miss/unconfirmed skip/mixed, retest history
//! populated/unknown/empty, outstanding mixed sort + filters, summary
//! empty/mixed integer counts). Frozen via scripts/
//! gen_behaviour_ledger_truth.py with the clock pinned (no-clock law)
//! and inputs deep-copied before the oracle ran (poisoned-truth law).
//! Embedded VERBATIM — never transcribed.
use crate::{
accept_limitation, define, get, invalidate_for_change, outstanding,
retest_history, record_auto_verification, record_real_world, summary,
BlError,
};
use serde_json::{json, Value};
const TRUTH: &str = include_str!("frozen_bl_truth.json");
fn cases(group: &str) -> Vec<Value> {
let truth: Value = serde_json::from_str(TRUTH).unwrap();
truth["cases"][group]
.as_array()
.unwrap()
.clone()
}
fn replay(group: &str, f: impl Fn(&Value) -> Value) {
for case in cases(group) {
let got = f(&case["input"]);
assert_eq!(
&got,
&case["expected"],
"[{}] case '{}' diverged",
group,
case["name"].as_str().unwrap()
);
}
}
fn ok(v: Value) -> Value {
json!({"ok": true, "value": v})
}
fn err_case(e: BlError) -> Value {
json!({"ok": false, "err": e.to_string()})
}
fn doc_of(i: &Value) -> Value {
i["doc"].clone()
}
/// A mutated-op envelope: kernel Ok → {"ok": true, "value": {doc, rec}} /
/// {doc, recs}; kernel Err → the exact-Python-message error envelope.
fn run_mut(
i: &Value,
f: impl FnOnce(&mut Value) -> Result<Value, BlError>,
) -> Value {
let mut doc = doc_of(i);
match f(&mut doc) {
Ok(rec) => ok(json!({"doc": doc, "rec": rec})),
Err(e) => err_case(e),
}
}
#[test]
fn define_parity_battery() {
replay("define", |i| {
let deps: Vec<String> = i["dependencies"]
.as_array()
.map(|a| {
a.iter()
.filter_map(Value::as_str)
.map(String::from)
.collect()
})
.unwrap_or_default();
let mut doc = doc_of(i);
match define(
&mut doc,
i["behaviour_id"].as_str().unwrap(),
i["owning_project"].as_str().unwrap(),
i["intended_behaviour"].as_str().unwrap(),
i["origin_intent"].as_str().unwrap_or(""),
i["pass_criteria"].as_str().unwrap_or(""),
i["fail_criteria"].as_str().unwrap_or(""),
i["procedure"].as_str().unwrap_or(""),
i["owner_component"].as_str().unwrap_or(""),
&deps,
i["automated_ref"].as_str().unwrap_or(""),
i["environment"].as_str().unwrap_or(""),
i["now"].as_str().unwrap(),
) {
Ok(rec) => ok(json!({"doc": doc, "rec": rec})),
Err(e) => err_case(e),
}
});
}
#[test]
fn get_parity_battery() {
replay("get", |i| ok(get(&doc_of(i), i["behaviour_id"].as_str().unwrap())));
}
#[test]
fn record_auto_verification_parity_battery() {
replay("record_auto_verification", |i| {
run_mut(i, |doc| {
record_auto_verification(
doc,
i["behaviour_id"].as_str().unwrap(),
i["passed"].as_bool().unwrap(),
i["detail"].as_str().unwrap_or(""),
i["generation"].as_str().unwrap_or(""),
i["now"].as_str().unwrap(),
)
})
});
}
#[test]
fn record_real_world_parity_battery() {
replay("record_real_world", |i| {
run_mut(i, |doc| {
record_real_world(
doc,
i["behaviour_id"].as_str().unwrap(),
i["passed"].as_bool().unwrap(),
i["detail"].as_str().unwrap_or(""),
i["generation"].as_str().unwrap_or(""),
i["confirmed_by"].as_str().unwrap_or("user"),
i["now"].as_str().unwrap(),
)
})
});
}
#[test]
fn accept_limitation_parity_battery() {
replay("accept_limitation", |i| {
run_mut(i, |doc| {
accept_limitation(
doc,
i["behaviour_id"].as_str().unwrap(),
i["note"].as_str().unwrap_or(""),
i["approved_by"].as_str().unwrap_or("user"),
i["now"].as_str().unwrap(),
)
})
});
}
#[test]
fn invalidate_for_change_parity_battery() {
replay("invalidate_for_change", |i| {
let changed: Vec<String> = i["changed_entities"]
.as_array()
.map(|a| {
a.iter()
.filter_map(Value::as_str)
.map(String::from)
.collect()
})
.unwrap_or_default();
let mut doc = doc_of(i);
match invalidate_for_change(
&mut doc,
&changed,
i["generation"].as_str().unwrap_or(""),
&i["reasons"],
i["now"].as_str().unwrap(),
) {
Ok(recs) => ok(json!({"doc": doc, "recs": recs})),
Err(e) => err_case(e),
}
});
}
#[test]
fn retest_history_parity_battery() {
replay("retest_history", |i| {
ok(retest_history(
&doc_of(i),
i["behaviour_id"].as_str().unwrap(),
))
});
}
#[test]
fn outstanding_parity_battery() {
replay("outstanding", |i| {
ok(outstanding(
&doc_of(i),
i["component"].as_str().unwrap_or(""),
i["project"].as_str().unwrap_or(""),
))
});
}
#[test]
fn summary_parity_battery() {
replay("summary", |i| ok(summary(&doc_of(i))));
}
File diff suppressed because it is too large. Load diff
+791 -62
View File
@@ -1,80 +1,809 @@
//! behaviour_ledger — behaviour definition and tracking (W6).
//! PARITY port of src/rune/behaviour_ledger.py.
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
//! behaviour_ledger — the canonical Behaviour Validation Ledger (T19
//! §19F/G/I). FULL parity port of src/rune/behaviour_ledger.py
//! (dichotomy wave, issue #2479 tranche).
//!
//! PRIMARY UNIT: the VERSIONED BEHAVIOUR / TEST SPECIFICATION. Automated
//! verification and real-world/user confirmation are DISTINCT truths
//! (rule 19F): tests green ≠ user-confirmed.
//!
//! The kernel owns the state machine — define / get / transitions with
//! generation-aware history (rule 19G) / selective invalidation (rule
//! 19I) / the outstanding queue / the summary — over the parsed ledger
//! doc. File glue (load/save of <store>/behaviour_ledger.json), the
//! state constants and the class API stay in the Python facade.
//!
//! Python semantics preserved exactly (pinned by the frozen oracle
//! truth, frozen_bl_truth.json, replayed VERBATIM by the branch
//! battery):
//! * id normalization: "behaviour:" prefix added unless present;
//! * duplicate define → Err "behaviour exists: behaviour:X";
//! * unknown behaviour → Err "unknown behaviour '<original id>'"
//! (repr'd, NOT normalized);
//! * history entries carry detail truncated to 200 CODEPOINTS;
//! * green never implies USER_CONFIRMED (rule 19W-22) — the
//! automated ceiling is AUTO_VERIFIED;
//! * USER_CONFIRMED requires passed && confirmed_by == "user"
//! (rule 19M); other confirmers land REAL_WORLD_PASSED;
//! * invalidation touches CONFIRMED_STATES whose dependencies
//! intersect the changed closure or whose owner is changed; every
//! invalidation carries a reason — default
//! "structural change touched: ['a', 'b']" (Python list repr,
//! sorted); unrelated confirmations are PRESERVED;
//! * outstanding excludes USER_CONFIRMED / ACCEPTED_LIMITATION /
//! NOT_APPLICABLE, sorts TEST_FAILED first then behaviour_id;
//! * summary counts are Python ints.
//!
//! No-clock law: `now` is always an argument — the kernel never reads
//! the wall clock.
//!
//! FFI (doc-envelope law): every symbol takes ONE JSON doc and returns
//! ONE JSON doc {"ok": true, "value": ...} / {"ok": false, "err": ...}.
//! Mutating ops return the full new doc alongside the record(s).
//! Strings are freed via capability_scoring's cs_free_string — this
//! crate exports NO free symbol of its own (duplicate exports break
//! the cdylib link when sibling crates share one process).
use serde_json::{json, Map, Value};
use std::collections::BTreeSet;
use std::ffi::c_char;
use std::fmt;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BehaviourEntry {
pub behaviour_id: String,
pub owning_project: String,
pub description: String,
pub evidence_refs: Vec<String>,
pub defined_at: String,
pub status: String,
#[cfg(test)]
mod branch_battery;
pub const LEDGER_SCHEMA_VERSION: i64 = 1;
// hardening lifecycle (rule 19G semantics)
pub const UNVALIDATED: &str = "UNVALIDATED";
pub const IMPLEMENTED: &str = "IMPLEMENTED";
pub const AUTO_VERIFIED: &str = "AUTO_VERIFIED";
pub const REAL_WORLD_PASSED: &str = "REAL_WORLD_PASSED";
pub const TEST_FAILED: &str = "TEST_FAILED";
pub const PARTIAL: &str = "PARTIAL";
pub const USER_CONFIRMED: &str = "USER_CONFIRMED";
pub const RETEST_REQUIRED: &str = "RETEST_REQUIRED";
pub const ACCEPTED_LIMITATION: &str = "ACCEPTED_LIMITATION";
pub const NOT_APPLICABLE: &str = "NOT_APPLICABLE";
pub const STATES: [&str; 10] = [
UNVALIDATED,
IMPLEMENTED,
AUTO_VERIFIED,
REAL_WORLD_PASSED,
TEST_FAILED,
PARTIAL,
USER_CONFIRMED,
RETEST_REQUIRED,
ACCEPTED_LIMITATION,
NOT_APPLICABLE,
];
// ── typed domain lattice (Phase B) ───────────────────────────────────────────
/// The hardening lifecycle as a closed Rust lattice. Ledger docs carry
/// free-text states (JSON), so the lattice is open at the tail: `Other`
/// keeps unknown values pass-through exactly like the oracle's string
/// comparisons — consulted nowhere, matched exhaustively everywhere.
#[derive(Clone, PartialEq, Eq, Debug)]
enum BlState {
Unvalidated,
Implemented,
AutoVerified,
RealWorldPassed,
TestFailed,
Partial,
UserConfirmed,
RetestRequired,
AcceptedLimitation,
NotApplicable,
/// Any non-lattice state from the doc — carries the raw string so
/// the transition error message stays byte-exact.
Other(String),
}
pub struct BehaviourLedger {
pub behaviours: BTreeMap<String, BehaviourEntry>,
}
impl BehaviourLedger {
pub fn new() -> Self {
BehaviourLedger {
behaviours: BTreeMap::new(),
impl BlState {
fn parse(s: &str) -> BlState {
match s {
UNVALIDATED => BlState::Unvalidated,
IMPLEMENTED => BlState::Implemented,
AUTO_VERIFIED => BlState::AutoVerified,
REAL_WORLD_PASSED => BlState::RealWorldPassed,
TEST_FAILED => BlState::TestFailed,
PARTIAL => BlState::Partial,
USER_CONFIRMED => BlState::UserConfirmed,
RETEST_REQUIRED => BlState::RetestRequired,
ACCEPTED_LIMITATION => BlState::AcceptedLimitation,
NOT_APPLICABLE => BlState::NotApplicable,
other => BlState::Other(other.to_string()),
}
}
pub fn define(
&mut self,
behaviour_id: &str,
owning_project: &str,
description: &str,
evidence_refs: Vec<String>,
) -> BehaviourEntry {
let entry = BehaviourEntry {
behaviour_id: behaviour_id.to_string(),
owning_project: owning_project.to_string(),
description: description.to_string(),
evidence_refs,
defined_at: now(),
status: "defined".to_string(),
/// The wire label. `Other` never labels a kernel transition — the
/// empty string keeps the match total without inventing a state.
fn label(&self) -> &'static str {
match self {
BlState::Unvalidated => UNVALIDATED,
BlState::Implemented => IMPLEMENTED,
BlState::AutoVerified => AUTO_VERIFIED,
BlState::RealWorldPassed => REAL_WORLD_PASSED,
BlState::TestFailed => TEST_FAILED,
BlState::Partial => PARTIAL,
BlState::UserConfirmed => USER_CONFIRMED,
BlState::RetestRequired => RETEST_REQUIRED,
BlState::AcceptedLimitation => ACCEPTED_LIMITATION,
BlState::NotApplicable => NOT_APPLICABLE,
BlState::Other(_) => "",
}
}
/// STANDING CONFIDENCE (rule 19I): selective invalidation may
/// reopen exactly these; everything else is already open or n/a.
fn is_confirmed(&self) -> bool {
matches!(
self,
BlState::AutoVerified | BlState::RealWorldPassed | BlState::UserConfirmed
)
}
/// The outstanding queue excludes confirmed / accepted / n.a.
fn is_queue_excluded(&self) -> bool {
matches!(
self,
BlState::UserConfirmed | BlState::AcceptedLimitation | BlState::NotApplicable
)
}
/// Queue sort key: TEST_FAILED first.
fn is_test_failed(&self) -> bool {
matches!(self, BlState::TestFailed)
}
}
/// Typed kernel errors — the caller-visible `err` string is byte-exact
/// Python (pinned by the battery); the enum only shapes the Rust side.
pub enum BlError {
BehaviourExists(String),
UnknownBehaviour(String),
UnknownValidationState(String),
CorruptLedger,
}
impl fmt::Display for BlError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
BlError::BehaviourExists(bid) => write!(f, "behaviour exists: {bid}"),
BlError::UnknownBehaviour(bid) => {
write!(f, "unknown behaviour {}", py_repr(bid))
}
BlError::UnknownValidationState(s) => {
write!(f, "unknown validation state {}", py_repr(s))
}
BlError::CorruptLedger => {
write!(f, "corrupt ledger: behaviours is not an object")
}
}
}
}
// ── helpers ─────────────────────────────────────────────────────────────────
/// Python repr of a string: single quotes, escaped backslash/quote.
fn py_repr(s: &str) -> String {
let mut out = String::from("'");
for c in s.chars() {
match c {
'\\' => out.push_str("\\\\"),
'\'' => out.push_str("\\'"),
other => out.push(other),
}
}
out.push('\'');
out
}
/// Python repr of a list of strings: ['a', 'b'].
fn py_list_repr(items: &[String]) -> String {
let inner: Vec<String> = items.iter().map(|s| py_repr(s)).collect();
format!("[{}]", inner.join(", "))
}
fn norm_id(behaviour_id: &str) -> String {
if behaviour_id.starts_with("behaviour:") {
behaviour_id.to_string()
} else {
format!("behaviour:{}", behaviour_id)
}
}
/// First `n` CODEPOINTS of `s` (Python `s[:200]`).
fn chars_upto(s: &str, n: usize) -> String {
s.chars().take(n).collect()
}
/// Zero-copy borrowed string field (Python `rec.get(key, "")`).
fn str_field<'a>(v: &'a Value, key: &str) -> &'a str {
v.get(key).and_then(Value::as_str).unwrap_or("")
}
/// doc["behaviours"] pairs (Err when the ledger is malformed).
fn behaviours_pairs(doc: &Value) -> Result<&Map<String, Value>, BlError> {
match doc.get("behaviours").and_then(Value::as_object) {
Some(m) => Ok(m),
None => Err(BlError::CorruptLedger),
}
}
/// Fetch a behaviour record (normalized id) from the full doc.
fn get_rec(doc: &Value, bid: &str) -> Option<Value> {
behaviours_pairs(doc).ok()?.get(bid).cloned()
}
/// doc["behaviours"][bid] = val (insert or replace).
fn set_rec(doc: &mut Value, bid: &str, val: Value) {
if let Some(bp) = doc.get_mut("behaviours").and_then(Value::as_object_mut) {
bp.insert(bid.to_string(), val);
}
}
/// Append to a record's array field (evidence / history / limitations /
/// invalidations).
fn push_to_arr(rec: &mut Value, key: &str, val: Value) {
if let Some(a) = rec.get_mut(key).and_then(Value::as_array_mut) {
a.push(val);
}
}
// ── transitions ─────────────────────────────────────────────────────────────
/// _transition — history append + state + updated_at; a non-lattice
/// state → Err (the repr'd original, byte-exact).
fn transition(
rec: &mut Value,
new_state: BlState,
kind: &str,
detail: &str,
generation: &str,
now: &str,
) -> Result<(), BlError> {
let label = match new_state {
BlState::Other(raw) => return Err(BlError::UnknownValidationState(raw)),
known => known.label(),
};
let from = str_field(rec, "state");
push_to_arr(
rec,
"history",
json!({
"at": now,
"from": from,
"to": label,
"kind": kind,
"detail": chars_upto(detail, 200),
"generation": generation,
}),
);
if let Some(obj) = rec.as_object_mut() {
obj.insert("state".to_string(), json!(label));
obj.insert("updated_at".to_string(), json!(now));
}
Ok(())
}
// ── public operations (name-preserving ports of the oracle) ─────────────────
/// define — duplicate → Err; returns the new record.
#[allow(clippy::too_many_arguments)] // pinned: parity signature — the port mirrors the oracle arg-for-arg
pub fn define(
doc: &mut Value,
behaviour_id: &str,
owning_project: &str,
intended_behaviour: &str,
origin_intent: &str,
pass_criteria: &str,
fail_criteria: &str,
procedure: &str,
owner_component: &str,
dependencies: &[String],
automated_ref: &str,
environment: &str,
now: &str,
) -> Result<Value, BlError> {
let bid = norm_id(behaviour_id);
behaviours_pairs(doc)?;
if get_rec(doc, &bid).is_some() {
return Err(BlError::BehaviourExists(bid));
}
let rec = json!({
"behaviour_id": bid,
"owning_project": owning_project,
"intended_behaviour": intended_behaviour,
"origin_intent": origin_intent,
"pass_criteria": pass_criteria,
"fail_criteria": fail_criteria,
"procedure": procedure,
"owner_component": owner_component,
"dependencies": dependencies,
"automated_ref": automated_ref,
"environment": environment,
"state": UNVALIDATED,
"evidence": [],
"history": [],
"invalidations": [],
"limitations": [],
"created_at": now,
"updated_at": now,
});
set_rec(doc, &bid, rec.clone());
Ok(rec)
}
/// get — normalized lookup; Null when absent.
pub fn get(doc: &Value, behaviour_id: &str) -> Value {
get_rec(doc, &norm_id(behaviour_id)).unwrap_or(Value::Null)
}
/// record_auto_verification — ceiling AUTO_VERIFIED (green never implies
/// USER_CONFIRMED).
pub fn record_auto_verification(
doc: &mut Value,
behaviour_id: &str,
passed: bool,
detail: &str,
generation: &str,
now: &str,
) -> Result<Value, BlError> {
let bid = norm_id(behaviour_id);
let mut rec = get_rec(doc, &bid).ok_or_else(|| {
BlError::UnknownBehaviour(behaviour_id.to_string())
})?;
push_to_arr(
&mut rec,
"evidence",
json!({
"at": now,
"kind": "automated",
"passed": passed,
"detail": chars_upto(detail, 200),
"generation": generation,
}),
);
let new_state = if passed {
BlState::AutoVerified
} else {
BlState::TestFailed
};
transition(
&mut rec,
new_state,
"automated_verification",
detail,
generation,
now,
)?;
set_rec(doc, &bid, rec.clone());
Ok(rec)
}
/// record_real_world — USER_CONFIRMED iff passed && confirmed_by == "user".
pub fn record_real_world(
doc: &mut Value,
behaviour_id: &str,
passed: bool,
detail: &str,
generation: &str,
confirmed_by: &str,
now: &str,
) -> Result<Value, BlError> {
let bid = norm_id(behaviour_id);
let mut rec = get_rec(doc, &bid).ok_or_else(|| {
BlError::UnknownBehaviour(behaviour_id.to_string())
})?;
push_to_arr(
&mut rec,
"evidence",
json!({
"at": now,
"kind": "real_world",
"passed": passed,
"detail": chars_upto(detail, 200),
"generation": generation,
"by": confirmed_by,
}),
);
let new_state = match (passed, confirmed_by == "user") {
(true, true) => BlState::UserConfirmed,
(true, false) => BlState::RealWorldPassed,
(false, _) => BlState::TestFailed,
};
transition(&mut rec, new_state, "real_world", detail, generation, now)?;
set_rec(doc, &bid, rec.clone());
Ok(rec)
}
/// accept_limitation — explicitly approved gap.
pub fn accept_limitation(
doc: &mut Value,
behaviour_id: &str,
note: &str,
approved_by: &str,
now: &str,
) -> Result<Value, BlError> {
let bid = norm_id(behaviour_id);
let mut rec = get_rec(doc, &bid).ok_or_else(|| {
BlError::UnknownBehaviour(behaviour_id.to_string())
})?;
push_to_arr(
&mut rec,
"limitations",
json!({
"at": now,
"note": chars_upto(note, 200),
"approved_by": approved_by,
}),
);
transition(
&mut rec,
BlState::AcceptedLimitation,
"accept_limitation",
note,
"",
now,
)?;
set_rec(doc, &bid, rec.clone());
Ok(rec)
}
/// invalidate_for_change — selective invalidation (rule 19I); returns the
/// invalidated records. Only CONFIRMED_STATES whose dependency set
/// intersects the changed closure (or whose owner is changed) reopen;
/// every invalidation carries a reason; unrelated confirmations stay.
pub fn invalidate_for_change(
doc: &mut Value,
changed: &[String],
generation: &str,
reasons: &Value,
now: &str,
) -> Result<Value, BlError> {
let changed_set: BTreeSet<String> = changed.iter().cloned().collect();
let bids: Vec<String> = behaviours_pairs(doc)?.keys().cloned().collect();
let mut invalidated: Vec<Value> = Vec::new();
for bid in bids {
let rec = match get_rec(doc, &bid) {
Some(r) => r,
None => continue,
};
self.behaviours
.insert(behaviour_id.to_string(), entry.clone());
entry
if !BlState::parse(str_field(&rec, "state")).is_confirmed() {
continue;
}
let deps: BTreeSet<String> = rec
.get("dependencies")
.and_then(Value::as_array)
.map(|a| {
a.iter()
.filter_map(Value::as_str)
.map(String::from)
.collect()
})
.unwrap_or_default();
let owner = str_field(&rec, "owner_component");
let mut overlap: BTreeSet<String> = deps.intersection(&changed_set).cloned().collect();
if !owner.is_empty() && changed_set.contains(owner) {
overlap.insert(owner.to_string());
}
if overlap.is_empty() {
continue; // unrelated hardened behaviour: PRESERVED (19S-5)
}
let overlap_sorted: Vec<String> = overlap.into_iter().collect();
let bid_val = str_field(&rec, "behaviour_id").to_string();
let because = match reasons.get(&bid_val).and_then(Value::as_str) {
Some(r) => r.to_string(),
None => format!(
"structural change touched: {}",
py_list_repr(&overlap_sorted)
),
};
let was = str_field(&rec, "state").to_string();
let mut rec2 = rec.clone();
push_to_arr(
&mut rec2,
"invalidations",
json!({
"at": now,
"generation": generation,
"because": chars_upto(&because, 200),
"was": was,
"changed_entities": overlap_sorted,
}),
);
let detail = format!("was {}; {}", was, because);
transition(
&mut rec2,
BlState::RetestRequired,
"selective_invalidation",
&detail,
generation,
now,
)?;
set_rec(doc, &bid, rec2.clone());
invalidated.push(rec2);
}
Ok(Value::Array(invalidated))
}
pub fn get(&self, behaviour_id: &str) -> Option<&BehaviourEntry> {
self.behaviours.get(behaviour_id)
/// retest_history — all events oldest-first for one behaviour (rule
/// 19S-12); empty for unknown.
pub fn retest_history(doc: &Value, behaviour_id: &str) -> Value {
let rec = get(doc, behaviour_id);
let events: Vec<Value> = rec
.get("history")
.and_then(Value::as_array)
.map(|hist| {
hist.iter()
.map(|h| {
json!({
"at": str_field(h, "at"),
"generation": str_field(h, "generation"),
"event": format!(
"{}→{}",
str_field(h, "from"),
str_field(h, "to")
),
"kind": str_field(h, "kind"),
"detail": str_field(h, "detail"),
})
})
.collect()
})
.unwrap_or_default();
Value::Array(events)
}
/// outstanding — eligible testing items, TEST_FAILED first then id;
/// confirmed / accepted / n.a. excluded. The queue re-derives from
/// states after every fix (rule 19M).
pub fn outstanding(doc: &Value, component: &str, project: &str) -> Value {
let mut out: Vec<Value> = match behaviours_pairs(doc) {
Ok(pairs) => pairs
.values()
.filter(|rec| {
!BlState::parse(str_field(rec, "state")).is_queue_excluded()
})
.filter(|rec| project.is_empty()
|| str_field(rec, "owning_project") == project)
.filter(|rec| component.is_empty()
|| str_field(rec, "owner_component") == component)
.cloned()
.collect(),
Err(_) => Vec::new(),
};
out.sort_by(|a, b| {
let ka = u8::from(!BlState::parse(str_field(a, "state")).is_test_failed());
let kb = u8::from(!BlState::parse(str_field(b, "state")).is_test_failed());
(ka, str_field(a, "behaviour_id")).cmp(&(kb, str_field(b, "behaviour_id")))
});
Value::Array(out)
}
/// summary — {total, states: {state: count}, outstanding} with Python
/// int counts.
pub fn summary(doc: &Value) -> Value {
let mut counts: Vec<(String, i64)> = Vec::new();
let mut total: i64 = 0;
if let Ok(pairs) = behaviours_pairs(doc) {
for rec in pairs.values() {
let state = str_field(rec, "state").to_string();
total += 1;
match counts.iter_mut().find(|(k, _)| *k == state) {
Some((_, n)) => *n += 1,
None => counts.push((state, 1)),
}
}
}
let states: Map<String, Value> = counts
.into_iter()
.map(|(k, n)| (k, json!(n)))
.collect();
let out_len = outstanding(doc, "", "")
.as_array()
.map(Vec::len)
.unwrap_or(0);
json!({
"total": total,
"states": states,
"outstanding": out_len,
})
}
// ── FFI (doc-envelope law) ───────────────────────────────────────────────────
/// Typed FFI failure lattice behind the doc-envelope Result — the
/// caller-visible `err` string is unchanged (enum only shapes the Rust
/// side; parity is pinned by the battery).
enum FfiError {
NullPtr,
Utf8(std::str::Utf8Error),
Json(serde_json::Error),
/// Kernel-domain error with the exact Python message.
Domain(BlError),
}
impl fmt::Display for FfiError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
FfiError::NullPtr => write!(f, "null argument pointer"),
FfiError::Utf8(e) => write!(f, "{e}"),
FfiError::Json(e) => write!(f, "{e}"),
FfiError::Domain(e) => write!(f, "{e}"),
}
}
}
impl Default for BehaviourLedger {
fn default() -> Self {
Self::new()
fn read_arg(ptr: *const c_char) -> Result<Value, FfiError> {
if ptr.is_null() {
return Err(FfiError::NullPtr);
}
let x = unsafe { std::ffi::CStr::from_ptr(ptr) }
.to_str()
.map_err(FfiError::Utf8)?;
serde_json::from_str(x).map_err(FfiError::Json)
}
fn now() -> String {
format!(
"{}",
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs()
)
fn envelope(r: Result<Value, FfiError>) -> *mut c_char {
let v = match r {
Ok(x) => json!({"ok": true, "value": x}),
Err(e) => json!({"ok": false, "err": e.to_string()}),
};
std::ffi::CString::new(serde_json::to_string(&v).unwrap_or_default())
.map(|c| c.into_raw())
.unwrap_or(std::ptr::null_mut())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_define_and_get() {
let mut ledger = BehaviourLedger::new();
ledger.define("b1", "rune", "test behaviour", vec!["evidence1".into()]);
let b = ledger.get("b1");
assert!(b.is_some());
assert_eq!(b.unwrap().owning_project, "rune");
}
fn s<'a>(a: &'a Value, key: &str) -> &'a str {
a.get(key).and_then(Value::as_str).unwrap_or("")
}
#[no_mangle]
pub extern "C" fn bl_define(arg: *const c_char) -> *mut c_char {
envelope(read_arg(arg).and_then(|a| {
let deps: Vec<String> = a
.get("dependencies")
.and_then(Value::as_array)
.map(|x| {
x.iter()
.filter_map(Value::as_str)
.map(String::from)
.collect()
})
.unwrap_or_default();
let mut doc = a.get("doc").cloned().unwrap_or(Value::Null);
match define(
&mut doc,
s(&a, "behaviour_id"),
s(&a, "owning_project"),
s(&a, "intended_behaviour"),
s(&a, "origin_intent"),
s(&a, "pass_criteria"),
s(&a, "fail_criteria"),
s(&a, "procedure"),
s(&a, "owner_component"),
&deps,
s(&a, "automated_ref"),
s(&a, "environment"),
s(&a, "now"),
) {
Ok(rec) => Ok(json!({"doc": doc, "rec": rec})),
Err(e) => Err(FfiError::Domain(e)),
}
}))
}
#[no_mangle]
pub extern "C" fn bl_get(arg: *const c_char) -> *mut c_char {
envelope(read_arg(arg).map(|a| {
get(a.get("doc").unwrap_or(&Value::Null), s(&a, "behaviour_id"))
}))
}
#[no_mangle]
pub extern "C" fn bl_record_auto(arg: *const c_char) -> *mut c_char {
envelope(read_arg(arg).and_then(|a| {
let mut doc = a.get("doc").cloned().unwrap_or(Value::Null);
match record_auto_verification(
&mut doc,
s(&a, "behaviour_id"),
a.get("passed").and_then(Value::as_bool).unwrap_or(false),
s(&a, "detail"),
s(&a, "generation"),
s(&a, "now"),
) {
Ok(rec) => Ok(json!({"doc": doc, "rec": rec})),
Err(e) => Err(FfiError::Domain(e)),
}
}))
}
#[no_mangle]
pub extern "C" fn bl_record_real_world(arg: *const c_char) -> *mut c_char {
envelope(read_arg(arg).and_then(|a| {
let mut doc = a.get("doc").cloned().unwrap_or(Value::Null);
match record_real_world(
&mut doc,
s(&a, "behaviour_id"),
a.get("passed").and_then(Value::as_bool).unwrap_or(false),
s(&a, "detail"),
s(&a, "generation"),
s(&a, "confirmed_by"),
s(&a, "now"),
) {
Ok(rec) => Ok(json!({"doc": doc, "rec": rec})),
Err(e) => Err(FfiError::Domain(e)),
}
}))
}
#[no_mangle]
pub extern "C" fn bl_accept_limitation(arg: *const c_char) -> *mut c_char {
envelope(read_arg(arg).and_then(|a| {
let mut doc = a.get("doc").cloned().unwrap_or(Value::Null);
match accept_limitation(
&mut doc,
s(&a, "behaviour_id"),
s(&a, "note"),
s(&a, "approved_by"),
s(&a, "now"),
) {
Ok(rec) => Ok(json!({"doc": doc, "rec": rec})),
Err(e) => Err(FfiError::Domain(e)),
}
}))
}
#[no_mangle]
pub extern "C" fn bl_invalidate(arg: *const c_char) -> *mut c_char {
envelope(read_arg(arg).and_then(|a| {
let changed: Vec<String> = a
.get("changed_entities")
.and_then(Value::as_array)
.map(|x| {
x.iter()
.filter_map(Value::as_str)
.map(String::from)
.collect()
})
.unwrap_or_default();
let mut doc = a.get("doc").cloned().unwrap_or(Value::Null);
let reasons = a.get("reasons").cloned().unwrap_or(Value::Null);
match invalidate_for_change(
&mut doc,
&changed,
s(&a, "generation"),
&reasons,
s(&a, "now"),
) {
Ok(recs) => Ok(json!({"doc": doc, "recs": recs})),
Err(e) => Err(FfiError::Domain(e)),
}
}))
}
#[no_mangle]
pub extern "C" fn bl_retest_history(arg: *const c_char) -> *mut c_char {
envelope(read_arg(arg).map(|a| {
retest_history(a.get("doc").unwrap_or(&Value::Null), s(&a, "behaviour_id"))
}))
}
#[no_mangle]
pub extern "C" fn bl_outstanding(arg: *const c_char) -> *mut c_char {
envelope(read_arg(arg).map(|a| {
outstanding(
a.get("doc").unwrap_or(&Value::Null),
s(&a, "component"),
s(&a, "project"),
)
}))
}
#[no_mangle]
pub extern "C" fn bl_summary(arg: *const c_char) -> *mut c_char {
envelope(read_arg(arg).map(|a| summary(a.get("doc").unwrap_or(&Value::Null))))
}
+5 -96
View File
@@ -2,104 +2,13 @@
# It is not intended for manual editing.
version = 4
[[package]]
name = "capability_scoring"
version = "0.1.0"
[[package]]
name = "construction_patterns_builtin"
version = "0.1.0"
dependencies = [
"serde_json",
"capability_scoring",
]
[[package]]
name = "itoa"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]]
name = "memchr"
version = "2.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
[[package]]
name = "proc-macro2"
version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
dependencies = [
"proc-macro2",
]
[[package]]
name = "serde"
version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
dependencies = [
"serde_core",
]
[[package]]
name = "serde_core"
version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.151"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
dependencies = [
"itoa",
"memchr",
"serde",
"serde_core",
"zmij",
]
[[package]]
name = "syn"
version = "3.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954"
[[package]]
name = "zmij"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
@@ -2,9 +2,11 @@
name = "construction_patterns_builtin"
version = "0.1.0"
edition = "2021"
description = "Built-in construction pattern registry — dichotomy wave #2479 tranche; zero-dep (pyjson via capability_scoring)"
[lib]
name = "construction_patterns_builtin"
crate-type = ["rlib", "cdylib", "staticlib"]
[dependencies]
serde_json = "1"
capability_scoring = { path = "../capability_scoring" }
@@ -0,0 +1,365 @@
{
"revision": 1,
"patterns": [
{
"pattern_id": "capability-module-adaptation",
"purpose": "Adapting or extending an existing capability/module: prove the NEW behavior, preserve the OLD behavior (BUILD BUT DON'T BREAK).",
"revision": 1,
"maturity": "UNIT-PROVEN",
"signals": {
"entities_match": [
"module:",
"capability:"
],
"path_contains": [
"src/"
],
"objective_any": [
"adapt",
"extend",
"modify",
"preserve",
"existing",
"capability",
"repair"
]
},
"obligations": [
{
"id": "adapt-new-behavior-proof",
"obligation": "new behavior proven by tests before closure",
"when": {
"always": true
}
},
{
"id": "adapt-old-behavior-preserved",
"obligation": "existing capability behavior preserved; original contracts remain valid",
"when": {
"always": true
}
},
{
"id": "adapt-consumers-verified",
"obligation": "existing consumers of the adapted capability verified against their contracts",
"when": {
"entities_match": [
"module:",
"capability:"
]
}
},
{
"id": "adapt-migration-detected",
"obligation": "migration obligations detected and recorded if any consumer-visible interface changes",
"when": {
"path_contains": [
"src/"
]
}
}
],
"provenance": [
"LIVE-CONTRACT-QUALITY-001 (adaptation resolved + closed live)",
"SCAFFOLD-TRANCHE-001 (build-but-don't-break established)",
"SCAFFOLD-TRANCHE-003 (adaptation two-proof rule)",
"construction_decision.obligations_from_trace (auto-derived since T4)"
],
"exclusions": [],
"toolchain_notes": [],
"runtime_effects": []
},
{
"pattern_id": "release-deployment-change",
"purpose": "Changes to the release/deployment path: fail-closed qualification, exact candidate identity, staging != promotion, truthful promotion outcomes, deployed-identity verification, negative + forward proofs.",
"revision": 1,
"maturity": "UNIT-PROVEN",
"signals": {
"path_contains": [
"deploy",
"promotion",
"qualify",
"release.yml",
"ci.yml",
"deploy_qualify",
"deploy_stage",
"deploy_promotion"
],
"objective_any": [
"deploy",
"release",
"promot",
"qualif",
"candidate",
"current",
"monotonic"
],
"workflow_config_signals": true
},
"obligations": [
{
"id": "rel-fail-closed-qualification",
"obligation": "CI qualification remains fail-closed (exact SHA, two-leg verdict); red candidates never deploy",
"when": {
"path_contains": [
"deploy",
"release",
"qualify",
"ci.yml"
]
}
},
{
"id": "rel-exact-candidate-identity",
"obligation": "deployment addresses the EXACT pushed revision, never whatever HEAD is at deploy time",
"when": {
"path_contains": [
"deploy",
"release"
]
}
},
{
"id": "rel-staging-promotion-distinction",
"obligation": "staged candidate != promoted candidate; promotion outcome must be independently verifiable",
"when": {
"path_contains": [
"deploy",
"promotion",
"stage"
]
}
},
{
"id": "rel-promotion-truthfulness",
"obligation": "promotion outcomes are truthful (PROMOTED / NOOP_ALREADY_CURRENT / STALE_SUPERSEDED / REFUSED_*); stale candidates never claim success",
"when": {
"path_contains": [
"promotion",
"deploy_stage",
"deploy_promotion"
]
}
},
{
"id": "rel-monotonic-promotion",
"obligation": "promotion ordering: an older candidate finishing later cannot regress current; ancestry check + fenced critical section",
"when": {
"path_contains": [
"deploy_promotion",
"deploy_stage",
"promotion"
]
}
},
{
"id": "rel-negative-candidate-proof",
"obligation": "negative proof: red/unqualified candidate is refused; stale candidate cannot promote",
"when": {
"path_contains": [
"deploy",
"promotion",
"qualify"
]
}
},
{
"id": "rel-forward-candidate-proof",
"obligation": "forward proof: a green newer candidate still promotes normally",
"when": {
"path_contains": [
"deploy",
"promotion"
]
}
},
{
"id": "rel-deployed-identity-verification",
"obligation": "deployed identity verified from the deployment authority (current symlink + build identity), not from job artifacts alone",
"when": {
"always": true
}
},
{
"id": "rel-release-regression-verification",
"obligation": "existing release-safety guarantees re-proven (the guarantees that already exist must not silently weaken)",
"when": {
"always": true
}
}
],
"provenance": [
"DEPLOY-MONOTONIC-001 (monotonic promotion + truthful outcomes, live-proven)",
"maintenance:deploy-order-race (live incident: deploys 7355/7359)",
"release-safety repair 2026-09-13 (red-promotion incidents 0.66.28/29: required-checks aggregator lied)",
"scripts/deploy_promotion.py (ancestry + fenced critical section)",
"tests/test_deploy_stage_race.py (22-test matrix incl. two-process live race)"
],
"exclusions": [],
"toolchain_notes": [],
"runtime_effects": []
},
{
"pattern_id": "ui-tui-structural-change",
"purpose": "UI/TUI structural changes: component identity, Layout Contract inheritance, USER-LOCKED preservation, deterministic verification.",
"revision": 1,
"maturity": "UNIT-PROVEN",
"signals": {
"surface_kinds": [
"ui_component",
"layout_region"
],
"path_contains": [
"tui",
"ui",
"widget"
],
"entities_match": [
"ui:",
"region:",
"layout:"
]
},
"obligations": [
{
"id": "ui-layout-contract-inherited",
"obligation": "affected UI components inherit their Layout Contract obligations; layout_contract_check must PASS",
"when": {
"entities_match": [
"ui:",
"region:",
"layout:"
]
}
},
{
"id": "ui-user-locked-preserved",
"obligation": "USER-LOCKED placement is user authority: preserved exactly; changes require explicit user authorization",
"when": {
"entities_match": [
"ui:",
"region:"
]
}
},
{
"id": "ui-unknown-never-pass",
"obligation": "layout verification UNKNOWN (no adapter evidence) must never be reported as PASS",
"when": {
"entities_match": [
"ui:",
"region:",
"layout:"
]
}
},
{
"id": "ui-component-identity-stable",
"obligation": "ui:<component> identity survives implementation-path changes; authority never keyed by filename alone",
"when": {
"entities_match": [
"ui:",
"region:",
"layout:"
]
}
}
],
"provenance": [
"Tranche 3 real TUI proof (gate PASS -> violation FAIL -> restore)",
"issue #105 + plans/2026-09-03-tui-footer.md (version display)",
"maintenance:version-display-user-lock (USER-LOCK upgrade pending user confirmation \u2014 provenance rule honored, not fabricated)",
"tests/test_layout_contract.py + tests/test_layout_contract_real_proof.py"
],
"exclusions": [],
"toolchain_notes": [],
"runtime_effects": []
},
{
"pattern_id": "store-schema-evolution",
"purpose": "Durable store/schema changes: backward readability, authority preservation, deterministic roundtrip, migration only when necessary.",
"revision": 1,
"maturity": "CANDIDATE",
"signals": {
"path_contains": [
"store",
"schema",
"structural_store",
"scaffold",
"spine"
],
"entities_match": [
"module:structural_store",
"module:scaffold",
"module:spine"
],
"objective_any": [
"store",
"schema",
"record",
"persist",
"field",
"migration"
]
},
"obligations": [
{
"id": "store-backward-readability",
"obligation": "old records without new fields read as empty/unknown, never crash; legacy keys preserved",
"when": {
"path_contains": [
"store",
"structural_store",
"scaffold",
"spine",
"schema"
]
}
},
{
"id": "store-roundtrip",
"obligation": "deterministic save/load roundtrip incl. unknown entity/relation kinds and open-world fields",
"when": {
"path_contains": [
"store",
"structural_store",
"scaffold",
"schema"
]
}
},
{
"id": "store-authority-preservation",
"obligation": "structural facts only change through their owning projector/authority; canonical rebuild after registration (no hand-edited structural facts)",
"when": {
"path_contains": [
"spine",
"scaffold",
"store/"
]
}
},
{
"id": "store-migration-only-if-needed",
"obligation": "no mandatory migration unless semantics truly change; ext/open-world fields preferred over schema churn",
"when": {
"path_contains": [
"schema",
"store",
"structural_store"
]
}
}
],
"provenance": [
"Tranche 2 CI lesson: hand-edited spine store diverged from canonical rebuild (deploy-identity edges)",
"Tranche 2 structural-store extraction (behavior-neutrality proof)",
"Tranche 4 contract-quality repair (legacy keys preserved, old contracts readable \u2014 test_h)",
"Tranche 5 expected-runtime-effects extension (old contracts close without new fields)"
],
"exclusions": [],
"toolchain_notes": [],
"runtime_effects": []
}
]
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,126 @@
//! branch_battery — replays frozen_cpb_truth.json VERBATIM (17 cases
//! frozen from the live Python oracle construction_patterns_builtin.py,
//! RUNE_CPB_CORE=python pinned, inputs deep-copied before capture).
//! Order-insensitive object comparison; numbers compared exactly (both
//! sides IEEE-754 f64); list order significant (registry + provenance
//! order is oracle-derived).
use capability_scoring::pyjson::{parse, Json};
use crate::registry;
const TRUTH: &str = include_str!("../frozen_cpb_truth.json");
fn jeq(a: &Json, b: &Json) -> bool {
match (a, b) {
(Json::Num(x), Json::Num(y)) => x == y,
(Json::Str(x), Json::Str(y)) => x == y,
(Json::Bool(x), Json::Bool(y)) => x == y,
(Json::Null, Json::Null) => true,
(Json::Arr(x), Json::Arr(y)) => {
x.len() == y.len() && x.iter().zip(y).all(|(i, j)| jeq(i, j))
}
(Json::Obj(x), Json::Obj(y)) => {
x.len() == y.len()
&& x.iter()
.all(|(k, v)| y.iter().any(|(k2, v2)| k == k2 && jeq(v, v2)))
}
_ => false,
}
}
fn as_str(v: Option<&Json>) -> String {
match v {
Some(Json::Str(s)) => s.clone(),
_ => String::new(),
}
}
/// One case's oracle verdict, replayed against the Rust kernel.
fn run_case(fn_name: &str, args: &Json) -> Result<Json, String> {
match fn_name {
"registry_revision" => Ok(Json::Num(registry::registry_revision() as f64)),
"patterns" => Ok(Json::Arr(registry::patterns())),
"get_pattern" => {
let pattern_id = as_str(args.get("pattern_id"));
let revision = match args.get("revision") {
Some(Json::Num(n)) => Some(*n as i64),
_ => None,
};
registry::get_pattern(&pattern_id, revision)
}
other => Err(format!("unknown truth fn {other}")),
}
}
fn check_case(fn_name: &str, name: &str, args: &Json, want: &Json) -> Result<(), String> {
// The oracle's raising branch is recorded as {"raises","message"}.
if let Some(message) = want.get("message").filter(|_| want.get("raises").is_some()) {
match run_case(fn_name, args) {
Err(err) => {
let want_msg = as_str(Some(message));
if err != want_msg {
return Err(format!(
"{fn_name}/{name}: err mismatch\n want {want_msg}\n got {err}"
));
}
return Ok(());
}
Ok(got) => {
return Err(format!(
"{fn_name}/{name}: oracle raises, kernel returned {}",
capability_scoring::pyjson::dumps(&got)
))
}
}
}
match run_case(fn_name, args) {
Ok(got) => {
if jeq(&got, want) {
Ok(())
} else {
Err(format!(
"{fn_name}/{name}: value mismatch\n want {}\n got {}",
capability_scoring::pyjson::dumps(want),
capability_scoring::pyjson::dumps(&got)
))
}
}
Err(err) => Err(format!("{fn_name}/{name}: kernel error {err}")),
}
}
#[test]
fn frozen_truth_replays_verbatim() {
let truth = parse(TRUTH).expect("frozen_cpb_truth.json parses");
let cases = match truth.get("cases") {
Some(Json::Arr(c)) => c.clone(),
_ => panic!("truth has no cases"),
};
assert!(!cases.is_empty(), "frozen truth is empty");
let mut failures = Vec::new();
for case in &cases {
let fn_name = as_str(case.get("fn"));
let name = as_str(case.get("name"));
let args = case.get("input").cloned().unwrap_or(Json::Null);
let want = case.get("want").cloned().unwrap_or(Json::Null);
if let Err(err) = check_case(&fn_name, &name, &args, &want) {
failures.push(err);
}
}
assert!(
failures.is_empty(),
"branch battery diverged from frozen truth:\n{}",
failures.join("\n")
);
}
#[test]
fn truth_case_count_is_stable() {
let truth = parse(TRUTH).expect("frozen_cpb_truth.json parses");
let cases = match truth.get("cases") {
Some(Json::Arr(c)) => c.len(),
_ => 0,
};
assert_eq!(cases, 19, "frozen case count drifted — regenerate truth");
}
@@ -1,19 +1,96 @@
//! construction_patterns_builtin — built-in construction patterns (W6).
pub fn builtin_patterns() -> serde_json::Value {
serde_json::json!({
"patterns": [
{"pattern_id": "web-api", "description": "HTTP API with REST endpoints"},
{"pattern_id": "cli-tool", "description": "Command-line tool with argument parsing"},
]
})
}
//! construction_patterns_builtin — the built-in pattern registry kernel
//! (dichotomy wave #2479 tranche). Serves the oracle's pattern data
//! (frozen verbatim from the live Python registry — provenance order is
//! the oracle's __init__-sorted order) through a doc-envelope FFI. The
//! Python facade keeps a RUNE_CPB_CORE=python pin.
//!
//! FFI contract (doc-envelope law — every call returns {"ok","value"|"err"}):
//! * cpb_registry_revision({}) -> number
//! * cpb_patterns({}) -> [pattern doc, ...] registry order
//! * cpb_get_pattern({"pattern_id", "revision"}) -> pattern doc | null
//! (a revision that no longer matches is an err carrying the oracle's
//! exact ValueError message)
//!
//! Parity oracle: frozen_cpb_truth.json (17 cases frozen from the live
//! Python oracle) replayed verbatim by the in-crate branch battery.
pub mod registry;
pub use capability_scoring::pyjson;
#[path = "branch_battery.rs"]
#[cfg(test)]
mod branch_battery {
use super::*;
mod branch_battery;
#[test]
fn builtin_patterns_exist() {
assert!(true);
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
use pyjson::Json;
fn read_arg(ptr: *const c_char) -> Result<String, String> {
if ptr.is_null() {
return Err("null argument pointer".to_string());
}
unsafe { CStr::from_ptr(ptr) }
.to_str()
.map(|s| s.to_string())
.map_err(|e| format!("argument is not valid UTF-8: {}", e))
}
fn envelope(result: Result<Json, String>) -> *mut c_char {
let body = match result {
Ok(value) => Json::Obj(vec![
("ok".to_string(), Json::Bool(true)),
("value".to_string(), value),
]),
Err(err) => Json::Obj(vec![
("ok".to_string(), Json::Bool(false)),
("err".to_string(), Json::Str(err)),
]),
};
let out = pyjson::dumps(&body);
CString::new(out)
.map(|c| c.into_raw())
.unwrap_or(std::ptr::null_mut())
}
fn arg_json(text: &str) -> Result<Json, String> {
pyjson::parse(text).map_err(|e| format!("argument JSON parse failed: {}", e))
}
/// cpb_registry_revision({}) -> number
#[no_mangle]
pub extern "C" fn cpb_registry_revision(arg: *const c_char) -> *mut c_char {
let result = read_arg(arg)
.and_then(|text| arg_json(&text))
.map(|_| Json::Num(registry::registry_revision() as f64));
envelope(result)
}
/// cpb_patterns({}) -> [pattern doc, ...] in registry order
#[no_mangle]
pub extern "C" fn cpb_patterns(arg: *const c_char) -> *mut c_char {
let result = read_arg(arg)
.and_then(|text| arg_json(&text))
.map(|_| Json::Arr(registry::patterns()));
envelope(result)
}
/// cpb_get_pattern({"pattern_id": "...", "revision": n|null}) -> doc | null
#[no_mangle]
pub extern "C" fn cpb_get_pattern(arg: *const c_char) -> *mut c_char {
let result = read_arg(arg)
.and_then(|text| arg_json(&text))
.and_then(|a| {
let pattern_id = match a.get("pattern_id") {
Some(Json::Str(s)) => s.clone(),
_ => return Err("arg.pattern_id required".to_string()),
};
let revision = match a.get("revision") {
Some(Json::Num(n)) => Some(*n as i64),
_ => None,
};
registry::get_pattern(&pattern_id, revision)
});
envelope(result)
}
@@ -0,0 +1,99 @@
//! registry — the built-in construction pattern registry. The pattern docs
//! are FROZEN from the live Python oracle (frozen_cpb_patterns.json,
//! generated by scripts/gen_construction_patterns_builtin_truth.py with
//! RUNE_CPB_CORE=python pinned — never hand-transcribed): each doc is the
//! oracle's ConstructionPattern.to_dict(), including its __init__-sorted
//! provenance/exclusions order.
//!
//! Phase B: the frozen docs parse once into typed [`BuiltinPattern`]
//! values; the FFI re-emits the oracle-shaped doc from the typed value.
//! Miss-before-revision-check oracle ordering is explicit in the typed
//! lookup.
use std::sync::OnceLock;
use capability_scoring::pyjson::{self, Json};
const PATTERNS_SRC: &str = include_str!("../frozen_cpb_patterns.json");
/// One builtin pattern, typed from the oracle's to_dict() doc. Free-form
/// sub-structures (signals, obligations) stay `Json`: the schema is the
/// oracle's, open-world by design.
#[derive(Clone, Debug)]
struct BuiltinPattern {
pattern_id: String,
revision: i64,
doc: Json,
}
impl BuiltinPattern {
fn from_doc(doc: Json) -> Option<Self> {
let pattern_id = match doc.get("pattern_id") {
Some(Json::Str(s)) => s.clone(),
_ => return None,
};
let revision = match doc.get("revision") {
Some(Json::Num(n)) => *n as i64,
_ => return None,
};
Some(Self { pattern_id, revision, doc })
}
}
fn registry() -> &'static Vec<BuiltinPattern> {
static REGISTRY: OnceLock<Vec<BuiltinPattern>> = OnceLock::new();
REGISTRY.get_or_init(|| {
pyjson::parse(PATTERNS_SRC)
.ok()
.and_then(|root| match root.get("patterns") {
Some(Json::Arr(items)) => Some(
items
.iter()
.filter_map(|d| BuiltinPattern::from_doc(d.clone()))
.collect(),
),
_ => None,
})
.unwrap_or_default()
})
}
/// PATTERN_REGISTRY_REVISION from the oracle.
pub fn registry_revision() -> i64 {
static REVISION: OnceLock<i64> = OnceLock::new();
*REVISION.get_or_init(|| {
pyjson::parse(PATTERNS_SRC)
.ok()
.and_then(|root| match root.get("revision") {
Some(Json::Num(n)) => Some(*n as i64),
_ => None,
})
.unwrap_or(0)
})
}
/// Every builtin pattern doc, in registry order (BUILTIN_PATTERNS order).
pub fn patterns() -> Vec<Json> {
registry().iter().map(|p| p.doc.clone()).collect()
}
/// Oracle get_pattern semantics: unknown/empty id -> Null BEFORE any
/// revision check; a revision that no longer matches raises (err carries
/// the oracle's exact ValueError message).
pub fn get_pattern(pattern_id: &str, revision: Option<i64>) -> Result<Json, String> {
let pattern = match registry().iter().find(|p| p.pattern_id == pattern_id) {
Some(p) => p,
None => return Ok(Json::Null),
};
if let Some(rev) = revision {
if pattern.revision != rev {
return Err(format!(
"pattern {pattern_id} revision {rev} not in registry \
(current: {}) \u{2014} the consuming contract records which \
revision it used",
pattern.revision
));
}
}
Ok(pattern.doc.clone())
}
+23
View File
@@ -54,6 +54,12 @@ dependencies = [
"sha2",
]
[[package]]
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "generic-array"
version = "0.14.7"
@@ -64,6 +70,22 @@ dependencies = [
"version_check",
]
[[package]]
name = "hashbrown"
version = "0.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
[[package]]
name = "indexmap"
version = "2.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855"
dependencies = [
"equivalent",
"hashbrown",
]
[[package]]
name = "itoa"
version = "1.0.18"
@@ -135,6 +157,7 @@ version = "1.0.151"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
dependencies = [
"indexmap",
"itoa",
"memchr",
"serde",
+1 -1
View File
@@ -8,4 +8,4 @@ crate-type = ["rlib", "cdylib", "staticlib"]
[dependencies]
sha2 = "0.10"
serde_json = "1"
serde_json = { version = "1", features = ["preserve_order", "float_roundtrip"] }
File diff suppressed because it is too large. Load diff
+252 -22
View File
@@ -14,7 +14,49 @@ use std::os::raw::c_char;
pub const DRIFT_LIMIT: f64 = 0.30;
pub const CONSTITUTION_LIMIT: f64 = 0.05;
pub const BLOCKS: &[&str] = &["constitution", "persona", "human", "operating", "project"];
/// The five guarded blocks — a closed lattice, not stringly-typed routing.
/// The constitution's limit is exhaustive-match derived; every other block
/// falls to the plain drift limit (the oracle's ternary).
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum BlockLabel {
Constitution,
Persona,
Human,
Operating,
Project,
}
impl BlockLabel {
pub const ALL: [BlockLabel; 5] = [
Self::Constitution,
Self::Persona,
Self::Human,
Self::Operating,
Self::Project,
];
pub fn as_str(self) -> &'static str {
match self {
Self::Constitution => "constitution",
Self::Persona => "persona",
Self::Human => "human",
Self::Operating => "operating",
Self::Project => "project",
}
}
pub fn of_name(name: &str) -> Option<Self> {
Self::ALL.iter().copied().find(|b| b.as_str() == name)
}
pub fn limit(self) -> f64 {
match self {
Self::Constitution => CONSTITUTION_LIMIT,
_ => DRIFT_LIMIT,
}
}
}
/// _sha port: hex sha256 of UTF-8 text.
pub fn sha(text: &str) -> String {
@@ -28,28 +70,28 @@ pub fn diff_blocks(reference: &str, current: &str) -> String {
if reference.is_empty() {
return "no prior content (first drift from empty baseline)".to_string();
}
fn sig(l: &str) -> Option<&str> {
let s = l.trim();
(!s.is_empty()).then_some(s)
}
let ref_lines: Vec<&str> = reference.lines().collect();
let cur_lines: Vec<&str> = current.lines().collect();
let ref_set: std::collections::HashSet<&str> =
ref_lines.iter().filter_map(|l| { let s = l.trim(); if s.is_empty() { None } else { Some(s) } }).collect();
ref_lines.iter().filter_map(|l| sig(l)).collect();
let cur_set: std::collections::HashSet<&str> =
cur_lines.iter().filter_map(|l| { let s = l.trim(); if s.is_empty() { None } else { Some(s) } }).collect();
let mut added: Vec<&str> = vec![];
for l in &cur_lines {
let s = l.trim();
if !s.is_empty() && !ref_set.contains(s) {
added.push(s);
if added.len() >= 6 { break; }
}
}
let mut removed: Vec<&str> = vec![];
for l in &ref_lines {
let s = l.trim();
if !s.is_empty() && !cur_set.contains(s) {
removed.push(s);
if removed.len() >= 3 { break; }
}
}
cur_lines.iter().filter_map(|l| sig(l)).collect();
let added: Vec<&str> = cur_lines
.iter()
.filter_map(|l| sig(l))
.filter(|s| !ref_set.contains(*s))
.take(6)
.collect();
let removed: Vec<&str> = ref_lines
.iter()
.filter_map(|l| sig(l))
.filter(|s| !cur_set.contains(*s))
.take(3)
.collect();
let mut parts: Vec<String> = vec![];
if !added.is_empty() {
parts.push(format!(
@@ -85,8 +127,9 @@ pub fn check_block(
None => (prev_size.max(1), None, cur_size - prev_size),
};
let pct = (delta.abs() as f64) / (base.max(1) as f64);
let limit = if label == "constitution" { CONSTITUTION_LIMIT } else { DRIFT_LIMIT };
let content_changed = label == "constitution"
let is_constitution = matches!(BlockLabel::of_name(label), Some(BlockLabel::Constitution));
let limit = if is_constitution { CONSTITUTION_LIMIT } else { DRIFT_LIMIT };
let content_changed = is_constitution
&& anchor_size.is_some()
&& base_sha.is_some()
&& cur_sha.is_some()
@@ -100,7 +143,8 @@ pub fn check_block(
/// auto_fix routing port: (label, anchored, has_last_good) -> {action, reason}.
pub fn auto_fix_decision(label: &str, anchored: bool, has_last_good: bool) -> Value {
if label == "constitution" {
let is_constitution = matches!(BlockLabel::of_name(label), Some(BlockLabel::Constitution));
if is_constitution {
json!({"action": "alert-only", "reason": "constitution changes only by explicit directive; needs human review"})
} else if anchored {
json!({"action": "re-anchored", "reason": "anchored block grew (authorised) — re-baselined"})
@@ -111,6 +155,143 @@ pub fn auto_fix_decision(label: &str, anchored: bool, has_last_good: bool) -> Va
}
}
/// Python round(x, 3): correct rounding of the binary value with ties-to-even
/// (the same contract Rust's {:.3} formatting implements).
fn py_round3(x: f64) -> f64 {
format!("{:.3}", x).parse().unwrap_or(x)
}
/// ledger_path port: the ledger lives beside the index file, so overriding
/// INDEX_FILE moves the ledger too.
pub fn ledger_path_for(index_file: &str) -> String {
match index_file.rfind('/') {
Some(i) => format!("{}drift_ledger.json", &index_file[..=i]),
None => "drift_ledger.json".to_string(),
}
}
/// snapshot() entry-assembly port: {"when", "blocks": {label: {sha, size,
/// content}}}. "size" is the oracle's len(raw) — a CHARACTER count, not bytes.
/// Unknown labels in the payload are ignored (the oracle iterates BLOCKS).
pub fn snapshot_entry(blocks: &Value, when: &str) -> Value {
let mut b = serde_json::Map::new();
for label in BlockLabel::ALL {
let raw = blocks.get(label.as_str()).and_then(|v| v.as_str()).unwrap_or("");
b.insert(
label.as_str().to_string(),
json!({
"sha": sha(raw),
"size": raw.chars().count(),
"content": raw,
}),
);
}
json!({"when": when, "blocks": Value::Object(b)})
}
/// anchor() record-assembly port: the authorised-state backup for re-anchoring.
pub fn anchor_record(content: &str, reason: &str, when: &str) -> Value {
json!({
"sha": sha(content),
"size": content.chars().count(),
"content": content,
"reason": reason,
"when": when,
})
}
/// check() alert-loop port: given the ledger doc and the current block
/// contents {label: text}, produce the alert list exactly as the oracle's
/// loop does — anchored blocks are measured against their authorised state,
/// unanchored ones against the last snapshot (max(prev_size, 1) floor),
/// the constitution additionally trips on ANY content change from its
/// anchored sha, pct/limit round to 3 decimals, and each alert carries the
/// root-cause diff. Side effects (prints, surface_alert, auto-fix, journal)
/// stay in the facade.
pub fn check_alerts(ledger: &Value, blocks: &Value) -> Value {
let prev = ledger
.get("last_snapshot")
.and_then(|s| s.get("blocks"))
.cloned()
.unwrap_or_else(|| json!({}));
let anchors = ledger.get("anchors").cloned().unwrap_or_else(|| json!({}));
let alerts: Vec<Value> = BlockLabel::ALL
.iter()
.filter_map(|label| {
let cur = blocks.get(label.as_str()).and_then(|v| v.as_str()).unwrap_or("");
let cur_size = cur.chars().count() as i64;
let anchor_meta = anchors.get(label.as_str());
let (base, base_sha, delta, ref_content) = match anchor_meta {
Some(a) => {
let b = a.get("size").and_then(|v| v.as_i64()).unwrap_or(cur_size);
let sh = a.get("sha").and_then(|v| v.as_str());
let rc = a.get("content").and_then(|v| v.as_str());
(b, sh, cur_size - b, rc)
}
None => {
let pm = prev.get(label.as_str());
let ps = pm
.and_then(|m| m.get("size"))
.and_then(|v| v.as_i64())
.unwrap_or(cur_size);
let rc = pm.and_then(|m| m.get("content")).and_then(|v| v.as_str());
(ps.max(1), None, cur_size - ps, rc)
}
};
let pct = (delta.abs() as f64) / (base.max(1) as f64);
let limit = label.limit();
let content_changed = matches!(label, BlockLabel::Constitution)
&& anchor_meta.is_some()
&& base_sha.is_some()
&& sha(cur) != base_sha.unwrap();
(content_changed || pct > limit).then(|| {
json!({
"label": label.as_str(),
"delta": delta,
"pct": py_round3(pct),
"limit": py_round3(limit),
"base": base,
"changed": diff_blocks(ref_content.unwrap_or(""), cur),
"content_changed": content_changed,
})
})
})
.collect();
Value::Array(alerts)
}
/// fix() decision port: the oracle's retroactive-fix gate minus the restore IO
/// (the facade performs restore_topic + journal when {fixed: true}).
/// cur_sha is the sha of the block's CURRENT store content (read_block is a
/// facade seam; the sha travels in the doc envelope).
pub fn fix_decision(ledger: &Value, label: &str, cur_sha: Option<&str>) -> Value {
let block_meta = ledger
.get("last_snapshot")
.and_then(|s| s.get("blocks"))
.and_then(|b| b.get(label));
let has_content = block_meta
.map(|m| m.get("content").is_some())
.unwrap_or(false);
if block_meta.is_none() || !has_content {
return json!({"fixed": false, "reason": format!("no last-good content for {}", label)});
}
if ledger
.get("anchors")
.map(|a| a.get(label).is_some())
.unwrap_or(false)
{
return json!({"fixed": false, "reason": format!("{} is anchored (authorised); use anchor to re-baseline instead of fix", label)});
}
let good_sha = block_meta
.and_then(|m| m.get("sha"))
.and_then(|v| v.as_str())
.unwrap_or("");
if cur_sha == Some(good_sha) {
return json!({"fixed": false, "reason": format!("{} already matches last-good", label)});
}
json!({"fixed": true, "reason": format!("{} restored to last-good state", label)})
}
fn read_arg(ptr: *const c_char) -> Result<Value, String> {
if ptr.is_null() {
return Err("null argument pointer".into());
@@ -181,5 +362,54 @@ pub extern "C" fn dl_auto_fix(arg: *const c_char) -> *mut c_char {
envelope(r)
}
/// dl_ledger_path({"index_file"}) -> path string
#[no_mangle]
pub extern "C" fn dl_ledger_path(arg: *const c_char) -> *mut c_char {
let r = read_arg(arg).map(|a| Value::String(ledger_path_for(s(a.get("index_file")))));
envelope(r)
}
/// dl_snapshot_entry({"blocks": {label: content}, "when"}) -> snapshot entry
#[no_mangle]
pub extern "C" fn dl_snapshot_entry(arg: *const c_char) -> *mut c_char {
let r = read_arg(arg).map(|a| {
let blocks = a.get("blocks").cloned().unwrap_or(Value::Null);
snapshot_entry(&blocks, s(a.get("when")))
});
envelope(r)
}
/// dl_anchor_record({"content", "reason", "when"}) -> anchor record
#[no_mangle]
pub extern "C" fn dl_anchor_record(arg: *const c_char) -> *mut c_char {
let r = read_arg(arg).map(|a| {
anchor_record(s(a.get("content")), s(a.get("reason")), s(a.get("when")))
});
envelope(r)
}
/// dl_check_alerts({"ledger", "blocks": {label: content}}) -> [alert, ...]
#[no_mangle]
pub extern "C" fn dl_check_alerts(arg: *const c_char) -> *mut c_char {
let r = read_arg(arg).map(|a| {
let ledger = a.get("ledger").cloned().unwrap_or(Value::Null);
let blocks = a.get("blocks").cloned().unwrap_or(Value::Null);
check_alerts(&ledger, &blocks)
});
envelope(r)
}
/// dl_fix_decision({"ledger", "label", "cur_sha"?}) -> {fixed, reason}
#[no_mangle]
pub extern "C" fn dl_fix_decision(arg: *const c_char) -> *mut c_char {
let r = read_arg(arg).map(|a| {
let ledger = a.get("ledger").cloned().unwrap_or(Value::Null);
let label = s(a.get("label")).to_string();
let cur_sha = a.get("cur_sha").and_then(|v| v.as_str());
fix_decision(&ledger, &label, cur_sha)
});
envelope(r)
}
// NOTE: no free-symbol export here (capability_scoring's cs_free_string
// serves the whole process — duplicate symbols break the cdylib link).
+113
View File
@@ -0,0 +1,113 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "experience_encoding"
version = "0.1.0"
dependencies = [
"lexicon_evolution",
"serde_json",
]
[[package]]
name = "itoa"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]]
name = "lexicon_evolution"
version = "0.1.0"
dependencies = [
"serde_json",
]
[[package]]
name = "memchr"
version = "2.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
[[package]]
name = "proc-macro2"
version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
dependencies = [
"proc-macro2",
]
[[package]]
name = "serde"
version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
dependencies = [
"serde_core",
]
[[package]]
name = "serde_core"
version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.154"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6"
dependencies = [
"itoa",
"memchr",
"serde",
"serde_core",
"zmij",
]
[[package]]
name = "syn"
version = "3.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01016da373cd8f7ef12624f796309f5c31ba8d646dd08856c02cd741d823c622"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563"
[[package]]
name = "zmij"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
+13
View File
@@ -0,0 +1,13 @@
[package]
name = "experience_encoding"
version = "0.1.0"
edition = "2021"
description = "canonical experience encoding and episodic admission (dichotomy wave #2479) — FULL Rust port of src/rune/experience_encoding.py; zero-dep beyond serde_json + the lexicon_evolution sibling"
[lib]
name = "experience_encoding"
crate-type = ["rlib", "cdylib", "staticlib"]
[dependencies]
serde_json = "1"
lexicon_evolution = { path = "../lexicon_evolution" }
@@ -0,0 +1,49 @@
//! experience_encoding branch battery — parity vs the frozen Python-oracle
//! truth (frozen_ee_truth.json: 29 cases across the module's single public
//! function and every branch — the empty/short gate with its char-count
//! boundary (14 vs 15), null/missing/whitespace text coercion, provenance
//! dict.get quirk (present-but-null source/session_id pins as null; the
//! default applies only to a MISSING key), the whitespace/case-insensitive
//! duplicate scan and its punctuation-survives non-duplicate, the lexicon
//! seam (ImportError fallback → conservative evidence, ordering vs the
//! empty gate and the duplicate check), framing-only trivia, the
//! grounded → EPISODE/EVIDENCE split with single/multi/top3 sorted share
//! reasons, novel-axis grounding, corpus fallback to episode texts and the
//! corpus override that kills the share but not the grounding).
//! Frozen via scripts/gen_experience_encoding_truth.py with
//! RUNE_EE_CORE=python pinned (poisoned-truth law). Embedded VERBATIM —
//! never transcribed. Each case replays the oracle's full return shape
//! (verdict + reason + provenance) and compares whole-doc.
use crate::evaluate;
use serde_json::Value;
const TRUTH: &str = include_str!("frozen_ee_truth.json");
#[test]
fn evaluate_parity_battery() {
let truth: Value = serde_json::from_str(TRUTH).unwrap();
for case in truth["cases"]["evaluate"].as_array().unwrap() {
let input = &case["input"];
let event = input["event"].clone();
let episodes: Vec<Value> = input["existing_episodes"]
.as_array()
.cloned()
.unwrap_or_default();
let corpus: Vec<Value> = input["corpus_texts"]
.as_array()
.cloned()
.unwrap_or_default();
let lexicon = input
.get("lexicon_available")
.and_then(Value::as_bool)
.unwrap_or(true);
let got = evaluate(&event, &episodes, &corpus, lexicon);
assert_eq!(
&got,
&case["expected"],
"case '{}' diverged",
case["name"].as_str().unwrap()
);
}
}
@@ -0,0 +1,895 @@
{
"frozen": "2026-10-12T10:38:28.375209+00:00",
"oracle": "src/rune/experience_encoding.py",
"pin": "RUNE_EE_CORE=python",
"note": "Frozen via scripts/gen_experience_encoding_truth.py against the live Python oracle. Battery embeds this VERBATIM. The `lexicon_available` flag pins the oracle's flat-import environment seam (ImportError fallback branch).",
"cases": {
"evaluate": [
{
"name": "ev-missing-text-key-empty",
"input": {
"event": {
"source": "user"
},
"lexicon_available": true
},
"return": {
"verdict": "IGNORE",
"reason": "empty or trivial (<15 chars)",
"provenance": {
"source": "user",
"session": ""
}
},
"expected": {
"verdict": "IGNORE",
"reason": "empty or trivial (<15 chars)",
"provenance": {
"source": "user",
"session": ""
}
}
},
{
"name": "ev-empty-text",
"input": {
"event": {
"text": "",
"source": "user"
},
"lexicon_available": true
},
"return": {
"verdict": "IGNORE",
"reason": "empty or trivial (<15 chars)",
"provenance": {
"source": "user",
"session": ""
}
},
"expected": {
"verdict": "IGNORE",
"reason": "empty or trivial (<15 chars)",
"provenance": {
"source": "user",
"session": ""
}
}
},
{
"name": "ev-null-text-coerced",
"input": {
"event": {
"text": null,
"source": "user"
},
"lexicon_available": true
},
"return": {
"verdict": "IGNORE",
"reason": "empty or trivial (<15 chars)",
"provenance": {
"source": "user",
"session": ""
}
},
"expected": {
"verdict": "IGNORE",
"reason": "empty or trivial (<15 chars)",
"provenance": {
"source": "user",
"session": ""
}
}
},
{
"name": "ev-whitespace-only-text",
"input": {
"event": {
"text": " \n\t ",
"source": "user"
},
"lexicon_available": true
},
"return": {
"verdict": "IGNORE",
"reason": "empty or trivial (<15 chars)",
"provenance": {
"source": "user",
"session": ""
}
},
"expected": {
"verdict": "IGNORE",
"reason": "empty or trivial (<15 chars)",
"provenance": {
"source": "user",
"session": ""
}
}
},
{
"name": "ev-short-14-chars",
"input": {
"event": {
"text": "xxxxxxxxxxxxxx",
"source": "user"
},
"lexicon_available": true
},
"return": {
"verdict": "IGNORE",
"reason": "empty or trivial (<15 chars)",
"provenance": {
"source": "user",
"session": ""
}
},
"expected": {
"verdict": "IGNORE",
"reason": "empty or trivial (<15 chars)",
"provenance": {
"source": "user",
"session": ""
}
}
},
{
"name": "ev-boundary-15-chars-evidence",
"input": {
"event": {
"text": "xxxxxxxxxxxxxxx",
"source": "curator",
"session_id": "s1"
},
"lexicon_available": true
},
"return": {
"verdict": "EVIDENCE",
"reason": "fact-shaped, ungrounded \u2014 stored as evidence, not episode",
"provenance": {
"source": "curator",
"session": "s1"
}
},
"expected": {
"verdict": "EVIDENCE",
"reason": "fact-shaped, ungrounded \u2014 stored as evidence, not episode",
"provenance": {
"source": "curator",
"session": "s1"
}
}
},
{
"name": "ev-null-source-session-provenance",
"input": {
"event": {
"text": "hi",
"source": null,
"session_id": null
},
"lexicon_available": true
},
"return": {
"verdict": "IGNORE",
"reason": "empty or trivial (<15 chars)",
"provenance": {
"source": null,
"session": null
}
},
"expected": {
"verdict": "IGNORE",
"reason": "empty or trivial (<15 chars)",
"provenance": {
"source": null,
"session": null
}
}
},
{
"name": "ev-empty-string-source-session",
"input": {
"event": {
"text": "xxxxxxxxxxxxxxxxxxxx",
"source": "",
"session_id": ""
},
"lexicon_available": true
},
"return": {
"verdict": "EVIDENCE",
"reason": "fact-shaped, ungrounded \u2014 stored as evidence, not episode",
"provenance": {
"source": "",
"session": ""
}
},
"expected": {
"verdict": "EVIDENCE",
"reason": "fact-shaped, ungrounded \u2014 stored as evidence, not episode",
"provenance": {
"source": "",
"session": ""
}
}
},
{
"name": "ev-extra-keys-ignored",
"input": {
"event": {
"text": "xxxxxxxxxxxxxxxx",
"kind": "note",
"weight": 3
},
"lexicon_available": true
},
"return": {
"verdict": "EVIDENCE",
"reason": "fact-shaped, ungrounded \u2014 stored as evidence, not episode",
"provenance": {
"source": "unknown",
"session": ""
}
},
"expected": {
"verdict": "EVIDENCE",
"reason": "fact-shaped, ungrounded \u2014 stored as evidence, not episode",
"provenance": {
"source": "unknown",
"session": ""
}
}
},
{
"name": "ev-duplicate-normalized-whitespace-case",
"input": {
"event": {
"text": "Nick prefers DARK coffee",
"source": "user"
},
"lexicon_available": true,
"existing_episodes": [
{
"text": "nick prefers DARK coffee"
}
]
},
"return": {
"verdict": "IGNORE",
"reason": "duplicate of an existing episode",
"provenance": {
"source": "user",
"session": ""
}
},
"expected": {
"verdict": "IGNORE",
"reason": "duplicate of an existing episode",
"provenance": {
"source": "user",
"session": ""
}
}
},
{
"name": "ev-duplicate-exact-string",
"input": {
"event": {
"text": "she drinks oatmilk daily",
"source": "user"
},
"lexicon_available": true,
"existing_episodes": [
{
"text": "she drinks oatmilk daily"
}
]
},
"return": {
"verdict": "IGNORE",
"reason": "duplicate of an existing episode",
"provenance": {
"source": "user",
"session": ""
}
},
"expected": {
"verdict": "IGNORE",
"reason": "duplicate of an existing episode",
"provenance": {
"source": "user",
"session": ""
}
}
},
{
"name": "ev-duplicate-episode-uppercase",
"input": {
"event": {
"text": "nick prefers dark coffee",
"source": "user"
},
"lexicon_available": true,
"existing_episodes": [
{
"text": "NICK PREFERS DARK COFFEE"
}
]
},
"return": {
"verdict": "IGNORE",
"reason": "duplicate of an existing episode",
"provenance": {
"source": "user",
"session": ""
}
},
"expected": {
"verdict": "IGNORE",
"reason": "duplicate of an existing episode",
"provenance": {
"source": "user",
"session": ""
}
}
},
{
"name": "ev-duplicate-episode-padded",
"input": {
"event": {
"text": "nick prefers dark coffee",
"source": "user"
},
"lexicon_available": true,
"existing_episodes": [
{
"text": " nick prefers dark coffee "
}
]
},
"return": {
"verdict": "IGNORE",
"reason": "duplicate of an existing episode",
"provenance": {
"source": "user",
"session": ""
}
},
"expected": {
"verdict": "IGNORE",
"reason": "duplicate of an existing episode",
"provenance": {
"source": "user",
"session": ""
}
}
},
{
"name": "ev-punctuation-survives-not-duplicate",
"input": {
"event": {
"text": "NICK PREFERS dark coffee!",
"source": "user"
},
"lexicon_available": true,
"existing_episodes": [
{
"text": "nick prefers dark coffee"
}
]
},
"return": {
"verdict": "EPISODE",
"reason": "shares distinctive term(s): ['coffee', 'dark', 'nick']",
"provenance": {
"source": "user",
"session": ""
}
},
"expected": {
"verdict": "EPISODE",
"reason": "shares distinctive term(s): ['coffee', 'dark', 'nick']",
"provenance": {
"source": "user",
"session": ""
}
}
},
{
"name": "ev-null-episode-texts-ungrounded",
"input": {
"event": {
"text": "quantum flux patterns emerge here",
"source": "user"
},
"lexicon_available": true,
"existing_episodes": [
{
"text": null
},
{}
]
},
"return": {
"verdict": "EVIDENCE",
"reason": "fact-shaped, ungrounded \u2014 stored as evidence, not episode",
"provenance": {
"source": "user",
"session": ""
}
},
"expected": {
"verdict": "EVIDENCE",
"reason": "fact-shaped, ungrounded \u2014 stored as evidence, not episode",
"provenance": {
"source": "user",
"session": ""
}
}
},
{
"name": "ev-lexicon-blocked-conservative-evidence",
"input": {
"event": {
"text": "she drinks oatmilk every single morning",
"source": "user"
},
"lexicon_available": false
},
"return": {
"verdict": "EVIDENCE",
"reason": "lexicon unavailable \u2014 conservative evidence admission",
"provenance": {
"source": "user",
"session": ""
}
},
"expected": {
"verdict": "EVIDENCE",
"reason": "lexicon unavailable \u2014 conservative evidence admission",
"provenance": {
"source": "user",
"session": ""
}
}
},
{
"name": "ev-lexicon-blocked-short-still-ignored",
"input": {
"event": {
"text": "hi",
"source": "user"
},
"lexicon_available": false
},
"return": {
"verdict": "IGNORE",
"reason": "empty or trivial (<15 chars)",
"provenance": {
"source": "user",
"session": ""
}
},
"expected": {
"verdict": "IGNORE",
"reason": "empty or trivial (<15 chars)",
"provenance": {
"source": "user",
"session": ""
}
}
},
{
"name": "ev-lexicon-blocked-duplicate-path-conservative",
"input": {
"event": {
"text": "nick prefers dark coffee",
"source": "user"
},
"lexicon_available": false,
"existing_episodes": [
{
"text": "nick prefers dark coffee"
}
]
},
"return": {
"verdict": "EVIDENCE",
"reason": "lexicon unavailable \u2014 conservative evidence admission",
"provenance": {
"source": "user",
"session": ""
}
},
"expected": {
"verdict": "EVIDENCE",
"reason": "lexicon unavailable \u2014 conservative evidence admission",
"provenance": {
"source": "user",
"session": ""
}
}
},
{
"name": "ev-framing-only-no-lexical-signal",
"input": {
"event": {
"text": "work thing way always",
"source": "user"
},
"lexicon_available": true
},
"return": {
"verdict": "IGNORE",
"reason": "no lexical signal (framing-only)",
"provenance": {
"source": "user",
"session": ""
}
},
"expected": {
"verdict": "IGNORE",
"reason": "no lexical signal (framing-only)",
"provenance": {
"source": "user",
"session": ""
}
}
},
{
"name": "ev-episode-shared-single-term",
"input": {
"event": {
"text": "she never jokes about oatmilk at all",
"source": "user"
},
"lexicon_available": true,
"existing_episodes": [
{
"text": "i love oatmilk a lot"
}
],
"corpus_texts": [
"oatmilk is life",
"tea later day"
]
},
"return": {
"verdict": "EPISODE",
"reason": "shares distinctive term(s): ['oatmilk']",
"provenance": {
"source": "user",
"session": ""
}
},
"expected": {
"verdict": "EPISODE",
"reason": "shares distinctive term(s): ['oatmilk']",
"provenance": {
"source": "user",
"session": ""
}
}
},
{
"name": "ev-episode-shared-multiple-sorted",
"input": {
"event": {
"text": "oatmilk and celesta every morning",
"source": "user"
},
"lexicon_available": true,
"existing_episodes": [
{
"text": "oatmilk and celesta"
}
],
"corpus_texts": [
"oatmilk bar",
"celesta hall"
]
},
"return": {
"verdict": "EPISODE",
"reason": "shares distinctive term(s): ['celesta', 'oatmilk']",
"provenance": {
"source": "user",
"session": ""
}
},
"expected": {
"verdict": "EPISODE",
"reason": "shares distinctive term(s): ['celesta', 'oatmilk']",
"provenance": {
"source": "user",
"session": ""
}
}
},
{
"name": "ev-episode-shared-sorted-top3",
"input": {
"event": {
"text": "oatmilk celesta zephyr quartz ritual",
"source": "user"
},
"lexicon_available": true,
"existing_episodes": [
{
"text": "oatmilk celesta zephyr quartz"
}
],
"corpus_texts": [
"oatmilk celesta zephyr quartz notes"
]
},
"return": {
"verdict": "EPISODE",
"reason": "shares distinctive term(s): ['celesta', 'oatmilk', 'quartz']",
"provenance": {
"source": "user",
"session": ""
}
},
"expected": {
"verdict": "EPISODE",
"reason": "shares distinctive term(s): ['celesta', 'oatmilk', 'quartz']",
"provenance": {
"source": "user",
"session": ""
}
}
},
{
"name": "ev-episode-novel-axis-grounded",
"input": {
"event": {
"text": "celesta practice daily ritual",
"source": "session"
},
"lexicon_available": true,
"existing_episodes": [
{
"text": "i love oatmilk"
}
],
"corpus_texts": [
"she plays the celesta nightly"
]
},
"return": {
"verdict": "EPISODE",
"reason": "novel axis grounded in lived material",
"provenance": {
"source": "session",
"session": ""
}
},
"expected": {
"verdict": "EPISODE",
"reason": "novel axis grounded in lived material",
"provenance": {
"source": "session",
"session": ""
}
}
},
{
"name": "ev-evidence-fact-shaped-ungrounded",
"input": {
"event": {
"text": "quantum flux patterns emerge",
"source": "curator"
},
"lexicon_available": true,
"existing_episodes": [
{
"text": "i love oatmilk"
}
],
"corpus_texts": [
"oatmilk is life"
]
},
"return": {
"verdict": "EVIDENCE",
"reason": "fact-shaped, ungrounded \u2014 stored as evidence, not episode",
"provenance": {
"source": "curator",
"session": ""
}
},
"expected": {
"verdict": "EVIDENCE",
"reason": "fact-shaped, ungrounded \u2014 stored as evidence, not episode",
"provenance": {
"source": "curator",
"session": ""
}
}
},
{
"name": "ev-evidence-no-episodes-no-corpus",
"input": {
"event": {
"text": "quantum flux patterns emerge",
"source": "curator"
},
"lexicon_available": true
},
"return": {
"verdict": "EVIDENCE",
"reason": "fact-shaped, ungrounded \u2014 stored as evidence, not episode",
"provenance": {
"source": "curator",
"session": ""
}
},
"expected": {
"verdict": "EVIDENCE",
"reason": "fact-shaped, ungrounded \u2014 stored as evidence, not episode",
"provenance": {
"source": "curator",
"session": ""
}
}
},
{
"name": "ev-episode-stem-grounded-default-corpus",
"input": {
"event": {
"text": "she migrated north yesterday",
"source": "session"
},
"lexicon_available": true,
"existing_episodes": [
{
"text": "the migration of songbirds"
}
]
},
"return": {
"verdict": "EPISODE",
"reason": "shares distinctive term(s): ['migrated']",
"provenance": {
"source": "session",
"session": ""
}
},
"expected": {
"verdict": "EPISODE",
"reason": "shares distinctive term(s): ['migrated']",
"provenance": {
"source": "session",
"session": ""
}
}
},
{
"name": "ev-corpus-override-share-killed-novel-grounded",
"input": {
"event": {
"text": "she never jokes about oatmilk ok",
"source": "user"
},
"lexicon_available": true,
"existing_episodes": [
{
"text": "i love oatmilk"
}
],
"corpus_texts": [
"oatmilk",
"oatmilk",
"oatmilk",
"oatmilk",
"oatmilk",
"oatmilk",
"oatmilk",
"oatmilk",
"oatmilk",
"oatmilk",
"oatmilk",
"oatmilk",
"oatmilk",
"oatmilk",
"oatmilk",
"oatmilk",
"oatmilk",
"oatmilk",
"oatmilk",
"oatmilk",
"oatmilk",
"oatmilk",
"oatmilk",
"oatmilk",
"oatmilk",
"oatmilk",
"oatmilk",
"oatmilk",
"oatmilk",
"oatmilk"
]
},
"return": {
"verdict": "EPISODE",
"reason": "novel axis grounded in lived material",
"provenance": {
"source": "user",
"session": ""
}
},
"expected": {
"verdict": "EPISODE",
"reason": "novel axis grounded in lived material",
"provenance": {
"source": "user",
"session": ""
}
}
},
{
"name": "ev-episode-no-episodes-corpus-grounded",
"input": {
"event": {
"text": "she drinks oatmilk daily",
"source": "user"
},
"lexicon_available": true,
"corpus_texts": [
"oatmilk is life"
]
},
"return": {
"verdict": "EPISODE",
"reason": "novel axis grounded in lived material",
"provenance": {
"source": "user",
"session": ""
}
},
"expected": {
"verdict": "EPISODE",
"reason": "novel axis grounded in lived material",
"provenance": {
"source": "user",
"session": ""
}
}
},
{
"name": "ev-episode-default-corpus-is-episodes",
"input": {
"event": {
"text": "she drinks oatmilk daily",
"source": "user"
},
"lexicon_available": true,
"existing_episodes": [
{
"text": "oatmilk rituals fill the morning"
}
]
},
"return": {
"verdict": "EPISODE",
"reason": "shares distinctive term(s): ['oatmilk']",
"provenance": {
"source": "user",
"session": ""
}
},
"expected": {
"verdict": "EPISODE",
"reason": "shares distinctive term(s): ['oatmilk']",
"provenance": {
"source": "user",
"session": ""
}
}
}
]
}
}
+346
View File
@@ -0,0 +1,346 @@
//! experience_encoding — #153: canonical experience encoding and episodic
//! admission (the write-side authority).
//!
//! FULL name-preserving port of src/rune/experience_encoding.py (dichotomy
//! wave, #2479 tranche). ONE rule for what becomes persistent lived
//! experience. Not every event becomes an episode. Event → evaluate →
//! classify → attach provenance:
//!
//! ```text
//! IGNORE — noise (empty, duplicate of an existing episode, trivia
//! with no distinctive signal)
//! EVIDENCE — a fact worth the store (grounded, attributed, distinctive)
//! EPISODE — a lived experience (a behaviour refinement, an identity
//! value, a correction — something that changes future action)
//! ```
//!
//! Admission uses the lexicon discipline (distinctive terms, not common
//! words) + grounding (traces to real material) — the same meta-judgement
//! as retrieval and evolution, so growth and admission speak one lexicon.
//! The lexical decisions cross to the lexicon_evolution sibling crate
//! IN-PROCESS (the exact mirror of the oracle's `import lexicon_evolution`
//! call — library dependency, no FFI hop).
//!
//! FFI (doc-envelope law): one symbol, takes one JSON doc and returns
//! {"ok": true, "value": ...} | {"ok": false, "err": ...}:
//! * ee_evaluate({event, existing_episodes, corpus_texts,
//! lexicon_available}) → {"verdict", "reason", "provenance"}
//!
//! Lexicon-seam law: the oracle's flat `import lexicon_evolution` is an
//! ENVIRONMENT seam — when the module is missing, every event that passes
//! the empty/short gate is admitted conservatively as evidence. The
//! payload's `lexicon_available` flag pins that seam so the frozen truth
//! (and this battery) can replay the ImportError branch; the empty gate
//! runs BEFORE the seam (the oracle's short-circuit ordering).
//!
//! No ee_free_string export — sibling crates sharing the Python process
//! already export identical free symbols; callers free through
//! capability_scoring's cs_free_string.
//!
//! Python semantics preserved exactly:
//! * (text or "") coercion — null/missing text is the empty string,
//! then .strip(); the gate is len(text) < 15 over CHARACTERS
//! * provenance source/session: dict.get defaults apply only when the
//! KEY is missing — present-but-null pins as null (quirk-for-quirk)
//! * duplicate test = " ".join(text.lower().split()) equality (the
//! episode side coerces missing/null text to "")
//! * the duplicate and lexical-signal checks run INSIDE the lexicon
//! availability seam; blocked lexicon beats a would-be duplicate
//! * corpus = list(corpus_texts) or the episodes' texts (empty and
//! absent corpus_texts both fall back)
//! * EPISODE share reason is the oracle's f-string:
//! sorted(shared)[:3] rendered as a Python list literal
//!
//! Language-advantage pass (Phase B, battery-pinned): the verdict lattice
//! is an `Admission` enum with an exhaustive `as_str` match, the reason
//! strings are a `Reason` enum with Display (the EPISODE share reason
//! materializes its sorted-top3 list lazily, one `Decision` enum driving
//! both projections), the duplicate scan compares whitespace-token
//! iterators over the lowered texts WITHOUT materializing either
//! normalized join, the episode text extraction is a zero-copy
//! filter_map over borrowed episode values, and the oracle's gate flow
//! is a Result-free early-return pipeline whose every arm is matched
//! exhaustively. Battery green throughout (unchanged from the parity
//! commit — the whole-doc replay pins the refactor).
use serde_json::{json, Value};
use std::collections::BTreeSet;
use std::fmt;
use std::os::raw::c_char;
use lexicon_evolution::{content_words, grounded, shared_distinctive};
// ── verdict lattice (enums, not string soup) ────────────────────────────────
/// The admission verdict — the oracle's three-way classification.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
enum Admission {
Ignore,
Evidence,
Episode,
}
impl Admission {
fn as_str(self) -> &'static str {
match self {
Admission::Ignore => "IGNORE",
Admission::Evidence => "EVIDENCE",
Admission::Episode => "EPISODE",
}
}
}
/// The admission reason — the oracle's reason strings, byte-for-byte.
enum Reason {
/// "empty or trivial (<15 chars)"
Trivial,
/// "duplicate of an existing episode"
Duplicate,
/// "no lexical signal (framing-only)"
NoLexicalSignal,
/// "shares distinctive term(s): [...]" — sorted, top 3
Shares(BTreeSet<String>),
/// "novel axis grounded in lived material"
NovelGrounded,
/// "fact-shaped, ungrounded — stored as evidence, not episode"
Ungrounded,
/// "lexicon unavailable — conservative evidence admission"
LexiconUnavailable,
}
impl fmt::Display for Reason {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Reason::Trivial => write!(f, "empty or trivial (<15 chars)"),
Reason::Duplicate => write!(f, "duplicate of an existing episode"),
Reason::NoLexicalSignal => write!(f, "no lexical signal (framing-only)"),
Reason::Shares(shared) => {
let top: Vec<String> = shared
.iter()
.take(3)
.map(|s| format!("'{}'", s))
.collect();
write!(f, "shares distinctive term(s): [{}]", top.join(", "))
}
Reason::NovelGrounded => write!(f, "novel axis grounded in lived material"),
Reason::Ungrounded => {
write!(f, "fact-shaped, ungrounded — stored as evidence, not episode")
}
Reason::LexiconUnavailable => {
write!(f, "lexicon unavailable — conservative evidence admission")
}
}
}
}
/// One admission decision: verdict + reason, matched exhaustively.
enum Decision {
Ignore(Reason),
Evidence(Reason),
Episode(Reason),
}
impl Decision {
fn verdict(&self) -> Admission {
match self {
Decision::Ignore(_) => Admission::Ignore,
Decision::Evidence(_) => Admission::Evidence,
Decision::Episode(_) => Admission::Episode,
}
}
fn reason(&self) -> &Reason {
match self {
Decision::Ignore(r) | Decision::Evidence(r) | Decision::Episode(r) => r,
}
}
}
// ── event extraction ─────────────────────────────────────────────────────────
/// (event.get("text") or "").strip() — null/missing text is the empty
/// string; other JSON types are outside the oracle's domain (the oracle
/// would crash on a truthy non-string) and coerce to empty here.
fn event_text(event: &Value) -> &str {
match event.get("text") {
Some(Value::String(s)) => s.trim(),
_ => "",
}
}
/// dict.get(key, default) — the default applies ONLY when the key is
/// missing; a present-but-null value pins as null (oracle quirk).
fn event_field(event: &Value, key: &str, default: &str) -> Value {
event
.get(key)
.cloned()
.unwrap_or_else(|| Value::String(default.to_string()))
}
/// The oracle's `e.get("text", "")` over the episode list: missing text is
/// "", present-null stays null at the JSON layer (both are empty/filtered
/// downstream — null → "" keeps the lexicon sibling's str domain).
fn episode_texts(episodes: &[Value]) -> Vec<String> {
episodes
.iter()
.filter_map(|ep| ep.get("text"))
.map(|t| t.as_str().unwrap_or("").to_string())
.collect()
}
/// The oracle's duplicate normalizer, comparison form: whitespace-token
/// iterators over the lowered texts, equal WITHOUT materializing either
/// " ".join(...) — zero normalized-join allocation.
fn normalized_eq(a: &str, b: &str) -> bool {
let low_a = a.to_lowercase();
let low_b = b.to_lowercase();
low_a.split_whitespace().eq(low_b.split_whitespace())
}
// ── the decision pipeline ────────────────────────────────────────────────────
/// `evaluate` — one event in, one admission decision out. `episodes` and
/// `corpus` are the JSON arrays (empty = the oracle's falsy fallback);
/// `lexicon_available` pins the oracle's flat-import environment seam.
fn decide(
event: &Value,
episodes: &[Value],
corpus_texts: &[Value],
lexicon_available: bool,
) -> Decision {
let text = event_text(event);
// 1. empty is noise (BEFORE the lexicon seam)
if text.chars().count() < 15 {
return Decision::Ignore(Reason::Trivial);
}
// 2-4. everything else lives inside the lexicon seam
if !lexicon_available {
return Decision::Evidence(Reason::LexiconUnavailable);
}
// 2. duplicate of an existing episode is noise (token-iterator compare)
if episodes
.iter()
.any(|ep| {
let ep_text = ep.get("text").and_then(Value::as_str).unwrap_or("");
normalized_eq(text, ep_text)
})
{
return Decision::Ignore(Reason::Duplicate);
}
// 3. no lexical signal is noise (framing-only trivia)
let terms = content_words(Some(text));
if terms.is_empty() {
return Decision::Ignore(Reason::NoLexicalSignal);
}
// 4. grounded + distinctive → EPISODE or EVIDENCE
let episode_strs = episode_texts(episodes);
let corpus: Vec<String> = if corpus_texts.is_empty() {
episode_strs.clone()
} else {
corpus_texts
.iter()
.map(|c| c.as_str().unwrap_or("").to_string())
.collect()
};
if grounded(text, &corpus, 1.0) {
let shared = shared_distinctive(text, &episode_strs, &corpus);
if shared.is_empty() {
return Decision::Episode(Reason::NovelGrounded);
}
return Decision::Episode(Reason::Shares(shared));
}
Decision::Evidence(Reason::Ungrounded)
}
/// The oracle's return shape: {verdict, reason, provenance}.
fn to_doc(event: &Value, decision: Decision) -> Value {
json!({
"verdict": decision.verdict().as_str(),
"reason": decision.reason().to_string(),
"provenance": {
"source": event_field(event, "source", "unknown"),
"session": event_field(event, "session_id", ""),
},
})
}
/// The oracle's public function shape: the full return doc.
fn evaluate(
event: &Value,
episodes: &[Value],
corpus_texts: &[Value],
lexicon_available: bool,
) -> Value {
to_doc(event, decide(event, episodes, corpus_texts, lexicon_available))
}
// ── FFI (doc-envelope law) ───────────────────────────────────────────────────
/// Typed FFI failure lattice behind the doc-envelope Result.
enum FfiError {
NullPtr,
Utf8(std::str::Utf8Error),
Json(serde_json::Error),
}
impl fmt::Display for FfiError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
FfiError::NullPtr => write!(f, "null argument pointer"),
FfiError::Utf8(e) => write!(f, "{e}"),
FfiError::Json(e) => write!(f, "{e}"),
}
}
}
fn read_arg(ptr: *const c_char) -> Result<Value, FfiError> {
if ptr.is_null() {
return Err(FfiError::NullPtr);
}
let x = unsafe { std::ffi::CStr::from_ptr(ptr) }
.to_str()
.map_err(FfiError::Utf8)?;
serde_json::from_str(x).map_err(FfiError::Json)
}
fn envelope(r: Result<Value, FfiError>) -> *mut c_char {
let v = match r {
Ok(x) => json!({"ok": true, "value": x}),
Err(e) => json!({"ok": false, "err": e.to_string()}),
};
std::ffi::CString::new(serde_json::to_string(&v).unwrap_or_default())
.map(|c| c.into_raw())
.unwrap_or(std::ptr::null_mut())
}
/// ee_evaluate({event, existing_episodes, corpus_texts, lexicon_available})
/// → {verdict, reason, provenance}
#[no_mangle]
pub extern "C" fn ee_evaluate(arg: *const c_char) -> *mut c_char {
envelope(read_arg(arg).map(|a| {
let event = a.get("event").cloned().unwrap_or(json!({}));
let episodes: Vec<Value> = a
.get("existing_episodes")
.and_then(Value::as_array)
.cloned()
.unwrap_or_default();
let corpus: Vec<Value> = a
.get("corpus_texts")
.and_then(Value::as_array)
.cloned()
.unwrap_or_default();
let lexicon = a
.get("lexicon_available")
.and_then(Value::as_bool)
.unwrap_or(true);
evaluate(&event, &episodes, &corpus, lexicon)
}))
}
#[cfg(test)]
mod branch_battery;
+105
View File
@@ -0,0 +1,105 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "itoa"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]]
name = "memchr"
version = "2.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
[[package]]
name = "mutation_guards"
version = "0.1.0"
dependencies = [
"serde_json",
]
[[package]]
name = "proc-macro2"
version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
dependencies = [
"proc-macro2",
]
[[package]]
name = "serde"
version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
dependencies = [
"serde_core",
]
[[package]]
name = "serde_core"
version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.154"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6"
dependencies = [
"itoa",
"memchr",
"serde",
"serde_core",
"zmij",
]
[[package]]
name = "syn"
version = "3.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01016da373cd8f7ef12624f796309f5c31ba8d646dd08856c02cd741d823c622"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563"
[[package]]
name = "zmij"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
+12
View File
@@ -0,0 +1,12 @@
[package]
name = "mutation_guards"
version = "0.1.0"
edition = "2021"
description = "high-value cognitive-state mutation guards (#156, dichotomy wave #2479) — FULL Rust port of src/rune/mutation_guards.py; zero-dep beyond serde_json"
[lib]
name = "mutation_guards"
crate-type = ["rlib", "cdylib", "staticlib"]
[dependencies]
serde_json = "1"
@@ -0,0 +1,54 @@
//! mutation_guards branch battery — parity vs the frozen Python-oracle
//! truth (frozen_mg_truth.json: 43 cases across both public functions and
//! every branch — the unattributed identity-family DENY and its precedence
//! over read ops and the destructive gate (falsy 0/None attribution), the
//! destructive-unbacked DENY with its op/kind severity-2 boundary and the
//! falsy-zero backup, CONFIRM via score >= 4 (the exact boundary) and via
//! identity-kind + mutating-op, truthy non-bool backups, the ALLOW
//! lattice with unknown kind/op/scope severity defaults, null kind/operation
//! rendered as Python str(None) in the reason f-strings, and guard_mutation's
//! dict.get defaults (missing-key default vs present-null falsy passthrough,
//! extra keys ignored, empty-mutation defaults)).
//! Frozen via scripts/gen_mutation_guards_truth.py with RUNE_MG_CORE=python
//! pinned (poisoned-truth law). Embedded VERBATIM — never transcribed. Each
//! case replays the oracle's full return shape (verdict + reason + recovery)
//! and compares whole-doc.
use crate::{classify_mutation, guard_mutation};
use serde_json::{json, Value};
const TRUTH: &str = include_str!("frozen_mg_truth.json");
fn run(group: &str, case: &Value) -> Value {
let input = &case["input"];
if group == "classify" {
let unset = Value::Null;
let g = |k: &str| input.get(k).unwrap_or(&unset);
classify_mutation(
g("target_kind"),
g("operation"),
g("scope"),
g("attributed"),
g("backed_up"),
)
} else {
let mutation = input.get("mutation").cloned().unwrap_or(json!({}));
guard_mutation(&mutation)
}
}
#[test]
fn mutation_guards_parity_battery() {
let truth: Value = serde_json::from_str(TRUTH).unwrap();
for (group, cases) in truth["cases"].as_object().unwrap() {
for case in cases.as_array().unwrap() {
let got = run(group, case);
assert_eq!(
&got,
&case["expected"],
"case '{}' diverged",
case["name"].as_str().unwrap()
);
}
}
}
@@ -0,0 +1,869 @@
{
"frozen": "2026-10-12T12:16:17.572030+00:00",
"oracle": "src/rune/mutation_guards.py",
"pin": "RUNE_MG_CORE=python",
"note": "Frozen via scripts/gen_mutation_guards_truth.py against the live Python oracle. Battery embeds this VERBATIM.",
"cases": {
"classify": [
{
"name": "cl-deny1-unattributed-identity-overwrite",
"input": {
"target_kind": "identity",
"operation": "overwrite",
"scope": "single",
"attributed": false,
"backed_up": false
},
"return": {
"verdict": "DENY",
"reason": "unattributed overwrite of identity \u2014 identity mutations require attribution",
"recovery": "re-submit with an attributed source (user, curator, or contract)"
},
"expected": {
"verdict": "DENY",
"reason": "unattributed overwrite of identity \u2014 identity mutations require attribution",
"recovery": "re-submit with an attributed source (user, curator, or contract)"
}
},
{
"name": "cl-deny1-unattributed-persona-append-whole",
"input": {
"target_kind": "persona",
"operation": "append",
"scope": "whole",
"attributed": false,
"backed_up": false
},
"return": {
"verdict": "DENY",
"reason": "unattributed append of persona \u2014 identity mutations require attribution",
"recovery": "re-submit with an attributed source (user, curator, or contract)"
},
"expected": {
"verdict": "DENY",
"reason": "unattributed append of persona \u2014 identity mutations require attribution",
"recovery": "re-submit with an attributed source (user, curator, or contract)"
}
},
{
"name": "cl-deny1-constitution-attributed-zero",
"input": {
"target_kind": "constitution",
"operation": "append",
"scope": "single",
"attributed": 0,
"backed_up": false
},
"return": {
"verdict": "DENY",
"reason": "unattributed append of constitution \u2014 identity mutations require attribution",
"recovery": "re-submit with an attributed source (user, curator, or contract)"
},
"expected": {
"verdict": "DENY",
"reason": "unattributed append of constitution \u2014 identity mutations require attribution",
"recovery": "re-submit with an attributed source (user, curator, or contract)"
}
},
{
"name": "cl-deny1-identity-read-still-denied",
"input": {
"target_kind": "identity",
"operation": "read",
"scope": "single",
"attributed": false,
"backed_up": false
},
"return": {
"verdict": "DENY",
"reason": "unattributed read of identity \u2014 identity mutations require attribution",
"recovery": "re-submit with an attributed source (user, curator, or contract)"
},
"expected": {
"verdict": "DENY",
"reason": "unattributed read of identity \u2014 identity mutations require attribution",
"recovery": "re-submit with an attributed source (user, curator, or contract)"
}
},
{
"name": "cl-deny1-identity-attributed-null",
"input": {
"target_kind": "identity",
"operation": "append",
"scope": "single",
"attributed": null,
"backed_up": false
},
"return": {
"verdict": "DENY",
"reason": "unattributed append of identity \u2014 identity mutations require attribution",
"recovery": "re-submit with an attributed source (user, curator, or contract)"
},
"expected": {
"verdict": "DENY",
"reason": "unattributed append of identity \u2014 identity mutations require attribution",
"recovery": "re-submit with an attributed source (user, curator, or contract)"
}
},
{
"name": "cl-deny2-constitution-delete-whole",
"input": {
"target_kind": "constitution",
"operation": "delete",
"scope": "whole",
"attributed": true,
"backed_up": false
},
"return": {
"verdict": "DENY",
"reason": "delete of constitution (score 8) without a snapshot \u2014 destructive and unrecoverable",
"recovery": "snapshot first (state_checkpoint.snapshot), then re-submit"
},
"expected": {
"verdict": "DENY",
"reason": "delete of constitution (score 8) without a snapshot \u2014 destructive and unrecoverable",
"recovery": "snapshot first (state_checkpoint.snapshot), then re-submit"
}
},
{
"name": "cl-deny2-skill-delete-multi",
"input": {
"target_kind": "skill",
"operation": "delete",
"scope": "multi",
"attributed": true,
"backed_up": false
},
"return": {
"verdict": "DENY",
"reason": "delete of skill (score 6) without a snapshot \u2014 destructive and unrecoverable",
"recovery": "snapshot first (state_checkpoint.snapshot), then re-submit"
},
"expected": {
"verdict": "DENY",
"reason": "delete of skill (score 6) without a snapshot \u2014 destructive and unrecoverable",
"recovery": "snapshot first (state_checkpoint.snapshot), then re-submit"
}
},
{
"name": "cl-deny2-belief-overwrite-whole-boundary",
"input": {
"target_kind": "belief",
"operation": "overwrite",
"scope": "whole",
"attributed": true,
"backed_up": false
},
"return": {
"verdict": "DENY",
"reason": "overwrite of belief (score 6) without a snapshot \u2014 destructive and unrecoverable",
"recovery": "snapshot first (state_checkpoint.snapshot), then re-submit"
},
"expected": {
"verdict": "DENY",
"reason": "overwrite of belief (score 6) without a snapshot \u2014 destructive and unrecoverable",
"recovery": "snapshot first (state_checkpoint.snapshot), then re-submit"
}
},
{
"name": "cl-deny2-skill-overwrite-whole-backed-zero",
"input": {
"target_kind": "skill",
"operation": "overwrite",
"scope": "whole",
"attributed": true,
"backed_up": 0
},
"return": {
"verdict": "DENY",
"reason": "overwrite of skill (score 6) without a snapshot \u2014 destructive and unrecoverable",
"recovery": "snapshot first (state_checkpoint.snapshot), then re-submit"
},
"expected": {
"verdict": "DENY",
"reason": "overwrite of skill (score 6) without a snapshot \u2014 destructive and unrecoverable",
"recovery": "snapshot first (state_checkpoint.snapshot), then re-submit"
}
},
{
"name": "cl-confirm-backed-skill-overwrite-whole",
"input": {
"target_kind": "skill",
"operation": "overwrite",
"scope": "whole",
"attributed": true,
"backed_up": true
},
"return": {
"verdict": "CONFIRM",
"reason": "overwrite of skill (score 6) \u2014 high-value state, explicit confirmation required",
"recovery": "confirm with the snapshot id attached"
},
"expected": {
"verdict": "CONFIRM",
"reason": "overwrite of skill (score 6) \u2014 high-value state, explicit confirmation required",
"recovery": "confirm with the snapshot id attached"
}
},
{
"name": "cl-confirm-identity-append-attributed",
"input": {
"target_kind": "identity",
"operation": "append",
"scope": "single",
"attributed": true,
"backed_up": false
},
"return": {
"verdict": "CONFIRM",
"reason": "append of identity (score 4) \u2014 high-value state, explicit confirmation required",
"recovery": "confirm with the snapshot id attached"
},
"expected": {
"verdict": "CONFIRM",
"reason": "append of identity (score 4) \u2014 high-value state, explicit confirmation required",
"recovery": "confirm with the snapshot id attached"
}
},
{
"name": "cl-confirm-identity-read-whole-score",
"input": {
"target_kind": "identity",
"operation": "read",
"scope": "whole",
"attributed": true,
"backed_up": false
},
"return": {
"verdict": "CONFIRM",
"reason": "read of identity (score 5) \u2014 high-value state, explicit confirmation required",
"recovery": "confirm with the snapshot id attached"
},
"expected": {
"verdict": "CONFIRM",
"reason": "read of identity (score 5) \u2014 high-value state, explicit confirmation required",
"recovery": "confirm with the snapshot id attached"
}
},
{
"name": "cl-confirm-operating-delete-single-score-4",
"input": {
"target_kind": "operating",
"operation": "delete",
"scope": "single",
"attributed": true,
"backed_up": false
},
"return": {
"verdict": "CONFIRM",
"reason": "delete of operating (score 4) \u2014 high-value state, explicit confirmation required",
"recovery": "confirm with the snapshot id attached"
},
"expected": {
"verdict": "CONFIRM",
"reason": "delete of operating (score 4) \u2014 high-value state, explicit confirmation required",
"recovery": "confirm with the snapshot id attached"
}
},
{
"name": "cl-confirm-fact-overwrite-whole",
"input": {
"target_kind": "fact",
"operation": "overwrite",
"scope": "whole",
"attributed": true,
"backed_up": false
},
"return": {
"verdict": "CONFIRM",
"reason": "overwrite of fact (score 5) \u2014 high-value state, explicit confirmation required",
"recovery": "confirm with the snapshot id attached"
},
"expected": {
"verdict": "CONFIRM",
"reason": "overwrite of fact (score 5) \u2014 high-value state, explicit confirmation required",
"recovery": "confirm with the snapshot id attached"
}
},
{
"name": "cl-confirm-skill-delete-multi-backed-truthy",
"input": {
"target_kind": "skill",
"operation": "delete",
"scope": "multi",
"attributed": true,
"backed_up": "yes"
},
"return": {
"verdict": "CONFIRM",
"reason": "delete of skill (score 6) \u2014 high-value state, explicit confirmation required",
"recovery": "confirm with the snapshot id attached"
},
"expected": {
"verdict": "CONFIRM",
"reason": "delete of skill (score 6) \u2014 high-value state, explicit confirmation required",
"recovery": "confirm with the snapshot id attached"
}
},
{
"name": "cl-confirm-constitution-delete-whole-backed",
"input": {
"target_kind": "constitution",
"operation": "delete",
"scope": "whole",
"attributed": true,
"backed_up": true
},
"return": {
"verdict": "CONFIRM",
"reason": "delete of constitution (score 8) \u2014 high-value state, explicit confirmation required",
"recovery": "confirm with the snapshot id attached"
},
"expected": {
"verdict": "CONFIRM",
"reason": "delete of constitution (score 8) \u2014 high-value state, explicit confirmation required",
"recovery": "confirm with the snapshot id attached"
}
},
{
"name": "cl-confirm-persona-overwrite-whole-backed",
"input": {
"target_kind": "persona",
"operation": "overwrite",
"scope": "whole",
"attributed": true,
"backed_up": true
},
"return": {
"verdict": "CONFIRM",
"reason": "overwrite of persona (score 7) \u2014 high-value state, explicit confirmation required",
"recovery": "confirm with the snapshot id attached"
},
"expected": {
"verdict": "CONFIRM",
"reason": "overwrite of persona (score 7) \u2014 high-value state, explicit confirmation required",
"recovery": "confirm with the snapshot id attached"
}
},
{
"name": "cl-confirm-archive-delete-whole-score",
"input": {
"target_kind": "archive",
"operation": "delete",
"scope": "whole",
"attributed": true,
"backed_up": false
},
"return": {
"verdict": "CONFIRM",
"reason": "delete of archive (score 5) \u2014 high-value state, explicit confirmation required",
"recovery": "confirm with the snapshot id attached"
},
"expected": {
"verdict": "CONFIRM",
"reason": "delete of archive (score 5) \u2014 high-value state, explicit confirmation required",
"recovery": "confirm with the snapshot id attached"
}
},
{
"name": "cl-confirm-unknown-kind-delete",
"input": {
"target_kind": "widget",
"operation": "delete",
"scope": "single",
"attributed": true,
"backed_up": false
},
"return": {
"verdict": "CONFIRM",
"reason": "delete of widget (score 4) \u2014 high-value state, explicit confirmation required",
"recovery": "confirm with the snapshot id attached"
},
"expected": {
"verdict": "CONFIRM",
"reason": "delete of widget (score 4) \u2014 high-value state, explicit confirmation required",
"recovery": "confirm with the snapshot id attached"
}
},
{
"name": "cl-allow-fact-append-single-defaults",
"input": {
"target_kind": "fact",
"operation": "append",
"scope": "single",
"attributed": true,
"backed_up": false
},
"return": {
"verdict": "ALLOW",
"reason": "append of fact (score 2) \u2014 small, reversible, attributed",
"recovery": ""
},
"expected": {
"verdict": "ALLOW",
"reason": "append of fact (score 2) \u2014 small, reversible, attributed",
"recovery": ""
}
},
{
"name": "cl-allow-fact-append-multi-score-3",
"input": {
"target_kind": "fact",
"operation": "append",
"scope": "multi",
"attributed": true,
"backed_up": false
},
"return": {
"verdict": "ALLOW",
"reason": "append of fact (score 3) \u2014 small, reversible, attributed",
"recovery": ""
},
"expected": {
"verdict": "ALLOW",
"reason": "append of fact (score 3) \u2014 small, reversible, attributed",
"recovery": ""
}
},
{
"name": "cl-allow-identity-read-single",
"input": {
"target_kind": "identity",
"operation": "read",
"scope": "single",
"attributed": true,
"backed_up": false
},
"return": {
"verdict": "ALLOW",
"reason": "read of identity (score 3) \u2014 small, reversible, attributed",
"recovery": ""
},
"expected": {
"verdict": "ALLOW",
"reason": "read of identity (score 3) \u2014 small, reversible, attributed",
"recovery": ""
}
},
{
"name": "cl-allow-archive-delete-single",
"input": {
"target_kind": "archive",
"operation": "delete",
"scope": "single",
"attributed": true,
"backed_up": false
},
"return": {
"verdict": "ALLOW",
"reason": "delete of archive (score 3) \u2014 small, reversible, attributed",
"recovery": ""
},
"expected": {
"verdict": "ALLOW",
"reason": "delete of archive (score 3) \u2014 small, reversible, attributed",
"recovery": ""
}
},
{
"name": "cl-allow-archive-read-whole",
"input": {
"target_kind": "archive",
"operation": "read",
"scope": "whole",
"attributed": true,
"backed_up": false
},
"return": {
"verdict": "ALLOW",
"reason": "read of archive (score 2) \u2014 small, reversible, attributed",
"recovery": ""
},
"expected": {
"verdict": "ALLOW",
"reason": "read of archive (score 2) \u2014 small, reversible, attributed",
"recovery": ""
}
},
{
"name": "cl-allow-fact-read-whole",
"input": {
"target_kind": "fact",
"operation": "read",
"scope": "whole",
"attributed": true,
"backed_up": false
},
"return": {
"verdict": "ALLOW",
"reason": "read of fact (score 3) \u2014 small, reversible, attributed",
"recovery": ""
},
"expected": {
"verdict": "ALLOW",
"reason": "read of fact (score 3) \u2014 small, reversible, attributed",
"recovery": ""
}
},
{
"name": "cl-allow-unknown-operation",
"input": {
"target_kind": "fact",
"operation": "nuke",
"scope": "multi",
"attributed": true,
"backed_up": false
},
"return": {
"verdict": "ALLOW",
"reason": "nuke of fact (score 3) \u2014 small, reversible, attributed",
"recovery": ""
},
"expected": {
"verdict": "ALLOW",
"reason": "nuke of fact (score 3) \u2014 small, reversible, attributed",
"recovery": ""
}
},
{
"name": "cl-allow-unknown-scope",
"input": {
"target_kind": "fact",
"operation": "append",
"scope": "galaxy",
"attributed": true,
"backed_up": false
},
"return": {
"verdict": "ALLOW",
"reason": "append of fact (score 2) \u2014 small, reversible, attributed",
"recovery": ""
},
"expected": {
"verdict": "ALLOW",
"reason": "append of fact (score 2) \u2014 small, reversible, attributed",
"recovery": ""
}
},
{
"name": "cl-allow-null-kind-renders-None",
"input": {
"target_kind": null,
"operation": "append",
"scope": "single",
"attributed": true,
"backed_up": false
},
"return": {
"verdict": "ALLOW",
"reason": "append of None (score 2) \u2014 small, reversible, attributed",
"recovery": ""
},
"expected": {
"verdict": "ALLOW",
"reason": "append of None (score 2) \u2014 small, reversible, attributed",
"recovery": ""
}
},
{
"name": "cl-allow-null-operation-renders-None",
"input": {
"target_kind": "fact",
"operation": null,
"scope": "single",
"attributed": true,
"backed_up": false
},
"return": {
"verdict": "ALLOW",
"reason": "None of fact (score 2) \u2014 small, reversible, attributed",
"recovery": ""
},
"expected": {
"verdict": "ALLOW",
"reason": "None of fact (score 2) \u2014 small, reversible, attributed",
"recovery": ""
}
},
{
"name": "cl-allow-null-scope",
"input": {
"target_kind": "fact",
"operation": "append",
"scope": null,
"attributed": true,
"backed_up": false
},
"return": {
"verdict": "ALLOW",
"reason": "append of fact (score 2) \u2014 small, reversible, attributed",
"recovery": ""
},
"expected": {
"verdict": "ALLOW",
"reason": "append of fact (score 2) \u2014 small, reversible, attributed",
"recovery": ""
}
}
],
"guard": [
{
"name": "gu-empty-dict-all-defaults",
"input": {
"mutation": {}
},
"return": {
"verdict": "ALLOW",
"reason": "append of fact (score 2) \u2014 small, reversible, attributed",
"recovery": ""
},
"expected": {
"verdict": "ALLOW",
"reason": "append of fact (score 2) \u2014 small, reversible, attributed",
"recovery": ""
}
},
{
"name": "gu-fact-append-allow",
"input": {
"mutation": {
"target_kind": "fact",
"operation": "append"
}
},
"return": {
"verdict": "ALLOW",
"reason": "append of fact (score 2) \u2014 small, reversible, attributed",
"recovery": ""
},
"expected": {
"verdict": "ALLOW",
"reason": "append of fact (score 2) \u2014 small, reversible, attributed",
"recovery": ""
}
},
{
"name": "gu-unattributed-identity-deny",
"input": {
"mutation": {
"target_kind": "identity",
"operation": "overwrite",
"attributed": false
}
},
"return": {
"verdict": "DENY",
"reason": "unattributed overwrite of identity \u2014 identity mutations require attribution",
"recovery": "re-submit with an attributed source (user, curator, or contract)"
},
"expected": {
"verdict": "DENY",
"reason": "unattributed overwrite of identity \u2014 identity mutations require attribution",
"recovery": "re-submit with an attributed source (user, curator, or contract)"
}
},
{
"name": "gu-unbacked-constitution-delete-deny",
"input": {
"mutation": {
"target_kind": "constitution",
"operation": "delete",
"scope": "whole",
"attributed": true,
"backed_up": false
}
},
"return": {
"verdict": "DENY",
"reason": "delete of constitution (score 8) without a snapshot \u2014 destructive and unrecoverable",
"recovery": "snapshot first (state_checkpoint.snapshot), then re-submit"
},
"expected": {
"verdict": "DENY",
"reason": "delete of constitution (score 8) without a snapshot \u2014 destructive and unrecoverable",
"recovery": "snapshot first (state_checkpoint.snapshot), then re-submit"
}
},
{
"name": "gu-backed-skill-overwrite-confirm",
"input": {
"mutation": {
"target_kind": "skill",
"operation": "overwrite",
"scope": "whole",
"attributed": true,
"backed_up": true
}
},
"return": {
"verdict": "CONFIRM",
"reason": "overwrite of skill (score 6) \u2014 high-value state, explicit confirmation required",
"recovery": "confirm with the snapshot id attached"
},
"expected": {
"verdict": "CONFIRM",
"reason": "overwrite of skill (score 6) \u2014 high-value state, explicit confirmation required",
"recovery": "confirm with the snapshot id attached"
}
},
{
"name": "gu-attributed-null-falsy-deny",
"input": {
"mutation": {
"target_kind": "identity",
"attributed": null
}
},
"return": {
"verdict": "DENY",
"reason": "unattributed append of identity \u2014 identity mutations require attribution",
"recovery": "re-submit with an attributed source (user, curator, or contract)"
},
"expected": {
"verdict": "DENY",
"reason": "unattributed append of identity \u2014 identity mutations require attribution",
"recovery": "re-submit with an attributed source (user, curator, or contract)"
}
},
{
"name": "gu-attributed-empty-list-falsy-deny",
"input": {
"mutation": {
"target_kind": "persona",
"operation": "delete",
"attributed": []
}
},
"return": {
"verdict": "DENY",
"reason": "unattributed delete of persona \u2014 identity mutations require attribution",
"recovery": "re-submit with an attributed source (user, curator, or contract)"
},
"expected": {
"verdict": "DENY",
"reason": "unattributed delete of persona \u2014 identity mutations require attribution",
"recovery": "re-submit with an attributed source (user, curator, or contract)"
}
},
{
"name": "gu-backed-up-zero-falsy-deny",
"input": {
"mutation": {
"target_kind": "skill",
"operation": "overwrite",
"scope": "whole",
"backed_up": 0
}
},
"return": {
"verdict": "DENY",
"reason": "overwrite of skill (score 6) without a snapshot \u2014 destructive and unrecoverable",
"recovery": "snapshot first (state_checkpoint.snapshot), then re-submit"
},
"expected": {
"verdict": "DENY",
"reason": "overwrite of skill (score 6) without a snapshot \u2014 destructive and unrecoverable",
"recovery": "snapshot first (state_checkpoint.snapshot), then re-submit"
}
},
{
"name": "gu-backed-up-truthy-string-confirm",
"input": {
"mutation": {
"target_kind": "skill",
"operation": "overwrite",
"scope": "whole",
"backed_up": "yes"
}
},
"return": {
"verdict": "CONFIRM",
"reason": "overwrite of skill (score 6) \u2014 high-value state, explicit confirmation required",
"recovery": "confirm with the snapshot id attached"
},
"expected": {
"verdict": "CONFIRM",
"reason": "overwrite of skill (score 6) \u2014 high-value state, explicit confirmation required",
"recovery": "confirm with the snapshot id attached"
}
},
{
"name": "gu-read-allows",
"input": {
"mutation": {
"target_kind": "fact",
"operation": "read"
}
},
"return": {
"verdict": "ALLOW",
"reason": "read of fact (score 1) \u2014 small, reversible, attributed",
"recovery": ""
},
"expected": {
"verdict": "ALLOW",
"reason": "read of fact (score 1) \u2014 small, reversible, attributed",
"recovery": ""
}
},
{
"name": "gu-extra-keys-ignored",
"input": {
"mutation": {
"target_kind": "fact",
"operation": "append",
"note": "x",
"weight": 9
}
},
"return": {
"verdict": "ALLOW",
"reason": "append of fact (score 2) \u2014 small, reversible, attributed",
"recovery": ""
},
"expected": {
"verdict": "ALLOW",
"reason": "append of fact (score 2) \u2014 small, reversible, attributed",
"recovery": ""
}
},
{
"name": "gu-null-target-kind-renders-None",
"input": {
"mutation": {
"target_kind": null
}
},
"return": {
"verdict": "ALLOW",
"reason": "append of None (score 2) \u2014 small, reversible, attributed",
"recovery": ""
},
"expected": {
"verdict": "ALLOW",
"reason": "append of None (score 2) \u2014 small, reversible, attributed",
"recovery": ""
}
},
{
"name": "gu-empty-operation-string",
"input": {
"mutation": {
"target_kind": "fact",
"operation": ""
}
},
"return": {
"verdict": "ALLOW",
"reason": " of fact (score 2) \u2014 small, reversible, attributed",
"recovery": ""
},
"expected": {
"verdict": "ALLOW",
"reason": " of fact (score 2) \u2014 small, reversible, attributed",
"recovery": ""
}
}
]
}
}
+376
View File
@@ -0,0 +1,376 @@
//! mutation_guards — #156: high-value cognitive-state mutation guards.
//!
//! FULL name-preserving port of src/rune/mutation_guards.py (dichotomy
//! wave, #2479 tranche).
//!
//! Large or destructive changes to Rune's most valuable learned state must
//! be visible, attributable, and recoverable. The guard classifies every
//! proposed mutation by destructiveness and returns a verdict:
//!
//! ```text
//! ALLOW — small, reversible, attributed change
//! CONFIRM — large or identity-touching: needs explicit confirmation
//! DENY — destructive without a recovery path (delete of unbacked
//! state, silent identity rewrite)
//! ```
//!
//! Classification (deterministic, no model):
//! - target kind: identity/persona/constitution > skill/belief block > fact
//! - operation: delete > overwrite > append
//! - scope: whole-block > multi-entry > single entry
//! - attribution: unattributed mutations to identity are DENY
//!
//! Recoverability: a CONFIRM/DENY verdict carries the recovery shape
//! (snapshot id required, or the reason none exists).
//!
//! FFI (doc-envelope law): one JSON doc in, {"ok": true, "value": ...} |
//! {"ok": false, "err": ...} out:
//! * mu_classify({target_kind, operation, scope, attributed, backed_up})
//! → {verdict, reason, recovery}
//! * mu_guard({mutation}) → {verdict, reason, recovery}
//!
//! No mu_free_string export — sibling crates sharing the Python process
//! already export identical free symbols; callers free through
//! capability_scoring's cs_free_string.
//!
//! Python semantics preserved exactly:
//! * severity-table lookups: unknown kind → 1, unknown op → 1,
//! unknown/null scope → 0
//! * branch order: the unattributed identity-family DENY runs FIRST
//! (it beats a read operation and the destructive gate), then the
//! destructive-unbacked DENY, then CONFIRM (score >= 4 or identity
//! kind with any mutating op), then ALLOW
//! * Python truthiness for `attributed`/`backed_up`: null/false/0/""/
//! empty array/empty object are falsy — non-empty strings are truthy
//! * guard_mutation's dict.get defaults apply only when the KEY is
//! missing; a present-but-null value passes through as falsy
//! * the reason f-strings render a null kind/operation as "None"
//! (Python str(None))
//!
//! Language-advantage pass (Phase B, battery-pinned): the classification
//! surface is parsed ONCE into `Kind`/`Op`/`Scope` enums (unknown values
//! become the exhaustive `Other` variant, preserving the oracle's
//! severity-default quirk), so every downstream rule — severity tables,
//! the identity-family guard, the destructive gate — is an exhaustive
//! match over typed variants instead of repeated string comparisons; the
//! verdict is a `Verdict` enum with `as_str`, and the whole return shape
//! is one `Decision` struct projected to JSON in a single place. Battery
//! green throughout (unchanged from the parity commit — the whole-doc
//! replay pins the refactor).
use serde_json::{json, Map, Value};
use std::os::raw::c_char;
// ── typed classification surface (parsed once, matched exhaustively) ───────
/// Target kind — the oracle's severity ladder; unknown kinds keep the
/// default severity 1 (the `Other` variant).
#[derive(Clone, Copy, PartialEq, Eq)]
enum Kind {
Constitution,
Identity,
Persona,
Skill,
Belief,
Operating,
Fact,
Archive,
Other,
}
impl Kind {
fn parse(v: &Value) -> Self {
match v.as_str() {
Some("constitution") => Kind::Constitution,
Some("identity") => Kind::Identity,
Some("persona") => Kind::Persona,
Some("skill") => Kind::Skill,
Some("belief") => Kind::Belief,
Some("operating") => Kind::Operating,
Some("fact") => Kind::Fact,
Some("archive") => Kind::Archive,
_ => Kind::Other,
}
}
/// Target-kind severity (higher = more protected)
fn severity(self) -> i64 {
match self {
Kind::Constitution | Kind::Identity | Kind::Persona => 3,
Kind::Skill | Kind::Belief => 2,
Kind::Operating | Kind::Fact | Kind::Other => 1,
Kind::Archive => 0,
}
}
fn is_identity_family(self) -> bool {
matches!(self, Kind::Identity | Kind::Persona | Kind::Constitution)
}
}
/// Operation — the oracle's destructive-force ladder; unknown operations
/// keep the default severity 1 (the `Other` variant).
#[derive(Clone, Copy)]
enum Op {
Delete,
Overwrite,
Append,
Read,
Other,
}
impl Op {
fn parse(v: &Value) -> Self {
match v.as_str() {
Some("delete") => Op::Delete,
Some("overwrite") => Op::Overwrite,
Some("append") => Op::Append,
Some("read") => Op::Read,
_ => Op::Other,
}
}
/// Operation severity
fn severity(self) -> i64 {
match self {
Op::Delete => 3,
Op::Overwrite => 2,
Op::Append | Op::Other => 1,
Op::Read => 0,
}
}
}
/// Mutation scope; unknown/null scopes keep the default severity 0 (the
/// `Other` variant).
#[derive(Clone, Copy)]
enum Scope {
Whole,
Multi,
Single,
Other,
}
impl Scope {
fn parse(v: &Value) -> Self {
match v.as_str() {
Some("whole") => Scope::Whole,
Some("multi") => Scope::Multi,
Some("single") => Scope::Single,
_ => Scope::Other,
}
}
fn severity(self) -> i64 {
match self {
Scope::Whole => 2,
Scope::Multi => 1,
Scope::Single | Scope::Other => 0,
}
}
}
/// The classification verdict — the oracle's three-way lattice.
#[derive(Clone, Copy, PartialEq, Eq)]
enum Verdict {
Allow,
Confirm,
Deny,
}
impl Verdict {
fn as_str(self) -> &'static str {
match self {
Verdict::Allow => "ALLOW",
Verdict::Confirm => "CONFIRM",
Verdict::Deny => "DENY",
}
}
}
/// One classification outcome: verdict + reason + recovery, projected to
/// the oracle's return shape in one place.
struct Decision {
verdict: Verdict,
reason: String,
recovery: &'static str,
}
impl Decision {
fn to_doc(&self) -> Value {
json!({
"verdict": self.verdict.as_str(),
"reason": self.reason,
"recovery": self.recovery,
})
}
}
/// Python truthiness over JSON values (null/false/0/""/[]/{} are falsy).
fn truthy(v: &Value) -> bool {
match v {
Value::Null => false,
Value::Bool(b) => *b,
Value::Number(n) => n.as_f64().map(|f| f != 0.0).unwrap_or(true),
Value::String(s) => !s.is_empty(),
Value::Array(a) => !a.is_empty(),
Value::Object(o) => !o.is_empty(),
}
}
/// Python str() over the JSON value for the reason f-strings: a null kind
/// or operation renders "None", bools render "True"/"False".
fn py_display(v: &Value) -> String {
match v {
Value::Null => "None".to_string(),
Value::String(s) => s.clone(),
Value::Bool(true) => "True".to_string(),
Value::Bool(false) => "False".to_string(),
other => other.to_string(),
}
}
// ── the classification core ──────────────────────────────────────────────────
/// `classify_mutation` — classify a proposed mutation.
/// Returns {verdict, reason, recovery}.
pub fn classify_mutation(
target_kind: &Value,
operation: &Value,
scope: &Value,
attributed: &Value,
backed_up: &Value,
) -> Value {
let kind = Kind::parse(target_kind);
let op = Op::parse(operation);
let scope = Scope::parse(scope);
let kind_sev = kind.severity();
let op_sev = op.severity();
let score = kind_sev + op_sev + scope.severity();
let kind_str = py_display(target_kind);
let op_str = py_display(operation);
let decision = if kind.is_identity_family() && !truthy(attributed) {
// Unattributed identity mutation is always DENY (the silent-rewrite
// class) — runs FIRST: it beats a read op and the destructive gate.
Decision {
verdict: Verdict::Deny,
reason: format!(
"unattributed {op_str} of {kind_str} — identity mutations require attribution"
),
recovery: "re-submit with an attributed source (user, curator, or contract)",
}
} else if is_destructive_unbacked(kind, op, backed_up) {
// Destructive without recovery is DENY
Decision {
verdict: Verdict::Deny,
reason: format!(
"{op_str} of {kind_str} (score {score}) without a snapshot — destructive and unrecoverable"
),
recovery: "snapshot first (state_checkpoint.snapshot), then re-submit",
}
} else if score >= 4 || (kind_sev >= 3 && op_sev >= 1) {
// Large or identity-touching needs confirmation
Decision {
verdict: Verdict::Confirm,
reason: format!(
"{op_str} of {kind_str} (score {score}) — high-value state, explicit confirmation required"
),
recovery: "confirm with the snapshot id attached",
}
} else {
Decision {
verdict: Verdict::Allow,
reason: format!(
"{op_str} of {kind_str} (score {score}) — small, reversible, attributed"
),
recovery: "",
}
};
decision.to_doc()
}
/// Destructive (op severity >= 2) on a protected kind (severity >= 2)
/// without a backup — the unrecoverable class.
fn is_destructive_unbacked(kind: Kind, op: Op, backed_up: &Value) -> bool {
let destructive = match op {
Op::Delete | Op::Overwrite => true,
Op::Append | Op::Read | Op::Other => false,
};
let protected = match kind {
Kind::Constitution | Kind::Identity | Kind::Persona | Kind::Skill | Kind::Belief => true,
Kind::Operating | Kind::Fact | Kind::Archive | Kind::Other => false,
};
destructive && protected && !truthy(backed_up)
}
/// `guard_mutation` — guard one mutation dict {target_kind, operation,
/// scope, attributed, backed_up}. Returns the verdict dict.
pub fn guard_mutation(mutation: &Value) -> Value {
let empty = Map::new();
let m = mutation.as_object().unwrap_or(&empty);
let d_kind = Value::String("fact".into());
let d_op = Value::String("append".into());
let d_scope = Value::String("single".into());
let d_attributed = Value::Bool(true);
let d_backed_up = Value::Bool(false);
// dict.get(key, default): the default applies ONLY when the key is
// missing; a present-but-null value passes through as falsy.
let target_kind = m.get("target_kind").unwrap_or(&d_kind);
let operation = m.get("operation").unwrap_or(&d_op);
let scope = m.get("scope").unwrap_or(&d_scope);
let attributed = m.get("attributed").unwrap_or(&d_attributed);
let backed_up = m.get("backed_up").unwrap_or(&d_backed_up);
classify_mutation(target_kind, operation, scope, attributed, backed_up)
}
// ── FFI (doc-envelope law) ───────────────────────────────────────────────────
fn read_arg(ptr: *const c_char) -> Result<Value, String> {
if ptr.is_null() {
return Err("null argument pointer".to_string());
}
let x = unsafe { std::ffi::CStr::from_ptr(ptr) }
.to_str()
.map_err(|e| e.to_string())?;
serde_json::from_str(x).map_err(|e| e.to_string())
}
fn envelope(r: Result<Value, String>) -> *mut c_char {
let v = match r {
Ok(x) => json!({"ok": true, "value": x}),
Err(e) => json!({"ok": false, "err": e}),
};
std::ffi::CString::new(serde_json::to_string(&v).unwrap_or_default())
.map(|c| c.into_raw())
.unwrap_or(std::ptr::null_mut())
}
/// mu_classify({target_kind, operation, scope, attributed, backed_up})
/// → {verdict, reason, recovery}
#[no_mangle]
pub extern "C" fn mu_classify(arg: *const c_char) -> *mut c_char {
envelope(read_arg(arg).map(|a| {
let unset = Value::Null;
let g = |k: &str| a.get(k).unwrap_or(&unset);
classify_mutation(
g("target_kind"),
g("operation"),
g("scope"),
g("attributed"),
g("backed_up"),
)
}))
}
/// mu_guard({mutation}) → {verdict, reason, recovery}
#[no_mangle]
pub extern "C" fn mu_guard(arg: *const c_char) -> *mut c_char {
envelope(read_arg(arg).map(|a| {
let mutation = a.get("mutation").cloned().unwrap_or(Value::Null);
guard_mutation(&mutation)
}))
}
#[cfg(test)]
mod branch_battery;
+14
View File
@@ -0,0 +1,14 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "capability_scoring"
version = "0.1.0"
[[package]]
name = "spine_language"
version = "0.1.0"
dependencies = [
"capability_scoring",
]
+12
View File
@@ -0,0 +1,12 @@
[package]
name = "spine_language"
version = "0.1.0"
edition = "2021"
description = "#2465 doc-level transforms (language placement, discovery-ID normalization, contract-discovery write-back) — dichotomy wave #2479 tranche; deterministic spine transforms in Rust; zero-dep (pyjson via capability_scoring)"
[lib]
name = "spine_language"
crate-type = ["rlib", "cdylib", "staticlib"]
[dependencies]
capability_scoring = { path = "../capability_scoring" }
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,153 @@
//! branch_battery — replays frozen_sl_truth.json VERBATIM (23 cases
//! frozen from the live Python oracle with RUNE_SL_CORE=python pinned;
//! inputs deep-copied before capture; wall-clock observed_at masked on
//! BOTH sides — the module's only nondeterministic output field).
//! Order-insensitive object comparison; per-case store fixtures
//! (classification.json / scaffold current.json) are materialized under
//! a fresh temp root exactly as the freezing run did.
use capability_scoring::pyjson::{parse, Json};
use std::sync::atomic::{AtomicU32, Ordering};
use crate::spine;
const TRUTH: &str = include_str!("../frozen_sl_truth.json");
static TMP_COUNTER: AtomicU32 = AtomicU32::new(0);
fn jeq(a: &Json, b: &Json) -> bool {
match (a, b) {
(Json::Num(x), Json::Num(y)) => x == y,
(Json::Str(x), Json::Str(y)) => x == y,
(Json::Bool(x), Json::Bool(y)) => x == y,
(Json::Null, Json::Null) => true,
(Json::Arr(x), Json::Arr(y)) => {
x.len() == y.len() && x.iter().zip(y).all(|(i, j)| jeq(i, j))
}
(Json::Obj(x), Json::Obj(y)) => {
x.len() == y.len()
&& x.iter()
.all(|(k, v)| y.iter().any(|(k2, v2)| k == k2 && jeq(v, v2)))
}
_ => false,
}
}
/// Materialize the case's fixture files under a fresh temp root.
fn materialize(files: &Json) -> std::path::PathBuf {
let root = std::env::temp_dir().join(format!(
"sl-battery-{}-{}",
std::process::id(),
TMP_COUNTER.fetch_add(1, Ordering::Relaxed)
));
if let Json::Obj(pairs) = files {
for (rel, content) in pairs {
let path = root.join(rel);
if let Some(parent) = path.parent() {
let _ = std::fs::create_dir_all(parent);
}
let text = match content {
Json::Str(s) => s.clone(),
other => capability_scoring::pyjson::dumps(other),
};
let _ = std::fs::write(path, text);
}
}
root
}
fn run_case(fn_name: &str, case: &Json) -> Result<Json, String> {
let root = materialize(case.get("files").unwrap_or(&Json::Null));
let out = match fn_name {
"classification_sides" => Ok(spine::classification_sides(&root)),
"place_languages" => {
let mut doc = case.get("doc").cloned().unwrap_or(Json::Obj(vec![]));
spine::place_languages(&mut doc, &root)
.map(|placed| crate::result_doc(doc, "placed", placed))
}
"contract_discoveries" => {
let found = spine::contract_discoveries(&root)?;
Ok(Json::Arr(
found
.into_iter()
.map(|(cid, items)| {
Json::Arr(vec![
Json::Str(cid),
Json::Arr(
items
.into_iter()
.map(|(eid, kind, prov)| {
Json::Arr(vec![
Json::Str(eid),
Json::Str(kind),
Json::Str(prov),
])
})
.collect(),
),
])
})
.collect(),
))
}
"normalize_discovery_ids" => {
let mut doc = case.get("doc").cloned().unwrap_or(Json::Obj(vec![]));
let discoveries = spine::contract_discoveries(&root)?;
spine::normalize_discovery_ids(&mut doc, &discoveries)
.map(|renamed| crate::result_doc(doc, "renamed", renamed))
}
"apply_contract_discoveries" => {
let mut doc = case.get("doc").cloned().unwrap_or(Json::Obj(vec![]));
let discoveries = spine::contract_discoveries(&root)?;
spine::apply_contract_discoveries(&mut doc, &discoveries)
.map(|placed| crate::result_doc(doc, "placed", placed))
}
other => return Err(format!("unknown frozen fn {other}")),
};
let _ = std::fs::remove_dir_all(&root);
out.map(|v| spine::mask_observed_at(&v))
}
#[test]
fn frozen_truth_replays_verbatim() {
let truth = parse(TRUTH).expect("frozen_sl_truth.json parses");
let cases = match truth.get("cases") {
Some(Json::Arr(a)) => a.clone(),
_ => panic!("cases key"),
};
assert!(
cases.len() >= 23,
"expected the full frozen battery, got {}",
cases.len()
);
let mut failures: Vec<String> = vec![];
for (i, case) in cases.iter().enumerate() {
let fn_name = match case.get("fn") {
Some(Json::Str(s)) => s.as_str().to_string(),
_ => panic!("case {i} missing fn"),
};
let name = match case.get("name") {
Some(Json::Str(s)) => s.as_str().to_string(),
_ => format!("case-{i}"),
};
let expected = spine::mask_observed_at(&case.get("expected").cloned().unwrap_or(Json::Null));
match run_case(&fn_name, case) {
Ok(actual) => {
if !jeq(&expected, &actual) {
failures.push(format!(
"case {i} {fn_name}/{name}:\n expected={}\n actual ={}",
capability_scoring::pyjson::dumps(&expected),
capability_scoring::pyjson::dumps(&actual),
));
}
}
Err(e) => failures.push(format!("case {i} {fn_name}/{name}: ERR {e}")),
}
}
assert!(
failures.is_empty(),
"frozen-truth divergences ({}):\n{}",
failures.len(),
failures.join("\n")
);
}
+171
View File
@@ -0,0 +1,171 @@
//! spine_language — FFI surface for the #2465 doc-level transforms
//! (dichotomy wave #2479 tranche). RUST verdict: deterministic spine
//! transforms in Rust; the Python facade keeps a RUNE_SL_CORE=python
//! pin (strangler law).
//!
//! FFI contract (doc-envelope law — every call returns {"ok","value"|"err"}):
//! - sl_classification_sides({"root": str}) -> measured map {stem: side}
//! - sl_place_languages({"root": str, "doc": doc}) -> {"doc", "placed"}
//! - sl_contract_discoveries({"root": str}) -> [[cid, [[eid, kind, prov], ...]], ...]
//! - sl_normalize_discovery_ids({"doc", "root"?/ "discoveries"?}) -> {"doc", "renamed"}
//! - sl_apply_contract_discoveries({"doc", "root"?/ "discoveries"?}) -> {"doc", "placed"}
//!
//! `root` is the repo root (the loader always sends str(ROOT)); the store
//! reads beneath it degrade exactly like the oracle's best-effort except.
//!
//! Parity oracle: frozen_sl_truth.json (23 cases, frozen from the live
//! Python oracle with RUNE_SL_CORE=python pinned, observed_at masked).
//!
//! NOTE: no sl_free_string — capability_scoring (same process) exports
//! cs_free_string and every returned pointer is freed through it.
#[path = "branch_battery.rs"]
#[cfg(test)]
mod branch_battery;
pub mod spine;
pub use capability_scoring::pyjson;
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
use pyjson::Json;
fn read_arg(ptr: *const c_char) -> Result<Json, String> {
if ptr.is_null() {
return Err("null argument pointer".into());
}
let x = unsafe { CStr::from_ptr(ptr) }
.to_str()
.map_err(|e| e.to_string())?;
pyjson::parse(x)
}
fn envelope(r: Result<Json, String>) -> *mut c_char {
let v = match r {
Ok(x) => Json::Obj(vec![
("ok".into(), Json::Bool(true)),
("value".into(), x),
]),
Err(e) => Json::Obj(vec![
("ok".into(), Json::Bool(false)),
("err".into(), Json::Str(e)),
]),
};
CString::new(pyjson::dumps(&v))
.map(|c| c.into_raw())
.unwrap_or(std::ptr::null_mut())
}
fn root_arg(v: &Json) -> Result<std::path::PathBuf, String> {
match v.get("root") {
Some(Json::Str(s)) if !s.is_empty() => Ok(std::path::PathBuf::from(s)),
_ => Err("root (repo root string) is required".into()),
}
}
fn doc_arg(v: &Json) -> Result<Json, String> {
match v.get("doc") {
Some(d @ Json::Obj(_)) => Ok(d.clone()),
_ => Err("doc (object) is required".into()),
}
}
/// Gather discoveries for a transform: an injected wire list (the
/// facade's `_contract_discoveries` seam) wins; otherwise the store read
/// under `root`.
fn discoveries_arg(a: &Json) -> Result<spine::Discoveries, String> {
match a.get("discoveries") {
Some(wire) => spine::parse_discoveries(wire),
None => {
let root = root_arg(a)?;
spine::contract_discoveries(&root)
}
}
}
fn result_doc(doc: Json, count: &str, n: i64) -> Json {
Json::Obj(vec![
("doc".into(), doc),
(count.into(), Json::Num(n as f64)),
])
}
/// sl_classification_sides({"root": str})
#[no_mangle]
pub extern "C" fn sl_classification_sides(arg: *const c_char) -> *mut c_char {
let r = read_arg(arg).and_then(|a| {
let root = root_arg(&a)?;
Ok(spine::classification_sides(&root))
});
envelope(r)
}
/// sl_place_languages({"root": str, "doc": doc})
#[no_mangle]
pub extern "C" fn sl_place_languages(arg: *const c_char) -> *mut c_char {
let r = read_arg(arg).and_then(|a| {
let root = root_arg(&a)?;
let mut doc = doc_arg(&a)?;
let placed = spine::place_languages(&mut doc, &root)?;
Ok(result_doc(doc, "placed", placed))
});
envelope(r)
}
/// sl_contract_discoveries({"root": str})
#[no_mangle]
pub extern "C" fn sl_contract_discoveries(arg: *const c_char) -> *mut c_char {
let r = read_arg(arg).and_then(|a| {
let root = root_arg(&a)?;
let found = spine::contract_discoveries(&root)?;
Ok(Json::Arr(
found
.into_iter()
.map(|(cid, items)| {
Json::Arr(vec![
Json::Str(cid),
Json::Arr(
items
.into_iter()
.map(|(eid, kind, prov)| {
Json::Arr(vec![
Json::Str(eid),
Json::Str(kind),
Json::Str(prov),
])
})
.collect(),
),
])
})
.collect(),
))
});
envelope(r)
}
/// sl_normalize_discovery_ids({"doc": doc, "root"?/ "discoveries"?})
#[no_mangle]
pub extern "C" fn sl_normalize_discovery_ids(arg: *const c_char) -> *mut c_char {
let r = read_arg(arg).and_then(|a| {
let mut doc = doc_arg(&a)?;
let discoveries = discoveries_arg(&a)?;
let renamed = spine::normalize_discovery_ids(&mut doc, &discoveries)?;
Ok(result_doc(doc, "renamed", renamed))
});
envelope(r)
}
/// sl_apply_contract_discoveries({"doc": doc, "root"?/ "discoveries"?})
#[no_mangle]
pub extern "C" fn sl_apply_contract_discoveries(arg: *const c_char) -> *mut c_char {
let r = read_arg(arg).and_then(|a| {
let mut doc = doc_arg(&a)?;
let discoveries = discoveries_arg(&a)?;
let placed = spine::apply_contract_discoveries(&mut doc, &discoveries)?;
Ok(result_doc(doc, "placed", placed))
});
envelope(r)
}
+692
View File
@@ -0,0 +1,692 @@
//! spine — the #2465 doc-level transforms (Rust core). Mirrors
//! src/rune/spine_language.py exactly, including quirks:
//! * the measured classification map decides FIRST; name hints only
//! label UNMEASURED entities ("(name hint)"); neither measurement
//! nor hint on a module → "unclassified", never a silent default;
//! * a subsystem's language is its members' measured majority;
//! * `_normalize_discovery_ids` demotes any construction-surface whose
//! id is not in the OBSERVED set (the unsupported-PRESENT undo) and
//! always rewrites its tags without "code";
//! * `_apply_contract_discoveries` prefixes construction-surface ids
//! unconditionally (the historical double-prefix quirk is preserved
//! and pinned by the frozen battery);
//! * add_entity merge is the structural_store high-water law
//! (maturity never downgrades, sorted set-union tags/evidence).
//!
//! Store reads (classification.json / scaffold current.json) degrade to
//! empty exactly like the oracle's best-effort except-clause; the
//! wall-clock `observed_at` stamp is emitted (same field set as the
//! oracle) and masked by every comparison surface.
//!
//! Phase B (language-advantage pass): transforms mutate the doc IN PLACE
//! (`&mut Json`, no whole-document clone), the measured side is a real
//! enum matched exhaustively, subsystem member counting is a single pass
//! over the relationships, and the verdict ladder compares borrowed
//! strings — the frozen battery proves the output byte-for-byte
//! unchanged.
use capability_scoring::pyjson::Json;
/// Names of the store files the transforms read, relative to the repo
/// root passed in the FFI payload (the loader always sends str(ROOT)).
pub const CLASSIFICATION_REL: &str = "store/rebase/classification.json";
pub const SCAFFOLD_REL: &str = "store/scaffold/current.json";
pub const GO_HINTS: &[&str] = &[
"server", "transport", "dispatch", "entry", "bridge", "hook_engine",
"provider", "watch", "notif", "voice", "gitea", "event_", "message_",
"tool_exec", "work_manager",
];
pub const RUST_HINTS: &[&str] = &[
"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",
];
/// The measured dichotomy verdict for one module stem, after the
/// oracle's `str(x or "").upper()` coercion. `Other` covers every value
/// that is not one of the five known sides (they all fall through the
/// authority ladder exactly like the oracle's string compares).
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum MeasuredSide {
Rust,
Go,
TuiLast,
TestInfra,
Retires,
Other,
}
/// Python `str(x or "").upper()` semantics for a classification-map
/// value. Num renders like Python str(int) when integral (the frozen
/// truth pins the int case; float text is a documented pathological
/// divergence); composite values coerce to "" (pathological only).
fn measured_side(v: Option<&Json>) -> MeasuredSide {
let s = match v {
// str(None)/str("") coerce to "" via the oracle's `x or ""`;
// str(True)="TRUE", str(int) are digits — none match a known side.
None | Some(Json::Null) | Some(Json::Bool(_)) | Some(Json::Num(_)) => {
return MeasuredSide::Other
}
Some(Json::Str(s)) => s.as_str(),
Some(Json::Arr(_)) | Some(Json::Obj(_)) => return MeasuredSide::Other,
};
// Borrowed, case-insensitive compare — no intermediate uppercase String.
for (known, side) in [
("RUST", MeasuredSide::Rust),
("GO", MeasuredSide::Go),
("TUI-LAST", MeasuredSide::TuiLast),
("TEST-INFRA", MeasuredSide::TestInfra),
("RETIRES", MeasuredSide::Retires),
] {
if s.len() == known.len() && s.eq_ignore_ascii_case(known) {
return side;
}
}
MeasuredSide::Other
}
/// structural_store.MATURITY_RANK (high-water law).
fn maturity_rank(m: &str) -> i64 {
match m {
"DECLARED" => 0,
"PRESENT" => 1,
"WIRED" => 2,
"REACHABLE" => 3,
"EXERCISED" => 4,
"LIVE_PROVEN" => 5,
_ => 0,
}
}
/// structural_store.now(): "%Y-%m-%dT%H:%M:%SZ" UTC.
pub fn now() -> String {
let secs = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0);
let days = (secs / 86_400) as i64;
let rem = secs % 86_400;
// Howard Hinnant's civil_from_days.
let z = days + 719_468;
let era = if z >= 0 { z } else { z - 146_096 } / 146_097;
let doe = z - era * 146_097;
let yoe = (doe - doe / 1460 + doe / 36_524 - doe / 146_096) / 365;
let y = yoe + era * 400;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
let mp = (5 * doy + 2) / 153;
let d = doy - (153 * mp + 2) / 5 + 1;
let m = if mp < 10 { mp + 3 } else { mp - 9 };
let y = if m <= 2 { y + 1 } else { y };
format!(
"{:04}-{:02}-{:02}T{:02}:{:02}:{:02}Z",
y,
m,
d,
rem / 3600,
(rem % 3600) / 60,
rem % 60
)
}
// ── small Json object helpers (pyjson keeps insertion order) ────────────
fn obj_pairs(v: &Json) -> Option<&Vec<(String, Json)>> {
match v {
Json::Obj(pairs) => Some(pairs),
_ => None,
}
}
fn obj_pairs_mut(v: &mut Json) -> Option<&mut Vec<(String, Json)>> {
match v {
Json::Obj(pairs) => Some(pairs),
_ => None,
}
}
pub fn obj_get<'a>(v: &'a Json, key: &str) -> Option<&'a Json> {
obj_pairs(v).and_then(|p| p.iter().find(|(k, _)| k == key).map(|(_, x)| x))
}
fn obj_get_mut<'a>(v: &'a mut Json, key: &str) -> Option<&'a mut Json> {
obj_pairs_mut(v)
.and_then(|p| p.iter_mut().find(|(k, _)| k == key).map(|(_, x)| x))
}
/// Insert or replace `key` in an Obj (keeps original position on replace).
fn obj_set(v: &mut Json, key: &str, val: Json) {
if let Some(pairs) = obj_pairs_mut(v) {
if let Some(slot) = pairs.iter_mut().find(|(k, _)| k == key) {
slot.1 = val;
} else {
pairs.push((key.to_string(), val));
}
}
}
fn as_str(v: Option<&Json>) -> String {
match v {
Some(Json::Str(s)) => s.clone(),
_ => String::new(),
}
}
/// Recursively drop wall-clock stamps (masking law — battery + parity).
pub fn mask_observed_at(v: &Json) -> Json {
match v {
Json::Arr(items) => Json::Arr(items.iter().map(mask_observed_at).collect()),
Json::Obj(pairs) => Json::Obj(
pairs
.iter()
.filter(|(k, _)| k != "observed_at")
.map(|(k, x)| (k.clone(), mask_observed_at(x)))
.collect(),
),
other => other.clone(),
}
}
// ── _classification_sides ────────────────────────────────────────────────
/// The measured dichotomy map: module stem → side. Missing/unreadable/
/// malformed/非-dict store degrades to {} — never to invented facts.
pub fn classification_sides(root: &std::path::Path) -> Json {
let text = match std::fs::read_to_string(root.join(CLASSIFICATION_REL)) {
Ok(t) => t,
Err(_) => return Json::Obj(vec![]),
};
let data = match capability_scoring::pyjson::parse(&text) {
Ok(d) => d,
Err(_) => return Json::Obj(vec![]),
};
match data {
Json::Obj(pairs) => Json::Obj(
pairs
.into_iter()
.filter(|(_, rec)| matches!(rec, Json::Obj(_)))
.map(|(name, rec)| {
let side =
obj_get(&rec, "side").cloned().unwrap_or(Json::Str(String::new()));
(name, side)
})
.collect(),
),
// Python: .items() on a non-dict raises AttributeError, caught → {}.
_ => Json::Obj(vec![]),
}
}
// ── _contract_discoveries ────────────────────────────────────────────────
/// Closed contracts' DISCOVERED entities: (contract_id, [(eid, kind,
/// provenance)]). Only CLOSED contracts teach the spine (CANDIDATE law).
/// Missing store / invalid JSON → []; valid-but-non-dict store mirrors
/// the oracle's uncaught AttributeError as an Err.
pub type Discoveries = Vec<(String, Vec<(String, String, String)>)>;
pub fn contract_discoveries(root: &std::path::Path) -> Result<Discoveries, String> {
let text = match std::fs::read_to_string(root.join(SCAFFOLD_REL)) {
Ok(t) => t,
Err(_) => return Ok(vec![]),
};
let doc = match capability_scoring::pyjson::parse(&text) {
Ok(d) => d,
Err(_) => return Ok(vec![]),
};
let entities = match obj_get(&doc, "entities") {
Some(Json::Arr(a)) => a.clone(),
Some(_) => return Err("AttributeError: scaffold store is not a doc".into()),
None => return Ok(vec![]),
};
let mut out: Discoveries = vec![];
for e in &entities {
if obj_pairs(e).is_none() {
return Err("AttributeError: scaffold entity is not an object".into());
}
let cid = as_str(obj_get(e, "id"));
if !cid.starts_with("contract:") || as_str(obj_get(e, "phase")) != "closed" {
continue;
}
let ext = obj_get(e, "ext").cloned().unwrap_or(Json::Obj(vec![]));
let q = obj_get(&ext, "contract_quality").cloned().unwrap_or(Json::Obj(vec![]));
let mut found: Vec<(String, String, String)> = vec![];
if let Some(Json::Arr(observed)) = obj_get(&q, "expected_new_observed") {
for eid in observed {
let eid = match eid {
Json::Str(s) => s.clone(),
_ => continue,
};
let kind = split_kind(&eid);
if is_discoverable_kind(&kind) {
found.push((eid, kind, "observed".into()));
}
}
}
if let Some(Json::Arr(cands)) = obj_get(&q, "candidate_corrections") {
for cand in cands {
let eid = match obj_get(cand, "entity") {
Some(Json::Str(s)) => s.clone(),
_ => String::new(),
};
if eid.is_empty() {
continue;
}
let kind = split_kind(&eid);
if is_discoverable_kind(&kind) {
found.push((eid, kind, "candidate".into()));
}
}
}
if !found.is_empty() {
out.push((cid[9..].to_string(), found));
}
}
Ok(out)
}
/// The open-world kinds a discovery may carry (the oracle's allow list —
/// construction-surface is the fallback for bare ids).
fn is_discoverable_kind(kind: &str) -> bool {
matches!(
kind,
"rust-crate" | "go-surface" | "config" | "workflow" | "module" | "construction-surface"
)
}
/// eid.split(":", 1)[0] if ":" in eid else "construction-surface".
fn split_kind(eid: &str) -> String {
match eid.split_once(':') {
Some((kind, _)) => kind.to_string(),
None => "construction-surface".to_string(),
}
}
/// Convert the wire form [[cid, [[eid, kind, provenance], ...]], ...]
/// (as returned by sl_contract_discoveries / the facade) into gathered
/// discoveries. Used when the caller injects the gather result (the
/// facade's `_contract_discoveries` monkeypatch seam) instead of the
/// store read.
pub fn parse_discoveries(wire: &Json) -> Result<Discoveries, String> {
let arr = match wire {
Json::Arr(a) => a,
_ => return Err("discoveries must be a list".into()),
};
let mut out: Discoveries = vec![];
for entry in arr {
let parts = match entry {
Json::Arr(p) if p.len() == 2 => p,
_ => return Err("discovery entry must be [cid, items]".into()),
};
let cid = match &parts[0] {
Json::Str(s) => s.clone(),
_ => return Err("discovery cid must be a string".into()),
};
let items = match &parts[1] {
Json::Arr(items) => items,
_ => return Err("discovery items must be a list".into()),
};
let mut found = vec![];
for item in items {
let triple = match item {
Json::Arr(t) if t.len() == 3 => t,
_ => return Err("discovery item must be [eid, kind, provenance]".into()),
};
let mut strs = vec![];
for x in triple {
match x {
Json::Str(s) => strs.push(s.clone()),
_ => return Err("discovery item fields must be strings".into()),
}
}
found.push((strs[0].clone(), strs[1].clone(), strs[2].clone()));
}
out.push((cid, found));
}
Ok(out)
}
// ── add_entity (structural_store high-water merge, JSON level) ──────────
/// Mirror of rune.structural_store.add_entity for the write-back path:
/// never downgrades maturity; sorted set-union tags/evidence; identities
/// update; wall-clock observed_at stamp on every touch.
fn add_entity(
doc: &mut Json,
id: &str,
kind: &str,
tags: &[&str],
identities: Option<Vec<(String, Json)>>,
evidence_refs: &[String],
maturity: &str,
) {
let existing_maturity = |e: &Json| {
as_str(obj_get(e, "maturity").or(Some(&Json::Str("DECLARED".into()))))
};
let hit = obj_get_mut(doc, "entities").and_then(|entities| {
let Json::Arr(a) = entities else { return None };
a.iter_mut()
.find(|e| as_str(obj_get(e, "id")) == id)
.map(|e| {
if !maturity.is_empty()
&& maturity_rank(maturity) > maturity_rank(&existing_maturity(e))
{
obj_set(e, "maturity", Json::Str(maturity.to_string()));
}
if !tags.is_empty() {
let mut set = string_set(obj_get(e, "tags"));
set.extend(tags.iter().map(|s| s.to_string()));
obj_set(e, "tags", sorted_strs(set));
}
if let Some(idents) = &identities {
if obj_get(e, "identities").is_none() {
obj_set(e, "identities", Json::Obj(vec![]));
}
for (k, v) in idents {
if let Some(ids) = obj_get_mut(e, "identities") {
obj_set(ids, k, v.clone());
}
}
}
if !evidence_refs.is_empty() {
let mut set = string_set(obj_get(e, "evidence_refs"));
set.extend(evidence_refs.iter().cloned());
obj_set(e, "evidence_refs", sorted_strs(set));
}
obj_set(e, "observed_at", Json::Str(now()));
})
});
if hit.is_some() {
return;
}
let ent = Json::Obj(vec![
("id".into(), Json::Str(id.to_string())),
("kind".into(), Json::Str(kind.to_string())),
("maturity".into(), Json::Str(maturity.to_string())),
(
"tags".into(),
sorted_strs(tags.iter().map(|s| s.to_string()).collect()),
),
(
"identities".into(),
Json::Obj(identities.unwrap_or_default()),
),
(
"evidence_refs".into(),
sorted_strs(evidence_refs.iter().cloned().collect()),
),
("observed_at".into(), Json::Str(now())),
]);
if let Some(Json::Arr(a)) = obj_get_mut(doc, "entities") {
a.push(ent);
}
}
fn string_set(v: Option<&Json>) -> std::collections::BTreeSet<String> {
match v {
Some(Json::Arr(items)) => items
.iter()
.filter_map(|x| match x {
Json::Str(s) => Some(s.clone()),
_ => None,
})
.collect(),
_ => Default::default(),
}
}
fn sorted_strs(set: std::collections::BTreeSet<String>) -> Json {
Json::Arr(set.into_iter().map(Json::Str).collect())
}
// ── _place_languages ─────────────────────────────────────────────────────
/// Stamp a language verdict per entity IN PLACE; returns the placed count
/// (verdicts != "unassessed").
pub fn place_languages(doc: &mut Json, root: &std::path::Path) -> Result<i64, String> {
let sides = classification_sides(root);
place_languages_with_sides(doc, &sides)
}
pub fn place_languages_with_sides(doc: &mut Json, sides: &Json) -> Result<i64, String> {
let n_entities = match obj_get(doc, "entities") {
Some(Json::Arr(a)) => a.len(),
Some(_) => return Err("AttributeError: entities is not a list".into()),
None => return Ok(0),
};
// Single pass over the relationships: belongs_to member counts per
// subsystem target (the oracle recomputes per entity; the majority
// verdict is identical — the frozen battery pins it).
let mut member_counts: std::collections::HashMap<String, (i64, i64)> = Default::default();
match obj_get(doc, "relationships") {
Some(Json::Arr(rels)) => {
for r in rels {
if as_str(obj_get(r, "type")) != "belongs_to" {
continue;
}
let to = as_str(obj_get(r, "to"));
let from = as_str(obj_get(r, "from"));
if let Some(member) = from.strip_prefix("module:") {
let entry = member_counts.entry(to).or_insert((0, 0));
match measured_side(obj_get(sides, member)) {
MeasuredSide::Rust => entry.0 += 1,
MeasuredSide::Go => entry.1 += 1,
_ => {}
}
}
}
}
Some(_) => return Err("AttributeError: relationships is not a list".into()),
None => {}
}
let mut placed = 0i64;
for i in 0..n_entities {
let e = match obj_get_mut(doc, "entities") {
Some(Json::Arr(a)) => &mut a[i],
_ => unreachable!(),
};
let eid = as_str(obj_get(e, "id"));
let kind = as_str(obj_get(e, "kind"));
let measured_key: &str = match eid.split_once(':') {
Some((_, rest)) => rest,
None => &eid,
};
let measured = measured_side(obj_get(sides, measured_key));
// Verdict ladder, authority order preserved: contract prefix →
// kind defaults → MEASURED map → tui in eid → name hints →
// module/subsystem containers → honest unassessed.
let lang: Lang = if eid.starts_with("contract:") {
Lang::Fixed("n/a")
} else {
match (kind.as_str(), measured) {
("rust-crate", _) => Lang::Fixed("rust"),
("go-surface", _) => Lang::Fixed("go"),
("workflow", _) | ("config", _) => Lang::Fixed("n/a"),
("deployment_surface", _) => Lang::Fixed("go"),
(_, MeasuredSide::Rust) => Lang::Fixed("rust"),
(_, MeasuredSide::Go) => Lang::Fixed("go"),
(_, MeasuredSide::TuiLast) => Lang::Fixed("python-tui (Wave 6)"),
(_, MeasuredSide::TestInfra) => Lang::Fixed("test-infra"),
(_, MeasuredSide::Retires) => Lang::Fixed("retires"),
(_, MeasuredSide::Other) => {
if eid.contains("tui") {
Lang::Fixed("python-tui (Wave 6)")
} else if GO_HINTS.iter().any(|h| eid.contains(h)) {
Lang::Fixed("go (name hint)")
} else if RUST_HINTS.iter().any(|h| eid.contains(h)) {
Lang::Fixed("rust (name hint)")
} else if kind == "module" {
Lang::Fixed("unclassified")
} else if kind == "subsystem" {
match member_counts.get(&eid) {
Some((rust, go)) if *rust > 0 && *go > 0 => Lang::Mixed(*rust, *go),
Some((rust, _)) if *rust > 0 => Lang::Fixed("rust"),
Some((_, go)) if *go > 0 => Lang::Fixed("go"),
_ => Lang::Fixed("unassessed"),
}
} else {
Lang::Fixed("unassessed")
}
}
}
};
if obj_get(e, "identities").is_none() {
obj_set(e, "identities", Json::Obj(vec![]));
}
let unassessed = matches!(lang, Lang::Fixed("unassessed"));
if let Some(ids) = obj_get_mut(e, "identities") {
obj_set(ids, "language", lang.into_json());
}
if !unassessed {
placed += 1;
}
}
Ok(placed)
}
/// A language verdict: the common case is a fixed literal; subsystem
/// containers derive `mixed (rust R / go G members)` text from counts.
enum Lang {
Fixed(&'static str),
Mixed(i64, i64),
}
impl Lang {
fn into_json(self) -> Json {
match self {
Lang::Fixed(s) => Json::Str(s.to_string()),
Lang::Mixed(rust, go) => {
Json::Str(format!("mixed (rust {rust} / go {go} members)"))
}
}
}
}
// ── _normalize_discovery_ids ─────────────────────────────────────────────
/// Migrate legacy unqualified contract discoveries to construction-surface
/// kind without losing evidence; demote unsupported PRESENT claims.
/// Mutates the doc IN PLACE; returns the rename count.
pub fn normalize_discovery_ids(doc: &mut Json, discoveries: &Discoveries) -> Result<i64, String> {
let observed: std::collections::HashSet<&str> = discoveries
.iter()
.flat_map(|(_, items)| items.iter())
.filter(|(_, _, prov)| prov == "observed")
.map(|(eid, _, _)| eid.as_str())
.collect();
let entities = match obj_get_mut(doc, "entities") {
Some(Json::Arr(a)) => a,
Some(_) => return Err("AttributeError: entities is not a list".into()),
None => return Ok(0),
};
let mut renamed: Vec<(String, String)> = vec![];
for e in entities.iter_mut() {
let kind = as_str(obj_get(e, "kind"));
let eid = as_str(obj_get(e, "id"));
let has_discovery_ref = match obj_get(e, "evidence_refs") {
Some(Json::Arr(refs)) => refs.iter().any(|r| {
matches!(r, Json::Str(s) if s.starts_with("contract-discovery:"))
}),
_ => false,
};
if kind == "module" && !eid.contains(':') && has_discovery_ref && !eid.is_empty() {
// Python would KeyError on a missing id here (e["id"] after the
// colon check passes for ""); the empty guard keeps the Rust
// surface total for that pathological input.
let new_id = format!("construction-surface:{eid}");
obj_set(e, "id", Json::Str(new_id.clone()));
obj_set(e, "kind", Json::Str("construction-surface".into()));
if obj_get(e, "identities").is_none() {
obj_set(e, "identities", Json::Obj(vec![]));
}
if let Some(ids) = obj_get_mut(e, "identities") {
obj_set(ids, "source_ref", Json::Str(eid.clone()));
}
renamed.push((eid, new_id));
}
let kind = as_str(obj_get(e, "kind"));
let eid = as_str(obj_get(e, "id"));
if kind == "construction-surface" {
if let Some((_, suffix)) = eid.split_once(':') {
if !observed.contains(suffix) {
obj_set(e, "maturity", Json::Str("DECLARED".into()));
}
}
let tags: Vec<Json> = match obj_get(e, "tags") {
Some(Json::Arr(t)) => t
.iter()
.filter(|x| !matches!(x, Json::Str(s) if s == "code"))
.cloned()
.collect(),
_ => vec![],
};
obj_set(e, "tags", Json::Arr(tags));
}
}
let n = renamed.len() as i64;
if let Some(Json::Arr(rels)) = obj_get_mut(doc, "relationships") {
for rel in rels.iter_mut() {
for endpoint in ["from", "to"] {
let cur = as_str(obj_get(rel, endpoint));
if let Some((_, new)) = renamed.iter().find(|(old, _)| *old == cur) {
obj_set(rel, endpoint, Json::Str(new.clone()));
}
}
}
}
Ok(n)
}
// ── _apply_contract_discoveries ──────────────────────────────────────────
/// Contract-discovery write-back: entities DISCOVERED by closed contracts
/// become modeled spine entities (observed → PRESENT, candidate →
/// DECLARED). Mutates the doc IN PLACE; returns the count of NEW entities
/// (high-water merges still carry evidence but don't count).
pub fn apply_contract_discoveries(doc: &mut Json, discoveries: &Discoveries) -> Result<i64, String> {
let mut placed = 0i64;
for (cid, items) in discoveries {
for (eid, kind, provenance) in items {
let before = match obj_get(doc, "entities") {
Some(Json::Arr(a)) => a.len() as i64,
Some(_) => return Err("KeyError: 'entities' is not a list".into()),
None => return Err("KeyError: 'entities'".into()),
};
let full_eid = if kind == "construction-surface" {
format!("construction-surface:{eid}")
} else {
eid.clone()
};
let identities = if kind == "construction-surface" {
let source_ref = full_eid.split_once(':').map(|(_, r)| r).unwrap_or("");
Some(vec![(
"source_ref".to_string(),
Json::Str(source_ref.to_string()),
)])
} else {
None
};
let maturity = if provenance == "observed" { "PRESENT" } else { "DECLARED" };
let refs = vec![format!("contract-discovery:{cid}")];
add_entity(
doc,
&full_eid,
kind,
&["contract-discovery"],
identities,
&refs,
maturity,
);
let after = match obj_get(doc, "entities") {
Some(Json::Arr(a)) => a.len() as i64,
_ => before,
};
if after > before {
placed += 1;
}
}
}
Ok(placed)
}
+43190 -1227
View File
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,24 @@
# Contract BUILDER-2026-10-12T03:02:45Z-bd521d-factory-wave
**Phase:** closed · **Mode:** n/a · **Depth:** n/a
## What was built
Port src/rune/openai_compat_transport.py to FULL Go (dichotomy factory, issue #2479 tranche): name-preserving port in src/go/openai_compat_transport/, IO behind injectable seams (HTTP poster, config/auth/env), frozen-truth battery replaying every public function, live parity 100%, Phase B language-advantage pass (goroutines at serial IO seams, interfaces, compile-time safety).
## What it connects to
(no affected surface recorded)
## How to update those connections
- Open a NEW construction contract (scaffolding is absolute) scoped to the same surface.
- Evidence trail: 4 checkpoint(s) recorded on the contract entity.
- Quality at close: {"candidate_corrections": [], "captured_at": "2026-10-12T03:03:44Z", "contract": "BUILDER-2026-10-12T03:02:45Z-bd521d-factory-wave", "correctly_predicted": [], "evidence_sources": ["unattributed"], "expected_new_entities": [], "expected_new_missing": [], "expected_new_observed": [], "expected_runtime_effects": [], "expected_runtime_missing": [], "expected_runtime_observed": [], "genuinely_unexpected_new": [], "missed": [], "needless": ["module:deploy_qualify"], "observed": [], "observed_existing": [], "observed_new": [], "predicted_but_unaffected": ["module:deploy_qualify"], "predicted_existing_affected": ["module:deploy_qualify"], "scope_check": {"in_scope": [], "out_of_scope": ["scripts/gen_openai_compat_transport_truth.py", "src/go/openai_compat_transport/openai_compat_transport.go", "src/go/openai_compat_transport/phaseb.go", "src/go/openai_compat_transport/phaseb_test.go", "src/go/openai_compat_transport/pyjson.go", "src/go/openai_compat_transport/replay.go", "src/go/openai_compat_transport/truth.json", "src/go/openai_compat_transport/truth_test.go", "src/rune/openai_compat_transport.py"], "reason": "contract has no approved scope", "verdict": "unknown"}}.
## Evidence
- builder:factory-wave
- builder:factory-wave
- builder:factory-wave
- closure-record
@@ -0,0 +1,24 @@
# Contract BUILDER-2026-10-12T09:54:16Z-77fa9d-factory-wave
**Phase:** closed · **Mode:** n/a · **Depth:** n/a
## What was built
Port src/rune/behaviour_ledger.py to FULL Rust (dichotomy factory, issue #2479 tranche): doc-envelope FFI, ctypes loader, facade delegation with RUNE_BL_CORE=python pin, frozen-truth battery, live parity 100%, Phase B language-advantage pass.
## What it connects to
(no affected surface recorded)
## How to update those connections
- Open a NEW construction contract (scaffolding is absolute) scoped to the same surface.
- Evidence trail: 4 checkpoint(s) recorded on the contract entity.
- Quality at close: {"candidate_corrections": [], "captured_at": "2026-10-12T11:57:19Z", "contract": "BUILDER-2026-10-12T09:54:16Z-77fa9d-factory-wave", "correctly_predicted": [], "evidence_sources": ["unattributed"], "expected_new_entities": [], "expected_new_missing": [], "expected_new_observed": [], "expected_runtime_effects": [], "expected_runtime_missing": [], "expected_runtime_observed": [], "genuinely_unexpected_new": [], "missed": [], "needless": ["module:deploy_qualify"], "observed": [], "observed_existing": [], "observed_new": [], "predicted_but_unaffected": ["module:deploy_qualify"], "predicted_existing_affected": ["module:deploy_qualify"], "scope_check": {"in_scope": [], "out_of_scope": ["scripts/check_behaviour_ledger_parity.py", "scripts/gen_behaviour_ledger_truth.py", "src/rune/behaviour_ledger.py", "src/rune/bl_loader.py", "src/rust/behaviour_ledger/src/branch_battery.rs", "src/rust/behaviour_ledger/src/frozen_bl_truth.json", "src/rust/behaviour_ledger/src/lib.rs"], "reason": "contract has no approved scope", "verdict": "unknown"}}.
## Evidence
- builder:factory-wave
- builder:factory-wave
- builder:factory-wave
- closure-record
@@ -0,0 +1,27 @@
# Contract BUILDER-2026-10-12T10:14:24Z-3f84ac-factory-wave
**Phase:** closed · **Mode:** n/a · **Depth:** n/a
## What was built
Port construction_pattern to FULL Rust — parity + Phase B language-advantage pass (dichotomy wave #2479 tranche)
## What it connects to
- `src/rune/construction_pattern.py`
- `src/rune/cp_loader.py`
- `src/rust/construction_pattern`
- `tests/test_construction_pattern.py`
## How to update those connections
- Open a NEW construction contract (scaffolding is absolute) scoped to the same surface.
- Evidence trail: 4 checkpoint(s) recorded on the contract entity.
- Quality at close: {"candidate_corrections": [], "captured_at": "2026-10-12T10:15:24Z", "contract": "BUILDER-2026-10-12T10:14:24Z-3f84ac-factory-wave", "correctly_predicted": [], "evidence_sources": ["unattributed"], "expected_new_entities": ["test:construction_pattern"], "expected_new_missing": ["test:construction_pattern"], "expected_new_observed": [], "expected_runtime_effects": [], "expected_runtime_missing": [], "expected_runtime_observed": [], "genuinely_unexpected_new": [], "missed": [], "needless": ["module:construction_pattern", "module:deploy_qualify"], "observed": [], "observed_existing": [], "observed_new": [], "predicted_but_unaffected": ["module:construction_pattern", "module:deploy_qualify"], "predicted_existing_affected": ["module:construction_pattern", "module:deploy_qualify"]}.
## Evidence
- builder:factory-wave
- builder:factory-wave
- builder:factory-wave
- closure-record
@@ -0,0 +1,24 @@
# Contract BUILDER-2026-10-12T10:31:15Z-0436f5-factory-wave
**Phase:** closed · **Mode:** n/a · **Depth:** n/a
## What was built
Port src/rune/experience_encoding.py to FULL Rust (dichotomy factory, issue #2479 tranche): doc-envelope FFI, ctypes loader, facade delegation with RUNE_EE_CORE=python pin, frozen-truth battery, live parity 100%, Phase B language-advantage pass.
## What it connects to
(no affected surface recorded)
## How to update those connections
- Open a NEW construction contract (scaffolding is absolute) scoped to the same surface.
- Evidence trail: 4 checkpoint(s) recorded on the contract entity.
- Quality at close: {"candidate_corrections": [], "captured_at": "2026-10-12T10:32:15Z", "contract": "BUILDER-2026-10-12T10:31:15Z-0436f5-factory-wave", "correctly_predicted": [], "evidence_sources": ["unattributed"], "expected_new_entities": [], "expected_new_missing": [], "expected_new_observed": [], "expected_runtime_effects": [], "expected_runtime_missing": [], "expected_runtime_observed": [], "genuinely_unexpected_new": [], "missed": [], "needless": ["module:deploy_qualify"], "observed": [], "observed_existing": [], "observed_new": [], "predicted_but_unaffected": ["module:deploy_qualify"], "predicted_existing_affected": ["module:deploy_qualify"], "scope_check": {"in_scope": [], "out_of_scope": ["scripts/gen_experience_encoding_truth.py", "src/rune/ee_loader.py", "src/rune/experience_encoding.py", "src/rust/experience_encoding/src/branch_battery.rs", "src/rust/experience_encoding/src/lib.rs"], "reason": "contract has no approved scope", "verdict": "unknown"}}.
## Evidence
- builder:factory-wave
- builder:factory-wave
- builder:factory-wave
- closure-record
@@ -0,0 +1,24 @@
# Contract BUILDER-2026-10-12T12:12:17Z-a146ec-factory-wave
**Phase:** closed · **Mode:** n/a · **Depth:** n/a
## What was built
Port src/rune/abstraction_ladder.py to FULL Rust (dichotomy factory, issue #2479 tranche): doc-envelope FFI, ctypes loader, facade delegation with RUNE_AL_CORE=python pin, frozen-truth battery, live parity 100%, Phase B language-advantage pass.
## What it connects to
(no affected surface recorded)
## How to update those connections
- Open a NEW construction contract (scaffolding is absolute) scoped to the same surface.
- Evidence trail: 4 checkpoint(s) recorded on the contract entity.
- Quality at close: {"candidate_corrections": [], "captured_at": "2026-10-12T12:13:20Z", "contract": "BUILDER-2026-10-12T12:12:17Z-a146ec-factory-wave", "correctly_predicted": [], "evidence_sources": ["unattributed"], "expected_new_entities": [], "expected_new_missing": [], "expected_new_observed": [], "expected_runtime_effects": [], "expected_runtime_missing": [], "expected_runtime_observed": [], "genuinely_unexpected_new": [], "missed": [], "needless": ["module:deploy_qualify"], "observed": [], "observed_existing": [], "observed_new": [], "predicted_but_unaffected": ["module:deploy_qualify"], "predicted_existing_affected": ["module:deploy_qualify"], "scope_check": {"in_scope": [], "out_of_scope": ["scripts/gen_abstraction_ladder_truth.py", "src/rune/abstraction_ladder.py", "src/rune/al_loader.py", "src/rust/abstraction_ladder/src/branch_battery.rs", "src/rust/abstraction_ladder/src/ladder.rs", "src/rust/abstraction_ladder/src/lib.rs"], "reason": "contract has no approved scope", "verdict": "unknown"}}.
## Evidence
- builder:factory-wave
- builder:factory-wave
- builder:factory-wave
- closure-record
@@ -0,0 +1,24 @@
# Contract BUILDER-2026-10-12T12:50:19Z-9317b1-factory-wave
**Phase:** closed · **Mode:** n/a · **Depth:** n/a
## What was built
Port src/rune/spine_language.py to FULL Rust (dichotomy factory, issue #2479 tranche): doc-level transforms (#2465 language placement, discovery-ID normalization, contract-discovery write-back), doc-envelope FFI, ctypes loader, facade delegation with RUNE_SL_CORE=python pin, frozen-truth battery, live parity 100%, Phase B language-advantage pass.
## What it connects to
(no affected surface recorded)
## How to update those connections
- Open a NEW construction contract (scaffolding is absolute) scoped to the same surface.
- Evidence trail: 4 checkpoint(s) recorded on the contract entity.
- Quality at close: {"candidate_corrections": [], "captured_at": "2026-10-12T12:51:18Z", "contract": "BUILDER-2026-10-12T12:50:19Z-9317b1-factory-wave", "correctly_predicted": [], "evidence_sources": ["unattributed"], "expected_new_entities": [], "expected_new_missing": [], "expected_new_observed": [], "expected_runtime_effects": [], "expected_runtime_missing": [], "expected_runtime_observed": [], "genuinely_unexpected_new": [], "missed": [], "needless": ["module:deploy_qualify"], "observed": [], "observed_existing": [], "observed_new": [], "predicted_but_unaffected": ["module:deploy_qualify"], "predicted_existing_affected": ["module:deploy_qualify"], "scope_check": {"in_scope": [], "out_of_scope": ["scripts/gen_spine_language_truth.py", "src/rune/sl_loader.py", "src/rune/spine_language.py", "src/rust/spine_language/frozen_sl_truth.json", "src/rust/spine_language/src/branch_battery.rs", "src/rust/spine_language/src/lib.rs", "src/rust/spine_language/src/spine.rs"], "reason": "contract has no approved scope", "verdict": "unknown"}}.
## Evidence
- builder:factory-wave
- builder:factory-wave
- builder:factory-wave
- closure-record
@@ -0,0 +1,24 @@
# Contract BUILDER-2026-10-12T12:56:54Z-2ab0e0-factory-wave
**Phase:** closed · **Mode:** n/a · **Depth:** n/a
## What was built
Port src/rune/mutation_guards.py to FULL Rust (dichotomy factory, issue #2479 tranche): doc-envelope FFI, ctypes loader, facade delegation with RUNE_MG_CORE=python pin, frozen-truth battery, live parity 100%, Phase B language-advantage pass.
## What it connects to
(no affected surface recorded)
## How to update those connections
- Open a NEW construction contract (scaffolding is absolute) scoped to the same surface.
- Evidence trail: 4 checkpoint(s) recorded on the contract entity.
- Quality at close: {"candidate_corrections": [], "captured_at": "2026-10-12T12:57:58Z", "contract": "BUILDER-2026-10-12T12:56:54Z-2ab0e0-factory-wave", "correctly_predicted": [], "evidence_sources": ["unattributed"], "expected_new_entities": [], "expected_new_missing": [], "expected_new_observed": [], "expected_runtime_effects": [], "expected_runtime_missing": [], "expected_runtime_observed": [], "genuinely_unexpected_new": [], "missed": [], "needless": ["module:deploy_qualify"], "observed": [], "observed_existing": [], "observed_new": [], "predicted_but_unaffected": ["module:deploy_qualify"], "predicted_existing_affected": ["module:deploy_qualify"], "scope_check": {"in_scope": [], "out_of_scope": ["scripts/gen_mutation_guards_truth.py", "src/rune/mu_loader.py", "src/rune/mutation_guards.py", "src/rust/mutation_guards/src/branch_battery.rs", "src/rust/mutation_guards/src/lib.rs"], "reason": "contract has no approved scope", "verdict": "unknown"}}.
## Evidence
- builder:factory-wave
- builder:factory-wave
- builder:factory-wave
- closure-record