Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aac5295c24 | ||
|
|
9563148f9d | ||
|
|
76984c6362 | ||
|
|
4072d36b1a | ||
|
|
6d871abc9c | ||
|
|
ce40acbb1f | ||
|
|
1de28b55a0 | ||
|
|
75d6d7d62a | ||
|
|
7f47969128 | ||
|
|
a65e5d44de | ||
|
|
26a040892d |
No files matched your search
@@ -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
@@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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
@@ -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
@@ -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"
|
||||
|
||||
|
||||
Whitespace-only changes.
Executable
+205
@@ -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())
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)."""
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
Reference in new issue
Block a user