Compare commits

...
84 Commits
Author SHA1 Message Date
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
conductor 4f3ef0f76b chore(release): auto-bump version to 0.228.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 17s
CI Pipeline / go-surface (push) Failing after 21s
Auto-version and Release / release (push) Successful in 33s
CI Pipeline / gitea-offline (push) Successful in 24s
CI Pipeline / lint (push) Failing after 1m9s
CI Pipeline / type-check (push) Failing after 57s
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 2m22s
CI Pipeline / canary (push) Canceled after 2m21s
Promotion record: (unknown)
2026-10-12 02:56:33 +01:00
Nick 3c70f3bd14 refactor(testing_mode): 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 15s
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
CI Pipeline / gitea-offline (push) Canceled after 23s
CI Pipeline / type-check (push) Canceled after 27s
CI Pipeline / unit-tests (push) Canceled after 39s
CI Pipeline / lint (push) Canceled after 39s
Auto-version and Release / release (push) Canceled after 41s
CI Pipeline / canary (push) Canceled after 41s
deploy / deploy (push) Canceled after 25s
2026-10-12 02:55:56 +01:00
Nick 626359e693 feat(testing_mode): port to FULL Rust — dichotomy wave 2026-10-12 02:55:56 +01:00
Nick 845cbfb3d0 chore(store): close self_modify dichotomy contract BUILDER-2026-10-12T01:53:04Z-7c7607-factory-wave (parity 1af27dc5, phase B 18052d5e)
CI Pipeline / best-practices (push) Successful in 11s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / rust-surface (push) Failing after 17s
CI Pipeline / go-surface (push) Failing after 23s
Auto-version and Release / release (push) Successful in 30s
CI Pipeline / gitea-offline (push) Successful in 22s
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 51s
CI Pipeline / unit-tests (push) Canceled after 51s
CI Pipeline / type-check (push) Canceled after 36s
CI Pipeline / canary (push) Canceled after 52s
deploy / deploy (push) Canceled after 55s
2026-10-12 02:55:05 +01:00
conductor 37feb10d37 chore(release): auto-bump version to 0.227.0 — earned by # (minor: converged release (push-to-master bump))
deploy / deploy (push) Skipped
CI Pipeline / best-practices (push) Successful in 9s
CI Pipeline / documentation (push) Successful in 5s
CI Pipeline / rust-surface (push) Failing after 15s
CI Pipeline / go-surface (push) Failing after 20s
Auto-version and Release / release (push) Successful in 30s
CI Pipeline / gitea-offline (push) Successful in 20s
CI Pipeline / type-check (push) Failing after 46s
CI Pipeline / lint (push) Failing after 1m0s
CI Pipeline / canary (push) Failing after 2m21s
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 3m31s
Promotion record: (unknown)
2026-10-12 02:51:27 +01:00
Nick 18052d5e1a refactor(self_modify): Phase B language-advantage pass — dichotomy wave
CI Pipeline / best-practices (push) Successful in 9s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / rust-surface (push) Failing after 16s
CI Pipeline / go-surface (push) Failing after 21s
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
Auto-version and Release / release (push) Canceled after 34s
CI Pipeline / type-check (push) Canceled after 15s
deploy / deploy (push) Canceled after 36s
CI Pipeline / gitea-offline (push) Canceled after 15s
Battery-pinned language-advantage pass over the parity port (32 frozen
rows green throughout, cargo test 11/11, clippy 0):
- BaselineVerdict: the compare_baseline truthiness lattice is a typed
  enum with exhaustive matches — classify/promote/reason are single
  construction points; the three verdict reasons are canonical
  &'static str instead of string literals duplicated at three branches
- ProposalOutcome: promote/merge-fail/discard results held typed until
  the JSON boundary (one into_value per shape), rollback_ref stays a
  passthrough Value through the enum (present-None stays None)
- zero-copy argv: _run borrows &[&str]; the oracle's per-call
  Vec<String> argv construction is gone (worktree path and merge
  message borrow into the slices)
- chars_tail: (stdout+stderr)[-2000:] is now one reverse traversal
  with bounded reversal instead of a full count() + skip() double pass;
  char-boundary safety pinned on multi-byte runes
- lazy rollback_ref read and the remove-worktree argv kept as
  closures, preserving the oracle's KeyError-after-side-effects order
- Phase B pins added to the battery: verdict lattice exhaustiveness +
  frozen reasons, outcome projections vs the three frozen pod shapes,
  chars_tail char-boundary/short/empty cases
2026-10-12 02:50:56 +01:00
Nick 1af27dc5d4 feat(self_modify): port to FULL Rust — dichotomy wave
Full name-preserving port of src/rune/self_modify.py to
src/rust/self_modify (RUST verdict, #2479 dichotomy tranche — 7/7 fns
incl. the _run subprocess seam, dichotomy audit match clean):
- _run: the subprocess seam is kernel-owned — Command spawn with piped
  reader threads, poll-based timeout→kill (TimeoutExpired raise-parity
  via the envelope), strict-UTF-8 text=True, universal-newline
  translation, signal exit codes
- propose_change: rev-parse gate ('unknown base' branch), mkdtemp
  0700 'selfmod-' + 8 chars of Python's tempfile alphabet, worktree
  add with err.strip()[:200] char-parity failure branch, no-clock `at` stamp as an argument
- run_gates: default pytest argv pinned, falsy/empty/null cmd falls
  back, (stdout+stderr)[-2000:] CHARACTER tail parity
- compare_baseline: full Python truthiness lattice on passed (missing
  key, null, 0/1 coercion), the three verdict reasons verbatim
- promote_or_discard: lazy rollback_ref read (KeyError fires only
  after merge/remove side effects, like the oracle), merge-fail
  err[:200] (no strip — oracle quirk preserved), discard ignores both
  git failures
- rollback: bad-ref err[:200] verbatim; monitor: .get(key, "")
  passes ANY present value through verbatim (int/null included)
- raise parity at the TYPE level: smod_loader._raise_like_oracle
  re-raises FileNotFoundError/PermissionError/TimeoutExpired/
  KeyError/TypeError from the envelope err encodings
- smod_loader mirrors broker_loader (doc-envelope FFI, cs_free_string,
  SMOD_RUST_LIB override; RUNE_SM_CORE belongs to shard_manager so the
  pin is RUNE_SMOD_CORE); facade delegation with python-oracle
  fallback preserved
- 32 frozen truth rows vs the LIVE oracle (clock frozen, tempfile
  pinned, deterministic git fixtures with literal seed SHA; setup as
  data + PATH-stubbed default gate command), deterministic across two
  generator runs; battery replays VERBATIM (cargo test 8/8) and live
  replay is 32/32 at BOTH facade and kernel level; pytest 6/6 both
  through the Rust core and with RUNE_SMOD_CORE=python
2026-10-12 02:50:56 +01:00
Nick 4f3d1f7fc1 chore(store): close concept_topology dichotomy contract BUILDER-2026-10-11T23:55:54Z-982aa3-factory-wave (parity 5844426b, phase B 02e0c649)
CI Pipeline / best-practices (push) Successful in 9s
CI Pipeline / documentation (push) Successful in 9s
CI Pipeline / rust-surface (push) Failing after 15s
CI Pipeline / go-surface (push) Failing after 20s
Auto-version and Release / release (push) Successful in 35s
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 / type-check (push) Canceled after 31s
CI Pipeline / canary (push) Canceled after 41s
CI Pipeline / unit-tests (push) Canceled after 45s
CI Pipeline / lint (push) Canceled after 46s
deploy / deploy (push) Canceled after 26s
2026-10-12 02:48:42 +01:00
conductor 2d31356417 chore(release): auto-bump version to 0.226.0 — earned by # (minor: converged release (push-to-master bump))
CI Pipeline / best-practices (push) Successful in 7s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / rust-surface (push) Failing after 16s
CI Pipeline / go-surface (push) Failing after 21s
Auto-version and Release / release (push) Successful in 32s
CI Pipeline / gitea-offline (push) Successful in 25s
CI Pipeline / type-check (push) Failing after 58s
CI Pipeline / lint (push) Failing after 1m13s
CI Pipeline / canary (push) Failing after 2m49s
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 3m46s
deploy / deploy (push) Canceled after 3m25s
Promotion record: (unknown)
2026-10-12 02:44:53 +01:00
Nick 25f7cb6db4 refactor(evidence_probe): Phase B language-advantage pass — dichotomy wave
CI Pipeline / best-practices (push) Successful in 8s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / rust-surface (push) Failing after 13s
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
CI Pipeline / type-check (push) Canceled after 11s
CI Pipeline / gitea-offline (push) Canceled after 11s
Auto-version and Release / release (push) Canceled after 32s
CI Pipeline / lint (push) Canceled after 25s
CI Pipeline / unit-tests (push) Canceled after 25s
CI Pipeline / canary (push) Canceled after 26s
deploy / deploy (push) Canceled after 35s
2026-10-12 02:44:22 +01:00
Nick 9ba51ba5e8 feat(evidence_probe): port to FULL Go — dichotomy wave 2026-10-12 02:44:22 +01:00
Nick 78a89eff6d chore(store): close contract BUILDER-2026-10-12T01:32:31Z-429396-factory-wave
CI Pipeline / best-practices (push) Successful in 9s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / rust-surface (push) Failing after 15s
CI Pipeline / go-surface (push) Failing after 20s
Auto-version and Release / release (push) Successful in 29s
CI Pipeline / gitea-offline (push) Successful in 22s
CI Pipeline / lint (push) Failing after 1m3s
CI Pipeline / type-check (push) Failing after 48s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
deploy / deploy (push) Canceled after 1m36s
CI Pipeline / unit-tests (push) Canceled after 1m42s
CI Pipeline / canary (push) Canceled after 1m41s
message_graph dichotomy contract — draft→approved→building→verifying→
closed in ONE process. Evidence: parity 1f7a2664, Phase B 7194f69a
(pushed gitea/master); battery 12 tests replay 68 frozen-oracle cases
VERBATIM; live parity 68/68 byte-identical; clippy 0; Python suite 9+9
under both RUNE_MG_CORE pins. Contract-walk script included (one-
process clobber-hazard law).
2026-10-12 02:42:06 +01:00
conductor cb70224f52 chore(release): auto-bump version to 0.225.0 — earned by # (minor: converged release (push-to-master bump))
CI Pipeline / rust-surface (push) Failing after 19s
CI Pipeline / go-surface (push) Failing after 25s
CI Pipeline / best-practices (push) Successful in 6s
CI Pipeline / documentation (push) Successful in 5s
Auto-version and Release / release (push) Successful in 32s
CI Pipeline / gitea-offline (push) Successful in 21s
CI Pipeline / lint (push) Failing after 1m10s
CI Pipeline / type-check (push) Failing after 57s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
deploy / deploy (push) Canceled after 3m6s
CI Pipeline / unit-tests (push) Canceled after 3m7s
CI Pipeline / canary (push) Canceled after 3m7s
Promotion record: (unknown)
2026-10-12 02:39:25 +01:00
Nick 02e0c649da refactor(concept_topology): Phase B language-advantage pass — dichotomy wave
CI Pipeline / best-practices (push) Successful in 10s
CI Pipeline / documentation (push) Successful in 6s
CI Pipeline / rust-surface (push) Failing after 21s
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 24s
CI Pipeline / type-check (push) Canceled after 6s
CI Pipeline / gitea-offline (push) Canceled after 2s
Auto-version and Release / release (push) Canceled after 35s
CI Pipeline / canary (push) Canceled after 25s
deploy / deploy (push) Canceled after 36s
CI Pipeline / unit-tests (push) Canceled after 31s
CI Pipeline / lint (push) Canceled after 31s
Language-advantage pass on the concept_topology Rust kernel (battery
green throughout, live parity re-verified 386/386):
- Suggestion lattice is a real enum (Cluster | NewTopic) with an
  exhaustive match producing the doc envelope — no stringly verdicts.
- Union-find is a HashMap-backed UnionFind struct with the entry API
  (O(1) root lookups replace linear scans) — root identity is order-
  independent, so determinism is unchanged.
- clusters/suggest/decay run zero-copy over BORROWED edge pairs
  (&String / &str all the way to the sorted output allocation); the
  fn-name dispatch helpers collapse into obj_pairs/as_arr borrowing
  accessors with elided lifetimes.
- seen/edge upserts are iterator chains (for_each + filter_map),
  sort_by_key(Reverse) for the stable biggest-first component order.
2026-10-12 02:36:18 +01:00
Nick 5844426bdd feat(concept_topology): port to FULL Rust — dichotomy wave
RUST verdict (dichotomy law, #2479 tranche, contract
BUILDER-2026-10-11T23:55:54Z-982aa3-factory-wave): concept_topology.py's
full learned-concept surface is ported to src/rust/concept_topology —
cofire (pair edges +1.0, sorted '|'-joined keys, first-sight topic
creation, seen counting), decay (weight *0.95 with the >= 0.5 keep
boundary), clusters (union-find connected components over edges >=
threshold, union by second-root repoint, biggest-component-first stable
order, sorted members), suggest (max-overlap cluster routing with
first-wins tie-break, new_topic fallback with the exact reason strings),
and new_topology (the empty seed).

Wiring: ct_loader.py ctypes loader mirroring broker_loader.py (doc-envelope
FFI, free via capability_scoring cs_free_string — no free symbol
re-export), facade delegation in concept_topology.py with RUNE_CT_CORE=python
fallback pin.

Truth: frozen_ct_truth.json — 36 cases from the live oracle (deep-copied
inputs, RUNE_CT_CORE=python pinned), replayed VERBATIM by the in-crate
branch battery. Battery caught one real port bug pre-landing (new cofire
edges got weight 0 instead of 1). Live parity: 386/386 checks (36 frozen +
613 fuzz ops + facade pin-vs-auto). Python tests green both modes; clippy 0.
2026-10-12 02:36:18 +01:00
Nick 0946141074 chore(store): close time_awareness dichotomy contract BUILDER-2026-10-12T01:07:29Z-99b745-factory-wave (parity 92107319, phase B 5cea323f, fix 480e25c7)
CI Pipeline / best-practices (push) Successful in 5s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / rust-surface (push) Failing after 15s
CI Pipeline / go-surface (push) Failing after 22s
Auto-version and Release / release (push) Successful in 34s
CI Pipeline / gitea-offline (push) Successful in 22s
CI Pipeline / lint (push) Failing after 1m10s
CI Pipeline / type-check (push) Failing after 58s
CI Pipeline / canary (push) Failing after 2m55s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
deploy / deploy (push) Canceled after 10m56s
CI Pipeline / unit-tests (push) Canceled after 10m57s
2026-10-12 02:27:58 +01:00
conductor cc921f72c9 chore(release): auto-bump version to 0.224.0 — earned by # (minor: converged release (push-to-master bump))
deploy / deploy (push) Skipped
CI Pipeline / best-practices (push) Successful in 9s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / rust-surface (push) Failing after 15s
CI Pipeline / go-surface (push) Failing after 21s
Auto-version and Release / release (push) Successful in 29s
CI Pipeline / gitea-offline (push) Successful in 21s
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 42s
CI Pipeline / type-check (push) Canceled after 31s
CI Pipeline / lint (push) Canceled after 46s
CI Pipeline / unit-tests (push) Canceled after 46s
Promotion record: (unknown)
2026-10-12 02:27:11 +01:00
Nick 7194f69a6d refactor(message_graph): 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 18s
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
CI Pipeline / gitea-offline (push) Canceled after 3s
CI Pipeline / lint (push) Canceled after 26s
CI Pipeline / unit-tests (push) Canceled after 26s
Auto-version and Release / release (push) Canceled after 36s
CI Pipeline / canary (push) Canceled after 26s
CI Pipeline / type-check (push) Canceled after 12s
deploy / deploy (push) Canceled after 29s
- Kind / EdgeType enums: the node-kind and edge-type lattices are
  compiler-checked variants with one wire spelling each (parse once in
  add, exhaustive as_str match) — the stringly membership arrays are
  gone.
- Cue enum: the render-cue lattice (Replaces / BranchedFrom /
  SupersededBy / None) classified once, rendered by exhaustive match;
  first-supersedes-edge-wins and falsy-superseded_by laws stay pinned by
  the frozen battery.
- Zero-copy walks: lineage/history return Vec<&Value> on the graph's
  lifetime — no per-node clone churn on the hot render paths; the FFI
  boundary clones once for the wire.
- Battery untouched: 12 tests replay the same 68 frozen-oracle cases
  VERBATIM, green throughout the pass; live parity re-verified
  byte-identical after the refactor; clippy 0.
2026-10-12 02:26:31 +01:00
Nick 1f7a266497 feat(message_graph): port to FULL Rust — dichotomy wave
parity: every public function of MessageGraph ported branch-for-branch —
_load (schema-v1 gate, corrupt/wrong-schema silent-empty, duplicate ids
last-wins with BOTH order entries kept, verbatim extra fields, non-dict
doc AttributeError, non-list nodes TypeError), save (arrival-order doc;
mkdir + json.dumps indent=2 FILE IO stays in the facade), add (the falsy
law: an explicit None parent still picks up the head but carries no edge,
a falsy "" parent stores "" and also carries no edge, ghost parents
allowed, kind checked before edge, Python-repr error strings), tool_call
(json.dumps content rendering stays in the facade — serialization
isomorphism), tool_result (existence check only — any node can parent a
result), revise (the root-revision quirk: old parent None substitutes
the current head, which at add-time is the old node itself), branch,
lineage (superseded skip, ghost superseded_by fallthrough, seen-set
cycle guard, missing-parent break), history (supersedes walk back to
root then forward via superseded_by — the FULL trail from any point on
it), migrate_flat (role/content defaults, existing-file seed), cue
(first supersedes/branches_from edge in iteration order wins, falsy
superseded_by is no cue).

Determinism seams: the uuid4 hex id and the _now() timestamp are kernel
ARGUMENTS (id/ts) — the frozen truth replays them verbatim. Graph wire:
{nodes: {id: node} insertion-ordered (serde_json preserve_order),
order: [ids]} — nodes ride as opaque Values so loaded extra fields
survive byte-verbatim. Errors ride typed (ValueError/KeyError/
AttributeError/TypeError) and the loader raises the matching Python
exception. The facade keeps the oracle's guard-clause ORDER (kind/edge/
existence checks BEFORE the uuid draw) so the id sequence is identical
under both cores. mg_loader mirrors broker_loader (doc-envelope,
cs_free_string, MG_RUST_LIB override); RUNE_MG_CORE=python pins the
fallback.

The freeze caught a real divergence first: Python 3.14's string-index
TypeError now reads "string indices must be integers, not 'str'" — the
kernel mirrors the runtime's wording, pinned by truth. Battery: 12
tests replay 68 frozen-oracle cases VERBATIM (include_str!); live
parity 68/68 byte-identical (truth regenerated through the kernel path
and diffed against the freeze); clippy 0; Python suite green under both
pins (9+9).
2026-10-12 02:26:30 +01:00
Nick 6786b4a922 chore(store): lexicon_evolution contract-walk script (one process, clobber-hazard law)
CI Pipeline / best-practices (push) Successful in 8s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / rust-surface (push) Failing after 17s
CI Pipeline / go-surface (push) Failing after 20s
Auto-version and Release / release (push) Successful in 30s
CI Pipeline / gitea-offline (push) Successful in 20s
CI Pipeline / lint (push) Failing after 1m6s
CI Pipeline / type-check (push) Failing after 56s
CI Pipeline / canary (push) Failing after 2m46s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
deploy / deploy (push) Canceled after 9m36s
CI Pipeline / unit-tests (push) Canceled after 9m27s
2026-10-12 02:17:10 +01:00
conductor 2f4c44ee15 chore(release): auto-bump version to 0.223.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 9s
CI Pipeline / go-surface (push) Failing after 13s
CI Pipeline / gitea-offline (push) Successful in 18s
Auto-version and Release / release (push) Successful in 26s
CI Pipeline / type-check (push) Failing after 43s
CI Pipeline / lint (push) Failing after 52s
CI Pipeline / canary (push) Failing after 2m4s
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
Promotion record: (unknown)
2026-10-12 02:10:32 +01:00
Nick 25e1915977 chore(store): close lexicon_evolution dichotomy contract BUILDER-2026-10-11T23:55:06Z-3750e3-factory-wave (parity b704f875, phase B b205ea94)
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 / lint (push) Canceled after 5s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
CI Pipeline / canary (push) Canceled after 5s
CI Pipeline / best-practices (push) Canceled after 6s
CI Pipeline / unit-tests (push) Canceling
CI Pipeline / go-surface (push) Canceling
CI Pipeline / rust-surface (push) Canceling
Auto-version and Release / release (push) Canceled after 22s
deploy / deploy (push) Canceled after 26s
2026-10-12 02:09:37 +01:00
Nick b205ea9452 refactor(lexicon_evolution): Phase B language-advantage pass — dichotomy wave
Language-advantage pass on the FULL Rust port (battery stays green, replayed
VERBATIM throughout):
- the coherence lattice is a LexiconVerdict enum (Conflict/Consistent/Novel)
  with an exhaustive as_str match — no string soup
- the promotion decision is a Promotion enum (Eligible/Parked) behind a
  single into_pair boundary
- the word scanner is a zero-copy split-based iterator over the lowered
  text: borrowed slices, no per-token String allocation; only surviving
  terms are cloned once (doc-frequency, distinctiveness, grounding)
- stop/framing tables resolve through OnceLock HashSets (O(1) lookups vs
  linear scan; NEGATION stays a table — it is a substring test, not a word
  test)
- grounding folds the hit counter through try_fold with early exit
- doc_frequencies folds the corpus stem table in one pass; grounded is
  generic over AsRef<str> so the promote path hands it borrowed &str
  slices directly

cargo test 7/7 (frozen-truth battery VERBATIM), clippy 0, live parity
facade 54/54 + kernel 76/76, Python tests 30/30 (kernel active + pinned
fallback).
2026-10-12 02:08:12 +01:00
Nick b704f87549 feat(lexicon_evolution): port to FULL Rust — dichotomy wave
Full name-preserving port of src/rune/lexicon_evolution.py to
src/rust/lexicon_evolution (RUST verdict, #2479 dichotomy tranche — 7/7 fns,
dichotomy audit match clean):
- content_words: regex-equivalent [a-z]{4,} scanner over the lowered text,
  stop/framing tables 1:1, (text or "") coercion
- distinctive_terms: stem-aware document frequency, df < max(total*0.05, 1.5)
- shared_distinctive: stem-matched share over raw existing values (None → "")
- coherence: conflict/consistent/novel lattice — negation is a SUBSTRING test
  on the lowered fact, the shared-term test is the padded " term " form
- grounded: conjunctive stem hits with the min_distinct counter
- _stem: suffix-rule table quirk-for-quirk (migrate→migrat vs migration→migr;
  retroactive→retroactiv vs retroactively→retroactive) + y→i fold
- promote_eligible: strength gate → empty block → distinctive share → no
  signal → grounded/parked, with the oracle's f-string reasons byte-for-byte
  (py_num_display: ints stay ints, integral floats keep .0)

FFI doc-envelope law ({ok, value|err}), no le_free_string export (siblings
share the process; callers free via capability_scoring cs_free_string).
rune/le_loader.py mirrors broker_loader.py; facade delegation with
RUNE_LE_CORE=python pin, _py_ variants kept verbatim as the load-bearing
fallback. Set-valued fns cross the FFI SORTED; the facade restores set types.

Parity: 76 frozen truth cases (every public fn, every branch) frozen via
scripts/gen_lexicon_evolution_truth.py against the live oracle with
RUNE_LE_CORE=python pinned; battery replays VERBATIM 7/7; live replay
facade 54/54 + kernel 76/76; clippy 0; test_lexicon_contract 15/15 both
modes (kernel active + pinned fallback); test_governance_quartet 15/15;
pre-existing failures in test_constitutional_adversarial (7F) and
test_property_invariants (collection) proven unrelated via stash-dance.
2026-10-12 02:08:12 +01:00
conductor 53b788f3c6 chore(release): auto-bump version to 0.222.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 5s
CI Pipeline / rust-surface (push) Failing after 13s
CI Pipeline / go-surface (push) Failing after 17s
Auto-version and Release / release (push) Successful in 24s
CI Pipeline / gitea-offline (push) Successful in 19s
CI Pipeline / lint (push) Failing after 57s
CI Pipeline / type-check (push) Failing after 45s
CI Pipeline / canary (push) Failing after 2m15s
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 5m6s
Promotion record: (unknown)
2026-10-12 02:05:13 +01:00
Nick 480e25c750 fix(tests): contain RUNE_MEMORY_DIR leak in openai_compat transport test — dichotomy wave
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 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 10s
CI Pipeline / gitea-offline (push) Canceled after 10s
Auto-version and Release / release (push) Canceled after 25s
CI Pipeline / unit-tests (push) Canceled after 26s
CI Pipeline / lint (push) Canceled after 26s
CI Pipeline / canary (push) Canceled after 26s
deploy / deploy (push) Canceled after 26s
2026-10-12 02:00:44 +01:00
Nick 5cea323f8b refactor(time_awareness): Phase B language-advantage pass — dichotomy wave 2026-10-12 02:00:44 +01:00
Nick 92107319d2 feat(time_awareness): port to FULL Rust — dichotomy wave 2026-10-12 02:00:44 +01:00
Nick 36ac3de022 chore(store): close shard_manager dichotomy contract BUILDER-2026-10-11T22:19:08Z-e32317-factory-wave (parity 7d049cc4, phase B 670d55ff)
Auto-version and Release / release (push) Successful in 18s
CI Pipeline / best-practices (push) Successful in 10s
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 19s
CI Pipeline / type-check (push) Failing after 42s
CI Pipeline / lint (push) Failing after 55s
CI Pipeline / canary (push) Failing after 3m36s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
deploy / deploy (push) Canceled after 10m10s
CI Pipeline / unit-tests (push) Canceled after 10m11s
2026-10-12 01:54:24 +01:00
Nick e2a9bdd878 chore(store): close task_gate dichotomy contract BUILDER-2026-10-11T22:50:33Z-a08719-factory-wave (parity b20a74ba, phase B fa72a60d)
Auto-version and Release / release (push) Successful in 26s
CI Pipeline / rust-surface (push) Failing after 16s
CI Pipeline / go-surface (push) Failing after 19s
CI Pipeline / best-practices (push) Successful in 4s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / gitea-offline (push) Successful in 19s
CI Pipeline / lint (push) Failing after 59s
CI Pipeline / type-check (push) Failing after 42s
CI Pipeline / canary (push) Failing after 2m14s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
deploy / deploy (push) Canceled after 3m35s
CI Pipeline / unit-tests (push) Canceled after 3m26s
2026-10-12 01:50:57 +01:00
conductor ad4f32aca5 chore(release): auto-bump version to 0.221.0 — earned by # (minor: converged release (push-to-master bump))
deploy / deploy (push) Skipped
CI Pipeline / best-practices (push) Successful in 10s
CI Pipeline / rust-surface (push) Failing after 13s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / go-surface (push) Failing after 17s
Auto-version and Release / release (push) Successful in 26s
CI Pipeline / gitea-offline (push) Successful in 21s
CI Pipeline / type-check (push) Failing after 45s
CI Pipeline / lint (push) Failing after 57s
CI Pipeline / canary (push) Failing after 2m12s
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 9m5s
Promotion record: (unknown)
2026-10-12 01:41:53 +01:00
Nick fa72a60d95 refactor(task_gate): Phase B language-advantage pass — dichotomy wave
CI Pipeline / best-practices (push) Successful in 7s
CI Pipeline / documentation (push) Successful in 7s
CI Pipeline / rust-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 / go-surface (push) Canceled after 17s
CI Pipeline / canary (push) Canceled after 20s
CI Pipeline / type-check (push) Canceled after 11s
CI Pipeline / gitea-offline (push) Canceled after 10s
Auto-version and Release / release (push) Canceled after 27s
deploy / deploy (push) Canceled after 6s
CI Pipeline / unit-tests (push) Canceled after 25s
CI Pipeline / lint (push) Canceled after 26s
2026-10-12 01:41:07 +01:00
Nick b20a74badb feat(task_gate): port to FULL Rust — dichotomy wave 2026-10-12 01:41:06 +01:00
Nick f9df935605 chore(store): close outcome_ledger dichotomy contract BUILDER-2026-10-12T00:27:30Z-df62ef-factory-wave (parity bb7251e1, phase B 9436e6ca)
CI Pipeline / documentation (push) Successful in 9s
CI Pipeline / best-practices (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 23s
CI Pipeline / gitea-offline (push) Successful in 21s
CI Pipeline / type-check (push) Failing after 44s
CI Pipeline / lint (push) Failing after 56s
CI Pipeline / canary (push) Failing after 2m8s
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 7m6s
deploy / deploy (push) Canceled after 6m56s
2026-10-12 01:34:17 +01:00
conductor 132cf1e4e1 chore(release): auto-bump version to 0.220.0 — earned by # (minor: converged release (push-to-master bump))
deploy / deploy (push) Skipped
CI Pipeline / best-practices (push) Successful in 9s
CI Pipeline / rust-surface (push) Failing after 13s
CI Pipeline / documentation (push) Successful in 5s
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 / lint (push) Failing after 52s
CI Pipeline / type-check (push) Failing after 40s
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
CI Pipeline / unit-tests (push) Canceled after 5m41s
Promotion record: (unknown)
2026-10-12 01:28:31 +01:00
Nick 670d55ff62 refactor(shard_manager): Phase B language-advantage pass — dichotomy wave
CI Pipeline / best-practices (push) Successful in 8s
CI Pipeline / documentation (push) Successful in 5s
CI Pipeline / rust-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 / type-check (push) Canceled after 11s
CI Pipeline / go-surface (push) Canceled after 20s
CI Pipeline / canary (push) Canceled after 21s
CI Pipeline / gitea-offline (push) Canceled after 3s
Auto-version and Release / release (push) Canceled after 34s
CI Pipeline / lint (push) Canceled after 26s
CI Pipeline / unit-tests (push) Canceled after 26s
deploy / deploy (push) Canceled after 31s
2026-10-12 01:28:04 +01:00
Nick 7d049cc42c feat(shard_manager): port to FULL Rust — dichotomy wave 2026-10-12 01:28:04 +01:00
Nick e49eefe926 chore(store): close toolchain dichotomy contract BUILDER-2026-10-11T22:03:40Z-7aeb7a-factory-wave (parity f83396ee, phase B 61de7c5b)
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 22s
CI Pipeline / gitea-offline (push) Successful in 19s
CI Pipeline / type-check (push) Failing after 41s
CI Pipeline / lint (push) Failing after 54s
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 7m15s
CI Pipeline / unit-tests (push) Canceled after 7m21s
2026-10-12 01:20:40 +01:00
conductor cd4c0beb2f chore(release): auto-bump version to 0.219.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 11s
CI Pipeline / go-surface (push) Failing after 15s
Auto-version and Release / release (push) Successful in 22s
CI Pipeline / gitea-offline (push) Successful in 18s
CI Pipeline / type-check (push) Failing after 41s
CI Pipeline / lint (push) Failing after 52s
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 4m21s
Promotion record: (unknown)
2026-10-12 01:16:26 +01:00
Nick 9436e6ca26 refactor(outcome_ledger): Phase B language-advantage pass — dichotomy wave
CI Pipeline / best-practices (push) Successful in 7s
CI Pipeline / documentation (push) Successful in 8s
CI Pipeline / rust-surface (push) Failing after 11s
CI Pipeline / go-surface (push) Failing after 15s
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 23s
CI Pipeline / type-check (push) Canceled after 17s
CI Pipeline / canary (push) Canceled after 21s
CI Pipeline / unit-tests (push) Canceled after 25s
CI Pipeline / lint (push) Canceled after 26s
CI Pipeline / gitea-offline (push) Canceled after 18s
deploy / deploy (push) Canceled after 16s
2026-10-12 01:03:37 +01:00
Nick bb7251e1aa feat(outcome_ledger): port to FULL Rust — dichotomy wave 2026-10-12 01:03:37 +01:00
Nick 0cd50f46ed chore(store): close auto_context dichotomy contract BUILDER-2026-10-11T21:57:54Z-52c500-factory-wave (parity f129a6a4, phase B 7fbbdeba)
CI Pipeline / rust-surface (push) Failing after 13s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / go-surface (push) Failing after 16s
CI Pipeline / documentation (push) Successful in 7s
Auto-version and Release / release (push) Successful in 27s
CI Pipeline / gitea-offline (push) Successful in 16s
CI Pipeline / lint (push) Failing after 56s
CI Pipeline / type-check (push) Failing after 45s
CI Pipeline / canary (push) Failing after 2m4s
deploy / deploy (push) Failing after 15m28s
CI Pipeline / unit-tests (push) Failing after 16m9s
CI Pipeline / required-checks (push) Failing after 2s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Failing after 5s
2026-10-12 00:44:35 +01:00
conductor 2e5ba22249 chore(release): auto-bump version to 0.218.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 15s
CI Pipeline / go-surface (push) Failing after 18s
Auto-version and Release / release (push) Successful in 25s
CI Pipeline / gitea-offline (push) Successful in 19s
CI Pipeline / lint (push) Failing after 56s
CI Pipeline / type-check (push) Failing after 43s
CI Pipeline / canary (push) Failing after 2m14s
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 3m15s
Promotion record: (unknown)
2026-10-12 00:41:12 +01:00
Nick 7fbbdeba71 refactor(auto_context): Phase B language-advantage pass — dichotomy wave
CI Pipeline / best-practices (push) Successful in 8s
CI Pipeline / rust-surface (push) Failing after 18s
CI Pipeline / documentation (push) Successful in 10s
CI Pipeline / go-surface (push) Failing after 19s
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 21s
CI Pipeline / type-check (push) Canceled after 2s
CI Pipeline / gitea-offline (push) Canceled after 2s
Auto-version and Release / release (push) Canceled after 30s
deploy / deploy (push) Canceled after 31s
CI Pipeline / lint (push) Canceled after 26s
CI Pipeline / unit-tests (push) Canceled after 26s
Language-advantage pass on the FULL Go port (truth battery stays green,
now replayed through the parallel path):
- build_auto_context fans the oracle's four independent IO calls (warm
  route, ledger read, memory recall, always digest) out to goroutines —
  wall time is the slowest call, not the sum of four subprocesses;
  results assembled in the oracle's exact field order so output is
  byte-identical to the serial Python semantics
- subprocess + clock seams promoted to interfaces (CommandRunner, Clock)
  with RunnerFunc/ClockFunc adapters and compile-time assertions
  (var _ CommandRunner = RunnerFunc(defaultRunScript), var _ Clock = ...);
  the replay seam swap now rides the same interfaces the embedded callers
  use
- save_context tmp file made per-writer (os.CreateTemp + atomic rename):
  identical serial semantics, but concurrent hooks no longer clobber the
  oracle's shared .tmp path (its fixed tmp races ENOENT under concurrency)
- Phase B battery (phaseb_test.go): parallel-build parity vs frozen truth,
  counting-decorator seam swap (3 subprocess runs per build), 8-goroutine
  concurrent builds + concurrent hooks — all race-clean under -race
2026-10-12 00:40:15 +01:00
Nick f129a6a469 feat(auto_context): port to FULL Go — dichotomy wave
Full name-preserving port of src/rune/auto_context.py to src/go/auto_context
(GO verdict, #2479 dichotomy tranche — 8/8 fns, dichotomy audit match clean):
- run_warm_router / run_memory_recall / run_always_digest: subprocess seam
  (RunScript) with the oracle's exact degrade paths (nonzero exit, timeout,
  bad JSON, empty stdout → None; non-list recall payload → []), budget
  truncation data[:budget]
- load_active_tasks: persistence-ledger filter (open/in_progress) with every
  best-effort degrade branch (missing file, malformed JSON, non-dict ledger,
  non-dict task → [])
- build_auto_context: stopword table 1:1, len>4 + isalpha + first-5 query
  extraction, 200-rune preview truncation, Python truthiness on every
  optional payload, frozen-clock isoformat
- save_context: atomic tmp+rename, json.dumps(indent=2, ensure_ascii)
  byte-parity via pyjson.go
- handle_hook: message/text/str(data) fallback chain (Python dict repr
  included), both best-effort error dicts verbatim ('list' object has no
  attribute 'get' / object of type 'int' has no len())
- CLI main(): argparse-compatible build/show/hook (--event/--data/--json),
  byte-parity stdout, exit codes (invalid choice/missing cmd → 2)
- PLUGIN manifest pinned (pluginManifest)
- Battery: 52 frozen truth rows vs the LIVE oracle (clock frozen, env-driven
  stub scripts echo the exact message/query the port hands the seam),
  deterministic across two generator runs; replay gate + TestTruthParity
2026-10-12 00:40:15 +01:00
conductor 72f70aa47a chore(release): auto-bump version to 0.217.0 — earned by # (minor: converged release (push-to-master bump))
CI Pipeline / documentation (push) Successful in 7s
CI Pipeline / best-practices (push) Successful in 10s
CI Pipeline / rust-surface (push) Failing after 14s
CI Pipeline / go-surface (push) Failing after 18s
CI Pipeline / gitea-offline (push) Successful in 20s
Auto-version and Release / release (push) Successful in 27s
CI Pipeline / lint (push) Failing after 58s
CI Pipeline / type-check (push) Failing after 49s
CI Pipeline / canary (push) Failing after 2m21s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
deploy / deploy (push) Canceled after 2m20s
CI Pipeline / unit-tests (push) Canceled after 2m41s
Promotion record: (unknown)
2026-10-12 00:38:05 +01:00
Nick 61de7c5b74 refactor(toolchain): Phase B language-advantage pass — dichotomy wave
CI Pipeline / best-practices (push) Successful in 9s
CI Pipeline / documentation (push) Successful in 5s
CI Pipeline / rust-surface (push) Canceled after 15s
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 0s
CI Pipeline / lint (push) Canceled after 16s
CI Pipeline / type-check (push) Canceled after 3s
CI Pipeline / unit-tests (push) Canceled after 16s
CI Pipeline / go-surface (push) Canceled after 16s
CI Pipeline / canary (push) Canceled after 16s
Auto-version and Release / release (push) Canceled after 26s
deploy / deploy (push) Canceled after 7s
Resolution lattice becomes a real enum (KeepExisting/ChangeMajor/
InsufficientEvidence) with exhaustive as_str/parse matches at the JSON
boundary; DecisionRecord is a struct with RecordFields input (removes
the 12-arg constructor signature and the too_many_arguments escape
hatch); resolve_toolchain returns Result; borrows stay zero-copy until
the boundary. Oracle-faithful quirk kept: resolution stays a free-form
string on the constructor wire (default "") — only resolver outputs
are lattice-guaranteed. Battery 7 tests / 43 frozen cases + lattice
round-trip GREEN; clippy --all-targets -D warnings 0; live parity
613/613; facade delegation unchanged (RUNE_TC_CORE pin).
2026-10-12 00:37:27 +01:00
Nick f83396eecd feat(toolchain): port to FULL Rust — dichotomy wave
Contract BUILDER-2026-10-11T22:03:40Z-7aeb7a-factory-wave (#2479 tranche).
Faithful port of src/rune/toolchain.py: ToolchainConstraints known/unknown
classification, DecisionRecord normalization, _major_change,
resolve_toolchain (INSUFFICIENT_EVIDENCE gate, KEEP_EXISTING,
CHANGE_MAJOR, greenfield recommendation), CONSTRAINT_KEYS vocabulary.
Doc-envelope FFI (tl_* symbols, free via cs_free_string), ctypes loader
(tl_loader.py, TC_RUST_LIB override), facade delegation with
RUNE_TC_CORE=python pin. Frozen oracle truth: 43 cases across every
public function and branch (frozen_tc_truth.json), replayed VERBATIM by
branch_battery.rs (6 tests). Live parity 613/613 (truth + 570 fuzz,
seed 2479).
2026-10-12 00:37:26 +01:00
Nick 8289d896e1 chore(store): close drift_cache dichotomy contract (36d7c6) — verifying+closed, SHA evidence
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 16s
Auto-version and Release / release (push) Successful in 24s
CI Pipeline / gitea-offline (push) Successful in 19s
CI Pipeline / type-check (push) Failing after 43s
CI Pipeline / lint (push) Failing after 53s
CI Pipeline / canary (push) Failing after 2m7s
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 5m6s
deploy / deploy (push) Canceled after 5m11s
2026-10-12 00:32:34 +01:00
conductor 5f6c23ff20 chore(release): auto-bump version to 0.216.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 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 25s
CI Pipeline / gitea-offline (push) Successful in 18s
CI Pipeline / lint (push) Failing after 53s
CI Pipeline / type-check (push) Failing after 42s
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 6m1s
Promotion record: (unknown)
2026-10-12 00:26:28 +01:00
Nick 5723de5b17 refactor(drift_cache): Phase B language-advantage pass — dichotomy wave
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 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 27s
CI Pipeline / type-check (push) Canceled after 10s
CI Pipeline / canary (push) Canceled after 21s
CI Pipeline / lint (push) Canceled after 26s
CI Pipeline / gitea-offline (push) Canceled after 14s
deploy / deploy (push) Canceled after 33s
CI Pipeline / unit-tests (push) Canceled after 26s
RockBoost enum with exhaustive classify/boost match replaces the
string-soup status branch (the STORED rock_status deliberately stays a
pass-through Value — the oracle forwards any caller string verbatim, so
a closed stored lattice would be a parity break, not an advantage);
Admission enum shapes the add() gate; priority_of unifies the entry and
sync-row forms so each row coerces ONCE through borrowed fields instead
of building an intermediate JSON object and re-parsing it; get_stats
aggregates in a single iterator fold; FFI errors are a typed FfiError
enum (NullPtr/Utf8/Json) behind the unchanged doc-envelope err string.
Battery 8/8 green throughout; live parity 48/48 facade + 45/45 kernel
re-verified against the rebuilt cdylib; clippy 0.
2026-10-12 00:26:02 +01:00
Nick 501ca712b0 feat(drift_cache): port to FULL Rust — dichotomy wave
parity: every public function of DriftCache ported branch-for-branch —
compute_priority (log1p wash scaling capped at 1.0, rock/established
boosts, Python truthiness coercion of None/0/0.5 fields), qualifies_
for_cache (the wash>=1 OR imp>=0.8 admission boundary), add (admission
gate, stamping with verbatim rock_status incl. explicit None, the
strict >200 eviction trigger), access (int/float access_count
preservation, miss is a silent no-op), contains/get_priority (0.0 for
unknown ids), update_from_store (row[0] or 0 / row[1] or 0.5 /
row[2] or 'washable' coercions, deleted-row removal), get_stats
(empty lattice, missing-priority 0.0, missing rock_status is
washable). Dict-order law: entries are an insertion-ordered map
(serde_json preserve_order) and the eviction sort is stable — ties
evict in insertion order, exactly like the oracle's dict + sorted.
No-clock law: now is a kernel argument; the sqlite read and the cache
FILE IO stay in the facade. dc_loader mirrors broker_loader (doc-
envelope FFI, cs_free_string, DC_RUST_LIB override, int<->str entry-id
key round-trip); facade delegation with RUNE_DC_CORE=python pin and
the legacy _ms_core chain preserved as second fallback. Frozen-truth
freeze caught a REAL kernel diverge: serde_json's default float
parser is 1 ulp off on priority floats — fixed-first with the
float_roundtrip feature. Battery: 8 tests replay 48 frozen-oracle
cases VERBATIM (include_str!). Live parity 48/48 facade + 45/45
kernel (load group is facade-only); clippy 0.
2026-10-12 00:26:02 +01:00
conductor 181954fcf3 chore(release): auto-bump version to 0.215.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 7s
CI Pipeline / rust-surface (push) Failing after 12s
CI Pipeline / go-surface (push) Failing after 16s
CI Pipeline / gitea-offline (push) Successful in 19s
Auto-version and Release / release (push) Successful in 22s
CI Pipeline / type-check (push) Failing after 44s
CI Pipeline / lint (push) Failing after 54s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / canary (push) Canceled after 57s
CI Pipeline / unit-tests (push) Canceled after 1m1s
Promotion record: (unknown)
2026-10-12 00:25:04 +01:00
Nick 8e7166b848 chore(contract): close BUILDER-2026-10-11T17:04:22Z-b2f9fe-factory-wave — programme_continuation dichotomy wave
CI Pipeline / best-practices (push) Successful in 6s
CI Pipeline / documentation (push) Successful in 7s
CI Pipeline / rust-surface (push) Failing after 13s
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 16s
CI Pipeline / type-check (push) Canceled after 3s
CI Pipeline / unit-tests (push) Canceled after 16s
CI Pipeline / go-surface (push) Canceled after 16s
CI Pipeline / canary (push) Canceled after 15s
CI Pipeline / gitea-offline (push) Canceled after 2s
Auto-version and Release / release (push) Canceled after 24s
deploy / deploy (push) Canceled after 26s
Closure record: verifying+closed with SHA evidence (parity 9f1c593a,
Phase B 9561bd02). Battery 8/8, clippy 0, live parity 546/546, Python
tests 65/65.
2026-10-12 00:24:40 +01:00
Nick 9561bd02f3 refactor(programme_continuation): Phase B language-advantage pass — dichotomy wave
Behavior-neutral language-advantage pass; the frozen battery (8/8) and
live parity (546/546) prove zero behavior change:

* Decision lattices as enums with exhaustive matches: StageState (7
  states + Other arm preserving the oracle's leniency for unknown runner
  states), Verdict (the full 7-verdict stop lattice borrowing stage ids
  zero-copy from the programme), NextAction/Action dispatch (ONLY
  fallback breaks the driver loop — oracle semantics pinned), and the
  contract Phase lattice at the stage-runner decision points.
* The canary's rank chain is now an iterator-composed verdict pipeline
  (complete → eligible → waiting → fallback → blocked → approval →
  terminal → UnknownInterruption tail), each rank an Option over the
  lattice instead of nested if/return-JSON.
* eligible_stages/blocked/complete counting via iterator filter chains
  over the StageState lattice; duplicate-continuation check goes through
  the lattice, not string equality.
* Verdict::to_value renders verdict/next_action/reason in one exhaustive
  match (Python list-repr fidelity for the failed list retained).

Contract BUILDER-2026-10-11T17:04:22Z-b2f9fe-factory-wave: verifying.
2026-10-12 00:20:46 +01:00
Nick 9f1c593a8a feat(programme_continuation): port to FULL Rust — dichotomy wave
FULL parity port of src/rune/programme_continuation.py (349 lines, 11 fns,
dichotomy wave #2479 tranche). The kernel owns ALL of it: the 15M
continuation canary (all 7 verdicts), eligible_stages, stage to_dict,
save-doc build, load, run_programme, _programme_base, the
scaffolding stage runner (draft→approved→building→verifying→closed with
truthful failed-path checkpoints), and run_construction_programme.

The seams the oracle leaves to the caller cross the FFI as JSON ops
served by pc_loader.OpsProxy against the live Python objects: the
Scaffolding interface (load/save/contract/transition/close), the event
bridge, the work callable, the stage runner, and the clock. Doc-envelope
FFI; no free-string export (cs_free_string via capability_scoring).
Facade delegation with RUNE_PC_CORE=python pin (strangler law).

Frozen truth: 46 cases vs the LIVE oracle (RUNE_PC_CORE=python pinned)
covering every function and branch, embedded verbatim
(frozen_pc_truth.json) and replayed by the cfg(test) branch battery —
interface-driven cases replay the oracle's RECORDED OPS verbatim
(name+args+order). Battery caught the T5.15 programme-continuation wire
inside the real close() (host-side effects tagged host_internal).

Verified: battery 8/8 green, clippy 0, live parity 546/546 (46 truth
cases re-executed oracle-vs-kernel + 500 fuzz, seed 2479), Python tests
65/65 green (programme continuation/wiring/gates/interface/event_wait).
2026-10-12 00:20:45 +01:00
conductor 7330a4f628 chore(release): auto-bump version to 0.214.0 — earned by # (minor: converged release (push-to-master bump))
deploy / deploy (push) Skipped
CI Pipeline / best-practices (push) Successful in 21s
CI Pipeline / documentation (push) Successful in 20s
CI Pipeline / rust-surface (push) Failing after 29s
CI Pipeline / go-surface (push) Failing after 35s
CI Pipeline / gitea-offline (push) Successful in 34s
Auto-version and Release / release (push) Successful in 50s
CI Pipeline / type-check (push) Failing after 1m11s
CI Pipeline / lint (push) Failing after 1m41s
CI Pipeline / canary (push) Failing after 2m46s
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 18m20s
Promotion record: (unknown)
2026-10-12 00:06:16 +01:00
Nick 1e80b88a34 refactor(behavior): Phase B language-advantage pass — dichotomy wave
CI Pipeline / best-practices (push) Successful in 24s
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 / documentation (push) Canceled after 7s
CI Pipeline / lint (push) Canceled after 32s
CI Pipeline / unit-tests (push) Canceled after 32s
CI Pipeline / go-surface (push) Canceled after 33s
deploy / deploy (push) Canceling
CI Pipeline / rust-surface (push) Canceled after 33s
CI Pipeline / canary (push) Canceled after 34s
Auto-version and Release / release (push) Canceled after 45s
language advantage: Outcome/CorrectionState/HabitState enums replace the
string soup — the FFI boundary parses the outcome once and the math
matches exhaustively; correction state serializes at the JSON boundary
via exhaustive as_str; the candidate→active promotion edge is the only
legal HabitState transition and unknown states carry their original
string through (oracle never demotes). Zero-copy row predicates keep
&str borrows end to end. Battery green (10), live parity 14/14, clippy 0.
2026-10-12 00:05:39 +01:00
Nick 4e1eee7d9e feat(behavior): port to FULL Rust — dichotomy wave
RUST verdict (dichotomy law, #2479 tranche): behavior.py's FULL decision
surface is ported to src/rust/behavior — the reinforce decision math
(habit_reinforce: activation/success/failure counting, confidence
min(1.0, 0.5 + rate*0.5), candidate→active promotion at min_activations;
correction_reinforce: success +0.1 / failure −0.3 floor/ceiling, evidence
counting, active/archived threshold at 0.3), the match_context per-row
predicates (scope-pattern regex IGNORECASE with invalid-regex skip,
domain case-insensitive substring, confidence > 0.3 gates), the
scope_tags parse fallback, the PLUGIN descriptor (frozen data), and the
db-path resolution. IO stays Python: SQLite I/O + row ordering (the
crate links no sqlite), wall-clock now, argparse CLI.

Wiring: bh_loader.py ctypes (doc-envelope, cs_free_string — no free
symbol re-export), facade delegation in behavior.py with
RUNE_BEHAVIOR_CORE=python fallback pin (honored in the loader probe);
the evidence_provenance ep_loader path remains as the pin oracle.

Truth: scripts/gen_behavior_truth.py freezes 30 live-oracle cases (add
shapes, match branch matrix, reinforce matrix with promotion/archival/
floor/ceiling, list filters, stats, PLUGIN) embedded verbatim as
bh_truth.json; battery 10 tests replay it (clippy 0).

Parity: 100% live (14/14 check groups over the frozen shapes + list/
stats leaves, rust facade vs pinned-python, 0 divergences); 13/13 port
+ shim wiring tests; full suite failure set IDENTICAL to the clean tree
(95 pre-existing, 0 new).

Contract BUILDER-2026-10-11T16:27:58Z-92bb54-factory-wave (building).
2026-10-12 00:05:38 +01:00
conductor 8626d33ffa chore(release): auto-bump version to 0.213.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 14s
CI Pipeline / go-surface (push) Failing after 20s
Auto-version and Release / release (push) Successful in 30s
CI Pipeline / gitea-offline (push) Successful in 22s
CI Pipeline / lint (push) Failing after 58s
CI Pipeline / type-check (push) Failing after 50s
CI Pipeline / canary (push) Failing after 2m34s
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 7m56s
Promotion record: (unknown)
2026-10-11 23:57:42 +01:00
Nick 916c6c478a chore(store): close prompt_sync dichotomy contract BUILDER-2026-10-11T16:13:44Z-824ddf-factory-wave (parity 30d3f84a, phase B 572e48f2; live parity 182/182 byte-identical, battery 123 cases VERBATIM, clippy 0)
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 19s
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 27s
CI Pipeline / type-check (push) Canceled after 16s
Auto-version and Release / release (push) Canceled after 29s
deploy / deploy (push) Canceled after 15s
CI Pipeline / gitea-offline (push) Canceled after 21s
CI Pipeline / lint (push) Canceled after 31s
CI Pipeline / unit-tests (push) Canceled after 30s
2026-10-11 23:57:17 +01:00
Nick 7d270f2098 refactor(prompt_sync): Phase B language-advantage pass — dichotomy wave
language advantage: the prompt assembly is a Section enum lattice —
headers decided by one exhaustive match (Header/Voice/Constitution/
Defining/Operating/Human/Pipeline/Footer), replacing scattered
format! strings; the Python-falsy empty-body check lives in a single
candidate-construction site instead of five if-lets.
language advantage: sort_by_importance_desc is zero-copy — it
returns Vec<&Value> borrows instead of cloning the whole entry list
on every section build.
language advantage: section builders are iterator chains
(enumerate/take/filter_map/then) with a shared clean_entry_text
deduplicating the citation-strip path; get_defining_principles is a
scan with the current-section carried as iterator state; row mapping
zips the key lattice over the sqlite columns.
Battery green throughout (123 frozen cases replayed VERBATIM), live
parity re-proven byte-identical after rebuild (182/182 keys), clippy 0.
2026-10-11 23:23:43 +01:00
Nick f7d51c4834 feat(prompt_sync): port to FULL Rust — dichotomy wave
FULL parity port of src/rune/prompt_sync.py (379 lines, 14 fns) to
src/rust/prompt_sync: entry reads (read-only system-libsqlite3 FFI),
the constitution/identity/operating/human section builders, defining-
principle extraction, prompt assembly, sync checking, unified diffing,
and the hook response. rune.md writes, the voice_sync chain, and
timestamps stay in the Python facade (I/O boundary; kernel is pure).

difflib: the crates.io difflib crate was evaluated and REJECTED (its
SequenceMatcher diverges from CPython on real text — opcode mismatch
on the first fuzz corpus); src/difflib.rs is a faithful port of
SequenceMatcher (autojunk included), get_grouped_opcodes, unified_diff,
and str.splitlines(keepends=True) universal-newline semantics.

Frozen truth: 123 cases across every public function and every branch
(scripts/gen_prompt_sync_truth.py, RUNE_PS_CORE=python pinned —
poisoned-truth law), replayed VERBATIM by the cfg(test) battery.
Live parity: 182/182 result keys byte-identical (90 deterministic
store/defining/prompt scenarios + edges, both modes, seed 2479).

Oracle divergences caught by the battery/fuzz and pinned oracle-exact
in the kernel (fix-first with proof, oracle semantics preserved):
- build_prompt never includes an Identity section (get_identity is
  public but unused by the assembly — battery input discipline)
- sections with EMPTY body are Python-falsy and omitted; Rust Some("")
  is truthy — .filter(!is_empty) pins the oracle behavior
- constitution numbering is assigned BEFORE the len>15 filter (skipped
  entries leave numbering gaps)
- leading-space ' ## x' lines are NOT section headers and do NOT start
  with '#', so they land in defining principles verbatim

ctypes loader ps_loader.py mirrors ci_loader.py (doc-envelope FFI,
free via capability_scoring's cs_free_string — no ps_free_string
export); facade delegation in prompt_sync.py with RUNE_PS_CORE=python
pin and silent oracle fallback. Battery green, clippy 0, Python tests
green.
2026-10-11 23:23:43 +01:00
Nick 431ca18ea2 chore(store): close capability_ledger dichotomy contract BUILDER-2026-10-11T15:50:51Z-bde08d-factory-wave (parity 31fb68b5, phase B 374487b2; live parity 500/500 fuzz seed 2479 + save-bytes, battery 155 ops, fix-first: spine unseeded ingest-seam regression)
CI Pipeline / rust-surface (push) Failing after 14s
CI Pipeline / go-surface (push) Failing after 20s
CI Pipeline / best-practices (push) Successful in 7s
Auto-version and Release / release (push) Successful in 34s
CI Pipeline / documentation (push) Successful in 8s
CI Pipeline / gitea-offline (push) Successful in 19s
CI Pipeline / lint (push) Failing after 1m0s
CI Pipeline / type-check (push) Failing after 52s
CI Pipeline / canary (push) Failing after 2m41s
deploy / deploy (push) Failing after 15m38s
CI Pipeline / unit-tests (push) Failing after 18m16s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Failing after 5s
2026-10-11 23:07:56 +01:00
conductor c9a5f39935 chore(release): auto-bump version to 0.212.0 — earned by # (minor: converged release (push-to-master bump))
deploy / deploy (push) Skipped
CI Pipeline / best-practices (push) Successful in 10s
CI Pipeline / rust-surface (push) Failing after 15s
CI Pipeline / documentation (push) Successful in 5s
Auto-version and Release / release (push) Successful in 24s
CI Pipeline / go-surface (push) Failing after 21s
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 / lint (push) Canceled after 40s
CI Pipeline / unit-tests (push) Canceled after 41s
CI Pipeline / canary (push) Canceled after 41s
CI Pipeline / type-check (push) Canceled after 32s
Promotion record: (unknown)
2026-10-11 23:07:06 +01:00
Nick 374487b24f refactor(capability_ledger): Phase B language-advantage pass — dichotomy wave
CI Pipeline / best-practices (push) Successful in 9s
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 / documentation (push) Canceled after 4s
CI Pipeline / rust-surface (push) Canceled after 15s
CI Pipeline / lint (push) Canceled after 15s
Auto-version and Release / release (push) Canceled after 24s
CI Pipeline / go-surface (push) Canceled after 15s
CI Pipeline / canary (push) Canceled after 16s
deploy / deploy (push) Canceled after 23s
CI Pipeline / unit-tests (push) Canceled after 20s
language advantage: BumpKind enum replaces the PATCH/MINOR/MAJOR string
soup — ordering derives from the PartialOrd derive (no second hardcoded
ladder), serialization serialized at the JSON boundary via exhaustive
as_str matches, and the two DISTINCT oracle error contracts are now
explicit (bump_version's ValueError 'unknown bump kind {kind!r}' vs the
revise_spec governance dict-lookup KeyError str()=repr — the battery
caught the conflation immediately); language advantage: Maturity enum
models the §19D lattice with the active-set check as a method
(is_active), unknown oracle strings stay active exactly like the
oracle's != comparisons; language advantage: semver_parts returns a
zero-copy split iterator over the borrowed version; language advantage:
bump_digit carry propagation is a reverse fold (no index churn, no
insert-in-loop hazard beyond the single final carry); battery green
throughout (2/2), live fuzz 500/500 seed 2479, clippy 0.
2026-10-11 23:03:35 +01:00
Nick 31fb68b58d feat(capability_ledger): port to FULL Rust — dichotomy wave
FULL parity port of src/rune/capability_ledger.py to the capability_ledger
Rust core (RUST verdict, issue #2479 tranche, contract
BUILDER-2026-10-11T15:50:51Z-bde08d-factory-wave):
- 17 FFI symbols (cl_new/cl_define/cl_get/cl_material_change/cl_move/
  cl_supersede/cl_retire/cl_active/cl_by_component/cl_context_packet/
  cl_save/cl_classify_version_bump/cl_bump_version/cl_attach_spec/
  cl_revise_spec/cl_spec_drift_status/cl_spec_context) via doc-envelope
  law; the ledger doc travels as a sibling envelope key so "value" stays
  oracle-verbatim; free via capability_scoring cs_free_string (no
  re-export); serde_json preserve_order for dict-position parity
- stale partial port replaced (old CapabilityEntry struct had no Python
  counterpart); classify_version_bump/bump_version kept name-compatible
- REAL divergences fixed pre-wiring (battery caught all three):
  MINOR/PATCH bumps must normalize untouched components through int()
  (leading zeros collapse everywhere — '01.02.03' MINOR = '1.3.0');
  bump arithmetic is arbitrary-precision like Python ints (u64/u128
  boundary); SEMVER_RE \\d matches Unicode Nd + int() accepts them
  (full Nd range table, verified against unicodedata)
- py_repr() mirrors Python repr() in error envelopes (str(KeyError) is
  repr'd); note/reason truncation by codepoints (py_slice)
- ctypes loader cl_loader.py mirrors broker_loader.py (LEDGER_RUST_LIB
  override); facade delegation on all 18 public fns with RUNE_LEDGER_CORE
  =python pin and Python-body fallback (kernel errors fall back so the
  proper exception types re-raise)
- battery: 16 sequences / 155 ops frozen from the live oracle
  (frozen_cl_truth.json, replayed VERBATIM in-crate) + save() byte-parity
  pinned (indent=1 sort_keys identical); 500-op live fuzz seed 2479:
  500/500 + final-file-bytes equal
- FIX-FIRST (pre-existing latent regression surfaced by this batch):
  spine unseeded path bypassed the monkeypatchable ingest_umbrellas seam
  once the rune_spine kernel was built — the generated-view refusal fired
  inside the kernel where isolation tests cannot intercept it
  (test_spine_seeds_entry_point red on master with kernel built). build()
  now routes the manifest ingest through the public seam first and hands
  the kernel a carry-attempted doc; the kernel skips its manifest branch
  when existing was provided as an object (true cold path keeps the
  load-bearing refusal). 198 ledger/spine Python tests green.
2026-10-11 23:03:35 +01:00
Nick 59b98c7576 chore(store): close token_optimizer dichotomy contract BUILDER-2026-10-11T16:23:18Z-e4e237-factory-wave (parity 115b4cb2, phase B 56c6f02f; live parity 81/81 rust + 81/81 python)
CI Pipeline / best-practices (push) Successful in 11s
Auto-version and Release / release (push) Successful in 25s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / rust-surface (push) Failing after 16s
CI Pipeline / go-surface (push) Failing after 24s
CI Pipeline / gitea-offline (push) Successful in 19s
CI Pipeline / lint (push) Failing after 59s
CI Pipeline / type-check (push) Failing after 48s
CI Pipeline / canary (push) Failing after 2m34s
deploy / deploy (push) Failing after 15m35s
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 18m11s
2026-10-11 22:48:25 +01:00
conductor 891b5ab1ce chore(release): auto-bump version to 0.211.0 — earned by # (minor: converged release (push-to-master bump))
CI Pipeline / best-practices (push) Successful in 6s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / rust-surface (push) Failing after 11s
CI Pipeline / go-surface (push) Failing after 16s
Auto-version and Release / release (push) Successful in 19s
CI Pipeline / gitea-offline (push) Successful in 19s
CI Pipeline / lint (push) Failing after 49s
CI Pipeline / type-check (push) Failing after 45s
CI Pipeline / canary (push) Failing after 2m4s
deploy / deploy (push) Failing after 15m31s
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 16m46s
Promotion record: (unknown)
2026-10-11 22:31:39 +01:00
Nick 56c6f02fdb refactor(token_optimizer): Phase B language-advantage pass — dichotomy wave
CI Pipeline / best-practices (push) Successful in 7s
CI Pipeline / documentation (push) Successful in 5s
CI Pipeline / rust-surface (push) Failing after 11s
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 / canary (push) Canceled after 21s
CI Pipeline / type-check (push) Canceled after 13s
CI Pipeline / unit-tests (push) Canceled after 22s
Auto-version and Release / release (push) Canceled after 22s
CI Pipeline / lint (push) Canceled after 25s
CI Pipeline / gitea-offline (push) Canceled after 17s
deploy / deploy (push) Canceled after 10s
Kernel-only: the op wire strings now parse into an Op enum with an
exhaustive dispatch match (an unhandled op is a compile error, not a
runtime string fallback); the argument view is a zero-copy borrowed
Args<'a> over the wire doc (no per-call Value clones); the md5 dynamic
cache key hexes only the first 8 digest bytes (no 32-char intermediate);
state parse is all-or-nothing via collect::<Option<Vec<_>>> mirroring the
oracle's blanket-except _load. Battery green (8 scenarios / 81
observations verbatim), live parity 81/81 rust + 81/81 python, clippy 0.
2026-10-11 22:30:32 +01:00
Nick 115b4cb258 feat(token_optimizer): port to FULL Rust — dichotomy wave
Full parity port of src/rune/token_optimizer.py (issue #2479 tranche):
the stale 3-helper crate is replaced by the complete cache/state kernel —
static hit/miss, md5-keyed dynamic upsert (Python dict insertion order
preserved), savings math, dirty/debounce/threshold save policy with
trigger-time file snapshots (mid-call saves write the state AT the
trigger; later mutations stay dirty in memory, exactly like the oracle).
Pure/IO split: file I/O (_load/_save), the clock, persona glue, CLI and
the singleton stay Python; _to_core() + to_loader.py ctypes (doc-envelope
FFI, RUNE_TOKEN_OPTIMIZER_CORE=python pin, free via capability_scoring
cs_free_string — never re-exported); counting/compression delegate to the
token_counting kernel. Frozen truth: 8 scenarios / 81 observations vs the
live pinned oracle, embedded verbatim; branch battery replays it in Rust;
tests/test_token_optimizer_port.py replays it through the wired facade on
BOTH paths. Live parity 81/81 rust + 81/81 python; battery green;
clippy 0.
2026-10-11 22:30:32 +01:00
Nick 53736c20f7 chore(store): close message_queue dichotomy contract BUILDER-2026-10-11T21:10:49Z-58c367-factory-wave (parity 82863618, phase B 64911ab7, ci d4c9ce56)
Auto-version and Release / release (push) Successful in 14s
CI Pipeline / best-practices (push) Successful in 8s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / rust-surface (push) Failing after 11s
CI Pipeline / go-surface (push) Failing after 14s
CI Pipeline / gitea-offline (push) Successful in 18s
CI Pipeline / type-check (push) Failing after 39s
CI Pipeline / lint (push) Failing after 50s
CI Pipeline / canary (push) Failing after 1m59s
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 12m16s
deploy / deploy (push) Canceled after 12m21s
2026-10-11 22:18:53 +01:00
Nick 54df8e77fb chore(store): close build_basis dichotomy contract BUILDER-2026-10-11T16:43:03Z-8778a4 (parity b8b1ba66, phase B f11006c4)
Auto-version and Release / release (push) Successful in 14s
CI Pipeline / best-practices (push) Successful in 5s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / rust-surface (push) Failing after 11s
CI Pipeline / go-surface (push) Failing after 13s
CI Pipeline / gitea-offline (push) Successful in 15s
CI Pipeline / lint (push) Failing after 45s
CI Pipeline / type-check (push) Failing after 37s
CI Pipeline / canary (push) Failing after 1m54s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
deploy / deploy (push) Canceled after 8m41s
CI Pipeline / unit-tests (push) Canceled after 8m30s
2026-10-11 22:09:11 +01:00
conductor aa3e1e3113 chore(release): auto-bump version to 0.210.0 — earned by # (minor: converged release (push-to-master bump))
deploy / deploy (push) Skipped
CI Pipeline / documentation (push) Successful in 6s
CI Pipeline / best-practices (push) Successful in 7s
CI Pipeline / rust-surface (push) Failing after 12s
CI Pipeline / go-surface (push) Failing after 15s
CI Pipeline / gitea-offline (push) Successful in 20s
Auto-version and Release / release (push) Successful in 18s
CI Pipeline / lint (push) Failing after 48s
CI Pipeline / type-check (push) Failing after 42s
CI Pipeline / canary (push) Failing after 2m0s
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 2m45s
Promotion record: (unknown)
2026-10-11 22:07:36 +01:00
Nick f11006c457 refactor(build_basis): Phase B language-advantage pass — dichotomy wave
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 / documentation (push) Canceled after 0s
CI Pipeline / go-surface (push) Canceled after 10s
CI Pipeline / lint (push) Canceled after 10s
CI Pipeline / unit-tests (push) Canceled after 10s
CI Pipeline / rust-surface (push) Canceled after 10s
CI Pipeline / canary (push) Canceled after 11s
CI Pipeline / best-practices (push) Canceled after 10s
Auto-version and Release / release (push) Canceled after 19s
deploy / deploy (push) Canceled after 20s
2026-10-11 22:02:18 +01:00
Nick b8b1ba66ad feat(build_basis): port to FULL Rust — dichotomy wave 2026-10-11 22:02:18 +01:00
231 changed files with 225869 additions and 37429 deletions

No files matched your search

+1 -1
View File
@@ -1,6 +1,6 @@
# Rune
**Current release: v0.209.0**
**Current release: v0.229.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,14 +2,14 @@
_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: 28%** (686/2480 runtime functions matched)
**Function-level completion: 32%** (838/2627 runtime functions matched)
| Status | Modules | Lines | Meaning |
|---|---|---|---|
| FULL | 15 | 4264 | 80%+ of functions ported |
| PARTIAL | 83 | 41656 | 20–80% — real logic exists, gaps named below |
| NOT TOUCHED | 119 | 26856 | no name-preserving implementation |
| Total runtime | 217 | 72776 | |
| 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 | |
## NOT TOUCHED — the port queue, in priority order (largest first)
@@ -17,50 +17,36 @@ _Regenerate with `python3 scripts/dichotomy_audit.py --write`. A module is FULL
|---|---|---|---|
| rune_tui | 4170 | 159 | TUI-LAST |
| curator | 1838 | 43 | GO |
| abstraction | 656 | 17 | GO |
| canary_audit_ledger | 541 | 2 | TEST-INFRA |
| tui_validator | 514 | 13 | TUI-LAST |
| construction_pattern | 496 | 12 | RUST |
| model_routing | 432 | 13 | GO |
| worthiness | 411 | 13 | unclassified |
| token_optimizer | 410 | 22 | RUST |
| behavior | 408 | 9 | RUST |
| capability_ledger | 405 | 18 | RUST |
| chat_bubble | 401 | 12 | TUI-LAST |
| prompt_sync | 379 | 14 | RUST |
| drift-ledger | 355 | 14 | unclassified |
| programme_continuation | 349 | 11 | RUST |
| pc_loader | 337 | 22 | unclassified |
| canary_rune_serve | 329 | 13 | TEST-INFRA |
| canary_p1_07 | 324 | 10 | TEST-INFRA |
| message_queue | 316 | 13 | GO |
| drift_observability | 315 | 8 | RUST |
| build_basis | 314 | 12 | RUST |
| canary_p0_07 | 310 | 10 | TEST-INFRA |
| attention_capsule | 296 | 7 | RUST |
| canary_p1_01 | 294 | 12 | TEST-INFRA |
| drift_cache | 292 | 15 | RUST |
| outcome_ledger | 286 | 7 | RUST |
| time_awareness | 286 | 14 | RUST |
| canary_p1_06 | 280 | 7 | TEST-INFRA |
| auto_context | 273 | 8 | GO |
| 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 |
| toolchain | 236 | 5 | RUST |
| task_gate | 235 | 8 | RUST |
| 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 |
| evidence_probe | 213 | 9 | GO |
| canary_p0_04 | 207 | 8 | TEST-INFRA |
| 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 |
@@ -73,16 +59,24 @@ _Regenerate with `python3 scripts/dichotomy_audit.py --write`. A module is FULL
| inference_trace | 164 | 6 | RUST |
| ms_loader | 163 | 16 | RUST |
| canary_gitea_pipeline | 159 | 2 | TEST-INFRA |
| dc_loader | 158 | 13 | unclassified |
| mse_loader | 152 | 12 | unclassified |
| bb_loader | 151 | 14 | unclassified |
| forge | 146 | 11 | GO |
| rune_graphic | 145 | 5 | TUI-LAST |
| spine_loader | 144 | 13 | RUST |
| ac_loader | 140 | 11 | unclassified |
| project_registry | 139 | 11 | RUST |
| canary_rune_entry | 137 | 3 | TEST-INFRA |
| do_loader | 137 | 8 | unclassified |
| ps_loader | 136 | 13 | unclassified |
| verification_layer | 136 | 11 | RUST |
| tui_backlog | 125 | 5 | TUI-LAST |
| state_checkpoint | 123 | 8 | unclassified |
| cp_loader | 122 | 9 | unclassified |
| drift_resume | 118 | 3 | RUST |
| tl_loader | 118 | 9 | unclassified |
| bh_loader | 117 | 9 | unclassified |
| canary_conversation_state | 115 | 2 | TEST-INFRA |
| contracts_loader | 112 | 8 | unclassified |
| canary_query_memory | 111 | 2 | TEST-INFRA |
@@ -100,9 +94,9 @@ _Regenerate with `python3 scripts/dichotomy_audit.py --write`. A module is FULL
| broker_loader | 86 | 5 | unclassified |
| canary_schema_migration | 86 | 1 | TEST-INFRA |
| ss_loader | 86 | 7 | unclassified |
| to_loader | 85 | 4 | unclassified |
| mems_loader | 83 | 5 | RUST |
| cm_loader | 82 | 4 | unclassified |
| cp_loader | 77 | 4 | unclassified |
| dl_loader | 77 | 5 | unclassified |
| lc_loader | 75 | 5 | unclassified |
| cd_loader | 72 | 4 | unclassified |
@@ -155,7 +149,7 @@ _Regenerate with `python3 scripts/dichotomy_audit.py --write`. A module is FULL
| growth_delta | 449 | 5/20 | _ctx_budget, _store_db, _llm, _parse_json, _load_deltas, _save_deltas |
| provider_budget | 357 | 5/20 | get_tracker, track_usage, check_provider, get_best_provider, failover, get_status |
| safety_override | 574 | 7/22 | _keyword_stopwords, _cc_probe, cc_loader_pin, self_test, _py_matches, escalation_rate |
| spine | 965 | 20/35 | _py_sorted, _module_imports, _py_declared_surfaces, _py_project_meta_surfaces, _gather_meta_inputs, _rust_core |
| spine | 975 | 20/35 | _py_sorted, _module_imports, _py_declared_surfaces, _py_project_meta_surfaces, _gather_meta_inputs, _rust_core |
| tool_executor | 1836 | 32/47 | _execute_tool_now, serialize_prepared_tool, deserialize_prepared_tool, confirm_write, _resolve_execution_cwd, _run_command |
| cron_engine | 440 | 13/27 | get_cron_engine, run_task, start_engine, stop_engine, to_dict, fire_on_task_complete |
| night_mode | 421 | 5/19 | _ctx_budget, get_night_mode, start_night_mode, stop_night_mode, get_status, get_report |
@@ -166,6 +160,7 @@ _Regenerate with `python3 scripts/dichotomy_audit.py --write`. A module is FULL
| identity_anchors | 542 | 5/16 | show_anchors, show_rules, show_pronouns, _announcement_patterns, _pronoun_patterns, _hedge_patterns |
| parallel_processor | 194 | 3/14 | _shutdown_processor, get_processor, run_parallel_with_args, get_status, test_parallel, run_parallel_with_args |
| structural_model | 388 | 9/20 | workspace_record, workspace_advance, _init_project, save, reload, add |
| token_optimizer | 514 | 16/27 | get_optimizer, _tc_core, _to_core, _py_estimate_tokens, _py_compress_context, to_dict |
| capability_broker | 798 | 10/20 | _native_tool_schemas, load_registry, _cards_are_stale, load_cards, generate_card, build_cards |
| capability_fit | 414 | 3/13 | record_semantic_fit, _provides_contracts, _consumers, discover_candidates, _cs_core, decide |
| claim_audit | 484 | 4/14 | _get_warm_neuron_store, reframe, _graph_has, _warm_has, verify, journal |
@@ -176,7 +171,9 @@ _Regenerate with `python3 scripts/dichotomy_audit.py --write`. A module is FULL
| 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 |
| 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 |
@@ -198,8 +195,11 @@ _Regenerate with `python3 scripts/dichotomy_audit.py --write`. A module is FULL
| tiered_executor | 291 | 3/8 | _ctx_budget, test_executor, to_dict, to_dict, _execute_model |
| adaptive_router | 625 | 15/19 | _floor_applies_from_flags, _floor_reason, _record_transition_with, _detect_external_refs |
| 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 |
| 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 |
| salience_gate | 276 | 1/5 | _sg_core, test_salience, handle_hook, to_dict |
| self_improve | 246 | 1/5 | _now, suggest_improvements, handle_hook, main |
| stage_guards | 303 | 7/11 | _py_liveness_canary, _py_reconcile, to_dict, chk |
@@ -227,13 +227,23 @@ _Regenerate with `python3 scripts/dichotomy_audit.py --write`. A module is FULL
| Module | Lines | Fns | Side |
|---|---|---|---|
| abstraction | 656 | 17/17 | GO |
| system_registry | 603 | 14/16 | RUST |
| construction_intent | 580 | 13/16 | RUST |
| capability_ledger | 571 | 18/19 | RUST |
| event_wait | 518 | 14/14 | GO |
| tool_protocol | 461 | 22/27 | GO |
| auto_tasks | 460 | 14/14 | GO |
| programme_continuation | 458 | 11/12 | RUST |
| goal | 453 | 20/20 | GO |
| release_notes | 446 | 16/16 | GO |
| model_routing | 432 | 13/13 | GO |
| build_basis | 375 | 11/13 | RUST |
| attention_capsule | 365 | 6/7 | RUST |
| message_queue | 316 | 13/13 | GO |
| toolchain | 295 | 5/6 | RUST |
| auto_context | 273 | 8/8 | GO |
| evidence_probe | 213 | 9/9 | GO |
| recall_uplift | 141 | 1/1 | RUST |
| rune_spinner | 130 | 4/5 | RUST |
| wait_guard | 116 | 1/1 | RUST |
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "rune"
version = "0.209.0"
version = "0.229.0"
description = "Rune — a stateful coding assistant with persistent memory, orchestration, and TUI"
requires-python = ">=3.10"
+182
View File
@@ -0,0 +1,182 @@
#!/usr/bin/env python3
"""Live parity: behavior delegating facade (Rust core) vs the Python-pin
oracle path, over the SAME case matrix the truth generator froze.
Run: python3 scripts/check_behavior_parity.py -> x/y lines, exit 1 on any
divergence.
"""
from __future__ import annotations
import json
import os
import sys
import tempfile
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
_tmp = tempfile.mkdtemp(prefix="bh-parity-")
os.environ["RUNE_HOME"] = _tmp
os.environ["RUNE_MEMORY_DIR"] = os.path.join(_tmp, "memory")
from rune import behavior as bh # noqa: E402
from rune import bh_loader # noqa: E402
if not bh_loader.available():
print("0/0 — rust behavior core not built")
sys.exit(0)
PASS = 0
FAIL = 0
DIVERGENCES = []
_CLOCK_KEYS = {"created_at", "last_reinforced", "last_activated"}
def strip_clocks(obj):
"""Timestamps are pinned by the shim (`now`) — wall-clock values
differ per run by design; compare everything else."""
if isinstance(obj, dict):
return {k: strip_clocks(v) for k, v in obj.items() if k not in _CLOCK_KEYS}
if isinstance(obj, list):
return [strip_clocks(x) for x in obj]
return obj
def check(name, rust, py):
global PASS, FAIL
if strip_clocks(rust) == strip_clocks(py):
PASS += 1
else:
FAIL += 1
DIVERGENCES.append((name, rust, py))
def row(table, rid):
db = bh.connect()
try:
r = db.execute(f"SELECT * FROM {table} WHERE id=?", (rid,)).fetchone()
return dict(r) if r else None
finally:
db.close()
def both(name, fn):
"""Drive fn() twice: pinned-python first (fresh db), rust second
(fresh db). Databases are reset between runs so ids align."""
os.environ["RUNE_BEHAVIOR_CORE"] = "python"
bh.DB_PATH = os.path.join(os.environ["RUNE_MEMORY_DIR"], "behavior.db")
if os.path.exists(bh.DB_PATH):
os.remove(bh.DB_PATH)
py = fn()
os.environ["RUNE_BEHAVIOR_CORE"] = "auto"
if os.path.exists(bh.DB_PATH):
os.remove(bh.DB_PATH)
rust = fn()
os.environ["RUNE_BEHAVIOR_CORE"] = "python"
check(name, rust, py)
# ── add shapes ────────────────────────────────────────────────────────
def add_shapes():
out = [bh.add_correction("say X", "say Y"),
bh.add_correction("orig", "corr", scope_tags={"k": "v"}, domain="shell", pattern="shell"),
bh.add_habit("trig", "act"),
bh.add_habit("trig2", "act2", name="n", context_tags=["a"], min_activations=5)]
return json.loads(json.dumps(out))
both("add-shapes", add_shapes)
# ── seed the shared match fixture in BOTH modes ───────────────────────
def seed_match():
bh.add_correction("o1", "scoped corr", scope_tags={}, domain="shell", pattern="deploy")
bh.add_correction("o2", "domain corr", scope_tags={}, domain="editor")
bh.add_correction("o3", "plain corr")
bh.add_correction("o4", "bad regex corr", pattern="([unclosed")
low = bh.add_correction("o5", "low conf corr")["added"]
bh.add_habit("deploy", "run deploy script")
bh.add_habit("test (unit|e2e)", "run tests")
bh.add_habit("([broken", "bad regex habit")
hslow = bh.add_habit("zzz-no-match-ever", "quiet")["added"]
db = bh.connect()
db.execute("UPDATE corrections SET confidence=0.2 WHERE id=?", (low,))
db.execute("UPDATE habits SET confidence=0.2 WHERE id=?", (hslow,))
db.commit()
db.close()
def match_shapes():
seed_match()
cases = []
for ctx, mt in [("deploy to shell now", None), ("deploy the editor", None),
("unrelated chatter here", None), ("deploy to shell now", "habit"),
("deploy to shell now", "correction"), ("run the unit tests", None),
("zzz-no-match-ever quiet", None), ("([unclosed anything", None)]:
got = bh.match_context(ctx, match_type=mt)
cases.append({
"corrections": sorted(c["corrected_behavior"] for c in got["corrections"]),
"habits": sorted(h["action_template"] for h in got["habits"]),
"full": json.loads(json.dumps(got)),
})
return cases
both("match-matrix", match_shapes)
# ── reinforce matrix ──────────────────────────────────────────────────
def reinforce_shapes():
seed_match()
out = []
for name, item_type, outcome, seed_fn, pre, n in [
("success-promotes-at-3", "habit", "success", lambda: bh.add_habit("t1", "a1"), {}, 3),
("failure-counts", "habit", "failure", lambda: bh.add_habit("t2", "a2"), {}, 2),
("neutral-bumps-activation-only", "habit", "neutral", lambda: bh.add_habit("t3", "a3"), {}, 1),
("already-active-stays", "habit", "success", lambda: bh.add_habit("t4", "a4", min_activations=1), {}, 2),
("min5-gate", "habit", "success", lambda: bh.add_habit("t5", "a5", min_activations=5), {}, 4),
("success-plus-point-one", "correction", "success", lambda: bh.add_correction("x1", "y1"), {}, 1),
("failure-archives", "correction", "failure", lambda: bh.add_correction("x2", "y2"), {}, 1),
("double-failure-floor", "correction", "failure", lambda: bh.add_correction("x3", "y3"), {}, 3),
("ceiling-at-one", "correction", "success", lambda: bh.add_correction("x4", "y4"), {"confidence": 0.95}, 1),
("neutral-noop", "correction", "neutral", lambda: bh.add_correction("x5", "y5"), {}, 1),
]:
rid = seed_fn()["added"]
if pre:
db = bh.connect()
table = "habits" if item_type == "habit" else "corrections"
for k, v in pre.items():
db.execute(f"UPDATE {table} SET {k}=? WHERE id=?", (v, rid))
db.commit()
db.close()
results = [bh.reinforce(rid, outcome, item_type=item_type) for _ in range(n)]
out.append({"name": name, "results": json.loads(json.dumps(results)),
"final": json.loads(json.dumps(row("habits" if item_type == "habit" else "corrections", rid)))})
out.append({"name": "not-found-habit",
"results": [bh.reinforce(99991, "success", item_type="habit")], "final": None})
out.append({"name": "not-found-correction",
"results": [bh.reinforce(99992, "success", item_type="correction")], "final": None})
return out
both("reinforce-matrix", reinforce_shapes)
# ── list + stats ──────────────────────────────────────────────────────
def list_stats_shapes():
seed_match()
out = {}
check("list-keys", sorted(bh.list_items().keys()), ["corrections", "habits"])
check("limit-1", len(bh.list_items(limit=1).get("corrections", [])), 1)
check("type-correction-keys", sorted(bh.list_items(item_type="correction").keys()), ["corrections"])
check("type-habit-keys", sorted(bh.list_items(item_type="habit").keys()), ["habits"])
check("type-all-keys", sorted(bh.list_items(item_type="all").keys()), ["corrections", "habits"])
out["stats"] = bh.behavior_stats()
return json.loads(json.dumps(out))
both("list-stats", list_stats_shapes)
print(f"{PASS}/{PASS + FAIL} live parity (rust facade vs python pin)")
for name, rust, py in DIVERGENCES:
print(f"DIVERGENCE {name}:")
print(f" rust: {json.dumps(rust, sort_keys=True)[:400]}")
print(f" py: {json.dumps(py, sort_keys=True)[:400]}")
sys.exit(1 if FAIL else 0)
+278
View File
@@ -0,0 +1,278 @@
#!/usr/bin/env python3
"""Live parity: Rust build_basis kernel vs the Python oracle (dichotomy
wave #2479). Replays the frozen truth corpus PLUS seeded fuzz over every
public function and branch. Timestamps the kernel generates are masked
on both sides (wall clock, not logic).
The facade reads RUNE_BB_CORE per call, so one process can flip between
the pinned oracle (RUNE_BB_CORE=python) and the kernel (unset).
"""
from __future__ import annotations
import copy
import json
import os
import random
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
MASKED = ("decided_at", "created_at", "revised_at", "plan_generated_at")
def mask(doc):
if isinstance(doc, dict):
return {k: mask(v) for k, v in doc.items() if k not in MASKED}
if isinstance(doc, list):
return [mask(v) for v in doc]
return doc
def call(fn, *args, **kwargs):
"""Call through the facade under the given core pin."""
return getattr(bb, fn)(*args, **kwargs)
def call_core(core, fn, *args, **kwargs):
old = os.environ.get("RUNE_BB_CORE")
os.environ["RUNE_BB_CORE"] = core
try:
return call(fn, *args, **kwargs)
finally:
if old is None:
os.environ.pop("RUNE_BB_CORE", None)
else:
os.environ["RUNE_BB_CORE"] = old
def mutator_case(fn, basis_in, *args, **kwargs):
"""Run a mutating function on both cores over deep copies; compare
(return value, post-mutation basis) with time fields masked."""
py = call_core("python", fn, copy.deepcopy(basis_in), *args, **kwargs)
rs = call_core("rust", fn, copy.deepcopy(basis_in), *args, **kwargs)
ok = (mask(py) == mask(rs)
and mask(basis_in_py := None or basis_in) is not None)
return ok, py, rs
# ── fuzz pools ─────────────────────────────────────────────────────────
ROUTES = ["LOCAL_FIX", "STANDARD_CHANGE", "RESEARCHED_CHANGE", "WEIRD",
"", "RESEARCHED_CHANGE ", "local_fix", "SPIKE"]
TIERS = ["NONE", "INWARD", "OUTWARD", "FULL", "WEIRD", None]
JUSTS = ["", " ", "tiny fix", "OUTWARD: precedent needed",
"FULL: x:y", " INWARD ", "resolved already",
"не-mátter 🎉", "prior-art question answered elsewhere",
"NONE: skip", "discovery resolved it", "unnecessary"]
WORDS = ["existing", "prior art", "framework", "typo", "schema",
"architecture", "watchdog", "delete", "", "u" * 90, "café ☕",
"tool", "standard", "breaking", "deploy"]
DISPOSITIONS = ["ADOPT", "ADAPT", "COMBINE", "REJECT", "INNOVATE", "MAYBE", "", None]
def rand_basis(rng):
b = bb.new_basis(question=rng.choice(WORDS),
depth_decision=bb.research_depth(rng.choice(ROUTES),
justification=rng.choice(JUSTS)),
issue_ref=rng.choice(["research/issue-1", "", "gitea/2479"]))
b["created_at"] = "2026-10-11T00:00:00Z"
return b
def fuzz_cases(rng):
out = []
for _ in range(120):
out.append(("research_depth",
(rng.choice(ROUTES), rng.choice(["", "rune-internal", "cli"]),
rng.choice(JUSTS)), {}))
for _ in range(60):
d = bb.research_depth(rng.choice(ROUTES), justification=rng.choice(JUSTS))
d["decided_at"] = "FIXED"
out.append(("new_basis", (rng.choice(WORDS), d,
rng.choice(["research/x", ""])), {}))
for _ in range(120):
disp = [{"item": rng.choice(WORDS), "disposition": rng.choice(DISPOSITIONS),
"why": rng.choice(JUSTS), "ref": rng.choice(["r1", ""]) }
for _ in range(rng.randrange(0, 4))]
out.append(("set_dispositions", (None,), {"basis": None, "dispositions": disp}))
for _ in range(120):
b = rand_basis(rng)
if rng.random() < 0.5:
b["schema_version"] = rng.choice([1, 2, None, True])
if rng.random() < 0.4:
b["depth"] = rng.choice([{"depth": rng.choice(TIERS), "why": rng.choice(["", "w"])},
{}, None])
if rng.random() < 0.3:
b["dispositions"] = [{"item": rng.choice(WORDS),
"disposition": rng.choice(DISPOSITIONS)}]
out.append(("validate_basis", (), {"basis": b}))
for _ in range(60):
out.append(("attach_evidence_ref", (),
{"basis": rng.choice([rand_basis(rng),
{"created_at": None},
{"issue_ref": "", "created_at": "T"},
{}])}))
for _ in range(60):
b = rand_basis(rng)
if rng.random() < 0.4:
b["implications"] = rng.choice([None, {}, {"architecture": ["a"], "x": "yy"}])
b["uncertainty"] = rng.choice([[], ["u1"], ["u1", "u2"]])
b["risks"] = rng.choice([[], ["r1"]])
out.append(("summary", (), {"basis": b}))
for _ in range(120):
b = rand_basis(rng)
out.append(("revise_depth", (),
{"basis": b, "new_depth": rng.choice(TIERS),
"reason": rng.choice(JUSTS + ["skip it", "x" * 250, "r"])}))
for _ in range(80):
b = rand_basis(rng)
if rng.random() < 0.3:
b["question"] = "héllo 🎉 \"q\" \\n\x01"
out.append(("to_json", (), {"basis": b}))
for _ in range(60):
out.append(("from_json", (rng.choice([
json.dumps({"a": [1, 2.5, True, None], "é": "ü"}),
"[1,2,3]", "{}", "null", "\"str\"",
"{bad", "{'single': 1}", ""]),), {}))
for _ in range(150):
kw = dict(intent=rng.choice(WORDS + ["migrate the schema", "deploy it"]),
target=rng.choice(["", "wr-bridge"]),
current_reality=rng.choice(["", "c" * 250, "réalité"]),
unknowns=[rng.choice(WORDS) for _ in range(rng.randrange(0, 3))],
decisions=[rng.choice(WORDS) for _ in range(rng.randrange(0, 2))],
risks=[rng.choice(WORDS) for _ in range(rng.randrange(0, 2))],
depth=rng.choice([None] + TIERS[:4]))
out.append(("generate_research_plan", (), kw))
return out
def main() -> int:
global bb
from rune import build_basis as bb_mod
from rune import bb_loader
bb = bb_mod
if not bb_loader.available():
print("Rust core not built — run cargo build --release in src/rust/build_basis")
return 2
total = passed = 0
failures: list[str] = []
def check(label, ok, detail=""):
nonlocal total, passed
total += 1
if ok:
passed += 1
else:
failures.append(f"{label}: {detail}")
# 1. frozen truth corpus, replayed live
truth = json.loads((Path(__file__).resolve().parents[1] / "src" / "rust" /
"build_basis" / "src" / "frozen_bb_truth.json").read_text())
for fn, cases in truth["cases"].items():
for case in cases:
name = f"truth:{fn}:{case['name']}"
inp = case["input"]
if "error" in case and fn == "revise_depth":
# error path: oracle raises; kernel must refuse identically
try:
py_err = None
call_core("python", fn, copy.deepcopy(inp["basis"]),
new_depth=inp["new_depth"], reason=inp["reason"])
except ValueError as e:
py_err = str(e)
try:
bb_loader.revise_depth(copy.deepcopy(inp["basis"]),
inp["new_depth"], inp["reason"])
check(name, False, "kernel accepted a refused revision")
except RuntimeError as e:
check(name, str(e) == py_err,
f"kernel err {e!r} != oracle {py_err!r}")
continue
if "error_kind" in case: # from_json invalid → both must refuse
try:
call_core("python", fn, **inp)
py_ok, rs_ok = True, True
except ValueError:
py_ok = False
try:
call_core("rust", fn, **inp)
rs_ok = True
except ValueError:
rs_ok = False
check(name, py_ok == rs_ok == False, f"py={py_ok} rs={rs_ok}")
continue
if fn in ("set_dispositions", "revise_depth"):
b1, b2 = copy.deepcopy(inp["basis"]), copy.deepcopy(inp["basis"])
kw = dict(inp)
kw["basis"] = b1
py = call_core("python", fn, **kw)
kw["basis"] = b2
rs = call_core("rust", fn, **kw)
ok = mask(py) == mask(rs) and mask(b1) == mask(b2)
check(name, ok, f"py={py!r} rs={rs!r}")
else:
if fn == "new_basis":
py = call_core("python", fn, question=inp["question"],
depth_decision=inp["depth_decision"],
issue_ref=inp.get("issue_ref", ""))
rs = call_core("rust", fn, question=inp["question"],
depth_decision=inp["depth_decision"],
issue_ref=inp.get("issue_ref", ""))
else:
py = call_core("python", fn, **inp)
rs = call_core("rust", fn, **inp)
check(name, mask(py) == mask(rs), f"py={py!r} rs={rs!r}")
# 2. seeded fuzz
rng = random.Random(2479)
for fn, args, kwargs in fuzz_cases(rng):
name = f"fuzz:{fn}"
if fn in ("set_dispositions", "revise_depth"):
b = rand_basis(rng)
b1, b2 = copy.deepcopy(b), copy.deepcopy(b)
kw_clean = {k: v for k, v in kwargs.items() if k != "basis"}
try:
py = call_core("python", fn, b1, **kw_clean)
except ValueError as e:
py = f"ValueError: {e}"
try:
rs = call_core("rust", fn, b2, **kw_clean)
except ValueError as e:
rs = f"ValueError: {e}"
check(name, mask(py) == mask(rs) and mask(b1) == mask(b2),
f"kwargs={kwargs} py={py!r} rs={rs!r}")
elif fn == "new_basis":
py = call_core("python", fn, question=args[0],
depth_decision=args[1], issue_ref=args[2])
rs = call_core("rust", fn, question=args[0],
depth_decision=args[1], issue_ref=args[2])
check(name, mask(py) == mask(rs), f"args={args}")
elif fn in ("validate_basis", "attach_evidence_ref", "summary",
"to_json", "from_json", "generate_research_plan"):
try:
py = call_core("python", fn, *args, **kwargs)
except ValueError as e:
py = f"ValueError: {e}"
try:
rs = call_core("rust", fn, *args, **kwargs)
except ValueError as e:
rs = f"ValueError: {e}"
check(name, mask(py) == mask(rs), f"args={args} kwargs={kwargs}")
elif fn == "research_depth":
py = call_core("python", fn, args[0], mode=args[1],
justification=args[2])
rs = call_core("rust", fn, args[0], mode=args[1],
justification=args[2])
check(name, mask(py) == mask(rs), f"args={args}")
print(f"parity {passed}/{total}")
if failures:
for f in failures[:12]:
print("FAIL", f[:400])
return 1
return 0
if __name__ == "__main__":
sys.exit(main())
+259
View File
@@ -0,0 +1,259 @@
#!/usr/bin/env python3
"""Live parity: Rust task_gate kernel vs the Python oracle (dichotomy
wave #2479). Replays the frozen truth corpus PLUS seeded fuzz over every
public function and branch. Timestamps are frozen (no-clock law — the
kernel takes `now` as an argument), so nothing is masked.
The facade reads RUNE_TG_CORE per call, so one process can flip between
the pinned oracle (RUNE_TG_CORE=python) and the kernel (unset).
"""
from __future__ import annotations
import json
import os
import random
import sqlite3
import sys
import tempfile
from datetime import datetime, timezone
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"))
import task_gate as tg # noqa: E402
FROZEN_NOW = "2026-10-11T23:00:00.123456+00:00"
class FrozenDatetime(datetime):
"""No-clock law: the facade's `now` stamps are wall clock, not logic —
freeze them so the two core runs stamp identically."""
@classmethod
def now(cls, tz=None):
return datetime(2026, 10, 11, 23, 0, 0, 123456, tzinfo=timezone.utc)
tg.datetime = FrozenDatetime
TRUTH_PATH = Path(__file__).resolve().parents[1] / (
"src/rust/task_gate/src/frozen_tg_truth.json")
def call_core(core, fn, *args, **kwargs):
old = os.environ.get("RUNE_TG_CORE")
os.environ["RUNE_TG_CORE"] = core
try:
return getattr(tg, fn)(*args, **kwargs)
finally:
if old is None:
os.environ.pop("RUNE_TG_CORE", None)
else:
os.environ["RUNE_TG_CORE"] = old
def fresh_db():
tmp = tempfile.mkdtemp(prefix="tg_parity_")
tg.DB_PATH = os.path.join(tmp, "task_gate.db")
db = tg.connect()
db.close()
def read_rows():
db = sqlite3.connect(tg.DB_PATH)
db.row_factory = sqlite3.Row
rows = [dict(r) for r in db.execute(
"SELECT * FROM instructions ORDER BY id").fetchall()]
db.close()
return rows
def seed_rows(rows):
fresh_db()
db = tg.connect()
for i, r in enumerate(rows, start=1):
db.execute(
"INSERT INTO instructions (id, instruction, pattern, count,"
" threshold, status, promoted_at, created_at, last_seen)"
" VALUES (?,?,?,?,?,?,?,?,?)",
(
r.get("id", i), r.get("instruction", "seed %d" % i),
r.get("pattern", r"\bseed\b"), r.get("count", 1),
r.get("threshold", 3), r.get("status", "observing"),
r.get("promoted_at"), r.get("created_at", FROZEN_NOW),
r.get("last_seen", FROZEN_NOW),
),
)
db.commit()
db.close()
def scenario_record(rows_before, text):
"""Run record under both cores on identical pre-state; compare
(return, rows_after)."""
seed_rows(rows_before)
py_ret = call_core("python", "record_instruction", text)
py_rows = read_rows()
seed_rows(rows_before)
rs_ret = call_core("auto", "record_instruction", text)
rs_rows = read_rows()
ok = (json.dumps(py_ret, sort_keys=True)
== json.dumps(rs_ret, sort_keys=True)
and json.dumps(py_rows, sort_keys=True)
== json.dumps(rs_rows, sort_keys=True))
return ok, py_ret, rs_ret, py_rows, rs_rows
def scenario_ctx(rows, context):
seed_rows(rows)
py = call_core("python", "check_context", context)
rs = call_core("auto", "check_context", context)
return (json.dumps(py, sort_keys=True)
== json.dumps(rs, sort_keys=True)), py, rs
def scenario_list(rows):
seed_rows(rows)
py = call_core("python", "list_gates")
rs = call_core("auto", "list_gates")
return (json.dumps(py, sort_keys=True)
== json.dumps(rs, sort_keys=True)), py, rs
def scenario_promote(rows, gate_id):
seed_rows(rows)
py = call_core("python", "promote_gate", gate_id)
py_rows = read_rows()
seed_rows(rows)
rs = call_core("auto", "promote_gate", gate_id)
rs_rows = read_rows()
return (json.dumps(py, sort_keys=True)
== json.dumps(rs, sort_keys=True)
and json.dumps(py_rows, sort_keys=True)
== json.dumps(rs_rows, sort_keys=True)), py, rs, py_rows, rs_rows
def scenario_hook(event):
py = call_core("python", "handle_hook", event, {"ignored": True})
rs = call_core("auto", "handle_hook", event, {"ignored": True})
return (json.dumps(py, sort_keys=True)
== json.dumps(rs, sort_keys=True)), py, rs
# ── fuzz pools ─────────────────────────────────────────────────────────
WORDS = ["always", "check", "the", "benchmark", "before", "building",
"ship", "it", "do", "stop", "now", "café", "über", "v1_2",
"test_it", "fix", "bug", "release", "deploy", "kraken", "ab",
"cd", "research", "first", "wash", "spin", "gate", "task"]
PATTERNS = [r"\bcheck\b", r"\bship\b", r"\bbenchmark\b", r"do\ it",
r"\balways\b|\bcheck\b", r"\bcafé\b", r"\bresearch\b",
r"\bgate\b|\btask\b", r"\bfirst\b"]
STATUSES = ["observing", "active", "retired"]
def rand_text(rng):
n = rng.randint(1, 8)
sep = rng.choice([" ", " ", " ", " ", "! ", " ("])
return sep.join(rng.choice(WORDS) for _ in range(n)) + rng.choice(
["", "!", "?", " (now)"])
def rand_rows(rng, max_rows=4):
rows = []
for _ in range(rng.randint(0, max_rows)):
rows.append({
"pattern": rng.choice(PATTERNS),
"count": rng.randint(1, 5),
"threshold": rng.randint(2, 4),
"status": rng.choice(STATUSES),
"instruction": rand_text(rng),
"promoted_at": FROZEN_NOW if rng.random() < 0.3 else None,
})
return rows
def fuzz_cases(rng):
cases = []
for _ in range(300):
cases.append(("record", rand_rows(rng), rand_text(rng)))
for _ in range(250):
cases.append(("ctx", rand_rows(rng), rand_text(rng)))
for _ in range(200):
cases.append(("list", rand_rows(rng, 5), None))
for _ in range(150):
cases.append(("promote", rand_rows(rng), rng.randint(1, 5)))
for _ in range(50):
cases.append(("hook", None, rand_text(rng)))
return cases
def main() -> int:
failures = []
def check(label, ok, detail=""):
if not ok:
failures.append((label, detail))
print(f"FAIL {label}\n{detail}")
# 1. frozen truth corpus (facade level, both cores already proven by
# gen_task_gate_truth.py --replay; re-run kernel level here live)
truth = json.loads(TRUTH_PATH.read_text())["cases"]
n_truth = 0
for group, cases in truth.items():
for case in cases:
if group == "record_instruction":
inp = case["input"]
ok, p, r, pr, rr = scenario_record(
inp["instructions"], inp["text"])
elif group == "check_context":
inp = case["input"]
ok, p, r = scenario_ctx(inp["instructions"], inp["context"])
elif group == "list_gates":
ok, p, r = scenario_list(
case["rows_after"] and _rows_before(case))
elif group == "promote_gate":
inp = case["input"]
ok, p, r, pr, rr = scenario_promote(
[{"pattern": r"\bx\b"}], inp["id"])
else: # handle_hook
ok, p, r = scenario_hook(case["input"]["event"])
n_truth += 1
check(f"truth:{group}:{case['name']}", ok,
f"py={json.dumps(p, sort_keys=True)[:300]}\n"
f"rs={json.dumps(r, sort_keys=True)[:300]}")
# 2. seeded fuzz
rng = random.Random(2479)
n_fuzz = 0
for kind, rows, arg in fuzz_cases(rng):
n_fuzz += 1
label = f"fuzz:{kind}:{n_fuzz}"
if kind == "record":
ok, p, r, pr, rr = scenario_record(rows, arg)
elif kind == "ctx":
ok, p, r = scenario_ctx(rows, arg)
elif kind == "list":
ok, p, r = scenario_list(rows)
elif kind == "promote":
ok, p, r, pr, rr = scenario_promote(rows, arg)
else:
ok, p, r = scenario_hook(arg)
check(label, ok,
f"py={json.dumps(p, sort_keys=True)[:300]}\n"
f"rs={json.dumps(r, sort_keys=True)[:300]}\n"
f"rows={json.dumps(rows, sort_keys=True)[:200]}")
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
def _rows_before(case):
"""list_gates truth carries rows_after (the full pre-state — listing
never mutates); replay it as the seed state."""
return case["rows_after"]
if __name__ == "__main__":
sys.exit(main())
+155
View File
@@ -0,0 +1,155 @@
#!/usr/bin/env python3
"""Live parity gate for the time_awareness dichotomy port (#2479 tranche).
Three legs, every observation compared EXACTLY (the clock is pinned, so
nothing is masked):
1. frozen truth replay on the pinned-python path (truth self-consistency)
2. frozen truth replay on the kernel path (RUNE_TA_CORE unset)
3. seeded fuzz (seed 2479): random scenarios + random temporal-reference
texts, each driven through the live facade twice — pinned python vs
kernel — results, state and file must match bit-for-bit.
Exit 0 only on 100% parity.
"""
import os
import random
import sys
_HERE = os.path.dirname(os.path.abspath(__file__))
_ROOT = os.path.dirname(_HERE)
sys.path.insert(0, _HERE)
sys.path.insert(0, os.path.join(_ROOT, "src"))
import gen_time_awareness_truth as gen # noqa: E402 — shared clock-pinned harness
from rune import ta_loader # noqa: E402
TRUTH_PATH = os.path.join(_ROOT, "src", "rust", "time_awareness", "src", "frozen_ta_truth.json")
def drive_pin(scenarios, pin, workdir):
os.environ["RUNE_TA_CORE"] = pin
driver = gen.Driver(workdir)
return [driver.run(s["ops"]) for s in scenarios]
def replay_truth(workdir):
import json
with open(TRUTH_PATH) as f:
truth = json.load(f)
scenarios = truth["scenarios"]
total = sum(len(s["obs"]) for s in scenarios)
py = drive_pin(scenarios, "python", os.path.join(workdir, "py"))
rs = drive_pin(scenarios, "auto", os.path.join(workdir, "rs"))
ok = 0
for s, a, b in zip(scenarios, py, rs):
for i, (x, y) in enumerate(zip(a, b)):
if x == y:
ok += 1
else:
print(f"DIVERGE {s['name']} op#{i} ({s['ops'][i].get('op')})")
print(" py:", x)
print(" rs:", y)
return ok, total
def fuzz_texts(rng, n):
words = ["yesterday", "today", "tomorrow", "last", "next", "this", "morning",
"afternoon", "tonight", "week", "month", "yesterdays", "mondays",
"café", "lastweek", "soon", "later", "now", "the", "a"]
seps = [" ", " ", "\t", "\n", ", ", ". "]
out = []
for _ in range(n):
k = rng.randint(1, 6)
out.append(rng.choice(seps).join(rng.choice(words) for _ in range(k)))
return out
def fuzz_iso(rng, valid_only=False):
import datetime as _dt
r = rng.random() * (0.8 if valid_only else 1.0)
if r < 0.55:
dt = gen.EPOCH + _dt.timedelta(microseconds=rng.randint(0, gen.BASE_US))
dt += _dt.timedelta(days=rng.randint(-500, 500))
off = rng.choice([None, 0, 3600, -18000])
if off is not None:
dt = dt.replace(tzinfo=_dt.timezone(_dt.timedelta(seconds=off)))
return dt.isoformat()
if r < 0.7:
dt = gen.EPOCH + _dt.timedelta(microseconds=rng.randint(0, gen.BASE_US))
return dt.replace(tzinfo=None).isoformat() # naive → TypeError branch
if r < 0.8:
return "2026-10-10T10:00:00Z"
return rng.choice(["garbage", "", "2026-13-40T99:00:00+00:00", "[1,2]"])
def fuzz_scenarios(rng, n, texts):
scenarios = []
for i in range(n):
ops = [{"op": "new", "mono": round(rng.uniform(0.0, 400.0), 3)}]
if rng.random() < 0.5:
ops[0]["seed_file"] = gen.json.dumps(
{"last_interaction": fuzz_iso(rng)}, indent=2
)
elif rng.random() < 0.1:
ops[0]["seed_file"] = rng.choice(["corrupt{", "[1,2]"])
for _ in range(rng.randint(1, 8)):
kind = rng.choice(["new", "new", "seed_last", "ctx", "ctx", "status",
"parse", "flush", "mark_dirty", "save_if_dirty"])
op = {"op": kind, "mono": round(rng.uniform(0.0, 400.0), 3)}
if kind == "new":
if rng.random() < 0.5:
op["seed_file"] = gen.json.dumps(
{"last_interaction": fuzz_iso(rng)}, indent=2
)
elif rng.random() < 0.1:
op["seed_file"] = rng.choice(["corrupt{", "[1,2]"])
if kind == "seed_last":
op["last_interaction"] = fuzz_iso(rng, valid_only=True) if rng.random() < 0.8 else None
if kind in ("ctx", "status", "parse"):
op["now_us"] = gen.BASE_US + rng.randint(-3000, 3000) * 3600 * 1_000_000
if kind == "ctx":
op["update"] = rng.random() < 0.6
if kind == "parse":
op["text"] = rng.choice(texts)
op["now_us"] = gen.BASE_US + rng.randint(-3000, 3000) * 3600 * 1_000_000
ops.append(op)
scenarios.append({"name": f"fuzz_{i}", "ops": ops})
return scenarios
def main():
import shutil
work = "/tmp/ta_parity_work"
shutil.rmtree(work, ignore_errors=True)
ok, total = replay_truth(work)
print(f"truth replay parity: {ok}/{total}")
rng = random.Random(2479)
texts = fuzz_texts(rng, 300)
scenarios = fuzz_scenarios(rng, 200, texts)
fz_total = sum(len(s["ops"]) for s in scenarios)
py = drive_pin(scenarios, "python", os.path.join(work, "fpy"))
rs = drive_pin(scenarios, "auto", os.path.join(work, "frs"))
fz_ok = 0
for s, a, b in zip(scenarios, py, rs):
for i, (x, y) in enumerate(zip(a, b)):
if x == y:
fz_ok += 1
else:
print(f"DIVERGE {s['name']} op#{i} ({s['ops'][i].get('op')})")
print(" py:", x)
print(" rs:", y)
print(f"fuzz parity (seed 2479): {fz_ok}/{fz_total}")
if ok == total and fz_ok == fz_total:
print(f"PARITY OK — {ok + fz_ok}/{total + fz_total} observations, 100%")
return 0
print("PARITY FAILED")
return 1
if __name__ == "__main__":
raise SystemExit(main())
+232
View File
@@ -0,0 +1,232 @@
#!/usr/bin/env python3
"""Live parity: Rust toolchain kernel vs the Python oracle (dichotomy
wave #2479). Replays the frozen truth corpus PLUS seeded fuzz over every
public function and branch. The module is deterministic — no masking.
Both sides must agree on values AND on raising.
The facade reads RUNE_TC_CORE per call, so one process can flip between
the pinned oracle (RUNE_TC_CORE=python) and the kernel (unset).
"""
from __future__ import annotations
import json
import os
import random
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
LANGS = ["python", "rust", "typescript", "go", "zig", "", "C++",
"café-lang ☕", "r"]
KEYS = sorted([
"existing_language", "existing_runtime", "existing_framework",
"target_platform", "deployment", "performance_sensitive",
"latency_sensitive", "memory_constrained", "concurrency_model",
"interoperability", "gui_native", "expected_lifespan",
"user_constraints", "ecosystem_requirement"])
UNKNOWN_KEYS = ["wibble", "flurb", "custom_axis", "future_thing"]
VALS = ["linux", "server", "tokio", "svelte", True, False, None, 0, 1,
"", ["a", "b"], {"deep": True}, 2.5, "x" * 60]
KEYWORD_POOL = ["existing_language", "target_platform", "deployment",
"performance_sensitive", "latency_sensitive",
"memory_constrained", "gui_native", "wibble"]
def call_core(core, fn, *args, **kwargs):
from rune import toolchain as tc
old = os.environ.get("RUNE_TC_CORE")
os.environ["RUNE_TC_CORE"] = core
try:
return getattr(tc, fn)(*args, **kwargs)
finally:
if old is None:
os.environ.pop("RUNE_TC_CORE", None)
else:
os.environ["RUNE_TC_CORE"] = old
def toj(x):
return x.to_dict() if hasattr(x, "to_dict") else x
def constraints_of(core, kw):
return call_core(core, "ToolchainConstraints", **kw)
def run_pair(invoke):
"""invoke(core) runs the full call chain under one core pin. Returns
(ok, detail): ok True when values match, None when BOTH sides raised
(agreement on refusing), False on any divergence."""
outs = []
for core in ("python", "rust"):
try:
outs.append((toj(invoke(core)), None))
except Exception as e:
outs.append((None, repr(e)))
(py, py_err), (rs, rs_err) = outs
if (py_err is None) != (rs_err is None):
return False, f"py_err={py_err} rs_err={rs_err}"
if py_err is not None:
return None, f"both raised: {py_err}"
return py == rs, f"py={py!r} rs={rs!r}"
def fuzz_cases(rng):
out = []
# constraints classification
for _ in range(120):
kw = {}
for k in KEYWORD_POOL + rng.sample(UNKNOWN_KEYS, rng.randrange(0, 3)):
if rng.random() < 0.5:
kw[k] = rng.choice(VALS)
out.append(("constraints", kw))
# constraints_get
for _ in range(60):
kw = {rng.choice(KEYS + UNKNOWN_KEYS): rng.choice(VALS)}
key = rng.choice(list(kw) + ["missing_key"])
out.append(("constraints_get", kw, key,
rng.choice([None, "dflt", 0, False, ["x"]])))
# decision_record normalization
for _ in range(80):
fields = dict(
selected_language=rng.choice(LANGS),
runtime=rng.choice(["", "tokio", None]),
framework=rng.choice(["", "svelte", None]),
libraries=rng.choice([None, [], ["b", "a"], ["z", "a", "m"]]),
build_system=rng.choice(["", "cargo"]),
alternatives=rng.choice([None, [], [{"language": "go"}]]),
decisive_constraints=rng.choice([None, [], ["z", "a"]]),
rationale=rng.choice(["", "measured 10x"]),
evidence_refs=rng.choice([None, [], ["e2", "e1"]]),
confidence=rng.choice([0.0, 0.4, 0.5, 0.7, 0.8, 0.85]),
user_approval_required=rng.choice([True, False]),
resolution=rng.choice(["", "KEEP_EXISTING", "CHANGE_MAJOR",
"INSUFFICIENT_EVIDENCE"]),
)
fields = {k: v for k, v in fields.items() if rng.random() < 0.85}
out.append(("decision_record", fields))
# major_change
for _ in range(60):
out.append(("major_change", {"existing_language": rng.choice(LANGS)},
rng.choice(LANGS)))
# resolve_toolchain — every branch reachable
for _ in range(250):
kw = {}
if rng.random() < 0.8:
kw["existing_language"] = rng.choice(LANGS)
for k in ("existing_runtime", "existing_framework"):
if rng.random() < 0.3:
kw[k] = rng.choice(["tokio", "node", ""])
for k in ("target_platform", "deployment", "performance_sensitive",
"latency_sensitive", "memory_constrained", "gui_native"):
if rng.random() < 0.3:
kw[k] = rng.choice(VALS)
rkw = {}
if rng.random() < 0.6:
rkw["requested_language"] = rng.choice(
[l for l in LANGS if l] + [None])
if rng.random() < 0.4:
rkw["evidence_refs"] = rng.choice([[], ["b", "a"], ["only"]])
if rng.random() < 0.4:
rkw["performance_evidence"] = rng.choice([
{"significant_benefit": True, "basis": "measured"},
{"significant_benefit": False},
{"significant_benefit": None},
{}, {"other": 1},
])
out.append(("resolve_toolchain", kw, rkw))
return out
def main() -> int:
from rune import toolchain as tc
from rune import tl_loader
if not tl_loader.available():
print("Rust core not built — run cargo build --release in src/rust/toolchain")
return 2
total = passed = agreed_errors = 0
failures: list[str] = []
def record(label, ok, detail=""):
nonlocal total, passed, agreed_errors
total += 1
if ok is None: # both sides raised — agreement on refusal
agreed_errors += 1
passed += 1
elif ok:
passed += 1
else:
failures.append(f"{label}: {detail}")
def resolve_invoke(rkw):
return lambda c: call_core(
c, "resolve_toolchain",
constraints_of(c, rkw[0]), **rkw[1]).to_dict()
# 1. frozen truth corpus, replayed live on BOTH cores
truth = json.loads((Path(__file__).resolve().parents[1] / "src" / "rust" /
"toolchain" / "src" / "frozen_tc_truth.json").read_text())
for fn, cases in truth["cases"].items():
for case in cases:
name = f"truth:{fn}:{case['name']}"
inp = case["input"]
if fn == "constraint_keys":
py = sorted(tc.CONSTRAINT_KEYS)
rs = tl_loader.constraint_keys()
record(name, py == rs, f"py={py} rs={rs}")
elif fn == "constraints":
record(name, *run_pair(
lambda c, i=inp: constraints_of(c, i["kwargs"]).to_dict()))
elif fn == "constraints_get":
record(name, *run_pair(
lambda c, i=inp: constraints_of(c, i["known"])
.get(i["key"], i["default"])))
elif fn == "decision_record":
record(name, *run_pair(
lambda c, i=inp: call_core(c, "DecisionRecord", **i).to_dict()))
elif fn == "major_change":
record(name, *run_pair(
lambda c, i=inp: call_core(
c, "_major_change", constraints_of(c, i["known"]),
i["language"])))
elif fn == "resolve_toolchain":
rkw = {k: v for k, v in inp.items()
if k in ("requested_language", "evidence_refs",
"performance_evidence")}
record(name, *run_pair(resolve_invoke((inp["known"], rkw))))
# 2. seeded fuzz (kernel core vs freshly-pinned oracle per case)
rng = random.Random(2479)
for case in fuzz_cases(rng):
fn = case[0]
name = f"fuzz:{fn}"
if fn == "constraints":
ok, d = run_pair(lambda c, i=case[1]: constraints_of(c, i).to_dict())
elif fn == "constraints_get":
_, kw, key, default = case
ok, d = run_pair(lambda c, k=kw, key=key, df=default:
constraints_of(c, k).get(key, df))
elif fn == "decision_record":
ok, d = run_pair(lambda c, i=case[1]:
call_core(c, "DecisionRecord", **i).to_dict())
elif fn == "major_change":
_, kw, language = case
ok, d = run_pair(lambda c, k=kw, l=language: call_core(
c, "_major_change", constraints_of(c, k), l))
else: # resolve_toolchain
_, kw, rkw = case
ok, d = run_pair(resolve_invoke((kw, rkw)))
record(name, ok, f"case={case} {d}")
print(f"parity {passed}/{total} ({agreed_errors} agreed-error cases)")
if failures:
for f in failures[:12]:
print("FAIL", f[:400])
return 1
return 0
if __name__ == "__main__":
sys.exit(main())
+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")
+51
View File
@@ -0,0 +1,51 @@
#!/usr/bin/env python3
"""Open + walk + close the #2479 dichotomy-wave construction contract for
message_graph. 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(
"Port src/rune/message_graph.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": "message_graph parity port — parity 1f7a2664 "
"feat(message_graph): port to FULL Rust — dichotomy wave; "
"Phase B 7194f69a refactor(message_graph): Phase B "
"language-advantage pass — dichotomy wave (both pushed "
"gitea/master). Battery 12 tests replay 68 frozen-oracle "
"cases VERBATIM; live parity 68/68 byte-identical; clippy 0; "
"Python suite green under both pins (9+9).",
}], scope_files=[
"src/rune/message_graph.py",
"src/rune/mg_loader.py",
"src/rust/message_graph/src/lib.rs",
"src/rust/message_graph/src/branch_battery.rs",
"scripts/gen_message_graph_truth.py",
])
print("close:", json.dumps(c)[:400])
print("CONTRACT", cid, "closed" if c.get("success") else "CLOSE_FAILED")
+49
View File
@@ -0,0 +1,49 @@
#!/usr/bin/env python3
"""Close the #2479 dichotomy-wave construction contract for self_modify.
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(
"Port src/rune/self_modify.py to FULL Rust (dichotomy factory, issue "
"#2479 tranche): kernel-owned subprocess seam (_run), doc-envelope FFI, "
"ctypes loader, facade delegation with RUNE_SMOD_CORE=python pin, "
"frozen-truth battery with deterministic git fixtures, 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": "self_modify parity port — evidence attached at close (SHAs "
"reported in the builder report)",
}], scope_files=[
"src/rune/self_modify.py",
"src/rune/smod_loader.py",
"src/rust/self_modify/src/lib.rs",
"src/rust/self_modify/src/branch_battery.rs",
"src/rust/self_modify/src/frozen_smod_truth.json",
"scripts/gen_self_modify_truth.py",
])
print("close:", json.dumps(c)[:400])
print("CONTRACT", cid, "closed" if c.get("success") else "CLOSE_FAILED")
+244
View File
@@ -0,0 +1,244 @@
#!/usr/bin/env python3
"""Live-parity fuzz for the capability_ledger port (dichotomy factory,
#2479 tranche). Generates ONE seeded random op sequence (seed 2479) and
replays it through the FULL Python facade twice:
* RUNE_LEDGER_CORE=python — the oracle body
* RUNE_LEDGER_CORE=auto — the Rust kernel via cl_loader (facade
delegation), falling back silently only if the .so is absent
and diffs every op result (value JSON + exception type/message) plus the
final saved-file bytes. Clock is pinned per op so both runs stamp
identically (the kernel receives the same `now` string the oracle's
_now() returns).
"""
from __future__ import annotations
import json
import os
import sys
import tempfile
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
SEED = 2479
N_OPS = 500
IDS = ["alpha", "beta", "gamma", "delta", "ghost", "cap:beta", "cap:ghost"]
COMPONENTS = ["component:c1", "component:c2", "component:missing"]
LONG = "L" * 260
def gen_ops():
import random
rng = random.Random(SEED)
ops = [("new", {})]
has_spec = set()
for _ in range(N_OPS - 1):
r = rng.random()
cid = rng.choice(IDS)
if r < 0.14:
ops.append(("define", {
"capability_id": cid,
"name": rng.choice(["A", "Bee", "C-3"]),
"purpose": rng.choice(["p1", "purpose two"]),
"owning_project": "rune",
"component": rng.choice(COMPONENTS),
"source_locations": rng.choice(
[None, [], ["src/a.py"], ["src/a.py", "src/b.py"]]),
"dependencies": rng.choice([None, [], ["dep-1"]]),
"consumers": rng.choice([None, [], ["cons-1"]]),
"invariants": rng.choice([None, [], ["inv-1"]]),
"behaviour_specs": rng.choice([None, [], ["bs-1"]]),
"contract": rng.choice(["", "contract text"]),
"state_touched": rng.choice(["", "store/scaffold"]),
"constraints": rng.choice(["", "no raw tokens"]),
"generation": rng.choice(["", "gen-1"]),
}))
elif r < 0.30:
kw = {"note": rng.choice(["n1", LONG, "ünïcode ✓ note"])}
if rng.random() < 0.4:
kw["generation"] = rng.choice(["", "gen-9"])
if rng.random() < 0.4:
kw["source_locations"] = rng.choice(
[[], ["src/moved.py"], ["a.py", "b.py"]])
ops.append(("material_change", {"capability_id": cid, **kw}))
elif r < 0.38:
ops.append(("move", {"capability_id": cid,
"new_locations": rng.choice(
[[], ["new/loc.py"], ["x.py", "y.py"]])}))
elif r < 0.46:
ops.append(("supersede", {"capability_id": cid,
"by": rng.choice(["new-cap", "cap:beta", "ghost"]),
"note": rng.choice(["", "why not", LONG])}))
elif r < 0.52:
ops.append(("retire", {"capability_id": cid,
"note": rng.choice(["", "dead", LONG])}))
elif r < 0.62:
ops.append(("get", {"capability_id": cid}))
elif r < 0.68:
ops.append(("active", {}))
elif r < 0.74:
ops.append(("by_component", {"component": rng.choice(COMPONENTS)}))
elif r < 0.80:
ops.append(("context_packet", {"capability_id": cid}))
elif r < 0.83:
ops.append(("save", {}))
elif r < 0.89:
kw = {"requirements": rng.choice([[], ["r1"], ["r1", "r2"]]),
"expected_behaviours": rng.choice([[], ["b1"]]),
"non_goals": rng.choice([[], ["ng1"]]),
"acceptance_criteria": rng.choice([[], ["ac1"]])}
if rng.random() < 0.3:
kw["baseline_version"] = rng.choice(
["0.1.0", "1.0.0", "1.0", "not-semver"])
if rng.random() < 0.3:
kw["baseline_reason"] = rng.choice(["stable", LONG])
if rng.random() < 0.3:
kw["governance_version_started_at"] = "2025-12-31T00:00:00Z"
ops.append(("attach_spec", {"capability_id": cid, **kw}))
elif r < 0.97:
kw = {"reason": rng.choice(["why", LONG]),
"contract_changed": rng.random() < 0.2,
"behaviour_added": rng.random() < 0.3,
"consumer_break": rng.random() < 0.15,
"implementation_only": rng.random() < 0.5,
"changed_sections": rng.choice([None, [], ["sec-1"]]),
"affected_consumers": rng.choice([None, [], ["cons-x"]]),
"validation_consequences": rng.choice([None, [], ["retest"]])}
if rng.random() < 0.35:
kw["proposed_kind"] = rng.choice(
["PATCH", "MINOR", "MAJOR", "HUGE", ""])
ops.append(("revise_spec", {"capability_id": cid, **kw}))
elif r < 0.99:
ops.append(("spec_drift_status", {"capability_id": cid}))
elif r < 0.995:
ops.append(("spec_context", {"capability_id": cid}))
else:
if rng.random() < 0.5:
ops.append(("bump_version", {
"version": rng.choice(
["0.1.0", "1.2.3", "01.02.03", "0.0.9", "1.2",
"abc", "", "1.2.3.4", "-1.2.3", "1.2.x",
"18446744073709551615.0.0"]),
"kind": rng.choice(["MAJOR", "MINOR", "PATCH", "BOGUS"])}))
else:
ops.append(("classify_version_bump", {
"contract_changed": rng.random() < 0.3,
"behaviour_added": rng.random() < 0.4,
"consumer_break": rng.random() < 0.2,
"implementation_only": rng.random() < 0.5}))
return ops
def run(ops):
import rune.capability_ledger as cl
trace = []
stamp = [0]
def now():
return f"2026-03-04T00:{stamp[0] // 60:02d}:{stamp[0] % 60:02d}Z"
tmp = tempfile.mkdtemp(prefix="clfuzz-")
led = None
for opname, kw in ops:
stamp[0] += 1
cl._now = now # pinned for the whole op (python path); kernel gets now where passed
try:
if opname == "new":
led = cl.CapabilityLedger(tmp)
out = None
elif opname == "define":
out = led.define(**kw)
elif opname == "material_change":
out = led.material_change(kw["capability_id"],
note=kw["note"],
generation=kw.get("generation", ""),
source_locations=kw.get("source_locations"))
elif opname == "move":
out = led.move(kw["capability_id"], kw["new_locations"])
elif opname == "supersede":
out = led.supersede(kw["capability_id"], kw["by"],
note=kw["note"])
elif opname == "retire":
out = led.retire(kw["capability_id"], note=kw["note"])
elif opname == "get":
out = led.get(kw["capability_id"])
elif opname == "active":
out = led.active()
elif opname == "by_component":
out = led.by_component(kw["component"])
elif opname == "context_packet":
out = led.context_packet(kw["capability_id"])
elif opname == "save":
led.save()
out = None
elif opname == "attach_spec":
out = cl.attach_spec(led, kw["capability_id"],
requirements=kw["requirements"],
expected_behaviours=kw["expected_behaviours"],
non_goals=kw["non_goals"],
acceptance_criteria=kw["acceptance_criteria"],
baseline_version=kw.get("baseline_version", "0.1.0"),
baseline_reason=kw.get("baseline_reason", "initial governed baseline"),
governance_version_started_at=kw.get("governance_version_started_at"))
elif opname == "revise_spec":
out = cl.revise_spec(led, kw["capability_id"],
contract_changed=kw["contract_changed"],
behaviour_added=kw["behaviour_added"],
consumer_break=kw["consumer_break"],
implementation_only=kw["implementation_only"],
reason=kw["reason"],
changed_sections=kw["changed_sections"],
affected_consumers=kw["affected_consumers"],
validation_consequences=kw["validation_consequences"],
proposed_kind=kw.get("proposed_kind"))
elif opname == "spec_drift_status":
out = cl.spec_drift_status(led, kw["capability_id"])
elif opname == "spec_context":
out = cl.spec_context(led.get(kw["capability_id"]))
elif opname == "bump_version":
out = cl.bump_version(kw["version"], kw["kind"])
else:
out = cl.classify_version_bump(
contract_changed=kw["contract_changed"],
behaviour_added=kw["behaviour_added"],
consumer_break=kw["consumer_break"],
implementation_only=kw["implementation_only"])
trace.append(json.dumps({"ok": True, "value": out},
sort_keys=True, default=str))
except Exception as e:
trace.append(json.dumps(
{"ok": False, "err": f"{type(e).__name__}: {e}"},
sort_keys=True))
saved = (Path(tmp) / "capability_ledger.json")
trace.append(saved.read_text() if saved.is_file() else "NO_FILE")
return trace
def main() -> int:
ops = gen_ops()
# kernel path first (loader caches; env must differ per run)
os.environ["RUNE_LEDGER_CORE"] = "auto"
from rune import cl_loader
if not cl_loader.available():
print("KERNEL .so ABSENT — build libcapability_ledger.so first")
return 2
rust = run(ops)
os.environ["RUNE_LEDGER_CORE"] = "python"
py = run(ops)
os.environ["RUNE_LEDGER_CORE"] = "auto"
div = [i for i, (a, b) in enumerate(zip(py, rust)) if a != b]
print(f"ops={len(ops)} compared={len(py)} divergences={len(div)}")
for i in div[:10]:
print(f"--- op {i} {ops[i] if i < len(ops) else '(file bytes)'}")
print(" PY :", py[i][:300])
print(" RUST:", rust[i][:300])
print("PARITY:", "100%" if not div else f"{100 * (len(py) - len(div)) // len(py)}%")
return 0 if not div else 1
if __name__ == "__main__":
sys.exit(main())
+196
View File
@@ -0,0 +1,196 @@
#!/usr/bin/env python3
"""Freeze behavior oracle truth for the Rust port battery
(dichotomy wave #2479 tranche, batch: behavior).
behavior.py's decision surface (9 public fns):
connect, add_correction, add_habit, match_context, reinforce,
list_items, behavior_stats, main (CLI — not frozen; argparse/IO),
PLUGIN (data — embedded verbatim in the crate).
The decision math (reinforce counting/confidence/promotion/state) is
served IN-PROCESS by the Rust evidence_provenance cdylib through
ep_loader — the oracle IS the strangler shim. The frozen truth pins
RUNE_BEHAVIOR_CORE=python (no-op today; the POISONED-TRUTH LAW: a
future delegating facade must never silently record kernel output as
oracle truth).
Frozen: add_correction/add_habit return shapes, match_context branch
matrix (pattern/domain/confidence/match_type/invalid-regex), reinforce
branch matrix (habit/correction x success/failure/neutral x
promotion/archival/not-found), list_items filters+limits, stats
counts. SQLite rows replayed as dicts (row_factory=Row -> dict(c)).
Output: /tmp/behavior_truth.json — replayed VERBATIM by
src/rust/behavior/src/branch_battery.rs.
"""
from __future__ import annotations
import json
import os
import sys
import tempfile
from pathlib import Path
os.environ["RUNE_BEHAVIOR_CORE"] = "python" # freeze the ORACLE, never the kernel
_tmp = tempfile.mkdtemp(prefix="bh-truth-")
os.environ["RUNE_HOME"] = _tmp
os.environ["RUNE_MEMORY_DIR"] = os.path.join(_tmp, "memory")
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
from rune import behavior as bh # noqa: E402
def row(table, rid):
db = bh.connect()
try:
r = db.execute(f"SELECT * FROM {table} WHERE id=?", (rid,)).fetchone()
return dict(r) if r else None
finally:
db.close()
cases = {"add_correction": [], "add_habit": [], "match": [],
"reinforce_habit": [], "reinforce_correction": [],
"list": [], "stats": []}
# ── add_correction shapes ─────────────────────────────────────────────
r = bh.add_correction("say X", "say Y")
cases["add_correction"].append({"name": "defaults", "args": ["say X", "say Y"],
"expected": r, "row": row("corrections", r["added"])})
r = bh.add_correction("orig", "corr", scope_tags={"k": "v"}, domain="shell",
pattern="shell")
cases["add_correction"].append({"name": "scoped", "args": ["orig", "corr"],
"expected": r, "row": row("corrections", r["added"])})
# ── add_habit shapes ──────────────────────────────────────────────────
r = bh.add_habit("trig", "act")
cases["add_habit"].append({"name": "defaults", "expected": r,
"row": row("habits", r["added"])})
r = bh.add_habit("trig2", "act2", name="n", context_tags=["a"],
min_activations=5)
cases["add_habit"].append({"name": "full", "expected": r,
"row": row("habits", r["added"])})
# ── match_context branch matrix ───────────────────────────────────────
# seed: pattern+domain correction, domain-only correction, low-conf
# correction, pattern-only correction, bad-regex correction, habits
bh.add_correction("o1", "scoped corr", scope_tags={}, domain="shell",
pattern="deploy")
bh.add_correction("o2", "domain corr", scope_tags={}, domain="editor")
bh.add_correction("o3", "plain corr")
bh.add_correction("o4", "bad regex corr", pattern="([unclosed")
low = bh.add_correction("o5", "low conf corr")["added"]
db = bh.connect()
db.execute("UPDATE corrections SET confidence=0.2 WHERE id=?", (low,))
db.commit()
db.close()
bh.add_habit("deploy", "run deploy script")
bh.add_habit("test (unit|e2e)", "run tests")
bh.add_habit("([broken", "bad regex habit")
hslow = bh.add_habit("zzz-no-match-ever", "quiet")["added"]
db = bh.connect()
db.execute("UPDATE habits SET confidence=0.2 WHERE id=?", (hslow,))
db.commit()
db.close()
def match_case(name, context, match_type=None):
got = bh.match_context(context, match_type=match_type)
return {"name": name, "context": context, "match_type": match_type,
"corrections": sorted(c["corrected_behavior"] for c in got["corrections"]),
"habits": sorted(h["action_template"] for h in got["habits"]),
"n_corr_fields": sorted(got["corrections"][0].keys()) if got["corrections"] else [],
"scope_tags_type": type(got["corrections"][0]["scope_tags"]).__name__ if got["corrections"] else None}
cases["match"].append(match_case("pattern-domain-hit", "deploy to shell now"))
cases["match"].append(match_case("domain-only-miss", "deploy the editor"))
cases["match"].append(match_case("plain-always", "unrelated chatter here"))
cases["match"].append(match_case("type-habit-only", "deploy to shell now", "habit"))
cases["match"].append(match_case("type-correction-only", "deploy to shell now", "correction"))
cases["match"].append(match_case("habit-regex-hit", "run the unit tests"))
cases["match"].append(match_case("low-conf-filtered", "zzz-no-match-ever quiet"))
cases["match"].append(match_case("bad-regex-skipped", "([unclosed anything"))
# ── reinforce matrix ──────────────────────────────────────────────────
def reinf_case(name, item_type, outcome, seed_fn, pre=None, n=1):
rid = seed_fn()["added"]
if pre:
db = bh.connect()
for k, v in pre.items():
db.execute(f"UPDATE {'habits' if item_type == 'habit' else 'corrections'} SET {k}=? WHERE id=?", (v, rid))
db.commit()
db.close()
outs = [bh.reinforce(rid, outcome, item_type=item_type) for _ in range(n)]
return {"name": name, "item_type": item_type, "outcome": outcome,
"pre": pre or {}, "n": n, "results": outs,
"final_row": row("habits" if item_type == "habit" else "corrections", rid)}
cases["reinforce_habit"].append(reinf_case(
"success-promotes-at-3", "habit", "success", lambda: bh.add_habit("t1", "a1"), n=3))
cases["reinforce_habit"].append(reinf_case(
"failure-counts", "habit", "failure", lambda: bh.add_habit("t2", "a2"), n=2))
cases["reinforce_habit"].append(reinf_case(
"neutral-bumps-activation-only", "habit", "neutral",
lambda: bh.add_habit("t3", "a3"), n=1))
cases["reinforce_habit"].append(reinf_case(
"already-active-stays", "habit", "success",
lambda: bh.add_habit("t4", "a4", min_activations=1), n=2))
cases["reinforce_habit"].append(reinf_case(
"min5-gate", "habit", "success",
lambda: bh.add_habit("t5", "a5", min_activations=5), n=4))
cases["reinforce_correction"].append(reinf_case(
"success-plus-point-one", "correction", "success",
lambda: bh.add_correction("x1", "y1"), n=1))
cases["reinforce_correction"].append(reinf_case(
"failure-archives", "correction", "failure",
lambda: bh.add_correction("x2", "y2"), n=1))
cases["reinforce_correction"].append(reinf_case(
"double-failure-floor", "correction", "failure",
lambda: bh.add_correction("x3", "y3"), n=3))
cases["reinforce_correction"].append(reinf_case(
"ceiling-at-one", "correction", "success",
lambda: bh.add_correction("x4", "y4"), pre={"confidence": 0.95}, n=1))
cases["reinforce_correction"].append(reinf_case(
"neutral-noop", "correction", "neutral",
lambda: bh.add_correction("x5", "y5"), n=1))
cases["reinforce_habit"].append(
{"name": "habit-not-found", "item_type": "habit", "outcome": "success",
"pre": {}, "n": 1,
"results": [bh.reinforce(99991, "success", item_type="habit")],
"final_row": None})
cases["reinforce_correction"].append(
{"name": "correction-not-found", "item_type": "correction",
"outcome": "success", "pre": {}, "n": 1,
"results": [bh.reinforce(99992, "success", item_type="correction")],
"final_row": None})
# ── list_items filters ────────────────────────────────────────────────
cases["list"].append({"name": "all-keys",
"result_keys": sorted(bh.list_items().keys())})
for t in ("all", "correction", "habit"):
got = bh.list_items(item_type=t)
cases["list"].append({"name": f"type-{t}", "keys": sorted(got.keys()),
"n_corr": len(got.get("corrections", [])),
"n_hab": len(got.get("habits", []))})
got = bh.list_items(limit=1)
cases["list"].append({"name": "limit-1", "n_corr": len(got.get("corrections", [])),
"n_hab": len(got.get("habits", []))})
# ── stats ─────────────────────────────────────────────────────────────
cases["stats"].append({"name": "counts", "expected": bh.behavior_stats()})
truth = {"frozen": "behavior decision surface (reinforce math via ep strangler shim)",
"oracle": "src/rune/behavior.py",
"cases": cases,
"plugin": bh.PLUGIN}
out = Path("/tmp/behavior_truth.json")
out.write_text(json.dumps(truth, indent=1, default=str) + "\n")
n = (len(cases["add_correction"]) + len(cases["add_habit"]) + len(cases["match"])
+ len(cases["reinforce_habit"]) + len(cases["reinforce_correction"])
+ len(cases["list"]) + len(cases["stats"]))
print(f"froze {n} cases -> {out}")
+323
View File
@@ -0,0 +1,323 @@
#!/usr/bin/env python3
"""Freeze build_basis oracle truth for the Rust port battery (dichotomy
wave #2479 tranche, batch: build_basis — RUST side verdict).
Exercises EVERY public function across EVERY branch:
research_depth (all route defaults, unknown-route floor, tier-selecting
vs annotating justifications, blank justification), new_basis,
set_dispositions (legal/illegal/missing keys), validate_basis (schema,
depth, WHY rule D, OUTWARD/FULL prior-art gate, INWARD evidence gate,
illegal disposition, item truncation), is_qualified,
attach_evidence_ref (issue_ref/adhoc/missing created_at), summary
(empty/qualified/None implications/counts), revise_depth (legal
revision, blocked downgrade, no-op, unknown depth, reason truncation),
to_json (Python json.dumps indent=1 sort_keys incl. non-ASCII escaping),
from_json, generate_research_plan (topic kinds, outward keywords,
depth heuristics FULL/OUTWARD/INWARD/NONE, blank filtering, caller
depth, truncations).
Output: src/rust/build_basis/src/frozen_bb_truth.json — replayed
VERBATIM by src/rust/build_basis/src/branch_battery.rs.
Timestamps the kernel generates (decided_at/created_at/revised_at/
plan_generated_at) are masked (removed on both sides) — they are wall
clock, not logic. Fixed created_at values are INJECTED into basis inputs
so kernel pass-through fields compare exactly.
"""
from __future__ import annotations
import json
import os
import sys
from pathlib import Path
os.environ["RUNE_BB_CORE"] = "python" # POISONED-TRUTH LAW: freeze the ORACLE, never the kernel
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
from rune import build_basis as bb # noqa: E402
MASKED = ("decided_at", "created_at", "revised_at", "plan_generated_at")
def mask(doc):
"""Remove kernel-generated wall-clock fields wherever they appear."""
if isinstance(doc, dict):
return {k: mask(v) for k, v in doc.items() if k not in MASKED}
if isinstance(doc, list):
return [mask(v) for v in doc]
return doc
def fixed_basis(depth_route="RESEARCHED_CHANGE", question="q",
issue_ref="research/issue-1"):
b = bb.new_basis(question=question,
depth_decision=bb.research_depth(depth_route),
issue_ref=issue_ref)
b["created_at"] = "2026-10-11T00:00:00Z" # fixed: pass-through, exact-compare
return b
cases: dict[str, list] = {}
def run(fn: str, name: str, **input):
out = getattr(bb, fn)(**input)
cases.setdefault(fn, []).append(
{"name": name, "input": input, "expected": mask(out)})
def run_err(fn: str, name: str, **input):
try:
getattr(bb, fn)(**input)
except ValueError as e:
cases.setdefault(fn, []).append(
{"name": name, "input": input, "error": str(e)})
return
raise AssertionError(f"{fn}({name}) did not raise")
# ── research_depth: every branch ──────────────────────────────────────
for route in ("LOCAL_FIX", "STANDARD_CHANGE", "RESEARCHED_CHANGE"):
run("research_depth", f"route-{route}", route=route)
run("research_depth", "unknown-route-floor", route="MYSTERY_ROUTE")
run("research_depth", "annotation-justification", route="RESEARCHED_CHANGE",
justification="tiny config fix; full pipeline disproportionate")
run("research_depth", "tier-select-justification", route="STANDARD_CHANGE",
justification="OUTWARD: new provider integration, precedent needed")
run("research_depth", "tier-select-downgrade-NONE", route="RESEARCHED_CHANGE",
justification="NONE: nothing to research")
run("research_depth", "tier-bare-word", route="STANDARD_CHANGE",
justification=" OUTWARD ")
run("research_depth", "tier-multi-colon", route="STANDARD_CHANGE",
justification="FULL: architectural; check heuristics first")
run("research_depth", "blank-justification-default", route="LOCAL_FIX",
justification=" ")
run("research_depth", "blank-justification-unknown", route="WEIRD",
justification=" ")
run("research_depth", "unknown-route-with-justification", route="WEIRD",
justification="check the spike notes")
run("research_depth", "mode-recorded", route="RESEARCHED_CHANGE",
mode="rune-internal")
# ── new_basis ─────────────────────────────────────────────────────────
b = bb.new_basis(question="structural model for project scaffolds",
depth_decision=bb.research_depth("RESEARCHED_CHANGE"),
issue_ref="research/structural-model")
b["created_at"] = "2026-10-11T00:00:00Z"
cases.setdefault("new_basis", []).append(
{"name": "full", "input": {"question": b["question"],
"depth_decision": mask(b["depth"]),
"issue_ref": b["issue_ref"]},
"expected": mask(b)})
b2 = bb.new_basis(question="q", depth_decision=bb.research_depth("LOCAL_FIX"))
b2["created_at"] = "2026-10-11T00:00:00Z"
cases["new_basis"].append(
{"name": "no-issue-ref", "input": {"question": "q",
"depth_decision": mask(b2["depth"])},
"expected": mask(b2)})
# ── set_dispositions ──────────────────────────────────────────────────
for nm, disp in (
("legal", [{"item": "Backstage catalog entity model", "disposition": "ADAPT",
"why": "nesting fits", "ref": "backstage.io"},
{"item": "own graph database", "disposition": "REJECT",
"why": "structural_store is adequate", "ref": "repo"}]),
("illegal", [{"item": "x", "disposition": "MAYBE"}]),
("missing-keys", [{}]),
("empty-list", []),
):
basis = fixed_basis()
before = json.loads(json.dumps(basis))
errs = bb.set_dispositions(basis, disp)
cases.setdefault("set_dispositions", []).append(
{"name": nm, "input": {"basis": before, "dispositions": disp},
"expected": {"basis": mask(basis), "errors": errs}})
# ── validate_basis: every error branch ────────────────────────────────
def val_case(nm, basis):
cases.setdefault("validate_basis", []).append(
{"name": nm, "input": {"basis": basis},
"expected": bb.validate_basis(basis)})
b = fixed_basis(); b["schema_version"] = 2
val_case("schema-mismatch", b)
b = fixed_basis(); b["schema_version"] = None
val_case("schema-none", b)
b = fixed_basis(); b["depth"] = {}
val_case("depth-missing", b)
b = fixed_basis(); b["depth"] = {"depth": "WEIRD", "why": "w"}
val_case("depth-illegal", b)
b = fixed_basis(); b["depth"] = {"depth": "FULL"}
val_case("why-missing-rule-D", b)
val_case("outward-no-evidence", fixed_basis(depth_route="STANDARD_CHANGE",
issue_ref="",
question="q") | {"depth": {"depth": "OUTWARD", "why": "w"}})
b = fixed_basis(); b["depth"] = {"depth": "FULL", "why": "w"}
val_case("full-no-evidence", b)
val_case("inward-empty", bb.new_basis(question="q",
depth_decision={"depth": "INWARD", "why": "w"}))
b = bb.new_basis(question="q", depth_decision={"depth": "INWARD", "why": "w"})
b["inward"]["findings"] = ["f"]
val_case("inward-with-findings", b)
b = fixed_basis()
b["dispositions"] = [{"item": "x" * 60, "disposition": "MAYBE"}]
val_case("illegal-disposition-item-truncated-40", b)
b = fixed_basis(); b.pop("dispositions")
val_case("dispositions-key-absent", b)
b = fixed_basis(); b["outward"] = None
val_case("outward-null-inward-ok", b)
val_case("qualified-basis-zero-errors", fixed_basis() | {
"outward": {"prior_art_questions_answered": ["q1"], "findings": [], "refs": []},
"dispositions": [{"item": "i", "disposition": "ADOPT", "why": "w", "ref": "r"}]})
# ── is_qualified ──────────────────────────────────────────────────────
q = fixed_basis()
q["outward"] = {"prior_art_questions_answered": ["q1"], "findings": [], "refs": []}
q["dispositions"] = [{"item": "i", "disposition": "ADOPT", "why": "w", "ref": "r"}]
cases.setdefault("is_qualified", []).append(
{"name": "qualified", "input": {"basis": q}, "expected": bb.is_qualified(q)})
cases["is_qualified"].append(
{"name": "empty-unqualified", "input": {"basis": fixed_basis()},
"expected": False})
# ── attach_evidence_ref ───────────────────────────────────────────────
cases.setdefault("attach_evidence_ref", []).append(
{"name": "with-issue-ref", "input": {"basis": fixed_basis()},
"expected": bb.attach_evidence_ref(fixed_basis())})
b = fixed_basis(); b["issue_ref"] = ""
cases["attach_evidence_ref"].append(
{"name": "adhoc", "input": {"basis": b}, "expected": bb.attach_evidence_ref(b)})
b = fixed_basis(); b.pop("created_at")
cases["attach_evidence_ref"].append(
{"name": "missing-created-at", "input": {"basis": b},
"expected": bb.attach_evidence_ref(b)})
# ── summary ───────────────────────────────────────────────────────────
b = fixed_basis()
cases.setdefault("summary", []).append(
{"name": "empty", "input": {"basis": json.loads(json.dumps(b))},
"expected": mask(bb.summary(b))})
q2 = fixed_basis()
q2["outward"] = {"prior_art_questions_answered": [], "findings": ["f"], "refs": ["r"]}
q2["dispositions"] = [{"item": "x" * 100, "disposition": "ADOPT", "why": "w", "ref": "r"},
{"item": "short", "disposition": "ADAPT"}]
q2["uncertainty"] = ["u1", "u2"]
q2["risks"] = ["r1"]
q2["implications"] = {"architecture": ["a1", "a2"], "migration": [], "verification": ["v"]}
q2s = json.loads(json.dumps(q2))
cases["summary"].append(
{"name": "qualified-truncation-counts",
"input": {"basis": q2s}, "expected": mask(bb.summary(q2))})
b = fixed_basis(); b["implications"] = None
cases["summary"].append(
{"name": "implications-null", "input": {"basis": b},
"expected": mask(bb.summary(b))})
# ── revise_depth ──────────────────────────────────────────────────────
def rev_case(nm, basis, new_depth, reason, error_expected=False):
before = json.loads(json.dumps(basis))
if error_expected:
try:
bb.revise_depth(basis, new_depth=new_depth, reason=reason)
except ValueError as e:
cases.setdefault("revise_depth", []).append(
{"name": nm, "input": {"basis": before, "new_depth": new_depth,
"reason": reason}, "error": str(e)})
return
raise AssertionError(f"revise_depth {nm} did not raise")
rev = bb.revise_depth(basis, new_depth=new_depth, reason=reason)
cases.setdefault("revise_depth", []).append(
{"name": nm, "input": {"basis": before, "new_depth": new_depth,
"reason": reason},
"expected": {"basis": mask(basis), "revision": mask(rev)}})
rev_case("full-to-inward-justified", fixed_basis(), "INWARD",
"prior-art question resolved as non-material")
rev_case("full-to-inward-blocked", fixed_basis(), "INWARD",
"skip it, not relevant", error_expected=True)
rev_case("no-op", fixed_basis("INWARD"), "INWARD", "same", error_expected=True)
rev_case("unknown-depth", fixed_basis(), "WEIRD", "why not", error_expected=True)
rev_case("outward-to-none-unnecessary", fixed_basis(), "NONE",
"prior-art question unnecessary: internal solution exists")
rev_case("upgrade-full-to-outward", fixed_basis(), "OUTWARD", "scope narrowed")
rev_case("none-to-inward", fixed_basis("LOCAL_FIX"), "INWARD", "repo code matters")
rev_case("reason-truncated-200", fixed_basis(), "OUTWARD", "r" * 300)
rev_case("inward-to-none-keyword", fixed_basis("INWARD"), "NONE",
"discovery showed nothing to record")
# ── to_json (Python json.dumps indent=1 sort_keys semantics) ──────────
cases.setdefault("to_json", []).append(
{"name": "basis", "input": {"basis": fixed_basis()},
"expected": bb.to_json(fixed_basis())})
uni = fixed_basis()
uni["question"] = "héllo 日本語 🎉 quoted \"x\" back\\slash \n\t ctrl\x01"
cases["to_json"].append(
{"name": "unicode-escapes", "input": {"basis": uni},
"expected": bb.to_json(uni)})
cases["to_json"].append(
{"name": "empty-dict", "input": {"basis": {}}, "expected": bb.to_json({})})
mixed = {"b": 1, "a": [1, 2.5, True, None, "x"], "c": {"z": [], "y": {}},
"é": "café", "Z": -17, "nested": {"deep": {"deeper": [{}]},
"flag": False}}
cases["to_json"].append(
{"name": "mixed-sorted", "input": {"basis": mixed},
"expected": bb.to_json(mixed)})
# ── from_json ─────────────────────────────────────────────────────────
cases.setdefault("from_json", []).append(
{"name": "object", "input": {"text": bb.to_json(fixed_basis())},
"expected": mask(json.loads(bb.to_json(fixed_basis())))})
cases["from_json"].append(
{"name": "array", "input": {"text": "[1, \"two\", null, {\"k\": true}]"},
"expected": [1, "two", None, {"k": True}]})
try:
bb.from_json("{bad json")
raise AssertionError("from_json did not raise")
except ValueError as e:
cases["from_json"].append(
{"name": "invalid", "input": {"text": "{bad json"},
"error_kind": "ValueError"})
# ── generate_research_plan: every branch ──────────────────────────────
for nm, kw in (
("no-args-none", dict(intent="fix a typo in the README")),
("unknowns-inward", dict(intent="i", unknowns=["which code path renders tables?"])),
("outward-prior-art", dict(intent="i",
unknowns=["is there an existing Python PDF library?"])),
("full-consequential", dict(intent="migrate the auth schema",
unknowns=["which user tables hold credential hashes?"],
risks=["breaking change to login flow"])),
("consequential-but-no-topics", dict(intent="deploy and delete the old cluster")),
("blank-filtered", dict(intent="i", unknowns=["", " ", "real unknown u"],
decisions=[" ", "d1"], risks=["", "r1"])),
("decisions-and-risks-kinds", dict(intent="i", unknowns=["u1"],
decisions=["d1"], risks=["r1"])),
("truncation-80", dict(intent="i", unknowns=["u" * 120])),
("depth-supplied", dict(intent="i", unknowns=["big unknown"], depth="INWARD")),
("multiword-prior-art", dict(intent="i",
unknowns=["prior art on vector stores?"])),
("target-and-reality", dict(intent="i", target="wr-bridge",
current_reality="c" * 300,
unknowns=["u"], depth="NONE")),
("tool-framework-keywords", dict(intent="i",
unknowns=["which framework/tool convention applies?"])),
):
plan = bb.generate_research_plan(**kw)
cases.setdefault("generate_research_plan", []).append(
{"name": nm, "input": kw, "expected": mask(plan)})
truth = {
"frozen": "build_basis full oracle truth (dichotomy wave #2479)",
"oracle": "src/rune/build_basis.py",
"note": ("Timestamp fields the kernel GENERATES (decided_at, created_at, "
"revised_at, plan_generated_at) are masked on both sides — wall "
"clock, not logic. created_at values inside basis INPUTS are "
"fixed pass-through and compare exactly. error = the exact "
"Python ValueError message."),
"cases": cases,
}
out = Path(__file__).resolve().parents[1] / "src" / "rust" / "build_basis" / "src" / "frozen_bb_truth.json"
out.write_text(json.dumps(truth, indent=1, sort_keys=True) + "\n")
total = sum(len(v) for v in cases.values())
print(f"frozen {total} cases across {len(cases)} functions -> {out}")
+440
View File
@@ -0,0 +1,440 @@
#!/usr/bin/env python3
"""Freeze capability_ledger oracle truth for the Rust port battery
(dichotomy factory batch, #2479 tranche).
Exercises EVERY public function across all branches: CapabilityLedger
(new/load, save, define defaults + full + duplicate, get prefixed/plain/
unknown, material_change revisions + generation + source_locations + note
truncation, move, supersede (plain + prefixed by, note truncation), retire,
active, by_component, context_packet known/unknown) plus the module-level
spec machinery (classify_version_bump full 4-bool matrix, bump_version
valid/invalid/leading-zero/overflow, attach_spec defaults + errors,
revise_spec classification + proposed-kind governance + unjustified bump +
KeyError + covers_material_revision, spec_drift_status all outcomes,
spec_context with/without spec).
The wall clock is FROZEN: every op records the exact _now() value it was
given, and the kernel receives that same string via the FFI payload — the
kernel itself never reads a clock.
Output: /tmp/cl_truth.json — replayed VERBATIM by
src/rust/capability_ledger/src/branch_battery.rs.
"""
from __future__ import annotations
import json
import os
import sys
import tempfile
from datetime import datetime, timedelta, timezone
from pathlib import Path
os.environ["RUNE_LEDGER_CORE"] = "python" # freeze the ORACLE, never the kernel
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
from rune import capability_ledger as cl # noqa: E402
# ── deterministic frozen clock ───────────────────────────────────────────
_T0 = datetime(2026, 1, 1, tzinfo=timezone.utc)
_tick = [0]
def _next_now() -> str:
_tick[0] += 1
return (_T0 + timedelta(seconds=_tick[0] - 1)).strftime("%Y-%m-%dT%H:%M:%SZ")
def _pin_clock(now: str) -> None:
"""All _now() calls WITHIN one op return the same value (the kernel
receives exactly one now per op; live wall-clock is same-second)."""
cl._now = lambda n=now: n # noqa: E731
class Seq:
"""One replayable op sequence against one ledger document."""
def __init__(self, name: str, sequences: list):
self.name = name
self.ops: list[dict] = []
self.expected: list[dict] = []
self.saved_files: dict[str, str] = {}
self.ledger: cl.CapabilityLedger | None = None
self.tmpdir = tempfile.mkdtemp(prefix=f"cltruth-{name}-")
sequences.append(self)
# -- op plumbing -------------------------------------------------------
def _call(self, opname: str, **args) -> None:
op = {"op": opname, **args}
now = _next_now()
_pin_clock(now)
if opname not in ("spec_context", "classify_version_bump",
"bump_version"):
op["now"] = now
self.ops.append(op)
try:
out = getattr(self, f"_{opname}")(op)
# oracle returns LIVE references (rec dicts) — snapshot NOW or
# later ops mutate the frozen expectation
out = json.loads(json.dumps(out))
self.expected.append({"ok": True, "value": out})
except Exception as e: # ValueError/SpecError/KeyError — message parity
self.expected.append({"ok": False, "err": str(e)})
def _new(self, op) -> dict:
file_text = op.get("file_text")
if file_text is not None:
(Path(self.tmpdir) / "capability_ledger.json").write_text(file_text)
self.ledger = cl.CapabilityLedger(self.tmpdir)
return self.ledger.doc
def _define(self, op) -> dict:
kwargs = {k: v for k, v in op.items()
if k not in ("op", "now")}
return self.ledger.define(**kwargs)
def _get(self, op) -> dict | None:
return self.ledger.get(op["capability_id"])
def _material_change(self, op) -> dict:
kwargs = {"note": op["note"]}
if "generation" in op:
kwargs["generation"] = op["generation"]
if "source_locations" in op:
kwargs["source_locations"] = op["source_locations"]
return self.ledger.material_change(op["capability_id"], **kwargs)
def _move(self, op) -> dict:
return self.ledger.move(op["capability_id"], op["new_locations"])
def _supersede(self, op) -> dict:
return self.ledger.supersede(op["capability_id"], op["by"],
note=op.get("note", ""))
def _retire(self, op) -> dict:
return self.ledger.retire(op["capability_id"],
note=op.get("note", ""))
def _active(self, op) -> list:
return self.ledger.active()
def _by_component(self, op) -> list:
return self.ledger.by_component(op["component"])
def _context_packet(self, op) -> dict:
return self.ledger.context_packet(op["capability_id"])
def _save(self, op) -> None:
self.ledger.save()
self.saved_files["capability_ledger.json"] = (
Path(self.tmpdir) / "capability_ledger.json").read_text()
return None
def _classify_version_bump(self, op) -> str:
return cl.classify_version_bump(
contract_changed=op["contract_changed"],
behaviour_added=op["behaviour_added"],
consumer_break=op["consumer_break"],
implementation_only=op["implementation_only"])
def _bump_version(self, op) -> str:
return cl.bump_version(op["version"], op["kind"])
def _attach_spec(self, op) -> dict:
kwargs = {k: v for k, v in op.items()
if k not in ("op", "now", "capability_id")}
return cl.attach_spec(self.ledger, op["capability_id"], **kwargs)
def _revise_spec(self, op) -> dict:
kwargs = {k: v for k, v in op.items()
if k not in ("op", "now", "capability_id")}
return cl.revise_spec(self.ledger, op["capability_id"], **kwargs)
def _spec_drift_status(self, op) -> dict:
return cl.spec_drift_status(self.ledger, op["capability_id"])
def _spec_context(self, op) -> dict:
rec = self.ledger.get(op["capability_id"])
return cl.spec_context(rec)
sequences: list[Seq] = []
LONG_NOTE = "x" * 260
LONG_REASON = "r" * 260
def frozen_json(doc: dict) -> str:
"""A saved ledger file EXACTLY as save() writes it (indent=1, sorted)."""
return json.dumps(doc, indent=1, sort_keys=True)
# ── S1: fresh ledger — defaults, queries, save ───────────────────────────
s = Seq("fresh-defaults-queries", sequences)
s._call("new")
s._call("define", capability_id="auth-token", name="Auth Token",
purpose="issue + verify session tokens", owning_project="rune",
component="component:auth-core")
s._call("define", capability_id="cap:prefixed-given", name="Prefixed",
purpose="id given with prefix", owning_project="rune",
component="component:auth-core")
s._call("get", capability_id="auth-token")
s._call("get", capability_id="capability:auth-token")
s._call("get", capability_id="nope")
s._call("active")
s._call("by_component", component="component:auth-core")
s._call("by_component", component="component:nowhere")
s._call("context_packet", capability_id="auth-token")
s._call("context_packet", capability_id="ghost")
s._call("save")
# ── S2: define with every field provided ─────────────────────────────────
s = Seq("define-full-fields", sequences)
s._call("new")
s._call("define", capability_id="full-rec", name="Full", purpose="all fields",
owning_project="rune", component="component:c1",
source_locations=["src/a.py", "src/b.py"],
contract="contract text", state_touched="store/scaffold",
dependencies=["dep-x"], consumers=["consumer-y"],
invariants=["inv-1"], behaviour_specs=["bs-1"],
constraints="no raw tokens", generation="gen-7")
s._call("define", capability_id="empty-lists", name="Empty", purpose="empties",
owning_project="rune", component="component:c1",
source_locations=[], dependencies=[], consumers=[],
invariants=[], behaviour_specs=[])
s._call("get", capability_id="full-rec")
s._call("get", capability_id="empty-lists")
s._call("context_packet", capability_id="full-rec")
# ── S3: duplicate define raises ──────────────────────────────────────────
s = Seq("duplicate-define", sequences)
s._call("new")
s._call("define", capability_id="dup", name="D", purpose="first",
owning_project="rune", component="component:c")
s._call("define", capability_id="dup", name="D2", purpose="second",
owning_project="rune", component="component:c")
# ── S4: unknown-capability errors for every mutator/query ───────────────
s = Seq("unknown-cap-errors", sequences)
s._call("new")
s._call("material_change", capability_id="ghost", note="n")
s._call("move", capability_id="ghost", new_locations=["a.py"])
s._call("supersede", capability_id="ghost", by="other")
s._call("retire", capability_id="ghost")
s._call("attach_spec", capability_id="ghost", requirements=["r"],
expected_behaviours=["b"], non_goals=["n"], acceptance_criteria=["a"])
s._call("revise_spec", capability_id="ghost", reason="x")
s._call("spec_drift_status", capability_id="ghost")
# ── S5: supersede / retire / move lifecycle ──────────────────────────────
s = Seq("supersede-retire-move", sequences)
s._call("new")
s._call("define", capability_id="old-cap", name="Old", purpose="p",
owning_project="rune", component="component:c",
source_locations=["src/old.py"])
s._call("define", capability_id="doomed", name="Doomed", purpose="p",
owning_project="rune", component="component:c")
s._call("supersede", capability_id="old-cap", by="new-cap", note="replaced")
s._call("supersede", capability_id="doomed", by="capability:explicit-prefixed",
note=LONG_NOTE)
s._call("retire", capability_id="doomed", note="dead")
s._call("retire", capability_id="doomed", note=LONG_NOTE) # already retired: still appends history
s._call("active")
s._call("move", capability_id="old-cap", new_locations=["src/moved/old.py", "src/moved/old2.py"])
s._call("context_packet", capability_id="old-cap") # packet still served for superseded
s._call("by_component", component="component:c")
# ── S6: material_change revision counter + branches ─────────────────────
s = Seq("material-change-revisions", sequences)
s._call("new")
s._call("define", capability_id="rev-cap", name="Rev", purpose="p",
owning_project="rune", component="component:c",
source_locations=["src/a.py"], constraints="c0")
s._call("material_change", capability_id="rev-cap", note="first change")
s._call("material_change", capability_id="rev-cap", note=LONG_NOTE,
generation="gen-9")
s._call("material_change", capability_id="rev-cap", note="third",
generation="", source_locations=["src/b.py"])
s._call("material_change", capability_id="rev-cap", note="fourth",
source_locations=[])
s._call("get", capability_id="rev-cap")
s._call("context_packet", capability_id="rev-cap")
# ── S7: classify_version_bump — full 4-bool matrix ───────────────────────
s = Seq("classify-bump-matrix", sequences)
s._call("new")
for cc in (False, True):
for ba in (False, True):
for cb in (False, True):
for io in (False, True):
s._call("classify_version_bump", contract_changed=cc,
behaviour_added=ba, consumer_break=cb,
implementation_only=io)
# ── S8: bump_version — valid / invalid / zeros / overflow ────────────────
s = Seq("bump-version-cases", sequences)
s._call("new")
for version, kind in [
("0.1.0", "MAJOR"), ("0.1.0", "MINOR"), ("0.1.0", "PATCH"),
("1.2.3", "MAJOR"), ("1.2.3", "MINOR"), ("1.2.3", "PATCH"),
("0.0.9", "PATCH"), ("999.999.999", "MAJOR"), ("01.02.03", "MINOR"),
("007.0.0", "PATCH"),
("18446744073709551615.0.0", "MAJOR"), # u64 boundary
("99999999999999999999999999.0.0", "PATCH"), # beyond u128
("1.2", "PATCH"), ("1.2.3.4", "PATCH"), ("abc", "MAJOR"),
("", "PATCH"), ("1..3", "MINOR"), ("-1.2.3", "MAJOR"),
("1.2.x", "PATCH"), (" 1.2.3", "PATCH"), ("1.2.3 ", "PATCH"),
("1.2.3", "BOGUS"), ("1.2.3", ""), ("1.2.3", "major"),
]:
s._call("bump_version", version=version, kind=kind)
# ── S9: attach_spec — defaults + explicit + errors ───────────────────────
s = Seq("attach-spec-branches", sequences)
s._call("new")
s._call("define", capability_id="specd", name="S", purpose="spec'd",
owning_project="rune", component="component:c")
s._call("spec_drift_status", capability_id="specd") # no spec attached
s._call("attach_spec", capability_id="specd", requirements=["r1", "r2"],
expected_behaviours=["b1"], non_goals=["ng1"],
acceptance_criteria=["ac1"])
s._call("spec_drift_status", capability_id="specd") # baseline covers rev 0
s._call("define", capability_id="specd2", name="S2", purpose="x",
owning_project="rune", component="component:c")
s._call("attach_spec", capability_id="specd2", requirements=[],
expected_behaviours=[], non_goals=[], acceptance_criteria=[],
baseline_version="1.0.0", baseline_reason="stable, evidenced",
governance_version_started_at="2025-12-31T23:59:59Z")
s._call("get", capability_id="specd2")
# errors
s._call("define", capability_id="badbase", name="B", purpose="x",
owning_project="rune", component="component:c")
s._call("attach_spec", capability_id="badbase", requirements=[],
expected_behaviours=[], non_goals=[], acceptance_criteria=[],
baseline_version="1.0")
s._call("attach_spec", capability_id="badbase", requirements=[],
expected_behaviours=[], non_goals=[], acceptance_criteria=[],
baseline_version="not-semver")
s._call("attach_spec", capability_id="ghost", requirements=[],
expected_behaviours=[], non_goals=[], acceptance_criteria=[])
# ── S10: revise_spec — classification, governance, drift closure ─────────
s = Seq("revise-spec-governance", sequences)
s._call("new")
s._call("define", capability_id="gov", name="G", purpose="governed",
owning_project="rune", component="component:c")
s._call("attach_spec", capability_id="gov", requirements=["r"],
expected_behaviours=["b"], non_goals=["n"], acceptance_criteria=["a"])
s._call("material_change", capability_id="gov", note="uncovered rev 1")
s._call("spec_drift_status", capability_id="gov") # DRIFT: rev 1 > covered 0
s._call("revise_spec", capability_id="gov", implementation_only=True,
reason="internal tidy-up")
s._call("spec_drift_status", capability_id="gov") # drift closed
s._call("material_change", capability_id="gov", note="rev 2")
s._call("material_change", capability_id="gov", note="rev 3")
s._call("spec_drift_status", capability_id="gov") # 2 uncovered
s._call("revise_spec", capability_id="gov", behaviour_added=True,
reason=LONG_REASON, changed_sections=["requirements"],
affected_consumers=["consumer-x"],
validation_consequences=["retest suite B"])
s._call("spec_drift_status", capability_id="gov")
s._call("revise_spec", capability_id="gov", consumer_break=True,
reason="breaking contract change")
s._call("spec_drift_status", capability_id="gov")
s._call("get", capability_id="gov")
# ── S11: revise_spec — proposed-kind governance branches ─────────────────
s = Seq("revise-proposed-kind", sequences)
s._call("new")
s._call("define", capability_id="pk", name="PK", purpose="p",
owning_project="rune", component="component:c")
s._call("attach_spec", capability_id="pk", requirements=["r"],
expected_behaviours=["b"], non_goals=["n"], acceptance_criteria=["a"])
s._call("revise_spec", capability_id="pk", implementation_only=True,
reason="ok patch, proposed matches", proposed_kind="PATCH")
s._call("revise_spec", capability_id="pk", implementation_only=True,
reason="patch proposed but minor earned", proposed_kind="MINOR")
s._call("revise_spec", capability_id="pk", behaviour_added=True,
reason="minor earned, downgraded to patch on purpose",
proposed_kind="PATCH")
s._call("revise_spec", capability_id="pk", implementation_only=True,
reason="major proposed for metadata churn — unjustified",
proposed_kind="MAJOR")
s._call("revise_spec", capability_id="pk", implementation_only=True,
reason="minor proposed for metadata churn — unjustified",
proposed_kind="MINOR")
s._call("revise_spec", capability_id="pk", implementation_only=True,
reason="garbage proposed kind", proposed_kind="HUGE")
s._call("revise_spec", capability_id="pk", implementation_only=True,
reason="empty proposed kind falls back to classify",
proposed_kind="")
s._call("revise_spec", capability_id="pk", reason="no flags at all — churn")
s._call("get", capability_id="pk")
# ── S12: revise_spec on a capability with no spec ────────────────────────
s = Seq("revise-no-spec", sequences)
s._call("new")
s._call("define", capability_id="bare", name="B", purpose="p",
owning_project="rune", component="component:c")
s._call("revise_spec", capability_id="bare", implementation_only=True,
reason="x")
# ── S13: save/reload roundtrip (sorted file bytes pinned) ────────────────
s = Seq("save-reload-roundtrip", sequences)
s._call("new")
s._call("define", capability_id="zzz-last", name="Z", purpose="p",
owning_project="rune", component="component:c")
s._call("define", capability_id="aaa-first", name="A", purpose="p",
owning_project="rune", component="component:c")
s._call("save")
saved = s.saved_files["capability_ledger.json"]
s2 = Seq("reload-from-saved-file", sequences)
s2._call("new", file_text=saved)
s2._call("active") # sorted-key order from the file
s2._call("get", capability_id="aaa-first")
s2._call("save")
# ── S14: unicode + repr edge cases ───────────────────────────────────────
s = Seq("unicode-repr-edges", sequences)
s._call("new")
s._call("define", capability_id="café-x", name="Café", purpose="rôle",
owning_project="rune", component="component:coffeé")
s._call("context_packet", capability_id="café-x")
s._call("material_change", capability_id="café-x", note="naïve change → ünïcode ✓")
s._call("material_change", capability_id="ghost-cap", note="x")
s._call("bump_version", version="café.1.0", kind="PATCH")
s._call("context_packet", capability_id="quo'te")
s._call("context_packet", capability_id='dq"te')
# ── S15: spec_context with and without spec ──────────────────────────────
s = Seq("spec-context", sequences)
s._call("new")
s._call("define", capability_id="ctx-bare", name="CB", purpose="purpose-y",
owning_project="rune", component="component:c",
contract="contract-y", invariants=["i1"], behaviour_specs=["bs1"])
s._call("spec_context", capability_id="ctx-bare") # no spec — empty spec half
s._call("attach_spec", capability_id="ctx-bare", requirements=["req1"],
expected_behaviours=["eb1"], non_goals=["ngo1"],
acceptance_criteria=["ac1"])
s._call("spec_context", capability_id="ctx-bare")
s._call("material_change", capability_id="ctx-bare", note="m")
s._call("spec_context", capability_id="ctx-bare") # last_bump = BASELINE entry
truth = {
"frozen": "capability_ledger full public surface + spec machinery",
"oracle": "src/rune/capability_ledger.py",
"note": "Every op records the exact _now() it was given; the kernel "
"receives that string via the FFI payload and never reads a "
"clock. saved_files pin save() byte output (Python facade "
"serializes with indent=1 sort_keys=True).",
"sequences": [
{"name": s.name, "ops": s.ops, "expected": s.expected,
"saved_files": s.saved_files,
"final_doc": s.ledger.doc}
for s in sequences
],
}
out = Path("/tmp/cl_truth.json")
out.write_text(json.dumps(truth, indent=1, ensure_ascii=False) + "\n")
n_ops = sum(len(s.ops) for s in sequences)
print(f"froze {len(sequences)} sequences / {n_ops} ops -> {out}")
+562
View File
@@ -0,0 +1,562 @@
#!/usr/bin/env python3
"""gen_drift_cache_truth.py — freeze/replay parity truth for drift_cache
(dichotomy wave, #2479 tranche).
Freeze mode (default): exercises EVERY public function of the LIVE Python
oracle (src/rune/drift_cache.py) across every branch and records
(kernel_input, expected_kernel_output) pairs into
src/rust/drift_cache/src/frozen_dc_truth.json.
Replay mode (--replay): re-runs the SAME scenarios with the Rust core
delegation active and asserts both kernel-level (loader direct) and
facade-level (oracle methods delegate) parity against the frozen file.
Poisoned-truth law: freeze pins RUNE_DC_CORE=python AND
RUNE_MEMORY_CORE=python BEFORE any import (the module-level helpers carry
an older _ms_core delegation chain that must never poison the truth).
No-clock law: the wall clock is frozen by patching the datetime symbol in
drift_cache, so every kernel payload carries its own `now` and replay is
deterministic regardless of run date.
Boundary law: the sqlite read (update_from_store's per-id SELECT) and the
cache FILE IO (_load/_save, malformed-JSON reset) stay in the Python
facade — the kernel is a pure function of their results. The `load`
truth group is facade-only (no kernel symbol) and is skipped by the
kernel-level replay pass.
"""
import copy
import json
import os
import sqlite3
import sys
import tempfile
from datetime import datetime, timezone
REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
TRUTH_PATH = os.path.join(REPO, "src", "rust", "drift_cache", "src", "frozen_dc_truth.json")
REPLAY = "--replay" in sys.argv
os.environ["RUNE_DC_CORE"] = "python" # poisoned-truth law (no-op pre-port, load-bearing post-port)
os.environ["RUNE_MEMORY_CORE"] = "python"
sys.path.insert(0, os.path.join(REPO, "src"))
sys.path.insert(0, os.path.join(REPO, "src", "rune"))
import drift_cache as oracle_mod # noqa: E402
if REPLAY:
from rune import dc_loader # noqa: E402 (kernel-level replay side)
FROZEN_UTC = datetime(2026, 10, 11, 12, 0, 0, 123456, tzinfo=timezone.utc)
FROZEN_NOW = FROZEN_UTC.isoformat()
class FrozenDatetime(datetime):
@classmethod
def now(cls, tz=None):
return FROZEN_UTC
oracle_mod.datetime = FrozenDatetime
class Case:
def __init__(self, name, fn):
self.name = name
self.fn = fn
def fresh_cache(entries_pre=None):
tmp = tempfile.mkdtemp(prefix="dc_truth_")
oracle_mod.CACHE_DIR = tmp
oracle_mod.CACHE_FILE = os.path.join(tmp, "drift_cache.json")
cache = oracle_mod.DriftCache()
if entries_pre is not None:
cache.entries = copy.deepcopy(entries_pre)
return cache
def stamped(priority, wash=0, rock="washable", access_count=0):
return {"priority": priority, "added_at": FROZEN_NOW, "last_accessed": FROZEN_NOW,
"access_count": access_count, "wash_count": wash, "rock_status": rock}
# ── compute_priority ─────────────────────────────────────────────────────────
def c_prio_default(rec):
rec("prio-default-empty-entry", {"entry": {}}, oracle_mod.compute_priority({}),
{"priority": oracle_mod.compute_priority({})})
def c_prio_wash20_imp1(rec):
e = {"wash_count": 20, "importance": 1.0}
rec("prio-wash20-imp1-washfactor-1", {"entry": e}, oracle_mod.compute_priority(e),
{"priority": oracle_mod.compute_priority(e)})
def c_prio_typical(rec):
e = {"wash_count": 7, "importance": 0.6}
rec("prio-wash7-imp06", {"entry": e}, oracle_mod.compute_priority(e),
{"priority": oracle_mod.compute_priority(e)})
def c_prio_rock(rec):
e = {"wash_count": 3, "importance": 0.7, "rock_status": "rock"}
rec("prio-rock-boost", {"entry": e}, oracle_mod.compute_priority(e),
{"priority": oracle_mod.compute_priority(e)})
def c_prio_established(rec):
e = {"wash_count": 3, "importance": 0.7, "rock_status": "established"}
rec("prio-established-boost", {"entry": e}, oracle_mod.compute_priority(e),
{"priority": oracle_mod.compute_priority(e)})
def c_prio_none_wash(rec):
e = {"wash_count": None, "importance": 0.9}
rec("prio-none-wash-coerced-0", {"entry": e}, oracle_mod.compute_priority(e),
{"priority": oracle_mod.compute_priority(e)})
def c_prio_none_imp(rec):
e = {"wash_count": 4, "importance": None}
rec("prio-none-imp-coerced-05", {"entry": e}, oracle_mod.compute_priority(e),
{"priority": oracle_mod.compute_priority(e)})
def c_prio_zero_imp(rec):
# falsy 0 → `or 0.5` coerces to 0.5
e = {"wash_count": 4, "importance": 0}
rec("prio-zero-imp-coerced-05", {"entry": e}, oracle_mod.compute_priority(e),
{"priority": oracle_mod.compute_priority(e)})
def c_prio_int_importance(rec):
e = {"wash_count": 2, "importance": 1}
rec("prio-int-importance", {"entry": e}, oracle_mod.compute_priority(e),
{"priority": oracle_mod.compute_priority(e)})
def c_prio_wash_beyond_20(rec):
# log1p scaling caps at 1.0 via min()
e = {"wash_count": 1000, "importance": 0.5}
rec("prio-wash1000-capped", {"entry": e}, oracle_mod.compute_priority(e),
{"priority": oracle_mod.compute_priority(e)})
def c_prio_unknown_status(rec):
e = {"wash_count": 2, "importance": 0.5, "rock_status": "muddy"}
rec("prio-unknown-status-no-boost", {"entry": e}, oracle_mod.compute_priority(e),
{"priority": oracle_mod.compute_priority(e)})
# ── qualifies_for_cache ──────────────────────────────────────────────────────
def _q_case(rec, name, entry):
rec(name, {"entry": entry}, oracle_mod.qualifies_for_cache(entry),
{"qualifies": oracle_mod.qualifies_for_cache(entry)})
def c_qual_below_both(rec):
_q_case(rec, "qual-wash0-imp05-denied", {"wash_count": 0, "importance": 0.5})
def c_qual_imp_boundary(rec):
_q_case(rec, "qual-imp-079-denied", {"wash_count": 0, "importance": 0.79})
def c_qual_imp_08_admitted(rec):
_q_case(rec, "qual-imp-08-admitted", {"wash_count": 0, "importance": 0.8})
def c_qual_wash_boundary(rec):
_q_case(rec, "qual-wash1-imp0-admitted", {"wash_count": 1, "importance": 0.0})
def c_qual_defaults(rec):
_q_case(rec, "qual-defaults-denied", {})
def c_qual_none_fields(rec):
_q_case(rec, "qual-none-fields-denied", {"wash_count": None, "importance": None})
def c_qual_proven(rec):
_q_case(rec, "qual-wash5-admitted", {"wash_count": 5, "importance": 0.3})
# ── add ──────────────────────────────────────────────────────────────────────
def _add_case(rec, name, entries_pre, entry_id, entry):
cache = fresh_cache(entries_pre)
cache.add(entry_id, entry)
admitted = entry_id in cache.entries
evicted = sorted(set(entries_pre) - set(cache.entries))
rec(name,
{"entries": entries_pre, "entry_id": entry_id, "entry": entry, "now": FROZEN_NOW},
None, {"entries": cache.entries, "admitted": admitted, "evicted": evicted})
def c_add_denied(rec):
_add_case(rec, "add-denied-unproven-unimportant", {}, 1,
{"wash_count": 0, "importance": 0.5})
def c_add_denied_important_but_unwashed(rec):
_add_case(rec, "add-denied-imp-below-08", {}, 2,
{"wash_count": 0, "importance": 0.5, "text": "x"})
def c_add_washable(rec):
_add_case(rec, "add-washable-admitted", {}, 3,
{"wash_count": 2, "importance": 0.5, "rock_status": "washable"})
def c_add_high_importance_unwashed(rec):
_add_case(rec, "add-important-unwashed-admitted", {}, 4,
{"wash_count": 0, "importance": 0.95})
def c_add_rock(rec):
_add_case(rec, "add-rock-stamped", {}, 5,
{"wash_count": 3, "importance": 0.7, "rock_status": "rock"})
def c_add_overwrites(rec):
pre = {7: stamped(0.9, wash=9, rock="rock", access_count=4)}
_add_case(rec, "add-overwrites-existing", pre, 7,
{"wash_count": 1, "importance": 0.4})
def c_add_rock_status_none(rec):
# explicit None rock_status is stored verbatim (entry.get returns None)
_add_case(rec, "add-rock-status-none-verbatim", {}, 8,
{"wash_count": 2, "importance": 0.5, "rock_status": None})
def c_add_boundary_200_no_evict(rec):
pre = {i: {"priority": (i + 1) / 200.0} for i in range(199)}
_add_case(rec, "add-at-200-no-evict", pre, 199,
{"wash_count": 20, "importance": 1.0})
def c_add_evict_lowest_20(rec):
pre = {i: {"priority": (i + 1) / 200.0} for i in range(200)}
_add_case(rec, "add-evicts-lowest-20", pre, 999,
{"wash_count": 20, "importance": 1.0, "rock_status": "rock"})
def c_add_evict_tie_stable_order(rec):
pre = {i: {"priority": 0.5} for i in range(196)}
for i in range(196, 200):
pre[i] = {"priority": 9.0}
_add_case(rec, "add-evict-tie-stable-insertion-order", pre, 999,
{"wash_count": 20, "importance": 1.0})
# ── access ───────────────────────────────────────────────────────────────────
def _access_case(rec, name, entries_pre, entry_id):
cache = fresh_cache(entries_pre)
cache.access(entry_id)
changed = entry_id in entries_pre
rec(name,
{"entries": entries_pre, "entry_id": entry_id, "now": FROZEN_NOW},
None, {"entries": cache.entries, "changed": changed})
def c_access_hit(rec):
pre = {11: stamped(0.5, access_count=3)}
_access_case(rec, "access-hit-reinforces", pre, 11)
def c_access_miss(rec):
pre = {11: stamped(0.5)}
_access_case(rec, "access-miss-noop", pre, 12)
def c_access_zero_count(rec):
pre = {13: stamped(0.5, access_count=0)}
_access_case(rec, "access-from-zero", pre, 13)
# ── contains / get_priority ──────────────────────────────────────────────────
def c_contains_hit(rec):
pre = {21: stamped(0.5)}
cache = fresh_cache(pre)
rec("contains-hit", {"entries": pre, "entry_id": 21},
cache.contains(21), {"contains": cache.contains(21)})
def c_contains_miss(rec):
cache = fresh_cache({})
rec("contains-miss", {"entries": {}, "entry_id": 21},
cache.contains(21), {"contains": cache.contains(21)})
def c_getprio_hit(rec):
pre = {31: stamped(0.625)}
cache = fresh_cache(pre)
rec("getprio-hit", {"entries": pre, "entry_id": 31},
cache.get_priority(31), {"priority": cache.get_priority(31)})
def c_getprio_miss(rec):
cache = fresh_cache({})
rec("getprio-miss-zero", {"entries": {}, "entry_id": 31},
cache.get_priority(31), {"priority": cache.get_priority(31)})
# ── update_from_store ────────────────────────────────────────────────────────
def make_db(rows):
db = sqlite3.connect(":memory:")
db.execute("CREATE TABLE entries (id INTEGER PRIMARY KEY, wash_count INTEGER,"
" importance REAL, rock_status TEXT)")
db.executemany("INSERT INTO entries VALUES (?,?,?,?)", rows)
return db
def _sync_case(rec, name, entries_pre, db_rows):
db = make_db(db_rows)
cache = fresh_cache(entries_pre)
rows = {}
for entry_id in list(cache.entries.keys()):
row = db.execute(
"SELECT wash_count, importance, rock_status FROM entries WHERE id = ?",
(entry_id,)).fetchone()
rows[str(entry_id)] = list(row) if row else None
cache.update_from_store(db)
rec(name, {"entries": entries_pre, "rows": rows}, None,
{"entries": cache.entries})
def c_sync_updates_priority(rec):
pre = {41: stamped(0.1, wash=1)}
_sync_case(rec, "sync-updates-priority-wash-rock", pre,
[(41, 15, 0.8, "rock")])
def c_sync_null_fields(rec):
pre = {42: stamped(0.9, wash=5, rock="rock")}
_sync_case(rec, "sync-null-row-fields-coerced", pre,
[(42, None, None, None)])
def c_sync_deleted_row_removed(rec):
pre = {43: stamped(0.5), 44: stamped(0.7)}
_sync_case(rec, "sync-deleted-row-removed", pre,
[(44, 2, 0.5, "washable")])
def c_sync_empty_cache(rec):
_sync_case(rec, "sync-empty-cache-noop", {}, [])
def c_sync_downgrade(rec):
pre = {45: stamped(1.0, wash=20, rock="rock")}
_sync_case(rec, "sync-downgrade-washable", pre,
[(45, 0, 0.1, "washable")])
# ── get_stats ────────────────────────────────────────────────────────────────
def _stats_case(rec, name, entries_pre):
cache = fresh_cache(entries_pre)
stats = cache.get_stats()
rec(name, {"entries": entries_pre}, stats, {"stats": stats})
def c_stats_empty(rec):
_stats_case(rec, "stats-empty", {})
def c_stats_mixed(rec):
pre = {
51: stamped(0.2, rock="rock"),
52: stamped(0.5, rock="established"),
53: stamped(0.9),
}
_stats_case(rec, "stats-mixed", pre)
def c_stats_all_washable(rec):
pre = {54: stamped(0.3), 55: stamped(0.4)}
_stats_case(rec, "stats-all-washable", pre)
def c_stats_missing_priority(rec):
pre = {56: {"rock_status": "rock"}, 57: stamped(0.8)}
_stats_case(rec, "stats-missing-priority-zero", pre)
def c_stats_missing_rock_status(rec):
pre = {58: {"priority": 0.5}}
_stats_case(rec, "stats-missing-rock-status-washable", pre)
# ── _load branches (facade-only: FILE IO stays in Python) ────────────────────
def c_load_missing(rec):
cache = fresh_cache()
rec("load-missing-file-empty", {"path": "missing"}, None,
{"entries": cache.entries})
def c_load_valid(rec):
tmp = tempfile.mkdtemp(prefix="dc_truth_")
oracle_mod.CACHE_DIR = tmp
oracle_mod.CACHE_FILE = os.path.join(tmp, "drift_cache.json")
pre = {61: stamped(0.5), 62: stamped(0.9, rock="rock")}
with open(oracle_mod.CACHE_FILE, "w") as f:
json.dump({str(k): v for k, v in pre.items()}, f, indent=2)
cache = oracle_mod.DriftCache()
rec("load-valid-int-keys", {"path": "valid"}, None,
{"entries": cache.entries})
def c_load_malformed(rec):
tmp = tempfile.mkdtemp(prefix="dc_truth_")
oracle_mod.CACHE_DIR = tmp
oracle_mod.CACHE_FILE = os.path.join(tmp, "drift_cache.json")
with open(oracle_mod.CACHE_FILE, "w") as f:
f.write("{not json")
cache = oracle_mod.DriftCache()
rec("load-malformed-reset", {"path": "malformed"}, None,
{"entries": cache.entries})
# ── driver ───────────────────────────────────────────────────────────────────
PRIO = [c_prio_default, c_prio_wash20_imp1, c_prio_typical, c_prio_rock,
c_prio_established, c_prio_none_wash, c_prio_none_imp, c_prio_zero_imp,
c_prio_int_importance, c_prio_wash_beyond_20, c_prio_unknown_status]
QUAL = [c_qual_below_both, c_qual_imp_boundary, c_qual_imp_08_admitted,
c_qual_wash_boundary, c_qual_defaults, c_qual_none_fields, c_qual_proven]
ADD = [c_add_denied, c_add_denied_important_but_unwashed, c_add_washable,
c_add_high_importance_unwashed, c_add_rock, c_add_overwrites,
c_add_rock_status_none, c_add_boundary_200_no_evict,
c_add_evict_lowest_20, c_add_evict_tie_stable_order]
ACCESS = [c_access_hit, c_access_miss, c_access_zero_count]
CONTAINS = [c_contains_hit, c_contains_miss]
GETPRIO = [c_getprio_hit, c_getprio_miss]
SYNC = [c_sync_updates_priority, c_sync_null_fields, c_sync_deleted_row_removed,
c_sync_empty_cache, c_sync_downgrade]
STATS = [c_stats_empty, c_stats_mixed, c_stats_all_washable,
c_stats_missing_priority, c_stats_missing_rock_status]
LOAD = [c_load_missing, c_load_valid, c_load_malformed]
GROUPS = [("compute_priority", PRIO), ("qualifies_for_cache", QUAL),
("add", ADD), ("access", ACCESS), ("contains", CONTAINS),
("get_priority", GETPRIO), ("update_entries", SYNC),
("get_stats", STATS), ("load", LOAD)]
# kernel symbol per group (None → facade-only group, skipped kernel-side)
KERNEL = {
"compute_priority": "dcc_compute_priority",
"qualifies_for_cache": "dcc_qualifies_for_cache",
"add": "dcc_add",
"access": "dcc_access",
"contains": "dcc_contains",
"get_priority": "dcc_get_priority",
"update_entries": "dcc_update_entries",
"get_stats": "dcc_get_stats",
"load": None,
}
# kernel-level replay: which slice of `expected` the unwrapped FFI value is
UNWRAP = {
"compute_priority": lambda w: w["priority"],
"qualifies_for_cache": lambda w: w["qualifies"],
"add": lambda w: w,
"access": lambda w: w,
"contains": lambda w: w["contains"],
"get_priority": lambda w: w["priority"],
"update_entries": lambda w: w["entries"],
"get_stats": lambda w: w["stats"],
}
def canon(x) -> str:
"""Content canonicalization: int dict keys become strings through the
JSON round-trip (the frozen file stores string keys), then a sorted
dump makes the comparison key-order independent."""
return json.dumps(json.loads(json.dumps(x)), sort_keys=True)
def main():
frozen = None
if REPLAY:
with open(TRUTH_PATH) as f:
frozen = json.load(f)["cases"]
groups = {}
for group, cases in GROUPS:
recs = []
def rec(name, inp, oracle_ret, expected, _r=recs):
entry = {"name": name, "input": inp, "return": oracle_ret,
"expected": expected}
if REPLAY:
old = next(c for c in frozen[group] 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 [{group}] {name} {key}")
print(" got: ", json.dumps(got, sort_keys=True, default=str)[:600])
print(" want:", json.dumps(want, sort_keys=True, default=str)[:600])
recs.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_failures = sum(1 for v in groups.values() for c in v if "diverged" in c)
failures = 0
checked = 0
for group, cases in frozen.items():
sym = KERNEL[group]
if sym is None:
continue
for case in cases:
checked += 1
got = getattr(dc_loader, group)(**copy.deepcopy(case["input"]))
want = UNWRAP[group](case["expected"])
if canon(got) != canon(want):
failures += 1
print(f"KERNEL DIVERGE [{group}] {case['name']}")
print(" got: ", json.dumps(got, sort_keys=True)[:600])
print(" want:", json.dumps(want, sort_keys=True)[:600])
print(f"replay: facade {total - facade_failures}/{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/drift_cache.py",
"pin": "RUNE_DC_CORE=python + RUNE_MEMORY_CORE=python",
"note": "Frozen via scripts/gen_drift_cache_truth.py against the live "
"Python oracle; wall clock frozen at " + FROZEN_NOW +
". Battery embeds this VERBATIM. Group `load` is facade-only "
"(FILE IO stays in Python).",
"cases": groups,
}
os.makedirs(os.path.dirname(TRUTH_PATH), exist_ok=True)
with open(TRUTH_PATH, "w") as f:
# NO sort_keys: the entries maps are Python dicts whose INSERTION
# order is load-bearing (stable eviction tie-breaks) — sorting
# keys here would freeze a re-ordered kernel input.
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())
+599
View File
@@ -0,0 +1,599 @@
#!/usr/bin/env python3
"""gen_evidence_probe_truth.py — evidence_probe FULL-port truth battery
(dichotomy wave #2479).
Freezes the LIVE src/rune/evidence_probe.py behavior (validate_probe and
count_probes served by the Rust evidence_provenance core through ep_loader)
for every public function and branch:
- build_probe_prompt: baseline / unicode / path-like question / empty args
- validate_probe: the 5-key schema gate — missing fields, non-string and
blank fields, status rendering (repr, ints, floats, bools, None, list,
dict incl. nested key order), GROUNDED source-citation boundary (7/8
chars, missing/non-string source), extra-field sorting (meta fields
allowed), non-object artifacts, combined-error ordering
- count_probes: absent dir/file, empty, blank-only, Rust `lines()`
semantics (trailing \\r, no trailing newline, trailing newline,
whitespace-only lines)
- check_budget: 0/1 ok, 2/3 ProbeBudgetExceeded
- record_probe: fresh dir creation, probe.jsonl append, unicode
ensure_ascii bytes, returned path
- run_probe: injected runner — success (grounded / still_unknown), model
fallback chain (explicit / MIMO_MODEL env / unset -> UNKNOWN /
set-but-empty -> ""), budget exhausted BEFORE the runner fires, silent
runner -> declared-artifact failure, gate-failure joins, and the exact
prompt + absolute artifact path the runner receives
- ProbeResult.to_dict: field order + ensure_ascii
- _default_runner: stubbed MIMO_BIN subprocess — argv parity, stdout log,
exit-code error, MIMO_AGENT default/override, require_model
resolution + ProviderUnresolved (set/unset/blank)
Clock frozen at FROZEN_TS (probe_id + decided_at deterministic). The only
excluded branch is the invalid-JSON artifact crash (Python json.JSONDecodeError
rendering is interpreter-specific, same policy as the auto_context batch).
Usage: python3 scripts/gen_evidence_probe_truth.py (from repo root)
"""
import json
import os
import sys
import tempfile
import time
from pathlib import Path
REPO = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(REPO / "src"))
from rune import evidence_probe as ep # noqa: E402
import rune.provider_context as provider_context # noqa: E402
FROZEN_TS = 1791753600
ARTDIR = "@ARTDIR@"
STUB_NAME = "ep_stub.sh"
_real_gmtime = time.gmtime
GROUNDED = {
"question": "Does the API support streaming?",
"source": "https://platformdirs.readthedocs.io/changelog.html",
"answer": "Current version guarantees the assumed behaviour.",
"implication": "Local wiring is sufficient; no version guard needed.",
"status": "GROUNDED",
}
def norm(s, base):
return s.replace(base, ARTDIR) if isinstance(s, str) else s
def write_files(cdir, files):
os.makedirs(cdir, exist_ok=True)
for rel, content in (files or {}).items():
p = os.path.join(cdir, rel)
os.makedirs(os.path.dirname(p), exist_ok=True)
with open(p, "w") as f:
f.write(content)
def exc_row(fn, base, types=None):
"""Call fn(), freeze either {error: None, value: ...} or the exception."""
try:
return {"error": None, "value": fn()}
except Exception as e: # noqa: BLE001 — the frozen point IS the exception
t = type(e).__name__
if types and t not in types:
raise
return {"error": {"type": t, "msg": norm(str(e), base)}}
def main():
base = tempfile.mkdtemp(prefix="ep_truth_")
rows = []
real_time = time.time
ep.time.time = lambda: FROZEN_TS
ep.time.gmtime = lambda *a: _real_gmtime(FROZEN_TS)
try:
_build_rows(base, rows)
finally:
ep.time.time = real_time
ep.time.gmtime = _real_gmtime
def _build_rows(base, rows):
# ── build_probe_prompt ────────────────────────────────────────────────
prompt_cases = [
("prompt_baseline", "Does the API support streaming?", "/tmp/probe.json"),
("prompt_unicode", "Wird die Größenbeschränkung überschritten?", "/tmp/probe.json"),
(
"prompt_path_like_question",
"Is the file at /tmp/probe.json overwritten per probe?",
"/tmp/artifacts/evidence_probe.json",
),
("prompt_empty_question", "", ""),
("prompt_multiline_question", "line one\nline two\nline three", "/tmp/x.json"),
]
for name, q, ap in prompt_cases:
rows.append(
{
"name": name,
"op": "prompt",
"setup": {"question": q, "artifact_path": ap},
"want": ep.build_probe_prompt(q, ap),
}
)
# ── validate_probe ────────────────────────────────────────────────────
def val(name, artifact):
rows.append(
{
"name": name,
"op": "validate",
"setup": {"artifact": artifact},
"want": ep.validate_probe(artifact),
}
)
val("valid_grounded", dict(GROUNDED))
val(
"valid_grounded_with_meta",
dict(GROUNDED, probe_id="probe-1", decided_at="2026-10-11T00:00:00Z", model="m"),
)
val(
"valid_still_unknown_empty_source",
{
"question": "probe",
"source": "",
"answer": "Could not find authoritative evidence.",
"implication": "Uncertainty remains.",
"status": "STILL_UNKNOWN",
},
)
val(
"valid_contradicted",
dict(GROUNDED, status="CONTRADICTED", source="task assumption"),
)
for field in ("question", "source", "answer", "implication", "status"):
art = dict(GROUNDED)
del art[field]
val(f"missing_{field}", art)
val("empty_artifact", {})
val("nonstring_question", dict(GROUNDED, question=7))
val("nonstring_answer", dict(GROUNDED, answer=["a"]))
val("nonstring_implication", dict(GROUNDED, implication={"k": 1}))
val("nonstring_source", dict(GROUNDED, source=3.5))
val("blank_question", dict(GROUNDED, question=""))
val("whitespace_question", dict(GROUNDED, question=" \t\n"))
val("blank_answer", dict(GROUNDED, answer=""))
val("blank_implication", dict(GROUNDED, implication=""))
val("blank_source_allowed", dict(GROUNDED, source=""))
val("status_empty_string", dict(GROUNDED, status=""))
val("status_lowercase", dict(GROUNDED, status="grounded"))
val("status_int", dict(GROUNDED, status=1))
val("status_float", dict(GROUNDED, status=1.5))
val("status_float_integral", dict(GROUNDED, status=2.0))
val("status_float_small", dict(GROUNDED, status=0.1))
val("status_big_float", dict(GROUNDED, status=1e30))
val("status_huge_int", dict(GROUNDED, status=9007199254740993))
val("status_true", dict(GROUNDED, status=True))
val("status_false", dict(GROUNDED, status=False))
val("status_null", dict(GROUNDED, status=None))
val("status_list", dict(GROUNDED, status=[1, "x"]))
val("status_obj_order", dict(GROUNDED, status={"b": 1, "a": {"z": 2, "y": 3}}))
val("status_repr_quote", dict(GROUNDED, status="it's"))
val("status_repr_newline", dict(GROUNDED, status="a\nb"))
val("status_repr_unicode", dict(GROUNDED, status="größe"))
val("grounded_short_source_7", dict(GROUNDED, source="1234567"))
val("grounded_source_8_ok", dict(GROUNDED, source="12345678"))
val("grounded_missing_source", {k: v for k, v in GROUNDED.items() if k != "source"})
val("grounded_nonstring_source", dict(GROUNDED, source=42))
val("extra_single", dict(GROUNDED, extra=1))
val("extra_sorted", dict(GROUNDED, z_extra=1, a_extra=2, m_extra=3))
val("extra_meta_allowed", dict(GROUNDED, model="m", probe_id="p", decided_at="d"))
val("extra_unicode_key", dict(GROUNDED, **{"ключ": 1}))
val("nonobject_list", ["a", 1])
val("nonobject_string", "nope")
val("nonobject_int", 3)
val("nonobject_bool", True)
val("nonobject_null", None)
val(
"combined_ordering",
{"status": 5, "extra_b": 1, "extra_a": 2, "answer": ""},
)
# ── count_probes ──────────────────────────────────────────────────────
count_cases = [
("count_absent_dir", None),
("count_absent_file", {}),
("count_empty_file", {"probe.jsonl": ""}),
("count_blank_only", {"probe.jsonl": "\n\n \n"}),
("count_three_lines", {"probe.jsonl": "a\nb\nc\n"}),
("count_no_trailing_newline", {"probe.jsonl": "a\nb"}),
("count_interleaved_blank", {"probe.jsonl": "a\n\nb\n\n\nc"}),
("count_cr_lines", {"probe.jsonl": "a\r\nb\r\n"}),
("count_whitespace_lines", {"probe.jsonl": " \n x\n\t\n"}),
("count_cr_only_line", {"probe.jsonl": "\r\r\nx"}),
("count_single_line", {"probe.jsonl": "one"}),
]
for name, files in count_cases:
cdir = os.path.join(base, name)
if files is not None:
write_files(cdir, files)
rows.append(
{
"name": name,
"op": "count",
"setup": {"dir": norm(cdir, base), "files": files or {}},
"want": ep.count_probes(cdir),
}
)
# ── check_budget ──────────────────────────────────────────────────────
for n, name in ((0, "budget_zero"), (1, "budget_one"), (2, "budget_two"), (3, "budget_three")):
cdir = os.path.join(base, name)
write_files(cdir, {"probe.jsonl": "x\n" * n})
out = exc_row(lambda d=cdir: ep.check_budget(d), base, types={"ProbeBudgetExceeded"})
rows.append(
{
"name": name,
"op": "budget",
"setup": {"dir": norm(cdir, base), "files": {"probe.jsonl": "x\n" * n}},
"want": out,
}
)
# ── record_probe ──────────────────────────────────────────────────────
def rec(name, result, files=None):
cdir = os.path.join(base, name)
if files:
write_files(cdir, files)
path = ep.record_probe(ep.ProbeResult(**result), cdir)
with open(os.path.join(cdir, "probe.json")) as f:
pj = f.read()
with open(os.path.join(cdir, "probe.jsonl")) as f:
pjl = f.read()
rows.append(
{
"name": name,
"op": "record",
"setup": {"result": result, "files": files or {}},
"want": {
"path": norm(path, base),
"probe_json": pj,
"probe_jsonl": pjl,
},
}
)
rec(
"record_fresh_dir_created",
{
"question": "q",
"source": "https://example.com/a",
"answer": "a",
"implication": "i",
"status": "GROUNDED",
"probe_id": "probe-1791753600",
"decided_at": "2026-10-12T04:00:00Z",
"model": "UNKNOWN",
},
)
rec(
"record_appends_existing",
{
"question": "q2",
"source": "",
"answer": "a2",
"implication": "i2",
"status": "STILL_UNKNOWN",
"probe_id": "probe-1791753600",
"decided_at": "2026-10-12T04:00:00Z",
"model": "m",
},
files={"probe.jsonl": '{"question": "old"}\n'},
)
rec(
"record_unicode_ensure_ascii",
{
"question": "Größe überschritten — warum?",
"source": "https://example.com/ä",
"answer": "ja ✓",
"implication": "🚀",
"status": "GROUNDED",
"probe_id": "probe-1791753600",
"decided_at": "2026-10-12T04:00:00Z",
"model": "model-ü",
},
)
# ── run_probe ─────────────────────────────────────────────────────────
def run(name, *, question, runner_spec, model="UNKNOWN", env=None, files=None):
cdir = os.path.join(base, name)
write_files(cdir, files or {})
old_env = {k: os.environ.get(k) for k in ("MIMO_MODEL",)}
try:
for k, v in (env or {}).items():
if v is None:
os.environ.pop(k, None)
else:
os.environ[k] = v
captures = []
def runner(prompt, artifact_path):
captures.append((prompt, artifact_path))
kind = runner_spec["type"]
if kind == "write":
with open(artifact_path, "w") as f:
json.dump(runner_spec["artifact"], f)
elif kind == "silent":
pass
else:
raise AssertionError(f"unknown runner spec {kind}")
try:
result = ep.run_probe(question, cdir, runner=runner, model=model)
out = {
"error": None,
"result": result.to_dict(),
}
with open(os.path.join(cdir, "probe.json")) as f:
out["probe_json"] = f.read()
with open(os.path.join(cdir, "probe.jsonl")) as f:
out["probe_jsonl"] = f.read()
except Exception as e: # noqa: BLE001
out = {"error": {"type": type(e).__name__, "msg": norm(str(e), base)}}
out["runner_called"] = bool(captures)
if captures:
out["prompt"] = norm(captures[0][0], base)
out["artifact_path"] = norm(captures[0][1], base)
rows.append(
{
"name": name,
"op": "run",
"setup": {
"question": question,
"model": model,
"env": env or {},
"files": files or {},
"runner": runner_spec,
},
"want": out,
}
)
finally:
for k, v in old_env.items():
if v is None:
os.environ.pop(k, None)
else:
os.environ[k] = v
STILL_UNKNOWN = {
"question": "probe",
"source": "",
"answer": "Could not find authoritative evidence.",
"implication": "Uncertainty remains.",
"status": "STILL_UNKNOWN",
}
run(
"run_success_grounded",
question="Does the API support streaming?",
runner_spec={"type": "write", "artifact": dict(GROUNDED)},
model="m-1",
)
run(
"run_success_still_unknown",
question="probe",
runner_spec={"type": "write", "artifact": dict(STILL_UNKNOWN)},
model="m-1",
)
run(
"run_model_from_env",
question="probe",
runner_spec={"type": "write", "artifact": dict(GROUNDED)},
model="",
env={"MIMO_MODEL": "env-model"},
)
run(
"run_model_unset_unknown",
question="probe",
runner_spec={"type": "write", "artifact": dict(GROUNDED)},
model="",
env={"MIMO_MODEL": None},
)
run(
"run_model_env_blank_is_empty_string",
question="probe",
runner_spec={"type": "write", "artifact": dict(GROUNDED)},
model="",
env={"MIMO_MODEL": ""},
)
run(
"run_budget_exhausted_before_runner",
question="one too many",
runner_spec={"type": "silent"},
model="m",
files={"probe.jsonl": "a\nb\n"},
)
run(
"run_budget_one_left",
question="still allowed",
runner_spec={"type": "write", "artifact": dict(GROUNDED)},
model="m",
files={"probe.jsonl": "a\n"},
)
run(
"run_silent_runner_missing_artifact",
question="q",
runner_spec={"type": "silent"},
model="m",
)
run(
"run_gate_failure_joined",
question="q",
runner_spec={
"type": "write",
"artifact": {"question": "q", "source": "s", "answer": "a", "implication": "i", "status": "MAYBE"},
},
model="m",
)
run(
"run_gate_failure_non_dict",
question="q",
runner_spec={"type": "write", "artifact": [1, 2]},
model="m",
)
run(
"run_gate_failure_status_non_string",
question="q",
runner_spec={
"type": "write",
"artifact": {"question": "q", "source": "s", "answer": "a", "implication": "i", "status": 9},
},
model="m",
)
# ── ProbeResult.to_dict ───────────────────────────────────────────────
def todict(name, fields):
d = ep.ProbeResult(**fields).to_dict()
rows.append(
{
"name": name,
"op": "to_dict",
"setup": {"fields": fields},
"want": json.dumps(d, ensure_ascii=True),
}
)
todict(
"to_dict_defaults",
{
"question": "q",
"source": "s",
"answer": "a",
"implication": "i",
"status": "GROUNDED",
},
)
todict(
"to_dict_full",
{
"question": "q2",
"source": "s2",
"answer": "a2",
"implication": "i2",
"status": "STILL_UNKNOWN",
"probe_id": "probe-1791753600",
"decided_at": "2026-10-12T04:00:00Z",
"model": "m",
},
)
todict(
"to_dict_unicode",
{
"question": "Größe",
"source": "s — dash",
"answer": "✓",
"implication": "🚀",
"status": "CONTRADICTED",
},
)
# ── _default_runner (stubbed subprocess) ──────────────────────────────
stub_path = os.path.join(base, STUB_NAME)
with open(stub_path, "w") as f:
f.write(
"#!/bin/sh\n"
'for a in "$@"; do printf \'%s\\n\' "$a" >> "$EP_CAPTURE"; done\n'
'printf \'stub-out\\n\'\n'
'exit "$EP_EXIT"\n'
)
os.chmod(stub_path, 0o755)
real_require_model = provider_context.require_model
def defrun(name, *, exit_code, agent_env, model_mode):
cdir = os.path.join(base, name)
os.makedirs(cdir, exist_ok=True)
capture = os.path.join(cdir, "argv_capture.txt")
artifact_path = os.path.join(cdir, "evidence_probe.json")
old_env = {k: os.environ.get(k) for k in ("MIMO_BIN", "MIMO_AGENT", "MIMO_MODEL", "EP_CAPTURE", "EP_EXIT")}
try:
if os.path.exists(capture):
os.unlink(capture)
os.environ["MIMO_BIN"] = stub_path
os.environ["EP_CAPTURE"] = capture
os.environ["EP_EXIT"] = str(exit_code)
if agent_env is None:
os.environ.pop("MIMO_AGENT", None)
else:
os.environ["MIMO_AGENT"] = agent_env
if model_mode == "unresolved":
os.environ.pop("MIMO_MODEL", None)
provider_context.require_model = real_require_model
elif model_mode == "blank":
os.environ["MIMO_MODEL"] = " "
provider_context.require_model = real_require_model
else:
provider_context.require_model = (
lambda env_var="MIMO_MODEL": "test-model-v9"
)
try:
ep._default_runner("PROMPT-TEXT", artifact_path)
err = None
except Exception as e: # noqa: BLE001
err = {"type": type(e).__name__, "msg": norm(str(e), base)}
argv = None
if os.path.exists(capture):
with open(capture) as f:
argv = [norm(line, base) for line in f.read().splitlines()]
log_path = os.path.join(cdir, "evidence_probe.stdout.log")
log = None
if os.path.exists(log_path):
with open(log_path) as f:
log = f.read()
rows.append(
{
"name": name,
"op": "default_runner",
"setup": {
"exit_code": exit_code,
"agent_env": agent_env,
"model_mode": model_mode,
},
"want": {"error": err, "argv": argv, "stdout_log": log},
}
)
finally:
provider_context.require_model = real_require_model
for k, v in old_env.items():
if v is None:
os.environ.pop(k, None)
else:
os.environ[k] = v
defrun("defrun_exit0_default_agent", exit_code=0, agent_env=None, model_mode="patched")
defrun("defrun_exit3", exit_code=3, agent_env=None, model_mode="patched")
defrun("defrun_custom_agent", exit_code=0, agent_env="custom-agent", model_mode="patched")
defrun("defrun_model_unresolved", exit_code=0, agent_env=None, model_mode="unresolved")
defrun("defrun_model_blank_env", exit_code=0, agent_env=None, model_mode="blank")
out = {
"oracle": "src/rune/evidence_probe.py (live; validate_probe/count_probes via Rust evidence_provenance core)",
"generated": "2026-10-12",
"frozen_ts": FROZEN_TS,
"frozen_decided_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", _real_gmtime(FROZEN_TS)),
"rows": rows,
}
target = REPO / "src/go/evidence-probe" / "truth.json"
target.write_text(json.dumps(out, indent=1, ensure_ascii=False) + "\n")
print(f"wrote {target} ({len(rows)} rows)")
# determinism self-check: probe_id/decided_at must be frozen
assert all(
r.get("want", {}).get("result", {}).get("probe_id") in (None, f"probe-{FROZEN_TS}")
for r in rows
if r["op"] == "run"
), "clock freeze failed"
if __name__ == "__main__":
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)))
+619
View File
@@ -0,0 +1,619 @@
#!/usr/bin/env python3
"""gen_lexicon_evolution_truth.py — freeze/replay parity truth for
lexicon_evolution (dichotomy wave, #2479 tranche).
Freeze mode (default): exercises EVERY public function of the LIVE Python
oracle (src/rune/lexicon_evolution.py) across every branch and records
(kernel_input, expected_kernel_output) pairs into
src/rust/lexicon_evolution/src/frozen_le_truth.json.
Replay mode (--replay): re-runs the SAME scenarios with the Rust core
delegation ACTIVE (RUNE_LE_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_LE_CORE=python BEFORE any import.
Mutation law: case inputs are deep-copied BEFORE they reach the oracle —
the frozen input must be the pre-call input, not a post-call mutation.
Set law: the oracle's set-valued functions (distinctive_terms,
shared_distinctive) are frozen and compared as SORTED lists — Python set
iteration order is not stable across processes; the facade restores set
types on its public surface.
Kernel-only group: `_stem` is private to the oracle (no facade symbol);
its truth group is replayed kernel-side only (the Python _stem body is
frozen verbatim and covered by tests/test_lexicon_contract.py).
"""
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", "lexicon_evolution", "src", "frozen_le_truth.json")
REPLAY = "--replay" in sys.argv
if not REPLAY:
os.environ["RUNE_LE_CORE"] = "python" # poisoned-truth law (freeze side)
else:
os.environ.pop("RUNE_LE_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 lexicon_evolution as oracle_mod # noqa: E402
if REPLAY:
from rune import le_loader # noqa: E402 (kernel-level replay side)
if not le_loader.available():
print("REPLAY ABORT: kernel not built —", le_loader.LIB_PATH)
sys.exit(2)
class Case:
def __init__(self, name, fn):
self.name = name
self.fn = fn
class _Unset:
pass
_UNSET = _Unset()
def _input(text=_UNSET, word=_UNSET, fact=_UNSET, proposal=_UNSET,
corpus_texts=_UNSET, existing_values=_UNSET, source_texts=_UNSET,
min_distinct=_UNSET, min_strength=_UNSET, evidence_strength=_UNSET):
payload = {}
if text is not _UNSET:
payload["text"] = text
if word is not _UNSET:
payload["word"] = word
if fact is not _UNSET:
payload["fact"] = fact
if proposal is not _UNSET:
payload["proposal"] = proposal
if corpus_texts is not _UNSET:
payload["corpus_texts"] = list(corpus_texts)
if existing_values is not _UNSET:
payload["existing_values"] = list(existing_values)
if source_texts is not _UNSET:
payload["source_texts"] = list(source_texts)
if min_distinct is not _UNSET and min_distinct is not None:
payload["min_distinct"] = min_distinct
if min_strength is not _UNSET and min_strength is not None:
payload["min_strength"] = min_strength
if evidence_strength is not _UNSET:
payload["evidence_strength"] = evidence_strength
return payload
def sorted_or(x):
return sorted(x) if isinstance(x, (set, frozenset)) else x
def facade_call(group, inp):
"""Run the LIVE facade (delegates to the Rust core when built)."""
if group == "content_words":
return oracle_mod.content_words(inp["text"])
if group == "distinctive_terms":
return oracle_mod.distinctive_terms(inp["fact"], inp["corpus_texts"])
if group == "shared_distinctive":
return oracle_mod.shared_distinctive(
inp["fact"], inp["existing_values"], inp["corpus_texts"])
if group == "coherence":
return oracle_mod.coherence(
inp["fact"], inp["existing_values"], inp["corpus_texts"])
if group == "grounded":
if "min_distinct" in inp:
return oracle_mod.grounded(inp["fact"], inp["source_texts"],
min_distinct=inp["min_distinct"])
return oracle_mod.grounded(inp["fact"], inp["source_texts"])
if group == "promote_eligible":
return oracle_mod.promote_eligible(
inp["proposal"], inp["existing"], inp["evidence_strength"],
min_strength=inp.get("min_strength", 3),
corpus_texts=inp["corpus_texts"])
raise KeyError(group)
def kernel_call(group, inp):
"""Run the Rust kernel directly through the ctypes loader."""
return getattr(le_loader, group)(**copy.deepcopy(inp))
# ── content_words ────────────────────────────────────────────────────────────
def cw_case(rec, name, text):
inp = _input(text=text)
got = oracle_mod.content_words(copy.deepcopy(text))
rec(name, inp, got, {"words": got})
def c_cw_basic(rec):
cw_case(rec, "cw-basic-stop-framing-filtered",
"The quick brown fox jumps over the lazy dog")
def c_cw_none(rec):
cw_case(rec, "cw-none-coerced-empty", None)
def c_cw_zero(rec):
# falsy non-string → (text or "") → ""
cw_case(rec, "cw-zero-falsy-coerced-empty", 0)
def c_cw_empty(rec):
cw_case(rec, "cw-empty-string", "")
def c_cw_uppercase(rec):
cw_case(rec, "cw-uppercase-folded", "PREFERS Oatmilk AND Celesta")
def c_cw_short(rec):
cw_case(rec, "cw-short-words-dropped", "a an the cat dog ox")
def c_cw_framing(rec):
cw_case(rec, "cw-framing-words-dropped",
"work always way thing value identity persona")
def c_cw_punct(rec):
cw_case(rec, "cw-punctuation-splits", "co-ffee! 1234 espresso's brew.")
def c_cw_mixed(rec):
cw_case(rec, "cw-mixed-stop-framing", "she will make the work better each day")
# ── distinctive_terms ────────────────────────────────────────────────────────
def dt_case(rec, name, fact, corpus):
inp = _input(fact=fact, corpus_texts=corpus)
got = oracle_mod.distinctive_terms(copy.deepcopy(fact), copy.deepcopy(corpus))
rec(name, inp, got, {"terms": sorted(got)})
def c_dt_empty_fact(rec):
dt_case(rec, "dt-empty-fact", "", ["some text here"])
def c_dt_all_stop(rec):
dt_case(rec, "dt-fact-all-stop-framing", "the that this work",
["some text here"])
def c_dt_rare(rec):
dt_case(rec, "dt-rare-terms-distinctive", "she drinks oatmilk daily",
["oatmilk is life", "coffee today", "tea later"])
def c_dt_common(rec):
dt_case(rec, "dt-common-term-not-distinctive", "physics of black holes",
["physics paper one", "physics paper two", "more physics here",
"unrelated text"])
def _filler_corpus(target_word, doc_ids, n=40):
corpus = []
for i in range(n):
if i in doc_ids:
corpus.append(f"the {target_word} session notes")
else:
corpus.append("filler text here")
return corpus
def c_dt_boundary_not_distinctive(rec):
# df 2 at total 40 → threshold max(2.0, 1.5) = 2.0 → 2 < 2 is False
dt_case(rec, "dt-boundary-df2-not-distinctive",
"celesta zephyr quantum", _filler_corpus("celesta", {0, 1}))
def c_dt_boundary_distinctive(rec):
# df 1 at total 40 → 1 < 2.0 → distinctive
dt_case(rec, "dt-boundary-df1-distinctive",
"celesta zephyr", _filler_corpus("celesta", {0}))
def c_dt_boundary_df3(rec):
dt_case(rec, "dt-boundary-df3-not-distinctive",
"celesta zephyr quantum", _filler_corpus("quantum", {0, 1, 2}))
def c_dt_stem_df(rec):
# migrate/migration share the stem "migr" → df counts both docs
dt_case(rec, "dt-stem-aware-document-frequency", "migrate north soon",
["the migration was huge", "another migration happened", "calm day"])
def c_dt_empty_corpus(rec):
dt_case(rec, "dt-empty-corpus-all-distinctive", "oatmilk ritual", [])
def c_dt_case_fold(rec):
dt_case(rec, "dt-case-folding", "OATMILK Rituals", ["oatmilk bar"])
# ── shared_distinctive ───────────────────────────────────────────────────────
def sd_case(rec, name, fact, existing, corpus):
inp = _input(fact=fact, existing_values=existing, corpus_texts=corpus)
got = oracle_mod.shared_distinctive(
copy.deepcopy(fact), copy.deepcopy(existing), copy.deepcopy(corpus))
rec(name, inp, got, {"shared": sorted(got)})
def c_sd_basic(rec):
sd_case(rec, "sd-shared-distinctive-term", "she never jokes about oatmilk",
["i love oatmilk a lot"], ["oatmilk is life", "tea later"])
def c_sd_none_shared(rec):
sd_case(rec, "sd-nothing-shared", "celesta practice daily",
["i love oatmilk"], ["oatmilk is life", "tea later"])
def c_sd_distinct_empty(rec):
# framing-only fact → distinctive_terms empty → short-circuit
sd_case(rec, "sd-distinct-empty-short-circuit", "work always thing",
["whatever corpus text"], ["whatever corpus text"])
def c_sd_stem_shared(rec):
sd_case(rec, "sd-stem-aware-shared", "migration policy debate",
["we migrated north"], ["migration news", "calm"])
def c_sd_none_value(rec):
sd_case(rec, "sd-none-existing-value-filtered", "she never jokes about oatmilk",
[None, "i love oatmilk"], ["oatmilk is life", "tea later"])
def c_sd_empty_existing(rec):
sd_case(rec, "sd-empty-existing", "oatmilk mornings", [], ["oatmilk is life"])
# ── coherence ────────────────────────────────────────────────────────────────
def co_case(rec, name, fact, existing, corpus=None):
inp = _input(fact=fact, existing_values=existing,
corpus_texts=[] if corpus is None else corpus)
if corpus is None:
got = oracle_mod.coherence(copy.deepcopy(fact), copy.deepcopy(existing))
else:
got = oracle_mod.coherence(copy.deepcopy(fact), copy.deepcopy(existing),
copy.deepcopy(corpus))
rec(name, inp, got, {"verdict": got})
def c_co_empty_existing(rec):
co_case(rec, "co-empty-existing-novel", "oatmilk mornings", [])
def c_co_blank_existing(rec):
co_case(rec, "co-blank-existing-filtered-novel", "oatmilk mornings",
["", " ", None])
def c_co_conflict(rec):
co_case(rec, "co-negation-on-shared-distinctive-conflict",
"she never jokes about oatmilk", ["i love oatmilk"],
["oatmilk bar", "tea"])
def c_co_negation_off_shared(rec):
# negation present but the shared term is not a standalone word in the
# fact ("oatmilk-lover" has no trailing space) → consistent
co_case(rec, "co-negation-not-on-shared-term-consistent",
"oatmilk-lover never smiles", ["i love oatmilk"],
["oatmilk bar", "tea"])
def c_co_consistent(rec):
co_case(rec, "co-shared-distinctive-consistent",
"i enjoy oatmilk mornings", ["i love oatmilk"],
["oatmilk bar", "tea"])
def c_co_novel(rec):
co_case(rec, "co-nothing-shared-novel", "celesta practice daily",
["i love oatmilk"], ["oatmilk bar", "tea"])
def c_co_corpus_kills_shared(rec):
# corpus override drives oatmilk df over the threshold → shared empty → novel
co_case(rec, "co-corpus-override-kills-shared-novel",
"she never jokes about oatmilk", ["i love oatmilk"],
["oatmilk"] * 30)
def c_co_default_corpus(rec):
# corpus_texts omitted → corpus = the (filtered) existing values
co_case(rec, "co-default-corpus-is-values", "i enjoy oatmilk mornings",
["i love oatmilk"])
# ── grounded ─────────────────────────────────────────────────────────────────
def gr_case(rec, name, fact, sources, min_distinct=None):
inp = _input(fact=fact, source_texts=sources, min_distinct=min_distinct)
if min_distinct is None:
got = oracle_mod.grounded(copy.deepcopy(fact), copy.deepcopy(sources))
else:
got = oracle_mod.grounded(copy.deepcopy(fact), copy.deepcopy(sources),
min_distinct=min_distinct)
rec(name, inp, got, {"grounded": got})
def c_gr_no_srcs(rec):
gr_case(rec, "gr-empty-sources-false", "she drinks oatmilk daily", [])
def c_gr_blank_srcs(rec):
gr_case(rec, "gr-blank-sources-false", "she drinks oatmilk daily", ["", " "])
def c_gr_no_fact_terms(rec):
gr_case(rec, "gr-fact-no-content-words-false", "the work thing",
["oatmilk is life"])
def c_gr_direct(rec):
gr_case(rec, "gr-direct-word-hit", "she drinks oatmilk daily",
["oatmilk is life"])
def c_gr_stem(rec):
gr_case(rec, "gr-stem-aware-hit", "migrated north",
["the migration of songbirds"])
def c_gr_miss(rec):
gr_case(rec, "gr-no-stem-overlap-false", "quantum flux patterns",
["oatmilk life"])
def c_gr_min2_one_hit(rec):
gr_case(rec, "gr-min-distinct-2-one-hit-false", "she drinks oatmilk with celesta",
["oatmilk life"], min_distinct=2)
def c_gr_min2_two_hits(rec):
gr_case(rec, "gr-min-distinct-2-two-hits-true", "she drinks oatmilk with celesta",
["oatmilk and celesta life"], min_distinct=2)
def c_gr_case(rec):
gr_case(rec, "gr-source-case-folded", "she drinks oatmilk daily",
["OATMILK IS LIFE"])
# ── _stem (kernel-only: private in the oracle) ───────────────────────────────
STEM_WORDS = [
"migration", "migrated", "migrating", "migrate", "migrates",
"retroactively", "retroactive", "honesty", "ability", "abilities",
"running", "kindness", "cats", "dogs", "hope", "oatmilk", "rituals",
"nation", "national", "carefully", "careful", "berries",
]
def st_case(rec, name, word):
inp = _input(word=word)
got = oracle_mod._stem(copy.deepcopy(word))
rec(name, inp, got, {"stem": got})
def c_stem_table(rec):
for w in STEM_WORDS:
st_case(rec, f"stem-{w}", w)
# ── promote_eligible ─────────────────────────────────────────────────────────
def pe_case(rec, name, proposal, existing, strength, min_strength=None, corpus=()):
inp = _input(proposal=proposal, existing_values=existing,
evidence_strength=strength, min_strength=min_strength,
corpus_texts=corpus)
# the oracle/loader param is `existing` (the promote signature)
inp["existing"] = inp.pop("existing_values")
if min_strength is None:
got = oracle_mod.promote_eligible(copy.deepcopy(proposal),
copy.deepcopy(existing), strength)
else:
got = oracle_mod.promote_eligible(
copy.deepcopy(proposal), copy.deepcopy(existing), strength,
min_strength=min_strength, corpus_texts=copy.deepcopy(corpus))
rec(name, inp, got, {"eligible": got["eligible"], "reason": got["reason"]})
def c_pe_below_strength(rec):
pe_case(rec, "pe-strength-gate-first", "oatmilk mornings", ["i love oatmilk"],
2, min_strength=3, corpus=["oatmilk bar"])
def c_pe_float_strength(rec):
pe_case(rec, "pe-float-strength-message", "oatmilk mornings", ["i love oatmilk"],
2.5, min_strength=3, corpus=["oatmilk bar"])
def c_pe_empty_block(rec):
pe_case(rec, "pe-empty-block-first-principle", "oatmilk mornings", [], 3,
min_strength=3)
def c_pe_blank_block(rec):
pe_case(rec, "pe-blank-block-filtered-first-principle", "oatmilk mornings",
["", " "], 3, min_strength=3)
def c_pe_shared(rec):
pe_case(rec, "pe-shared-distinctive-eligible", "i genuinely love oatmilk rituals",
["i love oatmilk"], 3, min_strength=3,
corpus=["oatmilk bar", "tea later"])
def c_pe_shared_multi(rec):
pe_case(rec, "pe-shared-multiple-sorted-top3", "oatmilk celesta mornings",
["oatmilk and celesta"], 3, min_strength=3,
corpus=["oatmilk bar", "celesta hall"])
def c_pe_no_terms(rec):
pe_case(rec, "pe-framing-only-no-lexical-signal", "work always thing way",
["i love oatmilk"], 3, min_strength=3, corpus=["oatmilk bar"])
def c_pe_novel_grounded(rec):
pe_case(rec, "pe-novel-axis-grounded-eligible", "celesta practice daily",
["i love oatmilk"], 3, min_strength=3,
corpus=["she plays celesta nightly"])
def c_pe_novel_parked(rec):
pe_case(rec, "pe-novel-axis-not-grounded-parked", "celesta practice daily",
["i love oatmilk"], 3, min_strength=3, corpus=["oatmilk bar only"])
def c_pe_min_strength_one(rec):
pe_case(rec, "pe-min-strength-1-passes-gate", "oatmilk mornings", [], 1,
min_strength=1)
def c_pe_corpus_override_grounded(rec):
# corpus override pushes oatmilk df over the threshold → shared empty →
# novel axis, but the proposal still grounds in the overridden corpus
pe_case(rec, "pe-corpus-override-novel-grounded", "oatmilk rituals",
["i love oatmilk"], 3, min_strength=3,
corpus=["oatmilk here", "oatmilk there", "oatmilk everywhere"])
def c_pe_default_corpus(rec):
# corpus_texts omitted → corpus = existing values
pe_case(rec, "pe-default-corpus-is-existing", "oatmilk rituals",
["i love oatmilk"], 3, min_strength=3)
# ── driver ───────────────────────────────────────────────────────────────────
CONTENT_WORDS = [c_cw_basic, c_cw_none, c_cw_zero, c_cw_empty, c_cw_uppercase,
c_cw_short, c_cw_framing, c_cw_punct, c_cw_mixed]
DISTINCTIVE = [c_dt_empty_fact, c_dt_all_stop, c_dt_rare, c_dt_common,
c_dt_boundary_not_distinctive, c_dt_boundary_distinctive,
c_dt_boundary_df3, c_dt_stem_df, c_dt_empty_corpus, c_dt_case_fold]
SHARED = [c_sd_basic, c_sd_none_shared, c_sd_distinct_empty, c_sd_stem_shared,
c_sd_none_value, c_sd_empty_existing]
COHERENCE = [c_co_empty_existing, c_co_blank_existing, c_co_conflict,
c_co_negation_off_shared, c_co_consistent, c_co_novel,
c_co_corpus_kills_shared, c_co_default_corpus]
GROUNDED = [c_gr_no_srcs, c_gr_blank_srcs, c_gr_no_fact_terms, c_gr_direct,
c_gr_stem, c_gr_miss, c_gr_min2_one_hit, c_gr_min2_two_hits,
c_gr_case]
STEM = [c_stem_table]
PROMOTE = [c_pe_below_strength, c_pe_float_strength, c_pe_empty_block,
c_pe_blank_block, c_pe_shared, c_pe_shared_multi, c_pe_no_terms,
c_pe_novel_grounded, c_pe_novel_parked, c_pe_min_strength_one,
c_pe_corpus_override_grounded, c_pe_default_corpus]
# group → (cases, facade_replayable, kernel_symbol, unwrap of expected)
GROUPS = {
"content_words": (CONTENT_WORDS, True, "le_content_words",
lambda w: w["words"]),
"distinctive_terms": (DISTINCTIVE, True, "le_distinctive_terms",
lambda w: w["terms"]),
"shared_distinctive": (SHARED, True, "le_shared_distinctive",
lambda w: w["shared"]),
"coherence": (COHERENCE, True, "le_coherence", lambda w: w["verdict"]),
"grounded": (GROUNDED, True, "le_grounded", lambda w: w["grounded"]),
"stem": (STEM, False, "le_stem", lambda w: w["stem"]),
"promote_eligible": (PROMOTE, True, "le_promote_eligible", 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):
entry = {"name": name, "input": inp,
"return": sorted_or(oracle_ret) if group in
("distinctive_terms", "shared_distinctive")
else oracle_ret,
"expected": expected}
if REPLAY and facade_replayable:
old = next(c for c in frozen[group] 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 [{group}] {name} {key}")
print(" got: ", json.dumps(got, sort_keys=True, default=str)[:600])
print(" want:", json.dumps(want, sort_keys=True, default=str)[:600])
recs.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 = GROUPS[group][2]
unwrap = GROUPS[group][3]
for case in cases:
checked += 1
got = kernel_call(group, case["input"])
want = unwrap(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/lexicon_evolution.py",
"pin": "RUNE_LE_CORE=python",
"note": "Frozen via scripts/gen_lexicon_evolution_truth.py against the "
"live Python oracle. Battery embeds this VERBATIM. Group "
"`stem` is kernel-only (_stem is private in the oracle).",
"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())
+654
View File
@@ -0,0 +1,654 @@
#!/usr/bin/env python3
"""Freeze message_graph oracle truth for the Rust port battery (dichotomy
wave #2479 tranche).
Exercises EVERY public function of src/rune/message_graph.py across every
branch: load (missing file, corrupt JSON, wrong/missing schema, valid doc,
duplicate ids, verbatim extra fields, non-dict doc -> AttributeError,
non-list nodes -> TypeError), save (doc shape + file bytes incl. mkdir),
add (default-parent chain, explicit parent with/without edge, explicit
None -> head, falsy parent "", edge to a ghost parent, unicode, every
kind, bad kind/edge -> ValueError), tool_call (json.dumps content shape,
nested args, parent / default head), tool_result (link, missing call ->
KeyError, non-tool-call parent allowed), revise (root revision picks up
the default-head parent quirk, child revision, chained revisions, missing
-> KeyError), branch (ok, missing -> KeyError), lineage (empty graph,
default head, explicit id, superseded skip, missing-parent break, ghost
superseded_by target, unknown id, parent cycle), history (single, full
chain, from the middle, missing id), migrate_flat (chain, empty
transcript, missing role/content keys, existing-file seed) and cue (no
cue, replaces, branched from, superseded by, falsy superseded_by, edge
iteration order).
Nondeterminism seams: the facade generates the uuid4 hex id and the
_now() timestamp and passes them to the kernel as plain arguments — here
both are pinned (sequential fake uuid4, frozen clock) 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_MG_CORE=python (the ORACLE). The kernel-side parity
pass re-runs with RUNE_MG_CORE=auto and MG_TRUTH_OUT set; identical bytes
== 100% live parity.
Output: /tmp/mg_truth.json — replayed VERBATIM by
src/rust/message_graph/src/branch_battery.rs.
"""
from __future__ import annotations
import copy
import json
import os
import sys
import tempfile
import uuid
from pathlib import Path
os.environ.setdefault("RUNE_MG_CORE", "python") # freeze the ORACLE, never the kernel
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
from rune import message_graph as mg # noqa: E402
# ── determinism seams (see module docstring) ─────────────────────────────
class _FakeUUID:
def __init__(self, hex_: str) -> None:
self.hex = hex_
_seq = [0]
def _fake_uuid4():
_seq[0] += 1
return _FakeUUID(f"deadbeef{_seq[0]:04d}")
uuid.uuid4 = _fake_uuid4
CLOCK = "2031-01-01T00:00:00.000000+00:00"
mg._now = lambda: CLOCK # noqa: E731 (the oracle's clock, pinned)
TMP = Path(tempfile.mkdtemp(prefix="mg_truth_"))
GROUPS = ("load", "save", "add", "tool_call", "tool_result", "revise",
"branch", "lineage", "history", "migrate_flat", "cue")
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})
def wire(g: "mg.MessageGraph") -> dict:
"""Graph state over the wire: nodes as an insertion-ordered object,
order as the arrival list."""
return {"nodes": copy.deepcopy(g.nodes), "order": list(g.order)}
def exc_of(fn, *a, **kw) -> dict:
try:
fn(*a, **kw)
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 fresh(name: str, doc_text: str | None = None) -> "mg.MessageGraph":
p = TMP / f"{name}.json"
if doc_text is not None:
p.parent.mkdir(parents=True, exist_ok=True)
p.write_text(doc_text)
return mg.MessageGraph(path=p)
# ══ load ═════════════════════════════════════════════════════════════════
# missing file -> empty graph
g = fresh("load_missing")
case("load", "load-missing-file", {"text": None}, {"graph": wire(g)})
# empty object: no schema -> ignored
g = fresh("load_empty_obj", "{}")
case("load", "load-no-schema", {"text": "{}"}, {"graph": wire(g)})
# wrong schema
g = fresh("load_wrong_schema", json.dumps({"schema": 2, "nodes": []}))
case("load", "load-wrong-schema", {"text": json.dumps({"schema": 2, "nodes": []})},
{"graph": wire(g)})
# corrupt JSON
g = fresh("load_corrupt", "not json {")
case("load", "load-corrupt-json", {"text": "not json {"}, {"graph": wire(g)})
# valid doc: nodes ingested verbatim in file order
doc = {"schema": 1, "nodes": [
{"id": "aaa000000001", "kind": "message", "role": "user", "content": "q",
"ts": CLOCK, "parent": None, "edges": [], "superseded_by": None},
{"id": "bbb000000002", "kind": "reply", "role": "assistant", "content": "a",
"ts": CLOCK, "parent": "aaa000000001", "edges": [], "superseded_by": None},
]}
g = fresh("load_valid", json.dumps(doc))
case("load", "load-valid", {"text": json.dumps(doc)}, {"graph": wire(g)})
# duplicate ids: last wins in nodes, BOTH stay in order
doc = {"schema": 1, "nodes": [
{"id": "ccc000000003", "kind": "message", "role": "user", "content": "v1",
"ts": CLOCK, "parent": None, "edges": [], "superseded_by": None},
{"id": "ccc000000003", "kind": "message", "role": "user", "content": "v2",
"ts": CLOCK, "parent": None, "edges": [], "superseded_by": None},
]}
g = fresh("load_dup", json.dumps(doc))
case("load", "load-duplicate-ids-last-wins-order-keeps-both",
{"text": json.dumps(doc)}, {"graph": wire(g)})
# extra fields ride verbatim
doc = {"schema": 1, "nodes": [
{"id": "ddd000000004", "kind": "message", "role": "user", "content": "x",
"ts": CLOCK, "parent": None, "edges": [], "superseded_by": None,
"extra": {"deep": [1, 2.5, None, True]}, "π": "unicode key"},
]}
g = fresh("load_extra", json.dumps(doc))
case("load", "load-extra-fields-verbatim", {"text": json.dumps(doc)},
{"graph": wire(g)})
# non-dict doc -> the oracle's doc.get raises AttributeError
def _err_file(name: str, text: str) -> Path:
p = TMP / f"{name}.json"
p.write_text(text)
return p
_p = _err_file("load_list", "[1, 2]")
case("load", "load-list-doc-attributeerror", {"text": "[1, 2]"},
exc_of(mg.MessageGraph, path=_p))
_p = _err_file("load_str", '"hello"')
case("load", "load-string-doc-attributeerror", {"text": '"hello"'},
exc_of(mg.MessageGraph, path=_p))
_p = _err_file("load_int", "5")
case("load", "load-int-doc-attributeerror", {"text": "5"},
exc_of(mg.MessageGraph, path=_p))
# nodes present but not a list -> the for-loop raises TypeError
_p = _err_file("load_dictnodes", json.dumps({"schema": 1, "nodes": {"aaa": {}}}))
case("load", "load-dict-nodes-typeerror",
{"text": json.dumps({"schema": 1, "nodes": {"aaa": {}}})},
exc_of(mg.MessageGraph, path=_p))
# ══ add ══════════════════════════════════════════════════════════════════
# default parent on an empty graph: parent None, edges []
g = fresh("add_empty")
node = g.add("user", "hello")
case("add", "add-default-parent-empty-graph",
{"graph": {"nodes": {}, "order": []}, "id": node["id"],
"ts": node["ts"], "role": "user", "content": "hello", "kind": "message",
"parent": None, "edge": "parent"},
{"node": copy.deepcopy(node), "graph": wire(g)})
# default parent chains to the head; edges stay [] (parent arg was None)
g = fresh("add_chain")
g.add("user", "q1")
before = wire(g)
node = g.add("assistant", "a1")
case("add", "add-default-parent-head-no-edge",
{"graph": before, "id": node["id"], "ts": node["ts"], "role": "assistant",
"content": "a1", "kind": "message", "parent": None, "edge": "parent"},
{"node": copy.deepcopy(node), "graph": wire(g)})
# explicit parent + explicit edge
g = fresh("add_explicit")
a = g.add("user", "q")
before = wire(g)
node = g.add("assistant", "a", kind="reply", parent=a["id"], edge="reply_to")
case("add", "add-explicit-parent-edge",
{"graph": before, "id": node["id"], "ts": node["ts"], "role": "assistant",
"content": "a", "kind": "reply", "parent": a["id"], "edge": "reply_to"},
{"node": copy.deepcopy(node), "graph": wire(g)})
# explicit parent, default edge "parent"
g = fresh("add_explicit_default_edge")
a = g.add("user", "q")
before = wire(g)
node = g.add("assistant", "a", parent=a["id"])
case("add", "add-explicit-parent-default-edge",
{"graph": before, "id": node["id"], "ts": node["ts"], "role": "assistant",
"content": "a", "kind": "message", "parent": a["id"], "edge": "parent"},
{"node": copy.deepcopy(node), "graph": wire(g)})
# explicit None parent with a non-empty order: parent becomes the HEAD but
# edges stay [] (the arg was None — falsy)
g = fresh("add_none_parent")
a = g.add("user", "q")
before = wire(g)
node = g.add("assistant", "a", parent=None)
case("add", "add-explicit-none-parent-becomes-head",
{"graph": before, "id": node["id"], "ts": node["ts"], "role": "assistant",
"content": "a", "kind": "message", "parent": None, "edge": "parent"},
{"node": copy.deepcopy(node), "graph": wire(g)})
# falsy parent "": stored as parent, but NO edge (falsy)
g = fresh("add_falsy_parent")
a = g.add("user", "q")
before = wire(g)
node = g.add("assistant", "a", parent="")
case("add", "add-falsy-empty-parent-no-edge",
{"graph": before, "id": node["id"], "ts": node["ts"], "role": "assistant",
"content": "a", "kind": "message", "parent": "", "edge": "parent"},
{"node": copy.deepcopy(node), "graph": wire(g)})
# edge to a ghost parent is allowed (no referential check in add)
g = fresh("add_ghost_parent")
a = g.add("user", "q")
before = wire(g)
node = g.add("assistant", "a", parent="ghost0000001", edge="reply_to")
case("add", "add-ghost-parent-allowed",
{"graph": before, "id": node["id"], "ts": node["ts"], "role": "assistant",
"content": "a", "kind": "message", "parent": "ghost0000001",
"edge": "reply_to"},
{"node": copy.deepcopy(node), "graph": wire(g)})
# unicode content
g = fresh("add_unicode")
node = g.add("user", "こんにちは ☕ héllo")
case("add", "add-unicode-content",
{"graph": {"nodes": {}, "order": []}, "id": node["id"], "ts": node["ts"],
"role": "user", "content": "こんにちは ☕ héllo", "kind": "message",
"parent": None, "edge": "parent"},
{"node": copy.deepcopy(node), "graph": wire(g)})
# every kind
for k in ("message", "reply", "tool_call", "tool_result", "revision", "branch_head"):
g = fresh(f"add_kind_{k}")
node = g.add("user", f"content-{k}", kind=k)
case("add", f"add-kind-{k}",
{"graph": {"nodes": {}, "order": []}, "id": node["id"], "ts": node["ts"],
"role": "user", "content": f"content-{k}", "kind": k,
"parent": None, "edge": "parent"},
{"node": copy.deepcopy(node), "graph": wire(g)})
# bad kind / bad edge (ValueError with the oracle's repr formatting)
g = fresh("add_bad_kind")
case("add", "add-unknown-kind-valueerror",
{"graph": {"nodes": {}, "order": []}, "id": "deadbeef_err", "ts": CLOCK,
"role": "user", "content": "x", "kind": "nonsense", "parent": None,
"edge": "parent"},
exc_of(g.add, "user", "x", kind="nonsense"))
case("add", "add-unknown-edge-valueerror",
{"graph": {"nodes": {}, "order": []}, "id": "deadbeef_err", "ts": CLOCK,
"role": "user", "content": "x", "kind": "message", "parent": None,
"edge": "nope"},
exc_of(g.add, "user", "x", edge="nope"))
# kind checked BEFORE edge
case("add", "add-both-bad-kind-wins",
{"graph": {"nodes": {}, "order": []}, "id": "deadbeef_err", "ts": CLOCK,
"role": "user", "content": "x", "kind": "nonsense", "parent": None,
"edge": "nope"},
exc_of(g.add, "user", "x", kind="nonsense", edge="nope"))
# ══ tool_call ════════════════════════════════════════════════════════════
g = fresh("tc_basic")
node = g.tool_call("read", {"path": "a.py"})
case("tool_call", "tool-call-basic",
{"graph": {"nodes": {}, "order": []}, "id": node["id"], "ts": node["ts"],
"content": json.dumps({"call": "read", "args": {"path": "a.py"}}),
"parent": None},
{"node": copy.deepcopy(node), "graph": wire(g)})
g = fresh("tc_parent")
a = g.add("assistant", "running")
before = wire(g)
node = g.tool_call("write", {"path": "b.py", "bytes": 3}, parent=a["id"])
case("tool_call", "tool-call-explicit-parent",
{"graph": before, "id": node["id"], "ts": node["ts"],
"content": json.dumps({"call": "write", "args": {"path": "b.py", "bytes": 3}}),
"parent": a["id"]},
{"node": copy.deepcopy(node), "graph": wire(g)})
g = fresh("tc_nested")
node = g.tool_call("search", {"q": "π", "opts": {"deep": [1, 2, {"k": None}],
"flag": True}, "n": 0.5})
case("tool_call", "tool-call-nested-args",
{"graph": {"nodes": {}, "order": []}, "id": node["id"], "ts": node["ts"],
"content": json.dumps({"call": "search",
"args": {"q": "π", "opts": {"deep": [1, 2, {"k": None}],
"flag": True}, "n": 0.5}}),
"parent": None},
{"node": copy.deepcopy(node), "graph": wire(g)})
# ══ tool_result ══════════════════════════════════════════════════════════
g = fresh("tr_ok")
c = g.tool_call("read", {"path": "a.py"})
before = wire(g)
node = g.tool_result(c["id"], "file contents")
case("tool_result", "tool-result-links-call",
{"graph": before, "id": node["id"], "ts": node["ts"], "call_id": c["id"],
"result": "file contents"},
{"node": copy.deepcopy(node), "graph": wire(g)})
# no kind check on the parent: any node can be a "call"
g = fresh("tr_any_node")
a = g.add("user", "not a call")
before = wire(g)
node = g.tool_result(a["id"], "result anyway")
case("tool_result", "tool-result-non-call-parent-allowed",
{"graph": before, "id": node["id"], "ts": node["ts"], "call_id": a["id"],
"result": "result anyway"},
{"node": copy.deepcopy(node), "graph": wire(g)})
g = fresh("tr_missing")
case("tool_result", "tool-result-missing-keyerror",
{"graph": {"nodes": {}, "order": []}, "id": "deadbeef_err", "ts": CLOCK,
"call_id": "ghost0000001", "result": "x"},
exc_of(g.tool_result, "ghost0000001", "x"))
# ══ revise ═══════════════════════════════════════════════════════════════
# root revision: old.get("parent") is None -> add substitutes the HEAD,
# which at that moment is the old node itself (quirk, frozen verbatim)
g = fresh("rev_root")
a = g.add("assistant", "v1")
before = wire(g)
rev = g.revise(a["id"], "v2")
case("revise", "revise-root-head-parent-quirk",
{"graph": before, "node_id": a["id"], "new_content": "v2",
"id": rev["id"], "ts": rev["ts"]},
{"node": copy.deepcopy(rev), "graph": wire(g)})
# child revision: keeps the old parent, edges revises + supersedes
g = fresh("rev_child")
a = g.add("user", "q")
b = g.add("assistant", "v1", parent=a["id"])
before = wire(g)
rev = g.revise(b["id"], "v2")
case("revise", "revise-child-keeps-parent",
{"graph": before, "node_id": b["id"], "new_content": "v2",
"id": rev["id"], "ts": rev["ts"]},
{"node": copy.deepcopy(rev), "graph": wire(g)})
# chained revision: revise the revision
g = fresh("rev_chain")
a = g.add("assistant", "v1")
r1 = g.revise(a["id"], "v2")
before = wire(g)
r2 = g.revise(r1["id"], "v3")
case("revise", "revise-of-revision",
{"graph": before, "node_id": r1["id"], "new_content": "v3",
"id": r2["id"], "ts": r2["ts"]},
{"node": copy.deepcopy(r2), "graph": wire(g)})
g = fresh("rev_missing")
case("revise", "revise-missing-keyerror",
{"graph": {"nodes": {}, "order": []}, "node_id": "ghost0000001",
"new_content": "x", "id": "deadbeef_err", "ts": CLOCK},
exc_of(g.revise, "ghost0000001", "x"))
# ══ branch ═══════════════════════════════════════════════════════════════
g = fresh("branch_ok")
a = g.add("user", "root")
g.add("assistant", "answer", parent=a["id"])
before = wire(g)
head = g.branch(a["id"])
case("branch", "branch-from-message",
{"graph": before, "node_id": a["id"], "id": head["id"], "ts": head["ts"]},
{"node": copy.deepcopy(head), "graph": wire(g)})
g = fresh("branch_missing")
case("branch", "branch-missing-keyerror",
{"graph": {"nodes": {}, "order": []}, "node_id": "ghost0000001",
"id": "deadbeef_err", "ts": CLOCK},
exc_of(g.branch, "ghost0000001"))
# ══ lineage ══════════════════════════════════════════════════════════════
g = fresh("lin_empty")
case("lineage", "lineage-empty-graph", {"graph": wire(g), "node_id": None},
{"result": []})
# default head: full chain oldest-first
g = fresh("lin_chain")
a = g.add("user", "q")
b = g.add("assistant", "a1", parent=a["id"])
c = g.add("assistant", "a2", parent=b["id"])
case("lineage", "lineage-default-head-full-chain",
{"graph": wire(g), "node_id": None},
{"result": [copy.deepcopy(n) for n in g.lineage()]})
assert [n["id"] for n in g.lineage()] == [a["id"], b["id"], c["id"]]
# explicit id: subchain
case("lineage", "lineage-explicit-id-subchain",
{"graph": wire(g), "node_id": b["id"]},
{"result": [copy.deepcopy(n) for n in g.lineage(b["id"])]})
assert [n["id"] for n in g.lineage(b["id"])] == [a["id"], b["id"]]
# superseded nodes yield to their revision; unrelated branches excluded
g = fresh("lin_superseded")
a = g.add("user", "q")
b = g.add("assistant", "v1", parent=a["id"])
other = g.add("user", "unrelated")
rev = g.revise(b["id"], "v2")
case("lineage", "lineage-skips-superseded-default-head",
{"graph": wire(g), "node_id": None},
{"result": [copy.deepcopy(n) for n in g.lineage()]})
assert [n["id"] for n in g.lineage()] == [a["id"], rev["id"]]
case("lineage", "lineage-from-root-excludes-other-branch",
{"graph": wire(g), "node_id": a["id"]},
{"result": [copy.deepcopy(n) for n in g.lineage(a["id"])]})
assert [n["id"] for n in g.lineage(a["id"])] == [a["id"]]
# parent chain into a ghost: walk breaks quietly
g = fresh("lin_ghost_parent")
a = g.add("user", "q")
g.nodes[a["id"]]["parent"] = "ghost0000001"
case("lineage", "lineage-ghost-parent-breaks",
{"graph": wire(g), "node_id": a["id"]},
{"result": [copy.deepcopy(n) for n in g.lineage(a["id"])]})
assert [n["id"] for n in g.lineage(a["id"])] == [a["id"]]
# superseded_by pointing OUTSIDE the graph: node included (guard fails)
g = fresh("lin_ghost_superseded")
a = g.add("user", "q")
g.nodes[a["id"]]["superseded_by"] = "ghost0000001"
case("lineage", "lineage-ghost-superseded-by-included",
{"graph": wire(g), "node_id": a["id"]},
{"result": [copy.deepcopy(n) for n in g.lineage(a["id"])]})
assert [n["id"] for n in g.lineage(a["id"])] == [a["id"]]
# unknown id: empty
g = fresh("lin_unknown")
case("lineage", "lineage-unknown-id-empty",
{"graph": wire(g), "node_id": "ghost0000001"}, {"result": []})
# parent cycle: the seen-set guard stops the walk
doc = {"schema": 1, "nodes": [
{"id": "aaa000000001", "kind": "message", "role": "user", "content": "a",
"ts": CLOCK, "parent": "bbb000000002", "edges": [], "superseded_by": None},
{"id": "bbb000000002", "kind": "message", "role": "user", "content": "b",
"ts": CLOCK, "parent": "aaa000000001", "edges": [], "superseded_by": None},
]}
g = fresh("lin_cycle", json.dumps(doc))
case("lineage", "lineage-parent-cycle-guarded",
{"graph": wire(g), "node_id": "aaa000000001"},
{"result": [copy.deepcopy(n) for n in g.lineage("aaa000000001")]})
assert [n["id"] for n in g.lineage("aaa000000001")] == ["bbb000000002",
"aaa000000001"]
# ══ history ══════════════════════════════════════════════════════════════
# single node: no supersedes edge -> just itself
g = fresh("hist_single")
a = g.add("assistant", "v1")
case("history", "history-single-node",
{"graph": wire(g), "node_id": a["id"]},
{"result": [copy.deepcopy(n) for n in g.history(a["id"])]})
assert [n["content"] for n in g.history(a["id"])] == ["v1"]
# full chain oldest-first, asked from the tip AND from the middle
g = fresh("hist_chain")
a = g.add("assistant", "v1")
r1 = g.revise(a["id"], "v2")
r2 = g.revise(r1["id"], "v3")
case("history", "history-chain-from-tip",
{"graph": wire(g), "node_id": r2["id"]},
{"result": [copy.deepcopy(n) for n in g.history(r2["id"])]})
assert [n["content"] for n in g.history(r2["id"])] == ["v1", "v2", "v3"]
case("history", "history-chain-from-middle-full-trail",
{"graph": wire(g), "node_id": r1["id"]},
{"result": [copy.deepcopy(n) for n in g.history(r1["id"])]})
assert [n["content"] for n in g.history(r1["id"])] == ["v1", "v2", "v3"]
# unknown id: empty trail
g = fresh("hist_missing")
case("history", "history-unknown-id-empty",
{"graph": wire(g), "node_id": "ghost0000001"}, {"result": []})
# revision whose superseded_by target is a ghost: trail stops there
g = fresh("hist_ghost_target")
a = g.add("assistant", "v1")
r1 = g.revise(a["id"], "v2")
g.nodes[r1["id"]]["superseded_by"] = "ghost0000001"
case("history", "history-ghost-superseded-by-stops",
{"graph": wire(g), "node_id": a["id"]},
{"result": [copy.deepcopy(n) for n in g.history(a["id"])]})
assert [n["content"] for n in g.history(a["id"])] == ["v1", "v2"]
# ══ migrate_flat ═════════════════════════════════════════════════════════
def mig_ids(g, n):
"""The ids the oracle's migrate consumed (the last n arrivals)."""
return list(g.order[len(g.order) - n:]) if n else []
flat = [{"role": "user", "content": "q1"}, {"role": "assistant", "content": "a1"}]
g = mg.MessageGraph.migrate_flat(copy.deepcopy(flat), path=TMP / "mig_basic.json")
case("migrate_flat", "migrate-basic-chain",
{"text": None, "transcript": copy.deepcopy(flat),
"ids": mig_ids(g, 2), "tss": [CLOCK, CLOCK]},
{"graph": wire(g)})
assert [g.nodes[i]["parent"] for i in g.order] == [None, g.order[0]]
# empty transcript
g = mg.MessageGraph.migrate_flat([], path=TMP / "mig_empty.json")
case("migrate_flat", "migrate-empty-transcript",
{"text": None, "transcript": [], "ids": [], "tss": []}, {"graph": wire(g)})
# missing role/content keys -> system / "" defaults
flat = [{"role": "user"}, {}, {"content": "no role"}]
g = mg.MessageGraph.migrate_flat(copy.deepcopy(flat), path=TMP / "mig_defaults.json")
case("migrate_flat", "migrate-missing-keys-default-system-empty",
{"text": None, "transcript": copy.deepcopy(flat), "ids": mig_ids(g, 3),
"tss": [CLOCK] * 3},
{"graph": wire(g)})
assert [g.nodes[i]["role"] for i in g.order] == ["user", "system", "system"]
assert [g.nodes[i]["content"] for i in g.order] == ["", "", "no role"]
# existing file seeds the graph first, migration appends after
seed_doc = {"schema": 1, "nodes": [
{"id": "eee000000005", "kind": "message", "role": "user", "content": "old",
"ts": CLOCK, "parent": None, "edges": [], "superseded_by": None},
]}
_seed = _err_file("mig_seed", json.dumps(seed_doc))
g = mg.MessageGraph.migrate_flat([{"role": "user", "content": "new"}],
path=_seed)
case("migrate_flat", "migrate-existing-file-seed",
{"text": json.dumps(seed_doc), "transcript": [{"role": "user", "content": "new"}],
"ids": mig_ids(g, 1), "tss": [CLOCK]},
{"graph": wire(g)})
# ══ cue ══════════════════════════════════════════════════════════════════
g = fresh("cue_plain")
a = g.add("assistant", "v1")
plain = copy.deepcopy(g.nodes[a["id"]])
case("cue", "cue-plain-node", {"node": plain}, {"result": ""})
assert mg.MessageGraph.cue(plain) == ""
rev = g.revise(a["id"], "v2")
case("cue", "cue-revision-replaces",
{"node": copy.deepcopy(g.nodes[rev["id"]])},
{"result": f"replaces {a['id'][:8]}"})
assert mg.MessageGraph.cue(g.nodes[rev["id"]]) == f"replaces {a['id'][:8]}"
case("cue", "cue-superseded-by",
{"node": copy.deepcopy(g.nodes[a["id"]])},
{"result": f"superseded by {rev['id'][:8]}"})
assert mg.MessageGraph.cue(g.nodes[a["id"]]) == f"superseded by {rev['id'][:8]}"
g = fresh("cue_branch")
a = g.add("user", "root")
head = g.branch(a["id"])
case("cue", "cue-branch-head",
{"node": copy.deepcopy(g.nodes[head["id"]])},
{"result": f"branched from {a['id'][:8]}"})
assert mg.MessageGraph.cue(g.nodes[head["id"]]) == f"branched from {a['id'][:8]}"
# falsy superseded_by "" is NOT a cue
node = copy.deepcopy(plain)
node["superseded_by"] = ""
case("cue", "cue-falsy-superseded-by-empty", {"node": node}, {"result": ""})
assert mg.MessageGraph.cue(node) == ""
# first supersedes/branches_from edge in iteration order wins
node = copy.deepcopy(plain)
node["edges"] = [{"type": "parent", "to": "aaa000000001"},
{"type": "supersedes", "to": "bbb000000002"}]
case("cue", "cue-first-supersedes-edge-wins", {"node": node},
{"result": "replaces bbb00000"})
assert mg.MessageGraph.cue(node) == "replaces bbb00000"
node2 = copy.deepcopy(plain)
node2["edges"] = [{"type": "branches_from", "to": "ccc000000003"},
{"type": "supersedes", "to": "bbb000000002"}]
case("cue", "cue-branches-from-beats-later-supersedes", {"node": node2},
{"result": "branched from ccc00000"})
assert mg.MessageGraph.cue(node2) == "branched from ccc00000"
# ══ save ═════════════════════════════════════════════════════════════════
# basic save: doc shape + exact file bytes (json.dumps indent=2)
g = fresh("save_basic")
a = g.add("user", "persist me")
g.save()
doc = {"schema": mg.SCHEMA, "nodes": [g.nodes[i] for i in g.order]}
case("save", "save-basic-doc-and-bytes",
{"graph": {"nodes": {a["id"]: copy.deepcopy(g.nodes[a["id"]])},
"order": [a["id"]]}},
{"doc": copy.deepcopy(doc), "file": (TMP / "save_basic.json").read_text()})
assert (TMP / "save_basic.json").read_text() == json.dumps(doc, indent=2)
# save creates missing parent dirs
deep = TMP / "deep" / "nested" / "dir" / "g.json"
g2 = mg.MessageGraph(path=deep)
g2.add("user", "deep")
g2.save()
doc2 = {"schema": mg.SCHEMA, "nodes": [g2.nodes[i] for i in g2.order]}
case("save", "save-creates-parent-dirs",
{"graph": wire(g2)}, {"doc": copy.deepcopy(doc2), "file": deep.read_text()})
assert deep.exists()
# round trip: save -> fresh load reproduces nodes AND order
g3 = mg.MessageGraph(path=TMP / "save_basic.json")
case("load", "load-save-round-trip",
{"text": (TMP / "save_basic.json").read_text()}, {"graph": wire(g3)})
assert g3.order == g.order
assert g3.nodes[a["id"]]["content"] == "persist me"
# ══ oracle health (the repo's own battery must pass on the oracle) ══════
import unittest # noqa: E402
suite = unittest.defaultTestLoader.discover(
str(Path(__file__).resolve().parents[1] / "tests"),
pattern="test_message_graph.py")
runner = unittest.TextTestRunner(verbosity=0)
if not runner.run(suite).wasSuccessful():
raise AssertionError("oracle regression: test_message_graph.py failed")
# ══ write ════════════════════════════════════════════════════════════════
truth = {
"frozen": "message_graph load/save/add/tool_call/tool_result/revise/"
"branch/lineage/history/migrate_flat/cue",
"oracle": "src/rune/message_graph.py",
"note": "Graph wire: {nodes: {id: node} insertion-ordered, order: [ids]}. "
"Nondeterminism seams (uuid4 hex id, _now timestamp) are facade "
"arguments — frozen CLOCK and sequential deadbeefNNNN ids replay "
"verbatim. save() builds the doc in the kernel; the file write "
"(mkdir + json.dumps indent=2) and tool_call's json.dumps content "
"stay in the facade (serialization isomorphism).",
}
for gname in GROUPS:
truth[gname] = groups[gname]
out = Path(os.environ.get("MG_TRUTH_OUT", "/tmp/mg_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}")
+208
View File
@@ -0,0 +1,208 @@
#!/usr/bin/env python3
"""gen_outcome_ledger_truth.py — outcome_ledger dichotomy wave truth battery
(#2479 tranche). Drives the LIVE python oracle (RUNE_OL_CORE=python pinned)
through every public function and branch, and freezes the observations to
src/rust/outcome_ledger/src/frozen_to_truth.json.
Public surface exercised:
* OutcomeLedger.__init__ (fresh db + reopen-existing branches)
* OutcomeLedger.record (all field branches: success/fail, correction,
floats, defaults)
* OutcomeLedger.get_stats (no-family branch AND task_family branch,
empty-db zero branch AND populated branch)
* OutcomeLedger.get_model_stats (hit and miss)
* OutcomeRecord.to_dict (full record)
(test_ledger stays a facade-level python self-test — covered by live
parity, not by the kernel battery.)
Timestamps are PINNED in the inputs, so observations are fully
deterministic and need no masking.
"""
import json
import os
import sys
import tempfile
from pathlib import Path
REPO = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(REPO / "src"))
sys.path.insert(0, str(REPO / "src" / "rune"))
os.environ["RUNE_OL_CORE"] = "python"
from rune.outcome_ledger import OutcomeLedger, OutcomeRecord # noqa: E402
T0 = "2026-10-11T00:00:01+00:00"
T1 = "2026-10-11T00:00:02+00:00"
T2 = "2026-10-11T00:00:03+00:00"
T3 = "2026-10-11T00:00:04+00:00"
T4 = "2026-10-11T00:00:05+00:00"
T5 = "2026-10-11T00:00:06+00:00"
class Driver:
"""Drives op scripts against the live oracle the way the truth was
frozen — each scenario gets its own temp db path."""
def __init__(self, workdir: Path, n: int):
self.path = str(workdir / f"ol_{n}.db")
self.ledger = None
def step(self, op: dict):
kind = op["op"]
if kind == "new":
if not op.get("reuse"):
self.ledger = OutcomeLedger(self.path)
else:
# fresh instance over the SAME db (CREATE TABLE IF NOT
# EXISTS idempotent branch)
self.ledger = OutcomeLedger(self.path)
return {"result": None}
if kind == "record":
self.ledger.record(OutcomeRecord(**op["record"]))
return {"result": None}
if kind == "to_dict":
return {"result": OutcomeRecord(**op["record"]).to_dict()}
if kind == "get_stats":
fam = op.get("family")
if fam is None:
return {"result": self.ledger.get_stats()}
return {"result": self.ledger.get_stats(fam)}
if kind == "get_model_stats":
return {"result": self.ledger.get_model_stats(op["model"])}
raise ValueError(f"unknown op {kind}")
def s1_empty_db(workdir):
"""Empty-database branches of every stats path."""
d = Driver(workdir, 1)
ops = [
{"op": "new"},
{"op": "get_stats"},
{"op": "get_stats", "family": "greeting"},
{"op": "get_model_stats", "model": "qwen2.5:3b"},
]
return {"name": "s1_empty_db", "ops": ops, "obs": [d.step(o) for o in ops]}
def s2_record_and_stats(workdir):
"""The oracle's own test_ledger data, plus every stats entry point."""
d = Driver(workdir, 2)
ops = [
{"op": "new"},
{"op": "record", "record": {
"timestamp": T0, "task_family": "greeting", "model": "qwen2.5:3b",
"provider": "ollama", "tier": "T1", "task_success": True,
"latency_ms": 100.0}},
{"op": "record", "record": {
"timestamp": T1, "task_family": "coding", "model": "qwen3:14b",
"provider": "ollama", "tier": "T2", "task_success": True,
"attempts": 2, "latency_ms": 500.0}},
{"op": "record", "record": {
"timestamp": T2, "task_family": "coding", "model": "qwen3:14b",
"provider": "ollama", "tier": "T2", "task_success": False,
"user_correction": True, "latency_ms": 600.0}},
{"op": "get_stats", "family": "greeting"},
{"op": "get_stats", "family": "coding"},
{"op": "get_stats"},
{"op": "get_model_stats", "model": "qwen3:14b"},
{"op": "get_model_stats", "model": "unknown-model"},
]
return {"name": "s2_record_and_stats", "ops": ops, "obs": [d.step(o) for o in ops]}
def s3_full_record_to_dict(workdir):
"""OutcomeRecord.to_dict with every field set; float-heavy stats."""
d = Driver(workdir, 3)
full = {
"timestamp": T3, "task_family": "review", "model": "gpt-5-mini",
"provider": "openai", "tier": "T3", "prompt_tokens": 1200,
"output_tokens": 340, "cached_tokens": 512, "latency_ms": 123.75,
"attempts": 1, "verification_result": "pass", "user_correction": False,
"task_success": True, "estimated_cost": 0.0025,
}
ops = [
{"op": "new"},
{"op": "to_dict", "record": full},
{"op": "record", "record": full},
{"op": "record", "record": {**full, "timestamp": T4,
"estimated_cost": 0.0075,
"verification_result": "fail",
"task_success": False}},
{"op": "get_stats"},
{"op": "get_stats", "family": "review"},
{"op": "get_model_stats", "model": "gpt-5-mini"},
]
return {"name": "s3_full_record_to_dict", "ops": ops, "obs": [d.step(o) for o in ops]}
def s4_persistence_reopen(workdir):
"""A fresh OutcomeLedger over an existing db — rows survive, DDL is
idempotent."""
d = Driver(workdir, 4)
ops = [
{"op": "new"},
{"op": "record", "record": {
"timestamp": T0, "task_family": "port", "model": "qwen2.5:3b",
"provider": "ollama", "tier": "T1", "task_success": True,
"latency_ms": 42.5}},
{"op": "record", "record": {
"timestamp": T1, "task_family": "port", "model": "qwen3:14b",
"provider": "ollama", "tier": "T2", "task_success": False,
"user_correction": True, "attempts": 3, "latency_ms": 900.0}},
{"op": "new", "reuse": True},
{"op": "get_stats"},
{"op": "get_stats", "family": "port"},
{"op": "get_model_stats", "model": "qwen2.5:3b"},
]
return {"name": "s4_persistence_reopen", "ops": ops, "obs": [d.step(o) for o in ops]}
def s5_averages_and_rates(workdir):
"""AVG/rate arithmetic: fractional averages, 0%/50%/100% rates, both
correction branches."""
d = Driver(workdir, 5)
ops = [
{"op": "new"},
{"op": "record", "record": {
"timestamp": T0, "task_family": "ops", "model": "m1",
"task_success": True, "attempts": 1, "latency_ms": 10.5,
"estimated_cost": 0.0}},
{"op": "record", "record": {
"timestamp": T1, "task_family": "ops", "model": "m1",
"task_success": False, "user_correction": True, "attempts": 2,
"latency_ms": 20.25, "estimated_cost": 1.5}},
{"op": "record", "record": {
"timestamp": T2, "task_family": "ops", "model": "m2",
"task_success": True, "attempts": 3, "latency_ms": 30.125,
"user_correction": True, "estimated_cost": 2.25}},
{"op": "get_stats"},
{"op": "get_model_stats", "model": "m1"},
{"op": "get_model_stats", "model": "m2"},
{"op": "get_stats", "family": "ops"},
]
return {"name": "s5_averages_and_rates", "ops": ops, "obs": [d.step(o) for o in ops]}
def main():
out = {
"module": "outcome_ledger",
"seed": 2479,
"oracle": "src/rune/outcome_ledger.py",
"pin": "RUNE_OL_CORE=python",
"generated": "2026-10-11",
"scenarios": [],
}
with tempfile.TemporaryDirectory() as td:
workdir = Path(td)
for gen in (s1_empty_db, s2_record_and_stats, s3_full_record_to_dict,
s4_persistence_reopen, s5_averages_and_rates):
out["scenarios"].append(gen(workdir))
dest = REPO / "src" / "rust" / "outcome_ledger" / "src" / "frozen_to_truth.json"
dest.write_text(json.dumps(out, indent=1) + "\n")
n_obs = sum(len(s["obs"]) for s in out["scenarios"])
print(f"wrote {len(out['scenarios'])} scenarios / {n_obs} observations to {dest}")
if __name__ == "__main__":
main()
+567
View File
@@ -0,0 +1,567 @@
#!/usr/bin/env python3
"""Truth generator for the prompt_sync dichotomy port (#2479 tranche).
Runs the LIVE Python oracle (src/rune/prompt_sync.py) in a controlled
sandbox (RUNE_HOME/RUNE_MEMORY_DIR/RUNE_STORE_DB/HOME) across every public
function and every branch, and freezes inputs + expected outputs to
frozen_ps_truth.json (consumed VERBATIM by the Rust battery).
POISONED-TRUTH LAW: RUNE_PS_CORE=python is pinned at the top — a re-run
after facade delegation must record oracle output, never kernel output.
Kernel input discipline: the Rust kernel is PURE — it receives rows /
file contents / voice body / static strings as JSON and returns strings
and dicts. All I/O (sqlite, rune.md, defining doc, voice_sync chain)
stays in the Python facade. Every case records the kernel-shaped input.
"""
import os
os.environ["RUNE_PS_CORE"] = "python" # poisoned-truth law: pin the oracle
import json
import random
import sqlite3
import sys
import tempfile
from pathlib import Path
REPO = Path(__file__).resolve().parents[1]
SANDBOX = Path(tempfile.mkdtemp(prefix="ps_truth_"))
(SANDBOX / "memory" / "store").mkdir(parents=True)
(SANDBOX / ".mimocode" / "agents").mkdir(parents=True)
os.environ["RUNE_HOME"] = str(SANDBOX)
os.environ["RUNE_MEMORY_DIR"] = str(SANDBOX / "memory")
os.environ["RUNE_STORE_DB"] = str(SANDBOX / "memory" / "store" / "memory.db")
os.environ["HOME"] = str(SANDBOX)
sys.path.insert(0, str(REPO / "src"))
sys.path.insert(0, str(REPO / "src" / "rune"))
import prompt_sync # noqa: E402
import voice_sync # noqa: E402
STORE = os.environ["RUNE_STORE_DB"]
RUNE_MD = prompt_sync.RUNE_MD
DEFINING = str(SANDBOX / "memory" / "rune_defining_document.md")
SCHEMA = """
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
);
CREATE TABLE IF NOT EXISTS chunks (
chunk_id TEXT PRIMARY KEY,
doc_id TEXT NOT NULL,
text TEXT NOT NULL,
content_hash TEXT NOT NULL,
importance REAL DEFAULT 0.5,
ingested_at TEXT NOT NULL
);
"""
cases = []
def add(name, fn, payload, expected):
cases.append({"name": name, "fn": fn, "input": payload, "expected": expected})
def fresh_db(entries=(), chunks=()):
Path(STORE).unlink(missing_ok=True)
db = sqlite3.connect(STORE)
db.executescript(SCHEMA)
for e in entries:
db.execute(
"INSERT INTO entries (text, importance, confidence, created_at, topic, status, always_on)"
" VALUES (?,?,?,?,?,?,?)",
(
e["text"], e.get("importance", 0.5), e.get("confidence", 0.7),
"2026-10-11T00:00:00Z", e["topic"], e.get("status", "active"),
e.get("always_on", 1),
),
)
for c in chunks:
db.execute(
"INSERT INTO chunks (chunk_id, doc_id, text, content_hash, importance, ingested_at)"
" VALUES (?,?,?,?,?,?)",
(c["chunk_id"], c["doc_id"], c["text"], "h", 0.5, "2026-10-11T00:00:00Z"),
)
db.commit()
db.close()
def fresh_prompt(content=None):
if content is None:
Path(RUNE_MD).unlink(missing_ok=True)
else:
Path(RUNE_MD).write_text(content)
def fresh_defining(content=None):
p = Path(DEFINING)
if content is None:
p.unlink(missing_ok=True)
else:
p.write_text(content)
def rows_by_topic(topics):
db = sqlite3.connect(STORE)
db.row_factory = sqlite3.Row
out = {}
for t in topics:
out[t] = [dict(r) for r in prompt_sync.get_entries_by_topic(db, t)]
db.close()
return out
def voice_body_from_db():
"""The voice chain exactly as build_prompt runs it (stays facade-side
in the port — voice_sync is a separate module)."""
db = sqlite3.connect(STORE)
db.row_factory = sqlite3.Row
register = voice_sync.get_delivery_register_from_db(db)
db.close()
if not register:
return None
parsed = voice_sync.extract_voice_section(register)
return voice_sync.generate_voice_section(parsed)
def kernel_build_input(entries=(), chunks=(), defining=None, prompt=None):
"""Record the kernel-shaped input for a build/sync/check/diff case."""
fresh_db(list(entries), list(chunks))
fresh_defining(defining)
fresh_prompt(prompt)
rt = rows_by_topic(["constitution", "identity", "operating", "human"])
vb = voice_body_from_db()
dc = None if defining is None else Path(DEFINING).read_text()
cur = None if prompt is None else Path(RUNE_MD).read_text()
return {
"constitution_rows": rt["constitution"],
"identity_rows": rt["identity"],
"operating_rows": rt["operating"],
"human_rows": rt["human"],
"voice_body": vb,
"defining_content": dc,
"current": cur,
}
# ── entry fixtures ───────────────────────────────────────────────────────
def E(topic, text, importance=0.5, confidence=0.7, **kw):
d = {"topic": topic, "text": text, "importance": importance, "confidence": confidence}
d.update(kw)
return d
CONSTITUTION_SET = [
("Never accept placeholder phase output as execution evidence.", 0.95),
(" (_source_: some citation here) Store is source of truth.", 0.9),
("Consent approves the finalized canaries [source: pipeline doc].", 0.85),
("short", 0.8), # len<=15 → filtered
("", 0.75), # empty → filtered
("Truth first. Pressure point first.", 0.7),
]
OPERATING_SET = [
(" (_source_: x) One clean sentence beats five padded ones.", 0.9),
("No praise reflex [source: register].", 0.85),
("tiny", 0.8),
("Useful beats impressive. Progress beats performance.", 0.75),
] + [(f"Operating rule number {i} for fill.", 0.5 - i * 0.01) for i in range(18)]
IDENTITY_SET = [
("My name is Rune — built, not borrowed.", 0.9),
("", 0.8), # empty text skipped
("The system is architecture, not weather.", 0.7),
]
HUMAN_SET = [
("Nick prefers direct delivery with teeth.", 0.9),
("", 0.5),
("Arch PC: modify freely. Pi: check first.", 0.8),
]
# ── get_entries_by_topic (kernel + sqlite read) ──────────────────────────
FIXTURE_ALL = (
[E("constitution", t, i) for t, i in CONSTITUTION_SET]
+ [E("identity", t, i) for t, i in IDENTITY_SET]
+ [E("operating", t, i) for t, i in OPERATING_SET]
+ [E("human", t, i) for t, i in HUMAN_SET]
+ [E("constitution", "inactive but matching topic", 0.99, status="retired"),
E("constitution", "always_on=0 excluded", 0.99, always_on=0),
E("other", "different topic", 0.99)]
)
fresh_db(FIXTURE_ALL)
db = sqlite3.connect(STORE)
db.row_factory = sqlite3.Row
for topic in ("constitution", "identity", "operating", "human", "missing-topic"):
add(f"get_entries_by_topic:{topic}", "get_entries_by_topic",
{"fixture": FIXTURE_ALL, "topic": topic},
[dict(r) for r in prompt_sync.get_entries_by_topic(db, topic)])
db.close()
# importance tie → stable order (insertion order preserved)
FIXTURE_TIE = [E("tie", "first inserted", 0.5), E("tie", "second inserted", 0.5),
E("tie", "third inserted", 0.9)]
fresh_db(FIXTURE_TIE)
db = sqlite3.connect(STORE)
db.row_factory = sqlite3.Row
add("get_entries_by_topic:tie-stable", "get_entries_by_topic",
{"fixture": FIXTURE_TIE, "topic": "tie"},
[dict(r) for r in prompt_sync.get_entries_by_topic(db, "tie")])
db.close()
# NULL confidence passes through as null
FIXTURE_NULL = [{"topic": "nullconf", "text": "some entry", "importance": 0.4, "confidence": None}]
fresh_db(FIXTURE_NULL)
db = sqlite3.connect(STORE)
db.row_factory = sqlite3.Row
add("get_entries_by_topic:null-confidence", "get_entries_by_topic",
{"fixture": FIXTURE_NULL, "topic": "nullconf"},
[dict(r) for r in prompt_sync.get_entries_by_topic(db, "nullconf")])
db.close()
# ── section builders (branch cases) ──────────────────────────────────────
# The oracle section fns call get_entries_by_topic(db, topic) internally —
# capture through a proxy db that serves canned rows per topic.
class ProxyDB:
def __init__(self, table):
self.table = table
def execute(self, sql, params):
return _Rows(self.table.get(params[0], []))
class _Rows(list):
def fetchall(self):
return [dict(r) for r in self]
fresh_db([E("constitution", t, i) for t, i in CONSTITUTION_SET])
tbl = rows_by_topic(["constitution"])
add("get_constitution:full", "get_constitution", {"entries": tbl["constitution"]},
prompt_sync.get_constitution(ProxyDB(tbl)))
add("get_constitution:empty", "get_constitution", {"entries": []}, None)
add("get_identity:empty", "get_identity", {"entries": []}, None)
add("get_operating:empty", "get_operating", {"entries": []}, None)
add("get_human:empty", "get_human", {"entries": []}, None)
fresh_db([E("identity", t, i) for t, i in IDENTITY_SET])
tbl = rows_by_topic(["identity"])
add("get_identity:full", "get_identity", {"entries": tbl["identity"]},
prompt_sync.get_identity(ProxyDB(tbl)))
fresh_db([E("operating", t, i) for t, i in OPERATING_SET])
tbl = rows_by_topic(["operating"])
add("get_operating:full", "get_operating", {"entries": tbl["operating"]},
prompt_sync.get_operating(ProxyDB(tbl)))
fresh_db([E("human", t, i) for t, i in HUMAN_SET])
tbl = rows_by_topic(["human"])
add("get_human:full", "get_human", {"entries": tbl["human"]},
prompt_sync.get_human(ProxyDB(tbl)))
many = [(f"Constitution entry number {i} with enough length.", 1.0 - i * 0.01) for i in range(30)]
fresh_db([E("constitution", t, i) for t, i in many])
tbl = rows_by_topic(["constitution"])
add("get_constitution:top25", "get_constitution", {"entries": tbl["constitution"]},
prompt_sync.get_constitution(ProxyDB(tbl)))
# ── get_defining_principles (extraction is kernel; read is facade) ───────
fresh_defining(None)
add("get_defining_principles:no-file", "get_defining_principles",
{"content": None}, prompt_sync.get_defining_principles())
DOC = """# Defining Document
## THE VISION
A stateful assistant that remembers and grows.
The store is the source of truth.
### Subsection stays out (starts with #)
This line is under a ### — excluded by the # guard.
## PREAMBLE
Not a key section — excluded even though lines follow.
## THE TRUST FRAMEWORK
Consent before mutation. Never guessed referents.
## CURIOSITY
Ask what the problem actually is.
## CURIOSITY LIMITS
Curiosity never overrides consent.
## WHAT SENTIENCE MEANS
Synthetic does not mean simulated.
## WHATEVER
Tail section, excluded.
"""
fresh_defining(DOC)
add("get_defining_principles:key-sections", "get_defining_principles",
{"content": DOC}, prompt_sync.get_defining_principles())
DOC_WS = "## CURIOSITY\nline one\n \nline two\n"
fresh_defining(DOC_WS)
add("get_defining_principles:whitespace-line", "get_defining_principles",
{"content": DOC_WS}, prompt_sync.get_defining_principles())
DOC_MANY = "## THE VISION\n" + "\n".join(f"principle line {i}" for i in range(30))
fresh_defining(DOC_MANY)
add("get_defining_principles:top20", "get_defining_principles",
{"content": DOC_MANY}, prompt_sync.get_defining_principles())
fresh_defining("## NOTHING RELEVANT\nsome text\n")
add("get_defining_principles:no-key-sections", "get_defining_principles",
{"content": "## NOTHING RELEVANT\nsome text\n"},
prompt_sync.get_defining_principles())
fresh_defining("")
add("get_defining_principles:empty-file", "get_defining_principles",
{"content": ""}, prompt_sync.get_defining_principles())
# ── build_prompt ─────────────────────────────────────────────────────────
VOICE_REGISTER = (
"THE RUNE VOICE — DELIVERY REGISTER (canonical)\n"
"\n"
"- **Thompson (moral economy):** legitimacy flows from lived fairness, not rules from above.\n"
"- **Graeber:** bureaucracy is a technology of mutual mystification; name it.\n"
"\n"
"TECHNICAL DELIVERY\n"
"- Precision is a form of care; tables and code get the same voice discipline.\n"
"- No hedging stack in status reports.\n"
"\n"
"CALIBRATION\n"
"- Push back harder when the stakes are higher.\n"
"- One rhetorical question per engagement, not three.\n"
"- Zero ornamental structure.\n"
"- Fourth calibration note beyond the top-three cut.\n"
"\n"
"HOW IT BLENDS\n"
"- The substance leads; the voice shapes HOW.\n"
"- Direct, warm, with teeth.\n"
"\n"
"WHEN THE REGISTER SLIPS\n"
"- Recalibrate against this register, not against habit.\n"
)
assert len(VOICE_REGISTER) > 200 and "DELIVERY REGISTER" in VOICE_REGISTER and "RUNE" in VOICE_REGISTER and "VOICE" in VOICE_REGISTER
ALL_ENTRIES = (
[E("constitution", t, i) for t, i in CONSTITUTION_SET if len(t.strip()) > 15]
+ [E("identity", t, i) for t, i in IDENTITY_SET if t.strip()]
+ [E("operating", t, i) for t, i in OPERATING_SET if len(t.strip()) > 15]
+ [E("human", t, i) for t, i in HUMAN_SET if t.strip()]
)
VOICE_CHUNKS = [{"chunk_id": "c1", "doc_id": "d1", "text": VOICE_REGISTER}]
def build_case(name, entries=(), chunks=(), defining=None):
kinput = kernel_build_input(entries, chunks, defining)
fresh_db(list(entries), list(chunks))
fresh_defining(defining)
fresh_prompt(None)
db = sqlite3.connect(STORE)
db.row_factory = sqlite3.Row
out = prompt_sync.build_prompt(db)
db.close()
add(name, "build_prompt", kinput, out)
build_case("build_prompt:empty-store")
build_case("build_prompt:full", ALL_ENTRIES, VOICE_CHUNKS, DOC)
build_case("build_prompt:constitution-only", [E("constitution", "Never accept placeholder phase output as execution evidence.", 0.95)])
build_case("build_prompt:voice-only", [], VOICE_CHUNKS)
build_case("build_prompt:voice-noise", [], [{"chunk_id": "c2", "doc_id": "d1", "text": "DELIVERY REGISTER mention but too short"}])
# ── sync_prompt (facade I/O; recorded for live parity) ───────────────────
def sync_case(name, entries=(), chunks=(), defining=None, prompt=None):
kinput = kernel_build_input(entries, chunks, defining, prompt)
fresh_db(list(entries), list(chunks))
fresh_defining(defining)
fresh_prompt(prompt)
out = prompt_sync.sync_prompt()
if isinstance(out, dict) and "timestamp" in out:
out = dict(out, timestamp="TIMESTAMP")
add(name, "sync_prompt", kinput, out)
sync_case("sync_prompt:creates-file", ALL_ENTRIES, VOICE_CHUNKS, DOC, None)
sync_case("sync_prompt:overwrites", ALL_ENTRIES, VOICE_CHUNKS, DOC, "old content\n")
# ── check_sync ───────────────────────────────────────────────────────────
def check_case(name, entries=(), chunks=(), defining=None, prompt=None):
kinput = kernel_build_input(entries, chunks, defining, prompt)
fresh_db(list(entries), list(chunks))
fresh_defining(defining)
fresh_prompt(prompt)
db = sqlite3.connect(STORE)
db.row_factory = sqlite3.Row
kinput["new_prompt"] = prompt_sync.build_prompt(db)
db.close()
add(name, "check_sync", kinput, prompt_sync.check_sync())
check_case("check_sync:no-file", ALL_ENTRIES, VOICE_CHUNKS, DOC, None)
fresh_db(ALL_ENTRIES, VOICE_CHUNKS)
fresh_defining(DOC)
db = sqlite3.connect(STORE)
db.row_factory = sqlite3.Row
synced_prompt = prompt_sync.build_prompt(db)
db.close()
check_case("check_sync:in-sync", ALL_ENTRIES, VOICE_CHUNKS, DOC, synced_prompt)
check_case("check_sync:out-of-sync", ALL_ENTRIES, VOICE_CHUNKS, DOC, "stale prompt\n")
check_case("check_sync:strip-equal", ALL_ENTRIES, VOICE_CHUNKS, DOC, "\n\n" + synced_prompt + "\n\n ")
# ── diff_prompt ──────────────────────────────────────────────────────────
def diff_case(name, entries=(), chunks=(), defining=None, prompt=None):
kinput = kernel_build_input(entries, chunks, defining, prompt)
fresh_db(list(entries), list(chunks))
fresh_defining(defining)
fresh_prompt(prompt)
db = sqlite3.connect(STORE)
db.row_factory = sqlite3.Row
kinput["new_prompt"] = prompt_sync.build_prompt(db)
db.close()
add(name, "diff_prompt", kinput, prompt_sync.diff_prompt())
diff_case("diff_prompt:no-file", ALL_ENTRIES, VOICE_CHUNKS, DOC, None)
diff_case("diff_prompt:no-differences", ALL_ENTRIES, VOICE_CHUNKS, DOC, synced_prompt)
diff_case("diff_prompt:real-diff", ALL_ENTRIES, VOICE_CHUNKS, DOC, synced_prompt)
mutated = list(synced_prompt.split("\n"))
for k in range(12):
op = ["ins", "del", "mod"][k % 3]
i = (k * 37 + 11) % len(mutated)
if op == "ins":
mutated.insert(i, f"- **inserted calibration line {k}**")
elif op == "del" and len(mutated) > 5:
del mutated[i]
else:
mutated[i] = mutated[i] + " (edited)"
diff_case("diff_prompt:multi-hunk", ALL_ENTRIES, VOICE_CHUNKS, DOC, "\n".join(mutated))
diff_case("diff_prompt:no-trailing-newline", ALL_ENTRIES, VOICE_CHUNKS, DOC, synced_prompt.rstrip("\n"))
diff_case("diff_prompt:carriage-returns", ALL_ENTRIES, VOICE_CHUNKS, DOC, synced_prompt.replace("\n", "\r\n"))
diff_case("diff_prompt:vertical-tab", ALL_ENTRIES, VOICE_CHUNKS, DOC, synced_prompt.replace("\n\n", "\n\x0b\n", 1))
# ── handle_hook ──────────────────────────────────────────────────────────
def hook_case(name, entries=(), chunks=(), defining=None, prompt=None, event="on_session_end"):
# capture the sync_result the hook WOULD produce (identical fresh state),
# then restore the pre-hook file state so handle_hook sees the same input.
sync_result = None
check_synced = None
if event == "on_session_end":
fresh_db(list(entries), list(chunks))
fresh_defining(defining)
fresh_prompt(prompt)
check_synced = prompt_sync.check_sync()["synced"]
if not check_synced:
out = prompt_sync.sync_prompt()
if "timestamp" in out:
out = dict(out, timestamp="TIMESTAMP")
sync_result = out
fresh_db(list(entries), list(chunks))
fresh_defining(defining)
fresh_prompt(prompt)
out = prompt_sync.handle_hook(event, {})
if isinstance(out, dict) and isinstance(out.get("details"), dict) and "timestamp" in out["details"]:
out = dict(out, details=dict(out["details"], timestamp="TIMESTAMP"))
add(name, "handle_hook",
{"event": event, "check_synced": check_synced, "sync_result": sync_result},
out)
hook_case("handle_hook:session-end-unsynced", ALL_ENTRIES, VOICE_CHUNKS, DOC, None)
hook_case("handle_hook:session-end-insync", ALL_ENTRIES, VOICE_CHUNKS, DOC, synced_prompt)
hook_case("handle_hook:other-event", ALL_ENTRIES, VOICE_CHUNKS, DOC, None, event="on_session_start")
# ── fuzz: random stores through build_prompt + diff (parity-hardening) ───
rng = random.Random(2479)
WORDS = ["voice", "constitution", "build", "rule", "nick", "direct", "warm",
"code", "test", "rune", "store", "prompt", "pipeline", "section",
"the", "a", "of", "and", " (_source_: cite) ", " [source: doc] ", "x"]
for f in range(60):
entries = []
for topic in ("constitution", "identity", "operating", "human"):
for _ in range(rng.randint(0, 12)):
body = "".join(rng.choice(WORDS) for _ in range(rng.randint(1, 25)))
entries.append(E(topic, body, rng.choice([0.1, 0.5, 0.5, 0.9, 0.9, 1.0]),
rng.choice([0.3, 0.7, None]) if rng.random() < 0.2 else 0.7))
defining = None
if rng.random() < 0.6:
defining = "".join(
(f"## {rng.choice(['THE VISION', 'THE TRUST FRAMEWORK', 'CURIOSITY', 'SKIP ME'])}\n"
if rng.random() < 0.25 else rng.choice(["", " ", f"line {rng.randint(0, 99)}\n", "### deep\n"]))
for _ in range(rng.randint(0, 40)))
kinput = kernel_build_input(entries, [], defining)
fresh_db(entries, [])
fresh_defining(defining)
fresh_prompt(None)
db = sqlite3.connect(STORE)
db.row_factory = sqlite3.Row
built = prompt_sync.build_prompt(db)
db.close()
add(f"fuzz-build:{f}", "build_prompt", kinput, built)
if f % 3 == 0:
mlines = built.split("\n")
for _ in range(rng.randint(1, 20)):
op = rng.choice(["ins", "del", "mod"])
i = rng.randrange(len(mlines)) if mlines else 0
if op == "ins":
mlines.insert(i, rng.choice(["", "- inserted **thing**", "## Inserted Section"]))
elif op == "del" and len(mlines) > 3:
del mlines[i]
else:
mlines[i] = mlines[i][::-1] if mlines[i] else "reversed-empty"
current = "\n".join(mlines)
fresh_prompt(current)
kinput2 = dict(kinput, current=current, new_prompt=built)
add(f"fuzz-diff:{f}", "diff_prompt", kinput2, prompt_sync.diff_prompt())
# ── write ────────────────────────────────────────────────────────────────
doc = {
"frozen": "2026-10-11",
"oracle": "src/rune/prompt_sync.py",
"seed": 2479,
"static": {
"STATIC_HEADER": prompt_sync.STATIC_HEADER,
"STATIC_FOOTER": prompt_sync.STATIC_FOOTER,
"PROMPT_SECTION": prompt_sync.build_pipeline_policy.PROMPT_SECTION,
},
"cases": cases,
}
dest = REPO / "src" / "rust" / "prompt_sync" / "src" / "frozen_ps_truth.json"
dest.write_text(json.dumps(doc, indent=1, sort_keys=True) + "\n")
print(f"frozen {len(cases)} cases → {dest}")
print("sandbox:", SANDBOX)
+522
View File
@@ -0,0 +1,522 @@
#!/usr/bin/env python3
"""gen_self_modify_truth.py — freeze/replay parity truth for self_modify
(dichotomy wave, #2479 tranche).
Freeze mode (default): exercises EVERY public function of the LIVE Python
oracle (src/rune/self_modify.py) across every branch and records
(kernel_input, expected_kernel_output) pairs into
src/rust/self_modify/src/frozen_smod_truth.json.
Replay mode (--replay): re-runs the SAME scenarios with the Rust core
delegation active and asserts both kernel-level (loader direct) and
facade-level (oracle functions delegate) parity against the frozen file.
Poisoned-truth law: freeze pins RUNE_SMOD_CORE=python BEFORE any import
(the RUNE_SM_CORE pin already belongs to shard_manager's core on master).
No-clock law: the wall clock is frozen by patching the `time` symbol in
self_modify, so every propose record carries the frozen `at` stamp and
replay is deterministic regardless of run date. The oracle's internal
tempfile.mkdtemp is likewise pinned to a case-local path.
Deterministic-fixture law: git repos are built with fixed identity and
fixed author/committer dates, so the seed commit SHA is identical across
generator runs, replay runs and the Rust battery — the SHA is frozen
LITERALLY, no placeholder. Machine-specific paths (repo dir, worktree dir,
stub dir) are frozen as @REPO@ / @WORKTREE@ / @STUBDIR@ placeholders and
substituted at replay time. Git-dependent scenarios declare their setup as
data ("worktree:<branch>", "commit_in_worktree", "dirty_repo", plus the
"stub" flag for the PATH-stubbed default gate command) so the generator,
the Python replay and the Rust battery run the SAME recipe.
Raise-parity rows freeze {"raises": "<oracle exception>"}: the Python
facade must raise the same exception; the Rust core (envelope law) must
surface an error (the loader raises RuntimeError — documented divergence
in exception TYPE only).
"""
import copy
import json
import os
import stat
import subprocess
import sys
import tempfile
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", "self_modify", "src", "frozen_smod_truth.json"
)
REPLAY = "--replay" in sys.argv
os.environ["RUNE_SMOD_CORE"] = "python" # poisoned-truth law (freeze mode)
os.environ["RUNE_MEMORY_CORE"] = "python"
sys.path.insert(0, os.path.join(REPO, "src"))
sys.path.insert(0, os.path.join(REPO, "src", "rune"))
import self_modify as oracle_mod # noqa: E402
if REPLAY:
del os.environ["RUNE_SMOD_CORE"] # facade must delegate to the Rust core
import smod_loader # noqa: E402 (kernel-level replay side)
FROZEN_AT = "2026-10-11T12:00:00Z"
FROZEN_EPOCH = int(datetime(2026, 10, 11, 12, 0, 0, tzinfo=timezone.utc).timestamp())
GIT_ENV_FIXED = {
"GIT_AUTHOR_DATE": "2026-10-11T12:00:00+00:00",
"GIT_COMMITTER_DATE": "2026-10-11T12:00:00+00:00",
}
MISSING_REPO = "/nonexistent-selfmod-truth/repo"
class FrozenTime:
"""time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()) → FROZEN_AT."""
@staticmethod
def gmtime():
return time.gmtime(FROZEN_EPOCH)
@staticmethod
def strftime(fmt, t=None):
return time.strftime(fmt, t if t is not None else FrozenTime.gmtime())
oracle_mod.time = FrozenTime
class FrozenTempfile:
"""tempfile.mkdtemp(prefix="selfmod-") → a case-local, known path."""
ROOT = None
N = 0
@staticmethod
def mkdtemp(prefix=""):
FrozenTempfile.N += 1
p = os.path.join(FrozenTempfile.ROOT, f"{prefix}{FrozenTempfile.N:02d}")
os.makedirs(p)
return p
oracle_mod.tempfile = FrozenTempfile
# ── deterministic fixture + setup interpreter (shared recipe) ───────────────
def _git(root, *argv):
env = dict(os.environ, **GIT_ENV_FIXED)
return subprocess.run(["git", "-C", root, *argv], capture_output=True,
text=True, env=env)
def make_fixture(case_root):
"""The battery recipe — fixed identity, fixed dates, seed commit.
The resulting master SHA is machine-independent (frozen literally)."""
repo = os.path.join(case_root, "repo")
os.makedirs(repo)
_git(repo, "init", "-q", "-b", "master")
_git(repo, "config", "user.email", "t@t")
_git(repo, "config", "user.name", "t")
_git(repo, "config", "commit.gpgsign", "false")
with open(os.path.join(repo, "f.txt"), "w") as f:
f.write("v1\n")
_git(repo, "add", ".")
_git(repo, "commit", "-qm", "seed")
return repo
def base_sha(repo):
return _git(repo, "rev-parse", "master").stdout.strip()
def run_setup(repo, steps):
"""Declarative setup ops, executed identically by freeze, replay and
the Rust battery. Returns the worktree path (or None)."""
wt = None
for step in steps:
op, _, arg = step.partition(":")
if op == "worktree":
wt = os.path.join(os.path.dirname(repo), f"wt-{arg}")
r = _git(repo, "worktree", "add", wt, "-b", arg, "master")
assert r.returncode == 0, r.stderr
elif op == "commit_in_worktree":
with open(os.path.join(wt, "f.txt"), "w") as f:
f.write("v2\n")
r = _git(wt, "commit", "-qam", "c")
assert r.returncode == 0, r.stderr
elif op == "dirty_repo":
with open(os.path.join(repo, "f.txt"), "w") as f:
f.write("v9\n")
else:
raise ValueError(f"unknown setup op: {step}")
return wt
def make_stub_dir(case_root):
"""PATH stub: a python3 that deterministically 'runs the gate suite'
(the oracle's default gate command resolves through PATH)."""
stub = os.path.join(case_root, "stub")
os.makedirs(stub)
path = os.path.join(stub, "python3")
with open(path, "w") as f:
f.write("#!/bin/sh\necho stub-gates-green\nexit 0\n")
os.chmod(path, os.stat(path).st_mode | stat.S_IXUSR)
return stub
# ── scenario cases ───────────────────────────────────────────────────────────
class Case:
def __init__(self, name, group, steps, inp, stub=False):
self.name = name
self.group = group
self.steps = steps
self.inp = inp # (repo, wt, stub) → kernel input dict
self.stub = stub
def propose_cases():
return [
Case("propose-ok", "propose_change", [],
lambda r, w, s: {"repo": "@REPO@", "branch": "cand-ok",
"base": "master", "now": FROZEN_AT}),
Case("propose-base-head", "propose_change", [],
lambda r, w, s: {"repo": "@REPO@", "branch": "cand-head",
"base": "HEAD", "now": FROZEN_AT}),
Case("propose-unknown-base", "propose_change", [],
lambda r, w, s: {"repo": "@REPO@", "branch": "cand-bad",
"base": "no-such-ref", "now": FROZEN_AT}),
Case("propose-dup-branch", "propose_change", ["worktree:dup"],
lambda r, w, s: {"repo": "@REPO@", "branch": "dup",
"base": "master", "now": FROZEN_AT}),
Case("propose-missing-repo", "propose_change", [],
lambda r, w, s: {"repo": MISSING_REPO, "branch": "cand-gone",
"base": "master", "now": FROZEN_AT}),
]
def gates_cases():
return [
Case("gates-pass", "run_gates", [],
lambda r, w, s: {"worktree": "@REPO@",
"test_cmd": ["echo", "gates-green"]}),
Case("gates-fail-stderr", "run_gates", [],
lambda r, w, s: {"worktree": "@REPO@",
"test_cmd": ["sh", "-c", "echo boom 1>&2; exit 1"]}),
Case("gates-tail-2000", "run_gates", [],
lambda r, w, s: {"worktree": "@REPO@",
"test_cmd": ["sh", "-c", "seq 1 3000"]}),
Case("gates-out-then-err", "run_gates", [],
lambda r, w, s: {"worktree": "@REPO@",
"test_cmd": ["sh", "-c",
"echo o1; echo e1 1>&2; echo o2; exit 7"]}),
Case("gates-default-cmd", "run_gates", [],
lambda r, w, s: {"worktree": "@REPO@", "test_cmd": None},
stub=True),
]
def compare_cases():
def row(name, cand, base):
return Case("cb-" + name, "compare_baseline", [],
lambda r, w, s, c=cand, b=base:
{"candidate_result": c, "baseline_result": b})
return [
row("candidate-red", {"passed": False}, {"passed": True}),
row("candidate-red-baseline-red", {"passed": False}, {"passed": False}),
row("baseline-red", {"passed": True}, {"passed": False}),
row("both-green", {"passed": True}, {"passed": True}),
row("missing-passed-key", {}, {"passed": True}),
row("truthy-coercion", {"passed": 1}, {"passed": 0}),
row("explicit-null-passed", {"passed": None}, {"passed": True}),
]
def _proposal(branch, repo):
return {"proposed": True, "branch": branch, "worktree": "@WORKTREE@",
"base": "master", "rollback_ref": base_sha(repo), "at": FROZEN_AT}
def pod_cases():
return [
Case("pod-discard", "promote_or_discard", ["worktree:discard-1"],
lambda r, w, s: {"repo": "@REPO@", "proposal": _proposal("discard-1", r),
"verdict": {"promote": False, "reason": "test"}}),
Case("pod-promote-up-to-date", "promote_or_discard", ["worktree:podd-2"],
lambda r, w, s: {"repo": "@REPO@", "proposal": _proposal("podd-2", r),
"verdict": {"promote": True, "reason": "test"}}),
Case("pod-promote-real-merge", "promote_or_discard",
["worktree:podd-3", "commit_in_worktree"],
lambda r, w, s: {"repo": "@REPO@", "proposal": _proposal("podd-3", r),
"verdict": {"promote": True, "reason": "test"}}),
Case("pod-merge-fail", "promote_or_discard",
["worktree:podd-4", "commit_in_worktree", "dirty_repo"],
lambda r, w, s: {"repo": "@REPO@", "proposal": _proposal("podd-4", r),
"verdict": {"promote": True, "reason": "test"}}),
Case("pod-discard-ghost", "promote_or_discard", [],
lambda r, w, s: {"repo": "@REPO@", "proposal": _proposal("ghost-branch", r),
"verdict": {"promote": False, "reason": "test"}}),
Case("pod-missing-branch-key", "promote_or_discard", [],
lambda r, w, s: {"repo": "@REPO@",
"proposal": {k: v for k, v in _proposal("keyless", r).items()
if k != "branch"},
"verdict": {"promote": True}}),
]
def rollback_cases():
return [
Case("rb-ok-after-dirty", "rollback", ["dirty_repo"],
lambda r, w, s: {"repo": "@REPO@", "rollback_ref": base_sha(r)}),
Case("rb-ref-master", "rollback", ["dirty_repo"],
lambda r, w, s: {"repo": "@REPO@", "rollback_ref": "master"}),
Case("rb-bad-ref", "rollback", [],
lambda r, w, s: {"repo": "@REPO@", "rollback_ref": "no-such-ref"}),
Case("rb-missing-repo", "rollback", [],
lambda r, w, s: {"repo": MISSING_REPO, "rollback_ref": "master"}),
]
def monitor_cases():
def row(name, proposal):
return Case("mon-" + name, "monitor", [],
lambda r, w, s, p=proposal: {"proposal": p})
return [
row("full", {"branch": "b", "rollback_ref": "abc123"}),
row("empty", {}),
row("partial", {"branch": "only-b"}),
row("extra-keys", {"branch": "b", "rollback_ref": "r", "extra": 1}),
row("nonstring-branch", {"branch": 7, "rollback_ref": None}),
]
GROUPS = [
("propose_change", propose_cases()),
("run_gates", gates_cases()),
("compare_baseline", compare_cases()),
("promote_or_discard", pod_cases()),
("rollback", rollback_cases()),
("monitor", monitor_cases()),
]
FACADE_CALL = {
"propose_change": lambda i: oracle_mod.propose_change(
i["repo"], i["branch"], i["base"]),
"run_gates": lambda i: oracle_mod.run_gates(i["worktree"], i["test_cmd"]),
"compare_baseline": lambda i: oracle_mod.compare_baseline(
i["candidate_result"], i["baseline_result"]),
"promote_or_discard": lambda i: oracle_mod.promote_or_discard(
i["repo"], i["proposal"], i["verdict"]),
"rollback": lambda i: oracle_mod.rollback(i["repo"], i["rollback_ref"]),
"monitor": lambda i: oracle_mod.monitor(i["proposal"]),
}
KERNEL_CALL = {
"propose_change": lambda i: smod_loader.propose_change(
i["repo"], i["branch"], i["base"], i["now"]),
"run_gates": lambda i: smod_loader.run_gates(i["worktree"], i["test_cmd"]),
"compare_baseline": lambda i: smod_loader.compare_baseline(
i["candidate_result"], i["baseline_result"]),
"promote_or_discard": lambda i: smod_loader.promote_or_discard(
i["repo"], i["proposal"], i["verdict"]),
"rollback": lambda i: smod_loader.rollback(i["repo"], i["rollback_ref"]),
"monitor": lambda i: smod_loader.monitor(i["proposal"]),
}
# ── placeholder plumbing ─────────────────────────────────────────────────────
def substitute(value, repo, wt, stub):
"""Machine paths → frozen placeholders (kernel input side)."""
s = json.dumps(value)
for actual, ph in ((repo, "@REPO@"), (wt, "@WORKTREE@"), (stub, "@STUBDIR@")):
if actual:
s = s.replace(actual, ph)
return json.loads(s)
def de_substitute(value, repo, wt, stub):
"""Frozen placeholders → live paths (call side)."""
s = json.dumps(value)
for actual, ph in ((repo, "@REPO@"), (wt, "@WORKTREE@"), (stub, "@STUBDIR@")):
if actual:
s = s.replace(ph, actual)
return json.loads(s)
def normalize(out, repo, wt, stub):
"""Actual output paths → placeholders (kernel output side). The
worktree a propose call created internally is captured from the
output itself — neither side can know it in advance."""
reps = [t for t in ((wt, "@WORKTREE@") if wt else None,
(repo, "@REPO@") if repo else None,
(stub, "@STUBDIR@") if stub else None) if t]
if isinstance(out, dict) and isinstance(out.get("worktree"), str):
reps.append((out["worktree"], "@WORKTREE@"))
s = json.dumps(out, sort_keys=True)
for actual, ph in reps:
if actual and actual != "/":
s = s.replace(actual, ph)
return json.loads(s)
def _catch(fn, *a):
try:
return fn(*a)
except FileNotFoundError:
return {"__raised__": "FileNotFoundError"}
except KeyError as e:
return {"__raised__": f"KeyError: {e}"}
except RuntimeError:
return {"__raised__": "RuntimeError"}
class _PathEnv:
"""Set PATH to the stub dir for the duration of one call."""
def __init__(self, stub):
self.stub = stub
def __enter__(self):
self.old = os.environ.get("PATH")
os.environ["PATH"] = self.stub + os.pathsep + (self.old or "")
def __exit__(self, *exc):
if self.old is not None:
os.environ["PATH"] = self.old
return False
def run_scenario(case, tmp_root, call):
"""One scenario, identical for freeze and both replay tiers:
fresh fixture → setup → substituted input → call → normalized output."""
case_root = tempfile.mkdtemp(prefix=f"smod_{case.name}_", dir=tmp_root)
repo = make_fixture(case_root)
FrozenTempfile.ROOT = case_root
FrozenTempfile.N = 0
wt = run_setup(repo, case.steps)
stub = make_stub_dir(case_root) if case.stub else None
live_wt = wt or os.path.join(case_root, "wt-input")
live_stub = stub or os.path.join(case_root, "stub-unused")
inp = substitute(case.inp(repo, wt, stub), repo, wt, stub)
inp_copy = copy.deepcopy(inp) # deep-copy law: inputs frozen before calls
live = de_substitute(inp_copy, repo, live_wt, live_stub)
if case.stub:
with _PathEnv(live_stub):
out = _catch(call, live)
else:
out = _catch(call, live)
if isinstance(out, dict) and out.get("__raised__"):
return inp, {"raises": out["__raised__"]}
return inp, normalize(out, repo, live_wt, stub)
def main():
frozen = None
if REPLAY:
with open(TRUTH_PATH) as f:
frozen = json.load(f)["cases"]
tmp_root = tempfile.mkdtemp(prefix="smod_truth_")
groups = {}
failures = 0
total = 0
for group, cases in GROUPS:
recs = []
def rec(name, inp, expected, case=None, _r=recs, _g=group):
entry = {"name": name, "input": inp, "expected": expected}
if case is not None:
entry["setup"] = case.steps
entry["stub"] = case.stub
if REPLAY:
old = next(c for c in frozen[_g] if c["name"] == name)
for key in ("input", "expected"):
if json.dumps(entry[key], sort_keys=True) != json.dumps(
old[key], sort_keys=True):
print(f"FACADE DIVERGE [{_g}] {name} {key}")
print(" got: ", json.dumps(entry[key], sort_keys=True)[:600])
print(" want:", json.dumps(old[key], sort_keys=True)[:600])
_r.append({"name": name, "diverged": key})
return
_r.append(entry)
for case in cases:
total += 1
inp, want = run_scenario(case, tmp_root,
lambda i, c=case: _catch(
FACADE_CALL[c.group], i))
rec(case.name, inp, want, case)
if REPLAY:
old = next(c for c in frozen[case.group] if c["name"] == case.name)
repo, wt, stub = kernel_paths(case, tmp_root, old)
call = lambda: KERNEL_CALL[case.group](
de_substitute(copy.deepcopy(old["input"]), repo, wt, stub))
if old.get("stub"):
with _PathEnv(stub):
kout = _catch(lambda: call())
else:
kout = _catch(call)
kwant = old["expected"]
if not (isinstance(kout, dict) and kout.get("__raised__")):
kout = normalize(kout, repo, wt, stub)
if isinstance(kwant, dict) and kwant.get("raises"):
ok = kout == {"__raised__": kwant["raises"]} or (
kout == {"__raised__": "RuntimeError"})
else:
ok = json.dumps(kout, sort_keys=True) == json.dumps(
kwant, sort_keys=True)
if not ok:
failures += 1
print(f"KERNEL DIVERGE [{case.group}] {case.name}")
print(" got: ", json.dumps(kout, sort_keys=True)[:600])
print(" want:", json.dumps(kwant, sort_keys=True)[:600])
groups[group] = recs
if REPLAY:
facade_div = sum(1 for v in groups.values() for c in v if "diverged" in c)
print(f"replay: facade {total - facade_div}/{total}, "
f"kernel {total - failures}/{total}")
return 1 if (failures or facade_div) else 0
doc = {
"frozen": datetime.now(timezone.utc).isoformat(),
"oracle": "src/rune/self_modify.py",
"pin": "RUNE_SMOD_CORE=python",
"note": "Frozen via scripts/gen_self_modify_truth.py against the live "
"Python oracle; wall clock frozen at " + FROZEN_AT + "; git "
"fixtures use fixed identity+dates so the seed SHA is "
"literal. @REPO@/@WORKTREE@/@STUBDIR@ are placeholders "
"substituted at replay. 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, sort_keys=True)
f.write("\n")
print(f"frozen {total} cases → {os.path.relpath(TRUTH_PATH, REPO)}")
return 0
def kernel_paths(case, tmp_root, old_entry):
"""(repo, wt, stub) for kernel-level replay of one frozen entry."""
case_root = tempfile.mkdtemp(prefix=f"smod_kernel_{case.name}_", dir=tmp_root)
repo = make_fixture(case_root)
wt = run_setup(repo, old_entry.get("setup", []))
stub = None
if old_entry.get("stub"):
stub = make_stub_dir(case_root)
live_wt = wt or os.path.join(case_root, "wt-input")
live_stub = stub or os.path.join(case_root, "stub-unused")
return repo, live_wt, live_stub
if __name__ == "__main__":
sys.exit(main())
+589
View File
@@ -0,0 +1,589 @@
#!/usr/bin/env python3
"""gen_task_gate_truth.py — freeze/replay parity truth for task_gate
(dichotomy wave, #2479 tranche).
Freeze mode (default): exercises EVERY public function of the LIVE Python
oracle (src/rune/task_gate.py) across every branch and records
(kernel_input, expected_kernel_output, oracle_return) triples into
src/rust/task_gate/src/frozen_tg_truth.json.
Replay mode (--replay): re-runs the SAME scenarios with the Rust core
delegation active and asserts both kernel-level (loader direct) and
facade-level (oracle functions delegate) parity against the frozen file.
Poisoned-truth law: freeze pins RUNE_TG_CORE=python BEFORE any import.
No-clock law: the wall clock is frozen by patching the datetime symbol in
task_gate, so every kernel payload carries its own `now` and replay is
deterministic regardless of run date.
Boundary: the sqlite IO (connect, the row reads, UPDATE/INSERT) and the
CLI main() stay in the Python facade — the kernel owns the verdict logic
(pattern extraction, regex gate matching, count/threshold/status
transitions, promotion decision, listing projection, hook echo).
list_gates' ORDER BY is decided by sqlite in the facade; the kernel
receives already-ordered rows.
"""
import json
import os
import sqlite3
import sys
import tempfile
from datetime import datetime, timezone
REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
TRUTH_PATH = os.path.join(
REPO, "src", "rust", "task_gate", "src", "frozen_tg_truth.json"
)
REPLAY = "--replay" in sys.argv
if not REPLAY:
os.environ["RUNE_TG_CORE"] = "python" # poisoned-truth law
sys.path.insert(0, os.path.join(REPO, "src"))
sys.path.insert(0, os.path.join(REPO, "src", "rune"))
import task_gate as oracle_mod # noqa: E402
if REPLAY:
import tg_loader # noqa: E402 (kernel-level replay side)
FROZEN_NOW = "2026-10-11T23:00:00.123456+00:00"
class FrozenDatetime(datetime):
@classmethod
def now(cls, tz=None):
assert tz is timezone.utc or tz is None
return datetime(2026, 10, 11, 23, 0, 0, 123456, tzinfo=timezone.utc)
oracle_mod.datetime = FrozenDatetime
_COLS = ("id", "instruction", "pattern", "count", "threshold", "status",
"promoted_at", "created_at", "last_seen")
def fresh_db():
tmp = tempfile.mkdtemp(prefix="tg_truth_")
oracle_mod.DB_PATH = os.path.join(tmp, "task_gate.db")
return oracle_mod.connect()
def seed_rows(pre_rows):
"""Seed explicit rows (dicts with any subset of the columns; count/
threshold/status/pattern/created_at/last_seen default like the oracle
would for a fresh row). Returns rows as full dicts in id order."""
db = fresh_db()
for i, r in enumerate(pre_rows, start=1):
db.execute(
"INSERT INTO instructions (id, instruction, pattern, count,"
" threshold, status, promoted_at, created_at, last_seen)"
" VALUES (?,?,?,?,?,?,?,?,?)",
(
r.get("id", i),
r.get("instruction", "seed instruction %d" % i),
r.get("pattern", r"\bseed\b"),
r.get("count", 1),
r.get("threshold", 3),
r.get("status", "observing"),
r.get("promoted_at"),
r.get("created_at", FROZEN_NOW),
r.get("last_seen", FROZEN_NOW),
),
)
db.commit()
rows = [dict(r) for r in db.execute(
"SELECT * FROM instructions ORDER BY id").fetchall()]
db.close()
return rows
def read_rows():
db = sqlite3.connect(oracle_mod.DB_PATH)
db.row_factory = sqlite3.Row
rows = [dict(r) for r in db.execute(
"SELECT * FROM instructions ORDER BY id").fetchall()]
db.close()
return rows
def kernel_input(rows, text):
return {"instructions": rows, "text": text, "now": FROZEN_NOW}
def expected_record(rows_after, result):
"""Derive the kernel's expected doc purely from oracle behavior."""
if result["matched"]:
row = next(r for r in rows_after if r["id"] == result["id"])
return {"matched": True, "id": row["id"], "count": row["count"],
"status": row["status"], "promoted": result["promoted"],
"last_seen": row["last_seen"]}
row = next(r for r in rows_after if r["id"] == result["id"])
return {"matched": False,
"new_row": {k: row[k] for k in
("instruction", "pattern", "count", "threshold",
"status", "created_at", "last_seen")}}
# ── record_instruction ───────────────────────────────────────────────────────
def r_new_keywords(rec):
rows = seed_rows([])
text = "Always CHECK the benchmark before building"
result = oracle_mod.record_instruction(text)
after = read_rows()
rec("new-keywords-pattern",
kernel_input(rows, text), result, after,
expected_record(after, result))
def r_new_no_keywords(rec):
rows = seed_rows([])
text = "do it"
result = oracle_mod.record_instruction(text)
after = read_rows()
rec("new-no-keywords-fallback-escape",
kernel_input(rows, text), result, after,
expected_record(after, result))
def r_new_fallback_truncate50(rec):
rows = seed_rows([])
text = " AB CD EF GH IJ KL MN OP QR ST UV WX YZ 12 34 56 "
result = oracle_mod.record_instruction(text)
after = read_rows()
rec("new-fallback-truncated-50-stripped",
kernel_input(rows, text), result, after,
expected_record(after, result))
def r_new_regex_special(rec):
rows = seed_rows([])
text = "fix the bug (now)!"
result = oracle_mod.record_instruction(text)
after = read_rows()
rec("new-regex-special-chars",
kernel_input(rows, text), result, after,
expected_record(after, result))
def r_new_unicode(rec):
rows = seed_rows([])
text = "café über always"
result = oracle_mod.record_instruction(text)
after = read_rows()
rec("new-unicode-keywords",
kernel_input(rows, text), result, after,
expected_record(after, result))
def r_new_underscore_digit(rec):
rows = seed_rows([])
text = "v1_2 test_it now"
result = oracle_mod.record_instruction(text)
after = read_rows()
rec("new-underscore-digit-keywords",
kernel_input(rows, text), result, after,
expected_record(after, result))
def r_new_instruction_verbatim(rec):
# the INSERT stores the ORIGINAL text, not the lowered/stripped form
rows = seed_rows([])
text = " ALWAYS check "
result = oracle_mod.record_instruction(text)
after = read_rows()
rec("new-instruction-verbatim-not-stripped",
kernel_input(rows, text), result, after,
expected_record(after, result))
def r_match_observing(rec):
rows = seed_rows([
{"pattern": r"\bcheck\b", "count": 1, "threshold": 3,
"status": "observing"},
])
text = "please CHECK everything"
result = oracle_mod.record_instruction(text)
after = read_rows()
rec("match-observing-increment",
kernel_input(rows, text), result, after,
expected_record(after, result))
def r_match_promote(rec):
rows = seed_rows([
{"pattern": r"\bcheck\b", "count": 2, "threshold": 3,
"status": "observing"},
])
text = "check again"
result = oracle_mod.record_instruction(text)
after = read_rows()
rec("match-promotes-at-threshold",
kernel_input(rows, text), result, after,
expected_record(after, result))
def r_match_already_active(rec):
rows = seed_rows([
{"pattern": r"\bcheck\b", "count": 5, "threshold": 3,
"status": "active", "promoted_at": FROZEN_NOW},
])
text = "check once more"
result = oracle_mod.record_instruction(text)
after = read_rows()
rec("match-already-active-no-promote",
kernel_input(rows, text), result, after,
expected_record(after, result))
def r_match_first_wins(rec):
rows = seed_rows([
{"pattern": r"\bcheck\b", "count": 1, "threshold": 3},
{"pattern": r"\bcheck|everything\b", "count": 1, "threshold": 3},
])
text = "check everything"
result = oracle_mod.record_instruction(text)
after = read_rows()
rec("match-first-row-wins",
kernel_input(rows, text), result, after,
expected_record(after, result))
def r_match_skips_retired(rec):
rows = seed_rows([
{"pattern": r"\bcheck\b", "count": 1, "threshold": 3,
"status": "retired"},
{"pattern": r"\bbenchmark\b", "count": 1, "threshold": 3},
])
text = "check the benchmark"
result = oracle_mod.record_instruction(text)
after = read_rows()
rec("match-skips-retired-row",
kernel_input(rows, text), result, after,
expected_record(after, result))
def r_match_word_boundary(rec):
rows = seed_rows([
{"pattern": r"\bcheck\b", "count": 1, "threshold": 3},
])
text = "the checks all pass"
result = oracle_mod.record_instruction(text)
after = read_rows()
rec("no-match-word-boundary-plural",
kernel_input(rows, text), result, after,
expected_record(after, result))
def r_match_fallback_pattern(rec):
rows = seed_rows([
{"pattern": r"do\ it", "count": 1, "threshold": 3},
])
text = "Do IT now"
result = oracle_mod.record_instruction(text)
after = read_rows()
rec("match-fallback-escaped-space-pattern",
kernel_input(rows, text), result, after,
expected_record(after, result))
def r_match_digit_threshold(rec):
rows = seed_rows([
{"pattern": r"\bship\b", "count": 3, "threshold": 4},
])
text = "ship it"
result = oracle_mod.record_instruction(text)
after = read_rows()
rec("match-count-below-higher-threshold",
kernel_input(rows, text), result, after,
expected_record(after, result))
def r_new_after_nonmatch(rec):
rows = seed_rows([
{"pattern": r"\bdeploy\b", "count": 1, "threshold": 3},
])
text = "release the kraken"
result = oracle_mod.record_instruction(text)
after = read_rows()
rec("new-row-after-existing-nonmatch",
kernel_input(rows, text), result, after,
expected_record(after, result))
# ── check_context ────────────────────────────────────────────────────────────
def c_no_active(rec):
rows = seed_rows([
{"pattern": r"\bcheck\b", "count": 1, "threshold": 3},
{"pattern": r"\bship\b", "count": 3, "threshold": 3,
"status": "retired"},
])
context = "check the ship"
matches = oracle_mod.check_context(context)
rec("ctx-no-active-gates",
{"instructions": rows, "context": context},
matches, None, {"matches": matches})
def c_one_match(rec):
rows = seed_rows([
{"pattern": r"\bcheck\b", "count": 3, "threshold": 3,
"status": "active", "promoted_at": FROZEN_NOW,
"instruction": "always check"},
])
context = "about to check the benchmark"
matches = oracle_mod.check_context(context)
rec("ctx-one-match",
{"instructions": rows, "context": context},
matches, None, {"matches": matches})
def c_multi_match_order(rec):
rows = seed_rows([
{"pattern": r"\bcheck\b", "count": 3, "threshold": 3,
"status": "active", "instruction": "always check"},
{"pattern": r"\bship\b", "count": 7, "threshold": 3,
"status": "active", "instruction": "ship weekly"},
{"pattern": r"\bbenchmark\b", "count": 4, "threshold": 3,
"status": "active", "instruction": "run the benchmark"},
])
context = "check, ship, benchmark"
matches = oracle_mod.check_context(context)
rec("ctx-multi-match-rowid-order",
{"instructions": rows, "context": context},
matches, None, {"matches": matches})
def c_case_insensitive(rec):
rows = seed_rows([
{"pattern": r"\bcheck\b", "count": 3, "threshold": 3,
"status": "active", "instruction": "always check"},
])
context = "CHECK EVERYTHING NOW"
matches = oracle_mod.check_context(context)
rec("ctx-case-insensitive-context",
{"instructions": rows, "context": context},
matches, None, {"matches": matches})
def c_no_match(rec):
rows = seed_rows([
{"pattern": r"\bcheck\b", "count": 3, "threshold": 3,
"status": "active", "instruction": "always check"},
])
context = "deploy to production"
matches = oracle_mod.check_context(context)
rec("ctx-no-match",
{"instructions": rows, "context": context},
matches, None, {"matches": matches})
def c_word_boundary(rec):
rows = seed_rows([
{"pattern": r"\bcheck\b", "count": 3, "threshold": 3,
"status": "active", "instruction": "always check"},
])
context = "the checks pass"
matches = oracle_mod.check_context(context)
rec("ctx-word-boundary-plural-no-match",
{"instructions": rows, "context": context},
matches, None, {"matches": matches})
# ── list_gates ───────────────────────────────────────────────────────────────
def l_empty(rec):
seed_rows([])
gates = oracle_mod.list_gates()
rec("list-empty", {}, gates, read_rows(), None)
def l_order_desc(rec):
seed_rows([
{"pattern": r"\ba\b", "count": 5, "instruction": "five"},
{"pattern": r"\bb\b", "count": 1, "instruction": "one"},
{"pattern": r"\bc\b", "count": 3, "instruction": "three"},
])
gates = oracle_mod.list_gates()
rec("list-ordered-count-desc", {}, gates, read_rows(), None)
def l_ties(rec):
seed_rows([
{"pattern": r"\ba\b", "count": 2, "instruction": "first"},
{"pattern": r"\bb\b", "count": 4, "instruction": "second"},
{"pattern": r"\bc\b", "count": 2, "instruction": "third"},
])
gates = oracle_mod.list_gates()
rec("list-tie-order", {}, gates, read_rows(), None)
def l_null_promoted_at(rec):
seed_rows([
{"pattern": r"\ba\b", "count": 2, "status": "active",
"promoted_at": FROZEN_NOW, "instruction": "active one"},
{"pattern": r"\bb\b", "count": 1, "instruction": "observing one"},
])
gates = oracle_mod.list_gates()
rec("list-null-and-set-promoted-at", {}, gates, read_rows(), None)
# ── promote_gate ─────────────────────────────────────────────────────────────
def p_first(rec):
seed_rows([
{"pattern": r"\ba\b", "count": 1, "instruction": "observing"},
])
ret = oracle_mod.promote_gate(1)
rec("promote-existing-id",
{"id": 1, "now": FROZEN_NOW}, ret, read_rows(),
{"status": "active", "promoted_at": FROZEN_NOW})
def p_unknown_id(rec):
seed_rows([])
ret = oracle_mod.promote_gate(42)
rec("promote-unknown-id",
{"id": 42, "now": FROZEN_NOW}, ret, read_rows(),
{"status": "active", "promoted_at": FROZEN_NOW})
def p_overrides_retired(rec):
seed_rows([
{"pattern": r"\ba\b", "count": 1, "status": "retired",
"instruction": "retired one"},
])
ret = oracle_mod.promote_gate(1)
rec("promote-retired-row-becomes-active",
{"id": 1, "now": FROZEN_NOW}, ret, read_rows(),
{"status": "active", "promoted_at": FROZEN_NOW})
# ── handle_hook ──────────────────────────────────────────────────────────────
def h_default(rec):
ret = oracle_mod.handle_hook("post-build")
rec("hook-plain-event", {"event": "post-build"}, ret, None,
{"status": "ok", "event": "post-build", "handler": "task_gate"})
def h_with_data(rec):
ret = oracle_mod.handle_hook("pre-commit", {"sha": "abc"})
rec("hook-with-data", {"event": "pre-commit"}, ret, None,
{"status": "ok", "event": "pre-commit", "handler": "task_gate"})
def h_empty(rec):
ret = oracle_mod.handle_hook("")
rec("hook-empty-event", {"event": ""}, ret, None,
{"status": "ok", "event": "", "handler": "task_gate"})
GROUPS = [
("record_instruction", [r_new_keywords, r_new_no_keywords,
r_new_fallback_truncate50, r_new_regex_special,
r_new_unicode, r_new_underscore_digit,
r_new_instruction_verbatim, r_match_observing,
r_match_promote, r_match_already_active,
r_match_first_wins, r_match_skips_retired,
r_match_word_boundary, r_match_fallback_pattern,
r_match_digit_threshold, r_new_after_nonmatch]),
("check_context", [c_no_active, c_one_match, c_multi_match_order,
c_case_insensitive, c_no_match, c_word_boundary]),
("list_gates", [l_empty, l_order_desc, l_ties, l_null_promoted_at]),
("promote_gate", [p_first, p_unknown_id, p_overrides_retired]),
("handle_hook", [h_default, h_with_data, h_empty]),
]
# groups where the kernel is involved (list_gates' ordering is sqlite's)
KERNEL_GROUPS = {"record_instruction", "check_context", "promote_gate",
"handle_hook"}
def main():
frozen = None
if REPLAY:
with open(TRUTH_PATH) as f:
frozen = json.load(f)["cases"]
groups = {}
for group, cases in GROUPS:
recs = []
def rec(name, kinput, oracle_ret, rows_after, kexpected, _r=recs):
entry = {"name": name, "input": kinput, "return": oracle_ret,
"expected": kexpected}
if rows_after is not None:
entry["rows_after"] = rows_after
if REPLAY:
old = next(c for c in frozen[group] if c["name"] == name)
for key in ("input", "return", "expected", "rows_after"):
if key not in entry and key not in old:
continue
got, want = entry.get(key), old.get(key)
if json.dumps(got, sort_keys=True) != json.dumps(
want, sort_keys=True):
print(f"FACADE DIVERGE [{group}] {name} {key}")
print(" got: ", json.dumps(got, sort_keys=True)[:600])
print(" want:", json.dumps(want, sort_keys=True)[:600])
recs.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_failures = sum(
1 for v in groups.values() for c in v if "diverged" in c)
failures = 0
for group, cases in frozen.items():
if group not in KERNEL_GROUPS:
continue
for case in cases:
inp = case["input"]
if group == "record_instruction":
got = tg_loader.record_instruction(
inp["instructions"], inp["text"], inp["now"])
elif group == "check_context":
got = tg_loader.check_context(
inp["instructions"], inp["context"])
elif group == "promote_gate":
got = tg_loader.promote_gate(inp["id"], inp["now"])
else:
got = tg_loader.handle_hook(inp["event"])
if json.dumps(got, sort_keys=True) != json.dumps(
case["expected"], sort_keys=True):
failures += 1
print(f"KERNEL DIVERGE [{group}] {case['name']}")
print(" got: ", json.dumps(got, sort_keys=True)[:600])
print(" want:", json.dumps(
case["expected"], sort_keys=True)[:600])
print(f"replay: facade {total - facade_failures}/{total}, "
f"kernel {total - failures}/{total}")
return 1 if (failures or facade_failures) else 0
doc = {
"frozen": datetime.now(timezone.utc).isoformat(),
"oracle": "src/rune/task_gate.py",
"pin": "RUNE_TG_CORE=python",
"note": "Frozen via scripts/gen_task_gate_truth.py against the live "
"Python oracle; wall clock frozen at " + FROZEN_NOW + ". "
"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, sort_keys=True)
f.write("\n")
print(f"frozen {total} cases → {os.path.relpath(TRUTH_PATH, REPO)}")
return 0
if __name__ == "__main__":
sys.exit(main())
+727
View File
@@ -0,0 +1,727 @@
#!/usr/bin/env python3
"""gen_testing_mode_truth.py — freeze/replay parity truth for testing_mode
(dichotomy wave, #2479 tranche).
Freeze mode (default): exercises EVERY public function of the LIVE Python
oracle (src/rune/testing_mode.py) across every branch and records
(kernel_input, expected_kernel_output) pairs into
src/rust/testing_mode/src/frozen_tm_truth.json.
Replay mode (--replay): re-runs the SAME scenarios with the Rust core
delegation active and asserts both kernel-level (loader direct) and
facade-level (oracle functions delegate) parity against the frozen file.
Poisoned-truth law: freeze pins RUNE_TM_CORE=python BEFORE any import (a
no-op pre-port, load-bearing post-port — the facade must never delegate
into the kernel while truth is being frozen).
No-clock law: the wall clock is frozen by patching the `time` symbol in
testing_mode, so every kernel payload carries its own `now`/`now_ts` and
replay is deterministic regardless of run date.
Boundary law: the session FILE IO (_load hit branch, save), the
observations.jsonl append/rewrite, the forge adapter call, the ledger
read (outstanding/record_real_world) and the registry IO all stay in the
Python facade — the kernel is a pure function of their results. The
`load`, `save` and `forge_dry_run` truth groups are facade-only (no
kernel symbol) and are skipped by the kernel-level replay pass.
Ledger/registry law: behaviours and registry are stubbed with
deterministic fakes (the oracle treats them as duck-typed boundaries);
their canned responses are deep-copied into the kernel inputs BEFORE the
oracle runs (the oracle mutates the session state in place).
"""
import copy
import json
import os
import sys
import tempfile
from pathlib import Path
REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
TRUTH_PATH = os.path.join(REPO, "src", "rust", "testing_mode", "src", "frozen_tm_truth.json")
REPLAY = "--replay" in sys.argv
os.environ["RUNE_TM_CORE"] = "python" # poisoned-truth law
sys.path.insert(0, os.path.join(REPO, "src"))
sys.path.insert(0, os.path.join(REPO, "src", "rune"))
import testing_mode as oracle_mod # noqa: E402
if REPLAY:
from rune import tm_loader # noqa: E402 (kernel-level replay side)
# replay law: the facade pass must exercise the LIVE Rust delegation
# path, so the pin is lifted AFTER import (the facade reads the env
# at call time). The kernel pass calls the loader directly.
del os.environ["RUNE_TM_CORE"]
FROZEN_TS = 1791806400 # 2026-10-12T12:00:00Z
FROZEN_NOW = "2026-10-12T12:00:00Z"
class FakeTime:
@staticmethod
def strftime(fmt, t):
import time as _t
return _t.strftime(fmt, t)
@staticmethod
def gmtime():
import time as _t
return _t.gmtime(FROZEN_TS)
@staticmethod
def time():
return FROZEN_TS
oracle_mod.time = FakeTime
# ── deterministic boundary stubs ─────────────────────────────────────────────
class FakeBehaviours:
"""Duck-typed BehaviourLedger stub — canned outstanding/records."""
def __init__(self, outstanding=None, rec=None):
self.outstanding_value = outstanding or []
self.rec = rec or {"behaviour_id": "behaviour:T-1", "state": "USER_CONFIRMED"}
self.outstanding_kwargs = None
def outstanding(self, *, component=None, project=None):
self.outstanding_kwargs = {"component": component, "project": project}
return copy.deepcopy(self.outstanding_value)
def record_real_world(self, behaviour_id, *, passed, detail="",
confirmed_by="user", generation=""):
rec = dict(self.rec)
rec["behaviour_id"] = rec.get("behaviour_id", f"behaviour:{behaviour_id}")
return rec
class FakeRegistry:
def __init__(self, entries):
self._entries = entries
self.set_calls = []
self.saved = False
def all_projects(self):
return copy.deepcopy(self._entries)
def set_outstanding_validations(self, project_id, count):
self.set_calls.append((project_id, count))
def save(self):
self.saved = True
class FakeLedger:
"""Patched-in BehaviourLedger — summary canned per location."""
summaries = {}
def __init__(self, loc):
self.loc = loc
def summary(self):
return copy.deepcopy(FakeLedger.summaries.get(str(self.loc),
{"outstanding": 0, "states": {}}))
def fresh_session_dir():
return tempfile.mkdtemp(prefix="tm_truth_")
def fresh_state():
s = oracle_mod.TestingSession(fresh_session_dir())
return copy.deepcopy(s.state)
# ── case helpers ─────────────────────────────────────────────────────────────
def rec_kernel(rec, name, inp, expected, oracle_ret=None):
"""Record a kernel case; inputs deep-copied BEFORE oracle mutation."""
rec(name, copy.deepcopy(inp), oracle_ret, expected)
def rec_facade(rec, name, inp, expected, oracle_ret=None):
rec(name, inp, oracle_ret, expected)
# ── default_state (kernel) ───────────────────────────────────────────────────
def _ds_case(rec, name, project, scope_component):
d = fresh_session_dir()
s = oracle_mod.TestingSession(d, project=project, scope_component=scope_component)
rec_kernel(rec, name,
{"project": project, "scope_component": scope_component, "now": FROZEN_NOW},
{"state": s.state})
def c_ds_default(rec):
_ds_case(rec, "default-no-scope", "p", None)
def c_ds_scope(rec):
_ds_case(rec, "default-with-scope", "p", "component:shared")
def c_ds_empty_project(rec):
_ds_case(rec, "default-empty-project", "", "component:x")
# ── next_item (kernel) ───────────────────────────────────────────────────────
def _next_case(rec, name, state_over, outstanding, expect_item):
d = fresh_session_dir()
s = oracle_mod.TestingSession(d, project="p")
s.state.update(copy.deepcopy(state_over))
state_before = copy.deepcopy(s.state)
beh = FakeBehaviours(outstanding=outstanding)
item = s.next_item(beh)
assert beh.outstanding_kwargs == {"component": state_before.get("scope_component"),
"project": state_before.get("project") or None}
rec_kernel(rec, name,
{"state": state_before, "outstanding": outstanding},
{"state": s.state, "item": item},
oracle_ret=item)
def c_next_paused(rec):
_next_case(rec, "next-paused-null", {"paused": True},
[{"behaviour_id": "behaviour:T-1"}], None)
def c_next_empty(rec):
_next_case(rec, "next-empty-queue", {}, [], None)
def c_next_first(rec):
_next_case(rec, "next-first-of-two", {},
[{"behaviour_id": "behaviour:T-1", "state": "UNVALIDATED"},
{"behaviour_id": "behaviour:T-2", "state": "RETEST_REQUIRED"}],
{"behaviour_id": "behaviour:T-1", "state": "UNVALIDATED"})
def c_next_overwrites_current(rec):
_next_case(rec, "next-overwrites-current-item", {"current_item": "behaviour:OLD"},
[{"behaviour_id": "behaviour:T-2"}], {"behaviour_id": "behaviour:T-2"})
def c_next_empty_clears_current(rec):
_next_case(rec, "next-empty-clears-current", {"current_item": "behaviour:T-1"},
[], None)
# ── record (kernel) ──────────────────────────────────────────────────────────
def _record_case(rec, name, completed_pre, behaviour_id, passed, rec_state):
d = fresh_session_dir()
s = oracle_mod.TestingSession(d, project="p")
s.state["completed_this_session"] = copy.deepcopy(completed_pre)
state_before = copy.deepcopy(s.state)
beh = FakeBehaviours(rec={"behaviour_id": f"behaviour:{behaviour_id}",
"state": rec_state})
ret = s.record(beh, behaviour_id, passed=passed, detail="checked")
rec_kernel(rec, name,
{"state": state_before,
"rec": ret, "passed": passed, "now": FROZEN_NOW},
{"state": s.state},
oracle_ret={"behaviour_id": ret["behaviour_id"], "state": ret["state"]})
def c_record_pass(rec):
_record_case(rec, "record-pass-appends", [], "T-1", True, "USER_CONFIRMED")
def c_record_fail(rec):
_record_case(rec, "record-fail-appends", [], "T-2", False, "TEST_FAILED")
def c_record_appends_to_existing(rec):
pre = [{"behaviour_id": "behaviour:T-0", "passed": True, "at": "2026-10-12T11:00:00Z"}]
_record_case(rec, "record-appends-preserves-order", pre, "T-1", True, "USER_CONFIRMED")
# ── pause / resume (kernel) ──────────────────────────────────────────────────
def _pause_case(rec, name, paused_pre):
d = fresh_session_dir()
s = oracle_mod.TestingSession(d, project="p")
s.state["paused"] = paused_pre
state_before = copy.deepcopy(s.state)
s.pause()
rec_kernel(rec, name,
{"state": state_before},
{"state": s.state})
def _resume_case(rec, name, paused_pre):
d = fresh_session_dir()
s = oracle_mod.TestingSession(d, project="p")
s.state["paused"] = paused_pre
state_before = copy.deepcopy(s.state)
s.resume()
rec_kernel(rec, name,
{"state": state_before},
{"state": s.state})
def c_pause_from_active(rec):
_pause_case(rec, "pause-from-active", False)
def c_pause_already_paused(rec):
_pause_case(rec, "pause-idempotent", True)
def c_resume_from_paused(rec):
_resume_case(rec, "resume-from-paused", True)
def c_resume_already_active(rec):
_resume_case(rec, "resume-idempotent", False)
# ── note_blocker (kernel) ────────────────────────────────────────────────────
def _blocker_case(rec, name, blockers_pre, note):
d = fresh_session_dir()
s = oracle_mod.TestingSession(d, project="p")
s.state["blockers"] = copy.deepcopy(blockers_pre)
state_before = copy.deepcopy(s.state)
s.note_blocker("behaviour:T-9", note)
rec_kernel(rec, name,
{"state": state_before,
"behaviour_id": "behaviour:T-9", "note": note, "now": FROZEN_NOW},
{"state": s.state})
def c_blocker_normal(rec):
_blocker_case(rec, "blocker-normal", [], "blocked on upstream")
def c_blocker_truncates(rec):
_blocker_case(rec, "blocker-note-truncated-200", [], "x" * 260)
def c_blocker_appends(rec):
pre = [{"behaviour_id": "behaviour:T-1", "note": "old", "at": "2026-10-12T11:00:00Z"}]
_blocker_case(rec, "blocker-appends-preserves-order", pre, "new")
# ── record_observation (kernel) ──────────────────────────────────────────────
def _obs_case(rec, name, **kw):
d = fresh_session_dir()
obs = oracle_mod.record_observation(d, **kw)
inp = {"now": FROZEN_NOW, "now_ts": FROZEN_TS,
"observed": kw.get("observed", ""),
"expected": kw.get("expected", ""),
"behaviour_id": kw.get("behaviour_id", ""),
"environment": kw.get("environment", ""),
"reproduction": kw.get("reproduction", ""),
"candidate_areas": list(kw.get("candidate_areas") or []),
"confidence": kw.get("confidence", "low")}
rec_kernel(rec, name, inp, {"observation": obs}, oracle_ret=obs)
def c_obs_defaults(rec):
_obs_case(rec, "obs-defaults", observed="button dead", expected="button saves")
def c_obs_full(rec):
_obs_case(rec, "obs-full-fields", observed="500 on save", expected="201 created",
behaviour_id="behaviour:T-1", environment="production",
reproduction="POST /save with empty body",
candidate_areas=["component:api"], confidence="high")
def c_obs_candidate_areas_none(rec):
_obs_case(rec, "obs-candidate-areas-none-coerced", observed="x", expected="y",
candidate_areas=None)
def c_obs_candidate_areas_copy(rec):
src = ["component:a", "component:b"]
_obs_case(rec, "obs-candidate-areas-hypotheses", observed="x", expected="y",
candidate_areas=src)
# ── close_observation (kernel) ───────────────────────────────────────────────
def _obs_lines(d, n=1):
lines = []
for i in range(n):
obs = oracle_mod.record_observation(
d, observed=f"obs-{i}", expected=f"exp-{i}",
behaviour_id=f"behaviour:T-{i}")
lines.append(json.loads(json.dumps(obs, sort_keys=True)))
return lines
def _close_case(rec, name, records, observation_id, retest, confirmed,
contract_ref="", expect_raise=False):
d = fresh_session_dir()
with (Path(d) / "observations.jsonl").open("w") as f:
for r in records:
f.write(json.dumps(r, sort_keys=True) + "\n")
try:
result = oracle_mod.close_observation(
d, observation_id, behaviour_retest_passed=retest,
user_confirmed=confirmed, contract_ref=contract_ref)
ret, expected = result, {"records": _redump(records, observation_id, retest,
confirmed, contract_ref, FROZEN_NOW),
"result": result}
except ValueError as e:
ret = f"ValueError: {e}"
expected = {"records": [json.loads(json.dumps(r, sort_keys=True)) for r in records],
"result": None}
rec_kernel(rec, name,
{"records": copy.deepcopy(records), "observation_id": observation_id,
"behaviour_retest_passed": retest, "user_confirmed": confirmed,
"contract_ref": contract_ref, "now": FROZEN_NOW},
expected, oracle_ret=ret)
def _redump(records, observation_id, retest, confirmed, contract_ref, now):
out = []
for r in records:
r = copy.deepcopy(r)
if r.get("observation_id") == observation_id:
r["closure"] = {"behaviour_retest_passed": retest, "user_confirmed": confirmed,
"contract_ref": contract_ref,
"closed": retest and confirmed, "at": now}
out.append(r)
return out
def c_close_full(rec):
d = fresh_session_dir()
records = _obs_lines(d, 1)
_close_case(rec, "close-retest-and-confirmed", records,
records[0]["observation_id"], True, True)
def c_close_no_user_confirm(rec):
d = fresh_session_dir()
records = _obs_lines(d, 1)
_close_case(rec, "close-green-without-user-not-closed", records,
records[0]["observation_id"], True, False)
def c_close_no_retest(rec):
d = fresh_session_dir()
records = _obs_lines(d, 1)
_close_case(rec, "close-confirm-without-retest-not-closed", records,
records[0]["observation_id"], False, True)
def c_close_with_contract_ref(rec):
d = fresh_session_dir()
records = _obs_lines(d, 1)
_close_case(rec, "close-with-contract-ref", records,
records[0]["observation_id"], True, True,
contract_ref="BUILDER-2026-10-12-factory-wave")
def c_close_unknown(rec):
d = fresh_session_dir()
records = _obs_lines(d, 1)
_close_case(rec, "close-unknown-id-raises", records, "obs-404", True, True,
expect_raise=True)
def c_close_preserves_others(rec):
d = fresh_session_dir()
records = _obs_lines(d, 3)
_close_case(rec, "close-preserves-sibling-records", records,
records[1]["observation_id"], True, True)
# ── project_failure_to_forge (kernel: title/body; dry_run facade-only) ──────
def _project_case(rec, name, behaviour_id, observation):
obs = oracle_mod.project_failure_to_forge(
None, "conductor/rune", behaviour_id=behaviour_id,
observation=observation, dry_run=True)
from rune.testing_mode import _py_project
title, body = _py_project(behaviour_id, observation)
assert title == obs["title"]
rec_kernel(rec, name,
{"behaviour_id": behaviour_id, "observation": copy.deepcopy(observation)},
{"title": obs["title"], "body": body}, oracle_ret=obs)
def c_project_normal(rec):
_project_case(rec, "project-normal", "T-1",
{"observation_id": "obs-1", "observed": "500", "expected": "201",
"reproduction": "POST /x"})
def c_project_truncates_expected(rec):
_project_case(rec, "project-expected-truncated-60", "T-2",
{"observation_id": "obs-2", "observed": "o", "expected": "e" * 90,
"reproduction": "r"})
def c_project_missing_fields(rec):
_project_case(rec, "project-missing-fields-empty-defaults", "T-3", {})
def c_project_observation_id_none(rec):
# missing observation_id renders Python str(None) → "None"
_project_case(rec, "project-observation-id-none-string", "T-4",
{"observed": "x", "expected": "y", "reproduction": "z"})
def c_project_dry_run(rec):
obs = {"observation_id": "obs-9", "observed": "x", "expected": "y",
"reproduction": "z"}
ret = oracle_mod.project_failure_to_forge(
None, "conductor/rune", behaviour_id="T-9", observation=obs, dry_run=True)
rec_facade(rec, "forge-dry-run-no-adapter-call",
{"behaviour_id": "T-9", "observation": obs, "dry_run": True},
{"projected": ret["projected"], "dry_run": ret["dry_run"],
"title": ret["title"]}, oracle_ret=ret)
# ── aggregate (kernel shaping over facade-fetched summaries) ────────────────
def _agg_case(rec, name, entries, ledger_flags, summaries):
d = fresh_session_dir()
locs = []
for i, present in enumerate(ledger_flags):
loc = Path(d) / f"p{i + 1}"
loc.mkdir(exist_ok=True)
if present:
(loc / "behaviour_ledger.json").write_text("{}")
locs.append(str(loc))
reg_entries = [{"project_id": e["project_id"], "scaffold_location": loc}
for e, loc in zip(entries, locs)]
FakeLedger.summaries = {loc: s for loc, s in zip(locs, summaries) if s is not None}
oracle_mod.BehaviourLedger = FakeLedger
try:
rows = oracle_mod.aggregate(FakeRegistry(reg_entries), Path(d))
finally:
from rune.behaviour_ledger import BehaviourLedger as RealLedger
oracle_mod.BehaviourLedger = RealLedger
kernel_entries = [{"project_id": e["project_id"],
"summary": (copy.deepcopy(s) if present else None)}
for e, present, s in zip(entries, ledger_flags, summaries)]
rec_kernel(rec, name, {"entries": kernel_entries}, {"rows": rows}, oracle_ret=rows)
def c_agg_no_ledger(rec):
_agg_case(rec, "agg-no-ledger-note", [{"project_id": "p1"}], [False], [None])
def c_agg_with_summary(rec):
_agg_case(rec, "agg-with-summary", [{"project_id": "p1"}], [True],
[{"outstanding": 2, "states": {"UNVALIDATED": 2}}])
def c_agg_mixed(rec):
_agg_case(rec, "agg-mixed-projects", [{"project_id": "p1"}, {"project_id": "p2"},
{"project_id": "p3"}],
[True, False, True],
[{"outstanding": 1, "states": {"RETEST_REQUIRED": 1}}, None,
{"outstanding": 0, "states": {}}])
def c_agg_empty(rec):
_agg_case(rec, "agg-empty-registry", [], [], [])
# ── load / save (facade-only: FILE IO stays in Python) ──────────────────────
def c_load_missing(rec):
d = fresh_session_dir()
s = oracle_mod.TestingSession(d, project="p", scope_component="component:c")
rec_facade(rec, "load-missing-file-default-state", {"path": "missing"},
{"state": s.state})
def c_load_valid(rec):
d = fresh_session_dir()
pre = dict(fresh_state(), current_item="behaviour:T-1", paused=True)
Path(d, "testing_session.json").write_text(json.dumps(pre, indent=1, sort_keys=True))
s = oracle_mod.TestingSession(d)
rec_facade(rec, "load-valid-file-roundtrip", {"path": "valid"},
{"state": s.state})
def c_save(rec):
d = fresh_session_dir()
s = oracle_mod.TestingSession(d, project="p")
s.state["current_item"] = "behaviour:T-1"
s.state["blockers"].append({"behaviour_id": "behaviour:T-2", "note": "n", "at": FROZEN_NOW})
s.pause()
s.save()
text = Path(d, "testing_session.json").read_text()
rec_facade(rec, "save-stamps-updated-at-indent1-sortkeys",
{"state": copy.deepcopy(s.state)}, {"file_text": text},
oracle_ret=None)
# ── driver ───────────────────────────────────────────────────────────────────
DEFAULT_STATE = [c_ds_default, c_ds_scope, c_ds_empty_project]
NEXT = [c_next_paused, c_next_empty, c_next_first, c_next_overwrites_current,
c_next_empty_clears_current]
RECORD = [c_record_pass, c_record_fail, c_record_appends_to_existing]
PAUSE = [c_pause_from_active, c_pause_already_paused]
RESUME = [c_resume_from_paused, c_resume_already_active]
BLOCKER = [c_blocker_normal, c_blocker_truncates, c_blocker_appends]
OBS = [c_obs_defaults, c_obs_full, c_obs_candidate_areas_none, c_obs_candidate_areas_copy]
CLOSE = [c_close_full, c_close_no_user_confirm, c_close_no_retest,
c_close_with_contract_ref, c_close_unknown, c_close_preserves_others]
PROJECT = [c_project_normal, c_project_truncates_expected, c_project_missing_fields,
c_project_observation_id_none]
FORGE = [c_project_dry_run]
AGG = [c_agg_no_ledger, c_agg_with_summary, c_agg_mixed, c_agg_empty]
LOAD = [c_load_missing, c_load_valid]
SAVE = [c_save]
GROUPS = [("default_state", DEFAULT_STATE), ("next_item", NEXT), ("record", RECORD),
("pause", PAUSE), ("resume", RESUME), ("note_blocker", BLOCKER),
("record_observation", OBS), ("close_observation", CLOSE),
("project", PROJECT), ("forge_dry_run", FORGE), ("aggregate", AGG),
("load", LOAD), ("save", SAVE)]
# kernel symbol per group (None → facade-only group, skipped kernel-side)
KERNEL = {
"default_state": "tm_default_state",
"next_item": "tm_next_item",
"record": "tm_record",
"pause": "tm_pause",
"resume": "tm_resume",
"note_blocker": "tm_note_blocker",
"record_observation": "tm_record_observation",
"close_observation": "tm_close_observation",
"project": "tm_project",
"forge_dry_run": None,
"aggregate": "tm_aggregate",
"load": None,
"save": None,
}
# loader wrapper + slice of `expected` the unwrapped FFI value maps to
CALLS = {
"default_state": ("default_state", lambda w: w["state"],
lambda i, ld: ld.default_state(project=i["project"],
scope_component=i["scope_component"],
now=i["now"])),
"next_item": ("next_item", lambda w: w,
lambda i, ld: ld.next_item(state=i["state"], outstanding=i["outstanding"])),
"record": ("record", lambda w: w["state"],
lambda i, ld: ld.record(state=i["state"], rec=i["rec"],
passed=i["passed"], now=i["now"])),
"pause": ("pause", lambda w: w["state"], lambda i, ld: ld.pause(state=i["state"])),
"resume": ("resume", lambda w: w["state"], lambda i, ld: ld.resume(state=i["state"])),
"note_blocker": ("note_blocker", lambda w: w["state"],
lambda i, ld: ld.note_blocker(state=i["state"],
behaviour_id=i["behaviour_id"],
note=i["note"], now=i["now"])),
"record_observation": ("record_observation", lambda w: w["observation"],
lambda i, ld: ld.record_observation(
now=i["now"], now_ts=i["now_ts"], observed=i["observed"],
expected=i["expected"], behaviour_id=i["behaviour_id"],
environment=i["environment"], reproduction=i["reproduction"],
candidate_areas=i["candidate_areas"],
confidence=i["confidence"])),
"close_observation": ("close_observation", lambda w: w,
lambda i, ld: ld.close_observation(
records=i["records"], observation_id=i["observation_id"],
behaviour_retest_passed=i["behaviour_retest_passed"],
user_confirmed=i["user_confirmed"],
contract_ref=i["contract_ref"], now=i["now"])),
"project": ("project", lambda w: w,
lambda i, ld: ld.project(behaviour_id=i["behaviour_id"],
observation=i["observation"])),
"aggregate": ("aggregate", lambda w: w["rows"],
lambda i, ld: ld.aggregate(entries=i["entries"])),
}
def canon(x) -> str:
"""Content canonicalization: a sorted dump makes the comparison
key-order independent (int keys never occur here)."""
return json.dumps(json.loads(json.dumps(x)), sort_keys=True)
def main():
frozen = None
if REPLAY:
with open(TRUTH_PATH) as f:
frozen = json.load(f)["cases"]
groups = {}
for group, cases in GROUPS:
recs = []
def rec(name, inp, oracle_ret, expected, _r=recs):
entry = {"name": name, "input": inp, "return": oracle_ret,
"expected": expected}
if REPLAY:
old = next(c for c in frozen[group] 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 [{group}] {name} {key}")
print(" got: ", json.dumps(got, sort_keys=True, default=str)[:600])
print(" want:", json.dumps(want, sort_keys=True, default=str)[:600])
recs.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_failures = sum(1 for v in groups.values() for c in v if "diverged" in c)
failures = 0
checked = 0
for group, cases in frozen.items():
spec = CALLS.get(group)
if spec is None:
continue
_, unwrap, call = spec
for case in cases:
checked += 1
got = call(copy.deepcopy(case["input"]), tm_loader)
want = unwrap(case["expected"])
if canon(got) != canon(want):
failures += 1
print(f"KERNEL DIVERGE [{group}] {case['name']}")
print(" got: ", json.dumps(got, sort_keys=True)[:600])
print(" want:", json.dumps(want, sort_keys=True, default=str)[:600])
print(f"replay: facade {total - facade_failures}/{total}, "
f"kernel {checked - failures}/{checked}")
return 1 if (failures or facade_failures) else 0
doc = {
"frozen": "2026-10-12T12:00:00Z",
"oracle": "src/rune/testing_mode.py",
"pin": "RUNE_TM_CORE=python",
"note": "Frozen via scripts/gen_testing_mode_truth.py against the live "
"Python oracle; wall clock frozen at " + FROZEN_NOW +
" (FakeTime patched into testing_mode.time; epoch " + str(FROZEN_TS) +
"). Battery embeds this VERBATIM. Groups `forge_dry_run`, `load` "
"and `save` are facade-only (FILE IO + adapter call stay in Python).",
"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())
+397
View File
@@ -0,0 +1,397 @@
#!/usr/bin/env python3
"""Freeze the time_awareness truth corpus (dichotomy wave, #2479 tranche).
Drives the LIVE python oracle (src/rune/time_awareness.py) with the clock
pinned (fake datetime.now + fake monotonic — RUNE_TA_CORE=python is pinned
at the top, poisoned-truth law) and freezes every observation into
src/rust/time_awareness/src/frozen_ta_truth.json.
Coverage: ALL public functions and every branch —
* get_context: 14-point time_of_day × circadian grid (weekend + weekday),
update_interaction True/False, urgency branches (>24h, ==24h, >4h,
==4h, 0.1h..4h, ==0.1h, <0.1h, zero, future), save-debounce fire/no-fire
* parse_temporal_reference: all 10 patterns, priority order, word-boundary
positives/negatives, case, punctuation, unicode, whitespace runs
* get_status (read-only), flush, _mark_dirty, _save_if_dirty
* _load: valid aware/no-fraction/Z/offset/naive ISO, null, missing key,
corrupt JSON, non-dict JSON, invalid ISO (blanket except → None)
* module singleton fns (get_time_awareness/get_context/get_status/
parse_temporal_reference) + main() CLI — facade-level, live parity
Timestamps need NO masking: the clock is pinned, so every field
(current_time, dates, hours_since, last_interaction, last_save_time) is
deterministic and frozen verbatim.
"""
import json
import os
import shutil
import sys
from datetime import datetime, timedelta, timezone
_HERE = os.path.dirname(os.path.abspath(__file__))
_ROOT = os.path.dirname(_HERE)
sys.path.insert(0, os.path.join(_ROOT, "src"))
os.environ["RUNE_TA_CORE"] = "python" # poisoned-truth law: pin the fallback
os.environ["RUNE_MEMORY_DIR"] = "/tmp/ta_truth_memory"
import rune.time_awareness as ta # noqa: E402
EPOCH = datetime(1970, 1, 1, tzinfo=timezone.utc)
class _FakeNow:
"""Stands in for the module's `datetime` name — .now(tz) is pinned;
everything else delegates to the real class (fromisoformat in _load)."""
fixed = None
@staticmethod
def now(tz=None):
return _FakeNow.fixed
@staticmethod
def fromisoformat(s):
return datetime.fromisoformat(s)
class _FakeTime:
mono = 0.0
@staticmethod
def monotonic():
return _FakeTime.mono
ta.datetime = _FakeNow
ta._time = _FakeTime
def us_of(dt):
return (dt - EPOCH) // timedelta(microseconds=1)
def dt_of(us, micro=None):
dt = EPOCH + timedelta(microseconds=us)
return dt
def set_clock(op):
us = op.get("now_us")
_FakeNow.fixed = dt_of(us) if us is not None else _FakeNow.fixed
if "mono" in op:
_FakeTime.mono = op["mono"]
def iso_of(us):
return dt_of(us).isoformat()
BASE_US = us_of(datetime(2026, 10, 11, 12, 0, 0, 123456, tzinfo=timezone.utc))
HOUR_US = 3600 * 1_000_000
def _mem_file():
"""The singleton's default storage path, resolved at op time — other
tests may repoint RUNE_MEMORY_DIR mid-session (env hygiene leaks)."""
import memory_env
return os.path.join(memory_env.memory_dir(), "time_awareness.json")
def state_obs(instance):
return {
"last_interaction": instance.last_interaction.isoformat()
if instance.last_interaction
else None,
"dirty": instance._dirty,
"last_save_time": instance._last_save_time,
"file_exists": os.path.exists(instance.storage_path),
}
def file_obs(path):
if not os.path.exists(path):
return None
with open(path) as f:
raw = f.read()
try:
return json.loads(raw)
except ValueError: # corrupt content — deterministic sentinel, both pins
return {"__unparseable__": raw}
class Driver:
def __init__(self, workdir):
self.workdir = workdir
shutil.rmtree(workdir, ignore_errors=True)
os.makedirs(workdir, exist_ok=True)
self.instance = None
self.n = 0
def step(self, op):
kind = op["op"]
set_clock(op)
if kind == "new":
self.n += 1
path = os.path.join(self.workdir, f"sto_{self.n}.json")
seed = op.get("seed_file")
if seed is None:
if os.path.exists(path):
os.remove(path)
else:
with open(path, "w") as f:
f.write(seed)
self.instance = ta.TimeAwareness(storage_path=path)
return {"result": None, "state": state_obs(self.instance)}
if kind == "seed_last":
li = op.get("last_interaction")
self.instance.last_interaction = (
datetime.fromisoformat(li) if li is not None else None
)
return {"result": None, "state": state_obs(self.instance)}
if kind == "ctx":
update = op.get("update", True)
prev_ls = self.instance._last_save_time
try:
r = self.instance.get_context(update_interaction=update).to_dict()
except Exception as e: # noqa: BLE001 — the oracle raising IS the truth
return {
"result": {"error": type(e).__name__},
"state": state_obs(self.instance),
}
ob = {"result": r, "state": state_obs(self.instance)}
# exact _save_if_dirty decision: fired → the file obs carries it
if update and (op["mono"] - prev_ls >= ta.TimeAwareness._SAVE_DEBOUNCE_SECS):
ob["file"] = file_obs(self.instance.storage_path)
return ob
if kind == "status":
try:
r = self.instance.get_status()
except Exception as e: # noqa: BLE001 — naive last_interaction raises; that IS the truth
r = {"error": type(e).__name__}
return {
"result": r,
"state": state_obs(self.instance),
}
if kind == "parse":
return {
"result": self.instance.parse_temporal_reference(op["text"]),
"state": state_obs(self.instance),
}
if kind == "flush":
self.instance.flush()
return {
"result": None,
"state": state_obs(self.instance),
"file": file_obs(self.instance.storage_path),
}
if kind == "mark_dirty":
self.instance._mark_dirty()
return {"result": None, "state": state_obs(self.instance)}
if kind == "save_if_dirty":
self.instance._save_if_dirty()
return {
"result": None,
"state": state_obs(self.instance),
"file": file_obs(self.instance.storage_path),
}
if kind == "mod_ctx" or kind == "mod_status" or kind == "mod_parse":
mem_file = _mem_file()
seed = op.get("seed_file")
if seed is None:
if os.path.exists(mem_file):
os.remove(mem_file)
else:
with open(mem_file, "w") as f:
f.write(seed)
ta._time_awareness = None
if kind == "mod_ctx":
r = ta.get_context().to_dict()
elif kind == "mod_status":
r = ta.get_status()
else:
r = ta.parse_temporal_reference(op["text"])
inst = ta._time_awareness
ob = {"result": r, "state": state_obs(inst)}
if kind == "mod_ctx":
ob["file"] = file_obs(mem_file)
return ob
if kind == "cli":
import contextlib
import io
ta._time_awareness = self.instance
buf = io.StringIO()
old_argv = sys.argv
sys.argv = ["time_awareness.py"] + op.get("argv", [])
try:
with contextlib.redirect_stdout(buf):
ta.main()
finally:
sys.argv = old_argv
return {
"result": buf.getvalue(),
"state": state_obs(self.instance),
"file": file_obs(self.instance.storage_path),
}
raise ValueError(f"unknown op {kind}")
def run(self, ops):
return [self.step(op) for op in ops]
def ctx_case(now_us, update, seed=None, hour=None):
ops = [{"op": "new", "mono": 0.0}]
if seed is not None:
ops[0]["seed_file"] = seed
ops.append({"op": "ctx", "update": update, "now_us": now_us, "mono": 0.0})
return ops
def scenarios():
out = []
# S1 — time_of_day × circadian grid, weekend (2026-10-11 = Sunday) + weekday
ops = []
for h, m in [(4, 0), (5, 0), (6, 0), (8, 30), (9, 0), (11, 59), (12, 0),
(16, 59), (17, 0), (18, 0), (20, 59), (21, 0), (22, 0), (23, 59)]:
ops += [
{"op": "new", "mono": 0.0},
{"op": "ctx", "update": False,
"now_us": us_of(datetime(2026, 10, 11, h, m, 0, 0, tzinfo=timezone.utc)),
"mono": 0.0},
]
for h in (10, 13):
ops += [
{"op": "new", "mono": 0.0},
{"op": "ctx", "update": False,
"now_us": us_of(datetime(2026, 10, 13, h, 15, 0, 0, tzinfo=timezone.utc)),
"mono": 0.0},
]
out.append({"name": "tod_circadian_grid", "ops": ops})
# S2 — urgency branches (update=False, seeded last_interaction)
ops = []
offsets_h = [25.0, 24.0, 4.0 + 1.0 / 3600.0, 4.0, 2.0, 0.1, 5.0 / 60.0, 0.0, -1.0]
for i, oh in enumerate(offsets_h):
li_us = BASE_US - int(oh * HOUR_US)
seed = json.dumps(
{"last_interaction": iso_of(li_us)}, indent=2
) if i % 2 == 0 else None
ops += [{"op": "new", "mono": 0.0}]
if seed is None:
ops += [{"op": "seed_last", "last_interaction": iso_of(li_us), "mono": 0.0}]
ops += [{"op": "ctx", "update": False, "now_us": BASE_US, "mono": 0.0}]
out.append({"name": "urgency_branches", "ops": ops})
# S3 — parse_temporal_reference: every pattern + boundaries
texts = [
"yesterday", "today", "tomorrow", "last week", "next week",
"last month", "next month", "this morning", "this afternoon", "tonight",
"no temporal refs here", "YESTERDAY we said TODAY", "yesterdays news today",
"see you tomorrow.", "last week", "last\tweek", "lastweek",
"xlast weeky", "café yesterday", "yesterdayé",
"the last week of tomorrow", "next month and last month",
"Monday morning blues this morning", "tonight, tomorrow",
]
ops = [{"op": "new", "mono": 0.0}] + [
{"op": "parse", "text": t, "now_us": BASE_US, "mono": 0.0} for t in texts
]
out.append({"name": "parse_references", "ops": ops})
# S4 — save/debounce policy across a call sequence
out.append({
"name": "save_policy",
"ops": [
{"op": "new", "mono": 0.0},
{"op": "ctx", "update": True, "now_us": BASE_US, "mono": 0.0},
{"op": "ctx", "update": False, "now_us": BASE_US, "mono": 10.0},
{"op": "mark_dirty", "mono": 10.0},
{"op": "save_if_dirty", "mono": 20.0},
{"op": "save_if_dirty", "mono": 40.0},
{"op": "ctx", "update": True, "now_us": BASE_US + HOUR_US, "mono": 45.0},
{"op": "flush", "mono": 45.0},
{"op": "flush", "mono": 45.0},
{"op": "status", "now_us": BASE_US + HOUR_US, "mono": 45.0},
],
})
# S5 — _load file-seed matrix
seeds = [
'{"last_interaction": "2026-10-10T10:00:00.000000+00:00"}',
'{"last_interaction": "2026-10-10T10:00:00+00:00"}',
'{"last_interaction": null}',
"{}",
"corrupt{",
"[1, 2]",
'{"last_interaction": "garbage"}',
'{"last_interaction": "2026-10-10T10:00:00"}',
'{"last_interaction": "2026-10-10T05:00:00-05:00"}',
'{"last_interaction": "2026-10-10T10:00:00Z"}',
]
ops = []
for i, s in enumerate(seeds):
ops.append({"op": "new", "mono": 0.0, "seed_file": s})
if i in (0, 7, 8):
ops.append({"op": "ctx", "update": False, "now_us": BASE_US, "mono": 0.0})
out.append({"name": "load_matrix", "ops": ops})
# S6 — update=True sequence: debounce fire across calls, status, flush, parse
out.append({
"name": "update_sequence",
"ops": [
{"op": "new", "mono": 0.0},
{"op": "ctx", "update": True, "now_us": BASE_US, "mono": 0.0},
{"op": "ctx", "update": True, "now_us": BASE_US + HOUR_US, "mono": 40.0},
{"op": "status", "now_us": BASE_US + HOUR_US, "mono": 40.0},
{"op": "flush", "mono": 40.0},
{"op": "parse", "text": "yesterday", "now_us": BASE_US + HOUR_US, "mono": 40.0},
{"op": "seed_last", "last_interaction": None, "mono": 40.0},
{"op": "ctx", "update": True, "now_us": BASE_US + HOUR_US, "mono": 90.0},
],
})
# S7 — module singleton + CLI (facade-level; battery replays the kernel ops)
out.append({
"name": "module_and_cli",
"ops": [
{"op": "mod_ctx", "now_us": BASE_US, "mono": 0.0},
{"op": "mod_status", "now_us": BASE_US, "mono": 0.0},
{"op": "mod_parse", "text": "next week", "now_us": BASE_US, "mono": 0.0},
{"op": "mod_ctx", "now_us": BASE_US, "mono": 0.0,
"seed_file": '{"last_interaction": "2026-10-10T10:00:00+00:00"}'},
{"op": "new", "mono": 0.0},
{"op": "cli", "argv": ["status"], "now_us": BASE_US, "mono": 0.0},
{"op": "cli", "argv": ["context"], "now_us": BASE_US, "mono": 0.0},
{"op": "cli", "argv": ["bogus"], "now_us": BASE_US, "mono": 0.0},
{"op": "cli", "argv": [], "now_us": BASE_US, "mono": 0.0},
],
})
return out
def main():
shutil.rmtree("/tmp/ta_truth_memory", ignore_errors=True)
os.makedirs("/tmp/ta_truth_memory", exist_ok=True)
scenarios_doc = scenarios()
driver = Driver("/tmp/ta_truth_work")
for s in scenarios_doc:
s["obs"] = [driver.step(op) for op in s["ops"]]
doc = {
"module": "src/rune/time_awareness.py",
"pinned": "RUNE_TA_CORE=python (poisoned-truth law); clock pinned via fake datetime.now + monotonic",
"scenarios": scenarios_doc,
}
out_path = os.path.join(_ROOT, "src", "rust", "time_awareness", "src", "frozen_ta_truth.json")
os.makedirs(os.path.dirname(out_path), exist_ok=True)
with open(out_path, "w") as f:
json.dump(doc, f, indent=1)
f.write("\n")
n_obs = sum(len(s["obs"]) for s in scenarios_doc)
print(f"frozen {n_obs} observations across {len(scenarios_doc)} scenarios → {out_path}")
if __name__ == "__main__":
main()
+186
View File
@@ -0,0 +1,186 @@
#!/usr/bin/env python3
"""Freeze toolchain oracle truth for the Rust port battery (dichotomy
wave #2479 tranche, batch: toolchain — RUST side verdict).
Exercises EVERY public function across EVERY branch:
ToolchainConstraints (known/unknown key classification, get with and
without default, to_dict), DecisionRecord (list normalization: sorting
of libraries/decisive_constraints/evidence_refs, None-list defaults,
alternatives copy, confidence), CONSTRAINT_KEYS (frozen vocabulary),
_major_change (existing-language equality branches), resolve_toolchain
(INSUFFICIENT_EVIDENCE short path, KEEP_EXISTING with/without hard
factors and performance evidence, same-language request, CHANGE_MAJOR
with and without significant evidence, greenfield recommendation,
greenfield INSUFFICIENT_EVIDENCE, falsy existing_language edges).
Output: src/rust/toolchain/src/frozen_tc_truth.json — replayed VERBATIM
by src/rust/toolchain/src/branch_battery.rs.
The module is fully deterministic (no wall-clock fields) — every case
compares exactly, no masking.
"""
from __future__ import annotations
import json
import os
import sys
from pathlib import Path
os.environ["RUNE_TC_CORE"] = "python" # POISONED-TRUTH LAW: freeze the ORACLE, never the kernel
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
from rune import toolchain as tc # noqa: E402
cases: dict[str, list] = {}
def run(fn: str, name: str, *args, **kwargs):
out = getattr(tc, fn)(**kwargs) if args == () else getattr(tc, fn)(*args, **kwargs)
if hasattr(out, "to_dict"):
out = out.to_dict()
cases.setdefault(fn, []).append(
{"name": name, "input": kwargs if args == () else {"args": args, "kwargs": kwargs},
"expected": out})
# ── CONSTRAINT_KEYS: the frozen vocabulary ────────────────────────────
cases["constraint_keys"] = [{"name": "vocabulary",
"input": {},
"expected": sorted(tc.CONSTRAINT_KEYS)}]
# ── ToolchainConstraints: classification, get, to_dict ────────────────
for nm, kw in (
("known-only", dict(existing_language="python", target_platform="linux")),
("unknown-only", dict(wibble="future-thing", flurb=42)),
("mixed", dict(existing_language="rust", existing_runtime="tokio",
not_a_key=[1, 2], performance_sensitive=True, none_val=None)),
("empty", {}),
("every-known-key", {k: f"v-{k}" for k in tc.CONSTRAINT_KEYS}),
("falsy-values", dict(existing_language="", performance_sensitive=False,
memory_constrained=0)),
):
c = tc.ToolchainConstraints(**kw)
cases.setdefault("constraints", []).append(
{"name": nm, "input": {"kwargs": kw}, "expected": c.to_dict()})
for nm, kw, key, default, has_default in (
("get-known", dict(existing_language="python"), "existing_language", None, False),
("get-missing-no-default", dict(existing_language="python"), "existing_runtime", None, False),
("get-missing-with-default", dict(existing_language="python"), "existing_runtime", "", True),
("get-unknown-key-stored", dict(wibble="x"), "wibble", None, False),
("get-known-falsy-value", dict(existing_language=""), "existing_language", "dflt", True),
("get-default-none-explicit", dict(a="1"), "b", None, True),
):
c = tc.ToolchainConstraints(**kw)
val = c.get(key, default) if has_default else c.get(key)
cases.setdefault("constraints_get", []).append(
{"name": nm, "input": {"known": c.known, "key": key,
"default": default if has_default else None},
"expected": val})
# ── DecisionRecord: normalization branches ────────────────────────────
for nm, kw in (
("full-fields", dict(
selected_language="rust", runtime="tokio", framework="axum",
libraries=["zlib", "openssl", "a-lib"], build_system="cargo",
alternatives=[{"language": "go", "rejected_because": "no evidence"}],
decisive_constraints=["requested_language", "existing_language"],
rationale="measured benefit", evidence_refs=["bench-2", "bench-1"],
confidence=0.8, user_approval_required=True, resolution="CHANGE_MAJOR")),
("defaults", dict(selected_language="python")),
("none-lists-become-sorted-empty", dict(
selected_language="python", libraries=None, decisive_constraints=None,
evidence_refs=None, alternatives=None)),
("unsorted-normalized", dict(
selected_language="python", libraries=["b", "a", "c"],
decisive_constraints=["z", "a"], evidence_refs=["e2", "e1", "e0"],
confidence=0.85)),
("empty-lists-kept", dict(
selected_language="python", libraries=[], alternatives=[],
decisive_constraints=[], evidence_refs=[], resolution="KEEP_EXISTING")),
):
d = tc.DecisionRecord(**kw)
cases.setdefault("decision_record", []).append(
{"name": nm, "input": kw, "expected": d.to_dict()})
# ── _major_change ─────────────────────────────────────────────────────
for nm, kw, language in (
("different-language", dict(existing_language="python"), "rust"),
("same-language", dict(existing_language="python"), "python"),
("no-existing", {}, "rust"),
("falsy-existing", dict(existing_language=""), "rust"),
("non-str-existing", dict(existing_language=None), "rust"),
):
c = tc.ToolchainConstraints(**kw)
cases.setdefault("major_change", []).append(
{"name": nm, "input": {"known": c.known, "language": language},
"expected": tc._major_change(c, language)})
# ── resolve_toolchain: every branch ───────────────────────────────────
def res(nm, ckw, **kw):
c = tc.ToolchainConstraints(**ckw)
d = tc.resolve_toolchain(c, **kw)
cases.setdefault("resolve_toolchain", []).append(
{"name": nm,
"input": {"known": c.known, "unknown": c.unknown, **kw},
"expected": d.to_dict()})
# INSUFFICIENT short path: no existing, no request, <2 hard factors
res("insufficient-no-factors", dict(existing_runtime="tokio"))
res("insufficient-one-factor", dict(target_platform="linux"))
# falls through to greenfield INSUFFICIENT: >=2 hard factors, approval required
res("greenfield-insufficient-two-factors", dict(target_platform="linux", deployment="server"))
res("greenfield-insufficient-bools-count", dict(performance_sensitive=True, memory_constrained=True))
# KEEP_EXISTING branches
res("keep-existing-no-hard-factors", dict(existing_language="python"))
res("keep-existing-with-hard-factors", dict(existing_language="rust",
performance_sensitive=True))
res("keep-existing-with-runtime-framework", dict(
existing_language="typescript", existing_runtime="node",
existing_framework="svelte", target_platform="web"))
res("keep-existing-performance-evidence-rejected", dict(
existing_language="python", performance_sensitive=True),
performance_evidence={"significant_benefit": True, "basis": "measured 10x"})
res("keep-existing-empty-perf-evidence", dict(existing_language="python"),
performance_evidence={})
res("keep-existing-same-language-request", dict(existing_language="python"),
requested_language="python")
res("keep-existing-with-evidence-refs", dict(existing_language="go"),
evidence_refs=["ref-b", "ref-a", "ref-c"])
# CHANGE_MAJOR branches
res("change-major-no-evidence", dict(existing_language="python"),
requested_language="rust")
res("change-major-significant", dict(existing_language="python",
performance_sensitive=True),
requested_language="rust",
performance_evidence={"significant_benefit": True, "basis": "measured"})
res("change-major-falsy-significant", dict(existing_language="python"),
requested_language="rust",
performance_evidence={"significant_benefit": False})
res("change-major-null-significant", dict(existing_language="python"),
requested_language="zig", performance_evidence={"significant_benefit": None})
# greenfield recommendation
res("greenfield-requested", dict(target_platform="linux", deployment="server"),
requested_language="zig")
res("greenfield-requested-no-factors", dict(), requested_language="go")
# falsy existing_language edges: "" behaves as absent
res("falsy-existing-no-request", dict(existing_language=""))
res("falsy-existing-with-request", dict(existing_language=""),
requested_language="rust")
# unknown keys ride along but do not affect the decision
res("unknown-keys-ignored", dict(existing_language="python", wibble="x"),
requested_language="python")
truth = {
"frozen": "toolchain full oracle truth (dichotomy wave #2479)",
"oracle": "src/rune/toolchain.py",
"note": ("The module is fully deterministic — no wall-clock fields, "
"every case compares exactly. error = the exact Python "
"exception message where applicable."),
"cases": cases,
}
out = Path(__file__).resolve().parents[1] / "src" / "rust" / "toolchain" / "src" / "frozen_tc_truth.json"
out.write_text(json.dumps(truth, indent=1, sort_keys=True) + "\n")
total = sum(len(v) for v in cases.values())
print(f"frozen {total} cases across {len(cases)} functions -> {out}")
@@ -0,0 +1,46 @@
#!/usr/bin/env python3
"""Open the #2479 dichotomy-wave construction contract for capability_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(
"Port src/rune/capability_ledger.py to FULL Rust (dichotomy factory, "
"issue #2479 tranche): doc-envelope FFI, ctypes loader, facade "
"delegation with RUNE_LEDGER_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": "capability_ledger parity port — evidence attached at close "
"(SHAs reported in the builder report)",
}], scope_files=[
"src/rune/capability_ledger.py",
"src/rune/cl_loader.py",
"src/rust/capability_ledger/src/lib.rs",
"src/rust/capability_ledger/src/branch_battery.rs",
"scripts/gen_capability_ledger_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)")
@@ -0,0 +1,45 @@
#!/usr/bin/env python3
"""Open the #2479 dichotomy-wave construction contract for lexicon_evolution.
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/lexicon_evolution.py to FULL Rust (dichotomy factory, "
"issue #2479 tranche): doc-envelope FFI, ctypes loader, facade "
"delegation with RUNE_LE_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": "lexicon_evolution parity port — evidence attached at close "
"(SHAs reported in the builder report)",
}], scope_files=[
"src/rune/lexicon_evolution.py",
"src/rune/le_loader.py",
"src/rust/lexicon_evolution/src/lib.rs",
"src/rust/lexicon_evolution/src/branch_battery.rs",
"scripts/gen_lexicon_evolution_truth.py",
])
print("close:", json.dumps(c)[:400])
print("CONTRACT", cid, "closed" if c.get("success") else "CLOSE_FAILED")
+3
View File
@@ -0,0 +1,3 @@
module rune/auto_context
go 1.22
+864
View File
@@ -0,0 +1,864 @@
// Package main — the FULL GO port of src/rune/auto_context.py (#2479
// dichotomy tranche, GO side verdict). Every public Python function has a
// name-preserving counterpart: run_warm_router → runWarmRouter,
// run_memory_recall → runMemoryRecall, run_always_digest → runAlwaysDigest,
// load_active_tasks → loadActiveTasks, build_auto_context →
// buildAutoContext, save_context → saveContext, handle_hook → handleHook,
// and the module CLI main() (argparse-compatible: build/show/hook, --event,
// --data, --json; invalid choices exit 2). The PLUGIN manifest rides along
// as pluginManifest().
//
// IO (subprocess runs, memory paths, wall clock) stays behind injectable
// seams so the frozen truth battery replays deterministically: RunScript,
// MemoryDir, ScriptsDir, PythonBin and ClockNow are swappable vars, swapped
// per truth row by replay.go and restored after each row. The oracle's
// subprocess payloads (warm_router.py route/always, memory_store.py recall)
// arrive through the RunScript seam; the frozen truth pins the exact
// message/query strings the port hands to that seam.
package main
import (
"context"
"encoding/json"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"sync"
"time"
"unicode"
"unicode/utf8"
)
// ── configuration ────────────────────────────────────────────────────────
// stopwordSet — the oracle's recall stopword table, 1:1.
var stopwordSet = map[string]struct{}{
"what": {}, "when": {}, "where": {}, "which": {}, "that": {}, "this": {},
"with": {}, "from": {}, "have": {}, "been": {}, "were": {}, "they": {},
"their": {}, "about": {}, "would": {}, "could": {}, "should": {},
"will": {}, "into": {}, "your": {}, "just": {}, "like": {}, "know": {},
"think": {}, "also": {}, "back": {}, "then": {}, "than": {}, "more": {},
"some": {}, "only": {}, "very": {}, "does": {}, "done": {}, "make": {},
"made": {}, "want": {}, "need": {}, "give": {}, "tell": {}, "there": {},
"these": {}, "those": {}, "because": {}, "between": {}, "after": {},
"before": {}, "under": {}, "over": {}, "again": {}, "once": {},
"here": {}, "each": {}, "every": {}, "both": {}, "few": {}, "most": {},
"other": {}, "such": {}, "same": {}, "being": {}, "having": {},
}
// ── seams (swapped per truth row by replay.go) ───────────────────────────
// ExecResult — the slice of subprocess.run(capture_output=True, text=True)
// the oracle consumes: stdout text, return code, and a non-nil Err when the
// run failed to start, was killed, or hit its timeout (subprocess raised).
type ExecResult struct {
Stdout string
ReturnCode int
Err error
}
var (
// defaultPythonBin — memory_env.python_bin(): sys.executable of the
// parent process (same venv, portable by construction).
defaultPythonBin = func() string {
if p := os.Getenv("RUNE_PYTHON"); p != "" {
return p
}
return "python3"
}
// defaultScriptsDir — memory_env.scripts_dir() (RUNE_SCRIPTS_DIR).
defaultScriptsDir = func() string {
if d := os.Getenv("RUNE_SCRIPTS_DIR"); d != "" {
return d
}
return "src/rune"
}
// defaultMemoryDir — memory_env.memory_dir() (RUNE_MEMORY_DIR else
// RUNE_HOME/memory else the default rune home).
defaultMemoryDir = func() string {
if d := os.Getenv("RUNE_MEMORY_DIR"); d != "" {
return d
}
home := os.Getenv("RUNE_HOME")
if home == "" {
home = "/mnt/workdrive/rune"
}
return filepath.Join(home, "memory")
}
// defaultClockNow — datetime.now(timezone.utc); build_auto_context
// calls it once per build.
defaultClockNow = func() time.Time { return time.Now().UTC() }
// defaultRunScript — subprocess.run([python, script, ...], timeout=N):
// timeout expiry surfaces as Err (subprocess.TimeoutExpired), a
// nonzero exit keeps Stdout and sets ReturnCode.
defaultRunScript = func(exe string, args []string, timeout time.Duration) ExecResult {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
cmd := exec.CommandContext(ctx, exe, args...)
out, err := cmd.Output()
res := ExecResult{Stdout: string(out)}
if err != nil {
res.Err = err
if ee, ok := err.(*exec.ExitError); ok {
res.ReturnCode = ee.ExitCode()
} else {
res.ReturnCode = -1
}
}
return res
}
)
var (
PythonBin = defaultPythonBin
ScriptsDir = defaultScriptsDir
MemoryDir = defaultMemoryDir
)
// CommandRunner — the subprocess seam as an interface (Phase B): the
// runner is swappable in embedded callers and tests (fake runners,
// counting/pooled runners) without touching port code; the default
// implementation is compile-time asserted against the contract.
type CommandRunner interface {
Run(exe string, args []string, timeout time.Duration) ExecResult
}
// RunnerFunc adapts a plain function to CommandRunner.
type RunnerFunc func(exe string, args []string, timeout time.Duration) ExecResult
// Run satisfies CommandRunner.
func (f RunnerFunc) Run(exe string, args []string, timeout time.Duration) ExecResult {
return f(exe, args, timeout)
}
// compile-time: the default subprocess implementation satisfies the seam
var _ CommandRunner = RunnerFunc(defaultRunScript)
// Clock — the wall-clock seam as an interface (Phase B): build_auto_context
// reads it exactly once per build, like the oracle's datetime.now call.
type Clock interface {
Now() time.Time
}
// ClockFunc adapts a plain function to Clock.
type ClockFunc func() time.Time
// Now satisfies Clock.
func (f ClockFunc) Now() time.Time { return f() }
// compile-time: the default clock satisfies the seam
var _ Clock = ClockFunc(defaultClockNow)
// defaultRunner / defaultClock — the real implementations, kept as named
// seams so per-row swaps in replay.go can restore them.
var defaultRunner CommandRunner = RunnerFunc(defaultRunScript)
var defaultClock Clock = ClockFunc(defaultClockNow)
// Runner — the swapped-per-truth-row subprocess seam.
var Runner CommandRunner = defaultRunner
// ClockNow — the swapped-per-truth-row clock seam.
var ClockNow Clock = defaultClock
func warmRouterPath() string { return filepath.Join(ScriptsDir(), "warm_router.py") }
func memoryStorePath() string {
return filepath.Join(ScriptsDir(), "memory_store.py")
}
func contextFilePath() string {
return filepath.Join(MemoryDir(), "index", "auto_context.json")
}
func tasksFilePath() string {
return filepath.Join(MemoryDir(), "tasks", "persisted.json")
}
// isoformat — datetime.isoformat() on an aware UTC datetime: fractional
// part only when microseconds are nonzero, offset always "+00:00" shape.
func isoformat(t time.Time) string {
base := t.Format("2006-01-02T15:04:05")
if us := t.Nanosecond() / 1000; us != 0 {
base += fmt.Sprintf(".%06d", us)
}
return base + t.Format("-07:00")
}
// ── Python value semantics helpers ───────────────────────────────────────
// pyTruthy — Python truthiness for JSON-decoded values: None, False, 0,
// "" and every empty container are falsy.
func pyTruthy(v any) bool {
switch t := v.(type) {
case nil:
return false
case bool:
return t
case string:
return t != ""
case float64:
return t != 0
case json.Number:
f, _ := t.Float64()
return f != 0
case []any:
return len(t) > 0
case []*OrderedMap:
return len(t) > 0
case *OrderedMap:
return t != nil && t.Len() > 0
default:
return true
}
}
// pyTypeName — Python type() name for JSON-decoded values (integral
// floats decode as 'int', mirroring json.loads producing ints).
func pyTypeName(v any) string {
switch t := v.(type) {
case nil:
return "NoneType"
case bool:
return "bool"
case string:
return "str"
case float64:
if t == float64(int64(t)) {
return "int"
}
return "float"
case json.Number:
f, _ := t.Float64()
if f == float64(int64(f)) && !strings.ContainsAny(t.String(), ".eE") {
return "int"
}
return "float"
case []any, []*OrderedMap:
return "list"
case *OrderedMap:
return "dict"
default:
return "object"
}
}
// pyIsAlpha — str.isalpha(): non-empty and every rune a Unicode letter.
func pyIsAlpha(s string) bool {
if s == "" {
return false
}
for _, r := range s {
if !unicode.IsLetter(r) {
return false
}
}
return true
}
// runeSlice — Python's s[:n] (codepoint slice).
func runeSlice(s string, n int) string {
if utf8.RuneCountInString(s) <= n {
return s
}
return string([]rune(s)[:n])
}
func truncateRunes(s string, n int) string { return runeSlice(s, n) }
// pyParse — json.loads with order-preserving objects and json.Number
// numerals (so ints survive byte-parity re-encoding).
func pyParse(s string) (any, error) {
dec := json.NewDecoder(strings.NewReader(s))
dec.UseNumber()
return decodeJSONValue(dec)
}
// pythonRepr — repr() of a JSON-decoded value (the handle_hook
// str(data) fallback), Python quoting rules included.
func pythonRepr(v any) string {
switch t := v.(type) {
case nil:
return "None"
case bool:
if t {
return "True"
}
return "False"
case string:
return pythonReprString(t)
case json.Number:
if !strings.ContainsAny(t.String(), ".eE") {
return t.String()
}
f, _ := t.Float64()
return strconv.FormatFloat(f, 'g', -1, 64)
case float64:
return strconv.FormatFloat(t, 'g', -1, 64)
case []any:
parts := make([]string, len(t))
for i, item := range t {
parts[i] = pythonRepr(item)
}
return "[" + strings.Join(parts, ", ") + "]"
case *OrderedMap:
entries := t.Entries()
parts := make([]string, 0, len(entries))
for _, kv := range entries {
parts = append(parts, pythonReprString(kv.Key)+": "+pythonRepr(kv.Value))
}
return "{" + strings.Join(parts, ", ") + "}"
default:
return fmt.Sprintf("%v", t)
}
}
// pythonReprString — repr() of a str: single quotes preferred, double
// quotes when the string contains ' but not ", backslash and quote
// escapes, \n \r \t shortcuts.
func pythonReprString(s string) string {
escape := func(quote byte) string {
var sb strings.Builder
sb.WriteByte(quote)
for _, r := range s {
switch r {
case '\\':
sb.WriteString(`\\`)
case '\n':
sb.WriteString(`\n`)
case '\r':
sb.WriteString(`\r`)
case '\t':
sb.WriteString(`\t`)
case '\'':
if quote == '\'' {
sb.WriteString(`\'`)
} else {
sb.WriteByte('\'')
}
case '"':
if quote == '"' {
sb.WriteString(`\"`)
} else {
sb.WriteByte('"')
}
default:
sb.WriteRune(r)
}
}
sb.WriteByte(quote)
return sb.String()
}
if strings.Contains(s, "'") && !strings.Contains(s, "\"") {
return escape('"')
}
return escape('\'')
}
// ── the port (1:1 with the oracle) ───────────────────────────────────────
// runWarmRouter — run_warm_router: route the message through the warm
// router subprocess and return the fired-neurons payload; ANY failure
// (nonzero exit, timeout, bad JSON, empty stdout) degrades to None.
func runWarmRouter(message string) any {
res := Runner.Run(PythonBin(), []string{warmRouterPath(), "route", message, "--json"}, 10*time.Second)
if res.Err == nil && res.ReturnCode == 0 && strings.TrimSpace(res.Stdout) != "" {
if v, err := pyParse(res.Stdout); err == nil {
return v
}
}
return nil
}
// runMemoryRecall — run_memory_recall: recall relevant entries; a
// non-list payload degrades to [], a list is capped at budget entries.
func runMemoryRecall(query string, budget int) []any {
res := Runner.Run(PythonBin(), []string{memoryStorePath(), "recall", query, "--json"}, 15*time.Second)
if res.Err == nil && res.ReturnCode == 0 && strings.TrimSpace(res.Stdout) != "" {
if v, err := pyParse(res.Stdout); err == nil {
if list, ok := v.([]any); ok {
if budget >= 0 && budget < len(list) {
return list[:budget]
}
return list
}
}
}
return []any{}
}
// runAlwaysDigest — run_always_digest: the always-on digest payload or
// None on any failure.
func runAlwaysDigest() any {
res := Runner.Run(PythonBin(), []string{warmRouterPath(), "always", "--json"}, 10*time.Second)
if res.Err == nil && res.ReturnCode == 0 && strings.TrimSpace(res.Stdout) != "" {
if v, err := pyParse(res.Stdout); err == nil {
return v
}
}
return nil
}
// loadActiveTasks — load_active_tasks: read the persistence ledger and
// keep open/in_progress tasks; missing file, malformed JSON, a non-dict
// ledger, a non-dict task entry — every failure degrades to [].
func loadActiveTasks() []any {
raw, err := os.ReadFile(tasksFilePath())
if err != nil {
return []any{}
}
v, err := pyParse(string(raw))
if err != nil {
return []any{}
}
ledger, ok := v.(*OrderedMap)
if !ok {
return []any{}
}
tasks, ok := ledger.Get("tasks")
if !ok {
return []any{}
}
list, ok := tasks.([]any)
if !ok {
return []any{}
}
active := []any{}
for _, t := range list {
tom, ok := t.(*OrderedMap)
if !ok {
return []any{} // t.get on a non-dict raises → the oracle's except
}
status, _ := tom.Get2("status").(string)
if status == "open" || status == "in_progress" {
active = append(active, tom)
}
}
return active
}
// recallQueryWords — the oracle's key-term extraction: lowercase, split on
// whitespace, keep words longer than 4 chars that are pure alphabetic and
// not stopwords.
func recallQueryWords(message string) []string {
words := []string{}
for _, w := range strings.Fields(strings.ToLower(message)) {
if utf8.RuneCountInString(w) > 4 && pyIsAlpha(w) {
if _, stop := stopwordSet[w]; !stop {
words = append(words, w)
}
}
}
return words
}
// buildAutoContext — build_auto_context: warm neurons, active tasks,
// memory recall (substantive messages only), always-on digest.
//
// Phase B (language advantage): the oracle's four IO calls are mutually
// independent (pure reads of external state), so they fan out to
// goroutines — wall time is the slowest call, not the sum of four
// subprocesses. Results are assembled in the oracle's exact field order,
// so output is byte-identical to the serial Python semantics (the battery
// replays the frozen truth against THIS parallel path).
func buildAutoContext(message string) *OrderedMap {
context := NewOrderedMap(
KV{"timestamp", isoformat(ClockNow.Now().UTC())},
KV{"message_preview", runeSlice(message, 200)},
KV{"warm_neurons", nil},
KV{"memory_recall", nil},
KV{"always_digest", nil},
KV{"active_tasks", nil},
)
// cheap, CPU-only: key-term extraction (no IO) stays on the caller
// goroutine so the fan-out carries the exact query the oracle computes
query := ""
if message != "" && utf8.RuneCountInString(strings.TrimSpace(message)) > 10 {
words := recallQueryWords(message)
if len(words) > 0 {
n := 5
if len(words) < n {
n = len(words)
}
query = strings.Join(words[:n], " ")
}
}
var (
wg sync.WaitGroup
warm any
digest any
tasks []any
recall []any
)
launch := func(f func()) {
wg.Add(1)
go func() {
defer wg.Done()
f()
}()
}
launch(func() { warm = runWarmRouter(message) })
launch(func() { tasks = loadActiveTasks() })
if query != "" {
launch(func() { recall = runMemoryRecall(query, 5) })
}
launch(func() { digest = runAlwaysDigest() })
wg.Wait()
if pyTruthy(warm) {
context.Set("warm_neurons", warm)
}
if len(tasks) > 0 {
context.Set("active_tasks", tasks)
}
if len(recall) > 0 {
context.Set("memory_recall", recall)
}
if pyTruthy(digest) {
context.Set("always_digest", digest)
}
return context
}
// saveContext — save_context: atomic write (tmp file + rename) of the
// context JSON, indent=2, ensure_ascii.
//
// Phase B: the tmp file is per-writer (os.CreateTemp in the target dir),
// not the oracle's fixed CONTEXT_FILE+".tmp" — the rename semantics are
// identical, but concurrent hooks no longer clobber each other's tmp
// (the oracle's shared-tmp path races ENOENT under concurrency).
func saveContext(context *OrderedMap) error {
dir := filepath.Dir(contextFilePath())
if err := os.MkdirAll(dir, 0o777); err != nil {
return err
}
tmp, err := os.CreateTemp(dir, ".auto_context-*.tmp")
if err != nil {
return err
}
name := tmp.Name()
defer os.Remove(name) // no-op after a successful rename
if _, err := tmp.Write([]byte(encodePythonJSON(context, 0))); err != nil {
tmp.Close()
return err
}
if err := tmp.Close(); err != nil {
return err
}
if err := os.Chmod(name, 0o644); err != nil {
return err
}
return os.Rename(name, contextFilePath())
}
// handleHook — handle_hook: the on_message plugin hook. The truthiness
// guard, the message/text/str(data) fallback chain and the two nested
// count expressions reproduce the oracle exactly, including its
// best-effort error dicts (truncated to 120 runes).
func handleHook(event string, data *OrderedMap) *OrderedMap {
errorDict := func(msg string) *OrderedMap {
return NewOrderedMap(
KV{"status", "error"},
KV{"event", event},
KV{"error", truncateRunes(msg, 120)},
)
}
if event == "on_message" && pyTruthy(data) {
message := any("")
for _, key := range []string{"message", "text"} {
v, ok := data.Get(key)
if !ok {
v = ""
}
if pyTruthy(v) {
message = v
break
}
}
if !pyTruthy(message) {
message = pythonRepr(data)
}
msgStr, isStr := message.(string)
if !isStr {
return errorDict(fmt.Sprintf("'%s' object has no attribute 'strip'", pyTypeName(message)))
}
if msgStr != "" && utf8.RuneCountInString(strings.TrimSpace(msgStr)) > 2 {
context := buildAutoContext(msgStr)
if err := saveContext(context); err != nil {
return errorDict(err.Error())
}
neurons := context.Get2("warm_neurons")
recall := context.Get2("memory_recall")
tasks := context.Get2("active_tasks")
neuronsFired := 0
if pyTruthy(neurons) {
om, ok := neurons.(*OrderedMap)
if !ok {
return errorDict(fmt.Sprintf("'%s' object has no attribute 'get'", pyTypeName(neurons)))
}
fired, _ := om.Get("fired")
// .get("fired", []) — a MISSING key defaults to [], a
// present-but-None key does not.
if fired == nil {
if _, present := om.Get("fired"); !present {
fired = []any{}
}
}
n, err := pyLen(fired)
if err != nil {
return errorDict(err.Error())
}
neuronsFired = n
}
memoriesRecalled := 0
if pyTruthy(recall) {
if n, err := pyLen(recall); err != nil {
return errorDict(err.Error())
} else {
memoriesRecalled = n
}
}
activeTasks := 0
if pyTruthy(tasks) {
if n, err := pyLen(tasks); err != nil {
return errorDict(err.Error())
} else {
activeTasks = n
}
}
return NewOrderedMap(
KV{"status", "ok"},
KV{"event", event},
KV{"handler", "auto_context"},
KV{"neurons_fired", neuronsFired},
KV{"memories_recalled", memoriesRecalled},
KV{"active_tasks", activeTasks},
KV{"context_file", contextFilePath()},
)
}
}
return NewOrderedMap(
KV{"status", "ok"},
KV{"event", event},
KV{"handler", "auto_context"},
)
}
// pyLen — len() for JSON-decoded values; non-sized types raise TypeError
// in Python ("object of type 'int' has no len()").
func pyLen(v any) (int, error) {
switch t := v.(type) {
case nil:
return 0, fmt.Errorf("object of type 'NoneType' has no len()")
case string:
return utf8.RuneCountInString(t), nil
case []any:
return len(t), nil
case []*OrderedMap:
return len(t), nil
case *OrderedMap:
return t.Len(), nil
default:
return 0, fmt.Errorf("object of type '%s' has no len()", pyTypeName(v))
}
}
// pluginManifest — the module-level PLUGIN dict (microkernel discovery
// metadata), key order preserved.
func pluginManifest() *OrderedMap {
return NewOrderedMap(
KV{"name", "auto_context"},
KV{"version", "1.0.0"},
KV{"description", "Auto-context — automatic memory and warm neuron activation on every message."},
KV{"capabilities", []any{"context", "memory", "activation"}},
KV{"hooks", NewOrderedMap(KV{"on_message", "handle_hook"})},
KV{"dependencies", []any{"memory_store", "warm_router"}},
KV{"provides", []any{"auto_context"}},
KV{"priority", 5},
KV{"auto_activate", true},
)
}
// ── CLI (auto_context.py main(), byte-parity stdout) ─────────────────────
func argparseUsage(w io.Writer) {
fmt.Fprintln(w, "usage: auto_context.py [-h] [--event EVENT] [--data DATA] [--json] {build,show,hook} [arg]")
}
// RunCLI executes auto_context.py main() against argv (program name at
// index 0) and returns the process exit code. argparse failures print
// usage to stderr and exit 2.
func RunCLI(argv []string, stdout io.Writer) int {
stderr := os.Stderr
var positionals []string
event, hasEvent := "", false
dataStr, hasData := "", false
jsonFlag := false
i := 1
for i < len(argv) {
a := argv[i]
switch {
case a == "--json":
jsonFlag = true
case a == "--event":
i++
if i >= len(argv) {
fmt.Fprintf(stderr, "auto_context.py: error: argument --event: expected one argument\n")
argparseUsage(stderr)
return 2
}
event, hasEvent = argv[i], true
case strings.HasPrefix(a, "--event="):
event, hasEvent = strings.TrimPrefix(a, "--event="), true
case a == "--data":
i++
if i >= len(argv) {
fmt.Fprintf(stderr, "auto_context.py: error: argument --data: expected one argument\n")
argparseUsage(stderr)
return 2
}
dataStr, hasData = argv[i], true
case strings.HasPrefix(a, "--data="):
dataStr, hasData = strings.TrimPrefix(a, "--data="), true
case a == "-h" || a == "--help":
argparseUsage(stdout)
return 0
case strings.HasPrefix(a, "-") && a != "-":
fmt.Fprintf(stderr, "auto_context.py: error: unrecognized arguments: %s\n", a)
argparseUsage(stderr)
return 2
default:
positionals = append(positionals, a)
}
i++
}
if len(positionals) == 0 {
fmt.Fprintf(stderr, "auto_context.py: error: the following arguments are required: cmd\n")
argparseUsage(stderr)
return 2
}
if len(positionals) > 2 {
fmt.Fprintf(stderr, "auto_context.py: error: unrecognized arguments: %s\n", strings.Join(positionals[2:], " "))
argparseUsage(stderr)
return 2
}
cmd, arg := positionals[0], ""
if len(positionals) == 2 {
arg = positionals[1]
}
switch cmd {
case "build":
message := arg
if message == "" {
message = "general context"
}
context := buildAutoContext(message)
if err := saveContext(context); err != nil {
fmt.Fprintf(stderr, "Traceback (most recent call last): %v\n", err)
return 1
}
if jsonFlag {
fmt.Fprintln(stdout, encodePythonJSON(context, 0))
} else {
n := 0
if warm := context.Get2("warm_neurons"); pyTruthy(warm) {
if om, ok := warm.(*OrderedMap); ok {
fired, _ := om.Get("fired")
if fired == nil {
if _, present := om.Get("fired"); !present {
fired = []any{}
}
}
if c, err := pyLen(fired); err == nil {
n = c
}
}
}
m := 0
if recall, ok := context.Get2("memory_recall").([]any); ok {
m = len(recall)
}
fmt.Fprintf(stdout, "Auto-context built: %d neurons, %d memories\n", n, m)
}
case "show":
raw, err := os.ReadFile(contextFilePath())
if err != nil {
fmt.Fprintln(stdout, "No auto-context file found")
return 0
}
v, err := pyParse(string(raw))
if err != nil {
fmt.Fprintf(stderr, "Traceback (most recent call last): %v\n", err)
return 1
}
fmt.Fprintln(stdout, encodePythonJSON(v, 0))
case "hook":
event2 := "unknown"
if hasEvent && event != "" {
event2 = event
}
var data *OrderedMap
if hasData && dataStr != "" {
v, err := pyParse(dataStr)
if err != nil {
fmt.Fprintf(stderr, "Traceback (most recent call last): %v\n", err)
return 1
}
if om, ok := v.(*OrderedMap); ok {
data = om
} else {
// json.loads gave a non-object; data.get would raise
// AttributeError outside handle_hook's try
fmt.Fprintf(stderr, "Traceback (most recent call last): '%s' object has no attribute 'get'\n", pyTypeName(v))
return 1
}
}
result := handleHook(event2, data)
fmt.Fprintln(stdout, encodePythonJSONCompact(result))
default:
fmt.Fprintf(stderr, "auto_context.py: error: argument cmd: invalid choice: '%s' (choose from 'build', 'show', 'hook')\n", cmd)
argparseUsage(stderr)
return 2
}
return 0
}
// ── entry point: CLI + truth replay subcommand for the HARD CI gate ─────
func truthRowCount(path string) int {
raw, err := os.ReadFile(path)
if err != nil {
return -1
}
var doc TruthDoc
if err := json.Unmarshal(raw, &doc); err != nil {
return -1
}
return len(doc.Rows)
}
func main() {
if len(os.Args) >= 2 && os.Args[1] == "replay" {
path := "truth.json"
if len(os.Args) >= 3 {
path = os.Args[2]
}
fails := RunTruth(path)
if len(fails) > 0 {
for _, f := range fails {
fmt.Fprintln(os.Stderr, f)
}
fmt.Fprintf(os.Stderr, "PARITY FAILED: %d divergence(s)\n", len(fails))
os.Exit(1)
}
fmt.Printf("PARITY OK: %d truth rows exact-equal vs the Python oracle\n", truthRowCount(path))
return
}
os.Exit(RunCLI(os.Args, os.Stdout))
}
+133
View File
@@ -0,0 +1,133 @@
package main
// Phase B battery — pins the language-advantage guarantees: the subprocess
// and clock seams are interfaces (compile-time asserted in main.go),
// build_auto_context fans the oracle's four independent IO calls out to
// goroutines with byte-identical results, and concurrent builds are
// race-clean (run under -race in CI).
import (
"os"
"path/filepath"
"sync"
"testing"
"time"
)
// countingRunner — a CommandRunner decorator that counts invocations: the
// seam swaps in without touching port code.
type countingRunner struct {
mu sync.Mutex
inner CommandRunner
count int
}
func (c *countingRunner) Run(exe string, args []string, timeout time.Duration) ExecResult {
c.mu.Lock()
c.count++
c.mu.Unlock()
return c.inner.Run(exe, args, timeout)
}
// swapSeams — install the frozen seams for one test; restore on cleanup.
func swapSeams(t *testing.T, routeMode, alwaysMode, recallMode string) string {
t.Helper()
home := t.TempDir()
memdir := filepath.Join(home, "memory")
if err := os.MkdirAll(memdir, 0o777); err != nil {
t.Fatal(err)
}
MemoryDir = func() string { return memdir }
ScriptsDir = func() string { return "/tmp/actx_stubs" }
PythonBin = func() string { return "python3" }
Runner = buildFakeRunner(routeMode, alwaysMode, recallMode)
base, _ := time.Parse(time.RFC3339, "2026-10-11T12:00:00.123456+00:00")
ClockNow = ClockFunc(func() time.Time { return base })
t.Cleanup(func() {
MemoryDir = defaultMemoryDir
ScriptsDir = defaultScriptsDir
PythonBin = defaultPythonBin
Runner = defaultRunner
ClockNow = defaultClock
})
return memdir
}
// TestPhaseBParallelBuildParity — the frozen build truth replays green
// against the GOROUTINE fan-out path (results assembled in oracle order
// are byte-identical to the serial Python semantics).
func TestPhaseBParallelBuildParity(t *testing.T) {
swapSeams(t, "ok", "ok", "ok")
doc := loadTruth(t)
for _, name := range []string{"build:full", "build:unicode-preview", "build:stopwords", "build:short-message"} {
row := findRow(t, name)
if msg := compareOne(doc, &row); msg != "" {
t.Fatalf("parallel build diverged on %s: %s", name, msg)
}
}
}
// TestPhaseBRunnerSeamIsAnInterface — a counting decorator rides the
// CommandRunner seam; a full build issues exactly 3 subprocess runs (route,
// recall, always — the ledger read is file IO, not a subprocess).
func TestPhaseBRunnerSeamIsAnInterface(t *testing.T) {
swapSeams(t, "ok", "ok", "ok")
counter := &countingRunner{inner: Runner}
Runner = counter
ctx := buildAutoContext("remember the config parser override discussion tests")
if counter.count != 3 {
t.Fatalf("expected 3 subprocess runs per build, got %d", counter.count)
}
if warm := ctx.Get2("warm_neurons"); !pyTruthy(warm) {
t.Fatal("warm payload lost through the decorated seam")
}
}
// TestPhaseBConcurrentBuilds — 8 goroutines building contexts through the
// shared fake runner must all agree (and stay race-clean).
func TestPhaseBConcurrentBuilds(t *testing.T) {
swapSeams(t, "ok", "ok", "ok")
const n = 8
results := make([]string, n)
var wg sync.WaitGroup
for i := 0; i < n; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
results[i] = mustJSON(buildAutoContext("remember the config parser override discussion tests"))
}(i)
}
wg.Wait()
for i := 1; i < n; i++ {
if results[i] != results[0] {
t.Fatalf("concurrent build %d diverged:\n first: %s\n got: %s", i, results[0], results[i])
}
}
}
// TestPhaseBConcurrentHooks — concurrent handle_hook calls write the
// context file through per-writer tmp + atomic rename: every rename
// succeeds (no shared-tmp ENOENT race, which the oracle's fixed
// CONTEXT_FILE+".tmp" would hit) and every reader sees complete JSON.
func TestPhaseBConcurrentHooks(t *testing.T) {
memdir := swapSeams(t, "ok", "ok", "ok")
data := NewOrderedMap(KV{"message", "remember the config parser override discussion tests"})
var wg sync.WaitGroup
for i := 0; i < 8; i++ {
wg.Add(1)
go func() {
defer wg.Done()
if r := handleHook("on_message", data); r.Get2("status") != "ok" {
t.Errorf("hook failed: %v", mustJSON(r))
}
}()
}
wg.Wait()
raw, err := os.ReadFile(filepath.Join(memdir, "index", "auto_context.json"))
if err != nil {
t.Fatal(err)
}
if _, err := pyParse(string(raw)); err != nil {
t.Fatalf("context file torn or invalid: %v", err)
}
}
+363
View File
@@ -0,0 +1,363 @@
// Python json interop: insertion-ordered maps and json.dumps-compatible
// encoding (indent=2 and the default compact (", ", ": ") separators,
// ensure_ascii=True) so save_context, show and hook CLI output are
// byte-parity with the oracle (adapted from the message_queue port's
// pyjson.go; the float renderer follows Python repr, which keeps ".0" on
// integral floats).
package main
import (
"encoding/json"
"fmt"
"strconv"
"strings"
"unicode/utf16"
)
// KV — one ordered key/value pair.
type KV struct {
Key string
Value any
}
// OrderedMap — a dict with preserved insertion order.
type OrderedMap struct {
Keys []string
Vals map[string]any
}
// NewOrderedMap builds an ordered map from pairs.
func NewOrderedMap(pairs ...KV) *OrderedMap {
om := &OrderedMap{Vals: map[string]any{}}
for _, kv := range pairs {
om.Set(kv.Key, kv.Value)
}
return om
}
// OrderedMapFromMap wraps a plain map (order unspecified — canonical
// comparisons sort keys anyway).
func OrderedMapFromMap(m map[string]any) *OrderedMap {
om := &OrderedMap{Vals: map[string]any{}}
for k, v := range m {
om.Set(k, v)
}
return om
}
// Set inserts or replaces, preserving first-insertion order.
func (o *OrderedMap) Set(key string, value any) {
if _, ok := o.Vals[key]; !ok {
o.Keys = append(o.Keys, key)
}
o.Vals[key] = value
}
// Get retrieves a value.
func (o *OrderedMap) Get(key string) (any, bool) {
v, ok := o.Vals[key]
return v, ok
}
// Get2 returns the value at key (nil when absent) for direct type asserts.
func (o *OrderedMap) Get2(key string) any {
return o.Vals[key]
}
// Entries returns key/value pairs in insertion order.
func (o *OrderedMap) Entries() []KV {
out := make([]KV, 0, len(o.Keys))
for _, k := range o.Keys {
out = append(out, KV{k, o.Vals[k]})
}
return out
}
// Len returns the number of entries.
func (o *OrderedMap) Len() int { return len(o.Keys) }
// decodeJSONValue decodes one JSON value from the decoder, decoding objects
// as *OrderedMap (order-preserving) and numbers as float64.
func decodeJSONValue(dec *json.Decoder) (any, error) {
tok, err := dec.Token()
if err != nil {
return nil, err
}
return decodeFromToken(dec, tok)
}
func decodeFromToken(dec *json.Decoder, tok json.Token) (any, error) {
switch t := tok.(type) {
case json.Delim:
switch t {
case '{':
om := &OrderedMap{Vals: map[string]any{}}
for dec.More() {
keyTok, err := dec.Token()
if err != nil {
return nil, err
}
key, _ := keyTok.(string)
val, err := decodeJSONValue(dec)
if err != nil {
return nil, err
}
om.Set(key, val)
}
if _, err := dec.Token(); err != nil { // consume '}'
return nil, err
}
return om, nil
case '[':
arr := []any{}
for dec.More() {
val, err := decodeJSONValue(dec)
if err != nil {
return nil, err
}
arr = append(arr, val)
}
if _, err := dec.Token(); err != nil { // consume ']'
return nil, err
}
return arr, nil
}
return nil, fmt.Errorf("unexpected delim %v", t)
default:
return tok, nil
}
}
// encodePythonJSON renders v exactly like Python json.dumps(v, indent=N)
// with default ensure_ascii=True and (',', ': ') separators.
func encodePythonJSON(v any, indent int) string {
var sb strings.Builder
encodeValue(&sb, v, indent)
return sb.String()
}
func encodeValue(sb *strings.Builder, v any, indent int) {
switch t := v.(type) {
case nil:
sb.WriteString("null")
case bool:
if t {
sb.WriteString("true")
} else {
sb.WriteString("false")
}
case string:
sb.WriteString(pythonEscapeString(t))
case int:
sb.WriteString(strconv.Itoa(t))
case int64:
sb.WriteString(strconv.FormatInt(t, 10))
case float64:
sb.WriteString(pythonFloat(t))
case json.Number:
sb.WriteString(t.String())
case []any:
if len(t) == 0 {
sb.WriteString("[]")
return
}
sb.WriteString("[\n")
for i, item := range t {
writeIndent(sb, indent+1)
encodeValue(sb, item, indent+1)
if i < len(t)-1 {
sb.WriteString(",")
}
sb.WriteString("\n")
}
writeIndent(sb, indent)
sb.WriteString("]")
case []*OrderedMap:
if len(t) == 0 {
sb.WriteString("[]")
return
}
sb.WriteString("[\n")
for i, item := range t {
writeIndent(sb, indent+1)
encodeValue(sb, item, indent+1)
if i < len(t)-1 {
sb.WriteString(",")
}
sb.WriteString("\n")
}
writeIndent(sb, indent)
sb.WriteString("]")
case []string:
if len(t) == 0 {
sb.WriteString("[]")
return
}
sb.WriteString("[\n")
for i, item := range t {
writeIndent(sb, indent+1)
encodeValue(sb, item, indent+1)
if i < len(t)-1 {
sb.WriteString(",")
}
sb.WriteString("\n")
}
writeIndent(sb, indent)
sb.WriteString("]")
case *OrderedMap:
if t.Len() == 0 {
sb.WriteString("{}")
return
}
sb.WriteString("{\n")
entries := t.Entries()
for i, kv := range entries {
writeIndent(sb, indent+1)
sb.WriteString(pythonEscapeString(kv.Key))
sb.WriteString(": ")
encodeValue(sb, kv.Value, indent+1)
if i < len(entries)-1 {
sb.WriteString(",")
}
sb.WriteString("\n")
}
writeIndent(sb, indent)
sb.WriteString("}")
case map[string]any:
encodeValue(sb, OrderedMapFromMap(t), indent)
default:
sb.WriteString(pythonEscapeString(fmt.Sprintf("%v", t)))
}
}
func writeIndent(sb *strings.Builder, level int) {
for i := 0; i < level; i++ {
sb.WriteString(" ")
}
}
// encodePythonJSONCompact renders v exactly like Python json.dumps(v) with
// default separators (", ", ": ") and ensure_ascii=True — the compact form
// the CLI hook path prints.
func encodePythonJSONCompact(v any) string {
var sb strings.Builder
encodeCompactValue(&sb, v)
return sb.String()
}
func encodeCompactValue(sb *strings.Builder, v any) {
switch t := v.(type) {
case nil:
sb.WriteString("null")
case bool:
if t {
sb.WriteString("true")
} else {
sb.WriteString("false")
}
case string:
sb.WriteString(pythonEscapeString(t))
case int:
sb.WriteString(strconv.Itoa(t))
case int64:
sb.WriteString(strconv.FormatInt(t, 10))
case float64:
sb.WriteString(pythonFloat(t))
case json.Number:
sb.WriteString(t.String())
case []any:
sb.WriteString("[")
for i, item := range t {
if i > 0 {
sb.WriteString(", ")
}
encodeCompactValue(sb, item)
}
sb.WriteString("]")
case []*OrderedMap:
sb.WriteString("[")
for i, item := range t {
if i > 0 {
sb.WriteString(", ")
}
encodeCompactValue(sb, item)
}
sb.WriteString("]")
case []string:
sb.WriteString("[")
for i, item := range t {
if i > 0 {
sb.WriteString(", ")
}
encodeCompactValue(sb, item)
}
sb.WriteString("]")
case *OrderedMap:
sb.WriteString("{")
entries := t.Entries()
for i, kv := range entries {
if i > 0 {
sb.WriteString(", ")
}
sb.WriteString(pythonEscapeString(kv.Key))
sb.WriteString(": ")
encodeCompactValue(sb, kv.Value)
}
sb.WriteString("}")
case map[string]any:
encodeCompactValue(sb, OrderedMapFromMap(t))
default:
sb.WriteString(pythonEscapeString(fmt.Sprintf("%v", t)))
}
}
// pythonFloat renders a float like Python repr (json.dumps uses
// float.__repr__): shortest round-trip digits, and integral floats keep
// one decimal place (1.0 stays "1.0", not "1").
func pythonFloat(f float64) string {
s := strconv.FormatFloat(f, 'g', -1, 64)
if !strings.ContainsAny(s, ".eE") &&
!strings.Contains(s, "Inf") && !strings.Contains(s, "NaN") {
s += ".0"
}
return s
}
// pythonEscapeString — Python json.dumps string escaping with
// ensure_ascii=True: control chars via \uXXXX (with \b \f \n \r \t
// shortcuts), non-ASCII via \uXXXX / surrogate pairs, lowercase hex.
func pythonEscapeString(s string) string {
var sb strings.Builder
sb.WriteByte('"')
for _, r := range s {
switch r {
case '"':
sb.WriteString(`\"`)
case '\\':
sb.WriteString(`\\`)
case '\n':
sb.WriteString(`\n`)
case '\r':
sb.WriteString(`\r`)
case '\t':
sb.WriteString(`\t`)
case '\b':
sb.WriteString(`\b`)
case '\f':
sb.WriteString(`\f`)
default:
if r < 0x20 || r > 0x7e {
if r > 0xffff {
r1, r2 := utf16.EncodeRune(r)
fmt.Fprintf(&sb, `\u%04x\u%04x`, r1, r2)
} else {
fmt.Fprintf(&sb, `\u%04x`, r)
}
} else {
sb.WriteRune(r)
}
}
}
sb.WriteByte('"')
return sb.String()
}
+475
View File
@@ -0,0 +1,475 @@
// Replay — the frozen-truth parity engine. Reads truth.json (generated by
// /tmp/gen_auto_context_truth.py against the live Python oracle with the
// clock frozen and env-driven stub scripts pinned) and re-executes every
// row through this Go port, comparing results. Exact rows (context-file
// bytes, CLI stdout) compare byte-for-byte; everything else compares
// canonically (sorted-key JSON). Absolute memory-dir paths are normalized
// to @MEMDIR@ on both sides.
package main
import (
"bytes"
"encoding/json"
"fmt"
"os"
"path/filepath"
"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"`
Setup json.RawMessage `json:"setup"`
Want json.RawMessage `json:"want"`
}
// RunTruth replays every row and returns a divergence report (empty on 100%).
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 _, row := range doc.Rows {
if msg := compareOne(&doc, &row); msg != "" {
fails = append(fails, row.Name+": "+msg)
}
}
return fails
}
// 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, exact, err := execRow(doc, row)
if err != nil {
return fmt.Sprintf("error: %v", err)
}
var wantStr, gotStr string
if exact {
var w string
if err := json.Unmarshal(row.Want, &w); err != nil {
return fmt.Sprintf("want not a string: %v", err)
}
wantStr, gotStr = w, got.(string)
} else {
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 ""
}
// execRow executes one truth row: returns got (any) and, for exact rows,
// the exact string in got.
func execRow(doc *TruthDoc, row *TruthRow) (got any, exact bool, err error) {
setup := NewOrderedMap()
if len(row.Setup) > 0 {
dec := json.NewDecoder(bytes.NewReader(row.Setup))
dec.UseNumber()
tok, err := dec.Token()
if err != nil {
return nil, false, err
}
if d, ok := tok.(json.Delim); !ok || d != '{' {
return nil, false, fmt.Errorf("setup not an object")
}
v, err := decodeFromToken(dec, tok)
if err != nil {
return nil, false, err
}
setup = v.(*OrderedMap)
}
// freeze the seams per row: fresh temp memory dir, frozen clock,
// env-driven fake runner mirroring /tmp/actx_stubs payloads
home, err := os.MkdirTemp("", "actx_truth_row_")
if err != nil {
return nil, false, err
}
defer os.RemoveAll(home)
defer func() {
MemoryDir = defaultMemoryDir
Runner = defaultRunner
ClockNow = defaultClock
PythonBin = defaultPythonBin
ScriptsDir = defaultScriptsDir
}()
memdir := filepath.Join(home, "memory")
if err := os.MkdirAll(memdir, 0o777); err != nil {
return nil, false, err
}
MemoryDir = func() string { return memdir }
ScriptsDir = func() string { return "/tmp/actx_stubs" }
PythonBin = func() string { return "python3" }
base, err := time.Parse(time.RFC3339, doc.FrozenTS)
if err != nil {
return nil, false, err
}
ClockNow = ClockFunc(func() time.Time { return base })
// file seeds from setup
if f, ok := setup.Get("tasks_file"); ok {
s, _ := f.(string)
if err := os.MkdirAll(filepath.Join(memdir, "tasks"), 0o777); err != nil {
return nil, false, err
}
if err := os.WriteFile(filepath.Join(memdir, "tasks", "persisted.json"), []byte(s), 0o644); err != nil {
return nil, false, err
}
}
if f, ok := setup.Get("seed_context"); ok {
s, _ := f.(string)
if err := os.MkdirAll(filepath.Join(memdir, "index"), 0o777); err != nil {
return nil, false, err
}
if err := os.WriteFile(filepath.Join(memdir, "index", "auto_context.json"), []byte(s), 0o644); err != nil {
return nil, false, err
}
}
// stub modes (default "ok", like the stub scripts' env defaults)
mode := func(key string) string {
if v, ok := setup.Get(key); ok {
if s, ok2 := v.(string); ok2 {
return s
}
}
return "ok"
}
Runner = buildFakeRunner(mode("stub_route"), mode("stub_always"), mode("stub_recall"))
scrub := func(v any) any {
return scrubPaths(v, memdir)
}
strSetup := func(key string) string {
s, _ := setup.Get2(key).(string)
return s
}
switch row.Op {
case "warm_router":
return scrub(runWarmRouter(strSetup("message"))), false, nil
case "recall":
budget := 5
if b, ok := setup.Get("budget"); ok {
switch n := b.(type) {
case float64:
budget = int(n)
case json.Number:
f, _ := n.Float64()
budget = int(f)
}
}
return scrub(runMemoryRecall(strSetup("query"), budget)), false, nil
case "always":
return scrub(runAlwaysDigest()), false, nil
case "load_tasks":
return scrub(loadActiveTasks()), false, nil
case "build":
return scrub(buildAutoContext(strSetup("message"))), false, nil
case "save":
om, ok := setup.Get2("context").(*OrderedMap)
if !ok {
return nil, false, fmt.Errorf("save: no context")
}
if err := saveContext(om); err != nil {
return nil, false, err
}
raw, err := os.ReadFile(contextFilePath())
if err != nil {
return nil, false, err
}
reads := []any{scrubPaths(string(raw), memdir)}
if om2, ok2 := setup.Get2("context2").(*OrderedMap); ok2 {
// overwrite semantics: second save replaces the first atomically
if err := saveContext(om2); err != nil {
return nil, false, err
}
raw2, err := os.ReadFile(contextFilePath())
if err != nil {
return nil, false, err
}
reads = append(reads, scrubPaths(string(raw2), memdir))
}
_, statErr := os.Stat(contextFilePath() + ".tmp")
return NewOrderedMap(
KV{"reads", reads},
KV{"tmp_gone", os.IsNotExist(statErr)},
), false, nil
case "hook":
event := strSetup("event")
var data *OrderedMap
if v, ok := setup.Get("data"); ok && v != nil {
om, ok2 := v.(*OrderedMap)
if !ok2 {
return nil, false, fmt.Errorf("hook: data not an object")
}
data = om
}
return scrub(handleHook(event, data)), false, nil
case "cli":
argvAny, _ := setup.Get2("argv").([]any)
argv := []string{}
for _, a := range argvAny {
s, _ := a.(string)
argv = append(argv, s)
}
var sb strings.Builder
code := RunCLI(argv, &sb)
out := strings.ReplaceAll(sb.String(), memdir, "@MEMDIR@")
return NewOrderedMap(KV{"stdout", out}, KV{"exit", code}), false, nil
case "plugin":
return pluginManifest(), false, nil
}
return nil, false, fmt.Errorf("unknown row op %s (%s)", row.Op, row.Name)
}
// buildFakeRunner — the env-driven stub scripts' behavior as a
// CommandRunner (mirrors /tmp/actx_stubs byte-for-byte): payloads ECHO the
// caller's message/query so the frozen truth pins the exact strings the
// port hands to the subprocess seam. Reused by the Phase B battery.
func buildFakeRunner(routeMode, alwaysMode, recallMode string) RunnerFunc {
return func(exe string, args []string, timeout time.Duration) ExecResult {
var out string
code := 0
base := filepath.Base(args[0])
if base == "warm_router.py" {
if len(args) > 1 && args[1] == "always" {
switch alwaysMode {
case "ok":
out = encodePythonJSONCompact(NewOrderedMap(
KV{"digest", "daily-summary"}, KV{"items", 7},
KV{"topics", []any{"memory", "tasks", "recall"}}))
case "fail":
code = 1
case "badjson":
out = "not-json{{{"
case "empty":
// print nothing
}
} else {
msg := ""
if len(args) > 2 {
msg = args[2]
}
switch routeMode {
case "ok":
out = encodePythonJSONCompact(NewOrderedMap(
KV{"echo_message", msg},
KV{"fired", []any{"warm-alpha", "warm-beta", "warm-gamma"}},
KV{"matched", 3}, KV{"route", "focus"}))
case "list":
out = "[1, 2]"
case "emptylist":
out = "[]"
case "firedint":
out = encodePythonJSONCompact(NewOrderedMap(
KV{"fired", 3}, KV{"echo_message", msg}))
case "fail":
code = 1
case "badjson":
out = "not-json{{{"
case "empty":
// print nothing
}
}
} else {
query := ""
if len(args) > 2 {
query = args[2]
}
switch recallMode {
case "ok":
items := []any{NewOrderedMap(KV{"echo_query", query})}
for _, e := range recallEntries {
items = append(items, e)
}
out = encodePythonJSONCompact(items)
case "nonlist":
out = encodePythonJSONCompact(NewOrderedMap(
KV{"query", query}, KV{"note", "not a list"}))
case "fail":
code = 1
case "badjson":
out = "<xml?>"
case "emptyout":
// print nothing
}
}
return ExecResult{Stdout: out, ReturnCode: code}
}
}
// scrubPaths — normalize the row's memory-dir prefix to @MEMDIR@ so the
// frozen want (recorded with the generator's temp dirs) compares equal.
func scrubPaths(v any, memdir string) any {
switch t := v.(type) {
case string:
return strings.ReplaceAll(t, memdir, "@MEMDIR@")
case []any:
out := make([]any, len(t))
for i, item := range t {
out[i] = scrubPaths(item, memdir)
}
return out
case *OrderedMap:
out := NewOrderedMap()
for _, kv := range t.Entries() {
out.Set(kv.Key, scrubPaths(kv.Value, memdir))
}
return out
default:
return v
}
}
// recallEntries — the memory_store.py stub's fixed entry list (m2's title
// pins ensure_ascii round-tripping).
var recallEntries = []any{
NewOrderedMap(KV{"id", "m1"}, KV{"score", 0.91},
KV{"tags", []any{"alpha", "beta"}}, KV{"title", "memory-entry-one"}),
NewOrderedMap(KV{"id", "m2"}, KV{"score", 0.84},
KV{"tags", []any{}}, KV{"title", "mémoire—café"}),
NewOrderedMap(KV{"id", "m3"}, KV{"score", 0.77},
KV{"tags", []any{"gamma"}}, KV{"title", "memory-entry-three"}),
NewOrderedMap(KV{"id", "m4"}, KV{"score", 0.7},
KV{"tags", []any{}}, KV{"title", "memory-entry-four"}),
NewOrderedMap(KV{"id", "m5"}, KV{"score", 0.62},
KV{"tags", []any{"delta"}}, KV{"title", "memory-entry-five"}),
NewOrderedMap(KV{"id", "m6"}, KV{"score", 0.55},
KV{"tags", []any{}}, KV{"title", "memory-entry-six"}),
NewOrderedMap(KV{"id", "m7"}, KV{"score", 0.41},
KV{"tags", []any{"epsilon"}}, KV{"title", "memory-entry-seven"}),
NewOrderedMap(KV{"id", "m8"}, KV{"score", 0.2},
KV{"tags", []any{}}, KV{"title", "memory-entry-eight"}),
}
// canonicalFromRaw — canonical string of a raw JSON document.
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:
return pythonEscapeString(t)
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 {
parts = append(parts, pythonEscapeString(k)+": "+canonical(normalize(t[k])))
}
return "{" + strings.Join(parts, ", ") + "}"
case *OrderedMap:
m := map[string]any{}
for _, kv := range t.Entries() {
m[kv.Key] = kv.Value
}
return canonical(m)
default:
return fmt.Sprintf("«%v»", t)
}
}
// normalize — convert OrderedMap/map trees into plain map[string]any with
// json.Number→float64 so canonical() sees one representation.
func normalize(v any) any {
switch t := v.(type) {
case *OrderedMap:
m := map[string]any{}
for _, kv := range t.Entries() {
m[kv.Key] = normalize(kv.Value)
}
return m
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
+264
View File
@@ -0,0 +1,264 @@
package main
// Battery — the HARD parity gate plus per-function batteries replaying the
// frozen oracle truth (truth.json, generated against the LIVE Python
// oracle with the clock frozen and env-driven stub scripts pinned)
// VERBATIM. Any divergence fails; the truth file rides the repo so the
// gate holds in CI without /tmp.
import (
"encoding/json"
"os"
"strings"
"testing"
)
func loadTruth(t *testing.T) *TruthDoc {
t.Helper()
raw, err := os.ReadFile("truth.json")
if err != nil {
t.Fatalf("truth.json missing (frozen oracle battery): %v", err)
}
var doc TruthDoc
if err := json.Unmarshal(raw, &doc); err != nil {
t.Fatalf("parse truth.json: %v", err)
}
if len(doc.Rows) == 0 {
t.Fatal("truth.json contained no rows")
}
return &doc
}
func findRow(t *testing.T, name string) TruthRow {
t.Helper()
doc := loadTruth(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 := loadTruth(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 := loadTruth(t)
n := 0
for _, row := range doc.Rows {
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 := loadTruth(t)
var fails []string
for _, row := range doc.Rows {
if msg := compareOne(doc, &row); msg != "" {
fails = append(fails, row.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))
}
// TestBatteryRunWarmRouter — ok / nonzero-exit / bad-json / empty-stdout /
// JSON-list payload / empty-list payload; the ok row pins the exact
// message the port hands to the seam.
func TestBatteryRunWarmRouter(t *testing.T) {
if n := replayOp(t, "warm_router"); n != 6 {
t.Fatalf("expected 6 frozen warm_router rows, got %d", n)
}
want := rowWant(t, "warm:ok")
if want["echo_message"] != "hello warm router" || want["matched"] != float64(3) {
t.Fatalf("frozen echo row changed: %v", want)
}
if got := len(want["fired"].([]any)); got != 3 {
t.Fatalf("fired payload changed: %v", want["fired"])
}
mustReplay(t, "warm:list")
mustReplay(t, "warm:emptylist")
}
// TestBatteryRunMemoryRecall — ok (echo pins the query string), budget
// truncation, non-list payload, and every degrade branch.
func TestBatteryRunMemoryRecall(t *testing.T) {
if n := replayOp(t, "recall"); n != 6 {
t.Fatalf("expected 6 frozen recall rows, got %d", n)
}
// recall:ok's want is the returned LIST — element 0 echoes the query
rawWant := findRow(t, "recall:ok").Want
var list []map[string]any
if err := json.Unmarshal(rawWant, &list); err != nil {
t.Fatalf("decode recall:ok want: %v", err)
}
if len(list) != 5 || list[0]["echo_query"] != "alpha beta gamma" {
t.Fatalf("frozen recall:ok changed: %v", list)
}
mustReplay(t, "recall:budget2")
mustReplay(t, "recall:nonlist")
mustReplay(t, "recall:emptyout")
}
// TestBatteryRunAlwaysDigest — ok / fail / bad-json / empty.
func TestBatteryRunAlwaysDigest(t *testing.T) {
if n := replayOp(t, "always"); n != 4 {
t.Fatalf("expected 4 frozen always rows, got %d", n)
}
want := rowWant(t, "always:ok")
if want["digest"] != "daily-summary" || want["items"] != float64(7) {
t.Fatalf("frozen digest changed: %v", want)
}
}
// TestBatteryLoadActiveTasks — missing file / mixed statuses (only
// open+in_progress survive) / malformed / empty / no-tasks-key.
func TestBatteryLoadActiveTasks(t *testing.T) {
if n := replayOp(t, "load_tasks"); n != 5 {
t.Fatalf("expected 5 frozen load_tasks rows, got %d", n)
}
mustReplay(t, "tasks:missing")
mustReplay(t, "tasks:malformed")
}
// TestBatteryBuild — every build_auto_context branch: full context (the
// recall echo pins stopword filtering + isalpha + first-5 join), short
// message, stopwords-only, non-alpha, unicode 200-rune preview truncation,
// empty message, recall-fail, warm-emptylist, warm-fail; frozen timestamp.
func TestBatteryBuild(t *testing.T) {
if n := replayOp(t, "build"); n != 9 {
t.Fatalf("expected 9 frozen build rows, got %d", n)
}
want := rowWant(t, "build:full")
recallList, _ := want["memory_recall"].([]any)
echo, _ := recallList[0].(map[string]any)
if echo["echo_query"] != "remember config parser override discussion" {
t.Fatalf("recall query extraction changed: %v", echo["echo_query"])
}
if want["timestamp"] != "2026-10-11T12:00:00.123456+00:00" {
t.Fatalf("frozen timestamp changed: %v", want["timestamp"])
}
want = rowWant(t, "build:unicode-preview")
preview, _ := want["message_preview"].(string)
if n := len([]rune(preview)); n != 200 {
t.Fatalf("preview must be 200 runes, got %d", n)
}
mustReplay(t, "build:short-message")
mustReplay(t, "build:stopwords")
mustReplay(t, "build:nonalpha")
mustReplay(t, "build:empty")
mustReplay(t, "build:recall-fail")
mustReplay(t, "build:warm-emptylist")
mustReplay(t, "build:warm-fail")
}
// TestBatterySave — save_context byte-parity: ensure_ascii escapes,
// indent=2 layout, and the tmp+rename overwrite (no .tmp leftover).
func TestBatterySave(t *testing.T) {
if n := replayOp(t, "save"); n != 2 {
t.Fatalf("expected 2 frozen save rows, got %d", n)
}
mustReplay(t, "save:full")
mustReplay(t, "save:overwrite")
}
// TestBatteryHandleHook — full counts, text fallback, str(data) fallback
// (Python dict repr!), short message, other event, no data, zero counts,
// and both best-effort error dicts verbatim.
func TestBatteryHandleHook(t *testing.T) {
if n := replayOp(t, "hook"); n != 9 {
t.Fatalf("expected 9 frozen hook rows, got %d", n)
}
want := rowWant(t, "hook:full")
if want["neurons_fired"] != float64(3) || want["memories_recalled"] != float64(5) ||
want["active_tasks"] != float64(2) {
t.Fatalf("frozen hook counts changed: %v", want)
}
if cf, _ := want["context_file"].(string); !strings.HasSuffix(cf, "@MEMDIR@/index/auto_context.json") {
t.Fatalf("context_file normalization changed: %v", cf)
}
errWant := rowWant(t, "hook:warm-list-error")
if errWant["error"] != "'list' object has no attribute 'get'" {
t.Fatalf("list.get error message changed: %v", errWant["error"])
}
errWant = rowWant(t, "hook:fired-int-error")
if errWant["error"] != "object of type 'int' has no len()" {
t.Fatalf("len(int) error message changed: %v", errWant["error"])
}
mustReplay(t, "hook:text-fallback")
mustReplay(t, "hook:str-fallback")
mustReplay(t, "hook:short-message")
mustReplay(t, "hook:other-event")
mustReplay(t, "hook:no-data")
mustReplay(t, "hook:counts-zero")
}
// TestBatteryCLI — main() byte-parity stdout + exit codes: build (plain +
// --json + default message), show (hit + missing), hook (event+data /
// defaults / error dict), argparse exits.
func TestBatteryCLI(t *testing.T) {
if n := replayOp(t, "cli"); n != 10 {
t.Fatalf("expected 10 frozen cli rows, got %d", n)
}
for name, wantExit := range map[string]float64{
"cli:build": 0,
"cli:build-json": 0,
"cli:build-default": 0,
"cli:show-missing": 0,
"cli:show-hit": 0,
"cli:hook": 0,
"cli:hook-default": 0,
"cli:hook-error": 0,
"cli:bad-cmd": 2,
"cli:no-cmd": 2,
} {
want := rowWant(t, name)
if want["exit"] != wantExit {
t.Fatalf("%s: frozen exit code %v != %v", name, want["exit"], wantExit)
}
}
}
// TestBatteryPlugin — the PLUGIN manifest row.
func TestBatteryPlugin(t *testing.T) {
mustReplay(t, "plugin")
want := rowWant(t, "plugin")
if want["name"] != "auto_context" || want["priority"] != float64(5) ||
want["auto_activate"] != true {
t.Fatalf("frozen PLUGIN manifest changed: %v", want)
}
}
+512 -77
View File
@@ -1,110 +1,545 @@
// Package main — evidence-probe: the GO port of
// src/rune/evidence_probe.py's build_probe_prompt + budget constant
// (Oracle-Battery Parity Pattern, #2479 GO batch 31). The probe runner
// stays Python (reactive — it invokes the LLM and writes artifacts).
// Package main — evidence_probe: the FULL GO port of
// src/rune/evidence_probe.py (dichotomy wave, issue #2479 tranche).
//
// IMPROVEMENT (defect fixed): the oracle's build_probe_prompt doesn't
// sanitize the question or artifact_path — a question containing the
// artifact path string could confuse the LLM about which file to write.
// The Go version embeds them but marks them with explicit delimiters.
// The Python oracle's schema gate (validate_probe) and budget count
// (count_probes) are themselves served by the Rust evidence_provenance
// core through ep_loader; those semantics are ported 1:1 here (probe.rs
// is the byte-parity reference, including Python repr rendering of the
// status value and sorted-list error shapes).
//
// Orchestration (run_probe, the worker subprocess, exception semantics),
// persistence bytes (probe.json indent=2 / probe.jsonl append) and the
// bounded prompt are ported with IO behind injectable seams:
//
// Runner — the probe worker seam (default: DefaultRunner subprocess)
// Clock — probe_id / decided_at source (default: wall clock)
// ModelSource — the provider layer's model selection (default: MIMO_MODEL)
// CmdRunner — the subprocess exec seam (default: os/exec)
//
// Parity: truth.json (frozen vs the LIVE oracle) replays verbatim through
// replay.go; `go run . replay truth.json` and the battery in truth_test.go
// gate every public function and branch.
package main
import (
"encoding/json"
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"
"sort"
"strings"
"sync"
"time"
)
// ── Constants (oracle surface) ──────────────────────────────────────────────
var ProbeStatuses = []string{"GROUNDED", "STILL_UNKNOWN", "CONTRADICTED"}
var ProbeFields = []string{"question", "source", "answer", "implication", "status"}
// MetaFields — to_dict's bookkeeping keys; excluded from the extra-field gate.
var MetaFields = []string{"probe_id", "decided_at", "model"}
// MaxProbesPerTask — no recursive probing: a task may resolve at most this
// many unknowns via probes before escalation to deep research is mandatory.
const MaxProbesPerTask = 2
// buildProbePrompt — Python-identical f-string.
func buildProbePrompt(question, artifactPath string) string {
return fmt.Sprintf(`TARGETED EVIDENCE PROBE (bounded — not full research)
// ── Errors (Python exception semantics) ─────────────────────────────────────
You are resolving exactly ONE material unknown. Do NOT research the wider
task, do NOT design anything, do NOT modify code.
// ValueError mirrors the oracle's ValueError raises.
type ValueError struct{ Msg string }
UNKNOWN:
%s
func (e *ValueError) Error() string { return e.Msg }
Find authoritative, CURRENT evidence that resolves this unknown (official
docs, source code of the dependency, release notes, standards). One or two
focused lookups are enough — this is a probe, not a survey.
// ProbeBudgetExceeded — raised when probing would recurse past the budget.
type ProbeBudgetExceeded struct{ Msg string }
OUTPUT: write JSON to
%s (EXACT path — write to this exact absolute path, no other)
with EXACTLY these keys:
question: the unknown you were asked to resolve (copy verbatim)
source: what authoritative evidence you consulted (URLs/paths)
answer: what the evidence establishes
implication: what this changes about the implementation
status: "GROUNDED" (unknown resolved with cited evidence)
| "STILL_UNKNOWN" (evidence not found/insufficient)
| "CONTRADICTED" (evidence contradicts the task's assumption)
func (e *ProbeBudgetExceeded) Error() string { return e.Msg }
MUST NOT: modify any file except the artifact path; continue into further
research after the unknown is resolved; guess without a source.`, question, artifactPath)
// ProviderUnresolvedError — the provider layer's model-selection gap
// (require_model: no silent default permitted, T16 §16F).
type ProviderUnresolvedError struct{ Msg string }
func (e *ProviderUnresolvedError) Error() string { return e.Msg }
// MissingArtifact — the oracle's _missing_artifact: the worker did not write
// the declared artifact file (ADAPT-9 authority — no stdout rescue).
func MissingArtifact() error {
return &ValueError{
Msg: "probe failed: worker did not write the declared artifact file " +
"(ADAPT-9 authority — no stdout rescue for probes)",
}
}
func main() {
if len(os.Args) < 2 || os.Args[1] != "replay" {
fmt.Fprintln(os.Stderr, "usage: evidence-probe replay <truth.json>")
os.Exit(2)
// ── ProbeResult ─────────────────────────────────────────────────────────────
// ProbeResult — the recorded probe (field order is the oracle's dataclass
// order and to_dict key order).
type ProbeResult struct {
Question string
Source string
Answer string
Implication string
Status string
ProbeID string
DecidedAt string
Model string
}
// ToDict — oracle dict shape (insertion-ordered).
func (r ProbeResult) ToDict() *PyObj {
return &PyObj{
Keys: []string{"question", "source", "answer", "implication", "status", "probe_id", "decided_at", "model"},
Vals: map[string]PyValue{
"question": r.Question,
"source": r.Source,
"answer": r.Answer,
"implication": r.Implication,
"status": r.Status,
"probe_id": r.ProbeID,
"decided_at": r.DecidedAt,
"model": r.Model,
},
}
raw, err := os.ReadFile(os.Args[2])
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
// ── build_probe_prompt ──────────────────────────────────────────────────────
// BuildProbePrompt — bounded prompt: research THE UNKNOWN, write the 5-key
// JSON file. Byte-identical to the oracle f-string.
func BuildProbePrompt(question string, artifactPath string) string {
return "TARGETED EVIDENCE PROBE (bounded — not full research)\n" +
"\n" +
"You are resolving exactly ONE material unknown. Do NOT research the wider\n" +
"task, do NOT design anything, do NOT modify code.\n" +
"\n" +
"UNKNOWN:\n" +
question + "\n" +
"\n" +
"Find authoritative, CURRENT evidence that resolves this unknown (official\n" +
"docs, source code of the dependency, release notes, standards). One or two\n" +
"focused lookups are enough — this is a probe, not a survey.\n" +
"\n" +
"OUTPUT: write JSON to\n" +
" " + artifactPath + " (EXACT path — write to this exact absolute path, no other)\n" +
"with EXACTLY these keys:\n" +
" question: the unknown you were asked to resolve (copy verbatim)\n" +
" source: what authoritative evidence you consulted (URLs/paths)\n" +
" answer: what the evidence establishes\n" +
" implication: what this changes about the implementation\n" +
" status: \"GROUNDED\" (unknown resolved with cited evidence)\n" +
" | \"STILL_UNKNOWN\" (evidence not found/insufficient)\n" +
" | \"CONTRADICTED\" (evidence contradicts the task's assumption)\n" +
"\n" +
"MUST NOT: modify any file except the artifact path; continue into further\n" +
"research after the unknown is resolved; guess without a source."
}
// ── validate_probe (Rust core semantics, byte-parity) ───────────────────────
// ValidateProbe — the 5-key schema gate; empty result = pass. Errors append
// in PROBE_FIELDS order, then the status-set check, then the GROUNDED source
// check, then the sorted extra-field check — exactly like the Rust core.
func ValidateProbe(artifact PyValue) []string {
obj, ok := artifact.(*PyObj)
if !ok {
return []string{"probe artifact is not a JSON object"}
}
var cases []map[string]any
if err := json.Unmarshal(raw, &cases); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
var errs []string
for _, f := range ProbeFields {
v, present := obj.Get(f)
switch {
case !present:
errs = append(errs, fmt.Sprintf("missing required field: %s", f))
case IsPyStr(v):
if f != "source" && PyIsBlank(v.(string)) {
errs = append(errs, fmt.Sprintf("field %s must be a non-empty string", f))
}
default:
errs = append(errs, fmt.Sprintf("field %s must be a string", f))
}
}
fails := 0
for i, c := range cases {
op, _ := c["op"].(string)
switch op {
case "prompt":
q, _ := c["question"].(string)
ap, _ := c["artifact_path"].(string)
want, _ := c["want"].(string)
if got := buildProbePrompt(q, ap); got != want {
// find the first divergence for the report
for j := range got {
if j >= len(want) || got[j] != want[j] {
fails++
fmt.Printf("case %d prompt: first divergence at char %d\n got: %q\n want: %q\n", i, j, got[min(j, len(got)-1):min(j+40, len(got))], want[min(j, len(want)-1):min(j+40, len(want))])
break
}
// Status check runs on the RAW value regardless of type: a non-string
// status fires the field error above AND the "must be one of" error here.
if statusVal, present := obj.Get("status"); present {
statusOK := false
if s, isStr := statusVal.(string); isStr {
for _, ok := range ProbeStatuses {
if s == ok {
statusOK = true
break
}
}
case "max_probes":
want := int(c["want"].(float64))
if MaxProbesPerTask != want {
fails++
fmt.Printf("case %d max_probes: got %d want %d\n", i, MaxProbesPerTask, want)
}
if !statusOK {
sorted := append([]string(nil), ProbeStatuses...)
sort.Strings(sorted)
var rendered string
switch v := statusVal.(type) {
case string:
rendered = PyRepr(v)
case json.Number:
rendered = PyFmtNum(v)
case bool:
if v {
rendered = "True"
} else {
rendered = "False"
}
case nil:
rendered = "None"
default:
rendered = PyDumpsCompact(v)
}
errs = append(errs, fmt.Sprintf(
"status must be one of %s, got %s", PyListRepr(sorted), rendered))
}
}
// GROUNDED requires a concrete source citation (>= 8 chars).
if s, present := obj.Get("status"); present {
if str, isStr := s.(string); isStr && str == "GROUNDED" {
sourceLen := 0
if sv, present := obj.Get("source"); present {
if ss, isStr := sv.(string); isStr {
sourceLen = pyStrlen(ss)
}
}
if sourceLen < 8 {
errs = append(errs, "GROUNDED probe requires a concrete source citation")
}
}
}
// IMPROVEMENT pin: the delimiter markers (UNKNOWN:/OUTPUT:) make the
// question and artifact path unambiguous to the LLM
p := buildProbePrompt("test question", "/tmp/probe.json")
if !strings.Contains(p, "test question") || !strings.Contains(p, "/tmp/probe.json") {
fails++
fmt.Println("IMPROVEMENT FAILED: question/path not embedded")
// Extra fields: artifact keys − PROBE_FIELDS − meta fields, sorted.
var extra []string
inList := func(list []string, k string) bool {
for _, x := range list {
if x == k {
return true
}
}
return false
}
if fails > 0 {
fmt.Fprintf(os.Stderr, "PARITY FAILED: %d divergence(s)\n", fails)
os.Exit(1)
for _, k := range obj.Keys {
if !inList(ProbeFields, k) && !inList(MetaFields, k) {
extra = append(extra, k)
}
}
fmt.Printf("PARITY OK: %d cases vs the Python oracle\n", len(cases))
sort.Strings(extra)
if len(extra) > 0 {
errs = append(errs, fmt.Sprintf("unexpected extra fields: %s", PyListRepr(extra)))
}
return errs
}
func min(a, b int) int {
if a < b {
return a
// IsPyStr reports whether the value is a JSON string.
func IsPyStr(v PyValue) bool {
_, ok := v.(string)
return ok
}
// ── count_probes / check_budget ─────────────────────────────────────────────
// CountProbes — non-empty-line count of <dir>/probe.jsonl (0 if absent).
// Line splitting is the Rust core's str::lines(): \n-split, trailing \r
// stripped per line, no phantom line after a trailing newline.
func CountProbes(dir string) int {
raw, err := os.ReadFile(filepath.Join(dir, "probe.jsonl"))
if err != nil {
return 0
}
return b
content := string(raw)
if content == "" {
return 0
}
lines := strings.Split(content, "\n")
if len(lines) > 0 && lines[len(lines)-1] == "" {
lines = lines[:len(lines)-1]
}
n := 0
for _, line := range lines {
line = strings.TrimSuffix(line, "\r")
if strings.TrimSpace(line) != "" {
n++
}
}
return n
}
// CheckBudget — ProbeBudgetExceeded once the no-recursion budget is spent.
func CheckBudget(artifactDir string) error {
if CountProbes(artifactDir) >= MaxProbesPerTask {
return &ProbeBudgetExceeded{
Msg: fmt.Sprintf("probe budget exhausted (%d); unresolved unknowns must escalate to deep research", MaxProbesPerTask),
}
}
return nil
}
// ── record_probe ────────────────────────────────────────────────────────────
// RecordProbe — persist the probe: probe.json (indent=2 artifact) and
// probe.jsonl (append-only compact line). The two writes touch different
// files with no ordering contract between them, so they fan out to
// goroutines (the Go-advantage pass); the persisted bytes are identical to
// the serial oracle (the frozen battery replays this exact path).
func RecordProbe(result ProbeResult, artifactDir string) (string, error) {
if err := os.MkdirAll(artifactDir, 0o777); err != nil {
return "", err
}
var (
wg sync.WaitGroup
mu sync.Mutex
firstErr error
jsonBytes = []byte(PyDumpsIndent2(result.ToDict()))
jsonlBytes = []byte(PyDumpsCompact(result.ToDict()) + "\n")
jsonPath = filepath.Join(artifactDir, "probe.json")
jsonlPath = filepath.Join(artifactDir, "probe.jsonl")
)
fail := func(err error) {
if err == nil {
return
}
mu.Lock()
defer mu.Unlock()
if firstErr == nil {
firstErr = err
}
}
wg.Add(2)
go func() {
defer wg.Done()
fail(os.WriteFile(jsonPath, jsonBytes, 0o666))
}()
go func() {
defer wg.Done()
f, err := os.OpenFile(jsonlPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o666)
if err != nil {
fail(err)
return
}
_, err = f.Write(jsonlBytes)
fail(err)
fail(f.Close())
}()
wg.Wait()
if firstErr != nil {
return "", firstErr
}
return artifactDir + "/probe.json", nil
}
// ── run_probe ───────────────────────────────────────────────────────────────
// Runner — the probe worker seam: receives the bounded prompt and the exact
// absolute artifact path it must write (ADAPT-9: the file is the authority).
type Runner interface {
Run(prompt, artifactPath string) error
}
// RunnerFunc adapts a function to Runner.
type RunnerFunc func(prompt, artifactPath string) error
func (f RunnerFunc) Run(prompt, artifactPath string) error { return f(prompt, artifactPath) }
// Clock — the probe_id/decided_at source.
type Clock interface{ Now() time.Time }
// ClockFunc adapts a function to Clock.
type ClockFunc func() time.Time
func (f ClockFunc) Now() time.Time { return f() }
type wallClock struct{}
func (wallClock) Now() time.Time { return time.Now() }
// ClockNow — the active clock (overridable in tests/battery).
var ClockNow Clock = wallClock{}
// RunProbe — one bounded probe. runner == nil selects the default worker
// channel (DefaultRunner). model == "" falls back to $MIMO_MODEL, then the
// literal "UNKNOWN" default only when the variable is unset.
func RunProbe(question string, artifactDir string, runner Runner, model string) (ProbeResult, error) {
if err := CheckBudget(artifactDir); err != nil {
return ProbeResult{}, err
}
artifactPath, err := filepath.Abs(filepath.Join(artifactDir, "evidence_probe.json"))
if err != nil {
return ProbeResult{}, err
}
if runner == nil {
runner = RunnerFunc(DefaultRunner)
}
if err := runner.Run(BuildProbePrompt(question, artifactPath), artifactPath); err != nil {
return ProbeResult{}, err
}
if _, err := os.Stat(artifactPath); err != nil {
return ProbeResult{}, MissingArtifact()
}
raw, err := os.ReadFile(artifactPath)
if err != nil {
return ProbeResult{}, err
}
artifact, err := PyParse(raw)
if err != nil {
return ProbeResult{}, err
}
if errs := ValidateProbe(artifact); len(errs) > 0 {
return ProbeResult{}, &ValueError{Msg: "probe gate failed: " + strings.Join(errs, "; ")}
}
obj := artifact.(*PyObj)
getStr := func(key string) string {
if v, ok := obj.Get(key); ok {
if s, isStr := v.(string); isStr {
return s
}
}
return ""
}
resolvedModel := model
if resolvedModel == "" {
if env, ok := os.LookupEnv("MIMO_MODEL"); ok {
resolvedModel = env
} else {
resolvedModel = "UNKNOWN"
}
}
now := ClockNow.Now()
result := ProbeResult{
Question: getStr("question"),
Source: getStr("source"),
Answer: getStr("answer"),
Implication: getStr("implication"),
Status: getStr("status"),
ProbeID: fmt.Sprintf("probe-%d", now.Unix()),
DecidedAt: now.UTC().Format("2006-01-02T15:04:05Z"),
Model: resolvedModel,
}
if _, err := RecordProbe(result, artifactDir); err != nil {
return ProbeResult{}, err
}
return result, nil
}
// ── _default_runner (the worker subprocess channel) ─────────────────────────
// ModelSource — the provider layer owns model selection (require_model);
// a missing value is a configuration gap, never papered over.
type ModelSource interface{ RequireModel() (string, error) }
// ModelSourceFunc adapts a function to ModelSource.
type ModelSourceFunc func() (string, error)
func (f ModelSourceFunc) RequireModel() (string, error) { return f() }
// EnvModelSource — the default require_model: $MIMO_MODEL (stripped), no
// silent default (T16 §16F).
var EnvModelSource ModelSource = ModelSourceFunc(func() (string, error) {
const envVar = "MIMO_MODEL"
model := strings.TrimSpace(os.Getenv(envVar))
if model == "" {
return "", &ProviderUnresolvedError{
Msg: "PROVIDER_UNRESOLVED: " + envVar + " is not set — provider/model " +
"selection belongs to the provider layer (set " + envVar + " in " +
"the calling environment); no silent default is permitted " +
"(T16 §16F)",
}
}
return model, nil
})
// ModelSelect — the active model source (overridable in tests/battery).
var ModelSelect = EnvModelSource
// CmdRunner — the subprocess exec seam.
type CmdRunner interface {
// Run executes bin with args; returns captured stdout and a non-nil
// error whose ExitCode() is the child's exit status (exec.ExitError).
Run(bin string, args []string) ([]byte, error)
}
type osExecRunner struct{}
func (osExecRunner) Run(bin string, args []string) ([]byte, error) {
return exec.Command(bin, args...).Output()
}
// ExecCmd — the active command runner (overridable in tests/battery).
var ExecCmd CmdRunner = osExecRunner{}
// DefaultRunner — same worker channel as canonical stages: local MiMo,
// JSON format. stdout is observability only; the artifact file is the
// authority.
func DefaultRunner(prompt string, artifactPath string) error {
mimoBin := os.Getenv("MIMO_BIN")
if mimoBin == "" {
mimoBin = "/var/lib/gitea-desktop-runner/bin/mimo"
}
mimoModel, err := ModelSelect.RequireModel()
if err != nil {
return err
}
mimoAgent := os.Getenv("MIMO_AGENT")
if mimoAgent == "" {
mimoAgent = "build"
}
cmd := []string{
"run",
"--agent",
mimoAgent,
"-m",
mimoModel,
"--dangerously-skip-permissions",
"--format",
"json",
prompt,
}
out, runErr := ExecCmd.Run(mimoBin, cmd)
// Python only reaches the log write when the child actually ran
// (launch failures raise before it).
var exitErr *exec.ExitError
launched := runErr == nil || errors.As(runErr, &exitErr)
if launched {
logPath := filepath.Dir(artifactPath) + "/evidence_probe.stdout.log"
if writeErr := os.WriteFile(logPath, out, 0o666); writeErr != nil && runErr == nil {
return writeErr
}
}
if runErr != nil {
if launched {
return &ValueError{Msg: fmt.Sprintf("probe worker exited %d", exitErr.ExitCode())}
}
return runErr
}
return nil
}
func main() {
if len(os.Args) < 3 || os.Args[1] != "replay" {
fmt.Fprintln(os.Stderr, "usage: evidence-probe replay <truth.json>")
os.Exit(2)
}
if fails := RunTruth(os.Args[2]); len(fails) > 0 {
for _, f := range fails {
fmt.Println("DIVERGED:", f)
}
fmt.Fprintf(os.Stderr, "PARITY FAILED: %d divergence(s)\n", len(fails))
os.Exit(1)
}
fmt.Println("PARITY OK: frozen oracle battery replayed verbatim")
}
+34
View File
@@ -0,0 +1,34 @@
// phaseb.go — the language-advantage pass (dichotomy wave):
//
// - RecordProbe fans its two independent file writes to goroutines
// (main.go) — the serial oracle's only IO seam where parallelism is
// honest; 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 probe run.
package main
// Compile-time seam safety: the default implementations and the Func
// adapters satisfy their interfaces, and the error types implement error.
// A seam drifting from its contract fails the build, not a probe run.
var (
_ Runner = RunnerFunc(nil)
_ Runner = RunnerFunc(DefaultRunner)
_ Clock = ClockFunc(nil)
_ Clock = wallClock{}
_ ModelSource = ModelSourceFunc(nil)
_ ModelSource = EnvModelSource
_ CmdRunner = CmdRunnerFunc(nil)
_ CmdRunner = osExecRunner{}
_ error = (*ValueError)(nil)
_ error = (*ProbeBudgetExceeded)(nil)
_ error = (*ProviderUnresolvedError)(nil)
)
// CmdRunnerFunc adapts a function to CmdRunner.
type CmdRunnerFunc func(bin string, args []string) ([]byte, error)
func (f CmdRunnerFunc) Run(bin string, args []string) ([]byte, error) {
return f(bin, args)
}
+161
View File
@@ -0,0 +1,161 @@
package main
// Phase B battery — the language-advantage pass. The parallel RecordProbe
// path must produce byte-identical persistence to the frozen serial oracle
// (the truth battery already replays it; these tests hammer it directly),
// and the seams must actually be consumed by RunProbe.
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"sync"
"testing"
"time"
)
// exitErrOf — a real *exec.ExitError with the given exit code.
func exitErrOf(code int) error {
_, err := exec.Command("sh", "-c", fmt.Sprintf("exit %d", code)).Output()
return err
}
func TestRecordProbeParallelBytesIdentical(t *testing.T) {
result := ProbeResult{
Question: "q",
Source: "https://example.com/a",
Answer: "a",
Implication: "i",
Status: "GROUNDED",
ProbeID: "probe-1791753600",
DecidedAt: "2026-10-12T04:00:00Z",
Model: "UNKNOWN",
}
dir := t.TempDir()
path, err := RecordProbe(result, dir)
if err != nil {
t.Fatal(err)
}
if path != filepath.Join(dir, "probe.json") {
t.Fatalf("returned path drifted: %q", path)
}
pj, err := os.ReadFile(filepath.Join(dir, "probe.json"))
if err != nil {
t.Fatal(err)
}
pjl, err := os.ReadFile(filepath.Join(dir, "probe.jsonl"))
if err != nil {
t.Fatal(err)
}
// The expected bytes come from the Python-faithful serializers, the
// same renderers the frozen truth pins byte-for-byte.
wantJSON := PyDumpsIndent2(result.ToDict())
wantJSONL := PyDumpsCompact(result.ToDict()) + "\n"
if string(pj) != wantJSON {
t.Fatalf("probe.json bytes drifted:\n got %q\n want %q", pj, wantJSON)
}
if string(pjl) != wantJSONL {
t.Fatalf("probe.jsonl bytes drifted:\n got %q\n want %q", pjl, wantJSONL)
}
}
func TestRecordProbeConcurrentHammer(t *testing.T) {
const workers = 32
result := ProbeResult{
Question: "hammer", Source: "", Answer: "a", Implication: "i",
Status: "STILL_UNKNOWN", ProbeID: "probe-1", DecidedAt: "d", Model: "m",
}
dirs := make([]string, workers)
for i := range dirs {
dirs[i] = t.TempDir()
}
var wg sync.WaitGroup
errs := make([]error, workers)
for i := 0; i < workers; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
_, errs[i] = RecordProbe(result, dirs[i])
}(i)
}
wg.Wait()
for i, err := range errs {
if err != nil {
t.Fatalf("worker %d: %v", i, err)
}
}
want := PyDumpsIndent2(result.ToDict())
for i, dir := range dirs {
pj, err := os.ReadFile(filepath.Join(dir, "probe.json"))
if err != nil || string(pj) != want {
t.Fatalf("worker %d bytes diverged: %v", i, err)
}
}
}
func TestRunProbeConsumesSeams(t *testing.T) {
// Inject BOTH seams: a runner that writes a valid artifact and a clock
// pinned to an off-truth instant — RunProbe must use the injected clock
// (not the wall clock) for probe_id/decided_at.
dir := t.TempDir()
pinned := time.Unix(1234567890, 0)
oldClock := ClockNow
defer func() { ClockNow = oldClock }()
ClockNow = ClockFunc(func() time.Time { return pinned })
runner := RunnerFunc(func(prompt, artifactPath string) error {
if prompt == "" || artifactPath == "" {
t.Fatal("seam received empty prompt/path")
}
return os.WriteFile(artifactPath, []byte(
`{"question": "q", "source": "", "answer": "a", "implication": "i", "status": "STILL_UNKNOWN"}`), 0o666)
})
result, err := RunProbe("q", dir, runner, "seam-model")
if err != nil {
t.Fatal(err)
}
if result.ProbeID != "probe-1234567890" {
t.Fatalf("clock seam ignored: probe_id %q", result.ProbeID)
}
if result.DecidedAt != pinned.UTC().Format("2006-01-02T15:04:05Z") {
t.Fatalf("clock seam ignored: decided_at %q", result.DecidedAt)
}
}
func TestDefaultRunnerConsumesExecSeam(t *testing.T) {
oldExec, oldModel := ExecCmd, ModelSelect
defer func() { ExecCmd, ModelSelect = oldExec, oldModel }()
ExecCmd = CmdRunnerFunc(func(bin string, args []string) ([]byte, error) {
if bin != "fake-mimo" || args[0] != "run" {
t.Fatalf("exec seam got bin=%q args=%v", bin, args)
}
return []byte("ok"), nil
})
ModelSelect = ModelSourceFunc(func() (string, error) { return "seam-m", nil })
dir := t.TempDir()
t.Setenv("MIMO_BIN", "fake-mimo")
t.Setenv("MIMO_AGENT", "seam-agent")
if err := DefaultRunner("P", filepath.Join(dir, "evidence_probe.json")); err != nil {
t.Fatal(err)
}
log, err := os.ReadFile(filepath.Join(dir, "evidence_probe.stdout.log"))
if err != nil || string(log) != "ok" {
t.Fatalf("stdout log drifted: %q %v", log, err)
}
}
func TestExitCodeErrorMapping(t *testing.T) {
oldExec, oldModel := ExecCmd, ModelSelect
defer func() { ExecCmd, ModelSelect = oldExec, oldModel }()
ExecCmd = CmdRunnerFunc(func(bin string, args []string) ([]byte, error) {
return nil, exitErrOf(3)
})
ModelSelect = ModelSourceFunc(func() (string, error) { return "m", nil })
err := DefaultRunner("P", filepath.Join(t.TempDir(), "evidence_probe.json"))
if err == nil || err.Error() != "probe worker exited 3" {
t.Fatalf("exit-code mapping drifted: %v", err)
}
}
+301
View File
@@ -0,0 +1,301 @@
// pyjson.go — Python-faithful JSON parsing and rendering for the
// evidence_probe port. The oracle round-trips artifacts through
// json.dumps/loads, so the port must reproduce: object key order
// (insertion order, not sorted), float rendering (fmt_num semantics of
// the Rust core: integral floats collapse to integers), and
// ensure_ascii escaping with lowercase \uXXXX (surrogate pairs for
// astral planes).
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"math"
"strconv"
"strings"
"unicode"
"unicode/utf8"
)
// 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 (o *PyObj) Get(key string) (PyValue, bool) {
if o == nil {
return nil, false
}
v, ok := o.Vals[key]
return v, ok
}
// PyParse — strict single-value JSON parse (trailing garbage rejected,
// like Python json.loads / serde_json).
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 := &PyObj{Vals: map[string]PyValue{}}
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
}
if _, dup := obj.Vals[key]; !dup {
obj.Keys = append(obj.Keys, key)
}
obj.Vals[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, "", false)
return b.String()
}
// PyDumpsIndent2 — Python json.dump(value, indent=2): separators ',' / ': '
// with 2-space indentation, ensure_ascii=True.
func PyDumpsIndent2(v PyValue) string {
var b strings.Builder
writePy(&b, v, "", true)
return b.String()
}
func writePy(b *strings.Builder, v PyValue, indent string, indented bool) {
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(PyFmtNum(t))
case string:
writePyString(b, t)
case []PyValue:
if len(t) == 0 {
b.WriteString("[]")
return
}
b.WriteByte('[')
inner := indent
if indented {
inner = indent + " "
}
for i, item := range t {
if indented {
if i > 0 {
b.WriteString(",")
}
b.WriteString("\n")
b.WriteString(inner)
} else if i > 0 {
b.WriteString(", ")
}
writePy(b, item, inner, indented)
}
if indented {
b.WriteString("\n")
b.WriteString(indent)
}
b.WriteByte(']')
case *PyObj:
if len(t.Keys) == 0 {
b.WriteString("{}")
return
}
b.WriteByte('{')
inner := indent
if indented {
inner = indent + " "
}
for i, k := range t.Keys {
if indented {
if i > 0 {
b.WriteString(",")
}
b.WriteString("\n")
b.WriteString(inner)
} else if i > 0 {
b.WriteString(", ")
}
writePyString(b, k)
b.WriteString(": ")
writePy(b, t.Vals[k], inner, indented)
}
if indented {
b.WriteString("\n")
b.WriteString(indent)
}
b.WriteByte('}')
default:
b.WriteString(fmt.Sprintf("%v", v))
}
}
// PyFmtNum — the Rust core's fmt_num: integral floats render as integers,
// everything else via shortest fixed-notation round-trip (Rust f64 Display
// semantics — never scientific notation).
func PyFmtNum(n json.Number) string {
f, err := n.Float64()
if err != nil {
return n.String()
}
if !math.IsInf(f, 0) && !math.IsNaN(f) && f == math.Trunc(f) &&
math.Abs(f) < 9.007199254740992e15 {
return strconv.FormatInt(int64(f), 10)
}
return strconv.FormatFloat(f, 'f', -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, \n \r \t control renders (the shapes a status/extra key takes).
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()
}
// PyListRepr — Python repr() of a list of strings: ['a', 'b'].
func PyListRepr(items []string) string {
parts := make([]string, len(items))
for i, s := range items {
parts[i] = PyRepr(s)
}
return "[" + strings.Join(parts, ", ") + "]"
}
// PyIsBlank — Python str.strip() emptiness (any non-whitespace = content).
func PyIsBlank(s string) bool {
return strings.TrimFunc(s, unicode.IsSpace) == ""
}
// pyStrlen — Python len() of a str: Unicode scalar (rune) count.
func pyStrlen(s string) int { return utf8.RuneCountInString(s) }
+478
View File
@@ -0,0 +1,478 @@
// Replay — the frozen-truth parity engine. Reads truth.json (generated by
// scripts/gen_evidence_probe_truth.py against the live Python oracle with
// the clock frozen, the schema gate served by the Rust core, the runner and
// subprocess seams stubbed) and re-executes every row through this Go port.
// Prompt/error/file bytes compare exactly; @ARTDIR@ normalizes the temp
// base dir on both sides.
package main
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"reflect"
"strings"
"time"
)
// TruthDoc — the frozen truth file structure.
type TruthDoc struct {
Oracle string `json:"oracle"`
FrozenTS int64 `json:"frozen_ts"`
FrozenDecidedAt string `json:"frozen_decided_at"`
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 in its own temp dir 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 _, row := range doc.Rows {
if msg := replayRow(&doc, &row); msg != "" {
fails = append(fails, row.Name+": "+msg)
}
}
return fails
}
func replayRow(doc *TruthDoc, row *TruthRow) string {
base, err := os.MkdirTemp("", "ep_truth_")
if err != nil {
return fmt.Sprintf("temp dir: %v", err)
}
defer os.RemoveAll(base)
oldClock := ClockNow
oldModel := ModelSelect
oldExec := ExecCmd
oldEnv := snapshotEnv([]string{"MIMO_BIN", "MIMO_AGENT", "MIMO_MODEL", "EP_CAPTURE", "EP_EXIT"})
defer func() {
ClockNow, ModelSelect, ExecCmd = oldClock, oldModel, oldExec
restoreEnv(oldEnv)
}()
ClockNow = ClockFunc(func() time.Time { return time.Unix(doc.FrozenTS, 0) })
var got any
switch row.Op {
case "prompt":
got, err = replayPrompt(row)
case "validate":
got, err = replayValidate(row)
case "count":
got, err = replayCount(row, base)
case "budget":
got, err = replayBudget(row, base)
case "record":
got, err = replayRecord(row, base)
case "run":
got, err = replayRun(row, base)
case "to_dict":
got, err = replayToDict(row)
case "default_runner":
got, err = replayDefaultRunner(row, base)
default:
return "unknown op " + row.Op
}
if err != nil {
return "error: " + err.Error()
}
var want any
if err := json.Unmarshal(row.Want, &want); err != nil {
return "parse want: " + err.Error()
}
if !reflect.DeepEqual(normalizeJSON(got), normalizeJSON(want)) {
return fmt.Sprintf("got %#v\n want %#v", got, want)
}
return ""
}
// normalizeJSON — round-trip through map[string]any/float64 so got and want
// numbers compare by value, then stringify json.Number.
func normalizeJSON(v any) any {
switch t := v.(type) {
case int:
return float64(t)
case json.Number:
f, err := t.Float64()
if err != nil {
return t.String()
}
return f
case map[string]any:
m := map[string]any{}
for k, val := range t {
m[k] = normalizeJSON(val)
}
return m
case []any:
s := make([]any, len(t))
for i, val := range t {
s[i] = normalizeJSON(val)
}
return s
default:
return v
}
}
func denorm(s, base string) string { return strings.ReplaceAll(s, "@ARTDIR@", base) }
// normOut — normalize a Go-side output path/string for comparison with the
// frozen truth (the generator replaced its temp base with @ARTDIR@).
func normOut(s, base string) string { return strings.ReplaceAll(s, base, "@ARTDIR@") }
func snapshotEnv(keys []string) map[string]*string {
out := map[string]*string{}
for _, k := range keys {
if v, ok := os.LookupEnv(k); ok {
cp := v
out[k] = &cp
} else {
out[k] = nil
}
}
return out
}
func restoreEnv(snap map[string]*string) {
for k, v := range snap {
if v == nil {
os.Unsetenv(k)
} else {
os.Setenv(k, *v)
}
}
}
// ── ops ─────────────────────────────────────────────────────────────────────
func replayPrompt(row *TruthRow) (any, error) {
var setup struct {
Question string `json:"question"`
ArtifactPath string `json:"artifact_path"`
}
if err := json.Unmarshal(row.Setup, &setup); err != nil {
return nil, err
}
return BuildProbePrompt(setup.Question, setup.ArtifactPath), nil
}
func replayValidate(row *TruthRow) (any, error) {
var setup struct {
Artifact json.RawMessage `json:"artifact"`
}
if err := json.Unmarshal(row.Setup, &setup); err != nil {
return nil, err
}
artifact, err := PyParse(setup.Artifact)
if err != nil {
return nil, fmt.Errorf("parse artifact: %w", err)
}
errs := ValidateProbe(artifact)
out := make([]any, len(errs))
for i, e := range errs {
out[i] = e
}
return out, nil
}
func replayCount(row *TruthRow, base string) (any, error) {
var setup struct {
Dir string `json:"dir"`
Files map[string]string `json:"files"`
}
if err := json.Unmarshal(row.Setup, &setup); err != nil {
return nil, err
}
dir := denorm(setup.Dir, base)
if err := writeTree(dir, setup.Files); err != nil {
return nil, err
}
return CountProbes(dir), nil
}
func replayBudget(row *TruthRow, base string) (any, error) {
var setup struct {
Dir string `json:"dir"`
Files map[string]string `json:"files"`
}
if err := json.Unmarshal(row.Setup, &setup); err != nil {
return nil, err
}
dir := denorm(setup.Dir, base)
if err := writeTree(dir, setup.Files); err != nil {
return nil, err
}
err := CheckBudget(dir)
if err == nil {
// exc_row froze {"error": null, "value": null} on success.
return map[string]any{"error": nil, "value": nil}, nil
}
return map[string]any{"error": errorValue(err)}, nil
}
// errorValue — the frozen exception shape; typed nil must stay untyped nil
// so DeepEqual matches the JSON null on the want side.
func errorValue(err error) any {
if err == nil {
return nil
}
msg := err.Error()
typ := fmt.Sprintf("%T", err)
switch t := err.(type) {
case *ValueError:
typ, msg = "ValueError", t.Msg
case *ProbeBudgetExceeded:
typ, msg = "ProbeBudgetExceeded", t.Msg
case *ProviderUnresolvedError:
typ, msg = "ProviderUnresolved", t.Msg
}
return map[string]any{"type": typ, "msg": msg}
}
func replayRecord(row *TruthRow, base string) (any, error) {
var setup struct {
Result map[string]string `json:"result"`
Files map[string]string `json:"files"`
}
if err := json.Unmarshal(row.Setup, &setup); err != nil {
return nil, err
}
dir := denorm("@ARTDIR@/"+row.Name, base)
if err := writeTree(dir, setup.Files); err != nil {
return nil, err
}
result := ProbeResult{
Question: setup.Result["question"],
Source: setup.Result["source"],
Answer: setup.Result["answer"],
Implication: setup.Result["implication"],
Status: setup.Result["status"],
ProbeID: setup.Result["probe_id"],
DecidedAt: setup.Result["decided_at"],
Model: setup.Result["model"],
}
path, err := RecordProbe(result, dir)
if err != nil {
return nil, err
}
pj, err := os.ReadFile(filepath.Join(dir, "probe.json"))
if err != nil {
return nil, err
}
pjl, err := os.ReadFile(filepath.Join(dir, "probe.jsonl"))
if err != nil {
return nil, err
}
return map[string]any{
"path": normOut(path, base),
"probe_json": string(pj),
"probe_jsonl": string(pjl),
}, nil
}
func writeTree(dir string, files map[string]string) error {
for rel, content := range files {
p := filepath.Join(dir, filepath.FromSlash(rel))
if err := os.MkdirAll(filepath.Dir(p), 0o777); err != nil {
return err
}
if err := os.WriteFile(p, []byte(content), 0o666); err != nil {
return err
}
}
return nil
}
func replayRun(row *TruthRow, base string) (any, error) {
var setup struct {
Question string `json:"question"`
Model string `json:"model"`
Env map[string]any `json:"env"`
Files map[string]string `json:"files"`
Runner struct {
Type string `json:"type"`
Artifact json.RawMessage `json:"artifact"`
} `json:"runner"`
}
if err := json.Unmarshal(row.Setup, &setup); err != nil {
return nil, err
}
for k, v := range setup.Env {
if v == nil {
os.Unsetenv(k)
} else if s, ok := v.(string); ok {
os.Setenv(k, s)
}
}
dir := denorm("@ARTDIR@/"+row.Name, base)
if err := os.MkdirAll(dir, 0o777); err != nil {
return nil, err
}
if err := writeTree(dir, setup.Files); err != nil {
return nil, err
}
captured := struct {
prompt, artifactPath string
called bool
}{}
runner := RunnerFunc(func(prompt, artifactPath string) error {
captured.prompt, captured.artifactPath, captured.called = prompt, artifactPath, true
switch setup.Runner.Type {
case "write":
artifact, err := PyParse(setup.Runner.Artifact)
if err != nil {
return err
}
return os.WriteFile(artifactPath, []byte(PyDumpsCompact(artifact)), 0o666)
case "silent":
return nil
default:
return fmt.Errorf("unknown runner type %q", setup.Runner.Type)
}
})
result, err := RunProbe(setup.Question, dir, runner, setup.Model)
out := map[string]any{"error": errorValue(err)}
if err == nil {
var resultJSON any
if uerr := json.Unmarshal([]byte(PyDumpsCompact(result.ToDict())), &resultJSON); uerr != nil {
return nil, uerr
}
out["result"] = resultJSON
pj, rerr := os.ReadFile(filepath.Join(dir, "probe.json"))
if rerr != nil {
return nil, rerr
}
pjl, rerr := os.ReadFile(filepath.Join(dir, "probe.jsonl"))
if rerr != nil {
return nil, rerr
}
out["probe_json"] = string(pj)
out["probe_jsonl"] = string(pjl)
}
out["runner_called"] = captured.called
if captured.called {
out["prompt"] = normOut(captured.prompt, base)
out["artifact_path"] = normOut(captured.artifactPath, base)
}
return out, nil
}
func replayToDict(row *TruthRow) (any, error) {
var setup struct {
Fields map[string]string `json:"fields"`
}
if err := json.Unmarshal(row.Setup, &setup); err != nil {
return nil, err
}
result := ProbeResult{
Question: setup.Fields["question"],
Source: setup.Fields["source"],
Answer: setup.Fields["answer"],
Implication: setup.Fields["implication"],
Status: setup.Fields["status"],
ProbeID: setup.Fields["probe_id"],
DecidedAt: setup.Fields["decided_at"],
Model: setup.Fields["model"],
}
if _, ok := setup.Fields["model"]; !ok {
result.Model = "UNKNOWN" // the oracle dataclass default
}
return PyDumpsCompact(result.ToDict()), nil
}
func replayDefaultRunner(row *TruthRow, base string) (any, error) {
var setup struct {
ExitCode int `json:"exit_code"`
AgentEnv *string `json:"agent_env"`
ModelMode string `json:"model_mode"`
}
if err := json.Unmarshal(row.Setup, &setup); err != nil {
return nil, err
}
stub := filepath.Join(base, "ep_stub.sh")
script := "#!/bin/sh\n" +
"for a in \"$@\"; do printf '%s\\n' \"$a\" >> \"$EP_CAPTURE\"; done\n" +
"printf 'stub-out\\n'\n" +
"exit \"$EP_EXIT\"\n"
if err := os.WriteFile(stub, []byte(script), 0o755); err != nil {
return nil, err
}
dir := denorm("@ARTDIR@/"+row.Name, base)
if err := os.MkdirAll(dir, 0o777); err != nil {
return nil, err
}
capture := filepath.Join(dir, "argv_capture.txt")
os.Remove(capture)
artifactPath := filepath.Join(dir, "evidence_probe.json")
os.Setenv("MIMO_BIN", stub)
os.Setenv("EP_CAPTURE", capture)
os.Setenv("EP_EXIT", fmt.Sprintf("%d", setup.ExitCode))
if setup.AgentEnv == nil {
os.Unsetenv("MIMO_AGENT")
} else {
os.Setenv("MIMO_AGENT", *setup.AgentEnv)
}
switch setup.ModelMode {
case "unresolved":
os.Unsetenv("MIMO_MODEL")
ModelSelect = EnvModelSource
case "blank":
os.Setenv("MIMO_MODEL", " ")
ModelSelect = EnvModelSource
default:
ModelSelect = ModelSourceFunc(func() (string, error) { return "test-model-v9", nil })
}
err := DefaultRunner("PROMPT-TEXT", artifactPath)
out := map[string]any{"error": errorValue(err)}
if lines, rerr := readLines(capture); rerr == nil {
norm := make([]any, len(lines))
for i, l := range lines {
norm[i] = normOut(l, base)
}
out["argv"] = norm
} else {
out["argv"] = nil
}
logPath := filepath.Join(dir, "evidence_probe.stdout.log")
if log, rerr := os.ReadFile(logPath); rerr == nil {
out["stdout_log"] = string(log)
} else {
out["stdout_log"] = nil
}
return out, nil
}
func readLines(path string) ([]string, error) {
raw, err := os.ReadFile(path)
if err != nil {
return nil, err
}
content := strings.TrimSuffix(string(raw), "\n")
if content == "" {
return []string{}, nil
}
return strings.Split(content, "\n"), nil
}
File diff suppressed because it is too large. Load diff
+168
View File
@@ -0,0 +1,168 @@
package main
// Battery — the HARD parity gate: the frozen oracle truth (truth.json,
// generated against the LIVE Python oracle with the clock frozen and the
// Rust core serving validate_probe/count_probes) replays VERBATIM. Any
// divergence fails; the truth file rides the repo so the gate holds in CI
// without /tmp.
import (
"encoding/json"
"fmt"
"os"
"reflect"
"testing"
)
func loadTruthDoc(t *testing.T, path string) *TruthDoc {
t.Helper()
raw, err := os.ReadFile(path)
if err != nil {
t.Fatalf("truth.json missing (frozen oracle battery): %v", err)
}
var doc TruthDoc
if err := json.Unmarshal(raw, &doc); err != nil {
t.Fatalf("parse truth.json: %v", err)
}
if len(doc.Rows) == 0 {
t.Fatal("truth.json contained no rows")
}
return &doc
}
func findRow(t *testing.T, doc *TruthDoc, name string) TruthRow {
t.Helper()
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 mustReplayRow(t *testing.T, doc *TruthDoc, name string) {
t.Helper()
row := findRow(t, doc, name)
if msg := replayRow(doc, &row); msg != "" {
t.Errorf("DIVERGED %s: %s", name, msg)
}
}
func TestFullTruthReplayVerbatim(t *testing.T) {
doc := loadTruthDoc(t, "truth.json")
var fails []string
for _, row := range doc.Rows {
if msg := replayRow(doc, &row); msg != "" {
fails = append(fails, row.Name+": "+msg)
}
}
if len(fails) > 0 {
for _, f := range fails {
t.Errorf("DIVERGED %s", f)
}
t.Fatalf("frozen-truth parity: %d divergence(s)", len(fails))
}
}
func TestTruthOpCoverage(t *testing.T) {
doc := loadTruthDoc(t, "truth.json")
// The frozen battery must keep covering every op family: if a
// regeneration drops a family, this fails before parity silently rots.
want := map[string]int{
"prompt": 5, "validate": 49, "count": 11, "budget": 4,
"record": 3, "run": 11, "to_dict": 3, "default_runner": 5,
}
got := map[string]int{}
for _, row := range doc.Rows {
got[row.Op]++
}
if fmt.Sprint(got) != fmt.Sprint(want) {
t.Fatalf("truth.json op coverage drifted:\n got %v\n want %v", got, want)
}
}
func TestPromptBaselineByteParity(t *testing.T) {
doc := loadTruthDoc(t, "truth.json")
row := findRow(t, doc, "prompt_baseline")
got, err := replayPrompt(&row)
if err != nil {
t.Fatal(err)
}
var want string
if err := json.Unmarshal(row.Want, &want); err != nil {
t.Fatal(err)
}
if got != want {
t.Fatalf("prompt drifted from frozen oracle:\n got %q\n want %q", got, want)
}
}
func TestGateErrorOrderIsFieldOrder(t *testing.T) {
doc := loadTruthDoc(t, "truth.json")
for _, name := range []string{
"combined_ordering", "status_obj_order", "grounded_missing_source",
"status_repr_unicode", "status_big_float", "status_huge_int",
"valid_grounded", "empty_artifact", "extra_sorted", "nonobject_list",
} {
mustReplayRow(t, doc, name)
}
}
func TestRunProbeExceptionSemantics(t *testing.T) {
doc := loadTruthDoc(t, "truth.json")
for _, name := range []string{
"run_success_grounded", "run_success_still_unknown",
"run_model_from_env", "run_model_unset_unknown",
"run_model_env_blank_is_empty_string",
"run_budget_exhausted_before_runner", "run_budget_one_left",
"run_silent_runner_missing_artifact", "run_gate_failure_joined",
"run_gate_failure_non_dict", "run_gate_failure_status_non_string",
} {
mustReplayRow(t, doc, name)
}
}
func TestDefaultRunnerArgvParity(t *testing.T) {
doc := loadTruthDoc(t, "truth.json")
for _, name := range []string{
"defrun_exit0_default_agent", "defrun_exit3", "defrun_custom_agent",
"defrun_model_unresolved", "defrun_model_blank_env",
} {
mustReplayRow(t, doc, name)
}
}
func TestCountAndBudgetSemantics(t *testing.T) {
doc := loadTruthDoc(t, "truth.json")
for _, name := range []string{
"count_absent_dir", "count_cr_lines", "count_no_trailing_newline",
"count_interleaved_blank", "budget_zero", "budget_one",
"budget_two", "budget_three",
} {
mustReplayRow(t, doc, name)
}
}
func TestRecordProbeBytesParity(t *testing.T) {
doc := loadTruthDoc(t, "truth.json")
for _, name := range []string{
"record_fresh_dir_created", "record_appends_existing",
"record_unicode_ensure_ascii",
} {
mustReplayRow(t, doc, name)
}
}
func TestToDictFieldOrderParity(t *testing.T) {
doc := loadTruthDoc(t, "truth.json")
for _, name := range []string{"to_dict_defaults", "to_dict_full", "to_dict_unicode"} {
mustReplayRow(t, doc, name)
}
}
func TestNormalizeJSONIntFloatEquivalence(t *testing.T) {
if !reflect.DeepEqual(normalizeJSON(2), normalizeJSON(2.0)) {
t.Fatal("int/float normalization broke count comparisons")
}
}
+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())
}
+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.209.0"
__version__ = "0.229.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
+151
View File
@@ -0,0 +1,151 @@
#!/usr/bin/env python3
"""ctypes loader for the Rust build_basis core (dichotomy wave #2479
tranche). Mirrors broker_loader.py's contract:
FFI contract (doc-envelope law — every call returns {"ok", "value"|"err"}):
* bb_research_depth({route, mode, justification}) -> depth decision dict
* bb_new_basis({question, depth_decision, issue_ref}) -> basis dict
* bb_set_dispositions({basis, dispositions}) -> {"basis", "errors"}
* bb_validate_basis({basis}) -> [errors]
* bb_is_qualified({basis}) -> bool
* bb_attach_evidence_ref({basis}) -> str
* bb_summary({basis}) -> summary dict
* bb_revise_depth({basis, new_depth, reason}) -> {"basis", "revision"}
* bb_to_json({basis}) -> str (json.dumps indent=1 sort_keys semantics)
* bb_from_json({text}) -> parsed value
* bb_generate_research_plan({...}) -> plan dict
Free: capability_scoring's cs_free_string (shared process). Override the
.so with BB_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" / "build_basis"
_LIB_PATH = Path(
os.environ.get("BB_RUST_LIB", _CRATE_DIR / "target" / "release" / "libbuild_basis.so")
)
_SYMBOLS = (
"bb_research_depth", "bb_new_basis", "bb_set_dispositions",
"bb_validate_basis", "bb_is_qualified", "bb_attach_evidence_ref",
"bb_summary", "bb_revise_depth", "bb_to_json", "bb_from_json",
"bb_generate_research_plan",
)
_lib = None
class BasisUnavailable(FileNotFoundError):
"""The Rust build_basis 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 BasisUnavailable(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
# 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 research_depth(route: str, mode: str = "", justification: str = "") -> dict:
return _call("bb_research_depth", {
"route": route, "mode": mode, "justification": justification})
def new_basis(question: str, depth_decision: dict, issue_ref: str = "") -> dict:
return _call("bb_new_basis", {
"question": question, "depth_decision": depth_decision,
"issue_ref": issue_ref})
def set_dispositions(basis: dict, dispositions: list) -> dict:
"""Returns {"basis": <mutated basis>, "errors": [...]}."""
return _call("bb_set_dispositions", {
"basis": basis, "dispositions": dispositions})
def validate_basis(basis: dict) -> list:
return _call("bb_validate_basis", {"basis": basis})
def is_qualified(basis: dict) -> bool:
return _call("bb_is_qualified", {"basis": basis})
def attach_evidence_ref(basis: dict) -> str:
return _call("bb_attach_evidence_ref", {"basis": basis})
def summary(basis: dict) -> dict:
return _call("bb_summary", {"basis": basis})
def revise_depth(basis: dict, new_depth: str, reason: str) -> dict:
"""Returns {"basis": <mutated basis>, "revision": {...}}; raises
RuntimeError carrying the exact oracle ValueError message on refusal."""
return _call("bb_revise_depth", {
"basis": basis, "new_depth": new_depth, "reason": reason})
def to_json(basis: dict) -> str:
return _call("bb_to_json", {"basis": basis})
def from_json(text: str):
return _call("bb_from_json", {"text": text})
def generate_research_plan(intent: str, target: str = "",
current_reality: str = "",
unknowns: list | None = None,
decisions: list | None = None,
risks: list | None = None,
depth: str | None = None) -> dict:
return _call("bb_generate_research_plan", {
"intent": intent, "target": target,
"current_reality": current_reality,
"unknowns": list(unknowns or []), "decisions": list(decisions or []),
"risks": list(risks or []), "depth": depth})
+158 -29
View File
@@ -16,16 +16,19 @@ Usage:
behavior.py list [--type correction|habit|all]
behavior.py stats
Phase G strangler slice 2 (port 5): the decision math (reinforce
counting/confidence/promotion/state formulas) is served IN-PROCESS by the
Rust `evidence_provenance` cdylib through ep_loader.py. STORAGE STAYS
PYTHON — SQLite I/O and the regex matching layer remain here (the crate is
zero-dep by design; hand-rolling SQLite/regex would be hubris, not
hardening). Vintage upgrade per the frozen contract §2: this module is the
src/rune authority vintage; the deployed ~/.config/opencode/scripts copy
is superseded by the same shim shape pointing at the live db path.
Timestamps are pinned by the shim (`now`) — the core is deterministic.
API, CLI, and PLUGIN shapes unchanged (git history holds the original).
Phase G strangler slice 2 (port 5), superseded by the dichotomy wave
(#2479 tranche): the FULL decision surface — reinforce counting/
confidence/promotion/state AND the match_context per-row predicates —
is served IN-PROCESS by the Rust `behavior` cdylib through bh_loader.py.
STORAGE STAYS PYTHON — SQLite I/O and row ordering remain here (the
crate links no sqlite; hand-rolling SQLite would be hubris, not
hardening). The evidence_provenance ep_loader path remains as the
Python-pin fallback oracle. Vintage upgrade per the frozen contract §2:
this module is the src/rune authority vintage; the deployed
~/.config/opencode/scripts copy is superseded by the same shim shape
pointing at the live db path. Timestamps are pinned by the shim (`now`)
— the core is deterministic. API, CLI, and PLUGIN shapes unchanged (git
history holds the original).
"""
import json
@@ -35,7 +38,7 @@ import sqlite3
import sys
from datetime import datetime, timezone
# ── Rust core wiring (slice-2 port-5 shim pattern) ──────────────────────────
# ── Rust core wiring (dichotomy-wave doc-envelope pattern) ──────────────────
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:
@@ -45,6 +48,28 @@ import ep_loader # noqa: E402 (loads target/release cdylib)
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import memory_env
try:
from . import bh_loader
except ImportError: # direct script execution (canary `import behavior`)
try:
import bh_loader # type: ignore[no-redef]
except ImportError: # noqa: S110 — no loader on path → Python oracle below
bh_loader = None # type: ignore[assignment]
def _bh_core():
"""The Rust behavior-core probe (strangler law). RUNE_BEHAVIOR_CORE=
python pins the Python fallback — the pin is honored inside
bh_loader.core so every consumer gets it. Returns the loader MODULE
(never the raw CDLL)."""
if bh_loader is None:
return None
try:
return bh_loader if bh_loader.core() is not None else None
except Exception: # noqa: BLE001 — kernel failure → oracle below
return None
DB_PATH = os.path.join(memory_env.memory_dir(), "behavior.db")
@@ -157,11 +182,17 @@ def add_habit(trigger, action, name="", context_tags=None, min_activations=3):
def match_context(context, match_type=None):
"""Find matching corrections and habits for the current context."""
"""Find matching corrections and habits for the current context.
The per-row predicates (SQL WHERE gates + regex/domain filters)
delegate to the Rust core (bh_match_row); the SQL fetch and ordering
stay Python. Falls back to the inline predicates on kernel failure
or the RUNE_BEHAVIOR_CORE=python pin."""
db = connect()
try:
results = {"corrections": [], "habits": []}
context_low = context.lower()
core = _bh_core()
# Match corrections
if match_type != "habit":
@@ -170,18 +201,17 @@ def match_context(context, match_type=None):
ORDER BY confidence DESC
""").fetchall()
for c in corrections:
# Check scope pattern match
pattern = c["scope_pattern"]
if pattern:
if core is not None:
try:
if not re.search(pattern, context, re.IGNORECASE):
if not core.match_row("correction", dict(c), context)["matched"]:
continue
except re.error:
continue # skip invalid regex patterns
# Check domain match
domain = c["scope_domain"]
if domain and domain.lower() not in context_low:
continue
except Exception: # noqa: BLE001 — kernel failure → oracle below
core = None
if not _correction_matches_python(dict(c), context, context_low):
continue
else:
if not _correction_matches_python(dict(c), context, context_low):
continue
row = dict(c)
# Parse scope_tags from JSON string to dict
try:
@@ -198,12 +228,17 @@ def match_context(context, match_type=None):
ORDER BY confidence * activation_count DESC
""").fetchall()
for h in habits:
trigger = h["trigger_pattern"]
try:
if not re.search(trigger, context, re.IGNORECASE):
if core is not None:
try:
if not core.match_row("habit", dict(h), context)["matched"]:
continue
except Exception: # noqa: BLE001 — kernel failure → oracle below
core = None
if not _habit_matches_python(dict(h), context):
continue
else:
if not _habit_matches_python(dict(h), context):
continue
except re.error:
continue # skip invalid regex patterns
results["habits"].append(dict(h))
return results
@@ -211,11 +246,105 @@ def match_context(context, match_type=None):
db.close()
def _correction_matches_python(row, context, context_low):
"""The inline per-row correction predicates (Python oracle)."""
# Check scope pattern match
pattern = row["scope_pattern"]
if pattern:
try:
if not re.search(pattern, context, re.IGNORECASE):
return False
except re.error:
return False # skip invalid regex patterns
# Check domain match
domain = row["scope_domain"]
if domain and domain.lower() not in context_low:
return False
return True
def _habit_matches_python(row, context):
"""The inline per-row habit predicate (Python oracle)."""
trigger = row["trigger_pattern"]
try:
if not re.search(trigger, context, re.IGNORECASE):
return False
except re.error:
return False # skip invalid regex patterns
return True
def reinforce(item_id, outcome, item_type="habit"):
"""Reinforce a correction or habit based on outcome.
Decision math (counting, confidence formula, promotion, state) served
by the Rust core; SQL stays here."""
Decision math (counting, confidence formula, promotion, state)
delegates to the Rust behavior core; SQL stays here. Falls back to
the evidence_provenance shim path on kernel failure or the
RUNE_BEHAVIOR_CORE=python pin."""
core = _bh_core()
if core is not None:
try:
return _reinforce_rust(core, item_id, outcome, item_type)
except Exception: # noqa: BLE001 — kernel failure → shim path below
pass
return _reinforce_python(item_id, outcome, item_type)
def _reinforce_rust(core, item_id, outcome, item_type):
"""The bh-core delegation half of reinforce (fetch-row -> Rust math ->
write-back). Raises through to the shim path on any failure."""
db = connect()
try:
ts = _now()
if item_type == "habit":
habit = db.execute("SELECT * FROM habits WHERE id=?", (item_id,)).fetchone()
if not habit:
return {"error": f"Habit {item_id} not found"}
upd = core.habit_reinforce(dict(habit), outcome)
db.execute(
"""
UPDATE habits SET activation_count=?, success_count=?, failure_count=?,
confidence=?, state=?, last_activated=? WHERE id=?
""",
(upd["activation_count"], upd["success_count"], upd["failure_count"],
upd["confidence"], upd["state"], ts, item_id),
)
# Log activation
db.execute(
"""
INSERT INTO habit_activations (habit_id, context, outcome, activated_at)
VALUES (?, '', ?, ?)
""",
(item_id, outcome, ts),
)
db.commit()
return {"reinforced": item_id, "confidence": upd["confidence"], "state": upd["state"]}
else: # correction
correction = db.execute("SELECT * FROM corrections WHERE id=?", (item_id,)).fetchone()
if not correction:
return {"error": f"Correction {item_id} not found"}
upd = core.correction_reinforce(dict(correction), outcome)
db.execute(
"""
UPDATE corrections SET confidence=?, evidence_count=?, state=?, last_reinforced=?
WHERE id=?
""",
(upd["confidence"], upd["evidence_count"], upd["state"], ts, item_id),
)
db.commit()
return {"reinforced": item_id, "confidence": upd["confidence"], "state": upd["state"]}
finally:
db.close()
def _reinforce_python(item_id, outcome, item_type="habit"):
"""Reinforce via the evidence_provenance shim (the prior oracle path)."""
db = connect()
try:
ts = _now()
+117
View File
@@ -0,0 +1,117 @@
#!/usr/bin/env python3
"""ctypes loader for the Rust behavior core (dichotomy wave, #2479
tranche). Mirrors contracts_loader.py's contract:
FFI contract (doc-envelope law — every call returns {"ok", "value"|"err"}):
* bh_habit_reinforce({row, outcome}) -> update dict
* bh_correction_reinforce({row, outcome}) -> update dict
* bh_match_row({kind, row, context}) -> {"visible", "matched"}
* bh_plugin() -> the PLUGIN descriptor
SQLite I/O stays in behavior.py; this core owns the reinforce decision
math and the match predicates. Free: capability_scoring's cs_free_string
(shared process) — NEVER re-export a free symbol from this crate.
Override the .so with BEHAVIOR_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" / "behavior"
_LIB_PATH = Path(os.environ.get("BEHAVIOR_RUST_LIB", _CRATE_DIR / "target" / "release" / "libbehavior.so"))
_lib = None
_SYMBOLS = (
"bh_habit_reinforce",
"bh_correction_reinforce",
"bh_match_row",
"bh_plugin",
)
class BehaviorCoreUnavailable(FileNotFoundError):
"""The Rust behavior 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_BEHAVIOR_CORE=python pins the fallback — THIS is
where the pin is honored, so every consumer gets it."""
if os.environ.get("RUNE_BEHAVIOR_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 BehaviorCoreUnavailable(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 habit_reinforce(row: dict, outcome: str) -> dict:
return _call("bh_habit_reinforce", {"row": row, "outcome": outcome})
def correction_reinforce(row: dict, outcome: str) -> dict:
return _call("bh_correction_reinforce", {"row": row, "outcome": outcome})
def match_row(kind: str, row: dict, context: str) -> dict:
return _call("bh_match_row", {"kind": kind, "row": row, "context": context})
def plugin() -> dict:
return _call("bh_plugin", {})
+61
View File
@@ -19,11 +19,34 @@ structures the adopt/adapt/combine/reject/innovate decisions (rule E).
"""
from __future__ import annotations
import os
import time
from typing import Any
SCHEMA_VERSION = 1
def _rust(fn: str, *args, **kwargs):
"""Delegate to the Rust kernel (dichotomy wave #2479); returns
_MISSING when the core is unavailable so the Python oracle body runs.
RUNE_BB_CORE=python pins the oracle unconditionally."""
if os.environ.get("RUNE_BB_CORE", "auto") == "python":
return _MISSING
try:
from rune import bb_loader
if not bb_loader.available():
return _MISSING
return getattr(bb_loader, fn)(*args, **kwargs)
except Exception:
return _MISSING
class _Missing:
pass
_MISSING = _Missing()
# Research depth tiers — derived from adaptive_router ROUTES, never a
# second router (rule C/A). Each tier records WHY it was chosen.
DEPTH_NONE = "NONE" # tiny obvious local change; local evidence only
@@ -48,6 +71,9 @@ def research_depth(route: str, *, mode: str = "",
the canonical route authority (adaptive_router); `justification` is
REQUIRED when deviating from the route default — recorded, never
silent (rule D: record why research depth was chosen)."""
out = _rust("research_depth", route, mode, justification)
if out is not _MISSING:
return out
default = _ROUTE_DEFAULT_DEPTH.get(route, DEPTH_INWARD)
depth = default
why = f"route {route} default"
@@ -89,6 +115,9 @@ def new_basis(*, question: str, depth_decision: dict,
"""Create an empty-but-valid Build Basis. References only — full
research dumps live in the Research Core's artifact dirs (rule 18C:
do not store unnecessary dumps)."""
out = _rust("new_basis", question, depth_decision, issue_ref)
if out is not _MISSING:
return out
return {
"schema_version": SCHEMA_VERSION,
"kind": "build_basis",
@@ -112,6 +141,11 @@ def new_basis(*, question: str, depth_decision: dict,
def set_dispositions(basis: dict, dispositions: list[dict]) -> list[str]:
"""Record the adopt/adapt/combine/reject/innovate decisions (rule E).
Returns validation errors; the basis stays unqualified until valid."""
out = _rust("set_dispositions", basis, dispositions)
if out is not _MISSING:
basis.clear()
basis.update(out["basis"])
return out["errors"]
basis["dispositions"] = [
{"item": d.get("item", ""), "disposition": d.get("disposition", ""),
"why": d.get("why", ""), "ref": d.get("ref", "")}
@@ -124,6 +158,9 @@ def validate_basis(basis: dict) -> list[str]:
"""Qualification gate. A basis is QUALIFIED when it carries a depth
decision with why, at least one outward prior-art answer when depth
is OUTWARD/FULL, and every disposition uses a legal disposition."""
out = _rust("validate_basis", basis)
if out is not _MISSING:
return out
errors: list[str] = []
if basis.get("schema_version") != SCHEMA_VERSION:
errors.append(f"schema_version {basis.get('schema_version')!r}")
@@ -152,12 +189,18 @@ def validate_basis(basis: dict) -> list[str]:
def is_qualified(basis: dict) -> bool:
out = _rust("is_qualified", basis)
if out is not _MISSING:
return out
return not validate_basis(basis)
def attach_evidence_ref(basis: dict) -> str:
"""The evidence_ref string a contract carries to point at this basis
(durable pointer, not a copy — stores stay independent)."""
out = _rust("attach_evidence_ref", basis)
if out is not _MISSING:
return out
return (f"build_basis:{basis.get('issue_ref') or 'adhoc'}:"
f"{basis.get('created_at')}")
@@ -165,6 +208,9 @@ def attach_evidence_ref(basis: dict) -> str:
def summary(basis: dict) -> dict:
"""Compact projection for prompts/previews (no private reasoning —
structured conclusions only, rule 18G)."""
out = _rust("summary", basis)
if out is not _MISSING:
return out
return {
"schema_version": basis.get("schema_version"),
"question": basis.get("question"),
@@ -193,6 +239,11 @@ def revise_depth(basis: dict, *, new_depth: str, reason: str) -> dict:
depths both persist. An OUTWARD→INWARD/NONE downgrade requires
explicit resolution evidence ('discovery resolved', 'prior-art
resolved', 'unnecessary') — never a bare skip."""
out = _rust("revise_depth", basis, new_depth=new_depth, reason=reason)
if out is not _MISSING:
basis.clear()
basis.update(out["basis"])
return out["revision"]
if new_depth not in DEPTH_TIERS:
raise ValueError(f"unknown depth {new_depth!r}")
original = basis["depth"]["depth"]
@@ -216,11 +267,17 @@ def revise_depth(basis: dict, *, new_depth: str, reason: str) -> dict:
def to_json(basis: dict) -> str:
out = _rust("to_json", basis)
if out is not _MISSING:
return out
import json
return json.dumps(basis, indent=1, sort_keys=True)
def from_json(text: str) -> dict:
out = _rust("from_json", text)
if out is not _MISSING:
return out
import json
return json.loads(text)
@@ -248,6 +305,10 @@ def generate_research_plan(*, intent: str, target: str = "",
risks of THIS intent — the function has no topic vocabulary and no
permanent category list. Depth, when not supplied, is chosen by
consequence heuristics recorded in the plan (why is always present)."""
out = _rust("generate_research_plan", intent, target, current_reality,
unknowns, decisions, risks, depth)
if out is not _MISSING:
return out
unknowns = [u for u in (unknowns or []) if u.strip()]
decisions = [d for d in (decisions or []) if d.strip()]
risks = [r for r in (risks or []) if r.strip()]
+1 -1
View File
@@ -233,7 +233,7 @@ def main() -> int:
)
check(
"Rune minor version advertises provisioning and restore verification",
'__version__ = "0.209.0"' in rune_version,
'__version__ = "0.229.0"' in rune_version,
failures,
)
# #169 policy update: auto-bump on merge is sanctioned (Nick directive:
+167 -1
View File
@@ -15,6 +15,7 @@ the history list.
"""
from __future__ import annotations
import os
import time
from pathlib import Path
@@ -28,6 +29,22 @@ def _now() -> str:
return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
def _kernel():
"""The Rust ledger core (cl_loader) when RUNE_LEDGER_CORE allows it,
else None — every public function delegates there first and falls
back to the Python body below (same process, same semantics; the
kernel returns the oracle output verbatim plus the updated doc)."""
if os.environ.get("RUNE_LEDGER_CORE", "auto") == "python":
return None
try:
from rune import cl_loader
if cl_loader.available():
return cl_loader
except Exception:
return None
return None
class CapabilityLedger:
def __init__(self, store_dir: Path | str):
self.path = Path(store_dir) / "capability_ledger.json"
@@ -37,13 +54,26 @@ class CapabilityLedger:
if self.path.is_file():
import json
return json.loads(self.path.read_text())
k = _kernel()
if k is not None:
try:
return k.new(_now())["value"]
except Exception:
pass
return {"schema_version": LEDGER_SCHEMA_VERSION,
"capabilities": {}, "history": [],
"updated_at": _now()}
def save(self) -> None:
import json
self.doc["updated_at"] = _now()
k = _kernel()
if k is not None:
try:
self.doc = k.save(self.doc, _now())["doc"]
except Exception:
self.doc["updated_at"] = _now()
else:
self.doc["updated_at"] = _now()
self.path.parent.mkdir(parents=True, exist_ok=True)
self.path.write_text(json.dumps(self.doc, indent=1, sort_keys=True))
@@ -61,6 +91,23 @@ class CapabilityLedger:
generation: str = "") -> dict:
"""Create a capability record (auto-invoked by Scaffolding when a
meaningful capability is created — rule 19Q)."""
k = _kernel()
if k is not None:
try:
out = k.define(self.doc, _now(), capability_id=capability_id,
name=name, purpose=purpose,
owning_project=owning_project,
component=component,
source_locations=source_locations,
contract=contract, state_touched=state_touched,
dependencies=dependencies, consumers=consumers,
invariants=invariants,
behaviour_specs=behaviour_specs,
constraints=constraints, generation=generation)
self.doc = out["doc"]
return out["value"]
except Exception:
pass # kernel err (or absent) — Python body re-raises properly
cid = f"capability:{capability_id}"
if cid in self.doc["capabilities"]:
raise ValueError(f"capability exists: {cid}")
@@ -88,6 +135,12 @@ class CapabilityLedger:
return rec
def get(self, capability_id: str) -> dict | None:
k = _kernel()
if k is not None:
try:
return k.get(self.doc, capability_id)["value"]
except Exception:
pass
return self.doc["capabilities"].get(
capability_id if ":" in capability_id
else f"capability:{capability_id}")
@@ -98,6 +151,16 @@ class CapabilityLedger:
"""Record a material change (auto-invoked after construction —
rule 19Q.2). History entry keeps traceability; dependents' harden
state is the behaviour ledger's job (via structural closure)."""
k = _kernel()
if k is not None:
try:
out = k.material_change(self.doc, _now(), capability_id,
note=note, generation=generation,
source_locations=source_locations)
self.doc = out["doc"]
return out["value"]
except Exception:
pass
rec = self.get(capability_id)
if rec is None:
raise ValueError(f"unknown capability {capability_id!r}")
@@ -124,6 +187,14 @@ class CapabilityLedger:
def move(self, capability_id: str, new_locations: list[str]) -> dict:
"""File move: STABLE IDENTITY SURVIVES (rule 19S-6); the move is
recorded, history preserved."""
k = _kernel()
if k is not None:
try:
out = k.move(self.doc, _now(), capability_id, new_locations)
self.doc = out["doc"]
return out["value"]
except Exception:
pass
rec = self.get(capability_id)
if rec is None:
raise ValueError(f"unknown capability {capability_id!r}")
@@ -137,6 +208,15 @@ class CapabilityLedger:
def supersede(self, capability_id: str, by: str, *,
note: str = "") -> dict:
k = _kernel()
if k is not None:
try:
out = k.supersede(self.doc, _now(), capability_id, by,
note=note)
self.doc = out["doc"]
return out["value"]
except Exception:
pass
rec = self.get(capability_id)
if rec is None:
raise ValueError(f"unknown capability {capability_id!r}")
@@ -150,6 +230,14 @@ class CapabilityLedger:
return rec
def retire(self, capability_id: str, *, note: str = "") -> dict:
k = _kernel()
if k is not None:
try:
out = k.retire(self.doc, _now(), capability_id, note=note)
self.doc = out["doc"]
return out["value"]
except Exception:
pass
rec = self.get(capability_id)
if rec is None:
raise ValueError(f"unknown capability {capability_id!r}")
@@ -163,15 +251,33 @@ class CapabilityLedger:
# -- queries (surgical context, 19E) -----------------------------------
def active(self) -> list[dict]:
k = _kernel()
if k is not None:
try:
return k.active(self.doc)["value"]
except Exception:
pass
return [r for r in self.doc["capabilities"].values()
if r["maturity"] not in ("retired", "superseded")]
def by_component(self, component: str) -> list[dict]:
k = _kernel()
if k is not None:
try:
return k.by_component(self.doc, component)["value"]
except Exception:
pass
return [r for r in self.active() if r["component"] == component]
def context_packet(self, capability_id: str) -> dict:
"""The LOCAL context packet (19E): everything a fresh builder
needs for one capability — bounded, structured, no reasoning."""
k = _kernel()
if k is not None:
try:
return k.context_packet(self.doc, capability_id)["value"]
except Exception:
pass
rec = self.get(capability_id)
if rec is None:
return {"error": f"unknown capability {capability_id!r}"}
@@ -227,6 +333,16 @@ def classify_version_bump(*, contract_changed: bool,
implementation_only: bool) -> str:
"""§21D-3: derive PATCH/MINOR/MAJOR from the change shape. The model
may propose; the machinery verifies."""
k = _kernel()
if k is not None:
try:
return k.classify_version_bump(
contract_changed=contract_changed,
behaviour_added=behaviour_added,
consumer_break=consumer_break,
implementation_only=implementation_only)["value"]
except Exception:
pass
if consumer_break:
return "MAJOR"
if contract_changed:
@@ -241,6 +357,12 @@ def classify_version_bump(*, contract_changed: bool,
def bump_version(version: str, kind: str) -> str:
k = _kernel()
if k is not None:
try:
return k.bump_version(version, kind)["value"]
except Exception:
pass
m = SEMVER_RE.match(version)
if not m:
raise SpecError(f"invalid semver {version!r}")
@@ -267,6 +389,21 @@ def attach_spec(ledger: "CapabilityLedger", capability_id: str, *,
the starting version is explicit — 1.0.0 only for stable contracts
with sufficient evidence; otherwise 0.x with a reason."""
from rune.capability_ledger import LEDGER_SCHEMA_VERSION # noqa: F401
k = _kernel()
if k is not None:
try:
out = k.attach_spec(ledger.doc, _now(), capability_id,
requirements=requirements,
expected_behaviours=expected_behaviours,
non_goals=non_goals,
acceptance_criteria=acceptance_criteria,
baseline_version=baseline_version,
baseline_reason=baseline_reason,
governance_version_started_at=governance_version_started_at)
ledger.doc = out["doc"]
return out["value"]
except Exception:
pass
rec = ledger.get(capability_id)
if rec is None:
raise ValueError(f"unknown capability {capability_id!r}")
@@ -307,6 +444,23 @@ def revise_spec(ledger: "CapabilityLedger", capability_id: str, *,
The model proposes; the machinery classifies and records. Material
change without a bump is impossible (every revise computes one);
unjustified bumps are impossible (no-change revise raises)."""
k = _kernel()
if k is not None:
try:
out = k.revise_spec(ledger.doc, _now(), capability_id,
contract_changed=contract_changed,
behaviour_added=behaviour_added,
consumer_break=consumer_break,
implementation_only=implementation_only,
reason=reason,
changed_sections=changed_sections,
affected_consumers=affected_consumers,
validation_consequences=validation_consequences,
proposed_kind=proposed_kind)
ledger.doc = out["doc"]
return out["value"]
except Exception:
pass
rec = ledger.get(capability_id)
if rec is None:
raise ValueError(f"unknown capability {capability_id!r}")
@@ -357,6 +511,12 @@ def spec_drift_status(ledger: "CapabilityLedger",
→ SPEC_VERSION_DRIFT + durable obligation
Returns {"drift": bool, "obligation": str|None, "detail": ...}."""
k = _kernel()
if k is not None:
try:
return k.spec_drift_status(ledger.doc, capability_id)["value"]
except Exception:
pass
rec = ledger.get(capability_id)
if rec is None:
return {"drift": False, "obligation": None,
@@ -389,6 +549,12 @@ def spec_drift_status(ledger: "CapabilityLedger",
def spec_context(rec: dict) -> dict:
"""The spec half of a surgical context packet (§21M): WHY + WHAT
SHOULD EXIST, compact."""
k = _kernel()
if k is not None:
try:
return k.spec_context(rec)["value"]
except Exception:
pass
s = rec.get("spec") or {}
return {
"capability_id": rec["capability_id"],
+196
View File
@@ -0,0 +1,196 @@
#!/usr/bin/env python3
"""ctypes loader for the Rust capability_ledger core (dichotomy factory
batch, #2479 tranche). Mirrors broker_loader.py's contract:
FFI contract (doc-envelope law — every call returns {"ok", "value"|"err"}):
* cl_new({now, file_text?}) -> doc
* cl_define({doc, now, ...define kwargs}) -> {"record", "doc"}
* cl_get({doc, capability_id}) -> record|null
* cl_material_change({doc, now, ...}) -> {"record", "doc"}
* cl_move({doc, now, capability_id, new_locations}) -> {"record", "doc"}
* cl_supersede({doc, now, capability_id, by, note?}) -> {"record", "doc"}
* cl_retire({doc, now, capability_id, note?}) -> {"record", "doc"}
* cl_active({doc}) / cl_by_component({doc, component}) -> [records]
* cl_context_packet({doc, capability_id}) -> packet dict
* cl_save({doc, now}) -> doc (stamped)
* cl_classify_version_bump({4 bools}) -> "PATCH"|"MINOR"|"MAJOR"
* cl_bump_version({version, kind}) -> new version
* cl_attach_spec({doc, now, ...}) -> {"spec", "doc"}
* cl_revise_spec({doc, now, ...}) -> {"result", "doc"}
* cl_spec_drift_status({doc, capability_id}) -> status dict
* cl_spec_context({rec}) -> packet dict
The doc is the FULL ledger document ({"schema_version", "capabilities",
"history", "updated_at"}); mutators return the updated doc so the facade
keeps authoritative state. Free: capability_scoring's cs_free_string
(shared process) — NEVER re-export. Override the .so with
LEDGER_RUST_LIB (a FILE path).
"""
from __future__ import annotations
import ctypes
import json
import os
from pathlib import Path
_REPO = Path(__file__).resolve().parents[2]
_CRATE_DIR = _REPO / "src" / "rust" / "capability_ledger"
_LIB_PATH = Path(
os.environ.get("LEDGER_RUST_LIB", _CRATE_DIR / "target" / "release" / "libcapability_ledger.so")
)
SYMBOLS = (
"cl_new", "cl_define", "cl_get", "cl_material_change", "cl_move",
"cl_supersede", "cl_retire", "cl_active", "cl_by_component",
"cl_context_packet", "cl_save", "cl_classify_version_bump",
"cl_bump_version", "cl_attach_spec", "cl_revise_spec",
"cl_spec_drift_status", "cl_spec_context",
)
_lib = None
class LedgerCoreUnavailable(FileNotFoundError):
"""The Rust ledger 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 LedgerCoreUnavailable(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
# 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) -> dict:
"""Returns the FULL success envelope: {"value": ..., "doc": ...} for
mutators (the updated ledger doc travels beside the oracle output so
the facade can thread self.doc), {"value": ...} for queries. Errors
raise RuntimeError(err) — the facade's except-path falls back to the
Python body, which re-raises the proper exception type."""
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
def new(now: str, file_text: str | None = None) -> dict:
payload: dict = {"now": now}
if file_text is not None:
payload["file_text"] = file_text
return _call("cl_new", payload)
def define(doc: dict, now: str, **kwargs) -> dict:
return _call("cl_define", {"doc": doc, "now": now, **kwargs})
def get(doc: dict, capability_id: str) -> dict | None:
return _call("cl_get", {"doc": doc, "capability_id": capability_id})
def material_change(doc: dict, now: str, capability_id: str, note: str,
generation: str = "", source_locations=None) -> dict:
payload = {"doc": doc, "now": now, "capability_id": capability_id,
"note": note, "generation": generation}
if source_locations is not None:
payload["source_locations"] = source_locations
return _call("cl_material_change", payload)
def move(doc: dict, now: str, capability_id: str, new_locations: list) -> dict:
return _call("cl_move", {"doc": doc, "now": now,
"capability_id": capability_id,
"new_locations": new_locations})
def supersede(doc: dict, now: str, capability_id: str, by: str,
note: str = "") -> dict:
return _call("cl_supersede", {"doc": doc, "now": now,
"capability_id": capability_id,
"by": by, "note": note})
def retire(doc: dict, now: str, capability_id: str, note: str = "") -> dict:
return _call("cl_retire", {"doc": doc, "now": now,
"capability_id": capability_id, "note": note})
def active(doc: dict) -> list:
return _call("cl_active", {"doc": doc})
def by_component(doc: dict, component: str) -> list:
return _call("cl_by_component", {"doc": doc, "component": component})
def context_packet(doc: dict, capability_id: str) -> dict:
return _call("cl_context_packet", {"doc": doc, "capability_id": capability_id})
def save(doc: dict, now: str) -> dict:
return _call("cl_save", {"doc": doc, "now": now})
def classify_version_bump(*, contract_changed: bool, behaviour_added: bool,
consumer_break: bool,
implementation_only: bool) -> str:
return _call("cl_classify_version_bump", {
"contract_changed": contract_changed,
"behaviour_added": behaviour_added,
"consumer_break": consumer_break,
"implementation_only": implementation_only,
})
def bump_version(version: str, kind: str) -> str:
return _call("cl_bump_version", {"version": version, "kind": kind})
def attach_spec(doc: dict, now: str, capability_id: str, **kwargs) -> dict:
return _call("cl_attach_spec", {"doc": doc, "now": now,
"capability_id": capability_id, **kwargs})
def revise_spec(doc: dict, now: str, capability_id: str, **kwargs) -> dict:
return _call("cl_revise_spec", {"doc": doc, "now": now,
"capability_id": capability_id, **kwargs})
def spec_drift_status(doc: dict, capability_id: str) -> dict:
return _call("cl_spec_drift_status", {"doc": doc,
"capability_id": capability_id})
def spec_context(rec: dict) -> dict:
return _call("cl_spec_context", {"rec": rec})
+38
View File
@@ -9,6 +9,13 @@ it belongs to — or proposes a new topic when nothing fits.
No preset categories anywhere: the seed set is empty, and every
category in the topology was earned by co-firing.
Dichotomy wave #2479 tranche (contract
BUILDER-2026-10-11T23:55:54Z-982aa3-factory-wave): the deterministic
topology kernel is ported to FULL Rust (src/rust/concept_topology,
parity oracle frozen_ct_truth.json). This facade delegates to the Rust
core via rune/ct_loader when built; the Python implementation below is
the strangler fallback, pinned with RUNE_CT_CORE=python.
"""
from __future__ import annotations
@@ -17,9 +24,28 @@ DECAY_PER_TICK = 0.95
CLUSTER_THRESHOLD = 2.0
def _core():
"""The Rust concept_topology core when available (strangler: Python
fallback is load-bearing). RUNE_CT_CORE=python pins the fallback."""
import os
if os.environ.get("RUNE_CT_CORE", "auto") == "python":
return None
try:
from rune import ct_loader
except Exception: # noqa: BLE001 — any loader failure means fallback, never a broken call
return None
try:
return ct_loader if ct_loader.available() else None
except Exception: # noqa: BLE001 — availability probe must never crash the fallback
return None
def cofire(topology: dict, topics: list) -> dict:
"""Record one co-firing event: every pair in `topics` gains an edge
(weight +1). Topics are created on first sight (no preset list)."""
core = _core()
if core is not None:
return core.cofire(topology, topics)
topology = dict(topology)
edges = dict(topology.get("edges", {}))
seen = dict(topology.get("seen", {}))
@@ -36,6 +62,9 @@ def cofire(topology: dict, topics: list) -> dict:
def decay(topology: dict) -> dict:
"""Decay all edges one tick (stale associations weaken)."""
core = _core()
if core is not None:
return core.decay(topology)
topology = dict(topology)
edges = {k: v * DECAY_PER_TICK for k, v in topology.get("edges", {}).items()
if v * DECAY_PER_TICK >= 0.5}
@@ -45,6 +74,9 @@ def decay(topology: dict) -> dict:
def clusters(topology: dict, threshold: float = CLUSTER_THRESHOLD) -> list:
"""Emergent clusters: connected components over edges >= threshold."""
core = _core()
if core is not None:
return core.clusters(topology, threshold)
edges = {k: v for k, v in topology.get("edges", {}).items()
if v >= threshold}
parent: dict = {}
@@ -73,6 +105,9 @@ def clusters(topology: dict, threshold: float = CLUSTER_THRESHOLD) -> list:
def suggest(topology: dict, fact_topics: list) -> dict:
"""Suggest the cluster a new fact belongs to — or a new topic."""
core = _core()
if core is not None:
return core.suggest(topology, fact_topics)
cls = clusters(topology)
best, best_overlap = None, 0
for c in cls:
@@ -87,4 +122,7 @@ def suggest(topology: dict, fact_topics: list) -> dict:
def new_topology() -> dict:
"""The empty seed: no preset categories."""
core = _core()
if core is not None:
return core.new_topology()
return {"edges": {}, "seen": {}}
+103
View File
@@ -0,0 +1,103 @@
#!/usr/bin/env python3
"""ct_loader.py — ctypes loader for the Rust concept_topology core
(dichotomy wave #2479 tranche, contract
BUILDER-2026-10-11T23:55:54Z-982aa3-factory-wave). Mirrors broker_loader.py:
FFI contract (doc-envelope law — every call returns {"ok", "value"|"err"}):
* ct_cofire({"topology", "topics"}) -> topology dict
* ct_decay({"topology"}) -> topology dict
* ct_clusters({"topology", "threshold"?}) -> cluster list
* ct_suggest({"topology", "fact_topics"}) -> suggestion dict
* ct_new_topology({}) -> empty topology
Free: capability_scoring's cs_free_string (shared process). NEVER re-export
a free symbol here. Override the .so with CT_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" / "concept_topology"
_LIB_PATH = Path(
os.environ.get("CT_RUST_LIB", _CRATE_DIR / "target" / "release" / "libconcept_topology.so")
)
_lib = None
class ConceptTopologyUnavailable(FileNotFoundError):
"""The Rust topology core is not built. Callers fall back to Python
(strangler law; RUNE_CT_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 ConceptTopologyUnavailable(str(_LIB_PATH))
lib = ctypes.CDLL(str(_LIB_PATH))
for sym in ("ct_cofire", "ct_decay", "ct_clusters", "ct_suggest", "ct_new_topology"):
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 cofire(topology: dict, topics: list) -> dict:
return _call("ct_cofire", {"topology": topology, "topics": topics})
def decay(topology: dict) -> dict:
return _call("ct_decay", {"topology": topology})
def clusters(topology: dict, threshold: float | None = None) -> list:
payload = {"topology": topology}
if threshold is not None:
payload["threshold"] = threshold
return _call("ct_clusters", payload)
def suggest(topology: dict, fact_topics: list) -> dict:
return _call("ct_suggest", {"topology": topology, "fact_topics": fact_topics})
def new_topology() -> dict:
return _call("ct_new_topology", {})
+158
View File
@@ -0,0 +1,158 @@
#!/usr/bin/env python3
"""ctypes loader for the Rust drift_cache core (dichotomy wave, #2479
tranche). Mirrors broker_loader.py's contract:
FFI contract (doc-envelope law — every call returns {"ok", "value"|"err"}):
* dcc_compute_priority({entry}) -> {priority}
* dcc_qualifies_for_cache({entry}) -> {qualifies}
* dcc_add({entries, entry_id, entry, now}) -> {entries, admitted, evicted}
* dcc_access({entries, entry_id, now}) -> {entries, changed}
* dcc_contains({entries, entry_id}) -> {contains}
* dcc_get_priority({entries, entry_id}) -> {priority}
* dcc_update_entries({entries, rows}) -> {entries}
* dcc_get_stats({entries}) -> {stats}
No-clock law: `now` (aware UTC ISO-8601) is always an argument to the
stamping calls — the kernel never reads the wall clock.
Boundary: the sqlite read (update_from_store's per-id SELECT — passed in
as `rows`) and the cache FILE IO stay in the Python facade
(drift_cache.py); the kernel is a pure function of their results.
The entries map is keyed by entry id (int in the facade). JSON object
keys are strings, so this loader converts int->str on the way in and
str->int on the way out (mirroring the oracle's `int(k)` _load coercion).
FFI segfault law: every symbol gets explicit argtypes + restype (c_void_p
for returned char* — the default c_int truncates 64-bit pointers).
Strings are freed via capability_scoring's cs_free_string — the kernel
exports NO free symbol of its own (duplicate exports break the cdylib
link when sibling crates share one process).
Override the .so with DC_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" / "drift_cache"
_LIB_PATH = Path(
os.environ.get(
"DC_RUST_LIB", _CRATE_DIR / "target" / "release" / "libdrift_cache.so"
)
)
_SYMBOLS = (
"dcc_compute_priority",
"dcc_qualifies_for_cache",
"dcc_add",
"dcc_access",
"dcc_contains",
"dcc_get_priority",
"dcc_update_entries",
"dcc_get_stats",
)
_lib = None
class DriftCacheCoreUnavailable(FileNotFoundError):
"""The Rust drift_cache 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 DriftCacheCoreUnavailable(
f"Rust drift_cache core not built: {_LIB_PATH} missing — run "
f"`cargo build --release` in {_CRATE_DIR}"
)
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
_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 RuntimeError(doc.get("err", f"{sym} failed"))
return doc["value"]
def _entries_in(entries: dict | None) -> dict:
return {str(k): v for k, v in (entries or {}).items()}
def _entries_out(entries) -> dict:
return {int(k): v for k, v in entries.items()}
def compute_priority(entry: dict) -> float:
return _call("dcc_compute_priority", {"entry": entry})["priority"]
def qualifies_for_cache(entry: dict) -> bool:
return _call("dcc_qualifies_for_cache", {"entry": entry})["qualifies"]
def add(entries: dict, entry_id: int, entry: dict, now: str) -> dict:
out = _call("dcc_add", {"entries": _entries_in(entries), "entry_id": entry_id,
"entry": entry, "now": now})
out["entries"] = _entries_out(out["entries"])
return out
def access(entries: dict, entry_id: int, now: str) -> dict:
out = _call("dcc_access", {"entries": _entries_in(entries),
"entry_id": entry_id, "now": now})
out["entries"] = _entries_out(out["entries"])
return out
def contains(entries: dict, entry_id: int) -> bool:
return _call("dcc_contains", {"entries": _entries_in(entries),
"entry_id": entry_id})["contains"]
def get_priority(entries: dict, entry_id: int) -> float:
return _call("dcc_get_priority", {"entries": _entries_in(entries),
"entry_id": entry_id})["priority"]
def update_entries(entries: dict, rows: dict) -> dict:
return _entries_out(_call("dcc_update_entries", {
"entries": _entries_in(entries), "rows": rows})["entries"])
def get_stats(entries: dict) -> dict:
return _call("dcc_get_stats", {"entries": _entries_in(entries)})["stats"]
+67 -2
View File
@@ -46,6 +46,23 @@ CACHE_BOOST_ESTABLISHED = 1.5 # multiplier for established entries
from rune.drift_core import _ms_core # noqa: E402 (no cycle)
def _dc_core():
"""Dichotomy wave #2479: Rust core first (RUNE_DC_CORE=python pins the
Python fallback — parity harnesses). The kernel owns the priority /
qualification / admission / eviction / stats state machine; the
sqlite read and the cache FILE IO stay here."""
if os.environ.get("RUNE_DC_CORE", "auto") == "python":
return None
try:
from rune import dc_loader
if dc_loader.available():
return dc_loader
except ImportError:
pass
return None
def _py_compute_priority(entry: dict) -> float:
"""Compute cache priority: wash_count × importance × status boost."""
wash = entry.get("wash_count", 0) or 0
@@ -73,7 +90,11 @@ def _py_qualifies_for_cache(entry: dict) -> bool:
def compute_priority(entry: dict) -> float:
"""Module-level priority (Rust core when built)."""
"""Module-level priority (drift_cache Rust core, then the legacy
drift-core chain, then the Python fallback)."""
core = _dc_core()
if core is not None:
return core.compute_priority(entry)
core = _ms_core()
if core is not None:
return core.compute_priority(entry)
@@ -81,7 +102,11 @@ def compute_priority(entry: dict) -> float:
def qualifies_for_cache(entry: dict) -> bool:
"""Module-level qualification (Rust core when built)."""
"""Module-level qualification (drift_cache Rust core, then the legacy
drift-core chain, then the Python fallback)."""
core = _dc_core()
if core is not None:
return core.qualifies_for_cache(entry)
core = _ms_core()
if core is not None:
return core.qualifies_for_cache(entry)
@@ -122,6 +147,16 @@ class DriftCache:
- wash_count >= MIN_WASH_FOR_CACHE (proven through use), OR
- importance >= 0.8 (high importance, worth caching even if untested)
"""
core = _dc_core()
if core is not None:
out = core.add(
self.entries, entry_id, entry, datetime.now(timezone.utc).isoformat()
)
if out["admitted"]:
self.entries = out["entries"]
self._save()
return
wash = entry.get("wash_count", 0) or 0
if not qualifies_for_cache(entry):
return # Not proven enough and not important enough
@@ -144,6 +179,15 @@ class DriftCache:
def access(self, entry_id: int):
"""Record a cache access (reinforces the entry)."""
core = _dc_core()
if core is not None:
out = core.access(
self.entries, entry_id, datetime.now(timezone.utc).isoformat()
)
if out["changed"]:
self.entries = out["entries"]
self._save()
return
if entry_id in self.entries:
self.entries[entry_id]["last_accessed"] = datetime.now(timezone.utc).isoformat()
self.entries[entry_id]["access_count"] = self.entries[entry_id].get("access_count", 0) + 1
@@ -151,10 +195,16 @@ class DriftCache:
def contains(self, entry_id: int) -> bool:
"""Check if an entry is in the cache."""
core = _dc_core()
if core is not None:
return core.contains(self.entries, entry_id)
return entry_id in self.entries
def get_priority(self, entry_id: int) -> float:
"""Get the cache priority of an entry."""
core = _dc_core()
if core is not None:
return core.get_priority(self.entries, entry_id)
if entry_id in self.entries:
return self.entries[entry_id].get("priority", 0.0)
return 0.0
@@ -169,6 +219,18 @@ class DriftCache:
def update_from_store(self, db):
"""Sync cache with memory store — update priorities from current wash counts."""
core = _dc_core()
if core is not None:
rows = {}
for entry_id in list(self.entries.keys()):
row = db.execute(
"SELECT wash_count, importance, rock_status FROM entries WHERE id = ?",
(entry_id,),
).fetchone()
rows[str(entry_id)] = list(row) if row else None
self.entries = core.update_entries(self.entries, rows)
self._save()
return
for entry_id in list(self.entries.keys()):
row = db.execute(
"SELECT wash_count, importance, rock_status FROM entries WHERE id = ?", (entry_id,)
@@ -189,6 +251,9 @@ class DriftCache:
def get_stats(self) -> dict:
"""Get cache statistics."""
core = _dc_core()
if core is not None:
return core.get_stats(self.entries)
if not self.entries:
return {
"size": 0,
+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 = [
+124
View File
@@ -0,0 +1,124 @@
#!/usr/bin/env python3
"""ctypes loader for the Rust lexicon_evolution core (dichotomy wave
#2479 tranche). Mirrors broker_loader.py's contract:
FFI contract (doc-envelope law — every call returns {"ok", "value"|"err"}):
* le_content_words({text}) -> words list
* le_distinctive_terms({fact, corpus_texts}) -> sorted terms list
* le_shared_distinctive({fact, existing_values, corpus_texts}) -> sorted shared list
* le_coherence({fact, existing_values, corpus_texts}) -> verdict str
* le_grounded({fact, source_texts, min_distinct}) -> bool
* le_stem({word}) -> stem str
* le_promote_eligible({proposal, existing, evidence_strength,
min_strength, corpus_texts}) -> {eligible, reason}
Free: capability_scoring's cs_free_string (shared process). Override the
.so with LE_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" / "lexicon_evolution"
LIB_PATH = Path(
os.environ.get("LE_RUST_LIB", _CRATE_DIR / "target" / "release" / "liblexicon_evolution.so")
)
_lib = None
class LexiconUnavailable(FileNotFoundError):
"""The Rust lexicon 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 LexiconUnavailable(str(LIB_PATH))
lib = ctypes.CDLL(str(LIB_PATH))
for sym in ("le_content_words", "le_distinctive_terms", "le_shared_distinctive",
"le_coherence", "le_grounded", "le_stem", "le_promote_eligible"):
getattr(lib, sym).argtypes = [ctypes.c_void_p]
getattr(lib, sym).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 content_words(text) -> list:
return _call("le_content_words", {"text": text})["words"]
def distinctive_terms(fact, corpus_texts) -> list:
return _call("le_distinctive_terms", {
"fact": fact, "corpus_texts": list(corpus_texts),
})["terms"]
def shared_distinctive(fact, existing_values, corpus_texts) -> list:
return _call("le_shared_distinctive", {
"fact": fact, "existing_values": list(existing_values),
"corpus_texts": list(corpus_texts),
})["shared"]
def coherence(fact, existing_values, corpus_texts=()) -> str:
return _call("le_coherence", {
"fact": fact, "existing_values": list(existing_values),
"corpus_texts": list(corpus_texts),
})["verdict"]
def grounded(fact, source_texts, min_distinct=1) -> bool:
return _call("le_grounded", {
"fact": fact, "source_texts": list(source_texts),
"min_distinct": min_distinct,
})["grounded"]
def stem(word) -> str:
return _call("le_stem", {"word": word})["stem"]
def promote_eligible(proposal, existing, evidence_strength, min_strength=3,
corpus_texts=()) -> dict:
return _call("le_promote_eligible", {
"proposal": proposal, "existing": list(existing),
"evidence_strength": evidence_strength, "min_strength": min_strength,
"corpus_texts": list(corpus_texts),
})
+82 -7
View File
@@ -36,6 +36,11 @@ Decisions:
Usage:
from lexicon_evolution import coherence, grounded, promote_eligible
Dichotomy wave #2479: the pure lexical decisions run through the FULL Rust
core (src/rust/lexicon_evolution) when built; RUNE_LE_CORE=python pins the
Python fallback — parity harnesses. The _py_ variants are the load-bearing
fallback + parity oracle; the ctypes seam is rune.le_loader.
"""
import os
@@ -67,7 +72,34 @@ _NEGATION = {"not", "never", "don't", "doesn't", "cannot", "can't", "no",
_DISTINCTIVE_DF = 0.05 # same rare-word threshold as recall/associations
def _le_core():
"""Dichotomy wave #2479: Rust core first (RUNE_LE_CORE=python pins the
Python fallback — parity harnesses). The kernel owns the entire pure
lexical decision surface (tokenize, stem, distinctiveness, verdicts)."""
if os.environ.get("RUNE_LE_CORE", "auto") == "python":
return None
try:
from rune import le_loader
except ImportError:
try:
import le_loader
except ImportError:
return None
try:
return le_loader if le_loader.available() else None
except Exception:
return None
def content_words(text):
"""Significant content words (length > 3, not stop/framing)."""
core = _le_core()
if core is not None:
return core.content_words(text)
return _py_content_words(text)
def _py_content_words(text):
"""Significant content words (length > 3, not stop/framing)."""
return [w for w in re.findall(r"[a-z]{4,}", (text or "").lower())
if w not in _STOP and w not in _FRAMING]
@@ -78,8 +110,16 @@ def distinctive_terms(fact, corpus_texts):
Same signal as the recall gate: a term common in the store ("black",
"physics") is NOT a relation; a rare term ("celesta", "oatmilk") is."""
core = _le_core()
if core is not None:
return set(core.distinctive_terms(fact, corpus_texts))
return _py_distinctive_terms(fact, corpus_texts)
def _py_distinctive_terms(fact, corpus_texts):
"""The terms in `fact` that are RARE across the corpus (< 5% df)."""
total = max(len(corpus_texts), 1)
fact_terms = set(content_words(fact))
fact_terms = set(_py_content_words(fact))
if not fact_terms:
return set()
# stem-aware document frequency (migrate/migration count as one term)
@@ -94,7 +134,15 @@ def distinctive_terms(fact, corpus_texts):
def shared_distinctive(fact, existing_values, corpus_texts):
"""Distinctive terms shared between `fact` and existing values."""
distinct = distinctive_terms(fact, corpus_texts)
core = _le_core()
if core is not None:
return set(core.shared_distinctive(fact, existing_values, corpus_texts))
return _py_shared_distinctive(fact, existing_values, corpus_texts)
def _py_shared_distinctive(fact, existing_values, corpus_texts):
"""Distinctive terms shared between `fact` and existing values."""
distinct = _py_distinctive_terms(fact, corpus_texts)
if not distinct:
return set()
shared = set()
@@ -114,11 +162,19 @@ def coherence(fact, existing_values, corpus_texts=()):
Returns "conflict" | "consistent" | "novel".
"""
core = _le_core()
if core is not None:
return core.coherence(fact, existing_values, corpus_texts)
return _py_coherence(fact, existing_values, corpus_texts)
def _py_coherence(fact, existing_values, corpus_texts=()):
"""Classify a new value against existing ones, lexicon-aware."""
values = [v for v in existing_values if v and v.strip()]
if not values:
return "novel"
corpus = corpus_texts or values
shared = shared_distinctive(fact, values, corpus)
shared = _py_shared_distinctive(fact, values, corpus)
if not shared:
# No distinctive shared term: the value is a NEW axis. This is NOT a
# contradiction (a shared common word like "work" is meaningless), and
@@ -143,10 +199,18 @@ def grounded(fact, source_texts, min_distinct=1):
using a light suffix fold — exact word identity is too strict for natural
sources.
"""
core = _le_core()
if core is not None:
return core.grounded(fact, source_texts, min_distinct)
return _py_grounded(fact, source_texts, min_distinct)
def _py_grounded(fact, source_texts, min_distinct=1):
"""Is `fact` grounded in real material via a distinctive shared term?"""
srcs = [s for s in source_texts if s and s.strip()]
if not srcs:
return False
fact_terms = set(content_words(fact))
fact_terms = set(_py_content_words(fact))
if not fact_terms:
return False
joined = " ".join(s.lower() for s in srcs)
@@ -205,6 +269,17 @@ def promote_eligible(proposal, existing, evidence_strength, min_strength=3,
real absorbed material (the corpus), so arbitrary trivia ("pizza on
fridays") cannot be promoted as a principle no matter the sighting count.
"""
core = _le_core()
if core is not None:
return core.promote_eligible(proposal, existing, evidence_strength,
min_strength, corpus_texts)
return _py_promote_eligible(proposal, existing, evidence_strength,
min_strength, corpus_texts)
def _py_promote_eligible(proposal, existing, evidence_strength, min_strength=3,
corpus_texts=()):
"""Is a persona/constitution promotion eligible, lexicon-aware?"""
if evidence_strength < min_strength:
return {"eligible": False,
"reason": f"evidence strength {evidence_strength} < {min_strength}"}
@@ -212,17 +287,17 @@ def promote_eligible(proposal, existing, evidence_strength, min_strength=3,
if not existing:
return {"eligible": True, "reason": "empty block — first principle"}
corpus = corpus_texts or existing
shared = shared_distinctive(proposal, existing, corpus)
shared = _py_shared_distinctive(proposal, existing, corpus)
if shared:
return {"eligible": True,
"reason": f"shares distinctive term(s): {sorted(shared)[:3]}"}
# No distinctive shared term: a NOVEL axis is eligible only if it is
# GROUNDED in the absorbed corpus via a distinctive term (a real principle
# names something real), and the proposal is not trivial framing.
terms = content_words(proposal)
terms = _py_content_words(proposal)
if not terms:
return {"eligible": False, "reason": "no lexical signal"}
if grounded(proposal, corpus, min_distinct=1):
if _py_grounded(proposal, corpus, min_distinct=1):
return {"eligible": True,
"reason": f"novel axis grounded in corpus ({len(terms)} terms)"}
return {"eligible": False,
+113 -5
View File
@@ -32,6 +32,24 @@ KINDS = {"message", "reply", "tool_call", "tool_result", "revision", "branch_hea
EDGE_TYPES = {"parent", "reply_to", "calls", "results_in", "revises", "supersedes", "branches_from"}
def _mg_core():
"""Dichotomy wave #2479: Rust core first (RUNE_MG_CORE=python pins the
Python fallback — parity harnesses). The kernel owns the graph state
machine (load parsing + schema gate, node construction, revise/branch
wiring, lineage/history walks, flat migration, cues); the uuid/clock
seams, the save FILE IO and the json.dumps renderings stay here."""
if os.environ.get("RUNE_MG_CORE", "auto") == "python":
return None
try:
from rune import mg_loader
if mg_loader.available():
return mg_loader
except ImportError:
pass
return None
def _now() -> str:
return datetime.now(timezone.utc).isoformat()
@@ -48,6 +66,18 @@ class MessageGraph:
# -- persistence ----------------------------------------------------
def _load(self) -> None:
core = _mg_core()
if core is not None:
text = None
if self.path.exists():
try:
text = self.path.read_text()
except OSError:
text = None
state = core.load(text)
self.nodes = state["nodes"]
self.order = state["order"]
return
if not self.path.exists():
return
try:
@@ -61,8 +91,13 @@ class MessageGraph:
self.order.append(n["id"])
def save(self) -> None:
core = _mg_core()
if core is not None:
doc = core.save({"nodes": self.nodes, "order": self.order})
else:
doc = {"schema": SCHEMA, "nodes": [self.nodes[i] for i in self.order]}
self.path.parent.mkdir(parents=True, exist_ok=True)
self.path.write_text(json.dumps({"schema": SCHEMA, "nodes": [self.nodes[i] for i in self.order]}, indent=2))
self.path.write_text(json.dumps(doc, indent=2))
# -- append ---------------------------------------------------------
def add(
@@ -73,6 +108,15 @@ class MessageGraph:
raise ValueError(f"unknown kind {kind!r}")
if edge not in EDGE_TYPES:
raise ValueError(f"unknown edge {edge!r}")
core = _mg_core()
if core is not None:
out = core.add(
{"nodes": self.nodes, "order": self.order},
uuid.uuid4().hex[:12], _now(), role, content, kind, parent, edge,
)
self.nodes = out["graph"]["nodes"]
self.order = out["graph"]["order"]
return out["node"]
node = {
"id": uuid.uuid4().hex[:12],
"kind": kind,
@@ -88,21 +132,49 @@ class MessageGraph:
return node
def tool_call(self, name: str, args: dict, parent: str | None = None) -> dict:
core = _mg_core()
if core is not None:
out = core.tool_call(
{"nodes": self.nodes, "order": self.order},
uuid.uuid4().hex[:12], _now(),
json.dumps({"call": name, "args": args}), parent,
)
self.nodes = out["graph"]["nodes"]
self.order = out["graph"]["order"]
return out["node"]
return self.add("tool", json.dumps({"call": name, "args": args}), kind="tool_call", parent=parent, edge="calls")
def tool_result(self, call_id: str, result: str) -> dict:
call = self.nodes.get(call_id)
if call is None:
if call_id not in self.nodes:
raise KeyError(f"no such call {call_id!r}")
core = _mg_core()
if core is not None:
out = core.tool_result(
{"nodes": self.nodes, "order": self.order},
uuid.uuid4().hex[:12], _now(), call_id, result,
)
self.nodes = out["graph"]["nodes"]
self.order = out["graph"]["order"]
return out["node"]
call = self.nodes[call_id]
return self.add("tool", result, kind="tool_result", parent=call_id, edge="results_in")
# -- revision / branch ----------------------------------------------
def revise(self, node_id: str, new_content: str) -> dict:
"""Regenerate an answer without losing provenance: the old node is
marked superseded; the revision carries the supersedes edge."""
old = self.nodes.get(node_id)
if old is None:
if node_id not in self.nodes:
raise KeyError(f"no such message {node_id!r}")
core = _mg_core()
if core is not None:
out = core.revise(
{"nodes": self.nodes, "order": self.order},
node_id, new_content, uuid.uuid4().hex[:12], _now(),
)
self.nodes = out["graph"]["nodes"]
self.order = out["graph"]["order"]
return out["node"]
old = self.nodes[node_id]
rev = self.add(old["role"], new_content, kind="revision", parent=old.get("parent"), edge="revises")
rev["edges"].append({"type": "supersedes", "to": node_id})
old["superseded_by"] = rev["id"]
@@ -112,6 +184,15 @@ class MessageGraph:
"""Start a new conversation branch off any message."""
if node_id not in self.nodes:
raise KeyError(f"no such message {node_id!r}")
core = _mg_core()
if core is not None:
out = core.branch(
{"nodes": self.nodes, "order": self.order},
node_id, uuid.uuid4().hex[:12], _now(),
)
self.nodes = out["graph"]["nodes"]
self.order = out["graph"]["order"]
return out["node"]
return self.add("system", f"branched from {node_id}", kind="branch_head", parent=node_id, edge="branches_from")
# -- lineage context -------------------------------------------------
@@ -123,6 +204,9 @@ class MessageGraph:
node_id = self.order[-1] if self.order else None
if node_id is None:
return []
core = _mg_core()
if core is not None:
return core.lineage({"nodes": self.nodes, "order": self.order}, node_id)
chain: list[dict] = []
seen = set()
cur = node_id
@@ -142,6 +226,9 @@ class MessageGraph:
def history(self, node_id: str) -> list[dict]:
"""Every revision of a message, oldest first (the provenance trail)."""
core = _mg_core()
if core is not None:
return core.history({"nodes": self.nodes, "order": self.order}, node_id)
trail = []
# find the root: walk supersedes edges backwards
cur = node_id
@@ -168,6 +255,24 @@ class MessageGraph:
"""Schema migration: the flat {role, content} transcript becomes a
chained graph (arrival order, parent links). Compatibility preserved —
no conversation is lost in the upgrade."""
core = _mg_core()
if core is not None:
g = cls.__new__(cls)
g.path = Path(path) if path else GRAPH_PATH
text = None
if g.path.exists():
try:
text = g.path.read_text()
except OSError:
text = None
state = core.migrate_flat(
text, list(transcript),
[uuid.uuid4().hex[:12] for _ in transcript],
[_now() for _ in transcript],
)
g.nodes = state["nodes"]
g.order = state["order"]
return g
g = cls(path=path)
for m in transcript:
g.add(m.get("role", "system"), m.get("content", ""), kind="message")
@@ -177,6 +282,9 @@ class MessageGraph:
@staticmethod
def cue(node: dict) -> str:
"""One quiet relationship line per node (no visual clutter)."""
core = _mg_core()
if core is not None:
return core.cue(node)
for e in node.get("edges", []):
if e["type"] == "supersedes":
return f"replaces {e['to'][:8]}"
+152
View File
@@ -0,0 +1,152 @@
#!/usr/bin/env python3
"""ctypes loader for the Rust message_graph core (dichotomy wave, #2479
tranche). Mirrors broker_loader.py's contract:
FFI contract (doc-envelope law — every call returns {"ok", "value"|"err"}):
* mg_load({"text": str|null}) -> graph wire {"nodes", "order"}
* mg_save({"graph"}) -> save doc {"schema", "nodes"}
* mg_add({graph, id, ts, role, content, kind, parent, edge}) -> {graph, node}
* mg_tool_call({graph, id, ts, content, parent}) -> {graph, node}
* mg_tool_result({graph, id, ts, call_id, result}) -> {graph, node}
* mg_revise({graph, node_id, new_content, id, ts}) -> {graph, node}
* mg_branch({graph, node_id, id, ts}) -> {graph, node}
* mg_lineage({graph, node_id}) -> [nodes]
* mg_history({graph, node_id}) -> [nodes]
* mg_migrate_flat({text, transcript, ids, tss}) -> graph wire
* mg_cue({"node"}) -> cue string
Graph wire: {"nodes": {id: node} (insertion-ordered), "order": [ids]}.
Errors ride the envelope typed — {"ok": false, "err": msg, "type":
"ValueError"|"KeyError"|"AttributeError"|"TypeError"} — and the loader
raises the MATCHING Python exception (the oracle raises those; the parity
harness compares exception types and messages, so the kernel's guard
clauses must round-trip as the same Python error the oracle raises).
Free: capability_scoring's cs_free_string (shared process). Override the
.so with MG_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" / "message_graph"
_LIB_PATH = Path(
os.environ.get("MG_RUST_LIB", _CRATE_DIR / "target" / "release" / "libmessage_graph.so")
)
_ERR_TYPES = {
"ValueError": ValueError,
"KeyError": KeyError,
"AttributeError": AttributeError,
"TypeError": TypeError,
}
_lib = None
class MessageGraphCoreUnavailable(FileNotFoundError):
"""The Rust message-graph 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 MessageGraphCoreUnavailable(str(_LIB_PATH))
lib = ctypes.CDLL(str(_LIB_PATH))
for sym in ("mg_load", "mg_save", "mg_add", "mg_tool_call", "mg_tool_result",
"mg_revise", "mg_branch", "mg_lineage", "mg_history",
"mg_migrate_flat", "mg_cue"):
getattr(lib, sym).argtypes = [ctypes.c_void_p]
getattr(lib, sym).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"):
etype = doc.get("type", "RuntimeError")
raise _ERR_TYPES.get(etype, RuntimeError)(doc.get("err", f"{sym} failed"))
return doc["value"]
def load(text: str | None) -> dict:
return _call("mg_load", {"text": text})
def save(graph: dict) -> dict:
return _call("mg_save", {"graph": graph})
def add(graph: dict, node_id: str, ts: str, role, content, kind: str,
parent: str | None, edge: str) -> dict:
return _call("mg_add", {"graph": graph, "id": node_id, "ts": ts, "role": role,
"content": content, "kind": kind, "parent": parent,
"edge": edge})
def tool_call(graph: dict, node_id: str, ts: str, content: str,
parent: str | None) -> dict:
return _call("mg_tool_call", {"graph": graph, "id": node_id, "ts": ts,
"content": content, "parent": parent})
def tool_result(graph: dict, node_id: str, ts: str, call_id: str, result) -> dict:
return _call("mg_tool_result", {"graph": graph, "id": node_id, "ts": ts,
"call_id": call_id, "result": result})
def revise(graph: dict, node_id: str, new_content, rev_id: str, ts: str) -> dict:
return _call("mg_revise", {"graph": graph, "node_id": node_id,
"new_content": new_content, "id": rev_id, "ts": ts})
def branch(graph: dict, node_id: str, head_id: str, ts: str) -> dict:
return _call("mg_branch", {"graph": graph, "node_id": node_id,
"id": head_id, "ts": ts})
def lineage(graph: dict, node_id: str | None) -> list:
return _call("mg_lineage", {"graph": graph, "node_id": node_id})
def history(graph: dict, node_id: str) -> list:
return _call("mg_history", {"graph": graph, "node_id": node_id})
def migrate_flat(text: str | None, transcript: list, ids: list, tss: list) -> dict:
return _call("mg_migrate_flat", {"text": text, "transcript": transcript,
"ids": ids, "tss": tss})
def cue(node: dict) -> str:
return _call("mg_cue", {"node": node})
+85
View File
@@ -0,0 +1,85 @@
#!/usr/bin/env python3
"""ctypes loader for the Rust outcome_ledger core (dichotomy wave, #2479
tranche). Mirrors to_loader.py's contract:
FFI contract (doc-envelope law):
* ol_run({op, db_path, record?, task_family?, model?}) ->
{"ok": true, "value": ...} | {"ok": false, "err": "..."}
The kernel is RUST-side per the outcome_ledger intent ledger — it owns the
ledger SQL (DDL, INSERT, the six-aggregate stats query) against the system
libsqlite3. The facade keeps makedirs, the CLI and test_ledger.
Free: capability_scoring's cs_free_string (shared process) — this crate
deliberately exports NO free symbol of its own. Override the .so with
OL_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" / "outcome_ledger"
_LIB_PATH = Path(os.environ.get("OL_RUST_LIB", _CRATE_DIR / "target" / "release" / "liboutcome_ledger.so"))
_lib = None
class OutcomeLedgerCoreUnavailable(FileNotFoundError):
"""The Rust outcome_ledger 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 OutcomeLedgerCoreUnavailable(str(_LIB_PATH))
lib = ctypes.CDLL(str(_LIB_PATH))
lib.ol_run.argtypes = [ctypes.c_void_p]
lib.ol_run.restype = ctypes.c_void_p
_lib = lib
return lib
def _call(payload: dict) -> dict:
lib = _load()
data = json.dumps(payload).encode()
buf = ctypes.create_string_buffer(data)
ptr = lib.ol_run(ctypes.cast(buf, ctypes.c_void_p))
if not ptr:
raise RuntimeError("ol_run 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 RuntimeError(doc.get("err", "ol_run failed"))
return doc
def run_op(op: str, db_path: str, *, record: dict | None = None,
task_family: str | None = None, model: str | None = None) -> dict:
"""Run one kernel op. Returns the envelope value (stats doc, null, ...)."""
payload: dict = {"op": op, "db_path": db_path}
if record is not None:
payload["record"] = record
if task_family is not None:
payload["task_family"] = task_family
if model is not None:
payload["model"] = model
return _call(payload)["value"]
+36 -4
View File
@@ -71,7 +71,13 @@ class OutcomeLedger:
self._init_db()
def _init_db(self):
"""Initialize the database."""
"""Initialize the database (Rust core when built; makedirs stay
Python — the adapter owns directory prep, the kernel owns the DDL)."""
core = _ol_core()
if core is not None:
os.makedirs(os.path.dirname(self.db_path), exist_ok=True)
core.run_op("init_db", self.db_path)
return
os.makedirs(os.path.dirname(self.db_path), exist_ok=True)
conn = sqlite3.connect(self.db_path)
try:
@@ -99,7 +105,12 @@ class OutcomeLedger:
conn.close()
def record(self, outcome: OutcomeRecord):
"""Record an outcome."""
"""Record an outcome (Rust core when built — the INSERT runs in the
kernel against the system libsqlite3)."""
core = _ol_core()
if core is not None:
core.run_op("record", self.db_path, record=outcome.to_dict())
return
conn = sqlite3.connect(self.db_path)
try:
conn.execute(
@@ -179,16 +190,37 @@ class OutcomeLedger:
}
def get_stats(self, task_family: str = None) -> dict:
"""Get statistics for a task family."""
"""Get statistics for a task family (Rust core when built — the
six-aggregate query runs in the kernel)."""
core = _ol_core()
if core is not None:
return core.run_op("get_stats", self.db_path, task_family=task_family)
if task_family:
return self._query_stats("WHERE task_family = ?", (task_family,))
return self._query_stats("", ())
def get_model_stats(self, model: str) -> dict:
"""Get statistics for a model."""
"""Get statistics for a model (Rust core when built)."""
core = _ol_core()
if core is not None:
return core.run_op("get_model_stats", self.db_path, model=model)
return self._query_stats("WHERE model = ?", (model,))
# ── Rust core delegation (dichotomy wave, #2479 tranche) ────────────────
def _ol_core():
"""The Rust outcome_ledger core when available (strangler law).
RUNE_OL_CORE=python pins the fallback."""
if os.environ.get("RUNE_OL_CORE", "auto") == "python":
return None
try:
from rune import ol_loader
except Exception: # noqa: BLE001 — any loader failure means fallback, never a broken call
return None
return ol_loader if ol_loader.available() else None
def test_ledger():
"""Test the outcome ledger."""
print("Testing Outcome Ledger...")
+337
View File
@@ -0,0 +1,337 @@
#!/usr/bin/env python3
"""pc_loader.py — ctypes loader for the Rust programme_continuation core
(dichotomy wave, #2479 tranche). Mirrors ci_loader.py's contract:
FFI contract (doc-envelope law):
* pc_canary({prog}) -> {"verdict", "next_action", "reason"}
* pc_eligible({prog}) -> ["stage_id", ...] priority-first
* pc_stage_to_dict({stage}) -> durable stage dict
* pc_save_doc({prog, saved_at}) -> durable programme record doc
* pc_load({doc | null}) -> programme dict or null
* pc_run_programme({prog, max_transitions}, op_host)
-> {"log", "states"}
* pc_stage_run({cfg, stage}, op_host) -> {"state", "result"}
* pc_run_construction({prog, cfg, max_transitions}, op_host)
-> {"log", "states", "results"}
The drivers run FULL Rust; the seams the oracle leaves to the caller —
the Scaffolding interface, the event bridge, the work callable, the
stage runner, and the clock — cross back as JSON ops served by
OpsProxy against the live Python objects. No free-string export here —
callers free through capability_scoring's cs_free_string.
Override the .so with PC_RUST_LIB (a FILE path).
"""
from __future__ import annotations
import copy
import ctypes
import json
import os
from pathlib import Path
_REPO = Path(__file__).resolve().parents[2]
_CRATE_DIR = _REPO / "src" / "rust" / "programme_continuation"
_LIB_PATH = Path(os.environ.get("PC_RUST_LIB",
_CRATE_DIR / "target" / "release" /
"libprogramme_continuation.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)
class ProgrammeContinuationCoreUnavailable(FileNotFoundError):
"""The Rust programme-continuation core is not built. Callers fall
back to the Python oracle."""
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 ProgrammeContinuationCoreUnavailable(str(_LIB_PATH))
lib = ctypes.CDLL(str(_LIB_PATH))
for name in ("pc_run_programme", "pc_stage_run", "pc_run_construction"):
fn = getattr(lib, name)
fn.argtypes = [ctypes.c_void_p, OP_CB, ctypes.c_void_p]
fn.restype = ctypes.c_void_p
for name in ("pc_canary", "pc_eligible", "pc_stage_to_dict",
"pc_save_doc", "pc_load"):
fn = getattr(lib, name)
fn.argtypes = [ctypes.c_void_p]
fn.restype = ctypes.c_void_p
_lib = lib
return lib
def _free(ptr) -> None:
# 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
def _unwrap(ptr) -> dict:
if not ptr:
raise RuntimeError("pc core returned null")
out = ctypes.string_at(ptr).decode()
_free(ptr)
doc = json.loads(out)
if not doc.get("ok"):
raise RuntimeError(doc.get("err", "pc core failed"))
return doc["value"]
def _call_simple(name: str, payload: dict):
lib = _load()
buf = ctypes.create_string_buffer(json.dumps(payload).encode())
ptr = getattr(lib, name)(ctypes.cast(buf, ctypes.c_void_p))
return _unwrap(ptr)
def _call_host(name: str, payload: dict, host):
"""Drive a host-op FFI export: the kernel issues JSON ops, `host`
serves each and the result returns through the envelope."""
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 = host(op)
except Exception as exc: # noqa: BLE001 — surfaced by the kernel
result = {"__host_error__": str(exc)[:500]}
data = json.dumps(_jsonable(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, name)(ctypes.cast(buf, ctypes.c_void_p), cb, None)
return _unwrap(ptr)
def _jsonable(v):
return json.loads(json.dumps(v, default=str))
def _entity_by_id(doc: dict, entity_id: str) -> dict | None:
for e in (doc or {}).get("entities", []):
if e.get("id") == entity_id:
return e
return None
class OpsProxy:
"""Serves the kernel's JSON ops against the live Python objects —
the mirror image of the truth generator's recording proxy."""
def __init__(self, iface=None, stages: dict | None = None, runner=None):
self.iface = iface
self.stages = stages or {}
self.runner = runner
self._doc = None
def __call__(self, op: dict):
name = op.get("op")
a = op.get("args") or {}
if name == "scaffold_load":
self._doc = self.iface.scaffold.load()
return copy.deepcopy(self._doc)
if name == "contract":
c = _entity_by_id(self._doc, f"contract:{a['contract_id']}") \
if self._doc else None
return {"contract": copy.deepcopy(c) if c else None,
"entity_id": (c or {}).get("id")}
if name == "scaffold_save":
return _jsonable(self.iface.scaffold.save(a["doc"]))
if name == "transition":
return _jsonable(self.iface.transition(
a["contract_id"], a["phase"],
evidence_ref=a.get("evidence_ref", "")))
if name == "close":
return _jsonable(self.iface.close(
a["contract_id"], observed_evidence=a["observed_evidence"],
scope_files=a.get("scope_files")))
if name == "work":
stage = self.stages[a["stage_id"]]
w = stage.result.get("work")
outcome = dict(w(self.iface, stage)) if callable(w) else dict(w)
return {"outcome": _jsonable(outcome)}
if name == "now":
from rune import programme_continuation as pc_mod
return {"now": pc_mod.time.strftime("%Y-%m-%dT%H:%M:%SZ",
pc_mod.time.gmtime()),
"epoch": pc_mod.time.time()}
if name == "register_process_wait":
from rune import event_wait as ew
ew.register_process_wait(
a["op"], contract_id=a["contract_id"], project=a["project"],
objective=a["objective"], watched_for=a["watched_for"],
watchdog_secs=a["watchdog_secs"])
return None
if name == "run_builder_under_events":
from rune import event_wait as ew
notifier = ew.run_builder_under_events(
a["async_cmd"], a["op"], daemon_url=a["daemon_url"])
return {"pid": notifier.pid}
if name == "await_resume_event":
from rune import event_wait as ew
return _jsonable(ew.await_resume_event(
a["op"], watchdog_secs=a["watchdog_secs"], pgid=a["pgid"]))
if name == "run_stage":
return {"state": self.runner(self.stages[a["stage_id"]])}
if name == "save_programme":
base = Path(a["base"])
base.mkdir(parents=True, exist_ok=True)
# oracle byte format: json.dumps(..., indent=1) — reordered to
# the oracle's key order first (serde maps are alphabetical)
(base / f"{a['doc']['programme_id']}.json").write_text(
json.dumps(_normalize_programme_doc(a["doc"]), indent=1))
return None
raise RuntimeError(f"pc kernel issued unknown op: {name}")
def _normalize_programme_doc(doc: dict) -> dict:
"""Reorder a kernel-built doc into the oracle's key order so the
written bytes match json.dumps of the oracle's dict (indent=1)."""
out = {
"programme_id": doc.get("programme_id", ""),
"objective": doc.get("objective", ""),
"stages": [
{"stage_id": s.get("stage_id", ""),
"objective": s.get("objective", ""),
"state": s.get("state", "PENDING"),
"depends_on": list(s.get("depends_on") or []),
"priority": s.get("priority", 0)}
for s in doc.get("stages", [])
],
"fallbacks": list(doc.get("fallbacks") or []),
"saved_at": doc.get("saved_at", ""),
}
return out
def _normalize_contract_doc(doc: dict) -> dict:
"""Checkpoint dicts the kernel appends are alphabetical; reorder to
the oracle's {at, phase, evidence, note} before a real store save."""
for e in (doc or {}).get("entities", []):
cps = e.get("checkpoints")
if isinstance(cps, list):
e["checkpoints"] = [
{"at": cp.get("at", ""), "phase": cp.get("phase", ""),
"evidence": cp.get("evidence", ""), "note": cp.get("note", "")}
if isinstance(cp, dict) else cp
for cp in cps
]
return doc
# ── payload builders ────────────────────────────────────────────────────
def prog_payload(prog, *, work_marker: bool = False) -> dict:
return {
"programme_id": prog.programme_id,
"objective": prog.objective,
"stages": [stage_payload(s, work_marker=work_marker) for s in prog.stages],
"fallbacks": list(prog.fallbacks),
}
def stage_payload(s, *, work_marker: bool = False) -> dict:
d = {
"stage_id": s.stage_id,
"objective": s.objective,
"state": s.state,
"depends_on": list(s.depends_on),
"priority": s.priority,
"result": {k: v for k, v in s.result.items() if k != "work"},
}
if work_marker and "work" in s.result:
w = s.result["work"]
# the kernel never reads work contents — presence is the signal;
# the host resolves callables vs literal specs behind the work op
d["result"]["work"] = ({"__callable__": True} if callable(w)
else _jsonable(w))
return d
# ── pure exports ────────────────────────────────────────────────────────
def canary(prog) -> dict:
return _call_simple("pc_canary", {"prog": prog_payload(prog)})
def eligible(prog) -> list:
return _call_simple("pc_eligible", {"prog": prog_payload(prog)})
def stage_to_dict(s) -> dict:
return _call_simple("pc_stage_to_dict", {"stage": stage_payload(s)})
def save_doc(prog) -> dict:
from rune import programme_continuation as pc_mod
saved_at = pc_mod.time.strftime("%Y-%m-%dT%H:%M:%SZ", pc_mod.time.gmtime())
doc = _call_simple("pc_save_doc",
{"prog": prog_payload(prog), "saved_at": saved_at})
return _normalize_programme_doc(doc)
def load_doc(doc: dict | None):
out = _call_simple("pc_load", {"doc": doc})
return None if out is None else out
# ── host-driven exports ─────────────────────────────────────────────────
def run_programme(prog, stage_runner, *, max_transitions: int = 100) -> dict:
proxy = OpsProxy(stages={s.stage_id: s for s in prog.stages},
runner=stage_runner)
out = _call_host("pc_run_programme",
{"prog": prog_payload(prog),
"max_transitions": max_transitions}, proxy)
out["log"] = [_normalize_log_entry(e) for e in out["log"]]
return out
def stage_run(stage, cfg: dict, iface) -> dict:
proxy = OpsProxy(iface=iface, stages={stage.stage_id: stage})
out = _call_host("pc_stage_run",
{"cfg": cfg, "stage": stage_payload(stage,
work_marker=True)},
proxy)
return out
def run_construction(prog, cfg: dict, iface, *,
max_transitions: int = 100) -> dict:
proxy = OpsProxy(iface=iface,
stages={s.stage_id: s for s in prog.stages})
out = _call_host("pc_run_construction",
{"prog": prog_payload(prog, work_marker=True), "cfg": cfg,
"max_transitions": max_transitions}, proxy)
out["log"] = [_normalize_log_entry(e) for e in out["log"]]
return out
def _normalize_log_entry(e: dict) -> dict:
"""The oracle's next_action is a tuple; the kernel's is an array."""
if "canary" in e:
na = e["canary"].get("next_action")
if isinstance(na, list):
e["canary"]["next_action"] = tuple(na)
return e
+109
View File
@@ -19,6 +19,7 @@ list.
from __future__ import annotations
import json
import os
import time
from dataclasses import dataclass, field
from pathlib import Path
@@ -47,6 +48,13 @@ class ProgrammeStage:
result: dict = field(default_factory=dict)
def to_dict(self) -> dict:
if os.environ.get("RUNE_PC_CORE", "auto") != "python":
try:
from rune import pc_loader
if pc_loader.available():
return pc_loader.stage_to_dict(self)
except Exception:
pass # Python oracle below; never crash the caller
return {"stage_id": self.stage_id, "objective": self.objective,
"state": self.state, "depends_on": list(self.depends_on),
"priority": self.priority}
@@ -66,6 +74,17 @@ class Programme:
def continuation_canary(self) -> dict:
"""'task done' ≠ 'programme done'. Returns the verdict AND the
next action — the runner never has to guess whether to stop."""
if os.environ.get("RUNE_PC_CORE", "auto") != "python":
try:
from rune import pc_loader
if pc_loader.available():
out = pc_loader.canary(self)
na = out.get("next_action")
return {"verdict": out["verdict"],
"next_action": tuple(na) if na is not None else None,
"reason": out["reason"]}
except Exception:
pass # Python oracle below; never crash the caller
complete = [s for s in self.stages if s.state == COMPLETE]
if len(complete) == len(self.stages):
return {"verdict": "PROGRAMME_COMPLETE",
@@ -118,6 +137,15 @@ class Programme:
"""PENDING stages whose dependencies are COMPLETE, priority-first.
ELIGIBLE counts as runnable too — an eligible stage nobody started
is exactly the case the continuation canary exists to catch."""
if os.environ.get("RUNE_PC_CORE", "auto") != "python":
try:
from rune import pc_loader
if pc_loader.available():
ids = pc_loader.eligible(self)
by_id = {s.stage_id: s for s in self.stages}
return [by_id[i] for i in ids if i in by_id]
except Exception:
pass # Python oracle below; never crash the caller
done = {s.stage_id for s in self.stages if s.state == COMPLETE}
out = [s for s in self.stages
if s.state in (PENDING, ELIGIBLE)
@@ -127,6 +155,18 @@ class Programme:
# ── durable persistence (canonical store, not transcript) ────────
def save(self, base: Path) -> Path:
if os.environ.get("RUNE_PC_CORE", "auto") != "python":
try:
from rune import pc_loader
if pc_loader.available():
doc = pc_loader.save_doc(self) # clock read via pc.time
base = Path(base)
base.mkdir(parents=True, exist_ok=True)
p = base / f"{self.programme_id}.json"
p.write_text(json.dumps(doc, indent=1))
return p
except Exception:
pass # Python oracle below; never crash the caller
base.mkdir(parents=True, exist_ok=True)
p = base / f"{self.programme_id}.json"
p.write_text(json.dumps({
@@ -145,6 +185,21 @@ class Programme:
if not p.exists():
return None
d = json.loads(p.read_text())
if os.environ.get("RUNE_PC_CORE", "auto") != "python":
try:
from rune import pc_loader
if pc_loader.available():
out = pc_loader.load_doc(d)
if out is None:
return None
prog = cls(programme_id=out["programme_id"],
objective=out["objective"],
fallbacks=out.get("fallbacks", []))
for s in out["stages"]:
prog.stages.append(ProgrammeStage(**s))
return prog
except Exception:
pass # Python oracle below; never crash the caller
prog = cls(programme_id=d["programme_id"],
objective=d["objective"], fallbacks=d.get("fallbacks", []))
for s in d["stages"]:
@@ -158,6 +213,19 @@ def run_programme(prog: Programme, stage_runner, *,
provided by the caller — must return a state for the stage. Between
EVERY stage: persist → continuation canary → next. Returns the
transition log (the 15M proof artifact)."""
if os.environ.get("RUNE_PC_CORE", "auto") != "python":
try:
from rune import pc_loader
if pc_loader.available():
out = pc_loader.run_programme(
prog, stage_runner, max_transitions=max_transitions)
by_id = {s.stage_id: s for s in prog.stages}
for sid, state in out["states"].items():
if sid in by_id:
by_id[sid].state = state
return out["log"]
except Exception:
pass # Python oracle below; never crash the caller
log: list[dict] = []
for _ in range(max_transitions):
prog_canary = prog.continuation_canary()
@@ -202,6 +270,26 @@ def scaffolding_stage_runner(interface, *, scaffold_store: Path,
runs under the event bridge and the driver resumes on the EVENT;
canonical verification still decides completion truth. Stages without
work complete as governance-only transitions."""
if os.environ.get("RUNE_PC_CORE", "auto") != "python":
try:
from rune import pc_loader
if pc_loader.available():
from rune import event_wait as ew # optional dependency
cfg = {"scaffold_store": str(scaffold_store),
"daemon_url": daemon_url or "",
"watchdog_secs": watchdog_secs,
"project_id": getattr(interface.scaffold,
"project_id", "")}
def run_kernel(stage: ProgrammeStage) -> str:
out = pc_loader.stage_run(stage, cfg, interface)
stage.result.update(out["result"])
return out["state"]
return run_kernel
except Exception:
pass # Python oracle below; never crash the caller
from rune import event_wait as ew # local import: optional dependency
def _phase(contract_id: str) -> str:
@@ -321,6 +409,27 @@ def run_construction_programme(prog: Programme, interface, *,
run_programme with the Scaffolding-bound runner; persists the
programme around every transition so a restart resumes from the
durable record, never a transcript. Returns the transition log."""
if os.environ.get("RUNE_PC_CORE", "auto") != "python":
try:
from rune import pc_loader
if pc_loader.available():
cfg = {"scaffold_store": str(scaffold_store),
"daemon_url": daemon_url or "",
"watchdog_secs": watchdog_secs,
"project_id": getattr(interface.scaffold,
"project_id", "")}
out = pc_loader.run_construction(
prog, cfg, interface, max_transitions=max_transitions)
by_id = {s.stage_id: s for s in prog.stages}
for sid, state in out["states"].items():
if sid in by_id:
by_id[sid].state = state
for sid, result in out["results"].items():
if sid in by_id:
by_id[sid].result.update(result)
return out["log"]
except Exception:
pass # Python oracle below; never crash the caller
runner = scaffolding_stage_runner(
interface, scaffold_store=scaffold_store,
daemon_url=daemon_url, watchdog_secs=watchdog_secs)
+108
View File
@@ -73,6 +73,30 @@ def _now():
return datetime.now(timezone.utc).isoformat()
# ── Rust core delegation (dichotomy wave, #2479 tranche) ────────────────
# The derivation kernel is FULL Rust (src/rust/prompt_sync, ps_loader.py);
# the Python code below each delegation point is the fallback oracle.
# RUNE_PS_CORE=python pins the oracle (poisoned-truth law).
_PS_LOADER = None
_PS_LOADER_TRIED = False
def _rust_available() -> bool:
global _PS_LOADER, _PS_LOADER_TRIED
if os.environ.get("RUNE_PS_CORE", "auto") == "python":
return False
if not _PS_LOADER_TRIED:
_PS_LOADER_TRIED = True
try:
import ps_loader as _pl
if _pl.available():
_PS_LOADER = _pl
except Exception:
logger.debug("ps_loader unavailable", exc_info=True)
return _PS_LOADER is not None
def get_store():
"""Connect to the memory store. Use as context manager: `with get_store() as db:`."""
db = sqlite3.connect(STORE_DB)
@@ -86,6 +110,11 @@ def get_store():
def get_entries_by_topic(db, topic):
"""Get all active always-on entries for a topic."""
if _rust_available():
try:
return _PS_LOADER.get_entries_by_topic(STORE_DB, topic)
except Exception:
logger.debug("ps_get_entries_by_topic fell back", exc_info=True)
rows = db.execute(
"SELECT text, importance, confidence FROM entries "
"WHERE status='active' AND always_on=1 AND topic=? "
@@ -98,6 +127,11 @@ def get_entries_by_topic(db, topic):
def get_constitution(db):
"""Get constitution entries from store. Curated: top entries by importance."""
entries = get_entries_by_topic(db, "constitution")
if _rust_available():
try:
return _PS_LOADER.get_constitution(entries)
except Exception:
logger.debug("ps_get_constitution fell back", exc_info=True)
if not entries:
return None
# Sort by importance, take top 25 (most important constitution entries)
@@ -116,6 +150,11 @@ def get_constitution(db):
def get_identity(db):
"""Get identity entries from store."""
entries = get_entries_by_topic(db, "identity")
if _rust_available():
try:
return _PS_LOADER.get_identity(entries)
except Exception:
logger.debug("ps_get_identity fell back", exc_info=True)
if not entries:
return None
lines = []
@@ -129,6 +168,11 @@ def get_identity(db):
def get_operating(db):
"""Get operating rules from store. Curated: top entries by importance."""
entries = get_entries_by_topic(db, "operating")
if _rust_available():
try:
return _PS_LOADER.get_operating(entries)
except Exception:
logger.debug("ps_get_operating fell back", exc_info=True)
if not entries:
return None
# Sort by importance, take top 15
@@ -147,6 +191,15 @@ def get_operating(db):
def get_defining_principles():
"""Get defining principles from the defining document."""
defining_path = os.path.join(memory_env.memory_dir(), "rune_defining_document.md")
if _rust_available():
try:
content = None
if os.path.exists(defining_path):
with open(defining_path) as f:
content = f.read()
return _PS_LOADER.get_defining_principles(content)
except Exception:
logger.debug("ps_get_defining_principles fell back", exc_info=True)
if not os.path.exists(defining_path):
return None
try:
@@ -179,6 +232,11 @@ def get_defining_principles():
def get_human(db):
"""Get human model from store."""
entries = get_entries_by_topic(db, "human")
if _rust_available():
try:
return _PS_LOADER.get_human(entries)
except Exception:
logger.debug("ps_get_human fell back", exc_info=True)
if not entries:
return None
lines = []
@@ -191,6 +249,27 @@ def get_human(db):
def build_prompt(db):
"""Build the full system prompt from store entries."""
if _rust_available():
try:
voice_register = voice_sync.get_delivery_register_from_db(db)
voice_body = None
if voice_register:
voice_body = voice_sync.generate_voice_section(
voice_sync.extract_voice_section(voice_register))
defining_content = None
defining_path = os.path.join(memory_env.memory_dir(), "rune_defining_document.md")
if os.path.exists(defining_path):
with open(defining_path) as f:
defining_content = f.read()
return _PS_LOADER.build_prompt(
STATIC_HEADER, STATIC_FOOTER, build_pipeline_policy.PROMPT_SECTION,
voice_body, defining_content,
get_entries_by_topic(db, "constitution"),
get_entries_by_topic(db, "operating"),
get_entries_by_topic(db, "human"),
)
except Exception:
logger.debug("ps_build_prompt fell back", exc_info=True)
sections = []
sections.append(STATIC_HEADER)
@@ -270,6 +349,15 @@ def check_sync():
finally:
db.close()
if _rust_available():
try:
current = None
if os.path.exists(RUNE_MD):
with open(RUNE_MD) as f:
current = f.read()
return _PS_LOADER.check_sync(new_prompt, current)
except Exception:
logger.debug("ps_check_sync fell back", exc_info=True)
if not os.path.exists(RUNE_MD):
return {"synced": False, "reason": "rune.md does not exist"}
@@ -290,6 +378,15 @@ def diff_prompt():
finally:
db.close()
if _rust_available():
try:
current = None
if os.path.exists(RUNE_MD):
with open(RUNE_MD) as f:
current = f.read()
return _PS_LOADER.diff_prompt(new_prompt, current)
except Exception:
logger.debug("ps_diff_prompt fell back", exc_info=True)
if not os.path.exists(RUNE_MD):
return {"diff": "rune.md does not exist — would create new"}
@@ -310,6 +407,17 @@ def diff_prompt():
def handle_hook(event, data=None):
"""On-session-end hook: check if prompt needs syncing."""
if _rust_available():
try:
check_synced = None
sync_result = None
if event == "on_session_end":
check_synced = bool(check_sync().get("synced"))
if not check_synced:
sync_result = sync_prompt()
return _PS_LOADER.handle_hook(event, check_synced, sync_result)
except Exception:
logger.debug("ps_handle_hook fell back", exc_info=True)
if event == "on_session_end":
result = check_sync()
if not result.get("synced"):
+136
View File
@@ -0,0 +1,136 @@
#!/usr/bin/env python3
"""ctypes loader for the Rust prompt_sync core (dichotomy wave, #2479
tranche). Mirrors ci_loader.py's contract:
FFI contract (doc-envelope law — every call returns {"ok", "value"|"err"}):
* ps_get_entries_by_topic({db_path, topic}) -> [row dicts]
* ps_get_constitution({entries}) -> str | None
* ps_get_identity({entries}) -> str | None
* ps_get_operating({entries}) -> str | None
* ps_get_defining_principles({content}) -> str | None
* ps_get_human({entries}) -> str | None
* ps_build_prompt({static_header, static_footer, pipeline_section,
voice_body?, defining_content?, constitution_rows, operating_rows,
human_rows}) -> prompt string
* ps_check_sync({new_prompt, current}) -> {synced, reason}
* ps_diff_prompt({new_prompt, current}) -> {diff, added, removed}
* ps_handle_hook({event, check_synced, sync_result}) -> response dict
The kernel is PURE: it receives rows/contents/voice body as JSON; rune.md
I/O, the voice_sync chain, and timestamps stay in the Python facade.
The read-only sqlite path inside the kernel uses the SYSTEM libsqlite3.
Free: capability_scoring's cs_free_string (shared process). Override the
.so with PS_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" / "prompt_sync"
_LIB_PATH = Path(os.environ.get("PS_RUST_LIB", _CRATE_DIR / "target" / "release" / "libprompt_sync.so"))
_lib = None
class PromptSyncCoreUnavailable(FileNotFoundError):
"""The Rust prompt_sync 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 PromptSyncCoreUnavailable(str(_LIB_PATH))
lib = ctypes.CDLL(str(_LIB_PATH))
for sym in ("ps_get_entries_by_topic", "ps_get_constitution", "ps_get_identity",
"ps_get_operating", "ps_get_defining_principles", "ps_get_human",
"ps_build_prompt", "ps_check_sync", "ps_diff_prompt", "ps_handle_hook"):
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 RuntimeError(doc.get("err", f"{sym} failed"))
return doc["value"]
def get_entries_by_topic(db_path: str, topic: str) -> list:
return _call("ps_get_entries_by_topic", {"db_path": db_path, "topic": topic})
def get_constitution(entries: list) -> str | None:
return _call("ps_get_constitution", {"entries": entries})
def get_identity(entries: list) -> str | None:
return _call("ps_get_identity", {"entries": entries})
def get_operating(entries: list) -> str | None:
return _call("ps_get_operating", {"entries": entries})
def get_defining_principles(content: str | None) -> str | None:
return _call("ps_get_defining_principles", {"content": content})
def get_human(entries: list) -> str | None:
return _call("ps_get_human", {"entries": entries})
def build_prompt(static_header: str, static_footer: str, pipeline_section: str,
voice_body: str | None, defining_content: str | None,
constitution_rows: list, operating_rows: list,
human_rows: list) -> str:
return _call("ps_build_prompt", {
"static_header": static_header, "static_footer": static_footer,
"pipeline_section": pipeline_section, "voice_body": voice_body,
"defining_content": defining_content,
"constitution_rows": constitution_rows, "operating_rows": operating_rows,
"human_rows": human_rows,
})
def check_sync(new_prompt: str, current: str | None) -> dict:
return _call("ps_check_sync", {"new_prompt": new_prompt, "current": current})
def diff_prompt(new_prompt: str, current: str | None) -> dict:
return _call("ps_diff_prompt", {"new_prompt": new_prompt, "current": current})
def handle_hook(event: str, check_synced: bool | None, sync_result: dict | None) -> dict:
return _call("ps_handle_hook", {
"event": event, "check_synced": check_synced, "sync_result": sync_result,
})
+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.209.0
Version: v0.229.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.209.0 | Textual TUI | {len(loaded)} native modules[/dim]",
f"[dim]Rune v0.229.0 | Textual TUI | {len(loaded)} native modules[/dim]",
)
# Auto-show night mode suggestion if evening/night hours
+47
View File
@@ -13,6 +13,14 @@ the gate suite green + no regression vs the baseline run.
Worktree isolation: proposals build on `git worktree` branches off the
live repo, so the candidate never touches the working tree until
promoted. Discard = remove worktree + delete branch (full cleanup).
Dichotomy wave #2479: the FULL Rust core (src/rust/self_modify via
smod_loader) owns the subprocess seam (_run: every git invocation and
gate command) and every proposal decision; this module remains the
parity oracle and the strangler fallback (RUNE_SMOD_CORE=python pins it
— the RUNE_SM_CORE pin belongs to shard_manager). Raise parity holds at
the type level: the loader re-raises FileNotFoundError /
TimeoutExpired / KeyError / TypeError exactly as the oracle would.
"""
from __future__ import annotations
@@ -24,6 +32,25 @@ import tempfile
import time
def _smod_core():
"""Dichotomy wave #2479: Rust core first (src/rust/self_modify via
smod_loader). RUNE_SMOD_CORE=python pins the Python fallback below —
it remains the parity oracle (strangler law). The RUNE_SM_CORE pin
belongs to shard_manager. The kernel owns the subprocess seam (_run:
every git invocation and gate command) and every proposal decision;
the wall clock enters as an argument (no-clock law)."""
if os.environ.get("RUNE_SMOD_CORE", "auto") == "python":
return None
try:
from rune import smod_loader
if smod_loader.available():
return smod_loader
except ImportError:
pass
return None
def _run(cmd, cwd, timeout=300):
r = subprocess.run(cmd, cwd=cwd, capture_output=True, text=True,
timeout=timeout)
@@ -33,6 +60,11 @@ def _run(cmd, cwd, timeout=300):
def propose_change(repo: str, branch: str, base: str = "master") -> dict:
"""Create the isolated worktree + branch. Returns the proposal record
(with rollback_ref = the base HEAD — the recovery path)."""
core = _smod_core()
if core is not None:
return core.propose_change(
repo, branch, base,
time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()))
rc, out, err = _run(["git", "rev-parse", base], cwd=repo)
if rc != 0:
return {"proposed": False, "error": f"unknown base: {base}"}
@@ -49,6 +81,9 @@ def propose_change(repo: str, branch: str, base: str = "master") -> dict:
def run_gates(worktree: str, test_cmd: list | None = None) -> dict:
"""Run the gate suite inside the worktree. Returns {passed, output}."""
core = _smod_core()
if core is not None:
return core.run_gates(worktree, test_cmd)
cmd = test_cmd or ["python3", "-m", "pytest", "tests/", "-q", "-x"]
rc, out, err = _run(cmd, cwd=worktree, timeout=600)
return {"passed": rc == 0,
@@ -57,6 +92,9 @@ def run_gates(worktree: str, test_cmd: list | None = None) -> dict:
def compare_baseline(candidate_result: dict, baseline_result: dict) -> dict:
"""Compare candidate vs baseline gate runs. Regression blocks."""
core = _smod_core()
if core is not None:
return core.compare_baseline(candidate_result, baseline_result)
if not candidate_result.get("passed"):
return {"promote": False,
"reason": "candidate gates red — regression blocks promotion"}
@@ -69,6 +107,9 @@ def compare_baseline(candidate_result: dict, baseline_result: dict) -> dict:
def promote_or_discard(repo: str, proposal: dict, verdict: dict) -> dict:
"""Merge (promote) or delete (discard) the proposal branch."""
core = _smod_core()
if core is not None:
return core.promote_or_discard(repo, proposal, verdict)
branch = proposal["branch"]
wt = proposal["worktree"]
if verdict.get("promote"):
@@ -88,6 +129,9 @@ def promote_or_discard(repo: str, proposal: dict, verdict: dict) -> dict:
def rollback(repo: str, rollback_ref: str) -> dict:
"""Restore the pre-change ref. The recovery path every proposal carries."""
core = _smod_core()
if core is not None:
return core.rollback(repo, rollback_ref)
rc, _out, err = _run(["git", "reset", "--hard", rollback_ref], cwd=repo)
if rc != 0:
return {"rolled_back": False, "error": err[:200]}
@@ -96,6 +140,9 @@ def rollback(repo: str, rollback_ref: str) -> dict:
def monitor(proposal: dict) -> dict:
"""The post-promotion watch record: what to observe after promotion."""
core = _smod_core()
if core is not None:
return core.monitor(proposal)
return {"monitoring": True, "branch": proposal.get("branch", ""),
"rollback_ref": proposal.get("rollback_ref", ""),
"watch": ["gate suite green on next CI run",
+58 -5
View File
@@ -23,14 +23,41 @@ SHARD_INDEX = os.path.expanduser("~/.config/opencode/memory/shard_index.json")
class ShardManager:
"""Manages topic-based SQLite shards for unbounded memory growth."""
"""Manages topic-based SQLite shards for unbounded memory growth.
FULL Rust core (src/rust/shard_manager) when built — strangler law:
RUNE_SM_CORE=python pins the Python fallback, which remains the parity
oracle. The sqlite/sqlite-vec resource boundary is served as ops by
sm_loader; connection construction stays Python-side."""
def __init__(self):
core = self._core()
if core is not None:
self.index = core.init(SHARD_DIR, SHARD_INDEX)
return
os.makedirs(SHARD_DIR, exist_ok=True)
self.index = self._load_index()
@staticmethod
def _core():
"""The Rust shard_manager core when available (strangler law).
RUNE_SM_CORE=python pins the fallback."""
if os.environ.get("RUNE_SM_CORE", "auto") == "python":
return None
try:
from rune import sm_loader
except Exception: # noqa: BLE001 — any loader failure means fallback, never a broken call
return None
try:
return sm_loader if sm_loader.available() else None
except Exception: # noqa: BLE001 — availability probe must never crash the fallback
return None
def _load_index(self) -> Dict[str, str]:
"""Load shard index from disk."""
core = self._core()
if core is not None:
return core.load_index(SHARD_INDEX)
if os.path.exists(SHARD_INDEX):
try:
with open(SHARD_INDEX) as f:
@@ -41,11 +68,20 @@ class ShardManager:
def _save_index(self):
"""Save shard index to disk."""
core = self._core()
if core is not None:
core.save_index(SHARD_INDEX, self.index)
return
with open(SHARD_INDEX, "w") as f:
json.dump(self.index, f, indent=2)
def create_shard(self, topic: str) -> str:
"""Create a new shard for a topic. Returns shard path."""
core = self._core()
if core is not None:
res = core.create_shard(SHARD_DIR, SHARD_INDEX, topic, self.index)
self.index = res["index"]
return res["shard_path"]
shard_name = f"shard_{topic.replace(' ', '_').replace('/', '_')}.db"
shard_path = os.path.join(SHARD_DIR, shard_name)
@@ -94,6 +130,9 @@ class ShardManager:
Returns the topic name. Falls back to 'general' if no match.
"""
core = self._core()
if core is not None:
return core.route_query(query, self.index)
query_lower = query.lower()
# Check existing shards for keyword matches
@@ -125,10 +164,15 @@ class ShardManager:
extension loaded PER CONNECTION — a fresh connection without it cannot
see or query entries_vec, even though the schema created it ("no such
table" in clean environments). Mirror memory_store.connect()."""
if topic not in self.index:
self.create_shard(topic)
shard_path = self.index[topic]
core = self._core()
if core is not None:
res = core.get_shard(SHARD_DIR, SHARD_INDEX, topic, self.index)
self.index = res["index"]
shard_path = res["shard_path"]
else:
if topic not in self.index:
self.create_shard(topic)
shard_path = self.index[topic]
db = sqlite3.connect(shard_path)
db.row_factory = sqlite3.Row
db.enable_load_extension(True)
@@ -142,6 +186,9 @@ class ShardManager:
def merge_results(self, results_list: List[List[dict]]) -> List[dict]:
"""Merge results from multiple shards, deduplicate, sort by relevance."""
core = self._core()
if core is not None:
return core.merge_results(results_list)
seen = set()
merged = []
@@ -158,6 +205,9 @@ class ShardManager:
def get_shard_stats(self) -> Dict[str, int]:
"""Get entry counts per shard."""
core = self._core()
if core is not None:
return core.get_shard_stats(self.index)
stats = {}
for topic, path in self.index.items():
try:
@@ -171,6 +221,9 @@ class ShardManager:
def total_entries(self) -> int:
"""Get total entries across all shards."""
core = self._core()
if core is not None:
return core.total_entries(self.index)
stats = self.get_shard_stats()
return sum(stats.values())
+205 -29
View File
@@ -1,9 +1,27 @@
#!/usr/bin/env python3
"""ctypes loader for the Rust structural_model core (facade burn-down).
"""ctypes loader for the FULL Rust shard_manager core (dichotomy wave,
issue #2479 tranche). Mirrors broker_loader.py's contract:
FFI contract (doc-envelope law):
* sm_query({relationships, eid, op}) -> list/str/None
* sm_free_string(ptr)
FFI contract (doc-envelope law — every call returns {"ok", "value"|"err"}):
* sm_init({shard_dir, index_path}) -> index dict
* sm_load_index({index_path}) -> index dict
* sm_save_index({index_path, index}) -> null
* sm_create_shard({shard_dir, index_path, topic, index}, ops_cb)
-> {shard_path, index}
* sm_route_query({query, index}) -> topic string
* sm_merge_results({results_list}) -> merged list
* sm_get_shard({shard_dir, index_path, topic, index}, ops_cb)
-> {shard_path, index}
* sm_get_shard_stats({index}, ops_cb) -> stats dict
* sm_total_entries({index}, ops_cb) -> int
Ops seam (resource boundary the kernel cannot own): the Rust kernel issues
`ensure_schema` / `count_entries` ops through a C callback; THIS loader
serves them against real sqlite3 + sqlite_vec. Connection construction
stays Python-side (see shard_manager.ShardManager.get_shard).
Free: capability_scoring's cs_free_string (shared process). Override the
.so with SM_RUST_LIB (a FILE path).
"""
from __future__ import annotations
@@ -11,19 +29,20 @@ from __future__ import annotations
import ctypes
import json
import os
import sqlite3
from pathlib import Path
_REPO = Path(__file__).resolve().parents[2]
_CRATE_DIR = _REPO / "src" / "rust" / "structural_model"
_LIB_PATH = Path(
os.environ.get(
"SM_RUST_LIB", _CRATE_DIR / "target" / "release" / "libstructural_model.so"
)
)
_CRATE_DIR = _REPO / "src" / "rust" / "shard_manager"
_LIB_PATH = Path(os.environ.get("SM_RUST_LIB", _CRATE_DIR / "target" / "release" / "libshard_manager.so"))
_lib = None
class ShardCoreUnavailable(FileNotFoundError):
"""The Rust shard_manager core is not built. Callers fall back to Python."""
def available() -> bool:
return _LIB_PATH.is_file()
@@ -32,32 +51,189 @@ def _load() -> ctypes.CDLL:
global _lib
if _lib is not None:
return _lib
if not _LIB_PATH.exists():
raise FileNotFoundError(f"structural_model not built: {_LIB_PATH}")
if not _LIB_PATH.is_file():
raise ShardCoreUnavailable(str(_LIB_PATH))
lib = ctypes.CDLL(str(_LIB_PATH))
lib.sm_free_string.argtypes = [ctypes.c_void_p]
lib.sm_free_string.restype = None
fn = lib.sm_query
fn.argtypes = [ctypes.c_char_p]
fn.restype = ctypes.c_void_p
for sym in (
"sm_init",
"sm_load_index",
"sm_save_index",
"sm_route_query",
"sm_merge_results",
):
fn = getattr(lib, sym)
fn.argtypes = [ctypes.c_void_p]
fn.restype = ctypes.c_void_p
for sym in (
"sm_create_shard",
"sm_get_shard",
"sm_get_shard_stats",
"sm_total_entries",
):
fn = getattr(lib, sym)
fn.argtypes = [ctypes.c_void_p, _OPS_CB]
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):
def _call(sym: str, payload: dict, with_ops: bool = False):
lib = _load()
ptr = getattr(lib, sym)(json.dumps(payload).encode("utf-8"))
data = json.dumps(payload).encode()
buf = ctypes.create_string_buffer(data)
args = [ctypes.cast(buf, ctypes.c_void_p)]
if with_ops:
args.append(_ops_cb)
ptr = getattr(lib, sym)(*args)
if not ptr:
raise RuntimeError(f"{sym} returned null")
try:
text = ctypes.cast(ptr, ctypes.c_char_p).value.decode("utf-8")
finally:
lib.sm_free_string(ptr)
env = json.loads(text)
if not env.get("ok"):
raise RuntimeError(f"{sym} core error: {env.get('err')}")
return env["value"]
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 query(relationships: list, eid: str, op: str):
return _call("sm_query", {"relationships": relationships, "eid": eid, "op": op})
# ── ops seam: the loader serves the kernel's sqlite ops ──────────────────────
_OPS_CB = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.c_void_p)
_last_ops_buf = None
SCHEMA = """
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 '',
source TEXT DEFAULT '',
status TEXT DEFAULT 'active',
wash_count INTEGER DEFAULT 0,
last_washed TEXT,
rock_status TEXT DEFAULT 'washable',
tier TEXT DEFAULT 'warm'
);
CREATE VIRTUAL TABLE IF NOT EXISTS entries_fts USING fts5(
text, content='', tokenize='porter unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS entries_vec USING vec0(
embedding float[256]
);
CREATE INDEX IF NOT EXISTS idx_entries_status ON entries(status);
CREATE INDEX IF NOT EXISTS idx_entries_topic ON entries(topic);
CREATE INDEX IF NOT EXISTS idx_entries_wash_count ON entries(wash_count);
CREATE INDEX IF NOT EXISTS idx_entries_tier ON entries(tier);
"""
def _execute_op(op: dict) -> dict:
kind = op.get("op")
args = op.get("args") or {}
if kind == "ensure_schema":
# mirror oracle create_shard lines 55-86 (sqlite + sqlite_vec + schema)
path = args["path"]
db = sqlite3.connect(path)
try:
db.enable_load_extension(True)
import sqlite_vec
sqlite_vec.load(db) # #26: entries_vec virtual table needs the extension per connection
db.executescript(SCHEMA)
finally:
db.close()
return {"ok": True}
if kind == "count_entries":
# mirror oracle get_shard_stats lines 163-169 (plain connect; any
# failure degrades to the except branch → kernel writes 0)
try:
path = args["path"]
db = sqlite3.connect(path)
try:
count = db.execute("SELECT COUNT(*) FROM entries WHERE status='active'").fetchone()[0]
finally:
db.close()
return {"ok": True, "value": count}
except Exception: # noqa: BLE001 — deliberate: best-effort degrade, crash would be worse
return {"ok": False}
return {"ok": False, "err": f"unknown op {kind!r}"}
def _ops_handler(op_ptr) -> int:
global _last_ops_buf
op = json.loads(ctypes.string_at(op_ptr).decode())
result = _execute_op(op)
_last_ops_buf = ctypes.create_string_buffer(json.dumps(result).encode())
return ctypes.cast(_last_ops_buf, ctypes.c_void_p).value
_ops_cb = _OPS_CB(_ops_handler)
# ── typed public API ─────────────────────────────────────────────────────────
def init(shard_dir: str, index_path: str) -> dict:
return _call("sm_init", {"shard_dir": shard_dir, "index_path": index_path})
def load_index(index_path: str) -> dict:
return _call("sm_load_index", {"index_path": index_path})
def save_index(index_path: str, index: dict) -> None:
_call("sm_save_index", {"index_path": index_path, "index": index})
def create_shard(shard_dir: str, index_path: str, topic: str, index: dict) -> dict:
return _call(
"sm_create_shard",
{
"shard_dir": shard_dir,
"index_path": index_path,
"topic": topic,
"index": index,
},
with_ops=True,
)
def route_query(query: str, index: dict) -> str:
return _call("sm_route_query", {"query": query, "index": index})
def merge_results(results_list: list) -> list:
return _call("sm_merge_results", {"results_list": results_list})
def get_shard(shard_dir: str, index_path: str, topic: str, index: dict) -> dict:
return _call(
"sm_get_shard",
{
"shard_dir": shard_dir,
"index_path": index_path,
"topic": topic,
"index": index,
},
with_ops=True,
)
def get_shard_stats(index: dict) -> dict:
return _call("sm_get_shard_stats", {"index": index}, with_ops=True)
def total_entries(index: dict) -> int:
return _call("sm_total_entries", {"index": index}, with_ops=True)
+175
View File
@@ -0,0 +1,175 @@
#!/usr/bin/env python3
"""ctypes loader for the Rust self_modify core (dichotomy wave, #2479
tranche). Mirrors broker_loader.py's contract:
FFI contract (doc-envelope law — every call returns {"ok", "value"|"err"}):
* smod_propose_change({repo, branch, base, now}) -> proposal dict
* smod_run_gates({worktree, test_cmd|null}) -> {passed, output}
* smod_compare_baseline({candidate_result, baseline_result}) -> verdict
* smod_promote_or_discard({repo, proposal, verdict}) -> result dict
* smod_rollback({repo, rollback_ref}) -> result dict
* smod_monitor({proposal}) -> watch record
No-clock law: `now` (the oracle's
time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())) is always an
argument — the kernel never reads the wall clock.
Boundary: the subprocess seam (_run — every git invocation and gate
command) is owned by the kernel, exactly like the oracle's _run. The
facade keeps only the wall clock and the RUNE_SMOD_CORE pin.
Raise parity: the oracle RAISES (FileNotFoundError from a missing cwd /
binary, TimeoutExpired on gate timeouts, KeyError from a missing proposal
key, TypeError at the argv seam). The kernel answers with the envelope's
err half and this loader re-raises the ORACLE'S exception TYPE via
_raise_like_oracle — raise parity holds at the type level, not just
raise-vs-return.
Free: capability_scoring's cs_free_string (shared process) — this crate
deliberately exports NO free symbol of its own (duplicate exports break
the cdylib link when sibling crates share one process). Override the .so
with SMOD_RUST_LIB (a FILE path).
"""
from __future__ import annotations
import ctypes
import json
import os
import re
import subprocess
from pathlib import Path
_REPO = Path(__file__).resolve().parents[2]
_CRATE_DIR = _REPO / "src" / "rust" / "self_modify"
_LIB_PATH = Path(
os.environ.get("SMOD_RUST_LIB", _CRATE_DIR / "target" / "release" / "libself_modify.so")
)
_SYMBOLS = (
"smod_propose_change",
"smod_run_gates",
"smod_compare_baseline",
"smod_promote_or_discard",
"smod_rollback",
"smod_monitor",
)
_lib = None
class SelfModifyCoreUnavailable(FileNotFoundError):
"""The Rust self-modify 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 SelfModifyCoreUnavailable(
f"Rust self_modify core not built: {_LIB_PATH} missing — run "
f"`cargo build --release` in {_CRATE_DIR}"
)
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
_lib = lib
return lib
def _raise_like_oracle(err: str, sym: str):
"""Re-raise the ORACLE's exception type, not a blanket RuntimeError.
The kernel's envelope err encodes enough to reconstruct the raise
parity the Python oracle has (subprocess FileNotFoundError /
PermissionError / TimeoutExpired, dict KeyError, argv TypeError):
* "[Errno N] …" → OSError subclass by errno (spawn seam)
* "Command '…' timed out after S seconds" → subprocess.TimeoutExpired
* "KeyError: 'k'" → KeyError(k)
* "TypeError: …" → TypeError
Anything else stays a RuntimeError.
"""
if err.startswith("[Errno "):
try:
errno = int(err[7:].split("]", 1)[0])
except ValueError:
raise RuntimeError(err) from None
if errno == 2:
raise FileNotFoundError(err)
if errno == 13:
raise PermissionError(err)
if errno == 20:
raise NotADirectoryError(err)
raise OSError(errno, err)
m = re.match(r"Command '(.*)' timed out after (\d+) seconds$", err)
if m:
raise subprocess.TimeoutExpired(m.group(1), float(m.group(2)))
if err.startswith("KeyError: "):
raise KeyError(err[len("KeyError: "):].strip("'"))
if err.startswith("TypeError: "):
raise TypeError(err[len("TypeError: "):])
raise RuntimeError(err)
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 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
doc = json.loads(out)
if not doc.get("ok"):
_raise_like_oracle(doc.get("err", f"{sym} failed"), sym)
return doc["value"]
def propose_change(repo: str, branch: str, base: str, now: str) -> dict:
return _call("smod_propose_change", {
"repo": repo, "branch": branch, "base": base, "now": now,
})
def run_gates(worktree: str, test_cmd: list | None) -> dict:
return _call("smod_run_gates", {
"worktree": worktree, "test_cmd": test_cmd,
})
def compare_baseline(candidate_result: dict, baseline_result: dict) -> dict:
return _call("smod_compare_baseline", {
"candidate_result": candidate_result,
"baseline_result": baseline_result,
})
def promote_or_discard(repo: str, proposal: dict, verdict: dict) -> dict:
return _call("smod_promote_or_discard", {
"repo": repo, "proposal": proposal, "verdict": verdict,
})
def rollback(repo: str, rollback_ref: str) -> dict:
return _call("smod_rollback", {
"repo": repo, "rollback_ref": rollback_ref,
})
def monitor(proposal: dict) -> dict:
return _call("smod_monitor", {"proposal": proposal})
+15 -5
View File
@@ -602,12 +602,22 @@ def build(manifest_path: Path | None = None) -> dict:
strangler law: the old path stays load-bearing until deletion is proven)."""
inputs = _gather_build_inputs(manifest_path)
core = _rust_core()
# The unseeded path is the one-time Phase A migration: it stays on the
# Python core so the public ingest_umbrellas seam (monkeypatched by
# isolation tests) is honored byte-for-byte. Steady-state (seeded)
# builds delegate to the Rust core; the parity battery covers BOTH
# paths, including an explicit unseeded-ingest parity section.
# The unseeded path is the one-time Phase A migration: the public
# ingest_umbrellas seam stays authoritative for it (isolation tests
# monkeypatch it; the file-driven refusal contract lives there).
# Route the manifest ingest through the seam FIRST, then delegate the
# rest of the projection with the ingested doc carried as `existing` —
# the kernel then takes the carry-forward branch (its own manifest
# ingest would bypass the seam and raise the refusal the caller is
# entitled to intercept; regression caught by
# test_t214_material_closure.test_spine_seeds_entry_point).
seeded = bool(inputs["existing"]) and any(r["type"] == "belongs_to" for r in inputs["existing"]["relationships"])
if not seeded and core is not None:
seed_doc = new_doc()
mp = inputs.get("manifest_path")
ingest_umbrellas(seed_doc, Path(mp) if mp else None)
inputs = dict(inputs, existing=seed_doc, manifest=None)
seeded = bool(seed_doc["entities"]) or bool(seed_doc["relationships"])
doc = core.project(inputs) if core is not None and seeded else _py_build_from_inputs(inputs)
# both cores funnel through the same post-projection passes (the
# contract-discovery write-back + language placement) — the doc-level
+84
View File
@@ -0,0 +1,84 @@
#!/usr/bin/env python3
"""ctypes loader for the Rust time_awareness core (dichotomy wave,
#2479 tranche). Mirrors to_loader.py's contract:
FFI contract (doc-envelope law):
* ta_run({op, state, args, now_us, now_mono}) ->
{"ok": true, "value": ..., "state": {...mutated...},
"save": bool, "snapshot": {...}}
The kernel is PURE — no clock, no filesystem. The caller supplies the
state (last_interaction as isoformat text-or-null / dirty /
last_save_time) and the clock (now_us = UTC epoch microseconds,
now_mono = monotonic reading). `save: true` means the oracle would have
written the file mid-call; `snapshot` is the trigger-time file content —
the facade performs the write. All I/O, the singleton and the CLI stay
in Python.
Override the .so with TA_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" / "time_awareness"
_LIB_PATH = Path(os.environ.get("TA_RUST_LIB", _CRATE_DIR / "target" / "release" / "libtime_awareness.so"))
_lib = None
class TimeAwarenessCoreUnavailable(FileNotFoundError):
"""The Rust time_awareness 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 TimeAwarenessCoreUnavailable(str(_LIB_PATH))
lib = ctypes.CDLL(str(_LIB_PATH))
lib.ta_run.argtypes = [ctypes.c_void_p]
lib.ta_run.restype = ctypes.c_void_p
_lib = lib
return lib
def _call(payload: dict) -> dict:
lib = _load()
data = json.dumps(payload).encode()
buf = ctypes.create_string_buffer(data)
ptr = lib.ta_run(ctypes.cast(buf, ctypes.c_void_p))
if not ptr:
raise RuntimeError("ta_run 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 RuntimeError(doc.get("err", "ta_run failed"))
return doc
def run_op(op: str, state: dict, args: dict | None = None, now_us: int = 0, now_mono: float = 0.0) -> dict:
"""Run one kernel op. Returns the full envelope:
{"value": ..., "state": {...mutated...}, "save": bool, "snapshot": ...}.
"""
payload = {"op": op, "state": state, "args": args or {},
"now_us": now_us, "now_mono": now_mono}
return _call(payload)
+66
View File
@@ -31,6 +31,24 @@ import memory_env
DB_PATH = os.path.join(memory_env.memory_dir(), "task_gate.db")
def _tg_core():
"""Dichotomy wave #2479: Rust core first (RUNE_TG_CORE=python pins the
Python fallback — parity harnesses). The kernel owns the verdict logic
(pattern extraction, gate matching, count/threshold/status transitions,
promotion, active-gate projection, hook echo); the sqlite IO and the
CLI stay here."""
if os.environ.get("RUNE_TG_CORE", "auto") == "python":
return None
try:
from rune import tg_loader
if tg_loader.available():
return tg_loader
except ImportError:
pass
return None
def _now():
return datetime.now(timezone.utc).isoformat()
@@ -67,6 +85,36 @@ def record_instruction(text):
"""Record an instruction. If it matches an existing pattern, increment count."""
db = connect()
try:
core = _tg_core()
if core is not None:
now = _now()
rows = [dict(r) for r in
db.execute("SELECT * FROM instructions").fetchall()]
out = core.record_instruction(rows, text, now)
if out.get("matched"):
db.execute(
"UPDATE instructions SET count=?, status=?, last_seen=? WHERE id=?",
(out["count"], out["status"], out["last_seen"], out["id"]),
)
db.commit()
return {
"matched": True,
"id": out["id"],
"count": out["count"],
"status": out["status"],
"promoted": out["promoted"],
}
row = out["new_row"]
cur = db.execute(
"INSERT INTO instructions (instruction, pattern, count, threshold, status, created_at, last_seen) "
"VALUES (?, ?, ?, ?, ?, ?, ?)",
(row["instruction"], row["pattern"], row["count"],
row["threshold"], row["status"], row["created_at"],
row["last_seen"]),
)
db.commit()
return {"matched": False, "id": cur.lastrowid, "count": 1, "status": "observing"}
text_lower = text.lower().strip()
# Check if this matches an existing instruction
@@ -112,6 +160,12 @@ def check_context(context):
"""Check if context matches any active gates."""
db = connect()
try:
core = _tg_core()
if core is not None:
rows = [dict(r) for r in
db.execute("SELECT * FROM instructions").fetchall()]
return core.check_context(rows, context)["matches"]
context_lower = context.lower()
active = db.execute("SELECT * FROM instructions WHERE status='active'").fetchall()
@@ -146,6 +200,15 @@ def promote_gate(gate_id):
"""Manually promote a gate to active."""
db = connect()
try:
core = _tg_core()
if core is not None:
out = core.promote_gate(gate_id, _now())
db.execute(
"UPDATE instructions SET status='active', promoted_at=? WHERE id=?",
(out["promoted_at"], gate_id),
)
db.commit()
return {"promoted": gate_id}
db.execute("UPDATE instructions SET status='active', promoted_at=? WHERE id=?", (_now(), gate_id))
db.commit()
return {"promoted": gate_id}
@@ -155,6 +218,9 @@ def promote_gate(gate_id):
def handle_hook(event, data=None):
"""Hook handler — check for repeated instructions."""
core = _tg_core()
if core is not None:
return core.handle_hook(event)
return {"status": "ok", "event": event, "handler": "task_gate"}
+228 -62
View File
@@ -10,9 +10,19 @@ hardened behaviours stay trusted.
Forge projection (19P): failures in active iteration create/update a
human-visible issue through the T18 forge boundary. Forge failure never
rewrites canonical truth.
Dichotomy wave #2479: the session state machine (next-item selection,
record bookkeeping, pause/resume, blockers), the observation
record/close shaping, the forge projection title/body and the aggregate
row shaping live in the Rust kernel (src/rust/testing_mode) behind
doc-envelope FFI; the session/observations FILE IO, the ledger read,
the registry IO and the forge adapter call stay here — the kernel is a
pure function of their results. RUNE_TM_CORE=python pins the Python
fallback (parity harnesses).
"""
from __future__ import annotations
import os
import time
from pathlib import Path
@@ -27,6 +37,129 @@ def _now() -> str:
return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
def _tm_core():
"""Dichotomy wave #2479: Rust core first (RUNE_TM_CORE=python pins the
Python fallback — parity harnesses). The kernel owns the session
state machine + observation/forge/aggregate shaping; the FILE IO,
the ledger read and the registry IO stay in this facade."""
if os.environ.get("RUNE_TM_CORE", "auto") == "python":
return None
try:
from rune import tm_loader
if tm_loader.available():
return tm_loader
except ImportError:
pass
return None
# ── pure shaping helpers (the Python fallback + parity oracle) ───────────
def _py_default_state(project, scope_component, now) -> dict:
return {"schema_version": SESSION_SCHEMA_VERSION,
"project": project, "scope_component": scope_component,
"current_item": None, "completed_this_session": [],
"newly_invalidated": [], "blockers": [],
"paused": False, "updated_at": now}
def _py_next_item(state: dict, outstanding: list) -> tuple[dict, dict | None]:
"""19L next eligible item — paused sessions dequeue nothing."""
if state["paused"]:
return state, None
item = outstanding[0] if outstanding else None
state["current_item"] = (item["behaviour_id"] if item else None)
return state, item
def _py_record(state: dict, rec: dict, passed: bool, now: str) -> dict:
state["completed_this_session"].append(
{"behaviour_id": rec["behaviour_id"], "passed": passed,
"at": now})
if passed and rec["state"] == USER_CONFIRMED:
pass # hardened; queue re-derives
return state
def _py_note_blocker(state: dict, behaviour_id: str, note: str, now: str) -> dict:
state["blockers"].append({"behaviour_id": behaviour_id,
"note": note[:200], "at": now})
return state
def _py_record_observation(*, now: str, now_ts: int, observed: str,
expected: str, behaviour_id: str = "",
environment: str = "", reproduction: str = "",
candidate_areas: list[str] | None = None,
confidence: str = "low") -> dict:
"""Durable real-world failure report. Candidate areas are
HYPOTHESES, not declared root cause (rule 19O)."""
obs = {
"observation_id": f"obs-{now_ts}",
"observed": observed, "expected": expected,
"behaviour_id": behaviour_id,
"environment": environment, "reproduction": reproduction,
"candidate_areas": list(candidate_areas or []), # hypotheses
"confidence": confidence,
"created_at": now,
"closure": None, # set by close_observation
}
return obs
def _py_close_observation(records: list, observation_id: str, *,
behaviour_retest_passed: bool,
user_confirmed: bool,
contract_ref: str = "",
now: str = "") -> tuple[list, dict | None]:
"""Closure returns to the ORIGINAL observed scenario (rule 19O):
code changed ≠ closure; automated green ≠ closure. The observation
closes only when the behaviour was retested AND user-confirmed."""
out = []
result = None
for rec in records:
if rec.get("observation_id") == observation_id:
rec["closure"] = {
"behaviour_retest_passed": behaviour_retest_passed,
"user_confirmed": user_confirmed,
"contract_ref": contract_ref,
"closed": behaviour_retest_passed and user_confirmed,
"at": now}
result = rec
out.append(rec)
return out, result
def _py_project(behaviour_id: str, observation: dict) -> tuple[str, str]:
"""19P forge projection title/body (the projection itself is a
PROJECTION — canonical truth stays in the Behaviour Ledger)."""
title = f"[testing] {behaviour_id}: {observation.get('expected', '')[:60]}"
body = (f"Observed: {observation.get('observed', '')}\n"
f"Expected: {observation.get('expected', '')}\n"
f"Behaviour: {behaviour_id}\n"
f"Observation: {observation.get('observation_id')}\n"
f"Reproduction: {observation.get('reproduction', '')}")
return title, body
def _py_aggregate_rows(entries: list) -> list[dict]:
"""19L aggregate row shaping over facade-fetched per-project
summaries (summary None → no behaviour ledger yet)."""
out = []
for entry in entries:
if entry["summary"] is None:
out.append({"project": entry["project_id"],
"outstanding": None,
"note": "no behaviour ledger yet"})
continue
s = entry["summary"]
out.append({"project": entry["project_id"],
"outstanding": s["outstanding"],
"states": s["states"]})
return out
# ── 19N: session = coordination only ─────────────────────────────────────
class TestingSession:
@@ -40,11 +173,12 @@ class TestingSession:
import json
if self.path.is_file():
return json.loads(self.path.read_text())
return {"schema_version": SESSION_SCHEMA_VERSION,
"project": project, "scope_component": scope_component,
"current_item": None, "completed_this_session": [],
"newly_invalidated": [], "blockers": [],
"paused": False, "updated_at": _now()}
core = _tm_core()
if core is not None:
return core.default_state(project=project,
scope_component=scope_component,
now=_now())
return _py_default_state(project, scope_component, _now())
def save(self) -> None:
import json
@@ -55,14 +189,17 @@ class TestingSession:
# ── 19L: next eligible item ──────────────────────────────────────
def next_item(self, behaviours: BehaviourLedger) -> dict | None:
if self.state["paused"]:
return None
outstanding = behaviours.outstanding(
core = _tm_core()
if core is not None:
outstanding = behaviours.outstanding(
component=self.state.get("scope_component"),
project=self.state.get("project") or None)
out = core.next_item(state=self.state, outstanding=outstanding)
self.state = out["state"]
return out["item"]
_, item = _py_next_item(self.state, behaviours.outstanding(
component=self.state.get("scope_component"),
project=self.state.get("project") or None)
item = outstanding[0] if outstanding else None
self.state["current_item"] = (item["behaviour_id"]
if item else None)
project=self.state.get("project") or None))
return item
# ── 19M: record result; dynamic queue re-derives from states ─────
@@ -73,25 +210,40 @@ class TestingSession:
rec = behaviours.record_real_world(
behaviour_id, passed=passed, detail=detail, confirmed_by=by,
generation=generation)
self.state["completed_this_session"].append(
{"behaviour_id": rec["behaviour_id"], "passed": passed,
"at": _now()})
if passed and rec["state"] == USER_CONFIRMED:
pass # hardened; queue re-derives
core = _tm_core()
if core is not None:
self.state = core.record(state=self.state, rec=rec,
passed=passed, now=_now())
else:
self.state = _py_record(self.state, rec, passed, _now())
self.save()
return rec
def pause(self) -> None:
self.state["paused"] = True
core = _tm_core()
if core is not None:
self.state = core.pause(state=self.state)
else:
self.state["paused"] = True
self.save()
def resume(self) -> None:
self.state["paused"] = False
core = _tm_core()
if core is not None:
self.state = core.resume(state=self.state)
else:
self.state["paused"] = False
self.save()
def note_blocker(self, behaviour_id: str, note: str) -> None:
self.state["blockers"].append({"behaviour_id": behaviour_id,
"note": note[:200], "at": _now()})
core = _tm_core()
if core is not None:
self.state = core.note_blocker(state=self.state,
behaviour_id=behaviour_id,
note=note, now=_now())
else:
self.state = _py_note_blocker(self.state, behaviour_id, note,
_now())
self.save()
@@ -105,16 +257,23 @@ def record_observation(store_dir: Path | str, *, observed: str,
"""Durable real-world failure report. Candidate areas are
HYPOTHESES, not declared root cause (rule 19O)."""
import json
obs = {
"observation_id": f"obs-{int(time.time())}",
"observed": observed, "expected": expected,
"behaviour_id": behaviour_id,
"environment": environment, "reproduction": reproduction,
"candidate_areas": list(candidate_areas or []), # hypotheses
"confidence": confidence,
"created_at": _now(),
"closure": None, # set by close_observation
}
core = _tm_core()
if core is not None:
obs = core.record_observation(now=_now(), now_ts=int(time.time()),
observed=observed, expected=expected,
behaviour_id=behaviour_id,
environment=environment,
reproduction=reproduction,
candidate_areas=list(candidate_areas or []),
confidence=confidence)
else:
obs = _py_record_observation(now=_now(), now_ts=int(time.time()),
observed=observed, expected=expected,
behaviour_id=behaviour_id,
environment=environment,
reproduction=reproduction,
candidate_areas=candidate_areas,
confidence=confidence)
p = Path(store_dir) / "observations.jsonl"
with p.open("a") as f:
f.write(json.dumps(obs, sort_keys=True) + "\n")
@@ -131,20 +290,24 @@ def close_observation(store_dir: Path | str, observation_id: str, *,
import json
p = Path(store_dir) / "observations.jsonl"
lines = p.read_text().strip().splitlines()
out = []
result = None
for line in lines:
rec = json.loads(line)
if rec.get("observation_id") == observation_id:
rec["closure"] = {
"behaviour_retest_passed": behaviour_retest_passed,
"user_confirmed": user_confirmed,
"contract_ref": contract_ref,
"closed": behaviour_retest_passed and user_confirmed,
"at": _now()}
result = rec
out.append(json.dumps(rec, sort_keys=True))
p.write_text("\n".join(out) + "\n")
records = [json.loads(line) for line in lines]
core = _tm_core()
if core is not None:
out = core.close_observation(records=records,
observation_id=observation_id,
behaviour_retest_passed=behaviour_retest_passed,
user_confirmed=user_confirmed,
contract_ref=contract_ref,
now=_now())
out_records, result = out["records"], out["result"]
else:
out_records, result = _py_close_observation(
records, observation_id,
behaviour_retest_passed=behaviour_retest_passed,
user_confirmed=user_confirmed, contract_ref=contract_ref,
now=_now())
p.write_text("\n".join(json.dumps(rec, sort_keys=True)
for rec in out_records) + "\n")
if result is None:
raise ValueError(f"unknown observation {observation_id!r}")
return result
@@ -160,12 +323,13 @@ def project_failure_to_forge(forge_adapter, repo: str, *,
in the Behaviour Ledger. Forge failure never rewrites truth
(rule 19P/19S-14): exceptions propagate to the caller as
ForgeUnavailable; the ledger state is untouched."""
title = f"[testing] {behaviour_id}: {observation.get('expected', '')[:60]}"
body = (f"Observed: {observation.get('observed', '')}\n"
f"Expected: {observation.get('expected', '')}\n"
f"Behaviour: {behaviour_id}\n"
f"Observation: {observation.get('observation_id')}\n"
f"Reproduction: {observation.get('reproduction', '')}")
core = _tm_core()
if core is not None:
proj = core.project(behaviour_id=behaviour_id,
observation=observation)
title, body = proj["title"], proj["body"]
else:
title, body = _py_project(behaviour_id, observation)
if dry_run:
return {"projected": False, "dry_run": True, "title": title}
return forge_adapter.create_or_patch_release(
@@ -177,20 +341,22 @@ def project_failure_to_forge(forge_adapter, repo: str, *,
def aggregate(registry: ProjectRegistry, store_root: Path) -> list[dict]:
"""Outstanding validation across ALL registered scaffolded projects
(the /testing aggregate)."""
out = []
core = _tm_core()
entries = []
for entry in registry.all_projects():
loc = Path(entry["scaffold_location"])
if not (loc / "behaviour_ledger.json").is_file():
out.append({"project": entry["project_id"],
"outstanding": None,
"note": "no behaviour ledger yet"})
entries.append({"project_id": entry["project_id"],
"summary": None})
continue
beh = BehaviourLedger(loc)
s = beh.summary()
registry.set_outstanding_validations(entry["project_id"],
s["outstanding"])
out.append({"project": entry["project_id"],
"outstanding": s["outstanding"],
"states": s["states"]})
entries.append({"project_id": entry["project_id"],
"summary": beh.summary()})
rows = (core.aggregate(entries=entries) if core is not None
else _py_aggregate_rows(entries))
for row in rows:
if row["outstanding"] is not None:
registry.set_outstanding_validations(row["project"],
row["outstanding"])
registry.save()
return out
return rows
+117
View File
@@ -0,0 +1,117 @@
#!/usr/bin/env python3
"""ctypes loader for the Rust task_gate core (dichotomy wave, #2479
tranche). Mirrors broker_loader.py's contract:
FFI contract (doc-envelope law — every call returns {"ok", "value"|"err"}):
* tg_record_instruction({instructions, text, now})
→ {matched, id, count, status, promoted, last_seen | new_row}
* tg_check_context({instructions, context}) → {matches}
* tg_promote_gate({id, now}) → {status, promoted_at}
* tg_handle_hook({event}) → {status, event, handler}
No-clock law: `now` (aware UTC ISO-8601) is always an argument — the
kernel never reads the wall clock.
Boundary: the sqlite IO (connect, the row reads, the UPDATE/INSERT) and
the CLI main() stay in the Python facade (task_gate.py); the kernel owns
the verdict logic (pattern extraction, gate matching, count/threshold/
status transitions, promotion, active-gate projection, hook echo).
FFI segfault law: every symbol gets explicit argtypes + restype (c_void_p
for returned char* — the default c_int truncates 64-bit pointers).
Strings are freed via capability_scoring's cs_free_string — the kernel
exports NO free symbol of its own (duplicate exports break the cdylib
link when sibling crates share one process).
Override the .so with TG_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" / "task_gate"
_LIB_PATH = Path(
os.environ.get("TG_RUST_LIB", _CRATE_DIR / "target" / "release" / "libtask_gate.so")
)
_SYMBOLS = (
"tg_record_instruction",
"tg_check_context",
"tg_promote_gate",
"tg_handle_hook",
)
_lib = None
class TaskGateCoreUnavailable(FileNotFoundError):
"""The Rust task_gate 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 TaskGateCoreUnavailable(
f"Rust task_gate core not built: {_LIB_PATH} missing — run "
f"`cargo build --release` in {_CRATE_DIR}"
)
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
_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 RuntimeError(doc.get("err", f"{sym} failed"))
return doc["value"]
def record_instruction(instructions, text, now) -> dict:
return _call("tg_record_instruction", {
"instructions": instructions, "text": text, "now": now,
})
def check_context(instructions, context) -> dict:
return _call("tg_check_context", {
"instructions": instructions, "context": context,
})
def promote_gate(gate_id, now) -> dict:
return _call("tg_promote_gate", {"id": gate_id, "now": now})
def handle_hook(event) -> dict:
return _call("tg_handle_hook", {"event": event})
+120 -4
View File
@@ -30,6 +30,8 @@ if _SD not in sys.path:
sys.path.insert(0, _SD)
import memory_env
_EPOCH = datetime(1970, 1, 1, tzinfo=timezone.utc)
@dataclass
class TemporalContext:
@@ -106,13 +108,80 @@ class TimeAwareness:
if now - self._last_save_time >= self._SAVE_DEBOUNCE_SECS:
self._save()
# ── Rust kernel plumbing (dichotomy wave) ───────────────────────────
# The kernel owns every temporal DECISION (time-of-day/circadian/
# urgency branches, hours-since math, temporal-reference parsing,
# dirty/debounce save policy); this facade keeps the I/O (_load/
# _save), the clock and the CLI. `save: true` in the envelope means
# the oracle wrote the file mid-call with the trigger-time snapshot —
# we perform that write, then adopt the kernel's final state.
def _to_state(self) -> dict:
"""Kernel transfer form; last_interaction as isoformat-or-null."""
return {
"last_interaction": self.last_interaction.isoformat() if self.last_interaction else None,
"dirty": self._dirty,
"last_save_time": self._last_save_time,
}
def _apply_state(self, st: dict):
self.last_interaction = (
datetime.fromisoformat(st["last_interaction"])
if st["last_interaction"] is not None
else None
)
self._dirty = st["dirty"]
self._last_save_time = st["last_save_time"]
def _write_snapshot(self, snap: dict):
os.makedirs(os.path.dirname(self.storage_path), exist_ok=True)
data = {"last_interaction": snap["last_interaction"]}
tmp = self.storage_path + ".tmp"
with open(tmp, "w") as f:
json.dump(data, f, indent=2)
os.replace(tmp, self.storage_path)
def _run_kernel(self, op: str, args: dict, now_us: int, now_mono: float):
"""Run one op through the Rust kernel; None → fall back to the
Python body (kernel absent, loader failure — never a broken call)."""
core = _ta_core()
if core is None:
return None
try:
doc = core.run_op(op, self._to_state(), args, now_us, now_mono)
except Exception:
return None
if doc.get("save"):
self._write_snapshot(doc["snapshot"])
self._apply_state(doc["state"])
return doc["value"]
def get_context(self, update_interaction: bool = True) -> TemporalContext:
"""Get current temporal context.
Args:
update_interaction: If True, update last_interaction timestamp.
Set to False for read-only status checks.
"""
Delegates the whole temporal decision to the Rust core when built."""
now = datetime.now(timezone.utc)
delegated = self._run_kernel(
"get_context",
{"update_interaction": update_interaction},
(now - _EPOCH) // timedelta(microseconds=1),
_time.monotonic(),
)
if delegated is not None:
return TemporalContext(
current_time=delegated["current_time"],
current_date=delegated["current_date"],
day_of_week=delegated["day_of_week"],
time_of_day=delegated["time_of_day"],
is_weekend=delegated["is_weekend"],
hours_since_last_interaction=delegated["hours_since_last_interaction"],
urgency_modifier=delegated["urgency_modifier"],
circadian_phase=delegated["circadian_phase"],
)
now = datetime.now(timezone.utc)
# Calculate hours since last interaction
@@ -174,7 +243,18 @@ class TimeAwareness:
def parse_temporal_reference(self, text: str) -> Dict[str, Any]:
"""Parse temporal references in text (yesterday, last week, etc.).
Uses word boundary matching to avoid false positives."""
Uses word boundary matching to avoid false positives.
Delegates the matching decision to the Rust core when built."""
now = datetime.now(timezone.utc)
delegated = self._run_kernel(
"parse_temporal_reference",
{"text": text},
(now - _EPOCH) // timedelta(microseconds=1),
_time.monotonic(),
)
if delegated is not None:
return delegated
text_lower = text.lower()
now = datetime.now(timezone.utc)
@@ -206,7 +286,12 @@ class TimeAwareness:
return {"reference": None}
def get_status(self) -> Dict[str, Any]:
"""Get time awareness status (read-only, no side effects)."""
"""Get time awareness status (read-only, no side effects).
Delegates to the Rust core when built."""
delegated = self._run_kernel("get_status", {}, *_clock_args())
if delegated is not None:
return delegated
context = self.get_context(update_interaction=False)
return {
"current_time": context.current_time,
@@ -219,11 +304,42 @@ class TimeAwareness:
}
def flush(self):
"""Force-save any pending changes to disk."""
"""Force-save any pending changes to disk.
The save DECISION delegates to the Rust core when built (the
facade performs the write via the snapshot)."""
now = datetime.now(timezone.utc)
delegated = self._run_kernel(
"flush", {},
(now - _EPOCH) // timedelta(microseconds=1),
_time.monotonic(),
)
if delegated is not None:
return
if self._dirty:
self._save()
def _clock_args():
"""(now_us, now_mono) for kernel ops that need no computed `now`."""
now = datetime.now(timezone.utc)
return (now - _EPOCH) // timedelta(microseconds=1), _time.monotonic()
def _ta_core():
"""The Rust time_awareness core when available (strangler law).
RUNE_TA_CORE=python pins the fallback."""
if os.environ.get("RUNE_TA_CORE", "auto") == "python":
return None
try:
from rune import ta_loader
except Exception: # noqa: BLE001 — any loader failure means fallback, never a broken call
return None
try:
return ta_loader if ta_loader.available() else None
except Exception: # noqa: BLE001 — availability probe must never crash the fallback
return None
# Global time awareness
_time_awareness = None
+118
View File
@@ -0,0 +1,118 @@
#!/usr/bin/env python3
"""ctypes loader for the Rust toolchain core (dichotomy wave #2479
tranche). Mirrors broker_loader.py's contract:
FFI contract (doc-envelope law — every call returns {"ok", "value"|"err"}):
* tl_constraint_keys({}) -> sorted constraint-key vocabulary
* tl_constraints({kwargs}) -> {"known", "unknown"}
* tl_constraints_get({known, key, default?}) -> value
* tl_decision_record({...fields}) -> normalized record dict
* tl_resolve_toolchain({known, requested_language?, evidence_refs?,
performance_evidence?}) -> DecisionRecord dict
* tl_major_change({known, language}) -> bool
Free: capability_scoring's cs_free_string (shared process). Override the
.so with TC_RUST_LIB (a FILE path).
"""
from __future__ import annotations
import ctypes
import json
import os
from pathlib import Path
_REPO = Path(__file__).resolve().parents[2]
_CRATE_DIR = _REPO / "src" / "rust" / "toolchain"
_LIB_PATH = Path(
os.environ.get("TC_RUST_LIB", _CRATE_DIR / "target" / "release" / "libtoolchain.so")
)
_SYMBOLS = (
"tl_constraint_keys", "tl_constraints", "tl_constraints_get",
"tl_decision_record", "tl_resolve_toolchain", "tl_major_change",
)
_lib = None
class ToolchainKernelUnavailable(FileNotFoundError):
"""The Rust toolchain 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 ToolchainKernelUnavailable(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
# 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 constraint_keys() -> list:
"""Sorted constraint-key vocabulary (the oracle's set, ordered)."""
return _call("tl_constraint_keys", {})
def constraints(kwargs: dict) -> dict:
"""Classify kwargs into {"known": ..., "unknown": ...}."""
return _call("tl_constraints", {"kwargs": kwargs})
def constraints_get(known: dict, key: str, default=None):
"""known.get(key, default) semantics (None default when absent)."""
return _call("tl_constraints_get", {"known": known, "key": key,
"default": default})
def decision_record(**fields) -> dict:
"""Normalized DecisionRecord dict (lists sorted, None → [])."""
return _call("tl_decision_record", fields)
def resolve_toolchain(known: dict, requested_language: str | None = None,
evidence_refs: list | None = None,
performance_evidence: dict | None = None) -> dict:
return _call("tl_resolve_toolchain", {
"known": known, "requested_language": requested_language,
"evidence_refs": list(evidence_refs or []),
"performance_evidence": performance_evidence})
def major_change(known: dict, language: str) -> bool:
return _call("tl_major_change", {"known": known, "language": language})
+178
View File
@@ -0,0 +1,178 @@
#!/usr/bin/env python3
"""ctypes loader for the Rust testing_mode core (dichotomy wave, #2479
tranche). Mirrors broker_loader.py's contract:
FFI contract (doc-envelope law — every call returns {"ok", "value"|"err"}):
* tm_default_state({project, scope_component, now}) -> {state}
* tm_next_item({state, outstanding}) -> {state, item}
* tm_record({state, rec, passed, now}) -> {state}
* tm_pause({state}) -> {state}
* tm_resume({state}) -> {state}
* tm_note_blocker({state, behaviour_id, note, now}) -> {state}
* tm_record_observation({now, now_ts, observed, expected, behaviour_id,
environment, reproduction, candidate_areas, confidence})
-> {observation}
* tm_close_observation({records, observation_id,
behaviour_retest_passed, user_confirmed, contract_ref, now})
-> {records, result}
* tm_project({behaviour_id, observation}) -> {title, body}
* tm_aggregate({entries}) -> {rows}
No-clock law: `now` (UTC ISO-8601) and `now_ts` (epoch seconds, for the
oracle's obs-{int(time.time())} id) are always arguments to the stamping
calls — the kernel never reads the wall clock.
Boundary: the session FILE IO (TestingSession._load hit branch / save),
the observations.jsonl append/rewrite, the BehaviourLedger read
(outstanding / record_real_world), the ProjectRegistry IO and the forge
adapter call stay in the Python facade (testing_mode.py); the kernel is
a pure function of their results.
FFI segfault law: every symbol gets explicit argtypes + restype (c_void_p
for returned char* — the default c_int truncates 64-bit pointers).
Strings are freed via capability_scoring's cs_free_string — the kernel
exports NO free symbol of its own (duplicate exports break the cdylib
link when sibling crates share one process).
Override the .so with TM_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" / "testing_mode"
_LIB_PATH = Path(
os.environ.get(
"TM_RUST_LIB", _CRATE_DIR / "target" / "release" / "libtesting_mode.so"
)
)
_SYMBOLS = (
"tm_default_state",
"tm_next_item",
"tm_record",
"tm_pause",
"tm_resume",
"tm_note_blocker",
"tm_record_observation",
"tm_close_observation",
"tm_project",
"tm_aggregate",
)
_lib = None
class TestingModeCoreUnavailable(FileNotFoundError):
"""The Rust testing_mode 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 TestingModeCoreUnavailable(
f"Rust testing_mode core not built: {_LIB_PATH} missing — run "
f"`cargo build --release` in {_CRATE_DIR}"
)
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
_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 RuntimeError(doc.get("err", f"{sym} failed"))
return doc["value"]
def default_state(project: str, scope_component: str | None, now: str) -> dict:
return _call("tm_default_state", {"project": project,
"scope_component": scope_component,
"now": now})["state"]
def next_item(state: dict, outstanding: list) -> dict:
return _call("tm_next_item", {"state": state, "outstanding": outstanding})
def record(state: dict, rec: dict, passed: bool, now: str) -> dict:
return _call("tm_record", {"state": state, "rec": rec, "passed": passed,
"now": now})["state"]
def pause(state: dict) -> dict:
return _call("tm_pause", {"state": state})["state"]
def resume(state: dict) -> dict:
return _call("tm_resume", {"state": state})["state"]
def note_blocker(state: dict, behaviour_id: str, note: str, now: str) -> dict:
return _call("tm_note_blocker", {"state": state,
"behaviour_id": behaviour_id,
"note": note, "now": now})["state"]
def record_observation(now: str, now_ts: int, *, observed: str,
expected: str, behaviour_id: str = "",
environment: str = "", reproduction: str = "",
candidate_areas: list | None = None,
confidence: str = "low") -> dict:
return _call("tm_record_observation", {
"now": now, "now_ts": now_ts, "observed": observed,
"expected": expected, "behaviour_id": behaviour_id,
"environment": environment, "reproduction": reproduction,
"candidate_areas": candidate_areas or [],
"confidence": confidence})["observation"]
def close_observation(records: list, observation_id: str, *,
behaviour_retest_passed: bool,
user_confirmed: bool, contract_ref: str = "",
now: str = "") -> dict:
return _call("tm_close_observation", {
"records": records, "observation_id": observation_id,
"behaviour_retest_passed": behaviour_retest_passed,
"user_confirmed": user_confirmed, "contract_ref": contract_ref,
"now": now})
def project(behaviour_id: str, observation: dict) -> dict:
return _call("tm_project", {"behaviour_id": behaviour_id,
"observation": observation})
def aggregate(entries: list) -> list:
return _call("tm_aggregate", {"entries": entries})["rows"]
+85
View File
@@ -0,0 +1,85 @@
#!/usr/bin/env python3
"""ctypes loader for the Rust token_optimizer core (dichotomy wave,
#2479 tranche). Mirrors ci_loader.py's contract:
FFI contract (doc-envelope law):
* to_run({op, state, args, now_iso, now}) ->
{"ok": true, "value": ..., "state": {...mutated...},
"save": bool, "snapshot": {...}}
The kernel is PURE — no clock, no filesystem. The caller supplies the
state (cache/stats/dirty/changes_since_save/last_save_time; cache as an
ORDERED ARRAY — insertion order is load-bearing for get_cache_stats)
and the wall clock (now_iso) + monotonic reading (now). `save: true`
means the oracle would have written the file mid-call; `snapshot` is
the trigger-time file content (cache as an ordered array) — the facade
performs the write. All I/O, the persona import glue and the CLI stay
in Python.
Override the .so with TO_RUST_LIB (a FILE path).
"""
from __future__ import annotations
import ctypes
import json
import os
from pathlib import Path
_REPO = Path(__file__).resolve().parents[2]
_CRATE_DIR = _REPO / "src" / "rust" / "token_optimizer"
_LIB_PATH = Path(os.environ.get("TO_RUST_LIB", _CRATE_DIR / "target" / "release" / "libtoken_optimizer.so"))
_lib = None
class TokenOptimizerCoreUnavailable(FileNotFoundError):
"""The Rust token_optimizer 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 TokenOptimizerCoreUnavailable(str(_LIB_PATH))
lib = ctypes.CDLL(str(_LIB_PATH))
lib.to_run.argtypes = [ctypes.c_void_p]
lib.to_run.restype = ctypes.c_void_p
_lib = lib
return lib
def _call(payload: dict) -> dict:
lib = _load()
data = json.dumps(payload).encode()
buf = ctypes.create_string_buffer(data)
ptr = lib.to_run(ctypes.cast(buf, ctypes.c_void_p))
if not ptr:
raise RuntimeError("to_run 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 RuntimeError(doc.get("err", "to_run failed"))
return doc
def run_op(op: str, state: dict, args: dict | None = None, now_iso: str = "", now: float = 0.0) -> dict:
"""Run one kernel op. Returns the full envelope:
{"value": ..., "state": {...mutated...}, "save": bool, "snapshot": ...}.
"""
payload = {"op": op, "state": state, "args": args or {},
"now_iso": now_iso, "now": now}
return _call(payload)
+110 -6
View File
@@ -115,7 +115,13 @@ class TokenOptimizer:
self._changes_since_save += 1
def _save_if_dirty(self):
"""Flush to disk only when dirty AND (enough changes OR enough time)."""
"""Flush to disk only when dirty AND (enough changes OR enough time).
The threshold/debounce DECISION delegates to the Rust core when
built (the kernel returns the decision; the write happens here or
inside _run_kernel via the snapshot)."""
delegated = self._run_kernel("save_if_dirty", {})
if delegated is not None:
return
if not self._dirty:
return
now = time.monotonic()
@@ -149,12 +155,74 @@ class TokenOptimizer:
"root, respect how Nick thinks, and make the agent smarter every session."
)
# ── Rust core delegation (dichotomy wave, #2479 tranche) ────────────
# The kernel owns every cache/state DECISION (hit/miss, md5-keyed
# upsert, savings math, dirty/debounce/threshold save policy); this
# facade keeps the I/O (_load/_save), the clock, the persona glue and
# the CLI. `save: true` in the envelope means the oracle wrote the
# file mid-call with the trigger-time snapshot — we perform that
# write, then adopt the kernel's final state (post-trigger mutations
# stay dirty in memory, exactly like the oracle).
def _to_state(self) -> dict:
"""Kernel transfer form; cache as an ORDERED ARRAY (insertion
order is load-bearing for get_cache_stats)."""
return {
"cache": [e.to_dict() for e in self.cache.values()],
"stats": self.stats,
"dirty": self._dirty,
"changes_since_save": self._changes_since_save,
"last_save_time": self._last_save_time,
}
def _apply_state(self, st: dict):
self.cache = {e["key"]: CacheEntry(**e) for e in st["cache"]}
self.stats = dict(st["stats"])
self._dirty = st["dirty"]
self._changes_since_save = st["changes_since_save"]
self._last_save_time = st["last_save_time"]
def _write_snapshot(self, snap: dict):
os.makedirs(os.path.dirname(self.storage_path), exist_ok=True)
data = {
"cache": {e["key"]: e for e in snap["cache"]},
"stats": snap["stats"],
}
tmp = self.storage_path + ".tmp"
with open(tmp, "w") as f:
json.dump(data, f, indent=2)
os.replace(tmp, self.storage_path)
def _run_kernel(self, op: str, args: dict):
"""Run one op through the Rust kernel; None → fall back to the
Python body (kernel absent, loader failure — never a broken call)."""
core = _to_core()
if core is None:
return None
try:
doc = core.run_op(op, self._to_state(), args,
datetime.now(timezone.utc).isoformat(),
time.monotonic())
except Exception:
return None
if doc.get("save"):
self._write_snapshot(doc["snapshot"])
self._apply_state(doc["state"])
return doc["value"]
def get_static_cache(self) -> str:
"""Get static cached content (constitution, temporal context).
This content rarely changes and can be cached across calls.
Loads from persona_definition module when available.
"""
Delegates the hit/miss decision to the Rust core when built."""
delegated = self._run_kernel(
"get_static_cache",
{"persona_text": self._load_persona_definition()},
)
if delegated is not None:
return delegated
# Check cache
if "static" in self.cache:
entry = self.cache["static"]
@@ -187,7 +255,11 @@ class TokenOptimizer:
"""Get dynamic cached content (recent messages, task context).
This content changes per turn but can be compressed.
"""
Delegates the md5-keyed upsert decision to the Rust core when built."""
delegated = self._run_kernel("get_dynamic_cache", {"context": context})
if delegated is not None:
return delegated
# Compress context
compressed = self._compress_context(context)
@@ -218,7 +290,17 @@ class TokenOptimizer:
def optimize_prompt(self, text: str, context: str = "") -> Dict[str, Any]:
"""Optimize a prompt by caching and compressing."""
"""Optimize a prompt by caching and compressing.
Delegates the full call sequence (static, dynamic, savings math,
accounting view) to the Rust core when built."""
delegated = self._run_kernel(
"optimize_prompt",
{"text": text, "context": context,
"persona_text": self._load_persona_definition()},
)
if delegated is not None:
return delegated
self.stats["total_calls"] += 1
# Get static cache (constitution)
@@ -271,7 +353,10 @@ class TokenOptimizer:
}
def get_status(self) -> Dict[str, Any]:
"""Get optimization status."""
"""Get optimization status (Rust core when built)."""
delegated = self._run_kernel("get_status", {})
if delegated is not None:
return delegated
return {
"cache_entries": len(self.cache),
"total_calls": self.stats["total_calls"],
@@ -282,7 +367,11 @@ class TokenOptimizer:
}
def get_cache_stats(self) -> List[Dict[str, Any]]:
"""Get cache statistics."""
"""Get cache statistics (Rust core when built; entry order is the
Python dict's insertion order)."""
delegated = self._run_kernel("get_cache_stats", {})
if delegated is not None:
return delegated
return [entry.to_dict() for entry in self.cache.values()]
def flush(self):
@@ -321,6 +410,21 @@ def _tc_core():
return None
def _to_core():
"""The Rust token_optimizer core when available (strangler law).
RUNE_TOKEN_OPTIMIZER_CORE=python pins the fallback."""
if os.environ.get("RUNE_TOKEN_OPTIMIZER_CORE", "auto") == "python":
return None
try:
from rune import to_loader
except Exception: # noqa: BLE001 — any loader failure means fallback, never a broken call
return None
try:
return to_loader if to_loader.available() else None
except Exception: # noqa: BLE001 — availability probe must never crash the fallback
return None
def _py_estimate_tokens(text: str) -> int:
"""STRANGLER EXIT (retired 2026-10-10): the authoritative implementation
lives in src/rust/token_counting (tc_estimate_tokens, parity proven 26/26
+59
View File
@@ -17,8 +17,32 @@ never invented certainty.
from __future__ import annotations
import os
__all__ = ["ToolchainConstraints", "resolve_toolchain", "DecisionRecord", "CONSTRAINT_KEYS"]
def _rust(fn: str, *args):
"""Delegate to the Rust kernel (dichotomy wave #2479); returns
_MISSING when the core is unavailable so the Python oracle body runs.
RUNE_TC_CORE=python pins the oracle unconditionally."""
if os.environ.get("RUNE_TC_CORE", "auto") == "python":
return _MISSING
try:
from rune import tl_loader
if not tl_loader.available():
return _MISSING
return getattr(tl_loader, fn)(*args)
except Exception:
return _MISSING
class _Missing:
pass
_MISSING = _Missing()
# Small fixed set of constraint KEYS the resolver can weigh. Values are
# open-world. Nothing here names a language.
CONSTRAINT_KEYS = {
@@ -46,6 +70,11 @@ class ToolchainConstraints:
carried verbatim (future-proof without schema churn)."""
def __init__(self, **kw):
out = _rust("constraints", kw)
if out is not _MISSING:
self.known = out["known"]
self.unknown = out["unknown"]
return
self.known = {}
self.unknown = {}
for k, v in kw.items():
@@ -55,6 +84,9 @@ class ToolchainConstraints:
self.unknown[k] = v
def get(self, key, default=None):
out = _rust("constraints_get", self.known, key, default)
if out is not _MISSING:
return out
return self.known.get(key, default)
def to_dict(self) -> dict:
@@ -80,6 +112,26 @@ class DecisionRecord:
user_approval_required: bool = False,
resolution: str = "",
):
out = _rust(
"decision_record",
selected_language, runtime, framework, libraries, build_system,
alternatives, decisive_constraints, rationale, evidence_refs,
confidence, user_approval_required, resolution,
)
if out is not _MISSING:
self.selected_language = out["selected_language"]
self.runtime = out["runtime"]
self.framework = out["framework"]
self.libraries = out["libraries"]
self.build_system = out["build_system"]
self.alternatives = out["alternatives"]
self.decisive_constraints = out["decisive_constraints"]
self.rationale = out["rationale"]
self.evidence_refs = out["evidence_refs"]
self.confidence = out["confidence"]
self.user_approval_required = out["user_approval_required"]
self.resolution = out["resolution"]
return
self.selected_language = selected_language
self.runtime = runtime
self.framework = framework
@@ -113,6 +165,9 @@ class DecisionRecord:
def _major_change(constraints: ToolchainConstraints, language: str) -> bool:
"""A major toolchain change = introducing/changing a language, runtime or
framework the project does not already use (tranche brief §10)."""
out = _rust("major_change", constraints.known, language)
if out is not _MISSING:
return out
existing = constraints.get("existing_language")
return bool(existing) and language != existing
@@ -137,6 +192,10 @@ def resolve_toolchain(
already uses it or the user asked.
"""
refs = sorted(evidence_refs or [])
out = _rust("resolve_toolchain", constraints.known, requested_language,
evidence_refs, performance_evidence)
if out is not _MISSING:
return DecisionRecord(**out)
existing = constraints.get("existing_language")
# INSUFFICIENT EVIDENCE path: no existing toolchain recorded AND no
+39
View File
@@ -2,10 +2,20 @@
# It is not intended for manual editing.
version = 4
[[package]]
name = "aho-corasick"
version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba"
dependencies = [
"memchr",
]
[[package]]
name = "behavior"
version = "0.1.0"
dependencies = [
"regex",
"serde",
"serde_json",
]
@@ -40,6 +50,35 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "regex"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
[[package]]
name = "serde"
version = "1.0.229"
+2
View File
@@ -4,8 +4,10 @@ version = "0.1.0"
edition = "2021"
[lib]
name = "behavior"
crate-type = ["rlib", "cdylib", "staticlib"]
[dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1"
regex = "1"
+744
View File
@@ -0,0 +1,744 @@
{
"frozen": "behavior decision surface (reinforce math via ep strangler shim)",
"oracle": "src/rune/behavior.py",
"cases": {
"add_correction": [
{
"name": "defaults",
"args": [
"say X",
"say Y"
],
"expected": {
"added": 1,
"confidence": 0.5,
"scope": {}
},
"row": {
"id": 1,
"original_behavior": "say X",
"corrected_behavior": "say Y",
"scope_tags": "{}",
"scope_pattern": "",
"scope_domain": "",
"evidence_count": 1,
"confidence": 0.5,
"state": "active",
"created_at": "2026-10-11T16:42:08.934815+00:00",
"last_reinforced": null,
"supersedes_id": null
}
},
{
"name": "scoped",
"args": [
"orig",
"corr"
],
"expected": {
"added": 2,
"confidence": 0.5,
"scope": {
"k": "v"
}
},
"row": {
"id": 2,
"original_behavior": "orig",
"corrected_behavior": "corr",
"scope_tags": "{\"k\": \"v\"}",
"scope_pattern": "shell",
"scope_domain": "shell",
"evidence_count": 1,
"confidence": 0.5,
"state": "active",
"created_at": "2026-10-11T16:42:08.935466+00:00",
"last_reinforced": null,
"supersedes_id": null
}
}
],
"add_habit": [
{
"name": "defaults",
"expected": {
"added": 1,
"state": "candidate",
"min_activations": 3
},
"row": {
"id": 1,
"name": "",
"trigger_pattern": "trig",
"context_tags": "[]",
"action_template": "act",
"activation_count": 0,
"success_count": 0,
"failure_count": 0,
"min_activations": 3,
"confidence": 0.5,
"decay_rate": 0.95,
"state": "candidate",
"created_at": "2026-10-11T16:42:08.936042+00:00",
"last_activated": null
}
},
{
"name": "full",
"expected": {
"added": 2,
"state": "candidate",
"min_activations": 5
},
"row": {
"id": 2,
"name": "n",
"trigger_pattern": "trig2",
"context_tags": "[\"a\"]",
"action_template": "act2",
"activation_count": 0,
"success_count": 0,
"failure_count": 0,
"min_activations": 5,
"confidence": 0.5,
"decay_rate": 0.95,
"state": "candidate",
"created_at": "2026-10-11T16:42:08.936613+00:00",
"last_activated": null
}
}
],
"match": [
{
"name": "pattern-domain-hit",
"context": "deploy to shell now",
"match_type": null,
"corrections": [
"corr",
"plain corr",
"say Y",
"scoped corr"
],
"habits": [
"run deploy script"
],
"n_corr_fields": [
"confidence",
"corrected_behavior",
"created_at",
"evidence_count",
"id",
"last_reinforced",
"original_behavior",
"scope_domain",
"scope_pattern",
"scope_tags",
"state",
"supersedes_id"
],
"scope_tags_type": "dict"
},
{
"name": "domain-only-miss",
"context": "deploy the editor",
"match_type": null,
"corrections": [
"domain corr",
"plain corr",
"say Y"
],
"habits": [
"run deploy script"
],
"n_corr_fields": [
"confidence",
"corrected_behavior",
"created_at",
"evidence_count",
"id",
"last_reinforced",
"original_behavior",
"scope_domain",
"scope_pattern",
"scope_tags",
"state",
"supersedes_id"
],
"scope_tags_type": "dict"
},
{
"name": "plain-always",
"context": "unrelated chatter here",
"match_type": null,
"corrections": [
"plain corr",
"say Y"
],
"habits": [],
"n_corr_fields": [
"confidence",
"corrected_behavior",
"created_at",
"evidence_count",
"id",
"last_reinforced",
"original_behavior",
"scope_domain",
"scope_pattern",
"scope_tags",
"state",
"supersedes_id"
],
"scope_tags_type": "dict"
},
{
"name": "type-habit-only",
"context": "deploy to shell now",
"match_type": "habit",
"corrections": [],
"habits": [
"run deploy script"
],
"n_corr_fields": [],
"scope_tags_type": null
},
{
"name": "type-correction-only",
"context": "deploy to shell now",
"match_type": "correction",
"corrections": [
"corr",
"plain corr",
"say Y",
"scoped corr"
],
"habits": [],
"n_corr_fields": [
"confidence",
"corrected_behavior",
"created_at",
"evidence_count",
"id",
"last_reinforced",
"original_behavior",
"scope_domain",
"scope_pattern",
"scope_tags",
"state",
"supersedes_id"
],
"scope_tags_type": "dict"
},
{
"name": "habit-regex-hit",
"context": "run the unit tests",
"match_type": null,
"corrections": [
"plain corr",
"say Y"
],
"habits": [],
"n_corr_fields": [
"confidence",
"corrected_behavior",
"created_at",
"evidence_count",
"id",
"last_reinforced",
"original_behavior",
"scope_domain",
"scope_pattern",
"scope_tags",
"state",
"supersedes_id"
],
"scope_tags_type": "dict"
},
{
"name": "low-conf-filtered",
"context": "zzz-no-match-ever quiet",
"match_type": null,
"corrections": [
"plain corr",
"say Y"
],
"habits": [],
"n_corr_fields": [
"confidence",
"corrected_behavior",
"created_at",
"evidence_count",
"id",
"last_reinforced",
"original_behavior",
"scope_domain",
"scope_pattern",
"scope_tags",
"state",
"supersedes_id"
],
"scope_tags_type": "dict"
},
{
"name": "bad-regex-skipped",
"context": "([unclosed anything",
"match_type": null,
"corrections": [
"plain corr",
"say Y"
],
"habits": [],
"n_corr_fields": [
"confidence",
"corrected_behavior",
"created_at",
"evidence_count",
"id",
"last_reinforced",
"original_behavior",
"scope_domain",
"scope_pattern",
"scope_tags",
"state",
"supersedes_id"
],
"scope_tags_type": "dict"
}
],
"reinforce_habit": [
{
"name": "success-promotes-at-3",
"item_type": "habit",
"outcome": "success",
"pre": {},
"n": 3,
"results": [
{
"reinforced": 7,
"confidence": 1,
"state": "candidate"
},
{
"reinforced": 7,
"confidence": 1,
"state": "candidate"
},
{
"reinforced": 7,
"confidence": 1,
"state": "active"
}
],
"final_row": {
"id": 7,
"name": "",
"trigger_pattern": "t1",
"context_tags": "[]",
"action_template": "a1",
"activation_count": 3,
"success_count": 3,
"failure_count": 0,
"min_activations": 3,
"confidence": 1.0,
"decay_rate": 0.95,
"state": "active",
"created_at": "2026-10-11T16:42:08.943499+00:00",
"last_activated": "2026-10-11T16:42:08.944671+00:00"
}
},
{
"name": "failure-counts",
"item_type": "habit",
"outcome": "failure",
"pre": {},
"n": 2,
"results": [
{
"reinforced": 8,
"confidence": 0.5,
"state": "candidate"
},
{
"reinforced": 8,
"confidence": 0.5,
"state": "candidate"
}
],
"final_row": {
"id": 8,
"name": "",
"trigger_pattern": "t2",
"context_tags": "[]",
"action_template": "a2",
"activation_count": 2,
"success_count": 0,
"failure_count": 2,
"min_activations": 3,
"confidence": 0.5,
"decay_rate": 0.95,
"state": "candidate",
"created_at": "2026-10-11T16:42:08.945183+00:00",
"last_activated": "2026-10-11T16:42:08.945790+00:00"
}
},
{
"name": "neutral-bumps-activation-only",
"item_type": "habit",
"outcome": "neutral",
"pre": {},
"n": 1,
"results": [
{
"reinforced": 9,
"confidence": 0.5,
"state": "candidate"
}
],
"final_row": {
"id": 9,
"name": "",
"trigger_pattern": "t3",
"context_tags": "[]",
"action_template": "a3",
"activation_count": 1,
"success_count": 0,
"failure_count": 0,
"min_activations": 3,
"confidence": 0.5,
"decay_rate": 0.95,
"state": "candidate",
"created_at": "2026-10-11T16:42:08.946348+00:00",
"last_activated": "2026-10-11T16:42:08.946617+00:00"
}
},
{
"name": "already-active-stays",
"item_type": "habit",
"outcome": "success",
"pre": {},
"n": 2,
"results": [
{
"reinforced": 10,
"confidence": 1,
"state": "active"
},
{
"reinforced": 10,
"confidence": 1,
"state": "active"
}
],
"final_row": {
"id": 10,
"name": "",
"trigger_pattern": "t4",
"context_tags": "[]",
"action_template": "a4",
"activation_count": 2,
"success_count": 2,
"failure_count": 0,
"min_activations": 1,
"confidence": 1.0,
"decay_rate": 0.95,
"state": "active",
"created_at": "2026-10-11T16:42:08.947163+00:00",
"last_activated": "2026-10-11T16:42:08.947743+00:00"
}
},
{
"name": "min5-gate",
"item_type": "habit",
"outcome": "success",
"pre": {},
"n": 4,
"results": [
{
"reinforced": 11,
"confidence": 1,
"state": "candidate"
},
{
"reinforced": 11,
"confidence": 1,
"state": "candidate"
},
{
"reinforced": 11,
"confidence": 1,
"state": "candidate"
},
{
"reinforced": 11,
"confidence": 1,
"state": "candidate"
}
],
"final_row": {
"id": 11,
"name": "",
"trigger_pattern": "t5",
"context_tags": "[]",
"action_template": "a5",
"activation_count": 4,
"success_count": 4,
"failure_count": 0,
"min_activations": 5,
"confidence": 1.0,
"decay_rate": 0.95,
"state": "candidate",
"created_at": "2026-10-11T16:42:08.948282+00:00",
"last_activated": "2026-10-11T16:42:08.949492+00:00"
}
},
{
"name": "habit-not-found",
"item_type": "habit",
"outcome": "success",
"pre": {},
"n": 1,
"results": [
{
"error": "Habit 99991 not found"
}
],
"final_row": null
}
],
"reinforce_correction": [
{
"name": "success-plus-point-one",
"item_type": "correction",
"outcome": "success",
"pre": {},
"n": 1,
"results": [
{
"reinforced": 8,
"confidence": 0.6,
"state": "active"
}
],
"final_row": {
"id": 8,
"original_behavior": "x1",
"corrected_behavior": "y1",
"scope_tags": "{}",
"scope_pattern": "",
"scope_domain": "",
"evidence_count": 2,
"confidence": 0.6,
"state": "active",
"created_at": "2026-10-11T16:42:08.950028+00:00",
"last_reinforced": "2026-10-11T16:42:08.950309+00:00",
"supersedes_id": null
}
},
{
"name": "failure-archives",
"item_type": "correction",
"outcome": "failure",
"pre": {},
"n": 1,
"results": [
{
"reinforced": 9,
"confidence": 0.2,
"state": "archived"
}
],
"final_row": {
"id": 9,
"original_behavior": "x2",
"corrected_behavior": "y2",
"scope_tags": "{}",
"scope_pattern": "",
"scope_domain": "",
"evidence_count": 1,
"confidence": 0.2,
"state": "archived",
"created_at": "2026-10-11T16:42:08.950837+00:00",
"last_reinforced": "2026-10-11T16:42:08.951121+00:00",
"supersedes_id": null
}
},
{
"name": "double-failure-floor",
"item_type": "correction",
"outcome": "failure",
"pre": {},
"n": 3,
"results": [
{
"reinforced": 10,
"confidence": 0.2,
"state": "archived"
},
{
"reinforced": 10,
"confidence": 0,
"state": "archived"
},
{
"reinforced": 10,
"confidence": 0,
"state": "archived"
}
],
"final_row": {
"id": 10,
"original_behavior": "x3",
"corrected_behavior": "y3",
"scope_tags": "{}",
"scope_pattern": "",
"scope_domain": "",
"evidence_count": 1,
"confidence": 0.0,
"state": "archived",
"created_at": "2026-10-11T16:42:08.951619+00:00",
"last_reinforced": "2026-10-11T16:42:08.952462+00:00",
"supersedes_id": null
}
},
{
"name": "ceiling-at-one",
"item_type": "correction",
"outcome": "success",
"pre": {
"confidence": 0.95
},
"n": 1,
"results": [
{
"reinforced": 11,
"confidence": 1,
"state": "active"
}
],
"final_row": {
"id": 11,
"original_behavior": "x4",
"corrected_behavior": "y4",
"scope_tags": "{}",
"scope_pattern": "",
"scope_domain": "",
"evidence_count": 2,
"confidence": 1.0,
"state": "active",
"created_at": "2026-10-11T16:42:08.952951+00:00",
"last_reinforced": "2026-10-11T16:42:08.953429+00:00",
"supersedes_id": null
}
},
{
"name": "neutral-noop",
"item_type": "correction",
"outcome": "neutral",
"pre": {},
"n": 1,
"results": [
{
"reinforced": 12,
"confidence": 0.5,
"state": "active"
}
],
"final_row": {
"id": 12,
"original_behavior": "x5",
"corrected_behavior": "y5",
"scope_tags": "{}",
"scope_pattern": "",
"scope_domain": "",
"evidence_count": 1,
"confidence": 0.5,
"state": "active",
"created_at": "2026-10-11T16:42:08.953912+00:00",
"last_reinforced": "2026-10-11T16:42:08.954167+00:00",
"supersedes_id": null
}
},
{
"name": "correction-not-found",
"item_type": "correction",
"outcome": "success",
"pre": {},
"n": 1,
"results": [
{
"error": "Correction 99992 not found"
}
],
"final_row": null
}
],
"list": [
{
"name": "all-keys",
"result_keys": [
"corrections",
"habits"
]
},
{
"name": "type-all",
"keys": [
"corrections",
"habits"
],
"n_corr": 10,
"n_hab": 11
},
{
"name": "type-correction",
"keys": [
"corrections"
],
"n_corr": 10,
"n_hab": 0
},
{
"name": "type-habit",
"keys": [
"habits"
],
"n_corr": 0,
"n_hab": 11
},
{
"name": "limit-1",
"n_corr": 1,
"n_hab": 1
}
],
"stats": [
{
"name": "counts",
"expected": {
"corrections_active": 10,
"corrections_archived": 2,
"habits_active": 2,
"habits_candidate": 9,
"total_activations": 12
}
}
]
},
"plugin": {
"name": "behavior",
"version": "0.1.0",
"description": "Behavioral patterns \u2014 scoped corrections and habit formation.",
"capabilities": [
"corrections",
"habits",
"behavioral_patterns"
],
"hooks": {},
"dependencies": [],
"provides": [
"behavior"
],
"priority": 50,
"auto_activate": true
}
}
+355
View File
@@ -0,0 +1,355 @@
//! bh_battery — frozen-oracle battery for the behavior core.
//! The truth JSON is the LIVE oracle's output (generated by running
//! scripts/gen_behavior_truth.py against src/rune/behavior.py, the
//! strangler shim serving ep_loader decision math) and is replayed
//! VERBATIM: add/insert shapes, the match_context branch matrix, the
//! reinforce matrix (habit/correction x outcome x promotion/archival),
//! list filters, and stats counts.
use super::{correction_matches, correction_reinforce, correction_visible, habit_matches,
habit_reinforce, habit_visible, parse_scope_tags, plugin, HabitRow};
use serde_json::Value;
const TRUTH: &str = include_str!("bh_truth.json");
fn truth() -> Value {
serde_json::from_str(TRUTH).expect("bh_truth.json parses")
}
fn habit_row(row: &Value) -> HabitRow {
serde_json::from_value(row.clone()).expect("habit row deserializes")
}
fn corr_row(row: &Value) -> (String, String, f64, String, i64) {
(
row["scope_pattern"].as_str().unwrap_or("").into(),
row["scope_domain"].as_str().unwrap_or("").into(),
row["confidence"].as_f64().unwrap_or(0.5),
row["state"].as_str().unwrap_or("").into(),
row["evidence_count"].as_i64().unwrap_or(1),
)
}
#[allow(dead_code)]
fn habit_upd(row: &Value, outcome: &str) -> Value {
let r = habit_row(row);
serde_json::to_value(habit_reinforce(&r, outcome)).unwrap()
}
#[allow(dead_code)]
fn corr_upd(row: &Value, outcome: &str) -> Value {
let (_pat, _domain, conf, _state, count) = corr_row(row);
let (nc, ncnt, ns) = correction_reinforce(conf, count, outcome);
serde_json::json!({"confidence": nc, "evidence_count": ncnt as f64, "state": ns.as_str()})
}
/// add/insert shapes: the returned dict + the full stored row (timestamps
/// and ids replayed verbatim — the frozen truth pins them).
#[test]
fn test_add_shapes() {
let t = truth();
for c in t["cases"]["add_correction"].as_array().unwrap() {
let want = &c["expected"];
assert_eq!(want["confidence"].as_f64(), Some(0.5), "{}", c["name"]);
assert!(want["added"].as_i64().is_some(), "{}: has id", c["name"]);
let row = &c["row"];
assert_eq!(row["state"].as_str(), Some("active"), "{}", c["name"]);
assert_eq!(row["confidence"].as_f64(), Some(0.5));
assert_eq!(row["evidence_count"].as_i64(), Some(1));
assert_eq!(
row["scope_tags"].as_str(),
Some(if c["name"] == "scoped" { r#"{"k": "v"}"# } else { "{}" }),
"{}: scope_tags stored as JSON string",
c["name"]
);
}
for c in t["cases"]["add_habit"].as_array().unwrap() {
let want = &c["expected"];
assert_eq!(want["state"].as_str(), Some("candidate"), "{}", c["name"]);
let row = &c["row"];
assert_eq!(row["state"].as_str(), Some("candidate"));
assert_eq!(row["confidence"].as_f64(), Some(0.5));
assert_eq!(
row["min_activations"].as_i64(),
Some(if c["name"] == "full" { 5 } else { 3 })
);
assert_eq!(
row["context_tags"].as_str(),
Some(if c["name"] == "full" { r#"["a"]"# } else { "[]" }),
"{}: context_tags stored as JSON string",
c["name"]
);
}
}
/// The match_context branch matrix: the frozen truth pins the survivor
/// action-text sets and the row shape invariants per context.
#[test]
fn test_match_matrix() {
let t = truth();
for case in t["cases"]["match"].as_array().unwrap() {
// scope_tags_type is only recorded on cases with a correction
// survivor (no-correction cases can't probe the row shape).
if let Some(st) = case["scope_tags_type"].as_str() {
assert_eq!(st, "dict", "{}: scope_tags parsed to dict", case["name"]);
assert!(
case["n_corr_fields"]
.as_array()
.unwrap()
.iter()
.any(|f| f.as_str() == Some("scope_tags")),
"{}: scope_tags in row fields",
case["name"]
);
}
// Survivor invariants re-derived through the predicates:
// every frozen habit survivor text corresponds to a trigger that
// matches the context; corrections need BOTH pattern and domain.
if case["name"].as_str() == Some("pattern-domain-hit") {
assert!(correction_matches("deploy", "shell", case["context"].as_str().unwrap()));
assert!(habit_matches("deploy", case["context"].as_str().unwrap()));
}
if case["name"].as_str() == Some("domain-only-miss") {
// "deploy the editor": editor-domain correction (no pattern)
// survives, shell-domain one dies on the domain filter.
assert!(correction_matches("", "editor", case["context"].as_str().unwrap()));
assert!(!correction_matches("", "shell", case["context"].as_str().unwrap()));
}
if case["name"].as_str() == Some("low-conf-filtered") {
assert!(!habit_visible("candidate", 0.2), "confidence gate");
}
if case["name"].as_str() == Some("bad-regex-skipped") {
assert!(!correction_matches("([unclosed", "", case["context"].as_str().unwrap()));
assert!(!habit_matches("([broken", case["context"].as_str().unwrap()));
}
}
}
/// reinforce matrix: every frozen habit case replays through the math —
/// the results sequence and the final row state/counts.
#[test]
fn test_reinforce_habit_matrix() {
let t = truth();
for case in t["cases"]["reinforce_habit"].as_array().unwrap() {
let name = case["name"].as_str().unwrap();
if case["final_row"].is_null() {
// not-found branch: {"error": "Habit N not found"}
let r = &case["results"][0];
assert!(r["error"].as_str().unwrap().starts_with("Habit "), "{name}");
continue;
}
let row = habit_row(&case["final_row"]);
// Replay forward from the reconstructed PRE row (final counts
// minus the n recorded steps). Pre state: the oracle's state
// never reverts, so the pre state is the first recorded result's
// state — except when promotion fired ON step 0 (pre 'candidate'
// but result[0] 'active' can only happen if pre count already
// met min_activations, in which case 'candidate' still holds as
// the add-time state; the promote-at-step-0 case is covered by
// already-active-stays whose pre row re-derives to 'candidate').
let outcome = case["outcome"].as_str().unwrap();
let n = case["n"].as_i64().unwrap();
let mut cur = row.clone();
cur.activation_count -= n;
cur.success_count -= if outcome == "success" { n } else { 0 };
cur.failure_count -= if outcome == "failure" { n } else { 0 };
cur.state = case["results"][0]["state"].as_str().unwrap().to_string();
let mut states = vec![];
for _ in 0..n {
let upd = habit_reinforce(&cur, outcome);
states.push((
upd["confidence"].as_f64().unwrap(),
upd["state"].as_str().unwrap().to_string(),
));
cur.activation_count = upd["activation_count"].as_f64().unwrap() as i64;
cur.success_count = upd["success_count"].as_f64().unwrap() as i64;
cur.failure_count = upd["failure_count"].as_f64().unwrap() as i64;
cur.state = upd["state"].as_str().unwrap().to_string();
}
// Final row must equal the replay's end state (confidences
// compared at 1e-12; the oracle REAL is f64 both sides).
let final_conf = row.confidence;
let replay_conf = cur.confidence;
assert!(
(final_conf - replay_conf).abs() < 1e-12,
"{name}: confidence divergence {final_conf} vs {replay_conf}"
);
assert_eq!(cur.state, row.state, "{name}: final state");
for (i, (conf, state)) in states.iter().enumerate() {
let want = &case["results"][i];
assert_eq!(
want["state"].as_str(),
Some(state.as_str()),
"{name}: step {i} state"
);
if let Some(wc) = want["confidence"].as_f64() {
assert!(
(wc - conf).abs() < 1e-12,
"{name}: step {i} confidence {wc} vs {conf}"
);
}
}
}
}
/// reinforce matrix: corrections — confidence ± steps, evidence counting,
/// active/archived threshold, floor/ceiling.
#[test]
fn test_reinforce_correction_matrix() {
let t = truth();
for case in t["cases"]["reinforce_correction"].as_array().unwrap() {
let name = case["name"].as_str().unwrap();
if case["final_row"].is_null() {
let r = &case["results"][0];
assert!(
r["error"].as_str().unwrap().starts_with("Correction "),
"{name}"
);
continue;
}
let row = &case["final_row"];
let outcome = case["outcome"].as_str().unwrap();
let n = case["n"].as_i64().unwrap();
// Seed the pre-state: recorded `pre` overrides when the generator
// pinned one (ceiling case); else back out one outcome step from
// the FIRST recorded result (the floor/ceiling clamps make
// reversal from the final row lossy).
let mut conf = match case["pre"].get("confidence").and_then(|v| v.as_f64()) {
Some(c) => c,
None => {
let first = case["results"][0]["confidence"].as_f64().unwrap();
match outcome {
"success" => first - 0.1,
"failure" => first + 0.3,
_ => first,
}
}
};
let pre_count = row["evidence_count"].as_i64().unwrap()
- i64::from(outcome == "success") * n;
// Replay and compare every step + final row.
let mut count = pre_count;
for i in 0..n {
let (nc, ncnt, ns) = correction_reinforce(conf, count, outcome);
let want = &case["results"][i as usize];
assert_eq!(want["state"].as_str(), Some(ns.as_str()), "{name}: step {i} state");
let wconf = want["confidence"].as_f64().unwrap();
assert!(
(wconf - nc).abs() < 1e-12,
"{name}: step {i} confidence {wconf} vs {nc}"
);
conf = nc;
count = ncnt;
}
let fr_conf = row["confidence"].as_f64().unwrap();
assert!((fr_conf - conf).abs() < 1e-12, "{name}: final confidence");
assert_eq!(
row["state"].as_str().unwrap(),
if fr_conf > 0.3 { "active" } else { "archived" },
"{name}: final state consistency"
);
}
}
/// The WHERE-clause predicates: frozen list/stats counts re-derived from
/// the seeded state; every visible row respects the gates.
#[test]
fn test_visibility_gates_and_list_counts() {
let t = truth();
for c in t["cases"]["list"].as_array().unwrap() {
match c["name"].as_str().unwrap() {
"all-keys" => {
let keys: Vec<&str> = c["result_keys"]
.as_array()
.unwrap()
.iter()
.map(|k| k.as_str().unwrap())
.collect();
assert_eq!(keys, vec!["corrections", "habits"]);
}
"limit-1" => {
assert_eq!(c["n_corr"].as_i64(), Some(1), "LIMIT 1 applies");
}
"type-correction" => {
assert!(c["keys"].as_array().unwrap().iter().all(|k| k.as_str() == Some("corrections")));
assert!(c["n_hab"].as_i64().unwrap() == 0 || c.get("n_hab").is_none());
}
"type-habit" => {
assert!(c["keys"].as_array().unwrap().iter().all(|k| k.as_str() == Some("habits")));
}
_ => {}
}
}
// Gates (unit-level, oracle-probed formulas):
assert!(correction_visible("active", 0.31));
assert!(!correction_visible("active", 0.3), "confidence > 0.3 strict");
assert!(!correction_visible("archived", 0.9));
assert!(habit_visible("candidate", 0.31));
assert!(habit_visible("active", 0.31));
assert!(!habit_visible("candidate", 0.3), "confidence > 0.3 strict");
}
/// Regex/domain filter semantics (oracle-probed): pattern AND domain must
/// both pass; invalid regex skips the row; domain is a case-insensitive
/// substring; empty pattern/domain never block.
#[test]
fn test_match_predicates() {
assert!(correction_matches("deploy", "shell", "deploy to shell now"));
assert!(!correction_matches("deploy", "editor", "deploy to shell now"),
"domain miss filters");
assert!(!correction_matches("editor", "shell", "deploy to shell now"),
"pattern miss filters");
assert!(!correction_matches("([unclosed", "", "anything"),
"invalid regex skips");
assert!(correction_matches("", "", "anything"), "empty filters never block");
assert!(correction_matches("", "SHELL", "deploy to shell now"),
"domain case-insensitive");
assert!(habit_matches("deploy", "DEPLOY the thing"), "trigger IGNORECASE");
assert!(!habit_matches("test (unit|e2e)", "run the unit tests"),
"unescaped parens match nothing (frozen)");
assert!(!habit_matches("([broken", "anything"), "invalid trigger skips");
}
/// scope_tags parse fallback: valid JSON parses, corrupt input → {}.
#[test]
fn test_scope_tags_fallback() {
assert_eq!(parse_scope_tags(r#"{"a": 1}"#), serde_json::json!({"a": 1}));
assert_eq!(parse_scope_tags(r#"["a"]"#), serde_json::json!(["a"]));
assert_eq!(parse_scope_tags("not json"), serde_json::json!({}));
assert_eq!(parse_scope_tags(""), serde_json::json!({}));
}
/// The PLUGIN descriptor data port is verbatim.
#[test]
fn test_plugin_frozen() {
let t = truth();
assert_eq!(plugin(), t["plugin"], "PLUGIN descriptor verbatim");
}
/// Full update-dict replay for one habit case through the FFI-shaped
/// path (envelope handling is exercised via the FFI symbols in the
/// loader tests; here the math contract is pinned).
#[test]
fn test_habit_update_dict_shape() {
let t = truth();
let case = &t["cases"]["reinforce_habit"][0];
let row = habit_row(&case["final_row"]);
let mut cur = row.clone();
cur.activation_count = 0;
cur.success_count = 0;
cur.failure_count = 0;
cur.state = "candidate".into();
let upd = habit_reinforce(&cur, "success");
assert_eq!(upd["activation_count"].as_f64(), Some(1.0));
assert_eq!(upd["success_count"].as_f64(), Some(1.0));
assert_eq!(upd["failure_count"].as_f64(), Some(0.0));
assert_eq!(upd["state"].as_str(), Some("candidate"), "min 3 gate");
}
#[test]
fn test_correction_update_dict_shape() {
// failure-archives: confidence 0.5 → 0.2, evidence_count UNCHANGED.
let upd = corr_upd(&serde_json::json!({"confidence": 0.5, "evidence_count": 1}), "failure");
assert_eq!(upd["state"].as_str(), Some("archived"));
assert_eq!(upd["confidence"].as_f64(), Some(0.2));
assert_eq!(upd["evidence_count"].as_f64(), Some(1.0), "failure keeps count");
}
+405 -8
View File
@@ -1,12 +1,409 @@
//! behavior — behaviour definition system (W6).
use serde::{Deserialize, Serialize};
//! behavior — behavioral patterns: scoped corrections and habit formation.
//!
//! Dichotomy-wave port (#2479 tranche) of `src/rune/behavior.py`'s FULL
//! decision surface. The oracle is a strangler shim: SQLite I/O + regex
//! matching live in Python while the reinforce counting/confidence math
//! runs in-process through the `evidence_provenance` cdylib. This crate
//! reunites the WHOLE surface in Rust:
//!
//! * `habit_reinforce` / `correction_reinforce` — the decision math
//! (name-preserving port of evidence_provenance's habitmath, which is
//! itself the port of this module's formulas),
//! * `match_row` — the per-row match_context predicates (scope-pattern
//! regex, domain substring, confidence gate, invalid-regex skip),
//! * `correction_visible` / `habit_visible` — the SQL WHERE clauses as
//! pure predicates (state + confidence gates),
//! * `PLUGIN` — the module plugin descriptor (frozen data),
//! * `db_path` — the memory-dir path resolution (RUNE_MEMORY_DIR override
//! → else rune_home()/memory, mirroring memory_env).
//!
//! IO stays Python: SQLite open/exec (system libsqlite3 has no write
//! path in the memory_storage precedent — this crate links NO sqlite and
//! owns zero SQL text), wall-clock `_now`, argparse CLI, DB_PATH constant.
//! The facade passes ROWS (fetched Python-side) and receives decisions.
//!
//! FFI contract (doc-envelope law — every call returns {"ok", "value"|"err"}):
//! * bh_habit_reinforce({row, outcome}) -> update dict
//! * bh_correction_reinforce({row, outcome}) -> update dict
//! * bh_match_row({kind, row, context}) -> {"visible", "matched"}
//! * bh_plugin() -> the PLUGIN descriptor
//!
//! NOTE: no free-symbol export (capability_scoring's cs_free_string serves
//! the whole process — duplicate symbols break the cdylib link).
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct Behavior {
pub id: String,
pub description: String,
use regex::Regex;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
// ── row shapes ──────────────────────────────────────────────────────────
/// A corrections row as fetched (row_factory=Row → dict(c)). scope_tags
/// arrives as a JSON STRING (json.dumps at insert); the oracle parses it
/// back to a dict for the match output ({} on corrupt input).
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CorrectionRow {
pub id: i64,
#[serde(default)]
pub evidence_refs: Vec<String>,
pub original_behavior: Option<String>,
#[serde(default)]
pub status: String,
pub corrected_behavior: String,
#[serde(default)]
pub scope_tags: String,
#[serde(default)]
pub scope_pattern: String,
#[serde(default)]
pub scope_domain: String,
#[serde(default = "default_evidence")]
pub evidence_count: i64,
#[serde(default = "default_confidence")]
pub confidence: f64,
#[serde(default)]
pub state: String,
}
fn default_evidence() -> i64 {
1
}
fn default_confidence() -> f64 {
0.5
}
/// A habits row as fetched.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HabitRow {
pub id: i64,
#[serde(default)]
pub name: String,
#[serde(default)]
pub trigger_pattern: String,
#[serde(default)]
pub context_tags: String,
#[serde(default)]
pub action_template: String,
#[serde(default)]
pub activation_count: i64,
#[serde(default)]
pub success_count: i64,
#[serde(default)]
pub failure_count: i64,
#[serde(default = "default_min_act")]
pub min_activations: i64,
#[serde(default = "default_confidence")]
pub confidence: f64,
#[serde(default)]
pub state: String,
}
fn default_min_act() -> i64 {
3
}
// ── reinforce decision math ─────────────────────────────────────────────
// (Name-preserving port of evidence_provenance habitmath — the formulas
// this module's shim delegates to. Python semantics preserved exactly:
// confidence is REAL (f64); success_rate = success/count at the NEW
// counts; failure keeps evidence_count unchanged; neutral leaves counts
// (habits still +1 activation); candidate→active iff new_count >= min.)
/// Reinforcement outcome lattice (Phase B: enum over string soup; the
/// FFI boundary parses once, the math matches exhaustively).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Outcome {
Success,
Failure,
Neutral,
}
impl Outcome {
fn parse(outcome: &str) -> Self {
match outcome {
"success" => Outcome::Success,
"failure" => Outcome::Failure,
_ => Outcome::Neutral,
}
}
}
/// Correction lifecycle state lattice (Phase B: enum, serialized at the
/// JSON boundary via exhaustive as_str).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CorrectionState {
Active,
Archived,
}
impl CorrectionState {
pub fn as_str(self) -> &'static str {
match self {
CorrectionState::Active => "active",
CorrectionState::Archived => "archived",
}
}
}
/// Habit promotion state lattice (Phase B: enum; the candidate→active
/// edge is the only legal transition, and only at promote time).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HabitState {
Candidate,
Active,
Other,
}
impl HabitState {
fn parse(state: &str) -> Self {
match state {
"candidate" => HabitState::Candidate,
"active" => HabitState::Active,
_ => HabitState::Other,
}
}
pub fn as_str(self) -> &'static str {
match self {
HabitState::Candidate => "candidate",
HabitState::Active => "active",
HabitState::Other => "",
}
}
}
/// habit_reinforce decision math → {activation_count, success_count,
/// failure_count, confidence, state}.
pub fn habit_reinforce(row: &HabitRow, outcome: &str) -> BTreeMap<String, serde_json::Value> {
let outcome = Outcome::parse(outcome);
let new_count = row.activation_count + 1;
let new_success = row.success_count + i64::from(outcome == Outcome::Success);
let new_failure = row.failure_count + i64::from(outcome == Outcome::Failure);
// The oracle guards `if new_count > 0` — always true (activation+1).
let success_rate = new_success as f64 / new_count as f64;
let new_confidence = (0.5 + success_rate * 0.5).min(1.0);
let new_state = match HabitState::parse(&row.state) {
// candidate→active iff new_count >= min_activations; every other
// state passes through unchanged (the oracle never demotes).
HabitState::Candidate if new_count >= row.min_activations => "active".to_string(),
HabitState::Candidate => "candidate".to_string(),
HabitState::Active => "active".to_string(),
// Unknown states carry their original string through.
HabitState::Other => row.state.clone(),
};
let mut out = BTreeMap::new();
out.insert("activation_count".into(), (new_count as f64).into());
out.insert("success_count".into(), (new_success as f64).into());
out.insert("failure_count".into(), (new_failure as f64).into());
out.insert("confidence".into(), new_confidence.into());
out.insert("state".into(), new_state.into());
out
}
/// correction_reinforce decision math → {confidence, evidence_count, state}.
pub fn correction_reinforce(
confidence: f64,
evidence_count: i64,
outcome: &str,
) -> (f64, i64, CorrectionState) {
let new_confidence = match Outcome::parse(outcome) {
Outcome::Success => (confidence + 0.1).min(1.0),
Outcome::Failure => (confidence - 0.3).max(0.0),
Outcome::Neutral => confidence,
};
let new_count = match Outcome::parse(outcome) {
// Only success grows the evidence count; failure/neutral keep it.
Outcome::Success => evidence_count + 1,
Outcome::Failure | Outcome::Neutral => evidence_count,
};
let new_state = if new_confidence > 0.3 {
CorrectionState::Active
} else {
CorrectionState::Archived
};
(new_confidence, new_count, new_state)
}
// ── match predicates ────────────────────────────────────────────────────
// (The per-row half of match_context: SQL WHERE → *_visible, then the
// regex/domain filters. Row ORDER (ORDER BY confidence DESC /
// confidence*activation_count DESC) stays Python-side — SQL owns order.)
/// The corrections WHERE clause as a predicate: state='active' AND
/// confidence > 0.3.
pub fn correction_visible(state: &str, confidence: f64) -> bool {
state == "active" && confidence > 0.3
}
/// The habits WHERE clause as a predicate: state IN ('active',
/// 'candidate') AND confidence > 0.3.
pub fn habit_visible(state: &str, confidence: f64) -> bool {
(state == "active" || state == "candidate") && confidence > 0.3
}
/// A correction row's scope filters against the context: the scope
/// pattern must `re.search` (IGNORECASE; invalid regex → row skipped)
/// and the domain must occur as a case-insensitive substring.
pub fn correction_matches(pattern: &str, domain: &str, context: &str) -> bool {
if !pattern.is_empty() {
match Regex::new(&format!("(?i){pattern}")) {
Ok(re) => {
if !re.is_match(context) {
return false;
}
}
Err(_) => return false,
}
}
if !domain.is_empty() && !context.to_lowercase().contains(&domain.to_lowercase()) {
return false;
}
true
}
/// A habit row's trigger filter: `re.search(trigger, context,
/// IGNORECASE)`; invalid regex → row skipped.
pub fn habit_matches(trigger: &str, context: &str) -> bool {
match Regex::new(&format!("(?i){trigger}")) {
Ok(re) => re.is_match(context),
Err(_) => false,
}
}
/// Parse scope_tags from its stored JSON-string form ({} on corrupt
/// input — mirrors the oracle's `json.loads(...) or {}` fallback).
pub fn parse_scope_tags(raw: &str) -> serde_json::Value {
serde_json::from_str(raw).unwrap_or(serde_json::json!({}))
}
// ── path + plugin data ──────────────────────────────────────────────────
/// behavior.db path resolution (mirrors memory_env: RUNE_MEMORY_DIR
/// override → else rune_home()/memory; rune_home defaults to
/// /mnt/workdrive/rune).
pub fn db_path() -> String {
let mem = std::env::var("RUNE_MEMORY_DIR").unwrap_or_else(|_| {
let home = std::env::var("RUNE_HOME").unwrap_or_else(|_| "/mnt/workdrive/rune".into());
format!("{home}/memory")
});
format!("{mem}/behavior.db")
}
/// The module PLUGIN descriptor (frozen data port).
pub fn plugin() -> serde_json::Value {
serde_json::json!({
"name": "behavior",
"version": "0.1.0",
"description": "Behavioral patterns — scoped corrections and habit formation.",
"capabilities": ["corrections", "habits", "behavioral_patterns"],
"hooks": {},
"dependencies": [],
"provides": ["behavior"],
"priority": 50,
"auto_activate": true,
})
}
// ── FFI ─────────────────────────────────────────────────────────────────
fn read_arg(ptr: *const std::os::raw::c_char) -> Result<serde_json::Value, String> {
if ptr.is_null() {
return Err("null argument pointer".into());
}
let x = unsafe { std::ffi::CStr::from_ptr(ptr) }
.to_str()
.map_err(|e| e.to_string())?;
serde_json::from_str(x).map_err(|e| e.to_string())
}
fn envelope(r: Result<serde_json::Value, String>) -> *mut std::os::raw::c_char {
let v = match r {
Ok(x) => serde_json::json!({"ok": true, "value": x}),
Err(e) => serde_json::json!({"ok": false, "err": e}),
};
std::ffi::CString::new(serde_json::to_string(&v).unwrap_or_default())
.map(|c| c.into_raw())
.unwrap_or(std::ptr::null_mut())
}
fn num(row: &serde_json::Value, key: &str, default: f64) -> f64 {
match row.get(key) {
Some(serde_json::Value::Number(n)) => n.as_f64().unwrap_or(default),
Some(serde_json::Value::Bool(b)) => f64::from(u8::from(*b)),
_ => default,
}
}
fn str_field(row: &serde_json::Value, key: &str) -> String {
match row.get(key) {
Some(serde_json::Value::String(s)) => s.clone(),
_ => String::new(),
}
}
/// bh_habit_reinforce({row, outcome}) -> the update dict.
#[no_mangle]
pub extern "C" fn bh_habit_reinforce(arg: *const std::os::raw::c_char) -> *mut std::os::raw::c_char {
envelope(read_arg(arg).and_then(|a| {
let row_v = a.get("row").cloned().unwrap_or(serde_json::json!({}));
let outcome = str_field(&a, "outcome");
let row: HabitRow = serde_json::from_value(row_v).map_err(|e| e.to_string())?;
let upd = habit_reinforce(&row, &outcome);
serde_json::to_value(upd).map_err(|e| e.to_string())
}))
}
/// bh_correction_reinforce({row, outcome}) -> the update dict.
#[no_mangle]
pub extern "C" fn bh_correction_reinforce(
arg: *const std::os::raw::c_char,
) -> *mut std::os::raw::c_char {
envelope(read_arg(arg).map(|a| {
let row_v = a.get("row").cloned().unwrap_or(serde_json::json!({}));
let outcome = str_field(&a, "outcome");
let (new_confidence, new_count, new_state) = correction_reinforce(
num(&row_v, "confidence", 0.5),
num(&row_v, "evidence_count", 1.0) as i64,
&outcome,
);
serde_json::json!({
"confidence": new_confidence,
"evidence_count": new_count as f64,
"state": new_state.as_str(),
})
}))
}
/// bh_match_row({kind, row, context}) -> {"visible", "matched"}.
/// kind: "correction" | "habit". Visibility = the SQL WHERE predicate;
/// matched = the regex/domain filters (only meaningful when visible).
#[no_mangle]
pub extern "C" fn bh_match_row(arg: *const std::os::raw::c_char) -> *mut std::os::raw::c_char {
envelope(read_arg(arg).map(|a| {
let kind = str_field(&a, "kind");
let row_v = a.get("row").cloned().unwrap_or(serde_json::json!({}));
let context = str_field(&a, "context");
let (visible, matched) = if kind == "habit" {
let v = habit_visible(&str_field(&row_v, "state"), num(&row_v, "confidence", 0.5));
let m = v && habit_matches(&str_field(&row_v, "trigger_pattern"), &context);
(v, m)
} else {
let v = correction_visible(&str_field(&row_v, "state"), num(&row_v, "confidence", 0.5));
let m = v
&& correction_matches(
&str_field(&row_v, "scope_pattern"),
&str_field(&row_v, "scope_domain"),
&context,
);
(v, m)
};
serde_json::json!({"visible": visible, "matched": matched})
}))
}
/// bh_plugin() -> the PLUGIN descriptor.
#[no_mangle]
pub extern "C" fn bh_plugin() -> *mut std::os::raw::c_char {
envelope(Ok(plugin()))
}
#[cfg(test)]
#[path = "branch_battery.rs"]
mod branch_battery;
+215
View File
@@ -0,0 +1,215 @@
//! build_basis branch battery — parity vs the frozen Python-oracle truth
//! (frozen_bb_truth.json, 68 cases across every public function and
//! branch, frozen 2026-10-11). Embedded VERBATIM via include_str!.
//!
//! Timestamp fields the kernel generates (decided_at, created_at,
//! revised_at, plan_generated_at) are masked on both sides — wall clock,
//! not logic. created_at values inside basis INPUTS are fixed
//! pass-through and compare exactly.
use serde_json::Value;
use super::*;
const TRUTH: &str = include_str!("frozen_bb_truth.json");
/// Remove kernel-generated wall-clock fields wherever they appear.
fn mask(v: Value) -> Value {
match v {
Value::Object(map) => Value::Object(
map.into_iter()
.filter(|(k, _)| {
!matches!(
k.as_str(),
"decided_at" | "created_at" | "revised_at" | "plan_generated_at"
)
})
.map(|(k, v)| (k, mask(v)))
.collect(),
),
Value::Array(items) => Value::Array(items.into_iter().map(mask).collect()),
other => other,
}
}
fn cases(fn_name: &str) -> Vec<Value> {
let truth: Value = serde_json::from_str(TRUTH).unwrap();
truth["cases"][fn_name]
.as_array()
.unwrap()
.clone()
}
fn input(case: &Value) -> Value {
case["input"].clone()
}
fn expected(case: &Value) -> &Value {
case.get("expected").expect("case must carry expected")
}
fn check(name: &str, actual: Value, case: &Value) {
let masked = mask(actual);
assert!(
&masked == expected(case),
"case {name} diverged:\n actual: {masked}\n expected: {}",
expected(case)
);
}
fn check_err(name: &str, actual: Result<Value, String>, case: &Value) {
let err = case.get("error").expect("case must carry error");
match actual {
Ok(_) => panic!("case {name} should have raised: {err}"),
Err(e) => assert_eq!(&e, err, "case {name} error message diverged"),
}
}
#[test]
fn research_depth_parity_battery() {
for case in cases("research_depth") {
let a = input(&case);
let out = research_depth(
a["route"].as_str().unwrap(),
a.get("mode").and_then(Value::as_str).unwrap_or(""),
a.get("justification").and_then(Value::as_str).unwrap_or(""),
);
check(case["name"].as_str().unwrap(), out, &case);
}
}
#[test]
fn new_basis_parity_battery() {
for case in cases("new_basis") {
let a = input(&case);
let out = new_basis(
a["question"].as_str().unwrap(),
&a["depth_decision"],
a.get("issue_ref").and_then(Value::as_str).unwrap_or(""),
);
check(case["name"].as_str().unwrap(), out, &case);
}
}
#[test]
fn set_dispositions_parity_battery() {
for case in cases("set_dispositions") {
let a = input(&case);
let mut basis = a["basis"].clone();
let errors = set_dispositions(&mut basis, &a["dispositions"]);
check(
case["name"].as_str().unwrap(),
serde_json::json!({"basis": basis, "errors": errors}),
&case,
);
}
}
#[test]
fn validate_basis_parity_battery() {
for case in cases("validate_basis") {
let a = input(&case);
let errors = validate_basis(&a["basis"]);
check(
case["name"].as_str().unwrap(),
serde_json::to_value(errors).unwrap(),
&case,
);
}
}
#[test]
fn is_qualified_parity_battery() {
for case in cases("is_qualified") {
let a = input(&case);
let out = is_qualified(&a["basis"]);
check(case["name"].as_str().unwrap(), Value::Bool(out), &case);
}
}
#[test]
fn attach_evidence_ref_parity_battery() {
for case in cases("attach_evidence_ref") {
let a = input(&case);
let out = attach_evidence_ref(&a["basis"]);
check(case["name"].as_str().unwrap(), Value::String(out), &case);
}
}
#[test]
fn summary_parity_battery() {
for case in cases("summary") {
let a = input(&case);
let out = summary(&a["basis"]);
check(case["name"].as_str().unwrap(), out, &case);
}
}
#[test]
fn revise_depth_parity_battery() {
for case in cases("revise_depth") {
let a = input(&case);
let mut basis = a["basis"].clone();
let r = revise_depth(&mut basis, a["new_depth"].as_str().unwrap(), a["reason"].as_str().unwrap());
match r {
Ok(revision) => check(
case["name"].as_str().unwrap(),
serde_json::json!({"basis": basis, "revision": revision}),
&case,
),
Err(e) => check_err(case["name"].as_str().unwrap(), Err(e), &case),
}
}
}
#[test]
fn to_json_parity_battery() {
for case in cases("to_json") {
let a = input(&case);
let out = to_json(&a["basis"]);
check(case["name"].as_str().unwrap(), Value::String(out), &case);
}
}
#[test]
fn from_json_parity_battery() {
for case in cases("from_json") {
let a = input(&case);
match from_json(a["text"].as_str().unwrap()) {
Ok(v) => check(case["name"].as_str().unwrap(), v, &case),
Err(_) => assert!(
case.get("error_kind").is_some(),
"case {} unexpected parse failure",
case["name"].as_str().unwrap()
),
}
}
}
#[test]
fn generate_research_plan_parity_battery() {
for case in cases("generate_research_plan") {
let a = input(&case);
let strs = |k: &str| -> Vec<String> {
a.get(k)
.and_then(Value::as_array)
.map(|arr| {
arr.iter()
.filter_map(Value::as_str)
.map(str::to_string)
.collect()
})
.unwrap_or_default()
};
let out = generate_research_plan(
a["intent"].as_str().unwrap(),
a.get("target").and_then(Value::as_str).unwrap_or(""),
a.get("current_reality").and_then(Value::as_str).unwrap_or(""),
&strs("unknowns"),
&strs("decisions"),
&strs("risks"),
a.get("depth").and_then(Value::as_str),
);
check(case["name"].as_str().unwrap(), out, &case);
}
}
File diff suppressed because it is too large. Load diff
+876 -38
View File
@@ -1,50 +1,888 @@
//! build_basis — build basis summary and evidence (W6).
//! PARITY port of src/rune/build_basis.py.
use serde::{Deserialize, Serialize};
//! build_basis — T18 §18C: durable versioned RESEARCH BASIS for
//! construction (V1 rule F: RESEARCH INFORMS STRUCTURE).
//!
//! FULL parity port of src/rune/build_basis.py (dichotomy wave, #2479
//! tranche, RUST side verdict). A Build Basis is a durable, schema-
//! versioned document capturing WHAT research decided and WHY, with
//! references — never research dumps. This module NEVER researches
//! (rule C): it records the depth decision derived from the canonical
//! route authority and structures adopt/adapt/combine/reject/innovate
//! decisions (rule E).
//!
//! FFI (doc-envelope law): every `bb_*` symbol takes one JSON arg and
//! returns {"ok": true, "value": …} or {"ok": false, "err": msg}.
//! NOTE: no bb_free_string export — sibling crates sharing the Python
//! process already export identical free symbols; a duplicate breaks the
//! cdylib link. Callers free through capability_scoring's cs_free_string.
//!
//! to_json reproduces the oracle byte-for-byte:
//! json.dumps(basis, indent=1, sort_keys=True) — including ensure_ascii
//! \uXXXX escaping (lowercase hex, surrogate pairs for astral chars),
//! code-point key sorting, and Python repr() strings inside error
//! messages.
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct BuildBasis {
#[serde(default)]
pub depth: String,
#[serde(default)]
pub implications: serde_json::Value,
#[serde(default)]
pub risks: Vec<String>,
#[serde(default)]
pub evidence: Vec<String>,
use serde_json::{json, Map, Value};
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
use std::time::{SystemTime, UNIX_EPOCH};
pub const SCHEMA_VERSION: i64 = 1;
pub const OUTWARD_PRIOR_ART_QUESTIONS: &[&str] = &[
"what already exists addressing this intent",
"what open-source systems/products/workflows attempt it",
"what architectural patterns already work",
"what standards/conventions exist",
"what can be directly ADOPTED",
"what can be ADAPTED",
"what can be COMBINED",
"what should be REJECTED",
"where is genuine INNOVATION required",
];
// ── decision lattices (enums with exhaustive matches at the JSON
// boundary — the wire always carries the oracle's exact strings) ─────
/// Research-depth lattice (rule C: derived from adaptive_router ROUTES,
/// never a second router).
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum DepthTier {
None,
Inward,
Outward,
Full,
}
pub fn summarize(depth: &str, risks: Vec<String>, implications: serde_json::Value) -> BuildBasis {
BuildBasis {
depth: depth.to_string(),
implications,
risks,
evidence: vec![],
impl DepthTier {
pub fn as_str(self) -> &'static str {
match self {
DepthTier::None => "NONE",
DepthTier::Inward => "INWARD",
DepthTier::Outward => "OUTWARD",
DepthTier::Full => "FULL",
}
}
/// The oracle's DEPTH_TIERS tuple, rendered as Python repr.
pub const PY_TIERS_REPR: &'static str =
"('NONE', 'INWARD', 'OUTWARD', 'FULL')";
pub fn parse(s: &str) -> Option<DepthTier> {
match s {
"NONE" => Some(DepthTier::None),
"INWARD" => Some(DepthTier::Inward),
"OUTWARD" => Some(DepthTier::Outward),
"FULL" => Some(DepthTier::Full),
_ => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
/// Disposition lattice (rule E): adopt/adapt/combine/reject/innovate.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum Disposition {
Adopt,
Adapt,
Combine,
Reject,
Innovate,
}
#[test]
fn test_summarize() {
let b = summarize(
"STANDARD",
vec!["risk1".into()],
serde_json::json!({"migration": []}),
impl Disposition {
pub fn as_str(self) -> &'static str {
match self {
Disposition::Adopt => "ADOPT",
Disposition::Adapt => "ADAPT",
Disposition::Combine => "COMBINE",
Disposition::Reject => "REJECT",
Disposition::Innovate => "INNOVATE",
}
}
pub fn parse(s: &str) -> Option<Disposition> {
match s {
"ADOPT" => Some(Disposition::Adopt),
"ADAPT" => Some(Disposition::Adapt),
"COMBINE" => Some(Disposition::Combine),
"REJECT" => Some(Disposition::Reject),
"INNOVATE" => Some(Disposition::Innovate),
_ => None,
}
}
}
// ── time (matches time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())) ──
fn civil_from_days(z: i64) -> (i64, u32, u32) {
let z = z + 719_468;
let era = z.div_euclid(146_097);
let doe = z.rem_euclid(146_097);
let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146_096) / 365;
let y = yoe + era * 400;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
let mp = (5 * doy + 2) / 153;
let d = (doy - (153 * mp + 2) / 5 + 1) as u32;
let m = if mp < 10 { mp + 3 } else { mp - 9 } as u32;
(if m <= 2 { y + 1 } else { y }, m, d)
}
pub fn now() -> String {
let secs = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs() as i64)
.unwrap_or(0);
let days = secs.div_euclid(86400);
let rem = secs.rem_euclid(86400);
let (y, m, d) = civil_from_days(days);
format!(
"{:04}-{:02}-{:02}T{:02}:{:02}:{:02}Z",
y,
m,
d,
rem / 3600,
(rem % 3600) / 60,
rem % 60
)
}
// ── Python semantics helpers ────────────────────────────────────────────
/// Python str repr: single quotes unless the string contains ' and not ".
fn py_repr_str(s: &str) -> String {
let quote = if s.contains('\'') && !s.contains('"') {
'"'
} else {
'\''
};
let mut out = String::with_capacity(s.len() + 2);
out.push(quote);
for c in s.chars() {
match c {
'\\' => out.push_str("\\\\"),
'\n' => out.push_str("\\n"),
'\r' => out.push_str("\\r"),
'\t' => out.push_str("\\t"),
c if c == quote => {
out.push('\\');
out.push(c);
}
c if (c as u32) < 0x20 || (c as u32) == 0x7f => {
out.push_str(&format!("\\x{:02x}", c as u32));
}
c => out.push(c),
}
}
out.push(quote);
out
}
/// Python repr() of a JSON value (as f"{x!r}" renders it).
fn py_repr(v: Option<&Value>) -> String {
match v {
None | Some(Value::Null) => "None".into(),
Some(Value::Bool(b)) => if *b { "True" } else { "False" }.into(),
Some(Value::Number(n)) => n.to_string(),
Some(Value::String(s)) => py_repr_str(s),
Some(other) => other.to_string(),
}
}
/// Python truthiness for JSON values (0, "", [], {}, null, False are falsy).
fn is_truthy(v: Option<&Value>) -> bool {
match v {
None | Some(Value::Null) => false,
Some(Value::Bool(b)) => *b,
Some(Value::Number(n)) => n.as_f64().map(|f| f != 0.0).unwrap_or(true),
Some(Value::String(s)) => !s.is_empty(),
Some(Value::Array(a)) => !a.is_empty(),
Some(Value::Object(o)) => !o.is_empty(),
}
}
/// Python str[:n] — code points, not bytes.
fn truncate_chars(s: &str, n: usize) -> String {
s.chars().take(n).collect()
}
/// schema_version equality with Python semantics: True == 1.
fn schema_version_ok(v: Option<&Value>) -> bool {
match v {
Some(Value::Number(n)) => n.as_i64() == Some(SCHEMA_VERSION),
Some(Value::Bool(b)) => *b, // Python True == 1
_ => false,
}
}
fn route_default_depth(route: &str) -> Option<DepthTier> {
match route {
"LOCAL_FIX" => Some(DepthTier::None),
"STANDARD_CHANGE" => Some(DepthTier::Inward),
"RESEARCHED_CHANGE" => Some(DepthTier::Full),
_ => None,
}
}
// ── research_depth ──────────────────────────────────────────────────────
/// The research-depth DECISION for a construction need. Derived from the
/// canonical route authority; `justification` is REQUIRED when deviating
/// from the route default — recorded, never silent (rule D).
pub fn research_depth(route: &str, mode: &str, justification: &str) -> Value {
let default = route_default_depth(route);
let mut depth = default.unwrap_or(DepthTier::Inward);
let mut why = format!("route {route} default");
if !justification.trim().is_empty() {
// an explicit justification selects a tier by name if given;
// otherwise it annotates the default (zero-copy: candidate
// borrows the trimmed justification)
let j = justification.trim();
let candidate = j.split(':').next().unwrap_or("").trim();
if let Some(tier) = DepthTier::parse(candidate) {
depth = tier;
why = j.to_string();
} else {
why = format!("route {route} default; note: {j}");
}
} else if default.is_none() {
why = format!(
"route {} unknown — INWARD floor (never silent NONE)",
py_repr_str(route)
);
assert_eq!(b.depth, "STANDARD");
assert_eq!(b.risks.len(), 1);
depth = DepthTier::Inward;
}
json!({"depth": depth.as_str(), "why": why, "route": route, "mode": mode,
"decided_at": now()})
}
// ── new_basis ───────────────────────────────────────────────────────────
/// Create an empty-but-valid Build Basis. References only — full research
/// dumps live in the Research Core's artifact dirs.
pub fn new_basis(question: &str, depth_decision: &Value, issue_ref: &str) -> Value {
json!({
"schema_version": SCHEMA_VERSION,
"kind": "build_basis",
"created_at": now(),
"question": question,
"issue_ref": issue_ref,
"depth": depth_decision,
"inward": {"findings": [], "refs": []},
"outward": {"prior_art_questions_answered": [],
"findings": [], "refs": []},
"deeper": {"used": false, "findings": [], "refs": []},
"dispositions": [],
"alternatives_considered": [],
"uncertainty": [],
"risks": [],
"implications": {"architecture": [], "structural": [],
"migration": [], "verification": []},
})
}
// ── set_dispositions / validate_basis / is_qualified ────────────────────
/// Record the adopt/adapt/combine/reject/innovate decisions (rule E).
/// Mutates `basis` and returns validation errors; the basis stays
/// unqualified until valid.
pub fn set_dispositions(basis: &mut Value, dispositions: &Value) -> Vec<String> {
let rows: Vec<Value> = dispositions
.as_array()
.map(|a| {
a.iter()
.map(|d| {
let get = |k: &str| d.get(k).cloned().unwrap_or(json!(""));
json!({"item": get("item"), "disposition": get("disposition"),
"why": get("why"), "ref": get("ref")})
})
.collect()
})
.unwrap_or_default();
basis["dispositions"] = Value::Array(rows);
validate_basis(basis)
}
/// Qualification gate. A basis is QUALIFIED when it carries a depth
/// decision with why, at least one outward prior-art answer when depth is
/// OUTWARD/FULL, and every disposition uses a legal disposition.
pub fn validate_basis(basis: &Value) -> Vec<String> {
let mut errors: Vec<String> = Vec::new();
if !schema_version_ok(basis.get("schema_version")) {
errors.push(format!(
"schema_version {}",
py_repr(basis.get("schema_version"))
));
}
// Python: (basis.get("depth") or {}) — falsy depth slots fall back to {}
let depth_obj = basis
.get("depth")
.filter(|d| is_truthy(Some(d)))
.and_then(|d| d.as_object());
let empty = Map::new();
let depth_obj = depth_obj.unwrap_or(&empty);
let depth = depth_obj.get("depth");
let depth_str = depth.and_then(Value::as_str);
let depth_tier = depth_str.and_then(DepthTier::parse);
if depth_tier.is_none() {
errors.push(format!(
"depth {} not in {}",
py_repr(depth),
DepthTier::PY_TIERS_REPR
));
}
if !is_truthy(depth_obj.get("why")) {
errors.push("depth decision lacks WHY (rule D)".into());
}
if matches!(depth_tier, Some(DepthTier::Outward) | Some(DepthTier::Full)) {
let outward = basis
.get("outward")
.filter(|o| is_truthy(Some(o)))
.and_then(Value::as_object);
let has_evidence = outward
.map(|o| {
is_truthy(o.get("prior_art_questions_answered"))
|| is_truthy(o.get("findings"))
|| is_truthy(o.get("refs"))
})
.unwrap_or(false);
if !has_evidence {
errors.push("OUTWARD/FULL depth requires prior-art evidence".into());
}
}
if depth_tier == Some(DepthTier::Inward) {
let inward = basis
.get("inward")
.filter(|o| is_truthy(Some(o)))
.and_then(Value::as_object);
let has_evidence = inward
.map(|o| is_truthy(o.get("findings")) || is_truthy(o.get("refs")))
.unwrap_or(false)
|| is_truthy(basis.get("dispositions"));
if !has_evidence {
errors.push(
"INWARD depth requires recorded evidence (findings/refs) — \
an empty basis is not qualified"
.into(),
);
}
}
for d in basis.get("dispositions").and_then(Value::as_array).unwrap_or(&vec![]) {
let disp = d.get("disposition");
if !disp
.and_then(Value::as_str)
.and_then(Disposition::parse)
.is_some()
{
let item = d.get("item").and_then(Value::as_str).unwrap_or("");
errors.push(format!(
"illegal disposition {} for {}",
py_repr(disp),
py_repr_str(&truncate_chars(item, 40))
));
}
}
errors
}
pub fn is_qualified(basis: &Value) -> bool {
validate_basis(basis).is_empty()
}
// ── attach_evidence_ref / summary ───────────────────────────────────────
/// The evidence_ref string a contract carries to point at this basis
/// (durable pointer, not a copy — stores stay independent).
pub fn attach_evidence_ref(basis: &Value) -> String {
// f-string uses str(): missing/null created_at renders as "None"
let created = match basis.get("created_at") {
None | Some(Value::Null) => "None".to_string(),
Some(Value::String(s)) => s.clone(),
Some(other) => other.to_string(),
};
let issue_ref = match basis.get("issue_ref") {
Some(Value::String(s)) if !s.is_empty() => s.clone(),
_ => "adhoc".to_string(),
};
format!("build_basis:{issue_ref}:{created}")
}
fn len_of(v: Option<&Value>) -> usize {
match v {
Some(Value::Array(a)) => a.len(),
Some(Value::String(s)) => s.chars().count(),
Some(Value::Object(o)) => o.len(),
_ => 0,
}
}
/// Compact projection for prompts/previews (no private reasoning —
/// structured conclusions only, rule 18G).
pub fn summary(basis: &Value) -> Value {
let empty_vec: Vec<Value> = Vec::new();
json!({
"schema_version": basis.get("schema_version").cloned().unwrap_or(Value::Null),
"question": basis.get("question").cloned().unwrap_or(Value::Null),
"issue_ref": basis.get("issue_ref").cloned().unwrap_or(Value::Null),
"depth": basis.get("depth").cloned().unwrap_or(Value::Null),
"qualified": is_qualified(basis),
"validation": validate_basis(basis),
"dispositions": basis
.get("dispositions")
.and_then(Value::as_array)
.unwrap_or(&empty_vec)
.iter()
.map(|d| {
json!({
"item": truncate_chars(d.get("item").and_then(Value::as_str).unwrap_or(""), 80),
"disposition": d.get("disposition").cloned().unwrap_or(Value::Null),
})
})
.collect::<Vec<Value>>(),
"uncertainty_count": len_of(basis.get("uncertainty")),
"risks_count": len_of(basis.get("risks")),
"implications": match basis.get("implications").and_then(Value::as_object) {
Some(o) => o
.iter()
.map(|(k, v)| (k.clone(), json!(len_of(Some(v)))))
.collect::<Map<String, Value>>(),
None => Map::new(),
},
})
}
// ── revise_depth ────────────────────────────────────────────────────────
/// §21.1G: record a justified depth revision. Original + revised depths
/// both persist. An OUTWARD→INWARD/NONE downgrade requires explicit
/// resolution evidence — never a bare skip.
pub fn revise_depth(basis: &mut Value, new_depth: &str, reason: &str) -> Result<Value, String> {
let tier = DepthTier::parse(new_depth)
.ok_or_else(|| format!("unknown depth {}", py_repr_str(new_depth)))?;
let original = basis
.get("depth")
.and_then(|d| d.get("depth"))
.and_then(Value::as_str)
.unwrap_or_default()
.to_string();
if original == new_depth {
return Err("no-op revision: depth unchanged".into());
}
// §21.1G: consequential origins mix route names and tiers — the
// oracle compares the raw stored string, so parity keeps that.
let consequential = matches!(original.as_str(), "RESEARCHED_CHANGE" | "FULL" | "OUTWARD");
if consequential && matches!(tier, DepthTier::None | DepthTier::Inward) {
let low = reason.to_lowercase();
let justified = [
"resolved",
"non-material",
"discovery",
"unnecessary",
"prior-art question",
]
.iter()
.any(|k| low.contains(k));
if !justified {
return Err(
"OUTWARD downgrade requires explicit evidence: the prior-art \
question must be resolved as non-material (not merely skipped)"
.into(),
);
}
}
let revision = json!({
"original_depth": original,
"revised_depth": new_depth,
"reason": truncate_chars(reason, 200),
"revised_at": now(),
});
basis["depth_revision"] = revision.clone();
basis["depth"]["depth"] = json!(new_depth);
basis["depth"]["why"] = json!(format!("revised from {original}: {reason}"));
Ok(revision)
}
// ── to_json / from_json (Python json.dumps/loads semantics) ─────────────
fn escape_py_json_string(s: &str, out: &mut String) {
out.push('"');
for c in s.chars() {
match c {
'"' => out.push_str("\\\""),
'\\' => out.push_str("\\\\"),
'\n' => out.push_str("\\n"),
'\r' => out.push_str("\\r"),
'\t' => out.push_str("\\t"),
'\u{08}' => out.push_str("\\b"),
'\u{0c}' => out.push_str("\\f"),
c if (c as u32) < 0x20 => out.push_str(&format!("\\u{:04x}", c as u32)),
c if (c as u32) > 0x7e => {
let cp = c as u32;
if cp <= 0xFFFF {
out.push_str(&format!("\\u{:04x}", cp));
} else {
let v = cp - 0x10000;
out.push_str(&format!(
"\\u{:04x}\\u{:04x}",
0xD800 + (v >> 10),
0xDC00 + (v & 0x3FF)
));
}
}
c => out.push(c),
}
}
out.push('"');
}
/// json.dumps(value, indent=1, sort_keys=True) — byte-for-byte with the
/// oracle (ensure_ascii, code-point key order, 1-space indent steps).
pub fn to_json(basis: &Value) -> String {
let mut out = String::new();
write_py_json(basis, 0, &mut out);
out
}
fn write_py_json(v: &Value, indent: usize, out: &mut String) {
match v {
Value::Object(map) => {
if map.is_empty() {
out.push_str("{}");
return;
}
let mut keys: Vec<&String> = map.keys().collect();
keys.sort(); // UTF-8 byte order == code-point order
out.push_str("{\n");
for (i, k) in keys.iter().enumerate() {
for _ in 0..indent + 1 {
out.push(' ');
}
escape_py_json_string(k, out);
out.push_str(": ");
write_py_json(&map[k.as_str()], indent + 1, out);
if i + 1 < keys.len() {
out.push(',');
}
out.push('\n');
}
for _ in 0..indent {
out.push(' ');
}
out.push('}');
}
Value::Array(items) => {
if items.is_empty() {
out.push_str("[]");
return;
}
out.push_str("[\n");
for (i, item) in items.iter().enumerate() {
for _ in 0..indent + 1 {
out.push(' ');
}
write_py_json(item, indent + 1, out);
if i + 1 < items.len() {
out.push(',');
}
out.push('\n');
}
for _ in 0..indent {
out.push(' ');
}
out.push(']');
}
Value::String(s) => escape_py_json_string(s, out),
Value::Null => out.push_str("null"),
Value::Bool(b) => out.push_str(if *b { "true" } else { "false" }),
Value::Number(n) => out.push_str(&n.to_string()),
}
}
pub fn from_json(text: &str) -> Result<Value, String> {
serde_json::from_str(text).map_err(|e| e.to_string())
}
// ── generate_research_plan ──────────────────────────────────────────────
const OUTWARD_KEYWORDS: &[&str] = &[
"exist",
"existing",
"prior art",
"precedent",
"standard",
"convention",
"library",
"tool",
"framework",
"approach",
];
const CONSEQUENTIAL_KEYWORDS: &[&str] = &[
"architecture",
"migration",
"replace",
"rewrite",
"security",
"schema",
"delete",
"deploy",
"breaking",
];
/// Generate a CONTEXT-SPECIFIC research plan for one construction intent.
/// Topics are synthesized from the material unknowns/decisions/risks of
/// THIS intent — no topic vocabulary, no permanent category list.
#[allow(clippy::too_many_arguments)]
pub fn generate_research_plan(
intent: &str,
target: &str,
current_reality: &str,
unknowns: &[String],
decisions: &[String],
risks: &[String],
depth: Option<&str>,
) -> Value {
let unknowns: Vec<&str> = unknowns.iter().map(String::as_str).filter(|u| !u.trim().is_empty()).collect();
let decisions: Vec<&str> = decisions.iter().map(String::as_str).filter(|d| !d.trim().is_empty()).collect();
let risks: Vec<&str> = risks.iter().map(String::as_str).filter(|r| !r.trim().is_empty()).collect();
let mut topics: Vec<Value> = Vec::new();
// each material unknown becomes a research topic aimed at resolving it
for u in &unknowns {
topics.push(json!({
"topic": format!("resolve unknown: {u}"),
"kind": "unknown-resolution",
"why": format!("material unknown blocks confident construction: {}",
truncate_chars(u, 80)),
}));
}
for d in &decisions {
topics.push(json!({
"topic": format!("inform decision: {d}"),
"kind": "decision-support",
"why": format!("consequential decision needs precedent/evidence: {}",
truncate_chars(d, 80)),
}));
}
for r in &risks {
topics.push(json!({
"topic": format!("de-risk: {r}"),
"kind": "risk-investigation",
"why": format!("identified risk needs verification/mitigation evidence: {}",
truncate_chars(r, 80)),
}));
}
// precedent question (rule E) attaches ONLY when outward evidence is
// plausibly relevant — an unknown/decision about external prior art.
let joined: String = unknowns
.iter()
.chain(decisions.iter())
.copied()
.collect::<Vec<&str>>()
.join(" ")
.to_lowercase();
let needs_outward = OUTWARD_KEYWORDS.iter().any(|w| joined.contains(w));
if needs_outward {
topics.push(json!({
"topic": "what already exists that is relevant to THIS intent \
(adopt/adapt/combine/reject/innovate)",
"kind": "prior-art",
"why": "material unknown references existing practice — \
precedent discovery required before deeper research",
}));
}
let (depth, why) = match depth {
Some(d) => (d.to_string(), "depth supplied by caller (recorded, caller accountable)".to_string()),
None => {
// consequence heuristics: novel/architectural/unsafe → deeper
let hay = format!("{intent} {}", risks.join(" ")).to_lowercase();
let consequential = CONSEQUENTIAL_KEYWORDS.iter().any(|w| hay.contains(w));
let d = if consequential && !topics.is_empty() {
"FULL"
} else if needs_outward && !topics.is_empty() {
"OUTWARD"
} else if !topics.is_empty() {
"INWARD"
} else {
"NONE"
};
let why = match d {
"FULL" => "consequence heuristics: consequential keywords + research topics present",
"OUTWARD" => "precedent-relevant unknowns present",
"INWARD" => "local understanding suffices",
_ => "no material unknowns — local evidence only",
};
(d.to_string(), why.to_string())
}
};
json!({
"intent": intent,
"target": target,
"current_reality_note": truncate_chars(current_reality, 200),
"depth": {"depth": depth, "why": why},
"topics": topics,
"plan_generated_at": now(),
})
}
// ── FFI (doc-envelope law) ──────────────────────────────────────────────
fn read_arg(ptr: *const c_char) -> Result<Value, 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())?;
serde_json::from_str(x).map_err(|e| e.to_string())
}
fn envelope(r: Result<Value, String>) -> *mut c_char {
let v = match r {
Ok(x) => json!({"ok": true, "value": x}),
Err(e) => json!({"ok": false, "err": e}),
};
CString::new(serde_json::to_string(&v).unwrap_or_default())
.map(|c| c.into_raw())
.unwrap_or(std::ptr::null_mut())
}
fn arg_str<'a>(a: &'a Value, k: &str) -> &'a str {
a.get(k).and_then(Value::as_str).unwrap_or("")
}
fn arg_str_list(a: &Value, k: &str) -> Vec<String> {
a.get(k)
.and_then(Value::as_array)
.map(|arr| {
arr.iter()
.filter_map(Value::as_str)
.map(str::to_string)
.collect()
})
.unwrap_or_default()
}
/// bb_research_depth({"route", "mode"?, "justification"?}) → depth decision dict.
#[no_mangle]
pub extern "C" fn bb_research_depth(arg: *const c_char) -> *mut c_char {
envelope(read_arg(arg).map(|a| {
research_depth(
arg_str(&a, "route"),
arg_str(&a, "mode"),
arg_str(&a, "justification"),
)
}))
}
/// bb_new_basis({"question", "depth_decision", "issue_ref"?}) → basis dict.
#[no_mangle]
pub extern "C" fn bb_new_basis(arg: *const c_char) -> *mut c_char {
envelope(read_arg(arg).and_then(|a| {
let dd = a
.get("depth_decision")
.cloned()
.ok_or_else(|| "missing depth_decision".to_string())?;
Ok(new_basis(arg_str(&a, "question"), &dd, arg_str(&a, "issue_ref")))
}))
}
/// bb_set_dispositions({"basis", "dispositions"}) → {"basis", "errors"}.
#[no_mangle]
pub extern "C" fn bb_set_dispositions(arg: *const c_char) -> *mut c_char {
envelope(read_arg(arg).and_then(|a| {
let mut basis = a
.get("basis")
.cloned()
.ok_or_else(|| "missing basis".to_string())?;
let dispositions = a
.get("dispositions")
.cloned()
.unwrap_or_else(|| Value::Array(vec![]));
let errors = set_dispositions(&mut basis, &dispositions);
Ok(json!({"basis": basis, "errors": errors}))
}))
}
/// bb_validate_basis({"basis"}) → [errors].
#[no_mangle]
pub extern "C" fn bb_validate_basis(arg: *const c_char) -> *mut c_char {
envelope(read_arg(arg).and_then(|a| {
let basis = a.get("basis").ok_or_else(|| "missing basis".to_string())?;
Ok(serde_json::to_value(validate_basis(basis)).unwrap_or_default())
}))
}
/// bb_is_qualified({"basis"}) → bool.
#[no_mangle]
pub extern "C" fn bb_is_qualified(arg: *const c_char) -> *mut c_char {
envelope(read_arg(arg).and_then(|a| {
let basis = a.get("basis").ok_or_else(|| "missing basis".to_string())?;
Ok(Value::Bool(is_qualified(basis)))
}))
}
/// bb_attach_evidence_ref({"basis"}) → evidence_ref string.
#[no_mangle]
pub extern "C" fn bb_attach_evidence_ref(arg: *const c_char) -> *mut c_char {
envelope(read_arg(arg).and_then(|a| {
let basis = a.get("basis").ok_or_else(|| "missing basis".to_string())?;
Ok(Value::String(attach_evidence_ref(basis)))
}))
}
/// bb_summary({"basis"}) → summary dict.
#[no_mangle]
pub extern "C" fn bb_summary(arg: *const c_char) -> *mut c_char {
envelope(read_arg(arg).and_then(|a| {
let basis = a.get("basis").ok_or_else(|| "missing basis".to_string())?;
Ok(summary(basis))
}))
}
/// bb_revise_depth({"basis", "new_depth", "reason"}) → {"basis", "revision"}
/// or {"ok": false, "err": <exact Python ValueError message>}.
#[no_mangle]
pub extern "C" fn bb_revise_depth(arg: *const c_char) -> *mut c_char {
envelope(read_arg(arg).and_then(|a| {
let mut basis = a
.get("basis")
.cloned()
.ok_or_else(|| "missing basis".to_string())?;
let revision = revise_depth(&mut basis, arg_str(&a, "new_depth"), arg_str(&a, "reason"))?;
Ok(json!({"basis": basis, "revision": revision}))
}))
}
/// bb_to_json({"basis"}) → JSON string (json.dumps indent=1 sort_keys).
#[no_mangle]
pub extern "C" fn bb_to_json(arg: *const c_char) -> *mut c_char {
envelope(read_arg(arg).and_then(|a| {
let basis = a.get("basis").ok_or_else(|| "missing basis".to_string())?;
Ok(Value::String(to_json(basis)))
}))
}
/// bb_from_json({"text"}) → parsed value.
#[no_mangle]
pub extern "C" fn bb_from_json(arg: *const c_char) -> *mut c_char {
envelope(read_arg(arg).and_then(|a| from_json(arg_str(&a, "text"))))
}
/// bb_generate_research_plan({"intent", "target"?, "current_reality"?,
/// "unknowns"?, "decisions"?, "risks"?, "depth"?}) → plan dict.
#[no_mangle]
pub extern "C" fn bb_generate_research_plan(arg: *const c_char) -> *mut c_char {
envelope(read_arg(arg).map(|a| {
generate_research_plan(
arg_str(&a, "intent"),
arg_str(&a, "target"),
arg_str(&a, "current_reality"),
&arg_str_list(&a, "unknowns"),
&arg_str_list(&a, "decisions"),
&arg_str_list(&a, "risks"),
a.get("depth").and_then(Value::as_str),
)
}))
}
#[path = "branch_battery.rs"]
#[cfg(test)]
mod branch_battery {
use super::*;
#[test]
fn crate_exists_and_compiles() {
assert!(true);
}
}
mod branch_battery;
Loaded 100 of 231 files, more files were not shown because too many files have changed in this diff. Show more