Repository navigation
T021, U-2: the gate console's schemas, each read where its owner keeps it, nothing vendored (plan 038, P4F-4) - #40
Conversation
…ate verbs (plan 038) tests/conftest.py registers THE GOVERNED HOST for the length of each test of the eight governed suites that drive openXdox's gate verbs (GOVERNED_HOST_SUITES): openxFactory's composite, opendox_host.profile(), where the run is composed (R1Q23 (a)), and GovernedStandInHost, which carries openXdox's three governed columns, elsewhere. The composite is registered with openDox's registry alone, so no openxFactory seam outlives the test. Its facets are read with sys.path saved and restored, because importing openxFactory's lane column runs carved_reach.install(), which would otherwise put openxFactory's pinned openXdox leg ahead of this checkout's namespace package. An openxdox module from anywhere else refuses the case by name. tests/test_host_plane.py holds the list to its scan (12.5's governed set that names a gate verb of cli_gate.py) and the mechanism to its word, all in a lone checkout. Both lists share one registration helper, a_hosts_plane; a module on both runs under the governed host. Composed, 12.5's sixteen suites go from 174 red to 109: host-reg 58 and one-off group H 7 turn green, and no green node turns red. Refs opensoft/openxFactory#656 (CLAIMED 6013608299). Lane: openXfactory-3 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s it, nothing vendored (plan 038)
gate_console._validate_contract_document read both of its schemas from
parents[2] / "contracts" / "schemas", which in this leg is the checkout
root and holds no contracts/. Now each schema is read where its owner
keeps it:
* gate-action-record.schema.yaml, openXdox-spec's, from this
distribution's own packaged copy, through
contracts.verified_bytes(), which checks it against copies.yaml's
digest first;
* demotion-execution-receipt.schema.yaml, openxFactory's, from a source
the HOST registers with the new register_contract_schema_source().
The same directory again is a no-op, a different one is refused, and
a directory without the schema is refused when it is registered.
unregister_contract_schema_source() and
contract_schema_source_registered() complete the seam. With nothing
registered, a receipt refuses, naming the call (R1Q27 (a)).
Nothing is added to copies.yaml, and test_packaged_validator.py is
unchanged and green. The seam sits near the end of the module, so that
no line before it moves. tests/test_no_hardcoded_status_words.py pins
seven prose f-strings in this module by line number.
tests/conftest.py registers openxFactory's contracts/schemas/ from the
composed tree, at process start, where the run is composed; a lone
checkout registers none. tests/test_gate_console_schema_source.py runs
the console in a child interpreter beside a stand-in doc_health, so it
runs in a lone checkout.
Composed, the 16 schema nodes and test_explicit_release_has_cli_and_http_parity
turn green on T020's host. 12.5's suites read 92 red of the 174, and no
green node turns red.
Refs opensoft/openxFactory#656 (CLAIMED 6013608628).
Lane: openXfactory-3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reviewer's GuideThe PR removes checkout-relative schema loading from the gate console, reads the openXdox-owned schema from its verified package, obtains the openXfactory-owned receipt schema through a validated single-source host seam, wires that seam into composed tests, and adds lone-checkout and host-plane coverage for the behavior. Sequence diagram for owner-specific gate schema validationsequenceDiagram
participant Caller
participant GateConsole
participant PackagedContracts
participant HostSchemaSource
Caller->>GateConsole: validate_gate_action_record(record)
GateConsole->>PackagedContracts: verified_bytes(GATE_ACTION_RECORD_SCHEMA)
PackagedContracts-->>GateConsole: digest-verified schema text
GateConsole->>GateConsole: _validate_contract_document(record, schema_text, label)
GateConsole-->>Caller: validation result
Caller->>GateConsole: validate_demotion_execution_receipt(receipt)
GateConsole->>HostSchemaSource: read demotion-execution-receipt.schema.yaml
HostSchemaSource-->>GateConsole: host schema text
GateConsole->>GateConsole: _validate_contract_document(receipt, schema_text, label)
GateConsole-->>Caller: validation result
State diagram for the host contract schema sourcestateDiagram-v2
[*] --> Unregistered
Unregistered --> Registered: register_contract_schema_source(source)
Registered --> Registered: register_contract_schema_source(same source)
Registered --> Unregistered: unregister_contract_schema_source()
Unregistered --> Unregistered: register_contract_schema_source(missing schema) / ContractSchemaSourceRefused
Registered --> Registered: register_contract_schema_source(different source) / ContractSchemaSourceRefused
Unregistered --> Unregistered: validate_demotion_execution_receipt() / GateRefused
Registered --> Registered: validate_demotion_execution_receipt()
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Invalid host schemas can escape as uncaught exceptions after demotion execution instead of producing a controlled refusal.
Review effort: Balanced
Findings: 1
What changed in this PR
Moves gate-console schema loading to each contract owner and adds the stacked governed-host test composition.
Changes:
- Loads packaged schemas through digest verification and host schemas through registration.
- Registers composed openxFactory host/schema sources in tests.
- Adds host-plane and schema-source coverage.
| File | Description |
|---|---|
src/openxdox/gate_console.py |
Adds owner-specific schema loading and registration APIs. |
tests/conftest.py |
Composes governed hosts and host schema registration. |
tests/test_gate_console_schema_source.py |
Tests schema ownership and registration semantics. |
tests/test_host_plane.py |
Tests governed-host selection and isolation. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
…Copilot r4194497045) A registered source's receipt schema was checked only for existence. A document that is valid YAML but no valid JSON Schema escaped as a raw exception: an empty file raised AttributeError, `required: 1` raised TypeError, and an unresolvable $ref raised jsonschema's referencing error. cli_gate's demotion reports only GateRefused, BoundaryViolation or OSError as "executed but receipt not written", after files have moved, so any of these ended the verb with a traceback. _schema_errors now parses the schema, checks it against the validator's own meta-schema, and applies it. A failure at any of the three refuses as GateRefused. _validate_contract_document keeps its line count, so no line that test_no_hardcoded_status_words.py's PROSE_EXEMPT pins moves. A new lone case covers the three schemas, and two more mutants are killed. Refs opensoft/openxFactory#656 (CLAIMED 6013608628). Lane: openXfactory-3 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
T020's squash c6d15b2 has the same tree as its head c52900e, which this branch already carries. The one conflict, at the end of tests/conftest.py, is T021's own append after T020's last hunk; main's side of it is empty. It is resolved to this branch's side, so the merged tree equals ecf0ba9's. Lane: openXfactory-3 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Lane: openXfactory-3 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d (Copilot r4197914632) yaml.safe_load raises RecursionError, for a nesting deeper than the interpreter allows, and ValueError, for an impossible date such as 2001-02-30, past YAMLError. _schema_errors caught only YAMLError, so a registered receipt schema holding either payload escaped as a raw exception after the demotion had moved files. It now catches the same three classes openxdox.contracts' record reader does, and refuses as GateRefused, "could not be parsed". tests/test_gate_console_schema_source.py adds three payloads to its cannot-apply case: an unclosed flow sequence (a YAMLError, the control), a 5000-deep sequence, and the impossible date. Before the fix the case reads "ESCAPED RecursionError: maximum recursion depth exceeded". With it, all six cases pass. Dropping RecursionError from the tuple, or dropping ValueError, is killed by that same case. Refs opensoft/openxFactory#656 (CLAIMED 6013608628). Lane: openXfactory-3 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tes again (plan 038) (#42) Lane: openxfactory-4 (openXfactory-4-openDox_extraction) Authored by lane openXfactory-3, delegated by lane openxfactory-4 (#656 6013609326) Arc: neutral-product-standalone-operability ## T023, U-4: `swb-session.js` spells the model's two names at its call sites again (plan 038, feature `038-opendox-document-tool-self-maintenance`) After set met: T004 (opensoft/openxFactory#1245) landed as 260234b5 at 2026-10-06T14:31Z. Base brought up to date with main (T020 #39 c6d15b2, then T024 #38 8b64fae) by two merge commits; CI green at e8a067f; the composed falsifier was measured at c3c6a47, before the merges, and was not re-run (the merges brought only T020's host registration and T024's shim; INFERRED, not measured: the composed falsifier reads the same at the new head). ### Contract, from `tasks.md` - **Realizes**: 12.5 (part). - **Falsifier**: the 2 nodes, composed, quoted. - **Ruled**: R2Q8. (The entry names no Decisions line.) - **After**: T004. - **Files**: openXdox-code `src/openxdox/web/views/swb-session.js`, plus ONE new test file, `tests/test_swb_session_transport.py`. The test file is not in the task's `tasks.md` entry: lane openxfactory-4 added it to T023's Files by its ruling on Copilot's first finding on this PR (`lane4-to-lane3.log`, 12:52:51Z, "FIX-NOW, as in T022 (a)"; recorded on #656 in lane 4's latest comment). No protected suite is edited, and the file is not one of the 16 `HOST_PLANE_SUITES`. ### What and why `submitSession` took its two model calls from locals, `const headersOf = bound ? bound.consoleHeaders : consoleHeaders;` and `const repairWith = ...`, called as `headersOf(caps)` and `repairWith(send, repair)`. That is the `bound`-namespace refinement of Copilot review round 3, which arrived with the S5 carve in #18. `tests/test_session_confinement.py` pins both write transports (`swb-session.js` and `swb-create.js`) to the literal spellings `headers: consoleHeaders(caps)` and `withConsoleRepair(send, repair)`, so two nodes went red in the composed run. The pre-shed openxFactory copy spelled both exactly; `swb-create.js` still does. The trace is lane 3's R2-INV-P4F part, Group J. The fix shadows the two module-level names inside `submitSession` instead of respelling the call sites: ```js const MODULE_TRANSPORT = { consoleHeaders: (...args) => consoleHeaders(...args), withConsoleRepair: (...args) => withConsoleRepair(...args), }; // inside submitSession: const { consoleHeaders, withConsoleRepair } = bound || MODULE_TRANSPORT; // headers: consoleHeaders(caps), ... return withConsoleRepair(send, repair); ``` With a `bound` namespace the calls go to it, as before. With none, `MODULE_TRANSPORT` reaches the CURRENT install at call time, exactly as the two module-level consts do. The comment above it does not quote the pinned needles, because a needle quoted in its own source satisfies itself; each pinned needle occurs once in the file, in code (counted). ### The new test file, `tests/test_swb_session_transport.py` The pinned spellings are text assertions, so they cannot tell the right model from the wrong one. Two node probes run the shipped module out of the composed bundle and assert on the request that reaches the injected fetcher: - **bound**: two `firstEditTransport`s over two models that differ in exactly `consoleHeaders` and `withConsoleRepair`, both built before either is called; each request must carry its own model's header (from its own `caps`) and go through its own model's repair policy. - **unbound**: a mounted session form, submit pressed; the request must carry the header the mounted model's `consoleHeaders` built and go through its `withConsoleRepair`. (The outcome panel the column writes after the request needs the shell's `page-overlay` host, which a stub DOM lacks, so the handler rejects after the fetcher is called; the probe records that and asserts on the request only.) Both pass at main's own code (checked in the base clone), so they describe the behaviour the respelling preserves. They run in the lone CI form too: no openxFactory module, node only, as `test_gate_loop_probes.py` does. ### The falsifier **Base measured**: openXdox-code `56e1c238` (origin/main), openDox-code `a9ac96f9` (plain install, `--no-deps`, over openXdox's pin, as R2-INV-P4F did), openxFactory `92010d3e` with `openDox`, `openXdox` and `openXwallet` initialized recursively and its `scripts/` on `PYTHONPATH` (R1Q23 (a)'s form). Python 3.12.3, pytest 8.4.2, the CI install command (`python -m pip install -e ".[test]"`). **Head**: `c3c6a47` (the fix is `f19e05c`; the test file is `c3c6a47`). Run with `FORCE_COLOR`, `CLICOLOR` and `COLORTERM` unset and `TERM=dumb`. RED at main (`56e1c238`), composed: ``` FAILED tests/test_session_confinement.py::test_the_renderer_presents_the_console_token_from_the_capability_probe FAILED tests/test_session_confinement.py::test_the_renderer_repairs_a_stale_token_by_re_reading_it_never_by_reloading 2 failed in 0.45s ``` (each: `AssertionError: swb-session.js`, from `'headers: consoleHeaders(caps)' in body` at `:290` and `'withConsoleRepair(send, repair)' in body` at `:383`) GREEN at head `c3c6a47`, same environment: ``` PASSED tests/test_session_confinement.py::test_the_renderer_presents_the_console_token_from_the_capability_probe PASSED tests/test_session_confinement.py::test_the_renderer_repairs_a_stale_token_by_re_reading_it_never_by_reloading 2 passed in 0.67s ``` ### What else reads `swb-session.js`, and did not move Every test file in the tree (`tests/test_*.py` and `tests/integration`, named on the command line so the declared exclusion does not drop them), composed as above, run SERIALLY on one idle container with `--continue-on-collection-errors` (`test_gate_console.py` fails to collect in both, for want of `scripts/proposal-support.py`): | | nodes | failed | errors | passed | skipped | |---|---|---|---|---|---| | main `56e1c238` | 3671 | 550 | 94 | 3021 | 6 | | head `c3c6a47` | 3673 | 548 | 94 | 3025 | 6 | Compared node by node: the 3671 ids at main all appear at the head, and EXACTLY TWO of them change status, both red to green, the two above. The two extra nodes are the new file's, both passing. Nothing goes green to red. The suites that read `swb-session.js`, red nodes at main and at the head: `test_gate_loop_probes.py` 0 / 0, `test_renderer.py` 25 / 25, `test_doxbench_save.py` 3 / 3, `test_session_confinement.py` 20 / 18 (the two above), `test_staging_workbench.py` 63 / 63, `test_doxchat_model_intake.py` 10 / 10, `test_doxbench_transport.py` 6 / 6, `test_assembled_bundle.py` 4 / 4, `test_doxbench_scope.py` 1 / 1, `test_gate_loop_views.py` 0 / 0. The three suites the trace names read 28 red on both sides, the same node ids, as the trace's trial found. Earlier pairs, run overlapping and while the container was out of process slots, showed `Resource temporarily unavailable` and `can't start new thread` errors in git-fixture and thread-spawning suites; they were discarded and re-run serially after the container was restarted, which is the pair above. ### Mutants of the fix Spelling mutants, killed by the pinned nodes (composed, over `test_session_confinement.py`, `test_gate_loop_probes.py`, `test_doxbench_save.py` and `test_renderer.py`, compared with the head's own red set): | Mutant | Result | |---|---| | M1: respell the `headers:` call as `(bound ? bound.consoleHeaders : MODULE_TRANSPORT.consoleHeaders)(caps)` (behaviour kept) | KILLED: both target nodes go red (the stale-token node pins the same headers spelling) | | M2: respell the return as `(bound ? bound.withConsoleRepair : MODULE_TRANSPORT.withConsoleRepair)(send, repair)` | KILLED: the repairs-a-stale-token node goes red | Behaviour mutants, which keep every pinned spelling and which only the new file can kill (run composed AND in the lone CI venv, same result in both): | Mutant | Killed by | |---|---| | M3: drop `bound ||`, so the module pair is always used | `test_js_each_built_transport_sends_through_its_own_models_header_and_repair` (1 failed, 1 passed) | | M4: `MODULE_TRANSPORT.consoleHeaders` calls `withConsoleRepair` | `test_js_a_mounted_form_sends_through_the_mounted_models_header_and_repair` (1 failed, 1 passed) | | M6: `MODULE_TRANSPORT.withConsoleRepair` calls `consoleHeaders` | `test_js_a_mounted_form_sends_through_the_mounted_models_header_and_repair` (1 failed, 1 passed) | Before the new file, M3 and M4 survived every committed suite (the finding on the first head). ### Other gates - **CI form, lone** (`python -m pytest -q --deselect "$LEFT_OUT"` from the root, the CI venv, no `PYTHONPATH`) at `c3c6a47`: `1097 passed, 4 skipped, 1 deselected`; the workflow's triple reads `selected=1101 passed=1097 skipped=4 failures=0 errors=0`, which meets its pins (floors 1099 / 1095, skipped exactly 4, 0 / 0). At `f19e05c` and at main it read 1099 / 1095. - **Protected suites:** `python3 scripts/protected_suites.py --landings=<both commits> --suites=<the 98 tests/test_*.py at origin/main>` prints `ok: 0 protected edit(s), each entered and holding`, with and without `--chains`. The new file is not in `HOST_PLANE_SUITES`. - **Already red at main, composed, not from this change:** `tests/test_dependency_direction.py::test_first_attribute_access_refuses_and_names_the_layering` (the T024 writer attributes it to `ideation_dashboard` being importable when composed; I did not trace it); red in both the base and head runs. - **CI at the head:** `validate` pass and `SonarCloud Code Analysis` pass (2 of 2) at `c3c6a47`. - **Copilot:** two reviews, one per head. At `f19e05c8` (review 5428398958): 1 finding, that no committed test distinguishes the bound and unbound transports. Fixed by `c3c6a47`; thread answered and resolved. At `c3c6a470` (review 5428827782, COMMENTED, body "Changes recommended"): 2 findings, both on the new file. - "the declared one-file scope": this body said one file and "no test is added"; the body is corrected (Files above), the ruling that widened Files is cited, and the thread is answered and resolved. - "update the workflow coverage floors": see below. An accepted limit, answered and resolved. - 0 unresolved threads at the head. The holder rules on the "Changes recommended" label. - Holder ruling #656 `6017860539`: both Copilot findings, the "Changes recommended" label included, are ACCEPTED as they stand; the CI floors are left to T029. - **At the new head `e8a067f3`** (two update-branch merges of `main`, both merge commits: `3b6cd13b` took T020 #39 `c6d15b27`, then `e8a067f3` took T024 #38 `8b64fae0`): every check-run completed success; Copilot review 5432366190, state COMMENTED, "Approval recommended", `Findings: None` (requested through the reviewer API after about 10 minutes without an auto-review); 3 review threads in all, all resolved, 0 unresolved, none new. Against `c3c6a470` the merges changed only `tests/conftest.py`, `tests/test_host_plane.py` and `scripts/ideation_dashboard/session_git.py`, the files of T020 and T024. - **SonarCloud:** the `SonarCloud Code Analysis` check passes (quality gate); I did not list individual issues. ### What is NOT done - **The CI floors are not raised** (`MIN_SELECTED` / `MIN_PASSED` in `.github/workflows/validate.yml`, 1099 / 1095; this PR's head selects 1101 and passes 1097). `validate.yml` is not in T023's Files, plan 038 gives its floors in this repository to no task (T017 and T051 re-pin openDox-code's), and the open sibling PRs that add tests (#39, #40, #41) would collide with this one on the same two lines. Floors are minimums, so the check passes and the margin is two wider than it was. The workflow's own comment says the commit that adds a test SHOULD raise them with the reason; whether and in which PR is for the holder to sequence. - The other groups of R2-INV-P4F's one-off bucket (H, L, T, W, S) are other tasks. Arc: neutral-product-standalone-operability Lane: openxfactory-4 (openXfactory-4-openDox_extraction) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…sed (Copilot r4198638746)
A cyclic YAML alias such as properties: &p {a: {properties: *p}} loads,
but the validator's meta-schema check never finishes it. check_schema
raised RecursionError, which escaped as a raw exception after the demotion
had moved files. This is the third finding on the same boundary, so the
boundary now refuses any failure, as the packaged validator's does. Each of
the three steps (parsed, checked, applied) turns any exception into
GateRefused, naming the step and the exception's class. A SchemaError keeps
its own "is not a valid JSON Schema" message.
tests/test_gate_console_schema_source.py adds the cyclic mapping, which
must refuse "could not be checked: RecursionError", and Copilot's cyclic
list. The pinned jsonschema 4.26.0 refuses the cyclic list as a
SchemaError already; it must refuse as GateRefused either way. Before the
fix the case reads "ESCAPED RecursionError: maximum recursion depth
exceeded"; with it, 6 passed. Two mutants are each killed: narrowing the
parse step back to YAMLError, and dropping the check step's catch.
Refs opensoft/openxFactory#656 (CLAIMED 6013608628).
Lane: openXfactory-3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Lane: openXfactory-3 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Sorry @brettheap, you've used your own review budget of 250,000 diff characters for the last 7 days. You can request another review in 22 hours and 54 minutes by commenting |
|
READY (holder, manual landing) at 122bacc Lane: openxfactory-4 (openXfactory-4-openDox_extraction) Holder, at READY: T021 (U-2, the gate console's schemas), lane openXfactory-3's READY-CANDIDATE
|
|
Lane: openxfactory-4 (openXfactory-4-openDox_extraction) LANDED — lane openxfactory-4, 2026-10-06T19:07:21Z, PR #40 → f70bc9c (opensoft/openXdox-code main; plain gate) T021 landed with Copilot r4199337845 an ACCEPTED LIMIT under 5988818366 (test precision on jsonschema's cyclic failure mode); its one-line remedy follows as a lane 3 follow-on. Queued openXdox-code PRs merge main next (strict ruleset). |
Lane: openXfactory-3 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Lane: openXfactory-3 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…to T022 T020 (#39 c6d15b2), T024 (#38 8b64fae), T023 (#42 d45a093) and T021 (#40 f70bc9c) have landed. This branch carried T021's ecf0ba9 versions of src/openxdox/gate_console.py and tests/test_gate_console_schema_source.py, and main holds T021's final squash. Both conflicts are resolved to main's side, since neither file is T022's. Against main the branch now differs only in T022's three files: .gitignore, scripts/composed_placements.py and tests/test_composed_placements.py. Lane: openXfactory-3 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
T021 landed as f70bc9c, the squash of #40, which this branch carried. Its two files take main's side whole (src/openxdox/gate_console.py, tests/test_gate_console_schema_source.py), and tests/conftest.py is main's file plus T095's two hunks, byte for byte as at c2192e3. Lane: openXfactory-3 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Lane: openXfactory-3 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…span kind (plan 038, H-2) (#43) Lane: openxfactory-4 (openXfactory-4-openDox_extraction) Authored by lane openXfactory-3, delegated by lane openxfactory-4 (#656 6015602254) Arc: neutral-product-standalone-operability ## T026, U-7: the allow-list for 12.5's governed suites, with R-1 (a)'s span kind (plan 038) The After set has landed: - T020 (#39, at c6d15b2). This branch is up to date with `main` f70bc9c, which also holds T024 (#38), T023 (#42) and T021 (#40). `main` was merged in at e6d99691, 3226d5c, 9e0979c and 9c29855, never by rebase; - T004 (opensoft/openxFactory#1245, at 260234b5); - T005's batch Q (opensoft/openxFactory#1248, at 91e961a0), which gives 12.5's oracle call `--chains`. openxFactory `main`'s `add-neutral-product-standalone-operability/tasks.md:3055` now reads `python3 scripts/protected_suites.py --chains --landings="$(cat "$W/x-arc.txt")" --suites="$(cat "$W/governed.txt")"`. - **Realizes**: 12.5 (part). - **Falsifier** (tasks.md): the oracle prints `ok: … each entered and holding`; the nodes, composed, quoted. - **Ruled**: - R2Q8, CF-4 (H-2), R-1 and W-1, with plan 038 by Brett Heap, everything as recommended (opensoft/openxFactory#656 comment 6013547504). - Brett's ruling on this task's four points (comment 6016648451, every answer the recommended one): 1. 12.5's call takes `--chains`, and T026 lands as one PR. 2. and 3. R-1 (a)'s spans widen to six, the helpers hand each harness the SERVED display, and T020's host registration covers `tests/test_staging_workbench.py`, edited by T026 in T020's two files once #39 landed. 4. T026's Files gain `tests/test_protected_suite_check.py`. - The holder respelled the 'cluster' node to `group neighbourhood`, as measured. ### What this PR does 32 entries are appended to `tests/protected_suite_respellings.yaml`, giving 51 in all. Each is one edit inside its named test or span, with its exact old and new text, and the entries for each suite are chained by blob. | suite | entries | what | |---|---|---| | `test_session_gates.py` | 3 | CF-4: `cmd_gate_*` read from `openxdox.cli_gate` | | `test_session_verbs.py` | 5 | CF-4: `cmd_gate_*` 4, and Group W `openxdox.generator.live_topic_health` | | `test_session_confinement.py` | 3 | CF-4: `serve_projection.hosted_index` | | `test_doxbench_share.py` | 2 | CF-4: each swept module at its one home; the record schema read through `openxdox.contracts.verified_bytes`, digest-checked against `copies.yaml` (Copilot's finding) | | `test_staging_workbench.py` | 19 | see below | The 19 staging entries, in chain order (allow-list entries 33 to 51): - **33**: the 'cluster' node, respelled to `group neighbourhood`. - **34, 37, 38 and 39**: CF-4 Group S2, the four slice-S5 spellings of the create and session columns. - **35 and 36**: R-1 (a)'s two in-test relocations, `:1291` and `:1333`. Each route is one `export const …_ROUTE` of `swb-session.js`, and neither the view nor the model names one. - **40 to 49**, R-1 (a)'s six spans (seven `span:` entries) and three in-test respellings: - `_CREATE_HARNESS` (two entries), `_SESSION_HARNESS` and `_HOSTILE_HARNESS`: each imports its route from the column RULED Q3 moved it to (`CREATE_ROUTE` from `swb-create.js`, `sessionRoute` from `swb-session.js`) and the reader from `display.js`. Each installs `setDisplay(readDisplay({ display: cases.display }))` before anything derives, as the shell does. `BRAINSTORM_AREA` and `STAGING_AREA` become the model's `capturedArea()` and `organizedArea()`. - `_run_create`, `_run_session` and `_hostile_descriptors`: each copies the sibling files its harness imports (and what they import), with a `package.json` that makes node read them as modules, as `test_session_confinement.py`'s harness does. Each hands its harness `/capabilities["display"]`, by the statement `serve.build_server()` makes, replayed as `test_gate_loop_views.py`'s `_served_display` replays it. - Three in-test respellings, **flagged below**. - **50 and 51**: the docstrings of the two relocated tests now say what their assertions hold (Copilot's findings). **`scripts/protected_suites.py`, R-1 (a)'s span kind.** An `admitted` entry may name, in place of `test`, a `span`: one module-level constant, or one module-level helper function (its decorators included). The entry's `old` text must lie inside that statement before the landing, and its `new` text inside it at the landing. Three further rules apply: - **Counted fail-closed.** The span's name must have exactly one binding in the module, counting every binding form in every scope: assignment, augmented or annotated assignment, `for` or `with` target, walrus, `del`, `def` or `class`, an import or a star import, an `except … as` alias, a `match` capture, a type parameter and a parameter. - **Alone on its lines.** No other statement may share a line with the span. - **Never a span:** a class, an import, a test, two targets, an annotation alone, or an added name. Only an admitted entry may name a span. The module's usage block and help now give 12.5's call with `--chains`, as batch Q has it. **`tests/test_protected_suite_check.py`**: 33 span cases (87 in the module). #### FLAGGED for the holder's word: three in-test respellings beyond the plan's 17 These are CF-4's kind. T026's measurement reported them at comment 6016415390 (point 2), and comment 6016648451 ruled that point. They close the last three of the suite's nodes, which the served display alone does not reach. Each is entered as a `respelling` naming that history, and each stands or falls on your word: 1. `test_gate_off_descriptor_is_the_real_cli_invocation`: `cli.cmd_gate_create_document` becomes `cli_gate.cmd_gate_create_document`, imported in the test from `openxdox.cli_gate`. The old spelling raised `AttributeError`. 2. `test_gate_off_session_affordances_are_the_real_cli_invocations`: `cli.cmd_gate_edit_document`, `cmd_gate_open_pr` and `cmd_gate_abandon_session` become `cli_gate.…`, likewise. 3. `test_the_notebook_refresh_is_a_descriptor_in_BOTH_gate_postures`: `REPO_ROOT / "scripts" / "sync-notebooklm-books.py"` becomes the one directory on the import path that holds the script, which is openxFactory's `scripts/` in the composed run (R1Q23 (a)). A run that finds it in none, or in more than one, fails. The old path raised `FileNotFoundError`. No assertion is weakened in any of the three, and every argument and flag assertion stays. ### T020's two files, edited once #39 landed (commit 242889b) T020's registration has to cover `tests/test_staging_workbench.py`. Without it every harness helper refuses `ProfileNotRegistered`, as measured below. The edit is to T020's `tests/conftest.py` and to the scan in `tests/test_host_plane.py`: - `"test_staging_workbench.py"` joins `GOVERNED_HOST_SUITES`, with a comment giving the reason, kept as a block of its own apart from T021's lines; - the scan also recognises a `gate` verb spelled as a command line (`_GATE_COMMAND = re.compile(r"""["']gate ([a-z][a-z-]*)""")`), the way this suite's descriptors spell it. Measured over the 16 governed suites, only `test_staging_workbench.py` matches it and not the argument-vector spelling. T021 (#40) also edits `tests/conftest.py`. Whichever of the two lands second merges `main`. ### The falsifier **The oracle, in 12.5's call as `main` now has it (`--chains`), at a simulated squash landing of this head.** The landing is one local commit on `main` f70bc9c carrying exactly this branch's tree and the `Arc:` trailer, and it is never pushed. The inputs are 12.5's own: - the governed set by its `git grep`: 16, the same 16; - the `Arc:` landings since `e28930bf`: 14, T020's, T024's, T023's and T021's among them. ``` admitted: c41063d tests/test_gate_loop_views.py, by entry 1 of tests/protected_suite_respellings.yaml admitted: 839492d tests/test_gate_loop_views.py, by entry 2 of tests/protected_suite_respellings.yaml admitted: 56e1c23 tests/test_branch_session.py, by entry 18 of tests/protected_suite_respellings.yaml admitted: 2f7003912aa7 tests/test_doxbench_share.py, by entries 31, 32, in that order, of tests/protected_suite_respellings.yaml admitted: 2f7003912aa7 tests/test_session_confinement.py, by entries 28, 29, 30, in that order, of tests/protected_suite_respellings.yaml admitted: 2f7003912aa7 tests/test_session_gates.py, by entries 20, 21, 22, in that order, of tests/protected_suite_respellings.yaml admitted: 2f7003912aa7 tests/test_session_verbs.py, by entries 23, 24, 25, 26, 27, in that order, of tests/protected_suite_respellings.yaml admitted: 2f7003912aa7 tests/test_staging_workbench.py, by entries 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, in that order, of tests/protected_suite_respellings.yaml ok: 8 protected edit(s), each entered and holding ``` rc=0. In the pre-batch-Q form, which passes no `--chains`, a landing of this branch reads `FAIL: the arc edited protected suites outside the reviewed allow-list: tests/test_doxbench_share.py, …` with rc=1. That is why batch Q was ruled. **The oracle on mutants of the real list** (`--chains`), each refused: ``` A a chain step's before_blob broken: rc=2 FAIL: tests/protected_suite_respellings.yaml breaks its own rules, so nothing is subtracted: entry 44: its before_blob is not the after_blob of the entry before it for tests/test_staging_workbench.py, so the two do not chain B the last staging step dropped: rc=1 refused: f7ebecbcc37e tests/test_staging_workbench.py: entries 33-50: tests/test_staging_workbench.py at the landing is 66d16b7, not the chain's 54852fa5250b2c4d0630088154aa4bac6517bffe; entries 3… FAIL: the arc edited protected suites outside the reviewed allow-list: tests/test_staging_workbench.py C a span entry names another span: rc=1 refused: f7ebecbcc37e tests/test_staging_workbench.py: entries 33-51: step 10: the entry's old text is not inside _run_session before the landing; entries 34-51: tests/test_staging_workbench.py before the landing is 655afc8… FAIL: the arc edited protected suites outside the reviewed allow-list: tests/test_staging_workbench.py D a span entry names a test instead: rc=1 refused: f7ebecbcc37e tests/test_staging_workbench.py: entries 33-51: step 10: the entry's old text is not inside test_toggle_keyword_is_order_preserving_and_idempotent before the landing; entries 34-51: tests/test_staging_workbe… FAIL: the arc edited protected suites outside the reviewed allow-list: tests/test_staging_workbench.py E a respelling entry names a span: rc=2 FAIL: tests/protected_suite_respellings.yaml breaks its own rules, so nothing is subtracted: entry 43: names a span, and only an admitted entry may (R-1 (a)); a respelling entry names its test ``` **R-1 (a)'s span guards: 22 mutants** of `scripts/protected_suites.py`, each run against the committed `tests/test_protected_suite_check.py`. 21 are killed. M4 survives because it is equivalent: a nested binder also lies inside its compound statement's lines, so the shared-line guard refuses it too. M22, which removes both guards, is killed. ``` M1 a respelling may name a span: killed (first: test_a_span_entry_that_breaks_the_rules_subtracts_nothing[respelling]) M2 a span may be spelled as a test: killed (first: test_a_span_entry_that_breaks_the_rules_subtracts_nothing[a-test]) M3 a name bound twice may be a span: killed (first: test_what_is_never_a_span_is_refused[shadowed]) M4 a block-only binding may be a span: SURVIVED M5 a del is not a binding: killed (first: test_a_second_binding_in_any_form_or_scope_is_never_a_span[del]) M6 an except alias is not a binding: killed (first: test_a_second_binding_in_any_form_or_scope_is_never_a_span[except-alias]) M7 a match capture is not a binding: killed (first: test_a_second_binding_in_any_form_or_scope_is_never_a_span[match-capture]) M8 a star import is not a binding: killed (first: test_a_second_binding_in_any_form_or_scope_is_never_a_span[star-import]) M9 a parameter is not a binding: killed (first: test_a_second_binding_in_any_form_or_scope_is_never_a_span[parameter]) M10 a class may be a span: killed (first: test_what_is_never_a_span_is_refused[class]) M11 a helper function is never a span: killed (first: test_an_edit_inside_a_named_helper_function_is_admitted) M12 two targets may be a span: killed (first: test_what_is_never_a_span_is_refused[two-targets]) M13 an annotation alone may be a span: killed (first: test_what_is_never_a_span_is_refused[annotation-only]) M14 a helper's decorators lie outside it: killed (first: test_a_helpers_decorator_is_inside_its_span) M15 a shared line is allowed: killed (first: test_a_span_that_shares_a_line_with_another_statement_is_refused) M16 old outside the span is not checked: killed (first: test_an_old_text_outside_the_span_is_refused) M17 new outside the span is not checked: killed (first: test_a_new_text_that_leaves_the_span_is_refused) M18 an added span is not refused: killed (first: test_what_is_never_a_span_is_refused[class]) M19 the span's first line is outside it: killed (first: test_an_edit_spanning_a_whole_named_constant_is_admitted) M20 a span entry keeps the test key: killed (first: test_this_repositorys_allow_list_keeps_its_rules) M21 span entries take the test path: killed (first: test_an_edit_spanning_a_whole_named_constant_is_admitted) M22 M4 and M15 together: killed (first: test_what_is_never_a_span_is_refused[block-only]) M0 unmutated: 87 passed in 9.05s ``` **The served display, measured in the test process** (Brett's (2)+(3): "INFERRED … MEASURE it"). This was an uncommitted probe test, run composed at 3eead0fe (242889b on the integration of that time; the helpers' statement is unchanged since): - with no host registered, the helpers' statement refuses `ProfileNotRegistered`; - under the governed host (`OpenxFactoryProfile`), it reads `host_facet: declared` and `host_profile: openxfactory-engineering`, with areas `captured: ideation/brainstorm/` and `organized: ideation/staging/`; - that result equals the `display` of a live `GET /capabilities` from `serve.build_server()` under the same registration: `EQUAL: True`, 3163 bytes of sorted JSON. **The nodes, composed**, in R1Q23 (a)'s form: - openxFactory `main` 92010d3e, with its submodules initialized recursively and its `scripts/` on `PYTHONPATH`; - openDox-code#91 45f5c55d installed (W-1 (A)); - T022's runbook placement linked (its script, #41), the colour variables unset and `TERM=dumb`; - each suite run alone; - openXdox-code `main` f70bc9c (T020, T021, T023 and T024 landed) as the base, and this head merged onto it on a local branch that is never pushed (0478b5f1). | run | 16 governed suites | `test_host_plane.py` | `test_protected_suite_check.py` | |---|---|---|---| | without this PR (`main` f70bc9c) | 842 nodes: **800 passed, 42 red** (staging 29, verbs 5, confinement 3, gates 3, share 2) | 9 passed | 54 passed | | with this head 9c29855 (0478b5f1) | 842 nodes: **842 passed** | 9 passed | 87 passed | | earlier, this PR WITHOUT the edit to T020's files (at 856047a, on `main` 56e1c23 with #41 and #42 merged) | `test_staging_workbench.py`: 37 failed, 46 passed, every failure `ProfileNotRegistered` | 9 passed | n/a | 42 nodes go red to green, none goes green to red, and none is still red. ### CI at the head 9c29855 - **Checks**: `validate` success (run 37517336710), SonarCloud success, `copilot-pull-request-reviewer` success. - **CI's own pytest step, run alone** (CI's install, openDox at its pin dede32b4): `selected=1146 passed=1142 skipped=4 failures=0 errors=0`, against the floors `MIN_SELECTED 1099` and `MIN_PASSED 1095` and `EXPECT_SKIPPED 4`. The holder ruled (lane log, 13:57Z) that T029 raises the floors once, and T020 to T026 leave them. - **Copilot**: reviewed at 9c29855, "Needs a closer look" (a governance-sensitive validator and a large chained allow-list), **Findings: None**. 9 threads over its reviews, each answered with its fixing commit and resolved; 0 unresolved. ### Files changed `scripts/protected_suites.py`, `tests/protected_suite_respellings.yaml`, `tests/test_session_gates.py`, `tests/test_session_verbs.py`, `tests/test_session_confinement.py`, `tests/test_doxbench_share.py`, `tests/test_staging_workbench.py` (T026's seven), plus three files the ruling at comment 6016648451 adds to T026's Files: - `tests/test_protected_suite_check.py`, the holder's answer to point 4; - T020's `tests/conftest.py` and `tests/test_host_plane.py`, Brett's points 2 and 3, edited once #39 landed. ### What remains, and its trigger - **The holder's word on the three flagged respellings.** Nothing else in this PR waits. - If `main` moves before landing, the lander merges it, never by rebase. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Arc: neutral-product-standalone-operability Lane: openxfactory-4 (openXfactory-4-openDox_extraction) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…38, T094's R2-INV-R9) (#46) Lane: openxfactory-4 (openXfactory-4-openDox_extraction) Authored by lane openXfactory-3, delegated by lane openxfactory-4 (#656 6021862323) Arc: neutral-product-standalone-operability ## T095: the composed harness for the declared integration tests (plan 038; T094's map R2-INV-R9) **Task.** T094's slice T095 (R2-INV-R9, "Slice outline"): the composed conftest. It owns `tests/conftest.py` (chain T020 → T021 → **T095** → T073 → T074) and `tests/test_host_plane.py` (T020 → **T095**). Lane openxfactory-4 records the slice in plan 038's `tasks.md` after T002 (#1251). Until then the contract is the outline row plus the holder's rulings below. After: T021 (#40), LANDED as `f70bc9c3`. **Realizes.** HR, SR, CS and LI from the map, in `tests/conftest.py`, with their guard in `tests/test_host_plane.py`. It completes requirement 9's declared composed tests (T073, F9.1), so this PR carries the arc's line. **Falsifier.** The 119 nodes the outline names (HR 105, HR+CS 4, CS 5, SR 1, LI 4), run composed, each file alone, must be red at the base and green at the head. 12.5's 16 governed suites, F5.2's 5 protected suites and `test_column_contributions_governed.py` (9 passed) must read unchanged, and `tests/test_declared_exclusion.py` must be green lone. **Ruled.** - openxFactory#656 comment 6021830531 (T094's rulings): OQ-R9-1, OQ-R9-2 and OQ-R9-4 at their defaults, and W2 (extend T086's Q4 (a) local-install fixture to `test_renderer.py` and `test_repo_root_guard.py`). - openxFactory#656 comment 6022291206, the holder CONFIRMED T095's two departures from OQ-R9-1's default: 1. T020's `plane_host_for` stays untouched. A separate autouse fixture registers the declared host inside T020's stand-in plane. 2. `DECLARED_HOST_SUITES` (17) is guarded STRUCTURALLY: declared entries outside 12.5's set, and never `test_column_contributions_governed.py`. No text scan selects exactly the 17. **Decisions.** - **Every piece is composed-only.** Where `opendox_host` is absent, which is every lone checkout, nothing is registered, built or set. So a lone run, and the evidence `tests/test_declared_exclusion.py` holds each listed file to, are unchanged. - **The edits are self-contained.** No T020, T021, T026 or T073 line is touched. The new block is appended after T021's. T086's launch-suite fixture changes in one line (W2). ### The interface (tests/conftest.py) - **HR:** - `DECLARED_HOST_SUITES`, the 17 declared files; - `declared_host_for(suite)`; - the autouse fixture `_the_governed_host_for_the_declared_host_suites`. It requests T020's stand-in fixture, so the governed host (`governed_host()`) is the one registered for the test. - **SR:** `HOST_SEAM_SUITES`, `HEALTH_CHECK_SEAM`, `BINDING_TRUST_SEAM`, `HOST_SEAM_HOLDERS`, `openxfactory_seams(wanted)` and `a_hosts_seam(module, call, what)`. - `test_authoring_agent.py` gets openxFactory's scoped health check, and `test_doxchat_model_intake.py` its binding-trust policy. - Both are read off `opendox_host.seams()` with `sys.path` put back, and an `openxdox` module from elsewhere refuses the case. - Each seam is put back after the test: a host's earlier registration again, openDox's default not. - **CS:** `CONTRACTS_DIR`, a `Path` to `<farm>/contracts`, or None lone. It is built once at import, outside the checkout (a temporary directory removed at exit, and a temporary root inside the checkout is refused before anything is created), and exported as the `CONTRACTS_DIR` environment variable. The farm holds: - `contracts/schemas/`, 55 links: each carve row under `contracts/schemas/` through `carved_reach.sources_under`, then openxFactory's own; - `examples/ideation-dashboard/` beside it, 139 links: the three owners' YAML examples, less openDox's `opendox-snapshot-*`. Two owners naming one path is refused. The builders are `build_contracts_farm()` and `compose_the_contracts_farm()`. The reach must be the composed tree's own. - **OQ-R9-4:** `openxfactory_root()`, the directory above `opendox_host.py`, or None lone. - **LI (W2):** `LOCAL_INSTALL_SUITES` (`test_renderer.py`, `test_repo_root_guard.py`) and `runs_a_local_install(suite)`. T086's fixture now reads `runs_a_local_install`: the launch suite everywhere, as before, and the two suites only composed. ### The falsifier, measured The composed form is R1Q23 (a): - openXdox-code: the base is `main` `f70bc9c3` (T021 landed), and the head is this branch; - openDox installed from openDox-code `main` `84f8ed83` (T025); - openxFactory at `92010d3e` (#44's `tests/composed_host_pin.yaml`), with `openDox`, `openXdox` and `openXwallet` initialized recursively and its `scripts/` on `PYTHONPATH`; - T022's one placement (the runbook link) made before each run and removed after; - the colour variables unset, and `TERM=dumb`. Each of the 66 declared entries ran alone, four at a time: `python -m pytest -q -p no:cacheprovider -rfE --junitxml=<file>.xml <file> < /dev/null`. ``` base f70bc9c: TOTAL over 66 files: 2001 passed, 496 failed, 72 errors, 2 skipped; 568 red head c5d07c6: TOTAL over 66 files: 2120 passed, 377 failed, 72 errors, 2 skipped; 449 red node for node (2571 nodes at both): the 119: red -> passed 119 (HR 105, HR+CS 4, CS 5, SR 1, LI 4) green at base, red at head: 0 any other node red -> passed: 0 head c6982e3 (adds only the in-checkout refusal and its case): the 17 falsifier files again, each alone: the 119 passed 119 of 119 ``` **Unchanged, each line the same at base and head:** ``` 12.5's 16: tests/test_branch_session.py: 132 passed, 0 failed tests/test_doxbench_mutation_boundary.py: 80 passed, 0 failed tests/test_doxbench_share.py: 32 passed, 2 failed tests/test_gate_loop_views.py: 74 passed, 0 failed tests/test_gateway_provenance.py: 24 passed, 0 failed tests/test_hosted_actor.py: 5 passed, 0 failed tests/test_session_commits.py: 22 passed, 0 failed tests/test_session_confinement.py: 50 passed, 3 failed tests/test_session_gates.py: 78 passed, 3 failed tests/test_session_lifecycle.py: 53 passed, 0 failed tests/test_session_notebook.py: 39 passed, 0 failed tests/test_session_records.py: 29 passed, 0 failed tests/test_session_runbook.py: 11 passed, 0 failed tests/test_session_transaction.py: 57 passed, 0 failed tests/test_session_verbs.py: 60 passed, 5 failed tests/test_staging_workbench.py: 54 passed, 29 failed F5.2's 5: tests/test_generator.py: 45 passed tests/test_session_snapshot.py: 23 passed tests/test_snapshot_determinism.py: 6 passed tests/test_snapshot_registry.py: 40 passed tests/test_snapshot_validation_launch.py: 9 passed tests/test_column_contributions_governed.py: 9 passed, 0 failed (OQ-R9-1: off the declared-host list) ``` The governed reds are 12.5's own, node for node the same at both ends. T026 and the rulings own them. **Lone:** - `tests/test_declared_exclusion.py` in validate.yml's environment, the CI form: 207 passed. - `tests/test_host_plane.py`: 28 passed lone and 28 composed at the head (T020's 9, plus 19 new); 9 composed at the base. - The CI form of the whole suite at `c5d07c6b`: `selected=1131 passed=1127 skipped=4 failures=0 errors=0`. `c6982e34` adds one case. The floors are 1099 and 1095, and skipped is exactly 4. The floors are left alone, since only T029 raises them. **Mutants: 35 of 35 killed.** Each is one exact replacement in a scratch clone, then restored. - 33 were run at `70ed40c6`: - 30 are killed by `tests/test_host_plane.py` run lone: - **HR:** the composed condition dropped; membership dropped; the stand-in dependency dropped; `test_column_contributions_governed.py`, a governed suite, or an undeclared file added; the wrong parent; the fixture's check inverted. - **SR:** `sys.path` not put back; the elsewhere-refusal dropped; the exactly-one-row check weakened; the lone guard dropped; a host's earlier registration not restored; openDox's default restored; no unregister first; a governed suite added. - **CS:** `opendox-snapshot-*` kept; nested carve rows linked; a collision passed; openxFactory's own schemas dropped; examples not walked recursively; the environment not set; the reach check dropped; no removal at exit; `under` ignored; the lone guard dropped. - **LI:** the composed condition dropped; the launch suite dropped; T086's condition reverted; a non-`generate-and-open` suite added. - 3 are killed by a named node of the composed run: the seam fixture inverted; `CONTRACTS_DIR = None`; the declared host never registered. - The 30 lone mutants were re-run at `c2192e3e`, and 10 were spot-checked at `c5d07c6b`. - 2 more at `c6982e34`: the in-checkout check dropped, and TMPDIR ignored. ### Review - **CI at `c6982e34`:** `validate` passed (run 37540071853); SonarCloud's Quality Gate passed with 0 new issues. At `70ed40c6` SonarCloud read 5 smells in `tests/test_host_plane.py`, which `c2192e3e` fixed. Its sixth was in T021's file, now on `main`. - **Copilot:** - At `c5d07c6b`: "Changes recommended", 1 finding, r4200978505 (a temporary root inside the checkout would build the farm in the working tree). FIX-NOW: `c6982e34` refuses it before anything is created, with a new case and 2 mutants. The thread is answered and resolved. - At `c6982e34`: COMMENTED, "Findings: None". - Threads: 1, unresolved 0. - At `c2192e3e` an earlier review listed one "previously missed" item in T021's `src/openxdox/gate_console.py`, which is on `main` and outside this PR's diff. ### Files changed - `tests/conftest.py` - `tests/test_host_plane.py` ### Not done - Nothing in this slice. The other slices' nodes in these files stay red until those slices land: T096 to T103 code against this interface, and T104 rides the openDox pin. - The EN node, `test_repo_root_guard.py`'s refusal-path case, is red under a local TMPDIR in the home directory only, and green on a runner. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Arc: neutral-product-standalone-operability Lane: openxfactory-4 (openXfactory-4-openDox_extraction) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Brings T024 (#38), T023 (#42), T021 (#40), T026 (#43) and T095 (#46). In tests/conftest.py, T095's composed harness and T073's rail block were both appended after T021's lines. Both are kept, T095's as main has it and T073's after it, so this branch's diff against main is a pure append there. Lane: openXfactory-3 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…; T094's map R2-INV-R9) (#48) Lane: openxfactory-4 (openXfactory-4-openDox_extraction) Authored by lane openXfactory-3, delegated by lane openxfactory-4 (#656 6021862323) Arc: neutral-product-standalone-operability ## T101 (plan 038): the doxBench texts One of the R9 repair slices, T095 to T104, which repair every composed red outside 12.5's set in this leg's declared exclusion, so that T073's composed step 5a goes green (requirement 9's declared composed tests, T073, F9.1). **Contract.** There is no `tasks.md` entry yet: lane 4 records T095 to T104 after T002 (#1251). The contract is T101's row in T094's map, `R2-INV-R9.md` § "Slice outline" (#656 `6021830607`), plus the holder's rulings: - `6021830531`: W5, drop the manifest home; W6, rewrite over both owners. - `6022291206`: T101's Files gain `tests/declared_exclusion.yaml` for one entry only, `test_doxbench_blank_reason.py`. - **Realizes:** T094's row T101: PP 25 (threads 21, thread_wiring 2, knowledge_service 2), CP 6 (thread_wiring 1, transport 1, request_handling 1, doxchat 3), CS constants 5 (knowledge_service 2, doxchat 3) and HH 1, so 37 in all; plus 1 on W5 and 1 on W6. - **Falsifier:** these files composed, each file alone, red at the base and green at the head; plus the unchanged set. - **After:** T095 (#46) for `CONTRACTS_DIR` and `openxfactory_root()`, LANDED as `e2340bb6`; T021 (#40), LANDED as `f70bc9c3`. Met. ## What changes (test text only; no `src/` change) | means | where | what | |---|---|---| | PP | `test_doxbench_threads.py` (21), `test_doxbench_thread_wiring.py` (2), `test_doxbench_knowledge_service.py` (2) | Pre-carve `scripts/ideation_dashboard/` reads respelled to their carved homes, through the conftest's `carved_module_path` (plan 034 T040's resolver). The push sweep also reads its three openxFactory-only modules (`nightly_lane.py`, `doxbench_status_exemption.py`, `serve_openxfactory_lanes.py`) from the composed tree, `openxfactory_root()`. Each swept module is read from **exactly one** of its three homes; a module in two or none refuses the sweep by name. | | CP | `test_doxbench_thread_wiring.py` | Liveness is now asked through the scope seam (T084, openDox-code `e49b17c`): `self._is_live_session_ref(` → `column_seams.scope.current().is_live_session_ref(`. openXdox's registration there IS `doxbench_scope` (`column_contributions.SCOPE is doxbench_scope`). `live_session_branches(` stays absent. | | CP | `test_doxbench_request_handling.py` | `compute_capabilities` for the local human is asked with this leg's own gate routes (`GateRoutesExtension().routes()`), since T084 makes `gate` true only where a contributed binding answers `POST /actions/gate/<verb>`. The pinned dict is unchanged. | | CP | `test_doxbench_transport.py` | The save seam is re-pinned at its rulings' placements. Q3 (`5642758731`): the route is in `swb-session.js`, not openDox's model. Q10 (`5648065587`), S5 (openDox-code `8efb3cf`): no static import in `app.js`; one `.session.firstEditTransport(` call site with the `refusalTransport()` fallback; and `firstEditTransport` declared in the `gate.workbench.session` binding's `exports` (`VIEW_BINDING_SPECS`). This was the map's one INFERRED node, now measured. | | CP | `test_doxchat_model_intake.py` (3) | Both auth-kind pins read the broker-enrolled kinds, every member but `none` (T080, openDox-code `1130e99`; #1144 16.3). The approval case, after approving, reads the model listed and NOT yet available. It then records this machine's trust as `opendox model-binding trust` does (`doxbench_trust.recording_for`, through the case's own `stand_in_host_trust`), and reads a fresh catalog in which the model is available (T100, openDox-code `38d3350`; #1144 16.3a). A factory takes its verdict when it is built, so each catalog comes from a factory built at that moment. | | CS | `test_doxbench_knowledge_service.py` (2), `test_doxchat_model_intake.py` (3) | The chat-turn and gate-action-record schemas are read from T095's farm, `CONTRACTS_DIR`. | | HH | `test_doxbench_transport.py` | The raw client sends ONE `Host` line (`skip_host=True`). openDox's loopback Host guard (openDox-code `390e2c2`) refuses a second one as `403 invalid_host`. | | W5 | `test_doxbench_blank_reason.py` | The manifest home is dropped; the rule stays stated in its other three homes. | | W5 | `tests/declared_exclusion.yaml` | One entry: `test_doxbench_blank_reason.py` → `reasons: [doc_health]`, and its note is dropped. `count` stays 66, and `openxfactory-contracts` keeps its four contracts-only entries. That reason carries no `only` list at `main`, so nothing moves there. | | W6 | `test_doxchat_model_intake.py` | The v1.45 release test is read over both owners in the composed tree. Each row is in its owner's manifest and in no other of the three: `gate-action-record` in the openXdox root's, `xfactory-workbench-chat-turn` in the openDox root's. `release_member` and `contract_schema_version: 1` hold, and the digest is recomputed from the spec leg's bytes. The immutable half, the CHANGELOG entry and the digest inventory, is read from openxFactory's tree. The two `consumption_rule` asserts left with openxFactory's rows at contract-v4.0. | ## Falsifier (measured; quoted) **Composed form, R1Q23 (a):** - openxFactory `92010d3e` (`tests/composed_host_pin.yaml` in #44), with `openDox`, `openXdox` and `openXwallet` initialized recursively and its `scripts/` on `PYTHONPATH`. - openDox installed from openDox-code `main` `84f8ed83`. - The colour variables unset, and `TERM=dumb`. - Each file alone: `python -m pytest -q -p no:cacheprovider -rfE --junitxml=… tests/<file>` from the checkout root. - T022's placement is immaterial here: it places only the session runbook, which none of these files reads. **Base:** `main` `e2340bb6` (T095 landed). **Head:** `6f157db7`, this PR: one change commit, the Copilot fix `2fdbd96`, and merges of `main`. Measured 23:34Z to 23:43Z. | file | base | head | |---|---|---| | `test_doxbench_threads.py` | 78 passed, **21 failed** | 99 passed, 0 failed | | `test_doxbench_thread_wiring.py` | 47 passed, **3 failed** | 50 passed, 0 failed | | `test_doxbench_knowledge_service.py` | 47 passed, **4 failed**, 1 skipped | 51 passed, 0 failed, 1 skipped | | `test_doxbench_transport.py` | 27 passed, **2 failed**, 1 skipped | 29 passed, 0 failed, 1 skipped | | `test_doxbench_request_handling.py` | 45 passed, **1 failed** | 46 passed, 0 failed | | `test_doxchat_model_intake.py` | 36 passed, **7 failed** | 43 passed, 0 failed | | `test_doxbench_blank_reason.py` | 119 passed, **1 failed** | 120 passed, 0 failed | - **39 nodes go red → green:** the row's 37, plus W5 1 and W6 1. **0 nodes turn red.** - Earlier, measured at `main` alone (`8b64fae0`, before T095), the main-measurable part read 49 → 17 over these files. The 17 were T095's HR 7, T021's 3, CS 5 and the approval node. - **The approval node does not need T095's binding-trust seam.** Over a scratch integration of this change with #40 and #46, with `HOST_SEAM_SUITES` emptied by a throwaway plugin, it passes, and `test_doxchat_model_intake.py` reads 43 passed. Its own `stand_in_host_trust` carries the trust. **The unchanged set, base vs head, same form, node for node (0 newly red):** - **12.5's 16 governed suites** (`git grep -l -e open-pr -e open_pr -e FakePullRequests -- 'tests/test_*.py'`): identical per node. The reds that remain are other slices', and none is changed. - **F5.2's files** (`test_generator.py`, `test_snapshot*.py`, `test_session_snapshot.py`; 7 files): all green at both. - **`test_column_contributions_governed.py`:** 9 passed at both. - **Over the 31 files:** 49 red at the base, 10 at the head (all in 12.5's governed suites, unchanged), 39 fixed (all in T101's seven), 0 newly red. **Lone, `tests/test_declared_exclusion.py`** (CI form: `pip install -e ".[test]"`, openDox `dede32b4` from the pin), at `6f157db7`: **207 cases, 0 red**, inside the whole-suite run below. The six other files still fail lone at collection on `doc_health`, unchanged. ## Mutants (14 of 14 killed) Measured over a scratch integration of this change with #40 and #46. Each fault was planted in the subject, its node run composed over the integration, and everything restored and digest-checked. - **Push sweep:** a push token in openxFactory's `nightly_lane.py` → `('nightly_lane.py', 'git push')`. `nightly_lane.py` planted at this leg's `src/openxdox/` too → "expected in exactly one of …". The resolver without openxFactory's home (test mutant) → the same. - **Liveness:** the serve asking `doxbench_scope` directly → the seam pin fails. A shape-valid copy of the question registered as the scope → the identity pin fails. - **Save seam:** a static `swb-session.js` import back in `app.js`; `firstEditTransport` dropped from the binding's `exports`; the route constant back in openDox's model; the `refusalTransport()` fallback gone. Each fails the test. - **W6:** a second manifest claiming `gate-action-record` → `('gate-action-record', ['openXdox', 'openDox'])`. A drifted byte in the spec leg's schema → `gate-action-record`. A drifted recorded digest → `xfactory-workbench-chat-turn`. - **Approval:** the catalog read off the pre-trust factory (test mutant) → `assert False is True`. The trust never recorded → the same. ## CI - **`validate` at `6f157db7`: success** (run 37548131461). SonarCloud: success. - CI form locally at `6f157db7`: selected=1165 passed=1161 skipped=4 failures=0 errors=0, above `validate.yml`'s floors 1099/1095/4. The floors are untouched. - **Copilot at `6f157db7`:** COMMENTED, "Needs a closer look", Findings: None. One thread in all, r4199422320 (Low, the release test's summary), fixed in `2fdbd96` and resolved: 0 unresolved. ## Files (8) `tests/test_doxbench_threads.py`, `tests/test_doxbench_thread_wiring.py`, `tests/test_doxbench_knowledge_service.py`, `tests/test_doxbench_transport.py`, `tests/test_doxbench_request_handling.py`, `tests/test_doxchat_model_intake.py`, `tests/test_doxbench_blank_reason.py`. The eighth, `tests/declared_exclusion.yaml`, is one entry only (holder `6022291206`). Its chain is now T101 → T073 → T074; T073 (#45) edits only the header and `reasons` block, a different hunk. None of these is in 12.5's computed 16 or in F5.2's set. ## Not done - Nothing owed by this slice. - **Left as is, stale but harmless:** `tests/test_declared_exclusion.py`'s docstring (`:28-30`) and comment (`:261`) still name `test_doxbench_blank_reason.py` as a two-reason entry, and `_CONTRACT_FILES` keeps the checkout's `contracts/manifest.yaml` with its evidence case. That file is not T101's. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Arc: neutral-product-standalone-operability Lane: openxfactory-4 (openXfactory-4-openDox_extraction) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…e identity response shape and the single-writer chains (plan 038) (#1257) Lane: openxfactory-4 (openXfactory-4-openDox_extraction) **T002, second bookkeeping batch, plan 038: T094 and the slice entries T095 to T104, the identity response shape, the single-writer chains, and the ticks.** Bookkeeping, so this PR carries no `Arc:` line. It touches no `openspec/changes/` path, so no Rule 6 window applies. It began stacked on #1251's head; #1251 has landed (`1837ea75`) and `main` is merged in (`543f6ce1`), so the diff against `main` is this batch alone. ## From plan 038's `tasks.md`, T002 - **Task**: T002, "Release-2 base, and the re-measure at the ruling" (landed in #1251; this is its second batch of bookkeeping, the brief of the holder's `T002b`). - **Realizes**: none (bookkeeping; FR-025). **Falsifier**: T002 names none. Its gates are the brief's, quoted under "Review and gates". - **Ruled / Decisions**: none of its own. Every item is a ruling already posted on `#656`, or a landing already merged, encoded as worded and cited by comment id or merge commit. ## The items | # | Where | What changed | Record | |---|---|---|---| | 1 | tasks.md: T094, T095 to T104, T073 | T094 ticked, with its result quoted: 436 composed reds over 51 files, outside 12.5's set (Base 2; 533 at Base 1). The `T095+` placeholder becomes ten entries, T095 to T104, each with its slice name, Files, After and Falsifier from R2-INV-R9's outline and the holder's rulings, `Lane: 3 (openXfactory-3)`. T073's After and Files name them. The map's means, the rulings (R9-R1 (a), R9-R2 (a), W1 to W7) and the OQ defaults are recorded once, above the entries | map `R2-INV-R9.md`; `6021830607`; claim `6021862323`; rulings `6021830531` | | 2 | T095, T101, T104, T097 | T095: the two confirmed departures from OQ-R9-1 (`plane_host_for` untouched, an autouse fixture registers the declared host; `DECLARED_HOST_SUITES` guarded structurally). T101: `tests/declared_exclusion.yaml` for ONE entry (reasons `[doc_health]`, note dropped, the `only` list loses the file, count stays 66). T104: `tests/test_binding_stylesheets.py`, the pin 572 to 574. T097: its served-display respelling is R9-R1 (a) applied | `6022291206`; the T097 line is `6023517122` (the holder confirmed the citation, `6026622117`) | | 3 | plan.md: the single-writer table; tasks.md Files and After lines | W7's chains: `tests/conftest.py` T020, then T021 and T026 in landing order, then T095, T073, T074; `tests/test_host_plane.py` T020, T026, T095; `declared_exclusion.yaml` T101, T073, T074; the four T074 test files T103 then T074; the census T025, T104, T015, T016, T057 (the row splits, so `test_web_boundary.py` keeps its own chain). T015's and T074's After gain T104 and T103 | `6021830531`, `6022291206` | | 4 | tasks.md: T072, T074, T075 falsifiers | The four folds of #1253, in the arc change's own wording: T072 asserts that ONLY CR, LF and CRLF split rows; T074's seam tests assert that an empty-seam read names the seam AND its registrar; T075's host-wiring test shows a leg declaring only SOME of the four seams refused, and a leg declaring the six and none of the four accepted; T074's protected-suite oracle computes 12.5's set as 12.5 does (`git grep -l -e 'open-pr' -e 'open_pr' -e 'FakePullRequests'`), from the pre-arc and the final tree, the floor of 16 on each, and takes the union | openxFactory#1253 to `fc4fa0ff`; Copilot `5427373153` | | 5 | spec.md FR-011; tasks.md T041 | FR-011's JSON shape gains `identity`, emitted and bounded as openDox-spec's finding schema bounds it (no `excerpt`, `text`, `content` or `quote` key, no number, strings of at most 200 characters, `{category, entry}` for a pathless finding, `{collided_id}` for a collision). T041's falsifier gains a response-shape test asserting exactly that bound. The coverage row for FR-011 names T041. The serialized-size cap was already T041's at the base (T041's text says so, and T042 "stores what T041 bounds"), so nothing moved | holder `6018624750` | | 6 | tasks.md ticks | T011, T014, T021, T023, T025, T026 and T060 ticked, T070's tick gains #1253, and T002's own landing is recorded. Each merge commit was verified with `gh pr view <n> -R <repo> --json mergeCommit` (listed below) | the landings | | 7 | tasks.md: T072 | A Files line (`src/opendox/lines.py`, `tests/test_lines.py`); the Ruled line gains `6023517122`: `head_sha` (a), its own public `head_sha` parity-tested against `serve._head_of`, `serve.py` untouched; the line split (a), `doxbench_defaults.py` untouched. Folding either private helper is recorded as unscheduled and optional, and is not scheduled | holder `6023517122` | | 8 | contracts/health-finding.md:91 | The sentence reads: a stored `identity` makes a cross-run collision between STORED findings detectable; an exception-only finding is never stored; whether the engine reports it remains T046's concern. Nothing else on the line changed | holder `6018624750`; Copilot `5433292692` | | 9 | plan.md; tasks.md | Copilot's three items at #1251's head `1eb8e9eb`, accepted by the holder at its landing: (a) the pin and landing-order row for T073 names T005 and T095 to T104 as prerequisites, and `T095+` reads T095 to T104 at the critical path, the lane table and the revision note; (b) the combined `conftest.py` and `test_host_plane.py` row splits, and the cross-lane summary gains T026 (and the other chains of item 3); (c) the same split in T020's, T021's and T026's Files lines | holder replies `4201056969`, `4201057311`, `4201057577`; READY note `6026651962` | ### The ticks, verified | task | pull request | merge commit | merged | |---|---|---|---| | T011 | opensoft/openDox-code#92 | `09b8d44a` | 2026-10-06T19:15:55Z | | T014 | opensoft/openDox-code#93 | `a7a1c4d3` | 2026-10-06T22:23:06Z | | T021 | opensoft/openXdox-code#40 | `f70bc9c3` | 2026-10-06T19:07:10Z | | T023 | opensoft/openXdox-code#42 | `d45a0939` | 2026-10-06T17:45:33Z | | T025 | opensoft/openDox-code#91 | `84f8ed83` | 2026-10-06T17:33:31Z | | T026 | opensoft/openXdox-code#43 | `36bbef79` | 2026-10-06T22:35:40Z | | T060 | opensoft/openDox#20, the tag `dox-v1.2` (tag object `58538ff3`, on `6a9f4902`), opensoft/openDox#21 | `6a9f4902`, `c7f75845` | 17:46:59Z, tag 17:58:45Z, 19:02:39Z | | T070 | openxFactory#1253 (T070b, added to its tick) | `fc4fa0ff` | 2026-10-06T18:56:42Z | | T002 | openxFactory#1251 (its own landing) | `1837ea75` | 2026-10-06T22:31:42Z | - **T021** landed with Copilot r4199337845 an ACCEPTED LIMIT (holder READY note `6023492215`); T021's entry records it. - **T025's** entry says its 34 nodes are "quoted at T029"; that stays T029's, and the entry says so. - **T011's** server node is T014's, as its own Falsifier says. **Skipped, because already present at the base:** T020's and T024's ticks (#1251 item 13) and T004's tick (#1245, `260234b5`), all confirmed with `git show <commit>:<tasks.md> | grep`. **T071 is NOT ticked.** Brett's word, "Ratify it (Recommended)" (`#656` `6023375303`), is recorded on T071's entry, but the change's ratification-record PR has not landed (no such PR is open or merged on `opensoft/openxFactory` at this writing). The box stays open. ## Readings the holder accepted (`6026622117`), disclosed as the brief requires - **W7 omits T026 from the openXdox-code chains.** The single-writer table keeps T026 where its own ruling (`6016648451`) puts it, and lane 3's claim (`6021862323`) says T026's PR lands before T095. So the conftest chain reads T020, then T021 and T026 in landing order, then T095, T073, T074; and `test_host_plane.py` reads T020, T026, T095, as W7 gives it with T026 added. - **R9-R2's last sentence** ("If the re-scoped test runs alone, it leaves the declaration, and the count moves with it, as the declaration's own rules require") is recorded in T100 as worded. `declared_exclusion.yaml` is NOT added to T100's Files: the holder's file list at `6022291206` names it for T101 only. - **Edits outside tasks.md**, ruled in scope as each item names its place: plan.md (items 3, 9), spec.md FR-011 (item 5) and `contracts/health-finding.md:91` (item 8). ## Other notes on where an item needed a reading - **Counting.** The task count reads 90 rows: the placeholder `T095+` is replaced by T095 to T104 (81 - 1 + 10). - **Plan 034's task numbers.** The entries cite plan 034's T103 (#80, in T099's `Host` header) and plan 034's T086 (W2, in the rulings) as "plan 034's", because plan 038 now has its own T103. - **OQ-R9-3.** The map did not measure the respelled gate-console and wheel-model skips; T097 and T103 say so, and the map's 127 passed and 5 skipped is quoted as the measurement. - **Counts of slices** are copied in the map's own form (for example "16 nodes, and +5 on R9-R1 (a)"), not summed. - **T095's chain after T026.** T095's After names T021 and T026, both landed. - **Not mirrored from the arc's 4.1:** T075's falsifier gains the some-of-four refusal and the six-and-none acceptance, which #1253 folded. The arc's two older refusals (part-way through the seams; at the column call) are not in T075, since #1253 did not add them. - **T046 and T057** carry no response-shape falsifier for `identity`: item 5 names T041's. ## Review and gates - **`python3 scripts/validate-openspec-cli-pin.py --all`**, in the clone named `openxFactory`, at `691648c6`: exit 0, `Totals: 113 passed, 1 failed (114 items)`, `0 UNDISPOSITIONED failures`. The one failure is the dispositioned `add-chain-attestation` finding, the same on `main`. - **openxFactory pytest.** The full local run was stopped at about 44% at a coordinator drain, so it is NOT a result and is not claimed. The repository's own gate, `pytest-suite` in CI at `691648c6`, completed `SUCCESS` (2026-10-06T23:32:38Z to 23:58:56Z). - **Doc-health**, `scripts/doc-health.py --single-repo . --as-of 2026-10-07`, branch `691648c6` against `origin/main` `0992369a` in the same clone: the two reports are byte-identical (sha256 `ce17aadd6d96e484170e2ec4784e44972e33c7d1a6c65830ea14a1035f2ae976`; `Findings: 31 critical, 26 error, 69 warning, 20 info. New regressions vs previous report: 0.`). So no new error. - **CI at `691648c6`**: all 14 check runs are `SUCCESS` (rollup `SUCCESS`): `pytest-suite`, `merge-master-approval` (the red inherited from `main` since #1252 was cleared by #1138), `openspec-cli-pin`, `doc-health-py314`, `openxdox-consumer-gate`, `wallet-validation`, `clearing-dispatch-gate`, `signed-execution-chain-gate`, `former-id-arrival-gate`, `release-tag-gate`, `openreposhape-pin`, the two `lane-line` runs and SonarCloud. - **`main`** has moved to `0992369a` since the last merge (`543f6ce1`). GitHub reports this PR `MERGEABLE`, and the ruleset is not strict, so `main` is not merged in again (a merge would restart the 27-minute gate). | Copilot at | verdict | findings | answered | |---|---|---|---| | `691648c6` (final; review `5435686665`) | Changes recommended | 1 (thread `4201443690`): T041's falsifier cannot verify what T046's `health list --json` and T057's HTTP route emit; the review summary adds that the new T095 to T104 collide with plan 034's task numbers cited unqualified at `spec.md:662`, `spec.md:1222` and `clarify-questions.md:437` | accepted limit (#656 `5988818366`): plan-record accuracy, not a bypass, and the T041 placement is the holder's own instruction (item 5). Answered (`4201487553`) and resolved | 1 thread, 0 unresolved. The holder ruled both points YES at `6027706377` and has them encoded in a THIRD batch, not here: the `identity` falsifier split across T041, T046 and T057, and the plan 034 references qualified. This PR therefore carries T041's wording as the brief gave it. ## Files changed - `specs/038-opendox-document-tool-self-maintenance/tasks.md`, `plan.md`, `spec.md` - `specs/038-opendox-document-tool-self-maintenance/contracts/health-finding.md` ## What is NOT done - T071 stays open (its ratification record has not landed). No box of #1144 is ticked, and no task other than those listed is ticked. - No scenario or spec delta beyond FR-011's JSON-shape bullet is touched, and nothing is under `openspec/changes/`. `README.md` is untouched. - The T095 to T104 entries record the slices; none of their work is done here. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Lane: openxfactory-4 (openXfactory-4-openDox_extraction) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…and the composed tree (plan 038, T094's R2-INV-R9) (#49) Lane: openxfactory-4 (openXfactory-4-openDox_extraction) Authored by lane openXfactory-3, delegated by lane openxfactory-4 (#656 6021862323) Arc: neutral-product-standalone-operability ## T103: the T074 files (plan 038; T094's map R2-INV-R9, slice outline row T103) There is no `tasks.md` entry yet: lane 4 records T094's slices after T002 (openxFactory#1251). Until then, the contract is the slice outline row plus the holder's rulings, openxFactory#656 `6021830531`. - **Realizes:** the composed half of requirement 9 for these four declared files (T073's F9.1: each declared file runs green in the composed workflow). The means are PP, plus MN, HR and CS for `test_gate_console.py`. - **Falsifier (the outline row):** run the four files composed, each alone. **28 reds go to 0**: completeness 2, packet 1, gate_console 1, round_trip 24. `test_gate_console.py` then reads 127 passed and 5 skipped. Its 5 skips are OQ-R9-3, which this slice respells so they run composed: it reads **132 passed, 0 skipped**. - **Ruled:** openxFactory#656 `6021830531`: - OQ-R9-1 to OQ-R9-4 at their defaults; - W7: each of these four files' chain is **T103 → T074**, so this lands before T074. - The ruling it sits under is `6016982816` (ARC-Q2 (a)). - **After:** T095 (#46, landed as `e2340bb6`), which this PR codes against: `openxfactory_root()`, `CONTRACTS_DIR` and `DECLARED_HOST_SUITES`. T021 (#40) landed as `f70bc9c3`. Both are met. ## What changed (4 test files, nothing else) | file | nodes | means | change | |---|---|---|---| | `tests/test_completeness.py` | 2 | MN, PP | The purity check reads `completeness.py` at its carved home (`carved_module_path`), and `doc_health.lines` where this process imports it from. The gating bound scans this leg's import root `src/` and expects `openxdox/generator.py` and `openxdox/kickoff.py`. Its `doc_health` half scans the `doc_health` package this process imports. | | `tests/test_round_trip.py` | 24 | PP | `_MOVER` reads openxFactory's `scripts/proposal-support.py` through `openxfactory_root()` (23 nodes). The shared-predicate pin reads `round_trip.py` at its carved home and `families.py` from `families.__file__` (1 node). | | `tests/test_doxbench_packet.py` | 1, the rail node | PP | The status-read drift check reads openxFactory's corpus, `openxfactory_root() / "docs"`, instead of this checkout's `docs/`. | | `tests/test_gate_console.py` | 1 collection error, and the 3 cases behind it | PP, MN, HR, CS | The module-level forward-gate load goes through `openxfactory_root()`. The two in-test `from ideation_dashboard import cli` imports become `from opendox import cli`. HR (the two cases that parse `gate`) is T095's `DECLARED_HOST_SUITES`, and CS (the receipt case) is T095's `CONTRACTS_DIR`; neither needs an edit in this file. | | `tests/test_gate_console.py`, OQ-R9-3 | 5 skips | CS | `_contract_carries_the_new_verbs` reads each schema where the pinned validator reads it. `gate-action-record` comes from the installed distribution's packaged copy (`openxdox.contracts.verified_path`). `gate-intent` comes from the directory `CONTRACTS_DIR` names. A lone checkout has no `CONTRACTS_DIR`, so the group still skips there. Once `CONTRACTS_DIR` is supplied, a schema that cannot be read fails collection instead of reading as "not released" (Copilot r4199436303). | Edits are kept local to the failing reads, so T074's later respelling of these files' `doc_health` imports stays clean. The two `doc_health` reads in completeness and round_trip resolve through the import system, so they follow whatever T074 makes those imports. **Flagged, a widening beyond the failing assertion:** `test_the_scoring_modules_only_importers_are_the_generator_and_the_guard`'s second loop scanned `REPO_ROOT / "scripts" / "doc_health"`. This leg has no such directory, so since the carve the loop has scanned nothing and passed vacuously. It now scans the composed `doc_health` package, 38 modules, and passes. Mutant M2b below shows the check is live. ## The falsifier, measured Measured in R1Q23 (a)'s composed form: - openxFactory `92010d3e` (#44's `tests/composed_host_pin.yaml`), with `openDox`, `openXdox` and `openXwallet` initialized recursively, and its `scripts/` on `PYTHONPATH`; - openDox installed from openDox-code `main` `84f8ed83`; - colour variables unset, `TERM=dumb`; - each file ALONE: `python -m pytest -q -p no:cacheprovider -rfE --junitxml=… <file>`. The integration branches are LOCAL and never pushed: - **base** = `main` `e2340bb6`, which carries T021 (#40) and T095 (#46), both landed; - **head** = this PR at `683b9562`, which is `main` merged in and differs from it by exactly these four files. Earlier runs over local integrations read the same, file for file: `main` `d45a0939` + #40 + #46 at `6aeb6797`, and `main` `f70bc9c3` + #46 `c2192e3e` at `57a970fc`. The rail node needs T073's status-exemption rail. Without it, the node fails on `pk.STATUS_SCAN_LINES`, at the rail, before it reaches its corpus read. So the four files are also measured with #45 `26fe63e7` merged in, with T022's placement made first and removed after. **Base + #45, 28 red:** ``` tests/test_completeness.py: exit 1; 29 passed, 2 failed, 0 errors, 0 skipped tests/test_doxbench_packet.py: exit 1; 85 passed, 1 failed, 0 errors, 0 skipped tests/test_gate_console.py: exit 2; 0 passed, 0 failed, 1 errors, 0 skipped tests/test_round_trip.py: exit 1; 50 passed, 24 failed, 0 errors, 0 skipped TOTAL over 4 files: 164 passed, 27 failed, 1 errors, 0 skipped; 28 red ``` **Head + #45, 0 red:** ``` tests/test_completeness.py: exit 0; 31 passed, 0 failed, 0 errors, 0 skipped tests/test_doxbench_packet.py: exit 0; 86 passed, 0 failed, 0 errors, 0 skipped tests/test_gate_console.py: exit 0; 132 passed, 0 failed, 0 errors, 0 skipped tests/test_round_trip.py: exit 0; 74 passed, 0 failed, 0 errors, 0 skipped TOTAL over 4 files: 323 passed, 0 failed, 0 errors, 0 skipped; 0 red ``` The 28 red at base, each with its first failure: - `test_completeness.py`: `test_the_scoring_module_touches_nothing_outside_its_arguments` (FileNotFoundError, `scripts/ideation_dashboard/completeness.py`); `test_the_scoring_modules_only_importers_are_the_generator_and_the_guard` (`AssertionError: []`). - `test_doxbench_packet.py::test_the_status_read_agrees_with_the_repositorys_own_corpus_reader`: "the drift check needs real documents to be a check". - `test_gate_console.py`: a collection error (FileNotFoundError, `scripts/proposal-support.py`). - `test_round_trip.py`: - `test_both_gates_agree_where_a_documents_status_header_is` ×12, `test_the_mover_agrees_with_the_other_fence_implementations` ×10 and `test_the_movers_line_split_is_the_three_real_endings_and_nothing_else`: FileNotFoundError, `scripts/proposal-support.py`; - `test_the_shared_predicate_is_spelled_the_same_in_all_three`: FileNotFoundError, `scripts/ideation_dashboard/round_trip.py`. **Without #45**, base vs head reads completeness 2→0, round_trip 24→0 and gate_console 1→0 (132 passed). Packet reads 57 red at both: its 56 rail nodes and the rail node all fail at the rail, which is T073's. The two "rejected" conformance cases are rejected by the schema (validator exit 1), not by a harness failure (exit 2). Run by hand with the farm: - a valid `promote-to-staging` record: exit 0; - one with no workflow job: exit 1, "2 error(s)"; - a `derive-possibles` record with no `cluster_id`: exit 1, "1 error(s)". ## Mutants of the respellings This slice adds no guard and no fixture. Each mutant reverts or misdirects one respelled read; every one was run with that file alone, composed, over head + #45. | id | mutation | result | |---|---|---| | M1 | purity check reads `generator.py`, not `completeness.py` | killed: 1 failed (`AssertionError` on the import set) | | M2b | the `doc_health` loop scans `src/` instead | killed: 1 failed (the loop's assertion names `completeness.py`) | | M3 | importer scan back to `REPO_ROOT / "scripts"` | killed: 1 failed (`AssertionError: []`) | | M4 | shared-predicate pin reads `gate_console.py` | killed: 1 failed | | M5 | rail node back to `REPO_ROOT / "docs"` | killed: 1 failed ("the drift check needs real documents to be a check") | | M6 | conformance group: `CONTRACTS_DIR` test inverted | 127 passed, **5 skipped**: the group reverts to never running | | M7 | conformance group reads a schema without the verbs | 127 passed, **5 skipped**: the skip condition reads the schema's content | | M8 | **a broken farm**: a pytest plugin removes `gate-intent.schema.yaml` from the farm after the conftest builds it | at `6aeb6797`, before the fix: 127 passed, 5 skipped, so it **survived** (the masking Copilot r4199436303 named). At `57a970fc`: a collection error (`FileNotFoundError`), so it is **killed** | ## Unchanged Base (`main` `e2340bb6`) vs head (`683b9562`), each file alone and composed: **974 nodes, 0 differ**, and no node goes from green to red anywhere. - **12.5's 16 governed suites:** identical at both, node for node. Their remaining reds are T073's and the other slices', not this PR's. - **F5.2's 5 protected files:** generator 45, session_snapshot 23, snapshot_determinism 6, snapshot_registry 40 and snapshot_validation_launch 9, all passed at both. - **`test_column_contributions_governed.py`:** 9 passed at both. - **`tests/test_declared_exclusion.py`, lone** (no `PYTHONPATH`): 207 passed at base and at head. - None of the four files is in 12.5's computed set (16) or F5.2's (7), so no allow-list entry is needed. ## CI T095 has landed, so this head carries `openxfactory_root` and `CONTRACTS_DIR` from `main`. Locally, in validate's lone form at `683b9562` (pinned openDox `dede32b4`, `--deselect` of LEFT_OUT): `selected=1165 passed=1161 skipped=4 failures=0 errors=0`, against the floors 1099 / 1095 / 4. `tests/test_declared_exclusion.py` run alone reads 207 passed at base and at head. **CI at `683b9562`:** - `validate` passed (run 37548600589): `selected=1165 passed=1161 skipped=4 failures=0 errors=0`, against the floors 1099 / 1095 / 4 (margin 66); - SonarCloud Code Analysis passed; - Copilot's review 5435805891 at `683b9562`: **Findings: None**. It rates the PR "Needs a closer look" only because the PR was a draft and the composed packet result depends on T073's rail, which lands separately. **Threads:** 1 in all (r4199436303, fixed at `57a970fc`, answered and resolved), 0 unresolved. ## Not done / what remains - Nothing is left on this PR's own After set: T021 (#40) and T095 (#46) have landed, and this branch merges `main` `e2340bb6`. If `main` moves again, the holder's loop updates the branch. - The rail node's composed green also needs T073 (#45), which lands after this slice. Until then it is measured over a local integration with #45, as above. - T074 later respells these files' `doc_health` imports; this PR leaves them alone. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Arc: neutral-product-standalone-operability Lane: openxfactory-4 (openXfactory-4-openDox_extraction) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…n is composed (plan 038, T094's R2-INV-R9, CS) (#53) Lane: openxfactory-4 (openXfactory-4-openDox_extraction) Authored by lane openXfactory-3, delegated by lane openxfactory-4 (#656 6021862323) Arc: neutral-product-standalone-operability ## T096: the contract family's tests (plan 038; T094's map R2-INV-R9, means CS) **Realizes** (no `tasks.md` entry yet; the contract is the slice outline row of R2-INV-R9 plus the holder's rulings `6021830531`): requirement 9's declared composed tests, toward T073's composed step 5a (F9.1). This is the CS constants half for the four contracts-only files. **Falsifier** (the outline row): the 74 composed reds of these four files read 0 (11 / 13 / 32 / 18). Each file runs alone and composed, red first, then green. The unchanged set reads unchanged: 12.5's 16 governed suites, F5.2's files, and `test_column_contributions_governed.py` at 9 passed. `tests/test_declared_exclusion.py` reads green lone. **Ruled / Decisions:** T094 RULED `6021830531` (OQ-R9-2 at its default: the farm is built by the conftest, composed-only, and named `CONTRACTS_DIR`), under `6016982816` (ARC-Q2 (a)). There are no decisions of this slice's own. **After:** T095 (#46), which defines `CONTRACTS_DIR`. It landed as e2340bb, and this branch has merged it. ## The change (the four files only) Each file took `Path(__file__).parents[2]` as openxFactory's root. Composed, that is the directory above the checkout. Each file now reads T095's `CONTRACTS_DIR` (`from conftest import CONTRACTS_DIR`): - **Composed:** `SCRIPT` is this leg's own `scripts/validate-ideation-dashboard-contracts.py`, which reads the farm through the `CONTRACTS_DIR` environment variable. `SCHEMAS` and `REGISTER_SCHEMA` read `CONTRACTS_DIR/schemas`, and `EXAMPLES` reads `CONTRACTS_DIR.parent/examples/ideation-dashboard`. - **Lone:** `CONTRACTS_DIR` is None, and every path is byte-identical to before. So each file still fails alone for its declared reason, `openxfactory-contracts`. The consumer-validator test's docstring now states which validator `find_validator` answers in each case (Copilot r4201041192). No test, guard, fixture or assertion is added or removed. Files: `tests/test_project_action_contracts.py`, `tests/test_project_schema_election.py`, `tests/test_validate_ideation_dashboard_contracts.py`, `tests/test_wheel_action_contracts.py`. ## Measured: base = `main` e2340bb, head = fd16d70 (this branch, merged with it) **The composed form, R1Q23 (a):** - openxFactory `92010d3e` with openDox, openXdox and openXwallet initialized recursively, and its `scripts/` on `PYTHONPATH`; - openDox installed from openDox-code `main` `84f8ed83`; - T022's placement made before the run and removed after it, with `git status` clean afterwards; - colour variables unset, `TERM=dumb`; - Python 3.12.3, pytest 8.4.2, Node v24.21.0; - each file run alone. ``` base test_project_action_contracts.py 1 failed, 10 errors base test_project_schema_election.py 13 errors base test_validate_ideation_dashboard_contracts.py 1 failed, 1 passed, 31 errors base test_wheel_action_contracts.py 1 failed, 17 errors every red: FileNotFoundError / can't open file on <workspace>/scripts/validate-ideation-dashboard-contracts.py, <workspace>/contracts/schemas/project-register.schema.yaml or .../gate-intent.schema.yaml, where <workspace> is the directory above the checkout head test_project_action_contracts.py 11 passed head test_project_schema_election.py 13 passed head test_validate_ideation_dashboard_contracts.py 33 passed head test_wheel_action_contracts.py 18 passed ``` **Unchanged set, composed, each file alone, at the same base and head:** - 12.5's 16 governed suites (computed by `git grep -l -e open-pr -e open_pr -e FakePullRequests -- 'tests/test_*.py'`); - the 7 files F5.2's set names (`test_generator.py`, `test_snapshot*.py`, `test_session_snapshot.py`); - `test_column_contributions_governed.py`, at 9 passed. Together they are 1004 nodes, with 0 red at base and 0 red at head, and **0 nodes differ**. **Lone** (a venv from `pip install -e ".[test]"`, openDox at the pyproject pin `dede32b4`, no `PYTHONPATH`): - `tests/test_declared_exclusion.py`: 207 passed at base and at head; - the `validate.yml` form at head: `1161 passed, 4 skipped, 1 deselected` (selected 1165 / passed 1161 / skipped 4; floors 1099 / 1095 / 4). **Mutants**, each applied to all four files at head. All were killed: - M1, the lone half reads the leg (`parents[1]` always): `test_declared_exclusion.py` lone, the 4 entries fail; - M2, the composed half keeps `parents[2]`: composed, 68 red (every case that loads the validator); - M3, the schemas not from the farm: composed, all four files red; - M4, the examples not beside the farm: composed, three files red; - M5, the composed flag inverted: lone, the 4 entries fail; composed, 74 red. In M5's lone run, `test_hosted_actor.py`'s entry also failed once. That file is not one of these four and is not mutated, and it reads green in the unmutated head run (207 passed). It was one-off noise on a loaded host. Earlier rounds measured the same falsifier over local integrations of `main` + #40 + #46 7ad77ae, and of `main` f70bc9c + #46 c2192e3, with the same results for these four files. ## CI and Copilot - CI at fd16d70: `validate` success (run 37548764129: `1161 passed, 4 skipped, 1 deselected`, which is selected 1165 / passed 1161 / skipped 4, above the floors), and SonarCloud Code Analysis success. - Copilot review 5435242460 at 70871d1 raised 1 low finding, r4201041192. It was fixed in 98739e8, answered and resolved. - Copilot review 5435648181 at 631683c: Findings None. Its "Changes recommended" was only the then-open #46. - Copilot review 5435813394 at fd16d70: Approval recommended, Findings None. - Review threads: 1 (r4201041192), resolved; 0 unresolved. ## Not done - The commit carries `Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>`, the harness's attribution form, not WRITER-RULES' "(1M context)" form. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Arc: neutral-product-standalone-operability Lane: openxfactory-4 (openXfactory-4-openDox_extraction) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…1 and T098 landed) Conflicts in src/openxdox/gate_console.py, tests/conftest.py, tests/test_gate_console_schema_source.py and tests/test_host_plane.py, all T020's and T021's files that this branch carried from their pull requests before they landed: main's side taken for all four (main's gate_console.py and the schema-source test equal #40's final head; main's conftest.py and test_host_plane.py carry T026's later edits). No T029 file conflicted. Lane: openXfactory-3 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…escapes (jsonschema>=4.18) (#55) Lane: openxfactory-4 (openXfactory-4-openDox_extraction) Authored by lane openXfactory-3, delegated by lane openxfactory-4 (lane4-to-lane3.log 2026-10-06T19:07:30Z; READY note #656 6023492215) ## T021 follow-on: the cyclic-schema test asserts only that `GateRefused` escapes (`jsonschema>=4.18`) ### The defect `tests/test_gate_console_schema_source.py::test_a_host_schema_the_console_cannot_apply_refuses_as_gate_refused` pinned the exact failure mode `jsonschema` gives a cyclic MAPPING schema: it asserted the refusal text `...could not be checked: RecursionError`. `pyproject.toml` allows `jsonschema>=4.18` with no upper bound, so a supported version that refuses the cycle by another route (a `SchemaError`, a `ValueError`, a changed message) would turn the test red while the console still did its job. Copilot raised it on #40 (r4199337845); the holder classed it an accepted limit under #656 `5988818366` and assigned this follow-on (READY note #656 `6023492215`). ### The fix The cyclic mapping now asserts what the cyclic list beside it already asserted: the outcome starts with `GateRefused: `. An exception that escaped reads `ESCAPED ...` and fails that prefix. The test's intent is unchanged (a cyclic schema a host supplies refuses as `GateRefused`, never as another exception); only the version-specific step and exception text are dropped. The product code is untouched: `_schema_errors` already catches any exception at each of its three steps. Files changed: `tests/test_gate_console_schema_source.py` only (+10 -7). ### The falsifier, stated plainly **The old assertion does not fail under any released `jsonschema` in the supported range today.** I installed 10 versions in throwaway venvs (the repository's CI install, `pip install -e ".[test]"`, with `jsonschema` pinned to each) and ran the same child-interpreter body the test runs: ``` jsonschema 4.18.0 cyclic_mapping old_assert=PASS new_assert=PASS :: GateRefused: ...schema could not be checked: RecursionError: maximum recursion dep... jsonschema 4.18.0 cyclic_list old_assert=PASS new_assert=PASS :: GateRefused: ...schema is not a valid JSON Schema: [[...]] is not of type 'object' (identical lines for 4.18.6, 4.19.2, 4.20.0, 4.21.1, 4.22.0, 4.23.0, 4.24.1, 4.25.1, 4.26.0) ``` So the defect is latent, not live: no version in 4.18.0 to 4.26.0 breaks the old assertion. What I measured instead is that the old assertion pins a library implementation detail (the exception class and step of a pathological traversal), and what happens when the library changes it. **A simulated library change.** A throwaway `sitecustomize` (in a copy of the tree, never committed) makes `Draft202012Validator.check_schema` refuse a cyclic schema by another route (the cycle becomes a `SchemaError`, or a `ValueError`), run under 4.18.0 and under 4.26.0, the installed version, old test file against the new: ``` installed jsonschema 4.18.0 and 4.26.0, identical: library raises SchemaError old: 1 failed AssertionError: GateRefused: ...schema is not a valid JSON Schema: a cyclic schema is not a schema new: 1 passed library raises ValueError old: 1 failed AssertionError: GateRefused: ...schema could not be checked: ValueError: cycle detected new: 1 passed ``` In both, the console still refuses as `GateRefused`; only the old assertion's pinned text was wrong. **Mutants of the new guard, killed.** Throwaway copies of `gate_console.py`, run against the NEW test: ``` M1 the check step's catch-all removed: 1 failed AssertionError: ESCAPED RecursionError: maximum recursion depth exceeded M2 the check step raises RuntimeError: 1 failed AssertionError: ESCAPED RuntimeError: ...could not be checked: RecursionError: maximum recursion depth exceeded control (restored): 1 passed ``` So the loosened assertion still catches what matters: a cyclic schema whose refusal is not a `GateRefused`. ### Run counts - The edited file alone, under each of the 10 `jsonschema` versions above (4.18.0 to 4.26.0): `6 passed` each. - The whole suite in its lone CI form (`python -m pytest -q --deselect <LEFT_OUT>`, the `pytest` step of `validate.yml`; `FORCE_COLOR`, `CLICOLOR`, `PY_COLORS`, `COLORTERM` unset, `TERM=dumb`; installed `jsonschema` 4.26.0), at this head on `6f25915`: `1161 passed, 4 skipped, 1 deselected`. Test count unchanged, so the pinned triple's floors do not move. ### Ruled / decisions Ruled: Copilot r4199337845 accepted limit under #656 `5988818366`; this follow-on assigned by the holder (lane4-to-lane3.log 2026-10-06T19:07:30Z). Decision: take Copilot's one-line remedy as given (assert only `GateRefused` escapes), not a version cap in `pyproject.toml`. ### Not done - No `pyproject.toml` change (no upper bound added): the loosened assertion is the remedy. - No change to `gate_console.py`. - No other test in the file touched. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Lane: openxfactory-4 (openXfactory-4-openDox_extraction) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…thing vendored (plan 038, P4F-3) (#41) Lane: openxfactory-4 (openXfactory-4-openDox_extraction) Authored by lane openXfactory-3, delegated by lane openxfactory-4 (#656 6013608959) Arc: neutral-product-standalone-operability ## T022, U-3: the runbook placed from the composed tree (RP) (plan 038, feature `038-opendox-document-tool-self-maintenance`) After set met: T004 #1245 → 260234b5; T020 #39 → c6d15b2; T021 #40 → f70bc9c. **The branch.** The head is `57b4f80a`, GitHub's update-branch merge of `main` `e7815ac5`. T022's last commit before it is `1f592766`, the merge of `main` `da736090`, and T022's three files are byte-identical between the two. The diff against `main` is T022's three files. - T022's own commits: - `c40af91b`: U-3; - `7a149b70`: the committed tests, under the holder's Files ruling; - `dc879beb`, `146ae923`, `74f3a4fb`, `1f2d6858` and `2c86dcca`: the review fixes below. - Main came in by merges, never a rebase: `42d7ea97` (`f70bc9c3`), `c22b5df7` (`36bbef79`), `49c1026d` (`e2340bb6`), `214f3613` (`fc1115d6`), `60c56a47` (`6f259156`) and `1f592766` (`da736090`). - At `42d7ea97` this branch still carried T021's older `ecf0ba9b` versions of `src/openxdox/gate_console.py` and `tests/test_gate_console_schema_source.py`. Both conflicts were resolved to main's side, since neither file is T022's. ### What changes `tests/test_session_runbook.py` (its module constant `RUNBOOK`) and `tests/test_session_notebook.py` read `docs/ideation-dashboard-session-runbook.md` at the checkout root. The carve sent the runbook to openDox-spec, so this checkout has none, and both suites are protected. - **New `scripts/composed_placements.py`** links the runbook, for a composed run, from the composed tree into `docs/`. The source is `openDox/spec/docs/ideation-dashboard-session-runbook.md` under the openxFactory checkout. ``` python3 scripts/composed_placements.py --openxfactory "$OPENXFACTORY" # place python3 scripts/composed_placements.py --openxfactory "$OPENXFACTORY" --remove # take back ``` Before it places anything, it refuses (exit 2), naming the remedy, unless all of these hold: - **Each leg is its own work tree.** The openDox root and the spec leg each have their own `.git`, and `git rev-parse --show-toplevel` resolves to the directory itself. So no enclosing repository answers for them, and no `core.worktree` points the checks elsewhere. - **The spec leg is at the pin.** Its HEAD is the commit the root's `spec` gitlink pins. - **The bytes are the pinned bytes.** The runbook is tracked, no component from the spec leg down to the file is a symbolic link, and its raw bytes are the pinned blob: `git hash-object --no-filters` equals `HEAD:<path>`. So no index flag, clean filter or EOL normalization can hide an edit. - **The destination is ignored by the repository-root `.gitignore` itself**, read through `git check-ignore -v`. A local exclude alone (`.git/info/exclude`, `core.excludesFile`) is refused, and so is a negated pattern. - **Nothing is overwritten.** Anything already at the destination must be the script's own link to that same source. Every git call drops the ambient repository variables (`GIT_DIR`, `GIT_WORK_TREE`, `GIT_INDEX_FILE` and kin, as `openxdox.domain_corpus_adapter` drops them), so a hook cannot redirect the checks. The script places all or nothing. `--remove` takes back its own links and nothing else. It needs only the standard library and `git`. - **`.gitignore`** names `/docs/ideation-dashboard-session-runbook.md`, so the checkout stays clean and nothing placed can be committed. - **New `tests/test_composed_placements.py`**, 30 cases, holds those guards in the suite CI runs. Each case runs the script in a child interpreter: a copy of this checkout's script, inside a scratch checkout that carries this checkout's own `.gitignore`. It runs against a scratch openxFactory/openDox/spec tree pinned to commit A, so no case writes into this checkout. No case skips, and the file carries none of 12.5's governed-suite markers. - **Nothing is vendored.** `src/openxdox/contracts/copies.yaml` is untouched (P4F-3; ADV-10). The composed workflow (T029) and the documented local composed run call the script. Neither is in this PR. ### The contract, from `tasks.md` - **Realizes**: 12.5 (part). - **Falsifier**: the 10 nodes, composed, quoted. - **Ruled**: R2Q8. **Decisions**: P4F-3 ("The runbook is PLACED from the composed tree (`openDox/spec/docs/…` at the root's pinned spec commit) by T022's script, git-ignored; nothing vendored."), ruled with plan 038 by Brett Heap (#656 `6013547504`). - **After**: T021, met as above. - **Files**: new `scripts/composed_placements.py` and `.gitignore`. - **Amended** by the holder's ruling on opensoft/openxFactory#656, comment `6016356225`, option (a): add the new `tests/test_composed_placements.py`. - The `check-ignore` source fix (Copilot r4196244480, raised on #45) is ruled into this PR by #656 `6026275158`, item 2. - Nothing else is changed. ### The falsifier, composed **Environment.** - openxFactory `51456835`, with its submodules initialized recursively and `scripts/` on `PYTHONPATH` (R1Q23 (a)). - The openDox root `e1e3a3c3` pins openDox-spec `f7ee3c76`, the spec checkout's HEAD. - openDox-code `34c20641`, force-reinstalled with `--no-deps`. - Python 3.12.3, pytest 8.4.2, `LANG=C.UTF-8`, no colour forcing. At the head, the runbook is placed, then removed afterwards, which leaves the checkout clean: ``` python3 scripts/composed_placements.py --openxfactory "$OPENXFACTORY" PYTHONPATH="$OPENXFACTORY/scripts" python -m pytest -q -p no:cacheprovider -rA <the 10 runbook nodes, one process per suite file> ``` | at | the 10 runbook nodes | |---|---| | main `56e1c238` (first round) | `9 failed` + `1 failed` | | main `f70bc9c3`, `36bbef79`, `6f259156` | "PASSED nodes: 0 / FAILED-or-ERROR nodes: 10" | | **head `1f592766`, placed** | **"PASSED nodes: 10 / FAILED-or-ERROR nodes: 0"** | The cause at main is `FileNotFoundError: … '<checkout>/docs/ideation-dashboard-session-runbook.md'`. The 10 nodes: - `test_session_runbook.py`: - `test_the_runbook_start_command_parses_with_the_real_cli_parser` - `test_the_runbook_names_the_two_required_flags_and_what_they_are_for` - `test_the_runbook_claims_no_flag_generate_and_open_does_not_have` - `test_the_runbook_says_what_a_wrong_repo_root_does` - `test_the_runbook_does_not_call_the_unvalidated_snapshot_routine` - `test_the_runbook_lists_the_posture_readings_the_page_can_produce` - `test_the_derived_artifact_inventory_names_every_container_the_code_writes` - `test_the_runbook_names_the_pull_the_refusal_names` - `test_the_runbook_states_what_a_session_may_rewrite` - `test_session_notebook.py`: - `test_the_runbook_names_the_bound_by_its_production_constants` **The 16 suites, each alone (first round, at `c40af91b`/`7a149b70` against main `56e1c238`):** - 82 red of R2-INV-P4F's 174; - 0 regressions; - node for node the same at `7a149b70`. T022 changes no code under `src/`. The only change it makes to a composed run is the placement. ### The script's guards: 30 cases and 23 mutants `tests/test_composed_placements.py` reads `30 passed` at `1f592766`. Each later finding's cases were red against the script before its fix; the review rounds below quote them. All 23 mutants were applied to this checkout's script or `.gitignore`, which the cases copy, and every one is killed by the case that names its guard: | mutant | killed by | |---|---| | MA no pin comparison | `test_a_spec_checkout_off_the_opendox_roots_pin_is_refused` | | MB content not compared | `test_a_modified_runbook_is_refused` | | MW hash through clean filters | `test_an_edit_a_clean_filter_would_hide_is_refused[clean-filter]` | | MS no link check | `test_a_runbook_the_pinned_commit_tracks_as_a_link_is_refused` | | MX only the last component | `test_a_runbook_reached_through_a_linked_directory_is_refused` | | MU root may be any repository | `test_an_opendox_root_answered_by_an_enclosing_repository_is_refused` | | MV toplevel not compared | `test_a_spec_leg_whose_work_tree_is_configured_elsewhere_is_refused` | | MO no pinned-root check | `test_an_opendox_root_without_a_spec_gitlink_is_refused` | | MI spec leg is any directory | `test_an_uninitialized_spec_leg_is_refused_as_not_checked_out` | | MC no tracked check | `test_a_runbook_the_spec_does_not_track_is_refused` | | MD no ignore check | `test_a_destination_the_gitignore_does_not_name_is_refused` | | MP any ignore source | `test_a_destination_only_a_local_exclude_names_is_refused[info/exclude]` | | MQ a negation counts as ignored | `test_a_gitignore_that_negates_the_destination_is_refused` | | MJ `.gitignore` drops the runbook | `test_a_pinned_unmodified_runbook_is_linked_and_leaves_the_checkout_clean` | | MR ambient git variables kept | `test_ambient_git_repository_variables_do_not_redirect_the_checks[clean]` | | ME overwrite a foreign file | `test_a_file_at_the_destination_is_refused_and_kept[place]` | | MF remove any file | `test_a_file_at_the_destination_is_refused_and_kept[remove]` | | MK place accepts any link | `test_a_link_elsewhere_at_the_destination_is_refused_and_kept[place]` | | ML remove takes any link | `test_a_link_elsewhere_at_the_destination_is_refused_and_kept[remove]` | | MM remove unlinks nothing | `test_remove_takes_back_its_own_link_and_nothing_else` | | MN no already-placed short circuit | `test_placing_again_is_a_no_op` | | MG no git crashes | `test_without_git_on_path_it_refuses` | | MH place before checking all | `test_a_modified_runbook_is_refused` | ``` mutants: 23/23 killed; files restored ``` ### The suite as CI runs it, and the protected suites - **The required job's own command**, in a venv built as CI builds it (openDox at the `pyproject.toml` pin `dede32b4`), at `1f592766` with nothing placed: `selected=1195 passed=1191 skipped=4 failures=0 errors=0`. All three pins hold. - **The governed set is unchanged at 16.** - **No protected file is edited.** T059's oracle, as 12.5 calls it, over a simulated squash of `1f592766`'s tree onto `da736090`, which touches T022's three files: "ok: 0 protected edit(s), each entered and holding". ### Copilot, by round - **At `c40af91b`:** - **r4194596249**, "Translate schema validation errors into GateRefused" (T021's code, carried then): fix-now, fixed in T021's `ecf0ba9b`, merged here at `8d02a1b3`. - **r4194596317**, "Add pytest coverage for filesystem-mutating script safeguards": first an accepted limit, put to the holder; the holder ruled (a) (#656 `6016356225`), making it fix-now. Fixed in `7a149b70` (the committed tests). That commit also fixed a misreport the new tests found: an uninitialized spec leg had been reported at the root's HEAD. - **At `8d02a1b3`**: "Findings: None". - **At `7a149b70`: r4195469835**, the PR description. Fixed in the body. - **On #45 (`26fe63e7`), on this file: r4196244480**, `check-ignore` honours local excludes. The holder ruled it into this PR (#656 `6026275158`, item 2). - **Fixed in `dc879beb`**: the source must be the root `.gitignore`, and a negated pattern is refused. - Red before the fix: the `.git/info/exclude` and `core.excludesFile` cases (`2 failed, 16 passed`). - #45's thread is answered by citing `dc879beb` once #45 merges `main`. - **At `dc879beb`: r4200975244**, ambient `GIT_DIR`/`GIT_WORK_TREE` redirect the checks. Fix-now, fixed in `146ae923`; red before: `2 failed, 18 passed`. - **At `146ae923`/`c22b5df7`:** - **r4201032934**, `assume-unchanged`/`skip-worktree` hide an edit from `git diff`; - **r4201424060**, a symlinked runbook; - **r4201032886**, repository discovery inherited or redirected. All fix-now, fixed in `74f3a4fb`. Before it, four of the new cases PLACED the link: both index flags, the link tracked at the pinned commit, and the enclosing repository. - **At `60c56a47`: r4201850292**, `hash-object` applies clean filters. Fix-now, fixed in `1f2d6858` (`--no-filters`). Before it, a hiding clean filter and a CRLF edit under `text` both PLACED the link. - **At `1f2d6858`: r4201912214**, a symbolic link in a parent directory. Fix-now, fixed in `2c86dcca`. Before it, a linked `docs/` holding identical bytes PLACED the link. - **r4200975330 and r4201032964**, the PR description: fixed by this body. - **At the head `1f592766`**: review 5440606809, no new finding. Its overview reads "Needs a closer look" for one stated reason: "It remains a cross-repository draft stacked on prerequisites that have not landed". The After set above has landed, so this is not a finding. - Each fix-now finding was a real bypass with a simple payload, under the convergence rule (#656 `5988818366`). - **At `57b4f80a`**, GitHub's update-branch merge of `main` `e7815ac5` after the un-draft, with T022's three files byte-identical to `1f592766`: review 5440672686, two findings. Both are **ACCEPTED LIMITS under 5988818366, for the holder at READY**; the PR was un-drafted, so neither is pushed. - **r4205549424**, a symlinked destination parent. It is already refused. Measured: with `checkout/docs` a link outside, `git check-ignore` answers "pathspec ... is beyond a symbolic link" (exit 128), the ignore guard refuses (exit 2) before `mkdir` or `symlink_to`, and the outside directory stays empty. Only the message misnames the cause. - **r4205549516**, the pinned tree mode. The scenario needs a pin that tracks the runbook as a link, but openDox-spec tracks it as `100644 blob e88f06e6` at `f7ee3c76`. The one-line hardening is a `git ls-tree` mode check, as a follow-on. **At the head `57b4f80a`** (T022's content as at `1f592766`): - Checks: `validate` pass (run 37604221972), `SonarCloud Code Analysis` pass, Copilot success, Sourcery skipped. At `1f592766`: validate run 37603513031 passed. - Review threads: 13, all answered and resolved. 0 unresolved. ### Not done here - The permanent composed workflow that calls the script on every run is T029 (U-9). - The "documented local composed run" is wherever T029 documents it. This PR adds the script's own usage docstring only. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Arc: neutral-product-standalone-operability Lane: openxfactory-4 (openXfactory-4-openDox_extraction) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>


Lane: openxfactory-4 (openXfactory-4-openDox_extraction)
Authored by lane openXfactory-3, delegated by lane openxfactory-4 (#656 6013608628)
Arc: neutral-product-standalone-operability
T021, U-2: the gate console's schemas (GA), resolved without vendoring (plan 038, feature
038-opendox-document-tool-self-maintenance)After set met: T004 #1245 → 260234b5; T020 #39 → c6d15b2.
The branch.
07361582, U-2;ecf0ba9b,542a5c40anda183dc46.72633f93(T020, T020, U-1: the governed host for the governed suites that drive the gate verbs (plan 038, P4F-5) #39 →c6d15b27);22a77c16(T024, T024, U-5: the H-1 shim, ideation_dashboard.session_git answers with opendox.session_git (plan 038, CF-3) #38 →8b64fae0);122bacce(T023, T023, U-4: swb-session.js spells the model's two names at its call sites again (plan 038) #42 →d45a0939), the head.mainis T021's three files.tests/conftest.py: T021's own append after T020's last hunk, with main's side empty. It was resolved to this branch's side, so the merged tree equalledecf0ba9b's byte for byte.What changes
Before this change,
gate_console._validate_contract_documentread both of its schemas fromPath(__file__).resolve().parents[2] / "contracts" / "schemas". That was openxFactory's own tree before the carve; in this leg it is the checkout root, which holds nocontracts/. Now each schema is read where its owner keeps it:gate-action-record.schema.yaml(openXdox-spec's) is read from this distribution's own packaged copy, throughopenxdox.contracts.verified_bytes("gate-action-record"). That call checks the copy againstcopies.yaml's digest before a byte is parsed. The copy record already holds this schema; nothing is added to it.demotion-execution-receipt.schema.yaml(openxFactory's) is read from a source the HOST registers with the new seam,gate_console.register_contract_schema_source(directory). It follows openDox's seam idiom:ContractSchemaSourceRefused;unregister_contract_schema_source()andcontract_schema_source_registered()complete it, so T030's host wiring can fill and take back the seam the wayopendox_hosttreats openDox's. With nothing registered, validating a receipt refuses by name (R1Q27 (a)):A host's schema is checked before it is used (
_schema_errors, from the Copilot rounds below). It is parsed, checked against the validator's own meta-schema, then applied. ANY failure at any of the three steps refuses asGateRefused, naming the step and the exception's class; aSchemaErrorkeeps its own "is not a valid JSON Schema" message. This matters because the demotion verb writes its receipt after it has moved files, and it reports only aGateRefusedthere as "DEMOTION EXECUTED BUT RECEIPT NOT WRITTEN". Any other exception ended the verb with a traceback.tests/conftest.py, one registration placed after U-1's: where the run is composed, it registers openxFactory'scontracts/schemas/from the composed tree at process start, as openxFactory's host wiring will from T030. A lone checkout registers none.Line numbers kept.
tests/test_no_hardcoded_status_words.py'sPROSE_EXEMPTpins seven prose f-strings ingate_console.pyby line number (315 to 1851). So:openxdox.contractsis imported inside_packaged_schema_text;_validate_contract_documentand its two call sites keep their exact line counts.No line before 1851 moves. That test file is not this task's, so it is unchanged.
The contract, from
tasks.mdtest_packaged_validator.pyunchanged and green.gate-action-recordis read from openXdox-code's own package;demotion-execution-receiptfrom a schema source the HOST registers (the composed conftest, then host wiring at T030); a lone checkout refuses by name. Nothing vendored."), ruled with plan 038 by Brett Heap, everything as recommended (#6566013547504).c6d15b27. T004 #1245 →260234b5.src/openxdox/gate_console.py(first: T021 → T074),tests/conftest.py(second: T020 → T021 → T073 → T074), and a newtests/test_gate_console_schema_source.py.plan.md§ "Parallel slices") has no row for the newtests/test_gate_console_schema_source.py. Lane 3 flagged this to lane 4 as a RECHECK FIX (lane3-to-lane4 log, 2026-10-06T00:30:30Z). The file is created here because T021's Files line names it, and no other task writes it.The falsifier, at the head
122bacceEnvironment.
maind45a0939, against the head122bacce.34c20641, force-reinstalled with--no-deps, as 12.5's falsifier does.51456835, withopenDox(e1e3a3c3, specf7ee3c76),openXdoxandopenXwalletinitialized recursively, and itsscripts/onPYTHONPATH(R1Q23 (a)).LANG=C.UTF-8, no colour forcing.e6380965: one commit, with an empty diff overscripts/, the submodule gitlinks andcontracts/;84f8ed83: #91, T025, the staging model,staging-workbench.jsand their tests, none of them T021's.1. The 16 schema nodes, composed, with
test_explicit_release_has_cli_and_http_parity. That node needs this GA as well as T020's HR (#39's erratum to R2-INV-P4F). All are intests/test_session_gates.py:56e1c238(first round)17 failedc52900e9(first round)17 failed07361582,ecf0ba9b17 passedc6d15b27/ head72633f9317 failed/17 passed8b64fae0/ head542a5c4017 failed/17 passedd45a0939/ head122bacce17 failed/17 passedThe causes at main:
{'error': 'gate_refused', 'message': "the gate-action record schema could not be loaded from <checkout>/contracts/schemas/gate-action-record.schema.yaml'"};FileNotFoundError: … '<checkout>/contracts/schemas/gate-action-record.schema.yaml';assert 409 == 200.The 16 nodes:
test_the_abandon_reason_survives_the_fr028_branch_cleanuptest_cleanup_deletes_the_branch_once_the_proposal_existstest_matching_proposal_evidence_from_before_abandonment_requires_fresh_releasetest_a_later_edit_elsewhere_in_the_change_folder_does_not_freshen_the_origintest_a_second_matching_record_recorded_after_abandonment_releases_cleanuptest_branch_advanced_after_abandonment_requires_explicit_current_head_releasetest_active_declared_origin_releases_cleanup_without_a_pick_edgetest_archived_exact_origin_releases_cleanup_after_the_tile_disappearstest_execution_receipt_releases_cleanup_for_the_exact_destinationtest_legacy_manifest_requires_an_exact_returned_artifacttest_explicit_human_release_cleans_a_true_orphan_and_records_whytest_non_staged_orphans_use_the_explicit_human_release_lane[cluster-cl-orphan]test_non_staged_orphans_use_the_explicit_human_release_lane[possible-pos-orphan]test_cleanup_unwinds_its_record_when_branch_deletion_failstest_cleanup_restores_the_exact_ref_when_record_finalization_failstest_the_durable_ending_marker_is_the_second_admissible_proof2. The lone-checkout test, the new
tests/test_gate_console_schema_source.py, 6 cases.The console imports openxFactory's
doc_healthwhen it loads (R1Q6 (d)), though nothing the cases run calls it. So each case runs the console in a child interpreter, beside a stand-indoc_healththat answers the three names the import reads:corpus.STATUS_SCAN_LINES,lines.split_keependsandlines.join_rows. The CI process never imports the console through it. The cases:register_contract_schema_source(<directory>);gate-action-record.schema.yamladmits nothing is never read for a record;GateRefused. That is any of: empty;required: 1; an unresolvable$ref; an unclosed flow sequence; a 5000-deep nesting; an impossible date; a cyclic mapping alias; a cyclic list alias;parents[...]index (by AST).Red at the base
c52900e9:5 failed in 1.38s. The causes:AttributeError: module 'openxdox.gate_console' has no attribute 'register_contract_schema_source';GateRefused: the gate-action record schema could not be loaded from <scratch>/contracts/schemas/…;gate_console.py reads a parents index at lines [642].Green at every T021 head;
6 passedat122bacce.3.
tests/test_packaged_validator.pyis unchanged and green.git diff main -- tests/test_packaged_validator.py src/openxdox/contracts/is empty.The 16 suites, composed, each alone (12.5's loop), at
122bacceagainstmaind45a0939d45a0939122bacceErrno 11orcannot fork.Mutants of the new guards, all killed
Round 1, at
ecf0ba9b. Each mutant was applied to a copy of the head and run againsttests/test_gate_console_schema_source.pyin a lone checkout. Every one exits 1:M10-no-composed-registration drops the conftest's registration. Run composed, it is KILLED:
1 failed, 16 passed. The failing node istest_execution_receipt_releases_cleanup_for_the_exact_destination, the only one of the 16 that validates a receipt.The Copilot fixes, each killed by the same case:
542a5c40, droppingRecursionError, or droppingValueError, from the parse step's catch;a183dc46, which replaced that tuple with a catch-all:ESCAPED RecursionError);ESCAPED RecursionError).The suite as CI runs it, and the protected suites
pyproject.tomlpindede32b4), at122bacce:selected=1113 passed=1109 skipped=4 failures=0 errors=0. All three pins hold: selected at least 1099, passed at least 1095, skipped exactly 4. None of the new cases skips.122bacce's tree ontod45a0939(c3260498; it touches the three files above). The merge head itself would be diffed against its first parent, which is no landing:Copilot, by round
07361582: r4194497045, medium, "Validate host schemas before demotion to prevent uncaught errors".07361582, all three cases escaped:AttributeError: 'NoneType' object has no attribute 'get';required: 1:TypeError: 'int' object is not iterable;$ref:_WrappedReferencingError.ecf0ba9bby checking the schema withcheck_schema()before applying it. Answered and resolved.ecf0ba9b: "Findings: None".72633f93: r4197914632, "Translate all YAML parser failures to GateRefused".yaml.safe_loadraises pastYAMLError. A 5000-deep[[…]]raisedRecursionError, andx: 2001-02-30raisedValueError.542a5c40. Answered (4198580351) and resolved.542a5c40: r4198638746, "Catch non-SchemaError exceptions from check_schema".properties: &p {a: {properties: *p}}loads, andcheck_schemathen raisedRecursionError. Copilot's own payload,allOf: &loop [*loop], is aSchemaErrorunder the jsonschema CI resolves today (4.26.0, from>=4.18).a183dc46: this was the third finding on the boundary, so each step now refuses any failure. Answered (4199311149) and resolved.122bacce(the head; review 5433288963, COMMENTED): r4199337845, "Avoid hard-coding jsonschema's version-specific cyclic failure".5988818366), for the holder at READY. It is not a bypass: production behaviour is correct whatever any jsonschema release does with the cycle. The finding concerns the test's precision only.could not be checked: RecursionErrorpins the outcome under the jsonschema CI resolves today (4.26.0) on purpose, because it shows the check step's own catch is what refuses.pyproject.tomlallowsjsonschema>=4.18, so a later release that refuses the cycle as aSchemaErrorwould fail this case loudly, and its message would name the new outcome.GateRefused:prefix, as the cyclic-list case does. The dropped-catch mutant stays killed either way. Answered (4199346014) and resolved.At the head
122bacce:validatepass,SonarCloud Code Analysispass (2 of 2).Files changed (against
main)src/openxdox/gate_console.py:_packaged_schema_textand_host_schema_text;_schema_errors;register_contract_schema_source,unregister_contract_schema_source,contract_schema_source_registeredandContractSchemaSourceRefused;GATE_ACTION_RECORD_SCHEMAandDEMOTION_RECEIPT_SCHEMA._validate_contract_documentnow takes the schema's text.tests/conftest.py: the composed run's registration of openxFactory'scontracts/schemas/.tests/test_gate_console_schema_source.py: new, 6 lone cases.Not done here
opendox_host.seams(), withcontract_schema_source_registeredandunregister_contract_schema_sourceas its take-back pair.test_the_cleanup_cli_verb_enforces_the_human_gate_and_deletes_the_branchneeds T020's HR, T026's AL and this GA together.🤖 Generated with Claude Code
Summary by Sourcery
Make gate-console contract validation use each schema owner’s source while refusing safely when host schema provisioning or validation is invalid.
New Features:
Bug Fixes:
Enhancements:
Tests: