Compare commits

...
7 Commits
Author SHA1 Message Date
conductor f82fed98fb chore(release): auto-bump version to 0.199.0 — earned by # (minor: converged release (push-to-master bump))
Auto-version and Release / release (push) Successful in 17s
CI Pipeline / best-practices (push) Successful in 5s
CI Pipeline / rust-surface (push) Failing after 12s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / go-surface (push) Failing after 15s
CI Pipeline / gitea-offline (push) Successful in 17s
CI Pipeline / lint (push) Failing after 44s
CI Pipeline / type-check (push) Failing after 38s
CI Pipeline / canary (push) Failing after 1m54s
deploy / deploy (push) Failing after 5m21s
CI Pipeline / unit-tests (push) Failing after 15m9s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Failing after 6s
Promotion record: (unknown)
2026-10-11 10:18:13 +01:00
Nick 55ad089c3f feat(spine): port to FULL Rust — dichotomy wave
CI Pipeline / documentation (push) Canceled after 0s
CI Pipeline / type-check (push) Canceled after 0s
CI Pipeline / gitea-offline (push) Canceled after 0s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
CI Pipeline / best-practices (push) Canceled after 3s
CI Pipeline / rust-surface (push) Canceled after 13s
Auto-version and Release / release (push) Canceled after 19s
deploy / deploy (push) Canceled after 20s
CI Pipeline / go-surface (push) Canceled after 16s
CI Pipeline / canary (push) Canceled after 16s
CI Pipeline / unit-tests (push) Canceled after 21s
CI Pipeline / lint (push) Canceled after 21s
Facade kernels ported to the rune_spine Rust core (RUST verdict, issue
#2479 tranche, contract BUILDER-2026-10-11T08:06:21Z-1a7a76-factory-wave):
- sf_sorted/sf_save_text/sf_entity/sf_declared_surfaces/
  sf_project_meta_surfaces/sf_ingest_umbrellas_data FFI via doc-envelope
- facade delegation wired (RUNE_SPINE_CORE=python pins the strangler
  fallback; entity returns the live dict to preserve mutation semantics)
- REAL divergence fixed pre-wiring: the Rust ingest refusal message was
  missing the oracle's exact text; battery caught the save redaction
  boundary (spine.save does NOT redact — that is structural_store.save)
- battery: 26 frozen-oracle cases replayed verbatim in-crate (45/45
  crate tests, clippy clean); live parity 26/26 vs the Python oracle
- 135 spine-adjacent Python tests green (delegation live); pinned
  fallback mode identical to pre-batch behavior
- dichotomy audit: spine 5/30 -> 20/35 functions matched (rune_spine
  crate registered in the alias table)

Dichotomy verdict: spine PARTIAL — pure kernels Rust-authoritative;
gather/IO and _py_* fallback oracles stay Python by the zero-dep split law.
2026-10-11 10:17:46 +01:00
conductor c8426bdddb chore(release): auto-bump version to 0.198.0 — earned by # (minor: converged release (push-to-master bump))
deploy / deploy (push) Skipped
CI Pipeline / best-practices (push) Successful in 8s
Auto-version and Release / release (push) Successful in 16s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / rust-surface (push) Failing after 11s
CI Pipeline / go-surface (push) Failing after 17s
CI Pipeline / gitea-offline (push) Successful in 17s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
CI Pipeline / type-check (push) Canceled after 21s
CI Pipeline / unit-tests (push) Canceled after 31s
CI Pipeline / lint (push) Canceled after 31s
CI Pipeline / canary (push) Canceled after 31s
Promotion record: (unknown)
2026-10-11 10:17:17 +01:00
Nick 27716e0243 feat(goal): port to FULL Go — dichotomy wave
CI Pipeline / best-practices (push) Successful in 8s
CI Pipeline / documentation (push) Successful in 8s
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 14s
CI Pipeline / go-surface (push) Canceled after 15s
CI Pipeline / rust-surface (push) Canceled after 13s
CI Pipeline / canary (push) Canceled after 16s
deploy / deploy (push) Canceled after 6s
CI Pipeline / gitea-offline (push) Canceled after 11s
CI Pipeline / type-check (push) Canceled after 12s
CI Pipeline / lint (push) Canceled after 20s
CI Pipeline / unit-tests (push) Canceled after 20s
Full name-preserving port of src/rune/goal.py to src/go/goal (GO verdict,
#2479 dichotomy tranche — 20/20 fns, dichotomy audit now FULL):
- Goal state machine: set/get_active/check/achieve/fail/clear/list,
  to_dict, load/save with insertion-order preservation
- VerifyWithLlm: judge prompt construction byte-parity, Ollama payload
  (num_ctx via CtxBudget over frozen DEFAULT_PROFILES snapshot),
  greedy (?s)\\{.*\\} verdict extraction, keyword fallback on any
  transport/parse failure — Python truthiness on 'achieved'
- VerifyWithKeywords: case-insensitive containment, Unmet: reason
- Module-level singleton + wrappers + RunCLI (goal.py main() exact
  stdout, incl. ✓ lines); IO (storage, LLM, clock, uuid, home) behind
  injectable seams
- pyjson.go: Python json.dumps(indent=2, ensure_ascii) byte-parity
  encoder + ordered-map decoder (dict insertion order is load-bearing)
- truth.json: 59 frozen rows from the live oracle covering EVERY public
  function and every branch (LLM achieved/not-yet/plaintext/badjson/
  missing-key/conn-error, partial/corrupt/unknown-field loads, CLI
  branches incl. captured llm_request) — replay 59/59 exact-equal,
  live-regenerated and re-verified; go test green; gofmt+vet clean
- CI: HARD goal FULL-port parity gate (replay + test, not swallowable)
- No _py_* fallback stubs existed; nothing excluded. goal.py untouched
  (Python oracle remains the parity authority; legacy partial
  goal-engine package unchanged)

Dichotomy audit: goal 20/20 → FULL (24%→25% function-level).
2026-10-11 10:13:17 +01:00
conductor caeaab3c1a chore(release): auto-bump version to 0.197.0 — earned by # (minor: converged release (push-to-master bump))
deploy / deploy (push) Skipped
Auto-version and Release / release (push) Successful in 13s
CI Pipeline / documentation (push) Successful in 5s
CI Pipeline / best-practices (push) Successful in 9s
CI Pipeline / rust-surface (push) Failing after 12s
CI Pipeline / go-surface (push) Failing after 18s
CI Pipeline / gitea-offline (push) Successful in 16s
CI Pipeline / lint (push) Failing after 43s
CI Pipeline / type-check (push) Failing after 38s
CI Pipeline / canary (push) Failing after 1m52s
CI Pipeline / unit-tests (push) Failing after 14m59s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Failing after 4s
Promotion record: (unknown)
2026-10-11 09:44:21 +01:00
Nick a95c6ce129 feat(curator): port to FULL Go — dichotomy wave
CI Pipeline / best-practices (push) Successful in 10s
CI Pipeline / documentation (push) Successful in 10s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
CI Pipeline / rust-surface (push) Canceled after 13s
CI Pipeline / type-check (push) Canceled after 2s
CI Pipeline / gitea-offline (push) Canceled after 1s
Auto-version and Release / release (push) Canceled after 14s
CI Pipeline / go-surface (push) Canceled after 18s
deploy / deploy (push) Canceled after 7s
CI Pipeline / canary (push) Canceled after 19s
CI Pipeline / lint (push) Canceled after 20s
CI Pipeline / unit-tests (push) Canceled after 20s
DICHOTOMY VERDICT: GO (reactive/entry surface — the port completes the
GO-side deterministic core; store/journal/embed/graph IO stays on the
Python adapter seams by design, not by omission).

Ported (name-preserving, src/go/curator-core/wave.go):
- IsTopicFact (topic-neuron heuristic, 21-word domain table)
- SemanticCandidateKey (cosine >= 0.62 accumulation, insertion-order
  tie-break; embed seam adapter-injected)
- GraphTriple/GraphWrite (ordered verb map, prep/punct object trim,
  subject guard; graph_memory.py subprocess stays a seam)
- AuthorSkillBody/AuthorSkill (SKILL.md doc-build, Evidence-section rule)
- ApplyOp (full write-path surgery: PROPOSE journaling, SKIP-DUPLICATE
  body+store dedup, SKIP-CONSTITUTION belt-and-braces, UPDATE supersede
  transition, DELETE line+meta removal, skill routing, unknown-label
  journal passthrough)
- HandleHook

Excluded (fallback/adapter by design): _aaak, _embed ollama IO,
coherence_check corpus/session IO, read_block/write_block/store_has
sqlite seams, apply_warm warm-router IO, run_plan drift-ledger IO —
these are the Python strangler surface, not ported per the scaffolding law.

Truth: scripts/gen_curator_wave_truth.py freezes 38 cases from the live
oracle (truth_curator_wave.json embedded verbatim); battery 38/38 +
live re-verified 38/38. Full curator-core suite green; Python surface
tests green (no facade delegation — Python oracle untouched).
2026-10-11 09:44:03 +01:00
Nick 9efb7d8697 feat(event_wait): port to FULL Go — dichotomy wave
DICHOTOMY VERDICT: GO (src/go/event-wait, #2479 tranche).

Name-preserving port of src/rune/event_wait.py's deterministic core:
wr_root (resolver chain), fifo_path, open_fifo_reader (mkfifo+chmod
0666 rendezvous), register_process_wait (durable record shaping),
pgroup_alive/cmdline_alive/liveness (/proc watchdog probes),
operation_wait, resume_or_recover (15E case-10), find_matching_event.

EXCLUDED (reactive surface, stays Python): run_builder_under_events,
await_resume_event (select/FIFO block), post_process_event (HTTP),
_load_waitstore (dynamic bridge loader). No _py_* stubs in this module.

Oracle-first truth: /tmp/ew_truth_gen.py freezes 36 cases from the LIVE
Python oracle across 10 fns (all resume_or_recover branches incl.
RESUME_UNVERIFIED-without-evidence, int/missing run_ref coercion,
newest-mtime scan, /proc liveness with live child probes, WR_WATCH_SECS
env arm, self-resume mode). Battery: replay 36/36 exact-equal, go test
gate green, fresh live-oracle regen 36/36. Python tests green:
test_event_wait.py 8/8, test_wr_root_authority.py 9/9.

Divergences found pre-wiring: 1 REAL — replay harness env leak (the
wr_root canonical-live case unset RUNE_WR_ROOT and never restored it,
poisoning later path cases); fixed with save/restore. Harness bug, not
oracle divergence. No issue filed (fix-first policy).
2026-10-11 09:43:38 +01:00
31 changed files with 6791 additions and 210 deletions

No files matched your search

+3
View File
@@ -237,6 +237,9 @@ jobs:
- name: goal-engine parity gate (HARD — #2479 batches 2+129: verifier + lifecycle)
run: |
cd src/go/goal-engine && go build -o /tmp/goal-engine-${{ gitea.run_id }} . && /tmp/goal-engine-${{ gitea.run_id }} replay truth.json && go test -run TestLifecycleParity -v
- name: goal FULL-port parity gate (HARD — #2479 dichotomy wave: 59-row frozen oracle)
run: |
cd src/go/goal && go build -o /tmp/goal-${{ gitea.run_id }} . && /tmp/goal-${{ gitea.run_id }} replay truth.json && go test -run TestTruthParity -v
- name: provider-budget parity gate (HARD — #2479 batches 3+146: budget + ties/headers/rpm)
run: |
cd src/go/provider-budget && go build -o /tmp/provider-budget-${{ gitea.run_id }} .
+1 -1
View File
@@ -1,6 +1,6 @@
# Rune
**Current release: v0.196.0**
**Current release: v0.199.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.**
+52 -39
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: 23%** (535/2357 runtime functions matched)
**Function-level completion: 25%** (620/2444 runtime functions matched)
| Status | Modules | Lines | Meaning |
|---|---|---|---|
| FULL | 8 | 734 | 80%+ of functions ported |
| PARTIAL | 79 | 39290 | 20–80% — real logic exists, gaps named below |
| NOT TOUCHED | 114 | 31504 | no name-preserving implementation |
| Total runtime | 201 | 71528 | |
| FULL | 11 | 2175 | 80%+ of functions ported |
| PARTIAL | 81 | 39948 | 20–80% — real logic exists, gaps named below |
| NOT TOUCHED | 122 | 30041 | no name-preserving implementation |
| Total runtime | 214 | 72164 | |
## NOT TOUCHED — the port queue, in priority order (largest first)
@@ -17,19 +17,15 @@ _Regenerate with `python3 scripts/dichotomy_audit.py --write`. A module is FULL
|---|---|---|---|
| rune_tui | 4170 | 159 | TUI-LAST |
| curator | 1838 | 43 | GO |
| spine | 1204 | 31 | RUST |
| spine | 965 | 35 | RUST |
| abstraction | 656 | 17 | GO |
| contracts | 629 | 7 | RUST |
| construction_intent | 555 | 16 | RUST |
| canary_audit_ledger | 541 | 2 | TEST-INFRA |
| event_wait | 518 | 14 | GO |
| tui_validator | 514 | 13 | TUI-LAST |
| memory_selector | 511 | 13 | RUST |
| system_registry | 504 | 14 | RUST |
| construction_pattern | 496 | 12 | RUST |
| auto_tasks | 460 | 14 | GO |
| drift_ledger | 456 | 16 | RUST |
| goal | 453 | 20 | GO |
| release_notes | 446 | 16 | GO |
| model_routing | 432 | 13 | GO |
| worthiness | 411 | 13 | unclassified |
@@ -42,7 +38,6 @@ _Regenerate with `python3 scripts/dichotomy_audit.py --write`. A module is FULL
| programme_continuation | 349 | 11 | RUST |
| canary_rune_serve | 329 | 13 | TEST-INFRA |
| canary_p1_07 | 324 | 10 | TEST-INFRA |
| layout_contract | 316 | 6 | RUST |
| message_queue | 316 | 13 | GO |
| drift_observability | 315 | 8 | RUST |
| build_basis | 314 | 12 | RUST |
@@ -68,14 +63,13 @@ _Regenerate with `python3 scripts/dichotomy_audit.py --write`. A module is FULL
| 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 |
| testing_mode | 196 | 12 | RUST |
| cc_loader | 195 | 26 | RUST |
| grading | 193 | 3 | unclassified |
| canary_spine_meta | 192 | 7 | TEST-INFRA |
| verification_layer | 190 | 12 | RUST |
| message_graph | 187 | 12 | unclassified |
| ffi_query_memory_smoke | 183 | 5 | RUST |
| behaviour_ledger | 174 | 12 | RUST |
@@ -84,10 +78,13 @@ _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 |
| mse_loader | 152 | 12 | unclassified |
| forge | 146 | 11 | GO |
| rune_graphic | 145 | 5 | TUI-LAST |
| spine_loader | 144 | 13 | RUST |
| project_registry | 139 | 11 | RUST |
| canary_rune_entry | 137 | 3 | TEST-INFRA |
| verification_layer | 136 | 11 | RUST |
| tui_backlog | 125 | 5 | TUI-LAST |
| state_checkpoint | 123 | 8 | unclassified |
| drift_resume | 118 | 3 | RUST |
@@ -95,7 +92,6 @@ _Regenerate with `python3 scripts/dichotomy_audit.py --write`. A module is FULL
| canary_query_memory | 111 | 2 | TEST-INFRA |
| tui_transport | 108 | 10 | TUI-LAST |
| canary_unbounded | 105 | 2 | TEST-INFRA |
| spine_loader | 105 | 7 | RUST |
| message_widgets | 104 | 6 | TUI-LAST |
| self_modify | 102 | 7 | unclassified |
| sc_loader | 96 | 6 | RUST |
@@ -104,13 +100,25 @@ _Regenerate with `python3 scripts/dichotomy_audit.py --write`. A module is FULL
| concept_topology | 90 | 7 | unclassified |
| cs_loader | 90 | 5 | RUST |
| tc_loader | 88 | 5 | RUST |
| broker_loader | 86 | 5 | unclassified |
| canary_schema_migration | 86 | 1 | TEST-INFRA |
| ss_loader | 86 | 7 | 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 |
| execution_trace | 71 | 4 | unclassified |
| gp_loader | 71 | 6 | unclassified |
| experience_encoding | 70 | 1 | unclassified |
| abstraction_ladder | 68 | 3 | unclassified |
| pane_authority | 67 | 2 | unclassified |
| ip_loader | 65 | 4 | unclassified |
| cb_loader | 64 | 3 | unclassified |
| skill_compiler | 64 | 4 | unclassified |
| sg_loader | 63 | 4 | unclassified |
| sm_loader | 63 | 4 | unclassified |
| meaningful_events | 61 | 3 | unclassified |
| outcome_utility | 59 | 4 | unclassified |
| memory_types | 54 | 2 | unclassified |
@@ -123,7 +131,7 @@ _Regenerate with `python3 scripts/dichotomy_audit.py --write`. A module is FULL
| evidence_capsule | 43 | 3 | unclassified |
| builder_bridge_cli | 42 | 1 | GO |
| __init__ | 40 | 1 | RUST |
| mr_loader | 38 | 13 | RUST |
| mr_loader | 40 | 15 | RUST |
| canary_memory_synthesis | 33 | 1 | TEST-INFRA |
| canary_resource_authority | 33 | 1 | TEST-INFRA |
| canary_surface_parity | 33 | 1 | TEST-INFRA |
@@ -134,46 +142,46 @@ _Regenerate with `python3 scripts/dichotomy_audit.py --write`. A module is FULL
| Module | Lines | Coverage | Missing (first named) |
|---|---|---|---|
| rune_operator | 2746 | 15/68 | _build_model_router, _bounded, _probe_failure_classes, _sorted_tool_names, _memory_query_from_trace, _observation_from_compaction |
| memory_store | 2586 | 29/78 | _ctx_budget, now_iso, ensure_memory_type_column, _embed, _temp_json, _entities_to_json |
| memory_store | 2333 | 30/78 | _ctx_budget, now_iso, ensure_memory_type_column, _embed, _temp_json, _entities_to_json |
| rune_operator | 2746 | 25/68 | _build_model_router, _sorted_tool_names, _filter_safety_noise, _wire_auto_filer, _reconcile_guarded, get_operator |
| context_broker | 870 | 6/28 | _py_default_estimator, _cc_core, _py_default_budget, _py_default_memory_budget, _py_memory_disabled_value, reset_discovered_windows |
| local_tool_runtime | 809 | 8/30 | _find_capability_executor, _extract_context_length, context_length, reset_context_length_cache, supports_tools, supports_thinking |
| construction_interface | 1193 | 6/27 | _to_stage_check, _guard_specs_from_obligations, t_layout, _obligations_projection, _obligation_scope_files, main |
| structural_store | 656 | 12/33 | _sorted, new_doc, load, _redact_secrets, save, canonical_digest |
| structural_store | 690 | 13/34 | _ss_core, _sorted, new_doc, _redact_secrets, save, canonical_digest |
| persona_gate | 724 | 7/26 | _now, _cc_pg_core, _py_lex, _lex_input, _py_overlap, _embed_raw |
| sleep_time | 984 | 8/26 | _ctx_budget, _ensure_dirs, db_connect, fetch_sessions, retain_to_store, reflect_local |
| provider_registry | 679 | 11/28 | _ctx_budget, _format_tools_openai, _execute_tool_calls, _execute_tool_calls_anthropic, get_registry, connect_provider |
| tool_protocol | 461 | 10/27 | _utc_now_iso, _contains_tool_object_key, to_ollama_tools, dispatch_validated_tool, tool_requires_approval, to_dict |
| warm_router | 1540 | 19/36 | _ensure_use_history_table, record_outcome, record_neuron_fire, _py_base_level_from_uses, base_level_activation, _db |
| construction_decision | 1014 | 4/20 | normalize_need, _sc_core, _py_language_placement, resolve_surface, ss_entity, implemented_contracts |
| tool_executor | 1836 | 31/47 | _execute_tool_now, _prepared_path, serialize_prepared_tool, deserialize_prepared_tool, confirm_write, _resolve_execution_cwd |
| warm_router | 1518 | 19/36 | _ensure_use_history_table, record_outcome, record_neuron_fire, _py_base_level_from_uses, base_level_activation, _db |
| construction_decision | 985 | 4/20 | normalize_need, _sc_core, _py_language_placement, resolve_surface, ss_entity, implemented_contracts |
| memory_consolidation | 770 | 7/23 | _consolidation_now, _consolidation_memory_oracle, _consolidation_neuron_oracle, _load_knowledge, _save_knowledge, _load_lessons |
| work_manager | 594 | 9/25 | group_by_file, load_config, save_config, load_batches, save_batches, create_work |
| growth_delta | 449 | 5/20 | _ctx_budget, _store_db, _llm, _parse_json, _load_deltas, _save_deltas |
| memory_consolidation | 743 | 7/22 | _consolidation_memory_oracle, _consolidation_neuron_oracle, _load_knowledge, _save_knowledge, _load_lessons, _save_lessons |
| 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 |
| 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 |
| scaffold | 868 | 17/31 | init, add_domain, add_component, add_contract, set_prd, set_next_action |
| build_mode | 521 | 20/33 | _load_state, _save_state, assess_complexity, start_research, start_feasible, start_consult |
| capability_broker | 908 | 7/20 | _native_tool_schemas, _select_for_job, load_registry, _native_tool_cards, _cards_are_stale, load_cards |
| failure_auto_filer | 527 | 11/24 | _error_text, unconsumed, mark_consumed, unconsumed, mark_consumed, submit |
| cache | 492 | 6/17 | _record_hit, _record_miss, flush_stats, get_cache, put_cache, invalidate_scope |
| elastic_cognition | 774 | 3/14 | _ctx_budget, _build_constitution, _get_constitution, _load_cloud_config, execute_at_tier, _execute_deterministic |
| 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 | 352 | 9/20 | workspace_record, workspace_advance, _init_project, save, reload, add |
| capability_fit | 455 | 3/13 | record_semantic_fit, _provides_contracts, _consumers, discover_candidates, _cs_core, decide |
| structural_model | 388 | 9/20 | workspace_record, workspace_advance, _init_project, save, reload, add |
| 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 |
| drift_core | 517 | 4/14 | _ms_core, _now_iso, _py_check_promotion_eligibility, promote_entry, compute_fama, compute_drift_velocity |
| drift_core | 471 | 4/14 | _ms_core, _now_iso, _py_check_promotion_eligibility, promote_entry, compute_fama, compute_drift_velocity |
| model_profiles | 438 | 5/15 | get_profile_path, load_profile, save_profile, build_profile_from_ollama, scan_all_models, get_or_create_profile |
| tier_manager | 262 | 5/15 | _cc_probe, cc_loader_pin, _py_core_now, _py_get_tier, _py_promote, _py_demote |
| witness | 475 | 8/18 | _ctx_budget, _now, _cc_probe, cc_loader_pin, _py_extract_topic, _py_detect_signals |
| witness | 446 | 8/18 | _ctx_budget, _now, _cc_probe, cc_loader_pin, _py_extract_topic, _py_detect_signals |
| drift_compression | 307 | 3/12 | _py_get_compression_ratio, _py_compress_text, _py_compress_entry, _py_should_warn, _py_warn_before_compress, get_compression_ratio |
| drift_ledger | 471 | 7/16 | ledger_path, read_block, load_ledger, save_ledger, surface_alert, snapshot |
| scaffold | 868 | 22/31 | init, set_prd, add_maintenance_item, bind_repo, observe_repo, repo_state_from_git |
| builder_bridge | 326 | 3/11 | _construction_store, _active_construction_lines, _default_selector, _cached_memory, _get_selector, _retrieve_memory |
| 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 |
| pins | 284 | 5/12 | _store_path, _save, _detail_blob, unpin, _py_pin_recall, list_pins |
| pins | 282 | 5/12 | _store_path, _save, _detail_blob, unpin, _py_pin_recall, list_pins |
| voice_hybrid | 427 | 3/10 | _connect, _load_growth, _save_growth, source_profiles, always, grow |
| voice_sync | 299 | 3/10 | _now, get_delivery_register_from_db, get_delivery_register, update_rune_md, check_sync, handle_hook |
| audit_ledger | 212 | 2/8 | _compute_hash, get_entries, log_promotion, log_gate_result, log_drift_alert, main |
@@ -191,38 +199,43 @@ _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 |
| construction_mode | 395 | 1/5 | _tokens, _contains_any, to_dict, from_dict |
| 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 |
| 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 |
| subconscious_pass | 187 | 1/5 | main, _extract_topic, save_reflection, get_recent_findings |
| construction_activation | 237 | 1/4 | _scaffold_for, _auto_structural_awareness, dispatch_from_decision |
| construction_projection | 225 | 1/4 | _contract_entity, validate, write_mini_doc |
| construction_mode | 412 | 2/5 | _contains_any, to_dict, from_dict |
| construction_projection | 254 | 1/4 | _contract_entity, validate, write_mini_doc |
| dependency_tracker | 202 | 2/5 | check_propagation, get_full_map, main |
| event_envelope | 303 | 5/8 | test_envelope, to_canonical_dict, to_dict |
| response_adapter | 222 | 4/7 | parse_plain_response, test_adapter, to_dict |
| salience_gate | 243 | 1/4 | test_salience, handle_hook, to_dict |
| graph_projection | 134 | 3/6 | _model_to_dicts, write_all, _now |
| memory_selector | 590 | 11/14 | _mse_core, add_source, _source_store |
| subsystem_health | 163 | 3/6 | set_auto_filer, record_failure, configure_logging |
| warm_neuron_store | 122 | 3/6 | _db, neuron_slugs, handle_hook |
| cognitive_budgeter | 208 | 1/3 | test_budgeter, to_dict |
| cognitive_budgeter | 221 | 1/3 | test_budgeter, to_dict |
| event_bus | 244 | 5/7 | _store_path, handle_hook |
| graph_projection | 108 | 3/5 | write_all, _now |
| response_adapter | 222 | 5/7 | test_adapter, to_dict |
| rune_profiler | 165 | 1/3 | profile_model_call, main |
| stage_guards | 281 | 7/9 | to_dict, chk |
| standardized_providers | 281 | 3/5 | list_providers, connect_all_local |
| structural_migration | 77 | 2/4 | apply_migration, consent_brief |
| voice_injector | 138 | 3/5 | test_injection, to_dict |
| construction_depth | 138 | 1/2 | to_dict |
| construction_depth | 155 | 1/2 | to_dict |
| recall_router | 165 | 2/3 | handle_hook |
## FULL — ported, parity-gated
| Module | Lines | Fns | Side |
|---|---|---|---|
| event_wait | 518 | 14/14 | GO |
| tool_protocol | 461 | 22/27 | GO |
| goal | 453 | 20/20 | GO |
| recall_uplift | 141 | 1/1 | RUST |
| rune_spinner | 130 | 4/5 | RUST |
| wait_guard | 116 | 1/1 | RUST |
| failure_recovery | 108 | 1/1 | RUST |
| programme_gates | 85 | 4/5 | RUST |
| impact_preview | 69 | 2/2 | RUST |
| impact_preview | 78 | 2/2 | RUST |
| provider_context | 48 | 1/1 | GO |
| route_risk_flags | 37 | 1/1 | RUST |
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "rune"
version = "0.196.0"
version = "0.199.0"
description = "Rune — a stateful coding assistant with persistent memory, orchestration, and TUI"
requires-python = ">=3.10"
+1 -1
View File
@@ -135,7 +135,7 @@ def module_to_keys(module_stem: str) -> list[str]:
"construction_interface": ["cognition-cognition", "cognition"],
"construction_decision": ["cognition-cognition", "cognition"],
"rune_spine": ["cognition-spine", "cognition"],
"spine": ["cognition-spine", "cognition"],
"spine": ["cognition-spine", "cognition", "rune_spine"],
"memory_storage": ["cognition-storage", "cognition"],
"memory_retrieval": ["cognition-retrieval", "cognition"],
"scaffold_core": ["cognition-scaffold", "cognition"],
+255
View File
@@ -0,0 +1,255 @@
#!/usr/bin/env python3
"""gen_curator_wave_truth.py — dichotomy wave #2479 (curator FULL-Go batch).
Freezes truth for the deterministic curator surface not yet covered by the
earlier batch truth files (batch22/lexicon/batch2-4): _is_topic_fact,
semantic_candidate_key (embed seam stubbed), graph_write decision core
(subprocess seam stubbed), author_skill doc-building (SKILLS_DIR → tmp),
apply_op surgery (store seams stubbed), handle_hook, skill_path.
Output: /tmp/truth.json — replayed verbatim by the Go battery
(src/go/curator-core/truth_curator_wave_test.go). Never hand-transcribe:
every `want` comes from the live oracle run below.
"""
import copy
import json
import os
import shutil
import subprocess
import sys
import tempfile
REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, os.path.join(REPO, "src", "rune"))
import curator # noqa: E402 (the live oracle)
cases = []
def add(fn, name, input_, want):
cases.append({"fn": fn, "name": name, "input": input_, "want": copy.deepcopy(want)})
# ---------------------------------------------------------------- is_topic_fact
add("is_topic_fact", "short fact", {"fact": "likes coffee"}, curator._is_topic_fact("likes coffee"))
add("is_topic_fact", "empty fact", {"fact": ""}, curator._is_topic_fact(""))
add("is_topic_fact", "no domain word", {"fact": "the user prefers coconut milk"},
curator._is_topic_fact("the user prefers coconut milk"))
add("is_topic_fact", "workflow", {"fact": "our workflow for shipping releases"},
curator._is_topic_fact("our workflow for shipping releases"))
add("is_topic_fact", "how to", {"fact": "how to build a birdhouse with hand tools"},
curator._is_topic_fact("how to build a birdhouse with hand tools"))
add("is_topic_fact", "pattern", {"fact": "the pattern of repeat failures in builds"},
curator._is_topic_fact("the pattern of repeat failures in builds"))
add("is_topic_fact", "project", {"fact": "the project migration is blocked on review"},
curator._is_topic_fact("the project migration is blocked on review"))
add("is_topic_fact", "strategy", {"fact": "the strategy for cache warming"},
curator._is_topic_fact("the strategy for cache warming"))
add("is_topic_fact", "architecture", {"fact": "architecture of the message queue"},
curator._is_topic_fact("architecture of the message queue"))
# ------------------------------------------------------ semantic_candidate_key
# Embed seam stubbed to a deterministic vector map (no ollama in the loop).
VEC = {
"fact alpha about deploy runners": [1.0, 0.0, 0.0],
"fact beta about gitea runners": [0.0, 1.0, 0.0], # orthogonal
"curator fact about nothing": [0.0, 0.0, 1.0],
}
curator._embed = lambda texts: {t: VEC[t] for t in texts if t in VEC}
def _ck(text):
return curator.candidate_key(text)
cands = {
_ck("fact alpha about deploy runners"): {"text": "fact alpha about deploy runners", "count": 1},
_ck("fact beta about gitea runners"): {"text": "fact beta about gitea runners", "count": 1},
}
add("semantic_candidate_key", "exact normalized hit",
{"fact": "fact alpha about deploy runners"},
curator.semantic_candidate_key("fact alpha about deploy runners", dict(cands)))
add("semantic_candidate_key", "rephrased semantic match",
{"fact": "fact alpha about deploy runners "},
curator.semantic_candidate_key("fact alpha about deploy runners ", dict(cands)))
add("semantic_candidate_key", "orthogonal vectors no match",
{"fact": "curator fact about nothing"},
curator.semantic_candidate_key("curator fact about nothing", dict(cands)))
add("semantic_candidate_key", "no embedding available",
{"fact": "unseen fact text entirely"},
curator.semantic_candidate_key("unseen fact text entirely", dict(cands)))
# ------------------------------------------------------------------ graph_write
# Subprocess seam stubbed: capture the argv the oracle hands to graph_memory.py
# (argv[3:6] = subj/pred/obj after the "add" verb) and return the exact
# stdout contract graph_write greps for ('"added": True').
_real_run = subprocess.run
captured = []
def fake_run(argv, **kw):
captured.append(list(argv))
return subprocess.CompletedProcess(argv, 0, stdout='{"added": True}\n', stderr="")
subprocess.run = fake_run
try:
for name, fact, strength in [
("strength below 3 no write", "the user prefers coconut milk", 2),
("simple prefer triple", "the user prefers coconut milk", 3),
("object trimmed at preposition", "the user teaches guitar at the studio", 3),
("unmapped verb no triple", "the user likes coffee", 3),
("short subject rejected", "ai builds stuff", 3),
("works at canonical verb", "the user works at acme", 3),
("object truncated at punctuation", "the user manages the example project. Also more", 3),
]:
captured.clear()
ok = curator.graph_write(fact, "ev", "src", strength)
triple = captured[0][3:6] if captured else None
add("graph_write", name, {"fact": fact, "strength": strength}, {"ok": ok, "triple": triple})
finally:
subprocess.run = _real_run
# ------------------------------------------------------------------ author_skill
tmp = tempfile.mkdtemp(prefix="curator-wave-")
try:
curator.SKILLS_DIR = tmp
jl = []
written = curator.author_skill(
{"name": "Deploy Checklist", "steps": ["pull latest", "run tests", "tag release"],
"description": "", "strength": 3, "target": "skill"}, jl)
slug_body = open(os.path.join(tmp, "deploy-checklist", "SKILL.md")).read()
add("author_skill", "fresh procedure write", {"name": "Deploy Checklist", "strength": 3},
{"written": written, "body": slug_body, "journal": jl})
jl = []
written = curator.author_skill({"name": "Deploy Checklist!!", "steps": ["a"], "strength": 1}, jl)
rel = os.path.relpath(curator.skill_path("Deploy Checklist!!"), tmp)
add("author_skill", "slug sanitization", {"name": "Deploy Checklist!!"},
{"written": written, "rel": rel, "journal": jl})
jl = []
written = curator.author_skill({"name": "No Steps", "steps": [], "strength": 2}, jl)
add("author_skill", "missing steps refused", {"name": "No Steps"}, {"written": written, "journal": jl})
jl = []
written = curator.author_skill({"name": "", "steps": ["x"], "strength": 2}, jl)
add("author_skill", "missing name refused", {"name": ""}, {"written": written, "journal": jl})
# existing file without an Evidence section -> Evidence block appended (timestamped)
os.makedirs(os.path.join(tmp, "recurring-procedure"), exist_ok=True)
with open(os.path.join(tmp, "recurring-procedure", "SKILL.md"), "w") as f:
f.write("---\nname: recurring-procedure\ndescription: d\n---\n\n# Recurring Procedure\n")
jl = []
written = curator.author_skill({"name": "Recurring Procedure", "steps": ["one"], "strength": 2}, jl)
body = open(os.path.join(tmp, "recurring-procedure", "SKILL.md")).read()
add("author_skill", "existing skill gains evidence section",
{"name": "Recurring Procedure", "strength": 2},
{"written": written, "has_evidence": "## Evidence" in body, "journal": jl})
add("skill_path", "path joins slug dirs", {"name": "Deploy X"},
os.path.relpath(curator.skill_path("Deploy X"), tmp))
add("handle_hook", "default handler", {"event": "on_memory_add"},
curator.handle_hook("on_memory_add", {"k": "v"}))
# -------------------------------------------------------------------- apply_op
# Store seams stubbed: read_block/write_block/store_has/touch_entry/
# graph_write. Every case resets the recorders so each `want` holds only
# its own delta.
body_by_label = {}
store_has = {}
state = {}
def reset():
state["writes"] = []
state["touches"] = []
state["graph"] = []
state["rel"] = None
def fake_read_block(label):
return {"label": label}, body_by_label.get(label, "")
def fake_write_block(label, fm, body):
state["writes"].append([label, body])
return curator.block_path(label)
curator.read_block = fake_read_block
curator.write_block = fake_write_block
curator.store_has = lambda text, topic=None: store_has.get((text.strip(), topic), False)
curator.touch_entry = lambda idx, label, text: state["touches"].append([label, text])
curator.graph_write = lambda fact, ev="", src="curator", strength=3: state["graph"].append([fact, strength]) or True
def run_apply(name, op, want_extra=None):
reset()
idx, jl = {}, []
# DELETE exercises the index-meta pop: seed the meta the oracle would
# have created when the entry was added.
if op["op"] == "DELETE" and op.get("before") == "ship on friday":
idx.setdefault(op["target"], {})[curator.entry_key(op["before"])] = {"importance": 0.7}
got = curator.apply_op(op, idx, jl)
want = {"applied": got, "journal": jl, "writes": state["writes"]}
if state["touches"]:
want["touches"] = state["touches"]
if state["graph"]:
want["graph"] = state["graph"]
if idx:
want["index"] = idx
if want_extra:
want.update(want_extra)
add("apply_op", name, {"op": op}, want)
run_apply("propose journaled only",
{"op": "PROPOSE", "target": "human", "after": "weak fact", "evidence": "e",
"source": "s", "strength": 1})
body_by_label["human"] = "- the user prefers tea\n- agent ships on friday"
run_apply("add duplicate in body",
{"op": "ADD", "target": "human", "after": "prefers tea", "evidence": "e",
"source": "s", "strength": 3})
store_has[("the user bakes bread", "human")] = True
run_apply("add duplicate in store",
{"op": "ADD", "target": "human", "after": "the user bakes bread", "evidence": "e",
"source": "s", "strength": 3})
store_has.clear()
body_by_label["human"] = "- the user prefers tea\n- agent ships on friday"
run_apply("add fresh appends and graphs",
{"op": "ADD", "target": "human", "after": "the user teaches guitar", "evidence": "ev",
"source": "s", "strength": 3})
run_apply("constitution never auto-applied",
{"op": "ADD", "target": "constitution", "after": "rule", "evidence": "e",
"source": "s", "strength": 3})
body_by_label["project"] = "- deploy at noon\n- ship on friday"
run_apply("update supersede transition",
{"op": "UPDATE", "target": "project", "before": "deploy at noon", "after": "deploy at dawn",
"evidence": "e", "source": "s", "strength": 3})
run_apply("update without before skipped",
{"op": "UPDATE", "target": "project", "after": "deploy at dawn", "evidence": "e",
"source": "s", "strength": 3})
run_apply("delete removes line and meta",
{"op": "DELETE", "target": "project", "before": "ship on friday", "evidence": "e",
"source": "s", "strength": 3})
run_apply("delete without match skipped",
{"op": "DELETE", "target": "project", "before": "no such line", "evidence": "e",
"source": "s", "strength": 3})
run_apply("unknown label journaled",
{"op": "ADD", "target": "archive", "after": "cold fact", "evidence": "e",
"source": "s", "strength": 3})
run_apply("skill with steps authored",
{"op": "ADD", "target": "skill", "after": "", "name": "Skill Thing", "steps": ["do it"],
"evidence": "e", "source": "s", "strength": 2})
finally:
subprocess.run = _real_run
shutil.rmtree(tmp, ignore_errors=True)
out = os.environ.get("TRUTH_OUT", "/tmp/truth.json")
with open(out, "w") as f:
json.dump(cases, f, indent=1, sort_keys=True)
print(f"frozen {len(cases)} cases -> {out}")
+673
View File
@@ -0,0 +1,673 @@
[
{
"fn": "is_topic_fact",
"input": {
"fact": "likes coffee"
},
"name": "short fact",
"want": false
},
{
"fn": "is_topic_fact",
"input": {
"fact": ""
},
"name": "empty fact",
"want": false
},
{
"fn": "is_topic_fact",
"input": {
"fact": "the user prefers coconut milk"
},
"name": "no domain word",
"want": false
},
{
"fn": "is_topic_fact",
"input": {
"fact": "our workflow for shipping releases"
},
"name": "workflow",
"want": true
},
{
"fn": "is_topic_fact",
"input": {
"fact": "how to build a birdhouse with hand tools"
},
"name": "how to",
"want": true
},
{
"fn": "is_topic_fact",
"input": {
"fact": "the pattern of repeat failures in builds"
},
"name": "pattern",
"want": true
},
{
"fn": "is_topic_fact",
"input": {
"fact": "the project migration is blocked on review"
},
"name": "project",
"want": true
},
{
"fn": "is_topic_fact",
"input": {
"fact": "the strategy for cache warming"
},
"name": "strategy",
"want": true
},
{
"fn": "is_topic_fact",
"input": {
"fact": "architecture of the message queue"
},
"name": "architecture",
"want": true
},
{
"fn": "semantic_candidate_key",
"input": {
"fact": "fact alpha about deploy runners"
},
"name": "exact normalized hit",
"want": "3a02c6d02a41e0eb"
},
{
"fn": "semantic_candidate_key",
"input": {
"fact": "fact alpha about deploy runners "
},
"name": "rephrased semantic match",
"want": "3a02c6d02a41e0eb"
},
{
"fn": "semantic_candidate_key",
"input": {
"fact": "curator fact about nothing"
},
"name": "orthogonal vectors no match",
"want": null
},
{
"fn": "semantic_candidate_key",
"input": {
"fact": "unseen fact text entirely"
},
"name": "no embedding available",
"want": null
},
{
"fn": "graph_write",
"input": {
"fact": "the user prefers coconut milk",
"strength": 2
},
"name": "strength below 3 no write",
"want": {
"ok": false,
"triple": null
}
},
{
"fn": "graph_write",
"input": {
"fact": "the user prefers coconut milk",
"strength": 3
},
"name": "simple prefer triple",
"want": {
"ok": true,
"triple": [
"the user",
"prefers",
"coconut milk"
]
}
},
{
"fn": "graph_write",
"input": {
"fact": "the user teaches guitar at the studio",
"strength": 3
},
"name": "object trimmed at preposition",
"want": {
"ok": true,
"triple": [
"the user",
"teaches",
"guitar"
]
}
},
{
"fn": "graph_write",
"input": {
"fact": "the user likes coffee",
"strength": 3
},
"name": "unmapped verb no triple",
"want": {
"ok": false,
"triple": null
}
},
{
"fn": "graph_write",
"input": {
"fact": "ai builds stuff",
"strength": 3
},
"name": "short subject rejected",
"want": {
"ok": false,
"triple": null
}
},
{
"fn": "graph_write",
"input": {
"fact": "the user works at acme",
"strength": 3
},
"name": "works at canonical verb",
"want": {
"ok": true,
"triple": [
"the user",
"works_at",
"acme"
]
}
},
{
"fn": "graph_write",
"input": {
"fact": "the user manages the example project. Also more",
"strength": 3
},
"name": "object truncated at punctuation",
"want": {
"ok": true,
"triple": [
"the user",
"manages",
"the example project"
]
}
},
{
"fn": "author_skill",
"input": {
"name": "Deploy Checklist",
"strength": 3
},
"name": "fresh procedure write",
"want": {
"body": "---\nname: deploy-checklist\ndescription: Procedure distilled by the curator: Deploy Checklist.\n---\n\n# Deploy Checklist (procedural memory)\n\n> Authored by the sleep-time curator after observing this procedure 3 times across sessions.\n\n## Procedure\n\n1. pull latest\n2. run tests\n3. tag release\n",
"journal": [
{
"after": "deploy-checklist (3 steps)",
"evidence": "procedure seen 3 times",
"op": "SKILL",
"reason": "recurring procedure distilled to procedural memory",
"source": "curator/EXPEL",
"target": "skill"
}
],
"written": true
}
},
{
"fn": "author_skill",
"input": {
"name": "Deploy Checklist!!"
},
"name": "slug sanitization",
"want": {
"journal": [
{
"after": "deploy-checklist (1 steps)",
"evidence": "procedure seen 1 times",
"op": "SKILL",
"reason": "recurring procedure distilled to procedural memory",
"source": "curator/EXPEL",
"target": "skill"
}
],
"rel": "deploy-checklist/SKILL.md",
"written": true
}
},
{
"fn": "author_skill",
"input": {
"name": "No Steps"
},
"name": "missing steps refused",
"want": {
"journal": [],
"written": false
}
},
{
"fn": "author_skill",
"input": {
"name": ""
},
"name": "missing name refused",
"want": {
"journal": [],
"written": false
}
},
{
"fn": "author_skill",
"input": {
"name": "Recurring Procedure",
"strength": 2
},
"name": "existing skill gains evidence section",
"want": {
"has_evidence": true,
"journal": [
{
"after": "recurring-procedure (1 steps)",
"evidence": "procedure seen 2 times",
"op": "SKILL",
"reason": "recurring procedure distilled to procedural memory",
"source": "curator/EXPEL",
"target": "skill"
}
],
"written": true
}
},
{
"fn": "skill_path",
"input": {
"name": "Deploy X"
},
"name": "path joins slug dirs",
"want": "deploy-x/SKILL.md"
},
{
"fn": "handle_hook",
"input": {
"event": "on_memory_add"
},
"name": "default handler",
"want": {
"event": "on_memory_add",
"handler": "default",
"status": "ok"
}
},
{
"fn": "apply_op",
"input": {
"op": {
"after": "weak fact",
"evidence": "e",
"op": "PROPOSE",
"source": "s",
"strength": 1,
"target": "human"
}
},
"name": "propose journaled only",
"want": {
"applied": false,
"journal": [
{
"after": "weak fact",
"evidence": "e",
"op": "PROPOSE",
"source": "s",
"strength": 1,
"target": "human"
}
],
"writes": []
}
},
{
"fn": "apply_op",
"input": {
"op": {
"after": "prefers tea",
"evidence": "e",
"op": "ADD",
"source": "s",
"strength": 3,
"target": "human"
}
},
"name": "add duplicate in body",
"want": {
"applied": false,
"journal": [
{
"after": "prefers tea",
"evidence": "e",
"op": "SKIP-DUPLICATE",
"reason": "already present: - the user prefers tea",
"source": "s",
"strength": 3,
"target": "human"
}
],
"touches": [
[
"human",
"- the user prefers tea"
]
],
"writes": []
}
},
{
"fn": "apply_op",
"input": {
"op": {
"after": "the user bakes bread",
"evidence": "e",
"op": "ADD",
"source": "s",
"strength": 3,
"target": "human"
}
},
"name": "add duplicate in store",
"want": {
"applied": false,
"journal": [
{
"after": "the user bakes bread",
"evidence": "e",
"op": "SKIP-DUPLICATE",
"reason": "already present in store (not just the always-on block)",
"source": "s",
"strength": 3,
"target": "human"
}
],
"writes": []
}
},
{
"fn": "apply_op",
"input": {
"op": {
"after": "the user teaches guitar",
"evidence": "ev",
"op": "ADD",
"source": "s",
"strength": 3,
"target": "human"
}
},
"name": "add fresh appends and graphs",
"want": {
"applied": true,
"graph": [
[
"the user teaches guitar",
3
]
],
"journal": [
{
"after": "the user teaches guitar",
"evidence": "ev",
"op": "ADD",
"source": "s",
"strength": 3,
"target": "human"
}
],
"touches": [
[
"human",
"the user teaches guitar"
]
],
"writes": [
[
"human",
"- the user prefers tea\n- agent ships on friday\n- the user teaches guitar\n"
]
]
}
},
{
"fn": "apply_op",
"input": {
"op": {
"after": "rule",
"evidence": "e",
"op": "ADD",
"source": "s",
"strength": 3,
"target": "constitution"
}
},
"name": "constitution never auto-applied",
"want": {
"applied": false,
"journal": [
{
"after": "rule",
"evidence": "e",
"op": "SKIP-CONSTITUTION",
"reason": "block is read_only; explicit user directive required",
"source": "s",
"strength": 3,
"target": "constitution"
}
],
"writes": []
}
},
{
"fn": "apply_op",
"input": {
"op": {
"after": "deploy at dawn",
"before": "deploy at noon",
"evidence": "e",
"op": "UPDATE",
"source": "s",
"strength": 3,
"target": "project"
}
},
"name": "update supersede transition",
"want": {
"applied": true,
"journal": [
{
"after": "deploy at dawn",
"before": "deploy at noon",
"evidence": "e",
"op": "UPDATE",
"source": "s",
"strength": 3,
"target": "project"
}
],
"touches": [
[
"project",
"deploy at dawn"
]
],
"writes": [
[
"project",
"- deploy at noon \u2192 (superseded deploy at dawn)\n- ship on friday"
]
]
}
},
{
"fn": "apply_op",
"input": {
"op": {
"after": "deploy at dawn",
"evidence": "e",
"op": "UPDATE",
"source": "s",
"strength": 3,
"target": "project"
}
},
"name": "update without before skipped",
"want": {
"applied": false,
"journal": [
{
"after": "deploy at dawn",
"evidence": "e",
"op": "SKIP-UPDATE",
"reason": "no exact before-match in block",
"source": "s",
"strength": 3,
"target": "project"
}
],
"writes": []
}
},
{
"fn": "apply_op",
"input": {
"op": {
"before": "ship on friday",
"evidence": "e",
"op": "DELETE",
"source": "s",
"strength": 3,
"target": "project"
}
},
"name": "delete removes line and meta",
"want": {
"applied": true,
"index": {
"project": {}
},
"journal": [
{
"before": "ship on friday",
"evidence": "e",
"op": "DELETE",
"source": "s",
"strength": 3,
"target": "project"
}
],
"writes": [
[
"project",
"- deploy at noon"
]
]
}
},
{
"fn": "apply_op",
"input": {
"op": {
"before": "no such line",
"evidence": "e",
"op": "DELETE",
"source": "s",
"strength": 3,
"target": "project"
}
},
"name": "delete without match skipped",
"want": {
"applied": false,
"journal": [
{
"before": "no such line",
"evidence": "e",
"op": "SKIP-DELETE",
"reason": "no exact match in block",
"source": "s",
"strength": 3,
"target": "project"
}
],
"writes": []
}
},
{
"fn": "apply_op",
"input": {
"op": {
"after": "cold fact",
"evidence": "e",
"op": "ADD",
"source": "s",
"strength": 3,
"target": "archive"
}
},
"name": "unknown label journaled",
"want": {
"applied": false,
"journal": [
{
"after": "cold fact",
"evidence": "e",
"op": "ADD",
"source": "s",
"target": "archive"
}
],
"writes": []
}
},
{
"fn": "apply_op",
"input": {
"op": {
"after": "",
"evidence": "e",
"name": "Skill Thing",
"op": "ADD",
"source": "s",
"steps": [
"do it"
],
"strength": 2,
"target": "skill"
}
},
"name": "skill with steps authored",
"want": {
"applied": true,
"journal": [
{
"after": "skill-thing (1 steps)",
"evidence": "procedure seen 2 times",
"op": "SKILL",
"reason": "recurring procedure distilled to procedural memory",
"source": "curator/EXPEL",
"target": "skill"
}
],
"writes": []
}
}
]
@@ -0,0 +1,325 @@
package main
// truth_curator_wave_test.go — dichotomy wave #2479 parity battery: replays
// /tmp/truth.json (frozen from the live Python oracle by
// scripts/gen_curator_wave_truth.py, 2026-10-11). The file is embedded
// verbatim at test-compile time via truth_curator_wave.json (copied from
// /tmp/truth.json — never hand-transcribe).
import (
"encoding/json"
"os"
"reflect"
"strings"
"testing"
)
func waveTruthCases(t *testing.T) []struct {
Fn string `json:"fn"`
Name string `json:"name"`
Input map[string]interface{} `json:"input"`
Want interface{} `json:"want"`
} {
t.Helper()
path := os.Getenv("TRUTH_PATH")
if path == "" {
path = "truth_curator_wave.json"
if _, err := os.Stat(path); err != nil {
path = "/tmp/truth.json"
}
}
b, err := os.ReadFile(path)
if err != nil {
t.Fatalf("truth file missing: %v", err)
}
var cases []struct {
Fn string `json:"fn"`
Name string `json:"name"`
Input map[string]interface{} `json:"input"`
Want interface{} `json:"want"`
}
if err := json.Unmarshal(b, &cases); err != nil {
t.Fatalf("unparseable truth: %v", err)
}
return cases
}
func normJSON(v interface{}) interface{} {
b, _ := json.Marshal(v)
var out interface{}
_ = json.Unmarshal(b, &out)
return out
}
func TestCuratorWaveParity(t *testing.T) {
cases := waveTruthCases(t)
if len(cases) == 0 {
t.Fatal("no truth cases")
}
fails := 0
for _, c := range cases {
switch c.Fn {
case "is_topic_fact":
fact, _ := c.Input["fact"].(string)
got := IsTopicFact(fant(fact))
if got != c.Want.(bool) {
fails++
t.Errorf("%s: IsTopicFact=%v want %v", c.Name, got, c.Want)
}
case "semantic_candidate_key":
got := waveSemanticCase(t, c.Input)
want, _ := c.Want.(string)
if got != want {
fails++
t.Errorf("%s: SemanticCandidateKey=%q want %q", c.Name, got, want)
}
case "graph_write":
got := waveGraphCase(t, c.Input)
if !reflect.DeepEqual(normJSON(got), normJSON(c.Want)) {
fails++
t.Errorf("%s: graph_write got %v want %v", c.Name, got, c.Want)
}
case "author_skill", "skill_path", "handle_hook", "apply_op":
got := waveSkillAndApplyCase(t, c.Fn, c.Name, c.Input)
if !reflect.DeepEqual(normJSON(got), normJSON(c.Want)) {
fails++
gb, _ := json.Marshal(got)
wb, _ := json.Marshal(c.Want)
t.Errorf("%s: got %s\nwant %s", c.Name, gb, wb)
}
default:
t.Fatalf("unknown truth fn: %s", c.Fn)
}
}
if fails > 0 {
t.Fatalf("%d/%d wave parity cases FAILED", fails, len(cases))
}
t.Logf("wave parity OK: %d cases", len(cases))
}
func fant(s string) string { return s } // reader-aid alias; no-op
// waveSemanticCase replays semantic_candidate_key with a deterministic embed
// seam built from the same vectors the generator used (the embed IO seam is
// adapter-owned; the decision math is what we parity).
func waveSemanticCase(t *testing.T, input map[string]interface{}) string {
t.Helper()
fact, _ := input["fact"].(string)
cands := Candidates{
CandidateKey("fact alpha about deploy runners"): {"text": "fact alpha about deploy runners", "count": 1},
CandidateKey("fact beta about gitea runners"): {"text": "fact beta about gitea runners", "count": 1},
}
keys := []string{CandidateKey("fact alpha about deploy runners"), CandidateKey("fact beta about gitea runners")}
embed := func(texts []string) map[string][]float64 {
out := map[string][]float64{}
for _, tx := range texts {
switch strings.TrimSpace(tx) {
case "fact alpha about deploy runners":
out[tx] = []float64{1, 0, 0}
case "fact beta about gitea runners":
out[tx] = []float64{0, 1, 0}
case "curator fact about nothing":
out[tx] = []float64{0, 0, 1}
}
}
return out
}
return SemanticCandidateKey(fact, keys, cands, embed)
}
// waveGraphCase replays graph_write with a captured subprocess seam.
func waveGraphCase(t *testing.T, input map[string]interface{}) map[string]interface{} {
t.Helper()
fact, _ := input["fact"].(string)
strength := 3
if f, ok := input["strength"].(float64); ok {
strength = int(f)
}
var triple interface{}
saved := seamGraphWrite
seamGraphWrite = func(subj, pred, obj, ev, src string, st int) bool {
triple = []string{subj, pred, obj}
return true
}
defer func() { seamGraphWrite = saved }()
ok := GraphWrite(fact, "ev", "src", strength)
return map[string]interface{}{"ok": ok, "triple": triple}
}
// waveSkillAndApplyCase replays author_skill / skill_path / handle_hook /
// apply_op with file+store seams pointed at a temp dir.
func waveSkillAndApplyCase(t *testing.T, fn, name string, input map[string]interface{}) interface{} {
t.Helper()
dir := t.TempDir()
savedSkills := seamSkillsDir
savedRead, savedWrite, savedHas, savedTouch, savedWarm := seamReadBlock, seamWriteBlock, seamStoreHas, seamTouchEntry, seamApplyWarm
savedSR, savedSW := seamSkillRead, seamSkillWrite
defer func() {
seamSkillsDir = savedSkills
seamReadBlock, seamWriteBlock, seamStoreHas, seamTouchEntry, seamApplyWarm = savedRead, savedWrite, savedHas, savedTouch, savedWarm
seamSkillRead, seamSkillWrite = savedSR, savedSW
}()
seamSkillsDir = dir
seamSkillRead = func(p string) string {
b, err := os.ReadFile(p)
if err != nil {
return ""
}
return string(b)
}
seamSkillWrite = func(p, body string) {
_ = os.MkdirAll(dirOf(p), 0o755)
_ = os.WriteFile(p, []byte(body), 0o644)
}
switch fn {
case "skill_path":
nm, _ := input["name"].(string)
return rel(t, dir, SkillPath(dir, nm))
case "handle_hook":
ev, _ := input["event"].(string)
return HandleHook(ev, nil)
case "author_skill":
return waveAuthorSkill(dir, input)
case "apply_op":
return waveApplyOp(dir, input)
}
t.Fatalf("unhandled fn %s", fn)
return nil
}
func waveAuthorSkill(dir string, input map[string]interface{}) interface{} {
// The generator ran each author_skill case in sequence against the same
// tmp dir; here each case is isolated, so the "existing skill" case
// pre-seeds its file exactly as the generator did before calling.
nm, _ := input["name"].(string)
if nm == "Recurring Procedure" {
_ = os.MkdirAll(dir+"/recurring-procedure", 0o755)
_ = os.WriteFile(dir+"/recurring-procedure/SKILL.md",
[]byte("---\nname: recurring-procedure\ndescription: d\n---\n\n# Recurring Procedure\n"), 0o644)
}
steps := map[string][]string{
"Deploy Checklist": {"pull latest", "run tests", "tag release"},
"Deploy Checklist!!": {"a"},
"No Steps": {},
"": {"x"},
"Recurring Procedure": {"one"},
}
op := map[string]interface{}{"name": nm, "strength": intOf(input["strength"], 1)}
if s, ok := steps[nm]; ok && len(s) > 0 {
op["steps"] = toAny(s)
}
jl := []map[string]interface{}{}
written := AuthorSkill(op, dir, &jl)
out := map[string]interface{}{"written": written, "journal": jl}
if nm == "Deploy Checklist" {
body, _ := os.ReadFile(dir + "/deploy-checklist/SKILL.md")
out["body"] = string(body)
}
if nm == "Deploy Checklist!!" {
out["rel"] = "deploy-checklist/SKILL.md"
}
if nm == "Recurring Procedure" {
body, _ := os.ReadFile(dir + "/recurring-procedure/SKILL.md")
out["has_evidence"] = strings.Contains(string(body), "## Evidence")
}
return out
}
// waveApplyOp replays one apply_op case with the same stub bodies/store state
// the generator used (per-case isolation).
func waveApplyOp(dir string, input map[string]interface{}) interface{} {
opRaw, _ := input["op"].(map[string]interface{})
op := copyMap(opRaw)
bodyByLabel := map[string]string{}
storeHas := map[string]bool{}
// Reproduce the generator's per-case preconditions from the op shape.
if strOf(op["op"]) == "ADD" && strOf(op["target"]) == "human" {
after := strOf(op["after"])
switch after {
case "prefers tea":
bodyByLabel["human"] = "- the user prefers tea\n- agent ships on friday"
case "the user bakes bread":
storeHas[("the user bakes bread")+"/human"] = true
case "the user teaches guitar":
bodyByLabel["human"] = "- the user prefers tea\n- agent ships on friday"
}
}
if strOf(op["target"]) == "project" {
bodyByLabel["project"] = "- deploy at noon\n- ship on friday"
}
if strOf(op["target"]) == "skill" {
op["steps"] = []interface{}{"do it"}
}
var writes = [][]interface{}{}
var touches = [][]interface{}{}
var graph = [][]interface{}{}
seamReadBlock = func(label string) (map[string]interface{}, string) {
return map[string]interface{}{"label": label}, bodyByLabel[label]
}
seamWriteBlock = func(label string, fm map[string]interface{}, body string) string {
writes = append(writes, []interface{}{label, body})
return "store://topic/" + label
}
seamStoreHas = func(text, topic string) bool {
return storeHas[text+"/"+topic]
}
seamTouchEntry = func(idx EntryIndex, label, text string) {
touches = append(touches, []interface{}{label, text})
}
savedGW := seamGraphWrite
seamGraphWrite = func(subj, pred, obj, ev, src string, st int) bool {
graph = append(graph, []interface{}{subj + " " + pred + " " + obj, st})
return true
}
defer func() { seamGraphWrite = savedGW }()
idx := EntryIndex{}
if strOf(op["op"]) == "DELETE" && strOf(op["target"]) == "project" {
old := strOf(op["before"])
if old == "ship on friday" {
idx["project"] = map[string]map[string]interface{}{
EntryKey(old): {"importance": 0.7},
}
}
}
jl := []map[string]interface{}{}
applied := ApplyOp(op, idx, &jl)
out := map[string]interface{}{"applied": applied, "journal": jl, "writes": writes}
if len(writes) > 0 {
out["writes"] = writes
}
if len(touches) > 0 {
out["touches"] = touches
}
if len(graph) > 0 {
out["graph"] = graph
}
if len(idx) > 0 {
out["index"] = idx
}
return out
}
func toAny(ss []string) []interface{} {
out := make([]interface{}, len(ss))
for i, s := range ss {
out[i] = s
}
return out
}
func dirOf(p string) string {
for i := len(p) - 1; i >= 0; i-- {
if p[i] == '/' {
return p[:i]
}
}
return "."
}
func rel(t *testing.T, base, p string) string {
t.Helper()
return strings.TrimPrefix(p, base+"/")
}
+451
View File
@@ -0,0 +1,451 @@
package main
// wave.go — dichotomy wave #2479 (curator FULL-Go tranche): the remaining
// deterministic cores from src/rune/curator.py not covered by the earlier
// batch files. Oracle-first truth: /tmp/truth.json via
// scripts/gen_curator_wave_truth.py, replayed by truth_curator_wave_test.go.
//
// Scaffolding law: IO seams (store DB, skill files, embed service, graph
// subprocess, warm neurons) stay callable from Python/Go adapters — the
// decision math ports 1:1. apply_warm / _aaak / coherence_check's embed IO
// / read_block / write_block / store_has / run_plan remain Python adapters
// (reactive surface) — excluded from this port by design.
import (
"regexp"
"strings"
"unicode/utf8"
)
// ---------------------------------------------------------- _is_topic_fact ----
var topicDomainWords = []string{
"about", "workflow", "procedure", "system", "method", "how to", "when",
"always", "the way", "pattern", "topic", "approach", "project", "research",
"process", "strategy", "design", "preference", "history", "build",
"architecture",
}
// IsTopicFact — NEW SCHEMA heuristic: is this fact a distinct TOPIC worth a
// warm neuron (vs a one-off detail)? Oracle: curator._is_topic_fact.
func IsTopicFact(fact string) bool {
low := strings.ToLower(fact)
if utf8.RuneCountInString(fact) < 15 {
return false
}
for _, d := range topicDomainWords {
if strings.Contains(low, d) {
return true
}
}
return false
}
// ------------------------------------------------ semantic_candidate_key ------
// EmbedThreshold — cosine cutoff for semantic candidate accumulation
// (curator._EMBED_THRESHOLD).
const EmbedThreshold = 0.62
// EmbedFn — the embed seam (curator._embed is an ollama subprocess in the
// oracle; adapters inject a deterministic or live implementation).
type EmbedFn func(texts []string) map[string][]float64
// seamEmbed is injected by the adapter; nil means "no embedding available"
// (the oracle's _embed failure path returns {}).
var seamEmbed EmbedFn
// SemanticCandidateKey — the existing candidate key whose text is
// semantically equivalent to fact (cosine >= EmbedThreshold), else "".
// keys preserves the Python dict's insertion order (tie-break law: the
// FIRST strictly-greater match wins). Oracle: curator.semantic_candidate_key.
func SemanticCandidateKey(fact string, keys []string, cands Candidates, embed EmbedFn) string {
exact := CandidateKey(fact)
if _, ok := cands[exact]; ok {
return exact
}
if embed == nil {
return ""
}
sample := keys
if len(sample) > 40 {
sample = sample[:40]
}
texts := []string{fact}
for _, k := range sample {
texts = append(texts, strOf(cands[k]["text"]))
}
vecs := embed(texts)
fv, ok := vecs[fact]
if !ok || fv == nil {
return ""
}
bestKey := ""
best := EmbedThreshold
for _, k := range sample {
cv, ok := vecs[strOf(cands[k]["text"])]
if !ok || cv == nil {
continue
}
s := Cosine(fv, cv)
if s > best {
bestKey, best = k, s
}
}
return bestKey
}
func strOf(v interface{}) string {
s, _ := v.(string)
return s
}
// ------------------------------------------------------------ graph_write ----
// graphVerbMap — ORDERED (Python dict iteration: the first verb found in the
// fact wins). Oracle: curator.GRAPH_VERB_MAP.
var graphVerbMap = []struct{ verb, canonical string }{
{"works on", "works_on"},
{"works at", "works_at"},
{"lives in", "lives_in"},
{"located in", "located_in"},
{"teaches", "teaches"},
{"manages", "manages"},
{"prefers", "prefers"},
{"uses", "uses"},
{"built", "builds"},
{"created", "created"},
{"leads", "leads"},
{"develops", "develops"},
{"builds", "builds"},
{"interested in", "interested_in"},
{"based in", "located_in"},
{"part of", "part_of"},
{"works for", "works_at"},
}
var graphObjSplit = regexp.MustCompile(`[.,;]`)
var graphPrepSplit = regexp.MustCompile(`\s+(at|in|on|for|to|from|with|and|through|via) `)
// GraphTriple — the heuristic triple extraction from a durable fact
// (deterministic core of curator.graph_write). ok=false means no triple /
// no write (failed parse, or the subject guard rejects junk).
func GraphTriple(fact string) (subj, pred, obj string, ok bool) {
low := strings.ToLower(fact)
for _, m := range graphVerbMap {
idx := strings.Index(low, m.verb)
if idx > 0 {
subj = strings.TrimSpace(fact[:idx])
subj = strings.TrimSpace(strings.TrimLeft(subj, "-* "))
obj = strings.TrimSpace(fact[idx+len(m.verb):])
obj = strings.TrimSpace(graphObjSplit.Split(obj, -1)[0])
if loc := graphPrepSplit.FindStringIndex(obj); loc != nil {
obj = strings.TrimSpace(obj[:loc[0]])
}
pred = m.canonical
if subj == "" || obj == "" || pred == "" {
return "", "", "", false
}
// Subject must be a proper noun-ish or the user keyword.
if subj != "user" && subj != "i" && subj != "we" && utf8.RuneCountInString(subj) < 3 {
return "", "", "", false
}
return subj, pred, obj, true
}
}
return "", "", "", false
}
// seamGraphWrite — the graph-memory subprocess seam (graph_memory.py add).
// Adapters inject; nil = write unavailable.
var seamGraphWrite func(subj, pred, obj, evidence, source string, strength int) bool
// GraphWrite — the oracle's graph_write: strength gate + triple parse +
// best-effort graph add. Returns whether the graph write succeeded.
func GraphWrite(fact, evidence, source string, strength int) bool {
if strength < 3 {
return false
}
subj, pred, obj, ok := GraphTriple(fact)
if !ok {
return false
}
if seamGraphWrite == nil {
return false
}
if utf8.RuneCountInString(evidence) > 200 {
evidence = string([]rune(evidence)[:200])
}
return seamGraphWrite(subj, pred, obj, evidence, source, strength)
}
// --------------------------------------------------------------- skills ------
// SkillPath — where a skill's SKILL.md lives (Oracle: curator.skill_path).
func SkillPath(skillsDir, name string) string {
return skillsDir + "/" + SanitizeSlug(name) + "/SKILL.md"
}
// AuthorSkillBody — the deterministic SKILL.md doc-build behind
// curator.author_skill. `existing` is the current file content (IO seam —
// the adapter reads/writes the file). Returns the new body and whether an
// Evidence section is appended. nowISO feeds the timestamped Evidence line
// (no-clock law).
func AuthorSkillBody(name, description string, steps []string, repeats int, existing, nowISO string) (string, bool) {
desc := strings.TrimSpace(description)
if desc == "" {
desc = "Procedure distilled by the curator: " + name + "."
}
slug := SanitizeSlug(name)
var b strings.Builder
b.WriteString("---\n")
b.WriteString("name: " + slug + "\n")
b.WriteString("description: " + desc + "\n")
b.WriteString("---\n\n")
b.WriteString("# " + name + " (procedural memory)\n\n")
b.WriteString("> Authored by the sleep-time curator after observing this procedure ")
b.WriteString(itoa(repeats) + " times across sessions.\n\n")
b.WriteString("## Procedure\n\n")
for i, step := range steps {
b.WriteString(itoa(i+1) + ". " + step + "\n")
}
appendEvidence := existing != "" && !strings.Contains(existing, "## Evidence")
if appendEvidence {
b.WriteString("\n## Evidence\n\n- first authored: " + nowISO + "\n")
}
return b.String(), appendEvidence
}
// seamSkillRead / seamSkillWrite — the SKILL.md file seam (adapter-owned).
var seamSkillRead func(path string) string
var seamSkillWrite func(path, body string)
// AuthorSkill — write one skill from a procedure op (curator.author_skill).
// Returns whether the skill was written.
func AuthorSkill(op map[string]interface{}, skillsDir string, journalLines *[]map[string]interface{}) bool {
name := strings.TrimSpace(strOf(op["name"]))
steps, _ := op["steps"].([]interface{})
if name == "" || len(steps) == 0 {
return false
}
desc := strOf(op["description"])
repeats := intOf(op["strength"], 1)
if repeats == 0 {
repeats = 1 // Python `or 1` semantics: 0/missing both yield 1
}
slug := SanitizeSlug(name)
path := SkillPath(skillsDir, name)
existing := ""
if seamSkillRead != nil {
existing = seamSkillRead(path)
}
stepStrs := make([]string, 0, len(steps))
for _, s := range steps {
stepStrs = append(stepStrs, strOf(s))
}
body, _ := AuthorSkillBody(name, desc, stepStrs, repeats, existing, timeNowUTC().Format("2006-01-02T15:04:05+00:00"))
if seamSkillWrite != nil {
seamSkillWrite(path, body)
}
*journalLines = append(*journalLines, map[string]interface{}{
"op": "SKILL",
"target": "skill",
"after": slug + " (" + itoa(len(steps)) + " steps)",
"evidence": "procedure seen " + itoa(repeats) + " times",
"source": "curator/EXPEL",
"reason": "recurring procedure distilled to procedural memory",
})
return true
}
// ------------------------------------------------------------- apply_op ------
// Store seams (adapters inject; the oracle reads/writes the sqlite store).
var (
seamReadBlock func(label string) (map[string]interface{}, string)
seamWriteBlock func(label string, fm map[string]interface{}, body string) string
seamStoreHas func(text, topic string) bool
seamTouchEntry func(idx EntryIndex, label, text string)
seamApplyWarm func(op map[string]interface{}, journalLines *[]map[string]interface{}) bool
)
// EntryIndex is the entry-metadata index shape (label -> key -> meta);
// distinct from Index (the whole saved index incl. the candidates ledger).
type EntryIndex = map[string]map[string]map[string]interface{}
// ApplyOp — the write-path surgery (curator.apply_op). Pure decision +
// index/journal mutation; block/store/warm IO goes through the seams.
func ApplyOp(op map[string]interface{}, idx EntryIndex, journalLines *[]map[string]interface{}) bool {
opName := strOf(op["op"])
label := strOf(op["target"])
if opName == "PROPOSE" || opName == "PROPOSE-CONSTITUTION" {
*journalLines = append(*journalLines, op)
return false
}
switch label {
case "operating", "project", "human", "persona", "warm":
default:
// constitution is never auto-applied here (belt-and-braces).
if opName == "ADD" && label == "constitution" {
skip := copyMap(op)
skip["op"] = "SKIP-CONSTITUTION"
skip["reason"] = "block is read_only; explicit user directive required"
*journalLines = append(*journalLines, skip)
return false
}
// skills ARE authored here from recurring-procedure evidence.
if label == "skill" && op["steps"] != nil {
return AuthorSkill(op, seamSkillsDir, journalLines)
}
*journalLines = append(*journalLines, map[string]interface{}{
"op": opName,
"target": label,
"after": strOf(op["after"]),
"evidence": strOf(op["evidence"]),
"source": strOf(op["source"]),
})
return false
}
if label == "warm" {
if seamApplyWarm == nil {
return false
}
return seamApplyWarm(op, journalLines)
}
fm, body := seamReadBlock(label)
switch opName {
case "ADD":
kind, line := DetectConflict(body, strOf(op["after"]))
if kind == "duplicate" {
if seamTouchEntry != nil {
seamTouchEntry(idx, label, line)
}
skip := copyMap(op)
skip["op"] = "SKIP-DUPLICATE"
skip["reason"] = "already present: " + truncate(line, 80)
*journalLines = append(*journalLines, skip)
return false
}
if seamStoreHas != nil && seamStoreHas(strOf(op["after"]), label) {
skip := copyMap(op)
skip["op"] = "SKIP-DUPLICATE"
skip["reason"] = "already present in store (not just the always-on block)"
*journalLines = append(*journalLines, skip)
return false
}
body = strings.TrimRight(body, " \t\n\r") + "\n- " + strOf(op["after"]) + "\n"
seamWriteBlock(label, fm, body)
if seamTouchEntry != nil {
seamTouchEntry(idx, label, strOf(op["after"]))
}
*journalLines = append(*journalLines, op)
strength := 3
if s, ok := op["strength"]; ok && s != nil && intOf(s, 0) != 0 {
strength = intOf(s, 3)
}
GraphWrite(strOf(op["after"]), strOf(op["evidence"]), strOr(op["source"], "curator"), strength)
return true
case "UPDATE":
old := strOf(op["before"])
if old == "" || !strings.Contains(body, old) {
skip := copyMap(op)
skip["op"] = "SKIP-UPDATE"
skip["reason"] = "no exact before-match in block"
*journalLines = append(*journalLines, skip)
return false
}
new := strOf(op["after"])
if new == "" {
return false
}
// Conflict-aware transition: keep the lineage visible, never erase.
body = strings.ReplaceAll(body, old, old+" → (superseded "+new+")")
seamWriteBlock(label, fm, body)
if seamTouchEntry != nil {
seamTouchEntry(idx, label, new)
}
upd := copyMap(op)
upd["op"] = "UPDATE"
upd["before"] = old
upd["after"] = new
*journalLines = append(*journalLines, upd)
return true
case "DELETE":
old := strOf(op["before"])
if old == "" {
old = strOf(op["after"])
}
if old == "" || !strings.Contains(body, old) {
skip := copyMap(op)
skip["op"] = "SKIP-DELETE"
skip["reason"] = "no exact match in block"
*journalLines = append(*journalLines, skip)
return false
}
var kept []string
for _, l := range strings.Split(body, "\n") {
if Norm(l) != Norm(old) && !strings.Contains(l, old) {
kept = append(kept, l)
}
}
body = strings.Join(kept, "\n")
seamWriteBlock(label, fm, body)
key := EntryKey(old)
if m, ok := idx[label]; ok {
delete(m, key)
}
del := copyMap(op)
del["op"] = "DELETE"
del["before"] = old
*journalLines = append(*journalLines, del)
return true
}
return false
}
// seamSkillsDir — where AuthorSkill writes (adapter-injected).
var seamSkillsDir string
func copyMap(m map[string]interface{}) map[string]interface{} {
out := make(map[string]interface{}, len(m))
for k, v := range m {
out[k] = v
}
return out
}
func strOr(v interface{}, def string) string {
if s, ok := v.(string); ok && s != "" {
return s
}
return def
}
func itoa(n int) string {
if n == 0 {
return "0"
}
neg := n < 0
if neg {
n = -n
}
var buf [20]byte
i := len(buf)
for n > 0 {
i--
buf[i] = byte('0' + n%10)
n /= 10
}
if neg {
i--
buf[i] = '-'
}
return string(buf[i:])
}
// ----------------------------------------------------------- handle_hook -----
// HandleHook — generic hook handler (curator.handle_hook).
func HandleHook(event string, data map[string]interface{}) map[string]interface{} {
return map[string]interface{}{"status": "ok", "event": event, "handler": "default"}
}
+362 -91
View File
@@ -1,7 +1,18 @@
// Package main — event-wait: the GO port of src/rune/event_wait.py's
// deterministic core (Oracle-Battery Parity Pattern, #2479 GO batch 4).
// The 15E-case-10 recovery state machine + event matching are the pure
// transforms; the FIFO rendezvous and HTTP stay Python (reactive surface).
// deterministic core (Oracle-Battery Parity Pattern, #2479 GO batch 4 →
// FULL-port dichotomy wave).
//
// DICHOTOMY VERDICT: GO. The full deterministic surface is name-preserving
// here: wr_root (resolver chain), fifo_path, open_fifo_reader (FIFO
// rendezvous setup), register_process_wait (durable-record shaping),
// _pgroup_alive/_cmdline_alive/liveness (/proc watchdog probes),
// operation_wait, resume_or_recover (15E-case-10 recovery state machine)
// and find_matching_event (durable event-log scan).
//
// EXCLUDED (reactive surface, stays Python): run_builder_under_events,
// await_resume_event (select() block on the FIFO), post_process_event
// (HTTP POST), _load_waitstore (dynamic bridge-module loader). These are
// IO seams, not deterministic kernels — parity-first, no improvements.
package main
import (
@@ -10,9 +21,333 @@ import (
"os"
"path/filepath"
"sort"
"strings"
"syscall"
"time"
)
const (
canonicalWRRoot = "/mnt/workdrive/wr-bridge"
)
func legacyWRRoot() string {
home, err := os.UserHomeDir()
if err != nil {
return "/root/.local/share/mimo-wait-resume"
}
return filepath.Join(home, ".local", "share", "mimo-wait-resume")
}
// wrRoot — the ONE wait/resume root authority (scripts/wr_root_resolver.py
// resolution order, mirrored 1:1): RUNE_WR_ROOT env → canonical production
// root (live store or dir exists) → legacy home root → canonical on demand.
// The resolver-unavailable fallback branch is a partial-checkout artifact —
// the resolver ships with the repo, so the chain below is its contract.
func wrRoot() string {
if env := os.Getenv("RUNE_WR_ROOT"); env != "" {
return env
}
if hasLiveStore(canonicalWRRoot) || dirExists(canonicalWRRoot) {
return canonicalWRRoot
}
legacy := legacyWRRoot()
if hasLiveStore(legacy) || dirExists(legacy) {
return legacy
}
return canonicalWRRoot
}
func dirExists(root string) bool {
info, err := os.Stat(root)
return err == nil && info.IsDir()
}
// hasLiveStore — the resolver's live-store test: events dir holding ≥1 record.
func hasLiveStore(root string) bool {
entries, err := os.ReadDir(filepath.Join(root, "events"))
if err != nil {
return false
}
for _, e := range entries {
if filepath.Ext(e.Name()) == ".json" {
return true
}
}
return false
}
// fifoPath — the operation's FIFO rendezvous point under the resolved root.
func fifoPath(operationID string) string {
return filepath.Join(wrRoot(), "resumes", operationID+".fifo")
}
// openFifoReader — open the operation's FIFO read end (O_NONBLOCK) BEFORE
// the wait is registered (§16Y #10 race fix): unlink+recreate, chmod 0666
// AFTER mkfifo (umask never applies), then O_RDONLY|O_NONBLOCK.
func openFifoReader(operationID string) (int, error) {
fifo := fifoPath(operationID)
os.MkdirAll(filepath.Dir(fifo), 0o755)
os.Remove(fifo)
if err := syscall.Mkfifo(fifo, 0o666); err != nil {
return -1, err
}
if err := os.Chmod(fifo, 0o666); err != nil {
return -1, err
}
return syscall.Open(fifo, syscall.O_RDONLY|syscall.O_NONBLOCK, 0)
}
// notifierWriterSrc — the per-wait resume_cmd writer program (verbatim).
const notifierWriterSrc = "import sys,json,time,os;" +
"fd=os.open(sys.argv[1], os.O_WRONLY | os.O_NONBLOCK);" +
"os.write(fd, json.dumps({'wait_id': sys.argv[2]," +
" 'resumed_at': time.time()}).encode());os.close(fd)"
// registerProcessWait — the durable PROCESS_EVENT record shape. The
// waitstore load/save seams stay Python; this is the pure record transform
// (kwargs → full new_wait kwargs + ext), verified against the oracle's
// shaped records.
func registerProcessWait(operationID string, kw map[string]any, env map[string]string) map[string]any {
get := func(key, def string) string {
if v, ok := kw[key].(string); ok && v != "" {
return v
}
return def
}
provider := get("provider", "builder")
eventType := get("event_type", "builder_exit")
sha := get("sha", "")
project := get("project", "")
objective := get("objective", "")
watchedFor := get("watched_for", "")
continuationState := get("continuation_state", "WAITING_EXTERNAL")
opType := get("op_type", "")
authority := get("authority", "")
taskID := get("task_id", "")
resumeMode := get("resume_mode", "fifo")
watchdogSecs := 0
switch v := kw["watchdog_secs"].(type) {
case json.Number:
n, _ := v.Int64()
watchdogSecs = int(n)
case float64:
watchdogSecs = int(v)
}
if watchdogSecs == 0 { // Python `watchdog_secs or int(env or 600)`
envSecs := "600"
if e := env["WR_WATCH_SECS"]; e != "" {
envSecs = e
}
fmt.Sscanf(envSecs, "%d", &watchdogSecs)
}
fifo := fifoPath(operationID)
os.MkdirAll(filepath.Dir(fifo), 0o755)
if _, err := os.Stat(fifo); os.IsNotExist(err) {
syscall.Mkfifo(fifo, 0o666)
os.Chmod(fifo, 0o666)
}
var resumeCmd any
if resumeMode == "self" {
resumeCmd = nil
} else {
resumeCmd = []any{"python3", "-c", notifierWriterSrc, fifo, "{wait_id}"}
}
repo := project
if repo == "" {
repo = "construction"
}
obj := objective
if obj == "" {
obj = "builder operation " + operationID
}
wf := watchedFor
if wf == "" {
wf = "builder process exit event"
}
rec := map[string]any{
"wait_id": "wr-fake-0000",
"session_id": "controller-" + operationID,
"status": "WAITING_EXTERNAL",
"provider": provider,
"event_type": eventType,
"sha": sha,
"repo": repo,
"run_ref": operationID,
"expected": "success",
"wait_kind": "PROCESS_EVENT",
"target_kind": "headless",
"objective": obj,
"waited_for": wf,
"verify_when_woken": "reload canonical state; exit canary; independent verification",
"on_success": "continue programme continuation canary",
"on_failure": "classify failure; recovery or block",
"resume_cmd": resumeCmd,
"ext": map[string]any{
"contract_id": get("contract_id", ""),
"operation_id": operationID,
"fifo": fifo,
"watchdog_secs": watchdogSecs,
"continuation_state": continuationState,
"op_type": opType,
"authority": authority,
"task_id": taskID,
"resume_mode": resumeMode,
},
}
return rec
}
// pgroupAlive — at least one NON-ZOMBIE member of the process group.
func pgroupAlive(pgid int) bool {
if err := syscall.Kill(-pgid, syscall.Signal(0)); err != nil {
// ESRCH (gone) and EPERM (foreign) both classify NOT-alive,
// matching the oracle's ProcessLookupError/PermissionError arm.
return false
}
entries, err := os.ReadDir("/proc")
if err != nil {
return false
}
for _, e := range entries {
if !isDigits(e.Name()) {
continue
}
raw, err := os.ReadFile(filepath.Join("/proc", e.Name(), "stat"))
if err != nil {
continue
}
state, procPgid, ok := parseStat(string(raw))
if !ok {
continue
}
if procPgid == pgid && state != "Z" {
return true
}
}
return false
}
// cmdlineAlive — any live process whose cmdline contains the needle.
func cmdlineAlive(needle string) bool {
entries, err := os.ReadDir("/proc")
if err != nil {
return false
}
for _, e := range entries {
if !isDigits(e.Name()) {
continue
}
raw, err := os.ReadFile(filepath.Join("/proc", e.Name(), "cmdline"))
if err != nil {
continue
}
statRaw, err := os.ReadFile(filepath.Join("/proc", e.Name(), "stat"))
if err != nil {
continue
}
state, _, ok := parseStat(string(statRaw))
if !ok {
continue
}
if strings.Contains(decodeReplace(raw), needle) && state != "Z" {
return true
}
}
return false
}
// liveness — pgid membership first, cmdline evidence as the
// regroup-tolerant fallback. pgid 0/None (Python falsy) skips the pgid arm.
func liveness(operationID string, pgid int) bool {
if pgid != 0 && pgroupAlive(pgid) {
return true
}
return cmdlineAlive("op:" + operationID)
}
func isDigits(s string) bool {
if s == "" {
return false
}
for _, r := range s {
if r < '0' || r > '9' {
return false
}
}
return true
}
// parseStat — comm (field 2) may contain spaces/parens: split after the
// LAST ')' then fields are state, ppid, pgid, ...
func parseStat(stat string) (state string, pgid int, ok bool) {
i := strings.LastIndex(stat, ")")
if i < 0 || i+1 >= len(stat) {
return "", 0, false
}
fields := strings.Fields(stat[i+1:])
if len(fields) < 3 {
return "", 0, false
}
n, err := parseInt(fields[2])
if err != nil {
return "", 0, false
}
return fields[0], n, true
}
func parseInt(s string) (int, error) {
n := 0
if s == "" {
return 0, fmt.Errorf("empty")
}
for _, c := range s {
if c < '0' || c > '9' {
return 0, fmt.Errorf("not a number: %q", s)
}
n = n*10 + int(c-'0')
}
return n, nil
}
// decodeReplace — errors="replace" semantics for the ASCII needles used here.
func decodeReplace(b []byte) string {
return strings.ToValidUTF8(string(b), "\uFFFD")
}
// operationWait — find the durable record carrying this operation_id.
func operationWait(waitsDir, operationID string) map[string]any {
entries, err := os.ReadDir(waitsDir)
if err != nil {
return nil
}
for _, e := range entries {
if filepath.Ext(e.Name()) != ".json" {
continue
}
raw, err := os.ReadFile(filepath.Join(waitsDir, e.Name()))
if err != nil {
continue
}
var rec map[string]any
if json.Unmarshal(raw, &rec) != nil {
continue
}
ext, _ := rec["ext"].(map[string]any)
if ext == nil {
continue
}
if op, _ := ext["operation_id"].(string); op == operationID {
return rec
}
}
return nil
}
// resumeOrRecover — Python-identical: the durable record is the authority.
func resumeOrRecover(rec map[string]any) map[string]any {
if rec == nil {
@@ -35,6 +370,7 @@ func resumeOrRecover(rec map[string]any) map[string]any {
}
// findMatchingEvent — newest mtime wins; identity = provider+event_type+run_ref.
// run_ref coercion is Python's str(): missing/null → "", numbers stringify.
func findMatchingEvent(eventsDir, provider, eventType, runRef string) map[string]any {
entries, err := os.ReadDir(eventsDir)
if err != nil {
@@ -42,7 +378,7 @@ func findMatchingEvent(eventsDir, provider, eventType, runRef string) map[string
}
type cand struct {
path string
mtime int64
mtime time.Time
}
var files []cand
for _, e := range entries {
@@ -53,16 +389,16 @@ func findMatchingEvent(eventsDir, provider, eventType, runRef string) map[string
if err != nil {
continue
}
files = append(files, cand{filepath.Join(eventsDir, e.Name()), info.ModTime().UnixNano()})
files = append(files, cand{filepath.Join(eventsDir, e.Name()), info.ModTime()})
}
sort.Slice(files, func(i, j int) bool { return files[i].mtime > files[j].mtime })
sort.SliceStable(files, func(i, j int) bool { return files[i].mtime.After(files[j].mtime) })
for _, f := range files {
raw, err := os.ReadFile(f.path)
if err != nil {
continue
}
var rec map[string]any
if err := json.Unmarshal(raw, &rec); err != nil {
if json.Unmarshal(raw, &rec) != nil {
continue
}
ev, _ := rec["event"].(map[string]any)
@@ -71,7 +407,10 @@ func findMatchingEvent(eventsDir, provider, eventType, runRef string) map[string
}
p, _ := ev["provider"].(string)
et, _ := ev["event_type"].(string)
rr := fmt.Sprintf("%v", ev["run_ref"])
rr := ""
if v, has := ev["run_ref"]; has && v != nil {
rr = fmt.Sprintf("%v", v)
}
if p == provider && et == eventType && rr == runRef {
return ev
}
@@ -79,95 +418,27 @@ func findMatchingEvent(eventsDir, provider, eventType, runRef string) map[string
return nil
}
// canonicalJSON — sorted-key compact dump for exact comparisons (Go marshals
// map keys sorted; HTML escaping disabled to match Python's json.dumps).
func canonicalJSON(v any) string {
var sb strings.Builder
enc := json.NewEncoder(&sb)
enc.SetEscapeHTML(false)
if err := enc.Encode(v); err != nil {
return fmt.Sprintf("<unmarshalable: %v>", err)
}
return strings.TrimRight(sb.String(), "\n")
}
func main() {
if len(os.Args) < 2 || os.Args[1] != "replay" {
fmt.Fprintln(os.Stderr, "usage: event-wait replay <truth.json>")
os.Exit(2)
}
raw, err := os.ReadFile(os.Args[2])
n, err := runReplay(os.Args[2])
if err != nil {
fmt.Fprintln(os.Stderr, err)
fmt.Fprintf(os.Stderr, "PARITY FAILED (%d cases): %v\n", n, err)
os.Exit(1)
}
var cases []map[string]any
if err := json.Unmarshal(raw, &cases); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
// reconstruct the oracle's fixture state deterministically
base, err := os.MkdirTemp("", "ew_truth_")
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
defer os.RemoveAll(base)
waits := filepath.Join(base, "waits")
os.MkdirAll(waits, 0o755)
statuses := []string{"COMPLETED", "FAILED", "READY_TO_RESUME", "READY_TO_RETRY",
"RESUMING", "RESUME_UNVERIFIED", "WAITING_EXTERNAL"}
for _, st := range statuses {
rec := map[string]any{
"id": "w-" + st, "status": st,
"ext": map[string]any{"operation_id": "op-" + st},
}
if st == "RESUME_UNVERIFIED" {
rec["process_event_evidence"] = map[string]any{"verdict": "success"}
}
b, _ := json.Marshal(rec)
os.WriteFile(filepath.Join(waits, "w-"+st+".json"), b, 0o644)
}
events := filepath.Join(base, "events")
os.MkdirAll(events, 0o755)
writeEv := func(name string, ev map[string]any) {
b, _ := json.Marshal(map[string]any{"event": ev})
os.WriteFile(filepath.Join(events, name), b, 0o644)
}
writeEv("a.json", map[string]any{"provider": "gitea", "event_type": "ci", "run_ref": "123", "verdict": "old"})
time.Sleep(20 * time.Millisecond)
writeEv("b.json", map[string]any{"provider": "gitea", "event_type": "ci", "run_ref": "123", "verdict": "new"})
writeEv("c.json", map[string]any{"provider": "gitea", "event_type": "deploy", "run_ref": "123"})
fails := 0
for i, c := range cases {
op, _ := c["op"].(string)
switch op {
case "recover":
st, _ := c["status"].(string)
wantKind, _ := c["want_kind"].(string)
wantRec, _ := c["want_recovered"].(bool)
rawRec, _ := os.ReadFile(filepath.Join(waits, "w-"+st+".json"))
var rec map[string]any
json.Unmarshal(rawRec, &rec)
got := resumeOrRecover(rec)
kind, _ := got["kind"].(string)
rec_, _ := got["recovered"].(bool)
if kind != wantKind || rec_ != wantRec {
fails++
fmt.Printf("case %d recover %s: got %v/%v want %v/%v\n", i, st, kind, rec_, wantKind, wantRec)
}
case "recover_unknown":
got := resumeOrRecover(nil)
if got["kind"] != "unknown_operation" {
fails++
fmt.Printf("case %d recover_unknown: got %v\n", i, got["kind"])
}
case "match":
got := findMatchingEvent(events, "gitea", "ci", "123")
v, _ := got["verdict"].(string)
if v != "new" {
fails++
fmt.Printf("case %d match: got %v (want newest)\n", i, got)
}
case "match_none":
if got := findMatchingEvent(events, "gitea", "ci", "999"); got != nil {
fails++
fmt.Printf("case %d match_none: got %v\n", i, got)
}
}
}
if fails > 0 {
fmt.Fprintf(os.Stderr, "PARITY FAILED: %d divergence(s)\n", fails)
os.Exit(1)
}
fmt.Printf("PARITY OK: %d cases exact-equal vs the Python oracle\n", len(cases))
fmt.Printf("PARITY OK: %d cases exact-equal vs the Python oracle\n", n)
}
+288
View File
@@ -0,0 +1,288 @@
package main
// replay.go — the oracle-first truth battery: replays the frozen truth
// (generated from the live Python oracle by /tmp/ew_truth_gen.py) against
// THIS Go port, case by case. Every case must match exactly.
import (
"encoding/json"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"syscall"
"time"
)
// runReplay executes the frozen truth against the Go port. Every case must
// match; any divergence is printed and makes the run exit non-zero.
func runReplay(truthPath string) (int, error) {
raw, err := os.ReadFile(truthPath)
if err != nil {
return 0, err
}
dec := json.NewDecoder(strings.NewReader(string(raw)))
dec.UseNumber()
var parsed struct {
Module string `json:"module"`
BaseRoot string `json:"base_root"`
Cases []map[string]any `json:"cases"`
}
if err := dec.Decode(&parsed); err != nil {
return 0, err
}
base, err := os.MkdirTemp("", "ew_replay_")
if err != nil {
return 0, err
}
defer os.RemoveAll(base)
os.Setenv("RUNE_WR_ROOT", base)
defer os.Unsetenv("RUNE_WR_ROOT")
fails := 0
for i, c := range parsed.Cases {
// base-root substitution: the truth froze the generator's temp
// root; the replay rebuilds the same state under its own root.
cj := strings.ReplaceAll(canonicalJSON(c), parsed.BaseRoot, base)
var caseMap map[string]any
d2 := json.NewDecoder(strings.NewReader(cj))
d2.UseNumber()
if err := d2.Decode(&caseMap); err != nil {
return len(parsed.Cases), fmt.Errorf("case %d: %w", i, err)
}
if msg := runCase(caseMap, base); msg != "" {
fails++
fmt.Printf("case %d (%s %s): %s\n", i, str(caseMap, "fn"), str(caseMap, "name"), msg)
}
}
if fails > 0 {
return len(parsed.Cases), fmt.Errorf("%d divergence(s)", fails)
}
return len(parsed.Cases), nil
}
func str(m map[string]any, k string) string {
v, _ := m[k].(string)
return v
}
func runCase(c map[string]any, base string) string {
switch str(c, "fn") {
case "wr_root":
saved := os.Getenv("RUNE_WR_ROOT")
defer os.Setenv("RUNE_WR_ROOT", saved)
os.Unsetenv("RUNE_WR_ROOT")
if env, ok := c["env"].(map[string]any); ok {
for k, v := range env {
os.Setenv(k, fmt.Sprintf("%v", v))
}
}
if got := wrRoot(); got != str(c, "want") {
return fmt.Sprintf("wr_root got %q want %q", got, str(c, "want"))
}
return ""
case "fifo_path":
if got := fifoPath(str(c, "op")); got != str(c, "want") {
return fmt.Sprintf("fifo_path got %q want %q", got, str(c, "want"))
}
return ""
case "open_fifo_reader":
op := str(c, "op")
fd, err := openFifoReader(op)
if err != nil {
return fmt.Sprintf("open_fifo_reader error: %v", err)
}
defer syscall.Close(fd)
if fd < 0 {
return "open_fifo_reader fd not positive"
}
info, err := os.Stat(fifoPath(op))
if err != nil {
return fmt.Sprintf("stat fifo: %v", err)
}
if info.Mode()&os.ModeNamedPipe == 0 {
return "path is not a FIFO"
}
if info.Mode().Perm() != 0o666 {
return fmt.Sprintf("fifo mode %o want 666", info.Mode().Perm())
}
return ""
case "register_process_wait":
kw, _ := c["kw"].(map[string]any)
env := map[string]string{}
if em, ok := c["env"].(map[string]any); ok {
for k, v := range em {
env[k] = fmt.Sprintf("%v", v)
}
}
got := registerProcessWait(str(c, "op"), kw, env)
if canonicalJSON(got) != canonicalJSON(c["want_rec"]) {
return fmt.Sprintf("record shape\n got %s\nwant %s",
canonicalJSON(got), canonicalJSON(c["want_rec"]))
}
return ""
case "resume_or_recover":
var rec map[string]any
if c["status"] != nil {
rec = map[string]any{"status": str(c, "status")}
if ev, _ := c["evidence"].(bool); ev {
rec["process_event_evidence"] = map[string]any{"verdict": "success"}
}
}
got := resumeOrRecover(rec)
if kind, _ := got["kind"].(string); kind != str(c, "want_kind") {
return fmt.Sprintf("kind %q want %q", kind, str(c, "want_kind"))
}
recd, _ := got["recovered"].(bool)
wantRec, _ := c["want_recovered"].(bool)
if recd != wantRec {
return fmt.Sprintf("recovered %v want %v", recd, wantRec)
}
if w, ok := got["wait"].(map[string]any); ok {
if st, _ := w["status"].(string); st != str(c, "want_status") {
return fmt.Sprintf("wait status %q want %q", st, str(c, "want_status"))
}
}
return ""
case "operation_wait":
waits := filepath.Join(base, "waits")
os.MkdirAll(waits, 0o755)
one := map[string]any{
"wait_id": "wr-1", "status": "WAITING_EXTERNAL",
"ext": map[string]any{"operation_id": "op-a"},
}
b, _ := json.Marshal(one)
os.WriteFile(filepath.Join(waits, "wr-1.json"), b, 0o644)
got := operationWait(waits, str(c, "op"))
found := got != nil
wantFound, _ := c["want_found"].(bool)
if found != wantFound {
return fmt.Sprintf("found %v want %v", found, wantFound)
}
if found {
ext, _ := got["ext"].(map[string]any)
op, _ := ext["operation_id"].(string)
st, _ := got["status"].(string)
if op != str(c, "want_op") || st != str(c, "want_status") {
return fmt.Sprintf("rec op/status %q/%q", op, st)
}
}
return ""
case "find_matching_event":
events := filepath.Join(base, "events")
os.MkdirAll(events, 0o755)
writeEv := func(name string, ev map[string]any) {
b, _ := json.Marshal(map[string]any{"event": ev})
os.WriteFile(filepath.Join(events, name), b, 0o644)
time.Sleep(20 * time.Millisecond)
}
writeEv("e_old.json", map[string]any{"provider": "gitea", "event_type": "ci", "run_ref": "123", "verdict": "old"})
writeEv("e_new.json", map[string]any{"provider": "gitea", "event_type": "ci", "run_ref": "123", "verdict": "new"})
writeEv("e_other.json", map[string]any{"provider": "gitea", "event_type": "deploy", "run_ref": "124", "verdict": "deploy"})
writeEv("e_int.json", map[string]any{"provider": "gitea", "event_type": "ci", "run_ref": json.Number("123"), "verdict": "int-new"})
writeEv("e_noref.json", map[string]any{"provider": "gitea", "event_type": "ci", "verdict": "noref-new"})
os.WriteFile(filepath.Join(events, "e_bad.json"), []byte("{not json"), 0o644)
os.WriteFile(filepath.Join(events, "e_notjson.txt"), []byte("nope"), 0o644)
got := findMatchingEvent(events, str(c, "provider"), str(c, "event_type"), str(c, "run_ref"))
wantNone, _ := c["want_none"].(bool)
if wantNone {
if got != nil {
return fmt.Sprintf("expected no match, got %v", got)
}
return ""
}
if got == nil {
return "expected a match, got none"
}
if v, _ := got["verdict"].(string); v != str(c, "want_verdict") {
return fmt.Sprintf("verdict %q want %q", v, str(c, "want_verdict"))
}
return ""
case "pgroup_alive":
want, _ := c["want"].(bool)
if got := runPgroupProbe(str(c, "probe")); got != want {
return fmt.Sprintf("pgroup_alive(%s) got %v want %v", str(c, "probe"), got, want)
}
return ""
case "cmdline_alive":
needle := str(c, "needle")
if needle == "__SELF__" {
needle = selfPath()
}
want, _ := c["want"].(bool)
if got := cmdlineAlive(needle); got != want {
return fmt.Sprintf("cmdline_alive(%q) got %v want %v", needle, got, want)
}
return ""
case "liveness":
want, _ := c["want"].(bool)
pgid := 0
switch str(c, "pgid_probe") {
case "own":
pgid = syscall.Getpgrp()
case "absent":
pgid = 999999999
}
if got := liveness(str(c, "op"), pgid); got != want {
return fmt.Sprintf("liveness(%s) got %v want %v", str(c, "pgid_probe"), got, want)
}
return ""
}
return fmt.Sprintf("unknown fn %q", str(c, "fn"))
}
// runPgroupProbe rebuilds the oracle's live process state per probe.
func runPgroupProbe(probe string) bool {
switch probe {
case "own":
return pgroupAlive(syscall.Getpgrp())
case "child-alive":
proc := spawnSleeper()
defer killReap(proc)
time.Sleep(200 * time.Millisecond)
return pgroupAlive(proc.Pid)
case "child-dead":
proc := spawnSleeper()
killReap(proc)
time.Sleep(100 * time.Millisecond)
return pgroupAlive(proc.Pid)
case "absent":
return pgroupAlive(999999999)
}
return false
}
func spawnSleeper() *os.Process {
cmd := exec.Command("/bin/sh", "-c", "sleep 30")
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
if err := cmd.Start(); err != nil {
return nil
}
return cmd.Process
}
func killReap(proc *os.Process) {
syscall.Kill(-proc.Pid, syscall.SIGKILL)
proc.Wait()
}
// selfPath — the __SELF__ probe: this process's own argv[0], matching the
// oracle's script-path needle.
func selfPath() string {
p, err := filepath.Abs(os.Args[0])
if err != nil {
return os.Args[0]
}
return p
}
+448 -56
View File
@@ -1,56 +1,448 @@
[
{
"op": "recover",
"status": "COMPLETED",
"want_kind": "event",
"want_recovered": true
},
{
"op": "recover",
"status": "FAILED",
"want_kind": "event",
"want_recovered": true
},
{
"op": "recover",
"status": "READY_TO_RESUME",
"want_kind": "event",
"want_recovered": true
},
{
"op": "recover",
"status": "READY_TO_RETRY",
"want_kind": "event",
"want_recovered": true
},
{
"op": "recover",
"status": "RESUMING",
"want_kind": "event",
"want_recovered": true
},
{
"op": "recover",
"status": "RESUME_UNVERIFIED",
"want_kind": "event",
"want_recovered": true
},
{
"op": "recover",
"status": "WAITING_EXTERNAL",
"want_kind": "still_waiting",
"want_recovered": false
},
{
"op": "recover_unknown",
"want_kind": "unknown_operation"
},
{
"op": "match",
"want_verdict": "new"
},
{
"op": "match_none",
"want": true
}
]
{
"base_root": "/tmp/ew_truth_kavaxsjd",
"cases": [
{
"env": {
"RUNE_WR_ROOT": "/tmp/ew_truth_kavaxsjd"
},
"fn": "wr_root",
"name": "env-pin",
"want": "/tmp/ew_truth_kavaxsjd"
},
{
"env": {},
"fn": "wr_root",
"name": "canonical-live",
"want": "/mnt/workdrive/wr-bridge"
},
{
"fn": "fifo_path",
"op": "op-x",
"want": "/tmp/ew_truth_kavaxsjd/resumes/op-x.fifo"
},
{
"fn": "open_fifo_reader",
"op": "op-open",
"want_fd_positive": true,
"want_isfifo": true,
"want_mode": "0o666"
},
{
"evidence": false,
"fn": "resume_or_recover",
"status": "COMPLETED",
"want_kind": "event",
"want_recovered": true,
"want_status": "COMPLETED"
},
{
"evidence": false,
"fn": "resume_or_recover",
"status": "FAILED",
"want_kind": "event",
"want_recovered": true,
"want_status": "FAILED"
},
{
"evidence": false,
"fn": "resume_or_recover",
"status": "READY_TO_RESUME",
"want_kind": "event",
"want_recovered": true,
"want_status": "READY_TO_RESUME"
},
{
"evidence": false,
"fn": "resume_or_recover",
"status": "READY_TO_RETRY",
"want_kind": "event",
"want_recovered": true,
"want_status": "READY_TO_RETRY"
},
{
"evidence": false,
"fn": "resume_or_recover",
"status": "RESUMING",
"want_kind": "event",
"want_recovered": true,
"want_status": "RESUMING"
},
{
"evidence": true,
"fn": "resume_or_recover",
"status": "RESUME_UNVERIFIED",
"want_kind": "event",
"want_recovered": true,
"want_status": "RESUME_UNVERIFIED"
},
{
"evidence": false,
"fn": "resume_or_recover",
"status": "RESUME_UNVERIFIED",
"want_kind": "still_waiting",
"want_recovered": false,
"want_status": "RESUME_UNVERIFIED"
},
{
"evidence": false,
"fn": "resume_or_recover",
"status": "WAITING_EXTERNAL",
"want_kind": "still_waiting",
"want_recovered": false,
"want_status": "WAITING_EXTERNAL"
},
{
"evidence": false,
"fn": "resume_or_recover",
"status": "RUNNABLE",
"want_kind": "still_waiting",
"want_recovered": false,
"want_status": "RUNNABLE"
},
{
"evidence": false,
"fn": "resume_or_recover",
"status": "TIMED_OUT",
"want_kind": "still_waiting",
"want_recovered": false,
"want_status": "TIMED_OUT"
},
{
"evidence": false,
"fn": "resume_or_recover",
"status": null,
"want_kind": "unknown_operation",
"want_recovered": false,
"want_status": null
},
{
"fn": "operation_wait",
"op": "op-a",
"want_found": true,
"want_op": "op-a",
"want_status": "WAITING_EXTERNAL"
},
{
"fn": "operation_wait",
"op": "op-missing",
"want_found": false,
"want_op": null,
"want_status": null
},
{
"event_type": "ci",
"fn": "find_matching_event",
"provider": "gitea",
"run_ref": "123",
"want_verdict": "int-new"
},
{
"event_type": "ci",
"fn": "find_matching_event",
"provider": "gitea",
"run_ref": "999",
"want_none": true
},
{
"event_type": "deploy",
"fn": "find_matching_event",
"provider": "gitea",
"run_ref": "124",
"want_verdict": "deploy"
},
{
"event_type": "ci",
"fn": "find_matching_event",
"provider": "gitea",
"run_ref": "",
"want_none": false,
"want_verdict": "noref-new"
},
{
"fn": "pgroup_alive",
"probe": "own",
"want": true
},
{
"fn": "pgroup_alive",
"probe": "child-alive",
"want": true
},
{
"fn": "pgroup_alive",
"probe": "child-dead",
"want": false
},
{
"fn": "pgroup_alive",
"probe": "absent",
"want": false
},
{
"fn": "cmdline_alive",
"needle": "__SELF__",
"want": true
},
{
"fn": "cmdline_alive",
"needle": "op:ew-truth-absent-op",
"want": false
},
{
"fn": "liveness",
"op": "ew-truth-absent-op",
"pgid_probe": "none",
"want": false
},
{
"fn": "liveness",
"op": "ew-truth-absent-op",
"pgid_probe": "zero",
"want": false
},
{
"fn": "liveness",
"op": "ew-truth-absent-op",
"pgid_probe": "own",
"want": true
},
{
"fn": "liveness",
"op": "ew-truth-absent-op",
"pgid_probe": "absent",
"want": false
},
{
"env": {},
"fn": "register_process_wait",
"kw": {},
"name": "defaults",
"op": "op-reg-default",
"want_rec": {
"event_type": "builder_exit",
"expected": "success",
"ext": {
"authority": "",
"continuation_state": "WAITING_EXTERNAL",
"contract_id": "",
"fifo": "/tmp/ew_truth_kavaxsjd/resumes/op-reg-default.fifo",
"op_type": "",
"operation_id": "op-reg-default",
"resume_mode": "fifo",
"task_id": "",
"watchdog_secs": 600
},
"objective": "builder operation op-reg-default",
"on_failure": "classify failure; recovery or block",
"on_success": "continue programme continuation canary",
"provider": "builder",
"repo": "construction",
"resume_cmd": [
"python3",
"-c",
"import sys,json,time,os;fd=os.open(sys.argv[1], os.O_WRONLY | os.O_NONBLOCK);os.write(fd, json.dumps({'wait_id': sys.argv[2], 'resumed_at': time.time()}).encode());os.close(fd)",
"/tmp/ew_truth_kavaxsjd/resumes/op-reg-default.fifo",
"{wait_id}"
],
"run_ref": "op-reg-default",
"session_id": "controller-op-reg-default",
"sha": "",
"status": "WAITING_EXTERNAL",
"target_kind": "headless",
"verify_when_woken": "reload canonical state; exit canary; independent verification",
"wait_id": "wr-fake-0000",
"wait_kind": "PROCESS_EVENT",
"waited_for": "builder process exit event"
}
},
{
"env": {},
"fn": "register_process_wait",
"kw": {
"resume_mode": "self"
},
"name": "self-mode",
"op": "op-reg-self",
"want_rec": {
"event_type": "builder_exit",
"expected": "success",
"ext": {
"authority": "",
"continuation_state": "WAITING_EXTERNAL",
"contract_id": "",
"fifo": "/tmp/ew_truth_kavaxsjd/resumes/op-reg-self.fifo",
"op_type": "",
"operation_id": "op-reg-self",
"resume_mode": "self",
"task_id": "",
"watchdog_secs": 600
},
"objective": "builder operation op-reg-self",
"on_failure": "classify failure; recovery or block",
"on_success": "continue programme continuation canary",
"provider": "builder",
"repo": "construction",
"resume_cmd": null,
"run_ref": "op-reg-self",
"session_id": "controller-op-reg-self",
"sha": "",
"status": "WAITING_EXTERNAL",
"target_kind": "headless",
"verify_when_woken": "reload canonical state; exit canary; independent verification",
"wait_id": "wr-fake-0000",
"wait_kind": "PROCESS_EVENT",
"waited_for": "builder process exit event"
}
},
{
"env": {},
"fn": "register_process_wait",
"kw": {
"authority": "spine",
"continuation_state": "RESUMABLE",
"contract_id": "contract:t15",
"event_type": "job_done",
"objective": "latency proof",
"op_type": "task",
"project": "t15-fixture",
"provider": "ci-adapter",
"resume_mode": "fifo",
"sha": "abc123",
"task_id": "T9",
"watchdog_secs": 120,
"watched_for": "watched thing"
},
"name": "explicit",
"op": "op-reg-custom",
"want_rec": {
"event_type": "job_done",
"expected": "success",
"ext": {
"authority": "spine",
"continuation_state": "RESUMABLE",
"contract_id": "contract:t15",
"fifo": "/tmp/ew_truth_kavaxsjd/resumes/op-reg-custom.fifo",
"op_type": "task",
"operation_id": "op-reg-custom",
"resume_mode": "fifo",
"task_id": "T9",
"watchdog_secs": 120
},
"objective": "latency proof",
"on_failure": "classify failure; recovery or block",
"on_success": "continue programme continuation canary",
"provider": "ci-adapter",
"repo": "t15-fixture",
"resume_cmd": [
"python3",
"-c",
"import sys,json,time,os;fd=os.open(sys.argv[1], os.O_WRONLY | os.O_NONBLOCK);os.write(fd, json.dumps({'wait_id': sys.argv[2], 'resumed_at': time.time()}).encode());os.close(fd)",
"/tmp/ew_truth_kavaxsjd/resumes/op-reg-custom.fifo",
"{wait_id}"
],
"run_ref": "op-reg-custom",
"session_id": "controller-op-reg-custom",
"sha": "abc123",
"status": "WAITING_EXTERNAL",
"target_kind": "headless",
"verify_when_woken": "reload canonical state; exit canary; independent verification",
"wait_id": "wr-fake-0000",
"wait_kind": "PROCESS_EVENT",
"waited_for": "watched thing"
}
},
{
"env": {
"WR_WATCH_SECS": "777"
},
"fn": "register_process_wait",
"kw": {},
"name": "watchdog-env",
"op": "op-reg-watchdog-env",
"want_rec": {
"event_type": "builder_exit",
"expected": "success",
"ext": {
"authority": "",
"continuation_state": "WAITING_EXTERNAL",
"contract_id": "",
"fifo": "/tmp/ew_truth_kavaxsjd/resumes/op-reg-watchdog-env.fifo",
"op_type": "",
"operation_id": "op-reg-watchdog-env",
"resume_mode": "fifo",
"task_id": "",
"watchdog_secs": 777
},
"objective": "builder operation op-reg-watchdog-env",
"on_failure": "classify failure; recovery or block",
"on_success": "continue programme continuation canary",
"provider": "builder",
"repo": "construction",
"resume_cmd": [
"python3",
"-c",
"import sys,json,time,os;fd=os.open(sys.argv[1], os.O_WRONLY | os.O_NONBLOCK);os.write(fd, json.dumps({'wait_id': sys.argv[2], 'resumed_at': time.time()}).encode());os.close(fd)",
"/tmp/ew_truth_kavaxsjd/resumes/op-reg-watchdog-env.fifo",
"{wait_id}"
],
"run_ref": "op-reg-watchdog-env",
"session_id": "controller-op-reg-watchdog-env",
"sha": "",
"status": "WAITING_EXTERNAL",
"target_kind": "headless",
"verify_when_woken": "reload canonical state; exit canary; independent verification",
"wait_id": "wr-fake-0000",
"wait_kind": "PROCESS_EVENT",
"waited_for": "builder process exit event"
}
},
{
"env": {},
"fn": "register_process_wait",
"kw": {
"watchdog_secs": 42
},
"name": "watchdog-explicit",
"op": "op-reg-watchdog-explicit",
"want_rec": {
"event_type": "builder_exit",
"expected": "success",
"ext": {
"authority": "",
"continuation_state": "WAITING_EXTERNAL",
"contract_id": "",
"fifo": "/tmp/ew_truth_kavaxsjd/resumes/op-reg-watchdog-explicit.fifo",
"op_type": "",
"operation_id": "op-reg-watchdog-explicit",
"resume_mode": "fifo",
"task_id": "",
"watchdog_secs": 42
},
"objective": "builder operation op-reg-watchdog-explicit",
"on_failure": "classify failure; recovery or block",
"on_success": "continue programme continuation canary",
"provider": "builder",
"repo": "construction",
"resume_cmd": [
"python3",
"-c",
"import sys,json,time,os;fd=os.open(sys.argv[1], os.O_WRONLY | os.O_NONBLOCK);os.write(fd, json.dumps({'wait_id': sys.argv[2], 'resumed_at': time.time()}).encode());os.close(fd)",
"/tmp/ew_truth_kavaxsjd/resumes/op-reg-watchdog-explicit.fifo",
"{wait_id}"
],
"run_ref": "op-reg-watchdog-explicit",
"session_id": "controller-op-reg-watchdog-explicit",
"sha": "",
"status": "WAITING_EXTERNAL",
"target_kind": "headless",
"verify_when_woken": "reload canonical state; exit canary; independent verification",
"wait_id": "wr-fake-0000",
"wait_kind": "PROCESS_EVENT",
"waited_for": "builder process exit event"
}
}
],
"generated_by": "/tmp/ew_truth_gen.py",
"module": "event_wait",
"oracle": "src/rune/event_wait.py"
}
+26
View File
@@ -0,0 +1,26 @@
package main
// truth_test.go — the HARD parity gate: replays the frozen oracle truth
// (generated from the live Python oracle) against this port. Any
// divergence fails the test; the truth file is embedded via the repo copy
// so the gate holds in CI without /tmp.
import (
"os"
"testing"
)
func TestTruthParity(t *testing.T) {
const path = "truth.json"
if _, err := os.Stat(path); err != nil {
t.Fatalf("truth.json missing (frozen oracle battery): %v", err)
}
n, err := runReplay(path)
if err != nil {
t.Fatalf("parity replay diverged after %d cases: %v", n, err)
}
if n == 0 {
t.Fatal("truth.json contained no cases")
}
t.Logf("PARITY OK: %d cases exact-equal vs the Python oracle", n)
}
+148
View File
@@ -0,0 +1,148 @@
// CLI — exact port of goal.py main(): same argv parsing, same printed
// lines (including the ✓ characters), byte-for-byte stdout parity.
package main
import (
"fmt"
"io"
"path/filepath"
"strings"
)
// RunCLI executes goal.py main() against the given argv (including
// program name at index 0) and writes the exact stdout bytes.
func RunCLI(argv []string, w io.Writer) {
managerMu.Lock()
if globalManager == nil {
globalManager = NewGoalManager(homeGoalsPath())
globalManager.Load()
}
managerMu.Unlock()
if len(argv) < 2 {
printUsage(w)
return
}
command := argv[1]
switch command {
case "set":
if len(argv) < 3 {
fmt.Fprintln(w, "Usage: goal.py set <goal> [--conditions <cond1,cond2>] [--verify <method>]")
return
}
description := argv[2]
var stopConditions []string
verificationMethod := "manual"
i := 3
for i < len(argv) {
if argv[i] == "--conditions" && i+1 < len(argv) {
stopConditions = strings.Split(argv[i+1], ",")
i += 2
} else if argv[i] == "--verify" && i+1 < len(argv) {
verificationMethod = argv[i+1]
i += 2
} else {
i++
}
}
result := setGoal(description, stopConditions, verificationMethod)
desc, _ := result.Get("description")
fmt.Fprintf(w, "✓ Goal set: %s\n", pyStr(desc))
if len(stopConditions) > 0 {
fmt.Fprintf(w, " Stop conditions: %s\n", strings.Join(stopConditions, ", "))
}
case "check":
var evidence *string
if len(argv) > 2 && argv[2] == "--evidence" {
if len(argv) > 3 {
evidence = &argv[3]
}
}
result := checkGoal(evidence)
status, _ := result.Get("status")
message, _ := result.Get("message")
fmt.Fprintf(w, "Status: %s\n", pyStr(status))
fmt.Fprintf(w, "Message: %s\n", pyStr(message))
if goalV, ok := result.Get("goal"); ok && goalV != nil {
goal := goalV.(*OrderedMap)
descV, _ := goal.Get("description")
fmt.Fprintf(w, "Goal: %s\n", pyStr(descV))
if metV, ok := result.Get("conditions_met"); ok {
totalV, _ := result.Get("conditions_total")
fmt.Fprintf(w, "Conditions: %s/%s\n", pyStr(metV), pyStr(totalV))
}
}
case "achieve":
var evidence *string
if len(argv) > 2 && argv[2] == "--evidence" {
if len(argv) > 3 {
evidence = &argv[3]
}
}
result := achieveGoal(evidence)
message, _ := result.Get("message")
fmt.Fprintf(w, "✓ %s\n", pyStr(message))
case "clear":
result := clearGoal()
message, _ := result.Get("message")
fmt.Fprintf(w, "✓ %s\n", pyStr(message))
case "status":
result := checkGoal(nil)
status, _ := result.Get("status")
if status == "no_goal" {
fmt.Fprintln(w, "No active goal")
} else {
goalV, _ := result.Get("goal")
goal := goalV.(*OrderedMap)
descV, _ := goal.Get("description")
statusV, _ := goal.Get("status")
condsV, _ := goal.Get("stop_conditions")
evidV, _ := goal.Get("evidence")
conds, _ := condsV.([]any)
evid, _ := evidV.([]any)
fmt.Fprintf(w, "Goal: %s\n", pyStr(descV))
fmt.Fprintf(w, "Status: %s\n", pyStr(statusV))
fmt.Fprintf(w, "Conditions: %d\n", len(conds))
fmt.Fprintf(w, "Evidence: %d\n", len(evid))
}
case "list":
goals := listGoals()
if len(goals) == 0 {
fmt.Fprintln(w, "No goals set")
} else {
fmt.Fprintf(w, "Goals: %d\n", len(goals))
for _, g := range goals {
goal := g.(*OrderedMap)
statusV, _ := goal.Get("status")
descV, _ := goal.Get("description")
fmt.Fprintf(w, " [%s] %s\n", pyStr(statusV), pyStr(descV))
}
}
default:
fmt.Fprintf(w, "Unknown command: %s\n", command)
}
}
func printUsage(w io.Writer) {
fmt.Fprintln(w, "Usage:")
fmt.Fprintln(w, " goal.py set <goal> [--conditions <cond1,cond2>] [--verify <method>]")
fmt.Fprintln(w, " goal.py check [--evidence <text>]")
fmt.Fprintln(w, " goal.py achieve [--evidence <text>]")
fmt.Fprintln(w, " goal.py clear")
fmt.Fprintln(w, " goal.py status")
fmt.Fprintln(w, " goal.py list")
}
func homeGoalsPath() string {
return filepath.Join(HomeDir(), ".rune", "goals.json")
}
+3
View File
@@ -0,0 +1,3 @@
module rune/goal
go 1.22
+725
View File
@@ -0,0 +1,725 @@
// Package main — the FULL GO port of src/rune/goal.py (#2479 dichotomy
// tranche, GO side verdict). Every public Python function has a
// name-preserving counterpart; the goal state machine + keyword
// verification are pure transforms, and IO (storage file, LLM endpoint,
// clock, uuid) stays behind injectable seams so the frozen truth battery
// replays deterministically.
package main
import (
"crypto/rand"
"encoding/json"
"fmt"
"net/http"
"os"
"path/filepath"
"regexp"
"strings"
"sync"
"time"
)
// Goal — Python-identical field semantics (goal.Goal dataclass).
type Goal struct {
GoalID string
Description string
StopConditions []string
VerificationMethod string
Status string // "active", "achieved", "failed", "abandoned"
CreatedAt string
CompletedAt *string
Evidence []string
}
// toDict — Goal.to_dict, key order preserved (goal_id, description,
// stop_conditions, verification_method, status, created_at, completed_at,
// evidence). Nil lists emit [] like Python's default_factory.
func (g *Goal) toDict() *OrderedMap {
conds := g.StopConditions
if conds == nil {
conds = []string{}
}
ev := g.Evidence
if ev == nil {
ev = []string{}
}
var completed any
if g.CompletedAt != nil {
completed = *g.CompletedAt
} else {
completed = nil
}
return NewOrderedMap(
KV{"goal_id", g.GoalID},
KV{"description", g.Description},
KV{"stop_conditions", toAnySlice(conds)},
KV{"verification_method", g.VerificationMethod},
KV{"status", g.Status},
KV{"created_at", g.CreatedAt},
KV{"completed_at", completed},
KV{"evidence", toAnySlice(ev)},
)
}
// goalFromDict rebuilds a Goal from a parsed JSON object (missing optional
// fields behave like the Python dataclass defaults).
func goalFromDict(om *OrderedMap) *Goal {
g := &Goal{
VerificationMethod: "manual",
Status: "active",
}
if v, ok := om.Get("goal_id"); ok {
g.GoalID, _ = v.(string)
}
if v, ok := om.Get("description"); ok {
g.Description, _ = v.(string)
}
if v, ok := om.Get("stop_conditions"); ok {
g.StopConditions = stringSlice(v)
}
if v, ok := om.Get("verification_method"); ok {
if s, ok := v.(string); ok {
g.VerificationMethod = s
}
}
if v, ok := om.Get("status"); ok {
if s, ok := v.(string); ok {
g.Status = s
}
}
if v, ok := om.Get("created_at"); ok {
if s, ok := v.(string); ok {
g.CreatedAt = s
}
}
if v, ok := om.Get("completed_at"); ok {
if s, ok := v.(string); ok {
g.CompletedAt = &s
}
}
if v, ok := om.Get("evidence"); ok {
g.Evidence = stringSlice(v)
}
return g
}
// ---------- seams (frozen by the truth battery) ----------
// ClockNow — datetime.now(timezone.utc).isoformat().
var ClockNow = func() string {
return time.Now().UTC().Format("2006-01-02T15:04:05.000000") + "+00:00"
}
// NewGoalID — str(uuid.uuid4())[:8].
var NewGoalID = func() string {
b := make([]byte, 16)
if _, err := rand.Read(b); err != nil {
panic(err)
}
b[6] = (b[6] & 0x0f) | 0x40
b[8] = (b[8] & 0x3f) | 0x80
s := fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:16])
return s[:8]
}
// HomeDir — os.path.expanduser("~") seam (default storage path).
var HomeDir = func() string {
h, err := os.UserHomeDir()
if err != nil {
return ""
}
return h
}
// LLMPost — urllib.request.urlopen seam: POSTs the payload, returns the
// response body. Any error triggers the keyword-verification fallback,
// exactly like the Python except-path.
var LLMPost = func(url string, payload []byte) ([]byte, error) {
req, err := http.NewRequest("POST", url, bytesReader(payload))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
client := &http.Client{Timeout: 30 * time.Second}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
buf := make([]byte, 0, 4096)
tmp := make([]byte, 4096)
for {
n, rerr := resp.Body.Read(tmp)
buf = append(buf, tmp[:n]...)
if rerr != nil {
break
}
}
return buf, nil
}
// ---------- GoalManager ----------
// GoalManager manages goals and stop conditions (goal.GoalManager).
type GoalManager struct {
StoragePath string
Goals *OrderedMap // goal_id -> *Goal, insertion-ordered
}
// NewGoalManager — GoalManager(storage_path=...).
func NewGoalManager(storagePath string) *GoalManager {
return &GoalManager{StoragePath: storagePath, Goals: NewOrderedMap()}
}
// Load — GoalManager._load: missing file and any parse/validation error are
// suppressed to an empty (or partially-filled) store, like the Python
// try/except around the whole loop.
func (m *GoalManager) Load() {
if _, err := os.Stat(m.StoragePath); err != nil {
return
}
data, err := os.ReadFile(m.StoragePath)
if err != nil {
return
}
dec := json.NewDecoder(bytesReader(data))
dec.UseNumber()
tok, err := dec.Token()
if err != nil {
return
}
if d, ok := tok.(json.Delim); !ok || d != '{' {
return // not an object — Python .items() AttributeError → suppressed
}
for dec.More() {
keyTok, err := dec.Token()
if err != nil {
return
}
key, _ := keyTok.(string)
val, err := decodeJSONValue(dec)
if err != nil {
return
}
goalData, ok := val.(*OrderedMap)
if !ok {
return // Goal(**goal_data) TypeError → loop aborts, prior kept
}
if _, ok := goalData.Get("goal_id"); !ok {
return // required field missing → TypeError → abort
}
if _, ok := goalData.Get("description"); !ok {
return
}
// unknown keys also raise TypeError(**kwargs) → abort mid-loop
for _, k := range goalData.Keys {
switch k {
case "goal_id", "description", "stop_conditions", "verification_method",
"status", "created_at", "completed_at", "evidence":
default:
return
}
}
m.Goals.Set(key, goalFromDict(goalData))
}
}
// Save — GoalManager._save: makedirs + json.dump(data, f, indent=2), no
// trailing newline, ensure_ascii escaping.
func (m *GoalManager) Save() {
os.MkdirAll(filepath.Dir(m.StoragePath), 0o755)
data := NewOrderedMap()
for _, kv := range m.Goals.Entries() {
data.Set(kv.Key, kv.Value.(*Goal).toDict())
}
os.WriteFile(m.StoragePath, []byte(encodePythonJSON(data, 0)), 0o644)
}
// SetGoal — GoalManager.set_goal.
func (m *GoalManager) SetGoal(description string, stopConditions []string, verificationMethod string) *Goal {
if stopConditions == nil {
stopConditions = []string{}
}
goal := &Goal{
GoalID: NewGoalID(),
Description: description,
StopConditions: stopConditions,
VerificationMethod: verificationMethod,
Status: "active",
CreatedAt: ClockNow(),
}
m.Goals.Set(goal.GoalID, goal)
m.Save()
return goal
}
// GetActiveGoal — GoalManager.get_active_goal.
func (m *GoalManager) GetActiveGoal() *Goal {
for _, kv := range m.Goals.Entries() {
g := kv.Value.(*Goal)
if g.Status == "active" {
return g
}
}
return nil
}
// CheckGoal — GoalManager.check_goal.
func (m *GoalManager) CheckGoal(evidence *string) *OrderedMap {
goal := m.GetActiveGoal()
if goal == nil {
return NewOrderedMap(
KV{"status", "no_goal"},
KV{"message", "No active goal set"},
)
}
if evidence != nil {
goal.Evidence = append(goal.Evidence, *evidence)
}
if len(goal.StopConditions) > 0 {
verification := m.VerifyWithLlm(goal)
if pyTruthy(omGet2(verification, "achieved")) {
goal.Status = "achieved"
now := ClockNow()
goal.CompletedAt = &now
goal.Evidence = append(goal.Evidence, "LLM verification: "+pyStr(dictDefault(verification, "reason", "")))
m.Save()
return NewOrderedMap(
KV{"status", "achieved"},
KV{"message", dictDefault(verification, "reason", "Goal achieved")},
KV{"goal", goal.toDict()},
KV{"verification", verification},
)
}
return NewOrderedMap(
KV{"status", "in_progress"},
KV{"message", dictDefault(verification, "reason", "Goal not yet achieved")},
KV{"goal", goal.toDict()},
KV{"verification", verification},
)
}
met := 0
for _, c := range goal.StopConditions {
for _, e := range goal.Evidence {
if strings.Contains(strings.ToLower(e), strings.ToLower(c)) {
met++
break
}
}
}
return NewOrderedMap(
KV{"status", "in_progress"},
KV{"message", "Goal not yet achieved"},
KV{"goal", goal.toDict()},
KV{"conditions_met", met},
KV{"conditions_total", len(goal.StopConditions)},
)
}
// VerifyWithLlm — GoalManager._verify_with_llm: builds the judge prompt,
// POSTs it, extracts the JSON verdict, and falls back to
// VerifyWithKeywords on any parse or transport failure.
func (m *GoalManager) VerifyWithLlm(goal *Goal) *OrderedMap {
ollamaURL := os.Getenv("OLLAMA_URL")
if ollamaURL == "" {
ollamaURL = "http://localhost:11434"
}
verifyModel := os.Getenv("RUNE_VERIFY_MODEL")
if verifyModel == "" {
verifyModel = "qwen2.5:3b"
}
var sb strings.Builder
sb.WriteString("You are a goal verification judge. Your job is to determine if a goal has been achieved based on the evidence provided.\n\n")
sb.WriteString("GOAL: " + goal.Description + "\n\n")
sb.WriteString("STOP CONDITIONS:\n")
for i, c := range goal.StopConditions {
if i > 0 {
sb.WriteString("\n")
}
sb.WriteString("- " + c)
}
sb.WriteString("\n\nEVIDENCE:\n")
if len(goal.Evidence) == 0 {
sb.WriteString("No evidence provided yet.")
} else {
for i, e := range goal.Evidence {
if i > 0 {
sb.WriteString("\n")
}
sb.WriteString("- " + e)
}
}
sb.WriteString("\n\nBased on the goal, stop conditions, and evidence, determine:\n")
sb.WriteString("1. Has the goal been achieved? (yes/no)\n")
sb.WriteString("2. What is your reasoning?\n")
sb.WriteString("3. Which conditions are met and which are not?\n\n")
sb.WriteString(`Respond in JSON format:
{"achieved": true/false, "reason": "your reasoning", "conditions_met": ["condition1"], "conditions_unmet": ["condition2"]}`)
prompt := sb.String()
payload := map[string]any{
"model": verifyModel,
"prompt": prompt,
"stream": false,
"options": map[string]any{
"temperature": 0.1,
"num_predict": 200,
"num_ctx": CtxBudget(verifyModel),
},
}
body, err := json.Marshal(payload)
if err != nil {
return m.VerifyWithKeywords(goal)
}
lastRequest.Store(string(body))
respBody, err := LLMPost(ollamaURL+"/api/generate", body)
if err != nil {
return m.VerifyWithKeywords(goal)
}
var result map[string]any
if err := json.Unmarshal(respBody, &result); err != nil {
return m.VerifyWithKeywords(goal)
}
response, ok := result["response"].(string)
if !ok {
return m.VerifyWithKeywords(goal) // .strip() on non-str raises → fallback
}
response = strings.TrimSpace(response)
re := regexp.MustCompile(`(?s)\{.*\}`)
match := re.FindString(response)
if match == "" {
return m.VerifyWithKeywords(goal)
}
var verification map[string]any
if err := json.Unmarshal([]byte(match), &verification); err != nil {
return m.VerifyWithKeywords(goal)
}
return OrderedMapFromMap(verification)
}
// lastRequest records the most recent LLM payload (truth battery captures
// the request the Python oracle sent).
var lastRequest atomicString
// lastURL records the most recent LLM endpoint URL (truth battery capture).
var lastURL atomicString
// VerifyWithKeywords — GoalManager._verify_with_keywords.
func (m *GoalManager) VerifyWithKeywords(goal *Goal) *OrderedMap {
if len(goal.StopConditions) == 0 {
return NewOrderedMap(
KV{"achieved", false},
KV{"reason", "No stop conditions defined"},
)
}
met := []any{}
unmet := []any{}
for _, condition := range goal.StopConditions {
condMet := false
for _, ev := range goal.Evidence {
if strings.Contains(strings.ToLower(ev), strings.ToLower(condition)) {
condMet = true
break
}
}
if condMet {
met = append(met, condition)
} else {
unmet = append(unmet, condition)
}
}
achieved := len(unmet) == 0
reason := fmt.Sprintf("Unmet: %s", strings.Join(toStrings(unmet), ", "))
if achieved {
reason = "All conditions met"
}
return NewOrderedMap(
KV{"achieved", achieved},
KV{"reason", reason},
KV{"conditions_met", met},
KV{"conditions_unmet", unmet},
)
}
// AchieveGoal — GoalManager.achieve_goal.
func (m *GoalManager) AchieveGoal(evidence *string) *OrderedMap {
goal := m.GetActiveGoal()
if goal == nil {
return NewOrderedMap(
KV{"status", "no_goal"},
KV{"message", "No active goal set"},
)
}
if evidence != nil {
goal.Evidence = append(goal.Evidence, *evidence)
}
goal.Status = "achieved"
now := ClockNow()
goal.CompletedAt = &now
m.Save()
return NewOrderedMap(
KV{"status", "achieved"},
KV{"message", "Goal marked as achieved"},
KV{"goal", goal.toDict()},
)
}
// FailGoal — GoalManager.fail_goal.
func (m *GoalManager) FailGoal(reason *string) *OrderedMap {
goal := m.GetActiveGoal()
if goal == nil {
return NewOrderedMap(
KV{"status", "no_goal"},
KV{"message", "No active goal set"},
)
}
goal.Status = "failed"
if reason != nil {
goal.Evidence = append(goal.Evidence, "Failed: "+*reason)
}
m.Save()
return NewOrderedMap(
KV{"status", "failed"},
KV{"message", "Goal marked as failed"},
KV{"goal", goal.toDict()},
)
}
// ClearGoal — GoalManager.clear_goal.
func (m *GoalManager) ClearGoal() *OrderedMap {
goal := m.GetActiveGoal()
if goal == nil {
return NewOrderedMap(
KV{"status", "no_goal"},
KV{"message", "No active goal set"},
)
}
goal.Status = "abandoned"
m.Save()
return NewOrderedMap(
KV{"status", "abandoned"},
KV{"message", "Goal cleared"},
)
}
// ListGoals — GoalManager.list_goals.
func (m *GoalManager) ListGoals() []any {
out := []any{}
for _, kv := range m.Goals.Entries() {
out = append(out, kv.Value.(*Goal).toDict())
}
return out
}
// ---------- module-level surface (global manager singleton) ----------
var (
managerMu sync.Mutex
globalManager *GoalManager
)
// getGoalManager — module get_goal_manager: lazily-built global manager on
// the default storage path (~/.rune/goals.json).
func getGoalManager() *GoalManager {
managerMu.Lock()
defer managerMu.Unlock()
if globalManager == nil {
globalManager = NewGoalManager(filepath.Join(HomeDir(), ".rune", "goals.json"))
globalManager.Load()
}
return globalManager
}
// setGoal — module set_goal.
func setGoal(description string, stopConditions []string, verificationMethod string) *OrderedMap {
return getGoalManager().SetGoal(description, stopConditions, verificationMethod).toDict()
}
// checkGoal — module check_goal.
func checkGoal(evidence *string) *OrderedMap {
return getGoalManager().CheckGoal(evidence)
}
// achieveGoal — module achieve_goal.
func achieveGoal(evidence *string) *OrderedMap {
return getGoalManager().AchieveGoal(evidence)
}
// clearGoal — module clear_goal.
func clearGoal() *OrderedMap {
return getGoalManager().ClearGoal()
}
// listGoals — module list_goals.
func listGoals() []any {
return getGoalManager().ListGoals()
}
// ---------- _ctx_budget: model-aware num_ctx ----------
// defaultProfiles — frozen snapshot of model_registry.DEFAULT_PROFILES
// (id -> context_window, local): the only profile fields resolve_context_budget
// consults for _ctx_budget(model_id). Pinned by the truth battery.
var defaultProfiles = map[string]struct {
window int64
local bool
}{
"deterministic": {0, true},
"qwen2.5:3b": {32768, true},
"qwen3:14b": {32768, true},
"qwen3.6:27b": {32768, true},
"mimo-v2.5-pro": {128000, false},
}
// outputReserve — model_registry.OUTPUT_RESERVE (task_kind defaults to
// "default" in _ctx_budget).
var outputReserve = map[string]int64{
"default": 2048,
"verify": 512,
"summarize": 1024,
"embed": 0,
}
const toolResultReserve = 1024
// CtxBudget — _ctx_budget(model_id): resolve_context_budget(model_id,
// task_kind="default")["num_ctx"] against the frozen default registry.
func CtxBudget(modelID string) int64 {
window := int64(8192)
local := true
if p, ok := defaultProfiles[modelID]; ok {
window = p.window
local = p.local
}
if window == 0 {
window = 8192
}
reserve := outputReserve["default"]
toolReserve := int64(0)
if !local {
toolReserve = toolResultReserve
}
numCtx := window - reserve - toolReserve
if numCtx < 1024 {
numCtx = 1024
}
return numCtx
}
// ---------- Python semantics helpers ----------
// pyTruthy — Python bool(v) for the JSON-decoded verdict "achieved".
func pyTruthy(v any) bool {
switch t := v.(type) {
case nil:
return false
case bool:
return t
case float64:
return t != 0
case json.Number:
f, _ := t.Float64()
return f != 0
case string:
return t != ""
case []any:
return len(t) > 0
case *OrderedMap:
return len(t.Keys) > 0
case map[string]any:
return len(t) > 0
default:
return true
}
}
// pyStr — Python str() for f-string interpolation of verdict fields.
func pyStr(v any) string {
switch t := v.(type) {
case nil:
return "None"
case bool:
if t {
return "True"
}
return "False"
case string:
return t
case float64:
if t == float64(int64(t)) {
return fmt.Sprintf("%d", int64(t))
}
return fmt.Sprintf("%v", t)
default:
return fmt.Sprintf("%v", t)
}
}
// pyStrOr — dict.get(key, default) with str() semantics for f-strings.
func pyStrOr(m *OrderedMap, key, def string) string {
v, ok := m.Get(key)
if !ok || v == nil {
if !ok {
return def
}
return pyStr(v)
}
return pyStr(v)
}
// dictDefault — dict.get(key, default) value-preserving (None stays None).
func dictDefault(m *OrderedMap, key string, def any) any {
if v, ok := m.Get(key); ok {
return v
}
return def
}
func toAnySlice(s []string) []any {
out := make([]any, len(s))
for i, v := range s {
out[i] = v
}
return out
}
func toStrings(s []any) []string {
out := make([]string, len(s))
for i, v := range s {
out[i], _ = v.(string)
}
return out
}
func stringSlice(v any) []string {
arr, ok := v.([]any)
if !ok {
return nil
}
return toStrings(arr)
}
// atomicString — tiny mutex-guarded string cell.
type atomicString struct {
mu sync.Mutex
v string
}
func (a *atomicString) Store(s string) {
a.mu.Lock()
a.v = s
a.mu.Unlock()
}
func (a *atomicString) Load() string {
a.mu.Lock()
defer a.mu.Unlock()
return a.v
}
// omGet2 returns the value at key (nil when absent).
func omGet2(om *OrderedMap, key string) any {
v, _ := om.Get(key)
return v
}
+92
View File
@@ -0,0 +1,92 @@
// Entry point: CLI (goal.py main() port) + truth replay subcommand for the
// HARD CI parity gate.
package main
import (
"crypto/rand"
"encoding/json"
"fmt"
"net/http"
"os"
"strings"
"time"
)
// default seams (goal.go swaps these per truth row).
var (
defaultClockNow = func() string {
return time.Now().UTC().Format("2006-01-02T15:04:05.000000") + "+00:00"
}
defaultNewGoalID = func() string {
b := make([]byte, 16)
if _, err := rand.Read(b); err != nil {
panic(err)
}
b[6] = (b[6] & 0x0f) | 0x40
b[8] = (b[8] & 0x3f) | 0x80
s := fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:16])
return s[:8]
}
defaultHomeDir = func() string {
h, err := os.UserHomeDir()
if err != nil {
return ""
}
return h
}
defaultLLMPost = func(url string, payload []byte) ([]byte, error) {
req, err := http.NewRequest("POST", url, strings.NewReader(string(payload)))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
client := &http.Client{Timeout: 30 * time.Second}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
buf := make([]byte, 0, 4096)
tmp := make([]byte, 4096)
for {
n, rerr := resp.Body.Read(tmp)
buf = append(buf, tmp[:n]...)
if rerr != nil {
break
}
}
return buf, nil
}
)
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())
return
}
RunCLI(os.Args, os.Stdout)
}
func truthRowCount() int {
raw, err := os.ReadFile("truth.json")
if err != nil {
return -1
}
var doc TruthDoc
if err := json.Unmarshal(raw, &doc); err != nil {
return -1
}
return len(doc.Rows)
}
+255
View File
@@ -0,0 +1,255 @@
// Python json interop: insertion-ordered maps and json.dumps-compatible
// encoding (indent, ensure_ascii) so _save/_load and CLI output are
// byte-parity with the oracle.
package main
import (
"encoding/json"
"fmt"
"io"
"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 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(" ")
}
}
// pythonFloat renders a float like Python repr for the values in play.
func pythonFloat(f float64) string {
if f == float64(int64(f)) {
return strconv.FormatInt(int64(f), 10)
}
return strconv.FormatFloat(f, 'g', -1, 64)
}
// 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()
}
// bytesReader — small io.Reader helper for byte slices.
func bytesReader(b []byte) io.Reader {
return strings.NewReader(string(b))
}
+488
View File
@@ -0,0 +1,488 @@
// Replay — the frozen-truth parity engine. Reads truth.json (generated by
// /tmp/gen_goal_truth.py against the live Python oracle) and re-executes
// every row through this Go port, comparing results. Exact-string rows
// (save file content, CLI stdout) compare byte-for-byte; everything else
// compares canonically (sorted-key JSON).
package main
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"sort"
"strings"
)
// TruthDoc — the frozen truth file structure.
type TruthDoc struct {
FrozenTS string `json:"frozen_ts"`
FrozenID string `json:"frozen_id"`
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 {
got, exact, err := execRow(&doc, &row)
if err != nil {
fails = append(fails, fmt.Sprintf("%s: error: %v", row.Name, err))
continue
}
var wantStr, gotStr string
if exact {
var w string
if err := json.Unmarshal(row.Want, &w); err != nil {
fails = append(fails, fmt.Sprintf("%s: want not a string: %v", row.Name, err))
continue
}
wantStr, gotStr = w, got.(string)
} else {
wantStr, err = canonicalFromRaw(row.Want)
if err != nil {
fails = append(fails, fmt.Sprintf("%s: canonicalize want: %v", row.Name, err))
continue
}
gotStr, err = canonicalFromRaw([]byte(mustJSON(got)))
if err != nil {
fails = append(fails, fmt.Sprintf("%s: canonicalize got: %v", row.Name, err))
continue
}
}
if wantStr != gotStr {
fails = append(fails, fmt.Sprintf("%s:\n want: %s\n got: %s", row.Name, wantStr, gotStr))
}
}
return fails
}
// 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(strings.NewReader(string(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
defer func() {
ClockNow = defaultClockNow
NewGoalID = defaultNewGoalID
HomeDir = defaultHomeDir
LLMPost = defaultLLMPost
}()
ClockNow = func() string { return doc.FrozenTS }
NewGoalID = func() string { return doc.FrozenID }
name := row.Name
switch {
case name == "to_dict:full" || name == "to_dict:achieved":
g := goalFromDict(asOrdered(omGet(setup, "goal")))
return g.toDict(), false, nil
case strings.HasPrefix(name, "ctx_budget:"):
mid, _ := setup.Get2("model_id").(string)
return CtxBudget(mid), false, nil
case strings.HasPrefix(name, "load:"):
path := filepath.Join(os.TempDir(), "goal_truth_load_test.json")
fileV, hasFile := setup.Get("file")
if hasFile && fileV != nil {
content, _ := fileV.(string)
os.WriteFile(path, []byte(content), 0o644)
} else {
os.Remove(path)
}
defer os.Remove(path)
m := NewGoalManager(path)
m.Load()
out := NewOrderedMap()
for _, kv := range m.Goals.Entries() {
c := canonical(kv.Value.(*Goal).toDict())
out.Set(kv.Key, c)
}
return out, false, nil
case strings.HasPrefix(name, "save:"):
path := filepath.Join(os.TempDir(), "goal_truth_save_test.json")
defer os.Remove(path)
m := NewGoalManager(path)
goals := asOrdered(omGet(setup, "goals"))
for _, kv := range goals.Entries() {
m.Goals.Set(kv.Key, goalFromDict(asOrdered(kv.Value)))
}
m.Save()
data, err := os.ReadFile(path)
if err != nil {
return "", true, err
}
return string(data), true, nil
case strings.HasPrefix(name, "verify_kw:"):
g := goalFromDict(asOrdered(omGet(setup, "goal")))
m := NewGoalManager(filepath.Join(os.TempDir(), "goal_truth_unused.json"))
return m.VerifyWithKeywords(g), false, nil
case strings.HasPrefix(name, "set_goal:"):
m := NewGoalManager(filepath.Join(os.TempDir(), "goal_truth_unused.json"))
desc, _ := setup.Get2("description").(string)
method, _ := setup.Get2("verification_method").(string)
conds := stringSlice(omGet(setup, "stop_conditions"))
return m.SetGoal(desc, conds, method).toDict(), false, nil
case strings.HasPrefix(name, "active:"):
m := NewGoalManager(filepath.Join(os.TempDir(), "goal_truth_unused.json"))
fillStore(m, setup)
g := m.GetActiveGoal()
if g == nil {
return nil, false, nil
}
return g.toDict(), false, nil
case strings.HasPrefix(name, "check_llm:"):
m := NewGoalManager(filepath.Join(os.TempDir(), "goal_truth_unused.json"))
fillStoreWith(m, setup, "goal")
var evidence *string
if ev, ok := setup.Get2("evidence").(string); ok {
evidence = &ev
}
resp := asOrdered(setup.Get2("response"))
if errMsgV, ok := resp.Get("error"); ok {
LLMPost = func(url string, payload []byte) ([]byte, error) {
lastRequest.Store(string(payload))
lastURL.Store(url)
return nil, fmt.Errorf("%v", errMsgV)
}
} else {
body, _ := resp.Get2("body").(string)
LLMPost = func(url string, payload []byte) ([]byte, error) {
lastRequest.Store(string(payload))
lastURL.Store(url)
return []byte(body), nil
}
}
result := m.CheckGoal(evidence)
out := NewOrderedMap()
for _, kv := range result.Entries() {
out.Set(kv.Key, kv.Value)
}
var req any
if raw := lastRequest.Load(); raw != "" {
json.Unmarshal([]byte(raw), &req)
}
out.Set("captured", NewOrderedMap(
KV{"llm_request", req},
KV{"llm_url", lastURL.Load()},
))
return out, false, nil
case strings.HasPrefix(name, "check:"):
m := NewGoalManager(filepath.Join(os.TempDir(), "goal_truth_unused.json"))
fillStoreWith(m, setup, "goal")
var evidence *string
if ev, ok := setup.Get("evidence"); ok {
if s, ok2 := ev.(string); ok2 {
evidence = &s
}
}
return m.CheckGoal(evidence), false, nil
case strings.HasPrefix(name, "check_manual:"):
m := NewGoalManager(filepath.Join(os.TempDir(), "goal_truth_unused.json"))
fillStoreWith(m, setup, "goal")
var evidence *string
if ev, ok := setup.Get("evidence"); ok {
if s, ok2 := ev.(string); ok2 {
evidence = &s
}
}
return m.CheckGoal(evidence), false, nil
case strings.HasPrefix(name, "achieve:"):
m := NewGoalManager(filepath.Join(os.TempDir(), "goal_truth_unused.json"))
fillStoreWith(m, setup, "goal")
var evidence *string
if ev, ok := setup.Get("evidence"); ok {
if s, ok2 := ev.(string); ok2 {
evidence = &s
}
}
return m.AchieveGoal(evidence), false, nil
case strings.HasPrefix(name, "fail:"):
m := NewGoalManager(filepath.Join(os.TempDir(), "goal_truth_unused.json"))
fillStoreWith(m, setup, "goal")
var reason *string
if r, ok := setup.Get("reason"); ok {
if s, ok2 := r.(string); ok2 {
reason = &s
}
}
return m.FailGoal(reason), false, nil
case strings.HasPrefix(name, "clear:"):
m := NewGoalManager(filepath.Join(os.TempDir(), "goal_truth_unused.json"))
fillStoreWith(m, setup, "goal")
return m.ClearGoal(), false, nil
case strings.HasPrefix(name, "list:"):
m := NewGoalManager(filepath.Join(os.TempDir(), "goal_truth_unused.json"))
fillStore(m, setup)
return m.ListGoals(), false, nil
case strings.HasPrefix(name, "mod:"):
tmpStore := filepath.Join(os.TempDir(), "goal_truth_mod_store.json")
os.Remove(tmpStore)
m := NewGoalManager(tmpStore)
managerMu.Lock()
globalManager = m
managerMu.Unlock()
defer func() {
managerMu.Lock()
globalManager = nil
managerMu.Unlock()
os.Remove(tmpStore)
}()
switch name {
case "mod:set_goal":
desc, _ := setup.Get2("description").(string)
conds := stringSlice(omGet(setup, "stop_conditions"))
method, _ := setup.Get2("verification_method").(string)
return setGoal(desc, conds, method), false, nil
case "mod:check_no_goal":
return checkGoal(nil), false, nil
case "mod:achieve_no_goal":
var ev *string
if s, ok := setup.Get2("evidence").(string); ok {
ev = &s
}
return achieveGoal(ev), false, nil
case "mod:clear_no_goal":
return clearGoal(), false, nil
case "mod:list_empty":
return listGoals(), false, nil
}
return nil, false, fmt.Errorf("unknown mod row %s", name)
case strings.HasPrefix(name, "cli:"):
argvAny, _ := setup.Get2("argv").([]any)
argv := []string{"goal.py"}
for _, a := range argvAny {
s, _ := a.(string)
argv = append(argv, s)
}
tmpStore := filepath.Join(os.TempDir(), "goal_truth_cli_store.json")
os.Remove(tmpStore)
m := NewGoalManager(tmpStore)
fillStore(m, setup)
managerMu.Lock()
globalManager = m
managerMu.Unlock()
defer func() {
managerMu.Lock()
globalManager = nil
managerMu.Unlock()
os.Remove(tmpStore)
}()
var sb strings.Builder
RunCLI(argv, &sb)
return sb.String(), true, nil
case name == "singleton:default_path":
// seed the home seam from the frozen path so the row is
// environment-independent; the assertion still checks the
// singleton identity and the expanduser-joined path shape
wantPath := ""
var want map[string]any
json.Unmarshal(row.Want, &want)
if p, ok := want["path"].(string); ok {
wantPath = p
}
HomeDir = func() string {
return strings.TrimSuffix(wantPath, filepath.Join(".rune", "goals.json"))
}
managerMu.Lock()
globalManager = nil
managerMu.Unlock()
defer func() {
managerMu.Lock()
globalManager = nil
managerMu.Unlock()
}()
m1 := getGoalManager()
m2 := getGoalManager()
return NewOrderedMap(
KV{"same", m1 == m2},
KV{"path", m1.StoragePath},
), false, nil
}
return nil, false, fmt.Errorf("unknown row %s", name)
}
// fillStore populates m from setup["store"] (goal_id -> goal dict).
func fillStore(m *GoalManager, setup *OrderedMap) {
storeV, ok := setup.Get("store")
if !ok || storeV == nil {
return
}
store := asOrdered(storeV)
for _, kv := range store.Entries() {
m.Goals.Set(kv.Key, goalFromDict(asOrdered(kv.Value)))
}
}
// fillStoreWith handles rows that carry a single "goal" dict (stored under
// k1 like the oracle harness did) or a "store" map.
func fillStoreWith(m *GoalManager, setup *OrderedMap, key string) {
if g, ok := setup.Get(key); ok && g != nil {
m.Goals.Set("k1", goalFromDict(asOrdered(g)))
return
}
fillStore(m, setup)
}
func asOrdered(v any) *OrderedMap {
switch t := v.(type) {
case *OrderedMap:
return t
case map[string]any:
return OrderedMapFromMap(t)
default:
return NewOrderedMap()
}
}
// canonicalFromRaw — canonical string of a raw JSON document.
func canonicalFromRaw(raw []byte) (string, error) {
var v any
dec := json.NewDecoder(strings.NewReader(string(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, ", ") + "}"
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
}
}
// omGet returns the value at key (nil when absent).
func omGet(om *OrderedMap, key string) any {
v, _ := om.Get(key)
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
+31
View File
@@ -0,0 +1,31 @@
// Frozen-truth battery: replays every row generated from the live Python
// oracle (src/rune/goal.py) through this Go port. 100% match required.
package main
import (
"fmt"
"testing"
)
func TestTruthParity(t *testing.T) {
fails := RunTruth("truth.json")
if len(fails) > 0 {
for _, f := range fails {
t.Error(f)
}
return
}
rows := truthRowCount()
if rows <= 0 {
t.Fatalf("truth.json unreadable or empty")
}
t.Logf("PARITY OK: %d truth rows exact-equal vs the Python oracle", rows)
}
func TestTruthRowCount(t *testing.T) {
rows := truthRowCount()
if rows < 59 {
t.Fatalf("expected >= 59 truth rows, got %d", rows)
}
fmt.Printf("truth rows: %d\n", rows)
}
+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.196.0"
__version__ = "0.199.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
+1 -1
View File
@@ -233,7 +233,7 @@ def main() -> int:
)
check(
"Rune minor version advertises provisioning and restore verification",
'__version__ = "0.196.0"' in rune_version,
'__version__ = "0.199.0"' in rune_version,
failures,
)
# #169 policy update: auto-bump on merge is sanctioned (Nick directive:
+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.196.0
Version: v0.199.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.196.0 | Textual TUI | {len(loaded)} native modules[/dim]",
f"[dim]Rune v0.199.0 | Textual TUI | {len(loaded)} native modules[/dim]",
)
# Auto-show night mode suggestion if evening/night hours
+86 -8
View File
@@ -115,11 +115,22 @@ def now() -> str:
def _sorted(obj):
"""Deterministic normalization: sort everything sortable, recursively."""
"""Deterministic normalization: sort everything sortable, recursively.
Dichotomy wave (issue #2479): delegates to the Rust core when built;
RUNE_SPINE_CORE=python pins the Python oracle below (strangler
fallback, parity authority)."""
core = _rust_core()
if core is not None:
return core.sorted_kernel(obj)
return _py_sorted(obj)
def _py_sorted(obj):
"""Python oracle for _sorted (strangler fallback)."""
if isinstance(obj, dict):
return {k: _sorted(obj[k]) for k in sorted(obj)}
return {k: _py_sorted(obj[k]) for k in sorted(obj)}
if isinstance(obj, list):
return sorted((_sorted(v) for v in obj), key=lambda v: json.dumps(v, sort_keys=True))
return sorted((_py_sorted(v) for v in obj), key=lambda v: json.dumps(v, sort_keys=True))
return obj
@@ -137,13 +148,21 @@ def load(path: Path | None = None) -> dict | None:
def save(doc: dict, path: Path | str | None = None) -> Path:
"""Canonical store write (dichotomy wave: the serialization core is
Rust; schema validation raises ValueError here — the caller contract).
RUNE_SPINE_CORE=python pins the Python oracle (strangler fallback)."""
incoming = doc.get("schema_version", SCHEMA_VERSION)
if incoming != SCHEMA_VERSION:
raise ValueError(f"schema_version {incoming} != {SCHEMA_VERSION}")
doc["schema_version"] = SCHEMA_VERSION
p = Path(path) if path else CURRENT # str paths are legitimate (CLI callers)
core = _rust_core()
if core is not None:
text = core.save_text(doc)
else:
text = json.dumps(_py_sorted(doc), indent=1, sort_keys=True) + "\n"
p.parent.mkdir(parents=True, exist_ok=True)
p.write_text(json.dumps(_sorted(doc), indent=1, sort_keys=True) + "\n")
p.write_text(text)
return p
@@ -195,11 +214,21 @@ WORKFLOWS_DIR = ROOT / ".gitea" / "workflows"
def _declared_surfaces() -> dict:
"""Umbrella-declared non-module surfaces: rust crates, Go surfaces,
configs. The manifest (config/umbrellas.json) is the declaration
authority — same law as modules, new kinds."""
authority — same law as modules, new kinds. Dichotomy wave: the file
read stays here (IO law), the manifest parse delegates to the Rust
core when built (RUNE_SPINE_CORE=python pins the Python oracle)."""
try:
manifest = json.loads((ROOT / "config" / "umbrellas.json").read_text())
except (OSError, json.JSONDecodeError):
return {"rust_crates": [], "go_surfaces": [], "configs": []}
core = _rust_core()
if core is not None:
return core.declared_surfaces(manifest)
return _py_declared_surfaces(manifest)
def _py_declared_surfaces(manifest: dict) -> dict:
"""Python oracle for the _declared_surfaces parse core (fallback)."""
out = {"rust_crates": [], "go_surfaces": [], "configs": []}
for name, u in manifest.get("umbrellas", {}).items():
for crate in u.get("rust_crates", []):
@@ -222,10 +251,23 @@ def project_meta_surfaces(doc: dict, inputs: dict | None = None) -> int:
H3-spine: when `inputs` is given (the gather-driven path), every
filesystem probe comes from the gathered dict so the Rust core and the
Python fallback consume byte-identical scans. The no-inputs path gathers
on the spot (backward compatibility for direct callers)."""
count = 0
on the spot (backward compatibility for direct callers). Dichotomy
wave: the core delegates to Rust when built (RUNE_SPINE_CORE=python
pins the Python oracle); the doc is mutated in place either way."""
if inputs is None:
inputs = _gather_meta_inputs()
core = _rust_core()
if core is not None:
out = core.project_meta_surfaces(doc, inputs, now())
doc.clear()
doc.update(out["doc"]) # preserve the in-place mutation contract
return out["count"]
return _py_project_meta_surfaces(doc, inputs)
def _py_project_meta_surfaces(doc: dict, inputs: dict) -> int:
"""Python oracle for the project_meta_surfaces core (fallback)."""
count = 0
manifest = inputs.get("config_manifest") or {}
declared = {"rust_crates": [], "go_surfaces": [], "configs": []}
for name, u in manifest.get("umbrellas", {}).items():
@@ -472,7 +514,27 @@ def _py_build_from_inputs(inputs: dict) -> dict:
def _ingest_umbrellas_data(doc: dict, data: dict) -> int:
"""The original ingest_umbrellas with the file read lifted out (H3-spine
gather boundary). Raises ValueError on a Spine-generated view, exactly
like the file-driven original."""
like the file-driven original. Dichotomy wave: the happy path delegates
to the Rust core when built (RUNE_SPINE_CORE=python pins the Python
oracle); the refusal check stays here — ValueError is the caller
contract and the core carries the identical message (battery-pinned)."""
if GENERATED_VIEW_MARKER in data:
raise ValueError(
"the manifest is a Spine-generated compatibility view; refusing to "
"ingest it (Phase A reads the manual authority exactly once). "
"Structural membership now lives in the Spine store."
)
core = _rust_core()
if core is not None:
out = core.ingest_umbrellas_data(doc, data, now())
doc.clear()
doc.update(out["doc"]) # preserve the in-place mutation contract
return out["count"]
return _py_ingest_umbrellas_data(doc, data)
def _py_ingest_umbrellas_data(doc: dict, data: dict) -> int:
"""Python oracle for the ingest core (strangler fallback)."""
if GENERATED_VIEW_MARKER in data:
raise ValueError(
"the manifest is a Spine-generated compatibility view; refusing to "
@@ -706,6 +768,22 @@ REL_STATUS = {"UNKNOWN", "CURRENT", "STALE", "CONFLICTING", "DEGRADED"}
def entity(doc: dict, id: str) -> dict | None:
"""Resolve an entity (rename-resilient via aliases). Dichotomy wave:
delegates to the Rust core when built; RUNE_SPINE_CORE=python pins the
Python oracle below (strangler fallback, parity authority). The core
owns the RESOLUTION verdict; the facade returns the LIVE dict so the
pre-port mutation-through-entity contract is preserved (JSON FFI gives
a detached copy — callers mutate docs through this reference)."""
core = _rust_core()
if core is not None:
if core.entity(doc, id) is None:
return None
return _py_entity(doc, id)
return _py_entity(doc, id)
def _py_entity(doc: dict, id: str) -> dict | None:
"""Python oracle for entity (strangler fallback)."""
doc_ents = doc.get("entities", [])
for e in doc_ents:
if e["id"] == id:
+40 -1
View File
@@ -60,7 +60,12 @@ def _load() -> ctypes.CDLL:
lib = ctypes.CDLL(str(_LIB_PATH))
lib.sp_free_string.argtypes = [ctypes.c_void_p]
lib.sp_free_string.restype = None
for sym in ("sp_project", "sp_drift", "sp_query", "sp_export"):
for sym in (
"sp_project", "sp_drift", "sp_query", "sp_export",
# dichotomy-wave facade kernels (issue #2479 tranche)
"sf_sorted", "sf_save_text", "sf_entity", "sf_declared_surfaces",
"sf_project_meta_surfaces", "sf_ingest_umbrellas_data",
):
fn = getattr(lib, sym)
fn.argtypes = [ctypes.c_char_p]
fn.restype = ctypes.c_void_p # char* — never default c_int
@@ -103,3 +108,37 @@ def query(doc: dict, entity_id: str) -> dict:
def export_umbrellas(doc: dict, now: str) -> dict:
"""Legacy umbrella compatibility view (output-only)."""
return _call("sp_export", {"doc": doc, "now": now})
# ── dichotomy-wave facade kernels (issue #2479 tranche) ────────────────────
def sorted_kernel(v):
"""spine._sorted — recursive deterministic normalization."""
return _call("sf_sorted", {"v": v})
def save_text(doc: dict) -> str:
"""The canonical text spine.save writes (validation inside the core)."""
return _call("sf_save_text", {"doc": doc})
def entity(doc: dict, entity_id: str):
"""Rename-resilient entity resolution (None when absent)."""
return _call("sf_entity", {"doc": doc, "id": entity_id})
def declared_surfaces(config_manifest: dict) -> dict:
"""The manifest-parse core of spine._declared_surfaces."""
return _call("sf_declared_surfaces", {"config_manifest": config_manifest})
def project_meta_surfaces(doc: dict, inputs: dict, now: str) -> dict:
"""Meta-scaffold projector core. Returns {"count", "doc"} — the doc is
the mutated copy; the facade copies it back to preserve the in-place
contract."""
return _call("sf_project_meta_surfaces", {"doc": doc, "inputs": inputs, "now": now})
def ingest_umbrellas_data(doc: dict, data: dict, now: str) -> dict:
"""Umbrella-manifest ingest core. Returns {"count", "doc"}."""
return _call("sf_ingest_umbrellas_data", {"doc": doc, "data": data, "now": now})
@@ -0,0 +1,240 @@
//! branch_battery_facade.rs — spine dichotomy-wave parity battery (#2479).
//!
//! The frozen oracle truth (generated against the LIVE Python oracle,
//! src/rune/spine.py, with a frozen clock — rune.structural_store.now
//! pinned to 2026-10-11T09:00:00Z) is embedded VERBATIM below as
//! TRUTH_CASES. Every case is replayed against the Rust facade kernel:
//! value cases must match byte-for-byte under pyjson::dumps; error cases
//! must carry the oracle's EXACT error message.
//!
//! Regenerate the truth with the batch truth generator; never hand-edit
//! the embedded literals.
use crate::facade;
use crate::pyjson::{self, Json};
fn parse(s: &str) -> Json {
pyjson::parse(s).unwrap_or_else(|e| panic!("parse failed: {} :: {}", e, s))
}
fn replay(case: &Json) {
let fname = pyjson::Json::get(case, "fn")
.map(crate::store::as_str)
.unwrap_or("")
.to_string();
let name = pyjson::Json::get(case, "name")
.map(crate::store::as_str)
.unwrap_or("")
.to_string();
let args = pyjson::Json::get(case, "args").expect("case.args");
let result = match fname.as_str() {
"_sorted" => facade::sorted_fn(args),
"save" => facade::save_text(args),
"entity" => facade::entity_fn(args),
"_declared_surfaces" => facade::declared_surfaces(args),
"project_meta_surfaces" => facade::project_meta_surfaces_fn(args),
"_ingest_umbrellas_data" => facade::ingest_umbrellas_data_fn(args),
other => panic!("unknown fn in truth: {}", other),
};
let err_doc = match pyjson::Json::get(case, "error") {
Some(Json::Null) | None => None,
Some(e) => Some(e),
};
match (result, err_doc) {
(Ok(got), None) => {
let expect = pyjson::Json::get(case, "expect").expect("case.expect");
// Key-order-insensitive comparison: the truth file encodes the
// oracle's dicts with sort_keys=True (canonical JSON), the same
// normalization spine.save applies. sorted_deep both sides.
assert_eq!(
pyjson::dumps(&crate::store::sorted_deep(&got)),
pyjson::dumps(&crate::store::sorted_deep(expect)),
"case {}/{} parity",
fname,
name
);
}
(Err(err), Some(err_doc)) => {
let msg = pyjson::Json::get(err_doc, "message")
.map(crate::store::as_str)
.unwrap_or("");
assert_eq!(err, msg, "case {}/{} error text", fname, name);
}
(other, _) => panic!(
"case {}/{}: unexpected outcome: {:?}",
fname, name, other
),
}
}
const TRUTH_CASES: &[&str] = &[
r#"{"args": {"v": {"a": {"c": 3, "z": 2}, "b": 1}}, "error": null, "expect": {"a": {"c": 3, "z": 2}, "b": 1}, "fn": "_sorted", "name": "nested_dict"}"#,
r#"{"args": {"v": [{"a": 2, "b": 1}, {"a": 9}, {"a": 1, "b": 1}]}, "error": null, "expect": [{"a": 1, "b": 1}, {"a": 2, "b": 1}, {"a": 9}], "fn": "_sorted", "name": "list_of_dicts_by_json_repr"}"#,
r#"{"args": {"v": [3, "x", null, true, 1.5, "a"]}, "error": null, "expect": ["a", "x", 1.5, 3, null, true], "fn": "_sorted", "name": "mixed_scalars"}"#,
r#"{"args": {"v": {"b": false, "f": 2.5, "i": 7, "n": null, "s": "héllo"}}, "error": null, "expect": {"b": false, "f": 2.5, "i": 7, "n": null, "s": "héllo"}, "fn": "_sorted", "name": "scalars_passthrough"}"#,
r#"{"args": {"v": {"d": {}, "l": []}}, "error": null, "expect": {"d": {}, "l": []}, "fn": "_sorted", "name": "empty_containers"}"#,
r#"{"args": {"v": {"k": [{"a": 2, "z": 1}, {"b": []}]}}, "error": null, "expect": {"k": [{"a": 2, "z": 1}, {"b": []}]}, "fn": "_sorted", "name": "nested_list_in_dict"}"#,
r#"{"args": {"doc": {"entities": [{"evidence_refs": ["x"], "id": "module:zebra", "kind": "module", "maturity": "PRESENT", "observed_at": "2026-10-11T09:00:00Z", "tags": ["b", "a"]}, {"evidence_refs": [], "ext": {"note": "unknown stays"}, "id": "module:alpha", "identities": {"source": "src/rune/alpha.py"}, "kind": "module", "maturity": "DECLARED", "observed_at": "2026-10-11T09:00:00Z", "tags": []}], "generated_at": "2026-10-11T09:00:00Z", "observations": [], "relationships": [{"evidence_refs": ["m#one"], "from": "module:alpha", "observed_at": "2026-10-11T09:00:00Z", "to": "subsystem:one", "type": "belongs_to"}], "schema_version": 1}}, "error": null, "expect": "{\n \"entities\": [\n {\n \"evidence_refs\": [\n \"x\"\n ],\n \"id\": \"module:zebra\",\n \"kind\": \"module\",\n \"maturity\": \"PRESENT\",\n \"observed_at\": \"2026-10-11T09:00:00Z\",\n \"tags\": [\n \"a\",\n \"b\"\n ]\n },\n {\n \"evidence_refs\": [],\n \"ext\": {\n \"note\": \"unknown stays\"\n },\n \"id\": \"module:alpha\",\n \"identities\": {\n \"source\": \"src/rune/alpha.py\"\n },\n \"kind\": \"module\",\n \"maturity\": \"DECLARED\",\n \"observed_at\": \"2026-10-11T09:00:00Z\",\n \"tags\": []\n }\n ],\n \"generated_at\": \"2026-10-11T09:00:00Z\",\n \"observations\": [],\n \"relationships\": [\n {\n \"evidence_refs\": [\n \"m#one\"\n ],\n \"from\": \"module:alpha\",\n \"observed_at\": \"2026-10-11T09:00:00Z\",\n \"to\": \"subsystem:one\",\n \"type\": \"belongs_to\"\n }\n ],\n \"schema_version\": 1\n}\n", "fn": "save", "name": "valid_doc"}"#,
r#"{"args": {"doc": {"entities": [{"evidence_refs": ["x"], "id": "module:zebra", "kind": "module", "maturity": "PRESENT", "observed_at": "2026-10-11T09:00:00Z", "tags": ["b", "a"]}, {"evidence_refs": [], "ext": {"note": "unknown stays"}, "id": "module:alpha", "identities": {"source": "src/rune/alpha.py"}, "kind": "module", "maturity": "DECLARED", "observed_at": "2026-10-11T09:00:00Z", "tags": []}], "generated_at": "2026-10-11T09:00:00Z", "observations": [], "relationships": [{"evidence_refs": ["m#one"], "from": "module:alpha", "observed_at": "2026-10-11T09:00:00Z", "to": "subsystem:one", "type": "belongs_to"}], "schema_version": 1}}, "error": null, "expect": "{\n \"entities\": [\n {\n \"evidence_refs\": [\n \"x\"\n ],\n \"id\": \"module:zebra\",\n \"kind\": \"module\",\n \"maturity\": \"PRESENT\",\n \"observed_at\": \"2026-10-11T09:00:00Z\",\n \"tags\": [\n \"a\",\n \"b\"\n ]\n },\n {\n \"evidence_refs\": [],\n \"ext\": {\n \"note\": \"unknown stays\"\n },\n \"id\": \"module:alpha\",\n \"identities\": {\n \"source\": \"src/rune/alpha.py\"\n },\n \"kind\": \"module\",\n \"maturity\": \"DECLARED\",\n \"observed_at\": \"2026-10-11T09:00:00Z\",\n \"tags\": []\n }\n ],\n \"generated_at\": \"2026-10-11T09:00:00Z\",\n \"observations\": [],\n \"relationships\": [\n {\n \"evidence_refs\": [\n \"m#one\"\n ],\n \"from\": \"module:alpha\",\n \"observed_at\": \"2026-10-11T09:00:00Z\",\n \"to\": \"subsystem:one\",\n \"type\": \"belongs_to\"\n }\n ],\n \"schema_version\": 1\n}\n", "fn": "save", "name": "missing_schema_defaults"}"#,
r#"{"args": {"doc": {"entities": [{"evidence_refs": ["x"], "id": "module:zebra", "kind": "module", "maturity": "PRESENT", "observed_at": "2026-10-11T09:00:00Z", "tags": ["b", "a"]}, {"evidence_refs": [], "ext": {"note": "unknown stays"}, "id": "module:alpha", "identities": {"source": "src/rune/alpha.py"}, "kind": "module", "maturity": "DECLARED", "observed_at": "2026-10-11T09:00:00Z", "tags": []}], "generated_at": "2026-10-11T09:00:00Z", "observations": [], "relationships": [{"evidence_refs": ["m#one"], "from": "module:alpha", "observed_at": "2026-10-11T09:00:00Z", "to": "subsystem:one", "type": "belongs_to"}], "schema_version": 2}}, "error": {"message": "schema_version 2 != 1", "type": "ValueError"}, "expect": null, "fn": "save", "name": "future_schema_raises"}"#,
r#"{"args": {"doc": {"entities": [], "observations": [], "relationships": [], "schema_version": 1, "z_last": {"a": {}, "b": [2, 1]}}}, "error": null, "expect": "{\n \"entities\": [],\n \"observations\": [],\n \"relationships\": [],\n \"schema_version\": 1,\n \"z_last\": {\n \"a\": {},\n \"b\": [\n 1,\n 2\n ]\n }\n}\n", "fn": "save", "name": "empty_store_sorting"}"#,
r#"{"args": {"doc": {"adapter_creds": {"api_token": "kept-here", "note": "x"}, "entities": [], "observations": [], "relationships": [], "schema_version": 1}}, "error": null, "expect": "{\n \"adapter_creds\": {\n \"api_token\": \"kept-here\",\n \"note\": \"x\"\n },\n \"entities\": [],\n \"observations\": [],\n \"relationships\": [],\n \"schema_version\": 1\n}\n", "fn": "save", "name": "no_redaction_spine_surface"}"#,
r#"{"args": {"doc": {"entities": [{"aliases": ["module:m_old"], "id": "module:m_a", "kind": "module", "maturity": "WIRED"}, {"id": "capability:c1", "kind": "capability", "maturity": "DECLARED"}], "relationships": [], "schema_version": 1}, "id": "module:m_a"}, "error": null, "expect": {"aliases": ["module:m_old"], "id": "module:m_a", "kind": "module", "maturity": "WIRED"}, "fn": "entity", "name": "hit_by_id"}"#,
r#"{"args": {"doc": {"entities": [{"aliases": ["module:m_old"], "id": "module:m_a", "kind": "module", "maturity": "WIRED"}, {"id": "capability:c1", "kind": "capability", "maturity": "DECLARED"}], "relationships": [], "schema_version": 1}, "id": "module:m_old"}, "error": null, "expect": {"aliases": ["module:m_old"], "id": "module:m_a", "kind": "module", "maturity": "WIRED"}, "fn": "entity", "name": "hit_via_alias"}"#,
r#"{"args": {"doc": {"entities": [{"aliases": ["module:m_old"], "id": "module:m_a", "kind": "module", "maturity": "WIRED"}, {"id": "capability:c1", "kind": "capability", "maturity": "DECLARED"}], "relationships": [], "schema_version": 1}, "id": "module:ghost"}, "error": null, "expect": null, "fn": "entity", "name": "miss_is_none"}"#,
r#"{"args": {"doc": {"entities": [], "schema_version": 1}, "id": "x"}, "error": null, "expect": null, "fn": "entity", "name": "empty_entities"}"#,
r#"{"args": {"config_manifest": {"umbrellas": {"cognition": {"configs": ["go-version.txt", "phantom.toml"], "description": "thinking parts", "go_surfaces": ["rune-entry"], "modules": ["spine"], "rust_crates": ["rune_spine", "ghost_crate"]}, "governance": {"description": "meta"}}}}, "error": null, "expect": {"configs": [["go-version.txt", "cognition"], ["phantom.toml", "cognition"]], "go_surfaces": [["rune-entry", "cognition"]], "rust_crates": [["rune_spine", "cognition"], ["ghost_crate", "cognition"]]}, "fn": "_declared_surfaces", "name": "full_manifest"}"#,
r#"{"args": {"config_manifest": {"umbrellas": {}}}, "error": null, "expect": {"configs": [], "go_surfaces": [], "rust_crates": []}, "fn": "_declared_surfaces", "name": "empty_umbrellas"}"#,
r#"{"args": {"config_manifest": {"umbrellas": {"solo": {"description": "d"}}}}, "error": null, "expect": {"configs": [], "go_surfaces": [], "rust_crates": []}, "fn": "_declared_surfaces", "name": "no_kind_lists"}"#,
r#"{"args": {"doc": {"entities": [], "generated_at": "2026-10-11T09:00:00Z", "observations": [], "relationships": [], "schema_version": 1}, "inputs": {"config_files": ["go-version.txt"], "config_manifest": {"umbrellas": {"cognition": {"configs": ["go-version.txt", "phantom.toml"], "description": "thinking parts", "go_surfaces": ["rune-entry"], "modules": ["spine"], "rust_crates": ["rune_spine", "ghost_crate"]}, "governance": {"description": "meta"}}}, "crate_canaries": {"rune_spine": ["canary_spine_rust"]}, "crate_dirs": ["rune_spine"], "go_dirs": ["rune-entry"], "workflows": ["ci.yml", "pipeline-gate.yml"]}, "now": "2026-10-11T09:00:00Z"}, "error": null, "expect": {"count": 7, "doc": {"entities": [{"evidence_refs": ["umbrella-manifest#cognition"], "id": "rust-crate:rune_spine", "identities": {"source": "src/rust/rune_spine"}, "kind": "rust-crate", "maturity": "PRESENT", "observed_at": "2026-10-11T09:00:00Z", "tags": ["code"]}, {"evidence_refs": ["crate-scan:rune_spine"], "id": "module:canary_spine_rust", "identities": {"source": "src/rust/rune_spine/canary_spine_rust.py"}, "kind": "module", "maturity": "PRESENT", "observed_at": "2026-10-11T09:00:00Z", "tags": ["canary", "code"]}, {"evidence_refs": ["umbrella-manifest#cognition"], "id": "rust-crate:ghost_crate", "identities": {"source": "src/rust/ghost_crate"}, "kind": "rust-crate", "maturity": "DECLARED", "observed_at": "2026-10-11T09:00:00Z", "tags": ["code"]}, {"evidence_refs": ["umbrella-manifest#cognition"], "id": "go-surface:rune-entry", "identities": {"source": "src/go/rune-entry"}, "kind": "go-surface", "maturity": "PRESENT", "observed_at": "2026-10-11T09:00:00Z", "tags": ["code"]}, {"evidence_refs": ["umbrella-manifest#cognition"], "id": "config:go-version.txt", "identities": {"source": "go-version.txt"}, "kind": "config", "maturity": "PRESENT", "observed_at": "2026-10-11T09:00:00Z", "tags": []}, {"evidence_refs": ["umbrella-manifest#cognition"], "id": "config:phantom.toml", "identities": {"source": "phantom.toml"}, "kind": "config", "maturity": "DECLARED", "observed_at": "2026-10-11T09:00:00Z", "tags": []}, {"evidence_refs": ["workflow-scan"], "id": "workflow:ci.yml", "identities": {"source": ".gitea/workflows/ci.yml"}, "kind": "workflow", "maturity": "PRESENT", "observed_at": "2026-10-11T09:00:00Z", "tags": ["infra"]}, {"evidence_refs": ["workflow-scan"], "id": "workflow:pipeline-gate.yml", "identities": {"source": ".gitea/workflows/pipeline-gate.yml"}, "kind": "workflow", "maturity": "PRESENT", "observed_at": "2026-10-11T09:00:00Z", "tags": ["infra"]}], "generated_at": "2026-10-11T09:00:00Z", "observations": [], "relationships": [{"evidence_refs": ["umbrella-manifest#cognition"], "from": "rust-crate:rune_spine", "observed_at": "2026-10-11T09:00:00Z", "to": "subsystem:cognition", "type": "belongs_to"}, {"evidence_refs": ["crate-scan:rune_spine"], "from": "rust-crate:rune_spine", "observed_at": "2026-10-11T09:00:00Z", "to": "module:canary_spine_rust", "type": "obligated_canary"}, {"evidence_refs": ["umbrella-manifest#cognition"], "from": "rust-crate:ghost_crate", "observed_at": "2026-10-11T09:00:00Z", "to": "subsystem:cognition", "type": "belongs_to"}, {"evidence_refs": ["umbrella-manifest#cognition"], "from": "go-surface:rune-entry", "observed_at": "2026-10-11T09:00:00Z", "to": "subsystem:cognition", "type": "belongs_to"}, {"evidence_refs": ["umbrella-manifest#cognition"], "from": "config:go-version.txt", "observed_at": "2026-10-11T09:00:00Z", "to": "subsystem:cognition", "type": "belongs_to"}, {"evidence_refs": ["umbrella-manifest#cognition"], "from": "config:phantom.toml", "observed_at": "2026-10-11T09:00:00Z", "to": "subsystem:cognition", "type": "belongs_to"}, {"evidence_refs": ["workflow-scan"], "from": "workflow:ci.yml", "observed_at": "2026-10-11T09:00:00Z", "to": "subsystem:governance", "type": "belongs_to"}, {"evidence_refs": ["workflow-scan"], "from": "workflow:pipeline-gate.yml", "observed_at": "2026-10-11T09:00:00Z", "to": "subsystem:governance", "type": "belongs_to"}], "schema_version": 1}}, "fn": "project_meta_surfaces", "name": "full_inputs"}"#,
r#"{"args": {"doc": {"entities": [], "generated_at": "2026-10-11T09:00:00Z", "observations": [], "relationships": [], "schema_version": 1}, "inputs": {}, "now": "2026-10-11T09:00:00Z"}, "error": null, "expect": {"count": 0, "doc": {"entities": [], "generated_at": "2026-10-11T09:00:00Z", "observations": [], "relationships": [], "schema_version": 1}}, "fn": "project_meta_surfaces", "name": "empty_inputs"}"#,
r#"{"args": {"doc": {"entities": [], "generated_at": "2026-10-11T09:00:00Z", "observations": [], "relationships": [], "schema_version": 1}, "inputs": {"config_manifest": null}, "now": "2026-10-11T09:00:00Z"}, "error": null, "expect": {"count": 0, "doc": {"entities": [], "generated_at": "2026-10-11T09:00:00Z", "observations": [], "relationships": [], "schema_version": 1}}, "fn": "project_meta_surfaces", "name": "null_manifest"}"#,
r#"{"args": {"doc": {"entities": [{"evidence_refs": ["earlier#ref"], "id": "rust-crate:rune_spine", "identities": {"source": "src/rust/rune_spine"}, "kind": "rust-crate", "maturity": "WIRED", "observed_at": "2026-10-11T09:00:00Z", "tags": ["code"]}], "generated_at": "2026-10-11T09:00:00Z", "observations": [], "relationships": [{"evidence_refs": ["earlier#ref"], "from": "rust-crate:rune_spine", "observed_at": "2026-10-11T09:00:00Z", "to": "subsystem:cognition", "type": "belongs_to"}], "schema_version": 1}, "inputs": {"config_files": ["go-version.txt"], "config_manifest": {"umbrellas": {"cognition": {"configs": ["go-version.txt", "phantom.toml"], "description": "thinking parts", "go_surfaces": ["rune-entry"], "modules": ["spine"], "rust_crates": ["rune_spine", "ghost_crate"]}, "governance": {"description": "meta"}}}, "crate_canaries": {"rune_spine": ["canary_spine_rust"]}, "crate_dirs": ["rune_spine"], "go_dirs": ["rune-entry"], "workflows": ["ci.yml", "pipeline-gate.yml"]}, "now": "2026-10-11T09:00:00Z"}, "error": null, "expect": {"count": 7, "doc": {"entities": [{"evidence_refs": ["earlier#ref", "umbrella-manifest#cognition"], "id": "rust-crate:rune_spine", "identities": {"source": "src/rust/rune_spine"}, "kind": "rust-crate", "maturity": "WIRED", "observed_at": "2026-10-11T09:00:00Z", "tags": ["code"]}, {"evidence_refs": ["crate-scan:rune_spine"], "id": "module:canary_spine_rust", "identities": {"source": "src/rust/rune_spine/canary_spine_rust.py"}, "kind": "module", "maturity": "PRESENT", "observed_at": "2026-10-11T09:00:00Z", "tags": ["canary", "code"]}, {"evidence_refs": ["umbrella-manifest#cognition"], "id": "rust-crate:ghost_crate", "identities": {"source": "src/rust/ghost_crate"}, "kind": "rust-crate", "maturity": "DECLARED", "observed_at": "2026-10-11T09:00:00Z", "tags": ["code"]}, {"evidence_refs": ["umbrella-manifest#cognition"], "id": "go-surface:rune-entry", "identities": {"source": "src/go/rune-entry"}, "kind": "go-surface", "maturity": "PRESENT", "observed_at": "2026-10-11T09:00:00Z", "tags": ["code"]}, {"evidence_refs": ["umbrella-manifest#cognition"], "id": "config:go-version.txt", "identities": {"source": "go-version.txt"}, "kind": "config", "maturity": "PRESENT", "observed_at": "2026-10-11T09:00:00Z", "tags": []}, {"evidence_refs": ["umbrella-manifest#cognition"], "id": "config:phantom.toml", "identities": {"source": "phantom.toml"}, "kind": "config", "maturity": "DECLARED", "observed_at": "2026-10-11T09:00:00Z", "tags": []}, {"evidence_refs": ["workflow-scan"], "id": "workflow:ci.yml", "identities": {"source": ".gitea/workflows/ci.yml"}, "kind": "workflow", "maturity": "PRESENT", "observed_at": "2026-10-11T09:00:00Z", "tags": ["infra"]}, {"evidence_refs": ["workflow-scan"], "id": "workflow:pipeline-gate.yml", "identities": {"source": ".gitea/workflows/pipeline-gate.yml"}, "kind": "workflow", "maturity": "PRESENT", "observed_at": "2026-10-11T09:00:00Z", "tags": ["infra"]}], "generated_at": "2026-10-11T09:00:00Z", "observations": [], "relationships": [{"evidence_refs": ["earlier#ref"], "from": "rust-crate:rune_spine", "observed_at": "2026-10-11T09:00:00Z", "to": "subsystem:cognition", "type": "belongs_to"}, {"evidence_refs": ["crate-scan:rune_spine"], "from": "rust-crate:rune_spine", "observed_at": "2026-10-11T09:00:00Z", "to": "module:canary_spine_rust", "type": "obligated_canary"}, {"evidence_refs": ["umbrella-manifest#cognition"], "from": "rust-crate:ghost_crate", "observed_at": "2026-10-11T09:00:00Z", "to": "subsystem:cognition", "type": "belongs_to"}, {"evidence_refs": ["umbrella-manifest#cognition"], "from": "go-surface:rune-entry", "observed_at": "2026-10-11T09:00:00Z", "to": "subsystem:cognition", "type": "belongs_to"}, {"evidence_refs": ["umbrella-manifest#cognition"], "from": "config:go-version.txt", "observed_at": "2026-10-11T09:00:00Z", "to": "subsystem:cognition", "type": "belongs_to"}, {"evidence_refs": ["umbrella-manifest#cognition"], "from": "config:phantom.toml", "observed_at": "2026-10-11T09:00:00Z", "to": "subsystem:cognition", "type": "belongs_to"}, {"evidence_refs": ["workflow-scan"], "from": "workflow:ci.yml", "observed_at": "2026-10-11T09:00:00Z", "to": "subsystem:governance", "type": "belongs_to"}, {"evidence_refs": ["workflow-scan"], "from": "workflow:pipeline-gate.yml", "observed_at": "2026-10-11T09:00:00Z", "to": "subsystem:governance", "type": "belongs_to"}], "schema_version": 1}}, "fn": "project_meta_surfaces", "name": "preexisting_entity_high_water"}"#,
r#"{"args": {"data": {"umbrellas": {"cognition": {"canaries": ["canary_spine_meta"], "depends_on": ["governance"], "description": "thinking parts", "future_field": {"unknown": "rides along"}, "mirrors": ["README.md"], "modules": ["spine", "structural_store"]}, "governance": {"description": "meta", "modules": []}}}, "doc": {"entities": [], "generated_at": "2026-10-11T09:00:00Z", "observations": [], "relationships": [], "schema_version": 1}, "now": "2026-10-11T09:00:00Z"}, "error": null, "expect": {"count": 2, "doc": {"entities": [{"evidence_refs": ["umbrella-manifest#cognition"], "ext": {"canaries": ["canary_spine_meta"], "future_field": {"unknown": "rides along"}}, "id": "subsystem:cognition", "identities": {"description": "thinking parts"}, "kind": "subsystem", "maturity": "DECLARED", "observed_at": "2026-10-11T09:00:00Z", "tags": []}, {"evidence_refs": ["umbrella-manifest#cognition"], "id": "module:spine", "identities": {"source": "src/rune/spine.py"}, "kind": "module", "maturity": "DECLARED", "observed_at": "2026-10-11T09:00:00Z", "tags": ["code"]}, {"evidence_refs": ["umbrella-manifest#cognition"], "id": "module:structural_store", "identities": {"source": "src/rune/structural_store.py"}, "kind": "module", "maturity": "DECLARED", "observed_at": "2026-10-11T09:00:00Z", "tags": ["code"]}, {"evidence_refs": ["umbrella-manifest#governance"], "id": "subsystem:governance", "identities": {"description": "meta"}, "kind": "subsystem", "maturity": "DECLARED", "observed_at": "2026-10-11T09:00:00Z", "tags": []}], "generated_at": "2026-10-11T09:00:00Z", "observations": [], "relationships": [{"evidence_refs": ["umbrella-manifest#cognition"], "from": "subsystem:cognition", "observed_at": "2026-10-11T09:00:00Z", "to": "subsystem:governance", "type": "depends_on"}, {"evidence_refs": ["umbrella-manifest#cognition"], "from": "subsystem:cognition", "observed_at": "2026-10-11T09:00:00Z", "to": "file:README.md", "type": "mirrors"}, {"evidence_refs": ["umbrella-manifest#cognition"], "from": "subsystem:cognition", "observed_at": "2026-10-11T09:00:00Z", "to": "module:canary_spine_meta", "type": "obligated_canary"}, {"evidence_refs": ["umbrella-manifest#cognition"], "from": "module:spine", "observed_at": "2026-10-11T09:00:00Z", "to": "subsystem:cognition", "type": "belongs_to"}, {"evidence_refs": ["umbrella-manifest#cognition"], "from": "module:structural_store", "observed_at": "2026-10-11T09:00:00Z", "to": "subsystem:cognition", "type": "belongs_to"}], "schema_version": 1}}, "fn": "_ingest_umbrellas_data", "name": "full_manifest"}"#,
r#"{"args": {"data": {"umbrellas": {}}, "doc": {"entities": [], "generated_at": "2026-10-11T09:00:00Z", "observations": [], "relationships": [], "schema_version": 1}, "now": "2026-10-11T09:00:00Z"}, "error": null, "expect": {"count": 0, "doc": {"entities": [], "generated_at": "2026-10-11T09:00:00Z", "observations": [], "relationships": [], "schema_version": 1}}, "fn": "_ingest_umbrellas_data", "name": "empty_umbrellas"}"#,
r#"{"args": {"data": {"_generated_by_spine": true, "umbrellas": {}}, "doc": {"entities": [], "generated_at": "2026-10-11T09:00:00Z", "observations": [], "relationships": [], "schema_version": 1}, "now": "2026-10-11T09:00:00Z"}, "error": {"message": "the manifest is a Spine-generated compatibility view; refusing to ingest it (Phase A reads the manual authority exactly once). Structural membership now lives in the Spine store.", "type": "ValueError"}, "expect": null, "fn": "_ingest_umbrellas_data", "name": "generated_view_refused"}"#,
r#"{"args": {"data": {"umbrellas": {"cognition": {"canaries": ["canary_spine_meta"], "depends_on": ["governance"], "description": "thinking parts", "future_field": {"unknown": "rides along"}, "mirrors": ["README.md"], "modules": ["spine", "structural_store"]}, "governance": {"description": "meta", "modules": []}}}, "doc": {"entities": [{"evidence_refs": ["umbrella-manifest#cognition"], "ext": {"canaries": ["canary_spine_meta"], "future_field": {"unknown": "rides along"}}, "id": "subsystem:cognition", "identities": {"description": "thinking parts"}, "kind": "subsystem", "maturity": "DECLARED", "observed_at": "2026-10-11T09:00:00Z", "tags": []}, {"evidence_refs": ["umbrella-manifest#cognition"], "id": "module:spine", "identities": {"source": "src/rune/spine.py"}, "kind": "module", "maturity": "DECLARED", "observed_at": "2026-10-11T09:00:00Z", "tags": ["code"]}, {"evidence_refs": ["umbrella-manifest#cognition"], "id": "module:structural_store", "identities": {"source": "src/rune/structural_store.py"}, "kind": "module", "maturity": "DECLARED", "observed_at": "2026-10-11T09:00:00Z", "tags": ["code"]}, {"evidence_refs": ["umbrella-manifest#governance"], "id": "subsystem:governance", "identities": {"description": "meta"}, "kind": "subsystem", "maturity": "DECLARED", "observed_at": "2026-10-11T09:00:00Z", "tags": []}], "generated_at": "2026-10-11T09:00:00Z", "observations": [], "relationships": [{"evidence_refs": ["umbrella-manifest#cognition"], "from": "subsystem:cognition", "observed_at": "2026-10-11T09:00:00Z", "to": "subsystem:governance", "type": "depends_on"}, {"evidence_refs": ["umbrella-manifest#cognition"], "from": "subsystem:cognition", "observed_at": "2026-10-11T09:00:00Z", "to": "file:README.md", "type": "mirrors"}, {"evidence_refs": ["umbrella-manifest#cognition"], "from": "subsystem:cognition", "observed_at": "2026-10-11T09:00:00Z", "to": "module:canary_spine_meta", "type": "obligated_canary"}, {"evidence_refs": ["umbrella-manifest#cognition"], "from": "module:spine", "observed_at": "2026-10-11T09:00:00Z", "to": "subsystem:cognition", "type": "belongs_to"}, {"evidence_refs": ["umbrella-manifest#cognition"], "from": "module:structural_store", "observed_at": "2026-10-11T09:00:00Z", "to": "subsystem:cognition", "type": "belongs_to"}], "schema_version": 1}, "now": "2026-10-11T09:00:00Z"}, "error": null, "expect": {"count": 2, "doc": {"entities": [{"evidence_refs": ["umbrella-manifest#cognition"], "ext": {"canaries": ["canary_spine_meta"], "future_field": {"unknown": "rides along"}}, "id": "subsystem:cognition", "identities": {"description": "thinking parts"}, "kind": "subsystem", "maturity": "DECLARED", "observed_at": "2026-10-11T09:00:00Z", "tags": []}, {"evidence_refs": ["umbrella-manifest#cognition"], "id": "module:spine", "identities": {"source": "src/rune/spine.py"}, "kind": "module", "maturity": "DECLARED", "observed_at": "2026-10-11T09:00:00Z", "tags": ["code"]}, {"evidence_refs": ["umbrella-manifest#cognition"], "id": "module:structural_store", "identities": {"source": "src/rune/structural_store.py"}, "kind": "module", "maturity": "DECLARED", "observed_at": "2026-10-11T09:00:00Z", "tags": ["code"]}, {"evidence_refs": ["umbrella-manifest#governance"], "id": "subsystem:governance", "identities": {"description": "meta"}, "kind": "subsystem", "maturity": "DECLARED", "observed_at": "2026-10-11T09:00:00Z", "tags": []}], "generated_at": "2026-10-11T09:00:00Z", "observations": [], "relationships": [{"evidence_refs": ["umbrella-manifest#cognition"], "from": "subsystem:cognition", "observed_at": "2026-10-11T09:00:00Z", "to": "subsystem:governance", "type": "depends_on"}, {"evidence_refs": ["umbrella-manifest#cognition"], "from": "subsystem:cognition", "observed_at": "2026-10-11T09:00:00Z", "to": "file:README.md", "type": "mirrors"}, {"evidence_refs": ["umbrella-manifest#cognition"], "from": "subsystem:cognition", "observed_at": "2026-10-11T09:00:00Z", "to": "module:canary_spine_meta", "type": "obligated_canary"}, {"evidence_refs": ["umbrella-manifest#cognition"], "from": "module:spine", "observed_at": "2026-10-11T09:00:00Z", "to": "subsystem:cognition", "type": "belongs_to"}, {"evidence_refs": ["umbrella-manifest#cognition"], "from": "module:structural_store", "observed_at": "2026-10-11T09:00:00Z", "to": "subsystem:cognition", "type": "belongs_to"}], "schema_version": 1}}, "fn": "_ingest_umbrellas_data", "name": "reingest_dedupe"}"#,
];
#[cfg(test)]
mod truth_battery {
use super::*;
/// Total frozen case count is pinned — a silently truncated embed is a
/// battery failure, not a smaller battery.
#[test]
fn truth_complete() {
assert_eq!(TRUTH_CASES.len(), 26);
}
#[test]
fn case_00_sorted_nested_dict() {
let case = parse(TRUTH_CASES[0]);
replay(&case);
}
#[test]
fn case_01_sorted_list_of_dicts_by_json_repr() {
let case = parse(TRUTH_CASES[1]);
replay(&case);
}
#[test]
fn case_02_sorted_mixed_scalars() {
let case = parse(TRUTH_CASES[2]);
replay(&case);
}
#[test]
fn case_03_sorted_scalars_passthrough() {
let case = parse(TRUTH_CASES[3]);
replay(&case);
}
#[test]
fn case_04_sorted_empty_containers() {
let case = parse(TRUTH_CASES[4]);
replay(&case);
}
#[test]
fn case_05_sorted_nested_list_in_dict() {
let case = parse(TRUTH_CASES[5]);
replay(&case);
}
#[test]
fn case_06_save_valid_doc() {
let case = parse(TRUTH_CASES[6]);
replay(&case);
}
#[test]
fn case_07_save_missing_schema_defaults() {
let case = parse(TRUTH_CASES[7]);
replay(&case);
}
#[test]
fn case_08_save_future_schema_raises() {
let case = parse(TRUTH_CASES[8]);
replay(&case);
}
#[test]
fn case_09_save_empty_store_sorting() {
let case = parse(TRUTH_CASES[9]);
replay(&case);
}
#[test]
fn case_10_save_no_redaction_spine_surface() {
let case = parse(TRUTH_CASES[10]);
replay(&case);
}
#[test]
fn case_11_entity_hit_by_id() {
let case = parse(TRUTH_CASES[11]);
replay(&case);
}
#[test]
fn case_12_entity_hit_via_alias() {
let case = parse(TRUTH_CASES[12]);
replay(&case);
}
#[test]
fn case_13_entity_miss_is_none() {
let case = parse(TRUTH_CASES[13]);
replay(&case);
}
#[test]
fn case_14_entity_empty_entities() {
let case = parse(TRUTH_CASES[14]);
replay(&case);
}
#[test]
fn case_15_declared_surfaces_full_manifest() {
let case = parse(TRUTH_CASES[15]);
replay(&case);
}
#[test]
fn case_16_declared_surfaces_empty_umbrellas() {
let case = parse(TRUTH_CASES[16]);
replay(&case);
}
#[test]
fn case_17_declared_surfaces_no_kind_lists() {
let case = parse(TRUTH_CASES[17]);
replay(&case);
}
#[test]
fn case_18_project_meta_surfaces_full_inputs() {
let case = parse(TRUTH_CASES[18]);
replay(&case);
}
#[test]
fn case_19_project_meta_surfaces_empty_inputs() {
let case = parse(TRUTH_CASES[19]);
replay(&case);
}
#[test]
fn case_20_project_meta_surfaces_null_manifest() {
let case = parse(TRUTH_CASES[20]);
replay(&case);
}
#[test]
fn case_21_project_meta_surfaces_preexisting_entity_high_water() {
let case = parse(TRUTH_CASES[21]);
replay(&case);
}
#[test]
fn case_22_ingest_umbrellas_data_full_manifest() {
let case = parse(TRUTH_CASES[22]);
replay(&case);
}
#[test]
fn case_23_ingest_umbrellas_data_empty_umbrellas() {
let case = parse(TRUTH_CASES[23]);
replay(&case);
}
#[test]
fn case_24_ingest_umbrellas_data_generated_view_refused() {
let case = parse(TRUTH_CASES[24]);
replay(&case);
}
#[test]
fn case_25_ingest_umbrellas_data_reingest_dedupe() {
let case = parse(TRUTH_CASES[25]);
replay(&case);
}
}
+231
View File
@@ -0,0 +1,231 @@
//! facade.rs — dichotomy-wave facade kernels (issue #2479 tranche).
//!
//! Name-preserving parity ports of the spine.py pure kernels the facade
//! delegates per-function, one doc-envelope FFI symbol each:
//! sf_sorted ← spine._sorted
//! sf_save_text ← spine.save (validation + canonical text; the write
//! itself is IO and stays Python)
//! sf_entity ← spine.entity (rename-resilient resolution)
//! sf_declared_surfaces ← spine._declared_surfaces (manifest-parse part;
//! the file read stays in the Python gather)
//! sf_project_meta_surfaces ← spine.project_meta_surfaces (pure part)
//! sf_ingest_umbrellas_data ← spine._ingest_umbrellas_data
//!
//! All primitives (add_entity/add_relationship high-water semantics,
//! sorted_deep, generated-view refusal) reuse store.rs / project.rs —
//! the exact code paths the proven sp_project battery already covers.
use crate::project;
use crate::pyjson::{self, Json};
use crate::store;
/// Python `json.dumps(value, indent=1, sort_keys=True) + "\n"` — the exact
/// serialization spine.save writes. indent=1 means ONE space per level,
/// item separator ",\n<indent>", key separator ": ".
fn dump_indent1_sorted(v: &Json) -> String {
let sorted = store::sorted_deep(v);
let mut out = String::new();
write_indent1(&mut out, &sorted, 0);
out.push('\n');
out
}
fn write_indent1(out: &mut String, v: &Json, depth: usize) {
match v {
Json::Arr(items) if !items.is_empty() => {
out.push_str("[\n");
for (i, item) in items.iter().enumerate() {
if i > 0 {
out.push_str(",\n");
}
push_indent(out, depth + 1);
write_indent1(out, item, depth + 1);
}
out.push('\n');
push_indent(out, depth);
out.push(']');
}
Json::Arr(_) => out.push_str("[]"),
Json::Obj(pairs) if !pairs.is_empty() => {
out.push_str("{\n");
for (i, (k, val)) in pairs.iter().enumerate() {
if i > 0 {
out.push_str(",\n");
}
push_indent(out, depth + 1);
pyjson::write_escaped(out, k);
out.push_str(": ");
write_indent1(out, val, depth + 1);
}
out.push('\n');
push_indent(out, depth);
out.push('}');
}
Json::Obj(_) => out.push_str("{}"),
other => pyjson::write_compact(out, other),
}
}
fn push_indent(out: &mut String, depth: usize) {
for _ in 0..depth {
out.push(' ');
}
}
// ── sf_sorted({"v": ...}) -> envelope(normalized) ───────────────────────────
pub fn sorted_fn(arg: &Json) -> Result<Json, String> {
let v = arg
.get("v")
.ok_or_else(|| "arg.v required".to_string())?;
Ok(store::sorted_deep(v))
}
// ── sf_save_text({"doc": ...}) -> envelope(canonical text) ─────────────────
pub fn save_text(arg: &Json) -> Result<Json, String> {
let doc = arg
.get("doc")
.cloned()
.ok_or_else(|| "arg.doc required".to_string())?;
let version = doc.get("schema_version").cloned().unwrap_or(Json::Num(1.0));
let version_int = match version {
Json::Num(n) => n as i64,
_ => 0, // non-numeric schema_version: mismatches 1 (parity with Python)
};
if version_int != 1 {
return Err(format!("schema_version {} != 1", fmt_version(&version)));
}
let mut out_doc = doc.clone();
if let Json::Obj(pairs) = &mut out_doc {
if let Some(pos) = pairs.iter().position(|(k, _)| k == "schema_version") {
pairs[pos].1 = Json::Num(1.0);
}
}
// PARITY LAW: spine.save does NOT redact (that is the structural_store
// sibling's behavior) — serialize exactly what was given.
Ok(Json::Str(dump_indent1_sorted(&out_doc)))
}
fn fmt_version(v: &Json) -> String {
match v {
Json::Num(n) => pyjson::fmt_num(*n),
Json::Str(s) => s.clone(),
other => pyjson::dumps(other),
}
}
// ── sf_entity({"doc": ..., "id": ...}) -> envelope(entity | null) ──────────
pub fn entity_fn(arg: &Json) -> Result<Json, String> {
let doc = arg
.get("doc")
.ok_or_else(|| "arg.doc required".to_string())?;
let id = arg
.get("id")
.map(|x| store::as_str(x).to_string())
.ok_or_else(|| "arg.id required".to_string())?;
Ok(match store::entity(doc, &id) {
Some(e) => e.clone(),
None => Json::Null,
})
}
// ── sf_declared_surfaces({"config_manifest": ...}) -> envelope(declared) ───
//
// The manifest-parse core of spine._declared_surfaces: the caller gathers
// the file bytes and passes the parsed manifest (missing/corrupt file
// arrives as null — the gather's empty result).
pub fn declared_surfaces(arg: &Json) -> Result<Json, String> {
let manifest = arg.get("config_manifest").cloned().unwrap_or(Json::Null);
let mut rust_crates: Vec<Json> = vec![];
let mut go_surfaces: Vec<Json> = vec![];
let mut configs: Vec<Json> = vec![];
if let Some(Json::Obj(umbrellas)) = manifest.get("umbrellas") {
for (name, u) in umbrellas {
for crate_name in str_list(u.get("rust_crates")) {
rust_crates.push(Json::Arr(vec![
Json::Str(crate_name),
Json::Str(name.clone()),
]));
}
for surface in str_list(u.get("go_surfaces")) {
go_surfaces.push(Json::Arr(vec![
Json::Str(surface),
Json::Str(name.clone()),
]));
}
for cfg in str_list(u.get("configs")) {
configs.push(Json::Arr(vec![
Json::Str(cfg),
Json::Str(name.clone()),
]));
}
}
}
Ok(Json::Obj(vec![
("rust_crates".to_string(), Json::Arr(rust_crates)),
("go_surfaces".to_string(), Json::Arr(go_surfaces)),
("configs".to_string(), Json::Arr(configs)),
]))
}
fn str_list(v: Option<&Json>) -> Vec<String> {
store::as_arr(v.unwrap_or(&Json::Arr(vec![])))
.iter()
.map(|x| store::as_str(x).to_string())
.collect()
}
// ── sf_project_meta_surfaces({"doc", "inputs", "now"}) -> envelope({count, doc}) ──
pub fn project_meta_surfaces_fn(arg: &Json) -> Result<Json, String> {
let mut doc = arg
.get("doc")
.cloned()
.ok_or_else(|| "arg.doc required".to_string())?;
let inputs = arg
.get("inputs")
.cloned()
.ok_or_else(|| "arg.inputs required".to_string())?;
let now = arg
.get("now")
.map(|x| store::as_str(x).to_string())
.ok_or_else(|| "arg.now required".to_string())?;
let manifest = inputs.get("config_manifest").cloned().unwrap_or(Json::Null);
let manifest_ref = match &manifest {
Json::Null => None,
m => Some(m),
};
let count = project::project_meta_surfaces(&mut doc, manifest_ref, &inputs, &now);
Ok(Json::Obj(vec![
("count".to_string(), Json::Num(count as f64)),
("doc".to_string(), doc),
]))
}
// ── sf_ingest_umbrellas_data({"doc", "data", "now"}) -> envelope({count, doc}) ──
pub fn ingest_umbrellas_data_fn(arg: &Json) -> Result<Json, String> {
let mut doc = arg
.get("doc")
.cloned()
.ok_or_else(|| "arg.doc required".to_string())?;
let data = arg
.get("data")
.cloned()
.ok_or_else(|| "arg.data required".to_string())?;
let now = arg
.get("now")
.map(|x| store::as_str(x).to_string())
.ok_or_else(|| "arg.now required".to_string())?;
let count = project::ingest_umbrellas(&mut doc, &data, &now)?;
Ok(Json::Obj(vec![
("count".to_string(), Json::Num(count as f64)),
("doc".to_string(), doc),
]))
}
// The parity battery lives in branch_battery_facade.rs — the frozen oracle
// truth embedded verbatim, one case per test.
+57
View File
@@ -9,12 +9,15 @@
pub mod discovery;
pub mod drift;
pub mod facade;
pub mod language;
pub mod project;
pub mod pyjson;
#[cfg(test)]
mod branch_battery;
#[cfg(test)]
mod branch_battery_facade;
pub mod query;
pub mod store;
@@ -112,6 +115,60 @@ pub extern "C" fn sp_export(arg: *const c_char) -> *mut c_char {
envelope(result)
}
/// sf_sorted({"v": ...}) -> envelope(normalized) — spine._sorted
#[no_mangle]
pub extern "C" fn sf_sorted(arg: *const c_char) -> *mut c_char {
let result = read_arg(arg)
.and_then(|text| arg_json(&text))
.and_then(|a| facade::sorted_fn(&a));
envelope(result)
}
/// sf_save_text({"doc": ...}) -> envelope(canonical text) — spine.save core
#[no_mangle]
pub extern "C" fn sf_save_text(arg: *const c_char) -> *mut c_char {
let result = read_arg(arg)
.and_then(|text| arg_json(&text))
.and_then(|a| facade::save_text(&a));
envelope(result)
}
/// sf_entity({"doc": ..., "id": ...}) -> envelope(entity | null)
#[no_mangle]
pub extern "C" fn sf_entity(arg: *const c_char) -> *mut c_char {
let result = read_arg(arg)
.and_then(|text| arg_json(&text))
.and_then(|a| facade::entity_fn(&a));
envelope(result)
}
/// sf_declared_surfaces({"config_manifest": ...}) -> envelope(declared)
#[no_mangle]
pub extern "C" fn sf_declared_surfaces(arg: *const c_char) -> *mut c_char {
let result = read_arg(arg)
.and_then(|text| arg_json(&text))
.and_then(|a| facade::declared_surfaces(&a));
envelope(result)
}
/// sf_project_meta_surfaces({"doc", "inputs", "now"}) -> envelope({count, doc})
#[no_mangle]
pub extern "C" fn sf_project_meta_surfaces(arg: *const c_char) -> *mut c_char {
let result = read_arg(arg)
.and_then(|text| arg_json(&text))
.and_then(|a| facade::project_meta_surfaces_fn(&a));
envelope(result)
}
/// sf_ingest_umbrellas_data({"doc", "data", "now"}) -> envelope({count, doc})
#[no_mangle]
pub extern "C" fn sf_ingest_umbrellas_data(arg: *const c_char) -> *mut c_char {
let result = read_arg(arg)
.and_then(|text| arg_json(&text))
.and_then(|a| facade::ingest_umbrellas_data_fn(&a));
envelope(result)
}
/// Free a string returned by any sp_* function.
#[allow(clippy::not_unsafe_ptr_arg_deref)]
// pinned: #[no_mangle] extern "C" — the Survivor loader ABI requires this exact safe-fn signature; the raw pointer IS the FFI contract
+15 -7
View File
@@ -42,13 +42,14 @@ fn split_name(id: &str) -> &str {
// ── ingest_umbrellas ────────────────────────────────────────────────────────
fn ingest_umbrellas(doc: &mut Json, manifest: &Json, now: &str) -> Result<i64, String> {
/// Faithful to spine._ingest_umbrellas_data — the generated-view refusal
/// carries the oracle's EXACT ValueError text (parity battery pins it).
pub fn ingest_umbrellas(doc: &mut Json, manifest: &Json, now: &str) -> Result<i64, String> {
if obj_get(manifest, GENERATED_VIEW_MARKER).is_some() {
return Err(
"manifest is a Spine-generated compatibility view; refusing to ingest it \
(Phase A reads the manual authority exactly once)"
.to_string(),
);
return Err("the manifest is a Spine-generated compatibility view; refusing to \
ingest it (Phase A reads the manual authority exactly once). \
Structural membership now lives in the Spine store."
.to_string());
}
let known = [
"description",
@@ -152,7 +153,14 @@ fn ingest_umbrellas(doc: &mut Json, manifest: &Json, now: &str) -> Result<i64, S
// ── project_meta_surfaces ───────────────────────────────────────────────────
fn project_meta_surfaces(doc: &mut Json, manifest: Option<&Json>, inputs: &Json, now: &str) -> i64 {
/// Faithful to spine.project_meta_surfaces (pure part; IO stays in the
/// Python gather). Public for the facade FFI (dichotomy wave batch).
pub fn project_meta_surfaces(
doc: &mut Json,
manifest: Option<&Json>,
inputs: &Json,
now: &str,
) -> i64 {
let mut count = 0;
let crate_dirs = str_list(obj_get(inputs, "crate_dirs"));
let go_dirs = str_list(obj_get(inputs, "go_dirs"));
+1 -1
View File
@@ -313,7 +313,7 @@ pub fn dumps(v: &Json) -> String {
out
}
fn write_compact(out: &mut String, v: &Json) {
pub fn write_compact(out: &mut String, v: &Json) {
match v {
Json::Null => out.push_str("null"),
Json::Bool(true) => out.push_str("true"),