diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 510fea7c2..f89f52b13 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -51,7 +51,8 @@ Evidence: every section relevant to this change in full. - [ ] I updated tests and documentation where behavior or architecture changed. - [ ] I did not include secrets, local settings, runtime state, logs, caches, or - generated build/review artifacts in the commit. + generated build/review artifacts in the commit; tracked material follows + DEVELOPMENT.md "Documentation contract" (including plans and optional reports). - [ ] I did **not** bump `VERSION` or release-only version carriers; maintainers assign the collision-free release version during final integration. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fdb5d288c..c38b40277 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -797,11 +797,6 @@ jobs: - uses: actions/setup-python@v5 with: python-version: '3.10' - - name: v7next adoption release bar - # The campaign ledger's release gate (every transplanted delta done or an - # explicit post-release row with its authority) runs on the tag path itself, - # not only inside the pytest wrapper (batch №13 item 4). - run: python scripts/v7next_adoption.py --release - name: Validate tag matches VERSION id: release_meta run: | diff --git a/.gitignore b/.gitignore index 4f5ea7741..6f8ff4ed4 100644 --- a/.gitignore +++ b/.gitignore @@ -60,7 +60,6 @@ MagicMock/ .final_combined_review.py .review_*.py /.review-drive/ -/.adversarial-review/ # Accidentally vendored site-packages fragments — guard against recurrence of the # pre-rc.7 "dump site-packages into the source tree" bug (see Version History diff --git a/ADOPTION_v7next.md b/ADOPTION_v7next.md deleted file mode 100644 index 56e2fda73..000000000 --- a/ADOPTION_v7next.md +++ /dev/null @@ -1,170 +0,0 @@ -# ADOPTION manifest — v7-side deltas over the v7next upstream base - -Ф0 skeleton (plan §5.1 as revised by roast F2: artifact/train-based, not -one-row-per-commit). Direction: this manifest covers the **v7 → upstream** -lane — every owner-approved v7 delta and campaign decision that must be -re-applied ON TOP of the integration base `ouroboros_v7next @ b9f7597f` -(upstream tip at branch creation). The opposite lane — upstream trains landing -after the cutoff — is adopted by owner signal («иди забирай») and is NOT -enumerated here; it gets its own train rows when the owner signals. - -Sources (frozen reference, read-only): - -- `ouroboros_v7_wip @ 9f691656` — `MIGRATION_v7.md` (3901 rows by the oracle's - own parser; the "3902" this bullet read until 2026-09-02 counted the header) and its - `APPROVED_SEMANTIC_DELTAS` registry (`scripts/v7_migration.py:130`): - 18 delta families `D02–D09, D11, D13, D18, D31, D33–D38` (the count read - "17" from the F0 skeleton to 2026-09-01; the list and the validator's - `REQUIRED_DELTAS` always held 18 — the word was wrong, not the inventory). -- `~/.claude/plans/v7next/V7NEXT_PLAN.md` — §2 mandatory returns, §6 ABI - package 7.0, §7 completeness, §5.4 three-column rule, v1.0 decision digest. - -Projection of the oracle's 3901 rows onto the families below — every row family -accounted for, with the residuals the family form does not prove — is the one-off -report `docs/archive/v7next/MIGRATION_PROJECTION.md` (owner batch №13 item 12 = A). - -Validator: `scripts/v7next_adoption.py` (unique ids; all 18 delta families -present; enum-valid disposition/status/phase; `--release` refuses any -`pending-decision` disposition or non-`done` status — "no unresolved rows at -release", plan §10). - -Schema (fixed; one row per artifact-level delta family, never per commit): - -- `id` — unique; `Dnn` = approved semantic delta family; `ABI-n` = plan §6 - item; `CPL-n` = plan §7 item; `R-*` = plan §2 class return; `TRAIN-*` = one - post-cutoff upstream adoption train; `DEFER-*` = a capability the owner - deferred out of 7.0; `Wn-Fn` = a defect a system-E2E wave found and - disclosed instead of fixing. -- `kind` — `semantic-delta` | `plan-item` | `class-return`. -- `disposition` — `retain` (owner decision stands, apply as decided) | - `re-prove` (re-apply and re-prove against the NEW bytes; plan §11: verbatim - ledger rows must be re-proved, precedent «сдвиг базы фальсифицировал 9 - строк») | `superseded-by-upstream` (upstream already carries the semantics; - residual named in `what`) | `pending-decision` (three-column resolution per - plan §5.4 still owed to the owner batch — forbidden at release). -- `status` — `pending` | `in-progress` | `done` | `deferred`. All rows start - `pending`; `deferred` is reserved for the owner-deferred post-release row. -- `phase` — target campaign phase (F1 calm domains, F2 hot organs, F3 ABI - package, F5 completeness, F6 synthesis and the rolling upstream sync). - For the required inventory the phase is pinned in the validator's - `REQUIRED_PHASE`, so a row cannot be rescheduled silently. -- `verification hook` — the suite/checker that proves the row when it lands - (suites named from the frozen reference arrive with their domain transplant, - plan §5.3 step 4; hooks marked "(new)" are named now, built in their phase). - Once the row is `done` the validator resolves EVERY token of its hook — path - and `::nodeid` half alike — in both modes, because a resolvable hook is a - property of a shipped row and not of the `--release` invocation. Until then - the hook may stay free prose naming the suite the work will land in. -- Prose outside the table (this header, the Notes) names row ids only as the - table has them: the validator resolves every id-shaped token it finds there - against the table, and an id the prose names WITHOUT a row must be declared - on one line of the form `No-row ids: A, B` — a declared id that has a row is - red too (the Notes called W4-F3/W4-F4 rowless for two days after d348ea46 - made them rows, past a green bar; a rowless claim in other words is not - read, so it belongs on that line). - -| id | kind | what | disposition | status | phase | verification hook | -|---|---|---|---|---|---|---| -| D02 | semantic-delta | Typed ToolResult/ToolCodeSpec seam with closed code table replacing re-read result prose (§4.3.3), incl. the loop-side retirement of result-text classification — the «D02-петля» mandatory return (plan §2). PHASE F1→F3 by the ratified F3 layout (2026-08-31): the typed organ is re-derived whole by the F3.1 lane A (design note docs/archive/v7next/DESIGN_TYPED_ORGAN.md); the deferral is documented across the D04/D05/D15 HOT-DEFERRED ledger rows, not silent. LANDED by the F3.1 lane A train (registry_core/tool_result re-split, producer cutovers, typed extension/MCP dispatch, loop cutover, T1 partition, D09 refusal subfamily incl. goldens 15→17); the ABI-9 digest-read and ABI-4 consumer sweep are the F3.2 seam's, not this organ's | re-prove | done | F3 | tests/test_tool_classification_differential.py + tests/test_tool_result.py | -| D03 | semantic-delta | Settings vocabulary seam: config.py -> settings_defaults / settings_scales / model_slots / review_model_routes / runtime_limits (§4.3.5); = plan §2 "S1 settings seam" return. LANDED in three trains: the vocabulary split (oracle rows 840-912) and the launcher half (rows 918-920) at a4481521; the semantic delta proper — rows 913-917 (config.normalize_settings_raw / serialize_settings, owner_settings.settings_document_digest + _owner_update_settings, the packaged_cli writer) and rows 1080-1081 (the server.py lifespan boot write) — by the D03 settings-read-seam lane (owner batch №11 2=A, base 1072a317), re-derived on tip bytes rather than replayed: the normalizer wraps the tip's settings_integrity read path instead of reverting it, the tip has four single-decision endpoints (the scope-review floor is retired, ABI 7.0 Q10=A) so the closed reader inventory is five names not six, the oracle's singular scope-pin ordering clause is dead here (both comma spellings are RETIRED_SETTING_KEYS) and the load-bearing order is re-stated as pass-count-before-purge and purge-before-rename, and the onboarding `_settings_fingerprint` wrapper was not kept (the digest is called directly; no passthrough). Red-first, stated for the file as DELIVERED rather than for the round-1 draft: 11 of that draft's 18 pins were observed failing on 1072a317, and the delivered 22-pin file has 15 failing on a 1072a317 export (the 11, the three round-2 pins, and the packaged-path pin, which round 2 turned behavioural and which the round-1 table still lists as a green golden); after round 4 deleted the six-file inventory pin (the tripwire closes the inventory over the whole tree) the file is 21 pins with 14 failing on that export. The 7 that stay green are goldens characterizing behaviour the seam must keep producing. The §4.3.5 defect no longer reproduces (one auto-grant POST changes exactly one key). Fix round 2 (same base): the Colab re-run is the third reader (`normalize_settings_raw` before its defaults merge, serializer bytes back on Drive), the owner reader shares the loader's verified read primitive (a changed pinned snapshot refuses both), and the context-fit route resolver reads the provider-normalized effective document — the read seam carries the vocabulary normalization only; the provider normalization is re-derived by every route consumer, which is what the retired boot write's "no reader needs it" rests on. Fix round 3 (same base): the prologue tripwire derives routing from the CALLS a function makes instead of from its source text (a docstring naming the prologue vouched for an unrouted writer) and sees a writer that neither names SETTINGS_PATH nor carries "settings" in its name; every writer now commits `serialize_settings` output through the byte-exact `utils.write_text_atomic`, so "one spelling on disk" is true on Windows too and is pinned on the mechanism as well as on the bytes; the packaged bootstrap saver takes the settings write guards on the path it writes; the context-fit pin takes its expectation from the task-start projection (the loop side) instead of restating the resolver's own expression; and the retired-key seam gains the classification pin for all fifteen keys. Fix round 4 (same base): the context-pair migration writes the one serializer's bytes (it re-derived the JSON text inside `atomic_write_json`, so "all five put the same spelling on disk" held only until the serializer changed, and was unpinned); the two writer scans are one predicate (`tests/_shared.py::settings_writers`) that sees a plain-handle write and any serializer call, parses every file (no prefilter), and closes the inventory over the tree against `tests/_shared.py::SETTINGS_WRITERS`, routed or not; the byte pin drives all five writers, requires each to call the serializer and to commit only through byte-exact shapes, and pins the spelling itself; the "every writer commits through write_text_atomic" wording names the config saver's `write_bytes` fallback. PHASE F1->F6 stays as the truth wave set it (operator scheduling correction, disclosed) | re-prove | done | F6 | tests/test_settings_read_seam.py (rows 913-917 and 1080-1081: normalizer golden + idempotence, read-writes-nothing, closed reader inventory, digest-bound locked update, one serializer, retired-key seam; round 2: the Colab fixture, the provider-normalized context-fit route, the pinned-snapshot refusal on every reader; round 3: the byte+mechanism writer pin, the packaged saver's pinned-snapshot refusal, the loop-side context-fit expectation, test_a_retired_key_is_absent_from_every_surface_that_would_react_to_it; round 4: test_every_settings_writer_puts_the_one_serializers_bytes_on_disk — all five writers driven, each calls the serializer, byte-exact commit shapes only, the spelling golden) + tests/test_config_extraction.py + tests/test_onboarding_host.py (the two earlier halves; the boot pin is test_server_boot_never_writes_the_settings_file plus the behavioural test_server_boot_leaves_the_settings_bytes_alone, red on the base) + tests/test_runtime_mode_elevation.py::test_every_settings_writer_routes_through_the_shared_prologue (one predicate, tests/_shared.py::settings_writers, no file prefilter; closed over ouroboros/**, supervisor/**, server.py and launcher.py against tests/_shared.py::SETTINGS_WRITERS, routed or not) | -| D04 | semantic-delta | Retired settings knobs (§4.3.6). Owner 1B (2026-09-01) after re-checking the knob set against the tip vocabulary: the flat wall-clock pair `OUROBOROS_SOFT_TIMEOUT_SEC`/`OUROBOROS_HARD_TIMEOUT_SEC` is RETIRED via `RETIRED_SETTING_KEYS` — stripped on load, every reader, default, init parameter, save-response bucket, `/status` line and doc row removed, and the RC auditor classes them as this ABI window's own removals (`since: 7.0`) | re-prove | done | F1 | tests/test_legacy_timeout_retirement.py | -| D05 | semantic-delta | Safety host facts (§4.3.8). Three-column outcome: `schedule_followup` = POLICY_SKIP is ALREADY carried by the tip (`ouroboros/safety.py:111`, byte-identical to the reference) — no port owed. The other two are ported by owner 2B (01.09) into the protected `ouroboros/safety.py`, retargeted onto tip bytes rather than copied from the reference: `_safety_drive_root(ctx)` replaces the cwd-relative `getattr(ctx, "drive_root", "../data")` at the safety model call with ctx-then-`config.DATA_DIR`, read late; `_record_safety_usage(ctx, payload)` takes the ledger writer off the module top level onto a CALL-TIME import of `supervisor.state`, the idiom the six sibling call sites in `ouroboros/` already use, so the module every worker imports carries no import-time edge into the supervisor package. Residual disclosed in the ledger: the reference's injectable `ctx.update_budget_from_usage` sink is ported with the function but has no ToolContext provider at this tip | re-prove | done | F1 | tests/test_safety_policy.py | -| D06 | semantic-delta | Events taxonomy: declared disposition of every event kind in four tiers, producer/answer pairing enforced (§4.3.12). Owner 3A (2026-09-01): the table is `supervisor/event_taxonomy.py` — PURE DATA (imports `__future__`/`dataclasses`/`typing` and nothing from the runtime, pinned by test, so it cannot become a second dispatcher) covering all 45 kinds the tree actually has, derived from the dispatch registry rather than restated: `worker_handler` (34 acting dispatch entries) + `telemetry_only` (7, and the tier IS `telemetry_events.TELEMETRY_EVENT_HANDLERS` — a handler that grows an action must change tier) together equal `EVENT_HANDLERS` exactly in BOTH directions, `server_intercept` (`restart_request`) and `nested_log_event` (`task_checkpoint`, `task_start_settings_reload_failed`, `review_reference`) are asserted ABSENT from it. Pairing is enforced both ways: the producer→answer direction REUSES the existing `test_worker_event_registry` AST scan (no second scanner) and every type it sees must be declared here; the answer→producer direction — which no scan can cover — declares each event's producing files and fails when one no longer names the event. The audit's producer-less `schedule_task` (events.py:272) is RETIRED rather than allowlisted: the handler function keeps its name and serves `schedule_subagent`, its only real producer, and a row with no producer is a hard failure with NO allowlist, so the next dead key must be retired too | re-prove | done | F1 | tests/test_event_taxonomy.py | -| D07 | semantic-delta | Emergency Stop 2A (§4.3.11): `execute_panic_stop` takes the actually bound main port as keyword-only `bound_port=None` and server.py hands down `_actual_bound_port()`; the lazy `import server` back-edge is gone, 8765 stays the last-resort fallback, and the cleanup / fail-soft / host-sweep / os._exit order is unchanged. THREE-COLUMN PASS TAKEN by owner batch №5 (5.5-5.8=A, 2026-08-31), so the disposition is no longer pending-decision: it reads re-prove and the row ships. LANDED at F1 by the D09-lane quiet edge (88479fa7) onto tip bytes with a two-way byte proof, and the F2 cancel/D07 matrices found no interaction with the upstream cancel machinery left to re-derive. Upstream (8d13373b, managed/ouroboros) still carries the lazy import, so this is a retained v7 delta, not superseded. The phase cell stays F2 — that is where the matrix retiring the pending-decision ran — even though the code landed in F1. The F0 hook (the cancellation E2E suite) is REPLACED, not extended: it pins nothing about the panic sweep | re-prove | done | F2 | tests/test_panic_stop_port_sweep.py::test_the_server_passes_its_bound_port_instead_of_the_leaf_reaching_back + tests/test_panic_stop_port_sweep.py::test_no_server_host_leaf_imports_the_composition_root + tests/test_server_control_panic_daemon.py + tests/test_post_task_evolution.py::test_execute_panic_stop_wires_owner_stop | -| D08 | semantic-delta | Cancellation/delegation fail-closed registries (§4.3.13; frozen rows 834-839 cancel_intents.py and 1083-1091 subagent_worktrees.py). THREE-COLUMN PASS TAKEN by owner batch №5 (2026-08-31), verbatim in the requirements archive: 5.6=A — the four fail-open cancel-intent mutators (mark_finalize_control_drained, mark_intent_scope, release_claim, settle_intent) re-derived on the upstream custody floor with a per-caller audit, request_cancel/claim_intent being the only already-strict, superseded sub-row; 5.10=A — the subagent_worktrees strict registry (absent = empty, malformed = typed SubagentWorktreeRegistryCorrupt plus a durable event, bytes kept, registration inside the cleanup scope) applied byte-identical to the reference. The disposition therefore moves off pending-decision to re-prove and the row ships. LANDED by 1b4a8da9 (registry) and 4fffefb1 (mutators); upstream 8d13373b is still fail-open at all thirteen sites, so this stays a live v7 return. The F0 cross-reference "same three-column pass as D07" is dropped: both rows now carry their own resolution | re-prove | done | F2 | tests/test_cancel_intent_corruption_s6.py + tests/test_subagent_worktree_registry_s6.py + tests/test_cancel_protocol_inventory_s6.py | -| D09 | semantic-delta | LLM one-physical-attempt-per-candidate ownership (§4.3.2) — mandatory return (plan §2). LANDED, both halves re-derived on tip bytes: the `_chat_local` `for attempt in range(3)` loop is deleted (86244943), so one physical attempt per call surfaces a transient failure to the single retry policy that owns the decision, and the typed-policy-refusal subfamily (PROVIDER_POLICY_REFUSAL / ProviderPolicyRefusal, the structural classify_llm_exception branch, goldens 15->17) landed with the F3.1 typed organ (b94a6d1d). ADDRESS CORRECTED: the F0 row's "llm.py:2487" is the pre-split base address — the lane lives in ouroboros/llm_local.py on this tree. Upstream still carries `for attempt in range(3)` (8d13373b ouroboros/llm.py:2476), so the pins re-verify the return on every rolling sync. DISCLOSED RESIDUAL: the Ф4 "D09-invariant" E2E scenario the F0 hook promised was never built — PARTIAL BY OWNER DECISION (batch №11, 5=A, 2026-09-02): the S24 LLM-routing scenario is POST-RELEASE backlog; the row reads done for the two landed halves and their unit pins only — the system_e2e S1-S23 set has no LLM-routing row — and the invariant is carried by the unit, golden and CPL-6 conformance pins instead. Building an S24 belongs to the scenario lane, not to this bookkeeping wave; recorded here so the promise is not silently dropped | re-prove | done | F1 | tests/test_context_overflow_hint.py::test_local_transport_makes_exactly_one_physical_attempt + tests/test_llm_typed_policy_refusal.py + tests/test_llm_provider_golden.py + tests/test_multiprovider_conformance.py::test_typed_policy_refusal_is_permanent_one_send_only + tests/test_llm_extraction.py | -| D11 | semantic-delta | FUNCTION_DEBT same-qualname relocation rule (§1.9/№8) — a ledger rule carried into the v7next ledger discipline unchanged. LANDED by the F1 D11 lane (d1c8fca4): the relocated_functions block replayed byte-identical from the oracle into the tip-shaped validate_manifest_transition in ouroboros/review.py, with the MODULE_DEBT_1500 layer deliberately NOT replayed (owner Q11=B) and the pin renamed per row 1033 with oracle bytes. Exercised on real history by the F2 addendum 14567df5 (the _handle_schedule_task debt key moving supervisor/events.py -> supervisor/events_schedule_task.py) and load-bearing for this branch's pairwise base-vs-tip ratchet gate. HOOK REPLACED: the F0 cell named a checker from the frozen reference (scripts/v7_migration.py) that does not exist in this tree, so it could never resolve | retain | done | F1 | tests/test_repo_health_smoke.py::test_transition_allows_a_same_qualname_relocation_but_not_a_swap + tests/test_smoke.py::test_size_ratchet_transition_against_explicit_base | -| D13 | semantic-delta | supervisor/git_ops pre-init roots follow OUROBOROS_* env (owner-ratified batch №11) | retain | done | F1 | tests/test_git_ops_default_roots.py | -| D18 | semantic-delta | Module-handle reads of rebound supervisor globals in queue/pool leaves (the `_queue()` / `_pool()` call-time handle idiom; plan §5.3 keeps it). LANDED in two trains — F1 D08 7d2dca49 (queue_schedules on `_queue`; worker_promotion, worker_chat_lane, worker_pool_lifecycle on `_pool`) and F2.2 4fffefb1 (queue_snapshot, queue_timeouts on `_queue`; worker_health, worker_assignment on `_pool`); rows 2041-2044 (queue_evolution) were resolved without the reference leaf per owner batch №5 5.8=A (upstream evolution_lifecycle.py). The four F2.2 leaves were proof-green but were NEVER PINNED: the f22 ledger entry claimed their declared sets were in LEAVES and the tree did not bear it out, so none of the three parametrized invariants was running on them. This truth wave adds the four rows with tool-derived exact read sets (147 passed, up from 135). RESIDUAL CLOSED by 091ee3b3, and closed in the direction OPPOSITE to the oracle — stated because the row's own hook would otherwise read as the oracle's. MIGRATION row 1030 asked for «supervisor.state owns the queue snapshot path; the queue reads it through the module at use time»; what landed here is supervisor.queue as the sole authority with no copy left in supervisor/state.py. The defect is the same one either way (two module globals answered one question and agreed only because both inits were handed the same drive root); the harness consequence is mirrored: the oracle noted that an isolation harness must then call state.init because queue.init alone would no longer redirect the snapshot, and on this tree it is queue.init that redirects and state.init alone that does not — production is invariant in both directions (server startup and the worker boot bind both). The oracle's hook name never came across either (`test_the_queue_snapshot_path_has_one_owner` in the heartbeat suite); the pin is the one this row names. Choosing the direction was the operator's call, not an owner decision, so it is disclosed here and in the ledger for the owner to overturn | re-prove | done | F1 | tests/test_module_handle_extraction.py (the eight queue/pool LEAVES rows through the three parametrized invariants) + tests/test_worker_process_extraction.py + tests/test_events_extraction.py::test_f22_queue_and_worker_leaves_keep_the_hot_label + tests/test_module_handle_extraction.py::test_queue_snapshot_path_has_a_single_authority (MIGRATION row 1030: supervisor.queue is the only QUEUE_SNAPSHOT_PATH authority) | -| D31 | semantic-delta | Contributor-review trust boundary: the lane always executes the review machinery of the target base via a detached trusted-base worktree (owner 2026-08-19 «всегда на старой версии», re-confirmed by batch №5 5.1=A) — mandatory return (plan §2). LANDED by the F2.3b lane 3b62c1d6 with the marker teeth of db944347, preserved through the F6 sync: `_run_on_trusted_base` in scripts/run_external_review.py re-executes the contributor review from a detached worktree of the target base with pinned base/head SHAs, wired ahead of any preflight in main(); the substrate path list is demoted to `review_substrate_changed` evidence, the contributor result is decided by the exit code alone, and a base without the wrapper fails closed rather than silently reviewing in place. Upstream still carries the classifier gate instead, so this is a genuine delta, not superseded | re-prove | done | F2 | tests/test_external_review_script.py (D31 pins: always-runs-on-the-trusted-base incl. the no-list-membership case, the e2e real-wrapper handoff, the wrapperless fail-closed refusal, exit-code-only result, receipt-drift fail-closed) | -| D33 | semantic-delta | L-B loop module-handle delta (`_loop()` call-time handle for the nine loop leaves; plan §5.3 keeps it). LANDED by the F1 D01 lane pick f0d8b147: nine `_loop()` leaves cut from tip bytes with 150 spans proof-green (ast = tokens = bytes), zero live v7 deltas, the full loop.py re-export surface kept (the reference's L3 trimming is a F5 consumer-rebind concern and was not carried) and HOT_CODE_PATHS closed over the nine leaves; declared sets re-derived after the F6 sync, where the owner acked that the FINALIZE_NOW drain calls owner_stop directly and the loop re-export stays | re-prove | done | F1 | tests/test_module_handle_extraction.py (the nine `_loop` LEAVES rows through the three parametrized invariants) + tests/test_loop_owner_facades.py (facade identity and HOT_CODE_PATHS parity over the nine leaves) | -| D34 | semantic-delta | Carrier-aware update engine: shared span-substitution resolver plus a span-descriptor SSOT in release_sync.py, applied at the three managed-update insertion points — mandatory return over the #276-based upstream engine (plan §2, §5.4). LANDED by the F2.4 update-engine train 7f0a1124 under owner answers 5.12-5.14=A: the span SSOT re-cut atop the tip release_sync.py to 25 descriptors over nine carrier paths (the eight reference spans incl. uv.lock and the README download refs, plus the install-page anchors derived from the release-asset templates); supervisor/update_carriers.py returned whole with bounded git; the three insertion points re-derived against the rewritten tip bodies in the re-split supervisor/update_merge_plan.py with zero resolver calls left in the parent; update_merge_policy reads CARRIER_SPAN_PATHS at call time; release-invariant, domain-manifest and popen-allowlist parity closed. DISCLOSED RESIDUALS: the engine governs steady state only, so the first pre-v7 upgrade still runs the OLD updater, and the boot-recovery M0 backfill does not re-run span resolution and degrades to assisted | re-prove | done | F2 | tests/test_update_carriers.py + tests/test_carrier_rebase_helper.py + tests/test_update_merge_owner_facade.py + tests/test_update_merge_assisted.py::test_materialize_projects_version_to_target_and_pins_m0 | -| D35 | semantic-delta | G1 git_ops module-handle delta (`_go()`; init rebinds REPO_DIR/DRIVE_ROOT/BRANCH_*) | re-prove | done | F1 | tests/test_module_handle_extraction.py + tests/test_git_ops_owner_facades.py | -| D36 | semantic-delta | DEL1 delegate-family module-handle delta (delegate_custody / tools/delegate / delegate_integration / subagent_integration leaves). LANDED by 782b5fe3 (F2.1: delegate_custody_reconcile on `_custody`, delegate_payload_patch on `_di`, subagent_integration_delegated on `_si`) and 1b4a8da9 (F2 finisher: tools/delegate_terminal_evidence on `_delegate`, renamed from the ledger's delegate_terminal per owner 5.9=A); all nineteen D36 rows read onto the tree with tip-derived declared sets rather than reference-verbatim ones, row 3467 is superseded-by-upstream, the facades keep every historical name, and both 1600-cap giants left the cap. Re-proven green on post-F6-sync bytes | re-prove | done | F2 | tests/test_module_handle_extraction.py (the four delegate-family LEAVES rows through the three parametrized invariants) + tests/test_delegate_owner_facades.py (facade identity and hot-code parity) | -| D37 | semantic-delta | L-C review-stack module-handle delta (`_rev()` / `_car()` over tools/review.py and tools/claude_advisory_review.py). LANDED by the F2.3a lane 04b1de9c (`_rev()` in tools/review_multi_model.py) and the F2.3b lane 3b62c1d6 (`_car()` in tools/preflight_review_prompt.py and tools/preflight_review_run.py), re-proved on tip bytes: the reference leaf names review_advisory_prompt/run were byte-falsified by the drift probe (the SDK transport had been retired) and were re-minted under the organ's public rename; declared sets are tool-exact read sets, supersets of the reference's, and the facades keep their EOF re-exports so the parent-namespace patch points survive. RESIDUAL BY DISPOSITION: `_parse_model_response` superseded by tools/review_response.py, the three deterministic preflights live with the upstream commit_admission.py (owner Q3=A), the review-model timeout constants and four SDK-era advisory names retired, and SLOT_ID_PREFIX plus seven prompt-vocabulary names stay import-bound behind a default-arg / f-string gate | re-prove | done | F2 | tests/test_module_handle_extraction.py (the review_multi_model, preflight_review_prompt and preflight_review_run LEAVES rows through the three parametrized invariants) + tests/test_review_owner_facades.py (REVIEW_LEAF_OWNERS identity plus the superseded-row pins for _parse_model_response and the deterministic preflights) | -| D38 | semantic-delta | L-C2 module-handle delta: agent-dispatch and usage legacy-import leaves. LANDED from tip bytes — usage_legacy_import.py on `_usage()` by the D16 pilot 1a17218d (the `_cached` name is upstream cache drift re-seated at the F6 sync), agent_dispatch.py on `_agent()` by the D01 lane f0d8b147 with a maximal declared set over the tip patch surfaces, plus the same-family post_task_synthesis.py on `_atp()` beyond the reference's handle-less leaf. Parents keep full facade re-exports. Upstream still carries the unsplit monoliths, so nothing here is superseded. DISCLOSED RESIDUAL: the usage declared set and the post_task_synthesis handle diverge from the frozen reference table — tip truth, recorded in the ledger — and a future F5 consumer-rebind wave may trim the facade re-exports without touching this mechanism | re-prove | done | F1 | tests/test_module_handle_extraction.py (the usage_legacy_import, agent_dispatch and post_task_synthesis LEAVES rows through the three parametrized invariants) + tests/test_lc2_owner_facades.py + tests/test_generated_inventories.py::test_facade_inventory_is_byte_identical | -| R-WINWAVE | class-return | Windows/cross-OS fix wave: re-apply by CLASS with a one-decision-per-class registry (both sides fixed the same territory independently — dedup, plan §2, risk §11). The registry now EXISTS as docs/archive/v7next/WINWAVE_CLASS_REGISTRY.md: sixteen reference-wave classes plus the one the matrix itself found, each with one recorded decision — 7 re-applied by the F1/F2/F3 lanes in reference form (fchmod guard plus the paid-lane non-POSIX refusal, the 0600 skipif, canonical-path compares, os.sep and !r-mirror expectations, the per-scenario registry route pin, the planted-stamp clock tick, the utf-8 ARCHITECTURE fixture), 3 superseded-by-upstream with one decision each (O_BINARY answered by write_bytes_atomic, reaper normpath/getuid, child-env SystemRoot forwarding), 6 not-applicable because the carriers the reference patched are absent here. DONE by the re-prove itself, after five red matrices — the history is kept because it is what the classes were decided against. The first 3-OS matrix ever dispatched on this branch (run 33555971481 on 9a28e58f) was green on ubuntu and macos and RED on windows — two source-text regex pins in the upstream-born web/tests/chat_plain_system_rows.test.js cannot match a CRLF checkout. That is a SEVENTEENTH cross-OS class neither the reference wave nor upstream had decided, not a regression of anything re-applied here; the registry now carries it as a decided row (normalize CRLF at the two source reads, landed by a0b35fcd on the campaign integration line — not on this worktree). The second matrix run 33563498919 on 196438c9 does carry that fix; it has since been read — it cleared class 17 and surfaced nine further Windows platform classes, fixed in 20afdbb7..e0aee1ac — so it is no longer the row's blocker. The re-prove is run 33569841899 on 8b27b507 (full 3-OS green on the first attempt) and it held on 33570328266, 33571681398 and 33572515529, each green on its first attempt. On the later branch tips two of the greens were RERUNS and the registry now says so: 33579445704 (1072a317) and 33624546416 (ac17fa03) each had full-test (windows-latest) fail on attempt 1 and go green on the rerun — ac17fa03's cause was rooted by the code fix 43dcc1d2 (the coarse-clock horizon pin), while 1072a317's two Windows failures (the observability GC copy-back and a preflight xdist worker timeout) have no landed fix and stay an intermittent, unrooted class: that is the O3 question put to the owner, disclosed rather than decided here. First-attempt greens on the later tips are 33626834806 (43dcc1d2, all three full-test legs green on attempt 1; only its separate system-e2e-mock job was rerun) and 33644668074 on the sync #3 merge f4abe0a5, green on every job on attempt 1 — verdict read 2026-09-02 15:00Z, so the newest tip is no longer a freshness gap. Read since as well: run 33574822693 (d21806d8) was not pending but RED — full-test (windows-latest) failed and the run was never rerun, so it is not a re-prove; the two tips after it (1072a317, ac17fa03) went green on the Windows leg only on rerun, and the first later first-attempt-green Windows leg is 43dcc1d2 (33626834806). Second item, still open on this tree: the accepted 2026-08-30 audit item to re-pin the registry route test on the actual alias condition instead of os.name — tests/test_registry_core.py:813 still reads os.name here, and the repin is being landed by the stage-2 smalls lane of this same fix wave, not by this row. PHASE F1->F6: plan §10 places the full 3-OS matrix in the F6 gate, and this row is not in the validator's REQUIRED_PHASE, so no pinning changes with it | re-prove | done | F6 | docs/archive/v7next/WINWAVE_CLASS_REGISTRY.md (the per-class decisions and the 3-OS run log) + the per-class Linux pins tests/test_atomic_write_v639.py + tests/test_launcher_server_reaper.py + tests/test_registry_core.py + tests/fixtures_e2e_cancellation.py + tests/test_e2e_cancellation_scenarios.py + tests/test_core_native_results.py + tests/test_owner_stop_fences_s6.py + tests/test_update_carriers.py + tests/test_evolution_state_integrity_v3.py + first-attempt-green 3-OS runs 33569841899 (8b27b507), 33570328266, 33571681398, 33572515529, 33626834806 (43dcc1d2, the three full-test legs) and 33644668074 (f4abe0a5, every job), all logged with their attempt counts in the registry run table | -| ABI-1 | plan-item | PluginAPI 2.0 via the convergent design (§6-1 + §6.1-Δ, owner «A» 30.08) — LANDED by the F3.1-B lane: PLUGIN_API_VERSION="2.0" with full manifest negotiation (major strict/minor minimum, closed-set capabilities, typed educational refusals, checked BEFORE plugin import/OOP catalog), absent field ≡ LEGACY "1.3" by construction, admission predicate at NEW-PASS issuance common to review/attest/native-seed (native_seed closed; $0 refusal before panel dispatch), hash-bound-PASS grandfather + clobber-guard, preflight fail-open fixed (fail-closed, no persist on infra failure), frozen RuntimeInfo TypedDict, per-version surface fingerprints fail-closed both directions, FORBIDDEN_EXTENSION_SETTINGS alias collapsed, legacy PluginAPIImpl kwargs removed, iframe_raw desync healed (VALID_EXTENSION_PERMISSIONS re-derived from skill_manifest — single carrier), negotiated generation in registration bundle + dispatch-surface stamps, bundled skills (telegram, unix_computer_use) declare the field with version bumps, grants resync on native re-seed with unchanged requested sets per owner «A», and the 6.2=A declarative dependency fingerprint (.ouroboros_env outside the hash, declared names inside; deps_declaration_desync typed refusal) | retain | done | F3 | tests/test_extension_plugin_api_matrix.py + tests/test_plugin_api_admission.py (packaged-artifact admission) | -| ABI-2 | plan-item | task-result `_schema_version=1` per Q8=B safe-B: no legacy converter, quarantine on unstamped/future/malformed/retired-`until_deadline` rows with log-only visibility (owner 6.3=B: one batched durable event, no UI counter, no chat notice); state.json/queue_snapshot get a stamp on write, shape unchanged. Ф3.1 fix-round closed the reader gaps: POST /api/tasks probes identity with the STRICT loader (an inadmissible stored row keeps its id occupied — 409 — and is never quarantined by the probe) and the unfiltered GET /api/tasks slice is admission-aware with one batched quarantine event per scan; fix-round-2 closed the last silent drop — a candidate whose bytes fail to parse reaches the SAME admission reader (quarantine + the one batched event) even beyond the slice window, with the parseable-inadmissible-beyond-window residual disclosed in the payload docstring. ONE CARVE-OUT (owner 4A, E9 lane): the quarantine stranded the very rows the cancellation redesign exists to rescue — a pre-redesign result still LATCHED at `cancel_requested` is unstamped by definition, so the first ordinary read quarantined it, `migrate_legacy_cancel_latches` no longer found it, and custody's own fail-soft read then settled `not_found` — the wedged task disappeared without ever reaching a terminal. The boot latch migration now opens with a pre-pass that re-writes exactly those rows through the ordinary writer (same status, same fields, the stamp-on-write `require_writable_task_result_schema` already admits as lawful for a live pre-upgrade task — a wedged one has no worker left to perform it); the row is then an ordinary latch that the existing intent→custody path drives to the `cancelled` terminal. Not a converter and deliberately not a general one: no other refusal reason, no other status, and every other unstamped row still quarantines on the next read; applying the carve-out is ONE typed durable `task_result_cancel_latch_admitted` event per boot (6.3=B log-only, never one per file) | retain | done | F3 | tests/test_task_result_schema_quarantine.py — F12 semantics: future-refusal, malformed, idempotency, N−1, rollback + tests/test_cancel_intents_phase_a.py::test_boot_migration_admits_the_unstamped_latch_and_quarantines_the_rest + ::test_the_admitted_latch_reaches_the_cancelled_terminal + tests/test_e2e_cancellation_scenarios.py::test_e9_boot_migration_adopts_a_legacy_cancel_requested_latch (mock lane) + tests/test_headless_task_api.py::test_task_api_identity_collision_check_is_strict_not_fail_soft + tests/test_tasks_list_slice.py::test_unfiltered_list_slice_is_admission_aware_with_one_batched_event + ::test_malformed_result_file_is_quarantined_not_silently_dropped + ::test_malformed_candidate_beyond_the_slice_window_is_still_quarantined | -| ABI-3 | plan-item | Gateway ABI — LANDED by the F3.1 lane D3 train: the five compat aliases removed (cost_usd/cost_usd_with_children stripped at the cost SSOT seams with stored read-tolerance kept; telegram_chat_id gone from the four outbound frames and the history mapper; project_last_viewed/hidden gone from UiPreferencesResponse + endpoint, unknown-key 400) + the contracts/api_v1 shim removed (ABI-6д routed here); executable ABI = gateway/schema.py JSON Schema DERIVED from the contracts TypedDicts, validated on INGRESS only (WS chat/command + typed HTTP request bodies; replay never validated); GATEWAY_ABI_VERSION="7.0" is the carrier decoupled from the product version (the stale alias JSDoc lines were cleaned in the F3.3 comma-sweep tact: the browser mirror is exact again and the parity test's field loop runs with NO excuse set — DocumentOutbound and UiPreferencesResponse joined the exact loop in the F3 adversarial fix-round with negative pins on the removed telegram_chat_id/project_last_viewed/project_hidden fields). Ф3.1 fix-round cut the remaining supervisor/task-result producers over to the honest cost names (task_admission, events_schedule_task, workers, events_task_done, queue/cancel_publication/task_lifecycle fallbacks, reconstruct_task_cost, post_task_checkpoint, post_task_synthesis evidence rows); fix-round-2 landed the projection-boundary semantics: the ABI carries NO alias — public_task_result/task detail/list row/history frames/cancel path emit honest names only, stored legacy resolves deprecated-wins and NORMALIZES at projection and at re-write (write_task_result strips aliases from the merged existing row; TASK_COST_META_FIELDS is honest-only with carry_cost_meta at every possibly-legacy copy seam), and the public-projection planes (subagent envelope, loop-outcome usage snapshot) are cut over and banned from the sweep allowlist; fix-round-3 landed the DEPTH: one shared normalizer (normalize_task_result_cost_planes) serves projection AND rewrite so the nested public planes (subagent envelope + envelope.usage — the actually supported producer path — and loop_outcome.usage) leave honest-only, build_subagent_envelope normalizes the stored usage snapshot before embedding, the evolution campaign history producer stamps the honest name with a /api/state projection-boundary conversion for stored legacy rows, the sweep allowlist is count-anchored per site (a new emission inside an allowlisted function fails), and the Logs UI reads the honest backfill name via the shared JS pair resolver. RESIDUAL BY DESIGN: internal evidence planes (review receipts, ledger rows, checkpoint/observability records) keep their own cost_usd spelling per the anchored per-site allowlist — their durable-log replays convert at the /api/logs projection boundary | retain | done | F3 | tests/test_gateway_abi3_removals.py incl. TestAliasProducerFanOutSweep (whole-runtime-tree emission sweep: write_task_result kwargs unallowlisted, dict-key/subscript/ANY-call-kwarg allowlist = PER-SITE (file, alias, scope) internal non-gateway planes only, staleness fails, public-projection planes banned) + TestProjectionBoundaryNormalization (stored legacy row → outbound honest-only, deep-scanned; rewrite normalization) + tests/test_gateway_ingress_schema.py + tests/test_contracts.py + F11 inventory docs/v7next/ABI3_GATEWAY_ALIAS_INVENTORY.md | -| ABI-4 | plan-item | `ResolvedModelTarget` frozen dataclass (D02-owner). LANDED by the F3.2 lane A sweep (base 3ba9f452), truth-scoped by the F3 adversarial fix-round: dataclass in `model_slots.py`, constructor `provider_models.resolve_model_target` at the existing seams; consumers typed — the cross-model fallback ladder (`fallback_candidate_targets` → tuple of targets, loop chain iterates `.model_id`; `provider_route` stays the "" sentinel because the chain's local-vs-remote dispatch lane is the loop's single global USE_LOCAL_FALLBACK flag, the pre-existing contract kept byte-identical), the reviewer SLOT builders (`resolved_review_model_target` read per slot in reviewer_slot_config; `get_review_targets`/`get_scope_review_targets` are typed views WITHOUT production consumers yet, disclosed in their docstrings) — PARTIAL BY OWNER DECISION (batch №11, 4=A, 2026-09-02): the views stay, the reviewer-surface migration onto them is POST-RELEASE backlog; the row reads done for the landed dataclass and constructor only, and the delegated lane (`DelegationRoute.resolved_target()` in ouroboros/subagents.py:183, read once by ouroboros/tools/delegate.py:245 into the run-request assembly; the relocation to a `provider_models.delegated_route_target(route)` free function that an earlier draft of this row named was NOT what landed — no such symbol exists on this tree). NAMED RESIDUAL (migration NOT performed, review surfaces untouched): plan_review_runtime, review_multi_model and the reviewer parallel vectors (models/routes/efforts in reviewer_slot_config/commit review) keep their string ABI. Strings also survive at transport wire boundaries (chat-API model param, Claudexor JSON body) — residual disclosed in the F3.2 lane A ledger section | retain | done | F3 | tests/test_resolved_model_target.py | -| ABI-5 | plan-item | Q10 removals (all owner «A») — LANDED by the F3.0 opening train: SCOPE_REVIEW_FLOOR removed on all surfaces (key retired via RETIRED_SETTING_KEYS, endpoint/contract/web client/shell+browser guards/SAFETY clause/tests; family read-carve survives), dead fail_tasks removed (pause = the only semantics; E8 superseded by E13 already in F2.2), until_deadline/stall_rounds aliases removed incl. bench adapters | retain | done | F3 | tests/test_abi5_q10_removals.py (per-surface removal pins) + tests/fixtures_e2e_cancellation.py (E13 supersession row) | -| ABI-6 | plan-item | P1 hygiene, per-item disposition, all seven items now closed on tip. LANDED by the F3.0 F9 train (03a835b9, e94f063d, cf320c78): the _call_llm_with_retry alias removed, the compute_cost_with_children plus format_handoff_message dead rollup removed, and the latent CHECKLISTS doc-vs-code fix applied (the env_allowlist row falsely listed TELEGRAM_BOT_TOKEN in FORBIDDEN_SKILL_SETTINGS; the quoted line number had drifted). CLOSED SINCE: the _typed_or_adapted branch executed inside the F3.1 lane A re-derivation (ccbb933a — the branch was not reproduced and has zero tip hits) and the contracts/api_v1 shim executed in lane D3 with negative pins (33ba6e83). SUPERSEDED-BY-UPSTREAM: the «failure-detector compat wrapper» and the «3 underscore renames» are not re-locatable on tip — the primary P1 inventory carries no addresses and a tip re-location sweep found no match, evidence in LEDGER_CORRECTIONS (F3.0 lane section); no replacements are to be invented. The _updater_imports change stays REJECTED — the baseline pin is intentional. DISCLOSED RESIDUAL: the three F3.0 removals are proven by surviving positive suites plus grep-level absence, not by dedicated negative-pin tests. HOOK REPLACED: the F0 cell was prose plus grep verbs and could never resolve | retain | done | F3 | tests/test_contracts.py::test_api_v1_shim_removed_and_gateway_declares_core_ws_message_types + tests/test_gateway_abi3_removals.py (TestApiV1ShimRemoval negative pins) + tests/test_tool_classification_differential.py + tests/test_tool_result.py (the loop holds no classifier of its own) + tests/test_run_llm_loop.py + tests/test_budget_limits.py (only the public call_llm_with_retry is a patch point) + tests/test_cost_projection.py + tests/test_task_status_flow.py (the surviving rollup readers) + tests/test_skill_exec.py + tests/test_extension_plugin_api.py (the FORBIDDEN_SKILL_SETTINGS consumers behind the CHECKLISTS fix) + docs/archive/v7next/LEDGER_CORRECTIONS.md (the F3.0 lane section carrying the re-location sweep for the two superseded items) | -| ABI-7 | plan-item | RC auditor/migrator — both halves LANDED: 7b RC auditor (F13, F3.3 serial tail) — scripts/rc_audit.py, a READ-ONLY pre-upgrade scan of a third-party install emitting the machine-readable scope document (abi 7.0, sources tree/inventories_frozen_at, checks[] of the five frozen classes: gateway-alias × ABI-3 F11 inventory, retired-setting × RETIRED_SETTING_KEYS, comma-list × RETIRED_COMMA_LIST_SETTING_KEYS snapped from settings_defaults at execution time, plugin-api × ABI-1 admission facts, schema-stamp × ABI-2 with the Q8=B quarantine consequence named) + typed JSON report + human render, exit 0/1/2 (F3 adversarial fix-round hardening: unreadable/unparseable mandatory sources are BLOCKING unauditable-source findings on exit 1, traversal/report-write OSError and an unsafe PYTHONPYCACHEPREFIX are exit 2, the grandfather note is hash-VERIFIED against the current payload bytes under the directory-basename state key, discovery reuses the runtime skill walk, sources.tree carries -dirty), everything non-machine-checkable printed as the OWNER ATTESTATION list (no pretend-coverage), N−1 fixtures = REAL previous-minor bytes in tests/fixtures/nminus1/ (settings + task result written by the v6.113.4 code itself, telegram manifest @ f0313064); 7a N−1 updater transition entry point/shim with crash-point tests (F14, Q10=A — landed: tx stamp, unstamped-N−1 accepted, future fail-closed) | retain | done | F3 | tests/test_update_tx_nminus1_shim.py (F14, landed) + tests/test_rc_audit_fixture_suite.py (the RC audit fixture suite — F13/F14, landed) | -| 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. DEFERRED OUT OF 7.0 BY THE OWNER: batch №7 item 6 (2026-09-01, requirements archive [A-BATCH-7-ANSWERS]), owner verbatim «6. ок» on «ABI-8 подтверждён в пост-релизный бэклог»; the no-«7.1» frame is Q16=A with the owner's поправка «никакого «7.1» как части кампании» ([A-V7NEXT-BATCH-2], 2026-08-30) | 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 — LANDED by the F3.1-B lane: stage→validate→swap of the registration snapshot for BOTH the in-process register() window and the child-catalog install (not `_lock` around inserts); deferred side effects (supervised runners, companion spawns) start only at publication, which structurally fixes the supervised-future leak (direct regression test proven red on 29e2b045 pre-fix); internal disposers list stays out of the ABI; per-publication extension-generation digest stamped into tool/route/ws surfaces + `extension_generation_digest` reader for physical-call provenance (dispatch-side read = Ф3.2 seam — LANDED by the F3.2 lane B: `_dispatch_extension_tool_result` stamps `extension_generation` — the descriptor's per-publication surface stamp, registry reader fallback — into the typed result meta of every physical dispatch attempt, so the tools.jsonl `tool_result_meta` record names the exact published generation the call ran against; a provenance READ only — no validation/gating, and the pre-dispatch typed refusals EXTENSION_UNAVAILABLE/SAFETY_VIOLATION keep their exact pre-seam shape). Ф3.1 fix-round hardened publication under ONE registry-lock hold with STAGED event subscriptions; fix-round-2 fixed the ordering to validate→SWAP→attach (the definitive validation runs first, the snapshot swap publishes the bundle, and only then do the deferred side effects attach — a handler is visible to the bus only for an already-published extension, closing the mid-publication EventBus race; a post-swap attach failure is disclosed and disposed through the standard unload path); fix-round-3 closed the OOP/unload tails as the disclosed STAGED PROTOCOL (not a false "one atomic publication" absolute): the OOP load stages catalog surfaces AND companion spawns on one snapshot and publishes them in a single transaction, the one structurally later publication (server-side companion recovery onto a live bundle) re-stamps every already-published descriptor with the freshly minted digest and routes ANY failure through dispose+unload (never a silent abort leaving a half-alive extension), and unload closes visibility outside-in (bus unsubscribe + runtime-API close BEFORE surfaces leave), with the EventBus copy-semantics residual disclosed in its docstring — the supported pin is "a publish started after unsubscribe never delivers"; fix-round-4 closed the recovery lifecycle TOCTOU: the companion-recovery publication is GENERATION-BOUND — `ensure_companions_running` snapshots the observed bundle generation with the companion names, publishes under the lifecycle lock, and `_publish_registrations(require_live_generation=…)` re-validates under the registry lock that the observed publication is still live (a vanished or reloaded bundle is a typed `ExtensionStaleRecoveryError` refusal with zero effects; the recovery form of the OOP publication helper structurally REQUIRES a pre-existing live bundle and can no longer resurrect a companion-only bundle after disable/unload — the test that pinned bundle-creation-on-recovery is replaced with the opposite pin, disclosed), and the recovery failure-disposal is generation-bound too (`unload_extension(expected_generation=…)` no-ops with disclosure on a newer publication); fix-round-5 made the stale refusal's zero effects true on the FILESYSTEM as well: the companion auth token (`auth_token.json`) is no longer minted/rotated during descriptor build — it materializes into the staged companion descriptors only in the post-swap attach, after the generation fence admitted the publication — so a recovery that lost the race to an unload/reload can no longer overwrite the live publication's token and permanently de-authorize its running companions (their spawn env holds the token while the Host Service rereads the file on every request); fix-round-6 extended the post-fence materialization to the WHOLE companion env: descriptor build no longer reads settings (`_scrub_env`→`load_settings` takes the settings lock and may persist a settings migration) and the recovery entry resolves the state dir without a creating mkdir — the settings-derived values, the manifest env overlay, the host bridge URL, the state dir and the token all materialize only in the post-swap attach, so a stale refusal has zero effects on the authorization/token/registry/bundle/companion-env planes; RESIDUAL BY DESIGN: an accepted post-fence mint can rotate the shared token while an older companion keeps its stale spawn-env copy — every rotation precondition (missing/corrupt/hash-stale token file) means that companion's token already failed the Host Service's per-request file+content-hash check, so rotation restores authorization for the publication's spawns and never revokes a valid one; the un-restarted old companion stays de-authorized until reload; fix-round-7 (final, converged) scoped the pre-fence contract honestly instead of rewriting the runtime-wide settings/grant read layer: on the way to the fence the liveness/grant projection may take infrastructure reads (the settings lock inside `load_settings` via `requested_core_setting_keys`, a state-dir mkdir via the health/grant projection) exactly as anywhere else in the runtime — the zero-effects guarantee covers authorization/token/registries/bundles/companion-env, never "every filesystem plane"; and made the token mint transient-safe: a hash/read failure with a parseable stored token reuses it byte-for-byte unrotated (the running companion stays authorized), and with no reusable token it fails closed with the typed `SkillTokenHashUnavailableError` instead of minting against an empty hash — which is what makes the round-6 "never revokes a valid one" claim true; RESIDUAL DISCLOSED (pre-existing, not introduced by this cycle): concurrent mints (publication attach / `get_skill_token` / process-runner child env) are read-decide-write over `auth_token.json` without a shared lock, so the last writer can supersede a token just returned to another caller | retain | done | F3 | tests/test_extension_registration_atomicity.py (incl. test_conflict_refused_publication_has_zero_external_effects + test_event_published_before_publication_never_invokes_the_handler + test_concurrent_publish_in_the_validate_to_attach_window_never_invokes_the_handler (real barrier-sequenced race) + test_supervised_effect_starts_only_after_the_swap + test_post_swap_attach_failure_disposes_through_the_standard_unload_path + test_out_of_process_surfaces_and_companions_publish_as_one_transaction + test_companion_recovery_failure_unloads_instead_of_silent_abort + test_late_publication_restamps_already_published_descriptors + test_unload_closes_bus_and_runtime_visibility_before_surfaces_leave + test_publish_started_after_unload_never_delivers) + tests/test_extension_companion.py fix-round-4 pins (test_unload_completing_between_snapshot_and_publication_refuses_recovery + test_recovery_publication_refuses_on_generation_mismatch_without_effects + test_generation_bound_disposal_skips_a_newer_publication + test_recovery_publication_requires_a_pre_existing_live_bundle) + fix-round-5 pin (test_stale_recovery_does_not_break_live_publication_authorization, red pre-fix) + fix-round-6 pin (test_stale_recovery_with_env_from_settings_has_zero_filesystem_effects; hardened in round-7: both load_settings seams tripwired in the post-snapshot window, size+mtime tree snapshot, settings-lock absence, settings-derived/manifest-overlay/PYTHONPATH delivery asserts) + fix-round-7 pins (test_transient_hash_error_never_rotates_a_valid_token, red pre-fix + test_transient_hash_error_without_reusable_token_fails_closed) + tests/test_extension_loader_extraction.py + F3.2 dispatch-read pins (test_dispatch_provenance_carries_the_published_generation_digest, red pre-fix + test_dispatch_provenance_falls_back_to_the_registry_reader, red pre-fix + test_unavailable_refusal_keeps_the_pre_seam_typed_shape) | -| ABI-10 | plan-item | Reviewer comma-lists → actor rows: the model is already upstream (#384); residual migration read REMOVED by the F3.1 lane D4 train (owner 5.4=A) — legacy block deleted, shipped default panel over the derived env plane (identical models on every config class), comma keys + phase-5 route envs retired via RETIRED_SETTING_KEYS, derived projection kept, bench templates migrated to structured slots with the same models | superseded-by-upstream | done | F3 | tests/test_comma_list_remnant_sweep.py (F3.3 count-anchored remnant sweep — the phase CI gate) + tests/test_comma_list_sweep.py (F3.1 migration-read sweep) + tests/test_reviewer_slot_config.py | -| CPL-1 | plan-item | Production manifest ouroboros/domains.toml (module→domain 1:1 over all 508 tracked runtime modules; completeness = red on drift) + domain gate: strict direction matrix pinned as FACTUAL baseline data (164 pairs; new direction = red), cycle gate against the pinned SCC ceiling (owner №8=A, 2026-09-01: the current all-20-domain strict-quotient SCC ceiling is ACCEPTED for v7.0 — the gate binds shrink-only, target `cycle_groups = []`; a true cycles=0 untangling is a post-release campaign), lazy/dynamic import classification pinned (92 lazy-only pairs); the 80 `[classification].proposed` new-upstream module placements are no longer open — owner batch №9 №10=A (2026-09-01) accepted them as the 7.0 base, the review marker retired and DOMAIN_MAP.md regenerated with zero starred rows, cross-domain literal-copy ban (baseline EMPTY — every new copied body is red); DOMAIN_MAP.md generated from the manifest (gen/verify pair). Report-only Ф0 stage superseded: the manifest moved from scripts/v7next_domains.toml, the report stays the witness-level companion on the shared scripts/domain_graph.py core | retain | done | F5 | scripts/check_domains.py (gate + --write regenerator) + tests/test_domain_manifest.py (verify half incl. synthetic red-branch pins) + docs/DOMAIN_MAP.md | -| CPL-2 | plan-item | gen/verify pairs shipped for all three inventories: frozen-contracts table (ARCHITECTURE §11.1 machine extraction, owner/anchor path resolution, contracts-package coverage gap pinned), data-layout tree (ARCHITECTURE §1 Data-layout tree — the factual carrier here; the reference PERSISTENCE_OWNERS.md doc does not exist in this tree — probed entry-by-entry against tracked paths and runtime-source literals), facade inventory (AST noqa:F401 re-export scan over the manifest population); staleness = red CI | retain | done | F5 | scripts/regenerate_inventories.py (--check) + tests/test_generated_inventories.py + docs/v7next/FROZEN_CONTRACTS_INVENTORY.md + docs/v7next/DATA_LAYOUT_INVENTORY.md + docs/v7next/FACADE_INVENTORY.md | -| CPL-3 | plan-item | code_intelligence architecture facts: owner_of(path\|symbol), domain_dependencies(d), facade_consumers(sym), persistence_entities_written_by(sym), protected_contracts_affected(diff); consumer №1 = Ouroboros self-evolution (Q12=B: completeness ships whole in v7.0). LANDED by the F5 lane C: five pure queries over the pinned carriers (domain manifest, facade/persistence/frozen-contract inventories, runtime_mode_policy protected sets) in `ouroboros/code_intelligence_architecture.py` — a D05 leaf beside code_intelligence; model seam = the EXISTING query_code tool, new `op=architecture` (no new registry tool — lane decision, ledger F5 lane C section) | retain | done | F5 | tests/test_architecture_facts.py (real-example pins + carrier completeness both ways + the query_code op seam) | -| CPL-4 | plan-item | Persistence: schema_version/migration/retention/reset decision per durable entity, local (no generic framework); close §16 findings (undocumented planes, unbounded ledgers, mismatched temp — the §16 source is unrecoverable; inventory rebuilt by factual writer scan, disclosed in the F5 lane B ledger section with the candidate-fix table). Inventory + verify hook DONE; the mechanical train (owner plan №9=A) landed CPL4-C1..C5, C7, C8, C10, C12..C15, C17, C18, C20, C23, and owner batch №8 (2026-09-01, all A) ratified + landed C9 (2A retire reader), C11 (3A tombstone), C16 (4A digest-only), C21 (6A agent media GC), C22 (7A retire knob), recorded C19 (5A task_results eternal for 7.0). CPL4-C6 (1A) — monetary usage-ledger compaction — LANDED by its own reviewed lane (rounds 2–5.4 plus the operator close-out of the 5.4 lens findings, owner batch №12 A) and INTEGRATED here by the lane merge (tip 8fb08d44): seq-preserving compaction under a two-tier kernel/name lock with per-caller ENOLCK refusal for money only, dir-fd anchored archive reader, quarantined-not-receipted swap-window loss; disclosed residuals in docs/archive/v7next/C6_REVIEW_PACKET.md §5/§10. The mechanical train landed; the corrective lane over audits #14/#15 landed the defects it left (append short-write, unreadable-chain typing, byte-exact atomic text, journal-compaction digest/lock/streaming guards, agent-media symlink containment, the durable reconcile failure fact, the bounded cross-skill history read, identity-based rotation detection, the missing ARCHITECTURE leaf rows). This row is `done` only when C6 is integrated and its verification hook is green — status must not run ahead of the work | retain | done | F5 | docs/PERSISTENCE.md + tests/test_persistence_inventory.py (AST writer scan, count-anchored both ways) + the persistence-train and corrective-lane ledger sections + tests/test_usage_compaction.py (C6 pass side: exact-money/projection equality, crash injection, in-flight survival, idempotency, trigger policy, swap-window and lock-tier pins) + tests/test_usage_compaction_archive.py (C6 reader side: the CPL-5 join across chained compactions and the baseline-block structural validation) + tests/fixtures_usage_compaction.py (the fixtures both hold) + tests/test_lockfile_helpers.py (lock tiers, ownership, ENOLCK per-caller policy) | -| CPL-5 | plan-item | Runtime invariant model-visible⟺logged, narrowed per F15: sealed model_send records at the last host-controlled pre-transport seam, typed exclusions (provider-native queries/transforms/secrets), reverse-⟺ for model_send only; canonicalization design note BEFORE code (batch-1 Q8=A confirmed). LANDED by the F5 lane D: `model_send_seal` block in the existing physical-candidate manifest (reuses canonical_json_v1 digests + persist_physical_candidate — no parallel serializer), at-call reconstruction of the durable record with a byte compare at `llm_attempt._candidate_before_dispatch` (mismatch = typed durable `model_send_invariant_violation` fact beside the seal + events.jsonl — an observability invariant, never a second dispatch gate), the CLOSED four-class exclusion enum with per-instance disclosure, per-rung seals on the retry ladder, delegated lanes disclosed `model_send_seal: unobserved`, and the bounded fail-soft reverse reconciliation sweep riding the startup-sweep family (orphan_seal / unlogged_attempt facts, never repairs) | retain | done | F5 | tests/test_model_send_seal.py (seal round-trip, damaged-record typed facts, closed-enum coverage, cost-shape pin, reverse sweep both ways) + docs/v7next/DESIGN_MODEL_VISIBLE_LOGGED.md | -| CPL-6 | plan-item | Conformance contracts for multi-provider seams: LLM providers and the executor axis native\|harness — one normative shared suite every new provider must pass. LANDED by the F5 lane C: the provider half parametrizes over the FACTUAL registry (`provider_models.PROVIDER_PREFIXES` + the local lane; a registered provider without a conformance driver = red) and pins route form, (message, usage) shape, honest-only cost planes, typed 400-refusal permanence (one physical send), 429-body typed marker, finish_reason:null surfacing, timeout propagation and the one-settled-ledger-row-per-send accounting on every lane over the golden recording fakes (reuse, no network); the executor half parametrizes over `subagents.SUBAGENT_EXECUTORS` (a new axis point without an outcome row = red) and pins the closed rule-table matrix across route states, typed refusals at both seams, launch/artifact semantics per point (native never contacts the daemon; harness leaves run identity + the durable last-delegation projection) | retain | done | F5 | tests/test_multiprovider_conformance.py (registry-derived shared suite) | -| CPL-7 | plan-item | Skill manifest "Model Experience" section (prose: what the model sees / token effect) + teaching errors on registration refusal | retain | done | F5 | tests/test_skill_model_experience.py (schema + model-visible surfaces + teaching fix_hint refusals; bundled manifests carry the section) | -| TRAIN-F6-8d13373b | plan-item | Post-cutoff upstream adoption train: the F6 rolling-upstream sync — 121 upstream commits b9f7597f..8d13373b merged whole as absorb merge 20850191 (literal second parent 8d13373b), which reached the mainline first-parent line as the second-parent side of lane-integration merge 0aa74e9f (over campaign commit 816e7b82) — both SHAs are named because the absorb merge is the one that took upstream and the integration merge is the one on the line — under «upstream = semantic truth, campaign = structural truth»; every upstream semantic delta re-seated in its campaign owner leaf, the upstream twin extractions (acceptance_dialogue.py, delivery_protocol.py, supervisor/chat_delivery_events.py) folded into their campaign owners (loop_acceptance/loop_acceptance_review, loop_delivery, events_chat_delivery). PROVENANCE, corrected 2026-09-01: the F0 cell read «(owner signal, 2026-09-01)», which claimed the «иди забирай» signal the Q1 adoption contract names. That signal was NOT given for this train. It was adopted on the operator's inference from the owner's «И надо как-то ускоряться … а то уроборос двигается вперёд быстрее чем ты работаешь» (10:10Z), and the owner sanctioned taking upstream POST HOC, after the merge, with «уроборос далеко уехал в ветке ouroboros … можешь ребейзнуться, забрать оттуда вещи новые» (19:12Z). The sanction is real and the train stands; the order is what the row now states honestly | retain | done | F6 | docs/archive/v7next/LEDGER_CORRECTIONS.md («From the F6 rolling-upstream sync» section) + absorb merge 20850191 + integration merge 0aa74e9f + the full non-serial + serial batteries on the merged tree | -| TRAIN-F6b-f3fbfdbb | plan-item | Post-cutoff upstream adoption train, sync #2 (owner signal, 2026-09-01): 101 upstream commits 8d13373b..f3fbfdbb (180 files, 20 new) merged whole as merge b9ceed6e under the same «upstream = semantic truth, campaign = structural truth» rule — every upstream semantic delta re-seated in its campaign owner leaf (registry post-exec organ, #447 H1 note ordering, В23=A owner-home read carve, #468 shape-first reasoning pin, delivery-control provenance, D4 export policy, A5 literal-argv disclosure), and the upstream twins of campaign organs folded into their owners (tools/read_inspection.py -> registry_guard_process, tools/result_envelope.py -> tools/tool_result, tools/output_export_policy.py -> shell_outputs; delivery_protocol.py stays folded in loop_delivery) | retain | done | F6 | docs/archive/v7next/LEDGER_CORRECTIONS.md («From the F6 rolling-upstream sync #2» section) + merge b9ceed6e + the full non-serial, serial, size_ratchet and E2E mock batteries on the merged tree | -| TRAIN-F6c-a76961de | plan-item | Post-cutoff upstream adoption train, sync #3 (owner signal 2026-09-02 ~12:50Z, requirements archive [A-BATCH-12-C6-SYNC], verbatim «+не забудь подтянуть все свежие изменения, там многое изменилось вреоятно уже в ветке ouroboros. Или это и так в плане в конце?» — the owner named the gap and the plan gained the step): 40 upstream commits f3fbfdbb..a76961de (upstream release 6.114.0, the live chat card, the Agents panel, the SYSTEM.md rewrite, governance schemas) merged whole as merge f4abe0a5 (parents 43dcc1d2, a76961de) under the same «upstream = semantic truth, campaign = structural truth» rule. Automerge took 18 of the 25 files both sides touched; the seven textual conflicts and the six semantic ones the automerge hid were resolved BY CLASS, and the classes are written out in the merge commit message (`git show --stat f4abe0a5`) rather than restated here: generated carriers taken from upstream and regenerated on the merged tree (size_ratchet_manifest — the upstream band rationale for tests/test_ui_smoke_project_continuity.py carried verbatim — domains, FROZEN_CONTRACTS_INVENTORY); protected prose taking upstream's compressed shape with the v7next deltas re-seated (prompts/SAFETY.md drops the retired OUROBOROS_SCOPE_REVIEW_FLOOR control per ABI-5 / owner Q10=A and regenerates the protected-path mirror from the merged runtime_mode_policy; the upstream-rewritten prompts/SYSTEM.md gains the v7next safety-critical set); upstream's Windows/CRLF/argv test fixes winning over the campaign's interim shims; and upstream's new governance and terminal-receipt tests adapted to the v7 contracts they actually meet on this tree (module-level registry_guard_process._run_shell_safety_check, the typed ToolResult code SKILL_STATE_WRITE_BLOCKED, the shell_outputs home of _sensitive_output_component_reason, the ABI-2 task-result stamp with its Q8=B quarantine), with the git catalog schema pin moved onto the bytes of upstream's advisory_review→preflight_review rename (332a02f1; the catalog itself unchanged) | retain | done | F6 | tests/test_packaging_sync.py (the SAFETY/SYSTEM mirrors) + tests/test_golden_capabilities.py + tests/test_capability_effect_predicates.py + tests/test_terminal_delegation_receipt.py + tests/test_git_extraction.py + docs/archive/v7next/LEDGER_CORRECTIONS.md («From the stage-2 fix wave, lane ledger-validator» section) + merge f4abe0a5 | -| DEFER-BROWSER | plan-item | Gateway/UI-truth E2E actor — the real-browser client over an isolated server's own web UI. DEFERRED OUT OF 7.0 BY THE OWNER: batch №9 №14 (2026-09-01, requirements archive [A-BATCH-9-ANSWERS]), owner verbatim «14. A» on the option recorded as «браузерная волна пост-релиз, смоук зелёным до тега» — the browser wave is post-release and the condition on the tag is a green smoke, not a green browser lane. tests/system_e2e/interfaces.py therefore keeps `PlaywrightUIClient` as an interface stub that REFUSES instantiation (NotImplementedError naming the gateway/UI-truth lane) instead of a fake that would read as coverage, and the refusal is itself pinned so the stub cannot rot into a silent no-op. residual: no system-level E2E scenario drives the real web UI in 7.0 — every UI claim of the campaign rests on the marker-gated Playwright smoke and on the owner's own manual pass, not on this actor | post-release | deferred | POST | tests/system_e2e/test_system_scenarios.py::test_interface_stubs_refuse_instantiation_until_their_lanes_land + tests/system_e2e/interfaces.py | -| W4-F1 | plan-item | Crash window between the reviewed `git commit` and its receipt: a crash between `git commit` and `record_evolution_commit` left a landed reviewed commit that NO boot path attributed — the markerless reconcile short-circuits on an empty `commit_sha` and `_preserve_evolution_orphan` runs on the authority-refusal path only. FIXED in 7.0 by owner batch №13 item 9 = B: the reviewed commit is now two-phase — the `pre_commit_authority` boundary records a `commit_intent` (the reviewed tree and parents of the post-review binding) BEFORE `git commit` runs, and boot reconciliation adopts the commit at HEAD only when its tree AND full parent list match that intent, writing the `commit_receipt` the crash never wrote; the task-done path adopts the same intent so a crashed cycle is classed commit-bearing instead of `no_op`. Attribution is structural, so a failed commit, a contained orphan or any later HEAD movement stays unattributed | re-prove | done | F6 | tests/test_evolution_restart_claims.py::test_boot_attributes_the_commit_a_crash_left_without_a_receipt, tests/test_evolution_restart_claims.py::test_boot_refuses_to_attribute_a_head_that_is_not_the_reviewed_material; docs/archive/v7next/LEDGER_CORRECTIONS.md («From the W4 crash-window lane (owner 9 = B)») | -| W4-F2 | plan-item | Absorb outcome ledger atomicity: the campaign absorb write and the `cycle_outcome` checkpoint append are not one transaction — the reconcile writes the campaign under `update_json_locked` and appends the row AFTER the lock (same shape on the claim path), so a crash in between left a campaign that says `absorbed` with no row and `build_solve_capability_digest` under-reported that cycle forever. FIXED in 7.0 by owner batch №13 item 9 = B: the row is DERIVABLE from the resolved transaction, so boot re-derives every missing commit-bearing outcome row (`source: boot_backfill`, idempotent — a task that already has a `cycle_outcome` row is skipped), and the swallow-wrapper both append sites share now lives with the ledger it writes | re-prove | done | F6 | tests/test_evolution_restart_claims.py::test_boot_backfills_the_cycle_outcome_row_a_crash_lost; ouroboros/evolution_checkpoints.py backfill_missing_cycle_outcomes; docs/archive/v7next/LEDGER_CORRECTIONS.md («From the W4 crash-window lane (owner 9 = B)») | -| DEFER-HEADLESS-CANCEL | plan-item | Panel stop receipts for headless (API/CLI-origin) tasks: a task not born in a chat gets no `cancel_receipt` block in the details panel after cancellation (W2-F1 of the F4 wave-2 system-E2E lane). DEFERRED OUT OF 7.0 BY THE OWNER: batch №7 item 5 (2026-09-01, requirements archive [A-BATCH-7-ANSWERS]), owner verbatim «5. A» on the option recorded as «панельные стоп-квитанции headless-задач — пост-релиз». residual: a `ouroboros run`/HTTP-API caller sees no stop receipt (who cancelled, what finished, what was spent) in 7.0; chat-origin tasks keep theirs | post-release | deferred | POST | docs/archive/v7next/LEDGER_CORRECTIONS.md (F4 wave-2 table, row W2-F1; «Owner closures for the F6-sync forks») | -| DEFER-FROZEN-2 | plan-item | Two modules of the frozen-contracts package (`ouroboros/contracts/task_constraint.py`, `ouroboros/contracts/skill_payload_policy.py`) are not listed in the §11.1 frozen table and carry no anchor test. DEFERRED OUT OF 7.0 BY THE OWNER: batch №9 re-ask (2026-09-01), owner verbatim «согласен со всеми рекомендациями твоими» on recommendation №12=A «not now; disclose the gap until post-release». residual: both contracts can change without breaking the frozen table or a mandatory anchor test in 7.0; the package set is pinned by tests/test_generated_inventories.py (a third module is red CI) | post-release | deferred | POST | docs/v7next/FROZEN_CONTRACTS_INVENTORY.md («Package coverage») + docs/archive/v7next/LEDGER_CORRECTIONS.md (CPL-2 item 6) | -| DEFER-C6-RESIDUALS | plan-item | Disclosed residuals of the CPL4-C6 monetary-ledger compaction (docs/archive/v7next/C6_REVIEW_PACKET.md §5, §10): on a filesystem the kernel says cannot lock (name tier) the pass never compacts and the ledger grows to the 20 MB tripwire; on a lockd-less NFS `state/` every monetary write refuses typed (ENOLCK, money only — other locks keep the name protocol); on Windows a charge landed inside the swap's one rename syscall is lost silently (POSIX quarantines it) and the directory chain is fsync'd only as a path-based best effort; the Windows LOCK TIER is no longer a residual — owner batch №13 item 1 = B (2026-09-02) made it a release condition and it is re-enabled IN 7.0 (`platform_layer._WIN32_LOCK_OFFSET`: one byte beyond any owner stamp, the capability probe running there like POSIX, the compaction pass landing there), with its Windows-EXECUTED proof pending the next CI matrix and its weaker-than-POSIX eviction guarantee disclosed in DESIGN §8; subscription/external/legacy/review rows never fold; orphan archive segments are never GC'd; the two usage suites sat at the 1600-line cap until owner batch №13 item 11 = A split them. DEFERRED OUT OF 7.0 BY THE OWNER: batch №12 (2026-09-02 ~12:50Z, [A-BATCH-12-C6-SYNC]), owner verbatim «A» on «микро-раунд 5.4 … LOW-остаток интегрируется с раскрытием, HIGH-остаток → назад к владельцу» (no HIGH remained). residual: as listed, minus the test-suite cap — C6-TESTCAP is CLOSED by owner batch №13 item 11 (2026-09-02, [A-BATCH-13-ANSWERS]), owner verbatim «11. A» on «тест компакции разрезать по естественной границе; platform_layer pay-down ≤1500 post-release + issue»: the split landed as tests/test_usage_compaction.py (900 lines) + tests/test_usage_compaction_archive.py (660 lines); what stays open post-release is the `ouroboros/platform_layer.py` pay-down (1587 → ≤1500 lines, a BAND_BASELINE_PATHS entry of ouroboros/size_ratchet_manifest.py) and its issue | post-release | deferred | POST | docs/archive/v7next/C6_REVIEW_PACKET.md §5 + §10; docs/v7next/DESIGN_USAGE_COMPACTION.md §8/§10/§12 | -| DEFER-C19-RETENTION | plan-item | `task_results/.json` are kept forever — no retention is introduced in 7.0 (CPL-4 item C19). DEFERRED BY THE OWNER: batch №8 item 5 (2026-09-01, [A-BATCH-8-ANSWERS]), owner verbatim «5. A» on «task_results eternal for 7.0». residual: `data/task_results/` grows monotonically for the life of an install; only a full data reset clears it | post-release | deferred | POST | docs/PERSISTENCE.md (task_results row) + ADOPTION row CPL-4 (C19 record) | -| W4-F3 | plan-item | Evolution restart marker vs manual restarts: `request_evolution_restart` returned BEFORE writing `pending_restart_verify.json` when `OUROBOROS_EVOLUTION_AUTO_RESTART` was off, so the exact-claim verify path (`require_claim=True`) was structurally unreachable for installs that restart by hand and absorb attribution rested on the weaker markerless reconcile. Found by the F4 wave-4 system-E2E lane (docs/archive/v7next/LEDGER_CORRECTIONS.md «E2E-находки w4»). FIXED in 7.0 BY OWNER DECISION — the v7 follow-up F3 owner batch (2026-09-04), item 5 «W4-F3 fix now (always write the marker)», owner verbatim «5. A»: the knob now skips ONLY the restart itself; the exact claim marker is written whenever the reviewed commit's authority still holds, so the owner's manual restart verifies the cycle by exact claim; the two marker writers (the supervisor path and the agent's `restart` tool) share one helper and one schema (`write_pending_restart_marker`). S22 keeps its contract — markerless boot reconcile absorbs exactly once after a crash between the campaign's `waiting_for_restart` write and the marker write — by shaping that durable state (open transaction, commit on HEAD, no marker) after the kill: the two writes are separate atomic files, so removing the marker is byte-identical to a crash between them; the scenario now also pins that the marker IS written with auto-restart off and that the tree does not restart | re-prove | done | F6 | tests/test_evolution_terminal_events.py::test_auto_restart_off_still_writes_the_exact_restart_marker, tests/test_evolution_terminal_events.py::test_both_restart_marker_writers_share_one_schema, tests/system_e2e/test_system_scenarios_w4.py::test_s22_absorb_kill_recovery_absorbs_once_and_never_twice; docs/archive/v7next/LEDGER_CORRECTIONS.md («From the W4-F3 lane (owner 5 = A)») | -| W4-F4 | plan-item | Rescue-local ref accumulation: `create_rescue_local_ref` pins every update stash to a durable `rescue-local-` branch and nothing ever deletes them — a refused/unwound attempt leaves its ref exactly like a successful one. Deliberate durability («git-gc can never lose the owner's work»); the unbounded per-distinct-stash growth is the disclosed cost. Same wave-4 lane. OWNER-deferred on 2026-09-04 — owner verbatim «5. A» (the operator's question: fix the restart marker now and keep this row deferred with a quote plus a backlog item for deleting the rescue-local refs of cancelled updates); backlog: delete the `rescue-local-` refs of a refused/unwound update once its attempt has no owner work left to lose. residual: the branch list grows with every distinct update stash in 7.0 | post-release | deferred | POST | docs/archive/v7next/LEDGER_CORRECTIONS.md (w4 findings table, row W4-F4) | -| DEFER-E2E-DELEG-MUT | plan-item | System-E2E scenarios for MUTATING delegated runs (snapshot + patch pull-in + isolation proof) were carried across the F4 waves and never landed: S1-S23 contained only non-mutating delegation runs. LANDED as the wave-5 pair (owner batch №13 item 15=B, 2026-09-02: fix now): S24 drives an EXTERNAL-WORKSPACE task through delegate_start(access=workspace_write) → private Git snapshot → the fake harness editing THAT snapshot → delegate_wait capture → integrate_delegated_patch(apply), pinning the durable custody chain, the capture artifacts, the containment facts read back from the run's own attempt record, the isolation proof (the live workspace still lacks the run's file at the step after the wait returned) and the released snapshot; S25 drives the conflicting variant — the live tree drifts on a patched path, the apply is refused typed (baseline_drift), nothing is disposed, the task's own terminal reads failed / delegated_custody_unreconciled, and snapshot + patch survive as resolution material. The fake daemon gained the mutating half it lacked (workspace edits + attempts/a01/attempt.yaml applied facts) | retain | done | F4 | tests/system_e2e/test_system_scenarios_w5.py::test_s24_mutating_delegated_run_is_isolated_until_an_explicit_clean_apply + tests/system_e2e/test_system_scenarios_w5.py::test_s25_mutating_delegated_patch_conflict_is_refused_and_keeps_its_material + tests/system_e2e/test_system_scenarios_w5.py::test_fake_daemon_mutating_run_edits_only_the_execution_workspace | -| DEFER-E2E-PAID-LANE | plan-item | The paid («live») system-E2E lane — E1 (delegated launch → wait → answer → cancel), E2/E3 (clean and conflicting delegated patch pull-in) and E13 (a task with an exhausted budget PAUSES before dispatch, replacing the withdrawn E8) — is written (tests/test_e2e_cancellation_scenarios.py, fixtures_e2e_cancellation.py LANE_PAID) and needs a model with a known price, which the mock lane has not; plan §8/§10 made an owner-live quittance on the exact SHA an acceptance criterion. EXECUTED BY OWNER DECISION: batch №13 item 2 (2026-09-02, [A-BATCH-13-ANSWERS]), owner verbatim «2. A». Runs 2026-09-02/03 on ad39ec54, 6bd799fb, ca1b38df, 9c04ff47 (operator receipts in LEDGER «From the paid E-lane»): E13 GREEN on `openrouter::anthropic/claude-haiku-4.5` (a priced route; `anthropic::` has no tariff, so the drain never fires there — by design, cost unknown is never enforced); E1 GREEN once its fault assertion read OPEN faults (the faults log carries resolution rows); E2/E3 reach the real Claudexor lane and are refused by it: `claude is unavailable: Claude subscription route is not ready` — `delegate_start` asks for the subscription substrate by design and the owned daemon of an isolated install has no login; an interactive login is the owner's act, which the operator may not perform — the E2/E3 remainder is a STRUCTURAL block, not an owner-decided deferral, and no quote waiving the §8/§10 criterion exists. residual: E2/E3 (clean and conflicting delegated patch pull-in) unexecuted until an install with a logged-in Claude account runs the lane | post-release | deferred | POST | tests/test_e2e_cancellation_scenarios.py (module docstring) + tests/fixtures_e2e_cancellation.py (LANE_PAID) | -| DEFER-TYPED-PROC-5 | plan-item | Owner batch №7 item 1=A retired the regex fallback that guessed process exit codes from prose; five surfaces were then left WITHOUT typed exit/signal facts: extension child-process death, `skill_exec`/`skill_preflight`, `verify_and_record` (printed `exit=`, stamped nothing), `run_command` timeouts and pre-exec failures, Windows kills. LANDED by owner batch №13 item 10 = B (typed process-facts lane): the thread-local channel is now PUBLISHER-scoped instead of tool-name-scoped (the `_PROCESS_META_TOOLS` table is retired; the loop clears the slot before EVERY dispatch, which is a stronger no-contamination contract than the name gate it replaces), and the family grows three members that exist exactly where an exit code does not — `timed_out`, `killed_by_host`, `pre_exec_failure` (the platform's exception class). Producers stamp at the point the truth is known: the extension child in `_run_child` (clean exit, abnormal exit with its POSIX signal, deadline kill, output-cap kill), `_run_skill_subprocess` (including the negative code its `returncode or 0` return flattens, and the spawn OSError), the `skill_preflight` validators — whose synthesized `-9`/`-1` are RETIRED for `returncode=None` plus the typed reason, since the fakes read downstream as real POSIX signal deaths (on Windows too, where a host kill produces none) — and the `verify_and_record` check, whose receipt now copies the SAME publication instead of deriving duration/signal a second time. Consumers: one projection feeds the UI live-log card, the tools.jsonl row and the durable trace. Windows kills are carried honestly rather than faked: `killed_by_host` beside whatever `TerminateProcess` left in `exit_code`, and never a fabricated signal name — Windows-executed proof pending the matrix | retain | done | F6 | tests/test_process_signal_observability.py::test_extension_child_death_publishes_typed_exit_and_signal + tests/test_process_signal_observability.py::test_skill_exec_signal_death_survives_the_or_zero_flattening + tests/test_process_signal_observability.py::test_skill_preflight_timeout_reports_no_returncode_instead_of_fake_minus_nine + tests/test_process_signal_observability.py::test_verify_check_publishes_typed_exit_and_signal + tests/test_process_signal_observability.py::test_run_shell_timeout_publishes_typed_timeout_facts + tests/test_process_signal_observability.py::test_windows_host_kill_carries_no_forged_signal + tests/test_process_signal_observability.py::test_tools_jsonl_row_carries_the_typed_process_facts | -| DEFER-SPEC64-PATHS | plan-item | Spec §6.4 «Paths/roots» (OUROBOROS_V7_SPEC_v72.md ~:815-820) — one `HostPaths`/`TaskPaths` authority, removal of the two `SimpleNamespace` Env clones in agent_task_pipeline.py (:241, :681) and of the silent `Path.home()/Ouroboros/data` fallbacks in seven domain modules — was never delivered on the oracle or here and never entered any inventory or decision; the bytes are inherited from upstream (no regression). DEFERRED OUT OF 7.0 BY THE OWNER: batch №13 item 8 (2026-09-02, [A-BATCH-13-ANSWERS]), owner verbatim «8. A» on «post-release строка — подтвердить». residual: with an incomplete OUROBOROS_* env set a process can still resolve two different data roots without an error | post-release | deferred | POST | OUROBOROS_V7_SPEC_v72.md §6.4 (the requirement); this row (the only tracking) | -| DEFER-F23-ACCEPTANCE | plan-item | Roast recommendation F23 — a machine-readable `acceptance.json` with schema and checker (exact SHA, run/artifact ids, scenario consumption, D02-D38 dispositions with decision ids, review quorum, cost cap) — was accepted into the plan (V7NEXT_PLAN.md :261) and never built; the release bar (`scripts/v7next_adoption.py --release`), `scripts/rc_audit.py` and the exact-SHA evidence manifests cover part of it (rows/hooks/gates), not quorum, cost or scenario consumption. Recorded so the gap is visible (owner question in the STOP batch: build before the tag / post-release / close as covered-by-gates). residual: campaign acceptance is proven by the evidence manifests and the validator, not by one generated capsule CLOSED AS COVERED-BY-GATES BY THE OWNER: batch №13 item 7 (2026-09-02, requirements archive [A-BATCH-13-ANSWERS]), owner verbatim «7. A» on «закрыть F23 как «покрыт гейтами» с записью пробелов». What covers it: the exact-SHA evidence manifests (every gate its own rc, HEAD after each gate, live-data inventory), `scripts/v7next_adoption.py --release` (rows/hooks/authority) with its pytest wrapper, `scripts/rc_audit.py`. residual: no single generated capsule; review quorum, spend and scenario consumption are proven by the ledger sections and the operator's manifests, not by one machine-readable file | retain | done | F6 | scripts/v7next_adoption.py + tests/test_v7next_adoption.py (the release bar) | - -Notes: - -- §7-8 (atomic extension-registration publication) is deliberately absent from - the CPL family: roast F8 moved it into Ф3 — it is row ABI-9. -- No row carries `pending-decision` any more. The four that did — D04, D05, - D07, D08 — each got their plan §5.4 three-column resolution from an owner - batch: D07 and D08 by batch №5 (5.5-5.8=A, 5.10=A, 2026-08-31), D04 by batch - №9 №1=B and D05 by batch №9 №2=B (2026-09-01). All four have since landed — D04 at - 5b1767fa and D05 at 0bf723cc — and all four rows read `done`; the sentence - that called the D04/D05 lanes owed and their rows `pending` outlived the - lanes. The enum value stays in the schema for the next genuine fork. -- W4-F3 and W4-F4 ARE rows (since d348ea46, 2026-09-02): the two wave-4 - observations the F4 lane disclosed instead of fixing — named asymmetries of - existing decisions (the restart-marker knob predates the claim machinery; - rescue-local refs are deliberately durable). Batch №13 item 13(и) asked to - ratify them and the owner answered that he had not read that item; the F3 - owner batch (2026-09-04) re-asked, and its item 5 = A pulled W4-F3 into 7.0 — - that row reads `done` (marker always written, one writer helper, S22 kept) and - left `DEFERRED_OUT_OF_V70`. The same answer kept W4-F4 deferred: its row - carries the owner's quote and a backlog item (delete the rescue-local refs of - a refused/unwound update), and the register records it as an owner deferral — - the declaration below mirrors that register. The sentence that called them - rowless «disclosed observations» outlived d348ea46 by two days past a green - bar; the prose-id lint above is its class fix. W4-F1 and W4-F2 are rows - because they are crash windows nobody decided to keep — and the owner pulled - both into 7.0 (batch №13 item 9 = B), so those two rows read `done` with - red-first pins. -- Deferral authorities, as `DEFERRED_OUT_OF_V70` in scripts/v7next_adoption.py - records them (a declaration that disagrees with the register turns the bar - red — free prose about authority is not read): ABI-8 owner, DEFER-BROWSER - owner, DEFER-HEADLESS-CANCEL owner, DEFER-FROZEN-2 owner, DEFER-C6-RESIDUALS - owner, DEFER-C19-RETENTION owner, W4-F4 owner, DEFER-E2E-PAID-LANE owner, - DEFER-SPEC64-PATHS owner. -- Hook cells are honest about existence. For a `done` row the validator - resolves every `tests/`, `scripts/` or `docs/` token it finds and refuses a - missing file, so a shipped row cannot point at a suite nobody wrote. For a - row that is not done, an owed suite is described in `what` rather than - spelled as a path in the hook cell — naming a non-existent file there would - read as a hook and prove nothing. -- D04, D05, D06 and D35 landed through their owner-decided lanes (1B/2B/3A/13B) - and read `done`; their phase cells still say F1 because re-phasing a required - row is an owner decision (the validator's `REQUIRED_PHASE` enforces exactly - that) and the lanes did not invent one. CPL-4, the last row that still owed - work, landed with the C6 usage-ledger compaction lane (merge 9faccf31), so no - row is open on this base. D03 landed its remainder at F6 (the - settings read seam, owner batch №11 2=A); its phase moved F1→F6 with the - matching `REQUIRED_PHASE` edit in the truth-wave commit, disclosed in the row - and in the ledger, and that move is the operator's scheduling correction — - not an owner decision — so the owner may still overturn it. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cf34cd802..32f85b705 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -64,7 +64,9 @@ direction-changing proposal. Small, well-understood fixes do not need ceremonial design work. Never commit local settings, credentials, runtime state, logs, caches, -benchmark runs, generated review runs, or build artifacts. +benchmark runs, generated review runs, or build artifacts. For tracked material, +including campaign plans and optional reports, follow the +[Documentation contract](docs/development/02-naming-and-boundaries.md#documentation-contract). ## 3. Branch from `ouroboros` and Do Not Bump the Version diff --git a/devtools/README.md b/devtools/README.md index 69d9e1be3..e3594c564 100644 --- a/devtools/README.md +++ b/devtools/README.md @@ -7,7 +7,8 @@ Rules: - Generated logs, datasets, run outputs, Docker layers, and secrets do not live here. -- Default benchmark outputs go under `/Users/anton/Ouroboros/bench_runs/`. +- Choose a benchmark output root outside the source checkout and runtime data; + use each runner's documented output option or `OUROBOROS_BENCH_RUNS_ROOT`. - Runtime modules must not import `devtools`. - This is not an immune-system bypass: touched files are reviewed normally. - Promote code out of `devtools` only through a separate reviewed runtime plan. diff --git a/docs/CHECKLISTS.md b/docs/CHECKLISTS.md index 4410dc39c..cdd3fe78d 100644 --- a/docs/CHECKLISTS.md +++ b/docs/CHECKLISTS.md @@ -178,7 +178,7 @@ Used by `commit_reviewed` for all changes to the Ouroboros repository. | # | item | what to check | severity when FAIL | |---|------|---------------|--------------------| | 1 | bible_compliance | Does the diff violate any BIBLE.md principle? | critical | -| 2 | development_compliance | Does it follow DEVELOPMENT.md patterns? Check explicitly: (a) naming conventions (snake_case modules/vars, PascalCase classes, UPPER_SNAKE_CASE constants); (b) entity type rules — Gateway classes contain ONLY transport, no business logic; Tool functions are thin wrappers; (c) Python everywhere (including `tests/`/`devtools/`) and first-party `web/**/*.js` (including `web/tests/`) target ~1000 lines; exact repo-relative module debt above the 1600-line hard gate, exact `(path, qualname)` Python-function debt above 300 lines, the exact-current 1001-1500 band (new/re-entered paths need a nonblank rationale), and exact byte debt above 200,000 canonical UTF-8/LF bytes are checked in to `ouroboros/size_ratchet_manifest.py`; the enforcing surface for all of these (and for `MAX_TOTAL_FUNCTIONS`) is the official repository CI's `size_ratchet` pytest lane — manifest exactness on the tip tree plus the pairwise base-vs-tip shrink-only transition — while local runs surface the same `validate_size_ratchet` findings as warnings (a stale or growing entry is therefore review debt to flag, not a local commit block); methods above 150 lines are a decomposition signal, runtime-code total Python function/method count stays under `ouroboros/review.py::MAX_TOTAL_FUNCTIONS`, and more than eight parameters is a decomposition signal, not a hard gate; (d) no gratuitous abstract layers, and any SOLID/minimalism finding names an exact symbol/authority, concrete duplication or coupling, and a smaller contract-preserving alternative rather than citing diff size (P7 Minimalism) — and when the diff ADDS a surface (a new module, state file, ledger, resolver, cache, retry path, tool, endpoint, or background loop), the reviewer consults the docs/ARCHITECTURE.md map and NAMES the existing mechanism that already covers the need when one exists (name it exactly — the reuse-first duty this checklist carries for a CHANGE; the plan-review checklist judges an intention and has no such generative duty); absence of a covering mechanism may be stated in one line; (e) new LLM calls go through the shared `LLMClient`/`llm.py` layer, not ad-hoc HTTP clients; (f) cognitive artifacts (identity.md, scratchpad, task reflections, review outputs, pattern register) must NOT use hardcoded `[:N]` truncation — when content must be shortened, summarize explicitly (attempts, changes, and conclusions survive) and disclose the omission with a resolvable reference, because an omission marker alone is disclosure, not sufficiency; (g) new `get_tools()` exports follow the ToolEntry pattern in registry.py; (h) provider independence — no change may make a core capability (agent loop, multi-model commit review, scope review, or memory/context flows) silently require a second provider or OpenRouter specifically, and every supported single direct provider (local, OpenAI, Anthropic, MiniMax, DeepSeek, Cloud.ru, GigaChat) must keep its model AND review/scope slots self-fillable (see DEVELOPMENT.md "Provider Independence"); (i) a claimed-complete visible UI change includes vision-inspected evidence from at least one relevant real consumer flow. A screenshot file without inspection is insufficient; states/viewports/additional engines are risk-selected, mobile/WebKit are not universal, and an unavailable optional engine alone is not degradation. | critical | +| 2 | development_compliance | Does it follow DEVELOPMENT.md patterns? Check explicitly: (a) naming conventions (snake_case modules/vars, PascalCase classes, UPPER_SNAKE_CASE constants); (b) entity type rules — Gateway classes contain ONLY transport, no business logic; Tool functions are thin wrappers; (c) Python everywhere (including `tests/`/`devtools/`) and first-party `web/**/*.js` (including `web/tests/`) target ~1000 lines; exact repo-relative module debt above the 1600-line hard gate, exact `(path, qualname)` Python-function debt above 300 lines, the exact-current 1001-1500 band (new/re-entered paths need a nonblank rationale), and exact byte debt above 200,000 canonical UTF-8/LF bytes are checked in to `ouroboros/size_ratchet_manifest.py`; the enforcing surface for all of these (and for `MAX_TOTAL_FUNCTIONS`) is the official repository CI's `size_ratchet` pytest lane — manifest exactness on the tip tree plus the pairwise base-vs-tip shrink-only transition — while local runs surface the same `validate_size_ratchet` findings as warnings (a stale or growing entry is therefore review debt to flag, not a local commit block); methods above 150 lines are a decomposition signal, runtime-code total Python function/method count stays under `ouroboros/review.py::MAX_TOTAL_FUNCTIONS`, and more than eight parameters is a decomposition signal, not a hard gate; (d) no gratuitous abstract layers, and any SOLID/minimalism finding names an exact symbol/authority, concrete duplication or coupling, and a smaller contract-preserving alternative rather than citing diff size (P7 Minimalism) — and when the diff ADDS a surface (a new module, state file, ledger, resolver, cache, retry path, tool, endpoint, or background loop), the reviewer consults the docs/ARCHITECTURE.md map and NAMES the existing mechanism that already covers the need when one exists (name it exactly — the reuse-first duty this checklist carries for a CHANGE; the plan-review checklist judges an intention and has no such generative duty); absence of a covering mechanism may be stated in one line; added tracked material needs a continuing purpose under DEVELOPMENT.md "Documentation contract", and completed campaign machinery is retired rather than preserved by presence-only tests; (e) new LLM calls go through the shared `LLMClient`/`llm.py` layer, not ad-hoc HTTP clients; (f) cognitive artifacts (identity.md, scratchpad, task reflections, review outputs, pattern register) must NOT use hardcoded `[:N]` truncation — when content must be shortened, summarize explicitly (attempts, changes, and conclusions survive) and disclose the omission with a resolvable reference, because an omission marker alone is disclosure, not sufficiency; (g) new `get_tools()` exports follow the ToolEntry pattern in registry.py; (h) provider independence — no change may make a core capability (agent loop, multi-model commit review, scope review, or memory/context flows) silently require a second provider or OpenRouter specifically, and every supported single direct provider (local, OpenAI, Anthropic, MiniMax, DeepSeek, Cloud.ru, GigaChat) must keep its model AND review/scope slots self-fillable (see DEVELOPMENT.md "Provider Independence"); (i) a claimed-complete visible UI change includes vision-inspected evidence from at least one relevant real consumer flow. A screenshot file without inspection is insufficient; states/viewports/additional engines are risk-selected, mobile/WebKit are not universal, and an unavailable optional engine alone is not degradation. | critical | | 3 | secrets_check | Are secrets, API keys, .env files, credentials present in the diff? | critical | | 4 | code_quality | Careful code review: bugs, logic errors, crashes, regressions, race conditions, resource leaks? | critical | | 5 | security_issues | Security vulnerabilities: injection, path traversal, secret leakage, unsafe operations? | critical | @@ -940,7 +940,7 @@ clean response. | 3 | cross_surface_consistency | If behavior changed, are adjacent surfaces still consistent: prompts, docs, comments, tool descriptions, automation, or user-visible workflow? Apply the shared `Critical surface whitelist` — only release metadata, tool schema, module map, behavioural documentation, or safety contracts count as critical; commentary and prose mismatches are advisory. | critical if the mismatch is in a whitelisted surface AND concrete; otherwise advisory | | 4 | regression_surface | Does wider repository context show a concrete sibling path, migration edge, or parallel flow that remains broken or incomplete after this change? | critical if it leaves a concrete broken/incomplete path; otherwise advisory | | 5 | prompt_doc_sync | If prompts or docs are relevant to the changed behavior, are they still accurate and mutually consistent? Apply the shared `Critical surface whitelist` — behavioural documentation describing what a tool/command DOES at runtime is critical; wording/style of comments is advisory. | critical if a whitelisted prompt/doc artifact becomes false; otherwise advisory | -| 6 | architecture_fit | Does the change solve the class of problem, or is it a narrow patch that leaves the underlying pattern unresolved? | advisory | +| 6 | architecture_fit | Does the change solve the class of problem, or is it a narrow patch that leaves the underlying pattern unresolved? Check tracked material and completed campaign machinery against DEVELOPMENT.md "Documentation contract" for a continuing purpose. | advisory | | 7 | cross_module_bugs | Does this change break something in a different module through implicit coupling, shared state, or assumed call/return patterns? Name the exact module, symbol, or call site. Follow DEVELOPMENT.md "Shared behavior and data-flow changes" for affected consumer paths. | critical if a concrete cross-module breakage can be cited; otherwise advisory | | 8 | implicit_contracts | Are there constants, data format assumptions, expected function signatures, or protocol invariants relied upon by OTHER modules that this change violates without updating those callers? Name the exact symbol or file. Apply DEVELOPMENT.md "Shared behavior and data-flow changes" to authority, scope, freshness, and preservation evidence. | critical if a concrete violated contract can be cited; otherwise advisory | diff --git a/docs/DELEGATED_ADMISSION.md b/docs/DELEGATED_ADMISSION.md index 64dd4d68a..4e1b28d0c 100644 --- a/docs/DELEGATED_ADMISSION.md +++ b/docs/DELEGATED_ADMISSION.md @@ -1,10 +1,11 @@ # Delegated-run admission — threat model -Status: **schema floor enforced at admission; the boundary is read back per attempt and +**Schema floor is enforced at admission; the boundary is read back per attempt and DISCLOSED, never required.** Owner: `ouroboros/config.py` (the two floors), -`ouroboros/subagents.route_health` (the admission decision), +`ouroboros/subagent_route_health.route_health` (the admission decision), `ouroboros/gateways/claudexor.attempt_containment` (the applied-fact reader) and -`ouroboros/tools/delegate.py` (the three-place disclosure). This document is the reason +`ouroboros/tools/delegate_terminal_evidence.py` and +`ouroboros/delegate_start_instructions.py` (the three-place disclosure). This document is the reason those numbers and that predicate are what they are; change it in the same commit as the code. Claudexor owns the mirror document, `docs/DELEGATED_CONFINEMENT.md` in its own tree, which @@ -13,32 +14,41 @@ the outside, and what it says when the answer is "nothing was applied". ## 1. The asset -`~/.claudexor/v3/daemon/token` is a bearer for the ENTIRE `/v2` control API. A process that -reads it can start runs at any access level on any registered project. Every authority -derivation Ouroboros performs — the access profile, the run shape, the write-surface -predicate — is decoration downstream of a child that holds it. +The token named by the selected daemon's `control-api.json` is a bearer for +the entire `/v2` control API. A process that reads it can request runs at other +access levels on registered projects. Child-scope instructions do not restrict +what a holder of the daemon token can ask the control API to do. -The daemon runs as the operator, so a daemon Ouroboros did not start keeps its token in the -operator's own home, at an absolute path a scoped `HOME` does not redirect. Ouroboros's OWN -daemon (D30) is the exception: it is spawned under `CLAUDEXOR_CONFIG_DIR`, and that override IS -the complete relocatable root — its token lives under `data/claudexor/`, not in the operator's -home. Either way the token is an absolute path the child does not have to guess. +Ouroboros owns a daemon under `DATA_DIR/claudexor/`: +`CLAUDEXOR_CONFIG_DIR` relocates its complete config, credential, run and daemon +state. A separately configured daemon may use its own runtime home instead. +In either case, changing a child's `HOME` redirects home-relative lookups; it +does not make the actual token path inaccessible. ## 2. The actor -A delegated MUTATING child: `mode: agent`, `access: workspace_write`, -`execution.isolation: live`. Claudexor hands the harness a real shell in the nanny's own -worktree. The command stream is model output and the host cannot review each command before it -runs. Not assumed hostile — assumed uncontrolled. +A delegated mutating child uses the shape from +`subagents.delegated_run_shape`: `mode: agent`, `access: workspace_write`, +`execution.isolation: live`, with the delegated marker. For Git and skill +payload work, `tools/delegate.py` provisions a private execution snapshot +before dispatch. `live` means that Claudexor works in that supplied execution +root; it does not mean direct edits to the authoritative target. The host +captures the result and applies it through its existing integration path. +Ordinary folders use the separate supported direct/copy work-product shape in +`delegate_directory.py`. -A delegated READ-ONLY child (`mode: ask`, `access: readonly`) is not this actor. It gets no -shell that can mutate, and it stays inside Claudexor's ordinary envelope. +The harness can execute model-generated commands under the operator's OS +identity. It is not assumed hostile, but the host cannot review each command +before it runs. -## 2a. Stable project identity and the persistent registration (#362) +A read-only child requests `mode: ask`, `access: readonly` under Claudexor's +ordinary envelope. The host reads effective access back for both shapes; +the delegated HOME/boundary checks below apply only to marker-carrying runs. -Fresh mutating delegated starts on an engine satisfying the workspace-root release -contract (`CLAUDEXOR_DELEGATED_WORKSPACE_ROOT_MIN_VERSION = "3.8.1"`, the next -compatible release carrying Claudexor PR216 after the pinned 3.8.0) register and retain +## 2a. Stable project identity and persistent registration + +For Git and skill payload work, fresh mutating starts on an engine satisfying the workspace-root release +contract (`CLAUDEXOR_DELEGATED_WORKSPACE_ROOT_MIN_VERSION = "3.8.1"`) register and retain the user's actual target project in `scope.root`, while the child's writable filesystem rides separately as the private snapshot in `execution.workspaceRoot`. That registration is the USER'S identity, not a disposable snapshot: it is marked `project_persistent` @@ -55,75 +65,56 @@ shape and retire their one-shot registration as before. ## 3. What Ouroboros actually controls -Only ADMISSION and REPORTING. Ouroboros is an HTTP client of a daemon it does not build, ship, -or version. It cannot confine the child; it can decline to start a run, and it can state -afterwards what the run actually got. +Ouroboros selects and delivers an immutable Claudexor runtime through +`claudexor_runtime_pin.json` and `claudexor_runtime.py`. Executable bytes live +under `DATA_DIR/state/cx`; credentials and daemon state remain separately under +`DATA_DIR/claudexor`. The reviewed pin selects the next spawn, while the serving +process may still run an earlier pin until its lifecycle ends. Admission uses +the engine version returned by the connected daemon's handshake. -The marginal escalation is worth naming before any defence is priced against it (AGENTS.md -"Name the marginal escalation, not the scary noun"): this child already holds a shell in the -nanny's worktree, running the operator's own code as the operator. The step from "shell" to -"shell plus the daemon token" is real but small, and it does not buy a lane-wide refusal. +Claudexor implements the harness boundary. Ouroboros controls admission, +execution-root preparation, custody and reporting through the control API; it +cannot infer an applied boundary merely from having delivered a particular +engine build. -So the question is NOT "against which engines is this an acceptable act?" but **"what did this -run actually get, and does everyone downstream know?"** +The marginal escalation matters: this child already holds a shell in its +assigned worktree, running the operator's code as the operator. Access to the +daemon token adds control-plane authority, but withholding the whole lane +because a host has no boundary mechanism would also remove useful delegated +execution. The contract therefore checks required request support and reports +what each attempt actually received. -## 4. The version bands (measured 2026-08-03, not assumed) +## 4. Compatibility floors and applied evidence -Probed live against the operator's running daemon, and read out of the Claudexor tree at -`/Users/anton/Clawdexor` for the bands no local daemon runs. +The constants in `ouroboros/config.py` answer request-compatibility questions: -| Engine | `execution.delegated` | What the child actually gets | Verdict | -| --- | --- | --- | --- | -| ≤ 3.2.x | **400** `invalid_request`, `fieldErrors: {"/execution/delegated": ["Unexpected field; not part of this request."]}` | run never starts | below the marker floor — refused, because it cannot run | -| 3.3.0 – 3.3.1 | accepted | a scoped `HOME` — a CONVENTION. `~`-relative lookups redirect; `/Users//.claudexor/v3/daemon/token` is read with an absolute path and is READABLE. No confinement fields exist on the attempt record at all | admitted, and reported as UNCONFINED | -| ≥ 3.3.2, macOS | accepted | Seatbelt profile denying the Claudexor runtime tree and the operator credential stores, PROVEN against a denied path before the harness spawns; recorded as `confinement_mechanism` + `confinement_verified_denied_path` | admitted, and reported as CONFINED | -| 3.3.2, elsewhere | accepted | nothing, and the run does not proceed: `applyConfinement` threw `ConfinementUnavailableError` off darwin and the evidence gate refused to terminalize | REFUSED by the engine (`delegated_confinement_unavailable`) | -| ≥ 3.3.3, elsewhere | accepted | nothing enforced. `confinementMechanism()` returns null off darwin and the engine works anyway, disclosing the absence — `docs/DELEGATED_CONFINEMENT.md` §7: "There is no second policy. On every other platform `confinement_mechanism` is null, `confinement_verified_denied_path` is null, and `confinement_unavailable_reason` says why." | admitted, and reported as UNCONFINED | +| Engine version | Request support used by Ouroboros | Consequence | +| --- | --- | --- | +| Below `CLAUDEXOR_MIN_VERSION` (3.2.0) | Below the supported control transport | Handshake refuses the route | +| From 3.2.0, below `CLAUDEXOR_DELEGATED_MARKER_MIN_VERSION` (3.3.0) | Read-only shape is supported; `execution.delegated` is not | Read-only delegation remains available; a mutating shape gets `engine_rejects_delegated_marker` | +| From 3.3.0 | Delegated marker is schema-compatible | Admission can proceed subject to route readiness; confinement is read from attempt evidence | +| From `CLAUDEXOR_DELEGATED_WORKSPACE_ROOT_MIN_VERSION` (3.8.1) | Separate `execution.workspaceRoot` is supported | Stable target registration and private execution root stay distinct (§2a) | -3.3.3 is where proceed-and-disclose replaced the refusal, not 3.3.6. Between them, 3.3.3–3.3.5 -did ship a real Linux bubblewrap boundary; 3.3.6 removed it as an owner decision, leaving the -scoped `HOME` plus a disclosed absence as the whole non-macOS design. None of 3.3.3–3.3.5 was -ever tagged or published, so the band above is what any reachable engine does. - -The live 3.2.0 daemon answers the read-only body with nothing but the fake-root error -(`project root does not exist`), i.e. it schema-accepts every field that lane sends. The -mutating body is rejected on the field, before the root is even looked at. - -### Why the floor is the MARKER release and not the boundary release - -Both were tried. The floor sat at 3.3.2 — the release that added the boundary — on the -reasoning that 3.3.0–3.3.1 write `harness_home_isolated: true` while the token stays readable, -so admitting them would produce a receipt for a confinement that is not there. - -That reasoning was right about the receipt and wrong about the remedy. **The last row of the -table is the same defect the floor was supposed to prevent, and the floor cannot see it:** a -3.3.2 build declares 3.3.2 on every host and applies a boundary on one of them. A version -describes a BUILD; it never describes what THIS attempt did. Using it as a proxy for "a -boundary was applied" is false in both directions — it refuses engines that would have been -honestly reported, and it passes hosts where nothing was applied. - -The receipt is fixed where the receipt is written (§8), not by narrowing admission. Once the -report tells the truth, the whole band from 3.3.0 up is admissible, and the floor means the -one thing a version can honestly mean: **below 3.3.0 the request is a 400 and no run exists.** +These floors are not a platform-support matrix. A version describes a build, +not what a particular attempt applied. Raising the marker floor to a release +that contains a boundary would still not prove that boundary exists on every +host; it would also refuse older engines that can execute with honest +unconfined disclosure. The report must instead follow the attempt evidence +in §8. The floors are compatibility minima, not a claim that the managed pin +or serving engine currently equals one of them. ## 5. Why a version at all, and why not a capability probe -For the SCHEMA question a version is the only answer available. Verified rather than assumed: +The marker floor prevents a known request-schema failure before dispatch. +The capability catalog's top-level `runControlKeys` does not establish support +for the nested `execution.delegated` field. Its per-harness `delegation` object +describes MCP injection for Claudexor's own delegation strategy, which is a +different capability. `subagent_route_health.route_health` therefore does not +use that field as proof of marker support. -- `POST /v2/handshake` returns `{protocolMajor, compatible, operationsPath, engine: {version, - sha, entry}}`. There is no capability list of any kind — checked live, and checked in the - 3.3.2 source, where the handshake responder is unchanged. -- `GET /v2/agent-capabilities` publishes `runControlKeys` derived from **top-level** request - keys only. `execution` appears; `execution.delegated` is nested and therefore invisible. - The catalog SCHEMA is field-identical between 3.2.0 and 3.3.2 — the only diff is one - `.describe()` string, so it gained nothing a probe could read. -- The per-harness `delegation` object in that catalog is about **Claudexor MCP injection** — - whether the harness can be handed sub-agent tools. It is `available: true` on the live 3.2.0 - daemon, which rejects the marker outright, so reading it as a delegation signal would admit - precisely the engines that cannot serve the lane. -- A probe by BEHAVIOUR is unavailable: `RunExecution` is `.strict()`, so the only way to learn - whether the field is accepted is to send it, and sending it on an engine that accepts it - STARTS THE RUN. There is no dry-run key in `runControlKeys`. The probe and the act are one. +A behavioral test of the start endpoint would be the operation itself: sending +the field to an engine that accepts it starts a run. Admission uses the +compatibility constant instead of spending a model run to probe that schema. For the BOUNDARY question no probe is needed, because the engine already answers it — after the fact, on the attempt record (§8). That answer is a fact about the run rather than a @@ -141,12 +132,12 @@ one question left that a floor cannot answer: `route_health` against the run SHAPE, before a token is spent. An engine below it would reject the request with a 400, so the lane refuses it with a typed reason (`engine_rejects_delegated_marker`) instead of spending a dispatch on a certain failure. -- `attempt_containment` — the applied-evidence reader. Not a gate: it decides what is SAID, - never whether the run happens. +- `attempt_containment` — the applied-evidence reader. Its boundary evidence + feeds disclosure, never a boundary-required admission gate. Its HOME facts + also feed the separate breach check in §8. An engine between the two floors serves read-only delegation and refuses mutating delegation. -That is the owner's explicit decision, and it is why the marker floor is not simply raised into -the transport floor. +Keeping the marker floor separate preserves that serving read-only lane. Both floors fail CLOSED: `engine_at_least` compares an absent or unparsable version as `(0,)`, below every floor. @@ -161,21 +152,21 @@ never produce. An `auto` request becomes an ordinary native subagent with a visi Stated plainly, because a floor described as total is worse than a narrow one. - **Not the enforcement.** Ouroboros admits; the engine confines. The floor is a claim about a - build, checked against a self-reported number, and it is now used only for the schema + build, checked against a self-reported number, and it is used only for the schema question, where that is enough. - **Not a lying or downgraded daemon.** The version is self-reported over loopback, and so are the applied facts on the attempt record. Anything that can forge either already runs as the operator and has the token. -- **Not the gap between two repos.** Ouroboros and Claudexor have no shared build. That the - release carrying the marker declares ≥ 3.3.0 is a RELEASE GATE on the engine side, not - something this pin can enforce. It holds without an edit for every bump above the floor and - fails closed if a release ever breaks it. +- **Not the engine implementation.** Claudexor is built separately and selected + by an exact reviewed runtime pin. Its release must actually implement the + request shape its version promises. The compatibility floor checks that + declared contract; it does not inspect the engine's code at dispatch. - **Not a promise that anything is confined.** A delegated mutating run is allowed on a host with no boundary mechanism at all. What is guaranteed is that the run is not DESCRIBED as confined when it is not — the disclosure, not the boundary, is the invariant. - **Not what the boundary itself leaves open where it does exist.** The vendor credential root stays readable to the child and the network is not fenced. Those are the engine's to state - and it states them in `docs/DELEGATED_CONFINEMENT.md` §8. Ouroboros must not re-describe + and it states them in `docs/DELEGATED_CONFINEMENT.md`. Ouroboros must not re-describe them as covered. - **Not the read-only lane's confinement.** A read-only child is scoped by Claudexor's ordinary envelope. Ouroboros asks for no marker and verifies no boundary there. @@ -183,24 +174,13 @@ Stated plainly, because a floor described as total is worse than a narrow one. boundary", so such a run is disclosed as unconfined when it was in fact confined. That is the honest limit of an applied-fact reader, and it is the safe direction: the consequence is a disclosure, never a refusal. -- **Not free of every harness NAME.** One named residual, disclosed rather than removed: - `gateway/claudexor_accounts.py::_build_login_request` branches on `harness == "codex"` in - three places (login setup only — never admission, routing or confinement). The branch is - load-bearing: `loginFlow` exists only for codex and is a 400 elsewhere, and a non-codex - login with no explicit transport would default daemon-side to `transport=daemon`, the - macOS Terminal.app handoff D30 forbids — so `client_pty` is forced instead. It mirrors - Claudexor's own setup-transport rule, not Ouroboros policy, and deleting it breaks D30. - It is the ONLY harness-name branch in the core (`ouroboros/`, `supervisor/`, `server.py`, - `launcher.py`). Removal condition: when the engine makes non-codex logins daemon-hosted, - the branch goes and this bullet with it. ## 8. Evidence, not intention — and the disclosure it feeds What the run actually got is read back from the run's own artifacts (`/attempts//attempt.yaml`). The HOME pair is artifact-only — the engine projects it onto no `/v2` response — while the boundary is also on the run detail, as -`candidates[].confinement` (`proven` / `mechanism` / `verifiedDeniedPath` / `unavailableReason`, -since 3.3.6); the artifact stays the one reader here because it answers both halves at once. +`candidates[].confinement` (`proven` / `mechanism` / `verifiedDeniedPath` / `unavailableReason`); the artifact stays the one reader here because it answers both halves at once. Two facts, one reader (`gateways.claudexor.attempt_containment`): - the HOME pair, `harness_home_isolated` / `harness_home_dir`; @@ -220,32 +200,32 @@ branch would have gone on reporting "no boundary" forever after that day. **The two halves take different rules about silence, on purpose.** A missing HOME fact stays UNPROVEN rather than false, because the consequence of "false" there is a CANCELLATION, and an attempt can legitimately record no `harness_home_isolated` — it is the one optional member of -the applied facts, omitted when the attempt died before its home was decided (and an engine -older than 3.3.2 put no applied facts on `attemptFailureRecord` at all). A missing mechanism +the applied facts, omitted when the attempt died before its home was decided (and an older engine may omit +those facts from `attemptFailureRecord`). A missing mechanism collapses to "no boundary", because the consequence there is a DISCLOSURE. Each silence is read in the direction whose failure mode is recoverable. -**A breach is exactly two facts** (simplified 2026-08-11, Poltergeist phase A3; the -2026-08-07 refinement went one step further): a recorded `harness_home_isolated: false`, -or an applied home EQUAL to the operator's own (the claim is the lie, whatever boundary -sits beside it). A scoped home NESTED under `$HOME` is NOT a breach — with or without a -recorded boundary. The engine roots every scoped home under its own runtime dir, which -lives under `$HOME` on every host it supports, and on a host with no boundary mechanism -(every non-macOS host today) it CANNOT record one — so the former nested-without-mechanism -rule cancelled every mutating Linux run post-factum while the work was already done and -healthy. The boundary-less nested shape flows to the existing disclosed-unconfined path -below instead: the token stays reachable by a relative walk and the disclosure SAYS so, -but the child already holds a shell in this worktree, and cutting the lane on every -boundary-less host costs more than the marginal step it prevents (AGENTS.md "Disclose -instead of forbid"). The engine's typed `confinement_unavailable_reason` — read from the -SAME attempt artifact — rides the disclosure as an amplifier (why this host has no -mechanism); it is telemetry, never an admission token, and its presence never excuses a -recorded FALSE. +**Confirmed HOME failures are distinct from missing evidence.** For attempts +that record the HOME isolation flag, `_home_isolation_breach` reports a breach +when that flag is false, or when the claimed isolated home resolves to the +operator's own home. A missing flag is skipped by this enforcement check and +remains unproven in the report. A scoped home nested under the operator's home +is not a breach, with or without an OS boundary: nesting is the engine's +ordinary layout and its absence of a boundary is disclosed rather than used +to cancel useful work. The engine's `confinement_unavailable_reason` amplifies +that disclosure; it never excuses a recorded false. -Where no boundary was applied, the fact is written LOUDLY into three places (AGENTS.md -"Disclose instead of forbid"): +The run-level report also preserves partial evidence. `verified` remains false +unless every recorded attempt discloses its HOME fact, no HOME breach exists, +the HOME is not nested under the operator's, and all attempts name the same +proven boundary mechanism. `nested_under_operator_home` stays visible even if +a boundary was applied: the boundary is evidence of confinement; the HOME +redirect alone is not. -1. **the durable record** — a `delegate_run_unconfined` event, once per run, carrying the +The disclosure reaches three places: + +1. **the durable record** — a `delegate_run_unconfined` event when no boundary + is reported or the HOME is nested under the operator's, once per run, carrying the note the parent was given, so the forensic trail of an integrated patch says where the work came from; 2. **the child's own prompt** — its instructions state that the boundary is a REQUEST and not diff --git a/docs/DOMAIN_MAP.md b/docs/DOMAIN_MAP.md index 34b591856..4591a7e13 100644 --- a/docs/DOMAIN_MAP.md +++ b/docs/DOMAIN_MAP.md @@ -1,4 +1,4 @@ -# Domain map — v7next +# Domain map Generated from `ouroboros/domains.toml` by `python scripts/check_domains.py --write`. Do not edit — edit the manifest and regenerate; `tests/test_domain_manifest.py` pins byte-identity. @@ -59,7 +59,7 @@ Rows may import columns (`[graph].allowed`). `·` = forbidden direction. ## Cycle status -1 pinned cycle group(s) — the SCC ceiling; the target is zero. Witness-level detail lives in `docs/v7next/DOMAIN_QUOTIENT_REPORT.md`. +1 pinned cycle group(s) — the SCC ceiling; the target is zero. Generate witness-level detail with `python scripts/domain_report.py`. - group 1 (20 domains): D01 ⇄ D02 ⇄ D03 ⇄ D04 ⇄ D05 ⇄ D06 ⇄ D07 ⇄ D08 ⇄ D09 ⇄ D10 ⇄ D11 ⇄ D12 ⇄ D13 ⇄ D14 ⇄ D15 ⇄ D16 ⇄ D17 ⇄ D18 ⇄ D19 ⇄ D20 diff --git a/docs/v7next/DESIGN_MODEL_VISIBLE_LOGGED.md b/docs/MODEL_SEND_OBSERVABILITY.md similarity index 79% rename from docs/v7next/DESIGN_MODEL_VISIBLE_LOGGED.md rename to docs/MODEL_SEND_OBSERVABILITY.md index 9ceda8957..919d792f7 100644 --- a/docs/v7next/DESIGN_MODEL_VISIBLE_LOGGED.md +++ b/docs/MODEL_SEND_OBSERVABILITY.md @@ -1,15 +1,12 @@ -# Design note — runtime invariant `model-visible ⟺ logged` (CPL-5) +# Model-send observability: `model-visible ⟺ logged` -Status: LANDED (plan §7 item 5; batch-1 Q8=A confirmed; narrowed per roast -finding F15). The code is `ouroboros/model_send_seal.py`, wired at +The physical-send observability contract is implemented in +`ouroboros/model_send_seal.py`, wired at `llm_attempt._candidate_before_dispatch` and swept from `server_maintenance`; -the pins are `tests/test_model_send_seal.py`. This note remains the contract, -kept narrow so neither the code nor a later reader drifts into a broader — -unprovable — claim. ONE clause changed between design and landing, and it is -marked in §3.2: a reconstruction mismatch is an OBSERVABILITY fact, not a -dispatch gate. +`tests/test_model_send_seal.py` verifies it. The claim is deliberately narrow: +a reconstruction mismatch is an observability fact, not a dispatch gate. -## 1. The claim, narrowed (F15) +## 1. Scope of the claim The invariant binds exactly one object: **`model_send` — the physical candidate payload at the last host-controlled pre-transport seam**. That seam @@ -22,8 +19,8 @@ after the cache-marker finalizer produced the final send copy durable record of its exact send copy BEFORE dispatch. - **Reverse (`logged ⟹ sent`)**: every sealed `model_send` record joins exactly one accounting attempt (dispatched, refused, or released). The - reverse direction holds for `model_send` records ONLY — F15 explicitly does - not claim it for any other log plane (events, chat, progress are narrations, + reverse direction is asserted only for `model_send` records, not + for any other log plane (events, chat, progress are narrations, not send truth). - **Everything else is out of the byte domain by typed exclusion, never by silence** (§4). @@ -35,21 +32,20 @@ its own previous answers is whatever the host replays into the NEXT send, so response-assembly truth is covered transitively by the next round's `model_send` record (§5.2). -## 2. What already exists (reuse-first — the note extends, it does not mint) +## 2. Shared mechanisms | Existing mechanism | Where | Role in the invariant | |---|---|---| | Canonical digest of the exact send copy (`canonical_json_v1`: sort_keys, compact separators, `ensure_ascii=False`, `allow_nan=False`, `default=str`) | `llm_attempt._attempt_request` / `_canonical_candidate_bytes` | The canonical form and its versioned basis (`candidate_measurement_kind`) | -| Pre-dispatch identity re-check: digests re-derived from the closed-over candidate and compared with the reservation's expected identity; drift refuses dispatch (`PhysicalAttemptPreparationFailed: physical candidate changed before dispatch`) | `llm_attempt._candidate_before_dispatch` | The forward gate's skeleton — today a digest compare of two in-memory copies | +| Pre-dispatch identity re-check: digests re-derived from the closed-over candidate and compared with the reservation's expected identity; drift refuses dispatch (`PhysicalAttemptPreparationFailed: physical candidate changed before dispatch`) | `llm_attempt._candidate_before_dispatch` | The forward gate's skeleton — a digest compare of two in-memory copies | | Durable candidate manifest + redacted CAS blob, written before dispatch, with two labelled digest domains (`canonical_json_v1_pre_redaction` facts vs `observability_json_v1_post_default_redaction_cas` blob) | `observability.persist_physical_candidate` / `persist_call` | The sealed record carrier | | Attempt lifecycle `reserved → dispatched → settled|unresolved` / `reserved → released`, short-lock append + sequence replay | `usage_accounting` | The join target for the reverse direction | | Anthropic native custody projection (opaque provider-native content replaced before persistence; disclosed as `anthropic_native_custody_projected`) | `anthropic_native_custody.physical_custody_projection` | Prototype of a typed exclusion | | Secret redaction with per-hit `RedactionRecord`s | `observability._redact_text` + rules | Prototype of a typed exclusion | -The gap this note was written to close: the pre-existing gate compared two -**in-memory** serializations, so a bug between "what we persisted" and "what we -believe we persisted" was assumed away rather than caught, and a mismatch was -only a raised exception. `model_send_seal.verify_sealed_candidate` closes it by +An identity gate that compares only two **in-memory** serializations cannot +establish whether the durable record matches either copy. +`model_send_seal.verify_sealed_candidate` checks that separate question by **reconstructing from the durable record** and byte-comparing that reconstruction against the wire-bound serialization, emitting a **typed durable fact** on any inequality (§3.2 — a fact, not a refusal). @@ -58,7 +54,7 @@ fact** on any inequality (§3.2 — a fact, not a refusal). ### 3.1 Sealed record (`model_send` seal, v1) -Extend the existing physical-candidate manifest (no new plane) with a +The existing physical-candidate manifest carries a `model_send_seal` block: - `seal_version: 1` @@ -66,18 +62,18 @@ Extend the existing physical-candidate manifest (no new plane) with a the serializer is a NEW basis string; a reader never re-interprets bytes under a different basis. - `pre_redaction_sha256` / `size_bytes` — digest of the canonical bytes of the - exact wire payload (exists today as `candidate_raw_sha256`). + exact wire payload (`candidate_raw_sha256`). - `exclusions: [...]` — every applied exclusion instance: `{class, path, opaque_sha256?}` (§4). An empty list is an explicit claim that the CAS blob reconstructs the wire bytes exactly (modulo nothing). -- `attempt_id` — the accounting join key (exists). +- `attempt_id` — the accounting join key. ### 3.2 Verification on call (forward) At the seam, in this order: 1. Serialize the wire-bound candidate to canonical bytes `W`. -2. Persist the sealed record (already the order today: persist, then gate). +2. Persist the sealed record (persist, then gate). 3. **Reconstruct** `R` from the durable record just written: read back the blob, undo nothing — instead apply the SAME exclusion map to `W` (redaction and custody projection are not invertible; §5.1) — and compare byte-for-byte @@ -87,10 +83,7 @@ At the seam, in this order: NOT blocked, and the verification never raises: this invariant is observability, and `verify_sealed_candidate` is fail-soft by contract. -That last step is the one place the landed contract differs from the first -draft of this note, which asked for a fail-closed refusal through -`PhysicalAttemptPreparationFailed`. It was rejected on its own merits, not for -convenience: +Verification remains fail-soft for two reasons: - The refusal it would add is not the same question as the existing gate. The in-memory identity re-check above this call still refuses dispatch when the @@ -106,7 +99,7 @@ convenience: disclosure, and a refusal path that can itself fail (write error, unreadable root) would have to decide between a silent skip and a dead runtime. -So the landed rule is: the fact is mandatory, the block is not. +The mismatch must be disclosed without blocking dispatch. `tests/test_model_send_seal.py` pins exactly this — a corrupted blob, a tampered seal digest, a dropped seal block, an undisclosed exclusion class and a foreign basis each produce their typed fact while the attempt still settles. @@ -152,7 +145,7 @@ violation. | `secret_redaction` | Secret VALUES masked in the CAS blob by the observability redaction rules | The durable copy must not carry live credentials; equality is digest-anchored instead (pre-redaction sha256) | existing `RedactionRecord`s → `{class, path}` rows | | `provider_native_custody` | Provider-owned opaque content (e.g. encrypted reasoning replay items) projected before persistence | Bytes are provider property; replay semantics are server-side | existing `anthropic_native_custody_projected` flag → per-item `{class, path, opaque_sha256}` | | `transport_envelope` | HTTP headers, auth, SDK-added transport fields (user-agent, idempotency keys, `stream` flag where the SDK owns it) | Below the seam by construction; carries secrets and transport identity, not model-visible content | class-level row (no per-call enumeration) | -| `provider_side_transform` | Server-side effects the host cannot observe pre-flight: prompt-cache application, provider truncation/normalization | Not host-controlled; the seam is the LAST host-controlled point, not the last point | class-level row; conformance suite (CPL-6) owns per-provider characterization | +| `provider_side_transform` | Server-side effects the host cannot observe pre-flight: prompt-cache application, provider truncation/normalization | Not host-controlled; the seam is the LAST host-controlled point, not the last point | class-level row | Delegated/harness model calls (`agent_session` executor lanes) are a lane-level instance of `provider_side_transform`: the host never holds the @@ -217,24 +210,19 @@ from "our two copies agree" to "the durable record agrees with the wire". narration planes (they remain projections; reverse-⟺ is `model_send` only). - No logical-call identity across retry rungs (§5.3). - No global "every log line reconstructs" framework — one seam, one record - kind, one sweep (plan: local decisions, no generic framework). + kind, one sweep. - No new persistence plane: the seal extends the existing physical-candidate manifest; facts ride `events.jsonl` + the seal's own directory. -## 7. Implementation sketch for the next lane (not this one) +## 7. Implementation owners -1. `llm_attempt.py`: extend `_candidate_before_dispatch` with read-back + - projection compare; thread the typed fact writer (small; the seam is one - closure). -2. `observability.py`: `model_send_seal` block in - `persist_physical_candidate` manifests (schema_version bump of the - manifest payload is NOT needed — additive key under the existing - `SCHEMA_VERSION` object; readers ignore unknown keys). -3. `server_maintenance.py`: reconciliation sweep behind the existing startup - sweep guardrails (fail-soft, bounded batch, UNKNOWN accounting state skips - destructive conclusions — there are none to skip: the sweep only writes - facts). -4. Tests: seal round-trip (write → reconstruct → equal); each §5 class forced - (mutating fake SDK, redaction-rule flip, double-assembly guard, per-rung - seals); reverse sweep on a synthetic orphan both ways; delegated-lane - `unobserved` disclosure. +- `llm_attempt.py` owns the pre-dispatch seam and its candidate identity gate. +- `model_send_seal.py` stamps the physical-candidate manifest, reads back the + durable projection, writes typed mismatch facts and reconciles both join + directions. The seal is an additive key under the existing manifest schema. +- `server_maintenance.py` runs the bounded reconciliation through the existing + startup sweep. Unknown accounting evidence does not become an orphan claim; + the sweep records facts without deleting records or fabricating attempts. +- `tests/test_model_send_seal.py` covers reconstruction, typed divergence, + non-blocking dispatch and reverse joins. Compacted history is resolved through + the live/archive union described in [Usage compaction](USAGE_COMPACTION.md). diff --git a/docs/PERSISTENCE.md b/docs/PERSISTENCE.md index c39409194..7010077e2 100644 --- a/docs/PERSISTENCE.md +++ b/docs/PERSISTENCE.md @@ -1,21 +1,14 @@ -# PERSISTENCE.md — durable data-plane inventory (CPL-4) +# PERSISTENCE.md — durable data-plane inventory Every durable entity under the runtime data root (`DATA_DIR`, default -`~/Ouroboros/data/`), with four decisions per entity (plan §7 item 4): -**schema_version** (present / not needed / needed→candidate), -**migration** path, **retention** (bounded / rotated / unbounded-accepted / -unbounded→candidate), and **reset** semantics (what deleting the entity while -the server is stopped does). Decisions are LOCAL per entity — there is no -generic persistence framework, by design. +`~/Ouroboros/data/`), with its schema, migration path, retention and reset +semantics (what deleting the entity while the server is stopped does). +These contracts are local to each entity; there is no generic persistence +framework. -Provenance disclosure: the plan references "§16 findings" (undocumented -planes, unbounded ledgers, mismatched temp); that findings document is not -recoverable in the plan, spec, or campaign archives, so this inventory was -built from scratch by an AST scan of every `data/`-path constructor in -`ouroboros/`, `supervisor/`, `server.py` and `launcher.py`, cross-checked by -manual reads of every writer. The scan is pinned as a verify test: -`tests/test_persistence_inventory.py` re-runs it and requires every scanned -data-relative path to be covered by a row here (count-anchored both ways). +`tests/test_persistence_inventory.py` scans data-path constructors in +`ouroboros/`, `supervisor/`, `server.py` and `launcher.py` and requires every +scanned data-relative path to be covered by a row here (count-anchored both ways). ## Shared idioms (the vocabulary the rows use) @@ -27,13 +20,13 @@ data-relative path to be covered by a row here (count-anchored both ways). - **GC retention** — `ouroboros/retention.py`: one owner knob `OUROBOROS_GC_RETENTION_DAYS` (default 7, clamped 1–365; legacy per-subsystem keys migrate). Governs subagent worktrees, headless/task drives, task trees, - service logs, and — since the CPL4 train — consumed schedule receipts, + service logs, consumed schedule receipts, confirmed capability probes, delegate recovery/supervision sweeps, code_intel and reconcile-failed prunes, memory-journal digesting and agent media. - **Rotation** — `supervisor/state.py::rotate_jsonl_log_if_needed`: >800 KB → atomic rename to `archive/_.jsonl` under the append lock. - Applied on the supervisor tick to `chat.jsonl`, `progress.jsonl` and — since - the CPL4-C1..C4 train — `events.jsonl`, `tools.jsonl`, `supervisor.jsonl`, + Applied on the supervisor tick to `chat.jsonl`, `progress.jsonl`, + `events.jsonl`, `tools.jsonl`, `supervisor.jsonl`, `task_reflections.jsonl`. Chain readers enumerate `archive/_*.jsonl` name-sorted (chronological by construction); `utils.jsonl_chain_handles` is the rotation-race-safe traversal @@ -44,11 +37,6 @@ data-relative path to be covered by a row here (count-anchored both ways). - **Atomic writes** — `atomic_write_json`/`atomic_write_text` (tmp+rename) and `update_json_locked` (sidecar `.lock`); JSONL appends go through `append_jsonl` (O_APPEND + sidecar lock) unless noted. -- **Candidate fixes** — the CPL4-C1..C23 candidate table lives in the campaign - ledger (`docs/v7next/LEDGER_CORRECTIONS.md`, F5 lane B section). The - mechanical train (owner №9=A) plus owner batch №8 closed every row except - CPL4-C6 (usage-ledger compaction — its own reviewed lane, monetary - authority); rows above cite their CPL4-Cn as provenance, not as open gaps. ## 1. Root files @@ -56,7 +44,7 @@ data-relative path to be covered by a row here (count-anchored both ways). |---|---|---|---|---|---| | `settings.json` | `ouroboros/config.py` save_settings (lock `settings.json.lock`, integrity guard) | JSON env-key map | none — not needed: migration is per-key inside `load_settings` (legacy keys migrate on read); external sha256 pin `OUROBOROS_SETTINGS_SHA256` makes it immutable when set | fixed-size overwrite | recreated from defaults; ALL owner secrets/modes lost — never delete casually | | `.ouroboros_isolated_benchmark` | EXTERNAL writer — the benchmark launchers (`devtools/benchmarks/evolve_smoke.py`, `devtools/benchmarks/editbench/run_editbench.py`, `devtools/benchmarks/cybergym/cybergym_server.py`) stamp it into their throwaway data root; the runtime only READS it (`supervisor/state.py` rotate suppression, `ouroboros/agent_startup_checks.py`) | one-line marker text | none — not needed: presence IS the fact | write-once per benchmark drive; never GC'd | the drive stops declaring itself synthetic: JSONL rotation and the benchmark-only startup carve-outs switch back to live-root behaviour on a throwaway root | -| `settings.json.lock`, `*.lock` sidecars, `locks/**` | `ouroboros/platform_layer.py` lock family (+ `supervisor/state.py`, `supervisor/update_merge.py`, `ouroboros/skill_lifecycle_queue.py`) | O_EXCL lockfiles (unlinked on release) or flock files (persistent); plus one transient kernel-lock capability probe file per lock directory (`.kernel-lock-probe..`, created, locked and unlinked once per process by `kernel_file_locks_enforced`) | none — not needed | self-healing (mtime/pid staleness) — except a lock whose owner died and whose pid was REUSED by a live process: it reads as alive whoever owns the pid (`kill(0)` succeeds for a same-uid impostor and answers EPERM for another user's — both alive since round 5.4), is never reclaimed by age while the impostor lives, and needs a hand repair meanwhile | zero durable state; deleting while stopped is a no-op | +| `settings.json.lock`, `*.lock` sidecars, `locks/**` | `ouroboros/platform_layer.py` lock family (+ `supervisor/state.py`, `supervisor/update_merge.py`, `ouroboros/skill_lifecycle_queue.py`) | O_EXCL lockfiles (unlinked on release) or flock files (persistent); plus one transient kernel-lock capability probe file per lock directory (`.kernel-lock-probe..`, created, locked and unlinked once per process by `kernel_file_locks_enforced`) | none — not needed | self-healing (mtime/pid staleness) — except a lock whose owner died and whose pid was REUSED by a live process: it reads as alive whoever owns the pid (`kill(0)` succeeds for a same-uid impostor and answers EPERM for another user's — both mean alive), is never reclaimed by age while the impostor lives, and needs a hand repair meanwhile | zero durable state; deleting while stopped is a no-op | ## 2. `state/` — singletons (fixed-size overwrite) @@ -66,11 +54,11 @@ data-relative path to be covered by a row here (count-anchored both ways). | `state/queue_snapshot.json` | `supervisor/queue_snapshot.py` | `_schema_version: 1` (ABI-2) | overwrite; self-expires (max_age 900 s) | absent = 0 restored; queued-unstarted tasks silently dropped | | `state/direct_roots.json` | `supervisor/direct_roots.py` — the main loop's off-lock projection of live direct-chat turns, written beside the snapshot on every tick (`publish_direct_roots`, `server.py`) and emptied when the queue initializes or restores (`clear_direct_roots`, `supervisor/queue.py`) so a dead process's turns never outlive it | none — not needed: the whole file is re-derived every tick, and an unreadable one reads as no direct roots | overwrite; the rows are whatever the actor registry holds this tick, and a turn mid-admission (its actor lock only ever TRIED) is skipped behind ONE aggregate `incomplete` fact rather than blocking the loop | absent/empty = no live direct-chat roots are known: a task's peer roster (`ouroboros/peer_roster.py`) lists only the pooled roots from `state/queue_snapshot.json` until the next tick rewrites it | | `state/advisory_review.json` | `ouroboros/review_state.py` (lock `locks/advisory_review.lock`) | own pre-ABI-2 spelling `state_version: 3` (+duplicate `schema_version`) — kept; `_schema_version` deliberately avoids this key | bounded on write: runs 10, attempts 50, debts 50; `open_obligations` coalesced | recreated empty; recorded blocking obligations forgiven, commit gate demands fresh review (fail-closed) | -| `state/scheduled_tasks.json` | `supervisor/queue_schedules.py` | `schema_version: 1` — authored at the write seam (`_write_scheduled_tasks`, CPL4-C7); legacy files gain it on their next write | consumed `once` receipts age out past GC retention on the scheduler tick (CPL4-C7; `prune_consumed_once_records`); 2 MB WARN = prune broken or live set huge | owner cron/once schedules lost; skill-manifest schedules resync automatically | +| `state/scheduled_tasks.json` | `supervisor/queue_schedules.py` | `schema_version: 1` — authored at the write seam (`_write_scheduled_tasks`); legacy files gain it on their next write | consumed `once` receipts age out past GC retention on the scheduler tick (`prune_consumed_once_records`); 2 MB WARN = prune broken or live set huge | owner cron/once schedules lost; skill-manifest schedules resync automatically | | `state/terminal_deliveries.json` | `supervisor/terminal_delivery.py` | own `schema_version: 2` | bounded: delivered 512, pending 64, replays 5 | dedupe + owed-outbox lost: possible double- or never-delivery of one buffered terminal answer | | `state/cancel_intents.json` | `ouroboros/cancel_intents.py` | own `schema_version: 1` | self-draining (settled rows leave) | in-flight cancels lost: cancelled-unsettled task revives as pending; forensics survive in supervisor.jsonl | | `state/update_letter.json` | `ouroboros/update_letter.py` (`refresh_after_check` — the ONE writer, synchronous inside a FETCHING update check: boot and the Updates panel's check button) | own record shape keyed by the update range (`base_sha`, `target_sha`, channel, target ref) — no `_schema_version` | overwrite per check; the letter is never deleted after the update lands (it outlives its range; `project_letter` reads `applied`/`other` by SHA equality + the recorded `target_in_head` fact) | absent = no letter: the Updates panel and the agent's Runtime context (`official_update_projection`) show the check's status alone; the next FETCHING check rewrites it | -| `state/capability_evidence.json` | `ouroboros/capability_evidence.py` | none — accepted (self-healing cache; TTLs on read) | expired probe keys drop at the write seam (CPL4-C8): failed/unprobeable past their read TTL, confirmed past GC retention (blip-keep evidence survives inside retention); owner acks never expire | recreated; ≥1M-context gates fail closed to `unknown`, owner acks must be re-given | +| `state/capability_evidence.json` | `ouroboros/capability_evidence.py` | none — accepted (self-healing cache; TTLs on read) | expired probe keys drop at the write seam: failed/unprobeable past their read TTL, confirmed past GC retention (blip-keep evidence survives inside retention); owner acks never expire | recreated; ≥1M-context gates fail closed to `unknown`, owner acks must be re-given | | `state/evolution_campaign.json` | `supervisor/evolution_lifecycle.py` (CAS under state.lock) | own `schema_version: 1` (campaign) / 2 (active_transaction); the active transaction carries the pre-commit `commit_intent` (reviewed tree + parents) written BEFORE `git commit`, which boot recovery turns into the `commit_receipt` a crash never wrote | bounded histories (50) | in-flight self-modification transaction unabsorbable; anti-repeat fingerprints lost | | `state/evolution_metrics_cache.json` | `ouroboros/utils.py` | own `schema: 1` (strictly validated) | one point per git tag, no prune — accepted (derived cache) | pure cache; recomputed from git | | `state/projects.json`, `state/project_task_bindings.json` | `ouroboros/projects_registry.py` (sidecar locks) | `_schema_version: 2` / `1` (ABI-2) | never age-pruned (owner curates); deletes are durable tombstones | tombstones live here: losing it can resurrect deleted project rooms (marker unlink mitigates) | @@ -99,7 +87,7 @@ data-relative path to be covered by a row here (count-anchored both ways). | Path | Writer | schema/record marker | Retention | Reset | |---|---|---|---|---| -| `state/usage_attempts.jsonl` (+ `state/usage_attempts.quarantine.jsonl`, `state/usage_attempts.lock`) | `ouroboros/usage_ledger.py` single chokepoint (own O_APPEND+fsync under named lock — NOT append_jsonl); `ouroboros/usage_compaction.py` rewrites it whole (verified candidate, atomic swap) under the same lock | no `_schema_version`; its own validated contract: dense `seq`, `kind` discriminator, `state` machine, per-row `candidate_measurement_kind`, attribution `physical_attempt_v1`; compacted files lead with a stamped `usage_baseline` header + `usage_baseline_group` rows — accepted | bounded by CPL4-C6 compaction (config `USAGE_LEDGER_COMPACT_BYTES`, 8 MB): terminal non-review attempt chains fold into the baseline block, raw segment archived first (fsync'd) — see docs/v7next/DESIGN_USAGE_COMPACTION.md; 20 MB WARN can reflect broken compaction, a large unfoldable residue, or a refused/skipped pass: the name-tier refusal emits `usage_ledger_compaction_refused` once per process per data root; a policy abort (`_Abort`) emits `usage_ledger_compaction_skipped` once per process per (data root, reason); the two snapshot-race exits before archive/swap only log warnings, without a typed event; torn tails — and a charge a compaction swap erased inside its last syscall, read back from the old inode (POSIX, round 5.4) — quarantined, never GC'd | monetary history destroyed, `seq` restarts, budget fences read $0; watermark survives so legacy import will NOT re-run — deleting the ledger alone is unrecoverable; and a ledger reset while `archive/usage_ledger/` survives leaves every history question a permanent `generation newer` corruption verdict (the stamp-less anchor) — move/delete the archive with it, or keep both | +| `state/usage_attempts.jsonl` (+ `state/usage_attempts.quarantine.jsonl`, `state/usage_attempts.lock`) | `ouroboros/usage_ledger.py` single chokepoint (own O_APPEND+fsync under named lock — NOT append_jsonl); `ouroboros/usage_compaction.py` rewrites it whole (verified candidate, atomic swap) under the same lock | no `_schema_version`; its own validated contract: dense `seq`, `kind` discriminator, `state` machine, per-row `candidate_measurement_kind`, attribution `physical_attempt_v1`; compacted files lead with a stamped `usage_baseline` header + `usage_baseline_group` rows — accepted | bounded by compaction (config `USAGE_LEDGER_COMPACT_BYTES`, 8 MB): terminal non-review attempt chains fold into the baseline block, raw segment archived first (fsync'd) — see docs/USAGE_COMPACTION.md; 20 MB WARN can reflect broken compaction, a large unfoldable residue, or a refused/skipped pass: the name-tier refusal emits `usage_ledger_compaction_refused` once per process per data root; a policy abort (`_Abort`) emits `usage_ledger_compaction_skipped` once per process per (data root, reason); the two snapshot-race exits before archive/swap only log warnings, without a typed event; torn tails — and a charge a compaction swap erased inside its last syscall, read back from the old inode (POSIX) — quarantined, never GC'd | monetary history destroyed, `seq` restarts, budget fences read $0; watermark survives so legacy import will NOT re-run — deleting the ledger alone is unrecoverable; a ledger reset beside `archive/usage_ledger/` makes history queries raise `generation newer` while an unreferenced segment represents a newer generation and is not a byte-prefix of the live file; after fresh compactions reach the surviving archive generations, those old unreferenced segments are skipped and their attempt IDs remain absent — see [history readers](USAGE_COMPACTION.md#10-history-readers-model-send-reconciliation-and-audits); keep or reset the ledger and archive together | | `state/skill_review_root_tasks.jsonl` (+ `state/skill_review_root_tasks.gaps.jsonl`) | `ouroboros/skill_review_history.py` (`_append_root_task_projection_once`: one compact row per root task appended when its skill review lands; a row the projection could not attribute goes to the `.gaps.jsonl` ledger via `_record_root_task_projection_gap`) | rows carry `usage_attribution_schema: physical_attempt_v1`; no version key — accepted (derived index over the per-skill `review_history.jsonl`, P7) | unbounded append; reads are BOUNDED (`skill_readiness.py`: 1 MiB / 512 records tail, a truncated or gapped read is disclosed as `projection_incomplete`) and the startup hot-store check warns past `SKILL_REVIEW_ROOT_TASKS_WARN_BYTES` | delete with `state/`; the index is not rebuilt — readiness and the acceptance packet read `projection_incomplete` until new rows accrue (the per-skill histories keep the truth) | | `state/process_ledger.jsonl` | `ouroboros/process_custody.py` (spawn chokepoint) | none — downgrade-safe field split (`start_time`/`start_time_boot`) is the versioning device — accepted | self-compacting: reapers rewrite survivors-only | prior-generation supervised processes permanently orphaned (fingerprint index lost) | | `state/evolution_checkpoints.jsonl` | `ouroboros/evolution_checkpoints.py` | `schema_version: 1` on every row (+`kind` on outcome rows) | unbounded append; read bounded (last 200) — accepted (structured solve-capability history is the product) | absorbed/abandoned objectives can be re-proposed (BUG3 regression); the outcome row is DERIVABLE from the resolved campaign transaction, so a row lost to a crash between the two writes is replayed at boot (`source: boot_backfill`) | @@ -109,14 +97,14 @@ data-relative path to be covered by a row here (count-anchored both ways). | Path | Writer | schema_version | Retention | Reset | |---|---|---|---|---| -| `state/skills//` owner state (`review.json`, `review_job.json`, `grants.json`, `enabled.json`, `deps.json`, `self_authored.json`, `owner_attestation.json`, `accepted_rebuttals.json`, `health.json`, `uninstalled.json`, provenance sidecars, `auto_repair.json`, `presence_profile_state.json`) | `ouroboros/skill_loader.py`, `skill_review_runner.py`, `skill_owner_attestation.py`, `skill_review_cycles.py`, `skill_uninstall_state.py`, `extension_health.py`, `marketplace/*`, `ouroboros/gateway/marketplace.py`; allowlist SSOT `contracts/skill_payload_policy.py` | `deps.json`/`self_authored.json`/provenance: `schema_version: 1`; `review.json`/`enabled.json`/`grants.json`/`review_job.json`/`owner_attestation.json`/`accepted_rebuttals.json`: `_schema_version: 1` (ABI-2, stamp-on-write — CPL4-C10; readers keep legacy-0 tolerance, unstamped files never retrofitted); verdict/grant staleness stays pinned by `content_hash` | no age GC; hub uninstalls write an `uninstalled.json` tombstone and the startup sweep clears the dead state BY that mark (CPL4-C11, owner 3A) — `grants.json` survives as owner authority, a reinstall self-heals the tombstone; the gateway's local delete removes the whole state dir | absent state = disabled + pending review + grants revoked (fail-closed); `owner_attestation` absence invalidates its verdict | -| `state/skills//review_history.jsonl` + `review_dispatch/` (legacy `review_dispatch.json`) | `ouroboros/skill_review_history.py` | rows carry `usage_attribution_schema: physical_attempt_v1`; no version key — accepted (derived-counter SSOT, P7) | history unbounded per skill — accepted with BOUNDED reads (CPL4-C12): every reader windows the 4 MB tail (`find_history_job_bounded` idiom); lifecycle terminal rows persist their ordinals so counters stay exact inside the window (a group aged past it restarts low — under-counts, never over-blocks); per-skill archive rotation declined (no per-skill archive plane; disclosed) | review-cycle ceiling resets to zero; paid dispatches become free again | +| `state/skills//` owner state (`review.json`, `review_job.json`, `grants.json`, `enabled.json`, `deps.json`, `self_authored.json`, `owner_attestation.json`, `accepted_rebuttals.json`, `health.json`, `uninstalled.json`, provenance sidecars, `auto_repair.json`, `presence_profile_state.json`) | `ouroboros/skill_loader.py`, `skill_review_runner.py`, `skill_owner_attestation.py`, `skill_review_cycles.py`, `skill_uninstall_state.py`, `extension_health.py`, `marketplace/*`, `ouroboros/gateway/marketplace.py`; allowlist SSOT `contracts/skill_payload_policy.py` | `deps.json`/`self_authored.json`/provenance: `schema_version: 1`; `review.json`/`enabled.json`/`grants.json`/`review_job.json`/`owner_attestation.json`/`accepted_rebuttals.json`: `_schema_version: 1` (ABI-2, stamp-on-write — readers keep legacy-0 tolerance, unstamped files never retrofitted); verdict/grant staleness stays pinned by `content_hash` | no age GC; hub uninstalls write an `uninstalled.json` tombstone and the startup sweep clears the dead state BY that mark — `grants.json` survives as owner authority, a reinstall self-heals the tombstone; the gateway's local delete removes the whole state dir | absent state = disabled + pending review + grants revoked (fail-closed); `owner_attestation` absence invalidates its verdict | +| `state/skills//review_history.jsonl` + `review_dispatch/` (legacy `review_dispatch.json`) | `ouroboros/skill_review_history.py` | rows carry `usage_attribution_schema: physical_attempt_v1`; no version key — accepted (derived-counter SSOT, P7) | history unbounded per skill — accepted with BOUNDED reads: every reader windows the 4 MB tail (`find_history_job_bounded` idiom); lifecycle terminal rows persist their ordinals so counters stay exact inside the window (a group aged past it restarts low — under-counts, never over-blocks); per-skill archive rotation declined (no per-skill archive plane; disclosed) | review-cycle ceiling resets to zero; paid dispatches become free again | | `state/delegate_project_retirements/.lock` | `ouroboros/delegate_custody_usage.py` (`project_retirement_lock`: exclusive file lock around one project's settlement/retirement decision; stale after 120 s, owner-aware) | none — not needed (lock file, no payload) | one file per project ever settled; reclaimed as stale by the next holder | delete freely; a live holder re-creates its lock | | `state/skills//` transport dirs: `extension_calls/`, `__extension_imports/` | `ouroboros/extension_process_runner.py`, `extension_import_staging.py` | none — not needed (per-call transport files, staged import trees) | per-call files consumed; import leaves reaped owner-dead+grace | transient; recreated per call | -| `state/delegate_recovery/`, `state/delegate_recovery_transactions/` (+`active.json`), `state/delegate_supervision/` | `ouroboros/delegate_recovery.py`, `delegate_supervision.py` (+ startup sweep `delegate_state_sweep.py`) | own `schema: 1` on supervision/transactions; recovery rows fingerprinted, unversioned | terminal+age startup sweep (CPL4-C13): terminal-status recovery rows (`vetoed`/`adopted`), unreferenced transactions and settled-task supervision files past GC retention; live/resumable rows, `active.json`, no-result tasks and unreadables kept fail-closed; unreadable custody log skips the sweep | interrupted delegated runs cancelled instead of adopted; duplicate wake replay; planned handoffs vetoed | +| `state/delegate_recovery/`, `state/delegate_recovery_transactions/` (+`active.json`), `state/delegate_supervision/` | `ouroboros/delegate_recovery.py`, `delegate_supervision.py` (+ startup sweep `delegate_state_sweep.py`) | own `schema: 1` on supervision/transactions; recovery rows fingerprinted, unversioned | terminal+age startup sweep: terminal-status recovery rows (`vetoed`/`adopted`), unreferenced transactions and settled-task supervision files past GC retention; live/resumable rows, `active.json`, no-result tasks and unreadables kept fail-closed; unreadable custody log skips the sweep | interrupted delegated runs cancelled instead of adopted; duplicate wake replay; planned handoffs vetoed | | `state/delegate_actor_claims/*.lock`, `state/.payload_delegation_claim.lock` | `ouroboros/delegate_custody.py`, `delegate_start_claims.py` | none — locks | unlinked on release | no durable state | -| `state/code_intel//inventory.json` | `ouroboros/code_intelligence.py` | own `schema_version: 2` (older/malformed rebuilt silently) | per-repo rewrite in place; stale roots age-pruned at startup by `inventory.json` mtime past GC retention (CPL4-C14, pure cache) | pure derived cache; one full re-index | -| `state/extension_reconcile/` (+`failed/`) | `ouroboros/extension_reconcile_queue.py` | none — not needed (one-shot markers) | consumed by server loop; after 5 attempts moved to `failed/`, where markers age-prune past GC retention (CPL4-C15; the failure fact stays durable in events.jsonl) | pending worker→server reconciles lost; re-toggle heals | +| `state/code_intel//inventory.json` | `ouroboros/code_intelligence.py` | own `schema_version: 2` (older/malformed rebuilt silently) | per-repo rewrite in place; stale roots age-pruned at startup by `inventory.json` mtime past GC retention (pure cache) | pure derived cache; one full re-index | +| `state/extension_reconcile/` (+`failed/`) | `ouroboros/extension_reconcile_queue.py` | none — not needed (one-shot markers) | consumed by server loop; after 5 attempts moved to `failed/`, where markers age-prune past GC retention (the failure fact stays durable in events.jsonl) | pending worker→server reconciles lost; re-toggle heals | | `state/workspace_executor_processes/` | `ouroboros/workspace_executor.py` | own `schema_version: 1` + owner tag | unlink on stop; stale rows filtered at read (pid/cmd-sha) | service processes survive unreaped | | `state/acceptance_fence_acks/` | `supervisor/events_worker_reports.py` (writer), `ouroboros/agent.py` (read+unlink) | none — transport ack | inline GC on write (255 newest / 3600 s) | waiting worker fails closed (TimeoutError) | | `state/headless_tasks//` (child data drives) | `ouroboros/headless.py` | child `state/state.json`: `schema_version: 1` | GC-retention prune at startup (terminal + age; skips artifacts-not-terminal / refs-unpromoted) | in-flight child drives and unpromoted child refs lost | @@ -126,7 +114,7 @@ data-relative path to be covered by a row here (count-anchored both ways). | `state/review_continuations/.json` (+ `state/review_continuations/archived/**`, `state/review_continuations/corrupt/**` with its `.txt` reason notes) | `ouroboros/task_continuation.py` (atomic write; `rename` to `archived/` on retire, `replace_atomic` to `corrupt/` on quarantine) | none — accepted: a typed dataclass with an ownership check (a `task_id` mismatch raises) and malformed JSON quarantined, never migrated | live payload per blocked task, cleared on resume; a settled, un-resumed continuation is retired to `archived/` past `RETIRE_SETTLED_CONTINUATION_AFTER_DAYS` (7) so history stops riding into every new prompt; `archived/` and `corrupt/` unbounded — accepted (small typed payloads; the quarantine IS the corruption evidence) | blocked tasks lose their resume pointer: findings, obligations and the commit intent must be re-derived by re-running review | | `state/skills//auth_token.json` | `ouroboros/extension_plugin_api.py` `mint_skill_token` (atomic write, chmod 0600) | none — not needed: the token is bound to `content_hash`, which IS its staleness contract | one token per skill, rotated on a content-hash change; a transient hash failure reuses or fails closed, never rotates | the next mint issues a new token, so a companion still holding the old one is de-authorized until respawn — do not delete while companions run | | `state/skills//repair_admission.json` | `ouroboros/skill_repair_admission.py` `record_repair_admission` (atomic; the newest admission owns the record) | own `schema_version: 1` | one record per skill, superseded by the next admitted repair | the repair CAS has nothing to check against and its writes are refused — fail-closed by design, since this record exists to remove exactly that fail-open | -| `state/skills//jobs//` (`assets/`, `output/`, `tmp/`) | `ouroboros/extension_plugin_api.py` `skill_job_dir` (creates the three children on request; the extension owns the contents) | none — not needed: a workspace, not a record | unbounded — candidate: nothing sweeps it, and the gateway's local skill delete is the only path that removes it with the state dir (disclosed by the stage-2 fix wave in `docs/v7next/LEDGER_CORRECTIONS.md`) | in-flight extension jobs lose their assets and output; the next call recreates the tree | +| `state/skills//jobs//` (`assets/`, `output/`, `tmp/`) | `ouroboros/extension_plugin_api.py` `skill_job_dir` (creates the three children on request; the extension owns the contents) | none — not needed: a workspace, not a record | unbounded: nothing sweeps it, and the gateway's local skill delete is the only path that removes it with the state dir | in-flight extension jobs lose their assets and output; the next call recreates the tree | | `state/skills//chat_id_counter.json` | `ouroboros/gateway/host_service.py` `allocate_internal_chat_id` (atomic, under the in-process counter lock) | none — not needed: `range_name` plus last/next id | one file per skill; ids descend from `A2A_CHAT_ID_MAX` | allocation restarts at the top of the range, so a fresh A2A room can reuse a chat id already present in history | | `state/project_source_locks/` | none in this tree — orphan plane seen in live layouts (removed feature leftover) | none | n/a | harmless; nothing reads or recreates it | @@ -136,14 +124,14 @@ data-relative path to be covered by a row here (count-anchored both ways). |---|---|---|---|---| | `logs/chat.jsonl` | `supervisor/message_bus.py` (+presence, project summaries) | `direction` + optional `type`; no version — accepted (projection replayed by chain-aware readers) | rotated 800 KB → `archive/chat_*.jsonl`; archive chain WARN at 100 MB | newest generation lost; consolidation cursor reports gap (recoverable) | | `logs/progress.jsonl` | `supervisor/message_bus.py` (+plan review) | `type: send_message`, `is_progress` | rotated 800 KB → `archive/progress_*.jsonl`; 8 MB WARN = rotation broken | current segment lost; readers archive-chain-aware | -| `logs/events.jsonl` | ~60 modules via `append_jsonl` (+`delegate_custody.emit`) | universal `type` discriminator — accepted (per-type payloads owned by emitters) | rotated 800 KB → `archive/events_*.jsonl` (CPL4-C1); custody readers (replay, fault tail-scan, `complete_custody_rows`, settled-terminal chain cursor, legacy-usage import, swarm rollup, worker-boot verify) are chain-aware; 8 MB live WARN = rotation broken; 100 MB chain WARN = replay degradation | delegated-run custody destroyed (chain incl. archive segments): open runs invisible/unreapable; lineage, citations, legacy-usage source lost | -| `logs/tools.jsonl` | `ouroboros/loop_tool_execution.py` (+budget-drive mirror) | `type: tool_call`, untruncated args | rotated 800 KB → `archive/tools_*.jsonl` (CPL4-C2); tail readers (api_logs_tail, task_events) archive-backfill; 8 MB WARN = rotation broken | untruncated tool record + `result_ref` pointers lost | -| `logs/supervisor.jsonl` | supervisor family, `process_custody`, gateway control, server shutdown | `type` (+secondary `event_type`) | rotated 800 KB → `archive/supervisor_*.jsonl` (CPL4-C3) + 8 MB tripwire; tail readers (`memory.read_jsonl_tail`, api_logs_tail) archive-backfill | reap receipts, rescue disclosures, shutdown causes lost | -| `logs/task_reflections.jsonl` | `ouroboros/reflection.py` (+ project-scoped copy under `projects//logs/`) | full rows unversioned; pointer rows `type: project_reflection_pointer` | rotated 800 KB → `archive/task_reflections_*.jsonl` (CPL4-C4) + 8 MB tripwire; tail-20 read archive-backfills; project-scoped copies follow project retention (never age-pruned) | inter-task memory-carry signal lost | +| `logs/events.jsonl` | ~60 modules via `append_jsonl` (+`delegate_custody.emit`) | universal `type` discriminator — accepted (per-type payloads owned by emitters) | rotated 800 KB → `archive/events_*.jsonl`; custody readers (replay, fault tail-scan, `complete_custody_rows`, settled-terminal chain cursor, legacy-usage import, swarm rollup, worker-boot verify) are chain-aware; 8 MB live WARN = rotation broken; 100 MB chain WARN = replay degradation | delegated-run custody destroyed (chain incl. archive segments): open runs invisible/unreapable; lineage, citations, legacy-usage source lost | +| `logs/tools.jsonl` | `ouroboros/loop_tool_execution.py` (+budget-drive mirror) | `type: tool_call`, untruncated args | rotated 800 KB → `archive/tools_*.jsonl`; tail readers (api_logs_tail, task_events) archive-backfill; 8 MB WARN = rotation broken | untruncated tool record + `result_ref` pointers lost | +| `logs/supervisor.jsonl` | supervisor family, `process_custody`, gateway control, server shutdown | `type` (+secondary `event_type`) | rotated 800 KB → `archive/supervisor_*.jsonl` + 8 MB tripwire; tail readers (`memory.read_jsonl_tail`, api_logs_tail) archive-backfill | reap receipts, rescue disclosures, shutdown causes lost | +| `logs/task_reflections.jsonl` | `ouroboros/reflection.py` (+ project-scoped copy under `projects//logs/`) | full rows unversioned; pointer rows `type: project_reflection_pointer` | rotated 800 KB → `archive/task_reflections_*.jsonl` + 8 MB tripwire; tail-20 read archive-backfills; project-scoped copies follow project retention (never age-pruned) | inter-task memory-carry signal lost | | `logs/containment_faults.jsonl` | `ouroboros/delegate_custody.py` (mirrored to events.jsonl) | `type` ∈ CONTAINMENT_FAULT/RESOLVED joined on run_id | unbounded BY DESIGN — read whole so an open fault never ages out — accepted | health invariant degrades to the 4 MB events tail scan (the regression this file fixed) | | `logs/chat_annotations.jsonl` | `ouroboros/project_dialogue.py` (`append_jsonl` under the shared sidecar append lock) | `type: chat_annotation` keyed by `(client_message_id, routing_token)`; latest row per key wins (an owner message keeps one row per routing act; task-authored acts ride synthetic `agent-steer:` ids) | self-compacting at 800 KB under the append lock: the latest row of every message still present in the chat chain (live `chat.jsonl` + the 3 newest `archive/chat_*.jsonl`) is rewritten, the rest is DROPPED — presentation state, so it is not rotated into `archive/` | annotation cards fall back to their plain chat rows; the one named exception (#198) also loses the durable picker decision-card token, so a pending manual routing choice must be made again | -| `logs/tasks/task_.txt` | `ouroboros/utils.py` log sanitization (`write_text`, best-effort) | raw oversized task text, no envelope; the log row keeps `text_full_path` | unbounded → candidate: no retention sweep names `logs/tasks/` (disclosed by the stage-2 fix wave in `docs/v7next/LEDGER_CORRECTIONS.md`) | the truncated text in the log row stays; only the spilled full text of oversized task prompts is lost | -| `logs/agent_stdout.log` | `launcher.py` pipe-copy thread | unstructured text | bounded ~8 MB (2 MB × `.1..3` backups, rotated by the copy thread — CPL4-C5) | pre-logging crash output lost; nothing parses it | +| `logs/tasks/task_.txt` | `ouroboros/utils.py` log sanitization (`write_text`, best-effort) | raw oversized task text, no envelope; the log row keeps `text_full_path` | unbounded: no retention sweep names `logs/tasks/` | the truncated text in the log row stays; only the spilled full text of oversized task prompts is lost | +| `logs/agent_stdout.log` | `launcher.py` pipe-copy thread | unstructured text | bounded ~8 MB (2 MB × `.1..3` backups, rotated by the copy thread) | pre-logging crash output lost; nothing parses it | | `logs/server.log` (+`.1..3`), `logs/launcher.log` | stdlib `RotatingFileHandler` (`server.py`, `launcher.py`) with secret-redacting filter | text | bounded ~8 MB (2 MB × 4) — the model citizen | stdlib log history lost; nothing parses it | ## 6. `memory/` (Ouroboros cognition — operator read-only) @@ -157,30 +145,30 @@ data-relative path to be covered by a row here (count-anchored both ways). | `memory/dialogue_blocks.json` + `dialogue_meta.json` | `ouroboros/consolidator.py` (locked atomic) | none | bounded by era compression (10 blocks, oldest 4 compressed) | blocks: compressed biography irreproducible; meta: full re-consolidation (cost, not loss) | | `memory/dialogue_summary.md` | none — legacy read-only (reader in context.py) | none | frozen | legacy artifact; nothing writes it | | `memory/knowledge/**` (topic .md + `index-full.md` + `patterns.md`) | `ouroboros/tools/knowledge.py`, `consolidator.py` (index rebuild), `reflection.py` (patterns CAS rewrite) | none | topic files unbounded — accepted (curated by consolidation); backlog topic merge-only fail-closed | recreated lazily; knowledge lost | -| `memory/*_journal.jsonl`, `memory/knowledge_history.jsonl`, `memory/knowledge/patterns_history.jsonl` | `ouroboros/memory.py`, `tools/control_runtime.py`, `tools/knowledge.py`, `reflection.py` — every append through the `append_jsonl` sidecar-lock seam (CPL4-C17) | scratchpad journal: `type` rows; others unversioned full-text snapshots; digested rows carry `content_digested: true` | full old+new text only inside GC retention (CPL4-C16, owner 4A): older identity/knowledge/patterns rows go digest-only (sha256+len) at startup (`memory_journal_compaction.py`, under the append lock, unreadable lines byte-preserved); scratchpad journal keeps its own eviction contract | undo/provenance record lost (live .md survives); eviction/rewrite paths fail closed when journal append fails; digested history is irreversible by design | -| `memory/owner_mailbox/.jsonl` + `.acks.jsonl` | `ouroboros/owner_mailbox.py` (append-only; revocation appends, reader resolves) | `kind` discriminator | lifecycle-bounded: unlinked at task terminal; a startup sweep unlinks mailboxes whose task has a SETTLED durable result (CPL4-C18; no result / non-terminal keeps the mailbox fail-closed) | undelivered owner directives + restart-surviving hurry latch lost; acks lost ⇒ re-delivery | +| `memory/*_journal.jsonl`, `memory/knowledge_history.jsonl`, `memory/knowledge/patterns_history.jsonl` | `ouroboros/memory.py`, `tools/control_runtime.py`, `tools/knowledge.py`, `reflection.py` — every append through the `append_jsonl` sidecar-lock seam | scratchpad journal: `type` rows; others unversioned full-text snapshots; digested rows carry `content_digested: true` | full old+new text only inside GC retention: older identity/knowledge/patterns rows go digest-only (sha256+len) at startup (`memory_journal_compaction.py`, under the append lock, unreadable lines byte-preserved); scratchpad journal keeps its own eviction contract | undo/provenance record lost (live .md survives); eviction/rewrite paths fail closed when journal append fails; digested history is irreversible by design | +| `memory/owner_mailbox/.jsonl` + `.acks.jsonl` | `ouroboros/owner_mailbox.py` (append-only; revocation appends, reader resolves) | `kind` discriminator | lifecycle-bounded: unlinked at task terminal; a startup sweep unlinks mailboxes whose task has a SETTLED durable result (no result / non-terminal keeps the mailbox fail-closed) | undelivered owner directives + restart-surviving hurry latch lost; acks lost ⇒ re-delivery | ## 7. Skills payloads, tasks, uploads, projects, services | Path | Writer | Marker | Retention | Reset | |---|---|---|---|---| -| `skills/{native,clawhub,ouroboroshub,external}//**` (+`.staging/`, `.ouroboros_env/` with `cache/ tmp/ home/`) | `ouroboros/marketplace/*`, `launcher_bootstrap.py` seed, agent self-authoring | provenance sidecars `schema_version: 1`; env `fingerprint.json: 1` | no age GC (payloads are installed software); staging rmtree'd per install, crash orphans recognized by name fragments; package caches live with the skill | bucket recreated empty; native seeds NOT re-seeded (deletion intent preserved) except post-bootstrap set; orphaned `state/skills/` rows keep grants + tombstone only after the CPL4-C11 sweep | +| `skills/{native,clawhub,ouroboroshub,external}//**` (+`.staging/`, `.ouroboros_env/` with `cache/ tmp/ home/`) | `ouroboros/marketplace/*`, `launcher_bootstrap.py` seed, agent self-authoring | provenance sidecars `schema_version: 1`; env `fingerprint.json: 1` | no age GC (payloads are installed software); staging rmtree'd per install, crash orphans recognized by name fragments; package caches live with the skill | bucket recreated empty; native seeds NOT re-seeded (deletion intent preserved) except post-bootstrap set; orphaned `state/skills/` rows keep grants + tombstone after the startup sweep | | `state/skills//dependency_cache/` | `marketplace/isolated_deps.py` via existing installer subprocesses and verified resource downloads | downloaded resources keyed by sha256; package-manager cache formats | reused across payload/environment replacement; follows existing skill-state cleanup | resources are verified/downloaded again and package caches rebuilt | | `state/skills//go/`, `state/skills//go-cache/` | Go compiler launched by `tools/skill_exec.py:_run_go_skill`, with GOPATH/GOCACHE bound to skill_state_dir | Go-owned cache formats | reused across script runs; follows existing skill-state cleanup | compiler recreates caches; skill payload and review remain unchanged | -| `task_results/.json` | `ouroboros/task_results.py` (locked merge) | `_schema_version: 1` (ABI-2); unstamped/future/malformed → quarantine, no conversion (Q8=B) — with the ONE carve-out (owner 4A): the boot latch migration re-stamps unstamped rows still at `cancel_requested` in place (same status, one typed `task_result_cancel_latch_admitted` event) so a wedged task still reaches its `cancelled` terminal | UNBOUNDED — one file per task forever, no GC — RATIFIED for 7.0 (CPL4-C19, owner batch №8 5A: lifecycle authority stays eternal deliberately; any future prune needs a fresh owner decision) | lifecycle authority lost; drive prunes degrade to age-only; strict authority reads break | +| `task_results/.json` | `ouroboros/task_results.py` (locked merge) | `_schema_version: 1` (ABI-2); unstamped/future/malformed → quarantine, no conversion — with one carve-out: the boot latch migration re-stamps unstamped rows still at `cancel_requested` in place (same status, one typed `task_result_cancel_latch_admitted` event) so a wedged task still reaches its `cancelled` terminal | UNBOUNDED — one file per task forever, no GC; lifecycle authority is retained deliberately | lifecycle authority lost; drive prunes degrade to age-only; strict authority reads break | | `task_results/quarantine/` | `ouroboros/task_result_schema.py` (same-dir rename) | quarantined bytes unchanged | NEVER GC'd (pinned); recovery is manual owner re-stamp | quarantined evidence lost | | `task_results/artifacts//**` (+`verification_receipts.jsonl`, `.directory.*.tmp`, `.directory.*.json.tmp`), `task_results/artifact_versions/` | `ouroboros/artifacts.py`, `headless.py`, `outcome_receipt_store.py` | artifact and complete directory manifests `schema_version: 1`; scratch manifest 2 | artifact versions bounded (5 per name); artifacts live with their result; directory capture stages ZIP and manifest beside the result, removing owned temporaries on caught failures | deliverable bytes lost; results keep dangling manifests | -| `task_drives//**` (+`tmp_scripts/`) | `ouroboros/headless.py`, `tools/tool_context.py`, `tools/shell.py` | child stamps as above | GC-retention prune at startup (terminal + age, default 7 d); the `data/tmp_scripts` fallback's hard-kill orphans are in `sweep_stale_temp_files` scope (CPL4-C20, startup-only when no script can be live) | scratch lost; canonical artifacts survive | +| `task_drives//**` (+`tmp_scripts/`) | `ouroboros/headless.py`, `tools/tool_context.py`, `tools/shell.py` | child stamps as above | GC-retention prune at startup (terminal + age, default 7 d); the `data/tmp_scripts` fallback's hard-kill orphans are in `sweep_stale_temp_files` scope (startup-only when no script can be live) | scratch lost; canonical artifacts survive | | `task_trees//blackboard.jsonl` | `ouroboros/task_tree_ledger.py` | rows unversioned; snapshot digest `schema_version: 1` | GC-retention prune at startup (root terminal + age) | swarm coordination facts lost for live trees | | `state/subagent_worktrees.json` (registry; checkouts live OUTSIDE data root) | `ouroboros/subagent_worktrees.py` | none — malformed → typed refusal (absent = empty is the designed asymmetry) — accepted; file_baseline retains copied binary input identities | prune_orphans (age + missing checkout; skips delegated_exec) + custody-cross-checked snapshot prune (fail-closed on unreadable custody) | permanent leak of checkouts + pinned refs (nothing else names them) | | `task_results/artifacts//delegated_runs//engine-files-manifest.json`, `task_results/artifacts//delegated_runs//*-*` | `ouroboros/delegate_directory.py`, existing streamed task-artifact owner | exact engine manifest SHA and per-file before/after digests | canonical result artifacts retained; engine keeps unapplied copy results under its existing disposition/retention owner | loss of complete binary results, baseline references and evidence after execution copy cleanup | -| `uploads/**` (+`screenshots/`, `views/`, atomic-copy `.tmp` files) | `ouroboros/gateway/files.py` through `artifacts.copy_artifact_file` for chat uploads; `tools/browser.py`, `tools/vision.py`, `server_owner_routing.py` | raw owner bytes; chat ingestion measures size and SHA256 while copying | owner attachments in the `uploads/` root: NO retention, owner-explicit delete only; chat upload no longer applies the former 50 MiB cap, while Files-browser and downstream transport limits retain their own contracts; agent-generated `screenshots/`/`views/` age out past GC retention at startup (CPL4-C21, owner 6A) | chat attachments dangle (readers skip missing); staged task copies survive | +| `uploads/**` (+`screenshots/`, `views/`, atomic-copy `.tmp` files) | `ouroboros/gateway/files.py` through `artifacts.copy_artifact_file` for chat uploads; `tools/browser.py`, `tools/vision.py`, `server_owner_routing.py` | raw owner bytes; chat ingestion measures size and SHA256 while copying | owner attachments in the `uploads/` root: NO retention, owner-explicit delete only; chat upload no longer applies the former 50 MiB cap, while Files-browser and downstream transport limits retain their own contracts; agent-generated `screenshots/`/`views/` age out past GC retention at startup | chat attachments dangle (readers skip missing); staged task copies survive | | `services//*.log` | `ouroboros/workspace_executor.py`, `tools/services.py` | none — raw text; archived content becomes observability blob with events.jsonl receipt | GC-retention prune at startup (archive-then-unlink); per-task terminal archive; oversize logs retained live | live tails lost; archived blobs survive | | `projects//**` (knowledge, journal, workpad, reflections, `.project.json` marker) | `ouroboros/project_facts.py`, `projects_registry.py` | marker unversioned; registry stamped (§2) | NEVER age-pruned (owner curates; delete = durable tombstone) | per-project memory lost; registry row survives, room reappears empty | | `projects//knowledge_history.jsonl`, `projects//knowledge_journal.jsonl` | `ouroboros/knowledge.py` through the shared knowledge write lock | append rows retain source topic, revision and operation facts | follows the owning project shelf; retained with the project until explicit deletion | history/provenance lost while authored project notes remain | | `archive/**` (rotated segments, `rescue/`, `usage_import/`, `managed_repo/`) | rotation + `supervisor/git_ops_rescue.py`, `usage_legacy_import.py`, `launcher_bootstrap.py` | segments inherit source shape; usage_import carries sha256 sidecar | UNBOUNDED BY DESIGN — durable history, never GC'd (P1) — accepted | memory horizon truncated; rescue copies of uncommitted work destroyed | -| `archive/usage_ledger/segment_*.jsonl` | `ouroboros/usage_compaction.py` (CPL4-C6: exact pre-compaction ledger bytes, written + fsync'd BEFORE the live swap) | each segment is a whole valid ledger generation; hash-pinned by the live `usage_baseline` header (`source_sha256`), chained recursively through each segment's own leading header | UNBOUNDED BY DESIGN — the folded monetary history, never GC'd (P1); read via `archived_attempt_ids` (tamper-evident, per-attempt joins for the CPL-5 reverse sweep) | folded per-attempt monetary history unrecoverable; live aggregates (baseline block) survive, but seal/attempt joins for folded ids break — the mirror of the ledger row: deleting the ARCHIVE alone under a stamped ledger is a typed chain break on every history question, deleting the LEDGER alone beside a surviving archive a permanent `generation newer` verdict; reset both together | -| `observability/{calls,blobs,salvaged}/**` | `ouroboros/observability.py` (private 0700/0600, CAS gzip); CPL-5 twins beside the call manifests: `model_send_seal` block + write-once `.model_send_violation.json` typed facts (`ouroboros/model_send_seal.py`) | call manifests `schema_version: 1` + custody/redaction honesty markers; blob refs sha-verified on read; `model_send_seal.seal_version: 1` with the `canonical_json_v1` basis string | preserved indefinitely BY CONTRACT (the startup census counts, never deletes); the inert `OUROBOROS_OBSERVABILITY_RETENTION_DAYS` knob is RETIRED (CPL4-C22, owner 7A; key in `RETIRED_SETTING_KEYS`) | every recorded `result_ref`/`manifest_ref` dangles (strict readers raise); salvaged outputs unrecoverable; a lost seal on a seam-dispatched attempt surfaces as a typed `unlogged_attempt` fact at the next startup sweep | +| `archive/usage_ledger/segment_*.jsonl` | `ouroboros/usage_compaction.py` (exact pre-compaction ledger bytes, written + fsync'd BEFORE the live swap) | each segment is a whole valid ledger generation; hash-pinned by the live `usage_baseline` header (`source_sha256`), chained recursively through each segment's own leading header | UNBOUNDED BY DESIGN — the folded monetary history, never GC'd (P1); read via `archived_attempt_ids` (tamper-evident, per-attempt joins for the model-send reverse sweep) | folded per-attempt monetary history unrecoverable; live aggregates (baseline block) survive, but seal/attempt joins for folded ids break — the mirror of the ledger row: deleting the ARCHIVE alone under a stamped ledger is a typed chain break on every history question, deleting the LEDGER alone raises `generation newer` for surviving newer, non-prefix segments; once fresh compactions reach those generations, old unreferenced segments are skipped and their attempt IDs are absent ([history readers](USAGE_COMPACTION.md#10-history-readers-model-send-reconciliation-and-audits)); reset both together | +| `observability/{calls,blobs,salvaged}/**` | `ouroboros/observability.py` (private 0700/0600, CAS gzip); model-send records beside the call manifests: `model_send_seal` block + write-once `.model_send_violation.json` typed facts (`ouroboros/model_send_seal.py`) | call manifests `schema_version: 1` + custody/redaction honesty markers; blob refs sha-verified on read; `model_send_seal.seal_version: 1` with the `canonical_json_v1` basis string | preserved indefinitely BY CONTRACT (the startup census counts, never deletes); the inert `OUROBOROS_OBSERVABILITY_RETENTION_DAYS` knob is RETIRED (key in `RETIRED_SETTING_KEYS`) | every recorded `result_ref`/`manifest_ref` dangles (strict readers raise); salvaged outputs unrecoverable; a lost seal on a seam-dispatched attempt surfaces as a typed `unlogged_attempt` fact at the next startup sweep | | `claudexor/**` | EXTERNAL writer — the claudexord daemon (Ouroboros only mkdirs, appends `daemon.log`, writes `ouroboros-owned.json` marker) | marker unversioned | daemon-owned; grows unbounded under our root — disclosed external plane | owner harness logins/profiles lost (fresh device-auth required) | | `playwright-browsers/` | `ouroboros/tools/browser.py` (vendor install) | none — vendor tree | no GC — accepted (vendor cache) | re-downloaded on next browser use | diff --git a/docs/v7next/DESIGN_USAGE_COMPACTION.md b/docs/USAGE_COMPACTION.md similarity index 90% rename from docs/v7next/DESIGN_USAGE_COMPACTION.md rename to docs/USAGE_COMPACTION.md index bfbb4b535..a2b0b72ab 100644 --- a/docs/v7next/DESIGN_USAGE_COMPACTION.md +++ b/docs/USAGE_COMPACTION.md @@ -1,23 +1,23 @@ -# Design note — usage-ledger compaction (CPL4-C6, monetary authority) +# Usage-ledger compaction and monetary authority -Owner sanction: batch №8 item 1A (2026-09-01) — "seq-preserving compaction -snapshot (settled rows folded into a stamped baseline row + archive of the raw -segment)", excised from the CPL-4 persistence train into its own reviewed lane -because the ledger is the monetary authority. +Terminal usage history is compacted into a stamped baseline block while the +exact original ledger bytes remain in an append-only archive. The ledger is +the monetary authority, so compaction preserves exact money, attribution, +in-flight attempts and historical joins. ## 1. Problem `state/usage_attempts.jsonl` is the append-only monetary authority (`ouroboros/usage_ledger.py`). Every reservation re-reads it under the cross-process monetary lock; a ~20 MB ledger costs ~0.5 s per full re-read -under that lock (the 2026-07-23 lock-timeout incident; -`USAGE_LEDGER_WARN_BYTES` in `ouroboros/context_budget.py` warns at exactly -that point). The in-process warm caches (#129) bound the *steady-state* cost, +under that lock. `USAGE_LEDGER_WARN_BYTES` in +`ouroboros/context_budget.py` warns at that measured degradation point. +The in-process warm caches bound the *steady-state* cost, but every cold read (process start, refold on any doubt) still replays the -whole file, and the file grows without bound: each physical attempt appends a -2–4 row lifecycle chain that stays forever after it is terminal. +whole file. Without compaction the live file grows without bound: each +physical attempt appends a 2–4 row lifecycle chain that remains after it is terminal. -## 2. Sanctioned shape +## 2. Compacted representation Fold the terminal history into a **stamped baseline block** at the head of the ledger and move the raw pre-compaction bytes, verbatim, into an append-only @@ -32,7 +32,7 @@ validated aggregate plus every row that is still live. | `kind="attempt"`, final state `settled` / `unresolved` / `released`, and **no review attribution** (`review_skill`/`review_wave_id`/`review_slot_id` all empty) | folded (their whole seq chain) | terminal, id never re-asserted by any writer (`attempt_id` is a one-shot uuid4 minted at reserve time); aggregation-complete under §5 | | `kind="attempt"`, final state `reserved` / `dispatched` (in-flight) | **retained verbatim** | INVARIANT: in-flight/unsettled rows are never folded — their terminal transition still has to join them by `attempt_id` in the live replay | | `usage_baseline` / `usage_baseline_group` from a previous compaction | re-folded (header replaced, groups merged by key, exact-decimal sums added) | baselines must not accumulate per epoch | -| `kind="subscription_session"`, `"external_unmetered"` | **retained** | their `attempt_id` is deterministically re-derived from a stable external id and re-asserted on replay: `_append_single_settled_row` dedups and conflict-checks against the LIVE replay. Folding them would turn an idempotent replay into a silent double charge. Disclosed residual: these rows keep growing (slowly — one row per delegated run / external dispatch); a future lane may fold them behind an archived-identity membership check. | +| `kind="subscription_session"`, `"external_unmetered"` | **retained** | their `attempt_id` is deterministically re-derived from a stable external id and re-asserted on replay: `_append_single_settled_row` dedups and conflict-checks against the LIVE replay. Folding them would turn an idempotent replay into a silent double charge. Disclosed residual: these rows keep growing (slowly — one row per delegated run / external dispatch). | | `kind="legacy_*"` | **retained** | same idempotency argument: `ensure_legacy_imported` dedups candidate rows against live `attempt_id`s if the completion watermark is ever lost mid-history. Bounded one-time set. | | attempts with review attribution | **retained** | `skill_review_usage` projects historical waves per-attempt (`attempt_ids`, `attempts` lists) for durable review receipts; folding would erase that projection. Disclosed residual (skill-review waves only; ordinary task/review traffic carries no `review_*` attribution). | | unknown future kinds | **retained** | fail-safe default: fold only what this design proves aggregation-complete | @@ -83,7 +83,7 @@ carrying: the key fields verbatim; `folded_attempt_count` (int ≥ 1); `root_limit_usd` = min over the group's known values (else absent); `baseline_id` joining the header; empty `review_*` attribution. -Why per-group rows and not the literally single row of the sanction sketch: +Why per-group rows rather than a single global aggregate: budget enforcement is **per-root** (`reserve_attempt` filters finals by `root_task_id`; `usage_projection` takes `min` of row `root_limit_usd`), and `usage_breakdown` groups by model/provider/category/task/root. A single global @@ -148,7 +148,7 @@ so instead the compacted file starts a fresh dense epoch: - the header records `source_first_seq`/`source_last_seq`, and the archive segment holds every original row with its original `seq` untouched. -Monotonicity and density are preserved (the lane invariant); the original seq +Monotonicity and density are preserved; the original seq values are never lost (archive + `pre_compaction_seq`). Nothing durable references ledger rows by `seq` (cross-references are `attempt_id`s); resume fingerprints are invalidated structurally by the inode change (§8). @@ -187,7 +187,7 @@ thing that decides what a well-formed row IS — checks it: ## 7. Aggregation contract (`_usage_rows`) -`_summary` and `_physical_call_count`/`_breakdown_bucket` become +`_summary` and `_physical_call_count`/`_breakdown_bucket` are baseline-aware in the narrowest way: - `usage_baseline` header: skipped (no money, no counts); @@ -195,7 +195,7 @@ baseline-aware in the narrowest way: `unknown_unmetered`, `non_final_rows`, physical calls, `prompt_cache_ttls`) uses `weight = folded_attempt_count`; every **sum** adds the row's carried aggregate once. For all existing kinds `weight == 1` and the code path is - byte-equivalent to today's. + unchanged. The group key (§4) makes each group homogeneous in every branch predicate `_summary` evaluates per row (`cost is None`, `cost_final`, @@ -221,14 +221,12 @@ exactly the per-row branch taken `weight` times with the sums pre-added. tier structurally unreachable there: a Windows volume without byte-range locks would fail EVERY monetary append closed instead of degrading to it. `ENOLCK` ("no locks available" — a filesystem without a lock daemon, or an - exhausted kernel lock table) is the third answer (round 5.4 close-out; round - 5.4 proper made it fail EVERY caller closed, which the lenses showed to be - product-wide: the same primitive locks state singletons, task results and - custody, so a lockd-less NFS `state/` would have stopped every locked write - and every model dispatch — a capability the name protocol had always - provided there): it selects the **name tier** like a filesystem that cannot, - but the probe RECORDS the errno beside the verdict, and a caller may refuse - that tier by errno (`acquire_exclusive_file_lock(refuse_name_tier_errnos=…)`). + exhausted kernel lock table) selects the **name tier** too. Making this + failure close every caller would stop locked state writes, task-result and + custody updates, and model dispatch on a lockd-less filesystem, although + those non-monetary consumers can use the name protocol. The probe records + the errno beside its verdict, and each caller can refuse that tier by errno + (`acquire_exclusive_file_lock(refuse_name_tier_errnos=…)`). Only the monetary lock does: `usage_ledger._named_lock` names `ENOLCK`, so on such an install every monetary write refuses typed (`UsageAccountingError` — no lock, no append, no pass; money never runs the name protocol where locks @@ -239,21 +237,14 @@ exactly the per-row branch taken `weight` times with the sums pre-added. probes that could disagree — except a directory where the scratch probe cannot be created, which answers enforced for that call and is probed again next time (not cached). - **Windows takes the ENFORCED tier in 7.0, on a byte range beyond the stamp.** - Its first shape could not ship. The 3-OS matrix on `bf8b6549` - (run 33654743857) locked the WHOLE file, and a Windows byte-range lock is - MANDATORY: a contender that opened the held lock file to read the owner's - stamp was refused the READ, could never judge the hold and waited out its - timeout — eight concurrent monetary writers all answered «lock unavailable», - `update_json_locked` timed out, a concurrent chat append was lost. - `kernel_file_locks_enforced` was made to answer False there (abea91ec), which - moved the defect rather than closing it: this design's name tier probes - identity and stamp on every poll (the pre-C6 protocol only `stat`ed), and on - Windows that contender handle made the owner's release unlink fail with a - sharing violation (no FILE_SHARE_DELETE), orphaning the lock with a live pid - until `_unlink_lock_path` retried the transient refusal for a bounded window - (run 33663258606). The owner then made the working tier a release condition - (batch №13 item 1, 2026-09-02), and it is back: the hold is ONE byte at + **Windows takes the enforced tier on a byte range beyond the stamp.** + Windows byte-range locks are mandatory: locking the whole file prevents a + contender from reading the owner's stamp, so it cannot judge the hold and + times out. Falling back to the name tier would expose another constraint: + the contender's open handle can prevent the owner's release unlink through + a sharing violation (no `FILE_SHARE_DELETE`). `_unlink_lock_path` retries + that transient refusal for a bounded window. The enforced-tier hold is one + byte at `platform_layer._WIN32_LOCK_OFFSET` (`0x7FFFFFFF00000000`, length 1 — the common Win32 idiom; a lock beyond end-of-file is legal there and no lock file's one-line stamp can reach that far), so the bytes a contender reads, @@ -277,9 +268,8 @@ exactly the per-row branch taken `weight` times with the sums pre-added. retries a contender's transient refusal). What Windows still does not have on this tier is named elsewhere in this section and unchanged: no directory fsync, and no old-inode witness across `os.replace`, so a charge landed in - the swap's last syscall is lost silently there. The Windows-EXECUTED proof is - the CI matrix, which is the only Windows host this work has: the Linux-side - pins (the range constant and its two wrappers, an emulated LockFileEx that + the swap's last syscall is lost silently there. Windows execution is checked + by the CI matrix; host-side pins (the range constant and its two wrappers, an emulated LockFileEx that refuses the same range, the delete-semantics simulator) stand in for the mechanism, never for the platform. *Enforced tier* (POSIX `fcntl.flock`, Windows `LockFileEx` — @@ -339,15 +329,11 @@ exactly the per-row branch taken `weight` times with the sums pre-added. inode. It fails closed, and the file we stamped with our LIVE pid is removed with it when its bytes are still exactly the ones we wrote — left behind, no owner-aware reclaimer could ever evict it and the lock wedges for good. - **Residual, disclosed (mechanism corrected in round 5.4):** the owner-aware - rule asks `pid_is_alive(owner_pid)`, and a RECYCLED pid — one a live - process now owns — reads as alive whoever owns it: `kill(0)` succeeds for - a same-uid impostor and answers EPERM for another user's, which round 5.4 - made "alive" too (it read as "dead" before, so another user's recycle was - reclaimed through the age path — the probe flock guarding it on the - enforced tier — while only a same-uid recycle wedged; this note named the - opposite mechanism). So a lock whose owner died and whose pid was reused - is never reclaimed by age while the impostor lives (`pid_is_alive` is the + **Residual, disclosed:** the owner-aware rule asks + `pid_is_alive(owner_pid)`, and a recycled PID reads as alive whoever now + owns it: `kill(0)` succeeds for the same user and answers `EPERM` for + another user's process; both mean alive. A lock whose owner died and whose + PID was reused is never reclaimed by age while the impostor lives (`pid_is_alive` is the ONE liveness primitive, shared by every consumer — custody settlements, claim reclaims, staging reaps — so a pid recycled onto another user's process reads alive everywhere and those defer while the impostor lives, @@ -368,11 +354,9 @@ exactly the per-row branch taken `weight` times with the sums pre-added. checkpoint, immediately before every rename attempt and once more AFTER the in-swap snapshot look, so the irreducible residual is the interval between that last proof and the rename syscall: a charge the robber lands - inside it is ERASED by the swap. Until round 5.4 this note claimed the - post-swap re-read or the next read's seq quarantine would surface it; - neither can — the re-read compares the NEW inode against the candidate and - the archive segment is the pre-row snapshot — so the loss was silent and - the pass returned a success receipt. Now, on POSIX, the swap holds the OLD + inside it is erased by the swap. A post-swap re-read cannot detect that + loss: it compares the new inode against the candidate, while the archive + contains the pre-row snapshot. On POSIX the swap therefore holds the old inode open across the rename (the only witness left) and reads whatever landed beyond the proven snapshot's length AFTER the fact: those bytes go to `state/usage_attempts.quarantine.jsonl` (`raw_base64`, the shape a torn @@ -492,11 +476,13 @@ exactly the per-row branch taken `weight` times with the sums pre-added. mechanism, exactly like the rotation-bounded log warns: it now fires only if compaction is broken or the unfoldable residue itself reaches 20 MB. -## 10. History readers: CPL-5 reconcile sweep, audits +## 10. History readers: model-send reconciliation and audits -CPL-5 (`DESIGN_MODEL_VISIBLE_LOGGED.md` §3.3, implementation landed on the integration tip as `ouroboros/model_send_seal.py`, wired and swept) reconciles `model_send` seals against "an attempt row in the -usage-accounting replay". After compaction a folded attempt is no longer in -the live replay, so this lane ships the join surface the sweep must use: +The reverse reconciliation in +[Model-send observability](MODEL_SEND_OBSERVABILITY.md#33-reverse-direction-audit-model_send-only) +(`ouroboros/model_send_seal.py`) joins model-send seals to usage-accounting +attempts. A folded attempt is absent from the live replay, so the sweep uses +the archive-aware join: - `usage_compaction.archived_attempt_ids(root)` — the `attempt_id` set of every archived segment, walked through the tamper-evident header chain @@ -560,7 +546,7 @@ the live replay, so this lane ships the join surface the sweep must use: open is the step a directory refuses and a writer-less FIFO blocks on. A first row that reads but does not parse is a torn segment from a crashed write: no evidence of any generation, left to the walk. Every path - inspection the reader makes is typed the same way (round 5.4): `pathlib` + inspection the reader makes is typed the same way: `pathlib` re-raises every `OSError` but `ENOENT`/`ENOTDIR`/`EBADF`/`ELOOP` from `is_symlink`/`is_dir`, so the symlink bounds on both archive levels and on the named segment (an `archive/usage_ledger` readable but not @@ -639,7 +625,7 @@ the live replay, so this lane ships the join surface the sweep must use: stamp-less file too, and the wrong answer there turns a folded attempt into a reported orphan seal. -Contract for the CPL-5 lane (recorded here and in the review packet): the +The model-send reconciliation contract is that the reverse sweep's "no attempt row" verdict (`orphan_seal`) must consult this union, not the live replay alone; an unreadable/mismatched segment is the sweep's existing UNKNOWN → skip-pass case (fail-soft, the API raises typed @@ -651,11 +637,11 @@ existing live-replay dedup keeps working under a lost watermark. ## 11. Module placement -New leaf `ouroboros/usage_compaction.py` (domain D16): fold policy + +`ouroboros/usage_compaction.py` (domain D16) owns fold policy + archive/verify/swap + history readers. It imports FROM `usage_ledger` (substrate) and `_usage_rows` (aggregation leaf); `usage_accounting` calls INTO it from `reserve_attempt`. The substrate stays policy-free (it learns -only the new row kinds' validation), the one-way seam +only the baseline row kinds' validation), the one-way seam `usage_ledger ← usage_accounting` is unchanged, and the compactor — which must know the aggregation semantics — lives beside the aggregation, not inside the byte authority. @@ -671,7 +657,7 @@ tests/fixtures_usage_compaction.py) limits) and of `usage_breakdown` (all axes) renders equal dicts: state counts and folded weights, physical calls, token sums, finality, subscription sessions, per-root limits, every axis shape. The float dollars - those renders carry are deliberately NOT part of that equality (R2-37). + those renders carry are deliberately NOT part of that equality. Readers round money at six places, so one history summed per row and summed per group can land on either side of that boundary. The regression fixture in `tests/test_usage_compaction_fingerprint.py` observes a 1e-6 USD shift, @@ -696,7 +682,7 @@ tests/fixtures_usage_compaction.py) and the ledger's directory after it. 4. **Budget limits are preserved**: root/global enforcement thresholds are unchanged across compaction. -5. **CPL-5 join survives**: every pre-compaction `attempt_id` remains +5. **Model-send join survives**: every pre-compaction `attempt_id` remains resolvable through live ∪ archive, across chained compactions; a tampered segment, a re-hashed but structurally broken segment, a deleted segment behind a warm cache, a same-size rewrite once the cache window closes, an @@ -777,11 +763,3 @@ tests/fixtures_usage_compaction.py) than the live file's own (bar an uncommitted orphan of it, proven by still being a prefix of that file), whether or not the live file carries a stamp. - -## 13. Explicitly out of scope - -- Folding subscription/external/legacy/review-attributed rows (disclosed - residuals, §3). -- Any GC of archive segments or the quarantine file (append-only, never). -- Changes to the CPL-5 sweep beyond the archive-aware membership join in §10. -- Changing `USAGE_LEDGER_WARN_BYTES` or the lock timeouts. diff --git a/docs/architecture/01-high-level-architecture.md b/docs/architecture/01-high-level-architecture.md index 5620dc4e9..026ae9105 100644 --- a/docs/architecture/01-high-level-architecture.md +++ b/docs/architecture/01-high-level-architecture.md @@ -130,7 +130,7 @@ server.py (Starlette+uvicorn) ← HTTP + WebSocket on configurable host:port (de ├── _outcome_receipts.py ← Receipt parsing and the ONE canonical receipt identity (`receipt_canonical_identity` → `ReceiptIdentity`; invariant in §10): three independent components — `criterion_id`; structurally canonical `check` text PAIRED with its `check_rendering` stamp (quoted shell punctuation is data, not syntax, and receipts from different renderings are never the same verification — the stored string alone cannot say which renderer wrote it); and the raw-sorted `canonical_path_set` (whitespace untouched — a leading space is a legal filename byte); `ReceiptIdentity.key` selects ONE typed (kind, value) and sameness is that key's equality, never a match across kinds — the parts are disclosures, never the comparison; the per-kind normalization answer lives in the closed `IDENTITY_KINDS`/`KIND_NORMALIZES_COMMAND_TEXT` table, so a fourth kind must state its own answer in its own row rather than inherit a default; the outstanding sets `unreconciled_failed`/`unreconciled_masked` scan every candidate against ALL later reconcilers and collapse repeated failures of one check onto the freshest receipt; the shared disclosed projections (`receipt_identity_projection`, `disclosed_list_projection`) make every bound explicit — exact omitted counts plus a hash over the injective serialization, string bounding via the SSOT `utils.truncate_review_artifact`, never a hand-rolled slice; `verification_receipt_ledger_row` splats that projection, so a new receipt key is dropped unless added there ├── _outcome_tool_errors.py ← Leaf SSOT for tool-trace status vocabularies and execution-axis classification; outcomes.py re-exports ├── code_intelligence.py ← Internal code inventory: derived-only file facts, hashes, polyglot symbol/import/call extraction via tree-sitter with Python on stdlib `ast`, a visible `structural_unavailable` fallback when a grammar is missing, and an incremental JSON cache (no raw source) - ├── code_intelligence_architecture.py ← Architecture facts over the pinned domain/contract/persistence carriers: `owner_of`, the domain quotient, and the facade inventory (`docs/v7next/FACADE_INVENTORY.md`) + ├── code_intelligence_architecture.py ← Architecture facts over the pinned domain/contract/persistence carriers: `owner_of`, the domain quotient, and the facade inventory (`docs/inventories/FACADE_INVENTORY.md`) ├── code_search_rg.py ← Optional ripgrep-backed search for search_code; every match is post-filtered through the protected/secret gates ├── pricing.py ← Exact-route best-effort provider-catalog lookup with nullable estimates; no static model tariffs (they go stale) and not the monetary ledger ├── usage_accounting.py ← Append-only physical-model-attempt monetary authority: reserved→dispatched→settled|unresolved (or reserved→released), short cross-process check+append+fsync lock, conservative global/root admission, validated replay/torn-tail quarantine, compatibility projections, resumable legacy import; application candidates carry exact raw/context identities + a pre-dispatch manifest on the same attempt id @@ -651,7 +651,7 @@ Bundled resources use the §1 CLI/headless lookup order rather than assuming the └── ouroboros.pid ← launcher PID lock; platform lock auto-released on crash ``` -Every entry of this tree is probed against the tree and the runtime sources by the generated `docs/v7next/DATA_LAYOUT_INVENTORY.md`, so a durable file renamed in code while its row here survives is red, not silent. +Every entry of this tree is probed against the tree and the runtime sources by the generated `docs/inventories/DATA_LAYOUT_INVENTORY.md`, so a durable file renamed in code while its row here survives is red, not silent. --- diff --git a/docs/architecture/06-agent-core.md b/docs/architecture/06-agent-core.md index 89da2ccfb..89f02d463 100644 --- a/docs/architecture/06-agent-core.md +++ b/docs/architecture/06-agent-core.md @@ -653,7 +653,7 @@ Rationale: diff reviewers catch line-level mistakes; the scope reviewer catches Structural smoke gates are a deterministic BIBLE P3 codebase-size component. `ouroboros/review.py::iter_gated_modules` is the one source inventory for smoke, `codebase_health`, census, and the UTF-8 byte gate (Python everywhere plus first-party `web/**/*.js`, vendored/minified excluded). `ouroboros/size_ratchet_manifest.py` is a generated, data-only debt register consumed through AST literals: exact module debt above 1600 lines, exact function debt above 300 lines, the 1001–1500 band with rationale authority, and exact byte debt above 200,000 UTF-8 bytes. `validate_size_ratchet` proves the live and staged manifests exact against their trees and shrink-only against the merge-aware committed authority — no first-parent history replay: the previous manifest resolves merge-aware from `HEAD` or any of its parents, and a checkout with no committed manifest anywhere bootstraps from its own tree, so a fork whose local line predates the manifest is never condemned by inherited topology. Official-repository CI runs the blocking `size_ratchet` pytest lane while every local surface reports the same findings as warnings; two disclosed residuals — pairwise validation covers only the base→HEAD interval, and the official block presupposes branch protection. Within a validated pair, debt can shrink but cannot be swapped, re-entered, grow on the byte axis, or survive stale. `MAX_TOTAL_FUNCTIONS` (`ouroboros/review.py`) remains the coarse runtime ceiling. A deterministic hot-store growth invariant sits beside these gates: `agent_startup_checks.py::hot_store_growth_notes` (surfaced by `context_health.py::build_health_invariants` and once per worker boot) stats eight hot stores plus the `archive/chat_*.jsonl` aggregate — `state/usage_attempts.jsonl`, `logs/events.jsonl`, `logs/tools.jsonl`, `logs/supervisor.jsonl`, `logs/task_reflections.jsonl`, `logs/progress.jsonl`, `state/scheduled_tasks.json`, and `state/skill_review_root_tasks.jsonl` — against justified byte thresholds in `ouroboros/context_budget.py` and emits a WARNING with a remediation pointer. -Three gen/verify inventories ride the same discipline as the size manifest (generator `scripts/regenerate_inventories.py`, verify `tests/test_generated_inventories.py`, staleness = red): the frozen-contracts inventory (`docs/v7next/FROZEN_CONTRACTS_INVENTORY.md`, a machine extraction of §11.1 with every owner/anchor path resolved against the tree and the `ouroboros/contracts/` package-coverage gap pinned), the data-layout inventory (`docs/v7next/DATA_LAYOUT_INVENTORY.md`, every entry of the §1 "Data layout" tree probed as a tracked repo path or a runtime-source literal, so a durable file renamed in code while its tree row survives turns red), and the facade inventory (`docs/v7next/FACADE_INVENTORY.md`, the AST-derived `noqa: F401` re-export surface with per-leaf domains from `ouroboros/domains.toml`). +Three gen/verify inventories ride the same discipline as the size manifest (generator `scripts/regenerate_inventories.py`, verify `tests/test_generated_inventories.py`, staleness = red): the frozen-contracts inventory (`docs/inventories/FROZEN_CONTRACTS_INVENTORY.md`, a machine extraction of §11.1 with every owner/anchor path resolved against the tree and the `ouroboros/contracts/` package-coverage gap pinned), the data-layout inventory (`docs/inventories/DATA_LAYOUT_INVENTORY.md`, every entry of the §1 "Data layout" tree probed as a tracked repo path or a runtime-source literal, so a durable file renamed in code while its tree row survives turns red), and the facade inventory (`docs/inventories/FACADE_INVENTORY.md`, the AST-derived `noqa: F401` re-export surface with per-leaf domains from `ouroboros/domains.toml`). The shared hard prompt-size SSOT is `REVIEW_PROMPT_TOKEN_BUDGET = 920_000` in `review_helpers.py`. `review_context_atlas.py` targets 850K estimated prompt tokens for scope review and deep self-review, and the final 920K gate stays in each caller as the hard stop (plan review builds no Atlas: its packet is sized per slot by `plan_review_runtime.plan_slot_fit`). Scope review additionally reserves output headroom inside the reviewer's window (`_SCOPE_MAX_TOKENS` 100K plus a tokenizer margin), because provider accounting can exceed the local chars/4 estimator on atlas-heavy prompts and a physically rejected oversize prompt has no authoritative verdict; `scope_review.py` gates assembled input on `_SCOPE_INPUT_TOKEN_LIMIT = min(920K, window − _SCOPE_MAX_TOKENS − margin)`. diff --git a/docs/architecture/11-frozen-contracts-v1.md b/docs/architecture/11-frozen-contracts-v1.md index 635326adf..5c962efa2 100644 --- a/docs/architecture/11-frozen-contracts-v1.md +++ b/docs/architecture/11-frozen-contracts-v1.md @@ -84,11 +84,10 @@ Add the field to the active frozen owner — `ouroboros/contracts/` for the pack install that emits a machine-readable scope document naming every incompatibility it finds across the five frozen classes (gateway alias, retired setting, comma list, plugin API, schema stamp), snapping the retired-key lists at execution time instead of hardcoding them. - - *Deliberately NOT in this window:* the handler ABI — tool handlers returning `ToolResult` instead of `str` — - is backlog, so handler signatures are unchanged. The external-executor family is the one INTERIOR - exception and does not open that window: its producers, decorators and host consumers pass a native - `ToolResult`, while its four REGISTERED entries still publish a `str` projection through - `tool_result._publish_tool_result`. No handler signature and no other family changed. + - *Handler ABI.* Registered tool handlers return `str`. The external-executor family uses a native + `ToolResult` internally across its producers, decorators and host consumers, while its four + registered entries publish a `str` projection through `tool_result._publish_tool_result`. + This preserves the public handler signature. - `5.25.0-rc.4` retired the native skill upgrade migration banner API (`GET /api/migrations`, `POST /api/migrations/{key}/dismiss`, and `MigrationsResponse`). The migration note is the release row itself: dismissed banner state in `data/state/migrations.json` is intentionally ignored by current runtimes. diff --git a/docs/archive/v7next/C6_REVIEW_PACKET.md b/docs/archive/v7next/C6_REVIEW_PACKET.md deleted file mode 100644 index a3a19c9d5..000000000 --- a/docs/archive/v7next/C6_REVIEW_PACKET.md +++ /dev/null @@ -1,944 +0,0 @@ -# C6 review packet — monetary usage-ledger compaction (CPL4-C6, owner 1A) - -Lane: `v7next_c6`, base `74a03082`. Owner sanction: batch №8 item 1A -(2026-09-01) — compaction of `state/usage_attempts.jsonl` in its own reviewed -lane (monetary authority). Design note ratified before code: -`docs/v7next/DESIGN_USAGE_COMPACTION.md` (commit `a1063124`); implementation -+ pins in the follow-up commit; this packet + ledger section close the lane. -Round 2 (§6) is the fix-round for the external adversarial wave against -`e2801c52`. Round 3 (§7) is the fix-round for the second wave, against -`830aa35a`: five findings were re-opened as still-open (1, 2, 3, 4, 6) and -all five are fixed here; four (5, 7, 8, 9) the wave confirmed closed. -Round 4 (§8) is the fix-round for the third wave, against `d7b487ab`: the -lock's exclusion becomes kernel-enforced, ownership is proven adjacent to -every irreversible decision, the swap re-proves its snapshot inside the -atomic replace, and the archive symlink bound moves from check-then-use to -the open itself (dir-fd `O_NOFOLLOW`). Round 5 (§9) is the fix-round for the -fourth wave (gpt-5.6-sol, read-only), against `13af62c5`: the lock's tier -becomes an explicit capability predicate with a fail-closed enforced tier, -the swap re-proves ownership and snapshot before EVERY rename attempt, the -epoch anchor scans through the handle the chain walk held, the two surviving -mutations are pinned, and the doc absolutes are stated per tier. Round 5.2 -(§9, second block) is the fix-round for the adversarial lenses over round 5, -against `2dd3e017`: the acquisition itself is identity-checked, the -name-tier refusal becomes a durable typed event, the swap proves ownership -on both sides of its last look, the anchor scan classifies non-regular -entries and cannot hang, the anchor-swap pin covers the open-through-fd -half, LockFileEx refusals classify by their Win32 error, and the remaining -absolutes are bounded per tier. - -## 1. Diff map (what to read, in review order) - -| surface | change | why | -|---|---|---| -| `docs/v7next/DESIGN_USAGE_COMPACTION.md` | NEW — the ratified contract | invariants, fold scope, decimal rule, seq policy, crash order, trigger, CPL-5 join | -| `ouroboros/usage_compaction.py` | NEW leaf (D16, ~490 lines; round 5.2: `owned_and_intact` beats on both sides of its look, the typed durable `usage_ledger_compaction_refused` event, non-regular archive entries classified — `O_NONBLOCK` open, `S_ISREG` — and typed `_load_segment` failures; round 5.3: the heartbeat REQUIRED by both entry points, the orphan exemption a byte-prefix proof read from the classified descriptor, the anchor running on a stamp-less live file, the root handle typed, the name-tier mark following the landed row, the path-shape scan classifying before the open, the chain-union cache bounded — 1197 lines; round 5.4: the refusal mark following a True append only, every reader path inspection typed and the stamp-less check exact on ENOENT, the swap's old-inode witness quarantining an erased charge and raising instead of receipting, `_build_candidate`'s beat required — 1245 lines) | fold policy + prove-then-swap + archive + history readers; imports FROM `usage_ledger`/`_usage_rows`, called INTO by `usage_accounting` — the one-way substrate seam is unchanged | -| `ouroboros/usage_ledger.py` | `_validate_records` learns the two baseline kinds; round 5: `LOCK_REL` (lock-path SSOT) and the atomic writer routing its precondition through `replace_atomic` | head-only baseline block, exactly one header at seq 1, group rows joined by `baseline_id` + positive `folded_attempt_count`; a baseline row in an appended tail or after any non-baseline row = corrupt. Everything else (locking, append arithmetic, quarantine, resume fingerprints) is untouched | -| `ouroboros/_usage_rows.py` | `_summary` + `_physical_call_count` baseline-aware | header skipped; group rows: count axes × `folded_attempt_count`, sums added once. Weight-1 paths are byte-equivalent to the previous code (pure refactor for existing kinds) | -| `ouroboros/usage_accounting.py` | +5 lines in `reserve_attempt` | the opportunistic trigger under the already-held monetary lock; contained (never raises into the reservation) | -| `ouroboros/config.py` | `USAGE_LEDGER_COMPACT_BYTES` (8 MB), `USAGE_LEDGER_COMPACT_RETRY_GROWTH_BYTES` (1 MB) | trigger policy from config SSOT, no env knob | -| `ouroboros/agent_startup_checks.py`, `ouroboros/context_budget.py` | warn-text updates only; round 5.2: the tripwire names the name-tier refusal as a third cause and points at its event | the 20 MB WARN becomes the broken-compaction tripwire — and can tell the tiers apart | -| `ouroboros/domains.toml`, `docs/DOMAIN_MAP.md` | new module seated D16; graph regenerated via `check_domains.py --write` | manifest completeness gate | -| `docs/PERSISTENCE.md` | usage-ledger row rewritten (bounded by compaction); NEW `archive/usage_ledger/segment_*.jsonl` row | inventory truth; scan pin 123→124 in `tests/test_persistence_inventory.py` | -| `tests/test_gateway_abi3_removals.py` | one per-site allowlist row | `_build_candidate` writes the internal ledger-plane `cost_usd` key (same class as every existing ledger writer row there) | -| `tests/test_usage_compaction.py` | NEW pin suite (61 test items after round 5.3; the suite entered the 1001-1500 size band with a recorded rationale — it stood at 1512 lines for one commit, `79a1b9fb`, with the manifest stale, and the round-5.2 fold `6ad110e9` returned it inside the band without a new abstraction; 1492 after round 5.2 — and LEFT the band in round 5.3 at `e08a0392`: 1597 at the tip, in the ungated 1501-1600 zone, the manifest regenerated with it; the owner decision is stated in §9 "Round 5.3"; round 5.4: 66 items, back at 1597 after five new/extended pins — paid for by a `compacted` fixture folding twenty-one seed-then-compact preambles and by argument-list/data-literal reflows, no claim dropped) | see §3, §6, §7, §8 and §9 | -| `tests/test_lockfile_helpers.py` | +5 lock-ownership pins in round 3, +2 in round 4, +5 in round 5 (one Windows-only), +2 in round 5.2 (the lock-less creator; LockFileEx classification), +2 in round 5.3 (an unreadable own identity is never a hold; the design note's refusal sets) with the classification pin extended to the unsupported set, +3 in round 5.4 (a pid answering EPERM is alive and its aged lock is not reclaimed; ENOLCK keeps the enforced tier and the acquisition fails closed; two threads racing the first probe run one probe) with the probe pin's name-tier selector moved to EOPNOTSUPP and an ENOLCK clause, and the identity pin extended with the heartbeat's refresh clauses | the finding-1 fixes are platform primitives, so they are pinned where those primitives live | -| `ouroboros/platform_layer.py` | lock ownership: `_lock_identity`, inode-guarded stale eviction and release, ownership-reporting heartbeat; round 5: `kernel_file_locks_enforced` capability predicate (enforced vs name tier), fail-closed kernel refusal, LockFileEx error classification; round 5.2: the owner pid written BEFORE the kernel lock and a won lock returned only while the path still names it (an evicted creator re-contends), `_win32_lock_error` classifying ERROR_LOCK_VIOLATION alone as busy; round 5.3: a descriptor whose own identity cannot be read is never a hold (its live-pid stamp removed with it), `_WIN32_LOCK_ERRNOS` mapping ERROR_INVALID_FUNCTION / ERROR_NOT_SUPPORTED onto the unsupported set, both kill-tree sweeps on `force_kill_pid` (exactly 1500 lines); round 5.4: ENOLCK out of the unsupported set (fails closed), winerror 1 onto ENOSYS, the tier cache decided once under `_KERNEL_LOCK_TIER_LOCK`, `pid_is_alive` reading EPERM as alive with `pid_provably_gone` its one-line negation (1497 lines) | round 3, finding 1; round 5, finding 1; round 5.2, findings 1 and W; round 5.3, findings 1 and L5; round 5.4, R1 and R7 | -| `ouroboros/utils.py` | `replace_atomic(precondition=)`: the precondition is asked before EVERY attempt, the Windows sharing-violation retries included; returns False without replacing when refused | round 5, finding 2 | -| `ADOPTION_v7next.md` | CPL-4 row: C6 landed + verification hook | adoption gate | -| `docs/v7next/LEDGER_CORRECTIONS.md` | append-only C6 lane section | provenance | - -## 2. Invariants to verify adversarially - -1. **Money is decimal-exact.** Group sums are `Decimal`s of the exact JSON - literals, carried as exact-decimal strings (`_number` accepts strings at - every reader: validator, `_summary`, projections). Retained rows are - verified Decimal-identical across re-serialization; a non-round-trippable - foreign literal aborts the pass (never approximates). -2. **Prove-then-swap.** Commit happens only after the candidate bytes - (a) re-validate structurally and (b) render EQUAL dicts through the - production aggregation on every consumed surface (global summary, per-root - summaries + min `root_limit_usd`, breakdown buckets on all five axes) and - (c) match decimal money totals. Any inequality → abort → ledger - byte-identical. Compaction is an optimization; it can only decline. -3. **In-flight rows never fold** (reserved/dispatched finals keep their whole - chain, verbatim modulo seq) and their later transitions work unchanged. -4. **Idempotency-bearing kinds never fold** (subscription/external/legacy): - their replay dedup + conflict checks read the live replay only. This is - why their exclusion is structural, not an optimization choice. -5. **Crash-safety order**: archive segment written + fsync'd (file and every - directory entry the chain created; POSIX failure is fatal, Windows is a - disclosed no-op) BEFORE the atomic ledger swap, and the swap is refused if - the live file changed since the snapshot. Crash anywhere = valid ledger - (old or new generation); orphan segments harmless. -6. **seq policy**: dense-seq validation authority preserved by starting a - fresh epoch; original seqs survive in the archive and as - `pre_compaction_seq` on retained rows. Substrate append/resume arithmetic - (`len(records)`-based) is deliberately UNCHANGED — check this holds. -7. **Concurrency**: everything under the existing monetary lock (`_locked`), - which is owner-aware and heartbeaten so a long pass cannot be evicted by - elapsed time; cache coherence is structural (atomic swap → new inode → - every resume fingerprint refolds). No cooperative invalidation. -8. **CPL-5 join**: every pre-compaction `attempt_id` resolves through - live ∪ `archived_attempt_ids` (hash-chained, tamper-evident, each segment - bounded to the archive directory, revalidated as a ledger, cached per - immutable segment BY FINGERPRINT, chain epochs stepping down to 1). The - CPL-5 reverse sweep (NOT on this base — only its design note is) must - consult this union and treat typed corruption as UNKNOWN/skip; contract - recorded in the design note §10 and the ledger section. - -## 3. Pins (tests/test_usage_compaction.py, 31 tests; round-2 pins in §6) - -- exact money + whole-projection equality (incl. `skill_review_usage` waves) -- global + root budget refusal thresholds identical across compaction -- in-flight survival + post-compaction settle/release -- crash injection between archive and swap → byte-identical ledger, retry OK -- chained compactions: id resolution live ∪ archive; tampered segment raises -- subscription/external replay dedup + identity-conflict still enforced -- legacy import: rows retained; watermark-loss replay appends nothing -- trigger: config threshold gates the reserve path; growth-throttle after an - unprofitable pass; verify-abort on a foreign non-canonical literal -- structure: baseline rows only at head (tail-smuggled row = corrupt; group - without header = corrupt); quarantine + `integrity_degraded` on a compacted - file unchanged; archive segment = exact source bytes, sha-pinned -- round 2 adds fifteen pins for lock ownership/heartbeat, the snapshot - re-check, the archive directory chain and its fsync failure, header - provenance and counts, bounded archive references, epoch-chain steps, - segment revalidation, warm-cache integrity, typed corruption of an - unreadable header, union caching, and decimal precision (§6) -- round 3 adds sixteen more (five of them in `tests/test_lockfile_helpers.py`) - for lock-file ownership on eviction/release/renewal, the pass abandoning a - lost hold, heartbeats inside the long span, writer exclusion at the swap and - the absence of any unlocked fallback, the post-swap re-read, retry - durability of the directory chain, the archive epoch anchor and its orphan - tolerance, source-range provenance, the segment-cache windows, and archive - symlink bounds (§7) -- round 4 and its verification add eight (two of them in - `tests/test_lockfile_helpers.py`): two racing reclaimers yield at most one - holder, a heartbeat after an atomic replacement of the lock file answers - False, an append between the pre-swap re-check and the rename aborts - without loss, a hold lost at the archive is seen before the snapshot - re-check is even asked, a hold lost after the re-check aborts before the - swap, a hold lost WHILE the candidate temp is written refuses the replace - (the verification round's panel fix), and a link planted after the bound - check can neither receive (writer) nor serve (reader) history (§8) -- round 5 adds ten (five of them in `tests/test_lockfile_helpers.py`, one - Windows-only): a non-contention kernel refusal fails the acquisition - closed; a stale lock is never evicted without the kernel hold; the name - tier is chosen by the predicate and makes no kernel call; the capability - probe decides once per directory and leaves no residue; LockFileEx - contention reads as busy (Windows); the pass refuses the name tier while - appends continue; a refused rename re-proves the hold and the snapshot - before retrying (append / hold-lost variants); the epoch anchor scans the - directory the chain was walked in; an entry the anchor cannot open is - typed corruption; a hold lost before the first commit look writes no - orphan (§9) -- round 5.2 adds two in `tests/test_lockfile_helpers.py` (a creator evicted - while still lock-less never returns a descriptor; LockFileEx refusals - classify by their Win32 error — runs on POSIX too) and, in this suite, one - new pin plus five strengthened ones: a hold lost the instant the last - snapshot look answered True refuses the rename; the after-recheck pin also - requires that the in-swap look is never asked once the hold is gone; the - anchor-swap pin carries the epoch-3 NAME with the forged live header and - requires `generation newer`; the cannot-open pin also plants a directory - and a FIFO (skipped, no hang) and requires `could not complete`; the - warm-cache pin adds the directory-in-place-of-segment shape (`not a - regular file`); the name-tier pin requires exactly one durable - `usage_ledger_compaction_refused` row and the tripwire text naming the - tier (§9, second block) -- round 5.4 adds four in `tests/test_lockfile_helpers.py` and, in this - suite, two new pins plus three strengthened ones: a stamp-less ledger still - inspects its archive fail-closed (a regular file where the directory - belongs, an uninspectable archive); a path inspection the reader cannot - make is typed corruption (the not-searchable segment directory, an - `is_symlink` the kernel refuses); the swap-lie pin gains the `erased` - variant (a charge landed inside the rename syscall is quarantined, flags - integrity, and the pass raises instead of receipting); the name-tier pin - gains the append-returned-False shape; the reserve-path pin proves the - heartbeat it is handed is THAT lock's (aged lock renewed), not a callable - (§9 "Round 5.4") - -Mutation-probed red (not just green-once): `_summary` weight math, group-sum -rounding, folding of dispatched rows — each flips at least one pin. - -## 4. Gate evidence (this host, isolated env roots) - -- targeted: usage family (7 files) green; budget family (5 files) green; - persistence inventory + domain manifest + rotation train green; - test_usage_compaction 16/16 green -- full CI-shape non-serial battery (`-m "not serial and not integration and - not browser and not ui_browser and not ui_browser_docker and not - portable_detail and not skill_smoke and not size_ratchet" -n 16 --dist - loadscope --max-worker-restart=0 --timeout=300 --timeout-method=thread`): - EXIT=0, ~13.4k outcomes, 0 failed (first run had exactly one red — - the ABI-3 alias sweep discovering the new `cost_usd` emission site — fixed - by the per-site allowlist row, battery relaunched whole and green) -- serial pass: EXIT=0 (622 passed / 39 skipped); size_ratchet: 5/5, exit 0 - (PIPESTATUS-preserved); `ruff check . --select F` clean; - `scripts/v7next_adoption.py` OK; `git diff --check` clean; - `git rev-parse HEAD` verified after every pytest run -- scale smoke: 24,000-row / 11.9 MB synthetic ledger → 183 KB (65×), - 280 groups, 1.16 s pass; projections byte-equal; post-compaction reserve - correctly refused over the folded money (accounted $1228 > $200 limit) - -## 5. Known residuals (disclosed, not defects) - -1. Subscription/external/legacy/review-attributed rows never fold → slow - residual growth on delegation- or skill-review-heavy installs; the 20 MB - WARN now names exactly this case. A future lane may fold them behind an - archived-identity membership check (design note §3). -2. The in-compactor render fingerprint mirrors the COMPOSITION of - `usage_projection`/`usage_breakdown` (using the same production `_summary` - / `_breakdown_bucket` primitives). A future divergence in that composition - would weaken the self-check, not correctness (worst case: a lawful pass - aborts); the end-to-end pin compares the real projection functions. -3. Directory fsync is a disclosed no-op on Windows (POSIX guaranteed and now - FATAL on failure, round 2 / finding 2); worst case there is a lost archive - dir entry AND a swap in the same crash window — mitigated by archive-first - ordering, disclosed in the design note. -4. A float-boundary rounding coincidence can make the rounded projections - differ pre/post → the pass aborts and the ledger simply stays uncompacted - (correctness over availability; disclosed in design note §5). -5. CPL-5's sweep is not on this base; its contract (consult live ∪ archive; - corrupt chain = UNKNOWN/skip) is recorded in the design note §10 for the - lane that lands it. `model_send_seal`-targeted gates therefore do not - exist on this base to run. -6. **Orphan archive segments** (round 2, widened in round 3): a pass that - loses the snapshot race, dies at its swap, or is abandoned by a lost lock - can leave a written-but-never-referenced segment. It carries no money and - no chain authority — readers start at the live header and follow only what - it names, and the epoch anchor recognises an orphan of the live generation - as legal because its bytes are still a PREFIX of the live file (round 5.3; - matching its leading row alone admitted a restored generation too). Repeated lost races nevertheless accumulate disk: LOW - availability / forensic clutter, not correctness. No GC by design (§13 of - the note). -7. **Warm segment-cache window** (round 3): the per-segment cache hit needs a - matching fingerprint, an mtime settled for > 2 s and an entry younger than - 60 s. An in-place same-size rewrite that ALSO restores `mtime_ns` exactly - can therefore still be answered from a warm entry for up to a minute. - Closing it means re-hashing every segment on every question — the - quadratic cost the cache exists to remove — for an attacker who already - has write access to the data root and can be caught a minute later, by any - other process, and by the chain hash on every segment an answer depends - on. -8. **Ownership is defended, not guaranteed — per tier, and bounded** (round - 3, stated per tier in round 5, bounded in round 5.2, identity-complete in - round 5.3): on the enforced tier the lock primitives are ownership-exact - and kernel-guarded (the acquisition itself included: neither an evicted, - still lock-less creator nor a descriptor whose own identity the kernel - cannot read ever returns a hold), and the pass heartbeats through its long - span, - immediately before every rename attempt and again after the in-swap - snapshot look. No claim is made that a pass can never be robbed of the - lock. The bounded claim: a concurrent holder can exist only after the lock - file is removed by an actor outside the lock protocol (a hand repair, a - foreign helper, a name-tier process of a mixed-tier install — in-protocol - eviction is impossible under the held flock and the heartbeat-fresh - mtime); such a robbery is caught at the next proof, and the irreducible - residual is the interval between the final ownership proof and the rename - syscall, in which a charge landed by that holder IS erased by the rename. - *(Correction, round 5.4: this packet, DESIGN §8/§12.9 and the round-5.2 - ledger line said the loss was "then surfaced by the post-swap re-read or - quarantined seq-misnumbered on the next read" — neither could ever see it: - the re-read compares the NEW inode against the candidate and the archive - segment is the pre-row snapshot, so the loss was SILENT and a success - receipt was returned. Now, on POSIX, the swap holds the old inode open - across the rename and reads back what landed beyond the proven snapshot: - those bytes are quarantined — `state/usage_attempts.quarantine.jsonl`, - which flips `integrity_degraded` — and the pass raises typed instead of - receipting; the charge is not re-appended. Windows cannot hold the - destination open through `os.replace`: silent there, disclosed. §9 "Round - 5.4", R5.)* In-protocol, no writer can append in the - compare→replace window, because every writer of this ledger takes the same - owner-aware lock and has no unlocked fallback. On the name tier no such - claim is made at all: the pass does not run there (§5.10). The round-5 - sentence "it cannot finish while robbed" was an absolute the round-5.2 - probe refuted (PROBE-1: a row appended after the third look answered True - and before `os.replace` was erased, receipt returned); corrected here. -9. **Epoch anchoring reads content, not names** (round 3, narrowed in round - 5, classified in round 5.2): a garbage REGULAR file whose first row reads - but does not parse (a torn segment from a crashed write) is no evidence of - any generation rather than corruption, so it cannot deny service to the - whole history; a directory or special file is not a segment (segments are - regular files by construction) and is skipped — a FIFO is opened - `O_NONBLOCK`, so it cannot hang the question either *(qualified, round 5.4: - an entry that OPENS but is not a regular file is skipped; one the kernel - refuses to open at all — a UNIX socket, ENXIO — is corruption like any - other unopenable entry on the dir-fd shape; the path shape's stat-before-open - classifies a socket as not regular and skips it)*; an entry the scan - cannot list, open or read IS corruption — the scan did not complete, the - data root's own handle included since round 5.3, and since round 5.4 every - path inspection the reader makes (the symlink bounds on both archive levels - and on the named segment; the stamp-less archive check, which ends the - question early only on an exact ENOENT) is typed the same way instead of - escaping as a bare `OSError` or a silent empty answer. Disclosed since - round 5.4: with a stamp-less live file every parsable regular file in the - archive that is not its byte-prefix is `generation newer`, so a ledger - reset beside a surviving archive is a `generation newer` corruption verdict - on every history question until the fresh ledger's epoch passes the - surviving segments, which are then silently ignored (reset both together) - and an operator's stray JSON file - there is corruption on a stamp-less ledger only; and the readers being - lock-free, a compaction that commits between a question's live-header read - and its anchor scan makes that ONE question UNKNOWN (`generation newer`), - the next question walking the new chain — and the scan runs through - the very handle the chain walk held, entries opened relative to it (and - without a dir-fd the classification happens BEFORE the open, which is the - step a directory refuses on Windows and a FIFO blocks on: round 5.3). The round-5 wording ("an entry the scan cannot list, open - or read" beside "a garbage file cannot deny service") contradicted itself - for a directory/FIFO/unopenable file and was false for the first two: - round 5 made a stray `backup/` directory typed-corrupt for every history - question (reproduced on `2dd3e017`; `13af62c5` answered) — an availability - regression with no correctness gain, corrected here. Round 5.3 then replaced - the orphan exemption itself: recognising an orphan by its leading row also - admitted a restored previous generation, which is NOT indistinguishable and - does hide ids — the attempts the rolled-back compaction folded exist - nowhere else. An orphan is the pre-swap copy of the live file, so its bytes - are still a prefix of it; that is the test now, and it runs on a stamp-less - live file too. Every - segment an answer actually depends on is still fully verified by the - chain walk, and a named segment that is not a regular file or whose read - fails is typed corruption, never a bare `OSError`. -10. **The lock has two tiers, by capability predicate** (round 4, made - explicit in round 5): `platform_layer.kernel_file_locks_enforced` locks a - scratch file in the lock directory once per process; only the kernel's - own "this filesystem cannot" selects the name tier — exactly - EOPNOTSUPP/ENOTSUP/ENOSYS *(correction, round 5.4: ENOLCK was in the set - until then — "no locks available" is a missing lock daemon OR an exhausted - kernel lock table, not a capability answer, and it selected the tier where - the round-3 race returns; round 5.4 made it keep the enforced tier and fail - EVERY live acquisition closed — product-wide, the lenses showed, since the - primitive is shared — so the close-out made ENOLCK the name tier with its - errno RECORDED and a per-caller refusal: only the monetary lock names it - (`refuse_name_tier_errnos={ENOLCK}`), so a lockd-less NFS refuses every - monetary write with `UsageAccountingError` instead of running the name - protocol while every other lock keeps the protocol it always ran there; - the per-directory verdict is decided ONCE under a module lock, so racing - threads share one probe; an unprobeable directory answers enforced for - that call, uncached — §9 "Round 5.4" and §10, R1)*, and since - round 5.3 the two Win32 answers of a volume without byte-range locks — - ERROR_INVALID_FUNCTION, which LockFileEx answers on `\\wsl$` - (microsoft/WSL#5762), and ERROR_NOT_SUPPORTED, error 50 on a Samba share — - map onto ENOSYS and EOPNOTSUPP (onto ENOLCK and EOPNOTSUPP before round 5.4): - CPython's winerror→errno table lands both on EINVAL, which left the name - tier structurally unreachable on Windows, so a lock-less volume there - failed EVERY monetary append closed instead of degrading to it. - *Enforced tier* — POSIX flock and Windows LockFileEx, both held on - the lock fd — a refusal that is not contention fails the acquisition - closed (no descriptor, our own file removed, a stale lock never evicted - without the hold); a live-but-WEDGED holder can no longer be evicted by - age — the deliberate trade of an availability incident (the wedged writer - must die first) for the correctness incident (age-evicting a live - monetary writer). Windows cannot unlink an open file: its eviction and - release re-check the path after the close (release included — the POSIX - "unlink under the still-held flock" is POSIX only, stated so since round - 5.4), and a freshly won lock — held open by its owner — is undeletable - there. Disclosed since round 5.4, both tiers: a contention answer on the - creator's OWN fresh file (a foreign flock holder that never unlinks it) - leaves that live-pid-stamped file on the path — the creator re-contends - against it until its timeout and owner-aware acquirers never age it out - while the process lives; no in-protocol holder produces that shape. - Also since round 5.4 the recycled-pid disclosure names its real - mechanism: `pid_is_alive` read EPERM as DEAD, so another user's recycled - pid was reclaimed through the age path (flock-guarded on the enforced - tier) and only a same-uid recycle wedged — the opposite of what round 5.3 - wrote; EPERM now reads alive (the process exists) — in `pid_is_alive`, the - one liveness primitive shared by every consumer (custody settlements, claim - reclaims, staging reaps defer for such a pid too) — so ANY live impostor — - same uid or another — wedges the lock from the 90 s staleness window - (`usage_ledger._locked`, `stale_sec=90.0`) until it exits, the probe - flock deliberately unconsulted while the pid reads alive (a mixed-tier - name-tier holder has none); Windows also has no - `dir_fd`/`O_DIRECTORY`, so its archive bound and anchor scan stay - path-based (fail-closed on any OSError since round 5). *Name tier* — - kernel-lockless filesystems only — keeps the O_EXCL name protocol with - re-check-then-unlink eviction, a disclosed best effort with no kernel - exclusion: the compaction pass refuses to run there - (`usage_compaction.NAME_TIER_REFUSAL`: logged, throttled by the growth - guard, and since round 5.2 written ONCE per process per data root as a - typed `usage_ledger_compaction_refused` event — the cause the 20 MB - tripwire now names; the round-5 claim that the tripwire "names the case" - was false until then) while ordinary appends continue under the name - protocol. Residual, disclosed: the tier is decided per process per - directory, so a lockd that dies mid-run can leave one process on each - tier until restart — the name-tier process never compacts, and it also - evicts by NAME with no kernel hold, so in that mixed mode the round-3 - two-writer class returns for the enforced-tier process's heartbeat-less - APPENDS, not only for compaction. Also since round 5.2: the acquisition - is identity-checked on both tiers (an evicted, still lock-less creator - re-contends instead of returning a descriptor; the owner pid is written - before the lock), and on Windows only ERROR_LOCK_VIOLATION reads as busy - — access-denied and sharing-violation fail the acquisition closed at - once instead of re-contending until the 45 s timeout (unexecuted here, - owed to the 3-OS matrix). The round-4 claim that the - anchor's path-based reads "can only ever ADD a corruption verdict" was - false: a directory swapped after the walk made the path-based scan FAIL - to add the verdict it owed; corrected in round 5 (§9, finding 3). - -## 6. Round 2 — adversarial wave disposition (fix-round base `e2801c52`) - -Verdict of the wave: NEEDS FIXES, nine findings. **All nine accepted and -fixed** — this is the monetary authority, so nothing was argued away as -theoretical. Every fix carries a pin that was verified RED against the exact -mutation it claims to catch (the mutation harness reverts one behaviour and -reruns the suite), and the three pins the wave called weak were rebuilt. - -| # | wave finding | disposition | fix | red-first pin | -|---|---|---|---|---| -| 1 | HIGH — a long pass can be robbed of the lock (`stale_sec=90`, no `owner_aware_stale`), and a prior owner can unlink the new owner's lockfile; the swap then replays a stale snapshot over a concurrently appended charge | **accepted, fixed both ways** | `_named_lock` acquires owner-aware (a live PID is never evicted by age) and yields a heartbeat (`platform_layer.refresh_exclusive_file_lock`, descriptor-targeted so a stolen lock is never refreshed for the thief) that the pass beats at each checkpoint; **and** the swap is refused unless the live bytes still equal the snapshot, re-read under the same held lock right before the rename | `test_monetary_lock_is_owner_aware_and_the_pass_heartbeats_it`; `test_append_between_snapshot_and_swap_aborts_instead_of_erasing_it` (injected append → pass returns `None`, the row survives, money = before + that row) | -| 2 | HIGH — archive durability: only the segment's own parent is fsync'd, and `_fsync_dir` swallows every error, including on POSIX | **accepted, fixed** | `_mkdir_fsync_chain` syncs every directory entry the chain creates (segment parent, `archive/`, data root); `_fsync_dir` raises on POSIX and is a no-op on Windows *by the platform predicate*, not by a bare `except` | `test_archive_directory_chain_is_durable_before_the_swap` (fsync'd inodes recorded and required BEFORE the swap); `test_posix_directory_fsync_failure_aborts_before_the_swap` | -| 3 | HIGH — the baseline validator accepts a rolled-back hash chain and forged seq/epoch provenance; the archive reader scrapes ids instead of validating | **accepted, fixed** | substrate validates the stamp: epoch, bounded `archive_rel`, 64-hex sha, closing counts (`folded + retained == source`, first seq 1, last seq == source rows), block↔header agreement (`group_count`, summed `folded_attempt_count`), and `pre_compaction_seq` uniqueness/monotonicity under a stamp only. The reader runs each segment through `_validate_records` and requires the chain's epochs to step down by one to a header-less epoch 1 | `test_repointing_the_header_at_an_older_segment_is_corrupt` (three epochs; both skip shapes); `test_baseline_header_counts_must_close`; `test_pre_compaction_seq_is_a_checked_provenance_claim`; `test_rehashed_segment_still_fails_the_ledger_structure`; `test_a_group_row_cannot_rejoin_the_block_after_it_closed` | -| 4 | MEDIUM — a warm segment cache hides a deleted or replaced segment | **accepted, fixed** | the cache hit additionally requires the file's `(ino, dev, size, mtime_ns)` fingerprint; a miss re-reads, re-hashes and re-validates | `test_warm_segment_cache_revalidates_the_file_it_cached` (delete, then rewrite, both after a warm read) | -| 5 | MEDIUM — "decimal exactness" is bounded by the ambient 28-digit context, and the self-check rounds the same way | **accepted, fixed** | sums run under `_exact_money` (`prec=60`, `Inexact` trapped), so a loss past even that aborts instead of approximating; the pin's oracle sums in its own wider context | `test_group_sums_survive_beyond_the_default_decimal_precision` (10²⁸ + 1 keeps its last digit; red-first showed the dollar vanishing) | -| 6 | MEDIUM — `archive_rel` is not bounded to the archive directory | **accepted, fixed with 3** | `usage_ledger.valid_archive_rel` (textual bound, substrate-owned) plus a resolved-path bound in the reader (defeats a planted symlink) | `test_archive_reference_is_bounded_to_the_archive_directory` (six shapes rejected by the validator; an existing, correctly hashed file outside the archive rejected by the reader) | -| 7 | MEDIUM — a corrupt live header reads as "never compacted" | **accepted, fixed** | `_live_baseline_header` raises `UsageLedgerCorrupt` on an unreadable or non-object first row; `None` now means only "a readable row that is not a stamp" | `test_unreadable_leading_row_is_typed_corruption_not_absence` (the CPL-5 join raises → UNKNOWN, never an orphan verdict) | -| 8 | LOW — the join primitive re-unions the whole archived id set per question | **accepted, fixed** | the union is cached by chain identity ((`archive_rel`, sha) per hop); the stat-checked walk still runs, so finding 4's guarantee is not traded for the cache | `test_archived_id_union_is_built_once_per_chain` (H questions → exactly one union build) | -| 9 | MEDIUM — three pins do not pin what they claim | **accepted, all three rebuilt** | crash pin injects at `os.replace` itself and asserts the segment is already on disk with the exact source bytes (a swap-before-archive reorder now fails it); the threshold pin proves the lock is HELD at the call rather than trusting the call site; the head-only pin contrasts one unmodified baseline block that validates at the head with the same rows rejected purely for position | `test_crash_at_the_ledger_rename_leaves_ledger_intact`; `test_reserve_path_compacts_only_past_config_threshold`; `test_baseline_header_is_rejected_by_POSITION_not_by_shape` | - -Not changed by round 2, and deliberately so: the fold scope (§3 of the -design note), the per-group baseline shape the wave independently confirmed -preserves per-root enforcement and all five breakdown axes, the trigger -thresholds, and the ABI-3 allowlist row the wave found correctly scoped. - -New residual disclosed by finding 1's fix: a pass that loses the snapshot -race leaves an orphan archive segment (already written, never referenced). -Orphan segments were disclosed as harmless before, and the archive is -append-only by design (§13); the alternative — swapping anyway — is the -defect being fixed. - -Round-2 code commits (author `ouroboros-agent`, single-intent): -`9e99eb55` (findings 1, 2, 9-crash, 9-threshold), `0ed2dc2c` (findings 3, 4, -6, 7, 8, 9-position), `6b03212e` (finding 5 + the ARCHITECTURE ownership -line). - -## 7. Round 3 — second adversarial wave disposition (fix-round base `830aa35a`) - -Verdict of the second wave: NEEDS FIXES. It re-read the round-2 fixes and -judged five of the nine findings still OPEN (1, 2, 3, 4, 6), closing 5, 7, 8 -and 9. **All five accepted and fixed**; nothing was argued away. Each fix carries a pin verified RED against the exact mutation it -claims to catch, on this base, before the fix landed. - -| # | round-2 verdict | what was still open | fix | red-first pin | -|---|---|---|---|---| -| 1 | HIGH, OPEN | ownership was never actually proven: stale inspection judged the path and then unlinked the path; release unlinked whatever now occupied the name; the POSIX heartbeat renewed the descriptor and answered success after it had been unlinked; `_beat` ignored the answer; nothing beat during the long build/verify span; and the snapshot compare→replace stayed a TOCTOU window | `platform_layer` now compares descriptor identity with path identity everywhere: the eviction removes only the exact file it judged (re-checked immediately before the unlink), the release removes only the file it still holds, and `refresh_exclusive_file_lock` returns an OWNERSHIP verdict. `_beat` aborts the pass on a lost or unanswerable hold and runs inside both candidate row walks and between every verification stage. The window is closed structurally — every ledger writer takes this same owner-aware lock with no unlocked fallback — and the swap re-reads what landed | `test_stale_eviction_never_removes_a_lock_re_created_under_it`, `test_release_never_unlinks_a_lock_that_was_stolen`, `test_heartbeat_reports_lost_ownership_instead_of_renewing` (+ deleted-lock variant) in `tests/test_lockfile_helpers.py`; `test_a_lost_lock_aborts_the_pass_instead_of_swapping`, `test_the_long_build_and_verification_section_beats_the_lock`, `test_no_writer_can_append_between_the_snapshot_check_and_the_swap`, `test_every_ledger_writer_refuses_when_the_lock_cannot_be_taken`, `test_a_swap_that_did_not_land_is_a_typed_failure_not_a_receipt` | -| 2 | HIGH, OPEN | durability was established only for the levels a pass CREATED, so the retry after a pass that died on its own fsync skipped directories that already existed but were not yet durable | `_mkdir_fsync_chain(path, root)` fsyncs the whole chain up to the data root unconditionally, every pass | `test_the_directory_chain_is_re_synced_on_the_retry_after_a_failed_pass` (fails the first pass on the directory fsync, then requires all three inodes fsync'd before the retry's swap) | -| 3 | HIGH, OPEN | the chain had no trusted live anchor — `compaction_epoch` is as mutable as the rest of the row, so repointing the header at an older genuine segment AND lowering the epoch walked a valid short chain; `pre_compaction_seq` was only required to increase | the archive anchors the stamp: no segment may carry a generation newer than the live one, derived from each segment's embedded header (content, not name), with an uncommitted orphan of the live generation explicitly legal. `pre_compaction_seq` must fall inside the header's declared source range | `test_repointing_the_header_at_an_older_segment_is_corrupt` — the forgery now copies `compaction_epoch` too, which the wave named as the pin's escape hatch; `test_pre_compaction_seq_must_name_a_row_the_named_source_held`; `test_an_orphan_segment_of_the_live_generation_is_not_a_rollback` guards the fix against over-reach | -| 4 | MEDIUM, OPEN | a fingerprint is not identity: an in-place same-size rewrite inside timestamp granularity, or with the mtime restored, kept the cache hit | a hit also requires an mtime settled for > 2 s and an entry younger than 60 s; past either, the bytes are hashed again. Remaining window disclosed as residual §5.7 | `test_a_rewrite_inside_the_timestamp_window_is_re_hashed_not_recalled`; `test_a_same_size_rewrite_is_caught_once_the_cache_entry_expires` | -| 6 | MEDIUM, OPEN | a symlink AT `archive/usage_ledger` escaped the resolved-parent bound, because segment and directory resolve through the same link | neither `archive/` nor `archive/usage_ledger` may be a link, the resolved directory must be exactly the resolved root's archive path, and no segment may be a link; the reader calls it corruption, the writer aborts its pass | `test_a_symlinked_archive_path_is_refused_by_writer_and_reader` (both levels, reader and writer) | -| 5, 7, 8, 9 | CLOSED by the wave | — | unchanged | unchanged | - -ARCHITECTURE and the design note carried absolutes the wave was right to -call out (`never robbed of it`, unqualified `bounded`). Both now state the -contract with its residuals: ownership is defended and its loss is -survivable; the archive bound is exact about symlinks; the cache window and -the orphan segments are named where the mechanism is described (§5.6–5.9). - -Round-3 code commits (author `ouroboros-agent`, single-intent): lock -ownership in `platform_layer` + its pins; the pass consuming ownership -(heartbeat abort, span checkpoints, post-swap verify); the unconditional -directory chain; the archive epoch anchor + source-range provenance; the -segment-cache shelf life; the archive symlink bound. - -## 8. Round 4 — third adversarial wave disposition (fix-round base `d7b487ab`) - -Verdict of the third wave: NEEDS FIXES. It judged the round-3 ownership and -bound fixes still short of the contract in four ways — the exclusion itself -was still only a name protocol, ownership was not proven adjacent to the -decisions it licenses, the recheck→replace gap remained, and the symlink -bound was still check-then-use. **All four accepted and fixed**; nothing was -argued away. - -| # | what round 3 left open | fix | red-first pin | -|---|---|---|---| -| 1 | exclusion rested on the O_EXCL name protocol: the stale eviction re-checked the inode and then unlinked the PATH (a pause between the re-check and the unlink lets a second reclaimer remove the first one's freshly won lock — two writers on one monetary authority), and the release had the same window between its look and its unlink | the lock fd HOLDS a kernel lock (`fcntl.flock`; `LockFileEx` on Windows) from acquisition; a stale lock is evicted only while flock-holding the very fd that was judged, with the path re-checked under that hold, and a release unlinks BEFORE its close, under the still-held flock. Windows (no unlink of an open file) and filesystems without kernel locks keep the re-check-then-unlink shape as a best effort chosen by the platform predicate — disclosed, never an exception swallowed *(correction, round 5: false at `13af62c5` — any `OSError` from the kernel lock selected the name shape, silently; fixed by round 5, finding 1)* | `test_two_racing_reclaimers_never_yield_two_holders` (both reclaimers herded into the check-to-unlink window; RED on the round-3 code with both returning descriptors); `test_heartbeat_after_an_atomic_swap_of_the_lock_reports_false` (the path never absent, so an existence check would renew; red against the utime-only mutation) — both in `tests/test_lockfile_helpers.py` | -| 2 | the pre-swap re-check and the rename were separated by the tmp write and fsync: a row appended in that gap was erased by the swap, receipt and all | `_write_bytes_atomic_fsync` takes a `precondition` evaluated after the temp bytes are durable, immediately before `os.replace` — the last instant the replace can still be refused; the compactor passes `_snapshot_intact`, so the pass aborts with the ledger (and the landed row) byte-identical | `test_an_append_between_the_recheck_and_the_replace_aborts_without_loss` (RED on `d7b487ab`: the row was erased and a receipt returned; now the pass returns `None`, the row survives, money = before + that row, no temp residue) | -| 3 | ownership was beaten through the span but not adjacent to the decisions: nothing proved the hold immediately before the snapshot re-checks, and nothing at all between the final re-check and the swap | `beat()` now runs immediately before EACH snapshot look: a hold lost at the archive write aborts before the post-archive re-check is even asked (its answer would be meaningless), and a hold lost after that re-check aborts before the replace — the proof before the swap was moved INSIDE the atomic replace by the verification pass (panel FIX_FIRST; see the verification block below) | `test_a_hold_lost_at_the_archive_is_seen_before_the_snapshot_is_trusted` (asserts exactly ONE `_snapshot_intact` call; the "remove the beat before the re-check" mutation makes it two — red against that exact mutation); `test_a_hold_lost_after_the_recheck_aborts_before_the_swap` (RED on `d7b487ab`: the swap ran) | -| 4 | the symlink bound was check-then-use: `_archive_dir_bounded` / `_segment_path` judged paths, then the write and the read re-resolved those paths — a link planted in between received the segment (writer) or served a foreign file (reader) | POSIX opens the chain root→`archive/`→`usage_ledger` `O_DIRECTORY\|O_NOFOLLOW` handle-to-handle and creates/opens the segment `O_NOFOLLOW` via `dir_fd`, fingerprinting and reading from the open fd; directory durability is fsync'd through the same held handles. The path-based checks remain as the early typed abort and as the Windows best effort (no `dir_fd`/`O_DIRECTORY` there), chosen by the platform predicate | `test_a_link_planted_after_the_writer_bound_check_cannot_receive_history` (RED on `d7b487ab`: the segment crossed the link and the swap completed); `test_a_link_planted_after_the_reader_bound_check_is_refused` (byte-identical copy behind the link — the hash cannot object, only refusing the traversal defends; RED on `d7b487ab`) | - -Confirmed rather than changed: every writer of this ledger already takes the -same owner-aware lock with no unlocked fallback (round-3 pin stands; what -changed is that the lock they all take is now kernel-held), and the -post-replace re-read stays, now after the in-swap re-proof. - -New/updated residuals (also §5): a live-but-WEDGED holder can no longer be -evicted by age on POSIX — the kernel lock outlives the staleness clock until -the process dies. That is the deliberate trade: age-evicting a live writer -was the two-writers defect; a wedged monetary writer is an availability -incident, not a correctness one. Windows and kernel-lockless filesystems -(bare NFS and friends) run the round-3 identity-re-check shape as a disclosed -best effort selected by the platform predicate *(correction, round 5: at -`13af62c5` the selection was by exception, not by predicate — §9, finding -1)*. `ouroboros/usage_compaction.py` -entered the 1001-1500 size band with a recorded rationale (the dir-fd -anchoring and the in-swap re-proof live beside the pass they defend); -`ouroboros/platform_layer.py` stays inside the band at 1498 lines, paid for -by prose compression in the same module. - -### Round-4 verification (base `d7b487ab`; the round-4 work had shipped unexecuted) - -Round 4 was authored in an execution-denied environment, so a dedicated -verification pass ran every claim for real. One finding of the round-4 -review panel (codex, FIX_FIRST, accepted by the coordinator) was fixed in -the same pass: - -- **The ownership proof stood before the swap, not inside it**: `beat()` ran - immediately before `_swap_ledger_fsync`, but the atomic writer can spend - arbitrarily long writing and fsyncing the candidate temp before its - snapshot look and `os.replace` — a hold lost in that window let a new - holder's charge (landing after the in-swap snapshot answer, before the - rename) be erased by the swap. The proof of ownership now lives in the - precondition of the atomic replace itself: once the temp bytes are - durable, immediately before the rename, ownership FIRST and the snapshot - compare only under a proven hold (`_swap_ledger_fsync` passes `beat` into - `_write_bytes_atomic_fsync`'s precondition). Pin: - `test_a_hold_lost_while_the_temp_is_written_refuses_the_replace` — RED on - the round-4-as-authored shape (ownership died with the temp on disk; the - snapshot-only precondition let the rename run and a receipt returned), - green with the fix: the replace is refused and the new holder's charge - survives byte-for-byte, money = before + that charge. - -Every round-4 red-first claim was then observed, not argued — each pin was -run against the exact mutation or base it names (mutation applied, pin RED, -mutation reverted, pin green): - -| pin | mutation | red observed | -|---|---|---| -| `test_two_racing_reclaimers_never_yield_two_holders` | `platform_layer.py` reverted to `d7b487ab` | both reclaimers returned descriptors: 2 holders | -| `test_heartbeat_after_an_atomic_swap_of_the_lock_reports_false` | identity comparison removed from `refresh_exclusive_file_lock` (utime-only) | heartbeat answered True for a replaced lock | -| `test_an_append_between_the_recheck_and_the_replace_aborts_without_loss` | swap precondition removed entirely | receipt returned; the injected row was erased | -| `test_a_hold_lost_at_the_archive_is_seen_before_the_snapshot_is_trusted` | post-archive `beat()` removed | 2 `_snapshot_intact` calls instead of 1 | -| `test_a_hold_lost_after_the_recheck_aborts_before_the_swap` | in-swap ownership proof removed (snapshot-only precondition, no outer beat) | the swap ran; a baseline landed | -| `test_a_hold_lost_while_the_temp_is_written_refuses_the_replace` | round-4-as-authored shape (outer `beat()` + snapshot-only precondition) | receipt returned while robbed | -| `test_a_link_planted_after_the_writer_bound_check_cannot_receive_history` | `usage_compaction.py` reverted to `d7b487ab` | the segment crossed the link; the swap completed | -| `test_a_link_planted_after_the_reader_bound_check_is_refused` | `usage_compaction.py` reverted to `d7b487ab` | the byte-identical copy was read through the link (no raise) | - -Windows tier: the two new lockfile pins exercise POSIX mechanics (flock-held -eviction; replacing an open, kernel-locked file), so both carry -`skipif(IS_WINDOWS)` with the disclosed-best-effort reason; the compaction -pins are platform-neutral, and the two planted-link pins already skip on -Windows. `fcntl` is imported only inside `not IS_WINDOWS` branches of the -`platform_layer` primitives, so the module imports cleanly where `fcntl` -does not exist. - -Round-4 verification gate evidence (this host, isolated env roots, venv -python 3.10.12 / pytest 9.1.1): recorded in -`docs/v7next/LEDGER_CORRECTIONS.md` §"From the C6 fix-round 4 verification" -— targeted usage/lockfile suites green; CI-shape non-serial battery EXIT=0; -`-m serial` EXIT=0; `-m size_ratchet` green; `ruff check . --select F` -clean; `scripts/check_domains.py` OK; `scripts/regenerate_inventories.py ---check` OK; `git diff --check` clean; `git rev-parse HEAD` verified after -every pytest run. With that run recorded, round 4 is verified, not merely -code-complete. - -## 9. Round 5 — fourth wave disposition (fix-round base `13af62c5`) - -Verdict of the fourth wave (gpt-5.6-sol, read-only): NEEDS FIXES. It closed -the round-3 split-brain class (stale eviction under flock with the inode -re-check, release of the own pathname only, identity heartbeat), all monetary -writers under one lock, and the archive writer/reader dir-fd anchoring; it -left four items open. **All four accepted and fixed**; nothing was argued -away. Each fix carries a pin verified RED against the exact pre-fix shape or -mutation it names, on this base, before the fix landed. - -| # | what round 4 left open | fix | red-first pin | -|---|---|---|---| -| 1 | HIGH — on ANY `OSError` from the kernel lock the acquisition silently degraded to the pathname/inode name tier, where the round-3 race returns (and on Windows the errno-less `LockFileEx` failure fell into the same degrade) | the tier is an explicit capability predicate, `platform_layer.kernel_file_locks_enforced(lock_path)`: one scratch-file kernel lock per lock directory per process; only ENOLCK/EOPNOTSUPP/ENOSYS select the name tier. On the enforced tier contention (EAGAIN/EACCES/EWOULDBLOCK) stands down and re-contends; every other refusal fails CLOSED — no descriptor, our own file removed, a stale lock never evicted without the held flock. `_win32_lock` raises an `OSError` carrying the Windows error so `ERROR_LOCK_VIOLATION` classifies as contention. The name tier makes no kernel call at all, and `compact_usage_ledger_locked` refuses it with the typed `NAME_TIER_REFUSAL` (logged; appends continue under the name protocol, disclosed). `usage_ledger.LOCK_REL` is the lock-path SSOT *(correction, round 5.3: the busy set is EAGAIN/EWOULDBLOCK alone — EACCES fails closed since round 5.2, finding W — and the unsupported set is ENOLCK/EOPNOTSUPP/ENOTSUP/ENOSYS plus the two Win32 codes round 5.3 maps onto it; §9 "Round 5.3", findings 2 and L5; correction, round 5.4: ENOLCK left the set — it fails closed — so the set is EOPNOTSUPP/ENOTSUP/ENOSYS, with winerror 1 mapped onto ENOSYS; §9 "Round 5.4", R1)* | `test_a_kernel_refusal_that_is_not_contention_fails_closed`, `test_a_stale_lock_is_never_evicted_without_the_kernel_hold`, `test_the_name_tier_is_chosen_by_the_predicate_not_by_a_refusal`, `test_the_capability_probe_decides_once_and_leaves_no_residue`, `test_windows_lockfileex_contention_reads_as_busy` (skipif not Windows) in `tests/test_lockfile_helpers.py`; `test_the_pass_refuses_on_the_name_tier_while_appends_continue` | -| 2 | HIGH — the ownership→snapshot precondition ran once before `utils.replace_atomic`, which retries `os.replace` up to ten times with pauses on a Windows sharing violation: a charge appended (or a hold lost) between attempts was erased by the retry that landed | `replace_atomic(src, dst, *, precondition=None)` asks the precondition immediately before EVERY attempt, retries included, and returns False without replacing when refused; `_write_bytes_atomic_fsync` routes its ownership-first, snapshot-second proof through it. POSIX behaviour is unchanged (one syscall) | `test_a_refused_rename_re_proves_the_hold_and_the_snapshot_before_retrying[append]` / `[hold_lost]` (first attempt raises `PermissionError`, the intrusion lands, the second call never happens, the row survives / the ledger is byte-identical) | -| 3 | MEDIUM — `_no_newer_archived_epoch` walked the archive by pathname and turned `OSError` into "no evidence": a directory swapped after the safe chain walk could hide a newer generation and admit a forged rollback (the §5.10 claim was false) | `archived_attempt_ids` opens the `O_DIRECTORY\|O_NOFOLLOW` handle chain ONCE — after the live-header read, for the rest of the question *(round 5.2: a directory swapped before that open is the same power as deleting the newer segments, disclosed; a non-regular entry is skipped, not corruption)*; segment loads and the anchor scan open entries relative to that same held handle (one `_open_archive_entry` rule; path-based only where `dir_fd` is absent). An entry the scan cannot list, open or read is `UsageLedgerCorrupt`; a first row that reads but does not parse stays the disclosed torn-segment case | `test_the_epoch_anchor_scans_the_directory_the_chain_was_walked_in` (POSIX; a look-alike directory swapped in after the walk), `test_an_archive_entry_the_anchor_cannot_open_is_typed_corruption` (a dangling entry) | -| 4 | LOW — two surviving mutations (deleting the first commit-section beat; losing the hold between rename retries) and three doc absolutes («cannot finish while robbed», «a hold lost anyway abandons», «kernel-held») stated without their tier | both pinned (the second by finding 2's `[hold_lost]` variant); DESIGN §8/§10/§12, ARCHITECTURE and this packet now state the contract per tier — enforced tier vs name tier | `test_a_hold_lost_before_the_first_commit_look_writes_no_orphan` | - -Red observed, not argued — each pin against the exact pre-fix shape or -mutation it names (pin red, fix applied or mutation reverted, pin green): - -| pin | mutation / base | red observed | -|---|---|---| -| `test_a_kernel_refusal_that_is_not_contention_fails_closed` | `13af62c5` (silent degrade on any OSError) | a descriptor was returned for an ENOLCK-refused lock | -| `test_a_stale_lock_is_never_evicted_without_the_kernel_hold` | `13af62c5` (`evict_flockless` on a non-contention errno) | the stale file was evicted by name and a descriptor returned | -| `test_the_name_tier_is_chosen_by_the_predicate_not_by_a_refusal` | `13af62c5` (kernel lock attempted unconditionally) | a kernel call was made on the name tier (`[16] == []`) | -| `test_the_capability_probe_decides_once_and_leaves_no_residue` | `13af62c5` | no predicate exists (`AttributeError: _KERNEL_LOCK_TIER`) | -| `test_the_pass_refuses_on_the_name_tier_while_appends_continue` | `13af62c5` (no tier check in the pass) | a receipt was returned on the name tier | -| `test_a_refused_rename_re_proves_the_hold_and_the_snapshot_before_retrying[append]` | `13af62c5` (precondition once, `replace_atomic` retries blind) | the retried rename landed: receipt returned, the appended row erased | -| `…[hold_lost]` | same | the retried rename landed while robbed: receipt returned | -| `test_the_epoch_anchor_scans_the_directory_the_chain_was_walked_in` | `13af62c5` (path-based `iterdir`) | DID NOT RAISE: the look-alike directory hid epoch 3, the forged rollback passed | -| `test_an_archive_entry_the_anchor_cannot_open_is_typed_corruption` | `13af62c5` (OSError → continue) | DID NOT RAISE: the dangling entry was swallowed as no evidence | -| `test_a_hold_lost_before_the_first_commit_look_writes_no_orphan` | first commit-section `beat()` deleted | `[1] == []`: the pre-archive look was asked and an orphan segment written | - -Windows tier, stated plainly: Windows ALREADY held `LockFileEx` on the lock -fd from acquisition (`file_lock_exclusive_nb` is platform-neutral); what was -missing was error classification, so the wave's suggestion of -`msvcrt.locking` was not adopted — it is a thinner CRT wrapper over the same -kernel lock with an EACCES/EDEADLOCK ambiguity, weaker than the `LockFileEx` -the module already owns. The Windows-only pin -(`test_windows_lockfileex_contention_reads_as_busy`, `skipif(not -IS_WINDOWS)`) and the `OSError(0, msg, None, winerror)` mapping it pins were -NOT executed on this host (Linux); they follow the documented CPython -constructor contract (errno derived from `winerror`, `ERROR_LOCK_VIOLATION` -→ `EACCES`) and stay disclosed as unexecuted until the 3-OS CI matrix runs -them. The four POSIX pins and the compaction pins ran here. - -Size ratchet, stated plainly: `ouroboros/platform_layer.py` stays at 1498 -lines inside the 1001-1500 band — paid for by prose compression in the same -module and by the pid lock and the port sweep reusing the module's own -primitives (`file_lock_exclusive_nb`/`file_unlock`, `force_kill_pid`), not -by any helper or neighbour module. `ouroboros/usage_compaction.py` grew -1094→1124 inside the band; its band rationale could NOT be extended — the -ratchet's own transition rule makes a surviving band rationale immutable -between adjacent manifests (`validate_manifest_transition`: "surviving band -rationale is immutable"), so the round-5 growth is recorded here and in the -ledger instead. `tests/test_usage_compaction.py` sits at 1492 inside the -band (the four copies of the raced charge folded into one `_raced_row` -helper paid for the new pins). - -Round-5 code commits (author `ouroboros-agent`, single-intent): `f5eb969f` -(finding 1: lock tiers, fail-closed acquisition, name-tier refusal), -`8ed4f11b` (finding 2: the precondition before every rename attempt), -`a3d4d51d` (finding 3: the anchor through the held dir-fd, fail-closed), -`4b872c22` (finding 4: the first-commit-beat pin); the docs commit follows. -Gate evidence for this round is recorded in `docs/v7next/LEDGER_CORRECTIONS.md` -§"From the C6 fix-round 5 (base 13af62c5)". - -### Round 5.2 — adversarial lenses over round 5 (fix-round base `2dd3e017`) - -Verdict of the lenses (independent read of `2dd3e017`, PoCs executed on -scratch copies): five HIGH/MEDIUM findings open, six LOW. **All eleven -accepted**; nine are fixed in code with red-first pins, two are closed by -the disclosure the finding asked for (the doc absolutes; the mixed-tier -eviction residual). Nothing was argued away. Every code fix carries a pin -observed RED against the exact pre-fix shape or mutation it names. - -| # | finding | fix | red-first pin | -|---|---|---|---| -| 1 | HIGH — a creator evicted while still lock-less flocks its own unlinked inode: between the O_EXCL create and the kernel lock the file is EMPTY (`owner_pid=0`, so owner-awareness cannot protect the window) and holds nothing an evictor must respect; stalled there past `stale_sec` (SIGSTOP, suspend, debugger, NFS clock skew) it is evicted, and its flock then SUCCEEDS on the unlinked inode — two descriptors believed to be one monetary lock (PoC `HOLDERS: 2`; the append transaction never heartbeats, so duplicate `seq` → a real charge quarantined). Same primitive with `stale_sec=10` and no owner-awareness at five non-monetary locks | the owner pid is written BEFORE the kernel lock, and a freshly won lock is returned only if the path still names the descriptor (one stat) — otherwise the creator closes it and re-contends. Both tiers, every caller of the primitive | `test_a_creator_evicted_while_lock_less_never_returns_a_descriptor` (`tests/test_lockfile_helpers.py`; the creator's first kernel lock ages its own file and runs an age-only reclaimer inline) | -| 2 | MEDIUM — the name-tier refusal was a throttled log line folded into the same `False` as "nothing foldable"; the "20 MB tripwire names the case" claim was false (the tripwire text named only a broken compaction or a large residue) | one typed `usage_ledger_compaction_refused` row per process per data root in `logs/events.jsonl` (the existing `append_jsonl`, contained like the compacted event; no return-type change); the tripwire text and the threshold comment name the third cause and the event; DESIGN §8, §5.10 and the module comment corrected | `test_the_pass_refuses_on_the_name_tier_while_appends_continue` (exactly one row after two refusals; the tripwire note names the tier and the event) | -| 3 | MEDIUM — «cannot finish while robbed» refuted in the last-proof→rename gap: `owned_and_intact` proved ownership, THEN read the whole file (≈1.8 ms on 8 MB), then `os.replace` — an fsync'd append (≈0.2 ms) by an out-of-protocol holder landed after the look answered True and before the rename (PROBE-1: receipt returned, row erased); the snapshot-first/beat-second mutation passed every pin | `owned_and_intact` beats, looks, beats AGAIN — the only interval between the last proof and the rename is the syscall (`replace_atomic` asks it before every attempt); DESIGN §8/§12, §5.8 and ARCHITECTURE state the bounded contract instead of the absolute | `test_a_hold_lost_after_the_last_snapshot_look_refuses_the_rename`; `test_a_hold_lost_after_the_recheck_aborts_before_the_swap` now also requires that the in-swap look is never asked once the hold is gone | -| 4 | MEDIUM — the anchor-swap pin pinned only the listing half: under "list via the held fd, OPEN BY PATH" it stayed green for the wrong reason (missing epoch-3 name → `FileNotFoundError` → "could not complete"), while a look-alike carrying the epoch-3 NAME with the forged live header as its leading row was ADMITTED by the orphan exemption | the look-alike now carries exactly that segment (forged header + the real epoch-3 body) and the pin requires `match="generation newer"` | `test_the_epoch_anchor_scans_the_directory_the_chain_was_walked_in` | -| 5 | MEDIUM — round 5's fail-closed rule made a stray subdirectory (an operator's `backup/`, no forgery) typed-corrupt for every history question forever (`os.read` → EISDIR); `13af62c5` answered. LOW siblings: a FIFO blocked the open indefinitely (pre-existing: neither fail-open nor fail-closed), and a directory standing where the header names a segment escaped as a bare `IsADirectoryError` the sweep's `except UsageLedgerCorrupt` would miss | `_open_archive_entry` opens `O_NONBLOCK` through the held dir-fd; `_no_newer_archived_epoch` fstat-classifies — a non-regular entry is no segment and is skipped, an entry it cannot list/open/read stays corruption; `_load_segment` raises typed on a non-regular named segment or any `OSError` of its fstat/read | `test_an_archive_entry_the_anchor_cannot_open_is_typed_corruption` (subdirectory + FIFO under a SIGALRM guard, then the dangling link with `could not complete`); `test_warm_segment_cache_revalidates_the_file_it_cached` directory shape (`not a regular file`) | -| W | LOW — the Windows busy set was a superset of contention: winerror 5/32/33 all land on EACCES, so a genuine access-denied re-contended until the 45 s timeout (latency only, no descriptor) | `_win32_lock_error` maps ERROR_LOCK_VIOLATION alone onto EAGAIN (winerror kept for diagnostics); every other Win32 error keeps its derived errno and fails closed; the busy set is {EAGAIN, EWOULDBLOCK} on both platforms | `test_lockfileex_refusals_classify_by_the_win32_error` (runs on POSIX too); the Windows-only contention pin keeps `winerror == 33` | -| D | LOW — doc absolutes and gaps: «cannot finish while robbed», «a hold lost anywhere abandons», «held for the whole question» (the handles open AFTER the live header read), the verbatim-restore rollback the orphan exemption admits, the mixed-tier residual omitting by-name eviction, §8 carrying the round-4 predicate claim without a marker, §5.9 contradicting itself | DESIGN §8/§10/§12, the ARCHITECTURE row, PERSISTENCE, §5.8/§5.9/§5.10/§8 of this packet rewritten as each finding asked; no code | — | - -Red observed, not argued — each pin against the exact pre-fix shape or -mutation it names, on a scratch copy of this lane (pin red, fix applied or -mutation reverted, pin green): - -| pin | mutation / base | red observed | -|---|---|---| -| `test_a_creator_evicted_while_lock_less_never_returns_a_descriptor` | `2dd3e017` | two descriptors returned (`[14, 15]`), `HOLDERS: 2` | -| `test_lockfileex_refusals_classify_by_the_win32_error` | `2dd3e017` | `EACCES in frozenset({11, 13})` | -| `test_a_hold_lost_after_the_last_snapshot_look_refuses_the_rename` | `2dd3e017` (beat → look → replace) | receipt returned while robbed, the charge erased | -| `test_a_hold_lost_after_the_recheck_aborts_before_the_swap` (look-count clause) | snapshot-first / beat-second (mutation M3) | `3 == 2`: the in-swap look was asked after the hold was gone | -| `test_the_epoch_anchor_scans_the_directory_the_chain_was_walked_in` | anchor opens entries by path (listing through the held fd kept) | `DID NOT RAISE UsageLedgerCorrupt` — the forged look-alike admitted; the previous pin shape passed under the same mutation | -| `test_an_archive_entry_the_anchor_cannot_open_is_typed_corruption` (subdirectory) | `79a1b9fb` | `anchor scan could not complete: [Errno 21] Is a directory` | -| same, FIFO half alone | `79a1b9fb` | `TimeoutError: FIFO open blocked` — the open hung until the 5 s alarm | -| `test_warm_segment_cache_revalidates_the_file_it_cached` (directory shape) | `79a1b9fb` | bare `IsADirectoryError: [Errno 21] Is a directory` from `os.read` | -| `test_the_pass_refuses_on_the_name_tier_while_appends_continue` (event + tripwire clauses) | `79a1b9fb` | no `events.jsonl` row at all (`FileNotFoundError`); the tripwire note named no tier | - -Windows tier, stated plainly: `_win32_lock_error` and the classification pin -run their errno arithmetic on POSIX too (the new pin is not skipped), but -the LockFileEx call itself and the Windows-only contention pin remain -unexecuted on this host and owed to the 3-OS CI matrix; the path-based -Windows anchor scan keeps the fail-closed rule from round 5 (no -`S_ISREG`/`O_NONBLOCK` classification there — a directory in the archive is -corruption on Windows, disclosed), and the FIFO/dangling-link pin is POSIX -(`skipif(IS_WINDOWS)`). - -Size ratchet, stated plainly: `79a1b9fb` (the round-5.2 agent's last -commit before the session limit) left `tests/test_usage_compaction.py` at -1512 lines with the manifest stale — `regenerate_size_ratchet.py --check` -exit 1 at that tree, the suite silently in the 1501-1600 zone; there is no -committed-history replay on this line (`review.py`: the local surface -warns), so the linear repair `6ad110e9` stands: three verbatim scaffolding -duplicates folded in place (the raced-charge-survived assertion, the -retry-durability pin re-running the first-pass proof, the single-caller lock -probe inlined) and PEP 8 spacing, 1512 → 1461, no new abstraction, every -folded pin still red under the swap-precondition-removed mutation; the -round-5.2 pins then bring it to 1492. `ouroboros/usage_compaction.py` grows -1124 → 1158 inside its band (immutable rationale, growth recorded here and -in the ledger); `ouroboros/platform_layer.py` 1499 and -`ouroboros/agent_startup_checks.py` 1490 stay inside theirs. - -Round-5.2 code commits (author `ouroboros-agent`, single-intent): `847a1151` -(fold of the lock family's try/except-pass into `contextlib.suppress`, no -behaviour change), `7923e624` (finding 1), `f2b118a4` (finding W), -`ff6bb399` (one snapshot-look recorder for the hold/append pins), -`79a1b9fb` (finding 3), `6ad110e9` (the suite fold), `503a0dd6` (finding 5 -and its LOW siblings), `95a53ad2` (finding 4), `208fe5ac` (finding 2); the -docs commit follows. Gate evidence: `docs/v7next/LEDGER_CORRECTIONS.md` -§"From the C6 fix-round 5.2 (base 2dd3e017)". - -### Round 5.3 — adversarial lenses over round 5.2 (fix-round base `5e4829e3`) - -Verdict of the lenses (independent read of `5e4829e3`, PoCs executed against -this lane's own code): six HIGH/MEDIUM findings open, seven LOW. **All -thirteen accepted**; twelve are fixed in code with red-first pins, one — the -recycled-pid wedge — is closed by the disclosure the finding itself offered -as its alternative (below). Nothing was argued away. Every code fix carries a -pin observed RED against the exact pre-fix shape or mutation it names. -The round ran in two halves: the first fix agent hit its session limit after -`cbfd23ce` with the docs staged and the ledger section unwritten; the resumed -half re-observed every red below in a scratch copy of this worktree before -changing anything, kept all ten commits, and closed two residues of the -round's own fixes (3b and 4b below). - -| # | finding | fix | red-first pin | -|---|---|---|---| -| 1 | HIGH — `_lock_identity` answers `()` for a descriptor it cannot `fstat` (ESTALE/EIO — the network filesystems this tier exists for), and the acquisition compared the two identities RAW: with the path momentarily absent (a reclaimer's own unlink→re-create window) `() == ()` was vacuously true and a descriptor for an unlinked inode was returned as the monetary lock — `HOLDERS: 2`, and the ordinary append transaction never heartbeats. Second half: with the path present but the fd unstatable the bare `os.close` left our file stamped with our LIVE pid, which an owner-aware reclaimer may never evict — the lock wedged for the life of the process | the won lock is returned only when its own identity READS and matches; an unreadable one fails closed (`return None`, warning) and takes our stamp off the path when the bytes there are still exactly the ones we wrote. The stamp is captured once, at the write. The module's four other identity comparisons already guarded for the empty answer; `:268` was the outlier this round's own delta introduced | `test_a_lock_whose_identity_cannot_be_read_is_never_a_hold` (`tests/test_lockfile_helpers.py`; an fd-blind `_lock_identity` plus an evicting flock) | -| 2 | MEDIUM — the RATIFIED design note still called EACCES a contention code, the negation of the code, of round 5.2's own pin and of §5.10; implementing the note re-opens finding W (a genuine access-denied re-contending for the whole 45 s monetary timeout). The unsupported set was named three-of-four in three places | DESIGN §8 states both sets exactly (`EAGAIN`/`EWOULDBLOCK`; `ENOLCK`/`EOPNOTSUPP`/`ENOTSUP`/`ENOSYS`) with the Win32 answers that map onto them, and a pin compares the note's spelled sets with the code's, by number (EWOULDBLOCK/ENOTSUP are aliases on Linux, not everywhere) | `test_the_design_note_names_the_exact_kernel_refusal_sets` | -| 3 | MEDIUM — `heartbeat` defaulted to `None` and `_beat` returned at once on it, so a pass entered without one swapped the monetary authority with NO ownership check at all; the single production wire was unpinned (the reserve-path pin looked only at the lock, never at the kwargs), and deleting it (MUT-U) left the whole battery green | both entry points take `heartbeat` as a required keyword and `_beat` has no `None` case — a dropped wire is a TypeError at the call, not a silent no-op — and the reserve-path pin asserts the callable it is handed | `test_reserve_path_compacts_only_past_config_threshold` (`assert callable(kwargs["heartbeat"])`), red under MUT-U | -| 3b | LOW, own residue of 3 (resumed half) — the required keyword closes the dropped wire, but a caller passing `heartbeat=None` was unpinned: `_beat(None)` fails at the call and that failure IS the existing "answer we cannot get at all" abort, so the pass is refused — unproven by any pin | pinned beside the False and the raising heartbeats: `None` answers `None`, the ledger stays byte-identical, no orphan is written | `test_a_lost_lock_aborts_the_pass_instead_of_swapping` (`None` clause), red on the pre-finding-3 module | -| 4 | HIGH — the orphan exemption decided on ONE row: a segment whose leading row equalled the live header was an uncommitted orphan. The newest segment IS the previous generation's whole file, so a ledger restored from a backup taken just after that compaction satisfied it while being a strict SUBSET of the exempted segment — the attempts that pass folded exist nowhere else, and the join reported them absent (PoC: 4 of 8 ids hidden, no corruption raised, both segments on disk) | an orphan is the pre-swap COPY of the live file and the live file only grows behind it, so its bytes are still a PREFIX of it — that is the test, and it needs no live-id parse. A restored generation carries rows past the end of the file it was restored from | `test_a_restored_previous_generation_is_out_anchored_not_taken_for_an_orphan[stamped]` | -| 5 | MEDIUM — with the stamp itself gone (a pre-compaction backup restored) the anchor never ran: every gate sat behind `live_header is not None`, so `archived_attempt_ids` answered `frozenset()` having touched the archive zero times, and `_live_baseline_header`'s docstring claimed `None` "means exactly one thing" | the anchor runs either way, with the floor at epoch zero; a data root with no archive directory and no stamp still answers empty at once; the docstring names both states and points at the archive as the thing that tells them apart | `…[unstamped]` (same pin) | -| 4b | LOW, own residue of 4 (resumed half) — the prefix proof re-opened the entry by NAME after classifying it, so the bytes compared against the live file came from a second open: an entry swapped in between (an empty file; a writer-less FIFO under `O_NONBLOCK`) read as zero bytes and passed the anchor although the segment claiming the newer generation had just been read — the same power as deleting that segment before the scan (disclosed), but one open more than the proof needs | one open per entry: classify, parse the leading row and — when it claims a newer generation — `lseek` to the start and compare from the same descriptor (six lines fewer) | `test_a_restored_previous_generation_is_out_anchored_not_taken_for_an_orphan` (a second open of any name answers an empty file; the verdict must still be reached), red on the two-open shape | -| 6 | MEDIUM — `_archive_dir_fds` wrapped every open below the root and left the ROOT's own outside the `try`; `archived_attempt_ids` calls it with no handler, so an unreadable data root (permissions, EMFILE/ENFILE) left a bare `OSError` on the join surface — the class round 5.2 closed one function away | the root open is inside the `try` and typed `usage archive root is not readable` | `test_an_archive_entry_the_anchor_cannot_open_is_typed_corruption` (chmod `0o111` half) | -| L1 | LOW — the name-tier root was marked BEFORE the append, so one transient failure (ENOSPC, an unwritable `logs/`) turned the durable typed event back into a log line for the process's life, and the 20 MB tripwire then names an event that does not exist; the key was the unresolved path while the sibling growth guard resolves (two spellings of one root on this workspace) | the mark follows the row that landed; both maps key on the resolved root | `test_the_pass_refuses_on_the_name_tier_while_appends_continue` (failing append, then a landing one; then the same root through a symlink) | -| L2 | LOW — a lock whose owner died and whose pid was REUSED is never reclaimed (POSIX `kill(0)` answers EPERM for another user's process on this shared host), although the enforced tier's probe flock would settle it; `PERSISTENCE.md` still called these locks self-healing *(correction, round 5.4: the mechanism was misstated — `pid_is_alive` read EPERM as DEAD, so another user's recycle WAS reclaimed through the age path and only a same-uid recycle wedged; EPERM reads alive since round 5.4 and the disclosure names the real wedge — §9 "Round 5.4", R7)* | **disclosed, not changed** — the finding's own alternative. Taking the probe flock whenever the file is aged would also evict a LIVE holder of a mixed-tier install (the name-tier process holds no flock), trading a rare wedge for the two-writer class §5.10 already names; DESIGN §8, the ARCHITECTURE row and the PERSISTENCE row now state the wedge and the hand repair | — | -| L3 | LOW — the swap's own crash durability was unpinned on both sides: deleting the candidate temp's `fsync` (MUT-H) or the ledger directory's `fsync` after the rename (MUT-L) left the battery green, while the archive half carries three pins | one pin records the fsync'd inodes and the moment of the replace | `test_the_swap_fsyncs_the_candidate_before_the_rename_and_its_directory_after` | -| L4 | LOW — `_snapshot_intact` reduced to a size comparison (MUT-E) also left everything green: every intrusion the pins inject is an append | one intrusion rewrites a byte in place, changing no length | `test_a_same_size_rewrite_between_the_recheck_and_the_replace_also_refuses` | -| L5 | LOW — no LockFileEx refusal could select the name tier on Windows (CPython lands ERROR_INVALID_FUNCTION and ERROR_NOT_SUPPORTED on EINVAL), so a lock-less Windows volume failed every monetary append closed instead of degrading as disclosed; the classification pin's POSIX half asserted through errnos the function does not set | `_win32_lock_error` classifies by one table — 33 busy, 1/50 unsupported, anything else winerror-derived and fail-closed — and the classified codes carry their own errno (the 4-argument form derives errno FROM the winerror on Windows and ignores the one passed). Live evidence for exactly those two codes: LockFileEx on `\\wsl$` answers ERROR_INVALID_FUNCTION ("Incorrect function", microsoft/WSL#5762) and on a Samba share ERROR_NOT_SUPPORTED (error 50, "The network request is not supported", samba list thread "FileLockEx Problem") | `test_lockfileex_refusals_classify_by_the_win32_error` (1/50 must land in the unsupported set; 5/32/6 in neither) | -| L6 | LOW — "a stray directory or FIFO is no segment and is skipped" held only where the dir-fd exists: without one the entry was OPENED first, which a directory refuses on Windows (every history question typed-corrupt for one operator `backup/`) and a writer-less FIFO blocks on | without a handle the classification happens BEFORE the open, and the path-based open carries `O_NONBLOCK` where the platform has one | `test_an_archive_entry_the_anchor_cannot_open_is_typed_corruption[False]` (the path shape taken on POSIX, FIFO under the SIGALRM guard) | -| L7 | LOW — `_CHAIN_UNION_CACHE` was never bounded or expired although its key changes at every compaction, and DESIGN §10 still claimed the per-question cost was "H cheap walks and ONE union" after round 5 added the anchor | the map is bounded (only the newest chain can be asked again) and §10 states the anchor's per-question cost honestly | `test_archived_id_union_is_built_once_per_chain` (bound clause) | - -Red observed, not argued — each pin against the exact pre-fix shape or -mutation it names, in this worktree (pin red, fix applied or mutation -reverted, pin green). Every row was re-observed by the resumed half in a -scratch copy of this worktree — the named source reverted to its base or -mutated as named, pin red; restored, the pins green together as one control: - -| pin | mutation / base | red observed | -|---|---|---| -| `test_a_lock_whose_identity_cannot_be_read_is_never_a_hold` | `5e4829e3` | `assert 14 is None` — a descriptor for an unlinked inode returned as the lock | -| same, second half | the truthiness guard applied, the stamp cleanup not | `a live pid was stamped on a lock nobody may reclaim` | -| `test_the_design_note_names_the_exact_kernel_refusal_sets` | `5e4829e3`'s design note | `assert 0 == 2` — the note spelled no set at all (and called EACCES contention) | -| `test_reserve_path_compacts_only_past_config_threshold` | MUT-U (`, heartbeat=ledger_lock` deleted at `usage_accounting.py:706`) | `the pass was entered without the lock's heartbeat` (`{'heartbeat': None}`); without the pin that mutation left 136 passed, EXIT=0 | -| `test_a_restored_previous_generation_is_out_anchored_not_taken_for_an_orphan[stamped]` / `[unstamped]` | `5e4829e3` | `DID NOT RAISE UsageLedgerCorrupt` in both; the standalone PoC measured 4 of 8 ids hidden with both segments still on disk | -| `test_an_archive_entry_the_anchor_cannot_open_is_typed_corruption` (chmod half) | `5e4829e3` | bare `PermissionError` from `_archive_dir_fds`' root open | -| same, `[False]` (no dir-fd) | `5e4829e3` | `TimeoutError: FIFO open blocked` — the path-based open hung until the 5 s alarm | -| `test_the_pass_refuses_on_the_name_tier_while_appends_continue` (event clauses) | `5e4829e3` | `FileNotFoundError: …/logs/events.jsonl` — one failed append suppressed the event for good | -| `test_the_swap_fsyncs_the_candidate_before_the_rename_and_its_directory_after` | MUT-H (temp `fsync` deleted) | the candidate's inode absent from the fsyncs before the rename | -| same | MUT-L (`_fsync_dir(path.parent)` deleted) | the ledger's directory absent from the fsyncs after it | -| `test_a_same_size_rewrite_between_the_recheck_and_the_replace_also_refuses` | MUT-E (`_snapshot_intact` by size only) | a receipt returned: the swap landed over the rewritten row | -| `test_lockfileex_refusals_classify_by_the_win32_error` (unsupported clause) | `5e4829e3` | `assert (0 in frozenset({37, 38, 95}))` for winerror 1 | -| `test_archived_id_union_is_built_once_per_chain` (bound clause) | `5e4829e3` | `AttributeError: … has no attribute '_CHAIN_UNION_CACHE_MAX'` | -| `test_a_lost_lock_aborts_the_pass_instead_of_swapping` (`None` clause) | `usage_compaction.py` @ `c71a36ea^` (heartbeat defaulted, `None` skipped) | a receipt returned: `None` skipped every proof | -| `test_a_restored_previous_generation_…` (second-open clause) | `cbfd23ce` (the two-open anchor) | `DID NOT RAISE UsageLedgerCorrupt`, both parametrizations | - -**Size ratchet — stated plainly, and an owner decision is now owed.** -`tests/test_usage_compaction.py` LEAVES the 1001-1500 band this round, -1492 → 1597, three lines under the 1600 HARD cap (the 1501-1600 zone is -ungated; above it the ratchet refuses new debt outright). Every commit of the -round carries a manifest that matches its own tree — the crossing commit -regenerates it — so the pairwise base-vs-tip lane is green at the tip and at -each parent, not only in the official CI shape. - -The round asked for eight new or extended pins on the monetary authority; two -of them (MUT-H/MUT-L, MUT-E) close mutations that had survived the entire -battery. The ways to stay inside the band were: delete contract-bearing pins; -fold the five distinct `hold lost at X` pins into one table, merging the -per-moment reasoning each docstring carries; or add a neighbour suite — which -this lane's own band rationale rules out ("as one suite") and the owner's -standing rule forbids as payment for a cap. None was taken. What was paid -honestly: the retry-durability pin now calls the fsync-failure pin instead of -re-implementing it verbatim (−11); this round's own docstrings are compressed -with every claim kept and one no-cover `fstat` guard is gone (−6); the resumed half added six lines (the `None` clause, the second-open clause) and compressed the same docstrings once more (−4). -`ouroboros/platform_layer.py` stays at exactly 1500 — the two kill-tree -sweeps reuse the module's own `force_kill_pid` (−17) and `unlink_lockfile` -lost its `exists()`-then-unlink race (−2), which paid for the identity guard -and the Win32 table. `ouroboros/usage_compaction.py` 1158 → 1197, inside its -band (rationale immutable between adjacent manifests; growth recorded here -and in the ledger). - -**The owner decision:** at 1597 the suite has three lines of headroom against -a cap that refuses new debt, so the NEXT pin on this surface cannot land -without one of — (a) splitting the CPL-5 join/history-reader pins into their -own suite (a real seam: a different module surface, a different consumer, its -own reason to change) against the recorded "as one suite" rationale, (b) an -authorized rebase of the ratchet baseline, or (c) accepting fewer pins on the -monetary authority. This round does not choose. - -Round-5.3 code commits (author `ouroboros-agent`, single-intent): `7d134fd8` -(the kill-tree fold, no behaviour change), `7e6b935e` (finding 1), `f7b8a578` -(finding L5), `c71a36ea` (finding 3), `e08a0392` (findings 4 and 5), -`82250a45` (finding 6), `023b2e84` (finding L1), `232500f4` (finding L6), -`c5fa1ac7` (findings L3 and L4), `cbfd23ce` (finding L7, and the band crossing it pays for); resumed half: `48f7b115` (finding 3b), `72d17f51` (finding 4b); the docs commit — -DESIGN §8/§10/§12, the ARCHITECTURE row, PERSISTENCE, this section and the -ledger, with the design-note pin — follows. Gate evidence: -`docs/v7next/LEDGER_CORRECTIONS.md` §"From the C6 fix-round 5.3 (base -`5e4829e3`)". - -### Round 5.4 — owner-bounded micro-round (base `096437c2`, owner batch №12, answer A) - -Scope fixed by the owner: the residual list left by the Fable lenses over -round 5.3 and the independent gpt-5.6-sol read-only review — eight items, no -new exploration, no redesign. **All eight disposed**: seven changed in code -with red-first pins, one (R8 c–f) closed by the disclosures it asked for. -Every behaviour fix on the monetary path was observed RED on the pre-fix -shape before the fix landed (table below), then green with it. - -| # | residual | disposition | fix | red-first pin | -|---|---|---|---|---| -| R1 | HIGH — `ENOLCK` sat in the unsupported set: "no locks available" is a missing lock daemon OR an exhausted kernel lock table, not the kernel saying this filesystem cannot, yet it selected the name tier — where the round-3 race returns; the per-directory tier cache was read and written with no synchronisation, so two first threads could run two probes and disagree | **fixed** | `_LOCK_UNSUPPORTED_ERRNOS` is exactly `EOPNOTSUPP`/`ENOTSUP`/`ENOSYS` (winerror 1 → `ENOSYS`, 50 → `EOPNOTSUPP`); ENOLCK keeps the enforced tier and a live acquisition the kernel refuses with it fails closed — no descriptor, our own file removed, no name protocol — so a lockd-less NFS refuses every monetary write typed (`UsageAccountingError`, the round-3 no-unlocked-fallback pin) and the pass is never entered; `_KERNEL_LOCK_TIER_LOCK` makes the probe single-flight: one probe, one verdict per directory per process. DESIGN §8, the ARCHITECTURE row and §5.10 spell the set (the design-note pin compares it by number) | `test_the_capability_probe_decides_once_and_leaves_no_residue` (ENOLCK clause; name tier now selected by EOPNOTSUPP), `test_enolck_keeps_the_enforced_tier_and_the_acquisition_fails_closed`, `test_two_threads_racing_the_first_probe_run_one_probe_and_read_one_tier` | -| R2 | MEDIUM — the root was marked "already told" whether or not `append_jsonl` landed the refusal row; the helper reports exhausted retries as `False`, not an exception, so one transient failure silenced the durable event for the process's life | **fixed** | the mark follows a `True` answer only; the failed append is logged by the helper and retried at the next refusal | `test_the_pass_refuses_on_the_name_tier_while_appends_continue` (a False-returning append between the raising and the landing one) | -| R3 | MEDIUM — the stamp-less fast path (`Path.is_dir()`) bypassed the typed root open: a regular file where the archive directory belongs answered a silent `frozenset()`, an uninspectable archive a bare `OSError` | **fixed** | before any compaction the question ends early only on the kernel's exact `ENOENT`; a non-directory or an uninspectable archive is `UsageLedgerCorrupt`; every case that reads anything then goes through `_archive_dir_fds`' typed root open as before (a plain `os.stat` classification first, because the Windows path shape has no dir-fd to route through) | `test_a_stamp_less_ledger_still_inspects_its_archive_fail_closed` | -| R4 | MEDIUM — bare `OSError` still escaped `archived_attempt_ids` through `pathlib`'s `is_symlink()` in `_archive_dir_bounded` (both levels) and `_segment_path` (the named segment): `pathlib` re-raises everything but ENOENT/ENOTDIR/EBADF/ELOOP, and an `archive/usage_ledger` readable but not searchable (a `chmod -R 600 data/` hardening) refused the segment's own `lstat` | **fixed, as a class** | both inspections wrap their `OSError` into `UsageLedgerCorrupt` ("cannot be inspected"), the same rule the opens follow since round 5.3 | `test_a_path_inspection_the_reader_cannot_make_is_typed_corruption` (the real chmod-600 shape, then `Path.is_symlink` raising `PermissionError`) | -| R5 | MEDIUM — DESIGN §8/§12.9, §5.8 here and the round-5.2 ledger line said a charge landed between the last proof and the rename is "erased, then surfaced by the post-swap re-read or quarantined at the next read"; neither could see it — the re-read compares the NEW inode against the candidate, the archive segment is the pre-row snapshot — so the loss was SILENT and a success receipt was returned | **fixed (POSIX) + docs corrected** | `_swap_ledger_fsync` holds the OLD inode open across the rename (the only witness left) and reads back whatever landed beyond the proven snapshot's length AFTER the fact: those bytes go to `state/usage_attempts.quarantine.jsonl` (`raw_base64`, the torn-tail shape, which flips `integrity_degraded`) and the pass raises `UsageLedgerCorrupt` instead of returning a receipt — never re-appended (`seq` belongs to the live file). Detected by size; Windows cannot hold the destination open through `os.replace` and stays a disclosed silent loss. The trigger's failure log no longer claims the ledger is uncompacted after such a raise | `test_a_swap_that_did_not_land_is_a_typed_failure_not_a_receipt[erased]` (the round-3 `[written_over]` variant unchanged) | -| R6 | MEDIUM — the production heartbeat wire was pinned as `callable(...)`: a constant-True stub (M9) survived the whole battery, every ownership proof a no-op | **fixed (pin)** | the reserve-path pin ages the lock file to the epoch, calls the heartbeat it is handed and requires `True` AND a renewed mtime — judged outside the contained call, so the red names the stub | `test_reserve_path_compacts_only_past_config_threshold` | -| R7 | MEDIUM — the recycled-pid disclosure named the wrong mechanism: `pid_is_alive` folded EPERM into "dead", so another user's recycled pid went down the age-eviction path (flock-guarded on the enforced tier) and only a same-uid recycle wedged — while DESIGN §8, PERSISTENCE and the round-5.3 L2 row said EPERM read alive | **fixed + disclosure corrected** | EPERM (the process EXISTS) reads alive; only ESRCH is dead; anything undeterminable reads present, as Windows already did — so `pid_provably_gone` is the exact negation of `pid_is_alive` and became one line (the reaper's docstring corrected with it). The real residual, stated in DESIGN §8, §5.10, ARCHITECTURE and PERSISTENCE: any live impostor — same uid or another — wedges the lock from the 90 s staleness window (`usage_ledger._locked`, `stale_sec=90.0`, a literal there, not a `config.py` constant) until it exits; the probe flock is deliberately not consulted while the pid reads alive (a mixed-tier name-tier holder has none) | `test_a_pid_that_refuses_our_signal_is_alive_and_its_lock_is_not_reclaimed` | -| R8a | LOW — `_build_candidate` defaulted `beat` to a no-op inside the monetary path | **fixed** | `beat` is required; omitting it is a `TypeError` at the call, contained by the pass, which then never compacts | control: `test_the_long_build_and_verification_section_beats_the_lock` red under M10 | -| R8b | LOW — the heartbeat's own failure modes were unpinned: the `not held or` guard (the heartbeat's analog of round 5.3 finding 1) and the `False` on a refused `utime` could each be removed with both suites green | **fixed (pin)** | the identity pin gains a refresh clause: a refused renewal answers False; an unreadable own identity with the path absent is not a match of two empty answers; a stranger's file at the path is not ours | `test_a_lock_whose_identity_cannot_be_read_is_never_a_hold` (refresh clauses) | -| R8c | LOW — a compaction committing between a question's live-header read and its anchor scan yields a transient false `generation newer` (UNKNOWN) | **disclosed** (DESIGN §10, §5.9, ARCHITECTURE) | the owner's scope for this item was disclosure; the bounded single retry the lens offered is not taken this round | — | -| R8d | LOW — stamp-less anchor consequences undisclosed: a ledger reset beside a surviving archive is a permanent `generation newer` verdict; a stray JSON file is corruption on a stamp-less ledger only | **disclosed** (PERSISTENCE ledger and archive rows' Reset columns, DESIGN §10, §5.9, ARCHITECTURE) | reset both together, or keep both | — | -| R8e | LOW — "an entry that is not a regular file … is skipped" was absolute; a UNIX socket cannot be opened at all (ENXIO) and reads as corruption | **qualified** (DESIGN §10/§12.5, §5.9, ARCHITECTURE) | an entry that OPENS but is not regular is skipped; one the kernel refuses to open at all is corruption. No socket pin: `AF_UNIX` paths are capped at 108 bytes and pytest's tmp paths exceed it, and a `chdir`-relative bind leaks process state into a parallel suite — a LOW not worth that hazard | — | -| R8f | LOW — two absolutes: "a release unlinks before its close, under the still-held flock" (POSIX only: Windows closes, then re-checks and unlinks) and no mention of the contention-branch orphan (an `EAGAIN` on the creator's OWN fresh file leaves a live-pid-stamped file the creator re-contends against and no owner-aware acquirer ages out) | **disclosed** (DESIGN §8, §5.10, ARCHITECTURE) | the orphan shape has no in-protocol producer (an evictor's flock unlinks what it judged): theoretical on the enforced tier, stated | — | - -Red observed, not argued — each pin against the exact pre-fix shape or -mutation it names, in this worktree (pin red, fix applied or mutation -reverted, pin green): - -| pin | mutation / pre-fix shape | red observed | -|---|---|---| -| `test_a_pid_that_refuses_our_signal_is_alive_and_its_lock_is_not_reclaimed` | `platform_layer.py` @ `bd9e99a4` (EPERM folded into dead) | `assert (False is True)` on `pid_is_alive(EPERM)`; the lock clause alone: the aged lock evicted, fd 3 returned, the file re-stamped with our pid | -| `test_the_capability_probe_decides_once_and_leaves_no_residue` (ENOLCK clause) | `platform_layer.py` @ `01c89685` (ENOLCK in the unsupported set) | `AssertionError: 37` — errno 37 selected the name tier (`False is True`) | -| `test_enolck_keeps_the_enforced_tier_and_the_acquisition_fails_closed` | same | `enforced = False`; a descriptor (fd 3) returned on the name tier, the lock file present, no kernel call made | -| `test_two_threads_racing_the_first_probe_run_one_probe_and_read_one_tier` | same (no cache lock) | `2 == 1`: both threads ran a probe | -| `test_the_pass_refuses_on_the_name_tier_while_appends_continue` (False-returning append) | `usage_compaction.py` @ `7ce7e83d` (mark regardless of the return value) | `FileNotFoundError: …/logs/events.jsonl` — the False answer marked the root, no row ever landed | -| `test_a_stamp_less_ledger_still_inspects_its_archive_fail_closed` | `usage_compaction.py` @ `b9c43911` (`is_dir()` fast path) | regular file: `DID NOT RAISE`, answered `frozenset()`; `archive/` chmod 000: bare `PermissionError` | -| `test_a_path_inspection_the_reader_cannot_make_is_typed_corruption` | same (bare `is_symlink()`) | `usage_ledger` chmod 600: bare `PermissionError: [Errno 13] … segment_ep0001_….jsonl` from the segment's own lstat; `Path.is_symlink` raising: bare `PermissionError` | -| `test_a_swap_that_did_not_land_is_a_typed_failure_not_a_receipt[erased]` | `usage_compaction.py` @ `d99ff6a9` (no old-inode witness) | `DID NOT RAISE`; standalone: receipt returned, the charge gone from the ledger, no quarantine file, `integrity_degraded` False | -| `test_reserve_path_compacts_only_past_config_threshold` (heartbeat clause) | M9: `heartbeat=lambda: True` at the production wire | `a stub, not the held lock's heartbeat` (`[False] == [True]`); the other 84 items of the two suites green under the same mutation | -| `test_a_lock_whose_identity_cannot_be_read_is_never_a_hold` (refresh clauses) | M15: the `not held or` guard removed from `refresh_exclusive_file_lock` | `assert True is False`: the blind descriptor renewed with the path absent | -| same | M16: a refused `utime` answers True | `assert True is False` | -| `test_the_long_build_and_verification_section_beats_the_lock` (control, R8a) | M10: `beat` dropped at the `_build_candidate` call | `TypeError: _build_candidate() missing 1 required positional argument: 'beat'`, contained by the pass → `assert None is not None` | - -Disclosed, not fixed (with the reason each time): - -1. **R1's consequence.** An install whose `state/` answers ENOLCK - persistently (bare NFS without lockd) now refuses every monetary write - closed — `UsageAccountingError` at the writer, one warning per attempt - naming errno 37 — where round 5 ran the name protocol there. That is the - owner's decision (fail closed, no name tier); the repair is a filesystem - that locks. "Compaction refuses with a typed reason" is structural, not a - new code path: with no lock the pass is never entered, and the round-3 pin - `test_every_ledger_writer_refuses_when_the_lock_cannot_be_taken` is the - typed refusal. sol's further suggestion — binding the established tier to - the returned hold and passing that attestation to the compactor — is not - taken: the module lock leaves one verdict per directory per process, which - is the disagreement the attestation would have caught; a mixed-tier - install ACROSS processes stays the round-5.2 disclosure. -2. **R2 and fsync.** No `events.jsonl` row is fsync'd, this one included; - the mark is per-process memory that dies with the same crash that could - lose an un-fsync'd row, so a new process re-tells. The residual — a - delayed writeback error with the process alive (row lost, mark standing) - — is the same for every event row and is not closed here. -3. **R5's bounds.** Detection is by size: a same-size in-place rewrite of - the old inode inside the rename syscall is not a landed charge and is - not seen. The erased bytes are preserved and flagged, never re-appended - (a hand repair from the quarantine row). Windows: silent, disclosed. -4. **R7's trade.** The wedge now covers another-uid recycles too (they - were reclaimed through the flock-guarded age path before); the - alternative — the probe flock on any aged file — would evict a live - name-tier holder of a mixed-tier install (round 5.3 L2 stands). -5. **R8c–f** are disclosures by the owner's scope: the transient UNKNOWN - (no retry added), the reset-beside-archive verdict, the socket shape (no - pin: the 108-byte `AF_UNIX` cap and a `chdir` hazard), the contention - orphan and the POSIX-only release-under-flock. -6. **Windows** stays unexecuted on this host, as in every round. - -Size ratchet, stated plainly: `ouroboros/platform_layer.py` 1500 → 1497 -inside its band — `pid_provably_gone` folded to the one-line negation it now -is and the docstrings it gained reflowed, paying for `threading`, the tier -lock and the EPERM branch. `ouroboros/usage_compaction.py` 1197 → 1245 -inside its band; the owner asked for the band rationale to be extended in the -same commit when the file grows, but the ratchet's own transition rule makes -a surviving rationale immutable between adjacent manifests -(`validate_manifest_transition`, "surviving band rationale is immutable"), -so — as in rounds 5, 5.2 and 5.3 — the growth is recorded here and in the -ledger instead. `tests/test_usage_compaction.py` 1597 → 1597: the round -added five new or extended pins (+67 lines) and paid with two no-behaviour -commits — a `compacted` fixture folding twenty-one verbatim seed-then-compact -preambles (−39) and argument-list/data-literal reflows within the file's line -width (−28); no claim, docstring, message or assertion was dropped, and no -neighbour suite was added. The three-line headroom under the 1600 hard cap is -what it was; the owner decision owed since round 5.3 still stands. - -Round-5.4 commits (author and committer `Ouroboros`, single-intent): -`bd9e99a4` (the fixture fold, no behaviour change), `01c89685` (R7), -`7ce7e83d` (R1), `12558046` (R2), `b9c43911` (the reflow, no behaviour -change), `d99ff6a9` (R3 + R4), `ea4d4337` (R5), `9306f962` (R6), -`02338c9b` (R8a + R8b); the docs commit — DESIGN §8/§10/§12, the -ARCHITECTURE row, PERSISTENCE, this section and the ledger — follows. Gate -evidence: `docs/v7next/LEDGER_CORRECTIONS.md` §"From the C6 micro-round 5.4 -(owner batch №12 A, base 096437c2)". - -## 10. Round 5.4 close-out — three read-only lenses on `b4938c31`, operator disposition (owner batch №12 A) - -Verdicts: 3 × NEEDS_FIXES, no HIGH; 3 MEDIUM + 7 LOW. Fixed here (base `b4938c31`), pinned red-first: - -| finding | disposition | pin → pre-fix shape → observed red | -|---|---|---| -| MEDIUM R1 (two lenses) — ENOLCK fail-closed landed in the SHARED primitive: on a lockd-less NFS `state/` every `acquire_exclusive_file_lock` caller failed, no model call could dispatch; the owner decided "compaction refuses", not this | **fixed**: ENOLCK is the name tier with its errno recorded beside the verdict (`_KERNEL_LOCK_TIER[dir] = (enforced, errno)`); `acquire_exclusive_file_lock(refuse_name_tier_errnos=…)` lets a caller fail closed on a recorded errno; only `usage_ledger._named_lock` names ENOLCK. Ordinary locks keep the name protocol they always ran there; money refuses typed | `test_the_capability_probe_decides_once_and_leaves_no_residue` (ENOLCK clause) and `test_enolck_is_the_name_tier_for_ordinary_locks_and_a_typed_refusal_for_money` → `platform_layer.py`/`usage_ledger.py` @ `b4938c31` → `assert True == (True, 5)` (a bare bool cached, ENOLCK enforced) / `assert True is False` | -| MEDIUM R4 — `_segment_path` resolved with `Path.resolve(strict=False)` one line BEFORE the typed `is_symlink()`: a symlink loop escaped as `RuntimeError("Symlink loop …")`, a readlink race as bare `OSError` | **fixed**: `os.path.realpath` (non-strict, never raises on a loop) inside the same `try`, `except (OSError, RuntimeError)` → `UsageLedgerCorrupt` | `test_a_path_inspection_the_reader_cannot_make_is_typed_corruption` (self-loop clause) → `usage_compaction.py` @ `b4938c31` → `RuntimeError: Symlink loop from …` and `OSError: [Errno 40] Too many levels of symbolic links` | -| LOW R3 — the stamp-less ENOENT exemption used a FOLLOWING `stat`: a dangling link at either archive level answered a silent empty set where the stamped reader answers corruption | **fixed**: `lstat` both levels first; `S_ISLNK` → typed `usage archive path is a symlink`, other `OSError` → typed `cannot be inspected`; pin deferred (the compaction suite sits at its 1600-line cap, disclosed below); mutation-verified by hand on this host (dangling link at `archive/` → typed) | — | -| LOW R5 — the old-inode witness was opened by PATH before the proof and not tied to the inode the precondition proved; a vanished ledger at the witness open was a bare `OSError` | **fixed**: `owned_and_intact` also proves `fstat(old_fd)` and `stat(path)` name one inode; the witness open is wrapped into `_Abort` (an abort by policy) | — (behaviour-preserving strengthening; no pin, disclosed) | -| LOW R1 — DESIGN §8 "decides once … cached" was absolute; an unprobeable directory answers enforced UNCACHED | **docs**: DESIGN §8, packet §5.10/§9, ARCHITECTURE row | — | -| LOW R6 — the strengthened heartbeat pin proves renewal + True, not ownership: a lock-TOUCHING stub survives it | **disclosed, not fixed**: the pin proves the callable renews THIS lock file's age (the production wire's only observable) — a stub that touches the production lock path is a contrived mutation; the suite is at its line cap | — | -| LOW R7 — EPERM→alive is a flip of a primitive shared by 12 non-test consumers, disclosed only for the monetary lock | **docs**: DESIGN §8 residual, packet §9 R7, ARCHITECTURE/PERSISTENCE wording name the shared primitive and the consumers that now defer | — | -| LOW R8d — "PERMANENT … for the life of the install" over-stated: the verdict lasts until the fresh ledger's epoch passes the surviving segments, which are then silently ignored | **docs**: DESIGN §10, packet §5.9, ARCHITECTURE row | — | -| LOW R8e — the socket qualification introduced its own absolute: a UNIX socket is corruption on the dir-fd shape only; the path shape's stat-before-open skips it | **docs**: DESIGN §10/§12.5, packet §5.9, ARCHITECTURE row | — | - -Sizes after the close-out: `platform_layer.py` 1500/1500 (band ceiling; net +3 on the policy, paid by rewrapping two prose blocks — no contract text dropped), `usage_compaction.py` 1262 (band), `tests/test_usage_compaction.py` **1600/1600** (the owner answered this in batch №13 item 11 = A: the archive-reader tests moved to their own module — the natural organ boundary, `archived_attempt_ids` vs the pass. After the split, the suite is `tests/test_usage_compaction.py` 900 + `tests/test_usage_compaction_archive.py` 660 + `tests/fixtures_usage_compaction.py` 123, same 64 node ids), `tests/test_lockfile_helpers.py` 568. - -### §10 addendum — the Windows matrix (run 33654743857 on bf8b6549) - -The lane never ran on Windows (not pushed until integrated). The first 3-OS matrix -after the merge was red on windows-latest only, in one class plus two test shapes: - -- **Class (product):** the `LockFileEx` tier held a MANDATORY byte-range lock on the - lock file, so a contender's `_lock_identity(probe)` read was refused and it could - never judge the hold — `test_concurrent_writers_keep_monotonic_sequence` («usage - accounting lock unavailable»), four `update_json_locked` timeouts, one lost - concurrent chat append. **Disposition:** `kernel_file_locks_enforced` answers - False on Windows — 7.0 ships Windows on the name tier it always ran (compaction - refuses there, typed and disclosed); the tier code stays for the post-release - re-enable with a stamp-safe byte range and a Windows-executed pin. - **Correction (stage-2 delta review, lens e2e-and-ci; run 33663258606 on `35b82db0`):** - the mandatory byte-range lock explained the bf8b6549 leg only; the same two tests - (`test_concurrent_writers_keep_monotonic_sequence`, - `test_terminal_projection_dedup_does_not_lose_concurrent_chat_append`) stayed red on - every name-tier leg after it, because the name tier is NOT «the protocol it always - ran»: since round 3 a contender opens the lock on every poll to read identity and - owner stamp, and on Windows (CPython opens without FILE_SHARE_DELETE) that handle - makes the owner's release unlink fail with a sharing violation — swallowed at debug, - the lock is orphaned with the owner's LIVE pid, which no owner-aware acquirer evicts: - the monetary lock refuses every later writer until restart, `append_jsonl` waits its - 2 s and lands unlocked (non-atomic append on Windows → lost rows). Reproduced on Linux - by the verifier's delete-semantics simulator (1 refusal → orphan → 120 timeouts in - 20 s). **Fix:** `_unlink_lock_path` retries a transient Windows refusal for a bounded - window at release and in `unlink_lockfile` (simulator: 288 refusals absorbed, 70 238 - acquisitions, no orphan); red-first pins - `test_windows_release_retries_a_contenders_transient_sharing_refusal`, - `test_windows_release_gives_up_a_refusal_that_never_clears`, - `test_posix_release_does_not_retry_a_permission_refusal`. Verified by the matrix on - the SHA carrying the fix (see LEDGER «From the Windows CI matrix on 35b82db0»). - **Re-enabled in 7.0 by the Windows kernel-tier lane (commit `eb3ba7a1`), owner batch - №13 item 1 = B: 7.0 does not ship until the kernel tier works.** The disposition above - stands as history; what changed is the byte range. `_win32_lock` now holds ONE byte at - `platform_layer._WIN32_LOCK_OFFSET` (`0x7FFFFFFF00000000`) instead of the whole file, so - the stamp bytes [0, 512) a contender must read are outside every locked range; - `kernel_file_locks_enforced` probes on Windows like POSIX and the compaction pass runs - there (`tests/test_usage_compaction.py`'s `data_root` no longer skips). Windows eviction - takes the same probe lock and unlinks after closing it — a WEAKER guarantee than POSIX's - «at most one may evict», stated as such in DESIGN §8: the loser's unlink is refused by - the winner's open handle, not by the kernel. Release order is unlock → close → unlink. - Linux-side pins (`tests/test_lockfile_helpers.py`): the range constant and its two - wrappers, an emulated LockFileEx refusing the same range while a contender still reads - the stamp, eviction only under the probe hold, and the release order read off the fd's - own liveness; plus the delete-semantics simulator (43 317 acquisitions in 20 s, 1 281 - sharing violations absorbed, no orphan). The Windows-EXECUTED proof is the next CI - matrix — see LEDGER «From the Windows kernel-tier lane (owner 1 = B)». -- **Test shape (lane pins, POSIX protocol):** five lock-ownership pins unlink or - rewrite a HELD lock file (impossible on Windows) and two swap pins assert - directory fsync/inode identity — `skipif(IS_WINDOWS)` with the reason stated; - `test_warm_segment_cache_revalidates_the_file_it_cached` accepts the path - shape's typed refusal text. -- **Bystander:** `kill_process_on_port`'s POSIX branch, now routed through - `force_kill_pid`, spelled `signal.SIGKILL`, which Windows lacks — the port-sweep - tests drive that branch with `IS_WINDOWS` patched False; spelled portably. diff --git a/docs/archive/v7next/DESIGN_RC_AUDIT_SCOPE.md b/docs/archive/v7next/DESIGN_RC_AUDIT_SCOPE.md deleted file mode 100644 index 7958aa67e..000000000 --- a/docs/archive/v7next/DESIGN_RC_AUDIT_SCOPE.md +++ /dev/null @@ -1,59 +0,0 @@ -# F3.3 design note — RC auditor machine-readable scope (ABI-7b, F13) - -The RC auditor is the migration-window instrument of Q6=A: a command that -scans a THIRD-PARTY install (skill manifests + settings document) and names -every ABI-7.0 incompatibility with its migration, before the owner upgrades. -It runs LAST in F3 (serial tail): its scope is the UNION of the FROZEN final -inventories of every F3 lane, so it cannot be built before they land. - -## Scope schema (machine-readable, one JSON document) - -```json -{ - "abi": "7.0", - "sources": {"tree": "", "inventories_frozen_at": ""}, - "checks": [ - {"id": "gateway-alias", "surface": "...", "removed": "...", "replacement": "...", "migration": "..."}, - {"id": "retired-setting", "key": "...", "since": "7.0", "behavior": "stripped-on-load", "migration": "..."}, - {"id": "comma-list", "key": "...", "replacement": "reviewer slots", "migration": "move config to slots BEFORE upgrade"}, - {"id": "plugin-api", "requirement": "manifest plugin_api field", "grandfather": "hash-bound PASS", "migration": "..."}, - {"id": "schema-stamp", "entity": "task_results", "consequence": "pre-7.0 history quarantined (Q8=B, BY DESIGN)"} - ] -} -``` - -Feeder inventories (each lane freezes its list as data, not prose): - -- ABI-3: the per-alias inventory (F11 axes: ingress/egress/JS/producer/ - stored/migration/removal-test) — five gateway aliases. -- ABI-5: the Q10-retired keys (`OUROBOROS_SCOPE_REVIEW_FLOOR` in - `RETIRED_SETTING_KEYS`; removed knobs `until_deadline`, - `stall_rounds_threshold`; removed `fail_tasks` has no install-visible key — - it is named only in the report prose). -- ABI-10: comma-list keys retired to `RETIRED_SETTING_KEYS` - (exact list snapped from `settings_defaults.py` at execution time). -- ABI-1: plugin_api admission facts (absent field ≡ LEGACY "1.3"; - new-PASS admission predicate; hash-bound grandfather). -- ABI-2: `_schema_version=1` stamps; the auditor MUST name the Q8=B - consequence: pre-7.0 task-result history is quarantined after upgrade, - deliberately (no converter exists). - -## Behavior - -- Read-only over the audited install; never mutates it. Output: typed report - (JSON + human rendering), exit 0 = clean, 1 = incompatibilities found, - 2 = install unreadable or the audit itself failed (traversal/report-write - OSError; PYTHONPYCACHEPREFIX inside the audited root without startup - bytecode suppression). A mandatory source the audit cannot read/parse is a - BLOCKING `unauditable-source` finding (exit 1, an audit-integrity plane - outside the five scope classes) — never a silent exit 0. -- N−1 fixtures (F14, shared with ABI-2/ABI-7a): a settings document and a - skill manifest authored by the previous minor run through the auditor as - test fixtures — real bytes, not synthetic shapes. -- Everything not machine-checkable stays an owner-attestation LIST the - auditor prints (F13 decision) — no pretend-coverage. - -## Verification hook - -RC audit fixture suite (new, F13/F14) — named in the ADOPTION ABI-7 row; -the auditor script lands under `scripts/` in F3.3. diff --git a/docs/archive/v7next/DESIGN_RESOLVED_MODEL_TARGET.md b/docs/archive/v7next/DESIGN_RESOLVED_MODEL_TARGET.md deleted file mode 100644 index ced981b30..000000000 --- a/docs/archive/v7next/DESIGN_RESOLVED_MODEL_TARGET.md +++ /dev/null @@ -1,48 +0,0 @@ -# F3.2 seam design note — ResolvedModelTarget (ABI-4) - -Greenfield §6-design: zero occurrences on tip and in the oracle — this is NOT -a transplant. Owner decision: plan §6 item 4 (frozen dataclass, typed -consumption by every lane). Home: the D02-owner domain — the -`model_slots.py` / `provider_models.py` seam (settings vocabulary side), so -the typed organ (lane A) must land first. - -## Contract - -One frozen dataclass describing a fully RESOLVED model destination — the -output of route resolution, consumed downstream without re-parsing strings: - -```python -@dataclasses.dataclass(frozen=True, slots=True) -class ResolvedModelTarget: - model_id: str # exact provider model id, e.g. "anthropic/claude-..." - provider_route: str # resolved transport lane, e.g. "openrouter" | "openai-compatible" | "local" - credential_ref: str # which configured credential/profile serves the call ("" = default) - effort: str # normalized reasoning-effort label ("" when N/A) - context_window: int # tokens; 0 = unknown (fail-open per cost-unknown rule) -``` - -Rules: - -- Frozen + slots; equality/hash by value. No Optional-by-default sprawl: - absent facts are typed sentinels ("" / 0), never None-vs-missing ambiguity. -- Constructed ONLY at the existing resolution seams; downstream code takes - the dataclass, never a raw comma/at-string. No parallel resolver: the - dataclass wraps what the current resolution already computes (reuse-first). -- No pricing fields: cost stays with the provider-route pricing SSOT - (hardcoded price tables remain banned). - -## Consumers (the F3.2 sweep, after lanes A and D4 integrate) - -1. `llm_fallback` candidate ladder — candidates become - `tuple[ResolvedModelTarget, ...]`. -2. `review_model_routes` / `reviewer_slot_config` — AFTER ABI-10 lands - (comma-list migration-read removed; slots are the only source). -3. Delegation lanes (delegate/claudexor route pinning) — typed target in the - run request instead of string slugs re-parsed per adapter. - -## Verification hook - -`tests/test_resolved_model_target.py` (new; the suite name is fixed by this -note — update the ADOPTION ABI-4 row's hook when the suite lands): frozen-ness, -value identity, construction at each seam, and a consumer sweep pin (grep-level: -no new comma/at-string parsing beside a seam that already yields the dataclass). diff --git a/docs/archive/v7next/DESIGN_TYPED_ORGAN.md b/docs/archive/v7next/DESIGN_TYPED_ORGAN.md deleted file mode 100644 index b5dc84b3b..000000000 --- a/docs/archive/v7next/DESIGN_TYPED_ORGAN.md +++ /dev/null @@ -1,68 +0,0 @@ -# F3.1 lane A design note — the typed tool-result organ (D02 re-derivation) - -Design-note-before-code (plan §5.4 rule). Audience: the F3.1 lane A operator. -Base for every claim: `ouroboros_v7next @ db944347`; oracle: `v7_wip @ 9f691656` -(frozen). Everything below is RE-DERIVED against tip bytes — verbatim reuse of -oracle spans is forbidden (re-prove trap, ledger D15 entry 3). - -## Why this lane is first in the F3.1 fan-out - -ABI-4 (`ResolvedModelTarget`, D02-owner) and ABI-6(б) (`_typed_or_adapted` -branch — exists ONLY in the oracle's `loop_tool_execution.py`, zero tip hits) -both execute inside this re-derivation; the lane's protection-closure returns -the D04 remainder (registry_core/tool_result into -SAFETY_CRITICAL_PATHS/HOT_CODE_PATHS). - -## Tip facts (spot-verified on db944347) - -- Zero `ToolResult` occurrences on the tree; `tools/registry.py` = 2686 lines - (ToolRegistry class ~2252 of them, from line 435); `loop_tool_execution.py` - = 1390 lines. -- Oracle organs: `tools/tool_result.py` (961 lines, 33 symbols), - `tools/registry_core.py` (1139); suites `test_tool_result{,_meta_boundaries,_t46}.py`, - `test_registry_core.py`, `test_tool_classification_differential.py`, - `test_tool_execution_classification.py`, `tool_classification_corpus.py`, - `test_llm_typed_policy_refusal.py`. - -## Composition (HOT-DEFERRED ledger rows; all re-derive, see f3 plan §4) - -1. `registry_core.py` — D04 entry 3: rows 156/167/170/171/174/175 + 17 - method→function extractions (receiver `self`→`registry`); the class does - not fit the band whole (>1500) — decompose through the extractions (Q11=B); - python_interpreter/artifacts import bindings (rows 213/214, 246/247) ride - along. -2. `tool_result.py` — D04 entry 4: closed code table, ToolResult/ToolCodeSpec; - row 139 `_compose_execute_result` is drifted — take tip bytes. -3. `extension_dispatch` typed dispatchers — D04 entry 5 (rows 187/188 +177 - producer-boundary lines) + `failure_kind` from extension_process_runner - (D14 entry 10). The ABI-9 digest READ in this file is the F3.2 seam, not - this lane. -4. `loop_tool_execution` cutover — rows 157-164, 826-828: retire result-text - classification (the «D02-петля» mandatory return). ABI-6(б) resolves here: - the unreachable `_typed_or_adapted` branch is NOT reproduced. -5. `_outcome_tool_errors` T1-partition + `reflection._trace_call_errored` - (D15 entries 3-4; re-derive against upstream status handling — the naive - port INVERTS the fix) + row 166 (retire 4 CLAUDE_CODE markers, 0 emitters). -6. D09 typed-policy-refusal subfamily — D02 entry 4: rows - 1706/1749/1751/1759/1760 + PROVIDER_POLICY_REFUSAL machinery in - llm_attempt, classification in loop_llm_call; pins - `test_llm_typed_policy_refusal.py` + fallback_ladder.json goldens 17→15. -7. Producer cutovers (tip==merge-base, reference typed): - core_file_tools/core_artifacts (10 producers via `_publish_tool_result`, - incl. row 332), shell_outputs 3-tuple, services.py, mcp_client, - tools/git a5e1cea3-cutover (`_publish_git_error`/`_publish_review_blocked` - + typed `_git_status`/`_git_diff`/stage cycles), control rows - 2548/2549/2556/2571/2574/2579 on the F2.1 leaves. -8. Test rows 832-833 + non-carried D04 entry 9 pins + protection-closure - (SAFETY_CRITICAL_PATHS/HOT_CODE_PATHS return, D04 entry 11). - -## Boundaries - -- Do NOT touch the ~60-70 str-returning tool handlers: handler-ABI conversion - is ABI-8 = POST-RELEASE (owner Q5=A + Q16=A; validator pins phase=POST). - Exactly one LegacyTextResultAdapter remains, with an inventory — the - owner-approved residual. -- ADOPTION hook for D02: `tests/test_tool_classification_differential.py` + - `tests/test_tool_result.py` (suites arrive with this lane). -- Size law Q11=B (1600 hard / band-rationale); `-m size_ratchet` before every - integration hand-off; ARCHITECTURE.md delta rides the same commit. diff --git a/docs/archive/v7next/LEDGER_CORRECTIONS.md b/docs/archive/v7next/LEDGER_CORRECTIONS.md deleted file mode 100644 index 50b822dc2..000000000 --- a/docs/archive/v7next/LEDGER_CORRECTIONS.md +++ /dev/null @@ -1,10314 +0,0 @@ -# Ledger corrections discovered during v7next transplants (append-only) - -Rows of the reference MIGRATION_v7.md / DOMAIN_MAP.md falsified by upstream drift, -with evidence, found lane by lane. Applied to the campaign's carried ledger at F5. - -## From the D15 pilot (base b9f7597f, 2026-08-30) -1. MIGRATION row 351 (`tools/core.py::_filter_out_project_store` -> - `project_facts.py::filter_out_project_store`, status "pending upstream - transfer") — SUPERSEDED-BY-UPSTREAM: the tip already carries the extraction - (project_facts.py byte-identical to the reference; core.py keeps only the - import alias at :17 with two call sites). -2. DOMAIN_MAP §D15 "v7 delta" prose — remeasure from the new base: consolidator - delta absorbed upstream (now 0); the true residue is +23/-12 in two files - (consciousness.py, reflection.py), not +25/-14 in three. -3. RE-PROVE TRAP (D02 family, reflection.py): the reference's - `_trace_call_errored` reads `_OK_TOOL_STATUSES` (with "untyped") from the v7 - leaf `_outcome_tool_errors`, which upstream does not have; a verbatim replay - of the delta over upstream's own status handling would invert the fix. The - D02 adoption must re-derive the delta against upstream bytes. -4. MIGRATION row 166 (retirement of 4 CLAUDE_CODE markers, id "none") — needs an - explicit ADOPTION disposition (umbrella under D02 or its own row): zero - production emitters of those markers exist at this tip (claim re-proven). - -## From the D16 split pilot (base 5d3398c1, 2026-08-30) -5. MIGRATION row 3911 (`usage_accounting.py::_legacy_snapshot` -> - `usage_legacy_import.py::_legacy_snapshot`, "verbatim extraction") — - BYTE-FALSIFIED as a copy source, transform still valid: upstream e9bf6f14 - rewrote the settings-hash comment inside the span (two lines "... prove - non-mutation by hash, but never copy / their contents into the usage - archive." became one line "... never copy contents."). The tool's --check of - the reference leaf against tip bytes fails token-lockstep on exactly this - span (ast=True, tokens=False); re-emitting from tip bytes is proof-green on - the first round with the reference declared set {_legacy_snapshot, _locked, - _read_records_locked} unchanged. Copying the reference leaf verbatim would - have silently reverted an upstream comment edit. -6. MIGRATION rows 3910-3914 status "pending upstream transfer" — RE-CONFIRMED - at this tip (contrast with the D15 project_facts case, entry 1 above): - upstream still carries the unsplit legacy import inside - ouroboros/usage_accounting.py (1600 lines, exactly at the hard cap; - IMPORT_REL at :60, the four defs at :1374-:1600). The extraction was - performed by this lane from tip bytes. - -## From the D03 lane (base f61ea3c2, 2026-08-30) -7. MIGRATION rows 3943-3946 (`ouroboros/context.py::{_project_room_fact, - _runtime_budget_info,_promoted_task_toolset,_delegation_capability_fact}` -> - `ouroboros/context_runtime_facts.py`, "pending upstream transfer") — - RE-CONFIRMED pending at this tip (context.py 1590 lines, the four defs at - :325-:544); the extraction was performed by this lane from tip bytes. The - reference leaf is BYTE-FALSIFIED as a copy source for ONE of the four - symbols: upstream b14ba397 ("expose available subagents in runtime - context") rewrote `_delegation_capability_fact` (docstring collapsed to a - one-line summary, `configured_route` dropped from the returned fact, - requested/applied profile evidence and `selected_subagent_id` added, plus - an all-absent -> None guard). Drift-probe `--check` of the reference leaf - against tip bytes: 3/4 spans ast=tokens=bytes=True, this span - ast=False/tokens=False; re-emitting from tip bytes was proof-green on the - first round. Copying the reference leaf verbatim would have silently - reverted the upstream subagent-profile feature. -8. MIGRATION row 3960 (`tests/test_context.py:: - test_delegation_fact_carries_configured_route_and_historical_rows` -> - `tests/test_context_runtime_section.py::`) — SOURCE SYMBOL FALSIFIED - by the same upstream train: b14ba397 replaced the test with - `test_delegation_fact_carries_historical_rows_and_profile_evidence` - (asserts `"configured_route" not in delegation`). The upstream successor - was moved to the row's destination as an identity continuation (tip - bytes); the carried ledger must rename the row at F5. -9. MIGRATION row 1641 (`tests/test_context.py:: - test_runtime_section_includes_improvement_backlog_digest` -> - `tests/test_context_runtime_section.py::`) — SOURCE SYMBOL FALSIFIED: - upstream 1b7f9497 replaced the test with - `test_improvement_backlog_digest_is_actor_scoped` (the digest is now - asserted ABSENT for ordinary/main/project/subagent tasks and present only - for evolution/deep_self_review). Moved to the row's destination as an - identity continuation (tip bytes); rename at F5. -10. S7a rows 1614-1640/1642-1648 — RE-CONFIRMED against tip bytes: every other - moved symbol of the tests/test_context.py split is byte-identical between - the tip monolith and the reference siblings (the D15-carried - tests/test_context_memory.py re-derived from tip bytes came out identical - — the carry was NOT stale), except row 1623's span - (`test_force_plan_metadata_adds_structured_notice_without_rewriting_user_text`), - which upstream drifted ADDITIVELY (rc-phaseC execution-shape assertions) — - tip bytes transplanted. Note: between the D15 pilot and this lane the 15 - memory tests existed in BOTH tests/test_context.py and - tests/test_context_memory.py on the integration branch (ran twice); this - lane completed the split and deduplicated. -11. NO-ROW upstream additions (candidate rows for the carried ledger): 3459dd12 - added 8 recent-chat/archive-generation tests to tests/test_context.py - (filters_archives_before_recent_bound, retention_proof_cross_thread, - reads_only_bounded_generation_suffix, materializes_a_bounded_row_suffix, - malformed_gap_even_when_search_matches_nothing, - resumes_unconsolidated_archived_generation, - archive_only_chat_chain_is_complete, missing_cursor_generation_hot_path). - They have no MIGRATION rows, so this lane left them in the remainder - tests/test_context.py (612 lines) rather than deciding their theme-home - unilaterally; by the memory-file theme they are candidates for - tests/test_context_memory.py at F5. -## From the D09 lane (base f61ea3c2, 2026-08-30) -7. MIGRATION rows 998-1013 (the 16-symbol task_lifecycle.py -> - cancel_custody.py settle-owner extraction) — HOT-FALSIFIED as a transplant - at this tip: upstream 65b5d19f ("Refactor cancellation ownership for size - ratchet") re-decomposed the same ownership differently (task_lifecycle - -408 lines into cancel_publication.py, owner_stop.py, - queue_transitions.py, task_reaper.py, new evolution_lifecycle.py, new - task_admission.py), then 3877e2ce/bea08137/21c59de2 reworked the - survivors. Of the 16 declared symbols, _intent_outcome_fields now lives - in cancel_publication.py:133 (task_lifecycle re-exports it at :26-35), - _durable_settled_status no longer exists, and the remaining bodies were - hardened by bea08137. Transplanting the reference cancel_custody.py would - create a second ownership answer -> F2 (cancel/delegation organ, re-split - from the upstream form). -8. MIGRATION rows 834-839 (cancel_intents.py D08 corrupt-projection rule) — - PARTIALLY SUPERSEDED-BY-UPSTREAM: at this tip request_cancel and - claim_intent already read strict and raise CancelIntentProjectionCorrupt - (upstream custody train 34ca9b02/38196641/c8048f2c/bea08137 rewrote the - module 888 -> 1281 lines), while release_claim, settle_intent, - mark_intent_scope and mark_finalize_control_drained remain fail-open - (AST probe over tip bytes; the reference pin - test_cancel_intent_corruption_s6.py runs red on exactly those four). - D08 must be re-derived against the rewritten bytes in F2 — same class as - entry 3 (the re-prove trap). -9. MIGRATION rows 2152-2180 (the S7b split of - tests/test_cancel_intents_phase_a.py) — falsified as a verbatim - transplant: the giant drifted upstream since the merge-base, and the - split's custody rows retarget monkeypatches to supervisor.cancel_custody, - which this tip does not have (row 2171's own note binds the split to the - extraction commit e3c107bd). Rides with entry 7 into F2. -10. DOMAIN_MAP §D09 pin test_subagent_worktree_registry_s6.py — - cross-listed: the module it pins, ouroboros/subagent_worktrees.py, is a - D07 owner, and the strict-registry behaviour the pin asserts lives in the - reference's +104/-22 delta to that module (upstream never touched it: - tip == merge-base). The pin transfers with D07's module delta, not with - the D09 lane (11 of its tests are red without it). -11. DOMAIN_MAP §D09 pin test_daemon_token_containment_s6.py — HOT-DEFERRED - with the delegation organ: its fixture's fresh delegate_start is refused - at this tip with reason "subagent_selection_required" ("A fresh delegated - start requires an explicit agent_session subagent_id. Only retry_of may - replay a selectorless immutable invocation.") — the upstream - delegation-by-construction train changed the entry contract the fixture - drives. -12. Two reference pins byte-falsified by upstream drift, residual facts - intact, re-pinned to tip bytes by this lane: (a) - test_panic_stop_port_sweep.py — the panic's kill_workers call now carries - reconcile_delegate_custody=False (dc4c0204), and this tree has 5 - ouroboros/server_*.py host leaves, not the reference's >= 11 (that floor - returns with the D11 server split); (b) test_owner_stop_fences_s6.py C5 — - _settle_descendants_hard now reuses the ordinary cascade's bounded - re-sweep loop (65b5d19f), so one live child yields two token-less sweep - calls instead of one; the pinned durable fact (the owner-stop sweep is - token-less) is unchanged. -## From the D17 lane (base def681bd, 2026-08-30) -7. Runtime split rows 465-494 (`headless.py` -> `headless_status.py` (11) + - `workspace_patch_capture.py` (19), "verbatim extraction") — RE-PROVEN at - this tip: all 30 spans byte-identical between the reference leaves and - `git show HEAD:ouroboros/headless.py` (hardened transplant --check, ast/ - tokens/bytes all green, both leaves, exit 0). The facade differs from the - reference only by upstream residue drift (child_ref promotion machinery, - `TASK_COST_META_FIELDS`/`replace_atomic` import changes) — replayed from - tip bytes, 947 lines. -8. Test-split rows for `tests/test_workspace_executor.py` -> - `test_workspace_executor_services.py` ("verbatim") — BYTE-FALSIFIED as a - copy source for exactly two functions, transform still valid: upstream - 06339bb7 ("fix: preserve service readiness truth") rewrote - `test_executor_local_service_lifecycle_hides_private_snapshot` (the READY - marker is now planted before a 25k log suffix and asserted scanned) and - upstream a849c9a6 ("fix: preserve executor probe uncertainty") extended - `test_executor_service_status_and_durable_record_redact_secret_like_args` - (adds the `'"readiness"' not in durable_text` clause). Both re-emitted - from tip giant bytes; the other 26 moved wexec spans are byte-identical. -9. Reference residual `tests/test_headless_cli.py` and sibling - `test_headless_workspace_shell.py` carry OTHER domains' v7 spellings - inside 9 moved/kept spans (`_run_shell_safety_check(registry, ...)` typed - result + `core_file_tools._repo_read` — D04/D05 split; `queue.init(path)` - 1-arg signature and `supervisor.state.QUEUE_SNAPSHOT_PATH` — D08/D33). - On this tree those leaves/signatures do not exist; per §5.3-Δ item 2 every - such span was reverse-mapped to the upstream spelling keyed to - `git show HEAD:tests/test_headless_cli.py` (upstream: string-returning - `registry._run_shell_safety_check`, module-binding `_repo_read`, - `queue.init(path, 600, 1800)`, `queue.QUEUE_SNAPSHOT_PATH`). These - adaptations return with their owning lanes, not with D17. -10. Thirteen upstream test functions written after the reference cutoff have - NO ledger rows (hcli: 4 task-api + 1 artifact-endpoint; wexec: 6 docker - stop/cleanup + 2 readiness). Placed by the split's own theme rule with - imports satisfied by the target headers (task_api×4, task_artifacts×1, - docker×6, services×2 + one `SimpleNamespace` header import); the carried - ledger needs rows minted for them at F5. Placement is disclosed, not - ledger-derived. -11. Row evidence `tests/test_headless_extraction.py` (rows 465-494): the - reference pin imports `ouroboros.tool_module_inventory` (a D04-family v7 - leaf absent from this tree); the transplanted pin keeps every clause that - types against THIS tree and replaces the frozen-tool-inventory clause - with an oracle-SHA note — the clause returns with the tools lane. -12. `ouroboros/task_results.py` (upstream-hot, +555 lines drift): the ledger - assigns NO D17 runtime split to it, and the reference copy is - byte-identical to the merge base (zero v7 delta) — nothing to transplant, - upstream bytes stand. Same zero-v7-delta fact re-proven for all 14 - non-split D17 runtime modules (task_status, retention, coop_checkpoint, - projects_registry, project_dialogue, project_lease, project_naming, - project_sources, tools/project_journal, workspace_admission, - workspace_preflight, workspace_executor, workspace_patch_rules). -## From the D18 lane (base d830cdba, 2026-08-30) -13. MIGRATION rows 3998-4000 (`launcher.py::{_prepare_windows_webview_runtime, - _show_windows_message,_windows_dll_dir_handles}` -> - `ouroboros/launcher_windows_runtime.py`, "pending upstream transfer") — - RE-CONFIRMED pending and transplanted by this lane. Drift-probe: hardened - `--check` of the reference leaf against `git show d830cdba:launcher.py` - is green on all three spans (ast=tokens=bytes=True, leaf invariants [], - exit 0), so the reference leaf IS tip bytes; adopted verbatim. Facade = - tip monolith minus the three spans plus the reference's re-export block; - byte-diff against the reference facade is exactly upstream dc4c0204's - +10 delegated-restart hunk (replayed from tip bytes). launcher.py - 1582 -> 1484 lines; band re-entry authorized via the official - regenerator's --band-rationale. -14. MIGRATION row 917 (`ouroboros/packaged_cli.py::_save_settings`, semantic - id D03: route the packaged bootstrap saver through the shared persistence - prologue and serializer) — HOT-DEFERRED with the settings seam. At this - tip `prepare_settings_for_persist` ALREADY EXISTS in ouroboros/config.py - :1084 (upstream absorbed part of the seam with a different signature — - an added `authored_keys` kwarg), while `serialize_settings` and the - row's pin tests/test_settings_read_seam.py do not exist. A verbatim - replay would bind a half-absorbed seam; the delta must be re-derived - against the tip seam form when the D12 config/settings split lands. - packaged_cli.py itself: tip == merge-base (zero upstream drift), so the - module stays untouched by this lane. -15. Reference `ouroboros/utils.py` +9/-1 delta (O_BINARY flag inside - `write_text_atomic`'s fsync path) — SUPERSEDED-BY-UPSTREAM as a class, - solved differently: upstream c15389f4 added `write_bytes_atomic` - (utils.py:276, fd opened with `getattr(os, "O_BINARY", 0)`) for - byte-canonical consumers and pinned `write_text_atomic` to "platform - newline semantics" in its docstring — a deliberate two-writer - decomposition. Replaying the reference's O_BINARY into - write_text_atomic would invert that upstream decision. No transplant; - cross-OS class registry should record ONE decision for this class - (upstream's). -16. Reference `tests/test_launcher_server_reaper.py` +8/-3 delta (normpath'd - REPO/DATA/OURS literals + POSIX-only skipif on - test_candidate_enumeration_uses_one_unbranded_full_width_ps_read) — - SUPERSEDED-BY-UPSTREAM as the same cross-OS class: upstream 7de26338 - normpaths the same three literals (also the python binary path, which - the reference did not) and, instead of skipping the enumeration test off - POSIX, monkeypatches `reaper.os` with a getuid stub so it runs on every - OS. Upstream form stands; nothing transplanted; the module itself is - byte-identical across tip/reference/base. -17. Reference `tests/test_packaged_runtime_and_lifecycle.py` +7/-2 delta — - DEFERRED WITH ITS OWNERS, not D18's to land: the `_enforce_harness` - clock hunk patches `supervisor.events_budget/events_chat_delivery/ - events_task_done` (D33 events-split leaves absent from this tree) and - `test_cancel_and_timeout_paths_share_one_salvage_helper` retargets to - `supervisor/cancel_custody.py` (HOT-FALSIFIED per D09 lane entry 7; - rides into F2). Tip bytes stand (upstream b3c9860e's -1 drift included). -18. Reference `tests/test_packaging_sync.py` +17/-7 delta - (test_system_prompt_lists_bible_in_safety_critical_set strengthened to - set-equality of BOTH prompts' inventories against - `runtime_mode_policy.SAFETY_CRITICAL_PATHS`) — UNROWED in MIGRATION_v7; - left at tip bytes per the wave-1 rule (unrowed test deltas are not - resolved unilaterally); candidate row for the carried ledger at F5. - Disjoint upstream drift a23e12b1 (push_to_remote test retargeted to - `_git_network_bounded`) stands. -## From the D04 lane (base d830cdba, 2026-08-30) -1. Registry-split rows RE-PROVEN against tip bytes for the four landed tools/ - leaves (tool_context, tool_catalog, tool_resolution, registry_guards, - registry_guard_process — 74 symbols): 61 spans byte-identical between the - reference leaves and `git show HEAD:ouroboros/tools/registry.py`; 13 spans - BYTE-FALSIFIED as copy sources by PURE UPSTREAM DRIFT (oracle==merge-base, - tip moved): _prepare_public_builtin_args, _executor_backend_candidate_allowed, - _authorized_managed_update_resolver (404B -> 1843B hardening), _disabled_tools, - _detect_runtime_mode_elevation, _SUBAGENT_SHELL_SECRET_MARKERS, - _detect_mutative_toggle_self_change, _detect_evolution_owner_control_self_change, - _detect_context_mode_self_lowering, _DENIED_READ_OPTIONS, - _is_pure_read_inspection, _detect_safety_mode_self_lowering, - _detect_owner_skill_attest_self_call. All re-emitted from tip bytes, - transplant proof green (ast=tokens=bytes on every symbol, exit 0). -2. Rows whose reference destination carries the TYPED-RESULT cutover semantics - (PURE V7 DELTA; tip==merge-base): 144 (_normalize_dispatch_path_args reduced - to a projection), 184 (_binding_error_text native codes), 185 - (_payload_dispatch_constraint typed second element), 226 - (_managed_update_code_tool_block thin wrapper), 138 (ToolEntry shallow-frozen - — also upstream-drifted: tip added the alias_for field). This lane moved the - TIP bodies verbatim; the typed deltas are deliberately NOT ported — they ride - with the F2 typed-result organ, not with a byte-preserving relocation of a - protected file. -3. HOT-DEFERRED: ouroboros/tools/registry_core.py (rows 156, 167, 170, 171, - 174, 175). Evidence: tip ToolRegistry is a 2252-line class (probe: tip span - 124364B vs reference 49860B, ast_equal=False); the reference slimmed it via - 17 method->function extractions (rows 189, 224, 225, 230, 235-242, 287, - 291-293) which change the receiver (self -> registry) and are NOT - byte-preserving relocation — out of bounds for the protected - tools/registry.py under this lane's mandate. ToolRegistry and the four - process/mutation constants stay in the facade; the class also would put the - new leaf straight into the >1500 band. Re-split from the upstream form in F2. -4. HOT-DEFERRED: ouroboros/tools/tool_result.py. 32 of the reference leaf's 33 - top-level symbols do not exist at tip (the ToolResult/ToolCodeSpec organ, - D02-family approved deltas); the single registry-sourced verbatim row 139 - (_compose_execute_result) also drifted at tip (661B vs 671B). Creating a - one-symbol leaf under the organ's name would falsely anchor the F2 re-split; - _compose_execute_result stays in the facade. -5. HOT-DEFERRED: rows 187/188 (ToolRegistry._dispatch_mcp_tool / - _dispatch_extension_tool -> extension_dispatch typed dispatchers). - tip tools/extension_dispatch.py == merge-base (116 lines); the reference's - +177 lines are the producer-boundary ToolResult typing plus method - retirement. Upstream bytes stand; the methods stay on the class. -6. loop_tool_execution.py D04 rows (157, 159-164, 826-828) are ALL - retire/rename/type rows of the classifier cutover — nothing is emittable as - a byte-preserving span. Shared-monolith convention honored: this lane did - not touch ouroboros/loop_tool_execution.py at all (D01 owns the rest). -7. tools/core.py shared-leaf note (row 353, core.py::active_repo_dir_for -> - tool_resolution.py): already satisfied at tip by an import alias - (core.py:20 imports it from the registry; the registry facade now re-exports - it from tool_resolution — same object). core.py untouched by this lane. -8. tool_access split rows 495-535 RE-PROVEN against tip bytes: 39/41 spans - byte-identical; 2 BYTE-FALSIFIED as copy sources by PURE UPSTREAM DRIFT: - _skill_payload_base (upstream re-homed the body into - skill_payload_binding.resolve_skill_payload_base — copying the reference - leaf would have reverted that refactor) and ResolvedResourceBinding - (upstream added the logical_base_path field). Both re-emitted from tip - bytes, proof green. The D1 mirror-path defect (safe_relpath lstrip('/'), - lying "caller rejects" docstrings) travels in the moved tip bytes UNFIXED, - per the lane instruction — it remains an upstream issue-candidate. -9. Pins carried with disclosed adaptations (identity continuations to tip - bytes): tests/test_tool_owner_facades.py (+ the alias_for row in the - ToolEntry contract — upstream drift); tests/test_tool_access_extraction.py - (4 adaptations, listed in its docstring: tool_module_inventory clause - dropped until that leaf lands, backedge check narrowed to import-time - imports because the D18/D33 call-time handle is deliberate, one-matrix - clause asserts through the facade re-export, size bounds kept); - tests/test_workspace_authority_binding.py gains the reference's - tool_resolution identity test while its typed companion - (_normalize_dispatch_path_args_result) is NOT carried — it pins deferred - machinery. test_registry_core.py, test_tool_result*.py and the - classification-differential suites are NOT carried for the same reason. -10. Test-split rows 784-825 (tests/test_tool_capabilities.py -> 4 siblings) - RE-PROVEN against tip bytes: 34/42 moved spans byte-identical to the - reference siblings, 8 re-emitted from tip (test_search_code_has_result_limit, - test_local_readonly_subagent_execute_blocks_forbidden_tools, - test_local_readonly_subagent_initial_schemas_are_allowlisted, - test_schedule_subagent_in_initial_schemas, - test_schedule_subagent_inherits_workspace_executor_ref, and the three - test_schedule_subagent_required_*_for_readonly tests). Lossless: 61 == 61 - test functions, zero lost, zero added, no duplicate names introduced - (tree-wide AST dup scan; the 10 pre-existing identical-body duplicates - between test_review_cycles_dispatch.py and test_review_cycles_skill_dispatch.py - plus the test_tool_registered same-name pair predate this lane — D06/D05 - territory, reported not touched). 21 unrowed/kept tip tests remain in the - remainder; 3 header imports that lost their last reader were dropped there. -11. Protection-surface closure (code-side, protective-only): the reference - extends ouroboros/runtime_mode_policy.py::SAFETY_CRITICAL_PATHS and - supervisor/update_merge_policy.py::HOT_CODE_PATHS over the registry split - leaves — without that, guard bodies moved out of the protected registry - become writable in advanced mode and lose the hot-code label (this tree's - own parity rule, tests/test_lc2_owner_facades.py, pins the inverse - direction). This lane mirrored the closure for the five leaves that exist - here (registry_core.py / tool_result.py rows return with their leaves) and - pinned it (tests/test_tool_owner_facades.py:: - test_registry_split_leaves_keep_protected_label_parity). NOT mirrored — - for the owner/F5: the reference's prose updates to prompts/SAFETY.md:10 - and prompts/SYSTEM.md "Immutable Safety Files" (operator-off-limits - runtime prompts; enforcement is code-side, prose enumerates only the - facade for now), and the reference's extra HOT_CODE_PATHS row for - ouroboros/tools/extension_dispatch.py (nothing moved there on this tree — - adding it is an oracle delta beyond relocation parity). -## From the D12 lane (base d830cdba, 2026-08-30) -13. Split rows 855-867 (settings_scales), 868-879 (model_slots), 880-886 - (review_model_routes) — RE-PROVEN against tip bytes: every span of the three - reference leaves is ast=tokens=bytes=True against - `git show HEAD:ouroboros/config.py` (drift-probe first, exit 0); the leaves - landed from tip bytes and differ from the reference only in BETWEEN-SPAN - comments upstream rewrote inside config.py (EFFORT_SCALE header now names - exact-route request-wire recovery; the PROMPT_CACHE_TTL comment rewrapped) — - carried from tip, since the span proof is blind to inter-span comment lines. -14. Shared-leaf rows 840-846/852-854 (config.py) + 3238-3241 (provider_models.py) - -> settings_defaults.py — BYTE-FALSIFIED as a copy source on 4 of 12 spans, - transform still valid: upstream rewrote SETTINGS_DEFAULTS (advisory slot is - the routed id `anthropic/claude-sonnet-5`, `CLAUDE_CODE_MODEL` retired, - MAX_SUBAGENT_DEPTH default 2->3, `OUROBOROS_SOFT/HARD_TIMEOUT_SEC` live - again with a display-only note, plus new PRESENCE/SUBAGENTS/CLAUDEXOR/ - REVIEW_NATIVE_* keys), RETIRED_SETTING_KEYS (upstream itself retired only - PLAN_TASK_SWARM_HEARTBEAT_STALE_SEC and kept SOFT/HARD live — the - reference's D04 retirement of those two knobs is DIVERGENT-SUPERSEDED and - must be re-derived in its own return, not replayed), ENDPOINT_AUTHORED_ - SETTINGS (+OUROBOROS_SUBAGENT_PRESET_RECEIPT) and OPENROUTER_REVIEW_DEFAULTS - (routed advisory id + comment). Leaf emitted FULL from BOTH parents (the - shared-leaf convention: drift-probe per parent separately; the final - transplant --check runs against the two parents concatenated into one - upstream source so every span is verified in a single exit-0 report). - provider_models.py was touched ONLY by span removal + the settings_defaults - re-export import; its call-time `from ouroboros.config import ...` imports - are tip truth (D02-owned) and stand. -15. Split rows 887-912 (runtime_limits) — 3 spans byte-falsified by upstream - drift (get_websearch_timeout_sec docstring; get_search_code_wall_sec now - routes through _clamped_number_setting; get_max_subagent_depth reads the - named cap), all re-emitted from tip bytes. STRUCTURAL: upstream reshaped - `MAX_ACTIVE_SUBAGENTS_HARD_CAP = 500` into the tuple statement - `MAX_ACTIVE_SUBAGENTS_HARD_CAP, MAX_SUBAGENT_DEPTH_HARD_CAP = 500, 10`; - the UNROWED twin (consumed by ouroboros/tools/control_delegation.py via - config) rides the rowed statement into runtime_limits and the facade - re-exports both — the carried ledger must mint its row at F5. Tool note: - the hardened --check flags this one statement as `assignment to ` under undeclared_top_level even though BOTH bound names are - requested symbols (Tuple-target blind spot; every span proof in the same - report is green, leaf_invariants=[]) — the one lane gate that exits 2 with - a proven false-positive cause; the tool wants Tuple support at F5. -16. Rows 918-920 (launcher_onboarding, semantic delta D03/settings seam, - launcher half) — RE-PROVEN applicable and LANDED: the module is - byte-identical between tip and merge-base (zero upstream drift), so the - reference bytes apply verbatim; the pin renamed per row 920. The SERVER - half of the same seam (rows 1080-1081, server.py lifespan) is NOT landed — - server.py keeps the tip guarded write and its old pin; it returns with the - D11 lane. Two unrowed oracle test adaptations were mirrored because they - pin exactly this delta and go red without it: test_onboarding_wizard.py:: - test_the_launcher_onboarding_module_authors_no_onboarding_settings - (reference bytes) and tests/test_server_runtime.py (launcher clause -> - `"save_settings(" not in launcher_host`; the server clause KEEPS the tip - guard-string assertion, diverging from the reference's both-sides form - until D11 lands). -17. Rows 913-917 (the rest of the D03 settings seam: config.py - normalize_settings_raw/serialize_settings, gateway/owner_settings digest + - locked update, packaged_cli writer) — HOT-DEFERRED: upstream rewrote - load_settings_lock_held's read path through the NEW post-cutoff - settings_integrity module (read_settings_json_verified / - SettingsIntegrityError raise-through), which the reference does not have; - replaying the reference seam verbatim would revert the integrity feature - (the re-prove-trap class, entry 3). The whole seam machinery re-derives - against tip bytes in its own return; its pin tests/test_settings_read_seam.py - (a DOMAIN_MAP D12 pin) defers WITH the machinery — not transplanted by this - lane. -18. Pin adaptations recorded: test_settings_env_on_disk.py re-pinned one - literal to tip bytes (ENDPOINT_AUTHORED_SETTINGS gains - OUROBOROS_SUBAGENT_PRESET_RECEIPT — same upstream train as entry 14); - test_config_extraction.py gains MAX_SUBAGENT_DEPTH_HARD_CAP in the owner - inventory, Tuple-target parsing in its _top_level_names helper, and a - narrowed provider_models clause (the reference's "no ouroboros.config - import anywhere" + top-level model_slots import clauses type against the - reference's D02 rework of provider_models and return with the D02 lane; - the surviving clauses pin no IMPORT-TIME config read and leaf-object - identity of both moved literals). -19. settings_integrity.py — NEW upstream module (post-cutoff, absent from the - reference and the merge base), already D12 in scripts/v7next_domains.toml; - no ledger rows; upstream bytes stand. Non-split D12 modules re-proven: - colab_bootstrap.py / onboarding_wizard.py / secret_masking.py / - update_channels.py byte-identical across tip==ref==merge-base; - settings_setup_contract.py / subscription_install_presets.py pure upstream - drift (ref==merge-base, zero v7 delta) — upstream bytes stand. - -## From the integration seam (coordinator, base 0859b681, 2026-08-30) -1. Superseding note to D04 entry "four landed leaves": the lane landed FIVE - registry leaves (tool_context, tool_catalog, tool_resolution, - registry_guards, registry_guard_process) — the list in that entry is the - authority, its count word is a typo (wave-2 conformance review item 6). -2. Superseding note to D12 entry on the tuple-target gate: the verifier fix - landed in the wave-2 seam commit (unfold at any depth; non-Name leaves are - complex targets; probes in tests/test_v7next_transplant.py) — the "future - work / exits 2" claim in that entry is superseded. -3. Seam repair: [split_pending] registry row carries domain IDs again - (["D04"]) and [split_pending_leaves] carries the two hot-deferred leaves — - the first seam commit wrote the leaf list into the wrong section. -## From the D05 lane (base 0859b681, 2026-08-30) -1. Shell split rows 416-464 RE-PROVEN against tip bytes and landed. shell_process - (11 spans, rows 416-426) and shell_effects (12 spans, rows 453-464): every - reference-leaf span ast=tokens=bytes=True against - `git show HEAD:ouroboros/tools/shell.py` (drift-probe first, exit 0) — the - reference leaves ARE tip bytes, adopted verbatim. shell_outputs: only 16 of - the row set remain in the tip monolith; 14 byte-identical, 2 re-emitted from - tip: `_register_process_outputs` (ref moved, tip==merge-base — the - reference's typed-cutover 3-tuple/artifact_registered plumbing is a PURE V7 - DELTA, deliberately NOT ported, rides with the F2 typed-result organ) and - `_resolve_declared_output` (ref==merge-base, tip moved — PURE UPSTREAM DRIFT: - the lexical deliverables/casefold machinery; tip bytes are the leaf). -2. Rows 429 (`_allowed_output_roots`), 439 (`_UNDECLARED_OUTPUTS_MARKER`), 445-451 - (the six output/user-file regexes + `_OUTPUT_STAT_SLACK_SEC`) and 452 - (`_mentioned_user_file_outputs_without_declaration`) — SUPERSEDED-BY-UPSTREAM - as shell_outputs rows: upstream c7315c57 ("Relax scoped browser, native-read, - and Deliverables false blocks") extracted those ten owners into its own NEW - leaf `ouroboros/tools/shell_audit.py` (D05-owned, no ledger rows), and the tip - facade already aliases/imports them from there. The carried ledger renames the - destination of those ten rows at F5; the facade identity contract - (tests/test_shell_extraction.py) covers them at their upstream owner. -3. Core split rows 311-349 RE-PROVEN against tip bytes and landed - (core_file_tools 30 spans incl. row 311's tip alias form - `_SKILL_OWNER_STATE_FILENAMES = SKILL_OWNER_STATE_FILENAMES`; core_artifacts - 9 spans): 29/39 byte-identical between the reference leaves and - `git show HEAD:ouroboros/tools/core.py`; 10 BYTE-FALSIFIED as copy sources, - ALL of the same class — tip==merge-base, reference moved (the typed-result - cutover producers `_repo_read/_repo_list/_data_read/_data_list/_read_file/ - _list_files/_access_or_block/_send_photo/_send_video/_send_file`, i.e. the - rows whose own notes disclose `_publish_tool_result`, including row 332's - A.20 marker change). Tip bodies moved verbatim; the typed deltas ride with - the F2 typed-result organ (same class as D04 entry 2). Both emitted leaves - are proof-green (ast=tokens=bytes on every span, leaf_invariants=[], exit 0). -4. FACADE CONVENTION DIVERGENCE (disclosed): the reference cut core.py over with - NO facade (rows 311-349 carry "-" in the re-export column; consumers rebound - by rows 360-371 and unrowed edits to vision/query_code/edit_ops/ - delegate_output/shell_guards). This tree keeps a re-export facade on - tools/core.py instead (the §5.3-Δ2 item-12 partial-split idiom, matching the - shell facade): the tip consumer surface grew far beyond the reference's (6 - production modules + 20+ test files import the moved names from tools.core - at this tip), and a no-facade cutover is a pure-hygiene consumer rebind that - can land as its own wave at F5 without re-proving spans. Identity is pinned - (`core.X is core_file_tools.X / core_artifacts.X`, - tests/test_core_extraction.py::test_core_facade_reexports_every_moved_identity); - the reference's `isdisjoint(vars(core))` clause is replaced by that pin. -5. Rowed TEST bindings landed: rows 363-371 (test_send_file/photo/video -> - core_artifacts) and row 361 (test_filesystem_root_observability::_read_file -> - core_file_tools). Row 362 (tests/test_headless_cli.py::_repo_read): the D17 - split moved that consumer into tests/test_headless_workspace_shell.py (D17 - lane entry 9 reverse-mapped it to the upstream spelling); this lane completed - the row at its successor location (core_file_tools binding). Row 360 - (browser.py::_readonly_subagent) NOT landed: the reference's browser delta - bundles a D01 rebinding (`loop_messages._append_or_merge_user_content`) - absent from this tree; the facade preserves the exact object meanwhile — - rides with the consumer-rebind wave. -6. Cross-domain core rows already satisfied at tip (SUPERSEDED-BY-UPSTREAM - class, no action): the five tool_access rows (active_tool_profile, - build_resolved_resource_binding, decide_tool_access, normalize_root, - normalize_runtime_data_path — tip core.py imports them from - ouroboros.tool_access), read_text -> utils, row 353 active_repo_dir_for -> - tool_resolution (import alias, per D04 entry 7), _filter_out_project_store -> - project_facts (per D15 entry 1), and the two contracts/skill_payload_policy - rows (tip imports them as the `_policy_*` aliases). Registry rows 213/214 - (python_interpreter) and 246/247 (artifacts) ride with the HOT-DEFERRED - registry_core leaf (D04 entry 3): tip registry.py still carries those import - bindings (:59, :83); the protected file was not touched by this lane. -7. Unrowed reference deltas NOT replayed (candidate rows for the carried - ledger): (a) code_intelligence.py `collect_top_level_python_imports` (+92) — - its only consumer is the reference-only tests/test_top_level_import_graph.py - (domain-graph tooling; F5/quotient territory); (b) mcp_client.py ToolResult - cutover + `tool_name_collisions` field — F2 typed organ; (c) services.py - `_publish_tool_result` cutover coupled to the 3-tuple - `_register_process_outputs` — F2; (d) health.py module-debt band rendering — - types against reference-only ratchet metrics keys (`module_debt_1500_active` - etc.) that no producer on this tree emits, and the owner's Q11=B decision - picked the upstream size law — DIVERGENT-SUPERSEDED, re-derive only if the - debt-band UI returns; (e) vision/query_code/edit_ops/delegate_output/ - shell_guards import rebinds — pending with the consumer-rebind wave (all - keep working through the facade). -8. Oracle test adaptations mirrored in this tree's equivalents (§5.3-Δ2 item - 10): load_settings monkeypatches retargeted to shell_process (its only - reader moved there) in tests/test_shell_run_shell.py and - tests/test_iteration2_fixes.py; module-object patch handles retargeted to - core_file_tools in tests/test_repo_read_limits.py (read_text), - tests/test_runtime_reliability_v655.py (_list_dir) and - tests/test_workspace_authority_binding.py (build_resolved_resource_binding). - Path-keyed mirror: tests/test_process_custody.py `_POPEN_ALLOWLIST` row - "ouroboros/tools/shell.py" -> "ouroboros/tools/shell_process.py" (the - facade's only Popen site moved with `_tracked_subprocess_run`; suite green). -9. Zero-v7-delta re-proofs for the rest of the domain: media.py / - python_interpreter.py / code_search_rg.py byte-identical tip==ref==merge-base; - artifacts.py / recent_tasks.py / search.py / verify.py pure upstream drift - (ref==merge-base) — upstream bytes stand; shell_audit.py NEW upstream module - (no rows, see entry 2). tools/core.py band re-entry (2283 -> 1373) recorded - via the official regenerator's --band-rationale. -## From the D02 lane (base 0859b681, 2026-08-30) -1. llm.py split rows 1666-1793 + 4001-4003 (131 rows, ten leaves) RE-PROVEN - against tip bytes: 100 spans byte-identical between the reference leaves and - `git show HEAD:ouroboros/llm.py`; 28 spans BYTE-FALSIFIED as copy sources by - PURE UPSTREAM DRIFT (oracle==merge-base for every non-D09 one) and re-emitted - from tip bytes. The drift is the post-cutoff provider train: request-wire - custody (041e6e39, issue-229 phase 2b — request_wire_scoped decorators and - wire send/receipt hooks inside the send drivers and lanes), OpenRouter - attribution rework (9a20df6a — OPENROUTER_APP_HEADERS), anthropic native - custody (native_content_for_replay/retain_native_assistant_content), - timeout/custody hardening (802f1056, f702439f). Transplant-tool verify: - every module-level span ast=tokens=bytes=True, undeclared_top_level=[], - leaf_invariants=[], plus a member-level byte proof for all 10 mixins - (117 members byte-identical to the tip LLMClient members). -2. Row 1674 (`_applied_payload_cache_ttl`) — the ledger's own documented - one-identifier requalification (LLMClient -> _PayloadCachePolicyMixin) kept - from the reference; the only non-tip-byte span in the split besides row 1784. -3. Row 1784 (`_chat_local`, semantic id D09, the approved one-attempt delta) — - CARRIED, but BYTE-FALSIFIED as a verbatim copy source: upstream 802f1056 - added the exception-owned capture custody clause INSIDE the retry loop the - delta deletes; replaying the reference span verbatim would have silently - reverted that upstream clause (the re-prove-trap class, D15 entry 3). The - delta was re-derived on tip bytes: the `for attempt in range(3)` loop and its - sleep/last_exc arms are gone (one physical attempt per call, transient - failures surface to call_llm_with_retry), the custody clause and the - warning/error identities are preserved. Pins: the reference's - test_local_transport_makes_exactly_one_physical_attempt carried into - tests/test_context_overflow_hint.py; the two sibling local-lane tests - re-pinned per the reference (attempt count 3 -> 1, monkeypatches renamed to - the owner leaf llm_local); upstream's post-cutoff - test_local_retry_does_not_inherit_unrelated_physical_capture re-pinned the - same way (its durable fact — exception-owned capture only, never the - ContextVar — is unchanged; its `calls == 3` pinned the deleted loop). -4. D09 typed-policy-refusal subfamily (rows 1706, 1749, 1751, 1759, 1760 and - the reference-only llm_attempt symbols PROVIDER_POLICY_REFUSAL / - ProviderPolicyRefusal / _is_provider_policy_refusal) — HOT-DEFERRED with - evidence: zero occurrences of `provider_policy_refusal` anywhere at this tip - (no raiser, no classifier — loop_llm_call has no such code), and all five - consuming ladder bodies drifted upstream (802f1056/f702439f hardened them); - the refusal never surfaces without its D01-side classification, so carrying - only the ladder half would ship dead semantics onto reworked bytes. The five - bodies moved as TIP bytes; the reference pins - tests/test_llm_typed_policy_refusal.py and the two - `typed_policy_refusal` golden cases (fallback_ladder.json 17 -> 15) are NOT - carried — they return with the delta's own re-derivation. -5. UNROWED tip symbols `_RESPONSE_METADATA_LABEL_MAX_CHARS` and - `_bounded_response_metadata_label` (post-cutoff, llm.py top level) moved to - ouroboros/llm_openai_compatible.py with their ONLY reader - (`_normalize_remote_response`, row 1788); the facade re-exports both, so the - tip import surface is unchanged. Candidate rows for the carried ledger at F5. -6. provider_models rows 840-886/3238-3241 note-contract COMPLETED: the rows' - own notes say "provider_models now imports this leaf instead of lazily - importing config"; D12 landed the leaves and left the consumption to D02. - The two remaining call-time `from ouroboros.config import ...` reads - (parse_fallback_chain at resolve_credentialed_model, SETTINGS_DEFAULTS at - declared_model_settings) are now top-level leaf imports - (model_slots/settings_defaults; cycle-free, verified at import). The - reference pin test_provider_models_reads_the_shared_leaves_instead_of_ - importing_config is restored under its ledger name, superseding the D12 - lane's disclosed placeholder test_provider_models_reads_the_shared_defaults_ - leaf (its identity clauses are kept as a superset). Upstream's own - provider_models evolution (ACTIVE/LEGACY_MODEL_SETTING_KEYS, - *_in_settings twins, CLAUDE_CODE_MODEL retirement) is tip truth and stands. -7. ouroboros/llm_probe.py reference delta (+8/-6, tip==merge-base) ADOPTED - verbatim: the lazy executor import redirects from the llm.py facade to the - owner leaf llm_attempt (an llm_* leaf never imports its parent). Unrowed in - MIGRATION; required by the leaf rule the carried pin - tests/test_llm_extraction.py::test_llm_leaves_never_import_their_parent - enforces. Candidate row at F5. -8. Provider-route goldens (tests/fixtures/llm_golden, 9 files) RE-BASELINED - from tip behaviour via the suite's own `--write` entry: every diff class maps - to a named upstream train — attribution headers (X-Title -> - X-OpenRouter-Title + new referer, 9a20df6a), the `request_wire` disclosure - block in usage (041e6e39), bounded `response_finish_reason` / - `response_provider` labels, effort/dialect-ladder evolution, anthropic - native-content retention. One suite adaptation: the per-process random - `usage.request_wire.attempt_id` is projected to a presence flag (exactly the - suite's existing ledger_attempt_ids treatment) — without it the recording is - nondeterministic across processes. -9. Dead-patch class closed across tests: after the split, - `execute_physical_attempt(_async)` is read in llm_attempt, - `_execute_candidate`/`last_physical_attempt_capture` on the chat path in - llm_fallback, and the local lane's executor in llm_local. Reference - adaptations applied (test_capability_probe_accounting_v664, - test_prompt_cache_v664, test_retry_bypass_response_cache verbatim — - tip==base; test_effort_floor_v6732, test_usage_scope_transport_v664, - test_provider_key_test re-derived on tip bytes); the same rule applied to - two POST-CUTOFF upstream tests the reference never saw - (test_openai_chat_dispatch, test_issue229_synthesis — llm -> - llm_fallback, disclosed in-file); path-keyed mirror - test_review_prompt_caching::test_global_ttl_docstrings_name_every_consumer - re-pinned to `ouroboros/llm_attempt.py` (matches the reference's own bytes - for that clause). Patches of names the facade still OWNS or that are read - lazily through it (test_pricing fetch_openrouter_pricing, test_web_search - server tools, all LLMClient-method patches) verified live and untouched. -10. Reference adaptations NOT carried (other domains' v7 spellings, - reverse-mapped to tip per §5.3-Δ item 2): tests/test_multimodal_chat.py and - tests/test_provider_failure_reporting.py retarget imports to - loop_messages/loop_round_limits (D01 leaves absent here — tip bytes stand; - tip already re-homed _provider_recovery_hint into loop_transport itself); - the same import line in tests/test_context_overflow_hint.py keeps the tip - spelling. - -## From the D14 lane (base 92238298, 2026-08-30) -1. extension_loader.py split rows 2467-2519 (53 rows, six leaves) RE-PROVEN - against tip bytes: 49 spans byte-identical between the reference leaves and - `git show HEAD:ouroboros/extension_loader.py`; 4 spans BYTE-FALSIFIED as - copy sources by PURE UPSTREAM DRIFT (oracle==merge-base 8028f1df for every - one) and re-emitted from tip bytes: `_validate_child_ui_descriptor` and - `PluginAPIImpl` (widget-geometry promotion `_widget_geometry_from_render`), - `runtime_state_for_skill_name` / `runtime_state_for_loaded_skill` (durable - companion-health overlay `_apply_durable_extension_health`). Transplant-tool - verify per leaf: every span ast=tokens=bytes=True, undeclared_top_level=[], - leaf_invariants=[], exit 0 (80 spans across the ten leaves of this lane). -2. UNROWED tip riders (candidate rows for the carried ledger): - `_widget_geometry_from_render` -> ouroboros/extension_surface_names.py - (readers live in two leaves — child_catalog and plugin_api — and it is the - theme sibling of rowed `_widget_span_from_render`, which those same leaves - already import); `_apply_durable_extension_health` -> - ouroboros/extension_liveness.py (its only readers are the two moved - runtime_state_* spans). The facade re-exports both; the carried identity - suite pins both owners. -3. Row 2519-family `_ws_broadcaster`: moved to extension_plugin_api.py and - deliberately NOT aliased on the facade (rebindable module global — a - facade copy would freeze the value); RE-CONFIRMED as the reference - contract, pinned by tests/test_extension_loader_extraction.py:: - test_the_broadcaster_slot_has_exactly_one_binding. server.py reaches it - only through re-exported `set_ws_broadcaster`. -4. skill_review.py split rows (31 rows, four leaves): 25 executed from tip - bytes. SIX rows SUPERSEDED by upstream's own re-decomposition (386e9417 - "Max Review Cycles" moved the accepted-rebuttal ledger and the wave-budget - refusal whole into ouroboros/skill_review_cycles.py before this lane): - `_accepted_rebuttals_path`, `_load_accepted_rebuttals`, - `_persist_rebuttal_flips`, `_fail_items_from_history_entry`, - `_record_accepted_rebuttal` (rebuttals-leaf rows) and - `_review_wave_budget_block` (prompt-leaf row). Upstream ownership stands; - the facade keeps the historical underscore aliases via tip's own cycles - import; the carried identity suite pins that alias identity. The rebuttals - leaf was emitted with its four remaining rows; the prompt leaf imports - `load_accepted_rebuttals` from skill_review_cycles (tip truth), not from - the rebuttals leaf as in the reference. -5. skill_review drifted spans re-emitted from tip bytes (5): `_read_skill_text` - + `_build_skill_file_packs` (payload-snapshot digest gate, - expected_content_hash), `_build_review_prompt` + - `_run_skill_advisory_pre_review` (provider-neutral advisory critic rework - f8d87c69 — "Optional Advisory Pre-Review", run_advisory_critic, hasattr - no-op trap removed), `render_skill_review_block` (slot_id actor keys, - distinct-item count, sanitize_tool_result_for_log). -6. Test rows tests/test_extension_loader.py (45, five siblings + shared): - tip file has ZERO upstream drift since merge-base; 43 moved bodies - byte-identical, 1 reference adaptation KEPT (dual supervisor patch in - test_server_pickup_spawns_stops_and_redrives_missing_companion — PluginAPI - owner reads the supervisor from its own leaf), 1 reference spelling - REVERSE-MAPPED to tip (worker_main lives in supervisor/workers.py at this - tip; the reference's supervisor/worker_process.py is the D08 split still - pending here). Lossless: 52 test names before == 52 after, zero dup names. -7. Test rows tests/test_skill_review.py (65, five siblings + shared): 58 - moved bodies byte-identical; 3 re-emitted from tip bytes (pure test drift: - advisory_model_credentials_missing label, provider-neutral advisory - heading, review-delivery capture in - test_review_skill_prompt_loads_core_governance_artifacts); 3 reference - adaptations KEPT (patch retargets to leaf owners in - test_review_skill_quorum_failure_on_one_responder and the two pack-budget - tests). Row `test_skill_advisory_private_guards_precede_availability` - SOURCE-FALSIFIED: upstream f8d87c69 deleted the test and replaced it with - `test_skill_advisory_pytest_guard_precedes_availability` + - `test_skill_advisory_missing_internal_symbol_is_loud_not_silent`; per the - wave-2 rule the successors stay in the remainder with tip bytes (theme - re-home is F5) and the reference copy of the deleted test was NOT carried. - Lossless: 74 test names before == 74 after, zero dup names. -8. Identity suites carried: tests/test_extension_loader_extraction.py gains - the two rider rows of entry 2; tests/test_skill_review_extraction.py - adapted to tip — the reference's tool_module_inventory clauses dropped - (v7-only mechanism, module absent at this tip; F5 restores it with its - owner), a cycles-alias identity test added for the six superseded names, - the facade size bound relaxed 800 -> 900 (tip retains the cycles gate, - paid-fact stamping and _persist_reviewed_outcome the oracle-era monolith - did not have), and the three tip-retained lifecycle members added to the - patchable-seams pin. -9. Dead-patch class closed: the remainder's - `patch("ouroboros.skill_review._run_skill_advisory_pre_review", ...)` - retargeted to the prompt owner (mirrors the reference remainder :314); - tests/test_extension_companion.py dual-patches get_global_supervisor on - extension_plugin_api + extension_loader (2 tests, mirrors the reference - adaptation; the single-module patch was proven dead by a red run). Every - other facade-level patch site of moved names was verified LIVE: all - production consumers of is_extension_live / runtime_state_for_* / - `_lock`+`_tools` (skill_loader:1414) do call-time facade imports. -10. NOT carried, no ledger rows: the 8 post-cutoff D14 modules - (betterleaks_runtime, skill_payload_binding, skill_publish_github/result/ - scanner/snapshot — secret-safe publishing train 8cc2ac69; - skill_review_cycles — 386e9417; skill_review_usage — f18da8c3) stand on - upstream bytes untouched. The reference's UNROWED `failure_kind` delta on - ouroboros/extension_process_runner.py (typed timeout classification, - consumed by the reference's tools/extension_dispatch.py:187) is NOT - replayed — typed-dispatch family, Ф3 territory; tip bytes stand. The - supervised-future leak (tip extension_plugin_api.py span of PluginAPIImpl) - is preserved as-is per the plan (Ф3-acceptance carries the direct - regression test). Pre-existing at base, untouched, for the record: 10 - ast-identical duplicate test bodies between - tests/test_review_cycles_dispatch.py and - tests/test_review_cycles_skill_dispatch.py. -## From the D08 lane (base 92238298, 2026-08-30) - -1. Scope executed (the QUIET part): 16 leaves landed from tip bytes with the - transplant tool (ast=tokens=byte-roundtrip=True on every span, exit 0, - leaf_invariants=[], unread_declared=[]): control_events (rows 2520-2528), - control_routing (2529-2536, 3954), control_runtime (2557-2568) — the D08 - half of the SHARED D07/D08 tools/control.py; queue_schedules (2029-2040 + - alias rows 3950-3952); worker_promotion (2045-2054), worker_chat_lane - (2055-2060), worker_pool_lifecycle (2065-2076), worker_process (1024-1029); - events_chat_delivery (921, 923-929), events_budget (980-984), - events_coop_checkpoint (964-969), events_project_routing (955-963), - events_schedule_task (945-949, 951, 953-954), events_subagent_admission - (930-944), events_worker_reports (985-991), events_runtime_controls - (993-997). Facades = tip parent − moved spans + grouped re-export block - (noqa discipline); facade audit green: every kept def/assign span - byte-identical to `git show HEAD:`, every moved name re-exported. -2. Drift-probe results (reference leaf --check against tip bytes, first step - per leaf): whole-leaf byte-true — control_events 9/9, queue_schedules - 12/12, events_coop_checkpoint 6/6, events_subagent_admission 15/15; - byte-falsified by pure upstream drift and re-emitted from tip bytes — - control_routing 5/9 spans, control_runtime 7/12, worker_promotion 3/10, - worker_chat_lane 2/6, worker_pool_lifecycle 2/12, worker_process 2/6, - events_chat_delivery 4/8, events_budget 3/5, events_project_routing 6/9, - events_schedule_task 2/9, events_worker_reports 4/7, - events_runtime_controls 1/5. "Verbatim" in the ledger was re-proven by - bytes in every case; no oracle semantics were replayed over tip drift. -3. SHARED-file convention (tools/control.py, D07/D08): this lane moved ONLY - the D08 rows (control_events/routing/runtime per DOMAIN_MAP); the D07 rows - (control_scheduling 2543-2556, control_subagent_spec 2537-2542, - control_task_results 2569-2579) remain in the facade untouched for the D07 - lane. Unrowed post-cutoff predecessor-authority family - (_MISSING_PREDECESSOR_SELECTOR, _predecessor_selector_error, - _attach_predecessor_authority_from_metadata) rides with its only readers - (_promote_chat_to_task/_route_to_project) into control_routing — a - def-time default-argument read of the sentinel makes a facade-retained - copy structurally impossible (F5 theme for the ledger's unrowed census). -4. HOT-DEFERRED, cancel/custody class (D09; upstream 65b5d19f re-decomposed - this ownership — replaying the reference rows would be a second answer): - - events_task_done rows 972-979: _resolve_lifecycle_fault reads - cancel_intents, _maybe_notify_provider_death reads task_lifecycle, - _task_done_durable_fault operates terminalization custody; the family is - one dispatch cluster, deferred whole. - - events_runtime_controls row 992 (_handle_cancel_task): the cancel ingress - handler itself. - - row 970 (_close_campaign_after_owner_stop -> queue_transitions.py) and - row 971 (events_evolution_done): owner-stop family; 65b5d19f made - queue_transitions.py its cancel-transition dumping ground, and the - evolution-done handler calls the deferred campaign-closure symbol as a - bare local name. - - queue_snapshot rows 2017-2020: restore_pending_from_snapshot restores - terminalization-retry rows and consults cancel_intents.has_active_intent - (65b5d19f machinery); persist snapshots the same fences. Deferred whole - (parse_iso_to_ts/_kept_service_pids ride only with their family). - - queue_timeouts rows 2021-2028: _enforce_task_timeouts_locked drives - cancel_intents/task_reaper/owner_stop. - - queue_evolution rows 2041-2044: upstream itself moved - _deliver_pending_owner_report/enqueue_evolution_task_if_needed into its - own supervisor/evolution_lifecycle.py (65b5d19f); creating the reference - leaf beside it would fork evolution-family ownership. - - worker_assignment rows 2077-2079 (assign_tasks reshaped by 65b5d19f's - 600-line workers.py rework; _cancel_unauthorized_evolution) and - worker_health rows 2061-2064 (_ensure_workers_healthy_locked writes - STATUS_CANCELLED terminal outcomes and terminalizes admission-blocked - retries). Both families stay on the facade. -5. Deferred SEMANTIC-DELTA rows (unsanctioned for this lane; tip bytes stand): - 1014-1015 (dispatch_event/EVENT_HANDLERS, delta D06 events taxonomy — the - event_taxonomy.py leaf and tests/test_event_taxonomy.py are NOT created); - 1021/1022/2082 (queue.init/workers.init/refresh_timeouts_from_settings, - delta D04 retired settings knobs — Q10/F3 territory); retired rows - 1017-1019, 1030, 2080-2081 (SOFT/HARD_TIMEOUT_SEC, TOTAL_BUDGET_LIMIT, - QUEUE_SNAPSHOT_PATH — deletions are semantics, not relocation). -6. Row 2016 (_handle_schedule_task -> events_schedule_task.py) DEFERRED with - a mechanism finding: the function carries the >300-line FUNCTION_DEBT entry - keyed by (path, qualname), and THIS tree's transition validator - (ouroboros/review.py::validate_manifest_transition) has no same-qualname - relocation rule — that rule is reference delta D11, ratchet machinery out - of this lane's bounds. The handler stays in the facade with its debt key; - the eight quiet schedule-family rows moved. Every seam name it reads - (_find_duplicate_task etc.) binds through the facade re-export, so existing - facade-targeted test patches keep intercepting (verified green). -7. Reverse-mapped preamble spots (oracle spelling -> tip truth): queue_schedules - `from supervisor.task_lifecycle import record_scheduled_admission` -> - `from supervisor.task_admission import ...` (65b5d19f moved it); the two - control leaves' `from ouroboros.tools.tool_result import ToolResult, - _publish_tool_result` deleted — the module does not exist at tip (D04 lane - hot-deferred that organ) and no tip span reads the names; alias mirrors - from tip parents: _bound_project_chat_id (supervisor/log_addressing.py, - upstream's own extraction), _build_scheduled_task_payload - (supervisor/task_dispatch.py), _reject_if_no_chat_target - (supervisor/task_admission.py), _once_due/_prune_consumed_once/ - _record_last_error (supervisor/schedule_time.py, rows 3950-3952 satisfied - as leaf preamble imports exactly like the tip parent). -8. Handle idiom: queue_schedules/_queue, worker_promotion|chat_lane| - pool_lifecycle/_pool declared sets re-derived on tip bytes (they grew past - the reference table by the post-cutoff facade helpers: - _announce_created_project, _apply_presence_promotion_authority, - _promoted_scheduled_outcome, _reject_promoted_after_attachment_stage, - _relocate_promoted_attachments, _stage_promoted_initial_attachments, - _reconcile_confirmed_dead_review_owner); events_project_routing gained the - D33-family handle `_events` for the single unrowed facade helper - _routing_attachments. All sets pinned in - tests/test_module_handle_extraction.py::LEAVES. -9. Path-keyed mirrors (Δ2 п.10): HOT_CODE_PATHS (supervisor/update_merge_policy.py) - += the 12 carried hot leaves (D04-block precedent); FUNCTION_DEBT key NOT - relocated (see 6); conftest _SERIAL_TEST_FILES needed no new rows (the new - suites are structural). Dead-patch class re-pointed to owner leaves, - mirroring the reference adaptations: test_coop_checkpoint_quiescence - (events_coop_checkpoint, events_subagent_admission), test_evolution_redesign - (queue_schedules._last_skill_schedule_sync), test_schedule_followup - (queue_schedules._write_scheduled_tasks), test_worker_crash_retry - (supervisor.worker_process trio), test_promote_chat_flow - (control_events._wait_for_promotion_admission, - control_routing._promotion_pool_disabled_from_snapshot), - test_evolution_restart_claims (`control_runtime as control`, the reference's - exact alias form), test_task_status_flow (control_runtime run_cmd/ - atomic_write_json), test_extension_loader (worker_main scan reads - supervisor/worker_process.py), test_process_resource_leaks (reference - bodies verbatim). All touched test files LOSSLESS (name multisets equal). -10. Pre-existing observation, NOT this lane's defect: tests/ - test_review_cycles_dispatch.py and tests/test_review_cycles_skill_dispatch.py - share 10 ast-identical test bodies at the base SHA (D15-class dup, D06 - domain) — left for the D06 lane. -11. Unrowed tip top-level symbols stayed in their facades (F5 census): - events.py _handle_main_llm_call_state/_parent_delegation_budget/ - _routing_attachments; queue.py 26 names (fences/admission/cancel seam); - workers.py 88 names (65b5d19f terminalization-retry/custody machinery); - control.py HIDDEN_LEGACY_SCHEDULE_PARAMS, _context_task_depth, - _materialize_child_attachment_manifest, maybe_emit_delegated_run_fanout, - get_tools + the predecessor family that rode into control_routing. -## From the integration seam (coordinator, D13 dispositions, 2026-08-30) -1. safety.py row 1016 (retire module-level supervisor import + _record_safety_usage, - pin test_safety_module_has_no_import_time_dependency_on_the_supervisor) — - LIVE, NOT landed on tip (import at :25, call at :1010). HOT-DEFERRED: - protected file; rides the protected-surface wave (F2/F3) with owner-visible - handling. -2. UNROWED live delta `_safety_drive_root` (fixes cwd-relative "../data" in - safety.py, tip site :899; oracle had no ledger row, prose-only in - DOMAIN_MAP). MUST gain a carried-ledger row before any replay; tip drift - collapsed two mb sites into one — replay needs re-derivation. Candidate - for the F5 carried-ledger mint. RISK: without this note the only useful - unrowed safety delta would be silently lost. -3. shell_guards.py lazy-import rebind (tools.core → core_file_tools) — - confirmed pending with the D05 consumer-rebind wave (D05 ledger §4(e)); - chain alive through the facade on tip. -4. runtime_mode_policy oracle delta remainder: registry_core.py + - tool_result.py protection closure returns WITH those two hot-deferred - leaves (D04 ledger §11); GIT_OPS_FAMILY_PATHS / RELEASE_INVARIANT_PATHS - re-cut returns with the G1 git_ops split (D10 wave) — recording now would - protect nonexistent files. -5. D13 census note: tip toml gives D13 eight owners vs oracle DOMAIN_MAP six — - write_shape.py and deliverables_shell.py are new upstream surfaces - post-freeze; not an oracle gap. -## From the D11 lane (base a56bb76a, 2026-08-30) -1. server.py split rows 1034-1078 + 3948-3949 (47 symbol rows): 43 landed into - the six reference leaves (process 5, routing_context 13, owner_routing 5, - liveness 4, maintenance 11, restart 5). Drift-probe FIRST per leaf: the - reference leaves are byte-true against tip except 10 spans byte-falsified - by upstream drift — _task_result_ground_truth (authority_source block), - _stage_mailbox_attachments / _route_project_chat_to_running_task / - _record_routing_receipt / _route_owner_message (attachment-report train), - _start_supervisor_liveness_watchdog (OB-03 monotonic clock + pid-keyed - toast), _periodic_supervisor_maintenance / _reconcile_delegated_runs - (child-ref promotion replay + terminal-reconciliation refresh), - _managed_update_pending_kwargs / _perform_supervisor_restart - (planned-handoff train). All 43 landed spans emitted from tip bytes by the - hardened transplant tool; --check green on every span (ast=tokens=bytes), - leaf_invariants=[], no oracle semantics replayed over drift. -2. HOT-DEFERRED rows 1070/1072/1073/1074 (_pending_restart, - _handle_restart_in_supervisor, _check_pending_restart_drain, - _perform_supervisor_restart): the upstream delegation train re-decomposed - restart ownership — _perform_supervisor_restart now WRITES the new module - global _planned_delegate_restart_transaction_id that server.main() reads at - the re-exec point; a byte-preserving relocation would fork that state (a - leaf `global` write is invisible to the facade's from-import binding). - D09-class "second answer about ownership" -> the four rows stay in the - facade, the drain record stays beside its only two readers; the deferred - inventory is pinned as the F2 work order in - tests/test_server_extraction.py::_SERVER_OWNED. -3. Rows 1080-1081 (server.py::lifespan, semantic delta D03 settings-seam - server half) HOT-DEFERRED: the reader-side halves of the same seam (rows - 913-917) are hot-deferred by the D12/D17 lanes (upstream rewrote the read - path through post-cutoff settings_integrity); landing the boot half alone - would leave provider normalization neither persisted nor re-derived. - server.py keeps the tip guarded write and its old pin — exactly the state - the D17 lane's note 16 anticipated. -4. Same-qualname ratchet delta (row 1033, semantic delta id D11) — LANDED. - ouroboros/review.py verified NOT in the AGENTS.md protected list. The - relocated_functions block replayed byte-identical from the oracle into the - tip-shaped validate_manifest_transition (tip keeps its adjacent= interval - form; the oracle-only MODULE_DEBT_1500 layer was NOT replayed — Q11=B keeps - the upstream size law). The pin renamed per the row, oracle bytes - (test_transition_rejects_function_swap_even_at_same_cardinality -> - test_transition_allows_a_same_qualname_relocation_but_not_a_swap). This - unblocks the D08 lane's row 2016 deferral (FUNCTION_DEBT relocation of - _handle_schedule_task). -5. Rows 1192/1259 (theme split into tests/test_delegated_reconciliation.py): - landed as the D11 SLICE only — the two tests that bind the - server_maintenance owner. The in-place owner-retarget grew the shrink-only - byte-debt giant test_delegated_subagent_transport.py by +40 bytes and the - ratchet refused it; the re-home is the designed pressure valve (the giant - shrinks 320340 -> 318310, the pin gains its family). The rest of the - reference sibling (orphan-sweep predicate, absent-run closure, release - points, _delegated_transport_shared helpers) arrives with the delegation - organ's test split (F2). Row 1656 (TestStartupGCFailClosed): only the - DATA_DIR owner-retarget mirrored; that file split also stays with F2. -6. Facade form: top from-import block (reference facade style), not an EOF - re-export block — forced by module-level reads of moved state (PORT_FILE = - DATA_DIR / ..., the logging bootstrap) before any def runs; base64 keeps a - noqa: F401 exactly as the reference facade does (its only user moved). - Facade audit green: every kept top-level span byte-identical to tip, no - facade-new symbols, every moved name re-exported by identity. server.py - 3191 -> 1640 lines; it remains a GIANT_PATHS entry (>1600 upstream law) and - only shrank, so the regenerated manifest changes one number (the transport - giant's byte debt). -7. Leaf conventions: emitted leaves carry `from __future__ import annotations` - (transplant-tool requirement; prior-lane convention) and tool span spacing. - Zero declared names and NO module handles — the reference design homes the - shared rebindable state in server_process (Events mutated in place, one - DATA_DIR, one logger), so all six are projection-only leaves. - Reverse-mapped preamble spots: server_liveness gains `import os` (drift: - os.getpid() in the toast key); server_restart's preamble/docstring describe - the landed five rows and name the deferral honestly. -8. Test adaptations mirrored path-keyed to THIS tree (Δ2 p.10): transport - giant tests -> sm owner (see 5); test_delegated_run_isolation._server_gc -> - server_maintenance.DATA_DIR (reference form); test_phase3c_observability_gc - (two post-cutoff tests, no oracle counterpart) -> maintenance owner for - DATA_DIR/_LAST_CANCEL_INTENT_SWEEP/time; test_project_routing_v664 -> - server_routing_context patch, compressed to one line so the file stays at - 1000 lines (below the 1001 band); test_client_surface -> owner_routing text - joined into the client_surface pin (reference form); - test_ws3_wedge_resilience (post-cutoff OB-03 tests) -> fake clock retargets - to server_liveness; test_panic_stop_port_sweep floor 5 -> 11 (the return - the D09 lane's note 12(a) anticipated). Deliberately NOT retargeted: - patches whose exercised readers stayed in the facade with the deferral - (test_server_shutdown, test_evolution_restart_claims, - test_restart_reconnect, test_promote_chat_flow, test_client_surface - _process_bridge_updates block). All touched test files lossless (the one - test rename is ledger row 1033; the two re-homed names moved whole). -9. Pre-existing base red, NOT this lane's defect: - tests/test_smoke.py::test_size_ratchet_transition_against_explicit_base - fails at pristine a56bb76a (probed in a throwaway worktree: 1 failed + 4 - passed) — parent 7d2dca49's manifest records - tests/test_devtools_benchmarks.py at 328116 bytes while its own tree holds - 328195 (the +79-byte cherry-pick residue the seam commit message itself - describes). The (a56bb76a -> this commit) pair is consistent: 327935 == - tree at the parent. -10. Module census, 34 D11 owners (tip vs merge-base 8028f1df vs oracle - 9f691656): 13 byte-identical in all three (client_surface, - gateway/__init__, gateway/files, gateway/logs, gateway/mcp, - gateway/onboarding_host, gateway/schedules, gateway/task_events, - gateway/task_hurry, gateway/ui_preferences, server_auth, server_entrypoint, - server_web); 17 pure upstream drift — tip bytes stand (gateway/_helpers, - claudexor_accounts, contracts, control, extensions, history, host_service, - marketplace, models, presence_settings, projects, router, skill_publish, - state, tasks, ws, server_runtime); 3 carry ONLY D03 settings-seam / - retired-knob (D04) oracle deltas -> HOT-DEFERRED with that seam - (gateway/owner_settings, gateway/onboarding, gateway/settings; D12/D17 - precedent); server.py split per 1. Gateway ABI/alias retirements untouched - (F3 territory); web/ untouched. -## From the D10 lane (base a56bb76a, 2026-08-30) -1. G1 split rows 3430-3457 (supervisor/git_ops.py -> 4 leaves, delta D35 - module-handle) executed for 26 of 28 rows from tip bytes with the transplant - tool (ast=tokens=byte-roundtrip=True on every span, leaf_invariants=[], - unread_declared=[], exit 0 per leaf). Drift-probe (reference leaf --check - against `git show HEAD:supervisor/git_ops.py`, first step per leaf): - git_ops_rescue 8/8 spans byte-true; git_ops_remotes 3/4 (push_to_remote - BYTE-FALSIFIED by PURE UPSTREAM DRIFT — a23e12b1 routed the push through the - bounded network runner; tip bytes emitted); git_ops_updates and - git_ops_reset byte-true on every span except the two f-string rows below. -2. Rows 3439 (prepare_managed_update) and 3449 (safe_restart) DEFERRED — both - spans stay facade DEFS: each reads the rebindable parent global BRANCH_DEV - (safe_restart also BRANCH_STABLE) inside f-strings, and the hardened - transplant gate fails closed on f-string reads of declared names ("the - token proof cannot cover f-string internals"). The reference leaves carry a - manual `_go().BRANCH_DEV` rewrite inside the f-strings, which this wave's - ast=tokens=bytes gate cannot re-prove (tokens_equal=False on exactly those - spans in the drift-probe). Their reads were dropped from the declared sets - (tool-verified unread otherwise); tests/test_git_ops_owner_facades.py pins - the two names as facade defs. Relocation returns if/when the tool grows - f-string token support (D12 lane already noted the same gate wants Tuple - support — same F5 tool-work theme). -3. git_ops rows 1031-1032 (DRIVE_ROOT/REPO_DIR config-aware pre-init defaults, - semantic id D13) — HOT-DEFERRED: live semantic delta to a protected file - (tip still binds `pathlib.Path.home()/"Ouroboros"` at :26-27, upstream did - NOT absorb the hermetic-isolation fix). Not byte-preserving relocation, so - out of this lane's mandate; rides the protected-surface wave with - owner-visible handling (same class as the coordinator's safety.py row 1016 - disposition). Its pin tests/test_git_ops_default_roots.py is NOT carried. -4. update_merge split rows 3426-3429 (-> supervisor/update_merge_plan.py) — - HOT-DEFERRED WHOLE with the update engine (F2 organ): rows 3427-3429 carry - semantic id D34 (carrier engine insertion points, spans SSOT - release_sync.py) and the single verbatim row 3426 (`_git_run`) is - SOURCE-FALSIFIED — upstream's update-flow redesign DELETED _git_run from - tip update_merge.py and rewrote the three D34 bodies (+517-line drift vs - merge-base; post-cutoff supervisor/update_candidate.py exists at tip, - absent from oracle AND merge-base). A one-symbol update_merge_plan.py would - falsely anchor the F2 re-split (the D04 tool_result.py class). The oracle's - +84 release_sync.py D34 delta (span-descriptor SSOT) defers with it; pins - tests/test_update_merge_owner_facade.py / test_update_carriers.py / - test_carrier_rebase_helper.py NOT carried. -5. tools/git.py split rows 374-415 executed for 41 of 42 rows from tip bytes - (five leaves, proof green per leaf, exit 0). Drift-probe against tip bytes: - git_plumbing 10/10 byte-true; git_evolution 3/5; git_repo_edit 2/4; - git_vcs_ops 7/10; git_review_cycle 7/12. Falsified spans, two classes: - (a) PURE UPSTREAM DRIFT (oracle==merge-base, tip moved): - _finalize_blocked_review, _review_cycle_infra_failure, - _check_evolution_commit_stage, _record_evolution_commit_receipt, - _repo_write, _str_replace_editor, _ff_pull (+ the drifted halves of - _run_reviewed_stage_cycle/_run_non_committing_review_cycle) — tip bytes - emitted; (b) PURE V7 DELTA (tip==merge-base, reference typed by the - git-control cutover a5e1cea3, oracle-only commit: _publish_git_error / - _publish_review_blocked plumbing and the typed returns in _git_status, - _git_diff, _stage_candidate_for_review and both stage cycles) — NOT - replayed, rides with the F2 typed-result organ (same class as D04 entry 2 - / D05 entry 3). The reference-only plumbing symbols _publish_git_error and - _publish_review_blocked were NOT created. -6. Row 392 (`_refuse_capped_attempt` -> git_review_cycle) — SOURCE-FALSIFIED: - upstream 386e9417 ("Max Review Cycles") DELETED the symbol and re-derived - the cap as the paid-cycle gate family (_free_cycle_gate, - _install_paid_dispatch_stamp, _advisory_and_tests_gate, - _repair_managed_merge_head, _finalize_pending_review, - _review_custody_pending, _subject_binding_mismatch_outcome, - _reconcile_and_clear_review_roster, _tests_preflight_block_message, - _managed_candidate_needs_proof, _managed_committing_phase_error, - _run_git_network_cmd — all unrowed post-cutoff facade symbols). The family - STAYS in the facade (F5 unrowed census); moved spans read it through the - call-time handle. -7. STRUCTURAL DIVERGENCE from the reference, disclosed: the reference's - tools/git leaves bind cross-leaf/parent helpers with plain import-time - from-imports; this tree's leaves declare EVERY parent-scope name their - spans read and route it through the call-time `_git()` handle (the - D18/D33/D35 mechanism, sets pinned in tests/test_module_handle_extraction.py). - Reason, twice re-proven by red runs during the lane: the tip test surface - monkeypatches those names on the PARENT facade - (test_git_review_bypass_gate `_run_parallel_review`, - test_update_status_cache `ensure_official_update_remote`), and an - import-bound leaf copy makes every such patch silently dead — the - monolith's module-global patchability is part of the moved behaviour. The - only import-bound exceptions are the f-string reads the gate cannot - rewrite (_sanitize_git_error in three leaves; format_protected_paths and - utc_now_iso in one each), named in each leaf docstring; zero test patch - surface exists for them today. The oracle's leaf-retarget test adaptations - (rows 770/775 monkeypatch targets on git_review_cycle, test_commit_gate / - test_vcs_target_binding / test_runtime_mode_registry_gating leaf imports) - are therefore NOT mirrored — tip facade targets stay correct on this tree. -8. Test-split rows 3150-3191 (tests/test_git_ops_recovery.py -> 3 siblings + - tests/_git_ops_recovery_shared.py) executed: 40/42 moved spans - byte-identical to the reference siblings; - test_official_fetch_timeout_kills_the_process_tree re-emitted from tip - bytes (upstream communicate(input=...) drift, same train as the - _run_git_process_bounded batch-stdin hunk); row 3179 - (test_dependency_sync_is_panic_tracked_and_killed_on_timeout) carried WITH - the reference's hermetic root binding per its own row note (the tmp_path - DRIVE_ROOT monkeypatch that keeps the mocked pip timeout from appending to - the live supervisor log). Lossless: 48 == 48 test functions. -9. Test-split rows 765-783 (tests/test_git_review_pipeline.py -> 4 siblings + - tests/_git_review_pipeline_shared.py) executed: 15/19 moved spans - byte-identical; re-emitted from tip: _get_registry_module (reference - imports the hot-deferred registry_core leaf — reverse-mapped to the tip - registry spelling), TestAdvisorySkipTests (post-cutoff upstream autouse - reviewer-slots fixture), TestBypassPathTestsRun / TestRouteSlotAwareBypassGate - (reference monkeypatch retargets, entry 7). The reference shared module's - unrowed `_get_git_review_cycle_module` accessor was NOT carried (nothing - on this tree reads it; facade targets stay live through the handle). - Lossless: 89 == 89 test callables. Path-keyed mirror: `_POPEN_ALLOWLIST` - in tests/test_process_custody.py += supervisor/git_ops_reset.py - (sync_runtime_dependencies moved with its waited+panic-tracked pip Popen — - mirrors the reference's own allowlist row). -10. D13-remainder protective closure (coordinator LEDGER entry 4) landed by - this lane per the D04 additive precedent: RELEASE_INVARIANT_PATHS - (ouroboros/runtime_mode_policy.py, protected — strictly additive literal - entries + comment) and scripts/run_external_review.py:: - _RELEASE_MACHINERY_PATHS += the four git_ops leaves; parity pinned by - tests/test_git_ops_owner_facades.py (protection + hot-code-parity clauses). - The reference's GIT_OPS_LEAF_MODULES/GIT_OPS_FAMILY_PATHS derived-set - re-cut of the protected file is NOT replayed (a structural rewrite beyond - additive closure — F5/owner decision); prompts-prose closure not touched - (same as D04 entry 11). HOT_CODE_PATHS needs NO git rows (parent unlabeled - at tip AND in the reference — parity, not blanket labelling). -11. Suite adaptation, disclosed: tests/test_module_handle_extraction.py - `_module_bindings` gained Tuple-target unfolding (the git_ops facade binds - its bounded-network aliases as `A, B = x, y` at :302-303) — same class as - the D12 config-extraction Tuple fix. tests/test_git_extraction.py carried - with adaptations named in its docstring (tool_module_inventory clauses - dropped until that leaf lands; owner map minus the three reference-only / - retired names; size bounds re-based on tip: facade <=1800 — it retains the - paid-cycle gate family, the two deferred f-string spans and the catalog). -12. Zero-v7-delta re-proofs for the rest of the domain: repo_remotes.py, - tools/git_rollback.py, version.py, update_recovery.py byte-identical - tip==ref==merge-base; tools/ci.py, tools/commit_gate.py, tools/git_pr.py, - tools/github.py, tools/review_revalidation.py, update_source.py pure - upstream drift (ref==merge-base, zero v7 delta) — upstream bytes stand. - update_candidate.py is a NEW post-cutoff upstream module (absent from - reference and merge-base; no rows) — upstream bytes stand. - update_merge_policy.py tri-divergence is fully owned by other lanes' - landed HOT_CODE closures vs the reference's fuller loop/tool rows (their - lanes) — no D10 action. size_ratchet_manifest.py regenerated with the - official generator (git_ops.py and both split test giants left - GIANT_PATHS; no new file enters any debt band). -13. Base-inherited, NOT this lane's defect: - tests/test_smoke.py::test_size_ratchet_transition_against_explicit_base - fails at the CLEAN base a56bb76a under its default HEAD-parent base - (pre-proven in a detached worktree; BYTE_DEBT rows of four untouched files - vs the wave-4 seam's parent); with the explicit base - OURO_SIZE_RATCHET_BASE_REF=a56bb76a this lane's transition validates green - (1 passed). Integration seam owns the default-base repair. -## From the D01 lane (base a56bb76a, 2026-08-30) -1. loop.py L-B split rows 3265-3425 (161 rows, nine leaves) executed against tip - bytes: 150 spans landed with the transplant tool (drift-probe first per leaf; - final --check per leaf: ast=tokens=bytes=True on every span, - leaf_invariants=[], unread_declared=[], undeclared_top_level=[], exit 0). - Drift-probe of the reference leaves against `git show HEAD:ouroboros/loop.py`: - 95/150 spans byte-identical, 55 BYTE-FALSIFIED as copy sources and re-emitted - from tip bytes. Falsification class verified against the merge base - (8028f1df): 52/55 pure upstream drift (oracle==merge-base, tip moved); the - other 3 (_drain_incoming_messages, _check_budget_limits — oracle line-wraps - around its own handle rewrites; _maybe_inject_finalization_nudges — oracle - comment-prose rewording) carry NO code delta. Zero live v7 semantic deltas in - the loop split; every span is tip truth. -2. ELEVEN loop rows SUPERSEDED-BY-UPSTREAM (upstream re-homed the symbol into - its own leaf before this lane; tip ownership stands, no transplant): - 3273 (_last_assistant_text -> loop_transport.last_assistant_text), - 3312/3313 (_provider_failure_hint/_provider_recovery_hint -> - loop_transport public pair; matches D02 lane entry 10), 3314 - (_task_deadline_epoch -> loop_transport.task_deadline_epoch), 3315/3316 - (_mark_owner_stop_control_drained/_owner_stop_window_elapsed -> - supervisor/owner_stop.py, the 65b5d19f re-decomposition), 3340 - (_DELEGATE_ACTIVITY_TOOLS -> nanny_pacing.DELEGATE_ACTIVITY_TOOLS with a - compat alias), 3341-3344 (the four _nanny_* helpers -> nanny_pacing.py - public names; loop.py imports underscore aliases). The carried ledger - renames those rows' destinations at F5. -3. Declared-set deltas against the reference LEAVES table, all tip truth, - pinned in tests/test_module_handle_extraction.py: (a) same-leaf members tip - tests monkeypatch on ouroboros.loop now read through _loop() even inside - their own leaf (the reference instead re-pinned those tests to the leaf — - its L3 wave; this tree keeps tip tests unchanged): - _execute_task_acceptance_panel (acceptance_review); - _compute_subagent_handoff, _resolve_delivery_control (delivery); - _call_forced_model_once, _claimed_child_dispositions, - _drain_forced_owner_directives (forced_finalization); - _dispatch_round_model, _measure_round_main_fit, _run_main_reclaim - (model_call); _skill_finalization_message (nudges); - _mark_owner_stop_control_drained (round_limits; upstream re-homed the def - into supervisor/owner_stop.py while tests still rebind it on the loop — - proven by a red run of tests/test_owner_stop_s3.py before the declare). - (b) round_limits gained _provider_unavailable_result and - _append_or_merge_user_content as handle reads (tip drift); several oracle - declared names dropped as unread on tip bytes (_last_assistant_text, - _live_delivery_candidate in round_limits; _handle_forced_finalization in - delivery) — the tool's unread_declared gate is the authority. -4. FACADE CONVENTION DIVERGENCE (disclosed, same class as D05 entry 4): the - reference's L3 package trimmed the loop.py re-export surface - (RETIRED_FROM_LOOP) after re-homing loop-private test imports to leaf - owners. This tree keeps the FULL re-export surface (all 150 moved names, - grouped per leaf at EOF) because the tip consumer set still addresses every - moved name at ouroboros.loop; the L3 trimming is a consumer-rebind wave for - F5, not part of the byte-preserving relocation. - tests/test_loop_owner_facades.py is carried ADAPTED: the identity and - hot-code-parity clauses survive over the full surface; the reference's - RETIRED_FROM_LOOP absence clauses and the surviving-reason invariant are NOT - carried (they pin the L3 state). HOT_CODE_PATHS closure mirrored for the - nine loop leaves (D04/D08 precedent). -5. agent.py rows 3882-3897 -> agent_dispatch.py (D38 handle _agent, declared - {write_task_result}): rows 3884-3897 executed from tip bytes (drift-probe: - 10/14 byte-identical, 4 pure upstream drift). Rows 3882-3883 SOURCE- - FALSIFIED: upstream v6.105.0 moved dispatch_executor_note / - executor_blocked_outcome into ouroboros/subagent_dispatch_notes.py; their - live rows are 3935-3936 and the pair moved from THERE (shared-leaf - convention: per-parent drift probes — the pair 0/2 byte-identical to the - reference, both re-emitted from tip sdn bytes; final --check against the two - parents concatenated, 16/16 green). subagent_dispatch_notes.py was touched - ONLY by removing the pair spans + the re-export import (D01 part); - its D07 rows 3937-3938 (SubagentExecutorResolution/SubagentLaneResolution - bindings and the module-retirement question) stay untouched for the D07 - lane; the lost-reader imports keep the surface under noqa. agent_dispatch's - tip spans additionally read _persist_early_origin_stub_impl (upstream - re-homed the impl into agent_startup_checks.persist_early_origin_stub — - tip-truth import, the D38 write_task_result handle read is intact). -6. agent_task_pipeline.py rows 3898-3909 -> post_task_synthesis.py: 11 rows - executed from tip bytes (8/11 byte-identical, 3 pure upstream drift: - _TASK_SUMMARY_PROMPT, _run_reflection, _run_task_summary). Row 3904 - (_summary_row_cost_fields) SUPERSEDED-BY-UPSTREAM: the symbol lives in - ouroboros/synthesis_cost_text.py (public re-export list) — leaf imports it, - ownership stands. The reference leaf has no handle; this tree's leaf is - likewise projection-only (the auto-generated handle was stripped; zero - declared). tests/test_lc2_owner_facades.py extended with the - agent_dispatch/post_task_synthesis rows per the reference table, minus - _summary_row_cost_fields (upstream home), with the sdn-facade note. -7. HOT-DEFERRED, typed-result/refusal class (tip bytes stand, nothing touched): - loop_tool_execution.py rows 157-164/826-828 (classifier cutover; confirms - D04 entry 6 from the D01 side — the shared monolith was not touched by - either lane); loop_llm_call.py reference delta (+PROVIDER_POLICY_REFUSAL - classification — imports llm_attempt symbols that do not exist at tip; - rides with the D09 typed-refusal subfamily per D02 entry 4); - _outcome_tool_errors.py reference delta (T1 status partitioning, D02 - family; re-prove trap per D15 entry 3); task_finalization.py reference - delta (register-before-persist ordering — cancel/custody organ, 65b5d19f - class, F2). -8. Test-split rows executed. tests/test_loop_misc.py (2037 lines, GIANT_PATHS) - -> 4 siblings from tip bytes: test_loop_acceptance_gate.py (rows 3495-3505; - 6/11 spans byte-falsified by tip test drift, tip bytes moved; - test_every_host_acceptance_writer_emits_a_canonical_status_and_typed_reason - carried in the REFERENCE-ADAPTED form — the split spread the writers over - loop.py + leaves and the reference's union-scan over loop_*.py is the - identity continuation of the pin; the tip span byte-differs only by those - two adaptation hunks), test_loop_image_attach.py (3506-3507), - test_loop_skill_finalization.py (3508-3511), test_run_llm_loop.py - (3512-3524; all byte-identical). UNROWED tip helper _seed_acceptance_root - rode with its only readers into test_loop_acceptance_gate.py (F5 census). - Rows 832-833 NOT executed: their destination suite pins the deferred typed - cutover (D04 entry 9 class); the two tests stay in the remainder on tip - bytes. Remainder 548 lines, left GIANT_PATHS; reader-less imports dropped. - Lossless: 45 == 45 test names. -9. tests/test_agent_task_pipeline.py split rows: 22 of 34 rows - SUPERSEDED-BY-UPSTREAM — upstream already extracted - test_root_post_task_synthesis.py (3544-3556), test_post_task_reflection.py - (3557-3560) and test_store_task_result.py (3561-3565) to the ledger's exact - destinations. This lane executed the remaining two: test_task_summary.py - (3535-3543) and test_collect_review_evidence.py (3566-3568), tip bytes, - all byte-identical to the reference siblings. Lossless: 21 == 21. -10. Rowed import rebinds landed (identity continuations; the facade keeps both - addresses live): 3915-3917/3932-3934 (test_v678_acceptance_state -> - loop_acceptance / loop_acceptance_review), 3920-3921/3925-3928 - (test_loop_misc remainder -> nudges/round_limits/messages/acceptance), 3922 - (test_v6502_capability), 3923-3924 (test_budget_limits), 3929 - (test_nanny_finalization_nudge), 3930 (test_review_eligibility), 3931 - (test_transcript_seal), and the D02-deferred function-local retargets in - tests/test_multimodal_chat.py (loop_messages; D02 entry 10 closure). Rows - 3918-3919 SUPERSEDED: tip already binds the provider hints from - loop_transport (public names) — tip spelling stands. -11. Zero-v7-delta re-proofs for the rest of the domain (tip==ref==merge-base: - _outcome_receipts.py, mutation_attribution.py; pure upstream drift, - ref==merge-base: agent_startup_checks.py, deadline_utils.py, outcomes.py, - owner_mailbox.py, post_task_checkpoint.py, synthesis_cost_text.py, - task_pacing.py; NEW upstream modules, no rows: loop_transport.py, - outcome_receipt_store.py) — upstream bytes stand. Ratchet: loop.py left - GIANT_PATHS/BYTE_DEBT by extraction; agent_task_pipeline.py and - loop_forced_finalization.py band entries recorded via the official - regenerator's --band-rationale. -12. Post-battery closure per the D10 lane's lessons (superseding notes to - entries 5-6): (a) MAXIMAL declared sets — a precise AST audit of every - frozen leaf-preamble import against test patch surfaces (setattr on parent - aliases + string-form patch targets) found three more dead facade patches - and converted them to handle reads: agent_dispatch declared grew to - {envelope_from_task, write_task_result} (test_available_subagents_runtime - patches envelope_from_task on ouroboros.agent), and post_task_synthesis is - NO LONGER projection-only — it carries the _atp() handle with declared - {_is_root_post_task, load_task_result} (test_presence_post_task / - test_agent_task_pipeline patch them on the pipeline), diverging from the - reference's handle-less leaf, which froze _is_root_post_task by import. - Zero f-string reads of rebindable globals were hit in any D01 emit (the - tool's f-string gate never fired — no f-string HOT-DEFERRED spans in this - lane). (b) tests/test_v7next_transplant.py loop probes re-pinned to the - pre-split monolith bytes of the lane base (git show a56bb76a:ouroboros/ - loop.py, the D10 recipe) with a self-contained fallback that inverse- - normalizes the landed loop_messages leaf — the suite is green either way. - -## From the F2.1 D07-quiet lane (base 50377313, 2026-08-31) - -1. Scope executed (the QUIET D07 part, F1 conveyor): 7 module leaves landed - from tip bytes with the transplant tool (ast=tokens=byte-roundtrip=True on - every span, exit 0, leaf_invariants=[], undeclared_top_level=[]): - delegate_custody_reconcile (rows 3458-3466; D36 handle `_custody()`), - delegate_payload_patch (3477-3483; `_di()`), subagent_integration_delegated - (3484-3494; `_si()`), subagent_route_health (3939-3942; projection-only, no - handle), and the D07 half of the SHARED tools/control.py — - control_subagent_spec (2537-2542), control_scheduling (2543-2556), - control_task_results (2569-2579). Facades = tip parent − moved spans + - grouped EOF re-export block (noqa discipline for historical imports); - facade audit green (every kept def/assign span byte-identical to `git show - HEAD:`, every moved name re-exported). Both 1600-hard-cap giants - this lane was allowed to touch shrank: delegate_custody.py 1600→1305, - control.py 2110→492 (control.py and the transport test giant LEAVE - GIANT_PATHS/BYTE_DEBT); delegate_integration.py 1540→868, - subagent_integration.py 1599→1027, subagents.py 1593→1370. -2. Drift-probe results (reference leaf `--check` against tip bytes, first - step per leaf): delegate_custody_reconcile 2/9 spans byte-true (7 - re-emitted from tip); delegate_payload_patch 6/7 (integrate_payload_patch - drifted); subagent_integration_delegated 10/11 (_integrate_delegated_patch - drifted); subagent_route_health 2/4 (route_health, _exhausted_window - drifted); control_subagent_spec 4/6 (schedule_subagent_properties, - _validated_schedule_fields drifted); control_scheduling 9/14 rowed spans - byte-true (5 drifted); control_task_results 7/11 (4 drifted). Every - "verbatim" ledger claim was re-proven by bytes; no oracle semantics were - replayed over tip drift (custody semantics: upstream is a strict superset - — only the split FORM was taken from the reference). -3. Declared-set recalcs against the reference LEAVES table (tool - unread/unresolved gates are the authority; new rows in - tests/test_module_handle_extraction.py): `_custody()` dropped STARTED, - START_REQUESTED, _CUSTODY, _iter_rows, event_log_path (tip drift stopped - reading them) and gained retire_settled_registrations (upstream retirement - decoupling 3226cc0c/8fe5a071); REVIEW_ATTRIBUTION_KEYS became a leaf - preamble import (constant, never rebound in tests). `_di()`/`_si()` sets - byte-matched the reference. control_scheduling declares exactly - {load_settings} (tests rebind it on the facade); the reference control - leaves were handle-free, the tip drift introduced that one read class. -4. Row 3467 (_capture_stranded_patch → delegate_custody_reconcile.py) - SUPERSEDED-BY-UPSTREAM: 81194970 re-homed it as the public - tools/delegate_integration.py::capture_stranded_patch and the body drifted - further there; ownership stands with upstream, the row's destination needs - an F5 rename (class: D01 lane entry 2). -5. Unrowed post-cutoff control.py neighbours ride with their only readers - into control_scheduling: _context_task_depth (read only by - _schedule_task), _materialize_child_attachment_manifest (same), - maybe_emit_delegated_run_fanout (external reader tools/delegate.py:935 - does a call-time facade import — the facade re-export is load-bearing), - HIDDEN_LEGACY_SCHEDULE_PARAMS. F2-matrix falsification: the matrix routed - HIDDEN_LEGACY_SCHEDULE_PARAMS with the row-2541 reader - (_validated_schedule_fields → control_subagent_spec); the tip readers are - _schedule_task:865 and the module-level handler-attribute stamp - `setattr(_schedule_task, "_hidden_legacy_params", …)`:1166 — probe beats - matrix, the set moved with control_scheduling. The setattr Expr is the one - facade statement RELOCATED below the re-export block (it reads two moved - names at import time; consumer tools/tool_resolution.py:337 reads the - attribute off the registered handler object, same object either way). -6. Transport test giant (6187 lines, 177 ledger rows): re-cut from tip bytes - as the S7a theme split — 140 rowed tests moved to 10 destinations - (cancellation_settlement 5, executor_axis 32, reconciliation 6 APPENDED to - the file the D11 lane already created, result_delivery 12, run_accounting - 17, run_containment 11, run_custody 13, run_profile 15, wait_timeline 10, - wait_window 19), 21 unrowed post-cutoff tests stayed in the remainder; - lossless proven: 163 unique test names before == after (161 giant + 2 - pre-existing reconciliation), zero new duplicate names, every moved span - byte-identical to the giant's bytes. Helper placement followed the rows - (15 defs → tests/_delegated_transport_shared.py, private stubs → their - sibling suites) with two documented lane placements: _plain_ctx went to - the SHARED module instead of run_accounting (tip-only external consumer - tests/test_delegation_account_pin.py imports it beside the autouse - fixture; its import was re-pointed to the shared home), and the unrowed - post-cutoff _transport_snapshot went to shared (the autouse fixture reads - it). Four rows re-homing giant constants into runtime SSOTs - (ACTING_SUBAGENT_TOOL_NAMES, LOCAL_READONLY_SUBAGENT_TOOL_NAMES → - tool_capabilities; CLAUDEXOR_DELEGATED_MARKER_MIN_VERSION → config; - MODEL_SETTING_KEYS → provider_models) are SATISFIED BY UPSTREAM (the tip - giant already imports them). 44 oracle-rowed test names are absent from - the tip giant (upstream renamed/re-homed/retired them; the upstream - test_delegated_run_isolation.py / test_delegated_skill_payload.py themes - were built upstream as its own files) — no rows executed for absent - names, F5 census item; tip bytes stand. -7. Dead-patch class (D08 lane entry 9 recipe; oracle adaptations mirrored - into THIS tree's file names): tests/test_task_status_flow.py — 3 - wait-grace sites re-pointed to control_task_results (mirror of oracle - test_task_status_wait_tools.py) and the queue-fallback test's - write_task_result patch alias re-pointed to control_scheduling (mirror of - oracle test_task_status_scheduling.py); tests/test_subagents_phase3.py — - prepare_task_drive patch alias → control_scheduling (mirror of oracle:94); - tests/test_external_workspace_access.py — system/active_repo_dir_for - patch alias → control_scheduling (mirror of oracle:86); - tests/test_cache_optimization.py — two wait aliases → control_task_results - (mirror of oracle:435/480). A sweep of every other moved/frozen name found - no further facade patches whose exercised path reads the leaf scope (the - join_ledger _emit_control_event patches stay live: that path re-imports - through the facade at call time; subagents.route_health and the custody - sweep patches stay live: their callers stayed in the facades / read - through `_custody()`). -8. HOT-DEFERRED with evidence (owner forks — nothing emitted): - - Ф-2 (delegate_terminal name collision): rows 3468-3476 NOT emitted; - tools/delegate.py stays exactly at the 1600 hard cap (at, not above — - ratchet green). Probe evidence recorded: 7/9 reference spans byte-true, - _terminal_payload + _delivered_terminal_payload upstream-drifted; the - reference facade-identity rows for this family are also held back. - - Ф-1 (subagent_worktrees.py strict-registry, rows 1083-1092 + the - 280-line pin suite): in-place semantic delta, not transplanted without - owner sanction; tip==merge-base for the module, so the delta stays - cleanly appliable. - - Ф-3 (subagent_dispatch_notes retirement): rows 3937-3938 verified - SATISFIED as identity on tip (sdn:17 imports the pair from - ouroboros.subagents under the D01-lane noqa marker); the 71-line facade - stays; retirement is an F5 consumer-rebind item (agent.py at its size - ceiling + 3 test files + 2 unrowed helpers). - - Six D02 rows (2548 _build_acting_constraint, 2549 - _select_subagent_constraint, 2556 _schedule_task, 2571 _get_task_result, - 2574 _wait_for_task, 2579 _wait_for_tasks) were cut in TIP form — - ouroboros/tools/tool_result.py does not exist on tip; the D02 delta - returns as a package with the typed-result organ (the plan's mandatory - "D02 loop" return). -9. Hot-code label parity: the three control leaves joined HOT_CODE_PATHS - beside the D08 trio (control.py stays labeled); the delegate/subagents - families are unlabeled and their leaves keep parity — pinned in the - adapted tests/test_delegate_owner_facades.py (reference file minus the - deferred delegate_terminal group and the superseded stranded-patch row). -10. Out-of-scope defect FOUND (not fixed here, D06/review-organ material, - D15 class): tests/test_review_cycles_dispatch.py and - tests/test_review_cycles_skill_dispatch.py carry 10 AST-identical - duplicate test functions (pre-existing at this lane's base). -11. For the integration seam: scripts/v7next_domains.toml rows for the seven - new runtime leaves (D07) and the ten new/regrown test siblings follow the - established seam convention (lanes do not edit the map); quotient report - regeneration likewise. - -## From the F2 addendum (coordinator, base 3c425206, 2026-08-31) -1. MIGRATION row 2016 (_handle_schedule_task -> events_schedule_task.py) — - EXECUTED: the D08 deferral was unblocked by the D11 same-qualname - relocation rule. Proof: span emit via the tool (ast=tokens=bytes=True, - one handle read `_parent_delegation_budget`); whole-leaf verify with - leaf_owned = the leaf's prior residents (the emit-time top-level gate is - structurally blind to append-into-existing-leaf — assembly ran with that - gate bypassed and the hardened verify as the actual authority; a - `--leaf-owned` CLI flag is an F5 tool candidate). FUNCTION_DEBT key - relocated with the function; events.py 1947->1406 entered the 1001-1500 - band by extraction with rationale. D08's work-order pins flipped as - designed (dispatch owners + facade census). -2. Stale docstring of tests/test_delegated_reconciliation.py refreshed - (F2.1 conformance item 7): the file owns the full reconciliation theme. -3. Addendum round 2 (battery findings): (a) tests patching - `events._find_duplicate_task` retargeted to the leaf module — the ORACLE's - own adaptation shape (its tests patch `schedule_module`), 25 sites across - three files; the declared-through-handle alternative is structurally - refused by the tool for leaf-resident names (ambiguous ownership, by - design). (b) `_build_scheduled_task_payload` restored as a noqa facade - import — tests import it from supervisor.events. -4. SUPERSEDING correction to entry 1 of this section (audit 31.08 07:27, F5): - the FINAL landed leaf carries TWO handle reads (_parent_delegation_budget - AND get_max_subagent_depth - the latter added when the patch-surface scan - found tests monkeypatching it on the facade), and the facade size after - the final import restores is 1392 lines, not 1406/1389 as the earlier - prose said. The proof chain (span emit + hardened verify with leaf_owned) - was re-run at each state; entry 1's figures describe an intermediate - state and are superseded by these. -5. Audit 31.08 F1 second name: tests patching `_resolve_subagent_constraint` - on the facade (one negative sentinel in test_nested_rights_depth) were - retargeted to the reading leaf module - same oracle retarget-to-owner - shape as the _find_duplicate_task sites; the sentinel's teeth are - restored (the leaf's import-bound name is the one the handler reads). - -## From the F2 D07-finisher lane (base 2878560e, 2026-08-31) - -1. Scope executed (the three D07 owner forks, decided 31.08 batch 5: 5.9A, - 5.10A, 5.11A): the deferred terminal leaf of tools/delegate.py, the - subagent_worktrees.py strict-registry delta with its pin suite, and NO - sdn retirement (the facade stays). -2. F5-RENAME record (owner fork F-2=A, ledger rows 3468-3476): the reference - leaf destination `ouroboros/tools/delegate_terminal.py` is renamed at - landing to `ouroboros/tools/delegate_terminal_evidence.py`. Rationale: - upstream already owns `ouroboros/delegate_terminal.py` ("terminal - reconciliation boundary", 189 lines) and the ledger name would put two - different delegate_terminal modules in neighbouring packages — a - permanent grep/reading trap. Same class as the D01/D03 F5 destination - renames. Rows 3468-3476 read onto the renamed file unchanged otherwise. -3. Terminal leaf landed from tip bytes (rows 3468-3476, D36 handle - `_delegate()`): drift-probe first (reference leaf `--check` against - `git show HEAD:ouroboros/tools/delegate.py`): 7/9 spans byte-true, - _terminal_payload and _delivered_terminal_payload upstream-drifted — - matching the quiet lane's held-back probe evidence — so the leaf was - EMITTED from tip bytes, no oracle semantics replayed. Final proof: - ast=tokens=byte-roundtrip=True on all 9 symbols, leaf_invariants=[], - undeclared_top_level=[], unread_declared=[], exit 0 (re-run after the - manual TYPE_CHECKING preamble addition, the D07-quiet - reconcile-leaf precedent). -4. Declared-set recalc, MAXIMAL form (D10 tools/git precedent, finisher - work-order): the reference cut this leaf with plain preamble imports and - declared only {_emit}; the landed leaf declares EVERY parent-scope name - the moved spans read at call time — 12 names: _Breach, - _PAYLOAD_ENVELOPE_HEADROOM, _emit, _home_isolation_breach, - _preview_payload, _resolve_full_primary_output, _stage_full_output, - _widened_access, add_terminal_source_verification, custody, - home_nested_under_operator_home, tool_result_limit — so patches on the - historical `ouroboros.tools.delegate` surface keep their teeth. Only - stdlib (json) and typing stay preamble imports; annotation-only names - (_Breach for its `-> Optional[_Breach]` use, _RunCustody, ToolContext, - DelegatedRunShape) ride an `if TYPE_CHECKING:` block, inert under future - annotations. New LEAVES row pinned in - tests/test_module_handle_extraction.py. -5. Facade: tools/delegate.py = tip parent - the 9 moved spans (lines - 225-576 of the HEAD file) + the grouped EOF re-export block + noqa - discipline: exactly four `# noqa: F401` markers on the import lines of - parent members now read only through `_delegate()` at call time - (_home_isolation_breach, _widened_access, home_nested_under_operator_home, - add_terminal_source_verification — the bindings are load-bearing for the - leaf and must survive ruff F). Every kept def/assign span proven - byte-identical to `git show HEAD:ouroboros/tools/delegate.py` (the diff - of the kept region is exactly those four marker lines); re-exports - proven same-object by import smoke. tools/delegate.py 1600 -> 1263: the - LAST 1600-hard-cap giant of the D07 organ leaves the cap and enters the - 1001-1500 band with a rationale. The reference facade-identity rows for this family (held - back by the quiet lane) landed in tests/test_delegate_owner_facades.py - under the renamed leaf. -6. Ф-1 strict-registry delta (rows 1083-1092, owner sanction 5.10A — - SANCTIONED SEMANTIC DELTA in an otherwise byte-preserving lane): - drift-probe first — tip blob of ouroboros/subagent_worktrees.py == - merge-base 8028f1df blob (fd2db424, upstream never touched the module), - so the reference diff (+104/-22) applied clean; the landed module is - byte-identical to the reference module (blob ee694e4d on both sides). - Semantics: absent registry stays an ordinary empty registry; malformed - registry raises typed SubagentWorktreeRegistryCorrupt for every author/ - destructor (provision_worktree, provision_execution_snapshot, - provision_payload_snapshot, find_execution_snapshot, - remove_execution_snapshot, prune_execution_snapshots, remove_worktree, - prune_orphans) instead of silently collapsing to empty; bytes are kept; - one durable subagent_worktree_registry_corrupt event; inspection reads - stay soft; registration moves INSIDE the cleanup scope on all three - provisioning branches. Pin suite - tests/test_subagent_worktree_registry_s6.py copied verbatim from the - oracle (281 lines, 11 tests, red without the delta per D09 entry 10): - imports only stdlib + the module itself, zero v7-only names to reverse- - map; its docstring's sibling reference - (test_delegated_skill_payload.py::test_registry_save_failure_leaves_no_orphan_snapshot_dir) - exists on tip; the oracle registered it in no conftest path-keyed table. - The one pre-existing tip test touching the registry - (tests/test_acting_subagents.py:1298) uses the soft read, whose - signature and behavior are unchanged. -7. Ф-3 (sdn): no action, per owner 5.11A — the quiet lane's entry 8 stands - (rows 3937-3938 satisfied as identity; retirement stays an F5 - consumer-rebind item). -8. Ratchet (official regenerator): ouroboros/tools/delegate.py enters the - band by extraction (1600->1263, rationale recorded); - ouroboros/subagent_worktrees.py enters the band by the sanctioned delta - (1000->1082, rationale recorded). domains.toml untouched (coordinator - seam owns the map). -## From the F2.4 update-engine lane (base 2878560e, 2026-08-31) -D34 return + 1A re-split, per owner answers Ф-1=A / Ф-2=A / Ф-3=A -(= plan rows 5.12-5.14A). Every re-derived body below is justified as -reference-fact ↔ tip-fact ↔ result. -1. Span-SSOT re-cut (ouroboros/tools/release_sync.py, merged ATOP the tip - file, not a replacement). Reference: 8 descriptors (v7_wip - release_sync.py:65-148). Tip inventory is WIDER: sync_release_metadata - writes the two public install pages (tip :423-434) and the README - direct-download reference block (:100-113); version_carrier_desyncs / - update_candidate.py:697-698 check them. Result: 25 descriptors = the 8 - reference spans + readme_download_refs (the contiguous - `[download-]:` block) + 8 anchors per install page, derived from - RELEASE_ASSET_TEMPLATES (a new installer automatically gets a span); - macos-arm64 appears twice per page and is disambiguated by the - quick-start step's literal "Click " prefix (lookaround pair) — a page - restructure degrades to malformed/duplicate-anchor, never a guess. - Latent-trap fix proven by span inspection: proof ids carry `x86_64`, so - a `[a-z0-9-]` class matched the tip block ONCE but covered only its - first 3 lines (wrong-coverage, silent partial substitution) — the class - is `[a-z0-9_-]`, and the live-tree pin asserts full-block coverage - indirectly through exactly-once anchoring of every descriptor. -2. supervisor/update_carriers.py returned WHOLE (no upstream analog); two - bodies re-derived against the redesign train's bounded-plumbing rule - (4795a810/c404c056 class): _run_git and the merge-file runner now start - the child in its own process group and kill the WHOLE TREE on a 300s - timeout (constant mirrors update_candidate._GIT_RUN_TIMEOUT_SEC) — - insertion point 3 runs while the update lock is held. Byte-exact capture - (text=False semantics) preserved from the reference. Deliberately NOT - routed through git_ops._run_git_process_bounded: that helper imports - ouroboros.tools.shell at call time (tool-registry package init), which - would break the standalone operator rebase helper; the - _active_subprocesses shutdown-tracking nicety is therefore not carried - (short-lived waited children — disclosed residual). Docstring - re-derived: insertion host is the re-cut update_merge_plan.py; the - resolver never runs `git merge` (explicit index stages + `git - merge-file`), so it is rerere-neutral by construction, in line with the - train's _MERGE_NEUTRAL_FLAGS discipline; M0 note per Ф-2=A. -3. Three insertion points re-derived against the REWRITTEN tip bodies - (reference bodies were pre-redesign; matrix rows MIGRATION:3427-3429): - (a) point 1 (row 3428): reference update_merge_plan.py:334-344 ↔ tip - plan_managed_update_merge (stash-first; snapshot via - worktree_snapshot_tree instead of the temp-index) → resolution after - the merge/inventory consistency check, BEFORE classify_conflicts; the - single body serves both the preview plan and the authoritative - build=True replan (control.py replans on the clean tree through the - same function). `carrier_resolved_paths` restored to the ff-clean, - base-conflict and main returns (reference shape). - (b) point 2 (row 3427): reference :88-97 ↔ tip _build_clean_merge_commit - (fast_forwardable early return, Q8 projection before write-tree) → - resolution inside the rc_bm==1 branch BEFORE write-tree; the tip's - `if base_conflicts: return` inverted to the reference's - no-inventory-error + resolve + `if remaining: return` shape; the Q8 - projection now runs AFTER span resolution, so its postcondition also - verifies the just-resolved carriers. - (c) point 3 (row 3429): reference :454-469 ↔ tip materializer - (rerere-off flags, mandatory Q8 projection, CAS re-parent, M0 pin) → - resolution after MERGE_HEAD validation and BEFORE the projection and - the M0 pin (Ф-2=A: span policy is part of the mechanical baseline; - reviewers diff an M0 already free of carrier markers); the tip 3-tuple - return (ok, message, m0_tree) preserved. - Handle idiom: the reference `_um()` handle is retained ONLY for - managed_update_constitution_present (monkeypatched on the parent facade - — test_update_merge_assisted.py:973); update_candidate members are read - through the `_uc` module object (test_update_hardening.py:99/125 - patches update_candidate.worktree_snapshot_tree) — the D10-lane entry-7 - patch-surface rule. The row-3426 verbatim `_git_run` relocation stays - SUPERSEDED (upstream re-homed it to update_candidate; the leaf reads - `_uc._git_run`). -4. Boot-recovery backfill window NOT extended (upstream recovery semantics - = floor): _recover_assisted_on_boot's M0 backfill re-runs only the Q8 - projection; a carrier still conflicted through that crash window - degrades to the assisted lane (fail-safe, never fail-wrong). Disclosed - in the wiring pin's docstring; keeps the "3 resolver calls in the leaf, - 0 in the parent" invariant intact. -5. 1A re-split executed per Ф-3=A from the two-module tip form: - update_merge.py 1593 → 1193 (tx/lock/rollback/boot-recovery facade, - re-exports both leaves), new supervisor/update_merge_plan.py (490 = - three tip bodies + the documented deltas). The reference leaf is the - THEME (same three owners), not bytes. Ratchet: update_merge.py entered - the 1001-1500 band by extraction with a rationale via the official - generator; `-m size_ratchet` = 5 passed. -6. update_merge_policy.py coordination (matrix row "согласовать"): - carrier_guidance's hand-list VERSION_CARRIER_PATHS (6 paths, already - narrower than the tip's own carrier inventory) replaced by a call-time - read of the span SSOT (CARRIER_SPAN_PATHS); prose re-derived — spans - resolved mechanically never reach the resolver's list (verified: - control.py:820 refreshes tx.conflict_paths from live_unmerged_paths - after materialization), so the guidance now describes exactly the - DEGRADED remainder and what degradation means. -7. Protection closure (the G1/D10 additive-literal precedent, coordinator - LEDGER entry 4 class): RELEASE_INVARIANT_PATHS += - supervisor/update_merge_plan.py, supervisor/update_carriers.py — - the split moved planner/materializer bodies out of a release-invariant - file and the resolver rewrites worktree files under the update lock; - parity pinned in tests/test_update_merge_owner_facade.py. DISCLOSED - upstream inventory gap, NOT repaired (Q4=A, upstream owns protected - surfaces): supervisor/update_candidate.py carries bodies upstream's own - redesign moved out of the same protected parent, yet is absent from - RELEASE_INVARIANT_PATHS — owner/Ф3 material. -8. Tests: test_update_carriers.py ported with re-derivations (leaf import - path unchanged; materializer test unpacks the tip 3-tuple and pins that - M0 names the official VERSION blob; corpus README fixture extended with - the FULL 7-id download-refs block — the Q8 postcondition checks every - RELEASE_ASSET_TEMPLATES member once a README opts into the projection, - and the new span must anchor; SSOT pin re-cut to 25; an explicit - "conflicted carrier never routes to assisted" strategy pin added per - the work order). test_carrier_rebase_helper.py + the operator helper - returned (helper docstring's carrier list re-cut). - test_update_merge_owner_facade.py re-derived: owners = update_merge_plan - (3 bodies) + update_candidate (the redesign's own boundary, identity - now pinned); hot-code and release-invariant parity clauses. - _POPEN_ALLOWLIST (tests/test_process_custody.py) += - supervisor/update_carriers.py (path-keyed mirror, D10 git_ops_reset row - class). -9. NAME COLLISION tests/test_update_merge_plan.py resolved as SUPERSEDED, - not transplanted: the oracle file's 13 test functions are - name-set-identical to the tip file and the tip bodies are the - upstream-evolved forms of the same assertions (stash status tuple - "ok"/sha, failed-update- forensics naming) — zero unique - oracle content; a rename-transplant would mint 13 AST-near-duplicates - (the D15 class the wave mandate bans). Tip bytes stand. -10. Upstream test re-derived (falsified-by-D34 fixture, the "test pinning - the gap" class): test_update_merge_assisted.py:: - test_materialize_projects_version_to_target_and_pins_m0 used a clean - 1.5.0-vs-2.0.0 VERSION token conflict, which the D34 planner now - resolves (plan turns clean — the scenario could no longer reach the - materializer's projection). The local token becomes a malformed anchor - ("not-a-version"), so the span resolver degrades honestly and the Q8 - projection clause the test pins stays reachable; docstring says why. -11. Ф3 joints named, untouched (report-only): the future N−1 shim surface - (finalize_managed_update_on_boot / _recover_assisted_on_boot / - _recover_replace_on_boot / _finalize_pending_boot_smoke / - apply_managed_merge_update / rollback_managed_update) stays WHOLE in - the parent — the re-split does not dissect ABI-7/F14 material; the RC - auditor's evidence surface (record_managed_tests_evidence / - managed_tests_evidence_covers) untouched in update_candidate; - git_ops.py:1031-1032 (D13) untouched — protected wave; Ф-4 derived - FAMILY_PATHS not executed (coordinator's tail item — the additive - entries in item 7 keep that door open). -12. Pre-existing at base, NOT this lane's defects (dup-scan receipts): - 10 AST-identical test pairs across test_review_cycles_dispatch.py / - test_review_cycles_skill_dispatch.py (already named by the Ф2-plan) and - an in-file duplicate def test_ripgrep_download_script_verifies_checksum - in tests/test_build_scripts.py (the later def shadows the earlier — - D15-class latent, review-organ/F5 material). -## From the F2.3a review-mechanics lane (base 2878560e, 2026-08-31) -1. FALSIFIED row: `tests/test_review_substrate_v2.py::_render_prompt -> - review_substrate` (repoint to the "canonical substrate owner"). Upstream - moved `_render_prompt`/`_render_prompt_parts` into review_execution - (substrate back-imports them as compat re-exports), so the row's target is - stale. Executed as re-derive: the split's prompts suite - (tests/test_review_substrate_prompts.py) imports `_render_prompt` from - ouroboros.review_execution. -2. ROW CORRECTION: `ouroboros/tools/scope_review.py::_load_canonical_context_docs - -> scope_review_pack.py` was NOT executed as written — the symbol stays a - facade def. Its body reads `load_governance_doc` inside an f-string (the - byte gate refuses f-string reads of rebindable globals) and tests rebind - that name on the parent (test_review_convergence_rule.py:122 et al.), so a - leaf copy would go dead-patch. Same class as the D10 lane's - safe_restart/prepare_managed_update facade retention. The pack leaf reads - it through the `_sr()` handle; 19/20 pack rows moved. -3. NEW-OWNER leaf (owner decision 5.3=B, one-cut): ouroboros/review_state_custody.py - carries nine post-cutoff upstream symbols no MIGRATION row names — - unrowed F5 candidates, recorded here as adoption rows: - review_state.py::{_ACTIVE_REVIEW_OPERATION_STATES, _attempt_review_roster_rows, - _review_roster_row_is_pending, _attempt_has_active_review_custody, - checkpoint_pending_review_invocation, _attempt_history_evictable, - _STRIPPED_DETAILS_LIMIT, _STRIPPED_MESSAGE_LIMIT, _strip_attempt_heavy_payload} - -> review_state_custody.py:: (adaptive-timeout/custody train; - tool-proof ast=tokens=bytes on every span). The four authority-shape - deserialization symbols of the same train (_malformed_roster_row, - _ATTEMPT_AUTHORITY_STRING_FIELDS, _ATTEMPT_AUTHORITY_BOOL_FIELDS, - _validate_attempt_authority_shape) stay with the parent STORE by design. -4. SUPERSEDED rows honored (upstream home wins, Q4=A; leaves/tests do not - replay them): review_evidence.py::{_ACCEPT_DELTA_CHILD_CAP, - _accept_capability_deltas} -> delegate_evidence (facade reads - acceptance_capability_deltas back at call time); - tools/review.py::_parse_model_response -> tools/review_response.py - (facade re-import is the single alias, pinned by - test_review_owner_facades.py). RETIRED rows honored: - tools/review.py::{DEFAULT_REVIEW_MODEL_TIMEOUT_SEC, _review_model_timeout_sec} - died with the adaptive-timeout contract and are not restored. -5. Import-bound exceptions (f-string/import-time gate; named in each leaf - docstring): review_multi_model: SLOT_ID_PREFIX (default argument); - review_file_pack: format_prompt_code_block (f-string; unpatched in tests); - scope_review_pack: format_review_history_entry, - _HISTORY_VERIFICATION_ONLY_RULE, _ANTI_THRASHING_RULE_VERDICT, - _CONVERGENCE_RULE_TEXT (f-strings; owner review_prompt_text); - review_evidence_sections: DEFAULT_TOOL_RESULT_LIMIT (default argument); - review_state_model: _STATE_SCHEMA_VERSION, _DEFAULT_ADVISORY_TOOL_NAME, - _REVIEW_ATTEMPT_TTL_SEC, _REVIEW_ATTEMPT_GRACE_SEC (class-level defaults), - _stable_digest (f-strings) — owner review_state_records; - review_records/review_verdict: ReviewRouteKind / OUTCOME_TIER_* (class-level - and module-level constants). None of these names is monkeypatched on the - parents anywhere in tests/ (verified by grep before binding). -6. TEST DELETION disclosure (owner decision 5.2=A): ten AST-identical test - functions plus seven byte-identical orphan helpers were deleted from - tests/test_review_cycles_dispatch.py; the owner of those tests is - tests/test_review_cycles_skill_dispatch.py (D14 family — they exercise - skill_review_* modules only). Verified byte-level: ast.dump-identical in - both files before deletion, zero shared-but-different defs. −510 lines of - double-executed runtime; the dispatch file remains the D06 commit-gate - paid-accounting suite. -7. Session-route split: three reference-authored tests absent from the tip - giant were NOT replayed (skipped, F5 material): - test_unhealthy_route_refuses_typed_never_falls_back, - test_route_status_refusal_carries_its_typed_code, - test_retry_of_a_pinned_session_health_checks_the_stored_account. Thirteen - tip-only (post-cutoff) tests were placed with the sibling that owns their - helpers (2 -> scope_wiring, 1 -> poller, 2 -> delivery, 8 stay in the - remainder with FakeGateway/_run_session_directly imported from the shared - module). Lossless: 102 == 102 test names across the five files, zero - duplicate names. -8. Substrate split lossless: 71 == 71 test names across six files — the - reference's five plus tests/test_review_substrate_custody.py, a NEW - sibling created by this lane for the eighteen post-cutoff upstream tests - (the adaptive-timeout/custody train theme, 907 lines of tip bytes); the - remainder would otherwise have stayed a >1600 giant. Both re-derived - extraction suites drop the reference's tool_module_inventory clauses - (that module exists only on the reference). -9. Path-keyed mirrors (D10 additive-closure precedent): - review_context_atlas._REVIEW_STACK_PATHS += the eight state/evidence/ - helpers/scope leaves (oracle placements) + the new custody leaf; - scripts/run_external_review.py::_REVIEW_SUBSTRATE_PATHS += all eleven - leaves beside their parents. The hand-list's structural rot (28/48 D06 - modules absent before this lane) is the Р1/D31 fork — Ф2.3b territory, - not repaired here beyond the additive closure for our own leaves. -10. review_records is a projection-only leaf (zero handle reads, zero - declared) and stays off the LEAVES table per the D07/D08 precedent; the - other ten leaves carry tool-derived exact declared sets there. -## From the f22 lane (base 2878560e, 2026-08-31) -1. Drift-probes (recipe §5.3-Δ2 step 9) of every oracle leaf against this - base's monolith bytes, before any emit. Byte-identical tip↔oracle: - _task_done_review_projection, _PROVIDER_DEATH_NOTIFIED, - _task_done_durable_fault, _handle_task_done, _handle_evolution_task_done, - _close_campaign_after_owner_stop, _kept_service_pids, parse_iso_to_ts, - all queue_timeouts symbols except _enforce_task_timeouts_locked, - _evolution_assignment_error, _cancel_unauthorized_evolution, - terminal_task_metadata, _emit_task_done_terminal, ensure_workers_healthy. - Byte-FALSIFIED as copy-source (upstream drift, re-emitted from tip bytes): - _authoritative_terminal_cost, _maybe_notify_provider_death, - _finish_task_done_dispatch, _resolve_lifecycle_fault, _handle_cancel_task, - persist_queue_snapshot, restore_pending_from_snapshot, - _enforce_task_timeouts_locked, assign_tasks, - _ensure_workers_healthy_locked. ALL families were emitted from tip bytes - regardless (proof: ast=tokens=True per symbol, leaf_invariants=[]). -2. Q-a=A (owner, 2026-08-31): the sixteen settle-owner rows 998-1013 - (task_lifecycle -> supervisor/cancel_custody.py) are SUPERSEDED — the - settle owner STAYS in task_lifecycle.py; the upstream custody cut - (65b5d19f/bea08137) is the authoritative floor and cancel_custody.py is - never created. tests/test_cancel_custody_extraction.py is NOT replayed - (its identity/size clauses are form-dependent on the extraction; matrix - §3.8). Row 1000 (_durable_settled_status) is doubly retired: upstream - removed the symbol (fail-soft equivalent lives as - cancel_intents.settled_status). -3. Q-b=A: rows 2041-2044 (queue.py -> supervisor/queue_evolution.py) are - RESOLVED WITHOUT the reference leaf. Upstream itself moved - _deliver_pending_owner_report and enqueue_evolution_task_if_needed into - supervisor/evolution_lifecycle.py; get_evolution_status_snapshot and - queue_deep_self_review_task stay on the queue facade by owner decision - (do not fork the evolution-family ownership a second time). -4. Q-c=A: row 970 EXECUTED — _close_campaign_after_owner_stop moved to - supervisor/queue_transitions.py (byte-identical span; the drift probe - proved tip==oracle here), events.py re-exports it, and - events_evolution_done reads it through the _events() handle (no bare - local name survives the split). queue_transitions.py entered the - 1001-1500 band with a rationale and joined HOT_CODE_PATHS (parity: the - span moved out of the hot events monolith). -5. Rows 971-979 (events_task_done + events_evolution_done), the cancel - ingress row (file row 994 / D08-ledger row 992), rows 2017-2028 - (queue_snapshot + queue_timeouts) and rows 2061-2064/2077-2079 - (worker_health + worker_assignment) EXECUTED as reference-named leaves - from tip bytes. Declared sets are MAXIMAL (wave-2 dead-patch lesson), - larger than the oracle's: every parent global the spans read at call time - routes through the handle, including same-leaf reads - (_PROVIDER_DEATH_NOTIFIED — tests rebind it on the facade), the - cross-family coop hooks (_checkpoint_coop_roots_on_root_done and - _maybe_checkpoint_coop_on_tree_quiescence: the GR4-3 probes patch them on - supervisor.events — a module-scope import here is the dead-patch class the - first emit reproduced and the re-emit fixed), `time` (the - enforce-harness in test_packaged_runtime_and_lifecycle rebinds - events.time), `_bound_project_chat_id` (the terminal-frame delivery tests - rebind it on supervisor.events — a MULTI-LINE setattr the first - single-line patch-surface grep missed; the closing sweep is an ast.walk - over every tests/*.py catching setattr in any form through module - aliases) and `BUDGET_ROOT_FENCES` in queue_snapshot (tests rebind it on - the queue facade while persist_queue_snapshot reads it at call time; the - pre-split span read queue's own re-export binding). Facade imports that - now serve ONLY leaf handle reads carry per-line noqa markers naming the - leaf. -6. Delta-D08 RE-DERIVED on tip bytes (Q-d=A): mark_finalize_control_drained, - mark_intent_scope, release_claim and settle_intent now read the projection - strict (_load_intents(strict=True) + strict_existing_dict=True) and turn - the typed ValueError into CancelIntentProjectionCorrupt via the - _refuse_corrupt helper (oracle shape); the tip GR5-6 docstring that - RATIONALIZED fail-open ("non-minting mutators find no row in {}") is - deliberately rewritten — that was the semantic delta, not a drift. - Upstream's own strict sites (request_cancel, claim_intent, active_intents) - keep their tip bytes. Caller audit (every tip call site, what happens on - raise): (a) task_lifecycle._settle_intent/_release_intent_claim wrappers — - except Exception, log.debug: the intent stays OPEN/CLAIMED for the - watchdog; (b) task_lifecycle cancel_task_by_id cascade postcondition — - outer except, cascade intent stays open, watchdog re-runs the cascade; - (c) task_lifecycle record-cascade-scope site — except Exception with - log.warning + typed cascade_scope_record_failed forensic row (loud, second - line of defense); (d) ouroboros/task_results.fail_tasks budget drain — - both settle and release wrapped, log.debug, intent stays for the watchdog; - its claim path already maps a raise to claim_refused and skips the task; - (e) workers pending-drop lanes (_settle_cancelled_pending_row, - _release_pending_claim, terminalization retry) — except Exception -> - claim_unresolved -> the row is RETAINED in the terminalization-retry lane, - nothing silently dropped; (f) owner_stop._mark_owner_stop_control_drained - — outer except returns False: no drain stamp, the finalization episode - stays bounded by the unstamped request anchor (a corrupt projection can - not buy an unlimited final turn). No caller needed a code change; the pin - is tests/test_cancel_intent_corruption_s6.py (C1/C2), re-keyed to the tip - bool contract of release_claim (upstream fence-proof return; the oracle's - `is None` clauses would pin a retired signature). -7. S7b split RE-DERIVED from tip bytes (rows 2152-2223): lossless — 107 - test functions / 112 expanded items before == after, zero duplicate - names, all green. The oracle partition is honored row-by-row for every - surviving name; tip-new (bea08137-class) objects were placed by theme and - these MINTED rows are: retry-race custody family - (_patch_retry_input_handoff, _root_retry_task, - test_retry_cancel_before_admission_publishes_no_successor, - test_retry_admission_before_cancel_canonicalizes_and_stops_leaf, - test_retry_leaf_cannot_escape_a_logical_root_cascade_at_final_boundary, - test_cancel_suppressed_retry_task_done_waits_for_summary_obligation, - test_timeout_precheck_yields_retry_leaf_to_logical_root_cascade, - test_retry_boundary_refuses_missing_physical_leaf_authority, - test_terminal_retry_leaf_wins_even_when_predecessor_lineage_is_corrupt, - test_terminal_before_retry_boundary_creates_no_scheduled_ghost, - test_same_id_timeout_retry_cancels_exactly, - test_retry_leaf_completion_between_request_and_custody_wins, - test_graceful_single_retry_targets_leaf_and_stop_now_hardens_same_intent, - test_task_lifecycle_keeps_scheduled_admission_import_surface, - test_task_lifecycle_keeps_capture_miss_calling_convention) - -> tests/test_cancel_custody.py; dispatch-authority family - (test_assignment_blocks_when_cancel_intent_projection_is_unreadable, - test_assignment_retains_pending_when_claim_authority_raises, - test_timeout_reaper_does_not_clone_over_unreadable_cancel_authority, - test_snapshot_restore_blocks_when_cancel_intent_projection_is_unreadable, - test_cancel_authority_hold_never_releases_a_terminal_row_to_dispatch, - test_preserve_pending_shutdown_keeps_cancel_authority_hold_nonterminal, - test_drop_cancelled_pending_retains_custody_until_task_done_is_published, - test_drop_cancelled_pending_releases_a_failed_intent_claim, - test_drop_cancelled_pending_does_not_assume_settled_when_settle_helper_missing, - test_drop_cancelled_pending_defers_when_intent_vanishes_before_settle) - -> tests/test_cancel_queue_integration.py; durable-gate additions - (test_blank_status_task_done_over_a_running_row_is_a_durable_fault, - test_blank_status_task_done_over_a_settled_row_is_admitted, - test_copy_back_exception_never_synthesizes_a_completed_row) - -> tests/test_cancel_task_done_validation.py; projection-primitive - additions (retry-lineage mint family rows 82-238 of the monolith, - test_claim_intent_refuses_an_existing_corrupt_projection, - test_claim_intent_absent_projection_is_a_read_only_miss) - -> residual tests/test_cancel_intents_phase_a.py; and - _write_root_retry_pair joined tests/_cancel_intents_shared.py (read by - both the mint suite and the custody retry suite — a tip extension of the - shared set, rows 2152-2155 class). The monolith's section-banner comments - are not carried (the same inter-span-comment loss the D14 lane recorded - for the emitter). tests/test_cancel_cascade_v664.py's source-scan clause - retargeted to the owner leaf (events_task_done) and its now-unused facade - import dropped. -8. Durable pins landed with tip re-keys: tests/test_e2e_cancellation_scenarios.py - + tests/fixtures_e2e_cancellation.py (E-suite; the driver extensions — - typed cancel_task with cascade/stop_policy, hurry_task, _api_status — - ported into devtools/benchmarks/common/server_runner.py, options-free - cancel keeps the legacy empty-body wire shape for the existing benchmark - callers); E8 is RETIRED and superseded by E13 (F6 disposition, owner - Q9=A/Q10=A: a budget-drained queued task PAUSES — durable scheduled - result with reason_code=budget_exhausted plus the typed - budget_scope_paused event — it is not failed); C5/R1 were already on tip - (D09 quiet edge); tests/test_cancel_protocol_inventory_s6.py (C7-C10) - re-keyed by symbol to the tip owners (settle-owner cluster in - task_lifecycle, miss lane in cancel_publication, admission in - task_admission, the F2.2 leaves) with the upstream retry/depth terminal - lanes ADDED to both the C7 manifest and the no-deliverable enumeration; - C9's task_finalization docstring (row 1093) corrected to the VERIFIED tip - call order (emit_task_results registers the owed row, then stores). -9. Mock-lane execution proof (post-commit verification, then amended in): - the eight mock scenarios (E4-E7, E9-E12) ran GREEN against a real isolated - server on this exact tree — after ONE harness adaptation of the class the - suite's own docstring predicts: upstream delegation-by-construction makes - subagent selection explicit (`subagent_configuration_unsaved` / - `subagent_selection_required`), so isolated_settings() now pins a saved - one-row Available-subagents roster (api_model on the lane's own slug) and - the stub's spawn turn passes subagent_id="mock-scout". Scenario semantics - untouched; the same class the deferred - test_daemon_token_containment_s6.py note in the matrix recorded. -10. tests/test_v7next_transplant.py queue probes re-pinned to the PRE-SPLIT - monolith bytes of this lane's base (git show 2878560e:supervisor/queue.py) - with the landed-leaf inverse-normalization fallback — the D01/D10 probe - recipe. -11. Path-keyed mirrors updated in the same commit: HOT_CODE_PATHS gained the - four F2.2 leaves + queue_transitions; test_contracts' literal - progress_meta scan gained events_runtime_controls + events_task_done; - test_heartbeat_presentation's message-seam scan gained the two worker - leaves; tests/test_events_extraction.py flipped from the pinned - partial-split work order to the completed shape; the five satisfied - [split_pending]/[split_pending_leaves] rows left scripts/v7next_domains.toml - with the two owner-retired leaves recorded in a comment. - -## From the F2.3b review-semantics lane (base dcf8dd4b, 2026-08-31) - -1. F5 leaf-name mint (advisory split): the reference leaves - `ouroboros/tools/review_advisory_prompt.py` / `review_advisory_run.py` are - NOT the landed names. The drift probe (`--check` of both reference leaves - against `git show dcf8dd4b:ouroboros/tools/claude_advisory_review.py`) - byte-falsified the organ's semantics: prompt leaf 4/5 rows byte-true with - `_build_advisory_prompt` falsified (governance_by_retrieval pointer form); - run leaf 10/18 byte-true with `_run_claude_advisory`, - `_run_advisory_delegated`, `_llm_extract_advisory_items`, - `advisory_review_route`, `advisory_slot_enabled`-adjacent route/gate - projections falsified (native episode + reviewer-slot SSOT replaced the - Claude-SDK transport). Landed as `preflight_review_prompt.py` / - `preflight_review_run.py` — the organ's public rename vocabulary (Q1) — - cut from tip bytes, tool proof green on every symbol. -2. Advisory row dispositions against the 30 ledger rows (3852-3881): - 23 transplanted-from-tip (5 prompt + 18 run); 3 SUPERSEDED — - `_release_metadata_preflight`, `_auto_sync_release_metadata_if_needed`, - `_syntax_preflight_staged_py_files` live with upstream's - `ouroboros/commit_admission.py` (Q3=A SSOT; the parent keeps the alias - monkeypatch seams, pinned by - test_review_owner_facades.test_the_deterministic_preflights_live_with_commit_admission); - 4 RETIRED with the SDK transport — `_changed_paths` (upstream's - `review_helpers.parse_changed_paths_from_porcelain` class), - `advisory_route_requires_api_key`, `_advisory_session_deltas`, - `_advisory_sdk_budget` (no tip bodies exist; not replayed). -3. Scope budget probe corrections (mandate: re-verify the matrix's - 6-superseded/1-retired): the reference leaf probed 7/8 byte-true against - tip; ONE row falsified — `_SCOPE_REVIEW_SLOT_TIMEOUT_SEC` (reference `900`, - tip `None`: the adaptive-timeout contract retired the constant; tip byte - kept). The matrix called `_SCOPE_BUDGET_TOKEN_LIMIT` retired (#383) — the - probe shows the NAME alive on tip as a private alias of - `review_helpers.REVIEW_PROMPT_TOKEN_BUDGET` (the reference-era standalone - constant is what died); it moved with the other five owner aliases - (`_SCOPE_MODEL_DEFAULT`, `_SCOPE_FAILCLOSED_WINDOW`, - `_SCOPE_MODEL_CONTEXT_WINDOW`, `_shared_window_scaled_reserves`, - `_calibrated_input_token_limit`) plus `_is_provider_oversize_error` into - the budget leaf per the ledger's rebind disposition, parent re-exports — - import-frozen on both sides exactly as before the split, so no - patch-visibility change. -4. D31 port (owner decision 5.1=A): `_run_on_trusted_base` re-derived from - the reference (scripts/run_external_review.py:539 @ 9f691656) onto the tip - script — the contributor lane now ALWAYS executes the target base's own - review machinery (self-re-run from a detached base worktree with pinned - base/head SHAs). `_REVIEW_SUBSTRATE_PATHS` demoted to EVIDENCE ONLY - (`review_substrate_changed` packet diagnostic), never a gate: - `_contributor_result` is exit-code-only (reference form), and - `finalize_contributor_outcome` dropped both the `snapshot` parameter and - the `trusted_base_rerun_required` downgrade (reference form; its one - script call site and two test call sites re-derived). The fail-closed - `INCOMPLETE_MAINTAINER_TRUSTED_BASE_RERUN_REQUIRED` vocabulary survives on - the ONE non-portable path — a target base whose tree carries no review - wrapper (new guard; the reference would have misfiled python's exit 2 - there as "empty diff"). -5. D31 pin suite ported from the reference (probe script, handoff/forwarding/ - in-place/dirty/e2e pins) with tip reverse-mapping: the seeded repo stubs - `ouroboros/openrouter_attribution.py::OPENROUTER_APP_HEADERS` (the tip - wrapper's module-level import) where the reference seeded - `runtime_mode_policy::GIT_OPS_FAMILY_PATHS` (its wrapper's import). NEW pin - beyond the reference: the always-runs-on-base parametrization includes - `ouroboros/review_native_episode.py` — a review-machinery module ABSENT - from the evidence hand-list — plus a fail-closed pin for the wrapperless - base. Disclosed test replacement: the old gate clause - (`test_contributor_outcome_fails_closed_on_receipt_or_trust_drift`'s - substrate-downgrade half) asserted the hand-list AS a gate — exactly the - semantics the owner retired — and is replaced by the reference's - receipt-drift-only pin plus `test_contributor_result_is_decided_by_the_exit_code_alone`. -6. Path-keyed mirrors in the same commit: `_REVIEW_SUBSTRATE_PATHS` (evidence - list) and `review_context_atlas._REVIEW_STACK_PATHS` gained the three new - leaves beside their parents; domains.toml gained the three D06 leaf rows - and cleared both satisfied [split_pending]/[split_pending_leaves] entries - (review_execution's row left untouched — matrix A2 marks it superseded by - upstream's review_verdict_extraction, an integrator decision, and this - lane's mandate excludes review_execution). - -## From the integration seam (coordinator, F2 close-out, 2026-08-31) -1. split_pending row `review_execution.py -> review_session_verdict.py` - retired as SUPERSEDED-BY-UPSTREAM (matrix D06 verdict A2, confirmed by the - F2.3a lane): upstream performed the same extraction itself as - review_verdict_extraction.py; the reference leaf name never materializes. - The F2.3b lane left this disposition to the integrator - recorded here. -2. SUPERSEDING note to the F2.3b lane's atlas claim: the two advisory leaves - entered _REVIEW_STACK_PATHS with the F2 close-out conformance fix, not - with the lane commit (the lane's ledger entry overstated); a membership - pin now accompanies them. -3. Cross-test fragility class (found by loadscope redistribution after the - close-out fixes): supervisor.queue globals (PENDING/RUNNING/...) are - rebound by init_queue_refs across ~35 upstream test sites with no restore - - an upstream-wide convention, not to be mass-rewritten. READER-SIDE RULE - for campaign pins: never assume those globals are empty; REPLACE the dict - for the test's scope (monkeypatch.setattr), never append into the live - one. Applied to test_both_custody_surfaces_see_the_same_live_task_set. - -## From the f30 lane (F3.0 opening train, base db944347, 2026-08-31) -1. ABI-6 re-location on tip (the roast-session scratchpad that minted the P1 - inventory did not survive; the surviving primary source is - V7NEXT_SYNTHESIS_DRAFT.md, which names items without addresses): - (a) `_call_llm_with_retry` alias re-located at ouroboros/loop.py:74 - - ZERO code readers on db944347 (every monkeypatch targets the public - name); removed. (e) `compute_cost_with_children` (task_status.py:1001) + - `format_handoff_message` (:1054) - zero production callers; the canonical - with-children rollup lives in agent_task_pipeline/post_task_synthesis - with cost_projection.py as projection SSOT; removed with their private - helper and tests. (zh) "CHECKLISTS:507" re-located: the line number - drifted on both b9f7597f and db944347; the actual finding (archive, - sol audit 30.08) is the env_allowlist checklist row claiming - TELEGRAM_BOT_TOKEN is in FORBIDDEN_SKILL_SETTINGS while - contracts/plugin_api.py:23 does not contain it - doc aligned to code - (10 keys), code deliberately unchanged. -2. ABI-6 items NOT re-locatable on tip - recorded, NOT replaced by - invention (f3 plan instruction): "failure-detector compat wrapper" and - "3 underscore renames". Evidence of the sweep: compat/alias comment grep - across ouroboros/ (12 candidates read - none is a failure-detector - wrapper); AST scan for one-line delegating wrappers with - fail/retry/error/detect/classify names (single hit: - git_review_cycle._handle_revalidation_failure, which is the D18/D33 - module-handle idiom, not a compat shim); targeted reads of - llm*/loop*/transport modules. Disposition: superseded-by-upstream inside - the ABI-6 row; a future lane finding the real item re-opens it with - bytes, not memory. -3. ABI-5 execution corrections against the f3 plan text: - - The two ws5 "read exemption" tests are NOT floor tests but family - read-carve mechanism tests that used the floor detector as vehicle; - deleted only the detector's own test, RETARGETED the two mechanism - tests to the surviving `_detect_safety_mode_self_lowering` (same - composition through `_owner_control_mention_blocks`). - - `effective_max_improvement_passes(has_deadline=)` existed solely for - the until_deadline count-axis branch; the parameter was removed with - the alias (callers: task_results wrapper + wallet cap + rails line; - BudgetSnapshot.has_deadline and every TIME rail untouched). - - The wallet-authority test derives its uncapped lane from - OUROBOROS_REVIEW_MAX_CYCLES=unlimited now (the alias lane is gone); - v664's deprecation-noise test now pins that resolve_budget_profile - emits NO deprecation events at all. - - Bench adapters (programbench, swe_bench_pro) switch to - improvement_policy=fixed: behavior-identical because their explicit - max_improvement_passes=6 was always the binding count axis. - - Ratchet: tests/test_v664_acceptance_planning.py briefly crossed the - 1001 band (1005 lines) after a test rewrite - shrunk back to 996 - instead of minting a band rationale; BYTE_DEBT for - tests/test_devtools_benchmarks.py regenerated 327935->327888 - (reduction) by the official generator. -4. Disclosed consequence (rides ABI-2/Q8=B): a pre-7.0 stored root contract - whose normalized profile says until_deadline is judged malformed by the - acceptance-wallet authority (pre-existing unknown-policy behavior); - pre-7.0 task-result history is quarantined wholesale by ABI-2 in the - same release and the ABI-7 RC auditor names the migration. - -## From the f31b lane (extensions, base 29e2b045, 2026-08-31) - -1. Plan line-ref drift, re-verified on base bytes: the supervised-future - leak pinned as "extension_plugin_api.py:459-466" lives at :460-466 on - 29e2b045 (future minted at :460, the second `_require_open_locked` - re-check at :461-462). The leak itself is REAL and was reproduced red - by the direct regression test - (tests/test_extension_registration_atomicity.py:: - test_supervised_future_never_leaks_when_unload_wins_the_registration_race) - before the ABI-9 fix: the factory ran despite the refusal. -2. ABI-9 semantic tightening, disclosed: `on_unload` callbacks registered - during a FAILED registration are no longer executed on abort (on the - base they ran via unload_extension because the bundle pre-existed the - register() call). Staged side effects (event-bus subscriptions, - supervised runners, companion spawns) are disposed/never-started - instead; on_unload fires only for a published extension. No test on - the base pinned the old failed-register callback behavior. -3. FORBIDDEN_EXTENSION_SETTINGS reader refs from the f3 plan - ("extension_plugin_api.py:513/:664") re-located on base bytes to - :513 (companion env filter) and :664 (get_settings protected set) — - both verified before the ABI-1 alias collapse. -4. ABI-1 execution notes (owner-ratified design + batch №6 answers): - - Admission timing: the ratified text anchors the predicate "at NEW-PASS - issuance"; the lane evaluates it EAGERLY, after the $0 free-replay gate - and BEFORE the paid panel dispatch — no outcome of a dispatched panel - could mint a PASS for an inadmissible payload, so dispatching would only - burn reviewer money. Byte-identical re-review of grandfathered bytes - still free-replays the recorded PASS first. - - Reload-aggregation hole found and closed: a persisted - plugin_api_admission FAIL finding re-aggregated to WARNINGS (executable!) - on load_review_state; aggregate_skill_review_status now treats it as a - structural gate like skill_preflight (PENDING under every enforcement). - - Preflight infra failures now fail closed WITHOUT persisting (a transient - breakage must not clobber live review state); genuine payload gate - failures keep persisting PENDING as before. - - 6.2=A scope note: the declarative dependency fingerprint is enforced on - the extension liveness path (deps_declaration_desync). Script-skill deps - flow through the same read_deps_state/specs-hash gates but their - readiness callers are outside this lane's files — residual disclosed for - the RC auditor (ABI-7) inventory. - - launcher_bootstrap plan ref ":565-579 resync grants" re-located: the - grant-carry seam landed as _carry_grants_across_reseed called from - _reseed_native_skill_in_place (the :565-579 span on 29e2b045 is - _stamp_native_seed_trust's docstring). -5. Test pinned to the pre-2.0 contract, updated with disclosure: - tests/test_native_seed_trust.py seed fixtures wrote type=extension seeds - WITHOUT the plugin_api field and asserted the native-trust stamp — under - ABI-1 that stamp is correctly refused. The fixtures now declare - plugin_api: "2.0" (matching real bundled seeds); the field-less refusal - itself is pinned in tests/test_plugin_api_admission.py:: - test_native_seed_trust_is_closed_to_fieldless_extensions. -## From the f31c lane (F3.1-C schema/updater, base 29e2b045, 2026-08-31) -1. ABI-2 reader seam widened beyond the plan's single address: the plan named - `load_task_result` (:665) as THE reader, but the sibling - `list_task_results` feeds UI/recent (gateway/tasks.py:796) from the same - rows - quarantine is implemented at BOTH, batched per scan. Direct - observational globs (server_routing_context.py:207, gateway/tasks.py:803) - are deliberately untouched: after the first swept read they see nothing, - and touching them would be compat machinery Q8=B forbids. The quarantine - subdirectory is invisible to every `*.json` glob (non-recursive). -2. Plan section 7 item (3) "ONE durable event / chat notice per batch" is - superseded by the batch-6 answer 6.3=B: visibility is the durable events - log ONLY - one `task_results_quarantined` row per read/scan batch, no UI - counter, no chat notice (pinned by a no-chat-jsonl test). The move itself - is the dedupe: a row can appear in exactly one batch ever. -3. Writer-inventory correction against "writers stamp": five writer sites - exist on tip, four stamp (write_task_result, the acceptance-state and - plan-review merge-writers in task_results.py, the owner_hurry projection - writer). The cancel-receipt amend-writer - (supervisor/terminal_delivery.py:1284) deliberately does NOT stamp: it - never creates a row, its dict-copy merge preserves whatever stamp the row - carries (so no downgrade path exists), and the module sits exactly at the - 1500-line band edge - a stamp there is correctness-redundant. Disclosed - residual: a pre-7.0 row whose ONLY post-upgrade write is a cancel receipt - stays unstamped and is later quarantined with its receipt - consistent - with wholesale pre-7.0 quarantine (f30 entry 4). -4. Module-size: the ABI-2 machinery lives in a new leaf - `ouroboros/task_result_schema.py` (task_results.py re-exports; callers - and tests import through the facade). Inlining it drove task_results.py - to 1592/1600 against the hard cap; after the split the ratchet manifest - is byte-identical to the base (task_results.py 1465, band entry kept). -5. Disclosed interaction: `restore_pending_from_snapshot` probes each - snapshot-pending task's result with `load_task_result(strict=True)` - (queue_snapshot.py:305). A pre-7.0 unstamped row now raises there, so the - task is terminalized through the existing result-authority custody path - instead of being revived - the N-1-snapshot restore of pre-7.0 tasks - degrades fail-closed, consistent with Q8=B wholesale quarantine. -6. Strict-path contract stability: for MALFORMED rows the pre-ABI-2 strict - messages are kept byte-stable ("task result authority is unreadable or - invalid" / "task result is unreadable or invalid" - test_review_cycles - pins the former); schema refusals raise the new typed message with - reason=quarantined_schema. Strict reads never mutate storage: an - authority probe is not allowed to be the mover. -7. ABI-7a: `read_update_tx_strict` grew the fourth status `"future"` - (integer stamp above ours; raw tx returned as evidence). Full strict - caller sweep on the base: update_merge internal consumers fail closed via - existing `!= "valid"` branches; `update_tx_phase` raises the typed - refusal without writing; `_safe_restart_serialized` defers the restart; - git_ops_reset.py:326 keeps `tx_matches` false and clears the orphan - intent (fail-closed). A NON-integer stamp reads `corrupt` (evidence kept - on disk, `{}` returned) - only a genuine newer-release stamp is `future`. - An unstamped marker stays `valid`: that IS the N-1 transition contract. -8. F2.4 boot-finalize family untouched byte-wise except the dispatch - docstrings and the future branch in `finalize_managed_update_on_boot`; - the carrier-conflict crash floor (F2.4 ledger entry 4) keeps its existing - pins - the shim suite adds the N-1 byte-form fixtures for every phase - seam plus the marker upgrade-on-first-rewrite assertion. -9. Fixture sweep: 9 test files hand-writing task-result rows as - current-version writers now stamp them (acting_subagents, presence_tools, - tasks_list_slice, headless_task_events, context_drive_state, - gateway_history, host_service_api, plan_review_public_projection - plus - the new F12 suite writes both forms deliberately). -## From the f31d lane (F3.1 polosa D: ABI-3 -> ABI-10, base 29e2b045, 2026-08-31) - -1. ABI-3 F11 inventory frozen BEFORE the first removal in - docs/v7next/ABI3_GATEWAY_ALIAS_INVENTORY.md (the RC-auditor feeder). Key - falsification against the plan text: the plan named api_types.js as a - removal surface, but the lane constraint (web/ untouchable; chat.js at its - BYTE_DEBT ceiling) plus the JS evidence made it a NON-surface: no alias - has a functional JS reader (`resolveCostPair` falls back to the honest - name; telegram/prefs aliases are JSDoc-only), so NO alias was HOT-DEFERRED - - only the stale JSDoc typedef lines and the GATEWAY_CONTRACT_VERSION - carrier switch are deferred, and tests/test_gateway_parity.py excuses - exactly that frozen extra-set and nothing else. -2. Cost-alias removal is CLASS-level at the SSOT seams (cost_projection.py - emitters strip the retired spellings; read tolerance + deprecated-wins - precedence for stored pairs kept verbatim). Consumer fixes rode along in - files outside the polosa-D list (disclosed cross-lane touches): - agent_task_pipeline.py, supervisor/events_task_done.py (polosa C - neighborhood - different hunks from C's ABI-2 seam at state.py:198/ - task_results.py:665/723; task_results.py itself was NOT touched), - post_task_synthesis.py + synthesis_cost_text.py (pre-synthesis snapshot - and prompt renderer moved to the honest with_children name - the snapshot - feeds task_summary chat rows, i.e. gateway egress), tools/recent_tasks.py, - tools/control_task_results.py. -3. Cross-lane invariant relied upon (for the coordinator's integration - check): the "stale stored cost_usd beside a fresh honest name after a - post-upgrade merge-write" class is consumed by polosa C's ABI-2 Q8=B - quarantine (pre-7.0 unstamped records never reach the merge path). Within - this lane's own tree the class is test-visible only via records written by - current code, which now write honest names. -4. Ingress validation (Q7=A) is inbound-only by test-pinned design: history - replay is egress and gateway/history.py must never import validate_ingress - (pinned). PEP 563 falsified `__required_keys__` on Python 3.10 (string - annotations make every total-class key read required, ExecutorRef lost its - Required["type"]) - requiredness is re-derived from resolved hints + - per-class totality in gateway/schema.py. -5. UpdateApplyRequest.strategy: the runtime silently defaulted a missing - strategy to auto_merge while the contract declares it REQUIRED; the - executable schema now enforces the contract as written (web client always - sends it; no test posted a bare body expecting the default). -6. ABI-10 default panel deliberately resolves through - get_review_models()/get_scope_review_models() (derived env plane), NOT a - static list: preserves the identical review models for every config class - (shipped defaults, single-direct-provider adaptation, bench env overrides) - - "review models change nowhere". The SETTINGS-plane comma keys die - (RETIRED_SETTING_KEYS, ghost purge); a comma-only-settings install gets - the default panel exactly as ratified (5.4=A). -7. server_runtime.apply_runtime_provider_defaults was a settings-plane WRITER - of the retired comma keys (direct-provider path INTRODUCED them; the - prior-scope-default migration would KeyError post-retirement): it now - normalizes only values it is fed and never introduces a retired key; the - read-time getters own the direct-provider review adaptation (pinned per - provider by new read-time tests). model_slots' singular->plural promotion - removed (dead after purge; both files outside the polosa-D list - - disclosed). -8. Bench templates (continual_learning, gaia, swe_bench_pro x4) migrated to - structured OUROBOROS_REVIEWER_SLOTS with byte-identical model sets; the - comma keys were dropped from all bench settings JSONs. NOT touched: - operator_patches/*.patch (append-only artifacts) and the env-plane - forwarding lists (server_runner/manifests/cybergym_lifecycle) - the env - spellings remain the legitimate derived/operational plane. -9. Removed-with-evidence test clauses (the "test pinning the bug" class): - test_gateway_parity's ChatOutbound cost_usd JSDoc pin; the legacy - migration tests of reviewer_slot_config (phase-5 route envs, session-row - migration, legacy advisory materialization) replaced by - retired-envs-are-ignored pins; test_git_review_bypass_gate's - "unroutable enabled session advisory" state is UNREACHABLE by construction - post-ABI-10 (the parser refuses an enabled session advisory without a - concrete target at save AND load) - the defensive fail-open branch stays - covered via a synthesized config in test_skill_advisory_pre_review. -10. Residuals disclosed (NOT executed, outside the named scope): the - phase-5 env reads that survive in review_substrate.scope_reviewer_slots - (route_env_key plumbing for explicit-models callers) and the - OUROBOROS_ADVISORY_REVIEW_ROUTE mentions in - preflight_review_run/claude_advisory_review prose/vocabulary - env-plane - remnants, candidates for the F3.3 sweep extension; the JS-side typedef - cleanup + GATEWAY_CONTRACT_VERSION carrier switch (web lane). -## From the F3.1 lane A (typed organ, base 29e2b045, 2026-08-31) -1. extension_dispatch.py typed dispatchers (D04 entry 5, rows 187/188) ADOPTED - WHOLE from the reference WITH BYTE PROOF: the tip file, the merge-base - (8028f1df) file and the v6.64.0 file are md5-identical (4e9ad3ba…), so - reference == tip + delta exactly (the same adoption class the lane used for - mcp_client.py). The ToolRegistry methods `_dispatch_extension_tool` / - `_dispatch_mcp_tool` and the hoisted `_extension_dispatch_candidate` retire - from registry_core; call sites read the module handle. The unknown-name - answer for a registered-but-not-live extension is typed - EXTENSION_UNAVAILABLE (the D02 liveness bit); the truly-unknown name keeps - the tip's alias-filtered legacy text (tip drift the oracle lacks). The - `failure_kind` delta on extension_process_runner (unrowed in MIGRATION, - named by D14 entry 10 as Ф3 territory) lands here: `ExtensionProcessError` - gains the kwarg, only the deadline kill raises `failure_kind="timeout"`. -2. D09 typed-policy-refusal subfamily (D02 entry 4): the five ladder bodies - drifted upstream after the fork (`plan_next_wire_retry` state machine, - request-wire custody, effort-clamp discard rules), so the reference deltas - were RE-DERIVED onto the tip structure: the three planning rungs decline a - refusal; the retries twins raise it out of the bounded state machine before - any wire-retry planning and out of the reroute/strip body-error arms instead - of absorbing it into the first errored response, discarding the pending - effort-clamp note on each raise path (tip custody rule the reference - predates). classify_llm_exception branch inserted before the prose - heuristics, after the tip's provider_code read (the tip computes - provider_message/classification_text the oracle lacks — refusal outranks - them). tests/test_llm_typed_policy_refusal.py carried whole: 25 passed with - zero adaptation. Goldens: the two typed_policy_refusal cases returned to - fallback_ladder.json (15 -> 17) with `expected` RE-RECORDED from this - tree's live code via the suite's own --write entry; the write left all 15 - existing cases untouched (append-only diff = no accidental drift), and both - recorded blocks carry the oracle-intended semantics (refusal raises; the - exception case spends exactly one physical send). -3. Reference test adaptations, each disclosed in-file at the non-verbatim - spot (reverse-mapping rule §5.3-Δ item 2): - - registry facade: the reference pins an exact-32-name minimal facade; this - tree deliberately keeps the broad historical import surface, so the pin - is re-derived as an AST "the facade module DEFINES nothing but the - disclosed read-carve helper" plus owner-leaf homing/retirement asserts - (test_registry_core, test_registry_guard_process ×2). - - guard collaborator patch points follow this tree's `_registry()` - call-time-handle idiom (protected_artifact_shell_block_reason, - workspace_executor_state_write_block, build_resolved_resource_binding, - resolve_shell_cwd, shell_cwd_block_message, system/active_repo_dir_for, - light_shell_repo_mutation, runtime_data_guard_targets, - workspace_git_safety_violation, run_shell_git_block_reason, run_cmd for - git_vcs_ops); the reference's `shell_has_write_indicator` seam does not - exist here — the tip write-shape seam is `non_interpreter_write_shape`. - - the managed-update resolver pin re-targets the tip's TYPED - `authorized_assisted_task_strict` (adds the corrupt-marker A4-channel - clause the reference could not know). - - SCOPE_REVIEW_FLOOR rows (detector signature, denial text, code-contract - row, precede-safety parametrization) removed: the setting, guard and code - were retired by ABI-5 (owner Q10=A) in F3.0. - - detector-family signatures pin the tip's whole-family `writeish` - read-carve; three constant cardinalities follow upstream drift - (secret markers 17->18, denied read options 11->12, owner-state stems - 12->14); `_workspace_shell_write_block` pins the upstream - `write_target_argvs` parameter. - - plan-review pins: the reference's sync-side `_record_raw_plan_request_attempt` - and the vacuous-note wrapper (`_reuse_or_disposition_plan_review`, - `_VACUOUS_*_NOTE`) do not exist on tip — the parametrized wrapper test is - re-derived over the tip's three projection paths (review mode, vacuous - disposition fall-through, `_apply_disposition`). - - `_parse_plan_review_control` readers (test_plan_spec, test_plan_review, - plan_spec docstring) re-point to its new home tools/plan_render. - - two loop fakes (test_openai_chat_dispatch._FakeTools, - test_owner_hurry_s3._ProbeTools) gain `execute_result` adapting their - text exactly as the registry adapts a legacy handler — the loop now reads - the typed seam. -4. Reference DELTA re-applied, not replayed: the plan handler's pool hop wraps - `asyncio.run` in `contextvars.copy_context().run` so the sidecar - publication reaches the dispatching thread's slot; the reference's - surrounding `asyncio.wait_for` wrapper-timeout machinery is NOT reproduced — - the tip deliberately retired the nested wait (its comment explains the - cancel-then-block hazard), and replaying the span verbatim would have - reverted that decision (re-prove-trap class, D15 entry 3). -5. Protection closure: registry_core.py + tool_result.py membership in - SAFETY_CRITICAL_PATHS/HOT_CODE_PATHS verified landed with the re-split - commit (parity pin green); extension_dispatch.py — already safety-critical — - JOINS HOT_CODE_PATHS here because the dispatch bodies moved onto it from - the hot ToolRegistry class (the same parity rule; oracle carries the same - membership), and the parity pin now lists it. -6. Owner 6.1=A edge checked and NOT implicated: lane A changes no - admission/review/PASS semantics — the extension liveness refusal existed on - tip and is only retyped (EXTENSION_UNAVAILABLE), so the auto_review=false - contract (no PASS issued, nothing blocked) is untouched by the typed organ. -7. Function-size law: the EXTENSION_UNAVAILABLE branch pushed - `_execute_legacy_text` to 303 lines; resolved by extracting the - module-level `_unknown_tool_result` helper (behavior identical), not by a - band exception. tests/test_tool_result.py enters the 1001-1500 band with a - rationale via the official regenerator. -## From the F3.1 conformance fix-round (base 9edb9199, 2026-08-31) - -Dispositions for the six blocking findings of the Ф3.1 conformance review -(GPT-5.6 Sol, read-only, range 29e2b045..9edb9199). One finding per entry; -every closure carries its pin. - -1. ABI-9 ordering (finding 1) — FIXED. `_publish_registrations` is now - validate -> effects -> swap under ONE `_lock` hold: the definitive - unload/conflict validation runs BEFORE any deferred side effect - (supervised runners, companion spawns, bus subscriptions), and the swap - follows in the same critical section, so no concurrent unload/conflicting - publication can interleave anywhere between the three steps (both mutate - only under `_lock`; CompanionSupervisor uses its own lock and never takes - the registry lock — checked). Event subscriptions are STAGED - (`_StagedEventSubscription`; `EventBus.subscribe` accepts a pre-minted - sub_id so the id returned by `subscribe_event` is the id the bus attaches - at publication). Pins: tests/test_extension_registration_atomicity.py:: - test_conflict_refused_publication_has_zero_external_effects (conflict - arising between staging and publication -> refusal with factory never - started, bus untouched, bundle empty) and :: - test_event_published_before_publication_never_invokes_the_handler - (pre-publication invisibility, not eventual cleanup; sub_id fidelity). - The pre-existing leak regression and disposer-ABI pins stay green. -2. ABI-2 readers (finding 2) — FIXED. POST /api/tasks identity-collision - probe switched from the fail-soft loader (which QUARANTINED the probed row - and then read "no result", freeing the id) to `load_task_result(strict= - True)`: any stored row — admissible or not — keeps its identity occupied - (409) and the probe never mutates storage. The unfiltered GET /api/tasks - slice-before-projection path now runs the same fail-soft admission as - `list_task_results` (quarantine + ONE batched `task_results_quarantined` - event per scan, 6.3=B) via the schema primitives imported from - `ouroboros.task_result_schema`. Pins: tests/test_headless_task_api.py:: - test_task_api_identity_collision_check_is_strict_not_fail_soft (both an - unstamped and a torn row: 409, bytes unchanged, no quarantine dir) and - tests/test_tasks_list_slice.py:: - test_unfiltered_list_slice_is_admission_aware_with_one_batched_event. -3. ABI-3 producers (finding 3) — FIXED as a CLASS, read tolerance untouched. - Honest-name cutover of every remaining task-result/task-done producer: - the four named files (supervisor/task_admission.py, - events_schedule_task.py, workers.py x2 fallbacks, events_task_done.py - root/subtree/unavailable branches) PLUS the same class found by the - sweep in supervisor/queue.py, cancel_publication.py, task_lifecycle.py - (x2 fallbacks), supervisor/state.py `reconstruct_task_cost` internals - (fields seam kept as idempotent guard; the tuple path reads the honest - key), ouroboros/post_task_checkpoint.py, and - ouroboros/post_task_synthesis.py child-evidence rows (resolve stored pair - deprecated-wins, emit honest name) — cross-file touches beyond the - review's four examples are this disclosure. Fan-out pin: - tests/test_gateway_abi3_removals.py::TestAliasProducerFanOutSweep — an - AST sweep of EVERY ouroboros/ + supervisor/ module: (a) no - `write_task_result` call passes a retired alias (kwarg or dict-literal - arg; NO allowlist), (b) every dict-key/subscript emission of a retired - spelling must be an allowlisted INTERNAL non-gateway plane (physical - ledger rows, llm/usage observability events, review/evidence receipts, - subagent envelope, evolution state, custody settlement events, reflection - records — 20 rows, each with its plane named), (c) stale allowlist rows - FAIL the test, (d) the three non-cost aliases have zero emissions with no - allowlist at all. Explicitly NOT cut over (they are not the gateway - alias): the internal planes above keep their own `cost_usd` field - spellings — renaming ledger/receipt/envelope schemas is outside ABI-3's - inventory and would be an unsanctioned break of their own producer/reader - pairs. -4. ADOPTION (finding 4) — ABI-2/ABI-3/ABI-9 remain `done` LAWFULLY after the - fixes above; each row's what/hook columns now name the fix-round closure - and the new pins (`scripts/v7next_adoption.py` green). No residual was - left open, so no row moved to in-progress. -5. Domain manifest (finding 5) — the stale `ouroboros/contracts/api_v1.py` - row (module removed by ABI-3 lane D3) dropped from - scripts/v7next_domains.toml [modules]; DOMAIN_QUOTIENT_REPORT.md - regenerated by the official scripts/v7next_domain_report.py: 487 modules, - "manifest drift: none (manifest == tracked population)". -6. Whitespace (finding 6) — `git diff --check 29e2b045..HEAD` is now clean - (rc=0). Provenance checked BEFORE fixing, per the campaign's verbatim- - bytes rule: all 50 trailing-whitespace lines are campaign-authored (the - `_shell_guard_text` rewrite introduced by lane A commit 2e575b82; the - frozen oracle v7_wip @ 9f691656 contains no `_shell_guard_text` call at - all), so no byte-proved span was touched and nothing had to be declined. - Blank-EOF fixes: tests/test_core_native_results.py, and - ouroboros/tools/registry.py — a PROTECTED file; the delta is exactly the - two trailing blank lines left by the lane-A facade assembly, zero code - bytes (disclosed in the commit). -7. Function-size law (found by the fix-round's own gate run, not by the - review): the ABI-2 strict-probe block pushed `api_tasks_create` to 310 - lines and `scripts/regenerate_size_ratchet.py --check` refused new - function debt. Resolved per the lane-A entry-7 precedent — module-level - helper extraction (`_task_identity_occupied`, behavior identical), no - band exception, no manifest change. Enforcement note for auditors: this - line's ratchet is pairwise base-vs-tip with NO committed-history replay - (`ouroboros/review.py::validate_size_ratchet` docstring), so the four - fix-round commits between the ABI-2 landing and the extraction carry the - over-limit function in their trees without being audited surfaces; the - final tree and the CI base (the pushed 9edb9199) are both clean, and the - local degraded parent-tree check is green from this commit's parent on. - DOMAIN_QUOTIENT_REPORT.md regenerated once more so its analyzed-inputs - fingerprint matches the final runtime tree. - -## From the F3.1 conformance fix-round-2 (base aae647fb, 2026-08-31) - -Round-2 verdict (GPT-5.6 Sol): three findings NOT-CLOSED (ABI-9, ABI-2, -ABI-3 + the dependent ADOPTION claims); Domains manifest, git hygiene, -helper extraction and new-defects CLOSED with no action owed. Dispositions: - -1. ABI-9 (finding 1) — FIXED. The round-1 order (validate -> effects -> - swap) attached the bus subscription and started the supervised runner - BEFORE the registry swap; EventBus.publish() takes only the bus's own - lock, so a concurrent publish could invoke a handler of a - not-yet-published extension, and the round-1 pins never exercised the - window. `_publish_registrations` is now validate -> SWAP -> attach under - the SAME single registry-lock hold: the validated snapshot becomes the - authoritative bundle (digest minted, every attachable effect recorded on - the bundle) BEFORE any effect attaches, so a handler is visible to the - bus only for an already-published extension. A post-swap attach failure - is disclosed (log.warning) and raised into the callers' standard - dispose+unload path — load_extension/unload_extension reap everything the - bundle recorded (surfaces, sub_ids, futures, companion names, on_unload). - Pins (tests/test_extension_registration_atomicity.py): a REAL - barrier-sequenced race — a publish interleaved between validation and - attach never invokes the handler while the bundle is provably already - published at attach time; the supervised effect observes a published - bundle at its start; a post-swap attach failure ends with empty - registries, an empty bus and the extension's on_unload having run. - ARCHITECTURE rows (extension_loader, extension_plugin_api) and the - registry-state staging docstrings restated to the true order. -2. ABI-2 (finding 2) — FIXED. `_raw_sorted_result_names()` no longer - silently drops a file whose bytes fail to parse: malformed candidates are - returned separately and `_tasks_list_payload` routes EVERY one through - the same admission reader — quarantine plus a contribution to the SINGLE - batched `task_results_quarantined` event of the scan — even when the - candidate would have sorted beyond the slice window (the sort had read - its bytes anyway). Torn-concurrent-write safety moved to where it truly - lives: the quarantine primitive re-checks under the row's own write lock - (kept_admissible), and a malformed name is never memoized. Disclosed - residual (documented in the payload docstring): a PARSEABLE inadmissible - row beyond the window is not classified by the sliced request — the next - full/filtered scan quarantines it. TEST-CONTRACT DISCLOSURE: the pre-fix - clause test_torn_result_file_is_skipped_then_recovered_without_ - poisoning_memo pinned the silent drop as "torn-write tolerance" — a test - asserting the defect; replaced by - test_malformed_result_file_is_quarantined_not_silently_dropped and the - REAL slice-boundary pin - test_malformed_candidate_beyond_the_slice_window_is_still_quarantined - (rows > limit; one batch event spans both sides of the boundary). -3. ABI-3 (finding 3) — FIXED as the projection-boundary semantics. The ABI - carries no alias: outbound surfaces (public_task_result, task detail, - history frames, the cancel path through queue) emit ONLY honest names; - stored legacy resolves deprecated-wins and NORMALIZES at projection and - at re-write. Landed: TASK_COST_META_FIELDS honest-only; - write_task_result merges over with_cost_aliases(existing) and normalizes - the merged row (a legacy mutator's edit still wins its pair, then is - stripped); public_task_result normalizes the top level + subagent - envelope + loop-outcome usage; history mapper converts via - carry_cost_meta at all three copy seams (task-summary replay, - progress-meta replay, terminal-truth annotate); task_lifecycle - stored/child cancel costs via carry_cost_meta; post_task_checkpoint - task_cost_finalized event via carry_cost_meta, with an explicit SCRUB set - that still pops the retired spellings (a stale legacy replica cannot - smuggle an amount past deprecated-wins at the write seam). Producers - whose data reaches the public projection are cut over and REMOVED from - the sweep allowlist: build_subagent_envelope/envelope_from_task (key and - kwarg now accounted_upper_bound_usd), the pipeline unavailable-patch, the - loop-outcome usage sub-dict. Fan-out pin upgraded per the round-2 - mandate: (a) runtime projection-boundary pins - (TestProjectionBoundaryNormalization — stored legacy row -> outbound - payload deep-scanned for alias keys; rewrite normalization; - legacy-mutator honor-then-strip) catch generic passthrough no AST scan - can see; (b) the static sweep now also treats keyword args on ANY call - as emission-shaped, and its allowlist is PER-SITE (file, alias, - enclosing scope) — a new emission in an allowlisted file fails, any - stale row fails; (c) a dedicated pin bans allowlisting outcomes.py / - subagents.py / agent_task_pipeline.py. TEST-CONTRACT DISCLOSURE (the - round-2 mandate names these as OLD-ABI contract tests): converted to - honest-name assertions — tests/test_gateway_history.py (windowed anchor - cost, terminal cost truth, override precedence, nullable bounds, - task-summary flat-field passthrough), tests/test_tasks_list_slice.py - compact-row cost clause, tests/test_cost_projection.py meta-field - derivation + stored-legacy-tolerance clauses, - tests/test_task_summary.py snapshot fixture (its legacy key was stale — - the real _pre_synthesis_usage_snapshot emits the honest name), - tests/test_headless_task_artifacts.py mirror-cost merge and - finalized-accounting clauses, tests/test_task_result_monotonic.py - kept-cost clause. NOT changed: the JS read seam keeps pair tolerance - (web mirror switch stays deferred per the ABI-3 row), and the internal - non-gateway planes (ledger rows, review/evidence receipts, evolution - state, custody settlement events, reflection/consciousness records) keep - their own spellings under anchored per-site allowlist rows. -4. ADOPTION claims (finding 4) — ABI-2/ABI-3/ABI-9 stay `done` LAWFULLY - after the fixes above; each row now names the fix-round-2 closure and the - new pins (scripts/v7next_adoption.py OK, 36 rows). The absolute ABI-9 - claim in docs/ARCHITECTURE.md:291 is restated to the true order - (validate -> swap -> attach) including the post-swap-failure disclosure, - so the "refused registration publishes nothing" clause is now exactly - true (refusal = validation failure; a post-swap attach failure is a - published-then-disposed bundle, said in the same sentence). -5. Findings 5-8 (domains manifest, git hygiene, helper extraction, new - defects) — CLOSED by the verdict itself; no action owed, nothing - changed there in this round. -6. Module-size law (found by the fix-round-2's own gate run, not by the - review; the round-1 entry-7 precedent): the ABI-2 admission routing - pushed ouroboros/gateway/tasks.py past the 1600-line hard cap, and the - ABI-3 honest-name comment pushed outcomes.py::derive_loop_outcome past - 300 lines. Resolved by extraction, no band exception, no debt entry: - the raw creation-ts sort scan + malformed-candidate admission moved to - the new ouroboros/gateway/task_list_scan.py (tasks.py 1615 -> 1563; - same objects imported back; ARCHITECTURE row added; module mapped to - D11 in scripts/v7next_domains.toml, DOMAIN_QUOTIENT_REPORT regenerated - by the official script — 488 mapped, drift none), and the loop-outcome - usage snapshot became module-level `_loop_usage_snapshot` - (derive_loop_outcome 304 -> ~292). Each extraction lands INSIDE the - commit whose growth caused it, so every first-parent tree of this - round satisfies its own manifest (no condemned intermediate commits — - the local unpushed round-2 series was arranged for this before any - push; the pushed tip aae647fb was not rewritten). -7. Serial-battery addendum (found by this round's full CI-shape battery, - serial pass): tests/test_cancel_live_kill_path.py - ::test_e2e_child_finishing_before_the_kill_keeps_its_completed_result - asserted the OLD-ABI alias on the kept row and the task_done relay — - converted to the honest name (same class as the entry-3 disclosure - list; the child writer's legacy kwarg is honored deprecated-wins, then - stripped by the write seam). - -## From the F3.1 conformance fix-round-3 (base f8e579de, 2026-08-31) - -Round-3 verdict (GPT-5.6 Sol, read-only @ f8e579de): ABI-9 and ABI-3 -NOT-CLOSED on the enumerated tails, ADOPTION/ARCHITECTURE claims dependent on -them, one NEW UI defect; ABI-2, ratchet extractions and manifest CLOSED with -no action owed. Dispositions: - -1. ABI-9 (finding 1) — FIXED as the disclosed STAGED PROTOCOL, not a false - "one atomic publication" absolute. (a) The OOP load published surfaces - and companions as TWO transactions (extension_loader - _register_out_of_process_surfaces + _spawn_out_of_process_companions), - with the second re-minting bundle.generation_digest without re-stamping - published descriptors. Both are replaced by ONE staged publication: - _stage_out_of_process_surfaces validates catalog descriptors through the - same _stage_surface_locked seam the in-process register() window uses, - and _publish_out_of_process_registration stages surfaces AND companion - spawns on one PluginAPI snapshot — one validate -> SWAP -> attach - transaction. The one structurally LATER publication that remains — - server-side companion recovery (reconcile_server_companions) onto a live - bundle — mints a fresh digest and _publish_registrations RE-STAMPS every - already-published descriptor the bundle owns in the same lock hold, so - per-surface provenance never diverges from bundle.generation_digest. - (b) The recovery failure path was a silent _abort_registration leaving - the extension half-alive; ANY failure of the shared seam now routes - through the standard dispose+unload path (unload_extension). (c) Unload - visibility: _unload_extension_locked popped the bundle and surfaces - BEFORE the bus unsubscribe and runtime-API close; the order is now - outside-in — subscription ids + the _unloading latch snapshot in ONE - registry-lock hold (no publication can interleave), bus unsubscribe, - runtime-API close, THEN bundle/surface removal, future cancel, companion - stop, module purge. RESIDUAL BY DESIGN (pinned + disclosed in - EventBus.publish's docstring): the bus COPIES subscribers under its own - lock before invoking handlers, so a publisher that copied a handler - before the unsubscribe may still invoke it after surfaces are gone; the - supported guarantee is "a publish STARTED after unsubscribe never - delivers", and the closed runtime API + _unloading latch make the late - call a host no-op. Pins: test_out_of_process_surfaces_and_companions_ - publish_as_one_transaction, test_companion_recovery_failure_unloads_ - instead_of_silent_abort, test_late_publication_restamps_already_ - published_descriptors, test_unload_closes_bus_and_runtime_visibility_ - before_surfaces_leave, test_publish_started_after_unload_never_delivers. - TEST-CONTRACT DISCLOSURE: test_spawn_out_of_process_companions_host_ - spawns_declared_name renamed to test_publish_out_of_process_registration_ - host_spawns_declared_name (the unified seam it exercises); the two - catalog-revalidation suites and the loader-extraction _STAYED list now - name the unified surface (_publish_out_of_process_registration / - _stage_out_of_process_surfaces). -2. ABI-3 (finding 3) — FIXED in depth. (a) build_subagent_envelope - normalizes the stored usage snapshot BEFORE embedding (deprecated-wins - kept; the amount fallback reads the resolved honest name). (b+c) ONE - shared normalizer cost_projection.normalize_task_result_cost_planes (top - level + subagent envelope + envelope.usage + loop_outcome.usage) serves - BOTH public_task_result and write_task_result (both merge passes) — the - sanctioned known-paths + deep-test-scan variant of the round-3 mandate: - internal evidence planes (review receipts, ledger rows) stay their own - schemas per the round-2 disposition, named per-site in the sweep - allowlist. (d) Evolution history: the update_evolution_campaign_after_ - task row now stamps accounted_upper_bound_usd (allowlist row REMOVED); - the one internal reader (Recent Campaign Cycles prompt block) resolves - the pair deprecated-wins; gateway/state._evolution_state_public converts - stored legacy rows at the /api/state projection boundary (copy-on-write - over the shared snapshot). (e) The deep-scan fixture now places the - legacy alias on the ACTUALLY SUPPORTED producer path - subagent_envelope.usage.cost_usd beside the envelope-root spelling, with - resolved-amount assertions on the public projection, the task-detail - endpoint and a new rewrite pin. (f) The sweep allowlist is COUNT- - ANCHORED per site — (file, alias, scope) -> (reason, exact count); a new - emission inside an allowlisted function breaks the anchor and fails. - (g) Own AST sweep re-run: 55 emission-shaped sites dispositioned — 1 - honest cutover (the evolution history row), 3 sites under the - events_evolution_done anchor re-classified honestly (2 internal - lifecycle/checkpoint call kwargs + 1 supervisor.jsonl observability row, - converted at the /api/logs boundary on replay), remaining 51 = internal - planes kept per-site with exact counts. -3. New defect (finding 6) — FIXED. /api/logs emits the honest name but - web/modules/log_events.js read only `cost_usd ?? cost`, so the LLM-round - money column was empty after a reload. All three read sites now resolve - through the existing SSOT JS helper accountedUpperBound() (the - resolve_cost_pair mirror, deprecated-wins) with the live-frame `cost` - spelling last — deliberately the shared precedence rule rather than a - hand-ordered honest-first list, so a diverged stored pair tells the same - story on every surface. Pinned in web/tests/cost_presentation.test.js - (backfill name, live frame, diverged pair). chat.js (BYTE_DEBT ceiling) - untouched; log_events.js stays in its band. -4. ADOPTION/ARCHITECTURE (finding 4) — restated to the post-fix truth: - ABI-9 row + docs/ARCHITECTURE.md extension_loader/extension_plugin_api - rows describe the staged protocol (single OOP transaction, recovery - restamp + unload-on-failure, outside-in unload visibility, EventBus copy - residual); ABI-3 row + the cost_projection ARCHITECTURE row describe the - shared nested-plane normalizer and the boundary conversions - (/api/logs, /api/state evolution history) with the internal-plane - residual named. scripts/v7next_adoption.py OK (36 rows). -5. Findings 2 and 5 (ABI-2, ratchet extractions/manifest) — CLOSED by the - verdict itself; nothing changed there in this round. - -## From the F3.1 conformance fix-round-4 (base 163c2765, 2026-08-31) - -Round-4 verdict (GPT-5.6 Sol, read-only @ 163c2765): ONE blocker — the ABI-9 -companion-recovery lifecycle TOCTOU (finding 1); ABI-3 and Logs UI CLOSED with -no action owed; ADOPTION/ARCHITECTURE NOT-CLOSED only as a dependent of the -race. Dispositions: - -1. ABI-9 recovery TOCTOU — FIXED as a GENERATION-BOUND protocol. The race: - ensure_companions_running snapshotted liveness/bundle, then published - WITHOUT the lifecycle lock (production re-invokes it from - extension_reconcile_queue after locked reconciliation returns); a - concurrent unload could complete in the window, after which the stale - recovery re-created an empty bundle inside _publish_registrations - (bundle-if-None branch) and started its companion — resurrecting a - companion-only bundle after disable/unload. The fix, per the pinned - protocol: (a) the recovery snapshot carries the observed - bundle.generation_digest (read in the same registry-lock hold as the - companion names); (b) the recovery publication runs UNDER the lifecycle - lock and _publish_registrations(require_live_generation=...) re-validates - under the registry lock that the observed publication is STILL live — a - vanished or reloaded bundle raises the typed ExtensionStaleRecoveryError - BEFORE any mutation (zero effects; ensure_companions_running surfaces it - as the typed action "stale_recovery_refused"), and the recovery form of - _publish_out_of_process_registration structurally REQUIRES a pre-existing - live bundle (exactly-one-of form gate: current_hash XOR - expected_generation), so recovery can never create a bundle; (c) the - failure-disposal is generation-bound: unload_extension gained - expected_generation and no-ops WITH DISCLOSURE (warning log) when the - live generation is not the one this recovery observed or itself swapped - in (_published_generation), so a failed recovery can never unload a newer - publication. Pins: test_unload_completing_between_snapshot_and_ - publication_refuses_recovery (deterministic same-thread barrier — the - unload completes between the snapshot and the publication), test_ - recovery_publication_refuses_on_generation_mismatch_without_effects, - test_generation_bound_disposal_skips_a_newer_publication, plus the two - recovery-form atomicity tests updated to the generation-bound call form. - TEST-CONTRACT DISCLOSURE (test-that-pinned-the-bug): the clause of - test_publish_out_of_process_registration_host_spawns_declared_name that - asserted the recovery-form helper accepts NO pre-existing live bundle and - spawns anyway pinned the resurrection defect itself; it is REPLACED by - test_recovery_publication_requires_a_pre_existing_live_bundle (opposite - pin: typed refusal, zero effects, nothing created), and the surviving - host-spawn/trust-boundary clauses now use the initial-load form. -2. LOW — the stale reference to a nonexistent reconcile_server_companions in - _publish_out_of_process_registration's docstring is gone; the docstring - names the real recovery caller (ensure_companions_running) and the - generation-bound contract. (The same stale name inside the round-3 ledger - section above is historical record and stays as written.) -3. ADOPTION/ARCHITECTURE — restated to the post-fix truth: the ABI-9 row and - the ARCHITECTURE extension_loader/extension_plugin_api rows now describe - recovery as generation-bound onto the still-live observed publication - (typed zero-effect refusal otherwise; generation-bound disposal), not as - an unconditional "re-publishes onto the already live bundle". -4. RATCHET-DRIVEN MOVE (disclosed): extension_loader.py sits at the pinned - <=1000-line extraction bound with 2 lines of headroom, so the fix is - funded by moving _stage_out_of_process_surfaces whole into - extension_child_catalog.py — its natural owner (it composes ONLY the - child-catalog validators + registry maps + the PluginAPI staging seam and - needs nothing from the loader); the loader re-exports it, the extraction - contract (_MOVED_OWNERS/_STAYED) and the ARCHITECTURE child-catalog row - are updated, and the leaf never imports the loader (DAG preserved). - -## From the F3.1 conformance fix-round-5 (base c26c89a3, 2026-08-31) - -Round-5 verdict (GPT-5.6 Sol, read-only @ c26c89a3): NEEDS FIXES — ONE HIGH -(the round-4 "zero effects" claim was false on the filesystem: recovery -mutated authorization state BEFORE the generation fence) plus one MEDIUM on -the strength of the round-4 test pins; verification points 2 and 5 CLOSED -(generation-bound disposal; extraction/sizes). Dispositions: - -1. HIGH — stale recovery mutated `auth_token.json` before the generation - fence: FIXED by post-fence token materialization. The defect: - `register_companion_process` called `get_skill_token()` during descriptor - build (before publication), and `mint_skill_token` WRITES - `auth_token.json` whenever the stored token is missing or its bound - content hash mismatches the live recompute; the fence sits in - `_publish_registrations`, so a recovery that lost the race to an - unload/reload holding a stale payload snapshot (an old skill root still - on disk with the pre-update content) rotated the G2-bound token file and - only THEN raised `ExtensionStaleRecoveryError` — the live G2 companion, - spawned with the current token in its env while the Host Service rereads - the file on every request (`host_service.authenticate_token_payload`), - was left permanently unauthorized. The fix separates descriptor build - (pure computation — env carries no HOST_SERVICE_TOKEN) from token - materialization: `_publish_registrations` mints the token and injects it - into every staged companion descriptor's env only inside the post-swap - attach, AFTER `require_live_generation` admitted the publication, in the - same registry-lock hold (a mint failure there routes through the - standard dispose+unload path like any attach failure). The initial-load - path is unchanged in effect — the token is still legitimately minted at - its publication and the spawned descriptors reference it — and the - runtime `get_skill_token()` API still mints on demand for a live - extension. Pin (red pre-fix on both): - test_stale_recovery_does_not_break_live_publication_authorization (the - verdict's exact repro through the REAL entry — G1 loaded from an old - root, in-window unload+reload of v2 content from a new root, stale - refusal, byte-identical token, end-to-end - HostServiceContext.authenticate_token_payload success for the G2 - spawn-env token) and the token-absence clause of - test_recovery_publication_requires_a_pre_existing_live_bundle (a - no-live-bundle refusal must not CREATE the token file). -2. MEDIUM — round-4 pin strength: (а) the unload-window interleaving test - now also asserts `auth_token.json` is byte-untouched after the stale - refusal, and the direct-call fixtures were moved off `drive_root/state` - onto the production per-skill directory - (`skill_state_dir(drive_root, name)` — the directory the Host Service - actually scans); (б) the generation-mismatch test is rebuilt through the - REAL recovery entry: `ensure_companions_running` with a deterministic - in-window unload+reload (same payload), asserting the typed refusal, the - preserved NEW generation, no recovery spawn, and the untouched token - file — no directly supplied digest remains in that test; (в) the new - live-G2-authorization test above. DISCLOSED: the byte-equality clauses - in (а) and (б) are belt-and-suspenders rather than red-pre-fix pins — - with an unchanged payload the pre-fix mint was a read (hash match, no - rotation); the red-pre-fix coverage of the HIGH lives in the two pins - named in item 1. -3. Docs — the ABI-9 row (ADOPTION_v7next.md), the ARCHITECTURE - extension_loader/extension_plugin_api rows, and the - `ExtensionStaleRecoveryError`/`mint_skill_token`/`_publish_registrations` - docstrings now state the post-fence materialization explicitly: the - round-4 "zero effects / before any mutation" wording is true only as of - this round, and the round-4 ledger section above stays as written - (append-only historical record). -4. Size pins: extension_loader.py untouched (998); extension_plugin_api.py - 998 after comment condensation (both within the <=1000 extraction pin, - 600 <= plugin API respected). - -## From the F3.1 conformance fix-round-6 (base 1aae9868, 2026-08-31) - -Round-6 verdict (GPT-5.6 Sol, read-only @ 1aae9868): NEEDS FIXES — ONE MEDIUM -(pre-fence filesystem side writes remained: the round-5 "pure computation" -claim was still false for `env_from_settings` manifests and the recovery -entry's state-dir mkdir) plus ONE LOW (a token rotation can strand an -already-running companion on its old spawn-env token); no HIGH; verification -points 3/4/5 CLOSED. Dispositions: - -1. MEDIUM — pre-fence side writes: FIXED by extending the round-5 post-fence - materialization to the WHOLE companion env. The defect: - `register_companion_process` called `_scrub_env` during descriptor build, - and for a manifest with `env_from_settings` that invokes - `load_settings()` — which creates/unlinks the settings lock file - (`config._acquire_settings_lock`) and can PERSIST a context-mode - settings migration (`config.load_settings_lock_held` → - `normalize_and_persist_context_mode_compat`) — before the generation - fence in `_publish_registrations`; additionally `ensure_companions_running` - resolved the state dir via the creating `skill_state_dir()` before the - fence. The fix: the staged spawn now carries the manifest companion spec - (`_StagedCompanionSpawn.spec`) and the descriptor is built with an EMPTY - env; `extension_child_catalog.materialize_companion_env` fills it — - settings-derived values, manifest env overlay, host bridge URL, - isolated-dep PYTHONPATH and the auth token — only inside the post-swap - attach, after `require_live_generation` admitted the publication, in the - same registry-lock hold where the state dir is now created - (`mkdir(parents=True, exist_ok=True)`) and the token is minted; the - recovery entry resolves its path via the new non-creating - `skill_state_path`. Env precedence is preserved (settings-derived base, - then manifest overlay, then reserved bridge keys). Pin: - test_stale_recovery_with_env_from_settings_has_zero_filesystem_effects — - an `env_from_settings` manifest recovery losing the race to an unload is - a typed refusal with ZERO `load_settings` calls from `_scrub_env` (the - lock-file/migration hazard) and an unchanged data-root file tree; the - same test proves the post-fence path still delivers the materialized env - (skill name, token, bridge URL) to the publication's spawns. - DISCLOSURE: the pin's semantic red-pre-fix content is the - `load_settings` tripwire (pre-fix, descriptor build called `_scrub_env`); - the test as written cannot execute verbatim on the pre-fix tree because - its interleave seam (`skill_state_path`) is introduced by the fix itself - — the round-4/5 interleave tests were migrated to the same seam. -2. LOW — stale spawn-env token after an accepted rotation: RESIDUAL BY - DESIGN, no code change (proportionality: no degradation case exists). - Preconditions analysis: `mint_skill_token` rotates ONLY when the stored - token file is missing, carries no token (corrupt), or its bound - content_hash mismatches the live recompute. The Host Service - authenticates EVERY request against the file (token equality) AND - against a freshly computed on-disk content hash - (`authenticate_token_payload` + `_assert_active_token` → - `find_skill().content_hash`), so in every rotation precondition the - already-running companion's spawn-env token was ALREADY non-authorizing - BEFORE the mint: missing/corrupt file fails the token compare; - hash-stale file fails the "token is stale" check. Rotation therefore - restores authorization for the publication's own spawns and can never - revoke a still-valid token. Residual: recovery stages only MISSING - companion names, and a supervisor auto-restart can re-spawn an old - descriptor whose publication's start then reports success without - replacing it — such a companion, de-authorized by a content change, is - not healed by companion recovery (same dead-token state as before the - recovery); the heal path is the ordinary unload/reload, which stops and - re-spawns every companion with the fresh env. -3. Docs truth: with item 1 landed, the absolute claims are now factual and - were tightened rather than weakened — - `ExtensionStaleRecoveryError`'s docstring adds "no settings read/lock or - state directory is materialized", `_publish_registrations` describes the - whole-env post-fence materialization, the ARCHITECTURE - extension_plugin_api row and the ADOPTION ABI-9 row carry the - fix-round-6 clause plus the item-2 residual disclosure. The round-5 - ledger section above stays as written (append-only historical record); - its "descriptor build stays pure" wording described the token plane only - and is superseded by this section for the settings/state-dir planes. -4. Size pins: extension_plugin_api.py 982 and extension_loader.py 1000 - (<=1000 extraction pin, 600 <= plugin API respected); the env - materialization helper and the non-creating path resolver live in - extension_child_catalog.py (222), `_StagedCompanionSpawn.spec` in - extension_registry_state.py (182). - -## From the F3.1 conformance fix-round-7 (base 267b71bf, 2026-09-01) — FINAL - -Round-7 verdict (GPT-5.6 Sol, read-only @ 267b71bf): NEEDS FIXES — ONE MEDIUM -(the round-6 zero-filesystem-effects claim is still absolute while the -liveness/grant projection legitimately reads settings/state pre-fence), ONE -LOW (a transient hash error rotates a live valid token), ONE LOW (pin blind -zones); verification points 2/3/6/8 CLOSED. This is the FINAL micro-round of -the Ф3.1 conformance cycle; the cycle is declared converged after it. -Dispositions: - -1. MEDIUM — pre-fence filesystem effects: SCOPED AS CLAIMS, deliberately NOT - fixed by rewriting the read layer. The pre-fence reads the verdict names — - `health_path` → creating `skill_state_dir` (extension_health.py / - skill_loader.py), the creating `skill_state_dir` inside - `load_skill_grants`, and `config.load_settings` (settings lock, possible - context-mode migration persist) inside `requested_core_setting_keys` — are - the RUNTIME-WIDE settings/grant read idiom, used identically by status - projections, the loader, skill exec and the UI; recovery merely calls the - same projections every other caller uses. Carving a non-creating, - non-locking read path through `skill_loader`/`config` for one caller would - fork the SSOT read layer (over-engineering for a refusal path whose reads - are idempotent infrastructure). Instead the false absolutes were removed: - `ExtensionStaleRecoveryError`'s docstring and the ADOPTION ABI-9 row now - state the exact contract — before the fence there are NO effects on - authorization/token/registries/bundles/companion-env; infrastructure - reads (settings lock inside `load_settings`, state-dir mkdir via the - grant/health projection) MAY occur, as anywhere in the runtime. The - round-6 sections above stay as written (append-only historical record); - their "zero effects hold on every filesystem plane" wording is superseded - by this scoped contract. -2. LOW — transient hash error rotates a valid token: FIXED in - `mint_skill_token`. "Could not read/compute the hash" (transient) is now - DISTINCT from "read fine and mismatched / token file missing or corrupt" - (legitimate mint/rotate): on a transient `compute_content_hash` failure - the stored token, when it parses, is returned byte-for-byte unrotated - (`auth_token.json` untouched — the running companion whose spawn env - holds it stays authorized against the file the Host Service rereads per - request); with no reusable stored token the mint fails closed with the - typed `SkillTokenHashUnavailableError` instead of minting a token bound - to an empty hash. Pins: - test_transient_hash_error_never_rotates_a_valid_token (red pre-fix: the - old code collapsed the error to content_hash="" and rotated) and - test_transient_hash_error_without_reusable_token_fails_closed (red - pre-fix structurally — the typed error class arrives with the fix). - RESIDUAL DISCLOSED (pre-existing, NOT introduced by this cycle, not fixed - here): concurrent mints (publication attach / `get_skill_token` / - process-runner child env) are read-decide-write over `auth_token.json` - with no shared lock or CAS — the last writer can supersede a token just - returned to another caller. -3. LOW — pin blind zones: FIXED by hardening - test_stale_recovery_with_env_from_settings_has_zero_filesystem_effects: - (a) the tripwire now covers BOTH seams — `skill_exec.load_settings` - (`_scrub_env`) and the direct `config.load_settings` - (`requested_core_setting_keys` and any other caller) — and measures - exactly the window "after the recovery's state-dir resolution, up to the - fence" (the counter clears where the interleave snapshot is taken), so - the legitimate pre-fence grant/liveness projection calls are not broken; - (b) `_data_root_tree` snapshots name + size + mtime_ns (an in-place - rewrite is now caught) and the settings lock file is asserted absent in - the window, with `SETTINGS_PATH` repointed test-locally so the assertion - cannot race a concurrent test process on the shared run-wide path; - (c) `EXT_DEMO_VALUE` is actually SET in the settings fixture, granted - (custom-secret key) and asserted DELIVERED in the materialized spawn env, - alongside the manifest companion env overlay (`EXT_OVERLAY`) and the - isolated-dep `PYTHONPATH` (a real `.ouroboros_env` site dir) — the - "settings-derived value silently lost" hole is closed. -4. Size pins: extension_loader.py untouched at 1000/1000; - extension_plugin_api.py 1000 (<=1000 extraction pin, 600 <= plugin API - respected); extension_child_catalog.py untouched (222). - -## From the F3.2 lane A (ResolvedModelTarget, base 3ba9f452) - -ABI-4 consumer sweep per docs/v7next/DESIGN_RESOLVED_MODEL_TARGET.md -(greenfield §6-design; zero occurrences on base — NOT a transplant). - -Seam inventory (rg over comma/at model-string parsing beside resolution -seams) and dispositions: - -| seam | prior output | consumers | migration | -| --- | --- | --- | --- | -| `model_slots.get_fallback_models` (cross-model ladder) | `list[str]` | `loop_model_call._run_cross_model_fallback_chain`; `tools/control_runtime` membership check | `provider_models.fallback_candidate_targets()` → `tuple[ResolvedModelTarget, ...]` (typed view over the ONE chain SSOT); the loop chain iterates typed candidates, `.model_id` crosses to a string only at the chat-API transport boundary | -| `review_model_routes.get_review_models` / `get_scope_review_models` | `list[str]` | reviewer slot builders (`reviewer_slots`, `structured_scope_review_slots`), review surfaces | typed views `get_review_targets` / `get_scope_review_targets` / `resolved_review_model_target` at the SAME seam; `review_model_uses_local` is applied ONCE at construction (`provider_route == "local"` ⇔ the predicate), and the slot builders read that fact off the dataclass instead of re-asking per string. MODELS UNCHANGED: purely typization, byte-identical per configuration class (structured, default panel, local-only route, exclusive-direct-provider rewrite) | -| `subagents.parse_subagent_harness` → `DelegationRoute` | typed route (already constructed at the parse seam) | `tools/delegate` run-request assembly | `DelegationRoute.resolved_model_target()` bridge; `_build_delegated_run_request` assembles harness pin, model, effort and credential pin from ONE typed target read | - -Contract facts: frozen+slots, value equality/hash, ""/0 sentinels (no -Optional/None-vs-missing), NO pricing fields; `context_window` stays 0 at -these seams (windows remain Capability Evidence's fact, fail-open). -Home: `model_slots.py` (dataclass) + `provider_models.resolve_model_target` -(constructor) — the D02-owner seam; `config.py` facade re-exports every new -name (`test_config_extraction` owner inventory extended accordingly). - -Verification hook: tests/test_resolved_model_target.py (name fixed by the -design note) — frozen-ness, value identity, construction at each seam, -consumer-sweep grep pins (no comma/at parsing in the swept consumers). -ADOPTION ABI-4 row: hook updated, status done. - -DISCLOSED RESIDUAL (typed up to the transport boundary, per the lane note — -transports NOT rebuilt): - -1. The Claudexor wire body serializes the typed target back to strings - (`model`/`effort`/`credentialProfileId`/`harnesses`) — the engine's JSON - contract; the adapter no longer re-parses a `harness[=model]` slug, the - parse seam (`parse_subagent_harness`) remains the one string reader. -2. Reviewer agent_session rows keep their OPAQUE `harness[=model]` spec - (RouteSpec/ReviewSlot vocabulary): a session spec is not an API model - destination, so it is not forced into `ResolvedModelTarget`; the shared - dataclass covers API-routed model destinations plus the delegated bridge. -3. `tools/control_runtime.py` still consumes `get_fallback_models()` as an id - membership check (no parsing, no route facts) — left on the string list - deliberately; `resolve_credentialed_model`/`vision` candidate walks are - internal to the provider seam itself. -4. `ReviewSlot`/`commit_triad_delivery` keep their existing parallel-vector - ABI (models/routes/efforts) — the sweep types the route-fact derivation, - not the review delivery contract (review models/behaviour byte-identical). - -## From the F3.2 lane B (dispatch digest read, base 3ba9f452) - -1. The digest-API inventory the seam relied on is EXACTLY as the F3.1-B - ledger promised — nothing new was built (reuse-first proven): the - per-surface `extension_generation` stamp minted at publication - (extension_plugin_api.py `_publish_registrations`) already reaches the - dispatcher through `extension_loader.get_tool()`'s descriptor copy, and - `extension_registry_state.extension_generation_digest` (re-exported by - extension_loader) already serves the registry read. The Ф3.2 delta is - confined to ouroboros/tools/extension_dispatch.py: - `_dispatch_extension_tool_result` became a stamping wrapper over the - verbatim inner dispatcher (`_dispatch_extension_tool_untagged`) plus the - `_generation_digest_for` reader (descriptor stamp first, registry reader - fallback for a descriptor predating the stamp). -2. Provenance seam REUSED, no new ledger: the digest rides the typed - `ToolResult.meta` (`extension_generation` key), which the loop already - projects into the tools.jsonl record via `_tool_result_fields` → - `tool_result_meta` (loop_tool_execution.py). `ToolResult` is frozen with - MappingProxyType meta, so the wrapper REBUILDS the result with the same - status/code/text — the model-facing projection is byte-identical. -3. Scope of the stamp, disclosed: only outcomes of a PHYSICAL dispatch - attempt carry the digest. The two pre-dispatch typed refusals — - EXTENSION_UNAVAILABLE (liveness) and SAFETY_VIOLATION (safety block) — - are excluded by `_UNDISPATCHED_CODES` and keep their exact pre-seam - shape (pin: test_unavailable_refusal_keeps_the_pre_seam_typed_shape - asserts meta == {"dynamic_provider": True}). No validation, no gate, no - behavior branch reads the digest (P5: a provenance fact, not a gate). -4. Pins (tests/test_extension_registration_atomicity.py), red-proof done by - running them against the base dispatcher: - test_dispatch_provenance_carries_the_published_generation_digest (red - pre-fix; also proves a reload's NEW publication puts the NEW digest on - the next call) and - test_dispatch_provenance_falls_back_to_the_registry_reader (red - pre-fix); the unavailable-shape pin is an invariance pin (green on both - sides by design). -5. Size pins: extension_loader.py and extension_plugin_api.py untouched at - 1000/1000; extension_child_catalog.py untouched (222); - extension_registry_state.py untouched (182). - -## From the F3.3 RC auditor (base 4fa2f01a) - -Sources of each check class of the machine-readable scope -(scripts/rc_audit.py, ABI-7b/F13), file:line of the feeder inventory at this -base, consumed READ-ONLY (reuse-first — no parallel parsers or lists): - -1. gateway-alias (5 checks) — the frozen F11 per-alias inventory - docs/v7next/ABI3_GATEWAY_ALIAS_INVENTORY.md (cost_usd/cost_usd_with_children - §1–2 at :16, telegram_chat_id §3 at :53, project_last_viewed/project_hidden - §4–5 at :76). Stored-axis tolerance kept per the inventory, so on-disk hits - (task_results alias keys, state/ui_preferences.json legacy keys) render as - NOTES, never blocking findings; the live-client half is owner attestation. -2. retired-setting — ouroboros/settings_defaults.py::RETIRED_SETTING_KEYS - (:314), imported at execution time; the ABI-5/Q10 semantics per - tests/test_abi5_q10_removals.py. fail_tasks (no install-visible key) and - until_deadline/stall_rounds_threshold (pacing knobs, not settings keys) - live in the report PROSE plane exactly as the design note requires. -3. comma-list — ouroboros/settings_defaults.py::RETIRED_COMMA_LIST_SETTING_KEYS - (:350, NEW in this commit): the ABI-10 classification INSIDE - RETIRED_SETTING_KEYS, placed at the retirement SSOT so the auditor snaps - the exact list at execution time instead of hardcoding it; subset - membership is fail-closed in build_scope() and pinned by the suite. -4. plugin-api — ouroboros/contracts/plugin_api.py: PLUGIN_API_VERSION="2.0" - (:29), LEGACY_PLUGIN_API_GENERATION="1.3" (:32, absent ≡ 1.3 by - construction), extension_new_pass_admission_error (:285) reused as THE - admission predicate; hash-bound grandfather adjudicated via - skill_review_status.skill_review_gate over the install's - state/skills//review.json (read without the creating - skill_state_dir helper — read-only guarantee). -5. schema-stamp — ouroboros/task_result_schema.py: - TASK_RESULT_SCHEMA_VERSION=1 (:34) and the PURE classifier - task_result_schema_refusal (:39) reused directly (never - load_task_result, which quarantines on read — a mutation). The Q8=B - consequence is named verbatim in the scope check, in every schema-stamp - finding, and in the owner-attestation list. - -N−1 fixture catalog (F14, real bytes): tests/fixtures/nminus1/ — -settings_v6.113.4.json and task_result_v6.113.4.json were produced by RUNNING -the v6.113.4 code itself (git archive of the tag; config.save_settings / -task_results.write_task_result in an isolated mktemp root; all secret fields -empty), telegram_SKILL_v6.113.4.md is `git show f0313064:skills/telegram/SKILL.md` -(the commit before ABI-1 added the plugin_api field). The inline N−1 byte -forms of the ABI-2 quarantine suite and the ABI-7a updater shim remain where -they are; this catalog is the one FILE-shaped N−1 store (no second catalog). - -Remaining owner attestation (F13 — printed by the auditor, never pretended -machine-checked): live custom gateway clients (send/read of the five removed -aliases), external automation treating the retired comma-list env spellings -as a settings surface, out-of-tree extension authors declaring plugin_api -"2.0" before new PASSes, reliance on fail_tasks / the removed pacing knobs, -and owner acceptance of the Q8=B quarantine consequence. - -## From the F3.3 comma-sweep (base 1bd342b1, 2026-09-01) - -1. The phase CI gate landed as tests/test_comma_list_remnant_sweep.py - (named in the ABI-10 hook column beside the F3.1 sweep): retired-key - mentions over ouroboros/ + web/ + supervisor/ snapped DYNAMICALLY from - RETIRED_COMMA_LIST_SETTING_KEYS, comma-split parsing in model/review - modules, phase-5 plumbing absence, and a retired-envs-are-ignored - runtime pin - all count-anchored per site with reasons (the - test_gateway_abi3_removals.py allowlist discipline). -2. Residual inventory matrix (remnant | live/dead | action | reason): - - review_substrate.scope_reviewer_slots route_env_key plumbing - (configured_review_routes + TRIAD/SCOPE_REVIEW_ROUTES_ENV in - review_execution.py) | DEAD post-ABI-10 | REMOVED | the only - production explicit-models caller (tools/scope_review.py:345) - overrides the route itself ("the caller's fanned-out route is - authoritative"); the models=None path reaches the env read only on a - structured-config-less install exporting a RETIRED spelling - exactly - the class ABI-10 retired. Rows built from plain model lists are now - pinned api_chat; retired-envs-are-ignored pinned in the sweep and in - test_review_agent_session_route.py (the phase-5 env-parsing clauses - were replaced, not deleted silently: test_configured_review_routes_ - parsing / test_scope_rows_carry_their_configured_routes asserted the - retired behavior). test_review_session_scope_wiring's mixed fan-out - now builds its mixed panel from a structured OUROBOROS_REVIEWER_SLOTS - payload. - - OUROBOROS_ADVISORY_REVIEW_ROUTE (preflight_review_run.py constant + - prose, claude_advisory_review.py messages) | DEAD (no os.environ read - anywhere; advisory_review_route() reads only the structured SSOT) | - prose/constant REMOVED | operator guidance now names the - OUROBOROS_REVIEWER_SLOTS advisory row; vestigial setenv/delenv lines - dropped from 4 test files (they were no-ops - nothing read the env). - - JS typedef cleanup (web/modules/api_types.js) | stale | REMOVED | the - 8 HOT-DEFERRED JSDoc lines from the ABI-3 inventory (ChatOutbound - cost_usd/cost_usd_with_children/telegram_chat_id, Photo/Video/ - DocumentOutbound telegram_chat_id, UiPreferencesResponse - project_last_viewed/project_hidden); the _abi3_deferred_js_extras - excuse set in tests/test_gateway_parity.py deleted - the browser - mirror is exact again. api_types.js only shrank (ratchet-safe); - chat.js untouched (BYTE_DEBT ceiling). node --test: 695/695 pass. - - GATEWAY_CONTRACT_VERSION carrier switch (api_types.js '6.113.4' -> - mirror of gateway.schema.GATEWAY_ABI_VERSION '7.0') | DEFERRED to the - release tact | NOT executed | it rewrites a release version carrier - and the test_gateway_parity pin that ties the JS constant to the - VERSION file - version carriers move synchronously in release - mechanics only. - - Allowlisted-with-reason remnants (live, NOT removed): settings_ - defaults retirement SSOT; the derived env plane (reviewer_slot_config - projection + review_model_routes/scope_review_budget readers); - server_runtime raw-dict retired-model default refresh; provider_ - models declared-model surface; gateway/settings changed-key warning - triggers; retirement prose in review_execution/preflight_review_run/ - web settings.js. -3. Same-commit collateral: ARCHITECTURE.md review-substrate paragraph now - states per-row delivery comes from the structured OUROBOROS_REVIEWER_ - SLOTS rows (retired phase-5 envs ignored); plan_review's - "Set OUROBOROS_REVIEW_MODELS in settings" operator error - a retired - settings key - now points at Review lanes / OUROBOROS_REVIEWER_SLOTS; - test_review_owner_facades' facade roster dropped the removed - ADVISORY_REVIEW_ROUTE_ENV re-export. - -## From the F3 adversarial fix-round (base 71e1f13f, 2026-09-01) - -Disposition of the 14 findings of the F3.2+F3.3 adversarial wave (sol), -verified against the code before fixing; every fix landed in the four -single-intent commits of this round. - -1. FIXED (HIGH, rollback fail-open). `rollback_managed_update` read the - marker with the permissive `read_update_tx()`, so a FUTURE-schema tx was - interpreted, re-phased and destructively reset. It now reads - `read_update_tx_strict()` and refuses typed on `future` BEFORE any marker - write or reset/checkout/clean (marker byte-identical, worktree and dirty - local work untouched); a corrupt-stamp marker now refuses on the empty tx - instead of being interpreted (the permissive reader returned the raw dict - for a non-integer stamp). RELEASE_INVARIANT surface - supervisor/update_merge.py: the delta is sanctioned by this fix-round and - minimal (one strict read + one typed refusal). Pin: the future-schema - suite now drives the direct rollback entry point (red pre-fix). -2. FIXED (HIGH, false grandfather PASS). rc_audit now looks review state up - by the skill DIRECTORY basename (the runtime identity, - skill_loader.load_skill) and verifies the stored PASS hash against the - runtime's own `compute_content_hash` over the CURRENT payload bytes - (read-only reuse); a mismatch is an INCOMPATIBLE stale review. Fixtures - store the real computed hash; the "a"*64 form is now the stale-hash red - fixture; a basename-vs-manifest.name divergence pin proves the lookup key. -3. FIXED (MEDIUM, exit contract). Chosen and documented in the module - docstring: an unreadable/unparseable MANDATORY source (manifest, payload - under a hash-bound PASS) is a BLOCKING `unauditable-source` finding - (exit 1, its own check id, outside the five scope classes — an - audit-integrity plane, not an ABI class); traversal OSError and - report-write OSError map to exit 2, so a bare Python exit 1 can no longer - read as "incompatibilities found". Pins on both planes. -4. FIXED WITH AN ADAPTED PIN (MEDIUM, bytecode). `sys.dont_write_bytecode = - True` is set before any runtime import. The requested "prefix in audited - root -> tree untouched" pin is PHYSICALLY unreachable for the naked - launcher mode: the interpreter writes ~40 stdlib .pyc files under - PYTHONPYCACHEPREFIX during startup, BEFORE the script's first line - (measured on this host). The landed contract, both sides pinned: a prefix - inside the audited root without startup bytecode suppression is REFUSED - loudly (exit 2, the guarantee was already violated by the invoking - environment); with PYTHONDONTWRITEBYTECODE=1 (or -B) the audit runs and - the audited tree stays byte-for-byte identical. -5. FIXED (MEDIUM, inventory parity). `_iter_skill_dirs` now yields - `skill_loader._walk_skill_packages(data_root/"skills")` — the runtime's - own discovery (hidden excluded, `.replaced-`/`.staging-`/`.tmp-` orphans - excluded, descent stops at a found package), read-only reuse instead of a - parallel-rules mirror. Pin: orphan/hidden dirs with broken manifests in an - otherwise clean install stay exit 0. -6. FIXED (MEDIUM, provenance). `sources.tree` appends `-dirty` when - `git status --porcelain --untracked-files=no` is non-empty (tracked - scope: untracked files supply no resolved classifier bytes), and - REPO_ROOT is moved to the FRONT of sys.path (an earlier checkout later in - PYTHONPATH could otherwise supply the classifiers). Pins: monkeypatched - dirty/clean `_tree_sha`, sys.path[0] identity. -7. FIXED AS DISCLOSED HONESTY, BEHAVIOR BYTE-IDENTICAL (MEDIUM, ignored - route). PROVEN OLD BEHAVIOR FIRST: on base 3ba9f452 the fallback loop - iterated `get_fallback_models` strings and the dispatch lane was the one - global USE_LOCAL_FALLBACK env flag — so per-candidate dispatch would be a - BEHAVIOR CHANGE the sweep's byte-identical contract forbids (consuming - `provider_route == "local"` per candidate would flip dispatch for a - `"(local)"-suffixed model with the flag unset). Disposition branch taken: - the ladder no longer fabricates the unconsumed fact — - `fallback_candidate_targets` leaves `provider_route` the "" sentinel, - docstrings on both sides and the loop comment state the lane contract, - and an equivalence pin fixes the loop's global-flag read. The finding's - mixed-ladder scenarios are therefore the PRE-EXISTING chain semantics, - disclosed rather than silently re-engineered inside a typing sweep. -8. FIXED (MEDIUM, ABI-4 over-claim). ADOPTION row truth-scoped: typed - consumers are the fallback ladder, the reviewer slot builders - (`resolved_review_model_target` — production consumers in - reviewer_slot_config) and the delegated lane; `get_review_targets`/ - `get_scope_review_targets` are marked typed views WITHOUT production - consumers in their docstrings (wiring a whole-list consumer is - review-surface work, not byte-identical); NAMED RESIDUAL: - plan_review_runtime, review_multi_model and the reviewer parallel vectors - keep their string ABI — their migration was NOT performed and review - surfaces were not touched. -9. FIXED (MEDIUM, digest not in tools.jsonl). The DIRECT tools.jsonl record - now carries `tool_result_meta` (bounded by the ToolResult contract: - <=32 producer keys, <=8KB, JSON-safe — no secret plane), so the ABI-9 - generation digest survives a failed `persist_call`, exactly as the - ADOPTION/ledger claims read. Consumers (memory summarizer, /api/logs - tail) read named fields from JSON lines — the key is additive. Pin: the - tools.jsonl row of a physical extension call carries - `extension_generation` with persist_call forced to fail. -10. FIXED (MEDIUM, pre-handler stamp). The stamp now keys on a POSITIVE - `physical_dispatch` meta fact set only when the handler / child process - is actually invoked, replacing the `_UNDISPATCHED_CODES` exclusion list; - the calling-convention resolution moved into its own pre-handler try. - Pins on all three pre-handler EXTENSION_ERROR paths (runner import, - disclosure gate, calling convention) plus a contrast pin that a genuine - handler exception with the same code IS stamped. -11. FIXED (LOW, fallback-reader race). The registry digest is snapshotted - BEFORE the handler call. Deterministic barrier pin: the handler itself - republishes the extension mid-call; the result carries the pre-call - digest while the live digest has moved on. -12. FIXED (MEDIUM, JS-parity hole). DocumentOutbound and - UiPreferencesResponse joined the exact field loop in - test_gateway_parity, plus explicit resurrection pins on - cost_usd/cost_usd_with_children/telegram_chat_id/project_last_viewed/ - project_hidden in BOTH mirrors; the stale ABI-3 "JS mirror switch - deferred / frozen excused set" ADOPTION claim replaced with the done - state (the cleanup landed in the F3.3 comma-sweep tact). -13. FIXED (LOW, comma-gate evasion). The model/review comma-split scan is - AST-level for Python (Attribute call `split`/`rsplit`, first positional - or `sep=` keyword constant ",", any spacing/quotes/maxsplit) with a - detector self-test on the evasion spellings; non-Python mirrors keep a - hardened textual scan (no Python AST exists for them). -14. FIXED (LOW, shrink-only). The DelegationRoute typed-target bridge - relocated to `provider_models.delegated_route_target` (the resolution- - seam owner, headroom); ouroboros/subagents.py is back at 1380 lines and - ouroboros/tools/delegate.py at 1263 — their 3ba9f452 base sizes. - Monotonicity ENFORCEMENT deliberately not built (out of scope per the - fix-round brief). - -## From the F3 adversarial fix-round 2 (base d1d131df, 2026-09-01) - -Disposition of the 8 defects the second adversarial wave (sol) left OPEN -against the round-1 dispositions above; every fix landed in the -single-intent commits of this round. - -1. FIXED (HIGH, null stamp read as legacy). `read_update_tx_strict` used a - plain `.get()`, so an explicit `_schema_version: null` was - indistinguishable from the accepted pre-7.0 UNSTAMPED form and read - `valid` — rollback would interpret and destructively act on a damaged - stamp. A dict-get sentinel now distinguishes key ABSENCE (legacy, valid) - from a stored `null` (corrupt, like every other non-integer stamp; no - writer ever stamps null). Pin: null-stamped marker reads `("corrupt", - {})` and the direct rollback entry point refuses typed — marker - byte-identical, HEAD unmoved, dirty owner work untouched; `None` joined - the invalid-stamp loop. Protected update_merge.py delta sanctioned by - this round and minimal (sentinel + docstring). -2. FIXED (HIGH, admission-state divergence). The auditor's grandfather - judgment trusted raw stored status/content_hash while the runtime admits - through `load_review_state` (findings re-aggregation + provenance - preconditions: official_hub sidecar, native_seed `.seed-origin`, - owner_attested marker). `_review_gate_for` now calls `load_review_state` - itself, with the runtime's identity inputs (directory basename, manifest - type, module-widget shape, skill_dir). Mutation-free reuse: - `load_review_state` now resolves state paths through the new - NON-CREATING `skill_state_dir_path` (writers keep the created-on-demand - `skill_state_dir`). Pin: native_seed PASS without `.seed-origin` → - INCOMPATIBLE, with the marker (native bucket, hash-exempt) → - grandfather note; both audits leave the install byte-identical. -3. FIXED (MEDIUM, both planes). (a) The audit walk no longer stands on the - runtime's fail-soft `_safe_listdir`: `_walk_skill_packages` accepts an - optional traversal reader (runtime default unchanged) and the auditor - passes a strict lister whose OSError propagates to the existing exit-2 - traversal handler — an unreadable skills subtree can no longer audit - clean. (b) `args.json.resolve()` moved under the OSError handler → exit - 2 (REPORT UNWRITABLE), never Python's bare exit 1. Pins on both. -4. FIXED (MEDIUM, fail-open provenance). `_tree_sha` with rev-parse OK but - `git status` failing/erroring returned the bare SHA as if proven clean. - Chosen and documented: the suffix `-unknown-dirty-state` (over the - conservative bare `-dirty`, so an auditor can tell proven-dirty from - could-not-check); only a zero-exit empty porcelain yields the bare SHA. - Pins: status exit 128 and status OSError. -5. FIXED (MEDIUM, pre-Popen stamp). The OOP branch stamped - `physical_dispatch` on EVERY exception from - `dispatch_extension_tool_subprocess`, though resolve/load/env/staging and - Popen itself fail BEFORE any child exists. Typed mechanism (not - text-guessing): `_run_child` stamps a positive child-spawned marker onto - every exception crossing the spawn boundary (both the on_spawn - disclosure path and the drain/poll/result protocol path); - `extension_child_was_spawned(exc)` reads it and the dispatcher's OOP - error arm keys `dispatched` on it. Pins: pre-spawn failure → no stamp; - marked post-spawn failure → stamp + digest; unit seam — Popen OSError - raises unmarked, post-spawn protocol failure raises marked. -6. FIXED (LOW, pre-snapshot gap). Descriptor and legacy-fallback digest are - now read under ONE lock hold: new combined reader - `get_tool_with_generation` in `extension_registry_state` (the lock's - home — extension_loader/plugin_api stay at their size caps); the - dispatch candidate pre-stamps the snapshot digest onto the detached - copy, so the separate registry fallback no longer runs on the live path. - Pin: republish AFTER the descriptor is taken moves the live digest on - while the dispatch result still names the snapshot generation. -7. FIXED (LOW, detector evasion). The comma-split AST detector now seeks - the separator in the first TWO positionals and every `sep=` keyword, so - the unbound forms `str.split(raw, sep=",")` / `str.split(raw, ",")` no - longer evade; self-test extended with the unbound evasions and the - `str.split(raw)` negative. Live allowlist counts unchanged. -8. FIXED (LOW, stale references). docs/ARCHITECTURE.md line 80 now names - the landed bridge `provider_models.delegated_route_target` instead of - the removed `DelegationRoute.resolved_model_target`. CORRECTION to the - ABI-4 seam-inventory table above (the `subagents.parse_subagent_harness - → DelegationRoute` row, written before the round-1 relocation): its - migration cell reads `DelegationRoute.resolved_model_target()` — the - landed form is the `provider_models.delegated_route_target(route)` - bridge (round-1 disposition 14). The table itself stays as written: - this ledger is append-only, corrections supersede in place of edits. - -## From the F3 adversarial fix-round 3 (base 4f894191, 2026-09-01) - -Disposition of the 5 defects the third adversarial wave (sol) left OPEN -against the round-2 dispositions above; every fix landed in the -single-intent commits of this round, and every pin was proven RED against -the base implementation before the fix. - -1. FIXED (HIGH, grandfather-predicate divergence). The auditor keyed the - grandfather on `skill_review_gate`'s `executable_review`, which under - the DEFAULT advisory enforcement admits a BLOCKERS verdict — while the - real PluginAPI grandfather (`plugin_api_admission_refusal_outcome`) - accepts only clean|warnings under every enforcement mode. The predicate - is now literally shared: new `review_status_grandfatherable` in - `skill_review_status` (clean|warnings only, enforcement-independent) is - consumed by BOTH the refusal path and the auditor's - `_admission_state_for`; `skill_review_gate` left the auditor entirely. - Pin: a hash-matching PASS carrying a critical blocker finding, audited - with `OUROBOROS_REVIEW_ENFORCEMENT=advisory`, reports plugin-api - INCOMPATIBLE and never the grandfather note. -2. FIXED (MEDIUM, audit identity ≠ runtime identity). The auditor bound - review state to the LEXICAL walk name while the runtime resolves the - directory first and derives state/tool identity from the sanitized - RESOLVED basename (`load_skill`), refusing identity collisions before - any review-state read. `_resolved_skill_identities` now mirrors that: - `skill_dir.resolve()` (failure → blocking unauditable-source finding), - dedup on the resolved path like the runtime inventory, identity = - `_sanitize_skill_name(resolved.name)`; two directories sanitising to - one identity emit a blocking collision finding and never reach - `load_review_state`. Pins: a symlinked skill grandfathers only on the - TARGET-basename state (link-name state → INCOMPATIBLE); a collision - pair yields the blocking finding and no plugin-api judgment. -3. FIXED (MEDIUM, mandatory-source traversal class). `task_results` - listing stood on fail-soft `Path.glob`, which on supported Python 3.10 - suppresses PermissionError — an unreadable directory audited clean. - New `_strict_json_files` (same direct-child `*.json` selection, OSError - raises to the exit-2 handler); a per-file read OSError now also - propagates (exit 2) instead of masquerading as a "malformed → - quarantine" verdict, and the `ui_preferences` reader keeps tolerating - content damage but no longer swallows read OSError. The class sweep: - settings already raised `InstallUnreadable` (exit 2), skills already - used the strict lister — task_results and ui_preferences were the - remaining fail-soft members. Pin: chmod-0 `task_results` → exit 2 - ("audit traversal failed"), never exit 0. -4. FIXED (MEDIUM, resolve-error exits). `data_root.resolve()` and the - pycache-prefix resolve ran outside any handler, and the report-path - handler caught only OSError — a 3.10 pathlib symlink loop raises - RuntimeError, giving Python's bare exit 1 ("incompatibilities found" to - automation). All three resolve points now catch (OSError, RuntimeError) - and map to exit 2 (INSTALL UNREADABLE / READ-ONLY GUARANTEE UNPROVABLE - / REPORT UNWRITABLE). Pins: RuntimeError from the data-root resolve → - exit 2; RuntimeError from the report-path resolve → exit 2. -5. FIXED (MEDIUM, spawn-marker gaps). (a) The process REGISTRATION between - Popen and the protected block could raise unmarked: the whole post-Popen - span (registration, on_spawn disclosure, protocol body) now lives in ONE - try whose handler stamps every BaseException — the separate on_spawn - cleanup arm collapsed into the shared finally (same kill/reap/unlink - semantics). (b) A cleanup failure in that finally could REPLACE a marked - in-flight exception with an unmarked one: the finally's own guard now - stamps the replacing exception too (original stays chained as context). - (c) `_mark_child_spawned` silently tolerated an unattachable marker: a - weak side-table (`_spawned_marker_fallback`, consulted by - `extension_child_was_spawned`) now records the fact for exceptions that - refuse setattr; the only unmarkable residue (no attributes AND no - weakref support) is logged, never dropped. Pins: registration exception - → stamped; cleanup exception over a marked one → stamp preserved on - both; a setattr-refusing exception → stamped via the side-table. - -## From the F3 adversarial fix-round 4 (final, base 5187fcdc) - -Disposition of the 3 defects the fourth adversarial wave (sol) raised -against the round-3 dispositions above — all MEDIUM, all fixed in this -round's single-intent commits, every pin proven RED against the base -implementation before the fix. - -1. FIXED (MEDIUM, spawn-marker fallback not identity-safe). The round-3 - WeakSet side-table depended on the exception being HASHABLE: an - unhashable exception raised TypeError on `add` (marker silently lost) - AND on the membership check — and the check runs inside the - dispatcher's except handler, so the secondary TypeError REPLACED the - original tool error; equal-but-distinct exceptions could also borrow - the marker through `__eq__`/`__hash__` (false `physical_dispatch`). - The side-table is now keyed by `id()` with a weakref finalizer purging - the entry (identity-safe, leak-free, no hashability requirement), and - `extension_child_was_spawned` is FAIL-CLOSED: any failure of the - marker read answers False — a physical call is never claimed on a - broken check and the in-flight exception is never masked (a hostile - `__getattr__` probe falls through to the side-table, which is exactly - where the marker would live for an attribute-refusing object). Pins: - unhashable post-spawn → stamped; unmarked unhashable → unstamped - without raising; equal-but-distinct twin → no false positive; - side-table entry dies with the exception; hostile exception at - dispatch level → ORIGINAL error reported unstamped, no masking - TypeError. -2. FIXED (MEDIUM, mandatory-source pre-checks fail-soft). The round-3 - strict listing/read only helped once the source was ENTERED: - `Path.is_dir()`/`is_file()` fold ELOOP and dangling symlinks into - plain False, so a symlink loop standing where `task_results` lives (or - a broken `state/ui_preferences.json` link) skipped the whole source - before the strict reader ran — a false-clean audit. New - `_stat_mandatory_source` probes with strict `os.stat`: only TRUE - absence (lstat agrees) is a legitimate skip; a loop or dangling link - raises to the exit-2 handler. Pins: task_results symlink loop → - exit 2; dangling ui_preferences symlink → exit 2; genuinely absent - sources still audit clean (contrast). -3. FIXED (MEDIUM, resolve-error class incompletely closed). Round 3 - covered the auditor's OWN three resolve points, but - `compute_content_hash` resolves manifest-DECLARED entry/script paths - unguarded (`skill_loader._add_if_confined`): a symlink loop there - raises RuntimeError on supported 3.10 — past the - `SkillPayloadUnreadable` clause and the OSError-only top handler into - Python's bare exit 1 with no report. Fixed on the AUDITOR side only - (skill_loader untouched — runtime semantics unchanged): the - compute_content_hash wrapper maps (OSError, RuntimeError) to the same - blocking unauditable-source finding as an unreadable payload - (per-skill scope, the rest of the install still gets audited — - consistent with the neighbouring SkillPayloadUnreadable and - skill-dir-resolve dispositions), and the top-level handler extends to - (OSError, RuntimeError) → exit 2 as the class backstop for every - other resolve the audit or its read-only runtime classifiers perform. - Pins: loop in a declared entry → blocking finding, report written, no - crash; RuntimeError from the audit walk → exit 2. - -CONVERGENCE. Four adversarial waves over the F3 surface: finding profile -14 → 8 → 5 → 3, severity ceiling HIGH → HIGH → MEDIUM → MEDIUM, with -every HIGH exhausted by round 3 and round 4 consisting solely of -narrowing residues of already-dispositioned classes (side-table -completeness, pre-check strictness, one more resolve seam). The wave-4 -verdict itself confirms fix claims 1–2 CLOSED and 3–5 OPEN only through -the three findings above — now fixed and pinned. Remaining review -surface is hygiene-grade; per the bounded-wave contract the adversarial -cycle is declared CONVERGED at this base. - -## From the F5 lane A (CPL-1/2, base 5187fcdc) - -1. CPL-1 LANDED. The Ф0 evidence manifest scripts/v7next_domains.toml is - promoted to the production manifest `ouroboros/domains.toml` (ships as - package data): module→domain 1:1 over all 488 tracked runtime modules, - D01–D20, [classification].proposed carried unchanged (80 rows, still - owner-review pending). New generated sections pin today's FACTUAL - dependency data as baseline: `[graph].allowed` (164 strict cross-domain - directions), `[graph].cycle_groups` (the single 20-domain strict-quotient - SCC as the ceiling; target `[]`), `[graph].lazy_only` (92 hidden-coupling - pairs), `[graph].dynamic_pairs` (empty), `[duplicates].allowed` (EMPTY — - zero cross-domain literal-copy bodies ≥10 normalized lines exist today, so - the literal-copy ban starts strict). Gate: scripts/check_domains.py - (--write regenerates the generated sections + docs/DOMAIN_MAP.md); verify: - tests/test_domain_manifest.py (completeness = red on drift, baseline - exactness, DOMAIN_MAP byte-identity, synthetic red-branch pins for every - detector). Shared core scripts/domain_graph.py extracted from the Ф0 - report generator; scripts/v7next_domain_report.py now consumes it and - stays report-only. -2. Plan §7.1 "циклы=0 на domain-нодах" honesty note: the live strict quotient - is ONE 20-domain SCC (921+ module-edge witnesses at Ф0, 164 domain pairs - now), so a flat cycles=0 assert would be red on the campaign's own tree. - Per the Ф5 baseline discipline (current reality = baseline, tightening = - separate owner decisions) the gate pins the SCC as a data ceiling: growth - (a new cycle group, a domain joining the SCC, a new direction) is red; - shrinkage must be banked by regeneration; `cycle_groups = []` is the - terminal state at which the gate becomes the literal cycles=0 check. -3. Ф0 report drift note: the Ф0 quotient report (generated at 1633b54f) - counted 163 strict domain pairs; the tree at 5187fcdc has 164 — ordinary - inter-phase drift, no population change (488 modules unchanged). The - report was regenerated on the shared core alongside the manifest move. -4. CPL-2 LANDED as three gen/verify pairs (generator - scripts/regenerate_inventories.py with --check; verify - tests/test_generated_inventories.py; staleness = red): - docs/v7next/FROZEN_CONTRACTS_INVENTORY.md (ARCHITECTURE §11.1 machine - extraction — 20 rows, all owner/anchor paths resolve), - docs/v7next/DATA_LAYOUT_INVENTORY.md (111 entries of the §1 Data-layout - tree, all probed: 91 code-ref, 17 repo paths/dirs, 3 placeholders, 0 - unresolved), docs/v7next/FACADE_INVENTORY.md (49 facades, 2175 marked - noqa:F401 re-export bindings, 121 cross-domain facade→leaf pairs). -5. PERSISTENCE_OWNERS carrier finding (CPL-2 spec said "найди фактический - носитель"): the reference tree ouroboros_v7_wip @ 9f691656 carries - docs/PERSISTENCE_OWNERS.md (hand-derived writer/reader/lifecycle rows) and - docs/FACADE_CONSUMERS.md; NEITHER exists in this tree. The factual - data-layout carrier here is the ARCHITECTURE §1 "Data layout - (`~/Ouroboros/`)" tree, and the inventory generator binds to it. Porting - the reference's full per-row writer/reader/lifecycle derivation is the - CPL-4 persistence pass, not this lane. -6. §11.1 package-coverage finding, now pinned as data: two - `ouroboros/contracts/` modules are documented in the §1 tree but have no - §11.1 frozen-table row — contracts/task_constraint.py and - contracts/skill_payload_policy.py. The inventory lists them as the exact - known gap and the verify test pins the set, so a THIRD uncovered frozen- - package module turns red even after regeneration; retiring the two-row gap - itself (writing their §11.1 rows) is an owner-visible follow-up, not - silently done here. - -## From the F4 lane 1 (system_e2e skeleton, base 5187fcdc) - -1. FIXED (HIGH, ABI-10 drift in the Ф0 harness). The keyless lane pinned the - RETIRED comma-list reviewer keys (`OUROBOROS_REVIEW_MODELS` / - `OUROBOROS_SCOPE_REVIEW_MODELS` / `OUROBOROS_SCOPE_REVIEW_MODEL`) in the - isolated settings.json; `load_settings` drops retired keys, so the review - organ fell back to the shipped OpenRouter default panel. Observed live on - the base SHA: S2's triad ledger named gemini-3.7-flash/gpt-5.6-terra/ - claude-opus-5, keyless, and commit_reviewed blocked deterministically at - scope-pack assembly ($0 spent; the keyless design failed CLOSED, which is - why Ф0's smoke run never caught it — the failure only shows in the mock - lane). `keyless_settings` now pins the STRUCTURED `OUROBOROS_REVIEWER_SLOTS` - (three api_chat triad rows + one scope row onto the stub slug), and a - default-lane pin feeds that value to the tree's own `parse_reviewer_slots`. - S2 green again (67s solo). -2. EGRESS HARDENING FIRST (plan §8: the ANTHROPIC_API_KEY hole). Two layers on - top of the Ф0 strip list: (a) a default-lane CLASS pin scans the runtime - tree (`ouroboros/**`, `supervisor/**`, `server.py`) for every - credential-shaped env key it actually reads (`os.environ[...]`/`.get`/ - `os.getenv`) and requires each to be covered by - `STRIPPED_PROVIDER_ENV_KEYS` ∪ secret-shape sanitizer ∪ - `STALE_INHERITED_ENV_KEYS` — a provider credential added upstream fails by - name; (b) scenario S3 boots a REAL server with poisoned fake credentials in - the parent env, completes a scripted task keyless, and probes - `/proc//environ` of the WHOLE live process tree (server + workers): - no planted or real credential VALUE (values, not names — a rename cannot - hide one) and no stripped provider key NAME (stub pair exempt: the server - legitimately projects the loopback-only pair from its settings) is visible - to any child. Linux-only probe by construction, skipif elsewhere. -3. NEW SURFACES on the skeleton (plan §8 first wave): S1 extended with the - WS-chat answer over the real `/ws` ingress (assistant reply frame + durable - chat.jsonl row) and port-file honesty (`state/server_port` == the port the - driver talks to); S4 — typed tools + safety: `write_file` onto - `prompts/SAFETY.md` under runtime_mode=advanced answers the typed - `CORE_PROTECTION_BLOCKED` refusal, the task still completes, and the clone - fingerprint (HEAD + porcelain + exact bytes of SAFETY.md/BIBLE.md) is - IDENTICAL before/after — zero side effects, not "still clean"; S5 — - cost-truth (ABI-3) on a live server: task detail and list projections are - deep-scanned for the retired `cost_usd[_with_children]` spellings (keys - derived from `COST_ALIAS_PAIRS`, never literals) and must be clean, the - honest `accounted_upper_bound_usd` is present at the detail top level, and - the durable stored row is honest-only at the top level (internal evidence - planes keep their own schemas by ABI-3 design, so the stored-row pin stays - top-level). -4. ReplayModel landed per the plan matrix: deterministic fixtures bound by - `(lineage, slot, attempt)` — slot = the wire's `model` field (scenarios pin - distinct stub slugs per model slot), lineage = the LAST `[E2E-LINEAGE:*]` - tag in the prompt text (default `root`), attempt = 1-based per-(lineage, - slot) ordinal of fixture-consulted calls. Review-organ/safety calls are - answered canned BEFORE the finalization check (shared `canned_review_answer` - with the scripted stub — one HTTP base class, so wire shape and window - evidence cannot drift between the two models) and never touch the fixture. - A miss answers loudly (`REPLAY_MISS`, the server cannot hang) and - `assert_consumed()` is red on ANY miss and ANY unconsumed row - (недоеденная фикстура = красный). Default-lane pins cover binding, ordinal - attempts, last-tag-wins, review-no-consume, and both red paths. -5. DEFERRED BY DESIGN (disclosed, plan §8 says later lanes): FakeClaudexorDaemon - (delegated-transport wave) and PlaywrightUIClient (gateway/UI-truth wave) - are interface STUBS in `tests/system_e2e/interfaces.py` that raise - NotImplementedError naming their lane — a default-lane pin asserts they - refuse instantiation, so nothing can silently pretend they exist. -6. LANE/MARKER DECISION: every scenario test carries `integration` AND `serial` - markers PLUS the `OUROBOROS_E2E_DEEP=mock` env gate. Both CI pytest passes - AND-exclude `integration`, the default local addopts excludes it, and the - CI-shape battery's serial pass (`-m "serial and not integration ..."`) - excludes it too — the suite cannot slow any existing lane. The manifest is - data with a two-direction gen/verify pin (manifest row without a test = red; - `test_s_*` without a manifest row = red) plus a marker-discipline pin - (scenario test without both markers = red). pyproject's `integration` - marker description and DEVELOPMENT's marker-lanes section now name the - keyless system_e2e lane; ARCHITECTURE gained the "System E2E suite" - subsection (same commit as the structural surface). -7. Suite time (this host, mock lane, serial): 23 tests in ~142s wall — inside - the plan's 10-25min PR keyless budget with room for the next scenario - waves; the default-lane pins add ~2.4s to the ordinary non-serial battery. - -## From the F5 lane B (CPL-4/7 + CPL-5 note, base 5187fcdc, 2026-09-01) - -Lane deliverables (single-intent commits on this lane): - -- CPL-7 (feat commit): skill-manifest `model_experience` prose section - (`what_model_sees` / `token_effect`; bare string = shorthand), rendered on - BOTH model-visible skill surfaces (`summarize_skills` rows → `list_skills` - JSON; installed-skills context section, bounded), preserved across clawhub - adaptation; teaching refusals — `SkillManifestError` gained a typed - `fix_hint` rendered into the message and EVERY refusal site in - `contracts/skill_manifest.py` now explains the repair. Bundled telegram + - unix_computer_use manifests carry the section (their content hash moves — - same owner-ratified class as the ABI-1 `plugin_api` field rollout). - Pins in `tests/test_skill_model_experience.py`: with-section parses and - reaches the surfaces; without-section behavior byte-identical; refusals - teach. Disclosed residual: `ExtensionRegistrationError` (registration - layer below the manifest) already carries prose guidance from ABI-1 - negotiation but was not converted to the typed fix_hint shape — separate - seam, untouched here. -- CPL-5 (docs commit): `docs/v7next/DESIGN_MODEL_VISIBLE_LOGGED.md` — the - F15-narrowed model-visible⟺logged contract (sealed model_send records at - `llm_attempt._candidate_before_dispatch`, reconstruction + byte compare on - call, typed durable mismatch facts, reverse-⟺ for model_send only, closed - exclusion enum, divergence-class canonicalization contracts incl. the - single-assembly rule for streaming and per-rung retry seals). Design only; - implementation sketch names the next lane's seams. ADOPTION row CPL-5 - stays in-progress until the reconstruction suite lands. -- CPL-4 (docs+test commit): `docs/PERSISTENCE.md` — full durable data-plane - inventory (≈60 entities; per-entity schema_version / migration / retention - / reset decisions, all local, no framework) + verify pair - `tests/test_persistence_inventory.py` (AST scan of every runtime - data-path writer; 118 distinct normalized paths, count-anchored both - directions, sentinel-guarded). - -§16 disclosure: the plan's "§16 findings" (undocumented planes, unbounded -ledgers, mismatched temp) could not be recovered from the plan, the v7 spec, -the campaign scratchpads, or the roast archives — no §16 exists in any of -them. Per the lane instruction the inventory was built from scratch by -factual scan; the classes §16 named were independently re-derived and are -covered: undocumented planes (e.g. `state/consciousness_observations.jsonl`, -`state/betterleaks/`, reader-only `state/crash_report.json`, orphan -`state/project_source_locks/`), unbounded ledgers (table below), and -temp/cache planes (`state/pycache`, `state/python-userbase`, `.staging`, -`tmp_scripts` fallback). - -### CPL-4 candidate code fixes (NOT touched in this lane — plan rule) - -Real gaps where a decision is recorded in PERSISTENCE.md but the closing code -change belongs to a later lane / post-release backlog. Each is local; none -proposes a generic framework. - -| id | entity | gap | proposed local fix | -|---|---|---|---| -| CPL4-C1 | logs/events.jsonl | unbounded, no rotation (100 MB WARN says "tracked as issue"); delegate custody replays it and the fault tail-scan reads last 4 MB | rotation with archive chain ONLY after custody readers (delegate_custody.replay, fault scan, legacy-usage import) become chain-aware; alternative: move custody rows to their own bounded store first | -| CPL4-C2 | logs/tools.jsonl | unbounded, no rotation (100 MB WARN) | reuse rotate_jsonl_log_if_needed on the supervisor tick; readers (recent-tools tail, ATIF auditors) are tail/chain-tolerant | -| CPL4-C3 | logs/supervisor.jsonl | unbounded AND no size tripwire at all | add hot-store tripwire row + same rotation | -| CPL4-C4 | logs/task_reflections.jsonl | unbounded, no tripwire; read is tail-20 | same rotation; project-scoped copies follow project retention | -| CPL4-C5 | logs/agent_stdout.log | launcher pipe-copy, unbounded, no cap | size-capped rotation in the launcher copy thread (keep N segments) | -| CPL4-C6 | state/usage_attempts.jsonl | unbounded monetary ledger; 20 MB WARN; full re-read under the monetary lock | seq-preserving compaction snapshot (settled rows folded into a stamped baseline row + archive of the raw segment) — needs its own reviewed design, monetary authority | -| CPL4-C7 | state/scheduled_tasks.json | consumed `once` receipts kept forever; `schema_version` defaulted on read, never authored on write | author the stamp in _write_scheduled_tasks; prune consumed-once receipts older than GC retention | -| CPL4-C8 | state/capability_evidence.json | TTL-expired entries never deleted; route-key growth unbounded | drop expired keys on write (same TTLs the reader applies) | -| CPL4-C9 | state/crash_report.json | reader-only orphan — the rollback writer no longer exists in this tree | either restore the writer on the rollback path or retire the reader + health line (owner decision: crash-rollback visibility) | -| CPL4-C10 | state/skills// core files | review.json/grants.json/enabled.json/review_job.json/owner_attestation.json/accepted_rebuttals.json carry no version key (ABI-2 idiom exists) | stamp `_schema_version: 1` on write; readers keep legacy-0 tolerance | -| CPL4-C11 | state/skills// lifetime | uninstall removes only deps.json; state dir + stale grants outlive the payload forever | tombstone-on-uninstall (keep grants as owner authority, mark payload-gone; GC only what the owner's uninstall names) | -| CPL4-C12 | state/skills//review_history.jsonl | unbounded per skill; load_history whole-file reads | bounded segment reads everywhere (find_history_job_bounded idiom), optional archive rotation per skill | -| CPL4-C13 | state/delegate_recovery*, delegate_supervision | one file per crashed/restarted task, never unlinked | terminal+age local sweep beside the existing startup custody sweep (fail-closed on unreadable custody, like _prune_delegated_snapshots) | -| CPL4-C14 | state/code_intel// | root-dir count unbounded; stale workspace roots never expire | age-prune roots whose inventory.json mtime exceeds GC retention (pure cache) | -| CPL4-C15 | state/extension_reconcile/failed/ | kept forever, never retried | age-prune failed markers past GC retention (events.jsonl already carries the failure) | -| CPL4-C16 | memory journals (identity_journal, knowledge_history, patterns_history) | full old+new document text per write → O(doc×edits) growth | keep full-text only for the newest N entries per journal, older entries digest-only — needs owner sign-off (cognitive provenance) | -| CPL4-C17 | knowledge_history.jsonl / knowledge_journal.jsonl appends | raw open("a") without the append sidecar lock → torn-line hazard | route through append_jsonl (same seam every other journal uses) | -| CPL4-C18 | memory/owner_mailbox/ | a task that dies off the terminal paths leaks its mailbox permanently | startup sweep: unlink mailboxes whose task is terminal per task_results (fail-closed when no result) | -| CPL4-C19 | task_results/.json | one file per task forever (lifecycle authority) | accepted-unbounded for 7.0; any prune needs an owner decision first (authority precedent: archive/ never GC'd) — named here so the decision is visible, not silently open | -| CPL4-C20 | data/tmp_scripts fallback | never swept (task-drive copies are swept transitively) | include the fallback dir in sweep_stale_temp_files scope | -| CPL4-C21 | uploads/ (+screenshots, views) | no retention of any kind; owner-explicit delete only | accepted for owner attachments; screenshots/views (agent-generated) could follow GC retention — owner decision | -| CPL4-C22 | observability retention knob | OUROBOROS_OBSERVABILITY_RETENTION_DAYS is parsed, clamped, reported — and deletes nothing (preserve-indefinitely contract) | retire the knob or make it honest (documented no-op is misleading operator surface); the preserve contract itself is accepted | -| CPL4-C23 | state/consciousness_observations.jsonl | unbounded append (render bounded last-10) | ACKed rows older than GC retention fold into an archive segment; unacknowledged rows never pruned (contract: survive restart/overflow) | - -Cross-lane note: CPL4-C1..C5 and C12 are one mechanism family (the existing -rotator + chain-aware readers) — a later lane should land them as one train, -not six bespoke rotators. C6, C9, C11, C16, C19, C21, C22 carry owner -decisions and must go to a batch before code. -## From the F6 rolling-upstream sync (upstream 8d13373b, base 5187fcdc, 2026-09-01) - -One merge (`git merge 8d13373b`, merge-base b9f7597f: 121 upstream commits, -319 files, 93 overlapping) under the standing principle **upstream = semantic -truth, campaign = structural truth**. Decision map by conflict class: - -- **Campaign-split monoliths** (loop.py, supervisor/events.py, tools/registry.py, - tools/control.py, tools/core.py, tools/shell.py, extension_loader.py, config.py, - llm.py, usage_accounting.py, delegate_custody.py, review_evidence.py, - review_substrate.py, supervisor/queue.py, supervisor/git_ops.py, server.py, - agent_task_pipeline.py, skill_review_status.py, subagent_dispatch_notes.py, - scope_review.py, claude_advisory_review.py): OUR facade form kept; every - upstream semantic delta re-seated in its owner leaf (retarget-to-owner). - Notable seats: DESIGN.md governance doc -> scope_review_pack + - preflight_review_prompt; EFFORT_SCALE `ultra` -> settings_scales + - llm_capability_policy + control_runtime (whole-call effort validation); - bounded prompt-token estimate -> llm_attempt + usage_accounting dataclass; - cached ledger read -> usage_legacy_import; sweep_orphaned_budget_fences -> - queue_snapshot restore seam; managed-update truthfulness (checked_at - discipline, availability recompute, `_public_repo_url` credential strip, - list_versions sha) -> git_ops_updates (+ facade re-export); custody - cursor/backfill passes -> server_maintenance; R5 delegated-custody - projection -> control_task_results; routing candidates reorder + durable - attachment carrier -> control_routing; owner_delivery live-first send family - -> control_runtime + core_artifacts; child-absorption hold + fresh-listing - prompts -> loop_delivery/loop_forced_finalization/loop_budget; quiz-answer - drain + extracted handle_finalize_now_entry -> loop_round_limits. -- **Double extractions — the upstream twin never lives**: - - `acceptance_dialogue.py` (853 L) -> folded into `loop_acceptance.py` - (REASON_* closed-set rows) and `loop_acceptance_review.py` (A-material - paid identity family: `acceptance_paid_identity`, - `bind_acceptance_paid_identity`, `_refuse_identical_acceptance`, - `acceptance_dialogue_history`, superseded-replay `_prior_acceptance_run`, - inconclusive/DEGRADED dialogue semantics in `_apply_task_acceptance_result`, - paid-cycles timing disclosure, UNHASHED history key). Importers (loop.py - re-export list, tests/test_acceptance_a_material.py) retargeted; file removed. - - `delivery_protocol.py` (170 L) -> folded into `loop_delivery.py` - (hold-control literals, RecursionError-degraded object parser, - `_parse_delivery_control_body` over `strip_protocol_fence` + - `extract_trailing_json_object`); file removed. - - `supervisor/chat_delivery_events.py` (150 L) -> folded into - `events_chat_delivery.py` (unified `_delivery_chat_id` incl. chat-0 media, - `send_links`/`send_quiz`, EVENT_HANDLERS merged as `**_CDE`); test importers - retargeted; file removed. `telemetry_events.py` has no campaign twin and - lands as-is; the campaign copy of `_handle_review_wave_budget_insufficient` - (events_budget) is retired for the typed telemetry registry, its - extraction-test pin removed with it. -- **Rename classes accepted** (same behavior, public name): `_deadline_expired` - -> `deadline_utils.deadline_expired` (tests/test_delegated_run_profile pins - moved); `_contract_valid_actors` -> `review_actor_aggregation - .contract_valid_actors` (review_verdict local copy deleted, re-export list + - extraction pin updated); `record_python_resolution`/`python_interpreter` -> - `process_interpreters.record_interpreter_resolution` + scoped - `interpreter_attestation` (registry_core save/restore replaced; node - post-gates seated as a registry_core helper under the 300-line function gate); - `_describe_returncode` -> `process_facts.describe_returncode` (shell_process - aliases it); `_write_verdict` -> `tools/patch_verdict.py` (upstream leaf - as-is, subagent_integration re-exports). -- **Protected surfaces as-is**: BIBLE.md (+P7 DESIGN.md), safety.py (chat.links, - node-runtime, escalate carve), docs/CHECKLISTS.md, gateway/contracts.py incl. - the `endpoint_index.py` extraction (campaign's scope-review-floor endpoint - removal re-applied there; upstream's `POST /api/decisions` kept), registry.py - deltas seated into the campaign leaf structure. -- **size_ratchet_manifest**: union-resolved, then regenerated twice via - `scripts/regenerate_size_ratchet.py` (band rationales recorded for - loop_acceptance_review, loop_delivery, tests/test_delegate_answer; - extension_plugin_api held at 1000 by moving the node argv policy beside its - PATH-prepend half in extension_child_catalog); `--check` green. To keep - tools/core.py under the 1600 giant gate the upstream-new link/quiz/escalate - spans live in `core_artifacts.py` (the D05 owner-chat delivery leaf) with - re-exports; `_run_shell` and the registry dispatch were shaved under the - 300-line function gate via shell_process/registry_core helpers. -- **Web/VERSION**: upstream web wave, vendor assets, VERSION 6.113.5 and - package data landed as-is (node --test: 812/812). - -Disclosed dispositions (contract-affecting): - -- **Upstream R5 regex fallback vs the ratified D02 typed organ**: upstream kept - `_EXIT_CODE_RE`/`_SIGNAL_RE` prose harvest as a fallback for untyped records - and pinned it in tests/test_process_signal_observability. The campaign organ - (owner-ratified D02) retired prose classification: process facts flow ONLY - from typed publications (ToolResult meta + the new thread-local - `process_facts` channel, which the loop merges with whole-family precedence). - The fallback pin was REPLACED by a campaign-contract pin (untyped legacy - prose forges no process facts); stale fallback comments corrected. This - replaces a clause that would have re-opened stdout forgery of - exit_code/signal. -- **Classification deltas A.23** (tests/test_tool_classification_differential): - BROWSER_SESSION_RETIRED ok->timeout/error, BROWSER_BACKLOG_RETIRED_SESSIONS - ok->unavailable/error (upstream #440 failure prefixes, typed here as exact - identifier codes), ESCALATE_UNAVAILABLE error->unavailable (generic marker - chain). Golden regenerated by the corpus recipe at 0f715831. The `A.23` - numbering continues the existing owner-item sequence but has NOT been through - an owner batch — see open fork Q-F6-2. -- **Cost-projection doc row**: upstream's ARCHITECTURE text still describes the - deprecated `cost_usd[_with_children]` outbound aliases; the campaign's ABI 7.0 - (ABI-3) removal is owner-ratified and its text was kept. No code conflict — - upstream never re-added the aliases. -- **test_v678_acceptance_state / test_owner_facing_honesty / test_loop_misc - writer inventory**: upstream counts assumed acceptance_dialogue.py; pins now - count the campaign leaves (19 writers incl. the A-material refusal; - expression-valued reasons resolved through loop_acceptance/_review/outcomes). -- **ABI 7.0 test fixtures**: upstream-new tests (routing_decision, - find_child_prefilter) wrote unstamped task-result rows, which campaign - readers QUARANTINE; fixtures now stamp `_schema_version` like real writers. - -Domain map (`scripts/v7next_domains.toml`): python_interpreter.py row replaced -by process_interpreters.py (D05); new rows D04 process_facts, D05 -owner_delivery, D07 delegate_registration_policy + patch_verdict, D08 -telemetry_events, D09 owner_quiz, D11 endpoint_index + routing_decision + -task_decision, D17 routing_wait, D18 node_runtime. - -Open fork questions for the owner: - -- **Q-F6-1 (D02 vs R5 fallback)**: the regex-fallback retirement above follows - the ratified D02 organ; if the owner wants upstream's fallback-for-legacy - reading preserved on THIS branch, the typed-absence contract pin and the two - comment corrections are the exact surface to revisit. -- **Q-F6-2 (A.23 numbering)**: three approved classification deltas are recorded - under a self-assigned `A.23` owner-item id (the table validator requires the - `A.` prefix); ratify or renumber in the next owner batch. -- **Q-F6-3 (owner-stop drain monkeypatch surface)**: the FINALIZE_NOW drain now - calls the upstream-extracted `supervisor.owner_stop.handle_finalize_now_entry` - directly; `loop._mark_owner_stop_control_drained` remains re-exported but the - drain path no longer reads through the loop facade. No test relied on that - monkeypatch point; flagged in case an external harness did. - -## From the F6 rolling-upstream sync #2 (upstream f3fbfdbb, base 3e4a6181) - -One merge (`git merge f3fbfdbb`, merge-base 8d13373b: 101 upstream commits, -180 files, 20 of them new, 13 overlapping campaign-touched paths) under the -standing principle **upstream = semantic truth, campaign = structural truth**. -Merge commit b9ceed6e; every conflict resolved by keeping OUR facade/leaf form -and re-seating the upstream semantic delta in the leaf that owns its span. - -Decision map by conflict class: - -- **Campaign-split monoliths** (registry.py, tools/core.py, tools/shell.py, - tools/git.py, llm.py, loop.py, loop_tool_execution.py, tool_access.py, - review_state.py, review_helpers.py, skill_review.py, headless.py, - supervisor/events.py, supervisor/workers.py, config.py, delegate_integration.py, - subagent_integration.py, and the four split test monoliths): OUR facade kept; - each upstream hunk re-seated in its owner. Notable seats: - - registry post-exec organ: the owner-state snapshot/restore was DELETED - upstream (it reverted ANY post-command difference without proving cause, and - read an OSError as "file absent" so a transient read error could unlink the - live settings.json). Its replacement — the `_owner_settings_snapshot` - baseline plus the OWNER_SETTINGS_CHANGED tripwire, which ANNOTATES and never - rolls back — lives in `registry_guard_process`, with the dispatch half - (`settings_before`, tripwires on the TOOL_ERROR path, #447 B2) in - `registry_core`. `_binding_state_drive_root` went with it as dead code. - - the read-carve's git classification now consumes the `git_shell_policy` - SSOT (`_git_subcommand_is_readonly` + `_git_output_file_args`, #447 A7); the - divergent `_READ_ONLY_GIT_SUBCOMMANDS` table is retired. - - `gh repo create/delete/auth` moved from substring matching to the - argv-positional `gh_shell_block_reason` resolver (A7). - - #447 H1 note ordering: ALL host notes (auto-route, safety warning, - light-repo, workspace-ref and settings tripwires) now TRAIL the payload, in - `tool_result._compose_execute_result`, `extension_dispatch` and the - post-exec guard. `_wrap_run_script_process_result` stops REPLACING a - successful script's payload with the undeclared-outputs nudge. - - В23=A owner-home read carve: `user_files_path_block_reason(operation=)` in - `tool_access_user_files` applies the credential-shape gate to MUTATIONS - only, delegating to the new upstream `credential_shapes` leaf; the read - egress is masked instead, in `core_file_tools` (read) and `tools/core` - (search). `_WRITE_LIKE_OPS` (H2) seats in `tool_access_types`. - - #468 shape-first reasoning pin: `transcript_has_sealed_reasoning` replaces - the model-family portability predicate in BOTH directions — - `llm_openai_compatible` (proactive dispatch pin) and `llm_fallback` - (reactive reroute) — with the pin fact staged on send success - (`llm_attempt._stage_reasoning_pin_disclosure`), carried on a ContextVar in - `llm_messages`, read into usage in `llm_openai_compatible`, and rendered by - `supervisor/events_budget`. `_pop_thread_disclosure` generalizes the - thread-local popper in `llm_capability_policy`. - - delivery control: `control_episode_seen` provenance, the - `_classify_parsed_delivery_control` classifier and - `_resolve_forced_delivery_control_body` seat in `loop_delivery`; the forced - flow (control resolved BEFORE the incomplete branch, `candidate_reason` - plumbed through `_forced_fallback_result`) in `loop_forced_finalization`. - - D4 export policy (per-member skip receipts, workspace-patch SSOT for - credential shapes) -> `shell_outputs`; A5 literal-argv DISCLOSURE (shell - operators/redirects/env refs in direct argv are notes, no longer refusals) - -> `tools/shell._literal_argv_notes`. - - patch capture: sensitive untracked files became a per-file exclusion rather - than a whole-manifest error, and PEM private-key material is detected by - CONTENT (`workspace_patch_capture`); the exclusion RENDERER seats in - `workspace_patch_rules` (the SSOT for the pure manifest rules it renders, - and the natural owner of a formatter two integration tools display). - - partial attachment staging (В25c) -> `supervisor/worker_chat_lane` beside - the already-merged `workers.py` half; `reason_kind` (H4) -> - `review_state_records`; `--untracked-files=all` -> `review_file_pack`; - the H3 module-load omission ledger -> `registry_core`. -- **Upstream twins of campaign organs — the twin never lives**: - - `tools/read_inspection.py` (verbatim extraction of the read-carve at - upstream's byte gate) folds into `registry_guard_process`, which already - owned it; the facade re-export serves every test. - - `tools/result_envelope.py` (the В12=A *minimal* typed-result variant) folds - into `tools/tool_result.py`, which is the same contract in stronger form - (status/code/meta published objects the loop reads directly). Its test was - retargeted onto the campaign organ. ONE product gap the fold surfaced is - closed with it: with a note appended, the whole text is no longer parseable - JSON, so the legacy TEXT adapter lost structured-failure detection — - `_structured_failure` now also tries the pre-note payload. - - `tools/output_export_policy.py` (extraction of the export-eligibility rules) - folds into `shell_outputs`, which already owned them. - - `delivery_protocol.py` stays folded in `loop_delivery` (sync #1 decision); - upstream's further edits to it were re-seated there. -- **Protected surfaces**: docs/CHECKLISTS.md took upstream's restructured item - 21 (executable guard-change trigger, owner-acceptance rule, standing - disclosures moved to the new `docs/CHECKLISTS_ARCHIVE.md`). - `docs/CHECKLISTS_ARCHIVE.md` is taken from upstream BYTE-IDENTICAL and the - campaign's floor correction lands as an APPENDED superseding entry — owner - batch #9 item 6=A, and the archive's own stated rule ("append-only: - corrections land as new superseding entries, not edits to old ones"). The - first pass of this sync edited the v6.80.0 entry in place; that was reverted - and re-done as the append. The entry states the ABI-5 train's own wording — - the `OUROBOROS_SCOPE_REVIEW_FLOOR` gateway surface (key, endpoint, contract - field, route, merge-skip, web client, self-lowering guards) IS removed in 7.0 - by owner Q10=A, the key retired via `RETIRED_SETTING_KEYS` — and names the one - clause of the superseded entry that narrows with it: the inverted-polarity - read-carve survives family-wide as `_owner_control_mention_blocks`, while the - floor-SPECIFIC detector went with its setting. gateway/contracts.py, - runtime_mode_policy.py and registry.py deltas landed by the leaf-seating rule - above. -- **size_ratchet_manifest**: union-resolved (mcp_client's rationale merges the - campaign typed-organ and upstream E5 reasons), then regenerated by - `scripts/regenerate_size_ratchet.py`; `--check` green. -- **Web/vendored assets**: the upstream web wave (chat.js +1379, style.css, - chat_decision.js) landed as-is; `node --test` 838/838. - -Disclosed dispositions (contract-affecting): - -- **`ambiguous_safety_wrapper` retired**: the meta recorded ambiguity about a - `---` separator the host itself inserted around the payload. With H1 the host - inserts no wrapper, so a separator in the composed text is the producer's own - markdown rule and carries no ambiguity. Which notes rode along stays disclosed - by the existing `route_note`/`safety_warning` host facts. Upstream's `notes` - LIST is deliberately NOT adopted: it carries full note text, and the campaign's - host-meta reserve is a bounded 256 bytes — a real safety warning would raise - ValueError inside composition. The note text is in the result itself. -- **`OWNER_STATE_RESTORED` kept as a legacy-only code**: no producer emits it - any more, but persisted traces from ≤6.113 still carry the marker text and - must keep classifying through `LegacyTextResultAdapter` rather than degrading - to `LEGACY_TOOL_ERROR`. Documented, not resurrected. -- **Two preflight heuristics removed (В27)** — the commit-message - version-reference guess and the tests-required predicate. Six enforcement - pins that asserted the block were inverted to assert the pass, with the - reason named in each; `test_copy_source_not_treated_as_deletion` was removed - because it pinned an effect that no longer exists. -- **Skill review judges binaries by CONTENT** (X4/В21): a renamed ELF still - hard-blocks, a text file with a scary extension no longer does, and a - non-executable non-UTF-8 file enters the pack as a typed descriptor. The - campaign's `test_skill_review_packs` pins were rewritten to the new contract - rather than kept asserting the retired "any non-UTF-8 blocks review" rule. -- **Function/band gates paid down where the change lives**, not by raising a - ceiling: `api_tasks_create` (dead pre-assignment + one duplicated statement of - the same fail-closed rationale, 299), `_execute_legacy_text` (one elif ladder - instead of a second early_error branch, 296), `subagent_integration` (the - exclusion renderer moved to its natural owner, 998). - `tests/test_services_tool_v2.py` took a band rationale. - -Domain map (`ouroboros/domains.toml`): new rows `credential_shapes.py` (D13), -`reasoning_artifacts.py` (D02), `gateway/claudexor_quota.py` (D11). - -What the merged tree's own battery then found (29 reds, all dispositioned): - -- **Three product gaps the sync opened, closed at the cause**: (a) the skill - owner-state read-carve was taught to the predicate but never passed at its - call site, so `rg review.json` stayed refused with a WRITE-named marker; - (b) a post-exec tripwire lost its typed fact whenever the producer returned - plain text — with the notes now TRAILING, the marker no longer owns line 1, - so a text-only reader could not re-derive the classification; the guard - adapts once through the ONE legacy adapter instead; (c) the reasoning-pin - ContextVar first landed in `llm_messages`, which imports `llm_attempt`, - closing an import cycle the leaf-graph test caught — it moved to - `reasoning_artifacts`, beside the fact it carries. -- **One golden case repaired rather than re-baselined**: - `openrouter.payload.or_provider_never_unpins_reasoning` held a READABLE - `reasoning: "t"`, which the shape-first classifier never pins, so accepting - the recorded flip would have left a case that no longer tested its own - contract (the owner preset "cannot lift the pin" with no pin to lift). Its - transcript now carries a sealed artifact and the pin holds. -- **One real classification delta recorded as A.24**: a structured - `{"ok": false}` answer behind an appended host note read as SUCCESS under the - retired pair, which json.loads()-ed the whole composed text. The same defect - class the 329 OSWorld rows measured, on the composition seam. -- **`echo ` stopped being a denial** in three ordering pins: - it is a pure inspection under A2, so those pins are spelled with a real write - (`cp payload.json …`) and still assert the ordering they exist for. - -Open fork questions for the owner: - -- **Q-F6b-1 (upstream `notes` meta vs the bounded host reserve)**: upstream - records every host note's TEXT in result_meta. This branch records the typed - booleans instead, because the campaign's `_MAX_HOST_META_BYTES` is 256 and a - single safety warning exceeds it — adopting the list verbatim would make - composition raise on ordinary traffic. If the owner wants the note text in - result_meta, the decision is whether to widen the host reserve (a numeric - contract change) or to store bounded note KINDS. -- **Q-F6b-2 (В23=A read carve scope)**: upstream's owner-home read carve lifts - the credential-NAME gate for the root principal on read/list/search and - relies on egress masking. The campaign inherits it unchanged, including the - disclosed residual that masking is shape-based. Re-affirm or narrow. -- **Q-F6b-3 (A5 literal-argv disclosure)**: shell operators, redirects and env - references in a direct argv array are now DISCLOSED notes rather than - refusals. This is a capability widening on the model-facing surface (commands - that used to be refused now run). Ratify, or keep the refusal for the - operator subset. - -## From the F5 lane C (CPL-3/6, base a12c873c) - -Lane deliverables (single-intent commits on this lane): - -- CPL-3 (feat commit): architecture facts with Ouroboros self-evolution as - consumer #1 — `ouroboros/code_intelligence_architecture.py`, a new D05 leaf - beside `code_intelligence.py` (the reuse-first survey found the existing - module at 801 lines against the 1000-line band floor, so the organ grew a - leaf instead of pushing the parent into the band; manifest row added, - DOMAIN_MAP regenerated, no new cross-domain direction — D05->D13 was - already in the pinned matrix). Five pure queries over data the repo - already pins, no LLM / caches / ledgers: `owner_of(path|symbol)` and - `domain_dependencies(d)` over `ouroboros/domains.toml` (symbol resolution - through the existing code inventory), `facade_consumers(sym)` over the - same noqa-F401 top-level re-export convention the generated facade - inventory pins (consumers = import statements across the manifest - population; attribute access on a plain module import is disclosed - out-of-scope), `persistence_entities_written_by(sym)` over the - `docs/PERSISTENCE.md` Path|Writer tables (module path, dotted module, or - bare writer-function name), `protected_contracts_affected(diff)` over the - `runtime_mode_policy` protected inventories plus the generated - `docs/v7next/FROZEN_CONTRACTS_INVENTORY.md` rows (unified-diff text or a - changed-path list). Suite `tests/test_architecture_facts.py`: every query - on real examples (protected diff → the contract is NAMED; facade → its - consumers; writer → its entities) plus completeness against each carrier - in both directions — every manifest module answers with exactly its - pinned domain, the per-domain edges reproduce `[graph].allowed` and - `[graph].lazy_only` exactly, the runtime facade scan equals the pinned - facade-inventory row set, the persistence parser yields one row per table - line with every exact writer span resolving, and every frozen-contract - row is reachable from its own owner file. -- CPL-3 tool-seam DECISION (lane decision, per the lane instruction): the - model consumes architecture facts through the EXISTING `query_code` tool — - a new `op=architecture` with `query=' '` — and NO new tool - enters the registry. Justification: `query_code` is the established - read-only code-intelligence seam, already carried by the main loop and - both subagent profiles and already op-vocabulary-shaped; architecture - facts are exactly its kind of answer (compact rows over repo structure). - The op serves code roots only (`active_workspace`/`system_repo`); any - other root is refused typed. Registry/tool_capabilities untouched. -- CPL-6 (test commit): `tests/test_multiprovider_conformance.py` — the - normative shared suite of the two multi-provider seams. Provider half: - parametrization DERIVES from the factual registry - (`provider_models.PROVIDER_PREFIXES` + the local lane), so a newly - registered provider without a conformance driver is structurally red; the - shared contract every lane passes: route-resolution form (required target - keys, unambiguous per-provider usage_model attribution), `(message, - usage)` shape, honest-only cost planes (`cost` always present, None when - unknown, `cost_final` never true over an estimate; the local lane's 0.0 - is its honest free contract), transport failure raises instead of - fabricating an answer, typed policy refusal is permanent by class (exactly - one physical send on EVERY lane — probed and true uniformly, incl. - anthropic/gigachat), HTTP-200 body 429 is a typed `provider_error` - rate-limit marker (lanes derived from the route's own - `supports_openrouter_extensions` fact, not a hardcoded list), - `finish_reason: null` is surfaced observably (key present, null marker) - on the choice-shaped family, caller timeout reaches the transport on - every lane (payload / request-row / client slot per transport), and one - successful send settles exactly one physical-attempt ledger row - (reserved→dispatched→settled) attributed to the right provider. - Transports are the recording fakes REUSED from - `tests/test_llm_provider_golden.py` — the golden suite characterizes each - route byte-level; this suite pins the cross-provider norm. Executor half: - parametrization derives from `subagents.SUBAGENT_EXECUTORS` (a new axis - point without an outcome row = red); the closed rule-table matrix - (requested × route state → executor/reason/blocked, reset instant riding - along whenever exhaustion is the surfaced fact), typed refusals at the - schema seam (`normalize_subagent_executor`) and the tool seam - (`delegate_start` → `subagent_selection_required`), stale stored executor - degrades to auto, a plain task is exempt from the axis, the native point - never contacts the daemon, a started harness run carries run identity - with the configured route on the wire, and the durable last-delegation - projection keeps requested vs applied facts separate. Native-side task - artifacts stay pinned by their own suites — disclosed scope, not a gap. - -### CPL-6 findings (observed, NOT fixed in this lane — plan rule) - -| id | seam | finding | proposed local fix | -|---|---|---|---| -| CPL6-F1 | local provider lane | the local lane stamps the physical-attempt LEDGER with provider=local but leaves the returned usage dict without the `provider`/`resolved_model` provenance keys every remote lane carries — downstream consumers reading usage alone cannot attribute the call; the conformance suite pins today's asymmetry via the driver's `usage_stamped=False` flag instead of hiding it | stamp `usage["provider"]="local"` / `usage["resolved_model"]="local-model"` in the local normalization path (one seam in `llm_local.py`), then drop the driver flag | -| CPL6-F2 | provider goldens | `tests/test_llm_provider_golden.py::test_golden_covers_every_declared_provider_lane` floors coverage with a HARDCODED lane set, so a new registry provider never turns the golden suite red | now structurally closed by the conformance registry pin; optionally re-derive the golden floor from `PROVIDER_PREFIXES` the same way | - -## From the F4 wave 2 (subagent/cancel/update, base a12c873c) - -Scenario wave on the lane-1 skeleton — three plan-§8 surfaces (subagent tree, -cancellation, managed-update core), five manifest rows S6-S10, all keyless -mock-lane, every scenario green on this host. - -1. SCENARIOS LANDED (tests/system_e2e/test_system_scenarios_w2.py): - - S6 subagent tree (~28s solo): a ReplayModel parent drives - schedule_subagent → wait_tasks → exact-hash tree_note - child_result_disposition → final. The child runs on its OWN stub slot - (roster row routes to `openai-compatible::mock-child`), the slot binder is - `model id × tool-bearing shape`, so the supervisor's tool-less semantic - duplicate probe (light slot) gets its own deterministic fixture ordinal - and parent/child concurrency can never mis-consume a step. Pinned: child - row lineage (parent_task_id / root_task_id / delegation_role=subagent / - depth_provenance.achieved_depth=1 / configured_subagent snapshot / - task_contract.lineage), swarm_fanout receipt in the PARENT's forked - drive, child's marker text reaching the parent verbatim through the - durable wait_tasks tool row, quiescence (child task_done strictly - precedes parent task_done; parent terminal clean, NOT - children_unabsorbed), the authoritative disposition row on - task_trees//blackboard.jsonl, root rollup keys - (accounted_upper_bound_usd_with_children on the parent's terminal event, - honest-only names both rows), and fixture integrity via assert_consumed - (the observed call pattern is EXACTLY the scripted tree — the fixture - model matched the live server first try). - - S7 cancellation single (~11s solo): typed `cancelled` durable terminal - with non-empty owed answer, honest cost plane - (accounted_upper_bound_usd + cost_accounting_status enum, no retired - aliases at top level), self-draining cancel_intents projection, forensic - requested→claimed→settled trail (source=http_single, outcome=cancelled), - terminal task_done event, and the /proc no-orphans oracle (below). - - S8 cancellation cascade (~20s solo): live child (schedule_subagent roster - row), cascade=true over the UI's endpoint; both rows cancelled, root - intent scope=cascade minted at the ingress and settled only on the - "cascade postcondition" detail, descendant carries its own - source=cascade_descendant intent naming the root, the durable - task_cancel_subtree_snapshot lists the child, both intents drained, both - task_done events written, no orphan processes. - - S9 managed update ff core (~90s solo, the expensive one — deliberately a - single test): a REAL managed install (`.git/ouroboros-managed.json` + - `managed` remote onto a LOCAL upstream one ff-commit ahead; - OUROBOROS_UPDATE_CHANNEL=development), DIRTY tree (tracked edit + - untracked file), preflight → apply(auto_merge) over the live HTTP - surface: stash-first insurance (Q1=C) carries the work, the server - re-execs, boot-finalize consumes tx + intent markers, writes - `managed_update_finalized` with head == target and - `managed_update_stash_restored` (context=boot_finalize), the worktree - lands exactly on target with the dirty work back as uncommitted content - and the durable `rescue-local-` pin present. - - S10 rollback contracts (~2s solo, subprocess driver on a second real - isolated install — the real supervisor code, no live server needed): - absent marker → typed "no pre_update_sha" refusal; explicit null stamp → - strict `corrupt`, same typed refusal, marker bytes byte-identical; - FUTURE-schema stamp → the "newer version" refusal from BOTH - rollback_managed_update and finalize_managed_update_on_boot, marker - byte-identical (left for the owner); and the restore contract — a - half-applied update (target commit + extra dirt + valid pending tx) - rolls back to a worktree whose FULL file fingerprint (sha256 of every - non-.git file) equals the pre-update snapshot, porcelain empty, failed - candidate preserved on `failed-update-`, tx cleared, durable - `managed_update_rolled_back` receipt with the exact pre_update_sha. -2. HARNESS DELTAS (tests/system_e2e/harness.py): (a) callable steps — a - ScriptedStubModel script step or ReplayModel fixture row may be - `callable(body) -> step`, deriving arguments the scenario cannot know - statically (server-minted child ids, exact result hashes) from the - transcript the model was actually shown; default-lane pins cover both. - (b) ReplayModel `model_ids=` override for compound slot binders (the - default /models advertisement derives from fixture slot names, which are - not wire model ids under a compound binder). (c) `pids_with_env_value` — - the /proc environ scan behind the no-orphans oracle: every pid carrying - the scenario's unique data root must sit INSIDE the live server tree - (`process_tree_pids`), re-polled via wait_until so a transiently exiting - worker cannot false-positive. (d) ArtifactOracle readers: - terminal_deliveries (owed-answer outbox), child_task_ids (lineage - enumeration — the parent row deliberately lists no children), - tree_blackboard. (e) The manifest gen/verify + marker pins now scan EVERY - test module of the package (wave modules stay visible to the discipline); - the shared session clone fixture moved to the package conftest.py — - scenarios that move HEAD or add remotes (S9/S10) build private clones. -3. LOCAL MANAGED REPO WITHOUT PATCHING (S9 design decision): the update path - hard-pins the managed remote's URL to the OFFICIAL github URL on every - fetch (`ensure_official_update_remote`, supervisor/git_ops_updates.py:80, - called from plan_managed_update_merge fetch=True), so a live-server local - managed repo is reached by redirecting that exact URL through standard git - `url..insteadOf` config in the ISOLATED clone — install - configuration, byte-identical runtime path (the set-url still happens, the - fetch resolves to the local mirror). The pip step of update_restart_smoke - was verified a no-op against this venv (`pip install --dry-run -r - requirements-runtime.lock` → nothing to install) and belted with - PIP_NO_INDEX=1 in the scenario env so an unexpected resolution attempt - fails loudly instead of reaching the network. -4. E2E-находки wave-2 (runtime defects/observations — NOT fixed in this lane, - per the lane rule): - - | id | surface | observation | evidence | - |---|---|---|---| - | W2-F1 | cancel owed-answer / details panel | The `cancel_receipt` block (Q5=A details-panel facts) and the outbox registration exist ONLY for tasks with chat lineage: `build_unreviewed_salvage_event` returns None for a chatless task and the owed answer degrades to the typed `terminal_delivery_handoff` row (reason=no_lineage_chat). Every API-submitted (headless) task therefore never gets the details-panel stop receipt. Contract-conformant per GR2-4, but the panel-facts gap for headless tasks may deserve an owner decision. | supervisor/cancel_publication.py:212-239; supervisor/terminal_delivery.py:1326-1327; observed live in S7 (receipt absent after 60s poll, handoff row present) | - | W2-F2 | managed update source | `managed_remote_url` from `.git/ouroboros-managed.json` is honored ONLY by launcher/colab bootstrap (launcher_bootstrap.py:280,323); every update fetch unconditionally retargets the remote to the hardcoded `OFFICIAL_UPDATE_REMOTE_URL` (git_ops_updates.py:86-90). An install bootstrapped from a fork/mirror (colab writes `managed_remote_url=source_url`) is silently retargeted to razzant/ouroboros on its first update check. Air-gapped/fork installs need git insteadOf config (as S9 does) or an owner decision to honor the meta URL. | git_ops_updates.py:80-90; colab_bootstrap.py:369; update_merge_plan.py:161-169 | - | W2-F3 | /api/state identity | `/api/state` answers `"sha": ""` and `"branch": null` on a source-mode isolated server even after a completed managed update — the state surface does not carry runtime repo identity here; identity lives in /api/health runtime_version + boot attestation. S9's restart proof therefore rests on the boot-finalize receipt (which only the restarted process can write), not on /api/state. | observed live in S9; devtools/.../server_runner.py current_sha() reads this field | - | W2-F4 | "managed" is two different predicates | server.py:144 gates the bootstrap safe_restart on the ENV flag only (`OUROBOROS_MANAGED_BY_LAUNCHER=1`), while the update surface gates on the meta file (`git_ops._is_launcher_managed_repo` accepts either). A meta-managed source install (S9's shape) gets managed UPDATES but no managed bootstrap reset — apparently intentional (source checkouts keep their tree), named here so the asymmetry is a decision, not an accident. | server.py:144,610; supervisor/git_ops.py:110-113 | - -5. LANE BUDGET: full mock lane (S1-S10 + default pins, serial) — 31 passed in - ~219s (3:38) on this host; the wave added ~78s over lane 1's ~142s, well - inside the plan's 10-25 min PR keyless budget and the wave's own 6-8 min - target. Solo timings: S6 ~28s, S7 ~11s, S8 ~20s, S9 ~21s, S10 ~2s. The - three new default-lane pins add well under 1s to the ordinary battery. Deferred to wave 3 (disclosed): carrier-conflict / - assisted-merge / crash-mid-phase update variations, chat-lineage cancel - receipt path (the outbox+receipt form of W2-F1), delegated-transport - (FakeClaudexorDaemon) and gateway/UI-truth (Playwright) waves. - -## From the external-audit correction lane (base 8827fd2c) - -Five externally-audited items, re-verified by the coordinator, fixed as five -single-intent commits on `ouroboros_v7next` (no push — P-LANE pause). Item 4 -was amended mid-lane by owner answers №8=A / №9=A (2026-09-01, relayed by the -coordinator); the amended form is what landed. - -1. **wait_tasks description vs producer (ABI-3 honesty)** — FIXED (d3db6424). - `tools/control.py` promised the model a `cost_usd` projection key while the - producer (`control_task_results` batch projection) emits - `accounted_upper_bound_usd` + `cost_final`. Description now names the - actual keys. Class sweep: no other builtin tool description and no - prompts/ text mentions the removed alias; the `_children_roster_projection` - docstring lied with the same legacy name and was aligned to the fields the - roster actually emits (`accounted_upper_bound_usd` only — no `cost_final` - there). Pins: `tests/test_cost_projection.py` - (`TestModelVisibleToolSurfaces`) — wait_tasks description carries the - actual keys and no legacy spelling, plus a class-wide registry sweep over - every builtin tool schema. -2. **write_text_atomic(fsync=True) short write** — FIXED (f772717c). The - fsync lane issued one bare `os.write` and trusted its return; a POSIX - partial write published a truncated file behind the successful atomic - rename. Both fsync lanes now share `_write_fd_fully` (the loop - `write_bytes_atomic` already had), each keeping its own open flags (no - `O_BINARY` on the text lane — historical platform newline semantics - unchanged). Pins: `tests/test_atomic_write_v639.py` — one-byte-at-a-time - `os.write` mock, both lanes, full content on disk. -3. **rc_audit: present-but-unparseable ui_preferences.json audited clean** — - FIXED (b0960407). The bare `except JSONDecodeError: return` violated the - fix-round-1 contract («a malformed mandatory source is never a clean - exit 0»); it is now a blocking `unauditable-source` finding (exit 1), same - class as an unparseable skill manifest; a read OSError still propagates to - exit 2. Class sweep of every `_audit_*` source: settings raises - InstallUnreadable (exit 2), skills raise `unauditable-source`, - task_results map parse damage to the blocking schema-stamp quarantine - finding — ui_preferences was the one surviving instance. A parsed - non-object still audits clean (it holds no keys; the legacy-key audit has - a truthful answer). Pins both ways in - `tests/test_rc_audit_fixture_suite.py`. -4. **ADOPTION_v7next.md status truth** — FIXED (1e9915ac), amended per owner: - CPL-1 stays `done` WITH SANCTION — owner №8=A (2026-09-01): the - all-20-domain strict-quotient SCC ceiling is accepted for v7.0 - (shrink-only gate, target empty; true cycles=0 = post-release campaign); - open residual disclosed: 80 `[classification].proposed` new-upstream - placements await owner review. CPL-4 `done` → `in-progress`: inventory + - verify hook done, 23 candidate code fixes (CPL4-C1..C23) deliberately not - touched; owner №9=A routes the mechanical fixes (rotation train + - retention knob + orphans) into v7.0 as a separate lane, the 7 - owner-decision rows into one pre-release batch. TRAIN-F6-8d13373b row - added (the header's promised train row for the post-cutoff upstream lane): - 121 upstream commits b9f7597f..8d13373b, merge 0aa74e9f, done, phase F6, - hook = this ledger's F6 sync section + the merge + the full batteries. - Validator: `ID_RE` already admits TRAIN- ids and phase F6; plain run OK - (37 rows); `--release` red AS EXPECTED — current count: rc=1, - 31 findings over 21 rows (18 pending + 3 in-progress status rows, 4 - pending-decision dispositions, 4 missing-hook-file + 3 prose-only-hook - findings among them). -5. **ARCHITECTURE.md stale after the F6 sync** — FIXED (b652bd15). Removed - the `/api/owner/scope-review-floor` endpoint-table row and corrected the - owner-endpoint count to four (the gateway mounts exactly - runtime-mode/auto-grant/context-mode/safety-mode); retargeted - `delivery_protocol.parse_delivery_control_body` → - `loop_delivery._parse_delivery_control_body` and - `acceptance_dialogue._set_acceptance_decision` → - `loop_acceptance._set_acceptance_decision` (both verified still - re-exported from `loop`); same-class fix in DEVELOPMENT.md's acceptance - checklist row. No `chat_delivery_events` mention existed in - ARCHITECTURE.md. `regenerate_inventories.py` reruns byte-identical (§11.1 - untouched); `check_domains` green. - -## Owner closures for the F6-sync forks (2026-09-01, batch 7 + re-ask) - -- **Q-F6-1 CLOSED (owner №1=A)**: typed process facts come only from - structured records; the upstream prose-regex fallback stays out. The owner - additionally commissioned a provenance investigation (how the fallback was - born and passed review, and whether the underlying gap — delegated runs - lacking typed exit facts — is deeper); its findings land as a separate - ledger section when verified. -- **Q-F6-2 CLOSED (owner «ок, A» on the re-asked plain-language question)**: - the `A.23` classification row is RATIFIED as-is (the three reclassifications - BROWSER_SESSION_RETIRED ok→timeout/error, BROWSER_BACKLOG_RETIRED_SESSIONS - ok→unavailable/error, ESCALATE_UNAVAILABLE error→unavailable under - upstream #440 semantics). -- **Q-F6-3 acknowledged (owner «ок»)**: the FINALIZE_NOW drain calling - `owner_stop.handle_finalize_now_entry` directly is accepted; the loop - re-export stays. -- Related batch-7 outcomes recorded for the campaign: №4=A (update remote - honors the configured source — F6-tail fix), №5=A (headless cancel - receipts — post-release), №6 confirmed (ABI-8 stays post-release backlog). -## From the F5 lane D (CPL-5 impl + small fixes, base 8827fd2c, 2026-09-01) - -1. CPL-5 LANDED (ratified note `docs/v7next/DESIGN_MODEL_VISIBLE_LOGGED.md`, - F15-narrowed to `model_send` at `llm_attempt._candidate_before_dispatch`). - Mechanics, all reuse-first (no parallel serializer, plane or scheduler): - - **Seal**: `persist_physical_candidate` now finalizes the - EXISTING manifest with a `model_send_seal` block (v1; basis - `canonical_json_v1`; `pre_redaction_sha256`/`size_bytes` = the existing - candidate digests; per-instance exclusion rows). `persist_call` gained the - one seam this needs: an optional `finalize_manifest(RedactionResult)` - callable, so the seal discloses the exact redaction instances of the CAS - write without re-running redaction. The returned `manifest_ref` is stamped - `model_send_seal_version`, which lands on the ledger row and is the - reverse-sweep join marker (legacy pre-seal rows are structurally excluded - — zero false positives by construction). `persist_physical_candidate` - itself moved whole into `model_send_seal.py` (its seal-bearing home) - because `observability.py` stood 29 lines under the 1500 band ceiling; - `observability.persist_physical_candidate` stays as the thin historical - compatibility name (llm_attempt's lazy import and existing monkeypatch - surfaces unchanged). - - **Forward, on the call**: after persist, the seam - (`model_send_seal.verify_sealed_candidate`) re-reads the manifest + CAS - blob FROM DISK, applies the same exclusion map to the wire-bound candidate - (`physical_custody_projection` → `observability_custody_projection` → - `redact_projection` → CAS-basis serialize) and compares byte-for-byte, - plus digest-compares the seal against the fresh seam identity (reused, not - recomputed). Mismatch = typed durable - `model_send_invariant_violation` fact written beside the seal - (`.model_send_violation.json`, write-once dedup latch) AND into - `logs/events.jsonl` — per the ratified narrowing this is an OBSERVABILITY - invariant: it never blocks dispatch; the pre-existing in-memory identity - refusal stays the only blocking authority. Kinds/classes: - `content_divergence/sdk_mutation` (durable claim vs wire bytes), - `reconstruction_divergence/{seal_unreadable, serializer_basis, - undisclosed_exclusion, record_unreadable, record_corrupt, redaction}`. - Facts carry digests and the first divergent offset only, never payload - bytes. - - **Closed exclusion enum** (§4): `secret_redaction` (per-instance rows from - the CAS write's RedactionRecords), `provider_native_custody` (per-item - rows named by a structural diff against the custody projector's own - output — the projector stays SSOT of what leaves the byte domain, with - `opaque_sha256` where the projection minted digests), `transport_envelope` - and `provider_side_transform` (class-level rows, always disclosed: the - SDK adds envelope below the seam on every lane and provider-side - transforms are unobservable, so an empty exclusions list would be a false - exact-reconstruction claim). An exclusion row with an unknown class is - itself a violation (`undisclosed_exclusion`). Delegated harness sessions - (`record_subscription_session`) now carry `model_send_seal: "unobserved"` - on their ledger rows; opaque SDK attempts remain disclosed by their - existing `candidate_measurement_kind: "opaque"`. - - **Note boundaries kept literally**: per-rung seals (each ladder rung = - new candidate + own seal/attempt id — pinned), versioned serializer basis - (a foreign basis is reported, never re-read), single-assembly rule stays a - design-level constraint on the response side (non-goal here; the next - round's send record covers assembly transitively). - - **Reverse sweep** (§3.3): `model_send_seal.reconcile_model_send_seals` - rides `server_maintenance._startup_custody_sweep` (no new scheduler); - bounded (2000 manifests / 50 facts per pass), fail-soft (unreadable - ledger = UNKNOWN state = whole pass skipped; the sweep only writes facts, - so there is no destructive conclusion to skip), facts `orphan_seal` / - `unlogged_attempt`, never repairs. Manifests promoted from child drives - are excluded via a new honest provenance marker - (`promoted_call_manifest: true`, stamped by `promote_call_manifest_ref`) - — their attempt rows legitimately live in the child ledger. - - **Cost, measured** (this host, isolated env, fake transport): on a - ~112 KiB canonical candidate the verification adds ~29 ms/call - (persist+dispatch path 46 → 75 ms; one read-back + one - projection+serialization, dominated by the redaction regex pass — the - same order as the persist itself, i.e. the note's budgeted "one - read-back and one projection"; sub-1% against a real multi-second - provider round-trip). Measured linear in candidate size - (~0.3 ms/KiB/projection: 111 KiB → 34 ms, 437 KiB → 152 ms, - 1.7 MiB → 523 ms), so a full-window candidate pays ~0.5 s — still small - against the provider latency of a request that size. Shape pinned by test: - verification adds ZERO raw `canonical_json_v1` serializations (seam - digests reused; 4 stays 4) and exactly ONE `redact_projection` pass - (persist 1 + verify 1 = 2 total per dispatch). - - Pins: `tests/test_model_send_seal.py` (19 tests — clean round-trip on an - ordinary call; deliberately damaged durable records: corrupted CAS blob, - tampered seal digest, dropped seal, smuggled exclusion class, foreign - basis → each a typed fact AND dispatch still settles; per-rung seals; - every enum class covered incl. custody per-item disclosure and the - delegated `unobserved` row; cost-shape; sweep: clean join, synthetic - orphan both ways, idempotent dedup, promoted-manifest skip, corrupt-ledger - skip, startup-family wiring). ADOPTION CPL-5 → done with that hook. -2. CPL6-F1 CLOSED (small fix, no owner decision): `llm_local._chat_local` now - stamps `usage["provider"]="local"` / `usage["resolved_model"]="local-model"` - symmetrically with every remote lane; the conformance driver's - `usage_stamped` escape flag is REMOVED (the provenance assertions now run - for every lane) and the golden fixture `local.dispatch.tool_call_from_text` - re-recorded via the suite's own `--write` (delta = exactly the two new - provenance keys). -3. W2-F3 CLOSED (small fix): `/api/state` no longer answers `sha:""` / - `branch:null` on source-mode installs. `gateway/state.py` resolves runtime - repo identity as: supervisor-stamped state values win (managed update/reset - provenance), else the ACTUAL git checkout at `config.REPO_DIR` read by pure - stdlib file reads on the snapshot thread (`.git` dir or worktree pointer, - `commondir`, loose then packed refs; detached HEAD = sha with honestly no - branch; unreadable layout degrades to unknown, never invented). No - subprocess on the poll path, no new dependencies, no contract change - (`branch`/`sha` fields keep their shape). Removed alongside: the dead - `st.get("current_branch", "ouroboros")` default — `load_state` always seeds - the key with None, so the "ouroboros" guess never fired and would have been - a lie where it could. Pins: `tests/test_api_state_runtime_identity.py` - (checkout reader layouts incl. linked worktree + packed refs; state-wins vs - source-mode-gap endpoint parametrization). -4. NOT touched, per the lane scope: W2-F1 (panel stop-facts for headless tasks - — product decision), W2-F2 (managed-remote repin — fork policy, owner - batch), Q-F6-* (sync forks). -## From the F4 wave 3b (claudexor transport + skills, base 8827fd2c) - -Scenario wave on the lane-1/2 skeleton — the plan-§8 delegated-transport -(+restart recovery, no-orphans) and skills-lifecycle surfaces, manifest rows -S11-S13, all keyless mock-lane, every scenario green on this host. - -1. FakeClaudexorDaemon LANDED (tests/system_e2e/interfaces.py — the wave the - lane-1 stub named): a loopback claudexord imitation serving the EXACT - client contract of ouroboros/gateways/claudexor.py, derived from the code, - not from memory: authenticated protocol-3 handshake (bearer token from the - installed descriptor; 401 otherwise), capability catalog - (`/v2/agent-capabilities` rows with accessProfilesSupported), harness rows, - fail-open empty quota envelope, Idempotency-Key'd project registry - (register idempotent per root / find / typed-404 delete), POST /v2/runs - with the ENGINE REPLAY CHECK (same key + byte-identical digest → the - ORIGINAL handle; same key + different digest → 409 idempotency_conflict; - missing key → typed 400), run detail with the summary facts the custody - settler consumes (state/model/spendUsd/spendEstimated/tokens/ - authRoute.profileId/effectiveAccess, untruncated primaryOutput), and the - cancel control verb (accepted → terminal `cancelled` on the next read). - Per-run behavior is scripted by prompt markers ([FAKE:HANG] never-terminal, - [FAKE:REFUSE] typed 400) plus the pinned ghost-profile 409; every request - is recorded (method/path/Idempotency-Key/protocol header/body) for wire - assertions. TWO load-bearing identity choices: (a) the fake reports the - TREE'S OWN claudexor_runtime_pin.json version+sha — `ensure_running` - attaches fast-path on exact pin identity, and ANY other version walks into - `runtime_manager.ensure()`, whose repair path downloads the pinned archive - (network egress the keyless lane must never take); (b) the descriptor is - installed at `/claudexor/daemon/control-api.json` — the owned - (D30) layout — so the server's default discovery prefers it with zero - monkeypatching. Default-lane contract pins drive the fake with the REAL - ClaudexorGateway (handshake, route_health == ("", ""), registry, replay, - both refusal codes, cancel), so fake↔client drift is a named failure. -2. SCENARIOS LANDED (tests/system_e2e/test_system_scenarios_w3b.py): - - S11 delegated transport (~27s solo): a scripted top-level nanny drives - delegate_start(subagent_id=cx-scout) → delegate_wait (run id parsed from - the transcript by a callable step) → two more intended starts that refuse - typed → final. Pinned: the durable custody chain in the canonical - events.jsonl — 3× START_REQUESTED (one pre-wire row per POST), exactly - one STARTED (route/model/access=readonly/mode=ask/ - selected_subagent_id=cx-scout), LEDGER_RECORDED, SETTLED - (state=succeeded, engine-reported model, cost_usd=0.0 + cost_final=true, - applied credential_profile_id) — wire Idempotency-Key == the STARTED - row's invocation_id, three DISTINCT invocation ids across the three - intended starts (fresh logical invocation per intention); the wire body - the derived SHAPE authored (authPreference=subscription, mode=ask, - access=readonly, harnesses==[route]==primaryHarness, model pin, - maxSeconds>0, non-empty host instructions, project scope; NO execution - block on a readonly run); project registration idempotency + the settled - run's owned registration retired (DELETE observed); the honest - requested-vs-applied last-delegation receipt - (state/subagent_last_delegation.json: requested_model=mock-model vs - applied_model=mock-model-echo, requested_profile="" vs applied profile); - and the transcript truth — FAKE_RUN_RESULT, cost_final, the - session_route_resolves_its_own_model capability_delta and the typed - refusal code all reached the model. The two refusals land as - delegate_run_start_failed rows with definite=true (invocation retired: - scripted 400 fake_route_refused + pinned-profile 409 - credential_profile_unknown — the strict D-U6 pin refused by the ENGINE, - $0, after route_health correctly fail-opened on the empty quota). - - S12 no-orphans restart recovery (~24s solo, Linux-only): the nanny holds - delegate_wait on a [FAKE:HANG] run (durable STARTED row + observed wire - GETs), the WHOLE server tree is SIGKILLed (hard crash, no cleanup; - /proc scan proves nothing carrying the data root survives), and a new - generation on the same clone+data root recovers custody at BOOT - (_startup_custody_sweep → reconcile_orphaned_runs with running=∅): - cancel control delivered (observed POST /v2/runs//control), - CANCEL_OUTCOME outcome=confirmed (verified terminal read-back), SETTLED - state=cancelled, RECONCILED action=cancelled — and EXACTLY ONE physical - POST /v2/runs across both generations (recovery adopts custody from the - durable rows; it never re-POSTs a run that has a STARTED row — the - late-result/custody semantics of delegate_custody_reconcile pinned as a - contract), with every pid carrying the data root inside the live gen-B - tree. - - S13 skills lifecycle E2E (~33s solo, two phases): local extension payload - (SKILL.md + plugin.py, plugin_api: "2.0", permissions tool+inject_chat, - model_experience prose) written into data/skills/external/ on a LIVE - server → POST /api/skills//review runs the REAL triad panel against - the loopback stub (≥3 skill_review-classified packets answered with the - canned all-PASS verdict over the tree's own _SKILL_REVIEW_ITEMS) → - status=clean, durable review.json carries the all-PASS findings plane - (and NO status key — this tree's derived-status contract for - verdict-bearing reviews) → POST grants {items:[inject_chat]} → - all_granted, durable grants.json → POST toggle enabled=true → - live_loaded+dispatch_live TRUE in the server process → RESTART (the - product's worker pickup point — finding W3B-F1) → a scripted task - dispatches the extension tool: durable tools.jsonl row with the result - text and the ABI-9 tool_result_meta.extension_generation digest (smoke - over the unit-pinned provenance), CPL-7 Model Experience prose observed - in a recorded agent body ("Model experience: E2E-MX-MARKER…" in the - Installed Skills context section) → disable (live_loaded/dispatch_live - FALSE) → delete (payload dir AND state/skills// both removed; listing - empty). -3. HARNESS DELTAS (tests/system_e2e/harness.py): manifest rows S11-S13; the - skill-review branch in the stub classifier — SKILL_REVIEW_MARKER ("You are - performing a SKILL review, …", pinned to ouroboros/skill_review_prompt.py - through the existing MARKER_SOURCES drift pin) classified FIRST among the - review branches (its pack embeds whole governance docs that can quote the - other markers) and answered with `skill_review_clean_text()` — an all-PASS - array derived from the tree's own `_SKILL_REVIEW_ITEMS`, verified by a - default-lane pin to aggregate to "clean" under - `aggregate_skill_review_status`. The lane-1 interface-stub pin now asserts - FakeClaudexorDaemon constructs (and releases its bound socket) while - PlaywrightUIClient still refuses. -4. E2E-находки w3b (runtime defects/observations — NOT fixed in this lane, - per the lane rule): - - | id | surface | observation | evidence | - |---|---|---|---| - | W3B-F1 | skills enable → running workers | Enable-time reconcile loads an extension ONLY in the SERVER process; task WORKERS are separate multiprocessing processes that load extensions once, at worker spawn (supervisor/worker_process.py:161 `reload_all`), and the reconcile queue (`extension_reconcile_queue`) is worker→server only — no server→worker channel exists. A skill enabled through the UI/API after boot is therefore INVISIBLE to every task until the workers respawn: the model calling the freshly enabled tool gets "Unknown tool: …" while /api/extensions truthfully reports live_loaded/dispatch_live TRUE (observed live: the S13 draft's dispatch task; generalizes the runbook's OSWorld lesson «сервер грузит расширения только на reload_all»). The UI likely masks this for chat turns served by the server process, but queued tasks run in workers. Candidate fix classes: a server→worker reconcile signal on the existing queue idiom, or a worker-side staleness probe at toolset materialization. | worker_process.py:161; extension_loader._request_server_reconcile_if_worker (worker→server only); registry_core extension discovery reads the process-local `_ext_tools`; S13 first-draft trace: tools.jsonl "Unknown tool: ext_11_r_e2e_probe_echo" with the base-tool Available list | - | W3B-F2 | skill review durable verdict | `SkillReviewState.to_dict` persists a `status` key ONLY for pending/no-verdict reviews; a verdict-bearing review persists the findings plane alone and status re-derives on load. Contract-conformant (the load side re-aggregates), named here because any external reader of `state/skills//review.json` that greps `status` will misread a CLEAN review as absent. | ouroboros/skill_loader.py `SkillReviewState.to_dict` (has_review_verdicts branch); observed live in S13 | - | W3B-F3 | new-extension review admission | A `type: extension` payload WITHOUT `plugin_api` is refused a NEW review PASS by the ABI-1 admission gate (typed plugin_api_admission FAIL, $0, review stays pending) — working as designed (grandfather covers only existing hash-bound PASSes), noted as the one non-obvious install-time requirement for local skill authors: a fresh extension must declare `plugin_api: "2.0"`. | ouroboros/contracts/plugin_api.py:285 extension_new_pass_admission_error; observed live in S13's first draft | - -5. LANE BUDGET: full mock lane (S1-S13 + default pins, serial) — 37 passed in - ~320s (5:20) on this host; the wave added ~100s over wave 2's ~219s, inside - the plan's 10-25 min PR keyless budget. Solo timings: S11 ~27s, S12 ~24s, - S13 ~33s. The new default-lane pins (fake-daemon contract ×2 + skill-review - verdict) add ~2s to the ordinary battery (loopback HTTP, no server). - Deferred (disclosed): the gateway/UI-truth (Playwright) wave; delegated - MUTATING-run scenarios (snapshot provisioning + integrate_delegated_patch - + containment evidence — needs the fake to serve attempt.yaml applied-fact - artifacts); delegate_answer/waiting_on_user interactive flow; the - carrier-conflict/assisted-merge/crash-mid-phase update variations carried - from wave 2. S-id note for the integrator: a parallel Ф4 wave (gateway/UI - truth) may also claim S11+; renumber ONE side's manifest rows at - integration if both landed — the gen/verify pins make a collision loud. -## From the F4 wave 3a (review/acceptance, base 8827fd2c) - -Scenario wave on the lane-1/2 skeleton — the plan-§8 review surfaces (plan -review; commit triad+scope in BOTH enforcement classes with stale-rejection; -acceptance loop), four manifest rows S11-S14, all keyless mock-lane, every -scenario green on this host. - -1. SCENARIOS LANDED (tests/system_e2e/test_system_scenarios_w3a.py): - - S11 plan review (~22s solo + ~21s cap variant): a scripted task drives - `plan_task` through a real REVISE→ACCEPT cycle — cycle 1: every triad - slot (plan review rides the configured triad rows) returns one blocking - finding with `breaks: goal` → aggregate REVISE_PLAN, open; cycle 2: a - CHANGED spec (new fingerprint, new paid cycle) → all-clean → GREEN, - closed. Durable chronicle asserted honest: `plan_review_state` on the - stored task row (`cycles_paid == 2`, last wave GREEN/closed) plus the - immutable per-wave artifacts (`task_results/artifacts//` - `plan-review-wave-*.json` — aggregates exactly [REVISE_PLAN, GREEN], the - scripted finding text preserved verbatim). The cap variant proves the - shared owner ceiling (`OUROBOROS_REVIEW_MAX_CYCLES`, shipped default 2) - end-to-end: two paid waves (6 slot calls total), then a THIRD changed - envelope answered with the typed `PLAN_REVIEW_CYCLES_EXHAUSTED` refusal - at $0 (no reviewer called, stub count still 6), - `current_attempt.status == "cycles_exhausted"` stamped, and the durable - `review_cycles_exhausted` escalation event (surface=plan_review, - cycles_paid=2, cap=2) — which lands in the SERVER-level events.jsonl, - not the task drive (see W3A-F4). - - S12 commit triad+scope, ADVISORY class (~25s in-lane): the SAME red - triad verdict that blocks S13 is waved through — the doc-only commit - LANDS, and the wave-through is loud and durable (BIBLE P3): typed - `review_advisory_override` event (block_reason=critical_findings), the - persistent `state/advisory_overrides.json` counter, and the verbatim - verdicts on the commit-attempt ledger row. Both organs dispatched - (3 triad + 1 scope stub calls). - - S13 commit triad+scope, BLOCKING class (~30s in-lane): red critical - triad FAIL → `REVIEW_BLOCKED`, repo HEAD does not move; a BYTE-IDENTICAL - resubmission → the typed `IDENTICAL_DIFF_REFUSED` free refusal (verdict - streak, $0 — no triad calls consumed); a FIXED diff → clean verdicts → - the commit lands exactly once (HEAD parent == pre-task HEAD). Durable: - the verdict-blocked attempt row (block_reason=critical_findings), 6 - triad + 2 scope calls total (none for the identical resubmit). - - S13 freshness stale-rejection (~50s in-lane, private clone): pins the - tree's ACTUAL freshness mechanics, both layers, live: - (a) advisory freshness — a REAL fresh `preflight_review` verdict (the - advisory row of `OUROBOROS_REVIEWER_SLOTS` pinned onto the stub; the - native inspection episode answers `[]`+NO_FINDINGS → status "fresh"), - then a worktree edit (`invalidate_advisory_after_mutation` via - write_file), then `commit_reviewed` WITHOUT the audited skip → - `ADVISORY_PRE_REVIEW_REQUIRED` naming the worktree edit, $0 (no - triad/scope dispatched), the fresh run demoted to status="stale" on the - durable ledger; - (b) post-verdict revalidation — with the advisory bypassed, a - `ReviewScript` CALLABLE hooked on the scope call stages NEW bytes into - the clone's index mid-wave (after the pre-dispatch - `_fingerprint_staged_diff`, before settlement) and returns an ALL-CLEAN - verdict; the commit is still refused: `REVIEW_REVALIDATION_FAILED`, - attempt row block_reason=revalidation_failed with - fingerprint_status="mismatch", typed - `reviewed_attempt_revalidation_failed` event — a verdict for other - bytes is never carried forward. HEAD never moves in this scenario. - - S14 acceptance loop, required+blocking (~25s + ~26s variants): the - terminal runs the REAL acceptance dialogue keyless (acceptance panels - ride the triad rows; the clean accept cites `verification_summary` — a - top-level packet section `build_task_acceptance_evidence` writes - host-attested unconditionally, so the supported criterion's evidence_ref - resolves under the exact-match vocabulary). Reject→rework→accept: panel - 1 FAIL (well-formed: tier best_effort + coach + typed finding + - criteria), improvement note fed back, the agent reworks, panel 2 accepts - clean — `acceptance_decision.status == "accepted"`, aggregate signals - PASS-only, and the durable wallet - (`task_acceptance_review_accounting.claims_by_binding`) carries TWO - claims: both candidate identities were separately paid. Free-replay - variant: a rework that changes NOTHING is refused for FREE — one paid - panel only (3 acceptance stub calls total), ONE wallet claim, - `acceptance_decision {status: finalized_unaccepted, reason: - identical_acceptance_refused}` — the keyless instance of the $0-refusal - class. -2. HARNESS DELTAS (tests/system_e2e/harness.py): - (a) two new prompt markers with source pins — `PLAN_REVIEW_MARKER` - (tools/plan_packet.py system-prompt head) and `NATIVE_EPISODE_MARKER` - (review_native_episode.py; deliberately the FIRST source line of the - two-literal sentence, because the marker-pin test greps the source file - where the concatenation does not exist); `classify_call` now returns - `plan_review` / `advisory_review` / `native_episode` (the native episode - is a TOOL-BEARING review call — unclassified it would eat agent script - steps; the surface is parsed off the episode's own `Surface:` line); - `REVIEW_KINDS` is the closed kind set. - (b) canned clean answers for the new kinds are the shared - `[]`+NO_FINDINGS shape — verified-clean under BOTH real parsers - (plan_spec.parse_findings; the advisory clean predicate shared with - triad_review.empty_array_is_verified_clean), pinned in the default lane. - (c) `ReviewScript` — ordered per-kind verdict queues for review-organ - calls: each call of a kind consumes one entry (str content / dict message - / CALLABLE over the request body — the S13 freshness hook both mutates - the staged tree and returns the verdict), an exhausted or absent queue - falls back to canned all-clean, `assert_consumed()` is the integrity - gate, and review calls still never consume agent script steps (default- - lane pins in both directions). `ScriptedStubModel(review_script=...)` → - `scripted_completion(..., review_next=...)`, consulted ONLY for - `REVIEW_KINDS`. - (d) `keyless_reviewer_slots(advisory=True)` — the ONE optional advisory - row pinned onto the stub slug, so the advisory pre-review runs its real - bounded native inspection episode keyless instead of degrading to the - audited-bypass compensation; the default form stays byte-identical. -3. E2E-находки wave-3a (runtime defects/observations — NOT fixed in this - lane, per the lane rule): - - | id | surface | observation | evidence | - |---|---|---|---| - | W3A-F1 | advisory admission vs commit gate (P9 scope) | `release_metadata_preflight` hard-blocks ANY changed set without VERSION in scope — including doc-only diffs, which the commit gate deliberately exempts (`_diff_is_doc_only` carve, applied only to the tests preflight; the commit path runs no release-metadata preflight). Net effect on every install: a doc-only change can NEVER obtain a fresh advisory verdict — the standard preflight_review→commit_reviewed flow structurally degrades to the audited bypass for doc-only work. S13b reaches a real verdict by naming the UNCHANGED `VERSION` in `paths` (satisfies the scope check; carriers already consistent). Owner decision candidates: a doc-only carve in the admission, or an explicit typed "advisory not applicable to doc-only" outcome instead of PREFLIGHT_BLOCKED. | ouroboros/commit_admission.py:90-99 vs ouroboros/tools/git.py:614; observed live (doc-only diff → PREFLIGHT_BLOCKED naming BIBLE P9) | - | W3A-F2 | tree state: README Version History over its own P9 limit | The checked-in README on this base carries 6 patch rows against the limit of 5 (`check_history_limit`), so EVERY VERSION-in-scope advisory admission on this tree is deterministically blocked before any review. The next release touching VERSION must trim the oldest patch entry anyway; until then advisory verdicts are unreachable on this checkout without the S13b fixture repair (the scenario trims the oldest patch row in its private clone and commits it). | ouroboros/tools/release_sync.py:576-605; README.md Version History (6.113.5..6.113.1 + 6.110.1); observed live (PREFLIGHT_BLOCKED "6 patch rows (limit 5)") | - | W3A-F3 | advisory stale mark transience | `last_stale_from_edit_ts` is a transient repo-scoped mark: it is cleared once a LATER advisory run row lands (including the audited-bypass row a subsequent `skip_advisory_review=True` commit records), so a post-hoc reader cannot rely on it. The durable evidence of edit-staleness is the demoted run row (status="stale") plus the refusal text naming the edit; the scenario pins those. | ouroboros/review_state_model.py add_run/mark_all_stale_except; observed live in S13b (mark set at refusal time, empty in the final state) | - | W3A-F4 | plan-review escalation event routing | `emit_review_cycles_exhausted` for surface=plan_review lands in the SERVER-level `logs/events.jsonl`, while the rest of the task's plan-review evidence (tool rows, wave artifacts via the task artifact store) is task-scoped — a reader scanning only the task drive misses the escalation row. Cheap disclosure, not necessarily a defect. | ouroboros/review_cycles.py:171-199; observed live in S11-cap | - -4. DEFERRED (disclosed, wave-4 remainder): self-evolution absorb with - kill-mid-absorb restart recovery (the instruction's optional item 4 — the - evolution-campaign fixture is a full wave of its own), plus the carried - wave-2 remainder (carrier-conflict / assisted-merge / crash-mid-phase - update variations, chat-lineage cancel receipt, delegated-transport - FakeClaudexorDaemon, gateway/UI-truth Playwright, skills lifecycle). -5. LANE BUDGET: full mock lane (S1-S14 + default pins, serial) — 43 passed - in ~610s (10:09) on this host, inside the plan's 10-25 min PR keyless - budget; wave 3a added 12 tests / ~6.5 min over wave 2's ~219s. The five - new default-lane pins add well under 1s to the ordinary battery. - -## Integration note for the F4 wave-3a section above (2026-09-01) - -The wave-3a scenarios were RENUMBERED at integration: the parallel wave-3b -lane claimed S11-S13 first, so wave-3a's rows landed as S14 (plan review), -S15 (advisory class), S16 (blocking class + freshness), S17 (acceptance -loop). The wave-3a section text above says "S11-S14" — read it through this -mapping; test names and the scenario manifest carry the final numbers. -## From the hermetic class-fix lane (base 8827fd2c, 2026-09-01) - -Issue #455 (confirmed by a LIVE repeat 2026-09-01 14:55 UTC): with all four -OUROBOROS_* env variables pointing at a temp root, update-merge tests still -wrote `managed_update_stash_restored (context=test)` / -`managed_update_wave_floor_refused` into the LIVE -`~/ouro/data/logs/supervisor.jsonl` (455 `context=test` lines accumulated, 56 -on 2026-09-01 alone). Root: `supervisor/git_ops.py` bound -`REPO_DIR`/`DRIVE_ROOT` at import to hardcoded `Path.home()/"Ouroboros"/...` -— the one process-global root pair that NEVER read the env — and -`update_merge._log_supervisor` (update_merge.py:742), the update_candidate -stash writers (update_candidate.py:554-576) and the git_ops_reset `_go()` -writers all resolve supervisor.jsonl through it; `tests/conftest.py -_bind_pytest_runtime_roots` rebound config/state/queue/workers but not -`git_ops.DRIVE_ROOT`. - -1. WRITER MAP (process-global data/repo roots, classified): - - | global | resolution | class | - |---|---|---| - | supervisor/git_ops.py REPO_DIR/DRIVE_ROOT | hardcoded home default, env never read, rebound only by init()/worker bind/monkeypatch | DEFECT (the proven live leak; fixed) | - | ouroboros/config.py APP_ROOT/REPO_DIR/DATA_DIR/SETTINGS_PATH/... | env at import (documented four-env recipe) | import-cache; healthy under the whole-process recipe, stale under late isolation — mitigated by the conftest rebind list + the new fail-closed guard | - | supervisor/state.py DRIVE_ROOT/STATE_PATH/... , queue.py DRIVE_ROOT, workers.py REPO_DIR/DRIVE_ROOT | Path(config.DATA_DIR) at import + init() rebind; conftest rebinds all three | import-cache; same mitigation, guard-covered | - | ouroboros/server_process.py DATA_DIR | env at import (own copy) | import-cache; read for child-env assembly, guard-covered downstream | - | ouroboros/tools/evolution_stats.py _REPO_DIR | env at import | import-cache (repo dir, read-only stats) | - | ouroboros/tools/browser.py:336 | env per call | healthy | - | hardcoded home paths in state.py:46/360, worker_process.py:166, server.py:117/1211, gateway/files.py:802, gateway/settings.py:1023 | comparison targets ("is this the live root?"), not writers | healthy by design | - | ouroboros/packaged_cli.py PYTHONPYCACHEPREFIX | known sibling class, packaged launcher only | out of this lane's scope (already documented in AGENTS) | - -2. CLASS FIX (both sides of the shared helper): - - Resolution side: `config.resolve_app_root()/resolve_repo_dir()/ - resolve_data_dir()` — the per-call form of the SSOT constants (same - precedence, two env reads + Path construction, no cache needed — file - I/O dominates every consumer). `git_ops.REPO_DIR`/`DRIVE_ROOT` are now - UNBOUND until init()/worker rebind/monkeypatch pins them; module - `__getattr__` resolves un-pinned attribute access per call via the - resolvers, and `current_repo_dir()/current_drive_root()` serve git_ops' - internal uses. This fixes every `_g.DRIVE_ROOT`/`_go().DRIVE_ROOT` - consumer at once (update_merge, update_candidate, update_merge_plan, - update_recovery, update_source, git_ops_reset, evolution_lifecycle, - gateway/control) with zero call-site edits outside git_ops, and covers - consumers nobody thought of (AGENTS class-fix rule). Live server - behavior unchanged: server.py:569 bootstrap still calls git_ops.init() - which pins real attributes; worker `_bind_worker_repo_root` unchanged. - - Guard side (the structural invariant): `assert_test_data_path` moved to - the ouroboros/utils leaf (state.py re-exports it) and `append_jsonl` now - calls it — the jsonl half of the durable-write plane was the unguarded - half that let the leak land silently while `state.atomic_write_text` was - already guarded. Outside pytest this is one env read per append. - - conftest `_bind_pytest_runtime_roots` now also pins - `git_ops.DRIVE_ROOT` (the one root the rebind list missed). - - Leaking fixture fixed: `tests/test_update_merge_plan.py _point_at` now - patches `DRIVE_ROOT` like its siblings in test_update_dirty_stash.py. - -3. PINS (all six RED on base 8827fd2c, verified on a git-archive copy of the - base tree — the E2E pin fails there with exactly - `home/Ouroboros/data/logs/supervisor.jsonl` leaked): - - tests/test_git_ops_default_roots.py (the D13 hook, previously named in - the ledger but nonexistent): unpinned roots follow env PER CALL, pinned - roots win, `_log_supervisor` lands in the env root, and the exact issue - #455 subprocess repro (all four env set + throwaway HOME → the - live-shaped root stays empty). D13 flipped pending→done. - - tests/test_hermetic_data_root.py: append_jsonl fails closed on a - live-root write (PYTEST_LIVE_DATA_WRITE_BLOCKED) + the serial E2E class - pin — a real subprocess pytest run of test_update_dirty_stash.py + - test_update_merge_plan.py under throwaway HOME + full env isolation with - an empty pre/post inventory delta of `$HOME/Ouroboros/data` (the AGENTS - `find -newermt` etalon as a regression test). - -4. ISSUE #455 CLOSURE: the fix closes exactly the issue's class — "a - process-global root that resolves before the isolation applies" — for the - supervisor writer plane (the proven leak), and adds the fail-closed guard - that turns ANY residual member (late in-process isolation of the - import-cached config-derived globals, future writers) into a loud pytest - failure instead of a silent live write. DISCLOSED RESIDUAL: - `utils.atomic_write_json` and other non-jsonl writers outside - state.atomic_write_text are not guard-wrapped (no observed leak path; - candidates for the same one-line guard if one appears), and non-pytest - drivers (bench harnesses) still rely on explicit rebinds per AGENTS. -5. LIVE PROOF + GATE COUNTERS (host 0897-oma, 2026-09-01, all pytest via - ~/ouro/venv with isolated OUROBOROS_APP_ROOT/DATA_DIR mktemp roots; - `git rev-parse HEAD` = 8827fd2c re-checked after EVERY pytest; - `git diff --check` clean; `ruff check . --select F` clean): - - Red repro pre-fix (throwaway HOME + all four OUROBOROS_* env): the write - landed in the fake home root, not the isolated one. Post-fix: isolated - root; a LATE env change is followed per call; init() pin honored; unpin - returns to lazy. - - All 6 new pins RED on a git-archive copy of base 8827fd2c (the E2E pin - fails there with exactly home/Ouroboros/data/logs/supervisor.jsonl - leaked); all GREEN on the fixed tree. - - Gates: targeted update-merge/git_ops suites 161 passed (-n 8, not - serial) + 35 passed (serial), both EXIT=0; full CI-shape battery - (-n 16, 'not serial' composed WITH the default addopts exclusions) → - 13351 passed, 3 skipped, EXIT=0; full serial pass → 622 passed, - 19 skipped, EXIT=0; size_ratchet lane 5 passed EXIT=0. (A first battery - attempt passed `-m 'not serial'` ALONE, which OVERRODE the default - addopts exclusions and pulled the skill_smoke/browser lanes in — its 4 - fails/4 errors were lane-selection noise plus two real same-class test - instances, both fixed below.) - - LIVE class check: tight-window run of the exact leak reproducers - (update_merge_plan / dirty_stash / merge_assisted / git_ops_managed) - 15:26:57-15:27:05 UTC → live supervisor.jsonl delta ZERO lines - (context=test 456→456, total 899→899); the green serial rerun window - (~15:33-15:38) likewise added ZERO context=test lines. The live log's - context=test stream CONTINUES from other writers exactly as before this - lane's gates (15:06/15:12/15:16 pre-gate; 15:25:30 and the 15:28 - cluster in-gate): concurrent NEIGHBOR pytest batteries on unfixed trees - (~/ouro/subagent_worktrees/v7next and ~/ouro/worktrees/ - frontend-sprint-20260901 were running `pytest tests/` at those moments, - recorded by PID+cwd) plus a foreign preflight runtime - (/tmp/ouro-issue449-formal-r1-runtime, the `wave_floor_refused - estimated_wave_usd=6.42` writer). A silent live write from THIS lane is - structurally excluded: its runs had the append_jsonl guard armed, under - which a live-root write is a LOUD red test, and the runs were green. - - Collateral same-class instances the new guard exposed (fixed here): - tests/test_v678_receipt_reconciliation.py and - tests/test_v652_scratch_and_masking.py shaped their tmp harness as - home/Ouroboros/data under a patched Path.home — moved to neutral tmp - layouts (their registries already take explicit roots). - - OBSERVED SIBLING-CLASS RESIDUAL (not fixed here, disclosed): fresh pyc - mirrors landed in the LIVE ~/ouro/data/state/pycache/tmp/ - ouroboros-pytest-data-*/skills/ouroboroshub/{a2a,duckduckgo}/ - .ouroboros_env/... at 15:28:39-54 — a skill-host/exec child resolved - PYTHONPYCACHEPREFIX (or its data dir) to the live root while importing - isolated skill deps from a pytest session dir. A twin directory from - 2026-07-18 shows the mechanism predates this lane entirely (the known - packaged_cli/PYTHONPYCACHEPREFIX sibling class from AGENTS). The - skill_smoke lane that triggers it runs only outside the default CI - shape. Follow-up candidate: the same lazy-resolution treatment for the - skill-host spawn env. - -## From the persistence mechanical train (№9=A, base d1276c5f, 2026-09-01) - -The CPL4-C1..C23 candidate table (F5 lane B) executed as one lane. Scope = -the owner-sanctioned mechanical set (№9=A) PLUS the owner batch №8 rulings -delivered mid-lane (all A: 1A..7A). Single-intent commits, author Ouroboros; -docs/PERSISTENCE.md rows and the verify pin moved in the same commit as each -fix. Disposition per row: - -| id | disposition | mechanism | -|---|---|---| -| CPL4-C1 | fixed (rotation train) | events.jsonl rotates on the supervisor tick AFTER its custody readers went chain-aware: `utils.jsonl_chain_handles` (open-live-first + inode dedup, rotation-race-safe) feeds `delegate_custody._iter_rows` (full replay + chain-windowed fault tail), strict `complete_custody_rows` (unopenable segment = incomplete view), `custody_log_unreadable` probes the chain, the settled-terminal cursor became a monotonic CHAIN offset (immutable archive ⇒ monotonic; torn archive line consumed, torn live line waits), legacy usage import snapshots the chain, the swarm-fanout rollup reads `iter_jsonl_chain_objects`, worker boot verify falls back to the newest segment on a rotated offset. 8 MB live tripwire (rotation-regression) + 100 MB chain watch inherit the old signals | -| CPL4-C2 | fixed (rotation train) | tools.jsonl rotates on the same tick; api_logs_tail/task_events were already archive-chain-aware; tripwire re-texted to rotation-regression | -| CPL4-C3 | fixed (rotation train) | supervisor.jsonl rotates + gains its tripwire row (`SUPERVISOR_LOG_WARN_BYTES`); `memory.read_jsonl_tail` (the tail-200 context read) backfills from newest archive segments | -| CPL4-C4 | fixed (rotation train) | task_reflections.jsonl rotates + gains its tripwire; the tail-20 read backfills; project-scoped copies stay under project retention (never age-pruned) | -| CPL4-C5 | fixed | launcher pipe-copy thread rotates agent_stdout.log at 2 MB × `.1..3` backups (the server.log RotatingFileHandler bound), rotation failure never kills the copy thread | -| CPL4-C6 | NOT touched (owner 1A) | monetary usage-ledger compaction is excised to its own reviewed lane — monetary authority; the 20 MB WARN and quarantine semantics stand | -| CPL4-C7 | fixed (half pre-landed) | the consumed-once receipt prune already landed with the scheduler tick (`prune_consumed_once_records` + GC retention, found in-tree at base); this lane authored the missing `schema_version` stamp at `_write_scheduled_tasks` and trued up the tripwire text | -| CPL4-C8 | fixed | `_store_evidence` drops expired probe keys on write: failed/unprobeable past `_FAILED_TTL_SEC`, confirmed past GC retention; owner acks never expire, in-retention stale confirmed survives (blip-keep invariant pinned), unreadable ts/unknown status kept fail-closed | -| CPL4-C9 | fixed (owner 2A) | reader surface retired: `inject_crash_report`, the CRITICAL crash-rollback health line and their stickiness tests removed; no writer existed in this tree, stale files inert; PERSISTENCE row retired, scan pin moved | -| CPL4-C10 | fixed | `_schema_version: 1` (ABI-2 `with_schema_version`) on all six owner-state writes; review_job gained the one `_write_review_job` seam so merge writers stamp; grants re-stamp at write (its read normalizes keys away); readers keep legacy-0 tolerance, no read-time retrofits; content_hash never covers state files so no verdict/grant staled | -| CPL4-C11 | fixed (owner 3A) | hub uninstalls write a stamped `uninstalled.json` tombstone; the startup sweep clears dead state BY the mark keeping `grants.json` (owner authority) + the tombstone; reinstall self-heals; tombstone filename joined the owner-state forgery allowlist; gateway local delete (whole-dir removal) untouched | -| CPL4-C12 | fixed (bounded reads; rotation declined) | every review_history reader windows the 4 MB tail (`find_history_job_bounded` idiom incl. `load_history` and the idempotent terminal dedup scan); counters stay exact inside the window because lifecycle terminal rows persist their ordinals and `normalize_history` takes max(stored, derived) — a group aged past the window under-counts, never over-blocks (disclosed). Per-skill archive rotation NOT taken: no per-skill archive plane exists and the bounded reads land the read-cost half; retention stays unbounded-accepted with disclosure | -| CPL4-C13 | fixed | terminal+age startup sweep beside the custody sweep: terminal-status recovery rows (vetoed/adopted) past GC retention, transactions no surviving row references (skipped wholesale if ANY recovery row unreadable), supervision files of SETTLED tasks; `active.json` never; unreadable custody log skips the sweep (the `_prune_delegated_snapshots` idiom) | -| CPL4-C14 | fixed | code_intel roots age-prune by inventory.json mtime past GC retention (pure cache; the root key is a one-way hash so mtime IS the liveness signal) | -| CPL4-C15 | fixed | `failed/` reconcile markers age-prune past GC retention (failure fact already durable in events.jsonl) | -| CPL4-C16 | fixed (owner 4A) | identity/knowledge/patterns journal entries older than GC retention become digest-only (sha256+len, existing hashes never overwritten, `content_digested` mark) under the append lock; unparseable lines and ts-less rows byte-preserved; scratchpad journal and the observation inbox out of scope | -| CPL4-C17 | fixed | both knowledge_history writers + knowledge_journal now append through the sidecar-locked `append_jsonl` seam (last raw `open("a")` journals) | -| CPL4-C18 | fixed | startup sweep unlinks mailboxes (+acks) of tasks with a SETTLED durable result; no result / non-terminal / unclassifiable name keeps them fail-closed; reuses `cleanup_task_mailbox` | -| CPL4-C19 | recorded (owner 5A) | task_results stay eternal for 7.0 deliberately — ratified in PERSISTENCE + ADOPTION; any future prune needs a fresh owner decision | -| CPL4-C20 | fixed | the data-root `tmp_scripts` fallback's `script_*` hard-kill orphans joined `sweep_stale_temp_files` scope (top-level dir only, same age guard, startup-only); task-drive copies stay owned by the drive prune | -| CPL4-C21 | fixed (owner 6A) | `uploads/screenshots` + `uploads/views` (agent-generated) age out past GC retention at startup; owner attachments in the uploads/ root untouched (owner-explicit delete only); readers already skip missing | -| CPL4-C22 | fixed (owner 7A) | `OUROBOROS_OBSERVABILITY_RETENTION_DAYS` removed entirely (parse/clamp/report); `prune_observability_blobs` is now the honest startup census; key added to `RETIRED_SETTING_KEYS` (ghost drop on load); ARCHITECTURE env row removed; preserve-indefinitely contract stands | -| CPL4-C23 | fixed | acknowledged observations older than GC retention fold — with their ack rows — into `archive/consciousness_observations_.jsonl` at startup under the store's writer lock; unACKed rows NEVER pruned; any malformed line / ghost ack skips the whole fold; archive written before the live rewrite (crash duplicates into forensic history, never loses) | - -Cross-cutting disclosures: - -- **Verify pin**: `tests/test_persistence_inventory.py` EXPECTED_SCAN_PATHS - 118 → 123 across the lane (C13 +2 dir enumerations, C14/C15 +2, C16 +2 - canonical journal paths, C9 −1 retired plane), each move in the same commit - as its rows. -- **Size ratchet**: utils.py (1451→1550), launcher.py (1500→1536) and - agent_startup_checks.py (1487→1515) left the tracked 1001–1500 band into - the untracked 1501–1600 zone; the regenerator retired their band - rationales and `--check` is green. The chain helpers stay in utils beside - the jsonl family (splitting the jsonl seam would scatter it); memory.py - was shaved back under 1000 instead of banding. -- **C12 residual**: direct (non-lifecycle) `append_history` rows still do - not persist ordinals; only a group whose newest ordinal-bearing row aged - past the 4 MB window under-counts. Named, not hidden. -- **Worker-boot verify residual (C1)**: on the rare rotated-offset fallback - the newest-segment scan can surface a PREVIOUS generation's boot event - (worst case: a spurious sha-verify supervisor row); the pre-train behavior - on that race was a spurious timeout instead. -- **C13/C18 residual**: state files of tasks that never wrote a durable - result are kept forever by design (fail-closed beats leak-free). -- **Owner batch №8** (2026-09-01, all A): 1A C6 excised to its own lane; - 2A C9 retire reader; 3A C11 tombstone with grants preserved; 4A C16 - digest-only past GC retention; 5A C19 recorded eternal; 6A C21 agent - media only; 7A C22 knob removed with `RETIRED_SETTING_KEYS` idiom. - prompts/SYSTEM.md's combined "CRASH ROLLBACK / RESCUE SNAPSHOT" line was - left untouched (rescue-snapshot half still live; prompts are outside this - lane's mechanical scope). - -## From the F4 wave 4 (update variants + interactive + W2-F2, base 74a03082) - -Tail scenario wave on the lane-1/2/3 skeleton — the carried update variations, -the chat-lineage cancel receipt (the W2-F1 counterpart), the absorb -kill-recovery remainder and the delegated interactive answer — plus the ONE -sanctioned runtime fix of the wave, W2-F2. Manifest rows S18-S23, all keyless -mock-lane, every scenario green on this host. Commits: 68b19a61 (W2-F2), -4e68526c (scenarios), 0196b3d7 (facade-inventory regen tail). - -1. W2-F2 FIXED (owner sanction 2026-09-01 batch 7, №4=A; commit 68b19a61). - The update-fetch path unconditionally repinned the managed remote to the - hardcoded official URL (`ensure_official_update_remote`), silently - retargeting fork/mirror/air-gap installs whose `managed_remote_url` both - bootstraps write into `.git/ouroboros-managed.json`. Class fix at the one - shared helper: NEW `git_ops_updates.managed_update_remote_url(meta=None)` - resolves the configured source (else the official default) and every repin - — set-url, add, and `compute_managed_update_status`'s `official_repo_url` - fallback — goes through it. No configured source = byte-identical former - default. Pins: unit (the configured source survives N repin cycles; blank/ - absent config keeps the official URL), the module-handle extraction table - and the owner-facade identity list extended, and S9 RESHAPED to the - fork-install form the fix serves: the local upstream is now CONFIGURED as - `managed_remote_url` (the former `url..insteadOf` workaround is - gone), the insteadOf config now redirects the OFFICIAL URL to a - non-existent path (a REGRESSED repin fails loudly with zero network - egress — proven red pre-fix on the base tree), and the scenario asserts - `git remote get-url managed` == the configured source AFTER the full - update cycle. ARCHITECTURE §8 updated same-commit; the generated - FACADE_INVENTORY row followed in the 0196b3d7 tail (caught by the - byte-identity pin in the CI battery — the first battery run failed exactly - there, 13430/13431 otherwise green). -2. SCENARIOS LANDED (tests/system_e2e/test_system_scenarios_w4.py): - - S18 update carrier path (~22s solo): a diverged fork (local commit - pinning VERSION=0.0.1) takes an official VERSION=9.9.9 bump through a - configured-source managed install — the merge conflicts EXACTLY inside - the declared version-carrier span. Pinned: plan kind=clean with - `carrier_resolved_paths == ["VERSION"]` and empty conflict inventories - (the carrier engine resolved the span to the official side in the - ISOLATED planner), the apply lands a REAL 2-parent merge commit - (`HEAD^1` == fork base, `HEAD^2` == official target), boot-finalize is - honest (`managed_update_finalized.head` == merge commit, tx consumed), - and THE CARRIER TRANSFER: VERSION, pyproject, the README badge and the - ARCHITECTURE header all name 9.9.9 and the tree's own - `check_worktree_version_sync` reports ZERO desyncs — the Q8 projection - moved every mechanical carrier token, none drifted. The W2-F2 configured - source survives the cycle. - - S19 update conflicting → typed refusal (~16s solo): a genuine code - conflict (both sides rewrite Makefile) routes the smart apply to the - assisted lane, whose admission refuses TYPED on an exhausted budget — - seeded HONESTLY through the tree's own monetary writers - (reserve→dispatch→settle at exactly the configured TOTAL_BUDGET=10.0; - no forged ledger rows). Pinned: preflight kind=conflicting with - code_conflict_paths=["Makefile"] and recommended_strategy=assisted; the - 409 names the budget and "nothing was changed"; and TREE INTACT as byte - truth — the FULL worktree fingerprint (dirty tracked edit + untracked - file included) is IDENTICAL before/after, HEAD/branch unmoved, no - MERGE_HEAD, no tx marker, no assisted resolver task anywhere in the - durable queue, server healthy after the writer fence reopened. - - S20 crash mid-apply (~4s solo, subprocess driver on a second real - install — the S10 idiom): three honest boot-finalize outcomes. (a) crash - between the durable `stashing_local_work` marker and the `stash_sha` - write → boot finds the stash BY ATTEMPT ID (`lookup_update_stash`), - restores the owner's work UNCOMMITTED, clears the marker, HEAD untouched, - one `managed_update_stash_recovered_on_boot` receipt; (b) a half-written - `pending_boot_smoke` tx whose merge commit never reached HEAD → honest - typed rollback (`managed_update_rollback_after_failed_boot` + - `managed_update_rolled_back` naming the exact pre_update_sha, tree clean - at pre) — and NO junk `failed-update-*` ref is minted for a non-attempt - (the guard that keeps a REPLAYED rollback from clobbering a real - preserved attempt, pinned green); (c) merge applied + crash before the - restart smoke → boot RUNS the smoke, finalizes - (`managed_update_finalized.head` == the applied merge), restores the - stashed dirty work, clears the tx — the applied update survives. - - S21 chat-lineage cancel (~12s solo): the ONLY input delta vs S7 is - `chat_id: 1` on the POST /api/tasks body, and it flips the owed-answer - accounting from the `no_lineage_chat` handoff row (asserted ABSENT here) - to the real thing: the outbox delivery `cancel::` - (the id derived from the intent forensics' own requested row) registered - AND drained to `delivered` with NO WS client ever connected, the - `cancel_receipt` chat row durably in logs/chat.jsonl - (direction=system, chat_id=1, the cancellation text naming the task), - and the `cancel_receipt` block on the stored result - (settled_status/outcome=cancelled, the exact delivery_id, the salvage - disclosure) — same requested→claimed→settled forensic trail as S7. - - S22 absorb kill-recovery (~95s solo, Linux-only, three generations): a - REAL evolution cycle on a private clone — campaign seeded, the - supervisor mints the evolution task itself, the scripted agent lands a - reviewed commit through the BLOCKING triad+scope organ (stub panel), the - campaign records the receipt and settles into `waiting_for_restart` — - then the whole tree is SIGKILLed in that crash window. - `OUROBOROS_EVOLUTION_AUTO_RESTART=false` makes the window STABLE and - the crash state exact (a real crash between the campaign write and the - restart-verify marker leaves precisely this durable shape: commit on - HEAD, open transaction, NO marker). Generation B (same clone+data root; - the owner's evolution toggle flipped off so no cycle-2 races the - assertions — the boot reconcile deliberately ignores that flag) absorbs - the cycle through the MARKERLESS dangling-transaction reconciliation: - absorbed_cycles_done == 1, the history row carries - verified_by=boot_reconciliation and the exact commit_sha, the commit is - still on HEAD (no loss), `evolution_tx_reconciled` in events.jsonl, - EXACTLY ONE cycle_outcome=absorbed checkpoint row. Generation C boots - and re-proves NOTHING absorbs twice: count still 1, single history row - for the transaction id, HEAD unmoved, no marker files ever created. - - S23 delegated interactive answer (~25s solo): a `[FAKE:ASK]` run pauses - on a pending interaction; `delegate_wait` returns IMMEDIATELY as - `waiting_on_user` with the full question set; the scripted nanny answers - through `delegate_answer` (run/interaction/question ids parsed from the - transcript the model actually saw); the run resumes and settles. Pinned: - the ORDERED custody chain (STARTED < `delegate_interaction_answered` - status=delivered/questions_answered=1 < SETTLED state=succeeded, one run - id), NO cancel row (a run that merely asked is never torn down), ONE - physical POST /v2/runs across the whole episode, the exact camelCase - wire on the exact path - (`/v2/runs//interactions//answer`, - `{"answers":[{"questionId","selectedLabels","freeText"}]}`), the - transcript truth (the pause, the question text, the delivered relay, - the terminal result all reached the model), and the honest - last-delegation receipt settled exactly once. -3. HARNESS DELTAS: FakeClaudexorDaemon (tests/system_e2e/interfaces.py) gained - the MINIMAL interactive surface — a `[FAKE:ASK]` prompt marker seeds one - `ControlPendingInteraction`-shaped row; the run detail surfaces - `pendingInteractions` + `summary.waitingOnUser` and STAYS running until the - answer verb clears the row (the next poll then flips terminal exactly as - before); `POST /v2/runs/:id/interactions/:iid/answer` answers the typed - statuses at their real HTTP codes (200 delivered / 409 already_resolved / - 400 rejected — free contract fidelity: the client accepts a typed status - at ANY code). A default-lane pin drives the surface with the REAL - ClaudexorGateway (`pending_interactions` normalization reads the row - whole; refusals never consume the question; the delivered answer resumes - the run; a late duplicate is already_resolved, never a re-run). Manifest - rows S18-S23; the new module carries its size-ratchet band rationale. -4. E2E-находки w4 (runtime defects/observations — NOT fixed in this lane, - per the lane rule; W2-F2 was the one sanctioned fix): - - | id | surface | observation | evidence | - |---|---|---|---| - | W4-F1 | evolution absorb, commit-vs-receipt crash window | A crash between the reviewed `git commit` and `record_evolution_commit` leaves a landed reviewed commit that NO boot path will ever attribute: the markerless reconcile short-circuits on an empty `commit_sha` (it only stamps the generation), and `_preserve_evolution_orphan` runs only on the AUTHORITY-REFUSAL path, never on a crash. The commit sits on HEAD forever — not lost as code, but the cycle never resolves and is never counted. | ouroboros/tools/git.py:1411-1436 (commit → receipt order); ouroboros/tools/git_evolution.py:272-330; ouroboros/agent_startup_checks.py:1130-1142 (`if not commit_sha … return`) | - | W4-F2 | absorb outcome ledger atomicity | The campaign absorb write and the `cycle_outcome` checkpoint append are NOT one transaction: the reconcile writes the campaign under `update_json_locked` and appends the checkpoint row AFTER the lock (same shape on the claim path). A crash in between yields a campaign that says `absorbed` with no `cycle_outcome` row — `build_solve_capability_digest` then under-reports the cycle forever, and nothing re-derives the row. | ouroboros/agent_startup_checks.py:1227-1243 (locked `update_json_locked` → post-lock `_append_cycle_outcome_tag`); S22 asserts the row IS written on the clean path | - | W4-F3 | evolution restart marker vs manual restarts | `request_evolution_restart` returns BEFORE writing `pending_restart_verify.json` when `OUROBOROS_EVOLUTION_AUTO_RESTART` is off — the exact-claim verify path (campaign∧transaction∧task∧commit authority, `require_claim=True`) is structurally unreachable for installs that restart manually; absorb attribution then rests wholly on the weaker markerless reconciliation. Deliberate-looking (the knob predates the claim machinery), named so the asymmetry is a decision, not an accident. S22 exploits exactly this to make its crash window deterministic. | supervisor/evolution_lifecycle.py:1437-1438 (early return before the marker write at :1457; re-anchored on rc.9 c1a4b2bc by the F3-C lane — the F2 relocation moved it from :1362-1368) | - | W4-F4 | rescue-local ref accumulation | `create_rescue_local_ref` pins every update stash to a durable `rescue-local-` branch and NOTHING ever deletes them — a refused/unwound attempt (S19's shape) leaves its ref behind exactly like a successful one. Deliberate durability ("git-gc can never lose the owner's work"); the unbounded per-distinct-stash accumulation is the disclosed cost. | supervisor/update_merge.py:294-305 (re-anchored on rc.9 c1a4b2bc by the F3-C lane; was :293-304); no deletion call site in the update flow (`rg rescue-local`) | - -5. LANE BUDGET + GATE COUNTERS (host 0897-oma, 2026-09-01; every pytest via - ~/ouro/venv with isolated OUROBOROS_APP_ROOT/DATA_DIR mktemp roots; - `git rev-parse HEAD` re-checked after EVERY pytest; `git diff --check` - clean; `ruff check . --select F` clean): full mock lane (S1-S23 + default - pins, serial) — 56 passed in ~833s (13:52), inside the wave's ≤18 min - budget (wave 4 added ~175s of scenarios over the integrated S1-S17 lane); - solo timings S18 ~22s, S19 ~16s, S20 ~4s, S21 ~12s, S22 ~95s, S23 ~25s; - the new default-lane pin adds ~1s to the ordinary battery. CI-shape - non-serial battery (-n 16 loadscope): first run 13430 passed / 1 failed — - the facade-inventory byte-identity pin catching W2-F2's new re-export - (fixed by regeneration, 0196b3d7); clean rerun 13431 passed, 3 skipped, - EXIT=0. Serial pass: 622 passed, 19 skipped, EXIT=0. size_ratchet lane: - 5 passed, EXIT=0. -6. DEFERRED (disclosed): the gateway/UI-truth (Playwright) wave — a separate - browser-bearing lane by instruction; the claim-file absorb crash windows - (dead-claim reclaim / stale-claim-after-absorb) stay unit-covered by - tests/test_evolution_restart_claims.py — the E2E covers the markerless - window honestly instead of forging claim files on the real drive; - delegated MUTATING-run scenarios (snapshot provisioning + - integrate_delegated_patch + containment evidence) carried from wave 3b. - -## Q-F6-1 provenance investigation — findings (owner batch 7 №1, 2026-09-01) - -Commissioned by the owner («изучи подробнее по чатам тебя и кодекса, отправь -скаут субагента opus-ов и перепроверь их результаты»). Two opus scouts -(upstream provenance; transcript provenance) — every load-bearing claim below -was re-verified by the coordinator against git objects, the GitHub API and the -plan files. - -1. **Origin.** `_EXIT_CODE_RE` / `_SIGNAL_RE` are as old as the repository: - present in the initial app-bundle commit `6700358a` (2026-04-22, - loop_tool_execution.py:42-43). For four months the regex harvest over the - rendered result text was the ONLY source of `exit_code`/`signal` facts. - What appeared in the drift window was not the fallback but the TYPED - channel (`ouroboros/tools/process_facts.py`, commit `55af051e`, PR #404 - «node-runtime sprint», merged 2026-08-30 20:01Z), which demoted the regex - to a documented fallback («read-fallback for records that lack typed meta»). -2. **How it passed review — it was BORN of review.** The sprint's plan - decision D6 (owner «6. A») said «regex-механика остаётся»; the plan roast - reversed it: fable-5 finding #5 MAJOR («D7 classification must not hang on - string matching — BIBLE P5») + grok-4.6 #6/#7 MAJOR. Disposition R5 in - `~/.claude/plans/node-runtime-sprint/PLAN.md:199`: typed channel for - run_command/run_script; «regex остаётся только read-легаси для старых - записей». Five later internal waves (triad+scope 12 runs, two delta rounds, - full-scope sol) raised no objection to the retained fallback; the - adversarial reviewer of stream B caught «false red from prose» → typed facts - given precedence over the whole key family (`705ffc51`), fallback kept. - GitHub PR #404: 0 reviews, 0 comments; heavy CI jobs skipped — all review - authority was internal. -3. **Two lines that did not know each other.** The v7 lane retired both - regexes on 2026-08-19 (`5440e407`, delta id D02: «the exact scrape a - producer-controlled stdout line could forge»), eleven days BEFORE PR #404 - resurrected them as a fallback on mainline. The F6 sync collided the two - decisions; owner №1=A keeps the campaign contract (typed facts only). -4. **Deeper gap the fallback masked.** On the upstream tip the regex ran - unconditionally for EVERY tool (a `signal=SIGKILL` inside a read_file - result forged a fact), and `run_command` status was recomputed on the - regex value BEFORE the typed merge (loop_tool_execution.py:629 vs :771). - Typed facts existed for exactly two tools. On this tree (regex gone) the - honest remaining «no fact» cases are: extension-child death signals - (extension_dispatch stamps typed codes but no exit_code/signal — closed by - deletion, not by typing), skill_exec/skill_preflight (preflight synthesizes - `-9` on timeout), verify_and_record (renders `exit=`, never stamped into - result_meta), timeout/pre-exec failures of run_command (by construction), - and Windows kills (structurally invisible to the POSIX signal partition). - The node-runtime sprint's unpublished issue drafts (`issue_drafts.md` - Issue 1-3) name the same stragglers. -5. **Disposition.** №1=A stands. The typed-producer gaps in item 4 are a - candidate post-release train («typed process facts for extension children, - preflight and verify»), to be batched to the owner; the unpublished issue - drafts of the node-runtime sprint are surfaced to the owner separately. - Lesson recorded for AGENTS (proposal pending owner «ok»): before keeping a - legacy mechanism as a fallback, `git log --all -S` — a parallel - line may already have retired it with a stated reason. -## From the persistence corrective lane (audits #14/#15, base 3e4a6181) - -The daemon audits found the mechanical persistence train (base d1276c5f) -landed with real defects around it. This lane is the corrective pass over -audit #15 findings 7 and 11-14 and audit #14 findings 5-6 — no new -persistence policy, only the guards the landed policy assumed it had. Every -fix is red-first pinned; each is one commit, author Ouroboros. - -| finding | disposition | mechanism | -|---|---|---| -| #15-11 (CRITICAL, journal compactor) | ACCEPTED, fixed | Three guards on the one sweep that destroys CONTENT. (a) `_digest_row` used `setdefault`, so a stored `*_sha256`/`*_len` that CONTRADICTED the text was kept while the text was deleted — the lie became the whole record. A row whose stored fact disagrees with its text now keeps its FULL content and is reported as a typed `digest_mismatch` on the existing `memory_journal_compaction` event. (b) The append lock is taken `owner_aware_stale=True`, so elapsed time alone can never hand a live journal to a second writer. (c) The rewrite streams line by line into the temp sibling, and the source is re-identified (bytes consumed + device + inode) immediately before `os.replace`; any delta abandons the rewrite and leaves the appender's file. Pins: false digest (both `_sha256` and `_len` shapes) → text untouched + typed fact; a concurrent unlocked append → the row survives whichever branch runs; a source swapped for a different inode → nothing published, `source_changed` reported, no temp left behind; whole-file readers poisoned → compaction still works; source pin on the owner-aware lock | -| #15-12a (append short-write) | ACCEPTED, fixed | `append_jsonl` issued one bare `os.write` and returned success without checking the byte count — the class the atomic writers closed in f772717c, left in the append SSOT every authority JSONL stream goes through, where a torn line is a LOST RECORD (not merely a truncated file). Both lanes now share `_write_fd_fully`. A failure MID-record returns False instead of replaying the whole line over the prefix already on disk (only the open is retried); the next append's `ensure_record_boundary` starts a clean record. The SAME commit removes the second duplicate in that function: the non-required lane hand-rolled its own `O_CREAT\|O_EXCL` + age-reclaim loop (the duplicate DEVELOPMENT.md tells feature code not to write), and that copy was NOT owner-aware — a high-volume appender could delete the lockfile of a LIVE holder, and the memory-journal compactor rewrites a journal under exactly this lock. Both lanes take the shared owner-aware primitive and release through it; a live holder is waited out and the non-required lane then appends unlocked, as before. Pins: one-byte-at-a-time `os.write` → whole record lands; die-after-4-bytes → `False`, exactly two write attempts, no whole-record replay | -| #15-12b / #14-6a (chain enumeration) | ACCEPTED, fixed — and the audit's evidence line was WEAKER than the defect | The audit quoted `jsonl_archive_segments`' `except OSError: return []`. That except was in fact nearly dead: `Path.glob` SWALLOWS a `PermissionError` on the archive directory and yields nothing, so an unreadable archive reached every reader as "this store never rotated" without any exception at all. Enumeration is now an explicit `scandir`, and `strict=True` raises the typed `JsonlChainUnreadable` from `jsonl_archive_segments`/`jsonl_chain_handles`. Strict callers = the authority readers: the legacy usage import (money — its `except OSError` → `UsageAccountingError` was written expecting exactly this and never received it), `complete_custody_rows` and `custody_log_unreadable`. Fail-soft (documented, unchanged): memory tail backfill, the settled-terminal cursor, worker-boot probe, the swarm-fanout rollup, `delegate_custody._iter_rows` (whose strict sibling IS `custody_log_unreadable`). `complete_custody_rows` also drops its hand-rolled duplicate of the chain traversal for the shared helper: one chain SSOT, −30 lines | -| #15-12c (ATIF) | ACCEPTED, fixed | `build_trajectory` read `events.jsonl`/`tools.jsonl` as single from-birth files while chat/progress already used `_read_jsonl_chain`; after the C1/C2 train those two rotate too, so a rotated trial published a trajectory missing its early tool calls and its usage/startup events — a FALSE trajectory. Both go through the chain reader. Pin: rotated tools+events → both calls present in order, tokens summed across the chain, the agent version read from the archived startup row | -| #15-13a (media prune symlinks) | ACCEPTED, fixed — defect reproduced | `is_file()`/`stat()` follow symlinks, so the C21 age sweep would unlink old files wherever a link pointed. The pre-fix pin proves it: the sweep deleted a file OUTSIDE the drive root. Only REGULAR files inside the real family directory are unlinked now (`lstat` + `S_ISREG`, one stat per entry); a symlinked `uploads/screenshots`/`uploads/views` and any non-regular entry are counted `skipped` and surfaced on the event | -| #15-13b (reconcile GC premise) | ACCEPTED, fixed | The C15 GC was chosen on the stated premise that "the failure fact is already durable in events.jsonl". It was not — `_mark_failed` wrote the marker and nothing else, so the age prune destroyed the only record of why an extension gave up. The terminal failure (skill, request id, reason, source, attempts, last error) is appended to the event log BEFORE the `failed/` marker is written, so the marker is genuinely a cache of a fact that outlives it. Pin: five failing attempts → exactly one `extension_reconcile_failed` row; the marker aged out and pruned; the fact still readable | -| #14-5 (Windows O_BINARY) | ACCEPTED, fixed — and BROADER than the audit or ledger item 15 said | Both parties were half right. Ledger item 15 rejected replaying the frozen reference's `O_BINARY` into `write_text_atomic`'s fsync path, correctly: that fixes ONE lane. What neither the reference, the audit, nor item 15 noticed is that the OTHER lane translated too — `Path.write_text` opens in text mode, so the non-fsync path rewrote newlines on Windows exactly the same way. "Platform newline semantics" was therefore not a decomposition anyone had chosen for a caller; it was an unexamined default on both halves. Caller survey: `atomic_write_json` (all durable JSON state), `write_text` , outcome receipts, reviewer-slot projections, benchmark `run_manifest.json` (byte-compared by `tests/test_devtools_benchmarks.py`), and `tools/core.py`'s `write_file`/`edit_file`, which round-trip source Python read back with universal newlines and would have re-saved LF files as CRLF. NOT ONE of them wants translation. The complete class fix is the decomposition upstream already built: `write_text_atomic` is now `write_bytes_atomic` plus a UTF-8 encode — one full-write loop, one flag set, byte-exact everywhere. DEVELOPMENT.md's shared-helper paragraph updated in the same commit. Pins: exact bytes on both lanes; the fsync lane's `os.open` flags carry `O_BINARY` under a simulated Windows `os` (POSIX has no translation to observe); `atomic_write_json` byte pin | -| #14-6b (unbounded history scan) | ACCEPTED, fixed | `load_history` documents "every reader is byte-bounded" (CPL4-C12) while `count_paid_skill_review_cycles` still scanned EVERY installed skill's `review_history.jsonl` whole — the read where the cost multiplies by the number of skills, and the one that made the claim false. The bound stays where it lives: `skill_review_history.iter_history_rows_bounded` (raw rows, same tail window) and the cycles module carries no window size. Same disclosed residual as the family — a group whose newest ordinal-bearing row aged past the window under-counts, never over-blocks — and the docstring now NAMES this reader so the claim is checkable rather than merely asserted. Pin: with the window shrunk, ancient paid rows fall out of the cross-skill count | -| #14-6c (rotation by size) | ACCEPTED, fixed — defect reproduced | Worker-boot verification decided "rotated" from `old_offset > new_size`. Under a busy supervisor the fresh live file has usually already grown past the old offset by the time the verify reads, so the rotation went unnoticed and the read seeked into a DIFFERENT file at a meaningless offset. Reproduced on the pre-fix tree: the boot lookup returns `None`. The cursor is now `(size, device, inode)` from `events_log_cursor()`; a moved identity reads the MATCHING archive segment from the same offset (exact continuation), a cursor whose file is gone falls back to the newest segment whole, and a truncated same-inode file still resets. Both capture sites (pool spawn, assisted-resolver boot wait) pass the cursor; the new re-export regenerated `FACADE_INVENTORY.md` in the same commit | -| #15-14 (ARCHITECTURE same-commit) | ACCEPTED, fixed for this lane's scope | Ownership/data-flow rows added for `delegate_state_sweep.py`, `memory_journal_compaction.py` and `skill_uninstall_state.py`: what each OWNS (which durable paths), what it removes and on what proof, where it fails closed. No absolutes. `scripts/regenerate_inventories.py --check` green. NOT covered here: `usage_compaction.py` belongs to the C6 lane and must carry its own row with it | -| #15-7 (CPL-4 status) | ACCEPTED, fixed | `CPL-4` `done` → `in-progress`, with honest text: the mechanical train landed, this corrective lane landed the defects it left, and C6 runs in a separate reviewed lane and is NOT integrated. The row becomes `done` only after C6 integrates with a green hook — status must not run ahead of the work. `scripts/v7next_adoption.py` OK | - -Cross-cutting notes: - -- **One bug-cementing test removed.** `tests/test_memory_journal_compaction.py` - asserted `digested["old_sha256"] == "pinned-old-hash"` — i.e. it pinned - that a digest known to contradict its own text survives the deletion of - that text. That is the #15-11 defect stated as intent, with a convincing - comment ("existing hash never overwritten") for the next reader. The row - is reshaped to a truthful stored digest and the false-fact case is now its - own pin. -- **`append_jsonl` failure semantics changed, narrowly.** A mid-record write - failure now returns `False` immediately instead of retrying the record and - then falling through to the text-mode fallback. Retrying a partially - landed record duplicates its prefix; open failures still retry three times - and still fall through. Callers already had to handle `False` (that is why - the helper returns a bool). -- **Not taken, deliberately.** `rotate_jsonl_log_if_needed` takes the same - sidecar lock without `owner_aware_stale`. Left as is: rotation is an - atomic rename, so a stolen lock cannot LOSE a row (a racing appender's - bytes land in the renamed inode, which the chain readers read). The - journal compactor is different in kind — it rewrites the file — which is - why the owner-aware lock went there. -- **Residual named.** `iter_jsonl_chain_objects` has no `strict` parameter: - its only runtime caller is the swarm-fanout rollup (observability). Adding - an unused knob would be surface without a caller; the strict path exists - where an authority reader asks for it. -- **Size ratchet, and why the lane's local history was rebuilt.** The chain - work took `ouroboros/utils.py` from 1560 to 1629 lines — over the 1600 hard - cap. The paydown is the append_jsonl lock dedup above: a real removal of a - duplicated primitive in the same function the lane was already fixing, not - a helper split and not comment golf. Because a commit that exceeds the cap - is condemned on the first-parent line forever (a linear repair descendant - does not heal it), the lock dedup was folded into the FIRST commit and the - lane's LOCAL, unpushed chain was rebuilt so no commit ever crossed the cap; - the byte-exact commit was ordered before the chain commit for the same - reason. Per-commit sizes on the rebuilt chain: 1546, 1545, then 1599 to the - tip. `regenerate_size_ratchet.py --check` green, `-m size_ratchet` green. - **Disclosed residual: utils.py sits ONE line under the cap.** The next lane - that touches it must reduce before it adds; there was no further honest - simplification available inside this lane's scope, and buying the room by - deleting contract-bearing docstrings or by exporting a fragment to a - neighbour module would have been the forbidden kind of paydown. - -## From the ui-smoke seed-stamp lane (base bef13f5e, 2026-09-01) - -The CI `ui-smoke` job (`pytest tests/ -m ui_browser`, gated to tag and -workflow_dispatch, so it never ran on the campaign's commit tier) carried 7 -campaign-born failures with ONE cause, proven by an HTTP probe against a live -seeded server plus a control run on this base: ABI-2 admission -(`ouroboros/task_result_schema.py::task_result_schema_refusal`) QUARANTINES a -durable task-result row that carries no `_schema_version`, reason -`unstamped_pre_7_0`. The browser lane's hand-written `task_results/.json` -seeds are exactly such rows, so every seeded card was read as "no result": the -cards never reached finished state, and the assertions on finished/cancelled -state, cost, chronology and the review checkpoint all timed out waiting for a -card that could never close. Commit 9c4bf0b5 (the ABI-2 stamp — see "From the -f31c lane") had already stamped the hand-written fixtures of 9 test files, but -it did not reach the `ui_browser` lane, whose gate its commit tier does not -run. - -Fix: the six hand-written seeds carry the same single additive key the writers -emit (`stamp_task_result_schema`). No production code, no assertion and no -test contract changed — the seeds now represent what a CURRENT-version writer -produces, which is what they always meant to represent. - -| file | seeded `task_results` row | before | after | -|---|---|---|---| -| `tests/test_subagent_final_lineage_ui.py` :32 | `child-final-only.json` | no stamp -> quarantined | `"_schema_version": 1` | -| `tests/test_ui_smoke_liveness.py` :67 | `swarm-root.json` | no stamp -> quarantined | `"_schema_version": 1` | -| `tests/test_ui_smoke_playwright.py` :999 | `named-act.json` | no stamp -> quarantined | `"_schema_version": 1` | -| `tests/test_ui_smoke_playwright.py` :1460 | `chronology-progress-only.json` | no stamp -> quarantined | `"_schema_version": 1` | -| `tests/test_ui_smoke_playwright.py` :3662 | `gone-root.json` | no stamp -> quarantined | `"_schema_version": 1` | -| `tests/test_ui_smoke_review_checkpoint.py` :158 | `review-no-summary.json` | no stamp -> quarantined | `"_schema_version": 1` | - -CLASS SWEEP, not instance. Every hand-written write into a `task_results` path -across the whole of `tests/` was enumerated by source scan (97 write sites), -then filtered to the lanes whose fixtures the classifier can bite -(`ui_browser`, `integration`, `serial`): - -- `ui_browser` — 14 marked files, 58 collected tests: exactly the six rows - above. The other twelve marked files seed no task-result row at all - (`tests/ui_media_delivery_smoke.py` writes only media blobs under - `task_results/artifacts/`, which no admission classifier reads). -- `serial` — no unstamped current-writer seed exists. Every serial file that - stores a row goes through `write_task_result`, which stamps. The two - hand-written exceptions are deliberately unstamped and must STAY that way: - `test_e2e_cancellation_scenarios.py:508` seeds a pre-redesign - `cancel_requested` latch whose whole point is the legacy shape, and - `test_promote_event_transport.py:757/784/801` seed malformed/empty bytes for - the fail-closed lookup pins. -- `integration` — `tests/test_provider_integration.py` touches no task result. -- Also left alone on purpose: the quarantine suite's own fixtures - (`test_tasks_list_slice.py` `unstamped.json`/`future.json`, - `test_task_result_schema_quarantine.py`). Those rows ARE the contract. - -DISCLOSED, NOT FIXED HERE: -`tests/test_ui_browser_smoke.py::test_gateway_frontend_uses_api_client_boundary` -stays red in the same CI job. It is upstream-born, not campaign-born: -`web/modules/chat_media.js:185` calls raw `fetch(` outside the api_client -boundary. Upstream already fixed it in 619d6177, which arrives with sync #2; -editing the file here would collide with that sync for no gain. - -GATES (host 0897-oma, base bef13f5e plus this commit; every pytest run with -all four `OUROBOROS_*` env vars on a fresh mktemp root, chromium+webkit from -`~/.cache/ms-playwright`; live `~/ouro/data` untouched — -`find ~/ouro/data -newermt ` empty): - -- before (control on a `git archive` copy of bef13f5e, the three cheap files): - `-m ui_browser test_subagent_final_lineage_ui.py test_ui_smoke_liveness.py - test_ui_smoke_review_checkpoint.py` -> **3 failed, 1 passed in 96.51s**, - each failure a Playwright timeout waiting for the card the quarantine - prevented from ever finishing. -- after (the lane gate, all four seed-bearing files): - `-m ui_browser test_subagent_final_lineage_ui.py test_ui_smoke_liveness.py - test_ui_smoke_review_checkpoint.py test_ui_smoke_playwright.py -q` -> - **35 passed, 1 deselected in 282.80s**, exit 0. -- rest of the lane (the ten remaining `ui_browser` files, so that the two runs - together cover the CI job's full 58-test collection): **1 failed, 22 passed - in 187.38s** — the single failure is the upstream-born boundary test above - (`assert ['chat_media.js'] == []`). Whole job after this commit: 57 passed, - 1 failed, and that one arrives fixed with sync #2. -- `ruff check . --select F` -> All checks passed. `git diff --check` clean. - `scripts/regenerate_size_ratchet.py --check` green (manifest byte-identical; - `test_ui_smoke_playwright.py` is a GIANT_PATHS entry and grew by 3 lines, - which the path-set manifest does not measure). `git rev-parse HEAD` - unchanged (bef13f5e) after every pytest. - -## Owner closures for the F6-sync #2 forks and A.24 (2026-09-01, batch 10) - -- **Q-F6b-1 CLOSED (owner 1A)**: host notes stay TYPED flags in result_meta - (note kinds, no text); the 256-byte host reserve is not widened — the note - text remains visible to the model in the composed result. -- **Q-F6b-2 CLOSED (owner 2A)**: the owner-home read carve (В23=A) is - re-affirmed as inherited from upstream — the credential-NAME gate applies to - mutations only; reads/list/search rely on shape-based egress masking, with - that residual disclosed. -- **Q-F6b-3 CLOSED (owner 3A)**: A5 literal-argv disclosure is ratified — - shell operators, redirects and env references inside a direct argv array are - DISCLOSED notes, not refusals (model-facing capability widening accepted). -- **A.24 RATIFIED (owner 4A)**: the composition-seam classification delta (a - structured `{"ok": false}` behind an appended host note is a typed refusal, - never a success) carries the owner-item id A.24. -- **Hub wave (owner 5A of batch 9) DELIVERED**: razzant/OuroborosHub PR #52 - merged by razzant (merge commit 67dd5ca7): all 22 extension manifests declare - `plugin_api: "2.0"`, catalog.json regenerated by the hub's own - `scripts/build_catalog.py`. Backward compatibility verified on the live - 6.113.5 code (f3fbfdbb): `parse_skill_manifest_text` parses the new - manifests, the field is preserved as unknown (`plugin_api=None`), `validate()` - is empty, and 6.113.5 has no PluginAPI negotiation/admission consumer — old - installs are unaffected beyond the ordinary content-hash bump. - -## From the Windows-lane green + daemon-audit #16 fixes (base 196438c9) - -CI runs 33555971481 (9a28e58f) and 33563498919 (196438c9) were the first -3-OS runs on the campaign branch: ubuntu and macos full-test green, the -Windows full-test lane red on sixteen tests. Dispositions, one per class: - -| class | tests | disposition | commit | -|---|---|---|---| -| chmod(0) unreadable probes (POSIX-only) | update-tx marker; rc_audit skills/task_results | skip on Windows; `os.geteuid` guarded | tests commit | -| open file cannot be unlinked (no FILE_SHARE_DELETE) | memory-journal replaced-source | skip on Windows | tests commit | -| `signal.alarm` POSIX-only | telegram chunker ×2 (upstream-identical file) | pytest-timeout is the Windows guard | tests commit | -| native separators | abi5 remnant scans ×1, golden credential listing | compare POSIX-relative paths | tests commit | -| shlex eats backslashes | glued `git -C` predicate pin | POSIX spellings; residual is upstream-owned (`git_shell_policy`), disclosed in the test | tests commit | -| cp1252 decode/encode | message-bus chat.jsonl reads (upstream-identical file); `rc_audit --scope-only` (U+2261 in the scope text) | explicit utf-8 read; ASCII-escaped scope JSON | tests commit | -| simulated `O_BINARY` stripped the real bit | byte-exact atomic-write pin `[fsync=True]` | pin the real bit where it exists, simulate only on POSIX | tests commit | -| byte-exact writer vs `os.linesep` expectation | cybergym applied-settings verification (upstream-identical test; v7next writer contract) | expected bytes = the serialization | devtools commit | -| `signal.Signals` knows only host signals | process-signal observability ×3 (`SIG9` ≠ `SIGKILL`) | one `platform_layer.posix_signal_name` SSOT; the runner's duplicate table removed | platform commit | - -Daemon audit #16 (sol, 22:11Z) — accepted findings landed here: -- finding 5: `owner_quiz._mutate_projection` lacked the ABI-2 write guard - that `owner_hurry` carries → guard + stamp-on-write, red-first pin - (`tests/test_quiz_answer.py`); -- finding 6 (partial): `supervisor.state.atomic_write_text` single - `os.write` → delegates to `utils.write_bytes_atomic` (write loop, fsync, - guard kept); the `view = view[os.write(fd, view):]` loops in - `skill_review_history` / `project_dialogue` were REJECTED — POSIX - `write()` returns 0 only for an empty buffer, the loop terminates. -- finding 4 (C6 after three rounds): owner checkpoint raised in the same - batch; round 4 runs as a non-integrating lane until an independent PASS - and the owner's choice. -- process findings 2/3/8/9 (uid-scoped pgrep, env on every python call, - separate gate calls, lane pools ≤ healthy profiles, per-delta code check - before merge) adopted as operator rules; full text in the coordinator - disposition file. -## From the safety-port + preflight carve-out lane (owner 2B/11A, base bef13f5e) - -Two owner decisions from batch №9, landed as two single-intent commits. Both -are gate-shaped: one moves a host fact into a PROTECTED file, the other -narrows an admission block that was structurally degrading a whole class of -commits to the audited bypass. - -**2B — ADOPTION D05 ported into the protected `ouroboros/safety.py`.** This -lane edits a protected surface (AGENTS.md protected-paths list) under an -EXPLICIT owner sanction («2. B», 2026-09-01); the delta is exactly the two -facts named there and nothing beside them. - -| fact | what the tip did | what it does now | -|---|---|---| -| observability root of a safety call | `chat_observed(drive_root=pathlib.Path(getattr(ctx, "drive_root", "../data")) if ctx is not None else pathlib.Path("../data"))` — a CWD-RELATIVE guess. It names whatever directory happens to sit beside the process's current working directory and only resolves to the real data root by coincidence of the dev layout; the `ctx=None` call shape (extension/MCP dispatch, and every direct `check_safety` call) took it unconditionally | `_safety_drive_root(ctx)`: the context when it has one, otherwise `config.DATA_DIR`, read LATE off the module so test isolation and runtime rebinding are honored — the same resolution order the review surfaces already use | -| who charges a safety call with no event queue | module-top-level `from supervisor.state import update_budget_from_usage` — an IMPORT-TIME edge from the module every worker imports, and which runs on every guarded tool call, into the supervisor package | `_record_safety_usage(ctx, payload)`: the context's own ledger writer when it injects one, else a CALL-TIME import of `supervisor.state` — the idiom the six sibling call sites of this writer in `ouroboros/` (reflection, post_task_synthesis ×3, post_task_evolution, improvement_backlog, semantic_dedup) already use. `ouroboros.safety` was the only module of that family importing it eagerly | - -Not a byte copy of the frozen reference: the reference's `_safety_model_call` -does not exist there in this shape, so both facts were re-seated on tip bytes -at their tip call sites (`safety.py:942` and the no-queue branch of -`_emit_safety_usage`). The third fact of the D05 row, `schedule_followup = -POLICY_SKIP`, needed no port at all — the tip already carries it byte-identical -(`ouroboros/safety.py:111`); the ADOPTION row now says so instead of claiming -the tip "does NOT carry these facts". - -Pins (`tests/test_safety_policy.py`, the D05 hook): `ctx=None` and a -context without `drive_root` both resolve to a REBOUND `config.DATA_DIR` -(late read, never `../data`); a context with `drive_root` wins; a CLEAN -interpreter importing `ouroboros.safety` leaves `supervisor.state` out of -`sys.modules`; the injected sink beats supervisor state and its absence falls -back to it. - -**Two monkeypatch seams moved, and why that is not a weakened test.** Three -tests patched `ouroboros.safety.update_budget_from_usage` — a module global -that no longer exists once the import is call-time. They now patch -`supervisor.state.update_budget_from_usage`, which is the SAME function object -the code reaches, resolved at call time; every assertion they make about the -fallback is unchanged. Nothing was deleted to make a test pass. - -**Residual, disclosed.** `_record_safety_usage`'s injected-sink branch is -ported with the function, but nothing on this tip provides it on the safety -path: `ToolContext` has no `update_budget_from_usage` field, and the object -that does carry that attribute is the supervisor's event context -(`server.py:781`, consumed at `supervisor/events_budget.py:122`), which never -reaches `check_safety`. So the branch's only current exerciser is its pin. It -is kept because it is half of the owner-adopted fact and because the attribute -name is an EXISTING convention in this codebase rather than an invented -protocol — not because a caller was found. - -**11A — the doc-only carve in `release_metadata_preflight` (finding W3A-F1).** -Two gates classified the same diff two different ways. `ouroboros/tools/git.py` -exempts a doc-only diff from the compensating tests preflight -(`_doc_only = _diff_aware and _diff_is_doc_only(classification_paths)`), while -`ouroboros/commit_admission.py::release_metadata_preflight` returned -PREFLIGHT_BLOCKED for ANY changed set without `VERSION` in scope — the doc-only -diff included. The consequence was not a nuisance refusal: it was a structural -one. `preflight_review` is the only producer of a FRESH advisory verdict, so a -doc-only change on any install could not obtain one at all, and the standard -`preflight_review` → `commit_reviewed` flow degraded to the AUDITED BYPASS for -the whole class. It bites hardest on the two commit classes BIBLE P9 itself -exempts from the bump (`BIBLE.md:717-725`): a version-neutral external -contribution and a forensic recovery snapshot have no `VERSION` to name by -construction, so for them the bypass was the ONLY door. - -The carve is three statements and reuses the commit gate's own detector, -imported from its owner module (`ouroboros.tools.git_review_cycle._diff_is_doc_only`, -which `git.py` re-exports at line 1635). SSOT on purpose: a second doc-only -predicate written here would be a detector the two gates could drift apart on, -and the whole finding is that they had already drifted. The import is -call-time and sits INSIDE the no-VERSION branch — the local idiom in this -module — so the ordinary VERSION-in-scope preflight pulls in no new module and -the admission layer keeps no import-time edge into `ouroboros.tools`. - -| scope | before | now | -|---|---|---| -| `docs/NOTES.md` | PREFLIGHT_BLOCKED (never reaches a critic) | admitted — the advisory actually runs | -| `ouroboros/feature.py` | PREFLIGHT_BLOCKED | PREFLIGHT_BLOCKED (unchanged) | -| `docs/NOTES.md` + `ouroboros/feature.py` | PREFLIGHT_BLOCKED | PREFLIGHT_BLOCKED (mixed is not doc-only) | -| `tests/NOTES.md` | PREFLIGHT_BLOCKED | PREFLIGHT_BLOCKED (`_diff_is_doc_only` excludes `tests/`) | -| `VERSION` + a desynced carrier | PREFLIGHT_BLOCKED | PREFLIGHT_BLOCKED (the carve never runs; carrier coherence owns the answer) | - -**Deliberately NARROWER than the two BIBLE classes it is motivated by.** A -version-neutral external contribution or a rescue snapshot that carries code -still blocks here, exactly as before. The carve keys on the diff's shape, not -on a claimed provenance, because provenance is caller-asserted and would be a -new trust surface on an admission gate; widening it is an owner decision this -lane did not take. Recorded as the residual of the finding rather than left -implicit. - -Pins (`tests/test_advisory_preflight.py`, the class that already owned the -block): `test_doc_only_diff_without_version_reaches_the_critic` drives the real -`_handle_advisory_pre_review` and asserts the critic is DISPATCHED (not merely -"not blocked") for a doc-only scope with no `VERSION`; -`test_doc_only_carve_is_the_commit_gate_classifier` walks the table above and -asserts the classifier verdict beside every admission verdict, so a divergence -between the two gates fails as a classifier mismatch rather than as a mystery -block. The pre-existing -`test_changed_diff_without_version_blocks_before_sdk` is untouched — the -ordinary code diff still blocks, and it still proves the SDK is never reached. - -**E2E S16 pinned the bypass-consequence, and is updated honestly.** In -`tests/system_e2e/test_system_scenarios_w3a.py`, S13B's `preflight_review` step -used to name the UNCHANGED `VERSION` alongside the doc — a workaround written -INTO the scenario precisely because the admission blocked the doc-only scope -(the finding says so in its own row). The step now names the doc-only scope -ALONE, which makes S16 a live end-to-end proof of the carve instead of a -scenario routing around it. Two consequences are disclosed rather than -silently absorbed: - -- Naming `VERSION` put the run on the VERSION-in-scope branch, where - `check_history_limit` runs. This checkout's README carries more patch rows - than the P9 limit (finding W3A-F2, a PRE-EXISTING tree-state violation), so - the scenario needed a `_trim_readme_history_to_p9_limit` fixture repair to - reach a verdict at all. With no `VERSION` in scope that branch is never - entered, so the fixture is deleted — it was compensation for the workaround, - not for the scenario. W3A-F2 itself is NOT fixed by this lane and remains - open against the tree. -- S16's contracts are unchanged: the fresh advisory, the post-verdict - stale-from-edit refusal, and the revalidation refusal are all still asserted - on the same steps. What changed is which door the first step walks through. - -**GATE EVIDENCE: NOT PRODUCED IN THIS SESSION — disclosed, not implied.** The -session that wrote this entry had no permission to execute Python: every form -of `python3 -m pytest`, `python3