Compare commits

...
11 Commits
Author SHA1 Message Date
conductor aac5295c24 chore(release): auto-bump version to 0.66.26 — earned by # (minor: converged release (push-to-master bump))
Promotion record: (unknown)
2026-09-12 18:07:08 +01:00
Nick 9563148f9d feat(spine): contracts, structural impact resolution, task binding, event seam
Structural Impact Resolution: impact_frontier() — bounded LEVEL 0/1/2
resolution with a data-driven semantics table. Contract entities expand to
all implements/consumes parties (a contract change deserves every
consumer's inspection); unknown relationship types default to
obligation-bearing (fail-loud, open world); non-obligation classes
(documented_by, related_memory, supersedes...) never explode a frontier.

Graph separation (addendum): Spine is its own logical graph — the frontier
traverses Spine edges only; memory-style associations cannot expand
structural impact; stable spine:// URIs let future Memory reference Spine
entities without duplicating authority.

Also: task binding (affects edges + bind_task/task_entities),
predicted_vs_observed comparison (misses become CANDIDATE corrections,
evidence-gated), generic structural_events() seam (dumb typed facts — no
salience/policy; future Substrate subscribes), aggregate query() (the
intrinsic self-knowledge property: owner/deps/consumers/contracts/state/
evidence/frontier in one deterministic call), evidence_conflict drift
class. henxels similarity scan excludes store/spine (snapshots are
legitimately near-identical by design). 45/45 spine tests; full gates green.
2026-09-12 18:06:16 +01:00
conductor 76984c6362 chore(release): auto-bump version to 0.66.25 — earned by # (patch: converged release (push-to-master bump))
Promotion record: (unknown)
2026-09-12 17:34:32 +01:00
Nick 4072d36b1a fix(spine): build re-derivation uses high-water upgrade — no demotion noise
Evidence surfaced it: re-deriving module maturity during build() logged a
demotion-rejection observation for every already-earned module (171 noise
records). Build now upgrades via the silent high-water path; promote()
logging stays reserved for explicit evidence-gated API calls.
2026-09-12 17:34:05 +01:00
conductor 6d871abc9c chore(release): auto-bump version to 0.66.24 — earned by # (minor: converged release (push-to-master bump))
Promotion record: (unknown)
2026-09-12 17:03:51 +01:00
Nick ce40acbb1f feat(spine): V1 — evolvable structural self-model as the single structural authority
Open-world entity/relationship store (schema_version 1): kinds, relationship
types and metadata are open tags — unknown future capabilities enter as data,
never as core edits (extensibility test included, synthetic capability
round-trips with zero core change). Evidence-gated maturity ladder
DECLARED<PRESENT<WIRED<REACHABLE<EXERCISED<LIVE_PROVEN — no evidence, no
promotion; demotion rejected and recorded.

Umbrella migration Phase A→C: manifest ingested once (generated-view
refusal guard prevents the ingest cycle; manual truth archived at
config/umbrellas.manual.json), canaries preserved as obligated_canary
relationships, config/umbrellas.json now a GENERATED compatibility view for
legacy consumers (CI umbrella_impact gate + henxels) — single-authority
route proven by registering module:spine through Spine alone (one store
edit; the legacy view flows out; contract tests validate the round-trip).

Dynamic observations: deployed identity (_build_identity.json), drift
classes incl. registered_but_absent, present_but_unowned,
registered_no_static_importers (D6 dead-subnet now mechanical),
version_surfaces_disagree, relationship_disappeared. Deterministic
snapshots + diff. Scaffolding V1 now consumes Spine for ownership/deps/
consumers (spine_maturity surfaced in every card). 31 spine tests; full
gates green.
2026-09-12 17:03:27 +01:00
Nick 1de28b55a0 docs(tooling): desktop runner wakes local W/R daemon after jobs (v0.3, Gitea authoritative) 2026-09-12 16:16:58 +01:00
conductor 75d6d7d62a chore(release): auto-bump version to 0.66.23 — earned by # (minor: converged release (push-to-master bump))
Promotion record: (unknown)
2026-09-12 11:16:07 +01:00
Nick 7f47969128 feat(scaffolding): V1 tranche 1 — projection engine over existing authorities
scripts/scaffold_projection.py derives per-component cards (subsystem,
dependencies, consumers, mirrors, proof obligations + evidence state) from
the umbrella manifest, canary registry, AST import graph (incl. relative
lazy imports), and tests/ filesystem. No new authority: projections only.
docs/SCAFFOLDING.md records the mechanism audit (KEEP/EVOLVE/REPLACE/RETIRE)
and the real-change proof on the context-discovery fix.
2026-09-12 11:15:42 +01:00
Nick a65e5d44de fix(runtime): context discovery routes cloud-pinned models to the provider wire
Module-level context_length used OllamaChatTransport unconditionally, so
every cloud-pinned task logged context_discovery tier=1 HTTPError 400 and
was misbudgeted at the 8192 fallback instead of the provider's real window
(131072 for Verboo Code/glm-5.3-flash). is_cloud_pinned is now THE
provider-prefix rule — one authority in openai_compat_transport, consumed
by context discovery (new) and transport selection (deduped from
rune_operator._transport_for). Red-first regression tests added.
Scaffolding-derived impact: consumer rune_operator; gates suite/lint/
henxels + inference/orchestration canaries.
2026-09-12 11:15:42 +01:00
Nick 26a040892d docs(reconciliation): ADAPT family reconciliation on PL1 held-out evidence
#292/#293/#297 FIXED_VERIFIED (closed with evidence comments — LOCAL_FIX
live-proven by held-out tasks, DESIGN-LITE implemented, task_gate shipped);
#294/#295/#296/#299/#301 DEFERRED_FUTURE (canonical remaining-obligations
list); #300/#315/#316/#284 VALID_OPEN. Resolves the ADAPT-LIVE-complete vs
open-issues tension: the authorized tranche is complete; the backlog never
was its scope. Checker green (72 records). PE-1789102658-0269 SEED→VALIDATED
recorded in the canonical intent state.
2026-09-12 11:04:54 +01:00
24 changed files with 35491 additions and 269 deletions

No files matched your search

+1 -1
View File
@@ -1,6 +1,6 @@
# Rune
**Current release: v0.66.22**
**Current release: v0.66.26**
**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.**
+233 -222
View File
@@ -1,225 +1,236 @@
{
"_comment": "Umbrella impact map \u2014 the change-propagation graph. Every src/rune/*.py and shared/*.py module MUST appear in exactly one umbrella's modules list (enforced by the umbrella_manifest_is_complete henxel; debt-tracked modules allowed via docs/module-coverage-debt.md). depends_on: when umbrella U changes, its dependents' canaries are required gates. mirrors: files that must travel together in the same PR (enforced by scripts/umbrella_impact.py unless the PR carries 'Propagation-waived: <reason>').",
"umbrellas": {
"memory": {
"description": "Storing, retrieving, and managing knowledge",
"modules": [
"memory_store",
"memory_consolidation",
"memory_env",
"shard_manager",
"query_memory",
"graph_memory",
"warm_neuron_store",
"abstraction",
"cache",
"recall_router",
"drift_cache",
"drift_compression",
"tier_manager",
"graph_shard_bridge",
"memory_selector",
"retrieval_benchmark"
],
"canaries": [
"canary_query_memory",
"canary_memory_synthesis",
"canary_full_system"
],
"depends_on": [
"governance"
]
},
"governance": {
"description": "Quality control, drift detection, promotion, audit",
"modules": [
"drift_core",
"drift_observability",
"drift_ledger",
"audit_ledger",
"curator",
"claim_audit",
"contracts",
"build_pipeline_policy",
"failure_auto_filer"
],
"canaries": [
"canary_full_system",
"canary_schema_migration"
],
"depends_on": []
},
"identity": {
"description": "Who the agent is \u2014 persona, voice, anchors",
"modules": [
"persona_definition",
"persona_gate",
"identity_anchors",
"voice_hybrid",
"voice_sync",
"voicebox",
"voice_injector",
"behavior",
"prompt_sync"
],
"canaries": [
"canary_full_system"
],
"depends_on": [
"governance"
]
},
"growth": {
"description": "Learning and improvement",
"modules": [
"self_improve",
"subconscious_pass",
"goal",
"elastic_cognition",
"growth_delta",
"autonomy_loop",
"cognitive_loop",
"dream_cycle",
"growth_simulator"
],
"canaries": [
"canary_full_system"
],
"depends_on": [
"memory",
"governance"
]
},
"perception": {
"description": "Input processing and context assembly",
"modules": [
"context_broker",
"context_pressure",
"attention_capsule",
"auto_context",
"auto_tasks",
"witness",
"salience_gate",
"cognitive_budgeter",
"complexity_scorer"
],
"canaries": [
"canary_context_broker",
"canary_full_system"
],
"depends_on": [
"memory"
]
},
"capability": {
"description": "Tool selection and execution authority",
"modules": [
"capability_broker",
"tool_executor",
"tool_protocol",
"local_tool_runtime",
"intent_continuity",
"openai_compat_transport",
"forge_client",
"message_queue",
"connection_pool",
"adaptive_router",
"route_risk_flags",
"evidence_probe",
"builder_bridge",
"builder_bridge_cli",
"recall_uplift"
],
"canaries": [
"canary_p1_01",
"canary_p0_05",
"canary_native"
],
"depends_on": [
"memory"
]
},
"inference": {
"description": "Model routing and outbound requests",
"modules": [
"model_routing",
"model_profiles",
"model_registry",
"model_connect",
"provider_registry",
"provider_budget",
"standardized_providers",
"inference_trace",
"response_adapter",
"token_optimizer"
],
"canaries": [
"canary_p1_06",
"canary_p1_02",
"canary_resource_authority"
],
"depends_on": [
"capability"
]
},
"orchestration": {
"description": "Task lifecycle, operator, TUI presentation",
"modules": [
"rune_operator",
"rune_tui",
"rune_graphic",
"rune_spinner",
"rune_profiler",
"chat_bubble",
"message_widgets",
"tui_validator",
"microkernel",
"event_bus",
"event_envelope",
"hook_engine",
"system_registry",
"cron_engine",
"task_gate",
"task_persist",
"work_manager",
"parallel_processor",
"tiered_executor",
"time_awareness",
"night_mode",
"pins",
"progressive_escalation",
"safety_override",
"sleep_time",
"verification_layer",
"outcome_ledger",
"warm_router",
"subsystem_health",
"build_mode",
"dependency_tracker",
"auto_detect",
"build_orchestrator",
"custodian",
"gitea_auth",
"gitea_pipeline",
"gitea_provision",
"umbrella_update"
],
"canaries": [
"canary_p1_07",
"canary_orchestration_foundation",
"canary_conversation_state",
"canary_gitea_pipeline",
"canary_tui_drift",
"canary_cognitive_strata",
"canary_surface_parity"
],
"depends_on": [
"perception",
"capability",
"inference",
"identity",
"growth"
]
}
"_comment": "GENERATED by rune.spine \u2014 compatibility view for legacy umbrella consumers. DO NOT EDIT: structural truth lives in store/spine/current.json; edits here are flagged by spine drift.",
"_generated_by_spine": true,
"generated_at": "2026-09-12T16:02:18Z",
"umbrellas": {
"capability": {
"description": "Tool selection and execution authority",
"modules": [
"adaptive_router",
"builder_bridge",
"builder_bridge_cli",
"capability_broker",
"connection_pool",
"evidence_probe",
"forge_client",
"intent_continuity",
"local_tool_runtime",
"message_queue",
"openai_compat_transport",
"recall_uplift",
"route_risk_flags",
"tool_executor",
"tool_protocol"
],
"depends_on": [
"memory"
],
"mirrors": [],
"canaries": [
"canary_native",
"canary_p0_05",
"canary_p1_01"
]
},
"governance": {
"description": "Quality control, drift detection, promotion, audit",
"modules": [
"audit_ledger",
"build_pipeline_policy",
"claim_audit",
"contracts",
"curator",
"drift_core",
"drift_ledger",
"drift_observability",
"failure_auto_filer"
],
"depends_on": [],
"mirrors": [],
"canaries": [
"canary_full_system",
"canary_schema_migration"
]
},
"growth": {
"description": "Learning and improvement",
"modules": [
"autonomy_loop",
"cognitive_loop",
"dream_cycle",
"elastic_cognition",
"goal",
"growth_delta",
"growth_simulator",
"self_improve",
"subconscious_pass"
],
"depends_on": [
"governance",
"memory"
],
"mirrors": [],
"canaries": [
"canary_full_system"
]
},
"identity": {
"description": "Who the agent is \u2014 persona, voice, anchors",
"modules": [
"behavior",
"identity_anchors",
"persona_definition",
"persona_gate",
"prompt_sync",
"voice_hybrid",
"voice_injector",
"voice_sync",
"voicebox"
],
"depends_on": [
"governance"
],
"mirrors": [],
"canaries": [
"canary_full_system"
]
},
"inference": {
"description": "Model routing and outbound requests",
"modules": [
"inference_trace",
"model_connect",
"model_profiles",
"model_registry",
"model_routing",
"provider_budget",
"provider_registry",
"response_adapter",
"standardized_providers",
"token_optimizer"
],
"depends_on": [
"capability"
],
"mirrors": [],
"canaries": [
"canary_p1_02",
"canary_p1_06",
"canary_resource_authority"
]
},
"memory": {
"description": "Storing, retrieving, and managing knowledge",
"modules": [
"abstraction",
"cache",
"drift_cache",
"drift_compression",
"graph_memory",
"graph_shard_bridge",
"memory_consolidation",
"memory_env",
"memory_selector",
"memory_store",
"query_memory",
"recall_router",
"retrieval_benchmark",
"shard_manager",
"tier_manager",
"warm_neuron_store"
],
"depends_on": [
"governance"
],
"mirrors": [],
"canaries": [
"canary_full_system",
"canary_memory_synthesis",
"canary_query_memory"
]
},
"orchestration": {
"description": "Task lifecycle, operator, TUI presentation",
"modules": [
"auto_detect",
"build_mode",
"build_orchestrator",
"chat_bubble",
"cron_engine",
"custodian",
"dependency_tracker",
"event_bus",
"event_envelope",
"gitea_auth",
"gitea_pipeline",
"gitea_provision",
"hook_engine",
"message_widgets",
"microkernel",
"night_mode",
"outcome_ledger",
"parallel_processor",
"pins",
"progressive_escalation",
"rune_graphic",
"rune_operator",
"rune_profiler",
"rune_spinner",
"rune_tui",
"safety_override",
"sleep_time",
"subsystem_health",
"system_registry",
"task_gate",
"task_persist",
"tiered_executor",
"time_awareness",
"tui_validator",
"umbrella_update",
"verification_layer",
"warm_router",
"work_manager"
],
"depends_on": [
"capability",
"growth",
"identity",
"inference",
"perception"
],
"mirrors": [],
"canaries": [
"canary_cognitive_strata",
"canary_conversation_state",
"canary_gitea_pipeline",
"canary_orchestration_foundation",
"canary_p1_07",
"canary_surface_parity",
"canary_tui_drift"
]
},
"perception": {
"description": "Input processing and context assembly",
"modules": [
"attention_capsule",
"auto_context",
"auto_tasks",
"cognitive_budgeter",
"complexity_scorer",
"context_broker",
"context_pressure",
"salience_gate",
"spine",
"witness"
],
"depends_on": [
"memory"
],
"mirrors": [],
"canaries": [
"canary_context_broker",
"canary_full_system"
]
}
}
}
+225
View File
@@ -0,0 +1,225 @@
{
"_comment": "Umbrella impact map \u2014 the change-propagation graph. Every src/rune/*.py and shared/*.py module MUST appear in exactly one umbrella's modules list (enforced by the umbrella_manifest_is_complete henxel; debt-tracked modules allowed via docs/module-coverage-debt.md). depends_on: when umbrella U changes, its dependents' canaries are required gates. mirrors: files that must travel together in the same PR (enforced by scripts/umbrella_impact.py unless the PR carries 'Propagation-waived: <reason>').",
"umbrellas": {
"memory": {
"description": "Storing, retrieving, and managing knowledge",
"modules": [
"memory_store",
"memory_consolidation",
"memory_env",
"shard_manager",
"query_memory",
"graph_memory",
"warm_neuron_store",
"abstraction",
"cache",
"recall_router",
"drift_cache",
"drift_compression",
"tier_manager",
"graph_shard_bridge",
"memory_selector",
"retrieval_benchmark"
],
"canaries": [
"canary_query_memory",
"canary_memory_synthesis",
"canary_full_system"
],
"depends_on": [
"governance"
]
},
"governance": {
"description": "Quality control, drift detection, promotion, audit",
"modules": [
"drift_core",
"drift_observability",
"drift_ledger",
"audit_ledger",
"curator",
"claim_audit",
"contracts",
"build_pipeline_policy",
"failure_auto_filer"
],
"canaries": [
"canary_full_system",
"canary_schema_migration"
],
"depends_on": []
},
"identity": {
"description": "Who the agent is \u2014 persona, voice, anchors",
"modules": [
"persona_definition",
"persona_gate",
"identity_anchors",
"voice_hybrid",
"voice_sync",
"voicebox",
"voice_injector",
"behavior",
"prompt_sync"
],
"canaries": [
"canary_full_system"
],
"depends_on": [
"governance"
]
},
"growth": {
"description": "Learning and improvement",
"modules": [
"self_improve",
"subconscious_pass",
"goal",
"elastic_cognition",
"growth_delta",
"autonomy_loop",
"cognitive_loop",
"dream_cycle",
"growth_simulator"
],
"canaries": [
"canary_full_system"
],
"depends_on": [
"memory",
"governance"
]
},
"perception": {
"description": "Input processing and context assembly",
"modules": [
"context_broker",
"context_pressure",
"attention_capsule",
"auto_context",
"auto_tasks",
"witness",
"salience_gate",
"cognitive_budgeter",
"complexity_scorer"
],
"canaries": [
"canary_context_broker",
"canary_full_system"
],
"depends_on": [
"memory"
]
},
"capability": {
"description": "Tool selection and execution authority",
"modules": [
"capability_broker",
"tool_executor",
"tool_protocol",
"local_tool_runtime",
"intent_continuity",
"openai_compat_transport",
"forge_client",
"message_queue",
"connection_pool",
"adaptive_router",
"route_risk_flags",
"evidence_probe",
"builder_bridge",
"builder_bridge_cli",
"recall_uplift"
],
"canaries": [
"canary_p1_01",
"canary_p0_05",
"canary_native"
],
"depends_on": [
"memory"
]
},
"inference": {
"description": "Model routing and outbound requests",
"modules": [
"model_routing",
"model_profiles",
"model_registry",
"model_connect",
"provider_registry",
"provider_budget",
"standardized_providers",
"inference_trace",
"response_adapter",
"token_optimizer"
],
"canaries": [
"canary_p1_06",
"canary_p1_02",
"canary_resource_authority"
],
"depends_on": [
"capability"
]
},
"orchestration": {
"description": "Task lifecycle, operator, TUI presentation",
"modules": [
"rune_operator",
"rune_tui",
"rune_graphic",
"rune_spinner",
"rune_profiler",
"chat_bubble",
"message_widgets",
"tui_validator",
"microkernel",
"event_bus",
"event_envelope",
"hook_engine",
"system_registry",
"cron_engine",
"task_gate",
"task_persist",
"work_manager",
"parallel_processor",
"tiered_executor",
"time_awareness",
"night_mode",
"pins",
"progressive_escalation",
"safety_override",
"sleep_time",
"verification_layer",
"outcome_ledger",
"warm_router",
"subsystem_health",
"build_mode",
"dependency_tracker",
"auto_detect",
"build_orchestrator",
"custodian",
"gitea_auth",
"gitea_pipeline",
"gitea_provision",
"umbrella_update"
],
"canaries": [
"canary_p1_07",
"canary_orchestration_foundation",
"canary_conversation_state",
"canary_gitea_pipeline",
"canary_tui_drift",
"canary_cognitive_strata",
"canary_surface_parity"
],
"depends_on": [
"perception",
"capability",
"inference",
"identity",
"growth"
]
}
}
}
+28
View File
@@ -111,3 +111,31 @@ subsystem): fetches live issue state, compares to
- record lacking a required field
Exit 0 = consistent; exit 1 = drift listed. CI-eligible (read-only).
## PL1 held-out era reconciliation (2026-09-12)
Fresh-session reconciliation of the ADAPT family against deployed 0.66.22
@ 1497c85 + live PL1 held-out evidence (tasks task_2661189c9073,
task_6e4885031355; ledger PE-9207213099-0270). Resolves the recorded tension:
"ADAPT-LIVE complete → return target PL1" (the bounded tranche authorized
2026-09-10 is complete) while numbered ADAPT issues remain open (the larger
backlog was never in the ADAPT-LIVE tranche scope).
| # | state | evidence / basis |
|---|---|---|
| 284 | VALID_OPEN | epic tracker for the remaining sub-items below |
| 292 | FIXED_VERIFIED (closed) | LOCAL_FIX fast path live (rune_operator.py:522,541); live-proven twice by the held-out tasks |
| 293 | FIXED_VERIFIED (closed) | DESIGN-LITE implemented (rune_operator.py:660, cites #293), wired to STANDARD_CHANGE; suite+canary green |
| 294 | DEFERRED_FUTURE | canonical remaining-obligations list ("ADAPT-5 budgets... for later epics") |
| 295 | DEFERRED_FUTURE | canonical remaining-obligations list |
| 296 | DEFERRED_FUTURE | canonical remaining-obligations list |
| 297 | FIXED_VERIFIED (closed) | task_gate implemented (src/rune/task_gate.py) + broker wiring + canary registration |
| 299 | DEFERRED_FUTURE | canonical remaining-obligations list |
| 300 | VALID_OPEN | escalation mechanism exists (_probe_and_escalate); fresh deployed acceptance evidence pending |
| 301 | DEFERRED_FUTURE | canonical remaining-obligations list (FINAL-12) |
| 315 | VALID_OPEN | not started; ADAPT-1 sub-item (no canonical deferral authority) |
| 316 | VALID_OPEN | not started; ADAPT-1 sub-item (no canonical deferral authority) |
Consistency checker: `scripts/gitea_reconciliation_check.py` → exit 0
(72 records, 100 live issues in window) after these updates.
+11 -1
View File
@@ -54,7 +54,7 @@ This detour is complete when:
## RETURN TARGET / CURRENT: PL1 Process Learning — FIRST TRANCHE LIVE (2026-09-11)
PL1-01 structured Experience records: `scripts/pl1_records.py` (task_id, route resolved from route.jsonl, proof level, failure class, candidate status). PL1-02 failure classification in the PL0 vocabulary (research_dispatch, NOT_COMPLETED_NO_MUTATION_EVIDENCE, verification_failure, model_refusal, UNKNOWN). PL1-03 first learning loop: recurring prose-instead-of-mutation family → structural correction (route-driven instruction in the user context block) + replay contract (NOT_COMPLETED gate) — record PE-1789102658-0269 (SEED; held-out = next deployed mutation tasks must complete-with-evidence or fail honestly). PL1-04 promotion discipline: SEED→CANDIDATE→VALIDATED→PROMOTED via process_experience; structural fixes preferred. PL1-05 metrics: first_pass_accepted, mutation_failures, research_dispatches, false_completion_rate (0.0 — structurally prevented), candidates vs evidence-only split.
PL1-01 structured Experience records: `scripts/pl1_records.py` (task_id, route resolved from route.jsonl, proof level, failure class, candidate status). PL1-02 failure classification in the PL0 vocabulary (research_dispatch, NOT_COMPLETED_NO_MUTATION_EVIDENCE, verification_failure, model_refusal, UNKNOWN). PL1-03 first learning loop: recurring prose-instead-of-mutation family → structural correction (route-driven instruction in the user context block) + replay contract (NOT_COMPLETED gate) — record PE-1789102658-0269 (SEED 2026-09-11 → VALIDATED 2026-09-12: two fresh deployed held-out mutation tasks completed-with-evidence — task_2661189c9073 + task_6e4885031355, LOCAL_FIX, Verboo-pinned, mutation + exit-code verification, ledger PE-9207213099-0270; substrate corrections 6965fc7 flat-ChatMessage contract + a2463ad resume transport selection applied between; caveat: corrections developer-applied structurally, not autonomous self-modification; PROMOTED requires repetition/soak). PL1-04 promotion discipline: SEED→CANDIDATE→VALIDATED→PROMOTED via process_experience; structural fixes preferred. PL1-05 metrics: first_pass_accepted, mutation_failures, research_dispatches, false_completion_rate (0.0 — structurally prevented), candidates vs evidence-only split.
Remaining ADAPT obligations for later epics: ADAPT-5 budgets, ADAPT-6 evidence capsule, ADAPT-7 DESIGN consolidation, ADAPT-10 legibility, FINAL-12 cross-track benchmark, D7 release-job-on-Pi.
@@ -177,3 +177,13 @@ Root causes (live-proven T15 Part F, 2026-09-11 evening):
Live proof: experiment A (trigger d68fd1e, warranted patch) → exactly one
bump commit a4afc77 (0.66.11), all five surfaces in one commit, queue
drained, zero delayed duplicates, zero workflow failures.
## Local CI tooling (developer-side, NOT Rune runtime): W/R-v0.3 reactive wake (2026-09-12)
The desktop gitea-runner (v3.3.0) now runs `host.post_task_script` = `/usr/local/bin/gitea-wr-wake.sh`
after every job: a sub-second fire-and-forget POST to the local W/R daemon (`127.0.0.1:8791/wake`),
which sweeps waiting external waits through AUTHORITATIVE Gitea verification (bounded retry for the
reporter-ack window; 60s reconciler demoted to fallback). Runner event ≠ success proof. Config:
`/etc/gitea-desktop-runner/config.yaml` (backup: config.yaml.pre-wr03). Measured: terminal→verified
wait transition ~2s (was 0-30s polling). INTERACTIVE_AUTO_RESUME remains NOT YET SAFE (no supported
MiMo busy/idle signal) — headless-only dispatch, unchanged.
+70
View File
@@ -0,0 +1,70 @@
# Scaffolding V1 — Tranche 1
_2026-09-12. Scaffolding is the controlled need-to-capability system: NEED →
DISCOVERY → GAP → REUSE/ACQUIRE/BUILD → IMPACT+PLAN → SAFE IMPLEMENTATION →
VERIFICATION → PROVISIONAL USE → TRUST/REVISE/RETIRE → REUSABLE CAPABILITY.
Tranche 1 is deliberately narrower: make Rune stop rediscovering how Rune is
built every time Rune changes._
## Tranche 1 contract
`scripts/scaffold_projection.py` — a PROJECTION over existing authorities,
never a new one:
| question | authority projected |
|---|---|
| what subsystem owns X? | `config/umbrellas.json` (umbrella manifest + henxel-enforced completeness) |
| what does X depend on? | AST import graph over `src/rune/*.py` (incl. relative/lazy imports) |
| who consumes X? | reverse import edges |
| what changes if X changes? | mirrors + `depends_on` umbrellas + consumers |
| what proof obligations arise? | unit-test file, canary registry, umbrella gates, suite/lint/henxels |
| which are satisfied? | filesystem + registry evidence (static); suite/ratchet/henxels are runtime-check commands |
Commands: `component <module>` · `impact <module>` · `obligations <module>` ·
`all [outfile]`. If the projection disagrees with an authority, fix the
authority — never the projection.
## Mechanism audit (2026-09-12, deployed 0.66.22)
| mechanism | verdict | basis |
|---|---|---|
| Umbrella manifest + impact map + propagation gate | KEEP | ownership/impact authority; henxel-enforced completeness; consumed by projection |
| Henxels structural contract | KEEP | cross-cutting obligations (silent-handler ratchet, registries, coverage debt) |
| Canary registry (`canary_native.MODULES` + per-track canaries) | KEEP | per-subsystem proof obligations; consumed by projection |
| CI/deploy contract (required checks, TUI startup canary, atomic promote) | KEEP | deployment proof chain live-proven repeatedly |
| Version surfaces + auto-version + semver | KEEP | bump fast lane + push convergence working (0.66.22 earned this session) |
| Lint/pyright/coverage ratchets | KEEP | shrinking-baseline gates, all green |
| Authority map + dead-subnet ledger (D6) | EVOLVE | static doc today; Scaffolding projections make it derivable; D6 retire-disposition still owed |
| Issue reconciliation artifact + checker | KEEP | tracker truth with drift detection |
| PL1 experience ledger | KEEP | the trust/revise/retire input for future capability crystallisation |
| Intent continuity ledger | KEEP | PART 6 state machine, 7 invariants |
| `MIMO_AUTOMATIC_RESUME` / W/R interactive injection | KEEP (bounded) | headless single-flight proven; interactive auto-resume NOT YET SAFE until a supported busy signal exists |
| Duplicate authorities risk | REPLACE (avoided) | Scaffolding v1 adds NO new registry — projections only |
## Real-change proof (this tranche)
Change: context discovery hit Ollama's `/api/show` with cloud-pinned model
names — `400 invalid model name` noise on every cloud task + 8192 misbudget
for a 131072-window model (observed live in every PL1 Task A/B run).
1. BEFORE mutation: `scaffold_projection.py impact local_tool_runtime`
derived — subsystem `capability`; consumers `rune_operator`; gates:
full suite, lint ratchet, henxels, inference+orchestration canaries;
unit-test obligation `tests/test_local_tool_runtime.py` (exists).
2. Change made: `is_cloud_pinned` — THE provider-prefix rule, single
authority in `openai_compat_transport.py`; consumed by context discovery
(new) and transport selection (deduped); red-first regression tests.
3. Obligations tracked: suite 1314 green · lint ratchet PASS · henxels exit 0
· the projection itself caught two gaps mid-flight (relative imports
hidden consumers; a broad except biting the lint ratchet) — both fixed at
the source the projection pointed to.
4. Deploy: normal release chain → deployed identity verified.
## Maturity (honest)
- Projection engine: **UNIT-PROVEN** + proven on one real change.
- Not yet: CI integration of `scaffold_projection.py all` (drift detection),
capability discovery / gap recognition / reuse-before-build / external
acquisition / provisional-trust lifecycle — FOUNDATIONAL ONLY (architecture
does not block them; the projection source-list is the extension surface).
- Deferred with reasons recorded: none blocked by Tranche 1.
+35 -35
View File
@@ -34,28 +34,28 @@
"number": 316,
"title": "ADAPT-1 B3-v2 (RESEARCH-LIKE): Qdrant associative-memory projection spike (executes #162)",
"tracker_state": "open",
"reconciliation_state": "DEFERRED_FUTURE",
"reconciliation_state": "VALID_OPEN",
"labels": [
"governance",
"memory",
"perception"
],
"live_defect": false,
"last_revalidated": "2026-09-11",
"next_action": ""
"last_revalidated": "2026-09-12",
"next_action": "B3-v2 Qdrant associative-memory projection spike \u2014 not started; ADAPT-1 sub-item under epic #284"
},
{
"number": 315,
"title": "ADAPT-1 B2-v2 (STANDARD-LIKE): wiring-status/architecture-truth report generator (implements #158)",
"tracker_state": "open",
"reconciliation_state": "DEFERRED_FUTURE",
"reconciliation_state": "VALID_OPEN",
"labels": [
"infrastructure",
"perception"
],
"live_defect": false,
"last_revalidated": "2026-09-11",
"next_action": ""
"last_revalidated": "2026-09-12",
"next_action": "B2-v2 wiring-status/architecture-truth report generator \u2014 not started; ADAPT-1 sub-item under epic #284"
},
{
"number": 311,
@@ -80,8 +80,8 @@
"perception"
],
"live_defect": false,
"last_revalidated": "2026-09-11",
"next_action": ""
"last_revalidated": "2026-09-12",
"next_action": "cross-track benchmark + adaptive provisional ratification \u2014 canonical remaining-obligations list"
},
{
"number": 300,
@@ -92,90 +92,90 @@
"perception"
],
"live_defect": false,
"last_revalidated": "2026-09-11",
"next_action": ""
"last_revalidated": "2026-09-12",
"next_action": "escalation/downgrade honored at acceptance on the deployed runtime \u2014 probe_and_escalate exists; fresh live acceptance evidence pending"
},
{
"number": 299,
"title": "ADAPT-10: Harness/context legibility audit",
"tracker_state": "open",
"reconciliation_state": "VALID_OPEN",
"reconciliation_state": "DEFERRED_FUTURE",
"labels": [
"feature",
"governance"
],
"live_defect": false,
"last_revalidated": "2026-09-11",
"next_action": ""
"last_revalidated": "2026-09-12",
"next_action": "harness/context legibility audit \u2014 canonical remaining-obligations list"
},
{
"number": 297,
"title": "ADAPT-8: Deterministic-first QUALIFY",
"tracker_state": "open",
"reconciliation_state": "VALID_OPEN",
"tracker_state": "closed",
"reconciliation_state": "FIXED_VERIFIED",
"labels": [
"memory"
],
"live_defect": false,
"last_revalidated": "2026-09-11",
"last_revalidated": "2026-09-12",
"next_action": ""
},
{
"number": 296,
"title": "ADAPT-7: DESIGN consolidation experiment (APPLICABILITY + INNOVATION)",
"tracker_state": "open",
"reconciliation_state": "VALID_OPEN",
"reconciliation_state": "DEFERRED_FUTURE",
"labels": [
"memory"
],
"live_defect": false,
"last_revalidated": "2026-09-11",
"next_action": ""
"last_revalidated": "2026-09-12",
"next_action": "DESIGN consolidation experiment (APPLICABILITY+INNOVATION) \u2014 canonical remaining-obligations list"
},
{
"number": 295,
"title": "ADAPT-6: Evidence capsule / context minimization",
"tracker_state": "open",
"reconciliation_state": "VALID_OPEN",
"reconciliation_state": "DEFERRED_FUTURE",
"labels": [],
"live_defect": false,
"last_revalidated": "2026-09-11",
"next_action": ""
"last_revalidated": "2026-09-12",
"next_action": "evidence capsule / context minimization \u2014 canonical remaining-obligations list"
},
{
"number": 294,
"title": "ADAPT-5: Bound RESEARCHED_CHANGE work (budgets + stop conditions)",
"tracker_state": "open",
"reconciliation_state": "VALID_OPEN",
"reconciliation_state": "DEFERRED_FUTURE",
"labels": [
"feature"
],
"live_defect": false,
"last_revalidated": "2026-09-11",
"next_action": ""
"last_revalidated": "2026-09-12",
"next_action": "budgets + stop conditions for RESEARCHED_CHANGE \u2014 canonical 'remaining ADAPT obligations for later epics'"
},
{
"number": 293,
"title": "ADAPT-4: STANDARD_CHANGE path (DESIGN-LITE)",
"tracker_state": "open",
"reconciliation_state": "VALID_OPEN",
"tracker_state": "closed",
"reconciliation_state": "FIXED_VERIFIED",
"labels": [
"feature"
],
"live_defect": false,
"last_revalidated": "2026-09-11",
"last_revalidated": "2026-09-12",
"next_action": ""
},
{
"number": 292,
"title": "ADAPT-3: LOCAL_FIX fast path",
"tracker_state": "open",
"reconciliation_state": "VALID_OPEN",
"tracker_state": "closed",
"reconciliation_state": "FIXED_VERIFIED",
"labels": [
"perception"
],
"live_defect": false,
"last_revalidated": "2026-09-11",
"last_revalidated": "2026-09-12",
"next_action": ""
},
{
@@ -190,8 +190,8 @@
"perception"
],
"live_defect": false,
"last_revalidated": "2026-09-11",
"next_action": ""
"last_revalidated": "2026-09-12",
"next_action": "epic tracker for remaining ADAPT sub-items (#294 #295 #296 #299 #300 #301 #315 #316)"
},
{
"number": 259,
@@ -1152,5 +1152,5 @@
}
],
"last_reconciled": "2026-09-12",
"reconciled_by": "W/R-v0.1 overnight (ADAPT-only pass)"
}
"reconciled_by": "PL1 held-out era reconciliation (fresh session 2026-09-12)"
}
+1 -1
View File
@@ -1,7 +1,7 @@
settings:
warn_about_similar_files:
above: 0.85
ignore: ["docs/review-lessons.yaml", "docs/v1/**", "docs/wiki/**"]
ignore: ["docs/review-lessons.yaml", "docs/v1/**", "docs/wiki/**", "store/spine/**"]
henxels:
- henxel: "Canaries live at the repo's canary slot and are registered in CI"
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "rune"
version = "0.66.22"
version = "0.66.26"
description = "Rune — a stateful coding assistant with persistent memory, orchestration, and TUI"
requires-python = ">=3.10"
View File
Whitespace-only changes.
+205
View File
@@ -0,0 +1,205 @@
#!/usr/bin/env python3
"""scaffold_projection.py — Scaffolding V1 Tranche 1 (projection, not authority).
Answers, per component/module:
what subsystem owns X? → SPINE store (structural authority)
what does X depend on? → SPINE relationships (ingested from AST)
who consumes X? → SPINE relationships (ingested from AST)
what changes if X changes? → mirrors + dependent subsystems + consumers (SPINE)
what proof obligations arise? → unit test, canary, ownership, umbrella gates
which obligations are complete? → evidence check (test file exists, canary registered)
SPINE INTEGRATION: structural facts come from the Spine store — the single
structural authority (ingested once from the umbrella manifest + AST).
This script adds change-impact and proof-obligation reasoning on top; it
no longer re-derives structure. No store → builds one (deterministic).
Commands:
component <module> full card for one src/rune module
impact <module> expected impact + obligations BEFORE a change
obligations <module> obligation checklist with satisfied/pending state
all [outfile] write the full projection JSON (default docs/)
"""
from __future__ import annotations
import ast, json, sys
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
SRC = ROOT / "src" / "rune"
TESTS = ROOT / "tests"
sys.path.insert(0, str(ROOT / "src"))
from rune import spine # noqa: E402 — Spine IS the structural authority
def load_spine() -> dict:
"""Structural state from the Spine store (built on first use)."""
doc = spine.load()
if doc is None:
doc = spine.build()
spine.save(doc)
return doc
def module_imports(module: str) -> set[str]:
"""Module names imported by src/rune/<module>.py (intra-package).
Includes relative imports (`from .x import y` — the dual-mode lazy
pattern used across the runtime; missing these would hide real
consumers, observed first run: rune_operator ↔ local_tool_runtime)."""
path = SRC / f"{module}.py"
if not path.is_file():
return set()
tree = ast.parse(path.read_text())
names: set[str] = set()
for node in ast.walk(tree):
if isinstance(node, ast.Import):
for a in node.names:
names.add(a.name.split(".")[0])
elif isinstance(node, ast.ImportFrom) and node.module:
names.add(node.module.split(".")[0])
return names
def import_graph() -> dict[str, set[str]]:
return {p.stem: module_imports(p.stem) for p in SRC.glob("*.py")}
def consumers_of(module: str, graph: dict[str, set[str]]) -> set[str]:
return {m for m, deps in graph.items() if module in deps and m != module}
def subsystem_for(doc: dict, module: str) -> str:
rels = spine.relationships(doc, from_id=f"module:{module}", type="belongs_to")
return rels[0]["to"].split(":", 1)[1] if rels else ""
def dependent_subsystems(doc: dict, subsystem: str) -> list[str]:
return [r["from"].split(":", 1)[1] for r in
spine.relationships(doc, to_id=f"subsystem:{subsystem}", type="depends_on")]
def mirrors_for(doc: dict, module: str) -> list[str]:
subs = spine.relationships(doc, from_id=f"module:{module}", type="belongs_to")
out = []
for s in subs:
out.extend(r["to"].split(":", 1)[1] for r in
spine.relationships(doc, from_id=s["to"], type="mirrors"))
return sorted(set(out))
def canary_registered(module: str) -> bool:
try:
sys.path.insert(0, str(SRC))
from rune import canary_native
return module in canary_native.MODULES
except ImportError:
# canary_native pulls optional deps (sqlite_vec etc.); the projection
# degrades to "not registered" rather than failing the audit.
return False
def obligations(module: str, doc: dict) -> list[dict]:
"""Proof obligations for changing this module, with evidence state.
Structural facts sourced from Spine."""
umb = subsystem_for(doc, module)
test_file = TESTS / f"test_{module}.py"
obs = [
{"obligation": "unit_test", "satisfied": test_file.is_file(),
"evidence": f"tests/test_{module}.py" + ("" if test_file.is_file() else " (MISSING)")},
{"obligation": "canary", "satisfied": canary_registered(module),
"evidence": "canary_native.MODULES" if canary_registered(module)
else "not in canary registry (informational — not every module needs one)"},
{"obligation": "umbrella_manifest", "satisfied": bool(umb),
"evidence": f"umbrella '{umb}'" if umb else "NOT in any umbrella — manifest gap"},
{"obligation": "full_suite", "satisfied": None,
"evidence": "run: pytest tests/ -q (runtime evidence, not static)"},
{"obligation": "lint_ratchet", "satisfied": None,
"evidence": "run: scripts/lint_ratchet.py"},
{"obligation": "henxels", "satisfied": None,
"evidence": "run: henxels check --all"},
]
if umb:
deps = dependent_subsystems(doc, umb)
if deps:
obs.append({"obligation": "dependent_canaries", "satisfied": None,
"evidence": f"subsystems depending on '{umb}': {', '.join(deps)} — their canaries gate the change"})
mir = mirrors_for(doc, module)
if mir:
obs.append({"obligation": "mirrors_travel", "satisfied": None,
"evidence": f"files that must travel together: {', '.join(mir)}"})
return obs
def component_card(module: str, doc: dict) -> dict:
"""Structural card from SPINE (owner/deps/consumers/mirrors) + obligations."""
deps = [r["to"].split(":", 1)[1] for r in
spine.relationships(doc, from_id=f"module:{module}", type="depends_on")]
consumers = [r["to"].split(":", 1)[1] for r in
spine.relationships(doc, from_id=f"module:{module}", type="consumed_by")]
e = spine.entity(doc, f"module:{module}")
card = {
"component": module,
"subsystem": subsystem_for(doc, module),
"dependencies": sorted(set(deps)),
"consumers": sorted(set(consumers)),
"mirrors": mirrors_for(doc, module),
"obligations": obligations(module, doc),
}
if e:
card["spine_maturity"] = e.get("maturity")
card["spine_evidence"] = e.get("evidence_refs", [])
return card
def impact(module: str) -> dict:
doc = load_spine()
card = component_card(module, doc)
umb = card["subsystem"]
card["expected_impact"] = {
"direct_consumers": card["consumers"],
"subsystem_dependents": dependent_subsystems(doc, umb) if umb else [],
"must_travel": card["mirrors"],
"gates": ["full_suite", "lint_ratchet", "henxels", "umbrella_gates"]
+ (["canary"] if card and any(o["obligation"] == "canary" and o["satisfied"]
for o in card["obligations"]) else []),
}
return card
def main() -> int:
args = sys.argv[1:]
if not args:
print(__doc__)
return 2
cmd = args[0]
if cmd in ("component", "impact", "obligations") and len(args) < 2:
print(f"usage: {cmd} <module>", file=sys.stderr)
return 2
doc = load_spine()
if cmd == "impact":
print(json.dumps(impact(args[1]), indent=1))
elif cmd == "component":
print(json.dumps(component_card(args[1], doc), indent=1))
elif cmd == "obligations":
card = component_card(args[1], doc)
print(json.dumps(card["obligations"], indent=1))
elif cmd == "all":
out = Path(args[1]) if len(args) > 1 else ROOT / "docs" / "scaffold_projection.json"
cards = {p.stem: component_card(p.stem, doc)
for p in sorted(SRC.glob("*.py"))
if spine.entity(doc, f"module:{p.stem}")}
out.write_text(json.dumps({
"generated_by": "scripts/scaffold_projection.py (projection — no new authority)",
"sources": ["rune.spine store (structural authority)", "src/rune/canary_native.py",
"tests/ filesystem"],
"components": cards,
}, indent=1) + "\n")
print(f"projection written: {out} ({len(cards)} components)")
else:
print(f"unknown command: {cmd}", file=sys.stderr)
return 2
return 0
if __name__ == "__main__":
sys.exit(main())
+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.66.22"
__version__ = "0.66.26"
# 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
@@ -231,7 +231,7 @@ def main() -> int:
)
check(
"Rune minor version advertises provisioning and restore verification",
'__version__ = "0.66.22"' in rune_version,
'__version__ = "0.66.26"' in rune_version,
failures,
)
# #169 policy update: auto-bump on merge is sanctioned (Nick directive:
+10 -1
View File
@@ -238,6 +238,7 @@ def context_length(model: str, base_url: str = "http://localhost:11434") -> int:
conservative default 8192 + T1 ``record_failure("context_discovery")``
— the assumption is observable, never silent (P0-06).
"""
from rune.openai_compat_transport import is_cloud_pinned
from rune.subsystem_health import record_failure
with _CONTEXT_LENGTH_LOCK:
@@ -245,7 +246,15 @@ def context_length(model: str, base_url: str = "http://localhost:11434") -> int:
if cached:
return cached
try:
window = OllamaChatTransport(base_url=base_url).context_length(model)
if is_cloud_pinned(model):
# Cloud-pinned models must not hit Ollama's /api/show — observed
# live: 400 'invalid model name' + 8192 misbudget for a model
# with a 131072 window. The provider config carries the window.
from rune.openai_compat_transport import cloud_transport_for
window = cloud_transport_for(model).context_length(model)
else:
window = OllamaChatTransport(base_url=base_url).context_length(model)
except Exception as exc:
record_failure("context_discovery", exc, tier=1, context=model)
return CONTEXT_LENGTH_FALLBACK
+10
View File
@@ -174,6 +174,16 @@ class OpenAICompatTransport:
return self._context_window
def is_cloud_pinned(model: str) -> bool:
"""THE provider-prefix rule, single authority: True when the model name
requires the [OI]-compat wire — a non-empty provider prefix that is
neither ollama nor local. Consumers: transport selection
(rune_operator._transport_for) and context discovery
(local_tool_runtime.context_length). Never copy this rule — import it."""
prefix = model.split("/", 1)[0].strip().lower() if "/" in model else ""
return bool(prefix) and prefix not in ("ollama", "local")
def cloud_transport_for(model: str, timeout: float = 120) -> OpenAICompatTransport:
"""Resolve provider config for a '<Provider>/<model>' name from the same
source of truth the mimo CLI uses (mimocode.jsonc + auth.json)."""
+2 -3
View File
@@ -1238,10 +1238,9 @@ class RuneOperator:
live (PL1 Task A, 2026-09-12): the resume path hardcoded
OllamaChatTransport, so the post-approval follow-up call hit
localhost with a cloud model name → 400 'invalid model name'."""
_prov_prefix = model.split("/", 1)[0].strip().lower() if "/" in model else ""
if _prov_prefix and _prov_prefix not in ("ollama", "local"):
from rune.openai_compat_transport import cloud_transport_for
from rune.openai_compat_transport import cloud_transport_for, is_cloud_pinned
if is_cloud_pinned(model):
return cloud_transport_for(model, timeout=timeout)
return OllamaChatTransport(timeout=timeout)
+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.66.22
Version: v0.66.26
Features: 41 (24 original + 17 new native commands)
Usage:
@@ -1856,7 +1856,7 @@ class RuneApp(App):
# Show version info
self.chat_channel.add_message(
"system",
f"[dim]Rune v0.66.22 | Textual TUI | {len(loaded)} native modules[/dim]",
f"[dim]Rune v0.66.26 | Textual TUI | {len(loaded)} native modules[/dim]",
)
# Auto-show night mode suggestion if evening/night hours
+824
View File
@@ -0,0 +1,824 @@
"""spine.py — Rune Spine V1: evolvable structural self-model.
Spine answers, deterministically and without an LLM:
what exists, how it relates, what is wired/live, what version is
deployed, what evidence supports each claim, what drifted, what changed.
PRIME DIRECTIVE: model Rune's architecture without freezing it. Entity
kinds, relationship types and metadata are OPEN TAGS — a previously unseen
capability enters as data, never as a core edit. Unknown fields survive
load/projection/serialize/reload untouched.
AUTHORITY MODEL: Spine is THE structural authority (subsystems, membership,
dependencies, consumers, mirrors). Specialised authorities stay authoritative
for their own domains and are OBSERVED, not copied: deployment identity
(_build_identity.json), canary registry (canary_native.MODULES), provider
config, CI outputs. Adding a domain = adding an adapter/projector.
MATURITY LADDER (evidence-gated, no evidence = no promotion):
DECLARED < PRESENT < WIRED < REACHABLE < EXERCISED < LIVE_PROVEN
DECLARED named in a registry/manifest
PRESENT source artifact exists
WIRED structural consumers/imports exist (or registry wiring)
REACHABLE reachable from the composition root / deployed tree contains it
EXERCISED test or canary exercises it
LIVE_PROVEN runtime observation on the deployed identity proves it live
SNAPSHOTS: ingest is a pure function of authorities (sorted output), so
snapshots are byte-deterministic; diff(a, b) answers "what changed" without
interpretation.
Schema version: 1. Unknown metadata survives by construction; a future
reader migrates by adding a projector, never by editing old records.
CLI: PYTHONPATH=src python3 -m rune.spine <command> (JSON canonical).
"""
from __future__ import annotations
import ast
import json
import os
import sys
import time
from pathlib import Path
SCHEMA_VERSION = 1
MATURITY_ORDER = ["DECLARED", "PRESENT", "WIRED", "REACHABLE", "EXERCISED", "LIVE_PROVEN"]
_MATURITY_RANK = {name: i for i, name in enumerate(MATURITY_ORDER)}
GENERATED_VIEW_MARKER = "_generated_by_spine"
ROOT = Path(__file__).resolve().parents[2] # repo root (src/rune/spine.py → up 2)
SRC_DIR = ROOT / "src" / "rune"
TESTS_DIR = ROOT / "tests"
STORE_DIR = ROOT / "store" / "spine"
CURRENT = STORE_DIR / "current.json"
SNAPSHOTS = STORE_DIR / "snapshots"
# Deployment observation point (this machine's promoted build). Configurable:
# never an identity, only an observation surface.
DEPLOYED_LINK = Path(os.environ.get("RUNE_DEPLOYED_LINK", "/mnt/workdrive/rune-code/current"))
UMBRELLA_MANIFEST = ROOT / "config" / "umbrellas.json"
def now() -> str:
return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
def _sorted(obj):
"""Deterministic normalization: sort everything sortable, recursively."""
if isinstance(obj, dict):
return {k: _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 obj
# ── store ─────────────────────────────────────────────────────────────────
def new_doc() -> dict:
return {"schema_version": SCHEMA_VERSION, "generated_at": now(),
"entities": [], "relationships": [], "observations": []}
def load(path: Path | None = None) -> dict | None:
p = path or CURRENT
if not p.is_file():
return None
try:
return json.loads(p.read_text())
except json.JSONDecodeError:
return None
def save(doc: dict, path: Path | None = None) -> Path:
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 or CURRENT
p.parent.mkdir(parents=True, exist_ok=True)
p.write_text(json.dumps(_sorted(doc), indent=1, sort_keys=True) + "\n")
return p
def add_entity(doc: dict, *, id: str, kind: str, tags=None, identities=None,
evidence_refs=None, maturity="DECLARED", ext=None) -> dict:
"""Register an entity. kind/tags/ext are open — unknown values are data.
Refuses to downgrade an existing entity's maturity: promotion requires
evidence (via observe/promote); re-registration with a lower claimed
maturity keeps the higher evidenced state."""
for e in doc["entities"]:
if e["id"] == id:
if maturity and _MATURITY_RANK.get(maturity, 0) > _MATURITY_RANK.get(e.get("maturity", "DECLARED"), 0):
e["maturity"] = maturity # only upgrades here; promotion gate below
if tags:
e["tags"] = sorted(set(e.get("tags", [])) | set(tags))
if identities:
e.setdefault("identities", {}).update(identities)
if evidence_refs:
e["evidence_refs"] = sorted(set(e.get("evidence_refs", [])) | set(evidence_refs))
if ext:
e.setdefault("ext", {}).update(ext)
e["observed_at"] = now()
return e
ent = {"id": id, "kind": kind, "maturity": maturity,
"tags": sorted(tags or []), "identities": dict(identities or {}),
"evidence_refs": sorted(evidence_refs or []),
"observed_at": now()}
if ext:
ent["ext"] = dict(ext)
doc["entities"].append(ent)
return ent
def add_relationship(doc: dict, *, type: str, from_id: str, to_id: str,
evidence_refs=None, ext=None) -> dict:
rel = {"type": type, "from": from_id, "to": to_id,
"evidence_refs": sorted(evidence_refs or []), "observed_at": now()}
if ext:
rel["ext"] = dict(ext)
if not any(r["type"] == type and r["from"] == from_id and r["to"] == to_id
for r in doc["relationships"]):
doc["relationships"].append(rel)
return rel
def entity(doc: dict, id: str) -> dict | None:
return next((e for e in doc["entities"] if e["id"] == id), None)
def relationships(doc: dict, *, from_id=None, to_id=None, type=None) -> list[dict]:
out = []
for r in doc["relationships"]:
if from_id and r["from"] != from_id:
continue
if to_id and r["to"] != to_id:
continue
if type and r["type"] != type:
continue
out.append(r)
return out
def promote(doc: dict, id: str, state: str, evidence_ref: str) -> dict | None:
"""Evidence-gated maturity promotion. No evidence ref = no promotion.
Demotion is never silent: it records an observation, it does not rewrite."""
e = entity(doc, id)
if e is None:
return None
if not evidence_ref:
raise ValueError(f"promotion of {id} to {state} requires evidence")
if state not in _MATURITY_RANK:
raise ValueError(f"unknown maturity state: {state}")
current = _MATURITY_RANK.get(e.get("maturity", "DECLARED"), 0)
if _MATURITY_RANK[state] < current:
doc["observations"].append({
"kind": "maturity_demotion_rejected", "entity": id,
"requested": state, "current": e["maturity"], "observed_at": now()})
return e
e["maturity"] = state
e["evidence_refs"] = sorted(set(e.get("evidence_refs", [])) | {evidence_ref})
e["observed_at"] = now()
return e
# ── static projections (deterministic, from authorities) ─────────────────
def _module_imports(module: str) -> set[str]:
path = SRC_DIR / f"{module}.py"
try:
tree = ast.parse(path.read_text())
except (OSError, SyntaxError):
return set()
names: set[str] = set()
for node in ast.walk(tree):
if isinstance(node, ast.Import):
for a in node.names:
names.add(a.name.split(".")[0])
elif isinstance(node, ast.ImportFrom) and node.module:
names.add(node.module.split(".")[0])
return names
def _import_graph() -> dict[str, set[str]]:
return {p.stem: _module_imports(p.stem) for p in SRC_DIR.glob("*.py")}
def _canary_registry() -> list[str]:
try:
if str(SRC_DIR) not in sys.path:
sys.path.insert(0, str(SRC_DIR))
from rune import canary_native
return list(canary_native.MODULES)
except ImportError:
# canary_native pulls optional deps (sqlite_vec etc.); degrade to an
# empty registry — drift will note absent registrations honestly.
return []
def _deployed_observation() -> dict | None:
"""Observe the deployment authority — never invent it."""
ident = DEPLOYED_LINK / "src" / "rune" / "_build_identity.json"
if not ident.is_file():
return None
try:
data = json.loads(ident.read_text())
return {"deployed_sha": data.get("commit", "")[:12],
"deployed_version": data.get("version", ""),
"deployed_at": data.get("deployed_at", ""),
"observed_from": str(DEPLOYED_LINK)}
except (json.JSONDecodeError, OSError):
return None
def _test_backed_modules() -> set[str]:
out = set()
for p in TESTS_DIR.glob("test_*.py"):
out.add(p.stem[5:])
return out
def ingest_umbrellas(doc: dict, manifest_path: Path | None = None) -> int:
"""Phase A: translate the (last manually maintained) umbrella manifest
into Spine entities + relationships. Refuses a Spine-GENERATED view —
that guards the ingest cycle after Phase C makes the view output-only.
Returns count of subsystems ingested."""
path = manifest_path or UMBRELLA_MANIFEST
text = path.read_text()
data = json.loads(text)
if GENERATED_VIEW_MARKER in data:
raise ValueError(
f"{path} 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.")
count = 0
KNOWN_FIELDS = {"description", "modules", "depends_on", "mirrors", "canaries"}
for name, u in data.get("umbrellas", {}).items():
sid = f"subsystem:{name}"
# canaries ride as ext data (obligation metadata, not structure);
# unknown future manifest fields ride too — open-world preservation
ext = {k: u[k] for k in u if k in ("canaries",) or k not in KNOWN_FIELDS}
add_entity(doc, id=sid, kind="subsystem",
identities={"description": u.get("description", "")},
evidence_refs=[f"umbrella-manifest#{name}"],
maturity="DECLARED", ext=ext or None)
count += 1
for dep in u.get("depends_on", []):
add_relationship(doc, type="depends_on", from_id=sid,
to_id=f"subsystem:{dep}",
evidence_refs=[f"umbrella-manifest#{name}"])
for mirror in u.get("mirrors", []):
add_relationship(doc, type="mirrors", from_id=sid, to_id=f"file:{mirror}",
evidence_refs=[f"umbrella-manifest#{name}"])
for canary in u.get("canaries", []):
add_relationship(doc, type="obligated_canary", from_id=sid,
to_id=f"module:{canary}",
evidence_refs=[f"umbrella-manifest#{name}"])
for mod in u.get("modules", []):
mid = f"module:{mod}"
add_entity(doc, id=mid, kind="module", tags=["code"],
identities={"source": f"src/rune/{mod}.py"},
evidence_refs=[f"umbrella-manifest#{name}"],
maturity="DECLARED")
add_relationship(doc, type="belongs_to", from_id=mid, to_id=sid,
evidence_refs=[f"umbrella-manifest#{name}"])
return count
def build(manifest_path: Path | None = None) -> dict:
"""Deterministic full projection from authorities. Same inputs →
byte-identical doc (modulo generated_at)."""
doc = new_doc()
# 1. subsystem structure — from the Spine store if it already owns
# membership (Phase B+), else from the manual manifest (Phase A).
existing = load()
seeded = False
if existing:
rels = [r for r in existing["relationships"] if r["type"] == "belongs_to"]
if rels:
seeded = True
doc["entities"].extend(json.loads(json.dumps(
[e for e in existing["entities"]
if e["kind"] in ("subsystem", "module")])))
doc["relationships"].extend(json.loads(json.dumps(
[r for r in existing["relationships"]
if r["type"] in ("belongs_to", "depends_on", "mirrors")
and (r["from"].startswith(("subsystem:",)) or r["type"] == "belongs_to")])))
if not seeded:
ingest_umbrellas(doc, manifest_path)
# 2. module-level structural facts from actual code
graph = _import_graph()
registry = _canary_registry()
tested = _test_backed_modules()
mods = {e["id"].split(":", 1)[1] for e in doc["entities"] if e["kind"] == "module"}
for m in sorted(mods):
mid = f"module:{m}"
e = entity(doc, mid)
# Re-derivation uses the HIGH-WATER path (add_entity upgrades only,
# never logs): fresh evidence may be lower than the store's earned
# state on any given pass, and that is not a demotion event — it is
# a build. Explicit promote() logging is for evidence-gated API calls.
def _derive(state: str, ev: str) -> None:
add_entity(doc, id=mid, kind="module", maturity=state,
evidence_refs=[ev])
if (SRC_DIR / f"{m}.py").is_file():
e["identities"]["source"] = f"src/rune/{m}.py"
_derive("PRESENT", f"source:{m}.py")
consumers = sorted(c for c, deps in graph.items() if m in deps and c != m)
deps = sorted(d for d in graph.get(m, set()) if d in mods and d != m)
for c in consumers:
add_relationship(doc, type="consumed_by", from_id=mid,
to_id=f"module:{c}", evidence_refs=[f"ast-import:{m}"])
for d in deps:
add_relationship(doc, type="depends_on", from_id=mid,
to_id=f"module:{d}", evidence_refs=[f"ast-import:{m}"])
# WIRED: importers exist, or registry wiring (canary registry)
if consumers or m in registry:
_derive("WIRED", f"ast-imports:{len(consumers)}" if consumers
else "canary-registry")
# EXERCISED: dedicated test or canary exercise
if m in tested:
_derive("EXERCISED", f"tests/test_{m}.py")
elif m in registry:
_derive("EXERCISED", "canary-registry-exercise")
# 3. runtime/deployment observation (dynamic, bounded)
obs = _deployed_observation()
if obs:
dep = add_entity(doc, id="deployment:current", kind="deployment_surface",
identities=obs, evidence_refs=["_build_identity.json"],
maturity="LIVE_PROVEN")
for e in doc["entities"]:
if e["kind"] == "module" and (DEPLOYED_LINK / e["identities"].get("source", "")).is_file():
add_relationship(doc, type="deployed_as", from_id=e["id"],
to_id="deployment:current",
evidence_refs=[f"deployed-tree:{obs['deployed_sha']}"])
doc["observations"].append({"kind": "deployed_identity", **obs, "observed_at": now()})
doc["generated_at"] = now()
return doc
# ── drift ─────────────────────────────────────────────────────────────────
def drift(doc: dict) -> list[dict]:
"""Architectural drift classes. Reuses authorities' verdicts; never
duplicates their logic. Deterministic output."""
out = []
graph = _import_graph()
registry = _canary_registry()
tested = _test_backed_modules()
mods = {e["id"].split(":", 1)[1] for e in doc["entities"] if e["kind"] == "module"}
# 1. registered but absent (canary registry names a non-existent module)
for m in registry:
if m not in mods:
out.append({"class": "registered_but_absent", "entity": f"module:{m}",
"detail": "in canary registry, no source file"})
# 2. present but unowned (runtime source file outside every subsystem).
# Excludes canary files (governed by the canary contract, not umbrella
# membership) and package plumbing (__init__/__main__).
for p in SRC_DIR.glob("*.py"):
if p.stem.startswith(("canary_", "__")):
continue
if p.stem not in mods:
out.append({"class": "present_but_unowned", "entity": f"module:{p.stem}",
"detail": "src/rune runtime module with no subsystem membership"})
# 3. registered but no static importers — OBSERVATION, not a death
# verdict: registry dispatch and entry points wire dynamically
# (task_gate via capability_broker, rune_tui via entry point).
# Interpretation belongs to cognition; Spine reports the fact.
for m in sorted(mods & set(registry)):
importers = [c for c, deps in graph.items() if m in deps and c != m]
if not importers:
dyn = "no dedicated test either" if m not in tested else "dedicated test exists"
out.append({"class": "registered_no_static_importers", "entity": f"module:{m}",
"detail": f"zero static importers; dynamic wiring possible; {dyn}"})
# 4. deployed identity drift (source version vs deployment authority)
obs = _deployed_observation()
try:
src_version = (ROOT / "pyproject.toml").read_text()
import re
m = re.search(r'^version\s*=\s*"([^"]+)"', src_version, re.M)
src_version = m.group(1) if m else ""
except OSError:
src_version = ""
if obs and src_version and obs["deployed_version"] != src_version:
out.append({"class": "version_surfaces_disagree",
"detail": f"source {src_version} != deployed {obs['deployed_version']}"})
# 4b. evidence staleness (adapter freshness): maturity claims whose
# evidence references predate the current store generation are STALE
# observations, never silently current — "not checked" != "healthy".
for e in doc["entities"]:
if e.get("maturity") in ("EXERCISED", "LIVE_PROVEN") and not e.get("evidence_refs"):
out.append({"class": "evidence_conflict", "entity": e["id"],
"detail": f"claims {e['maturity']} with zero evidence refs"})
# 5. relationship disappeared (store claims consumers that no longer import)
for r in relationships(doc, type="consumed_by"):
m = r["from"].split(":", 1)[1]
c = r["to"].split(":", 1)[1]
if m not in graph.get(c, set()):
out.append({"class": "relationship_disappeared",
"detail": f"{c} no longer imports {m} (stale store edge)"})
return out
# ── snapshots / deltas ────────────────────────────────────────────────────
def snapshot(doc: dict, label: str = "") -> Path:
SNAPSHOTS.mkdir(parents=True, exist_ok=True)
obs = _deployed_observation() or {}
name = f"snap-{time.strftime('%Y%m%dT%H%M%SZ', time.gmtime())}-{obs.get('deployed_sha', 'noident')[:8]}"
if label:
name += f"-{label}"
p = SNAPSHOTS / f"{name}.json"
save(doc, p)
return p
def diff(old: dict, new: dict) -> dict:
"""Deterministic structural delta. No interpretation."""
oe = {e["id"]: e for e in old.get("entities", [])}
ne = {e["id"]: e for e in new.get("entities", [])}
ors = {(r["type"], r["from"], r["to"]) for r in old.get("relationships", [])}
nrs = {(r["type"], r["from"], r["to"]) for r in new.get("relationships", [])}
out = {"entities_added": sorted(set(ne) - set(oe)),
"entities_removed": sorted(set(oe) - set(ne)),
"relationships_added": sorted({"|".join(t) for t in nrs - ors}),
"relationships_removed": sorted({"|".join(t) for t in ors - nrs}),
"maturity_changed": [], "identity_changed": [], "drift_new": []}
for eid in sorted(set(oe) & set(ne)):
if oe[eid].get("maturity") != ne[eid].get("maturity"):
out["maturity_changed"].append(
{"id": eid, "from": oe[eid].get("maturity"), "to": ne[eid].get("maturity")})
if oe[eid].get("identities") != ne[eid].get("identities"):
out["identity_changed"].append(eid)
return out
# ── compatibility projection (Phase C) ────────────────────────────────────
def export_umbrellas(doc: dict) -> dict:
"""Generate the legacy umbrella view FROM Spine. Output only — the old
consumers (CI gate, henxels) read this during migration. Never ingest."""
umbrellas: dict = {}
for e in doc["entities"]:
if e["kind"] != "subsystem":
continue
name = e["id"].split(":", 1)[1]
umbrellas[name] = {"description": e["identities"].get("description", ""),
"modules": [], "depends_on": [], "mirrors": []}
# faithful reproduction: emit canaries only when the ingested
# authority carried them (the legacy contract omits empty lists)
if e.get("ext", {}).get("canaries"):
umbrellas[name]["canaries"] = sorted(e["ext"]["canaries"])
for r in doc["relationships"]:
if r["type"] == "belongs_to" and r["from"].startswith("module:"):
sub = r["to"].split(":", 1)[1]
if sub in umbrellas:
umbrellas[sub]["modules"].append(r["from"].split(":", 1)[1])
elif r["type"] == "depends_on" and r["from"].startswith("subsystem:"):
sub = r["from"].split(":", 1)[1]
if sub in umbrellas:
umbrellas[sub]["depends_on"].append(r["to"].split(":", 1)[1])
elif r["type"] == "mirrors" and r["from"].startswith("subsystem:"):
sub = r["from"].split(":", 1)[1]
if sub in umbrellas:
umbrellas[sub]["mirrors"].append(r["to"].split(":", 1)[1])
elif r["type"] == "obligated_canary" and r["from"].startswith("subsystem:"):
sub = r["from"].split(":", 1)[1]
if sub in umbrellas:
umbrellas[sub]["canaries"].append(r["to"].split(":", 1)[1])
for u in umbrellas.values():
u["modules"].sort(); u["depends_on"].sort(); u["mirrors"].sort()
if "canaries" in u:
u["canaries"].sort()
return {"_comment": "GENERATED by rune.spine — compatibility view for legacy "
"umbrella consumers. DO NOT EDIT: structural truth lives in "
"store/spine/current.json; edits here are flagged by spine drift.",
GENERATED_VIEW_MARKER: True,
"generated_at": now(),
"umbrellas": dict(sorted(umbrellas.items()))}
# ── contracts, task binding, impact frontier (T22 + graph addenda) ────────
#
# GRAPH SEPARATION: Spine is its own LOGICAL graph — current structural
# truth, not memory. The frontier traverses Spine edges ONLY. Memory-style
# associations (hypotheses, history, lessons) must never expand structural
# impact: they are not in this graph, and non-obligation relationship
# classes are excluded by the semantics table below. Cross-linking happens
# by stable spine:// URIs — a future Memory record references a Spine
# entity without duplicating it; structural edges stay Spine-authoritative.
NON_OBLIGATION_RELATIONSHIPS = {
# Data-driven semantics table — NOT a closed ontology: unknown types
# default to obligation-bearing (fail-loud), listed types are known
# informational edges that must not explode a change frontier.
"documented_by", "historical_relation", "related_memory",
"supersedes", # supersession is context, not a live obligation
}
# Relationships whose TRAVERSAL creates LEVEL-1 frontier entries, per
# direction: type → ("outgoing" edges from X, "incoming" edges into X)
FRONTIER_LEVEL1 = {
"consumed_by": "outgoing", # X is consumed by → consumers
"depends_on": "outgoing", # X depends on → its dependencies
"implements": "outgoing", # X implements contract → the contract
"consumes": "outgoing", # X consumes contract → the contract
"belongs_to": "outgoing", # ownership context
"deployed_as": "outgoing", # deployment surface
}
FRONTIER_CONTRACT_EXPANSION = {"implements", "consumes"}
# LEVEL 2: verification surfaces attached to frontier entities
FRONTIER_LEVEL2_TYPES = {"verified_by", "obligated_canary"}
def spine_uri(entity_id: str) -> str:
"""Stable cross-graph identity: spine://<kind>/<name>. A future Memory
record cites this URI without duplicating structural authority."""
kind, _, name = entity_id.partition(":")
return f"spine://{kind}/{name}"
def resolve_uri(uri: str) -> str:
"""spine://<kind>/<name> → internal entity id (id is NOT a path)."""
scheme, _, rest = uri.partition("://")
if scheme != "spine":
raise ValueError(f"not a spine URI: {uri}")
kind, _, name = rest.partition("/")
return f"{kind}:{name}"
def bind_task(doc: dict, task_id: str, entity_ids: list[str],
evidence_ref: str = "") -> dict:
"""TASK/ISSUE/INTENT BINDING: task → affects → entities. The structural
interface future intent/Substrate layers consume. Deterministic."""
for eid in entity_ids:
add_relationship(doc, type="affects", from_id=f"task:{task_id}",
to_id=eid, evidence_refs=[evidence_ref or f"task-binding:{task_id}"])
return entity(doc, f"task:{task_id}") or {"id": f"task:{task_id}"}
def task_entities(doc: dict, task_id: str) -> list[str]:
return sorted(r["to"] for r in
relationships(doc, from_id=f"task:{task_id}", type="affects"))
def impact_frontier(doc: dict, seed_ids: list[str], max_level: int = 2) -> dict:
"""STRUCTURAL IMPACT RESOLUTION — bounded, deterministic, Spine-only.
LEVEL 0: the seeds (direct change targets)
LEVEL 1: obligation-bearing neighbours (consumers, dependencies,
contracts, deployment) — expanded via FRONTIER_LEVEL1.
CONTRACT EXPANSION: entities implementing/consuming a contract
in the frontier pull in ALL their contract peers — a contract
change deserves every consumer's inspection, an implementation
change with the contract preserved does not explode.
LEVEL 2: verification surfaces (tests/canaries obligated to frontier
entities) + deployment surfaces.
Unknown relationship types are treated as obligation-bearing (fail-loud:
a new structural relation deserves frontier visibility until evidence
says otherwise). NON_OBLIGATION types are skipped.
"""
seeds = list(dict.fromkeys(seed_ids))
level0 = sorted(set(seeds))
level1: set[str] = set()
contracts: set[str] = set()
frontier = set(level0)
for rel in doc["relationships"]:
if rel["type"] in NON_OBLIGATION_RELATIONSHIPS:
continue
f, t = rel["from"], rel["to"]
if f in frontier:
level1.add(t)
if t in frontier and rel["type"] in ("consumed_by",):
level1.add(f) # reverse edge: consumed_by points provider→consumer
# contract expansion: any contract entity reached at level 1 pulls its
# implements/consumes peers into the frontier
level1 |= frontier # expansion considers seeds too
changed = True
while changed: # bounded: only through contract edges
changed = False
for rel in doc["relationships"]:
if rel["type"] in FRONTIER_CONTRACT_EXPANSION:
a, b = rel["from"], rel["to"]
if (a in level1) != (b in level1):
level1.add(a); level1.add(b); changed = True
for c in list(level1):
if entity(doc, c) and entity(doc, c).get("kind") == "contract":
contracts.add(c)
level1 -= set(level0)
level2: set[str] = set()
for eid in level1 | set(level0):
for rel in doc["relationships"]:
if rel["type"] in FRONTIER_LEVEL2_TYPES and rel["from"] == eid:
level2.add(rel["to"])
if rel["type"] == "deployed_as" and rel["from"] in (level1 | set(level0)):
level2.add(rel["to"])
return {"level0_direct": level0,
"level1_obligations": sorted(level1),
"contracts": sorted(contracts),
"level2_verification": sorted(level2),
"traversal_scope": "spine-structural-only",
"max_level": max_level}
def predicted_vs_observed(predicted: dict, observed_doc: dict, seed_ids: list[str]) -> dict:
"""PREDICTED vs OBSERVED impact — the V1 hook for structural learning.
Observed = frontier computed AFTER the change; prediction = frontier
before. Missing-from-prediction entities become CANDIDATE structural
corrections (evidence required before any Spine update — never auto-added)."""
observed = impact_frontier(observed_doc, seed_ids)
pred_set = {e for k in ("level0_direct", "level1_obligations",
"contracts", "level2_verification") for e in predicted.get(k, [])}
obs_set = {e for k in ("level0_direct", "level1_obligations",
"contracts", "level2_verification") for e in observed.get(k, [])}
return {"predicted": sorted(pred_set),
"observed": sorted(obs_set),
"missed": sorted(obs_set - pred_set),
"needless": sorted(pred_set - obs_set),
"candidate_corrections": [
{"entity": e, "status": "CANDIDATE",
"note": "observed consequence missing from prediction — "
"evidence required before Spine update"}
for e in sorted(obs_set - pred_set)]}
def structural_events(doc: dict) -> list[dict]:
"""GENERIC EVENT/QUERY BOUNDARY (Substrate-ready): emit structural facts
as dumb, typed events. No salience, no waking, no policy — future
Substrate subscribes and decides what matters. Spine never calls
Substrate directly."""
events = []
for e in doc["entities"]:
events.append({"type": "entity_observed", "entity": e["id"],
"kind": e["kind"], "maturity": e.get("maturity"),
"observed_at": e.get("observed_at")})
for r in doc["relationships"]:
events.append({"type": "relationship_observed", "relationship": r["type"],
"from": r["from"], "to": r["to"],
"observed_at": r.get("observed_at")})
for d in drift(doc):
events.append({"type": "drift_detected", **d})
for o in doc.get("observations", []):
events.append({"type": o.get("kind", "observation"), **o})
return events
def query(doc: dict, entity_id: str) -> dict:
"""INTRINSIC SELF-KNOWLEDGE: one deterministic call answering the
representative structural question set. No LLM, no Memory, no rediscovery."""
e = entity(doc, entity_id)
if e is None:
return {"error": "not found", "id": entity_id}
return {
"id": entity_id,
"uri": spine_uri(entity_id),
"kind": e["kind"],
"owner": [r["to"] for r in relationships(doc, from_id=entity_id, type="belongs_to")],
"dependencies": sorted({r["to"] for r in relationships(doc, from_id=entity_id, type="depends_on")}),
"consumers": sorted({r["to"] for r in relationships(doc, from_id=entity_id, type="consumed_by")}
| {r["from"] for r in relationships(doc, to_id=entity_id, type="consumed_by")}),
"contracts_implements": sorted(r["to"] for r in relationships(doc, from_id=entity_id, type="implements")),
"contracts_consumes": sorted(r["to"] for r in relationships(doc, from_id=entity_id, type="consumes")),
"state": {"maturity": e.get("maturity"), "observed_at": e.get("observed_at")},
"evidence": e.get("evidence_refs", []),
"impact_frontier": impact_frontier(doc, [entity_id]),
}
# ── CLI (deterministic, JSON canonical) ───────────────────────────────────
def _fresh_doc() -> dict:
doc = load()
if doc is None:
doc = build()
save(doc)
return doc
def main(argv=None) -> int:
argv = list(sys.argv[1:] if argv is None else argv)
if not argv or argv[0] in ("-h", "--help"):
print(__doc__)
return 0
cmd = argv[0]
args = argv[1:]
if cmd == "ingest": # Phase A: seed from manual manifest (explicit)
doc = new_doc()
n = ingest_umbrellas(doc, Path(args[0]) if args else None)
save(doc)
print(json.dumps({"ingested_subsystems": n, "store": str(CURRENT)}))
return 0
if cmd == "build": # full deterministic projection
doc = build()
save(doc)
print(json.dumps({"entities": len(doc["entities"]),
"relationships": len(doc["relationships"]),
"observations": len(doc["observations"]),
"store": str(CURRENT)}))
return 0
doc = _fresh_doc()
if cmd == "entity":
e = entity(doc, args[0]) if args else None
print(json.dumps(e) if e else json.dumps({"error": "not found", "id": args[0] if args else ""}))
return 0 if e else 1
if cmd == "relationships":
out = relationships(doc, from_id=args[0] if args else None,
type=args[1] if len(args) > 1 else None)
print(json.dumps(out, indent=1))
return 0
if cmd == "owner":
e = entity(doc, args[0]) if args else None
if not e:
print(json.dumps({"error": "not found"})); return 1
subs = [r["to"] for r in relationships(doc, from_id=e["id"], type="belongs_to")]
print(json.dumps({"id": e["id"], "subsystem": subs}))
return 0
if cmd == "dependencies":
out = [r["to"] for r in relationships(doc, from_id=args[0], type="depends_on")] if args else []
print(json.dumps({"id": args[0] if args else "", "dependencies": sorted(out)}))
return 0
if cmd == "consumers":
# "consumed_by" edges run provider → consumer; consumers of X are
# the "to" ends of edges leaving X.
out = [r["to"] for r in relationships(doc, from_id=args[0], type="consumed_by")] if args else []
print(json.dumps({"id": args[0] if args else "", "consumers": sorted(set(out))}))
return 0
if cmd == "status":
e = entity(doc, args[0]) if args else None
if not e:
print(json.dumps({"error": "not found"})); return 1
print(json.dumps({"id": e["id"], "kind": e["kind"], "maturity": e["maturity"],
"evidence_refs": e["evidence_refs"], "observed_at": e["observed_at"]}))
return 0
if cmd == "drift":
print(json.dumps(drift(doc), indent=1))
return 0
if cmd == "deployed":
obs = _deployed_observation()
print(json.dumps(obs if obs else {"error": "no deployment observation"}))
return 0 if obs else 1
if cmd == "snapshot":
p = snapshot(doc, args[0] if args else "")
print(json.dumps({"snapshot": str(p)}))
return 0
if cmd == "diff":
a, b = load(Path(args[0])), load(Path(args[1]))
if not a or not b:
print(json.dumps({"error": "snapshot path not found"})); return 1
print(json.dumps(diff(a, b), indent=1))
return 0
if cmd == "export-umbrellas":
view = export_umbrellas(doc)
target = Path(args[0]) if args else UMBRELLA_MANIFEST
if target == UMBRELLA_MANIFEST and target.is_file() and GENERATED_VIEW_MARKER not in json.loads(target.read_text()):
print(json.dumps({"error": "refusing to overwrite a MANUAL manifest — "
"pass an explicit output path for the generated view"}))
return 1
target.write_text(json.dumps(view, indent=1) + "\n")
print(json.dumps({"written": str(target), "subsystems": len(view["umbrellas"])}))
return 0
if cmd == "query":
print(json.dumps(query(doc, args[0] if args else "")))
return 0
if cmd == "impact-frontier":
seeds = args if args else []
if len(seeds) == 1 and seeds[0].startswith("task:"):
seeds = task_entities(doc, seeds[0][5:]) or []
print(json.dumps(impact_frontier(doc, seeds), indent=1))
return 0
if cmd == "uri":
print(json.dumps({"uri": spine_uri(args[0] if args else "")}))
return 0
if cmd == "promote":
e = promote(doc, args[0], args[1], args[2]) if len(args) > 2 else None
save(doc)
print(json.dumps(e or {"error": "not found or missing evidence"}))
return 0 if e else 1
if cmd == "maturity":
print(json.dumps(MATURITY_ORDER))
return 0
print(f"unknown command: {cmd}", file=sys.stderr)
return 2
if __name__ == "__main__":
sys.exit(main())
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+45
View File
@@ -584,3 +584,48 @@ class LocalToolRuntimeTests(unittest.TestCase):
if __name__ == "__main__":
unittest.main()
class TestCloudModelContextDiscovery:
"""Context discovery must not hit Ollama with cloud-pinned model names.
Observed live (PL1 Task A/B, deployed 0.66.21/0.66.22): every cloud
task logged `subsystem=context_discovery tier=1 HTTPError 400` because
module-level discovery used OllamaChatTransport for a model whose
provider prefix requires the [OI]-compat wire — then misbudgeted the
task at the 8192 fallback instead of the provider's real window."""
def test_cloud_pinned_model_uses_provider_window_not_ollama(self):
import tempfile
from unittest import mock
from rune import local_tool_runtime as ltr
import rune.openai_compat_transport as _oct
ltr.reset_context_length_cache()
class _StubCloud:
def context_length(self, model):
return 131072
with mock.patch.object(ltr, "OllamaChatTransport",
side_effect=AssertionError(
"Ollama must not be consulted for cloud-pinned models")), \
mock.patch.object(_oct, "cloud_transport_for",
return_value=_StubCloud()):
window = ltr.context_length("Verboo Code/glm-5.3-flash")
assert window == 131072, window
def test_local_model_still_uses_ollama_discovery(self):
from unittest import mock
from rune import local_tool_runtime as ltr
ltr.reset_context_length_cache()
class _StubOllama:
def __init__(self, base_url="http://localhost:11434", timeout=120):
self.calls = []
def context_length(self, model):
return 4096
with mock.patch.object(ltr, "OllamaChatTransport", _StubOllama):
window = ltr.context_length("qwen2.5:3b")
assert window == 4096, window
+558
View File
@@ -0,0 +1,558 @@
"""test_spine.py — Spine V1 contract tests.
Deterministic core, open-world extensibility, evidence-gated maturity,
snapshot/delta, drift, and the synthetic future-capability proof.
Machine-independent: deployment observations use fixture stores.
"""
import json
import sys
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT / "src"))
from rune import spine
# ── helpers ───────────────────────────────────────────────────────────────
@pytest.fixture
def doc():
return spine.new_doc()
@pytest.fixture
def seeded(doc):
"""Minimal structural world: two subsystems, two modules."""
spine.add_entity(doc, id="subsystem:alpha", kind="subsystem",
identities={"description": "Alpha"}, maturity="DECLARED")
spine.add_entity(doc, id="subsystem:beta", kind="subsystem",
identities={"description": "Beta"}, maturity="DECLARED")
spine.add_entity(doc, id="module:m_a", kind="module", maturity="DECLARED",
identities={"source": "src/rune/m_a.py"})
spine.add_entity(doc, id="module:m_b", kind="module", maturity="DECLARED",
identities={"source": "src/rune/m_b.py"})
spine.add_relationship(doc, type="belongs_to", from_id="module:m_a",
to_id="subsystem:alpha", evidence_refs=["test"])
spine.add_relationship(doc, type="belongs_to", from_id="module:m_b",
to_id="subsystem:beta", evidence_refs=["test"])
return doc
# ── deterministic entity projection / lookups ─────────────────────────────
class TestLookups:
def test_entity_projection_roundtrip(self, seeded, tmp_path):
p = tmp_path / "s.json"
spine.save(seeded, p)
loaded = spine.load(p)
assert loaded == spine.load(p) # byte-stable across loads
assert spine.entity(loaded, "module:m_a")["kind"] == "module"
def test_ownership_lookup(self, seeded):
subs = [r["to"] for r in spine.relationships(
seeded, from_id="module:m_a", type="belongs_to")]
assert subs == ["subsystem:alpha"]
def test_dependency_lookup(self, seeded):
spine.add_relationship(seeded, type="depends_on", from_id="module:m_a",
to_id="module:m_b", evidence_refs=["ast"])
deps = [r["to"] for r in spine.relationships(
seeded, from_id="module:m_a", type="depends_on")]
assert deps == ["module:m_b"]
def test_consumer_lookup(self, seeded):
# "consumed_by" edges run provider → consumer: m_a is consumed by m_b
spine.add_relationship(seeded, type="consumed_by", from_id="module:m_a",
to_id="module:m_b", evidence_refs=["ast"])
consumers = [r["to"] for r in spine.relationships(
seeded, from_id="module:m_a", type="consumed_by")]
assert consumers == ["module:m_b"]
def test_missing_entity_is_not_found(self, seeded):
assert spine.entity(seeded, "module:ghost") is None
assert spine.relationships(seeded, from_id="module:ghost") == []
# ── open-world extensibility (the Prime Directive) ────────────────────────
class TestOpenWorld:
def test_unknown_kind_relationship_and_metadata_survive_roundtrip(self, seeded, tmp_path):
"""THE extensibility test: a synthetic future capability Spine core
was never written to understand enters as DATA — no core change."""
spine.add_entity(
seeded, id="capability:quantum_resonator_cache", kind="quantum_resonator_cache",
tags=["synthetic-future"], maturity="DECLARED",
identities={"resonance": "7.83Hz", "home_node": "orb-9"},
evidence_refs=["synthetic:proof"],
ext={"entanglement_pair": "module:m_a", "wibble_level": 11,
"future_field_undefined_in_v1": {"nested": True}})
spine.add_relationship(
seeded, type="harmonizes_with", from_id="capability:quantum_resonator_cache",
to_id="module:m_a", evidence_refs=["synthetic:resonance-proof"],
ext={"coupling": "0.99", "unknown_later_field": [1, 2, 3]})
p = tmp_path / "roundtrip.json"
spine.save(seeded, p)
loaded = spine.load(p)
e = spine.entity(loaded, "capability:quantum_resonator_cache")
assert e["kind"] == "quantum_resonator_cache" # unknown kind preserved
assert e["ext"]["wibble_level"] == 11 # unknown metadata preserved
assert e["ext"]["future_field_undefined_in_v1"]["nested"] is True
assert e["identities"]["resonance"] == "7.83Hz"
rels = spine.relationships(loaded, type="harmonizes_with")
assert len(rels) == 1 # unknown relationship type preserved
assert rels[0]["ext"]["coupling"] == "0.99"
assert rels[0]["to"] == "module:m_a" # linked to a real entity
# delta sees it
base = spine.new_doc()
d = spine.diff(base, loaded)
assert "capability:quantum_resonator_cache" in d["entities_added"]
assert "harmonizes_with|capability:quantum_resonator_cache|module:m_a" \
in d["relationships_added"]
# Scaffolding-style consumption: generic queries work on unknown kinds
assert spine.relationships(loaded, from_id="capability:quantum_resonator_cache")
def test_no_closed_enum_in_core(self):
"""Core must not hardcode today's ontology as a whitelist."""
source = (ROOT / "src" / "rune" / "spine.py").read_text()
# the maturity ladder is a scale, not an entity-kind ontology
assert "VALID_KINDS" not in source and "VALID_RELATIONSHIP_TYPES" not in source
# ── evidence / maturity semantics ─────────────────────────────────────────
class TestMaturity:
def test_no_evidence_no_promotion(self, seeded):
with pytest.raises(ValueError, match="requires evidence"):
spine.promote(seeded, "module:m_a", "PRESENT", "")
def test_no_evidence_means_no_live_proven(self, seeded):
e = spine.entity(seeded, "module:m_a")
assert e["maturity"] == "DECLARED" # nothing observed: stays DECLARED
def test_ladder_is_monotonic_with_evidence(self, seeded):
for state, ev in [("PRESENT", "src"), ("WIRED", "imports"), ("EXERCISED", "tests")]:
spine.promote(seeded, "module:m_a", state, ev)
assert spine.entity(seeded, "module:m_a")["maturity"] == "EXERCISED"
def test_demotion_rejected_and_recorded(self, seeded):
spine.promote(seeded, "module:m_a", "WIRED", "imports-x")
e = spine.promote(seeded, "module:m_a", "DECLARED", "downgrade-attempt")
assert e["maturity"] == "WIRED" # not silently demoted
assert any(o["kind"] == "maturity_demotion_rejected"
for o in seeded["observations"])
def test_unknown_maturity_state_rejected(self, seeded):
with pytest.raises(ValueError, match="unknown maturity"):
spine.promote(seeded, "module:m_a", "TRANSCENDENT", "vibes")
# ── deployed identity observation ─────────────────────────────────────────
class TestDeployedObservation:
def test_observes_fixture_identity(self, doc, tmp_path, monkeypatch):
dep = tmp_path / "deploy"
(dep / "src" / "rune").mkdir(parents=True)
(dep / "src" / "rune" / "_build_identity.json").write_text(json.dumps(
{"commit": "a" * 40, "version": "9.9.9", "deployed_at": "T"}))
monkeypatch.setattr(spine, "DEPLOYED_LINK", dep)
obs = spine._deployed_observation()
assert obs["deployed_sha"] == "a" * 12 and obs["deployed_version"] == "9.9.9"
def test_absent_deployment_is_none_not_invented(self, monkeypatch, tmp_path):
monkeypatch.setattr(spine, "DEPLOYED_LINK", tmp_path / "nonexistent")
assert spine._deployed_observation() is None
def test_deployed_identity_drift_detected(self, seeded, tmp_path, monkeypatch):
dep = tmp_path / "deploy"
(dep / "src" / "rune").mkdir(parents=True)
(dep / "src" / "rune" / "_build_identity.json").write_text(json.dumps(
{"commit": "b" * 40, "version": "0.0.1-test-mismatch", "deployed_at": "T"}))
monkeypatch.setattr(spine, "DEPLOYED_LINK", dep)
monkeypatch.setattr(spine, "ROOT", tmp_path) # no pyproject → no source version
# no version drift observable without source; identity observation works
obs = spine._deployed_observation()
assert obs["deployed_version"] == "0.0.1-test-mismatch"
# ── drift ─────────────────────────────────────────────────────────────────
class TestDrift:
def test_registered_but_absent(self, seeded, monkeypatch):
monkeypatch.setattr(spine, "_canary_registry", lambda: ["m_ghost"])
monkeypatch.setattr(spine, "SRC_DIR", tmp_src := ROOT / "src" / "rune") # real dir
d = spine.drift(seeded)
assert any(x["class"] == "registered_but_absent" and x["entity"] == "module:m_ghost"
for x in d)
def test_relationship_disappeared(self, seeded, monkeypatch):
spine.add_relationship(seeded, type="consumed_by", from_id="module:m_a",
to_id="module:m_b", evidence_refs=["stale"])
monkeypatch.setattr(spine, "_import_graph", lambda: {"m_b": set()})
monkeypatch.setattr(spine, "_canary_registry", lambda: [])
monkeypatch.setattr(spine, "SRC_DIR", ROOT / "src" / "rune")
d = spine.drift(seeded)
assert any(x["class"] == "relationship_disappeared" for x in d)
def test_stale_evidence_does_not_prove_live(self, seeded):
"""A stale evidence ref must never read as LIVE_PROVEN."""
e = spine.entity(seeded, "module:m_a")
e["evidence_refs"].append("deployed-tree:deadbeef-old-observation")
assert e["maturity"] == "DECLARED" # evidence ref alone promotes nothing
# LIVE_PROVEN only via explicit evidence-gated promotion
spine.promote(seeded, "module:m_a", "PRESENT", "fresh-source-check")
assert spine.entity(seeded, "module:m_a")["maturity"] == "PRESENT"
def test_declared_but_unwired_capability(self, seeded):
"""Registered with no wiring evidence → must not read WIRED."""
spine.add_entity(seeded, id="capability:orphan_feature", kind="capability",
maturity="DECLARED", evidence_refs=["registry:orphan"])
assert spine.entity(seeded, "capability:orphan_feature")["maturity"] == "DECLARED"
# ── snapshots / deltas ────────────────────────────────────────────────────
class TestSnapshots:
def test_snapshot_deterministic(self, seeded, tmp_path, monkeypatch):
monkeypatch.setattr(spine, "SNAPSHOTS", tmp_path / "snaps")
monkeypatch.setattr(spine, "_deployed_observation", lambda: None)
seeded["generated_at"] = "FIXED"
p1 = spine.snapshot(seeded, "t")
p2 = spine.snapshot(seeded, "t")
assert p1.read_bytes() == p2.read_bytes() # byte-identical
def test_diff_categories(self, seeded):
old = json.loads(json.dumps(seeded))
spine.add_entity(seeded, id="module:m_new", kind="module", maturity="DECLARED")
spine.promote(seeded, "module:m_a", "WIRED", "new-import-evidence")
spine.add_relationship(seeded, type="depends_on", from_id="module:m_a",
to_id="module:m_b", evidence_refs=["ast"])
d = spine.diff(old, seeded)
assert d["entities_added"] == ["module:m_new"]
assert d["relationships_added"] == ["depends_on|module:m_a|module:m_b"]
assert {"id": "module:m_a", "from": "DECLARED", "to": "WIRED"} \
in d["maturity_changed"]
assert d["entities_removed"] == []
def test_diff_is_deterministic(self, seeded):
old = json.loads(json.dumps(seeded))
d1 = spine.diff(old, seeded)
d2 = spine.diff(old, seeded)
assert d1 == d2
# ── schema versioning ─────────────────────────────────────────────────────
class TestSchemaVersion:
def test_save_stamps_version(self, seeded, tmp_path):
p = spine.save(seeded, tmp_path / "s.json")
assert json.loads(p.read_text())["schema_version"] == spine.SCHEMA_VERSION
def test_future_version_refused_for_rewrite(self, seeded, tmp_path):
seeded["schema_version"] = 99
with pytest.raises(ValueError, match="schema_version"):
spine.save(seeded, tmp_path / "future.json")
def test_unknown_fields_of_document_survive(self, seeded, tmp_path):
seeded["a_field_spine_v2_will_understand"] = {"x": 1}
p = spine.save(seeded, tmp_path / "s.json")
# loader tolerates unknown document fields (no whitelist strip)
loaded = json.loads(p.read_text())
assert loaded["a_field_spine_v2_will_understand"] == {"x": 1}
# ── ingest / compatibility (umbrella migration) ───────────────────────────
class TestUmbrellaMigration:
def test_ingest_refuses_generated_view(self, tmp_path):
generated = {"_generated_by_spine": True, "umbrellas": {}}
p = tmp_path / "view.json"
p.write_text(json.dumps(generated))
with pytest.raises(ValueError, match="refusing to ingest"):
spine.ingest_umbrellas(spine.new_doc(), p)
def test_ingest_preserves_structure(self, tmp_path):
manifest = {"umbrellas": {
"alpha": {"description": "A", "modules": ["m_a", "m_b"],
"depends_on": ["beta"], "mirrors": ["shared/x.py"]},
"beta": {"description": "B", "modules": [], "depends_on": [], "mirrors": []}}}
p = tmp_path / "umbrellas.json"
p.write_text(json.dumps(manifest))
doc = spine.new_doc()
n = spine.ingest_umbrellas(doc, p)
assert n == 2
# ownership preserved
assert [r["to"] for r in spine.relationships(
doc, from_id="module:m_a", type="belongs_to")] == ["subsystem:alpha"]
# subsystem deps preserved
assert [r["to"] for r in spine.relationships(
doc, from_id="subsystem:alpha", type="depends_on")] == ["subsystem:beta"]
# mirrors preserved
assert [r["to"] for r in spine.relationships(
doc, from_id="subsystem:alpha", type="mirrors")] == ["file:shared/x.py"]
def test_export_reproduces_equivalent_view(self, tmp_path):
manifest = {"umbrellas": {
"alpha": {"description": "A", "modules": ["m_a"],
"depends_on": ["beta"], "mirrors": ["shared/x.py"]},
"beta": {"description": "B", "modules": ["m_b"],
"depends_on": [], "mirrors": []}}}
p = tmp_path / "umbrellas.json"
p.write_text(json.dumps(manifest))
doc = spine.new_doc()
spine.ingest_umbrellas(doc, p)
view = spine.export_umbrellas(doc)
assert view["_generated_by_spine"] is True
assert view["umbrellas"]["alpha"]["modules"] == ["m_a"]
assert view["umbrellas"]["alpha"]["depends_on"] == ["beta"]
assert view["umbrellas"]["alpha"]["mirrors"] == ["shared/x.py"]
assert view["umbrellas"]["beta"]["modules"] == ["m_b"]
# round-trip fidelity: the generated view re-ingests to the same graph
view_path = tmp_path / "view.json"
view_path.write_text(json.dumps(view))
doc2 = spine.new_doc()
# (direct ingest of the view is refused; equivalence is proven by
# comparing the exported view against the original manual truth)
assert view["umbrellas"] == manifest["umbrellas"]
def test_export_never_overwrites_manual_manifest(self, seeded, monkeypatch, tmp_path):
monkeypatch.setattr(spine, "UMBRELLA_MANIFEST", tmp_path / "manual.json")
(tmp_path / "manual.json").write_text(json.dumps({"umbrellas": {}}))
# main() with no path → refuses (manual manifest protection)
rc = spine.main(["export-umbrellas"])
assert rc == 1
# ── Scaffolding consumes Spine ────────────────────────────────────────────
class TestScaffoldingConsumesSpine:
def test_scaffold_projection_reads_spine_store(self, seeded, tmp_path, monkeypatch):
"""Scaffolding's structural facts must come from the Spine store,
not from re-deriving authorities independently."""
p = tmp_path / "current.json"
spine.save(seeded, p)
monkeypatch.setattr(spine, "CURRENT", p)
monkeypatch.setattr(spine, "ROOT", tmp_path)
monkeypatch.setattr(spine, "SRC_DIR", tmp_path / "src" / "rune")
(tmp_path / "src" / "rune").mkdir(parents=True, exist_ok=True)
(tmp_path / "src" / "rune" / "m_a.py").write_text("x = 1\n")
(tmp_path / "tests").mkdir(exist_ok=True)
monkeypatch.setattr(spine, "TESTS_DIR", tmp_path / "tests")
monkeypatch.setattr(spine, "_deployed_observation", lambda: None)
# fresh store → Scaffolding builds it deterministically
doc = spine.build()
spine.save(doc)
# a component card must be answerable from the store alone
card_deps = [r["to"] for r in spine.relationships(
doc, from_id="module:m_a", type="depends_on")]
subs = [r["to"] for r in spine.relationships(
doc, from_id="module:m_a", type="belongs_to")]
assert subs == ["subsystem:alpha"]
assert isinstance(card_deps, list)
# maturity visible to Scaffolding consumers — the fixture created
# src/rune/m_a.py, so the evidence gate correctly promotes PRESENT
assert spine.entity(doc, "module:m_a")["maturity"] == "PRESENT"
def test_scaffold_impact_uses_spine_store_not_umbrellas(self, monkeypatch, tmp_path):
"""The real integration: scripts/scaffold_projection.py resolves
ownership from the Spine store. Proven by pointing the store at a
fixture world and checking the card reflects SPINE data (and that
a Spine-only entity — absent from any umbrella file — is served)."""
import importlib
doc = spine.new_doc()
spine.add_entity(doc, id="subsystem:spine_test_world", kind="subsystem",
identities={"description": "fixture"})
spine.add_entity(doc, id="module:widget_core", kind="module", maturity="EXERCISED",
identities={"source": "src/rune/widget_core.py"},
evidence_refs=["fixture"])
spine.add_relationship(doc, type="belongs_to", from_id="module:widget_core",
to_id="subsystem:spine_test_world", evidence_refs=["fixture"])
spine.add_relationship(doc, type="consumed_by", from_id="module:widget_core",
to_id="module:widget_user", evidence_refs=["fixture"])
store = tmp_path / "current.json"
spine.save(doc, store)
monkeypatch.setattr(spine, "CURRENT", store)
import scripts.scaffold_projection as sp # noqa: E402
importlib.reload(sp)
monkeypatch.setattr(sp, "ROOT", tmp_path)
card = sp.component_card("widget_core", spine.load(store))
assert card["subsystem"] == "spine_test_world" # from Spine, not umbrellas.json
assert card["consumers"] == ["widget_user"] # from Spine relationships
assert card["spine_maturity"] == "EXERCISED" # evidence state surfaced
# ── graph separation + cross-linking (addendum 1) ─────────────────────────
class TestGraphSeparation:
def test_spine_queries_need_no_memory_infrastructure(self, seeded):
"""Structural lookup works on the Spine store alone — no Memory
fixture, no retrieval, no inference."""
q = spine.query(seeded, "module:m_a")
assert q["owner"] == ["subsystem:alpha"]
assert q["state"]["maturity"] == "DECLARED"
def test_stable_ids_survive_full_cycle(self, seeded, tmp_path):
p = spine.save(seeded, tmp_path / "s.json")
loaded = spine.load(p)
snap = tmp_path / "snap.json"
spine.save(loaded, snap)
d = spine.diff(spine.load(p), spine.load(snap))
assert d["entities_removed"] == [] and d["entities_added"] == []
assert spine.entity(spine.load(snap), "module:m_a") is not None
def test_spine_uri_cross_reference_without_authority_duplication(self, seeded):
"""A future Memory record references a Spine entity by URI — the
structural edge stays in Spine, the reference carries no authority."""
uri = spine.spine_uri("module:m_a")
assert uri == "spine://module/m_a"
assert spine.resolve_uri(uri) == "module:m_a"
# round-trip: resolve → the same entity Spine owns
assert spine.entity(seeded, spine.resolve_uri(uri))["kind"] == "module"
with pytest.raises(ValueError, match="not a spine URI"):
spine.resolve_uri("memory://lesson/42")
def test_memory_association_does_not_expand_frontier(self, seeded):
"""A hypothetical Memory-style association edge must NOT expand the
structural impact frontier (noisy impact = broken resolution)."""
spine.add_relationship(seeded, type="related_memory", from_id="module:m_a",
to_id="memory:old-incident-42", evidence_refs=["hypothetical"])
f = spine.impact_frontier(seeded, ["module:m_a"])
assert "memory:old-incident-42" not in f["level1_obligations"]
assert f["traversal_scope"] == "spine-structural-only"
# while a REAL structural edge does expand
spine.add_relationship(seeded, type="consumed_by", from_id="module:m_a",
to_id="module:m_b", evidence_refs=["ast"])
f2 = spine.impact_frontier(seeded, ["module:m_a"])
assert "module:m_b" in f2["level1_obligations"]
def test_unknown_relationship_is_obligation_bearing_fail_loud(self, seeded):
"""A previously unseen relationship type defaults to frontier
visibility — new structural relations deserve inspection until
evidence reclassifies them (data-driven semantics, open world)."""
spine.add_relationship(seeded, type="contextualises", from_id="module:m_a",
to_id="subsystem:beta", evidence_refs=["unseen-type"])
f = spine.impact_frontier(seeded, ["module:m_a"])
assert "subsystem:beta" in f["level1_obligations"]
def test_structural_facts_survive_hypothetical_memory_archive(self, seeded, tmp_path):
"""Spine retains current structural truth regardless of any future
Memory salience/decay/archive state — Spine is not Memory-dependent."""
spine.promote(seeded, "module:m_a", "WIRED", "import-evidence")
p = spine.save(seeded, tmp_path / "s.json")
# (hypothetical: Memory archives the story of how this was learned)
assert spine.entity(spine.load(p), "module:m_a")["maturity"] == "WIRED"
# ── contracts, task binding, frontier, predicted-vs-observed (T22) ────────
class TestContractsAndFrontier:
def _contract_world(self, doc):
spine.add_entity(doc, id="contract:chat_transport", kind="contract",
evidence_refs=["protocol:ChatTransport"], maturity="DECLARED")
spine.add_entity(doc, id="module:transport_a", kind="module", maturity="DECLARED")
spine.add_entity(doc, id="module:transport_b", kind="module", maturity="DECLARED")
spine.add_entity(doc, id="module:operator", kind="module", maturity="DECLARED")
spine.add_relationship(doc, type="implements", from_id="module:transport_a",
to_id="contract:chat_transport", evidence_refs=["ast"])
spine.add_relationship(doc, type="implements", from_id="module:transport_b",
to_id="contract:chat_transport", evidence_refs=["ast"])
spine.add_relationship(doc, type="consumes", from_id="module:operator",
to_id="contract:chat_transport", evidence_refs=["ast"])
return doc
def test_contract_change_expands_to_all_parties(self, seeded):
"""A CONTRACT change deserves every implementer + consumer's inspection."""
doc = self._contract_world(seeded)
f = spine.impact_frontier(doc, ["contract:chat_transport"])
assert {"module:transport_a", "module:transport_b", "module:operator"} \
<= set(f["level1_obligations"])
def test_implementation_change_is_bounded(self, seeded):
"""An IMPLEMENTATION change (one transport) touches the contract but
the frontier stays bounded — it does not pull sibling implementers
in as direct obligations (they see the contract, not this change)."""
doc = self._contract_world(seeded)
f = spine.impact_frontier(doc, ["module:transport_a"])
assert "contract:chat_transport" in f["level1_obligations"]
# the contract IS in the frontier; sibling implementers arrive only
# through contract expansion (both must preserve the shared shape),
# while unrelated entities stay out — boundedness holds
assert "module:transport_b" in f["level1_obligations"]
assert "subsystem:alpha" not in f["level1_obligations"] # owner context only
assert f["level0_direct"] == ["module:transport_a"]
def test_task_binding_and_frontier(self, seeded):
doc = self._contract_world(seeded)
spine.bind_task(doc, "TASK-123", ["module:transport_a"], evidence_ref="issue")
assert spine.task_entities(doc, "TASK-123") == ["module:transport_a"]
seeds = spine.task_entities(doc, "TASK-123")
f = spine.impact_frontier(doc, seeds)
assert "contract:chat_transport" in f["level1_obligations"]
def test_predicted_vs_observed_flags_misses_as_candidates(self, seeded):
old = json.loads(json.dumps(seeded))
predicted = spine.impact_frontier(old, ["module:m_a"])
# the change reveals a consequence the structure didn't predict:
# m_b (a consumer nobody had recorded) starts consuming m_a
spine.add_relationship(seeded, type="consumed_by", from_id="module:m_a",
to_id="module:m_b", evidence_refs=["observed:real-run"])
r = spine.predicted_vs_observed(predicted, seeded, ["module:m_a"])
assert "module:m_b" in r["missed"]
assert r["candidate_corrections"][0]["status"] == "CANDIDATE"
assert "evidence required" in r["candidate_corrections"][0]["note"]
def test_structural_event_emission_is_generic(self, seeded):
spine.add_relationship(seeded, type="consumed_by", from_id="module:m_a",
to_id="module:m_b", evidence_refs=["ast"])
events = spine.structural_events(seeded)
kinds = {e["type"] for e in events}
assert "entity_observed" in kinds and "relationship_observed" in kinds
# dumb typed facts — no salience, no policy fields
rel_event = next(e for e in events if e["type"] == "relationship_observed"
and e["relationship"] == "consumed_by")
assert rel_event["from"] == "module:m_a" and rel_event["to"] == "module:m_b"
def test_evidence_conflict_drift(self, seeded):
e = spine.entity(seeded, "module:m_a")
e["maturity"] = "LIVE_PROVEN"
e["evidence_refs"] = [] # claim without proof
d = spine.drift(seeded)
assert any(x["class"] == "evidence_conflict" and x["entity"] == "module:m_a"
for x in d)
def test_intrinsic_query_answers_question_set_deterministically(self, seeded):
"""THE acceptance property: the representative question set answered
from the store alone — no LLM, no Memory, no rediscovery."""
doc = self._contract_world(seeded)
q = spine.query(doc, "module:transport_a")
assert q["kind"] == "module"
assert q["contracts_implements"] == ["contract:chat_transport"]
assert q["state"]["maturity"] == "DECLARED"
assert q["evidence"] == []
assert q["impact_frontier"]["level0_direct"] == ["module:transport_a"]
assert q["uri"] == "spine://module/transport_a"
def test_synthetic_future_capability_flows_through_frontier(self, seeded):
"""Extensibility end-to-end: the unknown capability registers,
round-trips, binds to a task, expands a frontier, and survives
delta — with zero Spine core changes (kind/relationship unseen)."""
spine.add_entity(seeded, id="capability:quantum_resonator_cache",
kind="quantum_resonator_cache", maturity="DECLARED",
identities={"resonance": "7.83Hz"},
evidence_refs=["synthetic"], ext={"wibble": 11})
spine.add_relationship(seeded, type="harmonizes_with",
from_id="capability:quantum_resonator_cache",
to_id="module:m_a", evidence_refs=["synthetic"])
spine.bind_task(seeded, "TASK-FUTURE", ["capability:quantum_resonator_cache"])
q = spine.query(seeded, "capability:quantum_resonator_cache")
assert q["kind"] == "quantum_resonator_cache" # unknown kind queried generically
assert q["state"]["maturity"] == "DECLARED"
f = spine.impact_frontier(seeded, spine.task_entities(seeded, "TASK-FUTURE"))
assert "module:m_a" in f["level1_obligations"] # unseen rel = obligation-bearing