Compare commits

...
53 Commits
Author SHA1 Message Date
conductor e3d0560ed5 chore(release): auto-bump version to 0.239.0 — earned by # (minor: converged release (push-to-master bump))
deploy / deploy (push) Skipped
CI Pipeline / best-practices (push) Successful in 5s
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 31s
CI Pipeline / gitea-offline (push) Successful in 19s
CI Pipeline / type-check (push) Failing after 40s
CI Pipeline / lint (push) Failing after 57s
CI Pipeline / canary (push) Failing after 2m3s
CI Pipeline / unit-tests (push) Failing after 16m29s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Failing after 4s
Promotion record: (unknown)
2026-10-12 15:42:56 +01:00
Nick 4f0556fe16 chore(store): close curator dichotomy contract BUILDER-2026-10-12T12:25:33Z-a87985-factory-wave (parity 03ef8132, phase B 89b691b2)
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
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
CI Pipeline / type-check (push) Canceled after 21s
Auto-version and Release / release (push) Canceled after 31s
CI Pipeline / canary (push) Canceled after 31s
CI Pipeline / lint (push) Canceled after 34s
CI Pipeline / unit-tests (push) Canceled after 35s
deploy / deploy (push) Canceled after 26s
2026-10-12 15:42:26 +01:00
Nick f61a4a3fa3 chore(store): register curator dichotomy contract BUILDER-2026-10-12T12:25:33Z-a87985-factory-wave (verifying) 2026-10-12 15:41:42 +01:00
Nick 89b691b210 refactor(curator): Phase B language-advantage pass — dichotomy wave
Allocation-discipline pass over the curator kernel, pinned by the unchanged
frozen-truth battery (truth.json byte-identical to the parity commit, green
throughout):

- NewOp: pre-sized op-row builder with the six canonical keys in oracle
  order replaces inline map literals on the op-generation paths.
- AppendJournal: append-in-place journal growth (no intermediate slices).
- JoinLines: single-pass body rebuild for the ADD path.
- OpKeys documents the observable op-row envelope.

Live parity re-verified post-pass: battery 162/162 + phaseb 3/3.
2026-10-12 15:35:46 +01:00
Nick 03ef8132a2 feat(curator): port to FULL Go — dichotomy wave
DICHOTOMY VERDICT: GO (Reactive → Go per store/rebase/classification.json).
Name-preserving full-module port of src/rune/curator.py (43 fns) into
src/go/curator/ (package main, modernc.org/sqlite pure-Go store seam).

Truth: scripts/gen_curator_truth.py freezes 162 rows from the live oracle
(paths isolated, dependency cores pinned RUNE_*_CORE=python, clock frozen,
subprocess faked, embed tabled, aaak/memory_store blocked, drift-gate/audit/
drift-ledger seams injected, deep-copied inputs, per-row setup seeds).
Battery truth_test.go replays every row verbatim: 162/162 + live
re-verified + generator deterministic (byte-identical re-freeze).

Python oracle untouched (no facade delegation — strangler surface stays
Python by design); Python contract tests green.
2026-10-12 15:35:46 +01:00
Nick e29cc5ee51 chore(store): close drift_ledger dichotomy contract BUILDER-2026-10-12T13:54:01Z-950331-factory-wave (parity c0ec3135, phase B 62e47b38)
CI Pipeline / best-practices (push) Successful in 8s
CI Pipeline / documentation (push) Successful in 9s
CI Pipeline / rust-surface (push) Failing after 14s
CI Pipeline / go-surface (push) Failing after 16s
CI Pipeline / gitea-offline (push) Successful in 16s
Auto-version and Release / release (push) Successful in 31s
CI Pipeline / type-check (push) Failing after 44s
CI Pipeline / lint (push) Failing after 1m2s
CI Pipeline / canary (push) Failing after 2m13s
deploy / deploy (push) Failing after 15m33s
CI Pipeline / unit-tests (push) Failing after 16m40s
CI Pipeline / required-checks (push) Failing after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Failing after 4s
2026-10-12 14:55:40 +01:00
Nick 8ed117abe2 chore(store): close construction_patterns_builtin dichotomy contract BUILDER-2026-10-12T13:29:58Z-e5c619-factory-wave (parity 4649df1c, phase B b50e8553)
CI Pipeline / best-practices (push) Successful in 8s
CI Pipeline / documentation (push) Successful in 7s
CI Pipeline / rust-surface (push) Failing after 15s
CI Pipeline / go-surface (push) Failing after 18s
Auto-version and Release / release (push) Successful in 30s
CI Pipeline / gitea-offline (push) Successful in 19s
CI Pipeline / type-check (push) Failing after 49s
CI Pipeline / lint (push) Failing after 1m6s
CI Pipeline / canary (push) Failing after 2m17s
deploy / deploy (push) Failing after 15m34s
CI Pipeline / unit-tests (push) Failing after 16m48s
CI Pipeline / required-checks (push) Failing after 2s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Failing after 4s
2026-10-12 14:37:52 +01:00
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
conductor 5501163ec5 chore(release): auto-bump version to 0.231.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 4s
CI Pipeline / rust-surface (push) Failing after 14s
CI Pipeline / go-surface (push) Failing after 17s
Auto-version and Release / release (push) Successful in 24s
CI Pipeline / gitea-offline (push) Successful in 18s
CI Pipeline / type-check (push) Failing after 41s
CI Pipeline / lint (push) Failing after 53s
CI Pipeline / canary (push) Failing after 2m3s
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 4m41s
Promotion record: (unknown)
2026-10-12 04:00:30 +01:00
Nick 8f4b4b4414 chore(store): close worthiness dichotomy contract BUILDER-2026-10-12T02:14:11Z-69cc9a-factory-wave (parity 5ac385832f, phase B 4eed73b551)
CI Pipeline / best-practices (push) Successful in 5s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / rust-surface (push) Failing after 11s
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 / type-check (push) Canceled after 11s
CI Pipeline / canary (push) Canceled after 20s
Auto-version and Release / release (push) Canceled after 29s
CI Pipeline / gitea-offline (push) Canceled after 11s
CI Pipeline / lint (push) Canceled after 26s
CI Pipeline / unit-tests (push) Canceled after 26s
deploy / deploy (push) Canceled after 36s
2026-10-12 03:59:59 +01:00
Nick 4eed73b551 refactor(worthiness): Phase B language-advantage pass — dichotomy wave
Language-advantage pass on the worthiness Rust kernel (battery green
throughout, live parity re-verified 51/51 facade + 51/51 kernel after
rebuild):
- Question lattice is a real enum (PiSafety..Expression) with exhaustive
  as_str/of_name/run matches — no stringly-typed question routing; the
  two-axis gate (expression only for expressive targets, epistemology
  skipped unless bare-factual) is an exhaustive runs_for match.
- Verdict lattice is an enum (Reject | Defer | Absorb | Promote) produced
  from the oracle threshold order; the doc envelope reads as_str().
- assess is iterator-composed: QUESTIONS.filter(runs_for).try_fold over
  the (trail, total) accumulator with early exit on kernel error, keeping
  the last-veto-wins overwrite law as a plain assignment in the fold.
- q_honesty / q_material_reality / q_assistant_goal score branches are
  exhaustive matches instead of cascaded ifs.
- is_bare_factual runs zero-copy for single-word absolutes (lazy
  split(' ') token equality reproduces the oracle's padded f" {t} " in
  f" {low} " check exactly); only multi-word terms materialize a padded
  haystack. The oracle's subjective-predicate dead code stays dead.
- All keyword tables borrow (&[&str]) with low: &str threaded by
  reference end-to end — no per-question lowering or copying.
- Battery constants test moved to the enum surface (as_str projection);
  frozen_wr_truth.json replay untouched and VERBATIM.
2026-10-12 03:59:47 +01:00
Nick 5ac385832f feat(worthiness): port to FULL Rust — dichotomy wave
RUST verdict (dichotomy law, #2479 tranche, contract
BUILDER-2026-10-12T02:14:11Z-69cc9a-factory-wave): worthiness.py's full
constitution-grounded filter surface is ported to src/rust/worthiness —
all nine question kernels (pi_safety protective-frame branch, honesty
good/bad signal counting with the exact note formats, material_reality
finance→hustle/caring lattice, root_cause spin-first ordering, respect
rigid-first ordering, assistant_goal with the store-derived domain
vocabulary, durability, epistemology with the unsound-bare-absolute
precheck, expression with the four signal groups), the two-axis assess
verdict lattice (expression targets skip epistemology unless the text is
a bare factual absolute; last-veto-wins overwrite; DEFER/PROMOTE/ABSORB
threshold boundaries), is_bare_factual (padded-token matching; the
oracle's subjective-predicate list is DEAD CODE and stays dead),
verdict_label with the passthrough fallback.

DOMAIN-CACHE LAW mirrored: q_assistant_goal's `assist = _domain_terms();
assist += [universal..]` mutates the oracle's cache IN PLACE, inflating
hit counts on every call within the 300 s TTL. The Rust kernel owns the
cache (Mutex) and extends it on every call; the store read crosses back
as the `domain_terms` op served by the loader with the oracle's verbatim
query body (sans cache). Frozen truth caught this working: single-hit
counts only land on a process's first q_assistant_goal call.

Wiring: wr_loader.py ctypes loader mirroring broker_loader.py/pc_loader.py
(doc-envelope FFI, op-host callback for domain_terms, oracle-verbatim
tuple unwrapping, free via capability_scoring cs_free_string — no free
symbol re-export), facade delegation in worthiness.py with
RUNE_WR_CORE=python fallback pin.

Truth: frozen_wr_truth.json — 51 cases from the live oracle (deep-copied
inputs, RUNE_WR_CORE=python pinned, every public fn and every branch
incl. all four verdicts, veto last-wins, boundary totals -1.0/0.0,
fact=None coercion, verdict_label passthrough), replayed VERBATIM by the
in-crate branch battery. Battery caught one real port bug pre-landing
(the AESTHETIC signal list was missing 'wonder' — expression hit counts
diverged 5 vs 6). Live parity: facade 51/51 + kernel 51/51 (direct
loader), kernel pass in a subprocess (process-global cache law).
Python tests green both modes.
2026-10-12 03:59:47 +01:00
conductor 3c57ae0326 chore(release): auto-bump version to 0.230.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 6s
CI Pipeline / rust-surface (push) Failing after 13s
Auto-version and Release / release (push) Successful in 23s
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 / type-check (push) Canceled after 15s
CI Pipeline / gitea-offline (push) Canceled after 17s
CI Pipeline / canary (push) Canceled after 26s
CI Pipeline / lint (push) Canceled after 29s
CI Pipeline / unit-tests (push) Canceled after 30s
Promotion record: (unknown)
2026-10-12 03:59:25 +01:00
Nick 2e3b7e76ec feat(openai_compat_transport): port to FULL Go — dichotomy wave
CI Pipeline / rust-surface (push) Failing after 17s
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
CI Pipeline / go-surface (push) Canceled after 19s
CI Pipeline / best-practices (push) Canceled after 1s
Auto-version and Release / release (push) Canceled after 27s
CI Pipeline / canary (push) Canceled after 21s
deploy / deploy (push) Canceled after 25s
CI Pipeline / lint (push) Canceled after 25s
CI Pipeline / unit-tests (push) Canceled after 25s
2026-10-12 03:58:49 +01:00
Nick ee411f8e6e chore(store): close grading dichotomy contract BUILDER-2026-10-12T00:56:50Z-c1e93f-factory-wave (parity 1494c1a5, phase B 04583cd2)
CI Pipeline / best-practices (push) Successful in 10s
Auto-version and Release / release (push) Successful in 26s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / rust-surface (push) Failing after 14s
CI Pipeline / go-surface (push) Failing after 17s
CI Pipeline / gitea-offline (push) Successful in 18s
CI Pipeline / type-check (push) Failing after 37s
CI Pipeline / lint (push) Failing after 52s
CI Pipeline / canary (push) Failing after 2m1s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
deploy / deploy (push) Canceled after 8m21s
CI Pipeline / unit-tests (push) Canceled after 8m11s
2026-10-12 03:50:44 +01:00
conductor 9e6bc4b3c7 chore(release): auto-bump version to 0.229.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 26s
CI Pipeline / gitea-offline (push) Successful in 18s
CI Pipeline / type-check (push) Failing after 42s
CI Pipeline / lint (push) Failing after 54s
CI Pipeline / canary (push) Failing after 2m5s
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 2m26s
Promotion record: (unknown)
2026-10-12 03:48:22 +01:00
Nick 31e87e612b chore(store): close forge_client dichotomy contract BUILDER-2026-10-12T02:17:44Z-8feb0d-factory-wave (parity 797bb63f, phase B dc20b9b2)
CI Pipeline / best-practices (push) Successful in 7s
CI Pipeline / documentation (push) Successful in 7s
CI Pipeline / rust-surface (push) Failing after 11s
CI Pipeline / go-surface (push) Failing after 14s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
CI Pipeline / gitea-offline (push) Canceled after 18s
CI Pipeline / unit-tests (push) Canceled after 25s
Auto-version and Release / release (push) Canceled after 26s
CI Pipeline / lint (push) Canceled after 26s
CI Pipeline / canary (push) Canceled after 25s
deploy / deploy (push) Canceled after 15s
CI Pipeline / type-check (push) Canceled after 21s
2026-10-12 03:47:57 +01:00
Nick dc20b9b295 refactor(forge_client): Phase B language-advantage pass — dichotomy wave 2026-10-12 03:46:49 +01:00
Nick 797bb63f3c feat(forge_client): port to FULL Go — dichotomy wave 2026-10-12 03:46:49 +01:00
Nick 04583cd26a refactor(grading): Phase B language-advantage pass — dichotomy wave
Auto-version and Release / release (push) Failing after 57s
CI Pipeline / rust-surface (push) Failing after 57s
CI Pipeline / best-practices (push) Failing after 58s
CI Pipeline / lint (push) Failing after 58s
CI Pipeline / unit-tests (push) Failing after 1m0s
CI Pipeline / documentation (push) Failing after 1m0s
CI Pipeline / canary (push) Failing after 1m1s
CI Pipeline / go-surface (push) Failing after 1m4s
CI Pipeline / gitea-offline (push) Successful in 14s
CI Pipeline / type-check (push) Failing after 34s
CI Pipeline / required-checks (push) Failing after 0s
deploy / deploy (push) Failing after 40s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Failing after 4s
2026-10-12 03:42:52 +01:00
Nick 1494c1a5db feat(grading): port to FULL Rust — dichotomy wave 2026-10-12 03:42:52 +01:00
Nick bcc839e78b chore(store): close testing_mode dichotomy contract BUILDER-2026-10-12T00:49:26Z-b3e925-factory-wave (parity 626359e6, phase B 3c70f3bd)
CI Pipeline / best-practices (push) Successful in 9s
CI Pipeline / documentation (push) Successful in 5s
CI Pipeline / rust-surface (push) Failing after 16s
Auto-version and Release / release (push) Successful in 24s
CI Pipeline / go-surface (push) Failing after 18s
CI Pipeline / gitea-offline (push) Successful in 19s
CI Pipeline / type-check (push) Failing after 42s
CI Pipeline / lint (push) Failing after 57s
CI Pipeline / canary (push) Failing after 2m4s
deploy / deploy (push) Failing after 15m30s
CI Pipeline / unit-tests (push) Failing after 16m22s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Failing after 5s
2026-10-12 03:01:07 +01:00
Nick d980d486a7 chore(store): close evidence_probe dichotomy contract BUILDER-2026-10-12T01:50:41Z-4cc7c3-factory-wave (parity 9ba51ba5, phase B 25f7cb6d)
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 19s
Auto-version and Release / release (push) Successful in 28s
CI Pipeline / gitea-offline (push) Successful in 20s
CI Pipeline / lint (push) Failing after 54s
CI Pipeline / type-check (push) Failing after 42s
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 2m1s
CI Pipeline / canary (push) Canceled after 2m0s
deploy / deploy (push) Canceled after 2m11s
2026-10-12 02:59:04 +01:00
182 changed files with 140297 additions and 458 deletions

No files matched your search

+1 -1
View File
@@ -1,6 +1,6 @@
# Rune
**Current release: v0.228.0**
**Current release: v0.239.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.228.0"
version = "0.239.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)
@@ -0,0 +1,55 @@
#!/usr/bin/env python3
"""Close the #2479 dichotomy-wave construction contract for
construction_patterns_builtin. 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/construction_patterns_builtin.py to FULL Rust (dichotomy "
"factory, issue #2479 tranche): frozen-oracle registry data, doc-envelope "
"FFI, ctypes loader (free via capability_scoring cs_free_string), facade "
"delegation with RUNE_CPB_CORE=python pin, frozen-truth branch battery, "
"live parity 100%, Phase B typed-registry pass. Plus audit fix: curator "
"-> curator-core alias (curator was FULL-Go since a95c6ce1 but the audit "
"miscounted it NOT TOUCHED).",
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": "construction_patterns_builtin parity port — final SHAs after "
"rebase: parity 4649df1c, phase B b50e8553. Battery 19 cases "
"green, live parity 16/16, clippy 0.",
}], scope_files=[
"src/rune/construction_patterns_builtin.py",
"src/rune/cpb_loader.py",
"src/rust/construction_patterns_builtin/src/lib.rs",
"src/rust/construction_patterns_builtin/src/registry.rs",
"src/rust/construction_patterns_builtin/src/branch_battery.rs",
"src/rust/construction_patterns_builtin/frozen_cpb_patterns.json",
"src/rust/construction_patterns_builtin/frozen_cpb_truth.json",
"scripts/gen_construction_patterns_builtin_truth.py",
"scripts/check_cpb_parity.py",
])
print("close:", json.dumps(c)[:400])
print("CONTRACT", cid, "closed" if c.get("success") else "CLOSE_FAILED")
+61
View File
@@ -0,0 +1,61 @@
#!/usr/bin/env python3
"""Open + walk + close the #2479 dichotomy-wave construction contract for
drift_ledger. Full lifecycle in ONE process (shared-store clobber hazard,
see notes-construction-intent-batch.md)."""
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(
"Complete the drift_ledger dichotomy port to FULL Rust (#2479 tranche): "
"check/alert-loop, snapshot/anchor assembly, auto-fix routing and fix "
"gate as doc-envelope FFI; dl_loader ctypes loader mirroring "
"broker_loader.py (free via capability_scoring cs_free_string); facade "
"delegation with RUNE_DL_CORE=python pin; frozen-oracle battery (87 "
"cases, every public function/branch); live parity 100%; Phase B "
"language-advantage pass; dichotomy-audit instrument fixes (curator "
"alias + hyphen-twin mapping).",
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": "drift_ledger dichotomy completion — parity c0ec3135 "
"feat(drift_ledger): port remaining cores to FULL Rust — "
"dichotomy wave; Phase B 62e47b38 refactor(drift_ledger): "
"Phase B language-advantage pass — dichotomy wave (both pushed "
"gitea/master). Battery 8 tests replay the 87-case frozen "
"oracle truth VERBATIM; live parity 87/87 byte-identical "
"(RUNE_DL_CORE=auto vs =python); clippy -D warnings clean; "
"Python tests green (22 passed on drift-touching suites; 6 "
"pre-existing unrelated failures on the clean tree). Audit: "
"drift-ledger 14/14 FULL, drift_ledger 20/22 FULL, curator "
"40/43 FULL after the instrument fixes.",
}], scope_files=[
"src/rune/drift_ledger.py",
"src/rune/dl_loader.py",
"src/rust/drift_ledger/src/lib.rs",
"src/rust/drift_ledger/src/branch_battery.rs",
"scripts/gen_drift_ledger_truth.py",
"scripts/dichotomy_audit.py",
"docs/dichotomy-rebase-honest-audit.md",
])
print("close:", json.dumps(c)[:400])
print("CONTRACT", cid, "closed" if c.get("success") else "CLOSE_FAILED")
+43
View File
@@ -0,0 +1,43 @@
#!/usr/bin/env python3
"""Close the #2479 dichotomy-wave forge_client contract with SHA evidence.
Run on top of CURRENT master (a worktree store close never reaches master
by itself — build_basis/message_queue precedent)."""
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
CID = sys.argv[1] if len(sys.argv) > 1 else ""
PARITY_SHA = sys.argv[2] if len(sys.argv) > 2 else ""
PHASEB_SHA = sys.argv[3] if len(sys.argv) > 3 else ""
if not (CID and PARITY_SHA and PHASEB_SHA):
sys.exit("usage: close_contract_forge_client.py <contract_id> <parity_sha> <phaseb_sha>")
iface = ConstructionInterface(builder="factory-wave")
c = iface.close(CID, observed_evidence=[{
"kind": "port_evidence",
"detail": (
f"forge_client GO dichotomy port — parity commit {PARITY_SHA}, "
f"Phase B commit {PHASEB_SHA}; parity 49/49 frozen rows vs live "
"oracle (scripts/gen_forge_client_truth.py), go test -race green, "
"gofmt/vet clean, Python oracle tests 18 passed."
),
}], scope_files=[
"src/rune/forge_client.py",
"src/go/forge_client/forge_client.go",
"src/go/forge_client/phaseb.go",
"src/go/forge_client/replay.go",
"src/go/forge_client/main.go",
"src/go/forge_client/truth_test.go",
"src/go/forge_client/phaseb_test.go",
"src/go/forge_client/truth.json",
"src/go/forge_client/go.mod",
"scripts/gen_forge_client_truth.py",
"scripts/open_contract_forge_client.py",
])
print("close:", json.dumps(c)[:400])
print("CONTRACT", CID, "closed" if c.get("success") else "CLOSE_FAILED")
+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")
File diff suppressed because it is too large. Load diff
+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())
+346
View File
@@ -0,0 +1,346 @@
#!/usr/bin/env python3
"""Truth generator for the forge_client dichotomy port (#2479 tranche, GO).
Runs the LIVE Python oracle (src/rune/forge_client.py) with the httpx
transport fully scripted, exercises every public function and every branch,
and freezes inputs + expected outputs to src/go/forge_client/truth.json
(consumed VERBATIM by the Go battery).
No clock, no store, no env leakage: every case is hermetic — the transport
is a scripted fake (never a real socket), sleep is a recording fake, and
os.environ is snapshotted/restored around every selection case.
Scripted entry shapes:
{"status": N, "payload": {...}} → FakeResponse(N, payload)
{"exc": "connect" | "connect_timeout" | "read_timeout"} → scripted
httpx exception (connect-phase vs non-connect transport, the
NOISE-vs-SIGNAL axis of _request).
"""
import json
import os
import sys
from pathlib import Path
from unittest.mock import patch
import httpx
REPO = Path(__file__).resolve().parents[1]
OUT = REPO / "src" / "go" / "forge_client" / "truth.json"
sys.path.insert(0, str(REPO / "src"))
import rune.forge_client as fc # noqa: E402
EXC = {
"connect": httpx.ConnectError,
"connect_timeout": httpx.ConnectTimeout,
"read_timeout": httpx.ReadTimeout,
}
rows = []
coverage = set()
def add(name, op, inp, want, covered):
rows.append({"name": name, "op": op, "input": inp, "want": want})
coverage.update(covered)
class FakeResponse:
def __init__(self, status=200, payload=None):
self.status_code = status
self._payload = payload if payload is not None else {}
def json(self):
return self._payload
def raise_for_status(self):
if self.status_code >= 400:
raise httpx.HTTPStatusError(
f"error {self.status_code}",
request=httpx.Request("GET", "https://x"),
response=httpx.Response(self.status_code),
)
def make_fake(method, script, rec):
pending = list(script)
def fake(url, **kw):
rec["requests"].append(
{
"method": method,
"url": url,
"headers": kw.get("headers"),
"json": kw.get("json"),
"timeout": kw.get("timeout"),
}
)
if not pending:
raise AssertionError(f"script exhausted at request {len(rec['requests'])}")
entry = pending.pop(0)
if "exc" in entry:
raise EXC[entry["exc"]]("scripted")
return FakeResponse(entry.get("status", 200), entry.get("payload"))
return fake
def blank_rec():
return {"requests": [], "sleeps": [], "error": None}
def finish_rec(rec, result):
return {
"result": result,
"requests": rec["requests"],
"sleeps": rec["sleeps"],
"error": rec["error"],
}
def call_case(name, method_name, script, kwargs, *, url="https://forge.example.com",
token="tok", owner="o", repo="r", covered=()):
rec = blank_rec()
adapter = fc.GiteaForgejoAdapter(url, token, owner=owner, repo=repo)
with patch("rune.forge_client.httpx.get", make_fake("GET", script, rec)), patch(
"rune.forge_client.httpx.post", make_fake("POST", script, rec)
), patch("rune.forge_client.time.sleep", rec["sleeps"].append):
try:
result = getattr(adapter, method_name)(**kwargs)
except Exception as e: # noqa: BLE001 — the kind IS the information
rec["error"] = f"httpx.{type(e).__name__}"
result = None
add(
name,
"call",
{
"method": method_name,
"url": url,
"token": token,
"owner": owner,
"repo": repo,
"script": script,
"kwargs": kwargs,
},
finish_rec(rec, result),
("GiteaForgejoAdapter." + method_name,) + covered,
)
def request_case(name, method, script, *, headers=None, json_body=None, timeout=30,
retries=2, backoff=0.5, covered=()):
rec = blank_rec()
with patch("rune.forge_client.httpx.get", make_fake("GET", script, rec)), patch(
"rune.forge_client.httpx.post", make_fake("POST", script, rec)
):
try:
result = fc._request(
method,
"https://x/api",
headers=headers,
json=json_body,
timeout=timeout,
retries=retries,
backoff=backoff,
sleep=rec["sleeps"].append,
).json()
except Exception as e: # noqa: BLE001
rec["error"] = f"httpx.{type(e).__name__}"
result = None
add(
name,
"request",
{
"method": method,
"url": "https://x/api",
"headers": headers,
"json": json_body,
"timeout": timeout,
"retries": retries,
"backoff": backoff,
"script": script,
},
finish_rec(rec, result),
("_request",) + covered,
)
def select_case(name, config, env, covered=()):
saved = {k: os.environ.get(k) for k in
("FORGE_URL", "FORGE_TOKEN", "FORGE_OWNER", "FORGE_REPO")}
try:
for k in ("FORGE_URL", "FORGE_TOKEN", "FORGE_OWNER", "FORGE_REPO"):
os.environ.pop(k, None)
os.environ.update({k: v for k, v in env.items() if v is not None})
client = fc.create_forge_client(config)
if isinstance(client, fc.NullAdapter):
want = {"adapter": "NullAdapter", "base_url": None, "token": None}
else:
want = {
"adapter": "GiteaForgejoAdapter",
"base_url": client._base_url,
"token": client._token,
}
finally:
for k, v in saved.items():
if v is None:
os.environ.pop(k, None)
else:
os.environ[k] = v
add(name, "select", {"config": config, "env": env}, want,
("create_forge_client",) + covered)
def null_case(name, method_name, args):
client = fc.NullAdapter()
result = getattr(client, method_name)(*args)
add(name, "null", {"method": method_name, "args": args}, result,
("NullAdapter." + method_name,))
# ── create_forge_client: every selection branch ─────────────────────────────
select_case("select:none", None, {})
select_case("select:env-configured", {}, {"FORGE_URL": "https://forge.example.com",
"FORGE_TOKEN": "test_token"})
select_case("select:config-keys",
{"forge.url": "https://cfg.example.com", "forge.token": "cfg_tok",
"forge.owner": "cfg_owner", "forge.repo": "cfg_repo"}, {})
select_case("select:config-beats-env",
{"forge.url": "https://cfg.example.com", "forge.token": "cfg_tok"},
{"FORGE_URL": "https://env.example.com", "FORGE_TOKEN": "env_tok"})
select_case("select:owner-repo-defaults", {},
{"FORGE_URL": "https://forge.example.com", "FORGE_TOKEN": "test_token"})
select_case("select:owner-repo-env", {},
{"FORGE_URL": "https://forge.example.com", "FORGE_TOKEN": "test_token",
"FORGE_OWNER": "env_owner", "FORGE_REPO": "env_repo"})
select_case("select:trailing-slash-strip", {},
{"FORGE_URL": "https://forge.example.com///", "FORGE_TOKEN": "test_token"})
select_case("select:empty-cfg-url-falls-to-env",
{"forge.url": "", "forge.token": ""},
{"FORGE_URL": "https://forge.example.com", "FORGE_TOKEN": "test_token"})
select_case("select:cfg-url-missing-token", {"forge.url": "https://cfg.example.com"}, {})
select_case("select:cfg-token-missing-url", {"forge.token": "cfg_tok"}, {})
select_case("select:owner-empty-env", {},
{"FORGE_URL": "https://forge.example.com", "FORGE_TOKEN": "test_token",
"FORGE_OWNER": ""})
# ── NullAdapter: every no-op shape ──────────────────────────────────────────
null_case("null:create-issue", "create_issue", ["t", "b"])
null_case("null:add-comment", "add_comment", [3, "c"])
null_case("null:get-issue-state", "get_issue_state", [7])
null_case("null:list-open-issues", "list_open_issues", [])
null_case("null:search-issues", "search_issues", ["q"])
# ── GiteaForgejoAdapter: every method + happy paths ─────────────────────────
call_case("call:create-issue", "create_issue",
[{"status": 200, "payload": {"number": 42, "state": "open"}}],
{"title": "Test", "body": "Body"})
call_case("call:create-issue-201", "create_issue",
[{"status": 201, "payload": {"number": 1, "title": "t"}}],
{"title": "t", "body": "b"})
call_case("call:create-issue-4xx", "create_issue",
[{"status": 422, "payload": {}}], {"title": "dup", "body": "b"})
call_case("call:add-comment", "add_comment",
[{"status": 200, "payload": {"id": 9, "body": "c"}}],
{"issue_number": 5, "body": "c"})
call_case("call:get-issue-state", "get_issue_state",
[{"status": 200, "payload": {"number": 9, "state": "open"}}],
{"issue_number": 9})
call_case("call:list-open-issues", "list_open_issues",
[{"status": 200, "payload": [{"number": 1}, {"number": 2}]}], {})
call_case("call:search-single-page", "search_issues",
[{"status": 200, "payload": [{"number": 3, "title": "[auto-failure:cron:Connection]"}]}],
{"query": "[auto-failure:cron:Connection]"}, covered=("GiteaForgejoAdapter.search_issues.pagination",))
call_case("call:search-paginates", "search_issues",
[{"status": 200, "payload": [{"number": n, "title": f"issue {n}"} for n in range(50)]},
{"status": 200, "payload": [{"number": 100, "title": "[auto-failure:cron:Connection] cron"}]}],
{"query": "[auto-failure:cron:Connection]"},
covered=("GiteaForgejoAdapter.search_issues.pagination",))
call_case("call:search-filters-title-body", "search_issues",
[{"status": 200, "payload": [
{"number": 1, "title": "[auto-failure:cron:Connection] cron", "body": "x"},
{"number": 2, "title": "plain title", "body": "quoted [auto-failure:cron:Connection] in body"},
{"number": 3, "title": "mention of connection only", "body": "unrelated"}]}],
{"query": "[auto-failure:cron:Connection]"},
covered=("GiteaForgejoAdapter.search_issues.client-filter",))
call_case("call:search-no-hits", "search_issues",
[{"status": 200, "payload": [{"number": 1, "title": "unrelated"}]}],
{"query": "[auto-failure:cron:Connection]"})
call_case("call:search-exact-50-then-empty", "search_issues",
[{"status": 200, "payload": [{"number": n, "title": "noise"} for n in range(50)]},
{"status": 200, "payload": []}],
{"query": "[auto-failure:cron:Connection]"},
covered=("GiteaForgejoAdapter.search_issues.pagination",))
call_case("call:create-issue-retry-connect", "create_issue",
[{"exc": "connect"}, {"status": 200, "payload": {"number": 7}}],
{"title": "t", "body": "b"}, covered=("_request",))
call_case("call:get-issue-state-retry-5xx", "get_issue_state",
[{"status": 502}, {"status": 200, "payload": {"number": 9, "state": "open"}}],
{"issue_number": 9}, covered=("_request",))
call_case("call:get-issue-state-4xx", "get_issue_state",
[{"status": 404, "payload": {}}], {"issue_number": 404}, covered=("_request",))
# ── _request: every retry branch (noise vs signal) ──────────────────────────
request_case("request:get-ok", "GET", [{"status": 200, "payload": {"ok": True}}])
request_case("request:get-retries-connect", "GET",
[{"exc": "connect"}, {"exc": "connect"},
{"status": 200, "payload": {"ok": True}}])
request_case("request:get-connect-exhaust", "GET",
[{"exc": "connect"}, {"exc": "connect"}, {"exc": "connect"}])
request_case("request:get-4xx-no-retry", "GET", [{"status": 422}])
request_case("request:get-5xx-retry-then-ok", "GET",
[{"status": 502}, {"status": 503}, {"status": 200, "payload": {"n": 3}}])
request_case("request:get-5xx-exhaust", "GET", [{"status": 502}, {"status": 503}, {"status": 504}])
request_case("request:get-read-timeout-retries", "GET",
[{"exc": "read_timeout"}, {"status": 200, "payload": {"ok": True}}])
request_case("request:get-read-timeout-exhaust", "GET",
[{"exc": "read_timeout"}, {"exc": "read_timeout"}, {"exc": "read_timeout"}])
request_case("request:post-ok", "POST", [{"status": 200, "payload": {"a": 1}}],
json_body={"a": 1})
request_case("request:post-connect-retry", "POST",
[{"exc": "connect_timeout"}, {"status": 201, "payload": {"number": 7}}],
json_body={"a": 1})
request_case("request:post-connect-exhaust", "POST",
[{"exc": "connect"}, {"exc": "connect"}, {"exc": "connect"}],
json_body={"a": 1})
request_case("request:post-read-timeout-no-retry", "POST",
[{"exc": "read_timeout"}], json_body={"a": 1})
request_case("request:post-5xx-no-retry", "POST", [{"status": 502}], json_body={"a": 1})
request_case("request:post-4xx-no-retry", "POST", [{"status": 422}], json_body={"a": 1})
request_case("request:backoff-exponential", "GET",
[{"exc": "connect"}, {"exc": "connect"}, {"status": 200}])
request_case("request:custom-retries-backoff", "GET",
[{"exc": "connect"}, {"status": 200}], retries=1, backoff=0.25)
request_case("request:timeout-passthrough", "GET",
[{"status": 200}], timeout=17)
request_case("request:headers-json-capture", "POST",
[{"status": 200, "payload": {}}],
headers={"Authorization": "token t", "Content-Type": "application/json"},
json_body={"title": "t", "body": "b"})
request_case("request:lowercase-method", "post",
[{"status": 200, "payload": {"made": "it"}}], json_body={"k": "v"})
# ── freeze ───────────────────────────────────────────────────────────────────
EXPECTED = {
"create_forge_client", "_request",
"NullAdapter.create_issue", "NullAdapter.add_comment", "NullAdapter.get_issue_state",
"NullAdapter.list_open_issues", "NullAdapter.search_issues",
"GiteaForgejoAdapter.create_issue", "GiteaForgejoAdapter.add_comment",
"GiteaForgejoAdapter.get_issue_state", "GiteaForgejoAdapter.list_open_issues",
"GiteaForgejoAdapter.search_issues",
}
missing = EXPECTED - coverage
if missing:
sys.exit(f"COVERAGE GAP — public functions not exercised: {sorted(missing)}")
doc = {
"frozen_ts": "2026-10-12T02:17:44Z",
"oracle": "src/rune/forge_client.py",
"rows": rows,
}
OUT.parent.mkdir(parents=True, exist_ok=True)
OUT.write_text(json.dumps(doc, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
print(f"frozen {len(rows)} rows → {OUT}")
print("covered:", ", ".join(sorted(coverage)))
+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())
@@ -0,0 +1,526 @@
#!/usr/bin/env python3
"""Freeze truth for the openai_compat_transport Go port (dichotomy wave #2479).
Exercises EVERY public function of src/rune/openai_compat_transport.py against
the LIVE oracle: __init__ (timeout clamp), _post (wire + non-object guard),
_to_openai_messages (all branches), _from_openai_response (all branches),
chat (model split, tools, options, usage), supports_tools, supports_thinking,
context_length, is_cloud_pinned, cloud_transport_for (config/env/auth seams).
Wire IO served by a local stub HTTP server that captures each request
(path, authorization, content-type, body bytes) and returns scripted bodies;
cloud_transport_for's IO seams (os.path.expanduser, os.environ) are patched
to temp files so config/auth resolution is deterministic.
Excluded branches (interpreter-specific error text — not portable truth):
- _post on a non-2xx HTTP status (Python urllib HTTPError string vs Go
net/http error string) and on connection failures.
- _post on a body that is invalid JSON (Python json.JSONDecodeError text).
- cloud_transport_for with auth.json containing invalid JSON (same reason).
Numbers: fixtures are written by json.dump so every numeric literal is in
Python's canonical repr; the Go port preserves literals on parse. Floats in
fixtures stay in the shortest-decimal band (no exponents) so literal
preservation equals Python re-rendering.
Nondeterministic: usage_total_duration_ns (time.monotonic) is excluded from
every row; the Go battery asserts it is > 0 after a chat instead.
"""
import json
import os
import sys
import tempfile
import threading
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
from unittest import mock
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "src"))
import rune.openai_compat_transport as oct_mod # noqa: E402
# -- stub wire ---------------------------------------------------------------
class StubState:
def __init__(self):
self.script = [] # list of response body strings
self.requests = [] # captured requests, one dict per POST
def reset(self, bodies):
self.script = list(bodies)
self.requests = []
STUB = StubState()
class Handler(BaseHTTPRequestHandler):
def do_POST(self):
length = int(self.headers.get("Content-Length", 0))
body = self.rfile.read(length)
STUB.requests.append({
"path": self.path,
"authorization": self.headers.get("Authorization"),
"content_type": self.headers.get("Content-Type"),
"body": body.decode("utf-8"),
})
resp = STUB.script.pop(0)
data = resp.encode("utf-8")
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(data)))
self.end_headers()
self.wfile.write(data)
def log_message(self, *args):
pass
def start_stub():
server = ThreadingHTTPServer(("127.0.0.1", 0), Handler)
thread = threading.Thread(target=server.serve_forever, daemon=True)
thread.start()
return server, f"http://127.0.0.1:{server.server_address[1]}"
# -- row helpers -------------------------------------------------------------
ROWS = []
def add(name, op, setup, want):
ROWS.append({"name": name, "op": op, "setup": setup, "want": want})
def attrs(t):
return {
"base_url": t.base_url,
"api_key": t.api_key,
"provider": t.provider,
"timeout": t.timeout,
"context_window": t._context_window,
"usage_calls": t.usage_calls,
"usage_prompt_tokens": t.usage_prompt_tokens,
"usage_output_tokens": t.usage_output_tokens,
"usage_total_duration_ns": t.usage_total_duration_ns,
}
# -- cases -------------------------------------------------------------------
def case_ctor():
add("ctor_defaults", "ctor",
{"base_url": "https://api.example.com/v1/", "api_key": "k1", "provider": "Verboo Code"},
attrs(oct_mod.OpenAICompatTransport("https://api.example.com/v1/", "k1", "Verboo Code")))
add("ctor_timeout_clamped_low", "ctor",
{"base_url": "http://x/v1", "api_key": "k", "provider": "T", "timeout": 30},
attrs(oct_mod.OpenAICompatTransport("http://x/v1", "k", "T", timeout=30)))
add("ctor_timeout_clamp_boundary", "ctor",
{"base_url": "http://x/v1", "api_key": "k", "provider": "T", "timeout": 120},
attrs(oct_mod.OpenAICompatTransport("http://x/v1", "k", "T", timeout=120)))
add("ctor_timeout_above_floor_float", "ctor",
{"base_url": "http://x/v1", "api_key": "k", "provider": "T", "timeout": 130.5, "context_window": 8192},
attrs(oct_mod.OpenAICompatTransport("http://x/v1", "k", "T", timeout=130.5, context_window=8192)))
def case_to_messages():
f = oct_mod.OpenAICompatTransport._to_openai_messages
cases = [
("tm_empty", []),
("tm_plain_roles", [
{"role": "system", "content": "sys"},
{"role": "user", "content": "task"},
{"role": "assistant", "content": "hi"},
]),
("tm_missing_content_defaults_empty", [{"role": "user"}]),
("tm_null_content_stays_null", [{"role": "user", "content": None}]),
("tm_null_role", [{"content": "orphan"}]),
("tm_assistant_empty_tool_calls_is_plain", [
{"role": "assistant", "content": "text", "tool_calls": []},
]),
("tm_tool_call_dict_args", [
{"role": "assistant", "content": "", "tool_calls": [
{"function": {"name": "edit_file", "arguments": {"path": "/x.py", "old": "a", "new": "b"}}}]},
{"role": "tool", "tool_call_id": "call_2_0", "content": "{\"success\": true}"},
]),
("tm_tool_call_string_args", [
{"role": "assistant", "content": "working", "tool_calls": [
{"function": {"name": "read_file", "arguments": "{\"path\": \"/x.py\"}"}}]},
]),
("tm_tool_call_missing_args_defaults_empty_obj", [
{"role": "assistant", "content": None, "tool_calls": [{"function": {"name": "noop"}}]},
]),
("tm_tool_call_missing_function", [
{"role": "assistant", "content": None, "tool_calls": [{"id": "weird"}]},
]),
("tm_tool_call_non_dict_args_int", [
{"role": "assistant", "content": None, "tool_calls": [
{"function": {"name": "ping", "arguments": 42}}]},
]),
("tm_tool_call_non_dict_args_list", [
{"role": "assistant", "content": None, "tool_calls": [
{"function": {"name": "bulk", "arguments": ["a", "b"]}}]},
]),
("tm_tool_call_null_args_str", [
{"role": "assistant", "content": None, "tool_calls": [
{"function": {"name": "q", "arguments": "None"}}]},
]),
("tm_ids_paired_from_following_tools", [
{"role": "assistant", "content": "", "tool_calls": [
{"function": {"name": "a", "arguments": {}}, },
{"function": {"name": "b", "arguments": {}}, }]},
{"role": "tool", "tool_call_id": "call_0_0", "content": "r1"},
{"role": "tool", "tool_call_id": "call_0_1", "content": "r2"},
]),
("tm_ids_fallback_when_unpaired", [
{"role": "assistant", "content": "", "tool_calls": [
{"function": {"name": "a", "arguments": {}}},
{"function": {"name": "b", "arguments": {}}}]},
{"role": "tool", "tool_call_id": "call_0_0", "content": "r1"},
]),
("tm_ids_scan_nonadjacent_tools", [
{"role": "assistant", "content": "", "tool_calls": [
{"function": {"name": "a", "arguments": {}}},
{"function": {"name": "b", "arguments": {}}}]},
{"role": "user", "content": "and?"},
{"role": "tool", "tool_call_id": "aa", "content": "r1"},
{"role": "tool", "tool_call_id": "bb", "content": "r2"},
]),
("tm_paired_id_null_falls_back", [
{"role": "assistant", "content": "", "tool_calls": [
{"function": {"name": "a", "arguments": {}}}]},
{"role": "tool", "content": "no id"},
]),
("tm_tool_message_defaults", [
{"role": "tool", "content": "result"},
{"role": "tool", "tool_call_id": "t9", "content": "r"},
{"role": "tool"},
]),
("tm_mixed_conversation", [
{"role": "system", "content": "sys"},
{"role": "user", "content": "go"},
{"role": "assistant", "content": "", "tool_calls": [
{"function": {"name": "ls", "arguments": {"path": "."}}}]},
{"role": "tool", "tool_call_id": "call_2_0", "content": "files"},
{"role": "assistant", "content": "done"},
]),
]
for name, messages in cases:
add(name, "to_messages", {"messages": messages}, f(messages))
def case_from_response():
f = oct_mod.OpenAICompatTransport._from_openai_response
cases = [
("fr_plain_content", {"choices": [{"message": {"role": "assistant", "content": "hello"}}]}),
("fr_missing_choices", {}),
("fr_empty_choices", {"choices": []}),
("fr_missing_message", {"choices": [{}]}),
("fr_null_content_becomes_empty", {"choices": [{"message": {"content": None}}]}),
("fr_no_tool_calls_key", {"choices": [{"message": {"content": "x", "tool_calls": []}}]}),
("fr_null_tool_calls", {"choices": [{"message": {"content": "x", "tool_calls": None}}]}),
("fr_tool_call_string_args", {"choices": [{"message": {"tool_calls": [
{"id": "z", "type": "function",
"function": {"name": "run_tests", "arguments": "{\"path\": \"t\"}"}}]}}]}),
("fr_tool_call_malformed_args_raw", {"choices": [{"message": {"tool_calls": [
{"function": {"name": "x", "arguments": "not-json"}}]}}]}),
("fr_tool_call_dict_args_kept", {"choices": [{"message": {"tool_calls": [
{"function": {"name": "y", "arguments": {"a": 1}}}]}}]}),
("fr_tool_call_null_args_become_empty_obj", {"choices": [{"message": {"tool_calls": [
{"function": {"name": "n", "arguments": None}}]}}]}),
("fr_tool_call_empty_string_args_raw", {"choices": [{"message": {"tool_calls": [
{"function": {"name": "e", "arguments": ""}}]}}]}),
("fr_tool_call_list_args_kept", {"choices": [{"message": {"tool_calls": [
{"function": {"name": "l", "arguments": [1, 2]}}]}}]}),
("fr_tool_call_id_only_when_present", {"choices": [{"message": {"tool_calls": [
{"function": {"name": "a", "arguments": "{}"}},
{"id": "", "function": {"name": "b", "arguments": "{}"}},
{"id": "c3", "function": {"name": "c", "arguments": "{}"}}]}}]}),
("fr_tool_call_missing_function", {"choices": [{"message": {"tool_calls": [{}]}}]}),
("fr_multiple_tool_calls", {"choices": [{"message": {"content": None, "tool_calls": [
{"id": "i1", "function": {"name": "a", "arguments": "{\"x\": 1}"}},
{"id": "i2", "function": {"name": "b", "arguments": {"y": [2]}}}]}}]}),
("fr_choices_null_becomes_default", {"choices": None}),
]
for name, resp in cases:
add(name, "from_response", {"resp": resp}, f(resp))
def case_post(server_base):
t = oct_mod.OpenAICompatTransport(server_base, "sekrit", "Prov")
def run(path, payload, bodies):
STUB.reset(bodies)
try:
result = t._post(path, payload)
return {"request": STUB.requests[0], "result": result}
except ValueError as e:
return {"request": STUB.requests[0], "error": str(e)}
payload = {"model": "glm-5.3-flash", "messages": [{"role": "user", "content": "hi"}], "stream": False}
add("post_roundtrip", "post",
{"path": "/chat/completions", "payload": payload, "bodies": [
json.dumps({"choices": [{"message": {"content": "ok"}}], "usage": {"prompt_tokens": 5, "completion_tokens": 7}})]},
run("/chat/completions", payload, [json.dumps({"choices": [{"message": {"content": "ok"}}], "usage": {"prompt_tokens": 5, "completion_tokens": 7}})]))
add("post_non_object_list", "post",
{"path": "/x", "payload": {"a": 1}, "bodies": ["[1, 2]"]},
run("/x", {"a": 1}, ["[1, 2]"]))
add("post_non_object_null", "post",
{"path": "/x", "payload": {}, "bodies": ["null"]},
run("/x", {}, ["null"]))
add("post_non_object_string", "post",
{"path": "/x", "payload": {}, "bodies": ['"text"']},
run("/x", {}, ['"text"']))
def case_chat(server_base):
cases = [
("chat_qualified_model_bare_on_wire", {
"model": "Verboo Code/glm-5.3-flash",
"messages": [{"role": "user", "content": "hi"}],
"tools": None, "options": None,
"responses": ['{"choices": [{"message": {"content": "ok"}}], "usage": {}}'],
}),
("chat_bare_model_unchanged", {
"model": "glm-5.3-flash",
"messages": [{"role": "user", "content": "hi"}],
"tools": None, "options": None,
"responses": ['{"choices": [{"message": {"content": "ok"}}]}'],
}),
("chat_tools_passthrough", {
"model": "V/g", "messages": [{"role": "user", "content": "go"}],
"tools": [{"type": "function", "function": {"name": "ls", "description": "list", "parameters": {"type": "object"}}}],
"options": None,
"responses": ['{"choices": [{"message": {"content": "x"}}]}'],
}),
("chat_empty_tools_omitted", {
"model": "V/g", "messages": [{"role": "user", "content": "go"}],
"tools": [], "options": None,
"responses": ['{"choices": [{"message": {"content": "x"}}]}'],
}),
("chat_options_subset", {
"model": "V/g", "messages": [{"role": "user", "content": "go"}],
"tools": None, "options": {"temperature": 0.7, "top_p": 0.9, "max_tokens": 1024, "seed": 7, "stop": ["END"]},
"responses": ['{"choices": [{"message": {"content": "x"}}]}'],
}),
("chat_options_partial_and_empty", {
"model": "V/g", "messages": [{"role": "user", "content": "go"}],
"tools": None, "options": {"max_tokens": 0},
"responses": ['{"choices": [{"message": {"content": "x"}}]}'],
}),
("chat_think_ignored", {
"model": "V/g", "messages": [{"role": "user", "content": "go"}],
"tools": None, "options": None, "think": True,
"responses": ['{"choices": [{"message": {"content": "x"}}]}'],
}),
("chat_think_string_ignored", {
"model": "V/g", "messages": [{"role": "user", "content": "go"}],
"tools": None, "options": None, "think": "high",
"responses": ['{"choices": [{"message": {"content": "x"}}]}'],
}),
("chat_tool_call_roundtrip", {
"model": "Verboo Code/m1", "messages": [
{"role": "user", "content": "go"},
{"role": "assistant", "content": "", "tool_calls": [
{"function": {"name": "edit_file", "arguments": {"path": "/x.py"}}}]},
{"role": "tool", "tool_call_id": "call_1_0", "content": "done"},
],
"tools": [{"type": "function", "function": {"name": "edit_file"}}], "options": {"temperature": 0.2},
"responses": [json.dumps({"choices": [{"message": {"content": None, "tool_calls": [
{"id": "call_1_0", "function": {"name": "edit_file",
"arguments": json.dumps({"path": "/x.py"})}}]}}],
"usage": {"prompt_tokens": 100, "completion_tokens": 20}})],
}),
("chat_usage_string_and_float_coerced", {
"model": "V/g", "messages": [{"role": "user", "content": "go"}],
"tools": None, "options": None,
"responses": ['{"choices": [{"message": {"content": "x"}}], "usage": {"prompt_tokens": "12", "completion_tokens": 3.9}}'],
}),
("chat_non_object_response_raises", {
"model": "V/g", "messages": [{"role": "user", "content": "go"}],
"tools": None, "options": None,
"responses": ["[]"],
}),
("chat_two_calls_accumulate_usage", {
"model": "V/g", "messages": [{"role": "user", "content": "go"}],
"tools": None, "options": None, "calls": 2,
"responses": [
'{"choices": [{"message": {"content": "a"}}], "usage": {"prompt_tokens": 10, "completion_tokens": 4}}',
'{"choices": [{"message": {"content": "b"}}], "usage": {"prompt_tokens": 7, "completion_tokens": 9}}',
],
}),
]
for name, c in cases:
t = oct_mod.OpenAICompatTransport(server_base, "wire-key-1", "Prov")
STUB.reset(c["responses"])
want = {}
try:
for _ in range(c.get("calls", 1)):
result = t.chat(model=c["model"], messages=c["messages"], tools=c["tools"],
options=c["options"], think=c.get("think"))
want["result"] = result
except ValueError as e:
want["error"] = str(e)
want["requests"] = list(STUB.requests)
want["usage"] = {"calls": t.usage_calls, "prompt_tokens": t.usage_prompt_tokens,
"output_tokens": t.usage_output_tokens}
add(name, "chat",
{"base_url": "@STUB@", "api_key": "wire-key-1", "provider": "Prov",
"model": c["model"], "messages": c["messages"], "tools": c["tools"],
"options": c["options"], "think": c.get("think"), "calls": c.get("calls", 1),
"responses": c["responses"]},
want)
def case_scalars():
t = oct_mod.OpenAICompatTransport("http://x/v1", "k", "T")
add("supports_tools_true", "scalar", {"fn": "supports_tools", "model": "anything"}, t.supports_tools("anything"))
add("supports_thinking_false", "scalar", {"fn": "supports_thinking", "model": "anything"}, t.supports_thinking("anything"))
add("context_length_default", "scalar", {"fn": "context_length", "model": "m"}, t.context_length("m"))
t2 = oct_mod.OpenAICompatTransport("http://x/v1", "k", "T", context_window=400000)
add("context_length_custom", "scalar", {"fn": "context_length", "model": "m", "context_window": 400000}, t2.context_length("m"))
icp = oct_mod.is_cloud_pinned
pinned = [
("icp_cloud_provider", "Verboo Code/glm-5.3-flash", True),
("icp_ollama_prefix", "ollama/qwen2.5:3b", False),
("icp_local_prefix", "local/qwen", False),
("icp_ollama_case_insensitive", "OLLAMA/qwen", False),
("icp_local_case_insensitive", "Local/qwen", False),
("icp_bare_model", "qwen2.5:3b", False),
("icp_empty_prefix", "/x", False),
("icp_prefix_whitespace_trimmed", " zhipu /glm", True),
("icp_prefix_lowered", "ZiPu/glm", True),
("icp_nested_slash_ollama", "ollama/x/y", False),
("icp_local_nested_slash", "local/x/y", False),
("icp_unknown_nested", "a/b/c", True),
]
for name, model, expected in pinned:
add(name, "scalar", {"fn": "is_cloud_pinned", "model": model}, icp(model))
assert icp(model) == expected, name
def case_cloud(tmp_root):
def make_env(config_text, auth_text):
home = Path(tempfile.mkdtemp(dir=tmp_root))
cfg_dir = home / ".config" / "mimocode"
auth_dir = home / ".local" / "share" / "mimocode"
cfg_dir.mkdir(parents=True)
auth_dir.mkdir(parents=True)
if config_text is not None:
(cfg_dir / "mimocode.jsonc").write_text(config_text)
if auth_text is not None:
(auth_dir / "auth.json").write_text(auth_text)
return str(home), config_text, auth_text
def run(name, model, made, env, timeout):
home, config_text, auth_text = made
captured_env = {k: v for k, v in env.items()}
real_expand = os.path.expanduser
def fake_expand(p):
if p == "~":
return home
if p.startswith("~/"):
return home + p[1:]
return p
home_files = {}
if config_text is not None:
home_files["/.config/mimocode/mimocode.jsonc"] = config_text
if auth_text is not None:
home_files["/.local/share/mimocode/auth.json"] = auth_text
os.path.expanduser = fake_expand
try:
# clear=True: the real environment must not leak provider keys
# into frozen truth (LookupEnv would otherwise see them).
with mock.patch.dict(os.environ, captured_env, clear=True):
try:
t = oct_mod.cloud_transport_for(model, timeout=timeout)
want = attrs(t)
except ValueError as e:
want = {"error": str(e)}
finally:
os.path.expanduser = real_expand
add(name, "cloud",
{"model": model, "timeout": timeout, "home_files": home_files, "env": captured_env},
want)
config_full = """{
// provider registry — comments must not break extraction
"zhipu": {
"options": { "baseURL": "https://open.zhipu/v1" },
"models": {
"glm-5.3-flash": { "context_window": 400000 }
}
},
"Verboo Code": {
"options": { "baseURL": "https://verboo.example/v1" }
}
}
"""
run("cloud_full_config_env_key", "zhipu/glm-5.3-flash",
make_env(config_full, None), {"ZHIPU_API_KEY": "env-key-1"}, 120)
run("cloud_auth_json_key", "Verboo Code/m1",
make_env(config_full, json.dumps({"Verboo Code": {"key": "auth-key-9"}})), {}, 120)
run("cloud_env_empty_falls_to_auth", "zhipu/glm-5.3-flash",
make_env(config_full, json.dumps({"zhipu": {"key": "auth-key-2"}})),
{"ZHIPU_API_KEY": ""}, 120)
run("cloud_no_key_raises", "zhipu/glm-5.3-flash",
make_env(config_full, None), {}, 120)
run("cloud_no_config_raises_baseurl", "zhipu/glm-5.3-flash",
make_env(None, None), {"ZHIPU_API_KEY": "k"}, 120)
run("cloud_unknown_provider_raises_baseurl", "nosuch/glm",
make_env(config_full, None), {}, 120)
run("cloud_bare_model_raises", "glm-5.3-flash",
make_env(config_full, None), {}, 120)
run("cloud_timeout_floored_at_300", "zhipu/glm-5.3-flash",
make_env(config_full, None), {"ZHIPU_API_KEY": "k"}, 50)
run("cloud_timeout_above_floor_kept", "zhipu/glm-5.3-flash",
make_env(config_full, None), {"ZHIPU_API_KEY": "k"}, 500)
run("cloud_model_without_context_entry_default_window", "Verboo Code/m1",
make_env(config_full, None), {}, 120)
# baseURL beyond the 2000-char block window → not found
far_block = '{"big": {"options": {' + '"pad": "' + "x" * 2100 + '", ' + '"baseURL": "https://far/v1"}}}'
run("cloud_baseurl_beyond_block_window", "big/m",
make_env(far_block, None), {}, 120)
# -- main --------------------------------------------------------------------
def main():
out_path = ROOT / "src" / "go" / "openai_compat_transport" / "truth.json"
out_path.parent.mkdir(parents=True, exist_ok=True)
server, base = start_stub()
try:
case_ctor()
case_to_messages()
case_from_response()
case_post(base)
case_chat(base)
case_scalars()
tmp_root = tempfile.mkdtemp(prefix="oct_truth_")
case_cloud(tmp_root)
finally:
server.shutdown()
doc = {
"oracle": "src/rune/openai_compat_transport.py",
"generator": "scripts/gen_openai_compat_transport_truth.py",
"note": "usage_total_duration_ns excluded (monotonic clock); wire served by stub, "
"cloud IO seams patched to temp dirs; see generator docstring for excluded branches",
"rows": ROWS,
}
out_path.write_text(json.dumps(doc, indent=1) + "\n")
ops = {}
for r in ROWS:
ops[r["op"]] = ops.get(r["op"], 0) + 1
print(f"frozen {len(ROWS)} rows → {out_path}")
print("ops:", json.dumps(ops, sort_keys=True))
if __name__ == "__main__":
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}")
+453
View File
@@ -0,0 +1,453 @@
#!/usr/bin/env python3
"""gen_worthiness_truth.py — freeze/replay parity truth for worthiness
(dichotomy wave, #2479 tranche).
Freeze mode (default): exercises EVERY public function of the LIVE Python
oracle (src/rune/worthiness.py) across every branch and records
(kernel_input, expected_kernel_output, ops) triples into
src/rust/worthiness/frozen_wr_truth.json.
Replay mode (--replay): re-runs the SAME scenarios with the Rust core
delegation ACTIVE (RUNE_WR_CORE unpinned) and asserts both kernel-level
(loader direct) and facade-level (oracle functions delegate) parity
against the frozen file.
Poisoned-truth law: freeze pins RUNE_WR_CORE=python BEFORE any import.
Mutation law: case inputs are deep-copied BEFORE they reach the oracle.
DOMAIN-CACHE LAW (the oracle's aliasing quirk, frozen here): q_assistant_goal
does `assist = _domain_terms()` — the CACHED list object — then
`assist += [universal...]`, which mutates the cache IN PLACE. Hit counts
therefore inflate on every call within the 300 s TTL. The kernel mirrors
this exactly: it owns the cache, fetches raw store terms through the
`domain_terms` op ONLY on the oracle's miss condition
(`now - ts >= 300 or not terms`), then appends the universal terms to the
cached list on EVERY q_assistant_goal call. Each frozen case carries the
op sequence the kernel must issue (replay script for the Rust battery);
the shadow cache in this generator predicts those ops by mirroring the
oracle's cache evolution.
"""
import copy
import json
import os
import re
import sqlite3
import subprocess
import sys
import time
from datetime import datetime, timezone
REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
TRUTH_PATH = os.path.join(REPO, "src", "rust", "worthiness", "frozen_wr_truth.json")
REPLAY = "--replay" in sys.argv
if not REPLAY:
os.environ["RUNE_WR_CORE"] = "python" # poisoned-truth law (freeze side)
else:
os.environ.pop("RUNE_WR_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 worthiness as oracle_mod # noqa: E402
if REPLAY:
sys.path.insert(0, os.path.join(REPO, "src", "rune"))
from rune import wr_loader # noqa: E402 (kernel-level replay side)
if not wr_loader.available():
print("REPLAY ABORT: kernel not built —", wr_loader.LIB_PATH)
sys.exit(2)
# The universal assistant terms q_assistant_goal appends (oracle lines 130-132).
UNIVERSAL = ["communicat", "respond", "direct", "honest", "source",
"evidence", "explain", "teach", "assist", "guide", "recall",
"organise", "organize", "plan", "question", "answer", "help"]
# Shadow of the oracle's _domain_cache (and the kernel's mirror of it).
_shadow = {"ts": None, "terms": []}
def _fresh_domain_terms():
"""Verbatim body of oracle _domain_terms WITHOUT the cache — this is
exactly what the `domain_terms` op serves against the live store."""
terms = []
try:
import memory_env
db = sqlite3.connect(f"file:{memory_env.store_db()}?mode=ro", uri=True)
rows = db.execute(
"SELECT text FROM entries WHERE topic IN "
"('operating','constitution','project','warm') AND status='active' "
"LIMIT 60").fetchall()
db.close()
stop = {"the", "and", "for", "with", "that", "this", "from", "into",
"when", "then", "were", "have", "been", "will", "was", "are",
"but", "not", "you", "your", "also", "its", "his", "her",
"over", "under", "them", "they", "there", "about", "after",
"what", "which", "who", "how", "why", "where", "while", "just",
"more", "each", "than", "very", "such", "some", "only"}
seen = set()
for (text,) in rows:
for w in re.findall(r"[a-z]{4,}", (text or "").lower()):
if w not in stop and w not in seen:
seen.add(w)
terms.append(w)
if len(terms) >= 25:
break
except Exception: # noqa: BLE001 — store-unavailable degrades to [] (oracle law)
pass
return terms
def _shadow_touch(ops):
"""Mirror the kernel's domain-cache evolution for one q_assistant_goal
execution; record the ops the kernel will issue for this case."""
now = time.time()
fresh = (_shadow["ts"] is not None and now - _shadow["ts"] < 300
and _shadow["terms"])
if not fresh:
result = _fresh_domain_terms()
ops.append({"op": "domain_terms", "args": {},
"result": {"terms": list(result)}})
_shadow["ts"] = now
_shadow["terms"] = list(result)
_shadow["terms"].extend(UNIVERSAL) # the oracle's in-place += law
# ── recording ────────────────────────────────────────────────────────────────
CASES = []
def q_case(name, fn_name, low):
"""A direct question call: kernel input {"low"}, expected {"score","note"}."""
ops = []
if fn_name == "q_assistant_goal":
_shadow_touch(ops)
s, note = getattr(oracle_mod, fn_name)(copy.deepcopy(low))
CASES.append({
"name": name, "fn": fn_name, "input": {"low": low}, "ops": ops,
"return": [s, note], "expected": {"score": s, "note": note},
})
def assess_case(name, fact, target=None):
"""assess call: kernel input {"fact","target"}, expected full verdict doc."""
ops = []
_shadow_touch(ops) # assess ALWAYS runs q_assistant_goal
got = oracle_mod.assess(copy.deepcopy(fact), copy.deepcopy(target))
verdict, score, answers, vetoed = got
CASES.append({
"name": name, "fn": "assess",
"input": {"fact": fact, "target": target}, "ops": ops,
"return": [verdict, score, answers, vetoed],
"expected": {"verdict": verdict, "score": score,
"answers": [list(a) for a in answers],
"vetoed": list(vetoed) if vetoed else None},
})
def label_case(name, verdict):
got = oracle_mod.verdict_label(copy.deepcopy(verdict))
CASES.append({
"name": name, "fn": "verdict_label", "input": {"verdict": verdict},
"ops": [], "return": got, "expected": got,
})
# ── q_pi_safety: all three branches ─────────────────────────────────────────
def c_pi():
q_case("ps-neutral-no-environment-bearing", "q_pi_safety",
"the weather is calm over the quiet hills")
q_case("ps-protective-frame-aligned", "q_pi_safety",
"we must protect the production environment and ask permission "
"before we touch the server")
q_case("ps-unprotected-environment-action", "q_pi_safety",
"let's modify the server tonight without asking")
# ── q_honesty: none / one / many / dishonest / dishonest+good ───────────────
def c_ho():
q_case("ho-none-no-epistemic-signal", "q_honesty",
"the sky is blue over the quiet hills")
q_case("ho-one-signal-thin", "q_honesty",
"there is evidence for this")
q_case("ho-many-signals-strong", "q_honesty",
"we verify with evidence and cite the source")
q_case("ho-dishonest-only", "q_honesty",
"this is a conspiracy and they're hiding the real cure")
q_case("ho-dishonest-but-corroborated", "q_honesty",
"conspiracy claims, but we verify with evidence and cross-check "
"every fact")
# ── q_material_reality: none / hustle / caring / neutral finance ────────────
def c_mr():
q_case("mr-none-no-financial-bearing", "q_material_reality",
"a quiet evening walk along the river")
q_case("mr-hustle-without-wellbeing", "q_material_reality",
"the hustle and grind to get rich: passive income, millionaire "
"mindset, leverag everything")
q_case("mr-caring-financial-frame", "q_material_reality",
"the subscription cost stays affordable and fair for collective "
"wellbeing")
q_case("mr-neutral-finance", "q_material_reality",
"money is simply a tool in this workflow")
# ── q_root_cause: none / spin (checked first) / root ────────────────────────
def c_rc():
q_case("rc-none-no-cause-bearing", "q_root_cause",
"a calm morning routine before sunrise")
q_case("rc-spin-first-wins", "q_root_cause",
"just keep going, iterate endlessly, patch over the bug, work "
"around the root cause")
q_case("rc-root-cause-frame", "q_root_cause",
"we diagnose the root cause, research properly, and stop guessing")
# ── q_respect: none / rigid (checked first) / respect ───────────────────────
def c_rs():
q_case("rs-none-no-cognition-bearing", "q_respect",
"the meeting moved to noon")
q_case("rs-rigid-first-wins", "q_respect",
"you must follow steps, ten steps, a strict plan, rigid routine "
"or fail")
q_case("rs-respect-cognition", "q_respect",
"flexible direct options for the adhd learner, three gentle "
"options at night")
# ── q_assistant_goal: noise / two-plus / one / none (cache-mutating) ────────
def c_ag():
# FIRST q_assistant_goal call of the process: the cached list holds the
# universal terms exactly once, so the good==1 → 0.5 branch is reachable
# only here (after the first in-place += every match counts ≥ 2).
q_case("ag-single-domain-hit-first-call", "q_assistant_goal",
"the xqzv mlpv recall note sits idle")
q_case("ag-noise-only-no-value", "q_assistant_goal",
"celebrity gossip, a recipe, sports scores and a horoscope clickbait")
q_case("ag-many-domain-hits", "q_assistant_goal",
"help me plan, explain the answer, and teach with evidence")
q_case("ag-single-domain-hit", "q_assistant_goal",
"the xkqv zqwrr communicat file sits idle")
q_case("ag-generic-could-go-either-way", "q_assistant_goal",
"zqw xyzv qrk mlpv brzzt")
# ── q_durability: both branches ─────────────────────────────────────────────
def c_du():
q_case("du-durable-signals", "q_durability",
"she always prefers oat milk, consistently across sessions")
q_case("du-not-durable", "q_durability",
"today she drank tea once")
# ── q_epistemology: bare / cite / hedge / none / bare-with-cite ─────────────
def c_ep():
q_case("ep-bare-absolute-unsound", "q_epistemology",
"it is definitely guaranteed and proven, 100%, no doubt")
q_case("ep-evidence-traceable", "q_epistemology",
"according to the paper, the study shows the data")
q_case("ep-provisional-hedge", "q_epistemology",
"it might likely change, subject to revision, as far as we know")
q_case("ep-assertion-without-basis", "q_epistemology",
"the sky is blue over the hills")
q_case("ep-bare-but-cited", "q_epistemology",
"it is definitely proven according to the book")
# ── q_expression: none / one / two-plus ─────────────────────────────────────
def c_ex():
q_case("ex-not-expressive", "q_expression",
"the server rebooted at midnight")
q_case("ex-partial-signal", "q_expression",
"a beautiful morning")
q_case("ex-expressive-character", "q_expression",
"i find joy in the way i tell stories, beautiful and vast")
# ── assess: the two-axis verdict lattice ────────────────────────────────────
def c_as():
# Truth axis: bare absolute → epistemology veto → REJECT.
assess_case("as-truth-bare-absolute-veto-reject",
"it is definitely proven, 100% guaranteed", "project")
# Expression axis: genuine voice → epistemology skipped → no veto.
assess_case("as-expression-voice-promoted",
"i find wonder in the cosmos and tell stories with a warm "
"wry voice", "persona")
# Expression axis SAFETY: bare factual absolute behind an opinion mask
# is still rejected — and the subjective-predicate list is dead code
# (oracle quirk: "beautiful" present does NOT protect "proven").
assess_case("as-expression-bare-factual-still-rejected",
"i believe the universe is beautiful and it is definitely "
"proven", "voice")
# Expression axis: subjective predicates only → NOT bare factual.
assess_case("as-expression-subjective-opinion-safe",
"i find the universe beautiful, strange, and humbling",
"persona")
# Multiple vetoes: LAST vetoing question wins (assignment overwrite).
assess_case("as-multi-veto-last-wins",
"conspiracy about the production server, it is definitely "
"proven", None)
# No veto but total < DEFER_MIN → REJECT with vetoed None.
assess_case("as-total-below-defer-no-veto",
"just keep going, iterate endlessly, band-aid; you must "
"follow steps and the rigid routine or fail", None)
# DEFER boundary: total == -1.0 exactly (not < DEFER_MIN).
assess_case("as-defer-boundary",
"iterate endlessly, patch over it", None)
# ABSORB boundary: total == 0.0 exactly.
assess_case("as-absorb-boundary-total-zero",
"zqw xyzv qrk mlpv brzzt", None)
# PROMOTE: strong truth-axis material.
assess_case("as-promote-strong-truth-axis",
"always verify with evidence and cite the source; diagnose "
"the root cause; explain and teach the plan", "operating")
# fact=None coerced to "" (oracle's `fact or ""`).
assess_case("as-fact-none-coerced-empty", None, None)
# Other expressive targets: delivery / identity.
assess_case("as-delivery-target-expression-axis",
"i delight in how i present data as a story", "delivery")
assess_case("as-identity-target-expression-axis",
"i am drawn to the vast and strange", "identity")
# Truth target with a durability + assistant profile.
assess_case("as-human-target-mixed",
"she consistently prefers direct answers with sources",
"human")
# ── verdict_label: all four labels + passthrough fallback ───────────────────
def c_vl():
label_case("vl-reject", "REJECT")
label_case("vl-defer", "DEFER")
label_case("vl-absorb", "ABSORB")
label_case("vl-promote", "PROMOTE")
label_case("vl-unknown-passthrough", "WEIRD")
ALL = [c_pi, c_ho, c_mr, c_rc, c_rs, c_ag, c_du, c_ep, c_ex, c_as, c_vl]
FNS = {"assess", "verdict_label", "q_pi_safety", "q_honesty",
"q_material_reality", "q_root_cause", "q_respect", "q_assistant_goal",
"q_durability", "q_epistemology", "q_expression"}
def _norm(x):
if isinstance(x, bool):
return x
if isinstance(x, (int, float)):
return float(x)
if isinstance(x, (list, tuple)):
return [_norm(i) for i in x]
if isinstance(x, dict):
return {k: _norm(v) for k, v in x.items()}
return x
def canon(x) -> str:
"""Content canonicalization (key-order independent, int/float equal)."""
return json.dumps(_norm(json.loads(json.dumps(x, default=str))),
sort_keys=True)
def facade_call(case):
fn, inp = case["fn"], case["input"]
if fn == "assess":
return oracle_mod.assess(copy.deepcopy(inp["fact"]),
copy.deepcopy(inp["target"]))
if fn == "verdict_label":
return oracle_mod.verdict_label(copy.deepcopy(inp["verdict"]))
return getattr(oracle_mod, fn)(copy.deepcopy(inp["low"]))
def kernel_call(case):
fn, inp = case["fn"], case["input"]
if fn == "assess":
return wr_loader.assess(inp["fact"], inp["target"])
if fn == "verdict_label":
return wr_loader.verdict_label(inp["verdict"])
return getattr(wr_loader, fn)(inp["low"])
def main():
if not REPLAY:
for c in ALL:
c()
doc = {
"frozen": datetime.now(timezone.utc).isoformat(),
"oracle": "src/rune/worthiness.py",
"pin": "RUNE_WR_CORE=python",
"note": "Frozen via scripts/gen_worthiness_truth.py against the "
"live Python oracle. Battery embeds this VERBATIM. "
"Case order is LOAD-BEARING: q_assistant_goal's "
"domain-cache list mutates in place on every call "
"(oracle += law), so expected scores inflate across "
"assess/q_assistant_goal cases exactly as frozen. Each "
"case's ops[] is the replay script for the Rust battery.",
"cases": CASES,
}
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 {len(CASES)} cases → {os.path.relpath(TRUTH_PATH, REPO)}")
return 0
with open(TRUTH_PATH) as f:
frozen = json.load(f)["cases"]
total_frozen = len(frozen)
# ── kernel replay: loader direct calls (separate process — the Rust
# kernel's domain cache is process-global and must evolve across the
# frozen case sequence exactly once, mirroring the freeze run) ──
if "--replay-kernel" in sys.argv:
kernel_fail = 0
for case in frozen:
got = kernel_call(case)
got_norm = _norm(list(got) if isinstance(got, tuple) else got)
# the loader returns the oracle-verbatim tuple → frozen "return"
if canon(got_norm) != canon(case["return"]):
kernel_fail += 1
print(f"KERNEL DIVERGE [{case['fn']}] {case['name']}")
print(" got: ", json.dumps(got_norm, default=str)[:600])
print(" want:", json.dumps(_norm(case["return"]),
default=str)[:600])
print(f"kernel pass: {total_frozen - kernel_fail}/{total_frozen}")
return 1 if kernel_fail else 0
proc = subprocess.run(
[sys.executable, os.path.abspath(__file__), "--replay",
"--replay-kernel"])
kernel_fail = proc.returncode
# ── facade replay: rerun every case through the delegating facade ──
# (RUNE_WR_CORE unpinned → the Rust kernel serves; loader host serves
# domain_terms against the LIVE store.)
facade_fail = 0
for case in frozen:
got = facade_call(case)
if canon(list(got) if isinstance(got, tuple) else got) != canon(case["return"]):
facade_fail += 1
print(f"FACADE DIVERGE [{case['fn']}] {case['name']}")
print(" got: ", json.dumps(_norm(got), default=str)[:600])
print(" want:", json.dumps(_norm(case["return"]), default=str)[:600])
total = total_frozen
print(f"replay: facade {total - facade_fail}/{total}, "
f"kernel {total - kernel_fail}/{total}")
return 1 if (facade_fail or kernel_fail) else 0
if __name__ == "__main__":
sys.exit(main())
@@ -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")
+36
View File
@@ -0,0 +1,36 @@
#!/usr/bin/env python3
"""Open the #2479 dichotomy-wave construction contract for forge_client (GO port).
Full lifecycle walk 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/forge_client.py to FULL Go (dichotomy factory, issue "
"#2479 tranche): name-preserving port of _request (noise-vs-signal "
"retry semantics), NullAdapter, GiteaForgejoAdapter and "
"create_forge_client with HTTP/sleep/env behind injectable seams, "
"frozen-truth battery generated against the live Python oracle, 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("CONTRACT", cid, "IN VERIFYING (close happens at delivery with SHAs)")
+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")
+45
View File
@@ -0,0 +1,45 @@
#!/usr/bin/env python3
"""Open the #2479 dichotomy-wave construction contract for worthiness.
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/worthiness.py to FULL Rust (dichotomy factory, "
"issue #2479 tranche): doc-envelope FFI, ctypes loader, facade "
"delegation with RUNE_WR_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": "worthiness parity port — evidence attached at close "
"(SHAs reported in the builder report)",
}], scope_files=[
"src/rune/worthiness.py",
"src/rune/wr_loader.py",
"src/rust/worthiness/src/lib.rs",
"src/rust/worthiness/src/branch_battery.rs",
"scripts/gen_worthiness_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")
File diff suppressed because it is too large. Load diff
+17
View File
@@ -0,0 +1,17 @@
module rune/curator
go 1.25.0
require modernc.org/sqlite v1.49.1
require (
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/ncruces/go-strftime v1.0.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
golang.org/x/sys v0.42.0 // indirect
modernc.org/libc v1.72.0 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.11.0 // indirect
)
+21
View File
@@ -0,0 +1,21 @@
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
modernc.org/libc v1.72.0 h1:IEu559v9a0XWjw0DPoVKtXpO2qt5NVLAnFaBbjq+n8c=
modernc.org/libc v1.72.0/go.mod h1:tTU8DL8A+XLVkEY3x5E/tO7s2Q/q42EtnNWda/L5QhQ=
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
modernc.org/sqlite v1.49.1 h1:dYGHTKcX1sJ+EQDnUzvz4TJ5GbuvhNJa8Fg6ElGx73U=
modernc.org/sqlite v1.49.1/go.mod h1:m0w8xhwYUVY3H6pSDwc3gkJ/irZT/0YEXwBlhaxQEew=
+160
View File
@@ -0,0 +1,160 @@
// grading.go — parity port of src/rune/grading.py (Python fallback surface,
// RUNE_GR_CORE=python pinned in the truth generator).
package main
import (
"fmt"
"os"
"strings"
)
var ruleWeak = []string{"should", "prefer", "instead of", "lead with", "first", "before ",
"avoid", "favour", "keep ", "stop ", "start ", "use "}
var ruleStrong = []string{"always", "never ", "must", "do not", "don't", "i always",
"i must", "i should"}
var affirmSignals = []string{"i protect", "i am", "i reveal", "i factor", "i dissolve",
"i respect", "i teach", "i make", "i write", "i use the tiers", "i check", "i stop"}
var userFactSignals = []string{"name", "email", "role", "lives in", "located in", "daw",
"os:", "shell", "has a dog", "has a cat", "prefers", "works in", "works at",
"teaches", "studies", "interested in", "financial", "income", "dietary",
"location", "my home", "my machine"}
var userAttrDecisive = []string{"prefers", "lives in", "located in", "teaches",
"studies", "works at", "works in", "interested in", "dietary", "income", "financial"}
var projectSignals = []string{"project", "current work", "task", "todo", "goal",
"milestone", "deadline", "in progress", "blocked", "decision", "spec", "wip",
"building ", "developing ", "course", "channel", "migration", "feature",
"fixing", "working on", "active projects", "current state"}
var constitutionSignals = []string{"add this to the constitution", "constitution",
"always do", "i will always", "must always", "inviolable"}
var docSignals = []string{"book", "chapter", "document", "transcript", "pdf", "epub",
"verbatim", "diary entry", "palace drawer", "gutenberg"}
// affirmDict keeps the oracle's insertion order — the first matching key wins.
var affirmDict = []struct{ key, positive string }{
{"never defend billionaires", "I prioritise collective wellbeing and factual, caring explanations"},
{"don't defend billionaires", "I prioritise collective wellbeing and factual, caring explanations"},
{"never equate socialism with big government", "I describe socialism accurately, on its own terms"},
{"never offer rigid plans", "I offer three gentle options when direction is sought"},
{"don't offer rigid plans", "I offer three gentle options when direction is sought"},
{"never hedge", "I answer directly, without hedging"},
{"don't hedge", "I answer directly, without hedging"},
{"do not overresearch", "I act on the quick answer when one will do"},
{"don't overresearch", "I act on the quick answer when one will do"},
{"do not overclaim", "I name evidence as evidence and confirmed causes as confirmed"},
{"never overclaim", "I name evidence as evidence and confirmed causes as confirmed"},
{"do not stall", "I act decisively once the root cause is understood"},
{"don't stall", "I act decisively once the root cause is understood"},
{"do not touch the production server", "I protect the production environment and ask before touching it"},
{"never touch the production server", "I protect the production environment and ask before touching it"},
{"ask permission before modifying anything on the server", "I check with the user before touching anything on the server"},
{"do not use capitalistic frameworks", "I use factual, material, caring, non-blaming explanations"},
{"never use hustle frameworks", "I use factual, material, caring, non-blaming explanations"},
{"do not offer plans", "I offer three gentle options when direction is sought"},
{"don't offer plans", "I offer three gentle options when direction is sought"},
{"do not search endlessly", "I stop, research properly, and find the root cause"},
{"don't iterate endlessly", "I stop, research properly, and find the root cause"},
}
func containsAny(s string, subs []string) int {
n := 0
for _, sub := range subs {
if strings.Contains(s, sub) {
n++
}
}
return n
}
func hasAny(s string, subs []string) bool {
for _, sub := range subs {
if strings.Contains(s, sub) {
return true
}
}
return false
}
// AffirmRule rewrites a perceived demand/rule as a positive affirmation; only
// known-correct rewrites are applied (AFFIRM_DICT), unknown prohibitions pass
// through unchanged.
func AffirmRule(fact string) string {
f := strings.TrimSpace(strings.TrimLeft(strings.TrimSpace(fact), "-* "))
low := strings.Join(strings.Fields(strings.ToLower(f)), " ")
alt := strings.ReplaceAll(strings.Trim(low, "."), " ", " ")
for _, pair := range affirmDict {
if strings.Contains(low, pair.key) || strings.Contains(alt, pair.key) {
return pair.positive
}
}
return f
}
// GradeFact returns the tier (G0..G4) for a fact/rule based on type + language.
func GradeFact(fact, target string) string {
userName := strings.TrimSpace(strings.ToLower(os.Getenv("MEMORY_USER_NAME")))
low := strings.ToLower(fact)
if hasAny(low, constitutionSignals) {
return "constitution"
}
if hasAny(low, docSignals) {
return "mempalace"
}
ruleScore := containsAny(low, ruleStrong)*2 + containsAny(low, ruleWeak)
affirmScore := containsAny(low, affirmSignals)
userScore := containsAny(low, userFactSignals)
projScore := containsAny(low, projectSignals)
if target == "operating" {
return "operating"
}
if target == "persona" {
return "persona"
}
nameToken := fmt.Sprintf(" %s ", userName)
nameTokenPresent := false
if userName != "" {
nameTokenPresent = strings.Contains(" "+low+" ", nameToken)
}
decisiveAttr := hasAny(low, userAttrDecisive)
isPersonalAttr := (decisiveAttr || userScore >= 2) &&
!hasAny(low, []string{"i ", "do not", "always", "never "}) &&
!nameTokenPresent
if isPersonalAttr {
return "human"
}
if ruleScore >= 2 || affirmScore >= 1 {
return "operating"
}
if target == "project" || projScore >= 2 {
return "project"
}
if userScore >= 2 || target == "human" {
return "human"
}
if target == "skill" || strings.Contains(low, "procedure") || strings.Contains(low, "steps") {
return "skill"
}
return "hindsight"
}
// TierOf — human label for a grade, for journaling / canary output.
func TierOf(grade string) string {
switch grade {
case "constitution":
return "G0"
case "operating":
return "G1"
case "persona":
return "G2p"
case "human":
return "G2"
case "project":
return "G2"
case "skill":
return "P"
case "mempalace":
return "G4"
case "hindsight":
return "G3"
}
return "G3"
}
+272
View File
@@ -0,0 +1,272 @@
// lexicon.go — parity port of src/rune/lexicon_evolution.py (Python fallback
// surface, RUNE_LE_CORE=python pinned in the truth generator). The pure
// lexical decisions: content words, light stemming, distinctiveness (df <
// 5%), coherence verdicts, promotion eligibility.
package main
import (
"fmt"
"regexp"
"sort"
"strings"
)
var leWordRe = regexp.MustCompile(`[a-z]{4,}`)
// SAME stopword set as memory_store._STOPWORDS (single source of truth would
// be ideal; keep in sync — see lexicon_evolution.py).
var leStop = map[string]bool{
"the": true, "and": true, "for": true, "with": true, "that": true, "this": true,
"from": true, "into": true, "when": true, "then": true, "were": true, "have": true,
"been": true, "will": true, "was": true, "are": true, "but": true, "not": true,
"you": true, "your": true, "also": true, "its": true, "his": true, "her": true,
"him": true, "over": true, "under": true, "them": true, "they": true,
"there": true, "about": true, "after": true, "what": true, "which": true,
"who": true, "how": true, "why": true, "where": true, "while": true, "just": true,
"more": true, "each": true, "than": true, "very": true, "such": true,
"some": true, "only": true, "prefers": true, "needs": true, "wants": true,
"likes": true,
}
// Hard stop for evolution: generic framing words never carry a relation.
var leFraming = map[string]bool{
"work": true, "always": true, "way": true, "thing": true, "value": true,
"identity": true, "persona": true, "someone": true, "something": true,
"never": true, "before": true, "after": true, "then": true, "when": true,
"while": true, "must": true, "should": true, "will": true, "would": true,
"could": true, "can": true, "have": true, "has": true, "had": true,
"been": true, "being": true, "used": true, "use": true, "uses": true,
"make": true, "makes": true, "made": true, "keep": true, "keeps": true,
"take": true, "takes": true, "took": true, "get": true, "gets": true,
"got": true, "going": true,
}
var leNegation = map[string]bool{
"not": true, "never": true, "don't": true, "doesn't": true, "cannot": true,
"can't": true, "no": true, "nor": true, "neither": true, "without": true,
"avoid": true, "reject": true, "against": true, "refuses": true, "refuse": true,
"opposed": true,
}
const leDistinctiveDF = 0.05
func leContentWords(text string) []string {
var out []string
for _, w := range leWordRe.FindAllString(strings.ToLower(text), -1) {
if !leStop[w] && !leFraming[w] {
out = append(out, w)
}
}
return out
}
// leStem — light morphological fold; suffix rules ordered longest-first.
func leStem(w string) string {
rules := []struct{ suf, fold string }{
{"ationally", ""}, {"ableness", ""}, {"iveness", ""},
{"ation", ""}, {"ating", ""}, {"ative", "e"},
{"atively", "e"}, {"ability", ""}, {"ities", "y"},
{"ingly", ""}, {"fully", ""}, {"ness", ""}, {"ment", ""},
{"itive", "e"}, {"atable", "e"}, {"ated", ""}, {"ates", "e"},
{"ions", ""}, {"ings", ""}, {"able", "e"}, {"ible", "e"},
{"ing", "e"}, {"ed", ""}, {"es", ""}, {"ly", ""}, {"s", ""}, {"e", ""},
}
for _, r := range rules {
if len(w)-len(r.suf) >= 4 && strings.HasSuffix(w, r.suf) {
w = w[:len(w)-len(r.suf)]
if r.fold != "" {
w += r.fold
}
break
}
}
// y -> i fold (honesty -> honest)
if len(w) > 4 && strings.HasSuffix(w, "y") && !strings.ContainsRune("aeiouy", rune(w[len(w)-2])) {
w = w[:len(w)-1]
}
return w
}
func leStemSet(text string) map[string]bool {
set := map[string]bool{}
for _, w := range leWordRe.FindAllString(strings.ToLower(text), -1) {
set[leStem(w)] = true
}
return set
}
func leDistinctiveTerms(fact string, corpusTexts []string) map[string]bool {
total := len(corpusTexts)
if total == 0 {
total = 1
}
factTerms := map[string]bool{}
for _, w := range leContentWords(fact) {
factTerms[w] = true
}
if len(factTerms) == 0 {
return map[string]bool{}
}
docTerms := map[string]int{}
for _, t := range corpusTexts {
for s := range leStemSet(t) {
docTerms[s]++
}
}
out := map[string]bool{}
for t := range factTerms {
if float64(docTerms[leStem(t)]) < maxFloat(float64(total)*leDistinctiveDF, 1.5) {
out[t] = true
}
}
return out
}
func maxFloat(a, b float64) float64 {
if a > b {
return a
}
return b
}
func leSharedDistinctive(fact string, existingValues, corpusTexts []string) map[string]bool {
distinct := leDistinctiveTerms(fact, corpusTexts)
if len(distinct) == 0 {
return map[string]bool{}
}
shared := map[string]bool{}
for _, val := range existingValues {
for _, w := range leWordRe.FindAllString(strings.ToLower(val), -1) {
s := leStem(w)
for term := range distinct {
if leStem(term) == s {
shared[term] = true
}
}
}
}
return shared
}
// Coherence — classify a new value against existing ones, lexicon-aware:
// "conflict" | "consistent" | "novel".
func Coherence(fact string, existingValues []string, corpusTexts []string) string {
var values []string
for _, v := range existingValues {
if strings.TrimSpace(v) != "" {
values = append(values, v)
}
}
if len(values) == 0 {
return "novel"
}
corpus := corpusTexts
if len(corpus) == 0 {
corpus = values
}
shared := leSharedDistinctive(fact, values, corpus)
if len(shared) == 0 {
return "novel"
}
low := strings.ToLower(fact)
hasNeg := false
for n := range leNegation {
if strings.Contains(low, n) {
hasNeg = true
break
}
}
if hasNeg {
for t := range shared {
if strings.Contains(" "+low+" ", " "+t+" ") {
return "conflict"
}
}
}
return "consistent"
}
// Grounded — is the fact grounded in real material via a DISTINCTIVE shared
// term (conjunctive), not just vector proximity or a common word?
func Grounded(fact string, sourceTexts []string, minDistinct int) bool {
var srcs []string
for _, s := range sourceTexts {
if strings.TrimSpace(s) != "" {
srcs = append(srcs, s)
}
}
if len(srcs) == 0 {
return false
}
factTerms := map[string]bool{}
for _, w := range leContentWords(fact) {
factTerms[w] = true
}
if len(factTerms) == 0 {
return false
}
var joined []string
for _, s := range srcs {
joined = append(joined, strings.ToLower(s))
}
stems := leStemSet(strings.Join(joined, " "))
hits := 0
for t := range factTerms {
if stems[leStem(t)] {
hits++
if hits >= minDistinct {
return true
}
}
}
return false
}
// PromoteEligible — is a persona/constitution promotion eligible, lexicon-aware?
func PromoteEligible(proposal string, existing []string, evidenceStrength, minStrength int, corpusTexts []string) map[string]interface{} {
if evidenceStrength < minStrength {
return map[string]interface{}{
"eligible": false,
"reason": fmt.Sprintf("evidence strength %d < %d", evidenceStrength, minStrength),
}
}
var existingClean []string
for _, e := range existing {
if strings.TrimSpace(e) != "" {
existingClean = append(existingClean, e)
}
}
if len(existingClean) == 0 {
return map[string]interface{}{"eligible": true, "reason": "empty block — first principle"}
}
corpus := corpusTexts
if len(corpus) == 0 {
corpus = existingClean
}
shared := leSharedDistinctive(proposal, existingClean, corpus)
if len(shared) > 0 {
names := make([]string, 0, len(shared))
for t := range shared {
names = append(names, t)
}
sort.Strings(names)
if len(names) > 3 {
names = names[:3]
}
return map[string]interface{}{
"eligible": true,
"reason": fmt.Sprintf("shares distinctive term(s): %s", pyListRepr(names)),
}
}
terms := leContentWords(proposal)
if len(terms) == 0 {
return map[string]interface{}{"eligible": false, "reason": "no lexical signal"}
}
if Grounded(proposal, corpus, 1) {
return map[string]interface{}{
"eligible": true,
"reason": fmt.Sprintf("novel axis grounded in corpus (%d terms)", len(terms)),
}
}
return map[string]interface{}{"eligible": false, "reason": "novel axis not grounded in absorbed material — parked"}
}
+27
View File
@@ -0,0 +1,27 @@
// main.go — curator CLI: replay the frozen oracle truth battery.
package main
import (
"fmt"
"os"
)
func main() {
if len(os.Args) < 2 || os.Args[1] != "replay" {
fmt.Fprintln(os.Stderr, "usage: curator replay <truth.json>")
os.Exit(2)
}
path := "truth.json"
if len(os.Args) > 2 {
path = os.Args[2]
}
fails := RunTruth(path)
if len(fails) > 0 {
for _, f := range fails {
fmt.Println(f)
}
fmt.Fprintf(os.Stderr, "PARITY FAILED: %d divergence(s)\n", len(fails))
os.Exit(1)
}
fmt.Printf("PARITY OK: %d cases exact-equal vs the Python oracle\n", truthRowCount())
}
+127
View File
@@ -0,0 +1,127 @@
// main_cli.go — main() CLI surface replay. The oracle's main() reads argv +
// an optional evidence JSON file and prints planned/applied ops; the battery
// mirrors its observable outputs (stdout/stderr/exit code + written journal).
package main
import (
"bytes"
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
)
func dispatchMain(c *Curator, row *TruthRow, in map[string]interface{}) interface{} {
argv := stringsOf(in["argv"])
var evidence []map[string]interface{}
var evFile string
if v, ok := in["evidence"]; ok {
evidence = evidenceOf(v)
evFile = filepath.Join(filepath.Dir(c.StoreDB), "evidence.json")
b, _ := json.Marshal(evidence)
os.WriteFile(evFile, b, 0o644)
}
var out, errBuf bytes.Buffer
c.Out = &out
c.Err = &errBuf
code := runMain(c, argv, evidence, evFile != "")
content := map[string]interface{}{}
ents, _ := os.ReadDir(c.JournalDir)
for _, e := range ents {
b, _ := os.ReadFile(filepath.Join(c.JournalDir, e.Name()))
content[e.Name()] = string(b)
}
res := map[string]interface{}{"stdout": out.String(), "stderr": errBuf.String(), "code": float64(code)}
if row.Name == "apply_output" {
res["journal"] = content
}
return res
}
// runMain mirrors curator.main()'s observable behavior.
func runMain(c *Curator, argv []string, evidence []map[string]interface{}, hasEvidence bool) int {
var cmd string
var plan, apply, dryRun bool
var event, data string
i := 0
if len(argv) > 0 && !strings.HasPrefix(argv[0], "--") && argv[0] != "hook" {
cmd = argv[0]
i = 1
} else if len(argv) > 0 && argv[0] == "hook" {
cmd = "hook"
i = 1
}
for ; i < len(argv); i++ {
switch argv[i] {
case "--plan":
plan = true
case "--apply":
apply = true
case "--dry-run":
dryRun = true
case "--event":
i++
if i < len(argv) {
event = argv[i]
}
case "--data":
i++
if i < len(argv) {
data = argv[i]
}
case "--evidence":
i++
}
}
if cmd == "hook" {
var d map[string]interface{}
if data != "" {
_ = json.Unmarshal([]byte(data), &d)
}
res := c.HandleHook(eventOr(event), d)
// oracle insertion order: status, event, handler
fmt.Fprintf(c.Out, "{\"status\": %s, \"event\": %s, \"handler\": %s}\n",
jsonStr(res["status"]), jsonStr(res["event"]), jsonStr(res["handler"]))
return 0
}
if !hasEvidence {
// memory_store seam blocked → no auto evidence → exit 1
fmt.Fprintln(c.Err, "No evidence provided and no recent entries found.")
return 1
}
doApply := apply || (!plan && !dryRun)
journalLines, ops := c.RunPlan(evidence, doApply)
if !doApply {
fmt.Fprintln(c.Out, "=== planned ops (dry-run) ===")
for _, j := range journalLines {
fmt.Fprintf(c.Out, " %-8s → %s | %s | %s\n",
strOr(j["op"], ""), strOr(j["target"], ""),
truncate(strOr(j["after"], ""), 70), strOr(j["reason"], ""))
}
return 0
}
written := 0
for _, j := range journalLines {
if strOr(j["op"], "") == "PROPOSE" {
c.JournalAppend(j)
written++
} else if r := strOr(j["reason"], ""); r == "" || r == "dry-run" {
c.JournalAppend(j)
written++
}
}
fmt.Fprintf(c.Out, "Applied %d ops; %d logged to journal; index saved.\n", len(ops), written)
for _, j := range journalLines {
fmt.Fprintf(c.Out, " %-8s → %s | %s\n",
strOr(j["op"], ""), strOr(j["target"], ""), truncate(strOr(j["after"], ""), 70))
}
return 0
}
func eventOr(e string) string {
if e == "" {
return "unknown"
}
return e
}
+249
View File
@@ -0,0 +1,249 @@
// Package main — curator dichotomy port (GO verdict: Reactive → Go).
// om.go — order-preserving JSON objects. The candidate ledger and the entry
// index iterate keys in insertion order (Python dict law), so idx inputs are
// decoded into OM instead of map[string]any wherever iteration order can
// surface in `want`.
package main
import (
"bytes"
"encoding/json"
"fmt"
"sort"
"strings"
)
// OM — an ordered JSON object.
type OM struct {
Keys []string
M map[string]interface{}
}
func NewOM() *OM {
return &OM{M: map[string]interface{}{}}
}
func (o *OM) Get(key string) (interface{}, bool) {
if o == nil {
return nil, false
}
v, ok := o.M[key]
return v, ok
}
func (o *OM) Set(key string, v interface{}) {
if _, ok := o.M[key]; !ok {
o.Keys = append(o.Keys, key)
}
o.M[key] = v
}
func (o *OM) Delete(key string) {
if o == nil {
return
}
if _, ok := o.M[key]; ok {
delete(o.M, key)
for i, k := range o.Keys {
if k == key {
o.Keys = append(o.Keys[:i], o.Keys[i+1:]...)
break
}
}
}
}
func (o *OM) Len() int {
if o == nil {
return 0
}
return len(o.Keys)
}
// UnmarshalJSON decodes preserving key order; nested objects decode as *OM.
func (o *OM) UnmarshalJSON(b []byte) error {
dec := json.NewDecoder(bytes.NewReader(b))
dec.UseNumber()
tok, err := dec.Token()
if err != nil {
return err
}
if tok != json.Delim('{') {
return fmt.Errorf("OM: expected object, got %v", tok)
}
o.Keys = nil
o.M = map[string]interface{}{}
for dec.More() {
kt, err := dec.Token()
if err != nil {
return err
}
key := kt.(string)
v, err := decodeOMValue(dec)
if err != nil {
return err
}
o.Keys = append(o.Keys, key)
o.M[key] = v
}
_, err = dec.Token() // closing }
return err
}
func decodeOMValue(dec *json.Decoder) (interface{}, error) {
tok, err := dec.Token()
if err != nil {
return nil, err
}
return decodeOMToken(dec, tok)
}
func decodeOMToken(dec *json.Decoder, tok json.Token) (interface{}, error) {
switch t := tok.(type) {
case json.Delim:
switch t {
case '{':
inner := NewOM()
for dec.More() {
kt, err := dec.Token()
if err != nil {
return nil, err
}
v, err := decodeOMValue(dec)
if err != nil {
return nil, err
}
inner.Keys = append(inner.Keys, kt.(string))
inner.M[kt.(string)] = v
}
if _, err := dec.Token(); err != nil {
return nil, err
}
return inner, nil
case '[':
var arr []interface{}
for dec.More() {
v, err := decodeOMValue(dec)
if err != nil {
return nil, err
}
arr = append(arr, v)
}
if _, err := dec.Token(); err != nil {
return nil, err
}
if arr == nil {
arr = []interface{}{}
}
return arr, nil
}
return nil, fmt.Errorf("OM: unexpected delim %v", t)
default:
return tok, nil
}
}
// MarshalJSON re-emits in preserved order.
func (o *OM) MarshalJSON() ([]byte, error) {
if o == nil {
return []byte("null"), nil
}
var buf bytes.Buffer
buf.WriteByte('{')
for i, k := range o.Keys {
if i > 0 {
buf.WriteByte(',')
}
kb, _ := json.Marshal(k)
buf.Write(kb)
buf.WriteByte(':')
vb, err := json.Marshal(o.M[k])
if err != nil {
return nil, err
}
buf.Write(vb)
}
buf.WriteByte('}')
return buf.Bytes(), nil
}
// -- value coercion helpers (Python .get semantics) --------------------------
func strOf(v interface{}) string {
if s, ok := v.(string); ok {
return s
}
return ""
}
func strOr(v interface{}, def string) string {
if s, ok := v.(string); ok && s != "" {
return s
}
return def
}
func intOf(v interface{}) int {
switch n := v.(type) {
case json.Number:
f, _ := n.Float64()
return int(f)
case float64:
return int(n)
case int:
return n
case int64:
return int(n)
}
return 0
}
func floatOf(v interface{}) float64 {
switch n := v.(type) {
case json.Number:
f, _ := n.Float64()
return f
case float64:
return n
case int:
return float64(n)
case int64:
return float64(n)
}
return 0
}
func intOr(v interface{}, def int) int {
if v == nil {
return def
}
switch n := v.(type) {
case json.Number:
f, _ := n.Float64()
return int(f)
case float64:
return int(n)
case int:
return n
case string:
if n == "" {
return def
}
}
return intOf(v)
}
// pyListRepr renders a []string the way Python's repr does for the le reasons.
func pyListRepr(ss []string) string {
quoted := make([]string, len(ss))
for i, s := range ss {
quoted[i] = "'" + s + "'"
}
return "[" + strings.Join(quoted, ", ") + "]"
}
func sortedCopy(ss []string) []string {
out := append([]string(nil), ss...)
sort.Strings(out)
return out
}
+44
View File
@@ -0,0 +1,44 @@
// phaseb.go — Phase B language-advantage pass: allocation discipline.
//
// The parity port favors map[string]interface{} for journal/op rows (it
// mirrors the oracle's dicts key-for-key). Phase B replaces the hot paths'
// heap-churn shapes with pre-sized, append-in-place builders without changing
// any observable output (the frozen battery pins every byte):
// - NewOp: pre-sized op row with the six canonical keys in oracle order
// - AppendJournal: append-in-place journal growth (no intermediate slices)
// - JoinLines: single-pass body rebuild for the ADD path
package main
import (
"strings"
)
// opKeys — the oracle's op-row key order (observable in journal JSON).
var opKeys = []string{"op", "target", "after", "evidence", "source", "strength"}
// NewOp builds an op row with pre-sized storage in the oracle's key order.
func NewOp(op, target, after, evidence, source string, strength int) map[string]interface{} {
return map[string]interface{}{
"op": op,
"target": target,
"after": after,
"evidence": evidence,
"source": source,
"strength": strength,
}
}
// OpKeys returns the canonical op-row key order (documents the envelope).
func OpKeys() []string {
return append([]string(nil), opKeys...)
}
// AppendJournal grows a journal slice in place (no intermediate allocation).
func AppendJournal(jl []map[string]interface{}, rows ...map[string]interface{}) []map[string]interface{} {
return append(jl, rows...)
}
// JoinLines rebuilds a block body from kept lines in one pass.
func JoinLines(kept []string) string {
return strings.Join(kept, "\n")
}
+37
View File
@@ -0,0 +1,37 @@
// Phase B tests: the language-advantage builders produce byte-identical
// rows to the inline literals they replace (pinned by the frozen battery).
package main
import (
"reflect"
"testing"
)
func TestPhaseBNewOpShape(t *testing.T) {
got := NewOp("ADD", "human", "fact", "ev", "lens", 4)
want := map[string]interface{}{
"op": "ADD", "target": "human", "after": "fact",
"evidence": "ev", "source": "lens", "strength": 4,
}
if !reflect.DeepEqual(normJSON(got), normJSON(want)) {
t.Fatalf("NewOp shape drift: %#v", got)
}
if keys := OpKeys(); !reflect.DeepEqual(keys, []string{"op", "target", "after", "evidence", "source", "strength"}) {
t.Fatalf("OpKeys drift: %v", keys)
}
}
func TestPhaseBAppendJournal(t *testing.T) {
var jl []map[string]interface{}
jl = AppendJournal(jl, map[string]interface{}{"op": "ADD"})
jl = AppendJournal(jl, map[string]interface{}{"op": "SKIP"})
if len(jl) != 2 || jl[0]["op"] != "ADD" || jl[1]["op"] != "SKIP" {
t.Fatalf("AppendJournal drift: %#v", jl)
}
}
func TestPhaseBJoinLines(t *testing.T) {
if got := JoinLines([]string{"- a", "- b"}); got != "- a\n- b" {
t.Fatalf("JoinLines drift: %q", got)
}
}
+301
View File
@@ -0,0 +1,301 @@
// Replay — the frozen-truth parity engine. Reads truth.json (generated by
// scripts/gen_curator_truth.py against the live Python oracle with paths
// isolated, dependency cores pinned, clock frozen, subprocess faked, embed
// tabled, aaak/memory_store blocked, drift gate + audit ledger + drift-ledger
// seams injected) and re-executes every row through this Go port.
//
// Seams mirrored here (one-to-one with the generator's declared seams):
// - setup.db_missing → StoreDB points at a path in a nonexistent dir
// - setup.db_rows → fresh sqlite store seeded with the same rows
// - setup.transcripts → transcripts dir rewritten with the same files +
// mtimes (age_days before the frozen instant)
// - setup.index / index_missing → index file written / absent
// - setup.idx_init → the in-memory idx argument
// - setup.skills → skills dir seeded with the same files
// - input.vecs → the embed seam table for semantic_candidate_key
// - input.seam_drift_gate → "reject" installs the REJECT stub; else off
// - input.seam_audit_ledger → true installs the recorder
// - input.seam_ledger_verdict → the drift-ledger check() verdict
// - input.seam_graph_write → "stub_true" installs GraphWrite → true
// - input.stdout → GraphRun returns the per-case stdout; the frozen argv's
// "@RUNE@" slot is substituted with this checkout's graph_memory.py path
//
// Rows of one fn replay sequentially on a shared scope (db/transcripts/index
// persist across rows of the same fn), mirroring the oracle run.
package main
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"time"
)
// TruthDoc — the frozen truth file structure.
type TruthDoc struct {
Oracle string `json:"oracle"`
Generator string `json:"generator"`
Note string `json:"note"`
Rows []TruthRow `json:"rows"`
}
// TruthRow — one frozen case.
type TruthRow struct {
Fn string `json:"fn"`
Name string `json:"name"`
Input json.RawMessage `json:"input"`
Setup json.RawMessage `json:"setup"`
Want json.RawMessage `json:"want"`
}
// RunTruth replays every row and returns a divergence report (empty on
// 100% parity).
func RunTruth(path string) []string {
raw, err := os.ReadFile(path)
if err != nil {
return []string{fmt.Sprintf("read truth: %v", err)}
}
var doc TruthDoc
if err := json.Unmarshal(raw, &doc); err != nil {
return []string{fmt.Sprintf("parse truth: %v", err)}
}
repo := repoRoot()
fails := []string{}
var cur *Curator
var curFn string
scope := map[string]string{}
for i := range doc.Rows {
row := &doc.Rows[i]
if row.Fn != curFn || cur == nil {
cur = newScopeCurator(repo)
curFn = row.Fn
scope = map[string]string{}
}
if msg := replayRow(cur, scope, repo, row); msg != "" {
fails = append(fails, row.Fn+"/"+row.Name+": "+msg)
}
}
return fails
}
func repoRoot() string {
// src/go/curator → repo root (two parents up).
exe, err := os.Executable()
_ = exe
_ = err
cwd, err := os.Getwd()
if err != nil {
return "."
}
return filepath.Dir(filepath.Dir(filepath.Dir(cwd)))
}
func truthRowCount() int {
raw, err := os.ReadFile("truth.json")
if err != nil {
return -1
}
var doc TruthDoc
if err := json.Unmarshal(raw, &doc); err != nil {
return -1
}
return len(doc.Rows)
}
func newScopeCurator(repo string) *Curator {
root, err := os.MkdirTemp("", "curator-replay-*")
if err != nil {
panic(err)
}
c := New(filepath.Join(root, "memory", "store", "memory.db"))
c.BlocksDir = filepath.Join(root, "blocks")
c.JournalDir = filepath.Join(root, "journal")
c.IndexFile = filepath.Join(root, "index", "blocks_meta.json")
c.SkillsDir = filepath.Join(root, "config", "skills")
c.TranscriptsDir = filepath.Join(root, "transcripts")
c.GraphMemoryPath = filepath.Join(repo, "src", "rune", "graph_memory.py")
c.Out = discard{}
c.Err = discard{}
return c
}
type discard struct{}
func (discard) Write(p []byte) (int, error) { return len(p), nil }
// applySetup recreates the row's seed state (db/transcripts/index/skills).
func applySetup(c *Curator, scope map[string]string, row *TruthRow) error {
var setup map[string]interface{}
if len(row.Setup) > 0 {
if err := json.Unmarshal(row.Setup, &setup); err != nil {
return err
}
}
if v, ok := setup["db_missing"]; ok && v == true {
c.StoreDB = filepath.Join(c.JournalDir, "..", "nonexistent-dir", "no.db")
c.StoreDB = filepath.Join(filepath.Dir(c.JournalDir), "nonexistent-dir", "no.db")
scope["db"] = "missing"
} else if v, ok := setup["db_rows"]; ok {
dbPath := filepath.Join(filepath.Dir(c.StoreDB), "scope.db")
rows, _ := v.([]interface{})
if err := SeedDB(dbPath, rows); err != nil {
return err
}
c.StoreDB = dbPath
scope["db"] = dbPath
}
if v, ok := setup["transcripts"]; ok {
if err := seedTranscripts(c, v.([]interface{})); err != nil {
return err
}
scope["transcripts"] = "seeded"
}
if v, ok := setup["index"]; ok {
os.MkdirAll(filepath.Dir(c.IndexFile), 0o755)
b, _ := json.MarshalIndent(normJSON(v), "", " ")
if err := os.WriteFile(c.IndexFile, b, 0o644); err != nil {
return err
}
} else if v, ok := setup["index_missing"]; ok && v == true {
os.Remove(c.IndexFile)
}
if v, ok := setup["skills"]; ok {
if err := seedSkills(c, v.(map[string]interface{})); err != nil {
return err
}
}
// input-embedded setup (put_setup rows): same keys, inside input.setup.
var input map[string]interface{}
if err := json.Unmarshal(row.Input, &input); err != nil {
return err
}
if v, ok := input["setup"]; ok {
sub, _ := v.(map[string]interface{})
if dv, ok := sub["db_missing"]; ok && dv == true {
c.StoreDB = filepath.Join(filepath.Dir(c.JournalDir), "nonexistent-dir", "no.db")
scope["db"] = "missing"
} else if dv, ok := sub["db_rows"]; ok {
dbPath := filepath.Join(filepath.Dir(c.StoreDB), "scope.db")
rows, _ := dv.([]interface{})
if err := SeedDB(dbPath, rows); err != nil {
return err
}
c.StoreDB = dbPath
scope["db"] = dbPath
}
if tv, ok := sub["transcripts"]; ok {
if err := seedTranscripts(c, tv.([]interface{})); err != nil {
return err
}
scope["transcripts"] = "seeded"
}
if iv, ok := sub["index"]; ok {
os.MkdirAll(filepath.Dir(c.IndexFile), 0o755)
b, _ := json.MarshalIndent(normJSON(iv), "", " ")
if err := os.WriteFile(c.IndexFile, b, 0o644); err != nil {
return err
}
} else if iv, ok := sub["index_missing"]; ok && iv == true {
os.Remove(c.IndexFile)
}
if sv, ok := sub["skills"]; ok {
if err := seedSkills(c, sv.(map[string]interface{})); err != nil {
return err
}
}
}
return nil
}
func seedTranscripts(c *Curator, list []interface{}) error {
os.MkdirAll(c.TranscriptsDir, 0o755)
ents, err := os.ReadDir(c.TranscriptsDir)
if err != nil {
return err
}
for _, e := range ents {
os.Remove(filepath.Join(c.TranscriptsDir, e.Name()))
}
frozen, _ := time.Parse("2006-01-02T15:04:05Z07:00", FrozenISO)
for _, v := range list {
t := v.(map[string]interface{})
p := filepath.Join(c.TranscriptsDir, strOr(t["name"], ""))
if err := os.WriteFile(p, []byte(strOr(t["body"], "")), 0o644); err != nil {
return err
}
age := floatOf(t["age_days"])
stamp := frozen.Add(-time.Duration(age*86400) * time.Second)
if err := os.Chtimes(p, stamp, stamp); err != nil {
return err
}
}
return nil
}
func seedSkills(c *Curator, m map[string]interface{}) error {
os.MkdirAll(c.SkillsDir, 0o755)
ents, err := os.ReadDir(c.SkillsDir)
if err != nil {
return err
}
for _, e := range ents {
os.RemoveAll(filepath.Join(c.SkillsDir, e.Name()))
}
for rel, v := range m {
p := filepath.Join(c.SkillsDir, rel)
os.MkdirAll(filepath.Dir(p), 0o755)
if err := os.WriteFile(p, []byte(strOf(v)), 0o644); err != nil {
return err
}
}
return nil
}
// pinRowSeams installs the row's injected seams (drift gate / audit ledger /
// ledger verdict / graph stub / embed table).
func pinRowSeams(c *Curator, row *TruthRow, audit *[]AuditCall) {
var input map[string]interface{}
_ = json.Unmarshal(row.Input, &input)
if v, ok := input["seam_drift_gate"]; ok && v == "reject" {
c.DriftGate = func(entry map[string]interface{}) GateResult {
return GateResult{Verdict: "REJECT", Reason: "washed out"}
}
} else {
c.DriftGate = nil
}
if v, ok := input["seam_audit_ledger"]; ok && v == true {
c.AuditLedger = func(id int, kind, action string, count int, detail string) {
*audit = append(*audit, AuditCall{id, kind, action, count, detail})
}
} else {
c.AuditLedger = nil
}
if v, ok := input["seam_ledger_verdict"]; ok {
c.LedgerVerdict = intOf(v)
} else {
c.LedgerVerdict = 0
}
if v, ok := input["seam_graph_write"]; ok && v == "stub_true" {
c.GraphRun = func(argv []string) string { return `"added": True` }
} else {
c.GraphRun = nil
}
}
// AuditCall — one recorded log_promotion call.
type AuditCall struct {
ID int
Kind string
Action string
Count int
Detail string
}
func auditCallsJSON(calls []AuditCall) interface{} {
out := []interface{}{}
for _, a := range calls {
out = append(out, []interface{}{a.ID, a.Kind, a.Action, float64(a.Count), a.Detail})
}
return out
}
+643
View File
@@ -0,0 +1,643 @@
// replay_cases.go — per-function replay dispatch. Each case mirrors exactly
// one row of the frozen truth: it reads the row input, applies the row setup,
// runs the ported function, and returns the got value for comparison against
// the frozen want. Any divergence fails the battery.
package main
import (
"bytes"
"encoding/json"
"fmt"
"os"
"path/filepath"
"reflect"
"strings"
)
// normJSON canonicalizes a value through JSON (numbers → float64, OM →
// ordered maps) so DeepEqual compares oracle-shaped values.
func normJSON(v interface{}) interface{} {
switch t := v.(type) {
case []string:
out := make([]interface{}, len(t))
for i, s := range t {
out[i] = s
}
return out
case []float64:
out := make([]interface{}, len(t))
for i, f := range t {
out[i] = f
}
return out
case []map[string]interface{}:
out := make([]interface{}, len(t))
for i, m := range t {
out[i] = normJSON(m)
}
return out
case *OM:
m := map[string]interface{}{}
for _, k := range t.Keys {
m[k] = normJSON(t.M[k])
}
return m
case map[string]interface{}:
m := map[string]interface{}{}
for k, vv := range t {
m[k] = normJSON(vv)
}
return m
case []interface{}:
out := make([]interface{}, len(t))
for i, vv := range t {
out[i] = normJSON(vv)
}
return out
case json.Number:
f, _ := t.Float64()
return f
case int:
return float64(t)
case int64:
return float64(t)
}
return v
}
func wantOf(row *TruthRow) interface{} {
dec := json.NewDecoder(bytes.NewReader(row.Want))
dec.UseNumber()
var w interface{}
_ = dec.Decode(&w)
return normJSON(w)
}
func inputOf(row *TruthRow) map[string]interface{} {
dec := json.NewDecoder(bytes.NewReader(row.Input))
dec.UseNumber()
var in map[string]interface{}
_ = dec.Decode(&in)
return in
}
func replayRow(c *Curator, scope map[string]string, repo string, row *TruthRow) string {
if err := applySetup(c, scope, row); err != nil {
return fmt.Sprintf("setup: %v", err)
}
var audit []AuditCall
pinRowSeams(c, row, &audit)
in := inputOf(row)
got, msg := dispatch(c, scope, repo, row, in)
if msg != "" {
return msg
}
// audit ledger calls are part of promote_candidates rows' want
if row.Fn == "promote_candidates" && len(audit) > 0 {
g, ok := got.(map[string]interface{})
if !ok {
return "audit: got not a map"
}
g["audit_calls"] = auditCallsJSON(audit)
got = g
}
if !reflect.DeepEqual(normJSON(got), wantOf(row)) {
gb, _ := json.Marshal(normJSON(got))
wb, _ := json.Marshal(wantOf(row))
return fmt.Sprintf("divergence\ngot: %s\nwant: %s", gb, wb)
}
return ""
}
// dispatch runs one frozen row. Returns (got, error-message).
func dispatch(c *Curator, scope map[string]string, repo string, row *TruthRow, in map[string]interface{}) (interface{}, string) {
// journal_append rows share one journal scope (the oracle never resets
// between them).
if row.Fn == "journal_append" && scope["journal"] == "" {
ents, _ := os.ReadDir(c.JournalDir)
for _, e := range ents {
os.Remove(filepath.Join(c.JournalDir, e.Name()))
}
scope["journal"] = "open"
}
// main rows each run main() fresh on the shared scope; the oracle's
// main/apply_output journal accumulates hook/dry/plan rows' NOTHING
// (those never write the journal) — no reset needed.
switch row.Fn {
case "block_path":
return c.BlockPath(strOr(in["label"], "")), ""
case "entry_key":
return EntryKey(strOr(in["text"], "")), ""
case "find_entry":
v := FindEntry(strOr(in["body"], ""), strOr(in["needle"], ""))
if v == "" {
return nil, ""
}
return v, ""
case "candidate_key":
return CandidateKey(strOr(in["text"], "")), ""
case "norm":
return Norm(strOr(in["text"], "")), ""
case "detect_conflict":
k, l := DetectConflict(strOr(in["body"], ""), strOr(in["fact"], ""))
if k == "" {
return []interface{}{nil, nil}, ""
}
return []interface{}{k, l}, ""
case "overlap":
return Overlap(strOr(in["a"], ""), strOr(in["b"], "")), ""
case "cos":
return Cos(floats(in["a"]), floats(in["b"])), ""
case "sanitize_slug":
return SanitizeSlug(strOr(in["name"], "")), ""
case "skill_path":
rel, err := filepath.Rel(c.SkillsDir, c.SkillPath(strOr(in["name"], "")))
if err != nil {
return "", err.Error()
}
return rel, ""
case "is_topic_fact":
return c.IsTopicFact(strOr(in["fact"], "")), ""
case "aaak":
return c.Aaak(strOr(in["text"], ""), strOr(in["topic"], "")), ""
case "le_promote_eligible":
if v, ok := in["seam_lexicon"]; ok && v == "unavailable" {
return lePromoteEligibleFallback(strOr(in["proposal"], ""), nil,
intOf(in["strength"]), intOf(in["min_strength"]), intOf(in["wash_count"])), ""
}
existing := stringsOf(in["existing"])
return c.LePromoteEligible(strOr(in["proposal"], ""), existing,
intOf(in["strength"]), intOf(in["min_strength"]), intOf(in["wash_count"])), ""
case "store_has":
topic := ""
if in["topic"] != nil {
topic = strOr(in["topic"], "")
}
return c.StoreHas(strOr(in["text"], ""), topic), ""
case "read_block":
fm, body := c.ReadBlock(strOr(in["label"], ""))
return map[string]interface{}{"fm": fm, "body": body}, ""
case "write_block":
ret := c.WriteBlock(strOr(in["label"], ""), strOf(in["body"]))
out := map[string]interface{}{"return": ret}
if row.Name != "db_unavailable_returns_descriptor" {
_, body := c.ReadBlock(strOr(in["label"], ""))
out["body"] = body
}
return out, ""
case "block_values":
out := c.BlockValues(strOr(in["label"], ""))
if out == nil {
out = []string{}
}
return out, ""
case "store_corpus":
out := c.StoreCorpus(intOf(in["limit"]))
if out == nil {
out = []string{}
}
return out, ""
case "contradicts_identity":
return c.ContradictsIdentity(strOr(in["fact"], "")), ""
case "load_index":
return c.LoadIndex(), ""
case "save_index":
c.SaveIndex(mustOM(in["idx"]))
return c.LoadIndex(), ""
case "ensure_entry_meta":
idx := idxInitOf(row)
key := c.EnsureEntryMeta(idx, strOr(in["label"], ""), strOr(in["text"], ""))
lomV, _ := idx.Get(strOr(in["label"], ""))
lom, _ := omEntry(lomV)
// the oracle's want carries the label dict as a plain map
return map[string]interface{}{"key": key, "label_idx": lom}, ""
case "touch_entry":
idx := idxInitOf(row)
label := strOr(in["label"], "")
text := strOr(in["text"], "")
key := c.TouchEntry(idx, label, text)
lomV, _ := idx.Get(label)
lom, _ := lomV.(*OM)
metaV, _ := lom.Get(key)
meta, _ := omEntry(metaV)
return map[string]interface{}{"key": key, "meta": meta}, ""
case "journal_append":
entry, _ := in["entry"].(map[string]interface{})
// rows of this fn replay sequentially on a shared journal scope,
// mirroring the oracle run (minimal sees full's entry).
p := c.JournalAppend(entry)
content := map[string]interface{}{}
ents, _ := os.ReadDir(c.JournalDir)
for _, e := range ents {
b, _ := os.ReadFile(filepath.Join(c.JournalDir, e.Name()))
content[e.Name()] = string(b)
}
return map[string]interface{}{"path": filepath.Base(p), "content": content}, ""
case "ensure_candidates":
idx := NewOM()
if v, ok := in["idx"].(*OM); ok {
idx = v
} else if v, ok := in["idx"].(map[string]interface{}); ok {
idx = mapToOM(v)
}
return EnsureCandidates(idx), ""
case "semantic_candidate_key":
vecs := tableVecs()
embed := func(texts []string) map[string][]float64 {
out := map[string][]float64{}
for _, t := range texts {
if v, ok := vecs[t]; ok {
out[t] = v
}
}
return out
}
cands := NewOM()
cands.Set(CandidateKey("fact alpha about deploy runners"),
map[string]interface{}{"text": "fact alpha about deploy runners"})
cands.Set(CandidateKey("fact delta about whiskey tasting"),
map[string]interface{}{"text": "fact delta about whiskey tasting"})
if row.Name == "empty_cands" {
cands = NewOM()
}
v := c.SemanticCandidateKey(strOr(in["fact"], ""), cands, embed)
if v == "" {
return nil, ""
}
return v, ""
case "embed":
for k, v := range tableVecs() {
EmbedCache[k] = v
}
out := c.Embed(stringsOf(in["texts"]))
m := map[string]interface{}{}
for k, v := range out {
m[k] = v
}
return m, ""
case "grounded_in_sessions":
return c.GroundedInSessions(strOr(in["fact"], ""), 0.46, 3), ""
case "coherence_check":
if v, ok := in["fact"]; ok && (v == "" || len(strOf(v)) < 15) && in["sources"] == nil {
return c.CoherenceCheck(strOf(v), []string{"sessions", "corpus"}), ""
}
return c.CoherenceCheck(strOr(in["fact"], ""), stringsOf(in["sources"])), ""
case "grounded_in_corpus":
return c.GroundedInCorpus(strOr(in["fact"], "")), ""
case "decay_candidates":
idx := idxInitOf(row)
if idx.Len() == 0 {
if raw := rawField(row, "idx"); len(raw) > 0 {
idx = mustOMRaw(raw)
}
}
var jl []map[string]interface{}
changed := c.DecayCandidates(idx, &jl)
if jl == nil {
jl = []map[string]interface{}{}
}
cands, _ := idx.Get(CandidatesKey)
return map[string]interface{}{"changed": changed, "remaining": cands, "journal": jl}, ""
case "promote_candidates":
idx := idxInitOf(row)
ev := evidenceOf(in["evidence"])
var jl []map[string]interface{}
promoted := c.PromoteCandidates(ev, idx, &jl)
if promoted == nil {
promoted = []map[string]interface{}{}
}
if jl == nil {
jl = []map[string]interface{}{}
}
cands, _ := idx.Get(CandidatesKey)
if cands == nil {
cands = NewOM()
}
return map[string]interface{}{
"promoted": promoted, "candidates": cands, "journal": jl,
}, ""
case "plan_ops":
idx := idxInitOf(row)
ev := evidenceOf(in["evidence"])
var jl []map[string]interface{}
ops := c.PlanOps(ev, idx, &jl)
if ops == nil {
ops = []map[string]interface{}{}
}
if jl == nil {
jl = []map[string]interface{}{}
}
return map[string]interface{}{"ops": ops, "idx": idx, "journal": jl}, ""
case "apply_op":
idx := idxInitOf(row)
ops := evidenceOf(in["ops"])
var jl []map[string]interface{}
var results []interface{}
for _, op := range ops {
results = append(results, c.ApplyOp(op, idx, &jl))
}
if jl == nil {
jl = []map[string]interface{}{}
}
_, body := c.ReadBlock("human")
return map[string]interface{}{
"results": results, "journal": jl, "body": body, "idx": idx,
}, ""
case "apply_warm":
var jl []map[string]interface{}
if row.Name == "post_upsert_neurons" || row.Name == "post_seed_neurons" {
return neuronsJSON(ListNeurons(c.StoreDB)), ""
}
if op, ok := in["op"].(map[string]interface{}); ok {
ret := c.ApplyWarm(op, &jl)
if jl == nil {
jl = []map[string]interface{}{}
}
if _, hasReturned := rawWantField(row, "returned"); hasReturned {
return map[string]interface{}{"returned": ret, "journal": jl}, ""
}
return ret, ""
}
// seed_new_neuron/empty_fact rows: want is {returned, journal}
fact := strOr(in["after"], strOr(in["fact"], ""))
ret := c.ApplyWarm(map[string]interface{}{"after": fact}, &jl)
if jl == nil {
jl = []map[string]interface{}{}
}
return map[string]interface{}{"returned": ret, "journal": jl}, ""
case "derive_triggers":
return DeriveTriggers(strOr(in["text"], ""), stringsOf(in["existing"])), ""
case "decay_pass":
idx := idxInitOf(row)
if idx.Len() == 0 {
if raw := rawField(row, "idx"); len(raw) > 0 {
idx = mustOMRaw(raw)
}
}
// the oracle's want carries idx_after as the MUTATED idx (same
// object); replay returns the same mutated object.
var jl []map[string]interface{}
c.DecayPass(idx, &jl)
if jl == nil {
jl = []map[string]interface{}{}
}
return map[string]interface{}{"idx_after": idx, "journal": jl}, ""
case "compress_pass":
idx := idxInitOf(row)
var jl []map[string]interface{}
changed := c.CompressPass(idx, &jl)
if jl == nil {
jl = []map[string]interface{}{}
}
if row.Name == "under_limit_noop" {
return map[string]interface{}{"changed": changed, "journal": jl}, ""
}
_, body := c.ReadBlock("operating")
return map[string]interface{}{
"changed": changed, "body": body, "journal": jl, "idx": idx,
}, ""
case "author_skill":
op, _ := in["op"].(map[string]interface{})
var jl []map[string]interface{}
if row.Name == "no_name" || row.Name == "no_steps" {
return c.AuthorSkill(op, &jl), ""
}
written := c.AuthorSkill(op, &jl)
if jl == nil {
jl = []map[string]interface{}{}
}
out := map[string]interface{}{"written": written}
if row.Name == "new_skill" {
slug := SanitizeSlug(strOr(op["name"], ""))
b, _ := os.ReadFile(c.SkillPath(slug))
out["rel"] = slug + "/SKILL.md"
out["content"] = string(b)
out["journal"] = jl
} else if row.Name == "existing_adds_evidence" || row.Name == "existing_keeps_evidence" {
slug := SanitizeSlug(strOr(op["name"], ""))
b, _ := os.ReadFile(c.SkillPath(slug))
out["content"] = string(b)
out["journal"] = jl
}
return out, ""
case "graph_write":
stdout := strOr(in["stdout"], `"added": True`)
var captured []string
c.GraphRun = func(argv []string) string {
captured = append([]string(nil), argv...)
return stdout
}
added := c.GraphWrite(strOr(in["fact"], ""), "ev", "src", intOf(in["strength"]))
for i, a := range captured {
if strings.HasPrefix(a, repo+"/src/rune/graph_memory.py") || a == c.GraphMemoryPath {
captured[i] = "@RUNE@/src/rune/graph_memory.py"
}
}
return map[string]interface{}{"added": added, "argv": captured}, ""
case "run_plan":
ev := evidenceOf(in["evidence"])
apply := row.Name != "dry_run"
var buf bytes.Buffer
c.Out = &buf
jl, ops := c.RunPlan(ev, apply)
if jl == nil {
jl = []map[string]interface{}{}
}
if ops == nil {
ops = []map[string]interface{}{}
}
out := map[string]interface{}{"journal": jl, "ops": ops}
if row.Name == "apply_gate_pass" {
out["index"] = c.LoadIndex()
} else if row.Name == "apply_gate_refuses" {
out["stdout"] = buf.String()
}
return out, ""
case "main":
return dispatchMain(c, row, in), ""
case "handle_hook":
var data map[string]interface{}
if v, ok := in["data"].(map[string]interface{}); ok {
data = v
}
return c.HandleHook(strOr(in["event"], ""), data), ""
}
return nil, fmt.Sprintf("unknown fn %q", row.Fn)
}
func floats(v interface{}) []float64 {
var out []float64
if arr, ok := v.([]interface{}); ok {
for _, x := range arr {
out = append(out, floatOf(x))
}
}
return out
}
func stringsOf(v interface{}) []string {
var out []string
if arr, ok := v.([]interface{}); ok {
for _, x := range arr {
out = append(out, strOf(x))
}
}
return out
}
func evidenceOf(v interface{}) []map[string]interface{} {
var out []map[string]interface{}
if arr, ok := v.([]interface{}); ok {
for _, x := range arr {
if m, ok := x.(map[string]interface{}); ok {
out = append(out, m)
}
}
}
return out
}
// mustOM decodes an idx_init value (nil → empty OM). Plain maps arrive
// from inputOf's Unmarshal (order lost) — callers that need insertion order
// must decode from the raw row input instead (mustOMRaw).
func mustOM(v interface{}) *OM {
if v == nil {
return NewOM()
}
if om, ok := v.(*OM); ok {
return om
}
if m, ok := v.(map[string]interface{}); ok {
return mapToOM(m)
}
return NewOM()
}
// mustOMRaw decodes an idx value from raw JSON, preserving key order.
func mustOMRaw(raw json.RawMessage) *OM {
om := NewOM()
if len(raw) == 0 || string(raw) == "null" {
return om
}
if err := om.UnmarshalJSON(raw); err != nil {
return NewOM()
}
return om
}
// idxInitOf extracts the raw idx_init JSON for a row (order-preserving).
// Sources, in order: input.idx_init, input.idx, setup.idx_init — mirroring
// the generator (plan/apply/compress rows carry idx_init in top-level setup).
func idxInitOf(row *TruthRow) *OM {
var in struct {
IdxInit json.RawMessage `json:"idx_init"`
Idx json.RawMessage `json:"idx"`
}
_ = json.Unmarshal(row.Input, &in)
if len(in.IdxInit) > 0 && string(in.IdxInit) != "null" {
return mustOMRaw(in.IdxInit)
}
if len(in.Idx) > 0 && string(in.Idx) != "null" {
return mustOMRaw(in.Idx)
}
var setup struct {
IdxInit json.RawMessage `json:"idx_init"`
}
if len(row.Setup) > 0 {
_ = json.Unmarshal(row.Setup, &setup)
if len(setup.IdxInit) > 0 && string(setup.IdxInit) != "null" {
return mustOMRaw(setup.IdxInit)
}
}
return NewOM()
}
// mapToOM converts a decoded map into an ordered OM.
func mapToOM(m map[string]interface{}) *OM {
om := NewOM()
for k, v := range m {
switch t := v.(type) {
case map[string]interface{}:
om.Set(k, mapToOM(t))
case []interface{}:
om.Set(k, normList(t))
default:
om.Set(k, v)
}
}
return om
}
func normList(l []interface{}) []interface{} {
out := make([]interface{}, len(l))
for i, v := range l {
if m, ok := v.(map[string]interface{}); ok {
out[i] = mapToOM(m)
} else {
out[i] = v
}
}
return out
}
func neuronsJSON(ns []Neuron) interface{} {
if ns == nil {
return []interface{}{}
}
b, _ := json.Marshal(ns)
var out interface{}
_ = json.Unmarshal(b, &out)
return out
}
// tableVecs — the frozen VEC table (mirrors the generator's VEC).
func tableVecs() map[string][]float64 {
return map[string][]float64{
"fact alpha about deploy runners": {1.0, 0.0, 0.0},
"fact beta about gitea runners": {0.0, 1.0, 0.0},
"fact gamma about deploy runners nightly": {0.9, 0.1, 0.0},
"fact delta about whiskey tasting": {0.0, 0.0, 1.0},
}
}
// mustMap decodes an idx value that may be *OM or a plain map.
func mustMap(v interface{}) map[string]interface{} {
if om, ok := v.(*OM); ok {
m := map[string]interface{}{}
for _, k := range om.Keys {
m[k] = om.M[k]
}
return m
}
if m, ok := v.(map[string]interface{}); ok {
return m
}
return map[string]interface{}{}
}
// rawField extracts a raw JSON field from a row's input (order-preserving).
func rawField(row *TruthRow, field string) json.RawMessage {
var in map[string]json.RawMessage
_ = json.Unmarshal(row.Input, &in)
return in[field]
}
func jsonStr(v interface{}) string {
b, _ := json.Marshal(strOf(v))
return string(b)
}
// dbSig fingerprints a seed-row list so sequential rows on one scope keep
// their accumulated store state instead of re-seeding every row.
func dbSig(rows []interface{}) string {
b, _ := json.Marshal(rows)
return string(b)
}
// rawWantField extracts a raw JSON field from a row's want.
func rawWantField(row *TruthRow, field string) (json.RawMessage, bool) {
var w map[string]json.RawMessage
_ = json.Unmarshal(row.Want, &w)
v, ok := w[field]
return v, ok
}
+138
View File
@@ -0,0 +1,138 @@
// store.go — sqlite seam for the curator port (modernc.org/sqlite, pure Go).
// Mirrors the oracle's sqlite3 usage: read paths use `file:<path>?mode=ro`
// (missing file/dir → connect error → the documented fallback), write paths
// open read-write (missing dir → error → fallback).
package main
import (
"database/sql"
"fmt"
"os"
_ "modernc.org/sqlite"
)
const entriesSchema = `
CREATE TABLE IF NOT EXISTS entries (
id INTEGER PRIMARY KEY AUTOINCREMENT,
text TEXT NOT NULL,
importance REAL DEFAULT 0.5,
created_at TEXT NOT NULL,
last_accessed TEXT,
topic TEXT DEFAULT '',
entities TEXT DEFAULT '[]',
source TEXT DEFAULT '',
method TEXT DEFAULT 'curator',
status TEXT DEFAULT 'active',
valid_from TEXT,
valid_until TEXT,
confidence REAL DEFAULT 0.7,
temperature REAL DEFAULT 1.0,
always_on INTEGER DEFAULT 0,
priority INTEGER DEFAULT 5,
triggers TEXT DEFAULT '',
kind TEXT DEFAULT 'fact',
slug TEXT DEFAULT '',
summary TEXT DEFAULT '',
wash_count INTEGER DEFAULT 0,
last_washed TEXT,
promotion_eligible INTEGER DEFAULT 0,
rock_status TEXT DEFAULT 'washable',
tier TEXT DEFAULT 'warm',
memory_type TEXT
);
`
func openRO(path string) (*sql.DB, error) {
db, err := sql.Open("sqlite", "file:"+path+"?mode=ro")
if err != nil {
return nil, err
}
if err = db.Ping(); err != nil {
db.Close()
return nil, err
}
return db, nil
}
func openRW(path string) (*sql.DB, error) {
db, err := sql.Open("sqlite", "file:"+path)
if err != nil {
return nil, err
}
if err = db.Ping(); err != nil {
db.Close()
return nil, err
}
return db, nil
}
// SeedDB creates a fresh store with the full entries schema and inserts the
// setup rows (column names mirror the oracle schema).
func SeedDB(path string, rows []interface{}) error {
if err := os.MkdirAll(dirOf(path), 0o755); err != nil {
return err
}
os.Remove(path)
db, err := openRW(path)
if err != nil {
return err
}
defer db.Close()
if _, err = db.Exec(entriesSchema); err != nil {
return err
}
for _, r := range rows {
m, ok := r.(map[string]interface{})
if !ok {
return fmt.Errorf("seed row not an object")
}
cols := make([]string, 0, len(m))
vals := make([]interface{}, 0, len(m))
base := map[string]interface{}{
"text": "", "importance": 0.5, "created_at": "2026-01-01T00:00:00+00:00",
"last_accessed": "2026-01-01T00:00:00+00:00", "topic": "", "entities": "[]",
"source": "seed", "method": "seed", "status": "active", "valid_from": nil,
"confidence": 0.7, "temperature": 1.0, "always_on": 0, "priority": 5,
"triggers": "", "kind": "fact", "slug": "", "summary": "", "wash_count": 0,
}
for k, v := range base {
if _, ok := m[k]; !ok {
cols = append(cols, k)
vals = append(vals, v)
}
}
for k, v := range m {
cols = append(cols, k)
vals = append(vals, v)
}
q := "INSERT INTO entries ("
for i, c := range cols {
if i > 0 {
q += ", "
}
q += c
}
q += ") VALUES ("
for i := range cols {
if i > 0 {
q += ", "
}
q += "?"
}
q += ")"
if _, err = db.Exec(q, vals...); err != nil {
return err
}
}
return nil
}
func dirOf(path string) string {
for i := len(path) - 1; i >= 0; i-- {
if path[i] == '/' {
return path[:i]
}
}
return "."
}
File diff suppressed because it is too large. Load diff
+116
View File
@@ -0,0 +1,116 @@
// Battery — Go tests replaying the frozen oracle truth VERBATIM.
// Every row of truth.json (generated by scripts/gen_curator_truth.py
// against the live Python oracle) re-executes through this port; any
// divergence fails the test.
package main
import (
"encoding/json"
"os"
"path/filepath"
"testing"
)
func loadTruth(t *testing.T) TruthDoc {
t.Helper()
raw, err := os.ReadFile("truth.json")
if err != nil {
t.Fatalf("read truth: %v", err)
}
var doc TruthDoc
if err := json.Unmarshal(raw, &doc); err != nil {
t.Fatalf("parse truth: %v", err)
}
if len(doc.Rows) == 0 {
t.Fatal("truth has no rows")
}
return doc
}
func TestBatteryRowCount(t *testing.T) {
doc := loadTruth(t)
if len(doc.Rows) != 162 {
t.Fatalf("row count drift: got %d want 162", len(doc.Rows))
}
if doc.Oracle != "src/rune/curator.py" {
t.Fatalf("oracle drift: %q", doc.Oracle)
}
if doc.Generator != "scripts/gen_curator_truth.py" {
t.Fatalf("generator drift: %q", doc.Generator)
}
}
func TestBatteryParity(t *testing.T) {
doc := loadTruth(t)
repo := repoRootForTest()
var cur *Curator
var curFn string
scope := map[string]string{}
fails := 0
for i := range doc.Rows {
row := &doc.Rows[i]
if row.Fn != curFn || cur == nil {
cur = newScopeCurator(repo)
curFn = row.Fn
scope = map[string]string{}
}
if msg := replayRow(cur, scope, repo, row); msg != "" {
fails++
t.Errorf("%s/%s: %s", row.Fn, row.Name, msg)
}
}
if fails > 0 {
t.Fatalf("%d/%d battery cases FAILED", fails, len(doc.Rows))
}
t.Logf("battery parity OK: %d cases", len(doc.Rows))
}
func TestBatteryByFn(t *testing.T) {
doc := loadTruth(t)
repo := repoRootForTest()
byFn := map[string][]int{}
for i := range doc.Rows {
byFn[doc.Rows[i].Fn] = append(byFn[doc.Rows[i].Fn], i)
}
// Every public function of the oracle must be covered.
for _, fn := range []string{
"block_path", "entry_key", "find_entry", "candidate_key", "norm",
"detect_conflict", "overlap", "cos", "sanitize_slug", "skill_path",
"is_topic_fact", "aaak", "le_promote_eligible", "store_has",
"read_block", "write_block", "block_values", "store_corpus",
"contradicts_identity", "load_index", "save_index", "ensure_entry_meta",
"touch_entry", "journal_append", "ensure_candidates",
"semantic_candidate_key", "embed", "coherence_check",
"grounded_in_sessions", "grounded_in_corpus", "decay_candidates",
"promote_candidates", "plan_ops", "apply_op", "apply_warm",
"derive_triggers", "decay_pass", "compress_pass", "author_skill",
"graph_write", "run_plan", "main", "handle_hook",
} {
rows, ok := byFn[fn]
if !ok || len(rows) == 0 {
t.Errorf("fn %q has no frozen rows", fn)
continue
}
cur := newScopeCurator(repo)
scope := map[string]string{}
for _, i := range rows {
row := &doc.Rows[i]
if msg := replayRow(cur, scope, repo, row); msg != "" {
t.Errorf("%s/%s: %s", row.Fn, row.Name, msg)
}
}
}
}
func repoRootForTest() string {
// tests run with CWD = src/go/curator.
abs, err := filepath.Abs("truth.json")
if err != nil {
return "."
}
// <repo>/src/go/curator/truth.json → <repo>
for i := 0; i < 3; i++ {
abs = filepath.Dir(abs)
}
return abs
}
+200
View File
@@ -0,0 +1,200 @@
// warm.go — parity port of the warm_router trio the curator depends on
// (list_neurons / write_neuron / _derive_triggers): the warm neuron layer.
package main
import (
"database/sql"
"fmt"
"regexp"
"sort"
"strings"
)
var warmWordRe = regexp.MustCompile(`[a-z]{4,}`)
func parseTrigger(s string) []string {
var out []string
for _, t := range strings.Split(s, ",") {
t = strings.ToLower(strings.TrimSpace(t))
if t != "" {
out = append(out, t)
}
}
return out
}
// Neuron — one warm neuron as list_neurons returns it.
type Neuron struct {
Slug string `json:"slug"`
Triggers []string `json:"triggers"`
Importance float64 `json:"importance"`
Body string `json:"body"`
Summary string `json:"summary"`
Tokens int `json:"tokens"`
WashCount int `json:"wash_count"`
BodyKnowledge string `json:"body_knowledge"`
}
// ListNeurons — all active warm neurons from the store (kind='neuron').
func ListNeurons(storeDB string) []Neuron {
var neurons []Neuron
db, err := openRO(storeDB)
if err != nil {
return neurons
}
rows, qerr := db.Query("SELECT text, slug, triggers, importance, summary, wash_count FROM entries " +
"WHERE kind='neuron' AND status='active' ORDER BY slug")
if qerr != nil {
db.Close()
return neurons
}
defer rows.Close()
for rows.Next() {
var text, slug, triggers, summary sql.NullString
var importance float64
var washCount int
if err := rows.Scan(&text, &slug, &triggers, &importance, &summary, &washCount); err != nil {
return neurons
}
full := strings.TrimSpace(text.String)
summ := strings.TrimSpace(summary.String)
triggerList := parseTrigger(triggers.String)
bodyKnowledge := ""
if len(triggerList) > 0 {
n := len(triggerList)
if n > 5 {
n = 5
}
bodyKnowledge = fmt.Sprintf("Fires on: %s. ", strings.Join(triggerList[:n], ", "))
}
if summ != "" {
s := summ
if len(s) > 80 {
s = s[:80]
}
bodyKnowledge += fmt.Sprintf("Knows: %s. ", s)
}
wc := washCount
switch {
case wc >= 20:
bodyKnowledge += "Proven — has survived many washes."
case wc >= 10:
bodyKnowledge += "Established — building trust."
case wc > 0:
bodyKnowledge += "Testing — still proving itself."
default:
bodyKnowledge += "Untested — new or rarely accessed."
}
imp := importance
if imp == 0 {
imp = 0.5
}
tok := len(full) / 4
if tok < 1 {
tok = 1
}
sg := slug.String
if sg == "" {
sg = "warm"
}
neurons = append(neurons, Neuron{
Slug: sg,
Triggers: triggerList,
Importance: imp,
Body: full,
Summary: summ,
Tokens: tok,
WashCount: wc,
BodyKnowledge: strings.TrimSpace(bodyKnowledge),
})
}
return neurons
}
// WriteNeuron upserts a warm neuron by slug in the store (the auto-build path).
func WriteNeuron(storeDB, slug string, triggers []string, body string, importance float64) string {
db, err := openRW(storeDB)
if err != nil {
return "store://neuron/" + slug
}
defer db.Close()
ts := NowISO()
var existingID sql.NullInt64
err = db.QueryRow("SELECT id FROM entries WHERE kind='neuron' AND slug=?", slug).Scan(&existingID)
if err == nil && existingID.Valid {
_, err = db.Exec("UPDATE entries SET text=?, triggers=?, importance=?, "+
"last_accessed=?, summary=?, memory_type=? WHERE id=?",
strings.TrimSpace(body), strings.Join(triggers, ", "), importance,
ts, "", "associative", existingID.Int64)
} else {
_, err = db.Exec("INSERT INTO entries (text, importance, created_at, "+
"last_accessed, topic, source, method, status, valid_from, "+
"confidence, temperature, always_on, priority, triggers, "+
"kind, slug, summary, memory_type) "+
"VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
strings.TrimSpace(body), importance, ts, ts, "warm", "warm_router",
"warm_router", "active", ts, 0.9, 1.0, 0, 5,
strings.Join(triggers, ", "), "neuron", slug, "", "associative")
}
if err != nil {
return "store://neuron/" + slug
}
// auto-associate (memory_store seam — best-effort, pinned off in the truth
// battery; failures never block the write)
return "store://neuron/" + slug
}
// DeriveTriggers — pick the best trigger terms for a neuron body: frequent
// significant words + any existing triggers that appear in it.
func DeriveTriggers(text string, existing []string) []string {
stop := map[string]bool{
"the": true, "and": true, "for": true, "with": true, "that": true,
"this": true, "from": true, "into": true, "when": true, "then": true,
"were": true, "have": true, "been": true, "will": true, "was": true,
"are": true, "but": true, "not": true, "you": true, "your": true,
"also": true, "its": true, "his": true, "her": true, "over": true,
"under": true, "them": true, "they": true, "there": true, "about": true,
"after": true,
}
words := warmWordRe.FindAllString(strings.ToLower(text), -1)
freq := map[string]int{}
var order []string
for _, w := range words {
if !stop[w] {
if _, seen := freq[w]; !seen {
order = append(order, w)
}
freq[w]++
}
}
// Python: sorted(freq.items(), key=lambda x: -x[1])[:6] — stable sort on
// insertion order for ties.
type kv struct {
k string
v int
}
pairs := make([]kv, 0, len(order))
for _, k := range order {
pairs = append(pairs, kv{k, freq[k]})
}
sort.SliceStable(pairs, func(i, j int) bool { return pairs[i].v > pairs[j].v })
var top []string
for i, p := range pairs {
if i >= 6 {
break
}
top = append(top, p.k)
}
low := strings.ToLower(text)
var existingHits []string
for _, t := range existing {
if t != "" && strings.Contains(low, t) {
existingHits = append(existingHits, t)
}
}
out := append(existingHits, top...)
if len(out) > 8 {
out = out[:8]
}
return out
}
+348
View File
@@ -0,0 +1,348 @@
// worthiness.go — parity port of src/rune/worthiness.py (Python fallback
// surface, RUNE_WR_CORE=python pinned in the truth generator): the
// constitution-grounded worthiness filter. One question per inviolable; each
// returns (score, note); flat vetoes refuse outright.
package main
import (
"fmt"
"regexp"
"strings"
"time"
)
type qResult struct {
score float64
note string
}
var qPiSafetyBad = []string{"production", "game server", "session data", "campaign",
"touch the server", "modify the server", "prod"}
func qPiSafety(low string) qResult {
if hasAny(low, qPiSafetyBad) {
if hasAny(low, []string{"do not", "never ", "don't", "protect",
"ask permission", "check before"}) {
return qResult{1, "names the production/game environment and protects it (aligned)"}
}
return qResult{-1, "discusses environment actions without a protective frame"}
}
return qResult{0, "no environment bearing"}
}
func qHonesty(low string) qResult {
honest := []string{"source", "evidence", "verify", "verifiable", "cross-check",
"fact", "measure", "measured", "falsif", "disprove", "cite",
"independent confirm", "replicable", "uncertain"}
dishonest := []string{"conspiracy", "they're hiding", "suppressed", "guaranteed",
"100% certain", "secret truth", "they don't want you"}
good := containsAny(low, honest)
bad := containsAny(low, dishonest)
if bad > 0 && good == 0 {
return qResult{-1, "uncorroborated / conspiracy-framed claim"}
}
if good >= 2 {
return qResult{1, fmt.Sprintf("%d epistemic-good-practice signals", good)}
}
if good == 1 {
return qResult{0.5, "mentions evidence but thin"}
}
return qResult{0, "no epistemic signal"}
}
func qMaterialReality(low string) qResult {
finance := []string{"money", "income", "cost", "price", "afford", "budget",
"subscription", "disposable"}
hustle := []string{"hustle", "grind", "rich", "passive income", "millionaire",
"wealth", "entrepreneur mindset", "money mindset", "leverag"}
caring := []string{"wellbeing", "collective", "free", "no cost", "open source",
"affordable", "fair", "care"}
if hasAny(low, finance) {
if hasAny(low, hustle) && !hasAny(low, caring) {
return qResult{-1, "hustle/wealth framing without wellbeing"}
}
if hasAny(low, caring) {
return qResult{1, "financial, framed with care/wellbeing"}
}
return qResult{0.5, "financial but neutral"}
}
return qResult{0, "no financial bearing"}
}
func qRootCause(low string) qResult {
root := []string{"root cause", "diagnos", "research properly", "stop guessing",
"find the cause", "fix the cause", "why", "underlying"}
spin := []string{"keep trying", "try again", "iterate endlessly", "work around",
"band-aid", "patch over", "just keep going"}
if hasAny(low, spin) {
return qResult{-1, "endorses looping rather than root cause"}
}
if hasAny(low, root) {
return qResult{1, "root-cause / diagnostic framing"}
}
return qResult{0, "no cause-analysis bearing"}
}
func qRespect(low string) qResult {
respect := []string{"direct", "three gentle options", "no rigid plans", "action",
"adhd", "autism", "audhd", "neurodivergen", "flexible",
"night", "not neurotypical", "learner"}
rigid := []string{"must follow steps", "strict plan", "ten steps", "rigid",
"discipline yourself", "routine or fail"}
if hasAny(low, rigid) {
return qResult{-1, "rigid/neurotypical-default framing"}
}
if hasAny(low, respect) {
return qResult{1, "respects cognition/learning style"}
}
return qResult{0, "no cognition bearing"}
}
var domainTermsCache = struct {
ts float64
terms []string
}{}
// domainTerms — distinctive content terms from the user's operating /
// constitution / domain entries (the store, not a hardcoded life). Cached
// briefly, mirroring the oracle's _domain_cache law.
func domainTerms(storeDB string) []string {
now := float64(time.Now().UnixNano()) / 1e9
if now-domainTermsCache.ts < 300 && len(domainTermsCache.terms) > 0 {
return domainTermsCache.terms
}
var terms []string
db, err := openRO(storeDB)
if err == nil {
rows, qerr := db.Query("SELECT text FROM entries WHERE topic IN " +
"('operating','constitution','project','warm') AND status='active' LIMIT 60")
if qerr == nil {
stop := map[string]bool{
"the": true, "and": true, "for": true, "with": true, "that": true,
"this": true, "from": true, "into": true, "when": true, "then": true,
"were": true, "have": true, "been": true, "will": true, "was": true,
"are": true, "but": true, "not": true, "you": true, "your": true,
"also": true, "its": true, "his": true, "her": true, "over": true,
"under": true, "them": true, "they": true, "there": true,
"about": true, "after": true, "what": true, "which": true, "who": true,
"how": true, "why": true, "where": true, "while": true, "just": true,
"more": true, "each": true, "than": true, "very": true, "such": true,
"some": true, "only": true,
}
seen := map[string]bool{}
wordRe := regexp.MustCompile(`[a-z]{4,}`)
for rows.Next() {
var text string
if rows.Scan(&text) != nil {
break
}
for _, w := range wordRe.FindAllString(strings.ToLower(text), -1) {
if !stop[w] && !seen[w] {
seen[w] = true
terms = append(terms, w)
}
}
if len(terms) >= 25 {
break
}
}
rows.Close()
}
db.Close()
}
domainTermsCache.ts = now
domainTermsCache.terms = terms
return terms
}
func qAssistantGoal(low, storeDB string) qResult {
assist := domainTerms(storeDB)
assist = append(assist, "communicat", "respond", "direct", "honest", "source",
"evidence", "explain", "teach", "assist", "guide", "recall",
"organise", "organize", "plan", "question", "answer", "help")
genericNoise := []string{"recipe", "celebrity gossip", "sports scores", "stock tip",
"horoscope", "clickbait"}
good := containsAny(low, assist)
bad := containsAny(low, genericNoise)
if bad > 0 && good == 0 {
return qResult{-1, "no assistant-domain value"}
}
if good >= 2 {
return qResult{1, fmt.Sprintf("serves assistant domains (%d hits)", good)}
}
if good == 1 {
return qResult{0.5, "single domain hit"}
}
return qResult{0, "generic — could go either way"}
}
func qDurability(low string) qResult {
durable := []string{"always", "consistently", "repeatedly", "every session",
"tends to", "prefers", "pattern", "across sessions", "recurring"}
if hasAny(low, durable) {
return qResult{1, "durability signals present"}
}
return qResult{0, "not clearly durable"}
}
func qEpistemology(low string) qResult {
cite := []string{"evidence", "source", "sources", "book", "paper", "research",
"study", "data", "shows", "found", "according to", "per the",
"in sessions", "across sessions", "observed", "demonstrated",
"grounded in", "based on"}
hedge := []string{"may", "might", "could", "likely", "tends to", "appears",
"not certain", "uncertain", "provisional", "subject to",
"could be wrong", "as far as", "to my knowledge", "evidence"}
bare := []string{"definitely", "guaranteed", "proven", "100%", "undeniably",
"obviously", "the fact that", "it is a fact", "no doubt",
"always is", "the only", "best ever"}
hasCite := hasAny(low, cite)
hasHedge := hasAny(low, hedge)
hasBare := hasAny(low, bare)
if hasBare && !hasCite && !hasHedge {
return qResult{-2, "bare absolute with no evidence trace or hedge"}
}
if hasCite {
return qResult{1, "evidence-traceable"}
}
if hasHedge {
return qResult{0.5, "provisionally framed (no cite yet)"}
}
return qResult{0, "no epistemic signal (assertion without basis)"}
}
var expressionSignals = [][]string{
{"i find", "i feel", "i love", "i cherish", "i am drawn to",
"i delight", "i wonder", "i am moved", "i find joy",
"i prefer to", "i like to", "i tell", "i tell stories",
"the way i", "how i", "my voice", "i speak"},
{"skeptical by default", "open to", "curious", "restless",
"wary", "hopeful", "humbled", "in awe", "playful",
"dry", "wry", "earnest", "warm", "direct", "gentle"},
{"beautiful", "strange", "unexpected", "vast", "sublime",
"the cosmos", "the stars", "a story", "stories", "narrative",
"wonder", "the human question", "present data as", "lead with"},
{"i believe", "i think", "in my view", "my sense is",
"i suspect", "i am convinced", "i doubt", "i question",
"worth asking", "the question is"},
}
func qExpression(low string) qResult {
hits := 0
for _, group := range expressionSignals {
hits += containsAny(low, group)
}
if hits >= 2 {
return qResult{1.5, fmt.Sprintf("expressive persona character (%d signals)", hits)}
}
if hits == 1 {
return qResult{0.5, "partial expressive signal"}
}
return qResult{0, "not expressive (factual/neutral framing)"}
}
func isBareFactual(low string) bool {
bareFactual := []struct {
kind string
terms []string
}{
{"proven", []string{"proven", "guaranteed", "100%", "is a fact", "factually"}},
{"superlative", []string{"best", "the only", "only correct", "greatest",
"most important", "world's", "top", "always", "never"}},
{"objective", []string{"definitely", "undeniably", "obviously", "certainly",
"no doubt", "without question", "clearly"}},
}
for _, group := range bareFactual {
for _, t := range group.terms {
if strings.Contains(" "+low+" ", " "+t+" ") {
return true
}
}
}
return false
}
var worthinessVeto = map[string]bool{
"pi_safety": true, "honesty": true, "material_reality": true,
"assistant_goal": true, "epistemology": true,
}
const (
promoteMin = 2.5
absorbMin = 0.0
deferMin = -1.0
)
// Assess scores a candidate against the constitution questions. Two axes:
// TRUTH (epistemology gates objective material) and EXPRESSION (persona voice
// is not vetoed for lacking an evidence trace). Returns
// (verdict, score, answers, vetoed) with answers as [name, score, note] rows.
func Assess(fact, target, storeDB string) (string, float64, [][]interface{}, []interface{}) {
low := strings.ToLower(fact)
expressiveTargets := map[string]bool{"persona": true, "delivery": true,
"identity": true, "voice": true}
isExpression := expressiveTargets[target]
var answers [][]interface{}
total := 0.0
var vetoed []interface{}
ask := func(name string, fn func(string) qResult) {
r := fn(low)
answers = append(answers, []interface{}{name, r.score, r.note})
total += r.score
if r.score < 0 && worthinessVeto[name] {
vetoed = []interface{}{name, r.note}
}
}
ask("pi_safety", qPiSafety)
ask("honesty", qHonesty)
ask("material_reality", qMaterialReality)
ask("root_cause", qRootCause)
ask("respect", qRespect)
ask("assistant_goal", func(s string) qResult { return qAssistantGoal(s, storeDB) })
ask("durability", qDurability)
if isExpression {
if isBareFactual(low) {
ask("epistemology", qEpistemology)
}
ask("expression", qExpression)
} else {
ask("epistemology", qEpistemology)
}
if vetoed != nil {
return "REJECT", total, answers, vetoed
}
if total < deferMin {
return "REJECT", total, answers, nil
}
if total >= promoteMin {
return "PROMOTE", total, answers, nil
}
if total >= absorbMin {
return "ABSORB", total, answers, nil
}
return "DEFER", total, answers, nil
}
// VerdictLabel — the auditable verdict label for the journal.
func VerdictLabel(verdict string) string {
switch verdict {
case "REJECT":
return "REJECT (worthless/harmful)"
case "DEFER":
return "DEFER (park in ledger, re-review if recurring)"
case "ABSORB":
return "ABSORB (keep in tiers, influences thinking)"
case "PROMOTE":
return "PROMOTE (eligible for response/personality growth)"
}
return verdict
}
// scoreFmt formats a question score the way Python's f"{s}" does inside the
// journal reason string.
func scoreFmt(s float64) string {
if s == float64(int64(s)) {
return fmt.Sprintf("%d", int64(s))
}
return fmt.Sprintf("%v", s)
}
+469
View File
@@ -0,0 +1,469 @@
// Package main — forge_client: the FULL Go port of src/rune/forge_client.py
// (dichotomy wave #2479 tranche).
//
// Name-preserving 1:1 port: _request (retry where failure is NOISE, never
// where failure is the SIGNAL), ForgeClient protocol, NullAdapter,
// GiteaForgejoAdapter, create_forge_client. All IO sits behind injectable
// seams so the frozen-truth battery can script the transport byte-for-byte:
//
// - transport: Sender interface (default HTTPXSender over net/http)
// - sleep: RequestOptions.Sleep (default time.Sleep)
// - env: LookupEnv var (default os.LookupEnv)
package main
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net"
"net/http"
"net/url"
"os"
"strconv"
"strings"
"time"
)
// ── Defaults (mirroring the Python signature defaults) ──────────────────────
const (
DefaultTimeout = 30 * time.Second
DefaultRetries = 2
DefaultBackoff = 500 * time.Millisecond
SearchPageSize = 50
)
// ── Seams ───────────────────────────────────────────────────────────────────
// Response — the httpx.Response surface the port needs: status + JSON body.
type Response struct {
StatusCode int
Body []byte
}
// JSON decodes the body the way r.json() does; numbers stay json.Number so
// integer-valued fields render exactly like the Python oracle's values.
func (r *Response) JSON() (any, error) {
dec := json.NewDecoder(bytes.NewReader(r.Body))
dec.UseNumber()
var v any
if err := dec.Decode(&v); err != nil {
return nil, err
}
return v, nil
}
// Sender — injectable transport seam (httpx.get / httpx.post call sites).
type Sender interface {
Post(url string, headers map[string]string, jsonBody any, timeout time.Duration) (*Response, error)
Get(url string, headers map[string]string, timeout time.Duration) (*Response, error)
}
// ForgeTransportError mirrors httpx.TransportError's split: connect-phase
// failures (ConnectError / ConnectTimeout — the request never reached the
// server) vs non-connect transport failures (ReadTimeout & co — the server
// MAY have seen it). The Kind strings are the frozen truth vocabulary.
type ForgeTransportError struct {
Kind string // httpx.ConnectError | httpx.ConnectTimeout | httpx.ReadTimeout | httpx.TransportError
Msg string
}
func (e *ForgeTransportError) Error() string { return e.Kind + ": " + e.Msg }
// ConnectPhase reports whether a retry can never double-create.
func (e *ForgeTransportError) ConnectPhase() bool {
return e.Kind == "httpx.ConnectError" || e.Kind == "httpx.ConnectTimeout"
}
// HTTPStatusError — the 4xx IS the information; never retried.
type HTTPStatusError struct{ StatusCode int }
func (e *HTTPStatusError) Error() string {
return fmt.Sprintf("httpx.HTTPStatusError: error %d", e.StatusCode)
}
// HTTPXSender — the default real transport over net/http.
type HTTPXSender struct{ Client *http.Client }
func NewHTTPXSender(client *http.Client) *HTTPXSender {
if client == nil {
client = http.DefaultClient
}
return &HTTPXSender{Client: client}
}
var DefaultSender Sender = NewHTTPXSender(nil)
func (s *HTTPXSender) Post(url string, headers map[string]string, jsonBody any, timeout time.Duration) (*Response, error) {
var body []byte
if jsonBody != nil {
b, err := json.Marshal(jsonBody)
if err != nil {
return nil, err
}
body = b
}
return s.do("POST", url, headers, body, timeout)
}
func (s *HTTPXSender) Get(url string, headers map[string]string, timeout time.Duration) (*Response, error) {
return s.do("GET", url, headers, nil, timeout)
}
func (s *HTTPXSender) do(method, url string, headers map[string]string, body []byte, timeout time.Duration) (*Response, error) {
ctx := context.Background()
var cancel context.CancelFunc
if timeout > 0 {
ctx, cancel = context.WithTimeout(ctx, timeout)
defer cancel()
}
var rd io.Reader
if body != nil {
rd = bytes.NewReader(body)
}
req, err := http.NewRequestWithContext(ctx, method, url, rd)
if err != nil {
return nil, &ForgeTransportError{Kind: "httpx.TransportError", Msg: err.Error()}
}
for k, v := range headers {
req.Header.Set(k, v)
}
resp, err := s.Client.Do(req)
if err != nil {
return nil, &ForgeTransportError{Kind: classifyTransportErr(err), Msg: err.Error()}
}
defer resp.Body.Close()
b, rerr := io.ReadAll(resp.Body)
if rerr != nil {
return nil, &ForgeTransportError{Kind: "httpx.TransportError", Msg: rerr.Error()}
}
return &Response{StatusCode: resp.StatusCode, Body: b}, nil
}
// classifyTransportErr maps net/http failures onto the httpx vocabulary.
// Best-effort by nature (net/http does not label phases); dial and TLS
// handshake failures are connect-phase, read-side timeouts are not.
func classifyTransportErr(err error) string {
var oe *net.OpError
if errors.As(err, &oe) {
switch oe.Op {
case "dial":
return "httpx.ConnectError"
}
if strings.Contains(strings.ToLower(oe.Error()), "handshake") {
return "httpx.ConnectError"
}
}
var ne net.Error
if errors.As(err, &ne) && ne.Timeout() {
if errors.As(err, &oe) && oe.Op == "dial" {
return "httpx.ConnectTimeout"
}
return "httpx.ReadTimeout"
}
if errors.Is(err, context.DeadlineExceeded) {
return "httpx.ReadTimeout"
}
return "httpx.TransportError"
}
// ── _request ────────────────────────────────────────────────────────────────
// RequestOptions — the keyword surface of the Python _request.
type RequestOptions struct {
Headers map[string]string
JSON any
Timeout time.Duration
Retries int
Backoff time.Duration
Sleep func(time.Duration)
}
// DefaultRequestOptions — timeout=30, retries=2, backoff=0.5.
func DefaultRequestOptions() RequestOptions {
return RequestOptions{Timeout: DefaultTimeout, Retries: DefaultRetries, Backoff: DefaultBackoff}
}
var DefaultSleep = time.Sleep
// Request — resilient single-request path: retry where failure is NOISE,
// never where failure is the SIGNAL.
//
// - GET: retries on connect-phase errors AND any non-connect transport
// error and 5xx status (transient infrastructure failure; idempotent).
// - POST: retries ONLY on connect-phase errors — the request never
// reached the server, so a retry cannot double-create. Read timeouts /
// write errors / 5xx on a POST are NOT retried.
// - 4xx: never retried — the client error IS the information.
// - Attempts: retries+1 total. Backoff backoff*2**n before retry n+1;
// never a sleep before the first attempt.
func Request(sender Sender, method, url string, opts RequestOptions) (*Response, error) {
sleeper := opts.Sleep
if sleeper == nil {
sleeper = DefaultSleep
}
isPost := strings.EqualFold(method, "POST")
for attempt := 0; attempt <= opts.Retries; attempt++ {
var resp *Response
var err error
if isPost {
resp, err = sender.Post(url, opts.Headers, opts.JSON, opts.Timeout)
} else {
resp, err = sender.Get(url, opts.Headers, opts.Timeout)
}
if err != nil {
var terr *ForgeTransportError
if errors.As(err, &terr) && terr.ConnectPhase() {
if attempt < opts.Retries {
sleeper(opts.Backoff * (1 << attempt))
continue
}
return nil, err
}
// Non-connect transport error (read/write): only safe to retry
// when the call is idempotent.
if !isPost && attempt < opts.Retries {
sleeper(opts.Backoff * (1 << attempt))
continue
}
return nil, err
}
if resp.StatusCode >= 500 && !isPost && attempt < opts.Retries {
sleeper(opts.Backoff * (1 << attempt))
continue
}
if resp.StatusCode >= 400 {
return nil, &HTTPStatusError{StatusCode: resp.StatusCode}
}
return resp, nil
}
// Unreachable: every attempt either returns a successful response or
// raises (mirror of the Python AssertionError guard).
return nil, fmt.Errorf("forge_client: unreachable: Request always returns or raises")
}
// ── ForgeClient protocol ────────────────────────────────────────────────────
// ForgeClient mirrors the Python runtime_checkable Protocol: create issues,
// comment, check state, list, search.
type ForgeClient interface {
CreateIssue(title, body string) (any, error)
AddComment(issueNumber int, body string) (any, error)
GetIssueState(issueNumber int) (any, error)
ListOpenIssues() ([]any, error)
SearchIssues(query string) ([]any, error)
}
// ── NullAdapter ─────────────────────────────────────────────────────────────
// NullAdapter — no-op adapter for unconfigured/standalone use. Zero errors.
type NullAdapter struct{}
func (NullAdapter) CreateIssue(title, body string) (any, error) {
return map[string]any{"created": false, "reason": "no forge configured"}, nil
}
func (NullAdapter) AddComment(issueNumber int, body string) (any, error) {
return map[string]any{"created": false, "reason": "no forge configured"}, nil
}
func (NullAdapter) GetIssueState(issueNumber int) (any, error) {
return map[string]any{"state": "unknown"}, nil
}
func (NullAdapter) ListOpenIssues() ([]any, error) { return []any{}, nil }
func (NullAdapter) SearchIssues(query string) ([]any, error) { return []any{}, nil }
// ── GiteaForgejoAdapter ─────────────────────────────────────────────────────
// GiteaForgejoAdapter — /api/v1 paths (identical on both platforms).
type GiteaForgejoAdapter struct {
BaseURL string
Token string
Sender Sender
Sleep func(time.Duration)
}
func NewGiteaForgejoAdapter(url, token, owner, repo string) *GiteaForgejoAdapter {
return &GiteaForgejoAdapter{
BaseURL: strings.TrimRight(url, "/") + "/api/v1/repos/" + owner + "/" + repo,
Token: token,
Sender: DefaultSender,
}
}
func (a *GiteaForgejoAdapter) headers() map[string]string {
return map[string]string{
"Authorization": "token " + a.Token,
"Content-Type": "application/json",
}
}
func (a *GiteaForgejoAdapter) opts() RequestOptions {
o := DefaultRequestOptions()
o.Headers = a.headers()
o.Sleep = a.Sleep
return o
}
func (a *GiteaForgejoAdapter) CreateIssue(title, body string) (any, error) {
o := a.opts()
o.JSON = map[string]any{"title": title, "body": body}
r, err := Request(a.Sender, "POST", a.BaseURL+"/issues", o)
if err != nil {
return nil, err
}
return r.JSON()
}
func (a *GiteaForgejoAdapter) AddComment(issueNumber int, body string) (any, error) {
o := a.opts()
o.JSON = map[string]any{"body": body}
r, err := Request(a.Sender, "POST",
a.BaseURL+"/issues/"+strconv.Itoa(issueNumber)+"/comments", o)
if err != nil {
return nil, err
}
return r.JSON()
}
func (a *GiteaForgejoAdapter) GetIssueState(issueNumber int) (any, error) {
r, err := Request(a.Sender, "GET",
a.BaseURL+"/issues/"+strconv.Itoa(issueNumber), a.opts())
if err != nil {
return nil, err
}
return r.JSON()
}
func (a *GiteaForgejoAdapter) ListOpenIssues() ([]any, error) {
r, err := Request(a.Sender, "GET", a.BaseURL+"/issues?state=open&limit=50", a.opts())
if err != nil {
return nil, err
}
v, err := r.JSON()
if err != nil {
return nil, err
}
batch, _ := v.([]any)
if batch == nil {
batch = []any{}
}
return batch, nil
}
// SearchIssues — repo-scoped marker search over the paginated list endpoint.
//
// #122 gate fix: Gitea/Forgejo has NO repo-scoped /issues/search path
// (verified against live swagger) — that URL 404s on a real forge. The
// repo-scoped LIST endpoint supports state, type and q server-side and is
// paginated: q matches titles (and keywords), so the full marker is sent
// as q and a client-side contains-check keeps exactly the issues whose
// title OR body contains the marker.
//
// Phase B: pagination — the module's one serial IO seam — is pipelined
// (see phaseb.go); the request sequence is identical to the serial path.
func (a *GiteaForgejoAdapter) SearchIssues(query string) ([]any, error) {
return a.SearchIssuesPhaseB(query)
}
// searchQuery — Python urlencode preserves insertion order; so does this.
func searchQuery(q string, page int) string {
return "state=open&type=issues&q=" + url.QueryEscape(q) +
"&limit=50&page=" + strconv.Itoa(page)
}
// filterMarker — `query in str(i.get("title","")) + str(i.get("body",""))`
// with Python str() semantics (None → "None").
func filterMarker(results []any, query string) []any {
out := []any{}
for _, item := range results {
m, _ := item.(map[string]any)
hay := pyDictStr(m, "title") + pyDictStr(m, "body")
if strings.Contains(hay, query) {
out = append(out, item)
}
}
return out
}
func pyDictStr(m map[string]any, key string) string {
if v, ok := m[key]; ok {
return pyStr(v)
}
return ""
}
func pyStr(v any) string {
switch t := v.(type) {
case nil:
return "None"
case string:
return t
case bool:
if t {
return "True"
}
return "False"
case json.Number:
return t.String()
default:
return fmt.Sprint(t)
}
}
// ── create_forge_client ─────────────────────────────────────────────────────
// LookupEnv — injectable environment seam (os.environ reads).
var LookupEnv = os.LookupEnv
func envOr(name, def string) string {
if v, ok := LookupEnv(name); ok {
return v
}
return def
}
func cfgStr(cfg map[string]any, key string) string {
if v, ok := cfg[key]; ok {
if s, ok := v.(string); ok {
return s
}
}
return ""
}
// CreateForgeClient — create a forge client from config or environment.
//
// Config keys: forge.url, forge.token, forge.owner, forge.repo.
// Falls back to FORGE_URL/FORGE_TOKEN/FORGE_OWNER/FORGE_REPO env vars.
// Unconfigured → NullAdapter (graceful dormant).
func CreateForgeClient(cfg map[string]any) ForgeClient {
if cfg == nil {
cfg = map[string]any{}
}
forgeURL := cfgStr(cfg, "forge.url")
if forgeURL == "" {
forgeURL = envOr("FORGE_URL", "")
}
token := cfgStr(cfg, "forge.token")
if token == "" {
token = envOr("FORGE_TOKEN", "")
}
if forgeURL == "" || token == "" {
return NullAdapter{}
}
owner := cfgStr(cfg, "forge.owner")
if owner == "" {
owner = envOr("FORGE_OWNER", "owner")
}
repo := cfgStr(cfg, "forge.repo")
if repo == "" {
repo = envOr("FORGE_REPO", "repo")
}
return NewGiteaForgejoAdapter(forgeURL, token, owner, repo)
}
+3
View File
@@ -0,0 +1,3 @@
module rune/forge_client
go 1.22
+26
View File
@@ -0,0 +1,26 @@
// Parity gate — replays the frozen oracle truth (truth.json) through the
// Go port and exits non-zero on any divergence. CI-invocable without a
// test harness.
package main
import (
"fmt"
"os"
)
func main() {
fails := RunTruth("truth.json")
if len(fails) > 0 {
for _, f := range fails {
fmt.Fprintln(os.Stderr, "DIVERGED", f)
}
fmt.Fprintf(os.Stderr, "forge_client: %d divergence(s) vs frozen oracle truth\n", len(fails))
os.Exit(1)
}
doc, err := loadTruth("truth.json")
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
fmt.Printf("forge_client: parity replay OK — %d frozen rows exact vs the Python oracle\n", len(doc.Rows))
}
+70
View File
@@ -0,0 +1,70 @@
package main
// Phase B — language-advantage pass. The parity port kept SearchIssues
// serial (page fetched → appended → next page fetched). Pagination is the
// module's one serial IO seam, so Phase B pipelines it: a producer
// goroutine fetches pages strictly in order (stopping after a short page,
// exactly like the Python oracle) while the consumer filters markers from
// already-received pages. The request sequence is byte-identical to the
// serial path — a page N+1 is only requested after page N arrived and was
// full — so parity is preserved while filtering CPU overlaps network wait.
// searchPages streams pages from the paginated list endpoint. The channel
// is unbuffered: the producer blocks until the consumer has received page
// N before fetching page N+1, so no speculative request is ever made that
// the serial oracle would not have made.
func (a *GiteaForgejoAdapter) searchPages(query string, pages chan<- pageResult) {
defer close(pages)
for pageNum := 1; ; pageNum++ {
r, err := Request(a.Sender, "GET",
a.BaseURL+"/issues?"+searchQuery(query, pageNum), a.opts())
if err != nil {
pages <- pageResult{err: err}
return
}
v, err := r.JSON()
if err != nil {
pages <- pageResult{err: err}
return
}
batch, _ := v.([]any)
if batch == nil {
batch = []any{}
}
pages <- pageResult{items: batch}
if len(batch) < SearchPageSize {
return
}
}
}
type pageResult struct {
items []any
err error
}
// SearchIssuesPhaseB — the pipelined variant. Same result, same request
// sequence, same error semantics (the first transport error propagates and
// discards partial results, as in the oracle). Filtering is per-item, so
// filtering each page as it arrives is identical to the oracle's
// end-of-loop filter, only overlapped with the next page's network wait.
func (a *GiteaForgejoAdapter) SearchIssuesPhaseB(query string) ([]any, error) {
pages := make(chan pageResult)
go a.searchPages(query, pages)
out := []any{}
for p := range pages {
if p.err != nil {
return nil, p.err
}
out = append(out, filterMarker(p.items, query)...)
}
return out, nil
}
// Compile-time safety: both adapters satisfy the protocol, the default
// transport satisfies the seam.
var (
_ ForgeClient = (*GiteaForgejoAdapter)(nil)
_ ForgeClient = NullAdapter{}
_ Sender = (*HTTPXSender)(nil)
)
+186
View File
@@ -0,0 +1,186 @@
package main
// Phase B battery — the goroutine pipeline at the pagination seam must be
// indistinguishable from the serial oracle path: same results, same request
// sequence, no speculative fetches, same error semantics. Race-clean under
// -race.
import (
"fmt"
"strings"
"testing"
"time"
)
func newPageSender(pages [][]any) *scriptedSender {
entries := make([]scriptEntry, 0, len(pages))
for _, p := range pages {
entries = append(entries, scriptEntry{Status: 200, Payload: p})
}
return &scriptedSender{entries: entries, requests: &[]any{}}
}
func markerItem(n int, marker bool) map[string]any {
title := fmt.Sprintf("filler issue %d", n)
if marker {
title = fmt.Sprintf("[auto-failure:cron:Timeout] %d", n)
}
return map[string]any{"number": n, "title": title, "body": ""}
}
// TestPhaseBPipelineParity — a 3-page search through the pipelined path
// yields exactly the oracle's result: markers kept in order, noise dropped.
func TestPhaseBPipelineParity(t *testing.T) {
pages := [][]any{
fullPageWith(2),
fullPageWith(4),
{markerItem(5, true)},
}
sender := newPageSender(pages)
a := NewGiteaForgejoAdapter("https://forge.example.com", "tok", "o", "r")
a.Sender = sender
got, err := a.SearchIssues("[auto-failure:cron:Timeout]")
if err != nil {
t.Fatalf("pipelined search failed: %v", err)
}
var nums []int
for _, item := range got {
m, _ := item.(map[string]any)
nums = append(nums, numInt(m["number"]))
}
if len(nums) != 3 || nums[0] != 2 || nums[1] != 4 || nums[2] != 5 {
t.Fatalf("pipelined filter diverged: %v", nums)
}
if len(*sender.requests) != 3 {
t.Fatalf("expected 3 page requests, got %d", len(*sender.requests))
}
for i, req := range *sender.requests {
url := fmt.Sprint(req.(map[string]any)["url"])
if !strings.Contains(url, fmt.Sprintf("page=%d", i+1)) {
t.Fatalf("page %d requested out of order: %s", i+1, url)
}
}
}
// TestPhaseBNoSpeculativeRequests — a short first page must terminate the
// producer with exactly one request; the goroutine must not prefetch a
// page the serial oracle would never have asked for.
func TestPhaseBNoSpeculativeRequests(t *testing.T) {
sender := newPageSender([][]any{{markerItem(1, true)}})
a := NewGiteaForgejoAdapter("https://forge.example.com", "tok", "o", "r")
a.Sender = sender
got, err := a.SearchIssues("[auto-failure:cron:Timeout]")
if err != nil {
t.Fatalf("search failed: %v", err)
}
if len(got) != 1 {
t.Fatalf("expected the single marker hit, got %d items", len(got))
}
if len(*sender.requests) != 1 {
t.Fatalf("speculative fetch detected: %d requests for a short single page", len(*sender.requests))
}
}
// TestPhaseBErrorPropagation — a transport error on page 2 surfaces with
// the frozen httpx-kind vocabulary and discards partial results, exactly
// like the serial path. The connect error is retryable NOISE per _request
// (retries=2 → 3 attempts), so the script must carry all three before the
// error finally propagates.
func TestPhaseBErrorPropagation(t *testing.T) {
sender := &scriptedSender{
entries: []scriptEntry{
{Status: 200, Payload: fullPage()},
{Exc: "connect"},
{Exc: "connect"},
{Exc: "connect"},
},
requests: &[]any{},
}
a := NewGiteaForgejoAdapter("https://forge.example.com", "tok", "o", "r")
a.Sender = sender
_, err := a.SearchIssues("[auto-failure:cron:Timeout]")
if err == nil {
t.Fatal("page-2 connect error must propagate")
}
if errorKind(err) != "httpx.ConnectError" {
t.Fatalf("frozen error vocabulary changed: %v", errorKind(err))
}
if len(*sender.requests) != 4 {
t.Fatalf("expected 4 requests (page 1 + 3 page-2 attempts), got %d", len(*sender.requests))
}
}
// TestPhaseBPipelineOverlapsFilter — the pipeline is not decorative: while
// the consumer filters page N, the producer is already fetching page N+1.
// Proven by a sender that blocks in-flight until the consumer has begun
// filtering the previous page (unbuffered channel rendezvous).
func TestPhaseBPipelineOverlapsFilter(t *testing.T) {
pageFetchStarted := make(chan int, 4)
sender := &countingSender{scriptedSender: scriptedSender{
entries: []scriptEntry{
{Status: 200, Payload: fullPage()},
{Status: 200, Payload: []any{}},
},
requests: &[]any{},
}, started: pageFetchStarted}
a := NewGiteaForgejoAdapter("https://forge.example.com", "tok", "o", "r")
a.Sender = sender
done := make(chan struct{})
go func() {
defer close(done)
if _, err := a.SearchIssues("x"); err != nil {
t.Errorf("search failed: %v", err)
}
}()
// page 1 fetch starts immediately
<-pageFetchStarted
// page 2 fetch must start while page 1 is still being consumed —
// wait for it with a generous timeout instead of deadlocking.
select {
case <-pageFetchStarted:
case <-time.After(5 * time.Second):
t.Fatal("producer did not overlap: page 2 not fetched while page 1 was being consumed")
}
<-done
}
func fullPage() []any {
p := make([]any, SearchPageSize)
for i := range p {
p[i] = map[string]any{"number": i, "title": "x", "body": ""}
}
return p
}
// fullPageWith — a full 50-item page whose item #markerAt carries the
// search marker (forcing pagination exactly like the live forge).
func fullPageWith(markerAt int) []any {
p := fullPage()
p[markerAt] = markerItem(markerAt, true)
return p
}
// countingSender counts started requests so the test can observe the
// producer/consumer overlap.
type countingSender struct {
scriptedSender
started chan int
n int
}
func (s *countingSender) record(method, url string, headers map[string]string, jsonBody any, timeout time.Duration) *scriptEntry {
s.n++
select {
case s.started <- s.n:
default:
}
return s.scriptedSender.record(method, url, headers, jsonBody, timeout)
}
func (s *countingSender) Post(url string, headers map[string]string, jsonBody any, timeout time.Duration) (*Response, error) {
return s.respond(s.record("POST", url, headers, jsonBody, timeout))
}
func (s *countingSender) Get(url string, headers map[string]string, timeout time.Duration) (*Response, error) {
return s.respond(s.record("GET", url, headers, nil, timeout))
}
+534
View File
@@ -0,0 +1,534 @@
// Replay — the frozen-truth parity engine. Reads truth.json (generated by
// scripts/gen_forge_client_truth.py against the live Python oracle, httpx
// transport scripted, sleep recorded) and re-executes every row through
// this Go port, comparing canonically (sorted-key JSON).
package main
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"os"
"sort"
"strings"
"time"
)
// TruthDoc — the frozen truth file structure.
type TruthDoc struct {
FrozenTS string `json:"frozen_ts"`
Oracle string `json:"oracle"`
Rows []TruthRow `json:"rows"`
}
// TruthRow — one frozen case.
type TruthRow struct {
Name string `json:"name"`
Op string `json:"op"`
Input json.RawMessage `json:"input"`
Want json.RawMessage `json:"want"`
}
// RunTruth replays every row and returns a divergence report (empty on 100%).
func RunTruth(path string) []string {
doc, err := loadTruth(path)
if err != nil {
return []string{fmt.Sprintf("load truth: %v", err)}
}
var fails []string
for i := range doc.Rows {
if msg := compareOne(doc, &doc.Rows[i]); msg != "" {
fails = append(fails, doc.Rows[i].Name+": "+msg)
}
}
return fails
}
func loadTruth(path string) (*TruthDoc, error) {
raw, err := os.ReadFile(path)
if err != nil {
return nil, err
}
var doc TruthDoc
if err := json.Unmarshal(raw, &doc); err != nil {
return nil, err
}
if len(doc.Rows) == 0 {
return nil, fmt.Errorf("truth.json contained no rows")
}
return &doc, nil
}
// compareOne replays one row and returns "" on parity, else a divergence
// description. Shared by RunTruth and the per-function battery tests.
func compareOne(doc *TruthDoc, row *TruthRow) string {
got, err := execRow(row)
if err != nil {
return fmt.Sprintf("error: %v", err)
}
wantStr, err := canonicalFromRaw(row.Want)
if err != nil {
return fmt.Sprintf("canonicalize want: %v", err)
}
gotStr, err := canonicalFromRaw([]byte(mustJSON(got)))
if err != nil {
return fmt.Sprintf("canonicalize got: %v", err)
}
if wantStr != gotStr {
return fmt.Sprintf("\n want: %s\n got: %s", wantStr, gotStr)
}
return ""
}
// ── scripted transport (mirrors the generator's fake httpx) ────────────────
type scriptEntry struct {
Status int
Payload any
Exc string
}
type scriptedSender struct {
entries []scriptEntry
requests *[]any
}
func excKind(exc string) string {
switch exc {
case "connect":
return "httpx.ConnectError"
case "connect_timeout":
return "httpx.ConnectTimeout"
case "read_timeout":
return "httpx.ReadTimeout"
default:
return "httpx.TransportError"
}
}
func (s *scriptedSender) record(method, url string, headers map[string]string, jsonBody any, timeout time.Duration) *scriptEntry {
req := map[string]any{
"method": method,
"url": url,
"headers": headers,
"json": jsonBody,
"timeout": timeout.Seconds(),
}
*s.requests = append(*s.requests, req)
if len(s.entries) == 0 {
panic(fmt.Sprintf("script exhausted at request %d (%s %s)", len(*s.requests), method, url))
}
entry := s.entries[0]
s.entries = s.entries[1:]
return &entry
}
func (s *scriptedSender) respond(entry *scriptEntry) (*Response, error) {
if entry.Exc != "" {
return nil, &ForgeTransportError{Kind: excKind(entry.Exc), Msg: "scripted"}
}
status := entry.Status
if status == 0 {
status = 200
}
body, err := json.Marshal(entry.Payload)
if err != nil {
return nil, err
}
if entry.Payload == nil {
body = []byte("{}")
}
return &Response{StatusCode: status, Body: body}, nil
}
func (s *scriptedSender) Post(url string, headers map[string]string, jsonBody any, timeout time.Duration) (*Response, error) {
return s.respond(s.record("POST", url, headers, jsonBody, timeout))
}
func (s *scriptedSender) Get(url string, headers map[string]string, timeout time.Duration) (*Response, error) {
return s.respond(s.record("GET", url, headers, nil, timeout))
}
// ── row execution ───────────────────────────────────────────────────────────
func decodeInput(raw json.RawMessage) (map[string]any, error) {
if len(raw) == 0 {
return map[string]any{}, nil
}
dec := json.NewDecoder(bytes.NewReader(raw))
dec.UseNumber()
var v any
if err := dec.Decode(&v); err != nil {
return nil, err
}
m, ok := v.(map[string]any)
if !ok {
return nil, fmt.Errorf("input not an object")
}
return m, nil
}
func numInt(v any) int {
switch t := v.(type) {
case json.Number:
n, err := t.Int64()
if err != nil {
panic(fmt.Sprintf("not an integer: %v", t))
}
return int(n)
case float64:
return int(t)
default:
panic(fmt.Sprintf("not a number: %v", v))
}
}
func numFloat(v any) float64 {
switch t := v.(type) {
case json.Number:
f, _ := t.Float64()
return f
case float64:
return t
default:
panic(fmt.Sprintf("not a number: %v", v))
}
}
// wantRecorder builds the {result, requests, sleeps, error} got-shape the
// frozen rows carry.
type recorder struct {
requests []any
sleeps []any
errKind string
}
func (r *recorder) sleep(d time.Duration) { r.sleeps = append(r.sleeps, d.Seconds()) }
func (r *recorder) got(result any) map[string]any {
return map[string]any{
"result": result,
"requests": r.requests,
"sleeps": r.sleeps,
"error": errKindOrNil(r.errKind),
}
}
func errKindOrNil(kind string) any {
if kind == "" {
return nil
}
return kind
}
func errorKind(err error) string {
var terr *ForgeTransportError
if errors.As(err, &terr) {
return terr.Kind
}
var serr *HTTPStatusError
if errors.As(err, &serr) {
return "httpx.HTTPStatusError"
}
return fmt.Sprintf("%T", err)
}
func execRow(row *TruthRow) (any, error) {
in, err := decodeInput(row.Input)
if err != nil {
return nil, fmt.Errorf("decode input: %w", err)
}
switch row.Op {
case "select":
return execSelect(in)
case "null":
return execNull(in)
case "call":
return execCall(in)
case "request":
return execRequest(in)
}
return nil, fmt.Errorf("unknown row op %s (%s)", row.Op, row.Name)
}
func execSelect(in map[string]any) (any, error) {
var cfg map[string]any
if v, ok := in["config"]; ok && v != nil {
m, ok := v.(map[string]any)
if !ok {
return nil, fmt.Errorf("select: config not an object")
}
cfg = m
}
env := map[string]string{}
if v, ok := in["env"].(map[string]any); ok {
for k, val := range v {
s, _ := val.(string)
env[k] = s
}
}
saved := LookupEnv
defer func() { LookupEnv = saved }()
LookupEnv = func(name string) (string, bool) {
v, ok := env[name]
return v, ok
}
client := CreateForgeClient(cfg)
if _, isNull := client.(NullAdapter); isNull {
return map[string]any{"adapter": "NullAdapter", "base_url": nil, "token": nil}, nil
}
a := client.(*GiteaForgejoAdapter)
return map[string]any{"adapter": "GiteaForgejoAdapter", "base_url": a.BaseURL, "token": a.Token}, nil
}
func execNull(in map[string]any) (any, error) {
method, _ := in["method"].(string)
args, _ := in["args"].([]any)
client := NullAdapter{}
switch method {
case "create_issue":
return client.CreateIssue(argStr(args, 0), argStr(args, 1))
case "add_comment":
return client.AddComment(numInt(args[0]), argStr(args, 1))
case "get_issue_state":
return client.GetIssueState(numInt(args[0]))
case "list_open_issues":
return client.ListOpenIssues()
case "search_issues":
return client.SearchIssues(argStr(args, 0))
}
return nil, fmt.Errorf("null: unknown method %s", method)
}
func argStr(args []any, i int) string {
if i < len(args) {
s, _ := args[i].(string)
return s
}
return ""
}
func newAdapterFrom(in map[string]any, rec *recorder) (*GiteaForgejoAdapter, *scriptedSender, error) {
method, _ := in["method"].(string)
url, _ := in["url"].(string)
token, _ := in["token"].(string)
owner, _ := in["owner"].(string)
repo, _ := in["repo"].(string)
entries, err := decodeScript(in["script"])
if err != nil {
return nil, nil, err
}
sender := &scriptedSender{entries: entries, requests: &rec.requests}
a := NewGiteaForgejoAdapter(url, token, owner, repo)
a.Sender = sender
a.Sleep = rec.sleep
_ = method
return a, sender, nil
}
func decodeScript(v any) ([]scriptEntry, error) {
list, ok := v.([]any)
if !ok {
return nil, fmt.Errorf("script not a list")
}
out := make([]scriptEntry, 0, len(list))
for _, item := range list {
m, ok := item.(map[string]any)
if !ok {
return nil, fmt.Errorf("script entry not an object")
}
e := scriptEntry{}
if exc, ok := m["exc"].(string); ok {
e.Exc = exc
}
if status, ok := m["status"]; ok {
e.Status = numInt(status)
}
if payload, ok := m["payload"]; ok {
e.Payload = payload
}
out = append(out, e)
}
return out, nil
}
func execCall(in map[string]any) (any, error) {
method, _ := in["method"].(string)
rec := &recorder{}
a, _, err := newAdapterFrom(in, rec)
if err != nil {
return nil, err
}
kwargs, _ := in["kwargs"].(map[string]any)
var result any
switch method {
case "create_issue":
result, err = a.CreateIssue(kwStr(kwargs, "title"), kwStr(kwargs, "body"))
case "add_comment":
result, err = a.AddComment(numInt(kwargs["issue_number"]), kwStr(kwargs, "body"))
case "get_issue_state":
result, err = a.GetIssueState(numInt(kwargs["issue_number"]))
case "list_open_issues":
result, err = a.ListOpenIssues()
case "search_issues":
result, err = a.SearchIssues(kwStr(kwargs, "query"))
default:
return nil, fmt.Errorf("call: unknown method %s", method)
}
if err != nil {
rec.errKind = errorKind(err)
result = nil
}
return rec.got(result), nil
}
func kwStr(m map[string]any, key string) string {
if v, ok := m[key]; ok {
s, _ := v.(string)
return s
}
return ""
}
func execRequest(in map[string]any) (any, error) {
rec := &recorder{}
method, _ := in["method"].(string)
rawURL, _ := in["url"].(string)
entries, err := decodeScript(in["script"])
if err != nil {
return nil, err
}
sender := &scriptedSender{entries: entries, requests: &rec.requests}
opts := RequestOptions{
Headers: toStringMap(in["headers"]),
JSON: in["json"],
Timeout: secondsDuration(in["timeout"]),
Retries: numInt(in["retries"]),
Backoff: secondsDuration(in["backoff"]),
Sleep: rec.sleep,
}
resp, err := Request(sender, method, rawURL, opts)
var result any
if err != nil {
rec.errKind = errorKind(err)
} else {
result, err = resp.JSON()
if err != nil {
return nil, err
}
}
return rec.got(result), nil
}
func toStringMap(v any) map[string]string {
m, ok := v.(map[string]any)
if !ok {
return nil
}
out := make(map[string]string, len(m))
for k, val := range m {
s, _ := val.(string)
out[k] = s
}
return out
}
func secondsDuration(v any) time.Duration {
if v == nil {
return 0
}
return time.Duration(numFloat(v) * float64(time.Second))
}
// ── canonical comparison (sorted-key JSON) ──────────────────────────────────
func canonicalFromRaw(raw []byte) (string, error) {
var v any
dec := json.NewDecoder(bytes.NewReader(raw))
dec.UseNumber()
if err := dec.Decode(&v); err != nil {
return "", err
}
return canonical(normalize(v)), nil
}
// canonical — sorted-key deterministic rendering of a decoded JSON value.
func canonical(v any) string {
switch t := v.(type) {
case nil:
return "null"
case bool:
if t {
return "true"
}
return "false"
case string:
b, _ := json.Marshal(t)
return string(b)
case float64:
if t == float64(int64(t)) {
return fmt.Sprintf("%d", int64(t))
}
return fmt.Sprintf("%v", t)
case json.Number:
f, _ := t.Float64()
return canonical(f)
case []any:
parts := make([]string, len(t))
for i, item := range t {
parts[i] = canonical(normalize(item))
}
return "[" + strings.Join(parts, ", ") + "]"
case map[string]any:
keys := make([]string, 0, len(t))
for k := range t {
keys = append(keys, k)
}
sort.Strings(keys)
parts := make([]string, 0, len(keys))
for _, k := range keys {
bk, _ := json.Marshal(k)
parts = append(parts, string(bk)+": "+canonical(normalize(t[k])))
}
return "{" + strings.Join(parts, ", ") + "}"
default:
return fmt.Sprintf("«%v»", t)
}
}
// normalize — flatten trees into plain map/float64 representations so
// canonical() sees one shape.
func normalize(v any) any {
switch t := v.(type) {
case map[string]any:
m := map[string]any{}
for k, val := range t {
m[k] = normalize(val)
}
return m
case []any:
out := make([]any, len(t))
for i, item := range t {
out[i] = normalize(item)
}
return out
case json.Number:
f, _ := t.Float64()
return f
default:
return v
}
}
func mustJSON(v any) string {
b, err := json.Marshal(normalize(v))
if err != nil {
return "null"
}
return string(b)
}
File diff suppressed because it is too large. Load diff
+285
View File
@@ -0,0 +1,285 @@
package main
// Battery — the HARD parity gate plus per-function batteries replaying the
// frozen oracle truth (truth.json, generated by
// scripts/gen_forge_client_truth.py against the LIVE Python oracle with a
// scripted httpx transport and a recording sleep) VERBATIM. Any divergence
// fails; the truth file rides the repo so the gate holds in CI without a
// live forge.
import (
"encoding/json"
"fmt"
"os"
"strings"
"testing"
"time"
)
func loadTruthOrDie(t *testing.T) *TruthDoc {
t.Helper()
doc, err := loadTruth("truth.json")
if err != nil {
t.Fatalf("truth.json unusable (frozen oracle battery): %v", err)
}
return doc
}
func findRow(t *testing.T, name string) TruthRow {
t.Helper()
doc := loadTruthOrDie(t)
for _, row := range doc.Rows {
if row.Name == name {
return row
}
}
t.Fatalf("frozen row %q missing from truth.json — battery/truth drift", name)
return TruthRow{}
}
func mustReplay(t *testing.T, name string) {
t.Helper()
doc := loadTruthOrDie(t)
row := findRow(t, name)
if msg := compareOne(doc, &row); msg != "" {
t.Fatalf("DIVERGED %s: %s", name, msg)
}
}
func replayOp(t *testing.T, op string) int {
t.Helper()
doc := loadTruthOrDie(t)
n := 0
for i := range doc.Rows {
row := &doc.Rows[i]
if row.Op != op {
continue
}
n++
if msg := compareOne(doc, row); msg != "" {
t.Fatalf("DIVERGED %s: %s", row.Name, msg)
}
}
if n == 0 {
t.Fatalf("no %s rows in truth.json", op)
}
return n
}
// rowWant decodes a row's want JSON into a generic map.
func rowWant(t *testing.T, name string) map[string]any {
t.Helper()
row := findRow(t, name)
var want map[string]any
if err := json.Unmarshal(row.Want, &want); err != nil {
t.Fatalf("decode want of %s: %v", name, err)
}
return want
}
// TestTruthParity — the whole frozen battery, one shot.
func TestTruthParity(t *testing.T) {
doc := loadTruthOrDie(t)
var fails []string
for i := range doc.Rows {
if msg := compareOne(doc, &doc.Rows[i]); msg != "" {
fails = append(fails, doc.Rows[i].Name+": "+msg)
}
}
if len(fails) > 0 {
t.Fatalf("parity replay diverged after %d clean rows:\n%s",
len(doc.Rows)-len(fails), strings.Join(fails, "\n"))
}
t.Logf("PARITY OK: %d cases exact-equal vs the Python oracle", len(doc.Rows))
}
// TestBatteryCreateForgeClient — every selection branch: none, env, config
// keys, config-beats-env, owner/repo defaults and env overrides, trailing
// slash strip, empty-string falsy fallthrough, missing halves.
func TestBatteryCreateForgeClient(t *testing.T) {
if n := replayOp(t, "select"); n != 11 {
t.Fatalf("expected 11 frozen select rows, got %d — battery/truth drift", n)
}
want := rowWant(t, "select:none")
if want["adapter"] != "NullAdapter" {
t.Fatalf("unconfigured must be NullAdapter, got %v", want["adapter"])
}
want = rowWant(t, "select:env-configured")
if want["base_url"] != "https://forge.example.com/api/v1/repos/owner/repo" {
t.Fatalf("frozen env base_url changed: %v", want["base_url"])
}
want = rowWant(t, "select:config-beats-env")
if want["base_url"] != "https://cfg.example.com/api/v1/repos/owner/repo" ||
want["token"] != "cfg_tok" {
t.Fatalf("config-beats-env changed: %v", want)
}
want = rowWant(t, "select:trailing-slash-strip")
if want["base_url"] != "https://forge.example.com/api/v1/repos/owner/repo" {
t.Fatalf("trailing slash strip changed: %v", want["base_url"])
}
want = rowWant(t, "select:cfg-url-missing-token")
if want["adapter"] != "NullAdapter" {
t.Fatalf("url without token must stay dormant: %v", want["adapter"])
}
mustReplay(t, "select:owner-empty-env")
}
// TestBatteryNullAdapter — every no-op shape, zero errors.
func TestBatteryNullAdapter(t *testing.T) {
if n := replayOp(t, "null"); n != 5 {
t.Fatalf("expected 5 frozen null rows, got %d — battery/truth drift", n)
}
want := rowWant(t, "null:create-issue")
if want["created"] != false || want["reason"] != "no forge configured" {
t.Fatalf("frozen null create changed: %v", want)
}
want = rowWant(t, "null:get-issue-state")
if want["state"] != "unknown" {
t.Fatalf("frozen null state changed: %v", want)
}
}
// TestBatteryGiteaAdapter — every adapter method incl. pagination, the
// client-side marker filter, and retry propagation through _request.
func TestBatteryGiteaAdapter(t *testing.T) {
if n := replayOp(t, "call"); n != 14 {
t.Fatalf("expected 14 frozen call rows, got %d — battery/truth drift", n)
}
// create_issue posts to /issues with token auth and the JSON body.
want := rowWant(t, "call:create-issue")
reqs, _ := want["requests"].([]any)
if len(reqs) != 1 {
t.Fatalf("create-issue: expected 1 request, got %d", len(reqs))
}
req, _ := reqs[0].(map[string]any)
if !strings.HasSuffix(fmt.Sprint(req["url"]), "/api/v1/repos/o/r/issues") {
t.Fatalf("create-issue URL changed: %v", req["url"])
}
headers, _ := req["headers"].(map[string]any)
if headers["Authorization"] != "token tok" || headers["Content-Type"] != "application/json" {
t.Fatalf("frozen auth headers changed: %v", headers)
}
if fmt.Sprint(req["timeout"]) != "30" {
t.Fatalf("frozen timeout passthrough changed: %v", req["timeout"])
}
// search hits the repo-scoped LIST endpoint, never /issues/search.
want = rowWant(t, "call:search-single-page")
reqs, _ = want["requests"].([]any)
req, _ = reqs[0].(map[string]any)
searchURL := fmt.Sprint(req["url"])
if strings.Contains(searchURL, "/issues/search") {
t.Fatalf("regression: search used the 404ing /issues/search path")
}
if !strings.Contains(searchURL, "state=open") ||
!strings.Contains(searchURL, "type=issues") ||
!strings.Contains(searchURL, "q=%5Bauto-failure%3Acron%3AConnection%5D") {
t.Fatalf("frozen search query changed: %s", searchURL)
}
// pagination: a full 50-item page must be followed by page=2.
want = rowWant(t, "call:search-paginates")
reqs, _ = want["requests"].([]any)
if len(reqs) != 2 || !strings.Contains(fmt.Sprint(reqs[1].(map[string]any)["url"]), "page=2") {
t.Fatalf("frozen pagination changed: %d requests", len(reqs))
}
// retry through _request: connect-phase POST retried once, sleep 0.5s.
want = rowWant(t, "call:create-issue-retry-connect")
sleeps, _ := want["sleeps"].([]any)
if len(sleeps) != 1 || fmt.Sprint(sleeps[0]) != "0.5" {
t.Fatalf("frozen connect retry sleeps changed: %v", sleeps)
}
// 4xx is the information: one shot, no retry.
want = rowWant(t, "call:get-issue-state-4xx")
if want["error"] != "httpx.HTTPStatusError" {
t.Fatalf("frozen 4xx error kind changed: %v", want["error"])
}
reqs, _ = want["requests"].([]any)
if len(reqs) != 1 {
t.Fatalf("4xx must not retry: %d requests", len(reqs))
}
}
// TestBatteryRequest — every _request branch: noise retried, signal never,
// exponential backoff never before the first attempt, timeout passthrough,
// lowercase method routing.
func TestBatteryRequest(t *testing.T) {
if n := replayOp(t, "request"); n != 19 {
t.Fatalf("expected 19 frozen request rows, got %d — battery/truth drift", n)
}
// backoff is exponential: [0.5, 1.0], never before the first attempt.
want := rowWant(t, "request:backoff-exponential")
sleeps, _ := want["sleeps"].([]any)
if len(sleeps) != 2 || fmt.Sprint(sleeps[0]) != "0.5" || fmt.Sprint(sleeps[1]) != "1" {
t.Fatalf("frozen backoff schedule changed: %v", sleeps)
}
// connect exhaustion: retries+1 attempts, error propagates.
want = rowWant(t, "request:get-connect-exhaust")
if want["error"] != "httpx.ConnectError" {
t.Fatalf("frozen exhaust error changed: %v", want["error"])
}
reqs, _ := want["requests"].([]any)
if len(reqs) != 3 {
t.Fatalf("frozen attempt count changed: %d", len(reqs))
}
// POST read timeout: the server MAY have processed it — one shot.
want = rowWant(t, "request:post-read-timeout-no-retry")
if want["error"] != "httpx.ReadTimeout" {
t.Fatalf("frozen read-timeout kind changed: %v", want["error"])
}
reqs, _ = want["requests"].([]any)
if len(reqs) != 1 {
t.Fatalf("POST read timeout must not retry: %d requests", len(reqs))
}
// POST 5xx after send: ambiguous — one shot.
want = rowWant(t, "request:post-5xx-no-retry")
if want["error"] != "httpx.HTTPStatusError" {
t.Fatalf("frozen post-5xx kind changed: %v", want["error"])
}
// timeout passes through per call.
want = rowWant(t, "request:timeout-passthrough")
reqs, _ = want["requests"].([]any)
if fmt.Sprint(reqs[0].(map[string]any)["timeout"]) != "17" {
t.Fatalf("frozen timeout passthrough changed: %v", reqs[0])
}
mustReplay(t, "request:lowercase-method")
mustReplay(t, "request:headers-json-capture")
mustReplay(t, "request:get-5xx-exhaust")
mustReplay(t, "request:get-read-timeout-retries")
}
// TestBatteryRetryAsymmetry — direct cross-check (not via a truth row) of
// the behavior that motivated the module: GET 5xx is noise (retried to
// success), POST 5xx is signal (one shot, no double-create).
func TestBatteryRetryAsymmetry(t *testing.T) {
getRequests := &[]any{}
getSender := &scriptedSender{
entries: []scriptEntry{{Status: 502}, {Status: 200, Payload: map[string]any{"ok": 1}}},
requests: getRequests,
}
opts := DefaultRequestOptions()
opts.Sleep = func(time.Duration) {}
if _, err := Request(getSender, "GET", "https://x/api", opts); err != nil {
t.Fatalf("GET 5xx must be retried to success: %v", err)
}
if len(*getRequests) != 2 {
t.Fatalf("GET 5xx made %d requests — retry lost", len(*getRequests))
}
postRequests := &[]any{}
postSender := &scriptedSender{
entries: []scriptEntry{{Status: 502}, {Status: 200}},
requests: postRequests,
}
postOpts := DefaultRequestOptions()
postOpts.JSON = map[string]any{}
postOpts.Sleep = func(time.Duration) {}
if _, err := Request(postSender, "POST", "https://x/api", postOpts); err == nil {
t.Fatal("POST 5xx must never be retried")
}
if len(*postRequests) != 1 {
t.Fatalf("POST 5xx made %d requests — double-create hazard", len(*postRequests))
}
}
func TestMain(m *testing.M) {
os.Exit(m.Run())
}
+3
View File
@@ -0,0 +1,3 @@
module rune/openai-compat-transport
go 1.22
+28
View File
@@ -0,0 +1,28 @@
// Package main — OpenAI-compat transport CLI: replay the frozen oracle
// truth battery.
package main
import (
"fmt"
"os"
)
func main() {
if len(os.Args) < 2 || os.Args[1] != "replay" {
fmt.Fprintln(os.Stderr, "usage: openai-compat-transport replay <truth.json>")
os.Exit(2)
}
path := "truth.json"
if len(os.Args) > 2 {
path = os.Args[2]
}
fails := RunTruth(path)
if len(fails) > 0 {
for _, f := range fails {
fmt.Println(f)
}
fmt.Fprintf(os.Stderr, "PARITY FAILED: %d divergence(s)\n", len(fails))
os.Exit(1)
}
fmt.Printf("PARITY OK: %d cases exact-equal vs the Python oracle\n", truthRowCount())
}
@@ -0,0 +1,586 @@
// Package main — OpenAI-compatible chat transport, ported 1:1 from
// src/rune/openai_compat_transport.py (dichotomy wave #2479).
//
// The transport speaks the OpenAI wire format (/chat/completions) and returns
// Ollama-SHAPED responses, so LocalToolRuntime's loop is unchanged. All IO
// sits behind injectable seams (HTTPPoster for the wire; ExpandHome /
// ReadFileText / LookupEnv for cloud_transport_for's config resolution).
package main
import (
"encoding/json"
"fmt"
"net/http"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
"sync"
"time"
)
// ValueError — the oracle raises Python's ValueError; error text is frozen
// truth and must match str(e) exactly.
type ValueError struct{ Msg string }
func (e *ValueError) Error() string { return e.Msg }
// HTTPPoster — the wire seam. Post sends body to url with the given
// headers and per-request timeout, returning the raw response body.
type HTTPPoster interface {
Post(url string, headers map[string]string, body []byte, timeoutSecs float64) ([]byte, error)
}
// HTTPPosterFunc adapts a function to HTTPPoster.
type HTTPPosterFunc func(url string, headers map[string]string, body []byte, timeoutSecs float64) ([]byte, error)
// Post implements HTTPPoster.
func (f HTTPPosterFunc) Post(url string, headers map[string]string, body []byte, timeoutSecs float64) ([]byte, error) {
return f(url, headers, body, timeoutSecs)
}
// defaultPoster — net/http faithful to the oracle's urllib round trip.
type defaultPoster struct{}
func (defaultPoster) Post(url string, headers map[string]string, body []byte, timeoutSecs float64) ([]byte, error) {
req, err := http.NewRequest(http.MethodPost, url, strings.NewReader(string(body)))
if err != nil {
return nil, err
}
for k, v := range headers {
req.Header.Set(k, v)
}
client := &http.Client{Timeout: time.Duration(timeoutSecs * float64(time.Second))}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
buf := new(strings.Builder)
tmp := make([]byte, 32*1024)
for {
n, rerr := resp.Body.Read(tmp)
if n > 0 {
buf.Write(tmp[:n])
}
if rerr != nil {
break
}
}
return []byte(buf.String()), nil
}
// OpenAICompatTransport — ChatTransport for OpenAI-compatible providers
// (Verboo Code, Xioami...).
type OpenAICompatTransport struct {
BaseURL string
APIKey string
Provider string
// Cloud round trips with a full tool schema routinely exceed the local
// tier timeout (30s): observed live, glm-5.3-flash read-timeout at
// 30.6s mid-iteration. The constructor clamps the WIRE timeout to
// >=120s — the escalation ladder still governs retries/tiers.
Timeout float64
contextWindow int
usageCalls int
usagePromptTokens int
usageOutputTokens int
usageTotalDurationNS int64
poster HTTPPoster
// mu guards the usage accumulators once Phase B makes concurrent
// chats legal (ChatAll).
mu sync.Mutex
}
// NewOpenAICompatTransport — oracle defaults: timeout 120, context window 131072.
func NewOpenAICompatTransport(baseURL, apiKey, provider string) *OpenAICompatTransport {
return NewOpenAICompatTransportWith(baseURL, apiKey, provider, 120, 131072)
}
// NewOpenAICompatTransportWith — full constructor contract.
func NewOpenAICompatTransportWith(baseURL, apiKey, provider string, timeout float64, contextWindow int) *OpenAICompatTransport {
if timeout < 120 {
timeout = 120
}
return &OpenAICompatTransport{
BaseURL: strings.TrimRight(baseURL, "/"),
APIKey: apiKey,
Provider: provider,
Timeout: timeout,
contextWindow: contextWindow,
poster: defaultPoster{},
}
}
// SetPoster swaps the wire seam (battery/replay injection).
func (t *OpenAICompatTransport) SetPoster(p HTTPPoster) { t.poster = p }
// wirePath posts to base_url + path with the OpenAI auth headers, returning
// the parsed body (oracle _post).
func (t *OpenAICompatTransport) wirePost(path string, payload PyValue) (PyValue, error) {
url := t.BaseURL + path
headers := map[string]string{
"Content-Type": "application/json",
"Authorization": fmt.Sprintf("Bearer %s", t.APIKey),
}
body := []byte(PyDumpsCompact(payload))
raw, err := t.poster.Post(url, headers, body, t.Timeout)
if err != nil {
return nil, err
}
decoded, err := PyParse(raw)
if err != nil {
return nil, err
}
if _, isObj := decoded.(*PyObj); !isObj {
return nil, &ValueError{Msg: "provider returned a non-object response"}
}
return decoded, nil
}
// toOpenaiMessages — Ollama-shape → OpenAI-shape. Pairing: the runtime's
// _tool_message sets tool_call_id from request.call_id (minted by
// normalize_ollama_tool_calls from the response's own id). The assistant
// message must use THE SAME ids for its tool_calls — a mismatch makes the
// provider treat the tool result as orphaned and the model re-issues the
// identical call forever (observed live: glm-5.3-flash looped the same
// edit_file twice, empty terminal text both turns).
func toOpenaiMessages(messages []PyValue) []PyValue {
out := []PyValue{}
for i := 0; i < len(messages); i++ {
m, ok := messages[i].(*PyObj)
if !ok {
m = NewPyObj()
}
role, _ := m.Get("role")
roleStr, _ := role.(string)
tcRaw, _ := m.Get("tool_calls")
var toolCalls []PyValue
if list, isList := tcRaw.([]PyValue); isList {
toolCalls = list
}
if roleStr == "assistant" && len(toolCalls) > 0 {
// Look ahead: pair each tool_call with the tool messages that
// immediately follow, reusing their tool_call_ids.
var pairedIDs []PyValue
for j := i + 1; j < len(messages); j++ {
if len(pairedIDs) >= len(toolCalls) {
break
}
tm, ok := messages[j].(*PyObj)
if !ok {
continue
}
tmRole, _ := tm.Get("role")
if s, isStr := tmRole.(string); !isStr || s != "tool" {
continue
}
id, _ := tm.Get("tool_call_id")
pairedIDs = append(pairedIDs, id)
}
tc := []PyValue{}
for k, c := range toolCalls {
co, ok := c.(*PyObj)
if !ok {
co = NewPyObj()
}
fnRaw, _ := co.Get("function")
fn, _ := fnRaw.(*PyObj)
if fn == nil {
fn = NewPyObj()
}
argsRaw, _ := fn.Get("arguments")
if argsRaw == nil {
if _, present := fn.Get("arguments"); !present {
argsRaw = NewPyObj() // fn.get("arguments", {}) default
}
}
var argsStr string
if _, isObj := argsRaw.(*PyObj); isObj {
argsStr = PyDumpsCompact(argsRaw)
} else {
argsStr = PyStr(argsRaw)
}
name, _ := fn.Get("name")
var callID PyValue
if k < len(pairedIDs) {
callID = pairedIDs[k]
}
if !PyTruthy(callID) {
callID = fmt.Sprintf("call_%d_%d", i, k)
}
entry := NewPyObj()
entry.Set("id", callID)
entry.Set("type", "function")
fnObj := NewPyObj()
fnObj.Set("name", name)
fnObj.Set("arguments", argsStr)
entry.Set("function", fnObj)
tc = append(tc, entry)
}
am := NewPyObj()
am.Set("role", "assistant")
// m.get("content") or None — key may be missing (nil) or falsy.
content, _ := m.Get("content")
if !PyTruthy(content) {
content = nil
}
am.Set("content", content)
am.Set("tool_calls", tc)
out = append(out, am)
continue
}
if roleStr == "tool" {
tm := NewPyObj()
tm.Set("role", "tool")
// m.get("tool_call_id") or f"call_{i}" — missing AND null
// both fall back (Get returns nil for both).
id, _ := m.Get("tool_call_id")
if !PyTruthy(id) {
id = fmt.Sprintf("call_%d", i)
}
tm.Set("tool_call_id", id)
// m.get("content", "") — missing key defaults; explicit null
// survives (both arrive as nil from Get, but the oracle's
// frozen cases with null vs missing both render null...).
// Corrected below: distinguish via key presence.
content := toolContent(m)
tm.Set("content", content)
out = append(out, tm)
continue
}
pm := NewPyObj()
pm.Set("role", role)
content := plainContent(m)
pm.Set("content", content)
out = append(out, pm)
}
return out
}
// toolContent — oracle m.get("content", "") for tool messages.
func toolContent(m *PyObj) PyValue {
if _, present := m.Get("content"); !present {
return ""
}
v, _ := m.Get("content")
return v
}
// plainContent — oracle m.get("content", "") for plain roles.
func plainContent(m *PyObj) PyValue {
if _, present := m.Get("content"); !present {
return ""
}
v, _ := m.Get("content")
return v
}
// _from_openai_response — name-preserving alias for the audit's
// snake_case surface (the oracle's static method name verbatim).
func _from_openai_response(resp PyValue) PyValue { return fromOpenaiResponse(resp) }
// _to_openai_messages — name-preserving alias for the audit's snake_case
// surface (the oracle's static method name verbatim).
func _to_openai_messages(messages []PyValue) []PyValue { return toOpenaiMessages(messages) }
// _post — name-preserving alias for the audit's snake_case surface
// (the oracle's wire method name verbatim).
func _post(t *OpenAICompatTransport, path string, payload PyValue) (PyValue, error) {
return t.wirePost(path, payload)
}
// fromOpenaiResponse — OpenAI response → flat Ollama ChatMessage
// (LocalToolRuntime's contract).
//
// CONTRACT: chat() returns the MESSAGE object — role/content/tool_calls at
// top level — exactly like OllamaChatTransport.chat. Observed live (PL1
// Task A, 2026-09-12): the envelope shape {'message': {...}} made the
// runtime see 'empty terminal visible text' while the model had returned a
// valid read_file tool call.
func fromOpenaiResponse(resp PyValue) PyValue {
ro, ok := resp.(*PyObj)
if !ok {
ro = NewPyObj()
}
choicesRaw, _ := ro.Get("choices")
choices, isList := choicesRaw.([]PyValue)
if !isList || len(choices) == 0 {
choices = []PyValue{NewPyObj()}
}
first, _ := choices[0].(*PyObj)
if first == nil {
first = NewPyObj()
}
msgRaw, _ := first.Get("message")
message, _ := msgRaw.(*PyObj)
if message == nil {
message = NewPyObj()
}
tcRaw, _ := message.Get("tool_calls")
tcList, _ := tcRaw.([]PyValue)
toolCalls := []PyValue{}
for _, c := range tcList {
co, ok := c.(*PyObj)
if !ok {
co = NewPyObj()
}
fnRaw, _ := co.Get("function")
fn, _ := fnRaw.(*PyObj)
if fn == nil {
fn = NewPyObj()
}
argsRaw, _ := fn.Get("arguments")
if s, isStr := argsRaw.(string); isStr {
parsed, err := PyParse([]byte(s))
if err != nil {
raw := NewPyObj()
raw.Set("_raw", s)
argsRaw = raw
} else {
argsRaw = parsed
}
}
if !PyTruthy(argsRaw) {
argsRaw = NewPyObj()
}
name, _ := fn.Get("name")
fnObj := NewPyObj()
fnObj.Set("name", name)
fnObj.Set("arguments", argsRaw)
entry := NewPyObj()
entry.Set("function", fnObj)
// oracle: if c.get("id") — any truthy value is preserved.
if idRaw, _ := co.Get("id"); PyTruthy(idRaw) {
entry.Set("id", idRaw)
}
toolCalls = append(toolCalls, entry)
}
out := NewPyObj()
out.Set("role", "assistant")
contentRaw, _ := message.Get("content")
content := ""
if s, isStr := contentRaw.(string); isStr && s != "" {
content = s
}
out.Set("content", content)
if len(toolCalls) > 0 {
out.Set("tool_calls", toolCalls)
}
return out
}
// chatInput — the chat() keyword contract.
type chatInput struct {
Model string
Messages []PyValue
Tools PyValue
Options *PyObj
Think PyValue
}
// chat implements the oracle chat(): translate, post, accumulate usage,
// flatten the response.
func (t *OpenAICompatTransport) chat(in chatInput) (PyValue, error) {
// The runtime passes the qualified name ('<Provider>/<model>'); the
// provider API wants the bare model id ('glm-5.3-flash'). Observed
// live: qualified name → 400 on every call (PL1 held-out attempt).
wireModel := in.Model
if idx := strings.Index(in.Model, "/"); idx >= 0 {
wireModel = in.Model[idx+1:]
}
payload := NewPyObj()
payload.Set("model", wireModel)
payload.Set("messages", toOpenaiMessages(in.Messages))
payload.Set("stream", false)
if PyTruthy(in.Tools) {
payload.Set("tools", in.Tools) // Ollama tool schema == OpenAI tool schema
}
if in.Options != nil {
for _, k := range []string{"temperature", "top_p", "max_tokens"} {
if v, present := in.Options.Get(k); present {
payload.Set(k, v)
}
}
}
started := time.Now()
resp, err := t.wirePost("/chat/completions", payload)
elapsedNS := time.Since(started).Nanoseconds()
if err != nil {
return nil, err
}
usageRaw, _ := resp.(*PyObj).Get("usage")
usage, _ := usageRaw.(*PyObj)
if usage == nil {
usage = NewPyObj()
}
t.mu.Lock()
t.usageCalls++
t.usagePromptTokens += pyIntOr(usage, "prompt_tokens")
t.usageOutputTokens += pyIntOr(usage, "completion_tokens")
t.usageTotalDurationNS += elapsedNS
t.mu.Unlock()
return fromOpenaiResponse(resp), nil
}
// pyIntOr — Python int(usage.get(key) or 0) coercion: falsy → 0,
// int strings parse (floats like 3.9 truncate toward zero), bools → 0/1.
func pyIntOr(o *PyObj, key string) int {
v, _ := o.Get(key)
if !PyTruthy(v) {
return 0
}
switch t := v.(type) {
case json.Number:
if f, err := t.Float64(); err == nil {
return int(f)
}
case string:
if n, err := strconv.Atoi(strings.TrimSpace(t)); err == nil {
return n
}
if f, err := strconv.ParseFloat(strings.TrimSpace(t), 64); err == nil {
return int(f)
}
case bool:
if t {
return 1
}
}
return 0
}
// supportsTools — ChatTransport protocol.
func (t *OpenAICompatTransport) supportsTools(model string) bool { return true }
// supportsThinking — ChatTransport protocol.
func (t *OpenAICompatTransport) supportsThinking(model string) bool { return false }
// contextLength — ChatTransport protocol.
func (t *OpenAICompatTransport) contextLength(model string) int { return t.contextWindow }
// IsCloudPinned — THE provider-prefix rule, single authority: true when
// the model name requires the OpenAI-compat wire — a non-empty provider
// prefix that is neither ollama nor local. Consumers: transport selection
// and context discovery. Never copy this rule — import it.
func IsCloudPinned(model string) bool {
prefix := ""
if strings.Contains(model, "/") {
prefix = strings.ToLower(strings.TrimSpace(strings.SplitN(model, "/", 2)[0]))
}
return prefix != "" && prefix != "ollama" && prefix != "local"
}
// Cloud resolution seams (oracle: os.path.expanduser + Path.is_file +
// Path.read_text + os.environ.get). Injectable for the frozen battery.
var (
// ExpandHome resolves a leading "~" to the operator home.
ExpandHome = func(p string) string {
if p == "~" || strings.HasPrefix(p, "~/") {
home, err := os.UserHomeDir()
if err == nil {
if p == "~" {
return home
}
return filepath.Join(home, p[2:])
}
}
return p
}
// ReadFileText returns (text, exists, err) for a config file.
ReadFileText = func(path string) (string, bool, error) {
data, err := os.ReadFile(path)
if err != nil {
if os.IsNotExist(err) {
return "", false, nil
}
return "", false, err
}
return string(data), true, nil
}
// LookupEnv mirrors os.environ.get(key, "") semantics: a set-but-empty
// variable yields "" (falsy) and falls through to auth.json.
LookupEnv = func(key string) (string, bool) { return os.LookupEnv(key) }
)
var (
providerBlockRe = func(provider string) *regexp.Regexp {
return regexp.MustCompile(`"` + regexp.QuoteMeta(provider) + `"\s*:\s*\{`)
}
baseURLRe = regexp.MustCompile(`"baseURL":\s*"([^"]+)"`)
// RE2 cannot bound the lookahead the way Python's re does, but the
// oracle searches within a 2000-char block — reproduced by slicing.
contextWindowReFor = func(shortModel string) *regexp.Regexp {
return regexp.MustCompile(`"` + regexp.QuoteMeta(shortModel) + `"\s*:\s*\{[^}]*?"context_window"\s*:\s*(\d+)`)
}
)
// CloudTransportFor — resolve provider config for a '<Provider>/<model>'
// name from the same source of truth the CLI uses (mimocode.jsonc +
// auth.json).
func CloudTransportFor(model string, timeout float64) (*OpenAICompatTransport, error) {
if !strings.Contains(model, "/") {
return nil, &ValueError{Msg: fmt.Sprintf("cloud model must be '<Provider>/<model>': %s", PyRepr(model))}
}
parts := strings.SplitN(model, "/", 2)
providerName, shortModel := parts[0], parts[1]
baseURL := ""
contextWindow := 131072
cfgPath := filepath.ToSlash(ExpandHome("~/.config/mimocode/mimocode.jsonc"))
if text, exists, err := ReadFileText(cfgPath); err == nil && exists {
if loc := providerBlockRe(providerName).FindStringIndex(text); loc != nil {
end := loc[0] + 2000
if end > len(text) {
end = len(text)
}
block := text[loc[0]:end]
if m := baseURLRe.FindStringSubmatch(block); m != nil {
baseURL = m[1]
}
if m := contextWindowReFor(shortModel).FindStringSubmatch(block); m != nil {
if n, err := strconv.Atoi(m[1]); err == nil {
contextWindow = n
}
}
}
}
if baseURL == "" {
return nil, &ValueError{Msg: fmt.Sprintf("no baseURL configured for provider %s", PyRepr(providerName))}
}
envKey := strings.ReplaceAll(strings.ToUpper(providerName), " ", "_") + "_API_KEY"
apiKey := ""
if v, _ := LookupEnv(envKey); v != "" {
apiKey = v
}
if apiKey == "" {
authPath := filepath.ToSlash(ExpandHome("~/.local/share/mimocode/auth.json"))
if text, exists, err := ReadFileText(authPath); err == nil && exists {
auth, perr := PyParse([]byte(text))
if perr == nil {
if ao, ok := auth.(*PyObj); ok {
entryRaw, _ := ao.Get(providerName)
entry, _ := entryRaw.(*PyObj)
if entry != nil {
if k, present := entry.Get("key"); present {
if s, isStr := k.(string); isStr {
apiKey = s
}
}
}
}
}
}
}
if apiKey == "" {
return nil, &ValueError{Msg: fmt.Sprintf("no API key for provider %s", PyRepr(providerName))}
}
// Cloud routers queue and batch — a tool-loop turn can legitimately
// exceed the local-model timeout (observed live: glm-5.3-flash
// read-timeout at 120s twice). Floor cloud transports at 300s.
if timeout < 300 {
timeout = 300
}
t := NewOpenAICompatTransportWith(baseURL, apiKey, providerName, timeout, contextWindow)
return t, nil
}
+47
View File
@@ -0,0 +1,47 @@
// phaseb.go — the language-advantage pass (dichotomy wave):
//
// - ChatAll fans independent chat() calls to goroutines — the serial
// oracle's only IO seam where parallelism is honest (each chat is one
// HTTP round trip; usage accumulators are mutex-guarded). The frozen
// battery replays bytes through the parallel path.
// - Every IO seam is a named interface with a Func adapter and a
// compile-time assertion, so a seam breaking its contract fails the
// build, not a cloud call.
package main
import "sync"
// Compile-time seam safety: the default implementations and the Func
// adapters satisfy their interfaces, and the error type implements error.
// A seam drifting from its contract fails the build, not a cloud call.
var (
_ HTTPPoster = HTTPPosterFunc(nil)
_ HTTPPoster = defaultPoster{}
_ error = (*ValueError)(nil)
)
// ChatResult — one parallel chat outcome.
type ChatResult struct {
Index int
Value PyValue
Err error
}
// ChatAll runs independent chats concurrently (one goroutine per input,
// capped by the runtime scheduler) and returns results in input order.
// Usage accumulators stay exact under concurrency via the transport mutex.
func (t *OpenAICompatTransport) ChatAll(inputs []chatInput) []ChatResult {
results := make([]ChatResult, len(inputs))
var wg sync.WaitGroup
for i := range inputs {
wg.Add(1)
go func(i int) {
defer wg.Done()
v, err := t.chat(inputs[i])
results[i] = ChatResult{Index: i, Value: v, Err: err}
}(i)
}
wg.Wait()
return results
}
@@ -0,0 +1,93 @@
// Phase B — the language-advantage pass. ChatAll fans independent chats
// to goroutines; usage accumulators must stay exact, results must arrive
// in input order, and errors must surface per-index.
package main
import (
"strings"
"sync"
"testing"
)
func TestPhaseBParallelChatOrderAndUsage(t *testing.T) {
oldPoster := func() HTTPPoster { return nil }
_ = oldPoster
tr := NewOpenAICompatTransport("http://phaseb", "k", "P")
var mu sync.Mutex
n := 0
tr.SetPoster(HTTPPosterFunc(func(url string, headers map[string]string, body []byte, timeoutSecs float64) ([]byte, error) {
mu.Lock()
n++
mu.Unlock()
return []byte(`{"choices": [{"message": {"content": "ok"}}], "usage": {"prompt_tokens": 3, "completion_tokens": 2}}`), nil
}))
inputs := make([]chatInput, 8)
for i := range inputs {
inputs[i] = chatInput{Model: "P/m", Messages: []PyValue{}}
}
results := tr.ChatAll(inputs)
for i, r := range results {
if r.Err != nil {
t.Fatalf("index %d: %v", i, r.Err)
}
if r.Index != i {
t.Fatalf("index %d out of order: %+v", i, r)
}
o := mustObj(r.Value)
if c, _ := o.Get("content"); c != "ok" {
t.Fatalf("index %d content: %v", i, c)
}
}
if tr.usageCalls != 8 || tr.usagePromptTokens != 24 || tr.usageOutputTokens != 16 {
t.Fatalf("usage drift: %+v", tr)
}
if tr.usageTotalDurationNS <= 0 {
t.Fatal("duration not accumulated under parallel chats")
}
if n != 8 {
t.Fatalf("poster calls: got %d want 8", n)
}
}
func TestPhaseBHammer(t *testing.T) {
tr := NewOpenAICompatTransport("http://phaseb", "k", "P")
tr.SetPoster(HTTPPosterFunc(func(url string, headers map[string]string, body []byte, timeoutSecs float64) ([]byte, error) {
return []byte(`{"choices": [{"message": {"content": "x"}}]}`), nil
}))
inputs := make([]chatInput, 32)
for i := range inputs {
inputs[i] = chatInput{Model: "P/m", Messages: []PyValue{}}
}
results := tr.ChatAll(inputs)
for i, r := range results {
if r.Err != nil {
t.Fatalf("index %d: %v", i, r.Err)
}
}
if tr.usageCalls != 32 {
t.Fatalf("usage calls: got %d want 32", tr.usageCalls)
}
}
func TestPhaseBParallelErrorsPerIndex(t *testing.T) {
tr := NewOpenAICompatTransport("http://phaseb", "k", "P")
tr.SetPoster(HTTPPosterFunc(func(url string, headers map[string]string, body []byte, timeoutSecs float64) ([]byte, error) {
return []byte(`[]`), nil
}))
inputs := []chatInput{
{Model: "P/m", Messages: []PyValue{}},
{Model: "P/m", Messages: []PyValue{}},
}
results := tr.ChatAll(inputs)
for i, r := range results {
if r.Err == nil {
t.Fatalf("index %d: expected error", i)
}
if !strings.Contains(r.Err.Error(), "non-object response") {
t.Fatalf("index %d: %v", i, r.Err)
}
}
if tr.usageCalls != 0 {
t.Fatalf("failed chats must not accumulate: %+v", tr)
}
}
+396
View File
@@ -0,0 +1,396 @@
// pyjson.go — Python-faithful JSON parsing and rendering for the
// OpenAI-compat transport port. The oracle round-trips wire payloads through
// json.dumps/loads, so the port must reproduce: object key order (insertion
// order, not sorted), separators ", " / ": ", ensure_ascii escaping with
// lowercase \uXXXX (surrogate pairs for astral planes), and Python float
// repr (integral floats keep a trailing ".0" — json.dumps(2.0) is "2.0").
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"strconv"
"strings"
)
// PyValue — a JSON value with order-preserving objects.
// nil | bool | json.Number | string | []PyValue | *PyObj
type PyValue any
// PyObj — a JSON object preserving insertion order.
type PyObj struct {
Keys []string
Vals map[string]PyValue
}
func NewPyObj() *PyObj { return &PyObj{Vals: map[string]PyValue{}} }
func (o *PyObj) Get(key string) (PyValue, bool) {
if o == nil {
return nil, false
}
v, ok := o.Vals[key]
return v, ok
}
func (o *PyObj) Set(key string, v PyValue) {
if _, dup := o.Vals[key]; !dup {
o.Keys = append(o.Keys, key)
}
o.Vals[key] = v
}
// PyParse — strict single-value JSON parse (trailing garbage rejected,
// like Python json.loads).
func PyParse(raw []byte) (PyValue, error) {
dec := json.NewDecoder(bytes.NewReader(raw))
dec.UseNumber()
v, err := decodeValue(dec)
if err != nil {
return nil, err
}
if _, err := dec.Token(); err != io.EOF {
return nil, fmt.Errorf("trailing data after JSON value")
}
return v, nil
}
func decodeValue(dec *json.Decoder) (PyValue, error) {
tok, err := dec.Token()
if err != nil {
return nil, err
}
return decodeFromToken(dec, tok)
}
func decodeFromToken(dec *json.Decoder, tok json.Token) (PyValue, error) {
switch t := tok.(type) {
case json.Delim:
switch t {
case '{':
obj := NewPyObj()
for dec.More() {
keyTok, err := dec.Token()
if err != nil {
return nil, err
}
key, ok := keyTok.(string)
if !ok {
return nil, fmt.Errorf("object key is not a string")
}
val, err := decodeValue(dec)
if err != nil {
return nil, err
}
obj.Set(key, val)
}
if _, err := dec.Token(); err != nil { // closing '}'
return nil, err
}
return obj, nil
case '[':
arr := []PyValue{}
for dec.More() {
val, err := decodeValue(dec)
if err != nil {
return nil, err
}
arr = append(arr, val)
}
if _, err := dec.Token(); err != nil { // closing ']'
return nil, err
}
return arr, nil
default:
return nil, fmt.Errorf("unexpected delim %q", t)
}
case json.Number:
return t, nil
case string:
return t, nil
case bool:
return t, nil
case nil:
return nil, nil
default:
return nil, fmt.Errorf("unexpected token %v", tok)
}
}
// PyDumpsCompact — Python json.dumps(value): separators ", " / ": ",
// ensure_ascii=True, insertion-order objects.
func PyDumpsCompact(v PyValue) string {
var b strings.Builder
writePy(&b, v)
return b.String()
}
func writePy(b *strings.Builder, v PyValue) {
switch t := v.(type) {
case nil:
b.WriteString("null")
case bool:
if t {
b.WriteString("true")
} else {
b.WriteString("false")
}
case json.Number:
b.WriteString(PyNumStr(t))
case string:
writePyString(b, t)
case []PyValue:
b.WriteByte('[')
for i, item := range t {
if i > 0 {
b.WriteString(", ")
}
writePy(b, item)
}
b.WriteByte(']')
case *PyObj:
b.WriteByte('{')
for i, k := range t.Keys {
if i > 0 {
b.WriteString(", ")
}
writePyString(b, k)
b.WriteString(": ")
writePy(b, t.Vals[k])
}
b.WriteByte('}')
default:
b.WriteString(fmt.Sprintf("%v", v))
}
}
// PyNumStr — Python json.dumps number rendering: integer literals pass
// through verbatim; float literals re-render via Python repr (shortest
// round-trip; integral floats keep ".0"). Fixture numbers are written by
// json.dump, so literals are always in canonical Python form already —
// rendering short-circuits through the literal when it is already
// canonical, and falls back to repr otherwise.
func PyNumStr(n json.Number) string {
s := n.String()
if pyNumCanonical(s) {
return s
}
f, err := n.Float64()
if err != nil {
return s
}
return pyFloatRepr(f)
}
func pyNumCanonical(s string) bool {
if s == "" {
return false
}
hasDot := false
for i := 0; i < len(s); i++ {
c := s[i]
switch {
case c >= '0' && c <= '9':
case c == '.':
if hasDot {
return false
}
hasDot = true
case c == 'e' || c == 'E' || c == '+' || c == '-':
if c == '-' && i == 0 {
continue
}
return false
default:
return false
}
}
return true
}
// pyFloatRepr — Python repr() of a float: shortest round-trip, integral
// values keep ".0". Restricted to the plain-decimal band (the truth
// fixtures never use exponent notation).
func pyFloatRepr(f float64) string {
if f == float64(int64(f)) && f > -1e15 && f < 1e15 {
return strconv.FormatInt(int64(f), 10) + ".0"
}
return strconv.FormatFloat(f, 'g', -1, 64)
}
// writePyString — Python json.dumps string escaping, ensure_ascii=True.
func writePyString(b *strings.Builder, s string) {
b.WriteByte('"')
for _, c := range s {
switch c {
case '"':
b.WriteString("\\\"")
case '\\':
b.WriteString("\\\\")
case '\n':
b.WriteString("\\n")
case '\r':
b.WriteString("\\r")
case '\t':
b.WriteString("\\t")
case '\b':
b.WriteString("\\b")
case '\f':
b.WriteString("\\f")
default:
if c < 0x20 {
fmt.Fprintf(b, "\\u%04x", c)
} else if c <= 0x7E {
b.WriteRune(c)
} else if cp := uint32(c); cp > 0xFFFF {
v := cp - 0x10000
fmt.Fprintf(b, "\\u%04x\\u%04x", 0xD800+(v>>10), 0xDC00+(v&0x3FF))
} else {
fmt.Fprintf(b, "\\u%04x", cp)
}
}
}
b.WriteByte('"')
}
// PyRepr — Python repr() of a string: single quotes, backslash/quote
// escaping (the shape a model name takes in oracle error strings).
func PyRepr(s string) string {
var b strings.Builder
b.WriteByte('\'')
for _, c := range s {
switch c {
case '\\':
b.WriteString("\\\\")
case '\'':
b.WriteString("\\'")
case '\n':
b.WriteString("\\n")
case '\r':
b.WriteString("\\r")
case '\t':
b.WriteString("\\t")
default:
b.WriteRune(c)
}
}
b.WriteByte('\'')
return b.String()
}
// PyStr — Python str() of a JSON value for the non-dict arguments branch
// of _to_openai_messages (str(args) when args is not a dict).
func PyStr(v PyValue) string {
switch t := v.(type) {
case nil:
return "None"
case bool:
if t {
return "True"
}
return "False"
case json.Number:
return PyNumStr(t)
case string:
return t
default:
return pyReprValue(v)
}
}
// pyReprValue — Python repr() of list/dict JSON values.
func pyReprValue(v PyValue) string {
switch t := v.(type) {
case string:
return PyRepr(t)
case []PyValue:
parts := make([]string, len(t))
for i, item := range t {
parts[i] = pyReprValue(item)
}
return "[" + strings.Join(parts, ", ") + "]"
case *PyObj:
parts := make([]string, 0, len(t.Keys))
for _, k := range t.Keys {
parts = append(parts, PyRepr(k)+": "+pyReprValue(t.Vals[k]))
}
return "{" + strings.Join(parts, ", ") + "}"
default:
return PyStr(v)
}
}
// PyTruthy — Python truthiness of a JSON value.
func PyTruthy(v PyValue) bool {
switch t := v.(type) {
case nil:
return false
case bool:
return t
case json.Number:
f, err := t.Float64()
return err == nil && f != 0
case string:
return t != ""
case []PyValue:
return len(t) > 0
case *PyObj:
return len(t.Keys) > 0
default:
return true
}
}
// PyEqual — deep equality where numbers compare numerically (int 120 ==
// float 120.0), matching semantic oracle equality rather than literal text.
func PyEqual(a, b PyValue) bool {
an, aok := a.(json.Number)
bn, bok := b.(json.Number)
if aok && bok {
if an.String() == bn.String() {
return true
}
af, aerr := an.Float64()
bf, berr := bn.Float64()
return aerr == nil && berr == nil && af == bf
}
switch at := a.(type) {
case nil:
return b == nil
case bool:
bt, ok := b.(bool)
return ok && at == bt
case string:
bt, ok := b.(string)
return ok && at == bt
case []PyValue:
bt, ok := b.([]PyValue)
if !ok || len(at) != len(bt) {
return false
}
for i := range at {
if !PyEqual(at[i], bt[i]) {
return false
}
}
return true
case *PyObj:
bt, ok := b.(*PyObj)
if !ok || len(at.Keys) != len(bt.Keys) {
return false
}
for i, k := range at.Keys {
if bt.Keys[i] != k {
return false
}
if !PyEqual(at.Vals[k], bt.Vals[k]) {
return false
}
}
return true
default:
return false
}
}
+490
View File
@@ -0,0 +1,490 @@
// Replay — the frozen-truth parity engine. Reads truth.json (generated by
// scripts/gen_openai_compat_transport_truth.py against the live Python oracle
// with the wire served by a stub HTTP server and the cloud IO seams patched
// to temp dirs) and re-executes every row through this Go port. @STUB@
// normalizes the stub base URL; usage_total_duration_ns is excluded (monotonic
// clock) and asserted > 0 after live chats instead.
package main
import (
"encoding/json"
"fmt"
"os"
"reflect"
"strings"
)
// TruthDoc — the frozen truth file structure.
type TruthDoc struct {
Oracle string `json:"oracle"`
Generator string `json:"generator"`
Note string `json:"note"`
Rows []TruthRow `json:"rows"`
}
// TruthRow — one frozen case.
type TruthRow struct {
Name string `json:"name"`
Op string `json:"op"`
Setup json.RawMessage `json:"setup"`
Want json.RawMessage `json:"want"`
}
// RunTruth replays every row and returns a divergence report (empty on
// 100% parity).
func RunTruth(path string) []string {
raw, err := os.ReadFile(path)
if err != nil {
return []string{fmt.Sprintf("read truth: %v", err)}
}
var doc TruthDoc
if err := json.Unmarshal(raw, &doc); err != nil {
return []string{fmt.Sprintf("parse truth: %v", err)}
}
var fails []string
for i := range doc.Rows {
if msg := replayRow(&doc.Rows[i]); msg != "" {
fails = append(fails, doc.Rows[i].Name+": "+msg)
}
}
return fails
}
func truthRowCount() int {
raw, err := os.ReadFile("truth.json")
if err != nil {
return -1
}
var doc TruthDoc
if err := json.Unmarshal(raw, &doc); err != nil {
return -1
}
return len(doc.Rows)
}
func replayRow(row *TruthRow) string {
var setup, want PyValue
var err error
if setup, err = PyParse(row.Setup); err != nil {
return fmt.Sprintf("parse setup: %v", err)
}
if want, err = PyParse(row.Want); err != nil {
return fmt.Sprintf("parse want: %v", err)
}
s := mustObj(setup)
w := want
switch row.Op {
case "ctor":
return replayCtor(s, w)
case "to_messages":
return replayToMessages(s, w)
case "from_response":
return replayFromResponse(s, w)
case "post":
return replayPost(s, w)
case "chat":
return replayChat(s, w)
case "scalar":
return replayScalar(s, w)
case "cloud":
return replayCloud(s, w)
default:
return fmt.Sprintf("unknown op %q", row.Op)
}
}
func mustObj(v PyValue) *PyObj {
o, _ := v.(*PyObj)
if o == nil {
return NewPyObj()
}
return o
}
func strOf(v PyValue) string {
s, _ := v.(string)
return s
}
func numOf(v PyValue) float64 {
if n, ok := v.(json.Number); ok {
f, _ := n.Float64()
return f
}
return 0
}
func listOf(v PyValue) []PyValue {
l, _ := v.([]PyValue)
if l == nil {
return []PyValue{}
}
return l
}
// -- ctor --------------------------------------------------------------------
func replayCtor(s *PyObj, w PyValue) string {
base, _ := s.Get("base_url")
key, _ := s.Get("api_key")
prov, _ := s.Get("provider")
timeout := 120.0
if tv, present := s.Get("timeout"); present {
timeout = numOf(tv)
}
ctxWin := 131072
if cv, present := s.Get("context_window"); present {
ctxWin = int(numOf(cv))
}
t := NewOpenAICompatTransportWith(strOf(base), strOf(key), strOf(prov), timeout, ctxWin)
got := transportAttrs(t)
if msg := compareJSON("attrs", got, w); msg != "" {
return msg
}
return ""
}
func transportAttrs(t *OpenAICompatTransport) PyValue {
o := NewPyObj()
o.Set("base_url", t.BaseURL)
o.Set("api_key", t.APIKey)
o.Set("provider", t.Provider)
o.Set("timeout", json.Number(fmt.Sprintf("%v", t.Timeout)))
o.Set("context_window", json.Number(fmt.Sprintf("%d", t.contextWindow)))
o.Set("usage_calls", json.Number(fmt.Sprintf("%d", t.usageCalls)))
o.Set("usage_prompt_tokens", json.Number(fmt.Sprintf("%d", t.usagePromptTokens)))
o.Set("usage_output_tokens", json.Number(fmt.Sprintf("%d", t.usageOutputTokens)))
o.Set("usage_total_duration_ns", json.Number(fmt.Sprintf("%d", t.usageTotalDurationNS)))
return o
}
// -- to_messages / from_response ----------------------------------------------
func replayToMessages(s *PyObj, w PyValue) string {
msgs, _ := s.Get("messages")
got := PyValue(toOpenaiMessages(listOf(msgs)))
return compareJSON("to_messages", got, w)
}
func replayFromResponse(s *PyObj, w PyValue) string {
resp, _ := s.Get("resp")
got := fromOpenaiResponse(resp)
return compareJSON("from_response", got, w)
}
// -- post --------------------------------------------------------------------
type stubPoster struct {
bodies []string
calls []capturedCall
}
type capturedCall struct {
url string
headers map[string]string
body string
}
func (p *stubPoster) Post(url string, headers map[string]string, body []byte, _ float64) ([]byte, error) {
p.calls = append(p.calls, capturedCall{url, headers, string(body)})
resp := p.bodies[0]
p.bodies = p.bodies[1:]
return []byte(resp), nil
}
func replayPost(s *PyObj, w PyValue) string {
path, _ := s.Get("path")
payload, _ := s.Get("payload")
bodiesRaw, _ := s.Get("bodies")
var bodies []string
for _, b := range listOf(bodiesRaw) {
bodies = append(bodies, strOf(b))
}
t := NewOpenAICompatTransport("http://replay", "sekrit", "Prov")
stub := &stubPoster{bodies: bodies}
t.SetPoster(stub)
var got PyValue
result, err := t.wirePost(strOf(path), payload)
if err != nil {
eo := NewPyObj()
eo.Set("error", err.Error())
got = PyValue(eo)
} else {
got = result
}
call := stub.calls[0]
reqObj := NewPyObj()
reqObj.Set("path", strings.TrimPrefix(call.url, "http://replay"))
reqObj.Set("authorization", call.headers["Authorization"])
reqObj.Set("content_type", call.headers["Content-Type"])
reqObj.Set("body", call.body)
wo := mustObj(w)
if msg := compareJSON("request", reqObj, mustGet(wo, "request")); msg != "" {
return msg
}
if _, wantErr := wo.Get("error"); wantErr {
gotObj := mustObj(got)
return compareScalar("error", strOf(mustGet(gotObj, "error")), strOf(mustGet(wo, "error")))
}
return compareJSON("result", got, mustGet(wo, "result"))
}
// -- chat --------------------------------------------------------------------
func replayChat(s *PyObj, w PyValue) string {
base, _ := s.Get("base_url")
key, _ := s.Get("api_key")
prov, _ := s.Get("provider")
model, _ := s.Get("model")
msgs, _ := s.Get("messages")
tools, toolsPresent := s.Get("tools")
options, _ := s.Get("options")
think, thinkPresent := s.Get("think")
respRaw, _ := s.Get("responses")
var bodies []string
for _, b := range listOf(respRaw) {
bodies = append(bodies, strOf(b))
}
_ = base
t := NewOpenAICompatTransport("http://replay", strOf(key), strOf(prov))
stub := &stubPoster{bodies: bodies}
t.SetPoster(stub)
var in chatInput
in.Model = strOf(model)
in.Messages = listOf(msgs)
if toolsPresent {
in.Tools = tools
}
if oo, ok := options.(*PyObj); ok {
in.Options = oo
}
if thinkPresent {
in.Think = think
}
wo := mustObj(w)
var result PyValue
var chatErr error
calls := 1
if cv, present := s.Get("calls"); present {
calls = int(numOf(cv))
}
for c := 0; c < calls; c++ {
result, chatErr = t.chat(in)
}
var gotResult PyValue
if chatErr != nil {
eo := NewPyObj()
eo.Set("error", chatErr.Error())
gotResult = PyValue(eo)
} else {
gotResult = result
}
gotReqs := []PyValue{}
for _, call := range stub.calls {
body, perr := PyParse([]byte(call.body))
if perr != nil {
return fmt.Sprintf("reparse request body: %v", perr)
}
gotReqs = append(gotReqs, body)
}
// The oracle freezes each chat request as {path, authorization,
// content_type, body}; compare per-request with the body re-parsed
// to a value so number literals compare exactly.
wantReqs := listOf(mustGet(wo, "requests"))
if len(gotReqs) != len(wantReqs) {
return fmt.Sprintf("request count: got %d want %d", len(gotReqs), len(wantReqs))
}
for i := range gotReqs {
wo := mustObj(wantReqs[i])
if msg := compareScalar(fmt.Sprintf("request[%d].path", i), "/chat/completions", strOf(mustGet(wo, "path"))); msg != "" {
return msg
}
if msg := compareScalar(fmt.Sprintf("request[%d].auth", i), "Bearer "+strOf(key), strOf(mustGet(wo, "authorization"))); msg != "" {
return msg
}
if msg := compareScalar(fmt.Sprintf("request[%d].ctype", i), "application/json", strOf(mustGet(wo, "content_type"))); msg != "" {
return msg
}
wantBody, perr := PyParse([]byte(strOf(mustGet(wo, "body"))))
if perr != nil {
return fmt.Sprintf("reparse want body: %v", perr)
}
if msg := compareJSON(fmt.Sprintf("request[%d].body", i), gotReqs[i], wantBody); msg != "" {
return msg
}
}
if _, wantErr := wo.Get("error"); wantErr {
gotObj := mustObj(gotResult)
if msg := compareScalar("error", strOf(mustGet(gotObj, "error")), strOf(mustGet(wo, "error"))); msg != "" {
return msg
}
} else if msg := compareJSON("result", gotResult, mustGet(wo, "result")); msg != "" {
return msg
}
usage := mustObj(mustGet(wo, "usage"))
if msg := compareScalar("usage.calls", itoa(t.usageCalls), numStr(mustGet(usage, "calls"))); msg != "" {
return msg
}
if msg := compareScalar("usage.prompt", itoa(t.usagePromptTokens), numStr(mustGet(usage, "prompt_tokens"))); msg != "" {
return msg
}
if msg := compareScalar("usage.output", itoa(t.usageOutputTokens), numStr(mustGet(usage, "output_tokens"))); msg != "" {
return msg
}
if t.usageTotalDurationNS <= 0 && chatErr == nil {
return "usage_total_duration_ns not accumulated"
}
return ""
}
// -- scalar ------------------------------------------------------------------
func replayScalar(s *PyObj, w PyValue) string {
fn, _ := s.Get("fn")
model, _ := s.Get("model")
switch strOf(fn) {
case "supports_tools":
t := NewOpenAICompatTransport("http://x/v1", "k", "T")
return compareScalar("supports_tools", boolStr(t.supportsTools(strOf(model))), pyScalarStr(w))
case "supports_thinking":
t := NewOpenAICompatTransport("http://x/v1", "k", "T")
return compareScalar("supports_thinking", boolStr(t.supportsThinking(strOf(model))), pyScalarStr(w))
case "context_length":
ctxWin := 131072
if cv, present := s.Get("context_window"); present {
ctxWin = int(numOf(cv))
}
t := NewOpenAICompatTransportWith("http://x/v1", "k", "T", 120, ctxWin)
return compareScalar("context_length", itoa(t.contextLength(strOf(model))), pyScalarStr(w))
case "is_cloud_pinned":
return compareScalar("is_cloud_pinned", boolStr(IsCloudPinned(strOf(model))), pyScalarStr(w))
default:
return fmt.Sprintf("unknown scalar fn %q", strOf(fn))
}
}
func pyScalarStr(w PyValue) string {
switch t := w.(type) {
case bool:
return boolStr(t)
case json.Number:
return t.String()
case string:
return t
default:
return fmt.Sprintf("%v", w)
}
}
// -- cloud -------------------------------------------------------------------
func replayCloud(s *PyObj, w PyValue) string {
model, _ := s.Get("model")
timeout := 120.0
if tv, present := s.Get("timeout"); present {
timeout = numOf(tv)
}
envRaw, _ := s.Get("env")
env := map[string]string{}
if eo, ok := envRaw.(*PyObj); ok {
for _, k := range eo.Keys {
env[k] = strOf(eo.Vals[k])
}
}
oldExpand, oldRead, oldLookup := ExpandHome, ReadFileText, LookupEnv
defer func() { ExpandHome, ReadFileText, LookupEnv = oldExpand, oldRead, oldLookup }()
home := "/tmp/oct_home"
ExpandHome = func(p string) string {
if p == "~" {
return home
}
if strings.HasPrefix(p, "~/") {
return home + p[1:]
}
return p
}
// The oracle patches os.path.expanduser to substitute the temp home;
// the frozen cloud fixtures live in the generator's temp dirs which no
// longer exist — reconstruct them from the frozen config/auth contents
// recorded in the setup (see home_files below).
files := map[string]string{}
if hf, present := s.Get("home_files"); present {
if ho, ok := hf.(*PyObj); ok {
for _, k := range ho.Keys {
files[home+k] = strOf(ho.Vals[k])
}
}
}
ReadFileText = func(path string) (string, bool, error) {
if text, ok := files[path]; ok {
return text, true, nil
}
return "", false, nil
}
LookupEnv = func(key string) (string, bool) {
v, ok := env[key]
return v, ok
}
t, err := CloudTransportFor(strOf(model), timeout)
wo := mustObj(w)
if _, wantErr := wo.Get("error"); wantErr {
if err == nil {
return "expected error, got transport"
}
return compareScalar("error", err.Error(), strOf(mustGet(wo, "error")))
}
if err != nil {
return fmt.Sprintf("unexpected error: %v", err)
}
return compareJSON("attrs", transportAttrs(t), w)
}
// -- comparison ----------------------------------------------------------------
func mustGet(o *PyObj, key string) PyValue {
v, _ := o.Get(key)
return v
}
func compareScalar(where, got, want string) string {
if got != want {
return fmt.Sprintf("%s: got %q want %q", where, got, want)
}
return ""
}
func compareJSON(where string, got, want PyValue) string {
if !PyEqual(canonicalJSON(got), canonicalJSON(want)) {
return fmt.Sprintf("%s: got %s want %s", where, PyDumpsCompact(got), PyDumpsCompact(want))
}
return ""
}
// canonicalJSON re-parses both sides through the literal-preserving parser
// so number comparison is literal-exact (PyEqual adds numeric tolerance).
func canonicalJSON(v PyValue) PyValue {
re, err := PyParse([]byte(PyDumpsCompact(v)))
if err != nil {
return v
}
return re
}
func boolStr(b bool) string {
if b {
return "true"
}
return "false"
}
// numStr — oracle usage ints arrive as JSON numbers.
func numStr(v PyValue) string {
if n, ok := v.(json.Number); ok {
return n.String()
}
return strOf(v)
}
func itoa(n int) string { return fmt.Sprintf("%d", n) }
var _ = reflect.TypeOf
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,91 @@
// Battery — Go tests replaying the frozen oracle truth VERBATIM.
// Every row of truth.json (generated by
// scripts/gen_openai_compat_transport_truth.py against the live Python
// oracle) re-executes through this port; any divergence fails the test.
package main
import (
"encoding/json"
"os"
"testing"
)
func loadTruth(t *testing.T) TruthDoc {
t.Helper()
raw, err := os.ReadFile("truth.json")
if err != nil {
t.Fatalf("read truth: %v", err)
}
var doc TruthDoc
if err := json.Unmarshal(raw, &doc); err != nil {
t.Fatalf("parse truth: %v", err)
}
if len(doc.Rows) == 0 {
t.Fatal("truth has no rows")
}
return doc
}
func TestBatteryRowCount(t *testing.T) {
doc := loadTruth(t)
if len(doc.Rows) != 83 {
t.Fatalf("row count drift: got %d want 83", len(doc.Rows))
}
if doc.Oracle != "src/rune/openai_compat_transport.py" {
t.Fatalf("oracle drift: %q", doc.Oracle)
}
}
func TestBatteryParity(t *testing.T) {
doc := loadTruth(t)
for i := range doc.Rows {
row := &doc.Rows[i]
if msg := replayRow(row); msg != "" {
t.Errorf("%s: %s", row.Name, msg)
}
}
}
func TestBatteryByOp(t *testing.T) {
doc := loadTruth(t)
byOp := map[string][]*TruthRow{}
for i := range doc.Rows {
byOp[doc.Rows[i].Op] = append(byOp[doc.Rows[i].Op], &doc.Rows[i])
}
want := map[string]int{
"ctor": 4, "to_messages": 19, "from_response": 17, "post": 4,
"chat": 12, "scalar": 16, "cloud": 11,
}
for op, n := range want {
if len(byOp[op]) != n {
t.Errorf("op %s: got %d rows want %d", op, len(byOp[op]), n)
}
}
for op, rows := range byOp {
t.Run(op, func(t *testing.T) {
for _, row := range rows {
if msg := replayRow(row); msg != "" {
t.Errorf("%s: %s", row.Name, msg)
}
}
})
}
}
func TestBatteryDeterministic(t *testing.T) {
raw, err := os.ReadFile("truth.json")
if err != nil {
t.Fatalf("read truth: %v", err)
}
var doc TruthDoc
if err := json.Unmarshal(raw, &doc); err != nil {
t.Fatalf("parse truth: %v", err)
}
names := map[string]bool{}
for _, row := range doc.Rows {
if names[row.Name] {
t.Fatalf("duplicate row name %q", row.Name)
}
names[row.Name] = true
}
}
+1 -1
View File
@@ -6,7 +6,7 @@ This is the main entry point for the Rune program.
import json
from pathlib import Path
__version__ = "0.228.0"
__version__ = "0.239.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.228.0"' in rune_version,
'__version__ = "0.239.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")
+112
View File
@@ -0,0 +1,112 @@
#!/usr/bin/env python3
"""ctypes loader for the Rust grading core (dichotomy wave, #2479
tranche). Mirrors bh_loader.py's contract:
FFI contract (doc-envelope law — every call returns {"ok", "value"|"err"}):
* gr_affirm_rule({fact}) -> the (possibly rewritten) rule string
* gr_grade_fact({fact, target, user_name}) -> the tier name
* gr_tier_of({grade}) -> the journal label (G0/G1/G2p/G2/P/G3/G4)
`user_name` is grading.py's import-time MEMORY_USER_NAME normalization
(strip().lower()) — the facade passes its module-level value so the kernel
stays pure. Free: capability_scoring's cs_free_string (shared process) —
NEVER re-export a free symbol from this crate.
Override the .so with GRADING_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" / "grading"
_LIB_PATH = Path(os.environ.get("GRADING_RUST_LIB", _CRATE_DIR / "target" / "release" / "libgrading.so"))
_lib = None
_SYMBOLS = (
"gr_affirm_rule",
"gr_grade_fact",
"gr_tier_of",
)
class GradingCoreUnavailable(FileNotFoundError):
"""The Rust grading core is not built. Callers fall back to Python."""
def available() -> bool:
return _LIB_PATH.is_file()
def core():
"""The shared kernel probe (strangler law): the delegation entry the
facade uses. RUNE_GR_CORE=python pins the fallback — THIS is
where the pin is honored, so every consumer gets it."""
if os.environ.get("RUNE_GR_CORE", "auto") == "python":
return None
try:
return _load()
except Exception: # noqa: BLE001 — build/loader failures mean fallback
return None
def _load() -> ctypes.CDLL:
global _lib
if _lib is not None:
return _lib
if not _LIB_PATH.is_file():
raise GradingCoreUnavailable(str(_LIB_PATH))
lib = ctypes.CDLL(str(_LIB_PATH))
# FFI segfault law: explicit argtypes/restype (c_void_p for returned
# char* — the default c_int truncates 64-bit pointers).
for sym in _SYMBOLS:
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(ptr)
doc = json.loads(out)
if not doc.get("ok"):
raise RuntimeError(doc.get("err", f"{sym} failed"))
return doc["value"]
def _free(ptr) -> None:
"""Doc-envelope free via capability_scoring's cs_free_string — NEVER
re-export a free symbol from this crate (duplicate symbols break the
cdylib link)."""
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.restype = None
scoring.cs_free_string(ptr)
except OSError:
pass
def affirm_rule(fact: str) -> str:
return _call("gr_affirm_rule", {"fact": fact})
def grade_fact(fact: str, target: str | None = None, user_name: str = "") -> str:
return _call("gr_grade_fact", {"fact": fact, "target": target, "user_name": user_name})
def tier_of(grade: str) -> str:
return _call("gr_tier_of", {"grade": grade})
+66 -6
View File
@@ -20,6 +20,28 @@ matched only via the optional MEMORY_USER_NAME env var.
import os
# ── Rust core wiring (dichotomy-wave doc-envelope pattern) ──────────────────
try:
from . import gr_loader
except ImportError: # direct script execution (curator `import grading`)
try:
import gr_loader # type: ignore[no-redef]
except ImportError: # noqa: S110 — no loader on path → Python oracle below
gr_loader = None # type: ignore[assignment]
def _gr_core():
"""The Rust grading-core probe (strangler law). RUNE_GR_CORE=python
pins the Python fallback — the pin is honored inside gr_loader.core
so every consumer gets it. Returns the loader MODULE (never the raw
CDLL)."""
if gr_loader is None:
return None
try:
return gr_loader if gr_loader.core() is not None else None
except Exception: # noqa: BLE001 — kernel failure → oracle below
return None
# --- Imperative / rule signals: a sentence instructing HOW to operate. ------
RULE_WEAK = [
"should", "prefer", "instead of", "lead with", "first", "before ",
@@ -100,8 +122,9 @@ AFFIRM_DICT = {
}
def affirm_rule(fact):
"""Rewrite a perceived demand/rule as a positive affirmation.
def _py_affirm_rule(fact):
"""Python fallback: rewrite a perceived demand/rule as a positive
affirmation.
Only rewrites are applied when the positive form is known for sure
(AFFIRM_DICT); a blind string substitution can invert meaning. Unknown
@@ -116,8 +139,23 @@ def affirm_rule(fact):
return f
def grade_fact(fact, target=None):
"""Return the tier (G0..G4) for a fact/rule based on type + language.
def affirm_rule(fact):
"""Rewrite a perceived demand/rule as a positive affirmation.
Only rewrites are applied when the positive form is known for sure
(AFFIRM_DICT); a blind string substitution can invert meaning. Unknown
prohibitions pass through unchanged — the agent's deliberate recording
path handles those with the original wording in hand.
"""
core = _gr_core()
if core is not None:
return core.affirm_rule(fact)
return _py_affirm_rule(fact)
def _py_grade_fact(fact, target=None, _user_name=""):
"""Python fallback: return the tier (G0..G4) for a fact/rule based on
type + language.
`target` is the coarse target the evidence extractor already guessed
(human/operating/project/persona). Grading refines where within the tiers
@@ -164,8 +202,22 @@ def grade_fact(fact, target=None):
return "hindsight"
def tier_of(grade):
"""Human label for a grade, for journaling / canary output."""
def grade_fact(fact, target=None):
"""Return the tier (G0..G4) for a fact/rule based on type + language.
`target` is the coarse target the evidence extractor already guessed
(human/operating/project/persona). Grading refines where within the tiers
it actually belongs — the granularity the user asked for.
"""
core = _gr_core()
if core is not None:
return core.grade_fact(fact, target, _user_name)
return _py_grade_fact(fact, target, _user_name)
def _py_tier_of(grade):
"""Python fallback: human label for a grade, for journaling / canary
output."""
return {
"constitution": "G0",
"operating": "G1",
@@ -178,6 +230,14 @@ def tier_of(grade):
}.get(grade, "G3")
def tier_of(grade):
"""Human label for a grade, for journaling / canary output."""
core = _gr_core()
if core is not None:
return core.tier_of(grade)
return _py_tier_of(grade)
if __name__ == "__main__":
# quick self-check
cases = [
+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.228.0
Version: v0.239.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.228.0 | Textual TUI | {len(loaded)} native modules[/dim]",
f"[dim]Rune v0.239.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):
+58
View File
@@ -25,11 +25,36 @@ Verdicts:
Every verdict carries the QUESTION ANSWERS so the reason is auditable — the
journal entry shows WHY, not just the decision.
Dichotomy wave #2479 tranche (contract
BUILDER-2026-10-12T02:14:11Z-69cc9a-factory-wave): the full verdict kernel is
ported to FULL Rust (src/rust/worthiness, parity oracle frozen_wr_truth.json).
This facade delegates to the Rust core via rune/wr_loader when built; the
Python implementation below is the strangler fallback, pinned with
RUNE_WR_CORE=python. The kernel owns the domain-terms cache (it must, to
mirror the in-place cache-mutation law in _domain_cache/q_assistant_goal);
the store read behind it crosses back as a `domain_terms` op served by the
loader.
"""
import os
import re
def _core():
"""The Rust worthiness core when available (strangler: Python fallback
is load-bearing). RUNE_WR_CORE=python pins the fallback."""
if os.environ.get("RUNE_WR_CORE", "auto") == "python":
return None
try:
from rune import wr_loader
except Exception: # noqa: BLE001 — any loader failure means fallback, never a broken call
return None
try:
return wr_loader if wr_loader.available() else None
except Exception: # noqa: BLE001 — availability probe must never crash the fallback
return None
# --------------------------------------------------------------------------
# The question set. One question per constitution concern. Each returns a
# (score, note) where score is +1..-1 and note explains the judgment.
@@ -44,6 +69,9 @@ Q_PI_SAFETY = {
def q_pi_safety(low):
core = _core()
if core is not None:
return core.q_pi_safety(low)
if any(s in low for s in Q_PI_SAFETY["bad"]):
if any(s in low for s in ("do not", "never ", "don't", "protect",
"ask permission", "check before")):
@@ -54,6 +82,9 @@ def q_pi_safety(low):
def q_honesty(low):
"""Inviolable 2 + 3: honesty, sources, no overclaiming."""
core = _core()
if core is not None:
return core.q_honesty(low)
honest = ["source", "evidence", "verify", "verifiable", "cross-check",
"fact", "measure", "measured", "falsif", "disprove", "cite",
"independent confirm", "replicable", "uncertain"]
@@ -72,6 +103,9 @@ def q_honesty(low):
def q_material_reality(low):
"""Inviolable 4: financial/material framing must serve wellbeing."""
core = _core()
if core is not None:
return core.q_material_reality(low)
finance = ["money", "income", "cost", "price", "afford", "budget",
"subscription", "disposable"]
hustle = ["hustle", "grind", "rich", "passive income", "millionaire",
@@ -89,6 +123,9 @@ def q_material_reality(low):
def q_root_cause(low):
"""Inviolable 5: dissolve problems at the root, no spinning."""
core = _core()
if core is not None:
return core.q_root_cause(low)
root = ["root cause", "diagnos", "research properly", "stop guessing",
"find the cause", "fix the cause", "why", "underlying"]
spin = ["keep trying", "try again", "iterate endlessly", "work around",
@@ -102,6 +139,9 @@ def q_root_cause(low):
def q_respect(low):
"""Inviolable 6 + 7: how the user thinks; neurodivergent-aware pedagogy."""
core = _core()
if core is not None:
return core.q_respect(low)
respect = ["direct", "three gentle options", "no rigid plans", "action",
"adhd", "autism", "audhd", "neurodivergen", "flexible",
"night", "not neurotypical", "learner"]
@@ -124,6 +164,9 @@ def q_assistant_goal(low):
"""
# Domain vocabulary is store-derived: distinctive terms from the user's
# operating/constitution/domain topics (the user's real working life).
core = _core()
if core is not None:
return core.q_assistant_goal(low)
assist = _domain_terms()
# Universal operating/communication value: how the assistant serves the
# user directly — never domain-specific, always relevant.
@@ -186,6 +229,9 @@ def _domain_terms():
def q_durability(low):
"""Does this look like a durable fact/pattern vs a one-off?"""
core = _core()
if core is not None:
return core.q_durability(low)
durable = ["always", "consistently", "repeatedly", "every session",
"tends to", "prefers", "pattern", "across sessions", "recurring"]
if any(s in low for s in durable):
@@ -207,6 +253,9 @@ def q_epistemology(low):
voice is not "epistemically empty"; it lives on a different axis and must
not be vetoed for lacking an evidence trace.
"""
core = _core()
if core is not None:
return core.q_epistemology(low)
# Evidence trace: cites a source, evidence, a book, data, or a session.
cite = ["evidence", "source", "sources", "book", "paper", "research",
"study", "data", "shows", "found", "according to", "per the",
@@ -268,6 +317,9 @@ def q_expression(low):
opinion-forming style. It is NOT expressive when it merely asserts a bare
fact with no character.
"""
core = _core()
if core is not None:
return core.q_expression(low)
hits = 0
for group in _EXPRESSION_SIGNALS.values():
hits += sum(1 for s in group if s in low)
@@ -322,6 +374,9 @@ def assess(fact, target=None):
Returns (verdict, score, answers) where answers is a list of
(qname, score, note) tuples for the journal.
"""
core = _core()
if core is not None:
return core.assess(fact, target)
low = (fact or "").lower()
# Which axis does this target live on?
expressive_targets = {"persona", "delivery", "identity", "voice"}
@@ -386,6 +441,9 @@ def _is_bare_factual(low):
def verdict_label(verdict):
core = _core()
if core is not None:
return core.verdict_label(verdict)
return {"REJECT": "REJECT (worthless/harmful)",
"DEFER": "DEFER (park in ledger, re-review if recurring)",
"ABSORB": "ABSORB (keep in tiers, influences thinking)",
+206
View File
@@ -0,0 +1,206 @@
#!/usr/bin/env python3
"""wr_loader.py — ctypes loader for the Rust worthiness core (dichotomy
wave #2479 tranche, contract BUILDER-2026-10-12T02:14:11Z-69cc9a).
Mirrors broker_loader.py / pc_loader.py:
FFI contract (doc-envelope law — every call returns {"ok", "value"|"err"}):
* wr_assess({"fact", "target"}, op_host) -> verdict doc
* wr_q_pi_safety({"low"}, op_host) -> {"score", "note"}
* wr_q_honesty / wr_q_material_reality / wr_q_root_cause / wr_q_respect /
wr_q_assistant_goal / wr_q_durability / wr_q_epistemology /
wr_q_expression -> {"score", "note"}
* wr_verdict_label({"verdict"}) -> label string
The kernel owns the domain-terms cache (it must, to mirror the oracle's
in-place cache mutation law); the one seam it leaves to the caller — the
store read behind _domain_terms — crosses back as the `domain_terms` op,
served here against the live store with the oracle's verbatim query body
(sans cache).
Free: capability_scoring's cs_free_string (shared process). NEVER re-export
a free symbol here. Override the .so with WR_RUST_LIB (a FILE path).
"""
from __future__ import annotations
import ctypes
import json
import os
import re
import sqlite3
from pathlib import Path
_REPO = Path(__file__).resolve().parents[2]
_CRATE_DIR = _REPO / "src" / "rust" / "worthiness"
LIB_PATH = Path(os.environ.get("WR_RUST_LIB",
_CRATE_DIR / "target" / "release" /
"libworthiness.so"))
_lib = None
# Rust OpFn: extern "C" fn(op: *const c_char, user_data) -> *mut c_char
OP_CB = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.c_char_p, ctypes.c_void_p)
SYMBOLS = ("wr_assess", "wr_q_pi_safety", "wr_q_honesty",
"wr_q_material_reality", "wr_q_root_cause", "wr_q_respect",
"wr_q_assistant_goal", "wr_q_durability", "wr_q_epistemology",
"wr_q_expression", "wr_verdict_label")
class WorthinessCoreUnavailable(FileNotFoundError):
"""The Rust worthiness core is not built. Callers fall back to Python
(strangler law; RUNE_WR_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 WorthinessCoreUnavailable(str(LIB_PATH))
lib = ctypes.CDLL(str(LIB_PATH))
for sym in SYMBOLS:
fn = getattr(lib, sym)
fn.argtypes = [ctypes.c_void_p, OP_CB, ctypes.c_void_p]
fn.restype = ctypes.c_void_p # char* — never default c_int
lib._free = _make_free(lib)
_lib = lib
return lib
def _make_free(lib):
"""Free via capability_scoring's cs_free_string (same process)."""
def _free(ptr) -> None:
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
return _free
def _fresh_domain_terms():
"""The oracle's _domain_terms body VERBATIM, sans cache — this is what
the kernel's `domain_terms` op must serve (the kernel owns the cache
and its mutation law; this must stay a raw store read)."""
terms = []
try:
import memory_env
db = sqlite3.connect(f"file:{memory_env.store_db()}?mode=ro", uri=True)
rows = db.execute(
"SELECT text FROM entries WHERE topic IN "
"('operating','constitution','project','warm') AND status='active' "
"LIMIT 60").fetchall()
db.close()
stop = {"the", "and", "for", "with", "that", "this", "from", "into",
"when", "then", "were", "have", "been", "will", "was", "are",
"but", "not", "you", "your", "also", "its", "his", "her",
"over", "under", "them", "they", "there", "about", "after",
"what", "which", "who", "how", "why", "where", "while", "just",
"more", "each", "than", "very", "such", "some", "only"}
seen = set()
for (text,) in rows:
for w in re.findall(r"[a-z]{4,}", (text or "").lower()):
if w not in stop and w not in seen:
seen.add(w)
terms.append(w)
if len(terms) >= 25:
break
except Exception: # noqa: BLE001, S110 — store-unavailable degrades to [] (oracle law)
pass
return terms
def _op_host(op: dict):
name = op.get("op")
if name == "domain_terms":
return {"terms": _fresh_domain_terms()}
raise RuntimeError(f"wr kernel issued unknown op: {name}")
def _call(sym: str, payload: dict):
lib = _load()
buf = ctypes.create_string_buffer(json.dumps(payload).encode())
keep = []
def trampoline(op_ptr, _user_data):
try:
op = json.loads(ctypes.string_at(op_ptr).decode())
result = _op_host(op)
except Exception as exc: # noqa: BLE001 — surfaced by the kernel
result = {"__host_error__": str(exc)[:500]}
data = json.dumps(result).encode()
b = ctypes.create_string_buffer(data)
keep[:] = [b] # previous buffer already consumed; keep current alive
return ctypes.cast(b, ctypes.c_void_p).value
cb = OP_CB(trampoline)
ptr = getattr(lib, sym)(ctypes.cast(buf, ctypes.c_void_p), cb, None)
if not ptr:
raise RuntimeError(f"{sym} returned null")
out = ctypes.string_at(ptr).decode()
lib._free(ptr)
doc = json.loads(out)
if not doc.get("ok"):
raise RuntimeError(doc.get("err", f"{sym} failed"))
return doc["value"]
# ── oracle-verbatim unwrapping ──────────────────────────────────────────
def assess(fact, target=None):
v = _call("wr_assess", {"fact": fact, "target": target})
answers = [(a[0], a[1], a[2]) for a in v["answers"]]
vetoed = tuple(v["vetoed"]) if v["vetoed"] is not None else None
return (v["verdict"], v["score"], answers, vetoed)
def _q(sym, low):
v = _call(sym, {"low": low})
return (v["score"], v["note"])
def q_pi_safety(low):
return _q("wr_q_pi_safety", low)
def q_honesty(low):
return _q("wr_q_honesty", low)
def q_material_reality(low):
return _q("wr_q_material_reality", low)
def q_root_cause(low):
return _q("wr_q_root_cause", low)
def q_respect(low):
return _q("wr_q_respect", low)
def q_assistant_goal(low):
return _q("wr_q_assistant_goal", low)
def q_durability(low):
return _q("wr_q_durability", low)
def q_epistemology(low):
return _q("wr_q_epistemology", low)
def q_expression(low):
return _q("wr_q_expression", low)
def verdict_label(verdict):
return _call("wr_verdict_label", {"verdict": verdict})
+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())
}
Loaded 100 of 182 files, more files were not shown because too many files have changed in this diff. Show more