mirror of
https://github.com/razzant/ouroboros.git
synced 2026-10-03 20:27:56 +00:00
v7next F0: address phase-review round 1 (verifier leaf invariants, adoption inventory gate, guarded-import classification)
F0 phase review (gpt-5.6-sol, adversarial, at def681bd) returned NEEDS FIXES;
this lands the dispositions:
- F1 CRITICAL: verify_transplant() proved span fidelity but not leaf
runnability. New _validate_leaf_invariants(): the handle must exist exactly
once with a canonical parent-returning body whenever the leaf reads through
it (projection-only leaves may omit it), declared and preamble-bound names
must be disjoint, declared names must actually be read, and the leaf-owned
allowlist may not absorb declared names. 6 new mutation/positive tests
(37 passed). Already-landed leaves are covered by the green full battery
(runtime import subsumes the static handle check).
- F2 HIGH: adoption validator now requires the full ABI-1..10 + CPL-1..7
inventory (mutation-probed: deleting ABI-3 turns rc=1), couples the
post-release row state (disposition/status/phase together), and lets
owner-deferred post-release rows through the release bar; docstring 17->18.
- F3 HIGH: domains.toml maps ouroboros/usage_legacy_import.py -> D16 and
retires the completed usage_accounting split-pending rows; quotient report
regenerated at this tree (163 domain edges).
- F4 MEDIUM: import collector now classifies __main__-guarded and
failure-swallowing-try imports as 'guarded' (import-time but tolerant),
removing false strict cycle witnesses (extension_process_runner:996,
launcher:133).
- F6 HIGH: ADOPTION rows synced to owner decisions - ABI-8 handler-ABI is
post-release backlog (Q5=A, Q16=A), ABI-6 drops the rejected
_updater_imports change (baseline pin is by design).
- F5 (duplicated D15 test copies) is closed by the queued D03 lane
integration which shrinks test_context.py and dedups; not double-fixed here.
Size ratchet: transplant tool entered the 1001-1500 band with rationale via
the official regenerator; -m size_ratchet lane 5 passed, ruff F clean.
This commit is contained in:
parent
def681bdd3
commit
a0d8f2f7be
8 changed files with 274 additions and 60 deletions
|
|
@ -65,9 +65,9 @@ Schema (fixed; one row per artifact-level delta family, never per commit):
|
|||
| ABI-3 | plan-item | Gateway ABI: drop compat aliases (cost_usd*, telegram_chat_id, project_last_viewed/hidden); executable ABI = JSON Schema derived from contracts.py, validated on ingress (Q7=A); ABI version separate from product version | retain | pending | F3 | tests/test_contracts.py + F11 per-alias inventory (ingress/egress/JS/producer/stored/migration/removal-test) |
|
||||
| ABI-4 | plan-item | `ResolvedModelTarget` frozen dataclass (D02-owner); every lane consumes the typed target | retain | pending | F3 | typed-target suite (new, named in the F3 design note) |
|
||||
| ABI-5 | plan-item | Q10 removals (all owner «A»): SCOPE_REVIEW_FLOOR (key, endpoint, UI, guards, SAFETY.md clause, tests), fail_tasks (pause = the only semantics; resumable-vs-terminal per Q9=A; E8 gets a successor scenario id), until_deadline/stall_rounds aliases (+bench adapters) | retain | pending | F3 | per-surface removal tests (F11 style) + E-suite E8 successor scenario |
|
||||
| ABI-6 | plan-item | P1 hygiene: _call_llm_with_retry alias, _typed_or_adapted branch, failure-detector compat wrapper, 3 underscore renames, api_v1 shim, compute_cost_with_children, format_handoff_message; latent fixes: _updater_imports → git_ops_reset.py path, CHECKLISTS:507. Behavior-preserving part lands BEFORE the ABI window in small commits (F9) | retain | pending | F3 | ruff F + targeted per-item suites + grep-level absence checks |
|
||||
| ABI-6 | plan-item | P1 hygiene: _call_llm_with_retry alias, _typed_or_adapted branch, failure-detector compat wrapper, 3 underscore renames, api_v1 shim, compute_cost_with_children, format_handoff_message; latent fix: CHECKLISTS:507 (the _updater_imports change was REJECTED — v7_evidence reads git_ops.py at the immutable BASELINE_SHA by design; see LEDGER_CORRECTIONS). Behavior-preserving part lands BEFORE the ABI window in small commits (F9) | retain | pending | F3 | ruff F + targeted per-item suites + grep-level absence checks |
|
||||
| ABI-7 | plan-item | RC auditor/migrator: machine-readable scope + N−1 fixtures, remainder = owner attestation (F13); N−1 updater transition entry point/shim with crash-point tests (F14, Q10=A) | retain | pending | F3 | RC audit fixture suite (new, F13/F14) |
|
||||
| ABI-8 | plan-item | Handler-ABI finale: tool handlers return ToolResult, not str (the true D02 finale) — pulled into v7.0 by Q16=A («7.1» не существует) | retain | pending | F3 | tests/test_core_native_results.py + tests/test_control_native_results.py extended to handler signatures |
|
||||
| ABI-8 | plan-item | Handler-ABI finale: tool handlers return ToolResult, not str (the true D02 finale). POST-RELEASE BACKLOG, not the v7.0 campaign: Q5=A kept it OUT of the ABI bundle; Q16=A retires the «7.1» label into post-release backlog, not into v7.0. (owner one-line confirm queued) | post-release | deferred | POST | tests/test_core_native_results.py + tests/test_control_native_results.py extended to handler signatures |
|
||||
| ABI-9 | plan-item | Atomic publication of extension registrations: stage→validate→swap of the registration snapshot (not `_lock` around inserts), internal disposers list (NOT exposed in ABI), extension-generation digest in physical-call provenance — moved from §7-8 into Ф3 by F8 (before ABI provenance) | retain | pending | F3 | tests/test_extension_loader_extraction.py + registration atomicity suite (new, F8) |
|
||||
| ABI-10 | plan-item | Reviewer comma-lists → actor rows: the model is already upstream (#384). Residual: sweep for comma-list remnants across settings/review lanes (§5.4) | superseded-by-upstream | pending | F3 | comma-list remnant sweep (grep-level checker, new) |
|
||||
| CPL-1 | plan-item | domains.toml + domain checker: cycles=0 on domain nodes AT GATE TIME, dependency direction, literal-copy-fix ban; DOMAIN_MAP.md generated from the manifest (gen/verify pair). Report-only stage SHIPPED in Ф0: scripts/v7next_domains.toml + scripts/v7next_domain_report.py | retain | pending | F5 | scripts/v7next_domain_report.py (Ф0 report) → gate checker (F5, new) |
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
# Domain quotient report — v7next (report-only)
|
||||
|
||||
Generated by `scripts/v7next_domain_report.py` on 2026-08-30 14:36 UTC. Do not edit.
|
||||
Generated by `scripts/v7next_domain_report.py` on 2026-08-30 16:49 UTC. Do not edit.
|
||||
|
||||
- target tree HEAD: `b9f7597f7baf3f0d613ed2fab2936cb3540f2487`
|
||||
- manifest: `scripts/v7next_domains.toml` sha256 `f63ecd488a7b872c9a2af08aa5bdcc55850e970b8cdc06991aea9c41d17c148f`
|
||||
- target tree HEAD: `def681bdd3b35b6c86e689b8ed33f0eea85dbfd3`
|
||||
- manifest: `scripts/v7next_domains.toml` sha256 `dceca50b960dcfb00f4f958db1f99370820438d01b59df32eed75d6a7dd14021`
|
||||
- reference (domain vocabulary): ouroboros_v7_wip @ 9f691656e7e756c483f72ef549418c0e7a1b8054 (frozen)
|
||||
- discipline: plan §7.1 / roast F17 — this is a REPORT; cycles are owner-batch
|
||||
items (regroup vs split vs allowed-edge), the checker never dictates.
|
||||
|
||||
## Population
|
||||
|
||||
- modules mapped: **368** across **20** domains (80 rows are `classification=proposed`)
|
||||
- modules mapped: **369** across **20** domains (80 rows are `classification=proposed`)
|
||||
- manifest drift: none (manifest == tracked population)
|
||||
|
||||
| domain | modules | proposed |
|
||||
|
|
@ -30,7 +30,7 @@ Generated by `scripts/v7next_domain_report.py` on 2026-08-30 14:36 UTC. Do not e
|
|||
| D13 — Safety, guards & runtime mode | 8 | 2 |
|
||||
| D14 — Skills & extensions | 41 | 8 |
|
||||
| D15 — Memory, knowledge, consciousness & self-evolution | 16 | 1 |
|
||||
| D16 — Observability, usage accounting & cost | 7 | 0 |
|
||||
| D16 — Observability, usage accounting & cost | 8 | 0 |
|
||||
| D17 — Projects, workspaces & task results | 16 | 1 |
|
||||
| D18 — Launcher, packaging, platform & shared substrate | 9 | 0 |
|
||||
| D19 — Frozen contracts (ABI) | 11 | 0 |
|
||||
|
|
@ -38,9 +38,9 @@ Generated by `scripts/v7next_domain_report.py` on 2026-08-30 14:36 UTC. Do not e
|
|||
|
||||
## Strict import graph
|
||||
|
||||
- module-level import edges (unconditional): **1089** unique, 3141 statements
|
||||
- cross-domain module edges: **724**
|
||||
- domain-level edges (quotient): **164**
|
||||
- module-level import edges (unconditional): **1090** unique, 3153 statements
|
||||
- cross-domain module edges: **723**
|
||||
- domain-level edges (quotient): **163**
|
||||
- quotient cycles (SCCs with >1 domain): **1**
|
||||
|
||||
## Quotient cycles
|
||||
|
|
@ -49,7 +49,7 @@ Generated by `scripts/v7next_domain_report.py` on 2026-08-30 14:36 UTC. Do not e
|
|||
|
||||
20 domains form one strongly connected component. Every edge below
|
||||
needs an owner disposition (regroup / split / allowed-edge).
|
||||
357 of the 724 module-edge witnesses touch a `[split_pending]`
|
||||
339 of the 723 module-edge witnesses touch a `[split_pending]`
|
||||
monolith (marked †): those edges are expected to move or dissolve when the
|
||||
ledger-derived leaves are transplanted (Ф1 recipe, plan §5.3).
|
||||
|
||||
|
|
@ -116,7 +116,7 @@ ledger-derived leaves are transplanted (Ф1 recipe, plan §5.3).
|
|||
- `ouroboros/loop.py` → `ouroboros/usage_accounting.py` (line 30) †
|
||||
- `ouroboros/loop_llm_call.py` → `ouroboros/_usage_response.py` (line 34)
|
||||
- `ouroboros/loop_llm_call.py` → `ouroboros/observability.py` (line 30)
|
||||
- `ouroboros/loop_llm_call.py` → `ouroboros/usage_accounting.py` (line 35) †
|
||||
- `ouroboros/loop_llm_call.py` → `ouroboros/usage_accounting.py` (line 35)
|
||||
- `ouroboros/loop_tool_execution.py` → `ouroboros/observability.py` (line 23) †
|
||||
- `ouroboros/loop_tool_execution.py` → `ouroboros/usage_accounting.py` (line 36) †
|
||||
- `ouroboros/post_task_checkpoint.py` → `ouroboros/cost_projection.py` (line 11)
|
||||
|
|
@ -167,8 +167,8 @@ ledger-derived leaves are transplanted (Ф1 recipe, plan §5.3).
|
|||
- **D02 → D16** (5 module edges)
|
||||
- `ouroboros/llm.py` → `ouroboros/usage_accounting.py` (line 37) †
|
||||
- `ouroboros/llm_observability.py` → `ouroboros/observability.py` (line 8)
|
||||
- `ouroboros/llm_probe.py` → `ouroboros/usage_accounting.py` (line 14) †
|
||||
- `ouroboros/request_wire_recovery.py` → `ouroboros/usage_accounting.py` (line 40) †
|
||||
- `ouroboros/llm_probe.py` → `ouroboros/usage_accounting.py` (line 14)
|
||||
- `ouroboros/request_wire_recovery.py` → `ouroboros/usage_accounting.py` (line 40)
|
||||
- `ouroboros/vision_routing.py` → `ouroboros/observability.py` (line 13)
|
||||
- **D02 → D18** (7 module edges)
|
||||
- `ouroboros/llm.py` → `ouroboros/utils.py` (line 54) †
|
||||
|
|
@ -276,10 +276,10 @@ ledger-derived leaves are transplanted (Ф1 recipe, plan §5.3).
|
|||
- **D05 → D15** (1 module edges)
|
||||
- `ouroboros/tools/core.py` → `ouroboros/project_facts.py` (line 17,18) †
|
||||
- **D05 → D16** (4 module edges)
|
||||
- `ouroboros/tools/search.py` → `ouroboros/usage_accounting.py` (line 15) †
|
||||
- `ouroboros/tools/search.py` → `ouroboros/usage_accounting.py` (line 15)
|
||||
- `ouroboros/tools/services.py` → `ouroboros/observability.py` (line 16)
|
||||
- `ouroboros/tools/vision.py` → `ouroboros/observability.py` (line 23)
|
||||
- `ouroboros/tools/vision.py` → `ouroboros/usage_accounting.py` (line 21) †
|
||||
- `ouroboros/tools/vision.py` → `ouroboros/usage_accounting.py` (line 21)
|
||||
- **D05 → D17** (6 module edges)
|
||||
- `ouroboros/artifacts.py` → `ouroboros/headless.py` (line 20) †
|
||||
- `ouroboros/artifacts.py` → `ouroboros/task_results.py` (line 22)
|
||||
|
|
@ -356,15 +356,15 @@ ledger-derived leaves are transplanted (Ф1 recipe, plan §5.3).
|
|||
- `ouroboros/tools/claude_advisory_review.py` → `ouroboros/skill_review_status.py` (line 24) †
|
||||
- **D06 → D16** (10 module edges)
|
||||
- `ouroboros/review_custody.py` → `ouroboros/observability.py` (line 23)
|
||||
- `ouroboros/review_custody.py` → `ouroboros/usage_accounting.py` (line 25) †
|
||||
- `ouroboros/review_custody.py` → `ouroboros/usage_accounting.py` (line 25)
|
||||
- `ouroboros/review_execution.py` → `ouroboros/usage_accounting.py` (line 31) †
|
||||
- `ouroboros/review_execution_projection.py` → `ouroboros/observability.py` (line 13)
|
||||
- `ouroboros/review_native_episode.py` → `ouroboros/usage_accounting.py` (line 30) †
|
||||
- `ouroboros/review_native_episode.py` → `ouroboros/usage_accounting.py` (line 30)
|
||||
- `ouroboros/review_session_usage.py` → `ouroboros/_usage_rows.py` (line 7)
|
||||
- `ouroboros/review_substrate.py` → `ouroboros/observability.py` (line 23) †
|
||||
- `ouroboros/review_substrate.py` → `ouroboros/usage_accounting.py` (line 58) †
|
||||
- `ouroboros/tools/plan_review_artifacts.py` → `ouroboros/usage_accounting.py` (line 16) †
|
||||
- `ouroboros/tools/plan_review_runtime.py` → `ouroboros/usage_accounting.py` (line 27) †
|
||||
- `ouroboros/tools/plan_review_artifacts.py` → `ouroboros/usage_accounting.py` (line 16)
|
||||
- `ouroboros/tools/plan_review_runtime.py` → `ouroboros/usage_accounting.py` (line 27)
|
||||
- **D06 → D17** (3 module edges)
|
||||
- `ouroboros/review_substrate.py` → `ouroboros/task_results.py` (line 29) †
|
||||
- `ouroboros/tools/plan_review.py` → `ouroboros/task_results.py` (line 47)
|
||||
|
|
@ -764,8 +764,7 @@ ledger-derived leaves are transplanted (Ф1 recipe, plan §5.3).
|
|||
- `ouroboros/skill_review.py` → `ouroboros/triad_review.py` (line 56) †
|
||||
- `ouroboros/skill_review_cycles.py` → `ouroboros/review_cycles.py` (line 44)
|
||||
- `ouroboros/skill_review_history.py` → `ouroboros/tools/review_helpers.py` (line 12) †
|
||||
- **D14 → D09** (2 module edges)
|
||||
- `ouroboros/extension_process_runner.py` → `ouroboros/process_custody.py` (line 996)
|
||||
- **D14 → D09** (1 module edges)
|
||||
- `ouroboros/skill_publish_scanner.py` → `ouroboros/process_custody.py` (line 25)
|
||||
- **D14 → D10** (2 module edges)
|
||||
- `ouroboros/skill_publish_github.py` → `ouroboros/tools/github.py` (line 12)
|
||||
|
|
@ -780,12 +779,12 @@ ledger-derived leaves are transplanted (Ф1 recipe, plan §5.3).
|
|||
- `ouroboros/tools/skill_exec.py` → `ouroboros/config.py` (line 15) †
|
||||
- `ouroboros/tools/skill_publish.py` → `ouroboros/config.py` (line 14) †
|
||||
- **D14 → D16** (6 module edges)
|
||||
- `ouroboros/extension_companion.py` → `ouroboros/usage_accounting.py` (line 18) †
|
||||
- `ouroboros/extension_process_runner.py` → `ouroboros/usage_accounting.py` (line 36) †
|
||||
- `ouroboros/extension_companion.py` → `ouroboros/usage_accounting.py` (line 18)
|
||||
- `ouroboros/extension_process_runner.py` → `ouroboros/usage_accounting.py` (line 36)
|
||||
- `ouroboros/skill_review_usage.py` → `ouroboros/_usage_rows.py` (line 8)
|
||||
- `ouroboros/skill_review_usage.py` → `ouroboros/_usage_rows_memo.py` (line 9)
|
||||
- `ouroboros/skill_review_usage.py` → `ouroboros/usage_ledger.py` (line 10)
|
||||
- `ouroboros/tools/skill_exec.py` → `ouroboros/usage_accounting.py` (line 44) †
|
||||
- `ouroboros/tools/skill_exec.py` → `ouroboros/usage_accounting.py` (line 44)
|
||||
- **D14 → D18** (28 module edges)
|
||||
- `ouroboros/betterleaks_runtime.py` → `ouroboros/platform_layer.py` (line 37)
|
||||
- `ouroboros/extension_companion.py` → `ouroboros/platform_layer.py` (line 16)
|
||||
|
|
@ -863,16 +862,17 @@ ledger-derived leaves are transplanted (Ф1 recipe, plan §5.3).
|
|||
- `ouroboros/consolidator.py` → `ouroboros/contracts/chat_id_policy.py` (line 8)
|
||||
- `ouroboros/memory.py` → `ouroboros/contracts/chat_id_policy.py` (line 12)
|
||||
- **D16 → D02** (2 module edges)
|
||||
- `ouroboros/usage_accounting.py` → `ouroboros/pricing.py` (line 25) †
|
||||
- `ouroboros/usage_accounting.py` → `ouroboros/transport_custody.py` (line 28) †
|
||||
- `ouroboros/usage_accounting.py` → `ouroboros/pricing.py` (line 25)
|
||||
- `ouroboros/usage_accounting.py` → `ouroboros/transport_custody.py` (line 28)
|
||||
- **D16 → D06** (1 module edges)
|
||||
- `ouroboros/usage_accounting.py` → `ouroboros/review_dispatch.py` (line 27) †
|
||||
- `ouroboros/usage_accounting.py` → `ouroboros/review_dispatch.py` (line 27)
|
||||
- **D16 → D14** (1 module edges)
|
||||
- `ouroboros/usage_accounting.py` → `ouroboros/skill_review_usage.py` (line 58) †
|
||||
- **D16 → D18** (3 module edges)
|
||||
- `ouroboros/usage_accounting.py` → `ouroboros/skill_review_usage.py` (line 58)
|
||||
- **D16 → D18** (4 module edges)
|
||||
- `ouroboros/observability.py` → `ouroboros/utils.py` (line 22)
|
||||
- `ouroboros/usage_accounting.py` → `ouroboros/utils.py` (line 49) †
|
||||
- `ouroboros/usage_accounting.py` → `ouroboros/utils.py` (line 49)
|
||||
- `ouroboros/usage_ledger.py` → `ouroboros/utils.py` (line 28)
|
||||
- `ouroboros/usage_legacy_import.py` → `ouroboros/utils.py` (line 26)
|
||||
- **D17 → D01** (4 module edges)
|
||||
- `ouroboros/headless.py` → `ouroboros/post_task_checkpoint.py` (line 22) †
|
||||
- `ouroboros/project_dialogue.py` → `ouroboros/task_finalization.py` (line 21)
|
||||
|
|
@ -912,8 +912,6 @@ ledger-derived leaves are transplanted (Ф1 recipe, plan §5.3).
|
|||
- **D18 → D12** (2 module edges)
|
||||
- `launcher.py` → `ouroboros/config.py` (line 29) †
|
||||
- `launcher.py` → `ouroboros/launcher_onboarding.py` (line 55) †
|
||||
- **D18 → D16** (1 module edges)
|
||||
- `launcher.py` → `ouroboros/observability.py` (line 133) †
|
||||
- **D19 → D11** (1 module edges)
|
||||
- `ouroboros/contracts/api_v1.py` → `ouroboros/gateway/contracts.py` (line 11,12)
|
||||
- **D19 → D14** (1 module edges)
|
||||
|
|
@ -1072,7 +1070,7 @@ ledger-derived leaves are transplanted (Ф1 recipe, plan §5.3).
|
|||
| D14 | D04 | 12 | yes |
|
||||
| D14 | D05 | 3 | yes |
|
||||
| D14 | D06 | 5 | yes |
|
||||
| D14 | D09 | 2 | yes |
|
||||
| D14 | D09 | 1 | yes |
|
||||
| D14 | D10 | 2 | yes |
|
||||
| D14 | D11 | 1 | yes |
|
||||
| D14 | D12 | 6 | yes |
|
||||
|
|
@ -1089,7 +1087,7 @@ ledger-derived leaves are transplanted (Ф1 recipe, plan §5.3).
|
|||
| D16 | D02 | 2 | yes |
|
||||
| D16 | D06 | 1 | yes |
|
||||
| D16 | D14 | 1 | yes |
|
||||
| D16 | D18 | 3 | yes |
|
||||
| D16 | D18 | 4 | yes |
|
||||
| D17 | D01 | 4 | yes |
|
||||
| D17 | D04 | 2 | yes |
|
||||
| D17 | D15 | 2 | yes |
|
||||
|
|
@ -1099,7 +1097,6 @@ ledger-derived leaves are transplanted (Ф1 recipe, plan §5.3).
|
|||
| D18 | D09 | 1 | yes |
|
||||
| D18 | D10 | 1 | yes |
|
||||
| D18 | D12 | 2 | yes |
|
||||
| D18 | D16 | 1 | yes |
|
||||
| D19 | D11 | 1 | yes |
|
||||
| D19 | D14 | 1 | yes |
|
||||
| D19 | D18 | 1 | yes |
|
||||
|
|
@ -1130,17 +1127,17 @@ Rows import columns (module-edge counts, strict graph).
|
|||
| **D11** | 7 | 3 | 1 | 1 | 2 | · | 3 | 1 | 1 | 1 | · | 17 | · | 19 | · | 3 | 11 | 18 | 4 | 3 |
|
||||
| **D12** | 1 | 4 | 1 | · | · | · | 1 | · | · | · | 1 | · | · | · | · | · | · | 3 | · | · |
|
||||
| **D13** | 1 | 2 | · | 2 | · | · | · | 1 | · | · | · | 1 | · | · | · | · | 1 | 2 | · | · |
|
||||
| **D14** | · | 5 | · | 12 | 3 | 5 | · | · | 2 | 2 | 1 | 6 | · | · | · | 6 | · | 28 | 12 | · |
|
||||
| **D14** | · | 5 | · | 12 | 3 | 5 | · | · | 1 | 2 | 1 | 6 | · | · | · | 6 | · | 28 | 12 | · |
|
||||
| **D15** | 3 | 2 | 3 | 2 | · | · | · | · | · | · | · | 1 | · | · | · | · | · | 14 | 2 | · |
|
||||
| **D16** | · | 2 | · | · | · | 1 | · | · | · | · | · | · | · | 1 | · | · | · | 3 | · | · |
|
||||
| **D16** | · | 2 | · | · | · | 1 | · | · | · | · | · | · | · | 1 | · | · | · | 4 | · | · |
|
||||
| **D17** | 4 | · | · | 2 | · | · | · | · | · | · | · | · | · | · | 2 | 2 | · | 13 | 3 | · |
|
||||
| **D18** | · | · | · | · | · | · | · | · | 1 | 1 | · | 2 | · | · | · | 1 | · | · | · | · |
|
||||
| **D18** | · | · | · | · | · | · | · | · | 1 | 1 | · | 2 | · | · | · | · | · | · | · | · |
|
||||
| **D19** | · | · | · | · | · | · | · | · | · | · | 1 | · | · | 1 | · | · | · | 1 | · | · |
|
||||
| **D20** | · | · | · | 1 | 1 | · | · | · | · | · | · | · | · | 1 | 1 | · | 1 | 3 | 6 | · |
|
||||
|
||||
## Lazy imports (function-level) — excluded from the strict graph
|
||||
|
||||
1501 module edges, 229 cross-domain pairs.
|
||||
1502 module edges, 229 cross-domain pairs.
|
||||
|
||||
| from | to | module edges | pair also strict? |
|
||||
|---|---|---:|:---:|
|
||||
|
|
@ -1356,7 +1353,7 @@ Rows import columns (module-edge counts, strict graph).
|
|||
| D18 | D11 | 1 | **lazy-only** |
|
||||
| D18 | D12 | 3 | yes |
|
||||
| D18 | D14 | 2 | **lazy-only** |
|
||||
| D18 | D16 | 1 | yes |
|
||||
| D18 | D16 | 1 | **lazy-only** |
|
||||
| D18 | D17 | 1 | **lazy-only** |
|
||||
| D18 | D19 | 1 | **lazy-only** |
|
||||
| D19 | D04 | 1 | **lazy-only** |
|
||||
|
|
@ -1699,6 +1696,8 @@ Cross-domain pairs reachable ONLY through lazy imports (hidden coupling):
|
|||
- **D18 → D14**:
|
||||
- `launcher.py` → `ouroboros/skill_loader.py`
|
||||
- `ouroboros/launcher_bootstrap.py` → `ouroboros/skill_loader.py`
|
||||
- **D18 → D16**:
|
||||
- `ouroboros/utils.py` → `ouroboros/observability.py`
|
||||
- **D18 → D17**:
|
||||
- `ouroboros/cli.py` → `ouroboros/task_status.py`
|
||||
- **D18 → D19**:
|
||||
|
|
@ -1719,6 +1718,20 @@ Cross-domain pairs reachable ONLY through lazy imports (hidden coupling):
|
|||
- **D20 → D12**:
|
||||
- `ouroboros/presence_runner.py` → `ouroboros/config.py`
|
||||
|
||||
## Guarded imports (__main__-only / failure-swallowing try) — excluded from the strict graph
|
||||
|
||||
2 module edges, 2 cross-domain pairs.
|
||||
|
||||
| from | to | module edges | pair also strict? |
|
||||
|---|---|---:|:---:|
|
||||
| D14 | D09 | 1 | yes |
|
||||
| D18 | D16 | 1 | no |
|
||||
|
||||
Cross-domain pairs reachable ONLY through guarded imports (hidden coupling):
|
||||
|
||||
- **D18 → D16**:
|
||||
- `launcher.py` → `ouroboros/observability.py`
|
||||
|
||||
## TYPE_CHECKING imports — excluded from the strict graph
|
||||
|
||||
13 module edges, 4 cross-domain pairs.
|
||||
|
|
|
|||
|
|
@ -169,6 +169,7 @@ BAND_PATHS = {
|
|||
"ouroboros/utils.py": None,
|
||||
"ouroboros/workspace_executor.py": None,
|
||||
"scripts/run_external_review.py": None,
|
||||
"scripts/v7next_transplant.py": "F0 phase-review CRITICAL hardening: whole-leaf runtime invariants (handle existence/shape, declared/preamble disjointness, unread-declared) added to the byte-proof verifier; campaign tool retires with F6, not a runtime module",
|
||||
"skills/telegram/plugin.py": None,
|
||||
"skills/telegram/scripts/companion.py": None,
|
||||
"skills/telegram/scripts/sidecar.py": None,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"""Validate ADOPTION_v7next.md — the v7-side adoption manifest (Ф0 skeleton).
|
||||
|
||||
The manifest enumerates every v7-side delta that must be re-applied on top of
|
||||
the v7next upstream base: the 17 approved semantic-delta families from the
|
||||
the v7next upstream base: the 18 approved semantic-delta families from the
|
||||
frozen reference ledger (``ouroboros_v7_wip @ 9f691656`` —
|
||||
``scripts/v7_migration.py::APPROVED_SEMANTIC_DELTAS`` minus ``"none"``) plus
|
||||
the campaign-decision items of plan §6 (ABI package 7.0) and §7 (completeness)
|
||||
|
|
@ -42,10 +42,15 @@ REQUIRED_DELTAS = (
|
|||
"D02", "D03", "D04", "D05", "D06", "D07", "D08", "D09", "D11",
|
||||
"D13", "D18", "D31", "D33", "D34", "D35", "D36", "D37", "D38",
|
||||
)
|
||||
# Required non-delta inventory (F0 phase review F2): the ABI package and the
|
||||
# compatibility retirements are release-gated too, not only the D-families.
|
||||
REQUIRED_ABI = tuple(f"ABI-{n}" for n in range(1, 11))
|
||||
REQUIRED_CPL = tuple(f"CPL-{n}" for n in range(1, 8))
|
||||
KINDS = frozenset({"semantic-delta", "plan-item", "class-return"})
|
||||
DISPOSITIONS = frozenset({"retain", "re-prove", "superseded-by-upstream", "pending-decision"})
|
||||
STATUSES = frozenset({"pending", "in-progress", "done"})
|
||||
PHASES = frozenset({"F0", "F1", "F2", "F3", "F4", "F5", "F6"})
|
||||
DISPOSITIONS = frozenset({"retain", "re-prove", "superseded-by-upstream",
|
||||
"pending-decision", "post-release"})
|
||||
STATUSES = frozenset({"pending", "in-progress", "done", "deferred"})
|
||||
PHASES = frozenset({"F0", "F1", "F2", "F3", "F4", "F5", "F6", "POST"})
|
||||
ID_RE = re.compile(r"^(D\d\d|ABI-\d+|CPL-\d+|R-[A-Z0-9]+|TRAIN-[A-Za-z0-9._-]+)$")
|
||||
|
||||
|
||||
|
|
@ -122,10 +127,31 @@ def validate(rows: list[dict[str, str]], release: bool) -> list[str]:
|
|||
errors.append(f"required semantic delta {d} is missing")
|
||||
elif row["kind"] != "semantic-delta":
|
||||
errors.append(f"{d}: must be kind=semantic-delta, got {row['kind']!r}")
|
||||
# F0 phase review F2: the ABI package and compatibility retirements are part
|
||||
# of the release inventory too — deleting their rows must turn --release red.
|
||||
for rid in (*REQUIRED_ABI, *REQUIRED_CPL):
|
||||
row = by_id.get(rid)
|
||||
if row is None:
|
||||
errors.append(f"required row {rid} is missing")
|
||||
elif row["kind"] != "plan-item":
|
||||
errors.append(f"{rid}: must be kind=plan-item, got {row['kind']!r}")
|
||||
# Row-specific coupling: post-release is a single coherent state, not three
|
||||
# independent knobs (prevents e.g. disposition=post-release with status=done
|
||||
# quietly counting as shipped).
|
||||
for r in rows:
|
||||
post_bits = [r["disposition"] == "post-release", r["status"] == "deferred",
|
||||
r["phase"] == "POST"]
|
||||
if any(post_bits) and not all(post_bits):
|
||||
errors.append(
|
||||
f"{r['id']}: post-release rows need disposition=post-release + "
|
||||
f"status=deferred + phase=POST together, got "
|
||||
f"{r['disposition']}/{r['status']}/{r['phase']}")
|
||||
if release:
|
||||
for r in rows:
|
||||
if r["disposition"] == "pending-decision":
|
||||
errors.append(f"release: {r['id']} still pending-decision")
|
||||
if r["disposition"] == "post-release":
|
||||
continue # explicitly deferred out of v7.0 by an owner decision
|
||||
if r["status"] != "done":
|
||||
errors.append(f"release: {r['id']} status {r['status']!r} != done")
|
||||
return errors
|
||||
|
|
|
|||
|
|
@ -39,6 +39,10 @@ MANIFEST = REPO_ROOT / "scripts" / "v7next_domains.toml"
|
|||
REPORT = REPO_ROOT / "docs" / "v7next" / "DOMAIN_QUOTIENT_REPORT.md"
|
||||
|
||||
STRICT, TYPE_ONLY, LAZY, DYNAMIC = "strict", "type_checking", "lazy", "dynamic"
|
||||
# Executed at import time but failure-tolerant / entrypoint-only (F0 review F4):
|
||||
# a `try: import x except ImportError/Exception` or an import under
|
||||
# `if __name__ == "__main__"` must not stand as a strict cycle witness.
|
||||
GUARDED = "guarded"
|
||||
|
||||
|
||||
def module_name(path: str) -> str:
|
||||
|
|
@ -56,6 +60,34 @@ def is_type_checking_test(test: ast.expr) -> bool:
|
|||
return False
|
||||
|
||||
|
||||
def is_main_guard_test(test: ast.expr) -> bool:
|
||||
"""`if __name__ == "__main__":` — the body never runs on import."""
|
||||
if not (isinstance(test, ast.Compare) and len(test.ops) == 1
|
||||
and isinstance(test.ops[0], ast.Eq) and len(test.comparators) == 1):
|
||||
return False
|
||||
sides = (test.left, test.comparators[0])
|
||||
has_name = any(isinstance(s, ast.Name) and s.id == "__name__" for s in sides)
|
||||
has_main = any(isinstance(s, ast.Constant) and s.value == "__main__" for s in sides)
|
||||
return has_name and has_main
|
||||
|
||||
|
||||
_SWALLOWING = {"ImportError", "ModuleNotFoundError", "Exception", "BaseException"}
|
||||
|
||||
|
||||
def try_swallows_import_failure(node: ast.Try) -> bool:
|
||||
"""True when at least one handler catches import failure (or everything)."""
|
||||
for h in node.handlers:
|
||||
if h.type is None: # bare except
|
||||
return True
|
||||
types = h.type.elts if isinstance(h.type, ast.Tuple) else [h.type]
|
||||
for t in types:
|
||||
if isinstance(t, ast.Name) and t.id in _SWALLOWING:
|
||||
return True
|
||||
if isinstance(t, ast.Attribute) and t.attr in _SWALLOWING:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class ImportCollector(ast.NodeVisitor):
|
||||
"""Collect (kind, raw dotted target or ImportFrom base+names, lineno)."""
|
||||
|
||||
|
|
@ -64,12 +96,15 @@ class ImportCollector(ast.NodeVisitor):
|
|||
# each record: (kind, base_or_module, aliases (() for plain import), lineno)
|
||||
self._depth = 0 # function nesting depth
|
||||
self._tc = 0 # TYPE_CHECKING nesting depth
|
||||
self._guard = 0 # __main__-guard / failure-swallowing-try nesting depth
|
||||
|
||||
def _kind(self) -> str:
|
||||
if self._tc:
|
||||
return TYPE_ONLY
|
||||
if self._depth:
|
||||
return LAZY
|
||||
if self._guard:
|
||||
return GUARDED
|
||||
return STRICT
|
||||
|
||||
def visit_FunctionDef(self, node: ast.FunctionDef) -> None:
|
||||
|
|
@ -89,15 +124,32 @@ class ImportCollector(ast.NodeVisitor):
|
|||
|
||||
def visit_If(self, node: ast.If) -> None:
|
||||
tc = is_type_checking_test(node.test)
|
||||
mg = is_main_guard_test(node.test)
|
||||
if tc:
|
||||
self._tc += 1
|
||||
if mg:
|
||||
self._guard += 1
|
||||
for child in node.body:
|
||||
self.visit(child)
|
||||
if tc:
|
||||
self._tc -= 1
|
||||
if mg:
|
||||
self._guard -= 1
|
||||
for child in node.orelse:
|
||||
self.visit(child)
|
||||
|
||||
def visit_Try(self, node: ast.Try) -> None:
|
||||
swallows = try_swallows_import_failure(node)
|
||||
if swallows:
|
||||
self._guard += 1
|
||||
for child in node.body:
|
||||
self.visit(child)
|
||||
if swallows:
|
||||
self._guard -= 1
|
||||
for part in (node.handlers, node.orelse, node.finalbody):
|
||||
for child in part:
|
||||
self.visit(child)
|
||||
|
||||
def visit_Import(self, node: ast.Import) -> None:
|
||||
for alias in node.names:
|
||||
self.records.append((self._kind(), alias.name, (), node.lineno))
|
||||
|
|
@ -180,6 +232,7 @@ def main() -> int:
|
|||
edges: dict[str, dict[tuple[str, str], list[int]]] = {
|
||||
STRICT: defaultdict(list), TYPE_ONLY: defaultdict(list),
|
||||
LAZY: defaultdict(list), DYNAMIC: defaultdict(list),
|
||||
GUARDED: defaultdict(list),
|
||||
}
|
||||
dynamic_unresolved: list[tuple[str, int]] = []
|
||||
|
||||
|
|
@ -363,6 +416,7 @@ def main() -> int:
|
|||
L.append("")
|
||||
|
||||
for kind, title in ((LAZY, "Lazy imports (function-level)"),
|
||||
(GUARDED, "Guarded imports (__main__-only / failure-swallowing try)"),
|
||||
(TYPE_ONLY, "TYPE_CHECKING imports"),
|
||||
(DYNAMIC, "Dynamic imports (importlib / __import__)")):
|
||||
pairs = edges[kind]
|
||||
|
|
|
|||
|
|
@ -375,6 +375,7 @@ D20 = "Presence (PROPOSED new domain, owner decision pending; alternative: fold
|
|||
"ouroboros/triad_review.py" = "D06"
|
||||
"ouroboros/update_channels.py" = "D12"
|
||||
"ouroboros/usage_accounting.py" = "D16"
|
||||
"ouroboros/usage_legacy_import.py" = "D16"
|
||||
"ouroboros/usage_ledger.py" = "D16"
|
||||
"ouroboros/utils.py" = "D18"
|
||||
"ouroboros/version.py" = "D10"
|
||||
|
|
@ -534,7 +535,6 @@ proposed = [
|
|||
"ouroboros/tools/scope_review.py" = ["D06"]
|
||||
"ouroboros/tools/shell.py" = ["D05"]
|
||||
"ouroboros/tools/subagent_integration.py" = ["D07"]
|
||||
"ouroboros/usage_accounting.py" = ["D16"]
|
||||
"server.py" = ["D11"]
|
||||
"supervisor/events.py" = ["D08"]
|
||||
"supervisor/git_ops.py" = ["D10"]
|
||||
|
|
@ -578,7 +578,6 @@ proposed = [
|
|||
"ouroboros/tools/scope_review.py" = ["ouroboros/tools/scope_review_budget.py", "ouroboros/tools/scope_review_pack.py"]
|
||||
"ouroboros/tools/shell.py" = ["ouroboros/tools/shell_effects.py", "ouroboros/tools/shell_outputs.py", "ouroboros/tools/shell_process.py"]
|
||||
"ouroboros/tools/subagent_integration.py" = ["ouroboros/tools/subagent_integration_delegated.py"]
|
||||
"ouroboros/usage_accounting.py" = ["ouroboros/usage_legacy_import.py"]
|
||||
"server.py" = ["ouroboros/server_liveness.py", "ouroboros/server_maintenance.py", "ouroboros/server_owner_routing.py", "ouroboros/server_process.py", "ouroboros/server_restart.py", "ouroboros/server_routing_context.py"]
|
||||
"supervisor/events.py" = ["supervisor/events_budget.py", "supervisor/events_chat_delivery.py", "supervisor/events_coop_checkpoint.py", "supervisor/events_evolution_done.py", "supervisor/events_project_routing.py", "supervisor/events_runtime_controls.py", "supervisor/events_schedule_task.py", "supervisor/events_subagent_admission.py", "supervisor/events_task_done.py", "supervisor/events_worker_reports.py"]
|
||||
"supervisor/git_ops.py" = ["supervisor/git_ops_remotes.py", "supervisor/git_ops_rescue.py", "supervisor/git_ops_reset.py", "supervisor/git_ops_updates.py"]
|
||||
|
|
|
|||
|
|
@ -738,16 +738,22 @@ def _lockstep_tokens(up_text: str, leaf_text: str, sites: List[ast.Attribute],
|
|||
|
||||
|
||||
def verify_transplant(upstream_source: str, leaf_source: str, symbols: List[str],
|
||||
declared: Any, handle: str) -> Dict[str, Any]:
|
||||
declared: Any, handle: str,
|
||||
leaf_owned: Optional[Set[str]] = None) -> Dict[str, Any]:
|
||||
"""The PROOF: per moved symbol, inverse-normalize the leaf span and require
|
||||
(1) AST equality with the upstream span (ast.dump, no attributes) and
|
||||
(2) byte-identical tokens outside the rewritten references.
|
||||
(1) AST equality with the upstream span (ast.dump, no attributes),
|
||||
(2) byte-identical tokens outside the rewritten references, AND
|
||||
(3) a byte-identical round trip. Beyond the per-symbol spans it validates the
|
||||
WHOLE leaf as a runnable module (F0 phase review, audit 2026-08-30): the
|
||||
handle must be defined exactly once with the canonical body, no name may be
|
||||
both declared and preamble-bound (ambiguous ownership), every declared name
|
||||
must actually be read, and nothing unexpected may sit at top level.
|
||||
"""
|
||||
declared = frozenset(declared)
|
||||
up_spans = extract_spans(upstream_source, symbols)
|
||||
leaf_spans = extract_spans(leaf_source, symbols)
|
||||
report: Dict[str, Any] = {"ok": True, "symbols": {}, "handle_reads": [],
|
||||
"unread_declared": []}
|
||||
"unread_declared": [], "leaf_invariants": []}
|
||||
reads: Set[str] = set()
|
||||
for span in _unique_spans(leaf_spans):
|
||||
up = up_spans[span.name]
|
||||
|
|
@ -828,10 +834,73 @@ def verify_transplant(upstream_source: str, leaf_source: str, symbols: List[str]
|
|||
report["ok"] = False
|
||||
report["handle_reads"] = sorted(reads)
|
||||
report["unread_declared"] = sorted(declared - reads)
|
||||
_flag_undeclared_top_level(leaf_source, symbols, handle, report)
|
||||
_flag_undeclared_top_level(leaf_source, symbols, handle, report, leaf_owned)
|
||||
_validate_leaf_invariants(leaf_source, symbols, declared, handle, reads, report,
|
||||
leaf_owned)
|
||||
return report
|
||||
|
||||
|
||||
def _validate_leaf_invariants(leaf_source: str, symbols: List[str], declared: frozenset,
|
||||
handle: str, reads: Set[str], report: Dict[str, Any],
|
||||
leaf_owned: Optional[Set[str]]) -> None:
|
||||
"""Whole-leaf invariants a byte-faithful span proof cannot see (audit 2026-08-30
|
||||
CRITICAL): the leaf must be a runnable module, not just faithful fragments."""
|
||||
owned = set(leaf_owned or _PREAMBLE_OK_ASSIGN_DEFAULT)
|
||||
problems: List[str] = []
|
||||
tree = ast.parse(leaf_source)
|
||||
# (a) when the leaf reads anything through the handle (or declares names),
|
||||
# the handle must be defined exactly once, as a canonical parameterless
|
||||
# function whose body returns/points at a parent module (never `return None`).
|
||||
# A projection-only leaf (zero handle reads, zero declared) may omit it.
|
||||
handle_defs = [n for n in tree.body
|
||||
if isinstance(n, (ast.FunctionDef, ast.AsyncFunctionDef))
|
||||
and n.name == handle]
|
||||
handle_required = bool(reads or declared)
|
||||
if handle_required and len(handle_defs) != 1:
|
||||
problems.append(f"handle {handle!r} defined {len(handle_defs)} times, expected 1")
|
||||
elif len(handle_defs) > 1:
|
||||
problems.append(f"handle {handle!r} defined {len(handle_defs)} times, expected at most 1")
|
||||
elif handle_defs:
|
||||
hd = handle_defs[0]
|
||||
if hd.args.args or hd.args.kwonlyargs or hd.args.vararg or hd.args.kwarg:
|
||||
problems.append(f"handle {handle!r} must take no parameters")
|
||||
returns = [n for n in ast.walk(hd) if isinstance(n, ast.Return)]
|
||||
if not returns or any(
|
||||
isinstance(r.value, ast.Constant) and r.value.value is None
|
||||
or r.value is None for r in returns):
|
||||
problems.append(f"handle {handle!r} body must return the parent module, "
|
||||
"never None / bare return")
|
||||
# (b) declared names and preamble-bound names must be DISJOINT (ambiguous
|
||||
# ownership: a name both imported locally and read through the handle).
|
||||
preamble_bound: Set[str] = set()
|
||||
span_names = set(symbols) | {handle}
|
||||
for node in tree.body:
|
||||
if isinstance(node, (ast.Import, ast.ImportFrom)):
|
||||
for a in node.names:
|
||||
preamble_bound.add((a.asname or a.name).split(".")[0])
|
||||
elif isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
|
||||
if node.name not in span_names:
|
||||
preamble_bound.add(node.name)
|
||||
elif isinstance(node, ast.Assign):
|
||||
for t in node.targets:
|
||||
if isinstance(t, ast.Name) and t.id not in span_names:
|
||||
preamble_bound.add(t.id)
|
||||
overlap = declared & preamble_bound
|
||||
if overlap:
|
||||
problems.append(f"names both declared and preamble-bound (ambiguous ownership): "
|
||||
f"{sorted(overlap)}")
|
||||
# (c) every declared name must actually be read through the handle.
|
||||
if declared - reads:
|
||||
problems.append(f"declared but never read through {handle}(): "
|
||||
f"{sorted(declared - reads)}")
|
||||
# (d) leaf-owned allowlist must not silently absorb a declared name.
|
||||
if owned & declared:
|
||||
problems.append(f"leaf-owned allowlist overlaps declared set: {sorted(owned & declared)}")
|
||||
report["leaf_invariants"] = problems
|
||||
if problems:
|
||||
report["ok"] = False
|
||||
|
||||
|
||||
_PREAMBLE_OK_ASSIGN_DEFAULT = frozenset({"log"})
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -563,14 +563,19 @@ def test_cli_emit_and_check_roundtrip(tmp_path):
|
|||
# ---------------------------------------------------------------------------
|
||||
|
||||
_MUT_UP = "def f(a, b):\n return a + b + PARENT\n"
|
||||
_MUT_LEAF_OK = "def f(a, b):\n return a + b + _h().PARENT\n"
|
||||
_MUT_FN = "def f(a, b):\n return a + b + _h().PARENT\n"
|
||||
# A COMPLETE, runnable leaf (F0 review: verify the whole module, not a fragment).
|
||||
_MUT_LEAF_OK = ('"""doc"""\nfrom __future__ import annotations\n'
|
||||
"from ouroboros import config as _parent\n\n\n"
|
||||
"def _h():\n return _parent\n\n\n" + _MUT_FN)
|
||||
|
||||
|
||||
def test_mutation_whitespace_change_fails_byte_proof():
|
||||
"""Inter-token whitespace edits are invisible to the token proof; the
|
||||
mandatory byte round trip must catch them."""
|
||||
from scripts.v7next_transplant import verify_transplant
|
||||
leaf_ws = "def f(a, b):\n return a + b + _h().PARENT\n" # collapsed spaces
|
||||
fn_ws = "def f(a, b):\n return a + b + _h().PARENT\n" # collapsed spaces
|
||||
leaf_ws = _MUT_LEAF_OK.replace(_MUT_FN, fn_ws)
|
||||
rep = verify_transplant(_MUT_UP, leaf_ws, ["f"], {"PARENT"}, "_h")
|
||||
assert rep["ok"] is False
|
||||
assert "byte-identical" in (rep["symbols"]["f"]["detail"] or "")
|
||||
|
|
@ -587,17 +592,64 @@ def test_mutation_extra_top_level_def_fails():
|
|||
|
||||
|
||||
def test_mutation_import_time_side_effect_fails():
|
||||
leaf = "import os\n" + _MUT_LEAF_OK + "\nprint('boom')\n"
|
||||
leaf = _MUT_LEAF_OK + "\nprint('boom')\n"
|
||||
from scripts.v7next_transplant import verify_transplant
|
||||
rep = verify_transplant(_MUT_UP, leaf, ["f"], {"PARENT"}, "_h")
|
||||
assert rep["ok"] is False
|
||||
assert any("Expr" in e or "line" in e for e in rep["undeclared_top_level"])
|
||||
|
||||
|
||||
def test_preamble_allowlist_still_passes():
|
||||
leaf = ('"""doc"""\nfrom __future__ import annotations\nimport os\n'
|
||||
"log = None\n\ndef _h():\n return os\n\n" + _MUT_LEAF_OK)
|
||||
def test_complete_leaf_passes():
|
||||
from scripts.v7next_transplant import verify_transplant
|
||||
rep = verify_transplant(_MUT_UP, leaf, ["f"], {"PARENT"}, "_h")
|
||||
assert rep["undeclared_top_level"] == []
|
||||
rep = verify_transplant(_MUT_UP, _MUT_LEAF_OK, ["f"], {"PARENT"}, "_h")
|
||||
assert rep["undeclared_top_level"] == [] and rep["leaf_invariants"] == []
|
||||
assert rep["ok"] is True
|
||||
|
||||
|
||||
# --- F0 phase-review CRITICAL: whole-leaf invariants a span proof cannot see ---
|
||||
|
||||
def test_missing_handle_def_fails():
|
||||
"""A leaf that reads _h().PARENT but never defines _h is not runnable."""
|
||||
from scripts.v7next_transplant import verify_transplant
|
||||
rep = verify_transplant(_MUT_UP, _MUT_FN, ["f"], {"PARENT"}, "_h")
|
||||
assert rep["ok"] is False
|
||||
assert any("defined 0 times" in e for e in rep["leaf_invariants"])
|
||||
|
||||
|
||||
def test_handle_returning_none_fails():
|
||||
from scripts.v7next_transplant import verify_transplant
|
||||
bad = _MUT_LEAF_OK.replace("def _h():\n return _parent",
|
||||
"def _h():\n return None")
|
||||
rep = verify_transplant(_MUT_UP, bad, ["f"], {"PARENT"}, "_h")
|
||||
assert rep["ok"] is False
|
||||
assert any("must return the parent module" in e for e in rep["leaf_invariants"])
|
||||
|
||||
|
||||
def test_declared_and_preamble_bound_overlap_fails():
|
||||
"""PARENT both declared (read via handle) and imported in the preamble =
|
||||
ambiguous ownership."""
|
||||
from scripts.v7next_transplant import verify_transplant
|
||||
bad = _MUT_LEAF_OK.replace("from ouroboros import config as _parent\n",
|
||||
"from ouroboros import config as _parent\nimport PARENT\n")
|
||||
rep = verify_transplant(_MUT_UP, bad, ["f"], {"PARENT"}, "_h")
|
||||
assert rep["ok"] is False
|
||||
assert any("ambiguous ownership" in e for e in rep["leaf_invariants"])
|
||||
|
||||
|
||||
def test_unread_declared_name_fails():
|
||||
from scripts.v7next_transplant import verify_transplant
|
||||
rep = verify_transplant(_MUT_UP, _MUT_LEAF_OK, ["f"], {"PARENT", "UNUSED"}, "_h")
|
||||
assert rep["ok"] is False
|
||||
assert any("never read through" in e for e in rep["leaf_invariants"])
|
||||
|
||||
|
||||
def test_projection_only_leaf_without_handle_passes():
|
||||
"""A leaf with zero handle reads and zero declared names (pure projection,
|
||||
e.g. context_runtime_facts.py) legitimately carries no handle def."""
|
||||
from scripts.v7next_transplant import verify_transplant
|
||||
up = "def g():\n return 1\n"
|
||||
leaf = ('"""doc"""\nfrom __future__ import annotations\n\n\n'
|
||||
"def g():\n return 1\n")
|
||||
rep = verify_transplant(up, leaf, ["g"], set(), "_h")
|
||||
assert rep["leaf_invariants"] == []
|
||||
assert rep["ok"] is True
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue