Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
de30cd958e | ||
|
|
fd6e9b4bca | ||
|
|
24413e4228 | ||
|
|
407c23a689 | ||
|
|
982a9b07d5 | ||
|
|
ff506ebe55 | ||
|
|
2032819d99 | ||
|
|
e2464ae9d6 | ||
|
|
d7e04f1098 | ||
|
|
e639383c49 | ||
|
|
57a274d97d | ||
|
|
aac5295c24 | ||
|
|
9563148f9d | ||
|
|
76984c6362 | ||
|
|
4072d36b1a | ||
|
|
6d871abc9c | ||
|
|
ce40acbb1f | ||
|
|
1de28b55a0 |
No files matched your search
@@ -1,6 +1,6 @@
|
||||
# Rune
|
||||
|
||||
**Current release: v0.66.23**
|
||||
**Current release: v0.66.30**
|
||||
|
||||
**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"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
+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.23"
|
||||
version = "0.66.30"
|
||||
description = "Rune — a stateful coding assistant with persistent memory, orchestration, and TUI"
|
||||
requires-python = ">=3.10"
|
||||
|
||||
|
||||
Whitespace-only changes.
@@ -77,9 +77,17 @@ def _extract_keywords(text: str) -> list[str]:
|
||||
|
||||
|
||||
def _load_manifest() -> dict:
|
||||
"""Structural facts from SPINE (the single structural authority).
|
||||
|
||||
Phase D: reads the Spine-generated compatibility view (same schema as
|
||||
the old manual manifest). The view is OUTPUT — the authority is
|
||||
store/spine/current.json; direct edits to the view are flagged by
|
||||
`spine drift`. If a manual manifest is present (Spine not yet seeded
|
||||
in this checkout), it degrades gracefully to that legacy truth."""
|
||||
if os.path.exists(UMBRELLA_MANIFEST):
|
||||
with open(UMBRELLA_MANIFEST) as f:
|
||||
return json.load(f)
|
||||
data = json.load(f)
|
||||
return data # generated view or manual manifest — both work
|
||||
return {"umbrellas": {}}
|
||||
|
||||
|
||||
|
||||
@@ -2,17 +2,17 @@
|
||||
"""scaffold_projection.py — Scaffolding V1 Tranche 1 (projection, not authority).
|
||||
|
||||
Answers, per component/module:
|
||||
what subsystem owns X? → config/umbrellas.json (existing authority)
|
||||
what does X depend on? → AST import graph (actual) + umbrella depends_on
|
||||
who consumes X? → reverse import edges (actual)
|
||||
what changes if X changes? → mirrors + dependent umbrellas + import consumers
|
||||
what proof obligations arise? → unit test, canary, henxel coverage, umbrella gates
|
||||
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)
|
||||
|
||||
PROJECTION DISCIPLINE: every field is DERIVED from an existing authority
|
||||
(umbrella manifest, canary_native registry, filesystem, AST). This script
|
||||
introduces no new state — if it disagrees with the authorities, fix the
|
||||
authority, never this projection.
|
||||
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
|
||||
@@ -27,11 +27,17 @@ from pathlib import Path
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
SRC = ROOT / "src" / "rune"
|
||||
TESTS = ROOT / "tests"
|
||||
UMBRELLAS = ROOT / "config" / "umbrellas.json"
|
||||
sys.path.insert(0, str(ROOT / "src"))
|
||||
from rune import spine # noqa: E402 — Spine IS the structural authority
|
||||
|
||||
|
||||
def load_umbrellas() -> dict:
|
||||
return json.loads(UMBRELLAS.read_text())["umbrellas"]
|
||||
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]:
|
||||
@@ -62,23 +68,23 @@ 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 umbrella_for(module: str, umbrellas: dict) -> str:
|
||||
for name, u in umbrellas.items():
|
||||
if module in u.get("modules", []):
|
||||
return name
|
||||
return ""
|
||||
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_umbrellas(umbrella: str, umbrellas: dict) -> list[str]:
|
||||
return [name for name, u in umbrellas.items()
|
||||
if umbrella in u.get("depends_on", [])]
|
||||
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(module: str, umbrellas: dict) -> list[str]:
|
||||
for u in umbrellas.values():
|
||||
if module in u.get("modules", []):
|
||||
return list(u.get("mirrors", []))
|
||||
return []
|
||||
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:
|
||||
@@ -92,9 +98,10 @@ def canary_registered(module: str) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def obligations(module: str, graph: dict[str, set[str]], umbrellas: dict) -> list[dict]:
|
||||
"""Proof obligations for changing this module, with evidence state."""
|
||||
umb = umbrella_for(module, umbrellas)
|
||||
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(),
|
||||
@@ -112,38 +119,45 @@ def obligations(module: str, graph: dict[str, set[str]], umbrellas: dict) -> lis
|
||||
"evidence": "run: henxels check --all"},
|
||||
]
|
||||
if umb:
|
||||
deps = dependent_umbrellas(umb, umbrellas)
|
||||
deps = dependent_subsystems(doc, umb)
|
||||
if deps:
|
||||
obs.append({"obligation": "dependent_canaries", "satisfied": None,
|
||||
"evidence": f"umbrellas depending on '{umb}': {', '.join(deps)} — their canaries gate the change"})
|
||||
mir = mirrors_for(module, umbrellas)
|
||||
"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, graph: dict[str, set[str]], umbrellas: dict) -> dict:
|
||||
deps = {d for d in graph.get(module, set())
|
||||
if (SRC / f"{d}.py").is_file() and d != module}
|
||||
return {
|
||||
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": umbrella_for(module, umbrellas),
|
||||
"dependencies": sorted(deps),
|
||||
"consumers": sorted(consumers_of(module, graph)),
|
||||
"mirrors": mirrors_for(module, umbrellas),
|
||||
"obligations": obligations(module, graph, umbrellas),
|
||||
"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:
|
||||
umbrellas = load_umbrellas()
|
||||
graph = import_graph()
|
||||
card = component_card(module, graph, umbrellas)
|
||||
doc = load_spine()
|
||||
card = component_card(module, doc)
|
||||
umb = card["subsystem"]
|
||||
card["expected_impact"] = {
|
||||
"direct_consumers": card["consumers"],
|
||||
"umbrella_dependents": dependent_umbrellas(umb, umbrellas) if umb else [],
|
||||
"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"]
|
||||
@@ -161,23 +175,23 @@ def main() -> int:
|
||||
if cmd in ("component", "impact", "obligations") and len(args) < 2:
|
||||
print(f"usage: {cmd} <module>", file=sys.stderr)
|
||||
return 2
|
||||
umbrellas = load_umbrellas()
|
||||
graph = import_graph()
|
||||
doc = load_spine()
|
||||
if cmd == "impact":
|
||||
print(json.dumps(impact(args[1]), indent=1))
|
||||
elif cmd == "component":
|
||||
print(json.dumps(component_card(args[1], graph, umbrellas), indent=1))
|
||||
print(json.dumps(component_card(args[1], doc), indent=1))
|
||||
elif cmd == "obligations":
|
||||
card = component_card(args[1], graph, umbrellas)
|
||||
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, graph, umbrellas)
|
||||
for p in sorted(SRC.glob("*.py"))}
|
||||
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": ["config/umbrellas.json", "src/rune/canary_native.py",
|
||||
"AST import graph", "tests/ filesystem"],
|
||||
"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)")
|
||||
|
||||
@@ -6,7 +6,7 @@ This is the main entry point for the Rune program.
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
__version__ = "0.66.23"
|
||||
__version__ = "0.66.30"
|
||||
|
||||
# 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.23"' in rune_version,
|
||||
'__version__ = "0.66.30"' in rune_version,
|
||||
failures,
|
||||
)
|
||||
# #169 policy update: auto-bump on merge is sanctioned (Nick directive:
|
||||
|
||||
@@ -1,8 +1,18 @@
|
||||
#!/usr/bin/env python3
|
||||
"""dependency_tracker.py — Auto-propagation system for umbrella dependencies.
|
||||
"""dependency_tracker.py — Auto-propagation for the OpenCode HARNESS scripts.
|
||||
|
||||
SCOPE CLASSIFICATION (Spine V1 pre-Memory closure, 2026-09-12): the
|
||||
DEPENDENCY_GRAPH below describes ~/.config/opencode/scripts/*.py — the
|
||||
developer HARNESS (verified: 20/23 names collide with src/rune modules but
|
||||
all exist in the scripts dir; propagate() resolves against SCRIPTS_DIR).
|
||||
This is NOT Rune architecture and NOT a competing structural authority:
|
||||
Rune's structural truth lives in the Spine store (store/spine/current.json,
|
||||
consumed via rune.spine / scripts/scaffold_projection.py). The name
|
||||
collisions with src/rune modules are coincidental — do not read this map
|
||||
as Rune structure.
|
||||
|
||||
When a component changes, automatically updates dependent components.
|
||||
Maps the dependency graph between umbrella systems and fires
|
||||
Maps the dependency graph between harness systems and fires
|
||||
propagation rules when changes are detected.
|
||||
|
||||
Usage:
|
||||
|
||||
@@ -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.23
|
||||
Version: v0.66.30
|
||||
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.23 | Textual TUI | {len(loaded)} native modules[/dim]",
|
||||
f"[dim]Rune v0.66.30 | Textual TUI | {len(loaded)} native modules[/dim]",
|
||||
)
|
||||
|
||||
# Auto-show night mode suggestion if evening/night hours
|
||||
|
||||
+1113
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
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
@@ -0,0 +1,955 @@
|
||||
"""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
|
||||
import tempfile
|
||||
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"])
|
||||
# HARDENED SEMANTICS (T1A): implementation change, contract preserved
|
||||
# → contract is a direct obligation; siblings are CONTEXTUAL, never
|
||||
# direct. (The pre-hardening expectation that siblings land in
|
||||
# level1 encoded the wrong semantics — see final report.)
|
||||
assert "contract:chat_transport" in f["level1_obligations"]
|
||||
assert "module:transport_b" in f["contextual_not_obligations"]
|
||||
assert "module:transport_b" not in f["level1_obligations"]
|
||||
assert f["change_kind"] == "implementation_change"
|
||||
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
|
||||
|
||||
|
||||
# ── HARDENING T1: contract-change vs implementation-change semantics ──────
|
||||
|
||||
class TestHardeningContractSemantics:
|
||||
def _world(self, seeded):
|
||||
spine.add_entity(seeded, id="contract:chat_transport", kind="contract",
|
||||
evidence_refs=["protocol"], maturity="DECLARED")
|
||||
for m in ("transport_a", "transport_b"):
|
||||
spine.add_entity(seeded, id=f"module:{m}", kind="module", maturity="DECLARED")
|
||||
spine.add_relationship(seeded, type="implements", from_id=f"module:{m}",
|
||||
to_id="contract:chat_transport", evidence_refs=["ast"])
|
||||
spine.add_entity(seeded, id="module:operator", kind="module", maturity="DECLARED")
|
||||
spine.add_relationship(seeded, type="consumes", from_id="module:operator",
|
||||
to_id="contract:chat_transport", evidence_refs=["ast"])
|
||||
return seeded
|
||||
|
||||
def test_A_implementation_change_siblings_not_direct(self, seeded):
|
||||
doc = self._world(seeded)
|
||||
f = spine.impact_frontier(doc, ["module:transport_a"])
|
||||
assert f["change_kind"] == "implementation_change"
|
||||
assert f["level0_direct"] == ["module:transport_a"]
|
||||
assert "contract:chat_transport" in f["level1_obligations"]
|
||||
assert "module:transport_b" in f["contextual_not_obligations"]
|
||||
assert "module:transport_b" not in f["level1_obligations"]
|
||||
# X (consumer) is reached through the contract's contextual parties
|
||||
# or obligations per evidence — must NOT be silently direct here
|
||||
assert "module:operator" in f["contextual_not_obligations"] or \
|
||||
"module:operator" in f["level1_obligations"]
|
||||
|
||||
def test_B_contract_change_all_parties_direct(self, seeded):
|
||||
doc = self._world(seeded)
|
||||
f = spine.impact_frontier(doc, ["contract:chat_transport"])
|
||||
assert f["change_kind"] == "contract_change"
|
||||
for party in ("module:transport_a", "module:transport_b", "module:operator"):
|
||||
assert party in f["level1_obligations"], party
|
||||
|
||||
def test_C_contract_violation_escalates(self, seeded):
|
||||
doc = self._world(seeded)
|
||||
f = spine.impact_frontier(doc, ["module:transport_a"],
|
||||
change_kind="contract_violation")
|
||||
# verification proved the implementation breaks the contract:
|
||||
# every party becomes a direct obligation
|
||||
assert f["change_kind"] == "contract_violation"
|
||||
for party in ("module:transport_b", "module:operator"):
|
||||
assert party in f["level1_obligations"], party
|
||||
|
||||
def test_kind_inference_is_deterministic(self, seeded):
|
||||
doc = self._world(seeded)
|
||||
assert spine.impact_frontier(doc, ["contract:chat_transport"])["change_kind"] == "contract_change"
|
||||
assert spine.impact_frontier(doc, ["module:transport_a"])["change_kind"] == "implementation_change"
|
||||
|
||||
|
||||
# ── HARDENING T3/T4: projector ownership, reconciliation, state semantics ─
|
||||
|
||||
class TestHardeningReconciliation:
|
||||
def _fact(self, doc, type="depends_on", frm="module:x", to="module:y", auth="proj-x"):
|
||||
spine.add_entity(doc, id=frm, kind="module", maturity="DECLARED")
|
||||
spine.add_entity(doc, id=to, kind="module", maturity="DECLARED")
|
||||
r = spine.add_relationship(doc, type=type, from_id=frm, to_id=to,
|
||||
evidence_refs=["fixture"])
|
||||
r["authority"] = auth
|
||||
r["status"] = "CURRENT"
|
||||
return r
|
||||
|
||||
def test_foreign_projector_refresh_never_deletes_other_authority_facts(self, seeded):
|
||||
"""Projector Y's refresh must not touch projector X's facts —
|
||||
the reconciliation boundary is AUTHORITY OWNERSHIP."""
|
||||
doc = seeded
|
||||
self._fact(doc, auth="proj-x")
|
||||
foreign = spine.add_relationship(doc, type="related_thing", from_id="module:x",
|
||||
to_id="module:y", evidence_refs=["fixture"])
|
||||
spine._authority_observed(doc, foreign, "proj-y") # proj-y ran and owns it
|
||||
# proj-x refresh observes its own fact; reconcile must leave proj-y's
|
||||
# fact alone — only proj-y's own pass can stale it
|
||||
spine._doc_authorities(doc).setdefault("proj-x", {"generation": 1, "observed": ["depends_on|module:x|module:y"]})
|
||||
staled = spine.reconcile_authorities(doc)
|
||||
assert foreign["status"] == "CURRENT" # foreign fact survives
|
||||
assert all(s["authority"] != "proj-y" for s in staled)
|
||||
|
||||
def test_owner_stopping_emission_stales_not_deletes(self, seeded):
|
||||
"""Projector X later stops observing its fact → STALE (marked,
|
||||
reported in drift, excluded from frontier) — not silently deleted,
|
||||
not silently current."""
|
||||
doc = seeded
|
||||
self._fact(doc, auth="proj-x")
|
||||
spine._doc_authorities(doc)["proj-x"] = {"generation": 2, "observed": []}
|
||||
staled = spine.reconcile_authorities(doc)
|
||||
assert len(staled) == 1
|
||||
f = spine.impact_frontier(doc, ["module:x"])
|
||||
assert "module:y" not in f["level1_obligations"] # stale ≠ current truth
|
||||
d = spine.drift(doc)
|
||||
assert any(x["class"] == "stale_fact" for x in d)
|
||||
|
||||
def test_projector_failure_does_not_masquerade_as_fresh(self, seeded):
|
||||
doc = seeded
|
||||
self._fact(doc, auth="proj-x")
|
||||
spine._doc_authorities(doc)["proj-x"] = {"generation": 1, "observed": ["depends_on|module:x|module:y"]}
|
||||
spine.authority_failed(doc, "proj-x", "source tree unreachable")
|
||||
h = spine.authority_health(doc)
|
||||
assert h["proj-x"]["status"] == "DEGRADED"
|
||||
r = [r for r in doc["relationships"] if r.get("authority") == "proj-x"][0]
|
||||
assert r["status"] == "STALE"
|
||||
f = spine.impact_frontier(doc, ["module:x"])
|
||||
assert "module:y" not in f["level1_obligations"]
|
||||
assert any(o["kind"] == "authority_degraded" for o in doc["observations"])
|
||||
|
||||
def test_manual_fact_without_authority_survives(self, seeded):
|
||||
doc = seeded
|
||||
r = self._fact(doc, auth=None)
|
||||
r.pop("authority", None); r.pop("status", None)
|
||||
spine.reconcile_authorities(doc)
|
||||
assert r["status"] != "STALE" if "status" in r else True
|
||||
assert r in doc["relationships"]
|
||||
|
||||
def test_two_authorities_conflicting_preserve_both(self, seeded):
|
||||
spine.mark_conflict(seeded, "module:m_a", "registry", "exists",
|
||||
"runtime", "unavailable", "evidence:live-check")
|
||||
e = spine.entity(seeded, "module:m_a")
|
||||
assert e["status"] == "CONFLICTING"
|
||||
assert len(e["ext"]["conflicts"]) == 1 # BOTH preserved, not last-wins
|
||||
assert spine.resolve_status(e) == "CONFLICTING"
|
||||
q = spine.query(seeded, "module:m_a")
|
||||
assert q["state"]["resolved_status"] == "CONFLICTING"
|
||||
|
||||
def test_conflict_requires_evidence(self, seeded):
|
||||
with pytest.raises(ValueError, match="requires evidence"):
|
||||
spine.mark_conflict(seeded, "module:m_a", "a", "x", "b", "y", "")
|
||||
|
||||
def test_unknown_is_not_healthy(self, seeded):
|
||||
"""A never-observed entity is UNKNOWN — not current, not false."""
|
||||
spine.add_entity(seeded, id="module:never_checked", kind="module",
|
||||
maturity="DECLARED")
|
||||
assert spine.resolve_status(spine.entity(seeded, "module:never_checked")) == "UNKNOWN"
|
||||
|
||||
def test_live_proven_without_evidence_is_conflict_not_stale(self, seeded):
|
||||
"""Case A vs B distinction: no evidence at all = CONFLICTING claim;
|
||||
a projector that stopped refreshing = STALE. Different conditions."""
|
||||
spine.add_entity(seeded, id="module:no_proof", kind="module",
|
||||
maturity="LIVE_PROVEN") # zero evidence refs
|
||||
assert spine.resolve_status(spine.entity(seeded, "module:no_proof")) == "CONFLICTING"
|
||||
|
||||
|
||||
# ── HARDENING T5: durable identity — rename/alias/supersession ────────────
|
||||
|
||||
class TestHardeningDurableIdentity:
|
||||
def test_rename_old_spine_reference_stays_meaningful(self, seeded, tmp_path):
|
||||
"""Memory stores spine://module/memory_selector; the module is later
|
||||
renamed memory_orchestrator. The old reference must still resolve."""
|
||||
spine.alias(seeded, "module:m_a", "module:m_a_old_name")
|
||||
p = spine.save(seeded, tmp_path / "s.json")
|
||||
reloaded = spine.load(p)
|
||||
# old URI resolves to the same conceptual entity
|
||||
old_ref = spine.resolve_uri("spine://module/m_a_old_name")
|
||||
e = spine.entity(reloaded, old_ref)
|
||||
assert e is not None and e["id"] == "module:m_a"
|
||||
# survives another build cycle (aliases are data, carried forward)
|
||||
doc2 = spine.build.__wrapped__(reloaded) if hasattr(spine.build, "__wrapped__") else reloaded
|
||||
assert spine.entity(doc2, "module:m_a_old_name") is not None
|
||||
|
||||
def test_supersession_requires_evidence_and_preserves_history(self, seeded):
|
||||
spine.add_entity(seeded, id="module:new_thing", kind="module", maturity="DECLARED")
|
||||
with pytest.raises(ValueError, match="requires evidence"):
|
||||
spine.supersede(seeded, "module:new_thing", "module:m_a", "")
|
||||
spine.supersede(seeded, "module:new_thing", "module:m_a", "evidence:decision-record")
|
||||
old = spine.entity(seeded, "module:m_a")
|
||||
assert old is not None # never deleted
|
||||
assert old["superseded_by"] == "module:new_thing"
|
||||
assert old["status"] == "SUPERSEDED"
|
||||
# the supersedes relationship is data, round-trips, and is
|
||||
# non-obligation for frontier purposes (context, not live wiring)
|
||||
p = spine.save(seeded, Path(tempfile.mkdtemp()) / "s.json")
|
||||
assert spine.entity(spine.load(p), "module:m_a")["superseded_by"] == "module:new_thing"
|
||||
|
||||
def test_superseded_entity_not_treated_as_current(self, seeded):
|
||||
spine.add_entity(seeded, id="module:new_thing", kind="module", maturity="DECLARED")
|
||||
spine.supersede(seeded, "module:new_thing", "module:m_a", "evidence:decision")
|
||||
f = spine.impact_frontier(seeded, ["module:m_a"])
|
||||
# a superseded seed is still answerable, but its status is explicit
|
||||
assert spine.resolve_status(spine.entity(seeded, "module:m_a")) == "SUPERSEDED"
|
||||
|
||||
|
||||
# ── HARDENING T2: independent observed impact ─────────────────────────────
|
||||
|
||||
class TestHardeningObservedImpact:
|
||||
def test_observed_impact_comes_from_evidence_not_frontier_rerun(self, seeded):
|
||||
"""The observed side must be built from EXTERNAL evidence items —
|
||||
test failures, canary results, runtime errors — each naming its
|
||||
authority. The frontier function is not invoked for the observed set."""
|
||||
spine.add_entity(seeded, id="module:m_c", kind="module", maturity="DECLARED")
|
||||
ev = [{"entity": "module:m_b", "source": "pytest-failure",
|
||||
"authority": "test-suite", "detail": "test_consumer_lookup failed"},
|
||||
{"entity": "module:m_c", "source": "canary", "authority": "canary-runner",
|
||||
"detail": "canary_native red"}]
|
||||
r = spine.record_observed_impact(seeded, "TASK-X", ev)
|
||||
assert r["observed_entities"] == ["module:m_b", "module:m_c"]
|
||||
kinds = [o["kind"] for o in seeded["observations"]]
|
||||
assert kinds.count("observed_consequence") == 2
|
||||
# nothing was auto-promoted into structural relationships
|
||||
assert not spine.relationships(seeded, type="consumed_by",
|
||||
from_id="module:m_a", to_id="module:m_c")
|
||||
|
||||
def test_predicted_vs_observed_with_independent_evidence(self, seeded):
|
||||
old = json.loads(json.dumps(seeded))
|
||||
predicted = spine.impact_frontier(old, ["module:m_a"])
|
||||
# the change ran; independent evidence implicates an entity the
|
||||
# structure never predicted
|
||||
spine.record_observed_impact(seeded, "TASK-X", [
|
||||
{"entity": "module:m_b", "source": "runtime-error",
|
||||
"authority": "deployed-runtime", "detail": "import crash"}])
|
||||
# compare: prediction's frontier vs the EVIDENCE-derived set
|
||||
observed_set = {"module:m_b"}
|
||||
pred_set = {e for k in ("level0_direct", "level1_obligations",
|
||||
"contracts", "level2_verification")
|
||||
for e in predicted.get(k, [])}
|
||||
missed = observed_set - pred_set
|
||||
assert missed == {"module:m_b"}
|
||||
# and the comparison helper records it as CANDIDATE, not truth
|
||||
result = spine.predicted_vs_observed(predicted, seeded, ["module:m_a"])
|
||||
assert all(c["status"] == "CANDIDATE" for c in result["candidate_corrections"])
|
||||
|
||||
|
||||
# ── HARDENING: candidate relationships + event seam (T8) ──────────────────
|
||||
|
||||
class TestHardeningCandidatesAndEvents:
|
||||
def test_model_speculation_is_candidate_never_truth(self, seeded):
|
||||
"""A model-hypothesised relationship (no evidence) enters as
|
||||
CANDIDATE: representable, queryable, but NOT structural truth —
|
||||
excluded from impact frontiers until evidence promotes it."""
|
||||
spine.add_relationship(seeded, type="depends_on", from_id="module:m_a",
|
||||
to_id="module:m_b", evidence_refs=[]) # speculation
|
||||
rel = spine.relationships(seeded, from_id="module:m_a", type="depends_on")[0]
|
||||
assert rel["status"] == "CANDIDATE"
|
||||
f = spine.impact_frontier(seeded, ["module:m_a"])
|
||||
assert "module:m_b" not in f["level1_obligations"]
|
||||
# evidence arrives → promotion to CURRENT makes it real
|
||||
rel["evidence_refs"] = ["ast-import:m_a"]
|
||||
rel["status"] = "CURRENT"
|
||||
f2 = spine.impact_frontier(seeded, ["module:m_a"])
|
||||
assert "module:m_b" in f2["level1_obligations"]
|
||||
|
||||
def test_event_seam_carries_future_event_kinds_without_core_redesign(self, seeded):
|
||||
"""The seam must transport stale/conflict/supersession/degradation
|
||||
events as typed facts — future event kinds flow without policy."""
|
||||
spine.add_entity(seeded, id="module:new_thing", kind="module", maturity="DECLARED")
|
||||
spine.supersede(seeded, "module:new_thing", "module:m_a", "evidence:decision")
|
||||
spine.mark_conflict(seeded, "module:new_thing", "registry", "exists",
|
||||
"runtime", "unavailable", "evidence:check")
|
||||
spine._authority_observed(seeded, None, "proj-x")
|
||||
spine.authority_failed(seeded, "proj-x", "boom")
|
||||
events = spine.structural_events(seeded)
|
||||
kinds = {e["type"] for e in events}
|
||||
assert {"superseded", "authority_conflict", "authority_degraded"} <= kinds
|
||||
# all dumb typed facts: no salience/policy/wake fields anywhere
|
||||
for e in events:
|
||||
assert not {"salience", "attention", "wake", "priority"} & set(e.keys())
|
||||
|
||||
def test_intrinsic_query_on_real_store_is_fast_and_deterministic(self):
|
||||
"""T6: the self-knowledge property on the REAL store — one query,
|
||||
deterministic, no LLM/memory/rebuild. And fast (bounded lookup)."""
|
||||
doc = spine.load()
|
||||
if doc is None:
|
||||
pytest.skip("no spine store in this checkout")
|
||||
import time as _t
|
||||
t0 = _t.monotonic()
|
||||
q1 = spine.query(doc, "module:rune_operator")
|
||||
q2 = spine.query(doc, "module:rune_operator")
|
||||
dt = _t.monotonic() - t0
|
||||
assert q1 == q2 # deterministic
|
||||
assert q1["owner"] and q1["state"]["maturity"]
|
||||
assert "impact_frontier" in q1
|
||||
assert dt < 2.0, f"query took {dt:.2f}s — intrinsic property lost"
|
||||
|
||||
|
||||
# ── CLOSURE 1: durable identity through the REAL build path ───────────────
|
||||
|
||||
class TestClosureRealBuildDurability:
|
||||
"""The hardening pass tested alias/supersession around save/load with a
|
||||
build-path fallback that could mock the real refresh. Memory will depend
|
||||
on spine:// references — prove durability through the ACTUAL build."""
|
||||
|
||||
def _seeded_store(self, tmp_path):
|
||||
doc = spine.new_doc()
|
||||
spine.add_entity(doc, id="subsystem:memory", kind="subsystem",
|
||||
identities={"description": "fixture"}, maturity="DECLARED")
|
||||
for m in ("memory_selector",):
|
||||
spine.add_entity(doc, id=f"module:{m}", kind="module", maturity="DECLARED",
|
||||
identities={"source": f"src/rune/{m}.py"})
|
||||
spine.add_relationship(doc, type="belongs_to", from_id=f"module:{m}",
|
||||
to_id="subsystem:memory", evidence_refs=["fixture"])
|
||||
spine.save(doc, spine.CURRENT)
|
||||
return doc
|
||||
|
||||
def test_alias_survives_real_build_refresh(self, tmp_path, monkeypatch):
|
||||
"""create A → alias A_old → save → reload → REAL build() → save →
|
||||
reload → spine://.../A_old still resolves to A. No mocked path."""
|
||||
monkeypatch.setattr(spine, "STORE_DIR", tmp_path / "store")
|
||||
monkeypatch.setattr(spine, "CURRENT", tmp_path / "store" / "current.json")
|
||||
monkeypatch.setattr(spine, "SNAPSHOTS", tmp_path / "store" / "snaps")
|
||||
monkeypatch.setattr(spine, "_deployed_observation", lambda: None)
|
||||
monkeypatch.setattr(spine, "SRC_DIR", tmp_path / "src" / "rune")
|
||||
(tmp_path / "src" / "rune").mkdir(parents=True)
|
||||
(tmp_path / "src" / "rune" / "memory_selector.py").write_text("x = 1\n")
|
||||
monkeypatch.setattr(spine, "TESTS_DIR", tmp_path / "tests")
|
||||
(tmp_path / "tests").mkdir(exist_ok=True)
|
||||
monkeypatch.setattr(spine, "UMBRELLA_MANIFEST", tmp_path / "no-manifest.json")
|
||||
|
||||
self._seeded_store(tmp_path)
|
||||
doc = spine.load()
|
||||
spine.alias(doc, "module:memory_selector", "module:mem_sel_old")
|
||||
spine.save(doc)
|
||||
|
||||
# THE REAL PATH: spine.build() (loads the persisted store, carries
|
||||
# everything forward, re-derives module facts, reconciles, saves)
|
||||
rebuilt = spine.build()
|
||||
spine.save(rebuilt)
|
||||
|
||||
reloaded = spine.load()
|
||||
old_ref = spine.resolve_uri("spine://module/mem_sel_old")
|
||||
e = spine.entity(reloaded, old_ref)
|
||||
assert e is not None, "alias lost by real build"
|
||||
assert e["id"] == "module:memory_selector"
|
||||
assert "module:mem_sel_old" in e.get("aliases", [])
|
||||
|
||||
def test_supersession_survives_real_build_refresh(self, tmp_path, monkeypatch):
|
||||
"""B supersedes A → REAL build() → superseded_by intact, historical
|
||||
entity not silently deleted, old reference resolves meaningfully."""
|
||||
monkeypatch.setattr(spine, "STORE_DIR", tmp_path / "store")
|
||||
monkeypatch.setattr(spine, "CURRENT", tmp_path / "store" / "current.json")
|
||||
monkeypatch.setattr(spine, "SNAPSHOTS", tmp_path / "store" / "snaps")
|
||||
monkeypatch.setattr(spine, "_deployed_observation", lambda: None)
|
||||
monkeypatch.setattr(spine, "SRC_DIR", tmp_path / "src" / "rune")
|
||||
(tmp_path / "src" / "rune").mkdir(parents=True)
|
||||
(tmp_path / "src" / "rune" / "memory_selector.py").write_text("x = 1\n")
|
||||
monkeypatch.setattr(spine, "TESTS_DIR", tmp_path / "tests")
|
||||
(tmp_path / "tests").mkdir(exist_ok=True)
|
||||
monkeypatch.setattr(spine, "UMBRELLA_MANIFEST", tmp_path / "no-manifest.json")
|
||||
|
||||
self._seeded_store(tmp_path)
|
||||
doc = spine.load()
|
||||
spine.add_entity(doc, id="module:memory_orchestrator", kind="module",
|
||||
maturity="DECLARED")
|
||||
spine.supersede(doc, "module:memory_orchestrator", "module:memory_selector",
|
||||
"evidence:closure-decision")
|
||||
spine.save(doc)
|
||||
|
||||
rebuilt = spine.build()
|
||||
spine.save(rebuilt)
|
||||
|
||||
reloaded = spine.load()
|
||||
old = spine.entity(reloaded, "module:memory_selector")
|
||||
assert old is not None, "superseded entity deleted by real build"
|
||||
assert old["superseded_by"] == "module:memory_orchestrator"
|
||||
assert old["status"] == "SUPERSEDED"
|
||||
# the supersedes edge survives as data (non-obligation context)
|
||||
assert spine.relationships(reloaded, type="supersedes",
|
||||
from_id="module:memory_orchestrator",
|
||||
to_id="module:memory_selector")
|
||||
|
||||
|
||||
# ── CLOSURE 2+3: no competing structural authority for RUNE ──────────────
|
||||
|
||||
class TestClosureNoCompetingAuthority:
|
||||
def test_dependency_tracker_scope_is_harness_not_rune(self):
|
||||
"""dependency_tracker's graph describes ~/.config/opencode/scripts
|
||||
(the harness), NOT Rune modules. The name collisions with src/rune
|
||||
are coincidental. This test pins the separation so the harness map
|
||||
can never silently become a second Rune structural authority.
|
||||
|
||||
CI-safe: asserts the CLASSIFICATION (resolution root is the harness
|
||||
dir; the map is not derived from Spine), never harness filesystem
|
||||
state — the CI runner has no ~/.config/opencode."""
|
||||
import inspect
|
||||
from rune import dependency_tracker as dt
|
||||
assert "opencode" in dt.SCRIPTS_DIR, "tracker scope moved — re-classify"
|
||||
# resolution root is the harness dir, by construction: propagate()
|
||||
# checks os.path.exists against SCRIPTS_DIR, not src/rune
|
||||
prop_src = inspect.getsource(dt.propagate)
|
||||
assert "SCRIPTS_DIR" in prop_src and "os.path.join(SCRIPTS_DIR" in prop_src
|
||||
# the map is a locally-owned table, NOT derived from the Spine store
|
||||
# (check IMPORTS, not prose — the module docstring legitimately
|
||||
# mentions Spine when documenting its scope classification)
|
||||
import ast as _ast
|
||||
tree = _ast.parse(inspect.getsource(dt))
|
||||
imported = set()
|
||||
for node in _ast.walk(tree):
|
||||
if isinstance(node, _ast.Import):
|
||||
imported.update(a.name for a in node.names)
|
||||
elif isinstance(node, _ast.ImportFrom) and node.module:
|
||||
imported.add(node.module)
|
||||
assert not any("spine" in m for m in imported), \
|
||||
f"tracker now imports Spine — re-classify as a consumer: {imported}"
|
||||
# and Spine remains the authority for Rune structure
|
||||
assert (spine.SRC_DIR / "spine.py").is_file()
|
||||
Reference in new issue
Block a user