Repository navigation
T022, U-3: the runbook placed from the composed tree, git-ignored, nothing vendored (plan 038, P4F-3) - #41
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>
…thing vendored (plan 038)
tests/test_session_runbook.py (its RUNBOOK) and tests/test_session_notebook.py
read docs/ideation-dashboard-session-runbook.md at this checkout, where the
runbook sat before the carve sent it to openDox-spec. The new
scripts/composed_placements.py links it, for a composed run, from the
composed tree (openDox/spec/docs/ under the openxFactory checkout) into
docs/. .gitignore names the placed path, so the checkout stays clean and
nothing placed can be committed.
Before it places anything, the script checks four things and refuses
(exit 2), naming the remedy, if any fails:
* the spec checkout's HEAD is the commit the openDox root's spec
gitlink pins;
* the runbook is tracked there, unmodified;
* the destination is ignored by .gitignore;
* anything already at the destination is the script's own link to that
same source.
It places all or nothing. --remove takes back the script's own links and
nothing else. The composed workflow (T029) and the documented local
composed run both call it.
Composed, the 10 runbook nodes turn green on T021's tree, 12.5's suites
read 82 red of the 174, and no green node turns red.
Refs opensoft/openxFactory#656 (CLAIMED 6013608959).
Lane: openXfactory-3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reviewer's GuideThis PR implements composed-tree operability by safely linking the pinned session runbook into an ignored destination, sourcing contract schemas from their owning distributions or registered host, and executing governed suites with scoped, isolated host integrations; it also adds focused tests for these boundaries. Sequence diagram for safe composed runbook placementsequenceDiagram
participant Runner
participant Script as composed_placements.py
participant Factory as openxFactory
participant Root as openDox
participant Spec as openDox-spec
participant Checkout as openXdox checkout
Runner->>Script: place(openxfactory)
Script->>Root: rev-parse --verify HEAD:spec
Root-->>Script: pinned spec commit
Script->>Spec: rev-parse --verify HEAD
Spec-->>Script: current HEAD
Script->>Spec: ls-files --error-unmatch runbook
Script->>Spec: diff --quiet HEAD -- runbook
Script->>Checkout: git check-ignore -q runbook
Script->>Checkout: inspect existing destination
alt all checks pass
Script->>Checkout: symlink_to(spec runbook)
Script-->>Runner: placed runbook
else guard fails
Script-->>Runner: PlacementRefused exit 2
end
Runner->>Script: remove(openxfactory)
Script->>Checkout: unlink only matching symlink
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 registered schemas can escape the refusal path, and the placement script lacks committed regression coverage.
Review effort: Balanced
Findings: 1
Open (2)
What changed in this PR
Adds composed-run support across the stacked T020–T022 changes.
Changes:
- Registers governed hosts for gate-driven suites.
- Resolves contract schemas from their owning package or host.
- Places the pinned runbook via a git-ignored symlink.
| File | Description |
|---|---|
.gitignore |
Ignores the placed runbook. |
scripts/composed_placements.py |
Validates and manages the runbook symlink. |
src/openxdox/gate_console.py |
Adds owner-aware schema resolution. |
tests/conftest.py |
Configures governed hosts and schema registration. |
tests/test_gate_console_schema_source.py |
Tests schema ownership and registration. |
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>
Lane: openXfactory-3 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lacements.py (Copilot r4194596317; holder ruling (a)) The holder's ruling on opensoft/openxFactory#656 (comment 6016356225, option (a)) adds tests/test_composed_placements.py to T022's files. The script changes the file system, so its guards now live in the suite CI runs, not in a scratch harness. Each case runs the script in a child interpreter, as the composed run calls it. The script under test is a copy of this checkout's script, inside a scratch checkout that carries this checkout's own .gitignore, and the tree it reads is a scratch openxFactory/openDox/spec tree pinned to commit A. No case writes into this checkout. 14 cases cover: * placing, with the checkout left clean; * the no-op of placing again; * --remove; * a spec off its pin; * a modified runbook; * an untracked runbook; * an uninitialized spec leg; * an uninitialized openDox root; * a destination .gitignore does not name; * no git; * a file, or a link elsewhere, at the destination, kept under both place and --remove. No case skips, and the file carries none of 12.5's governed-suite markers. Writing the uninitialized-leg case found a misreport in the script, now fixed. An uninitialized spec leg is an empty directory, so git answered for the openDox root above it, and the refusal read "spec is at <the root's HEAD>". The script now asks git about the leg only when the leg has its own .git, so the refusal reads "is not checked out" with the remedy. It refused before as well; only the message was wrong. Refs opensoft/openxFactory#656 (CLAIMED 6013608959). Lane: openXfactory-3 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Lane: openXfactory-3 Co-Authored-By: Claude Opus 5.5 (1M context) <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>
…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>
…nly by a local exclude (Copilot r4196244480; holder ruling 6026275158) git check-ignore honours .git/info/exclude and core.excludesFile, so the guard passed on a machine whose repository .gitignore no longer named the placement, and the placed link could then be committed from another checkout. The holder ruled the fix into T022, which owns the script (opensoft/openxFactory#656, comment 6026275158, item 2); Copilot raised it on #45, which stacks on this file. The guard now reads git check-ignore -v, which prints the deciding pattern as <source>:<line>:<pattern><TAB><path>, with the source relative to the repository root. It requires that source to be .gitignore. It also refuses a negated pattern: -v exits 0 for a "!" pattern although the path is not ignored, which the old -q exit status had covered. tests/test_composed_placements.py adds four cases: * a destination only .git/info/exclude names: refused; * a destination only core.excludesFile names: refused; * a local exclude beside the .gitignore line still places, because the root .gitignore outranks it and stays the source; * a .gitignore that negates the destination: refused. Against the script before this commit, the two local-exclude cases fail (2 failed, 16 passed). With it, 18 passed. All 17 mutants are killed, each by its own case, including MP-any-ignore-source (the dropped source check) and MQ-a-negation-counts-as-ignored. Refs opensoft/openxFactory#656 (CLAIMED 6013608959). 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>
Lane: openXfactory-3 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ilot r4201850292) By default git hash-object applies the path's clean filters and EOL normalization, so a filter in the spec checkout (.git/info/attributes plus a filter.<name>.clean command, or the text attribute on a CRLF edit) turned edited bytes back into the pinned blob. Against the script before this commit, both cases PLACED the link. The check now runs hash-object --no-filters, so it compares the raw bytes the placed link exposes. tests/test_composed_placements.py adds test_an_edit_a_clean_filter_would_hide_is_refused[clean-filter|eol-normalization]. Both cases fail before the fix and pass with it: 29 passed. MW-hash-through-clean-filters is killed, and all 22 mutants are killed. Refs opensoft/openxFactory#656 (CLAIMED 6013608959). Lane: openXfactory-3 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… CP, PI) (plan 038) (#47) Lane: openxfactory-4 (openXfactory-4-openDox_extraction) Authored by lane openXfactory-3, delegated by lane openxfactory-4 (#656 6021862323) Arc: neutral-product-standalone-operability ## T099: `tests/test_renderer.py`, the renderer's 21 composed reds (plan 038) This is slice T099 of T094's map (R2-INV-R9). It works under the holder's rulings at opensoft/openxFactory#656 comment `6021830531` and lane 3's CLAIMED comment `6021862323`. Plan 038's `tasks.md` has no entry for it yet: lane 4 records T094's slices after openxFactory#1251. Until then, the contract is the map's outline row plus those rulings. - **Realizes** (the outline row): DJ + VR 5, HH 12, CP 3 and PI 1, all in `tests/test_renderer.py`. That is 20 nodes, plus 1 on R9-W3. They are among requirement 9's declared composed tests (T073, F9.1). Each slice clears its own reds so that T073's composed step 5a can read green. - **Falsifier** (the outline row): the composed run of `tests/test_renderer.py`, alone, reads 0 for the 21 named reds. Its 3 LI nodes arrive with T095 (R9-W2), and its 1 SF node with T104's openDox pin. The unchanged set reads unchanged, and `tests/test_declared_exclusion.py` reads green lone. - **Ruled** (`6021830531`; and `6028383410` for the accepted limit below): - W1 is (A″): the harness copies `display.js` beside the module it copies, as #43 does. - W3: `test_dynamic_import_is_absent` is INVERTED, so it now asserts the ruled dynamic import of contributed bindings. - R9-R1 (a): where a test asserts words, they are the SERVED words. - **Decisions:** - No node of this file asserts a display WORD, so R9-R1 (a) needs no served display here (point 1 below). - Every node keeps its name, the inverted pin's (point 4 below) and the collapse node's (Copilot's rename finding, declined below), so the 21 nodes T094's map and T073's declared run account for keep their identity. - **After:** none. Nothing here waits on T095. ### What changed: one file, `tests/test_renderer.py` 1. **DJ + VR, 5 nodes: the `model.js` harness.** - Since § 3.4 slice S7 (openDox-code `1e46971`), `model.js` imports `./display.js`, which is itself import-free. The harness copied `model.js` alone, so node read `ERR_MODULE_NOT_FOUND`. It now copies `display.js` beside it, as #43's harness copies the staging model's siblings, and writes a `package.json` of `{"type": "module"}`. - The six columns are keyed BY ROLE since S7: `source`, `grouping`, `candidate`, `selection`, `submission` and `completion`, where they were `docs`, `clusters`, `possibles`, `staged`, `proposals` and `realized`. The harness and its assertions now read the role keys. Only the keys moved: every count is the one the test always pinned (6/6/5/2/1/1; 16 topic, 1 pick and 1 flow; 8 left after the collapse). - No assertion reads a display word, so the harness hands `buildFunnelModel` no display, and the model takes openDox's neutral one. 2. **HH, 12 nodes: ONE `Host` line.** `_raw_get` sent two `Host` lines: `putrequest(..., skip_host=False)` added one, then `putheader("Host", ...)` added a second. openDox-code `390e2c2` (T103, openDox-code#80) admits a loopback request only with exactly one, and answered every request with `403 invalid_host` ahead of every route. `putrequest` now skips its own line. 3. **CP, 3 nodes: pinned claims, respelled to the carve's ruled design.** - **The fetch-bearing set** (`test_data_fetches_target_only_snapshot_and_source_passthrough`, and its arithmetic in `test_the_session_transport_stays_out_of_the_fetch_bearing_set`). - Slice S4 (openDox-code `69d2760`) split `repo-selector.js` under RULED Q3 (openxFactory#656 `5642758731`): "a route constant travels with the binding that calls it". So the set gains `projection-index.js` (the index read, count 1). - `repo-selector.js` falls from 6 call sites to 2: the refresh POST and the register-projection read, which stay class A. - The create-project and edit-project POSTs went with `gate-projects.js`, which slice S5 leg B (`8efb3cf`) then moved into this leg's own views. That puts them outside openDox's class-A surface, which is what this pin covers. - The apply POST left the bundle: it is openxFactory's own fulfilment lane. - The map traced only the new file. The count change was measured here, against openDox-code `84f8ed83`. - The sibling test's self-quoting guard (its own "finding 24") now quotes six files and six counts, each needle still assembled at runtime. - **The collapse modifier** (`test_six_column_default_and_five_column_collapse_marker`). openDox-code `3c3a9e3` (openDox-code#35) renamed `.collapsed-docs` / `.only-docs` to the role, `.collapsed-source` / `.only-source`. 4. **PI, 1 node, W3: `test_dynamic_import_is_absent` INVERTED.** - The old pin asserted that no bundle file spelled `import(` anywhere. It read red on a COMMENT in `app.js`. - The pin now asserts that the bundle's dynamic imports, in CODE, are exactly the carve's ruled loaders of contributed bindings, each argument exactly as written: - `views/view_extension.js`: `new URL(binding.module, bundleRoot).href`. This is the view registry's loader, written by slice S3 (`c5edac8`); slice S5 leg B (`8efb3cf`, RULED Q1 to Q12) mounts the contributed gate loop through it. - `views/repo-selector.js`: `"./projection-index.js"`, from slice S4 (`69d2760`, RULED Q3). - `views/intent-binding.js`: `"./intent-feed.js"`, from slice S2 (`c7ab3d8`, RULED Q5). - The pin also asserts that the registry's loader resolves against the bundle's own root (`new URL("../", import.meta.url)`), read from executable code. - So none of the three can pull a URL: - Two name a bundle-relative sibling literally. - The third is bounded where a binding is declared: openDox's `viewBinding()` refuses an absolute URL, a root-relative path and a `..` climb. openDox-code's own `tests/test_view_registry.py` holds that refusal. - **How the scan reads code** (`_without_comments`, shaped by Copilot's two findings): - It reads each file WHOLE, as a JavaScript lexer reads it. It tracks string, template and regular-expression literal state, and replaces each comment with one space. It KEEPS the literals, so an `import(` spelled inside one still counts: the scan may over-count, but it never hides a call. - So none of these hides a call: a line break or a comment between `import` and `(`; a generator method's line that opens with `*`; a `//` comment ended by CR, U+2028 or U+2029; a spread (`...import(`); or U+FEFF before `(`. Only a member call (`a.import(`) is skipped. - It reads `.js`, `.mjs`, `.cjs`, `.html`, `.htm` and `.css` files. - **Two readings.** The default reading keeps every spelling, so it can only over-count: it refuses an unknown import. The `executable_only` reading skips a spelling inside a literal, and it must equal the same three sites: it proves each ruled loader is an executable call, so a decoy string cannot stand in for a removed loader. - **The loader's base** is read as every executable assignment to `bundleRoot` (plain or compound, comments not read, literals skipped). It must be exactly `bundleRoot = new URL("../", import.meta.url)`, so neither the old line kept in a comment or a string nor a later reassignment passes. - **The scan's own regression cases**, committed: - `test_the_dynamic_import_scan_reads_code_as_a_lexer_does`: 21 probe modules, each written as exact bytes, with the site map the scan must read; - `test_the_dynamic_import_scan_reads_every_script_suffix`; - `test_the_executable_reading_counts_only_calls`: 6 cases; - `test_the_loader_base_is_read_from_executable_code`: 5 cases. - **Stated limit:** the lexer tells a regular-expression literal from a division by the token before the `/`, not by a full parse. So a regex literal straight after `)` (as in `if (x) /re/.test(y)`) reads as a division. That is the limit the holder accepted for openDox-code's import-free scan (opensoft/openDox-code#91, r4198449977): closing it needs a JavaScript parser. - The name is kept on purpose. The node T094's map and T073's declared run account for keeps its identity, and the name still reads true: a dynamic import is absent from every line of code but these three. ### The falsifier, composed (R1Q23 (a)), each file alone **The form:** - openxFactory `92010d3e` (the commit `tests/composed_host_pin.yaml` names), with `openDox`, `openXdox` and `openXwallet` initialized recursively, and its `scripts/` on `PYTHONPATH`; - openDox installed from openDox-code `main` `84f8ed83` (T025), over the pin; - T022's one placement: the session runbook, linked from the composed tree's openDox-spec at `f7ee3c76` before the run and removed after it. T022's script is on #41, not `main`, and it refuses a checkout whose `.gitignore` lacks its line. So the run makes that one link itself, with the script's own checks (the source tracked and unmodified, and nothing already at the destination). The checkout read clean after each run; - the colour variables unset and `TERM=dumb`; - each file alone, with its own short TMPDIR (38 bytes, under the PostgreSQL socket limit): `python -m pytest -q -p no:cacheprovider -rfE --junitxml=… <file> < /dev/null` from the checkout root. | `tests/test_renderer.py`, alone | result | |---|---| | base: `main` `fc1115d6` | `exit 1; 28 passed, 22 failed, 0 errors, 0 skipped` | | head: `99508076` (`main` merged) | `exit 1; 82 passed, 1 failed, 0 errors, 0 skipped` | - **Red to green: 21, and 0 green to red.** The head's other 33 nodes are the scan's new regression cases, all green. The 21 are exactly the named nodes: - DJ + VR: `test_model_derives_the_six_columns`, `test_model_edge_derivation`, `test_model_tally_equals_edge_count_through_the_real_js`, `test_five_column_collapse_drops_doc_edges`, `test_model_degrades_on_empty_snapshot`; - HH: `test_serve_snapshot_route_and_alignment`, `test_serve_static_bundle`, `test_serve_source_passthrough`, `test_serve_divergence_diverged`, `test_serve_divergence_unknown_without_head`, and `test_serve_rejects_path_traversal` × 7; - CP: `test_data_fetches_target_only_snapshot_and_source_passthrough`, `test_the_session_transport_stays_out_of_the_fetch_bearing_set`, `test_six_column_default_and_five_column_collapse_marker`; - PI: `test_dynamic_import_is_absent`. - **The 3 LI nodes cleared with T095 (#46), at the base:** `test_generate_and_open_generates_and_always_prints_url`, `test_generate_and_open_respects_no_open` and `test_generated_snapshot_is_schema_shaped` were red at `36bbef79` and are green at `e2340bb6` and since. They were T095's under R9-W2, not this slice's. - **Still red: 1, not this slice's.** SF, which arrives with T104's openDox pin (`doxchat-no-model` and `doxchat-untrusted` have no `styles.css` rule in the openDox these runs install, openDox-code `84f8ed83`): `test_every_constructed_doxbench_class_has_a_styles_rule`. - **Every base and head along the way read the same way.** The bases `8b64fae0`, `d45a0939` (T023), `f70bc9c3` (T021) and `36bbef79` (T026) read the same 25 failures, and `e2340bb6` (T095) read the same 22. Every head of this PR, from `f82abd40` to `99508076`, read the same 21 flips with 0 green to red. ### The unchanged set (the same runs, each file alone) | set | base `fc1115d6` | head `99508076` | |---|---|---| | 12.5's 16 governed suites (`git grep -l -e open-pr -e open_pr -e FakePullRequests -- 'tests/test_*.py'`) | 842 passed, 0 failed | 842 passed, 0 failed | | F5.2's 5 (`test_generator`, `test_session_snapshot`, `test_snapshot_determinism`, `test_snapshot_registry`, `test_snapshot_validation_launch`) | 123 passed, 0 failed | 123 passed, 0 failed | | `tests/test_column_contributions_governed.py` | 9 passed | 9 passed | - All 22 files read node for node identical at base and head. - `tests/test_declared_exclusion.py` **lone**, with CI's install (openDox at its pin `dede32b4`) and no composed tree, at `99508076`: `207 passed in 27.51s`. Run lone, `tests/test_renderer.py` still fails on its declared reason, `doc_health`. ### Mutants: 32 of 32 killed The bundle mutants run over a fresh copy of the installed openDox bundle, which a test-only plugin hands to the suite as its `WEB`. The plugin is not committed. The control run, with the copy unmutated, read `4 passed`. | mutant | what goes red | |---|---| | M1: an extra `await import("https://cdn.example/x.js")` in `views/wheel.js` | `test_dynamic_import_is_absent` | | M2: the ruled projection-column import removed from `repo-selector.js` | `test_dynamic_import_is_absent` | | M3: `"./intent-feed.js"` made an absolute URL | `test_dynamic_import_is_absent` | | M4: a spaced `import ("./display.js")` added to `views/model.js` | `test_dynamic_import_is_absent` | | M5: `/* a note */ const x = import("https://…")`, code after a block comment, in `app.js` | `test_dynamic_import_is_absent` | | M6: the registry loader's base moved off the bundle (`new URL("https://cdn.example/")`) | `test_dynamic_import_is_absent` | | M7: the registry loader's `import()` removed | `test_dynamic_import_is_absent` | | M8: an extra `fetch(` in `projection-index.js` | both fetch-set pins | | M9: the old `.collapsed-docs` spelling restored in `styles.css` | `test_six_column_default_and_five_column_collapse_marker` | | M12: `import`, a line break, then `("https://…")` | `test_dynamic_import_is_absent` | | M13: a generator method, `*load() { yield import("https://…"); }` | `test_dynamic_import_is_absent` | | M14: `import /* why */ ("https://…")` | `test_dynamic_import_is_absent` | | M15: a string holding `//` ahead of a call on the same line | `test_dynamic_import_is_absent` | | M16: a regex literal holding a quote and `//` ahead of a call | `test_dynamic_import_is_absent` | | M17: `// c` CR `import("https://…")` | `test_dynamic_import_is_absent` | | M18: `// c` U+2028 `import("https://…")` (the previous scanner missed it) | `test_dynamic_import_is_absent` | | M19: `[...import("https://…")]` (the previous scanner missed it) | `test_dynamic_import_is_absent` | | M20: `import` U+FEFF `("https://…")` (the previous scanner missed it) | `test_dynamic_import_is_absent` | | M21: a CR LF line continuation in a string, then a URL string and a call | `test_dynamic_import_is_absent` | | M31: the real projection loader replaced by a decoy string, `'import("./projection-index.js")'` | `test_dynamic_import_is_absent` | | M32: the loader's base moved to `new URL("../../", import.meta.url)`, with the old line kept in a comment | `test_dynamic_import_is_absent` | | M10: the test's own `repo-selector.js` count pin relaxed from `==` to `>=` (in a copy of this file) | `test_the_session_transport_stays_out_of_the_fetch_bearing_set` | | M11: the test's own `projection-index.js` count pin deleted (in a copy of this file) | `test_the_session_transport_stays_out_of_the_fetch_bearing_set` | The SCANNER mutants (M22 to M30) each run in a temporary copy of this file, and each is killed by a named regression case: | mutant | the case that goes red | |---|---| | M22: U+2028 and U+2029 dropped from the line ends | `u2028-ends-line-comment`, `u2029-ends-line-comment` | | M23: a lookbehind that skips any dot | `spread` | | M24: U+FEFF dropped | `ufeff-before-paren` | | M25: the member-call skip removed | `member-call-is-not-one`, `optional-member-call-is-not-one` | | M26: string state removed | `slashes-in-string`, `slashes-in-template`, `crlf-continuation-in-string` | | M27: regex-literal state removed | `escaped-slash-then-slash-in-regex`, `in-a-regex` | | M28: the executable reading ignores literals | `in-a-string`, `in-a-template`, `in-a-regex`, `call-beside-a-decoy` | | M29: the loader's base ignores literals | `changed-with-the-old-line-in-a-string` | | M30: the loader's base reads plain assignments only | `a-later-compound-assignment` | DJ's own mutant is the base: the same harness without the `display.js` copy reads `ERR_MODULE_NOT_FOUND` on all five model nodes. ### CI at the head At the head `99508076`, every check passed: - `validate`, run 37553249790: CI's own pytest step reads `triple: selected=1107 passed=1103 skipped=4 failures=0 errors=0`, against `pins: selected>=1099 (margin 8) passed>=1095 (margin 8) skipped==4`. This is the lone CI form, and it is unchanged: `tests/test_renderer.py` is a declared file, so the step never collects it. - SonarCloud Code Analysis. - `copilot-pull-request-reviewer`. ### Copilot Reviews at each head, all requested through the reviewer API: | head | review | verdict | findings | |---|---|---|---| | `f82abd40` | 5433258934 | Changes recommended | 1: the line scan missed `import` + newline + `(` and generator lines (fixed, `19be4997`) | | `19be4997` | 5433357731 | Changes recommended | 1: `//` ends at CR, U+2028 and U+2029 too (fixed, `8081f9ff`, with spread, U+FEFF and CR LF found by audit) | | `8081f9ff` | 5435170341 | Changes recommended | 2: regression tests for the scanner (fixed, `ec1486ca`); rename `test_five_column_collapse_drops_doc_edges` (declined, below) | | `ec1486ca` | 5435232658 | Needs a closer look | None | | `75eb3779` | 5435680993 | Needs a closer look | None | | `a95900c0` | 5435796042 | Changes recommended | 2: prove the ruled loaders are executable calls, and read the loader's base from executable code (both fixed, `781c7917`) | | `99508076` | 5436219525 | Changes recommended | 1: a shadowing `bundleRoot` binding at the import site (an ACCEPTED LIMIT, ruled, below) | **Threads: 7, all answered and resolved, 0 unresolved.** - **The rename, declined (r4200975189).** `test_five_column_collapse_drops_doc_edges` keeps its name. It is one of the 21 nodes that T094's map and T073's declared run account for BY NAME, so a rename would turn a red-to-green flip into a vanished node and a new one. The name still holds: the edges the collapse drops are the source station's edges, built from the snapshot's `document_edges` and checked against `document_links`. Only the column KEY became the role. - **The accepted limit (r4201878108), RULED by the holder** under the convergence rule (`5988818366`) at opensoft/openxFactory#656 comment `6028383410`. The pin is a static, whole-source lexer check, so it cannot resolve scopes. A parameter or inner binding named `bundleRoot` that shadows the base at the import site would not be seen. That payload needs a structural edit of reviewed openDox code, not a simple one. The head's review therefore still reads "Changes recommended" for this one ruled limit. ### Files changed `tests/test_renderer.py`, and nothing else. ### What is NOT done - **The 1 node still red:** SF, which is T104's. T104 is on openDox-code `main` (`d616da3`), and the node clears composed once its openDox pin pair moves. - **A stale comment in a file that is not this slice's:** `src/openxdox/web/views/swb-session.js:9` still says `test_renderer.py:130` pins the set "to five". The set is six now, and the line number has moved. It is a comment, so no test reads it. - **`validate.yml`'s floors are untouched.** `tests/test_renderer.py` is a declared file that the lone CI step never collects, and only T029 raises the floors. 🤖 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>
Path.is_symlink() looked only at the final component. With spec/docs replaced by a link to an outside directory holding identical bytes, the script PLACED a link to that outside file, which the pin does not fix. Every component from the spec leg down to the file is now checked, and a link anywhere is refused. tests/test_composed_placements.py adds test_a_runbook_reached_through_a_linked_directory_is_refused. It fails before the fix (placed, rc 0) and passes with it: 30 passed. MX-only-the-last-component is killed, and all 23 mutants are killed. Refs opensoft/openxFactory#656 (CLAIMED 6013608959). 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>
Lane: openXfactory-3 Co-Authored-By: Claude Opus 5.5 (1M context) <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 2 days and 11 hours by commenting |
|
|
READY (holder, manual landing) at 57b4f80 Lane: openxfactory-4 (openXfactory-4-openDox_extraction) Holder, on Brett's word "land lane 3's PRs when green" (2026-10-06). At |
…s role keys and the served words (plan 038) (#50) Lane: openxfactory-4 (openXfactory-4-openDox_extraction) Authored by lane openXfactory-3, delegated by lane openxfactory-4 (#656 6021862323) Arc: neutral-product-standalone-operability ## T097, display harnesses (B): `display.js` beside the copied module, S7's role keys, the served words (plan 038) - **Contract:** there is no `tasks.md` entry yet; lane 4 records T094's slices after T002 (opensoft/openxFactory#1251). Until then the contract is the slice outline row of T094's map (R2-INV-R9, posted as opensoft/openxFactory#656 comment 6021830607) and the holder's rulings (comment 6021830531). The row reads: **T097, display harnesses (B): DJ (A″) + VR keys; files `tests/test_wheel_model.py`, `tests/test_grouping.py`, `tests/test_project_aggregates.py`; depends on R9-W1; 87 reds (wheel_model 76, grouping 7, project_aggregates 4).** - **Realizes:** requirement 9's declared composed tests (#1144, through T073's composed step 5a, F9.1). This slice repairs 87 of the composed reds T094 mapped. - **Falsifier:** the composed run of the three files, each alone, red at the base and green at the head; plus the unchanged set (12.5's 16 governed suites, F5.2's 5 protected files, `test_column_contributions_governed.py` at 9 passed, `tests/test_declared_exclusion.py` green run lone). - **Ruled:** - **R9-W1 (A″)** (comment 6021830531): each harness copies `display.js` beside the module it copies, as #43 does. No openDox module is made import-free. - **R9-R1 (a)** (same comment): "every word-asserting pin is respelled, through VR, to the SERVED words". It is given under Brett Heap's served-display ruling for the staging suite (comment 6016648451: "the helpers … hand each harness the SERVED governed display"). The holder's record line for this slice (comment 6023517122): "T097's served-display respelling is R9-R1 (a) applied (6021830531 ...). It needs no new ruling." - **Decisions:** none of this slice's own. No After dependency; it needs nothing from T095. ### What this PR does **DJ, R9-W1 (A″).** Since § 3.4 slice S7, `wheel-model.js` and `grouping.js` import `./display.js`. Each harness copied its module ALONE, so node failed with `ERR_MODULE_NOT_FOUND … display.js`. That is the cause of every one of the 87 reds at the base, and of nothing else. Each harness now copies `display.js` beside its module, under its own name (`display.js` imports nothing), and writes a `package.json` so that node reads `.js` as a module, as #43's harness does. `test_wheel_model.py`'s ten copy sites go through one helper, `_copy_wheel_model`. **VR, the keys.** - The model's wheel keys are S7's six stage roles (`WHEEL_KEYS = STAGE_ROLES`). The old wheel names map to them as follows: `documents` → `source`, `clusters` → `grouping`, `possibles` → `candidate`, `staged` → `selection`, `active` → `submission`, `archived` → `completion`. - Every wheel key the cases pass in or the assertions read is respelled: the deck order, edge endpoints, the second-degree and gather cases, the expand events, the 48 action cases, and `test_project_aggregates.py`'s three `actionsFor` calls. - `grouping.js` keys its tallies `"tally:" + role`, so the tally keys are respelled to `tally:source`, `tally:grouping`, `tally:candidate`, `tally:submission` and `tally:completion`. - Snapshot FIELD names (`documents`, `clusters`, `staged_topics`, …) are schema and are unchanged, and so are the action cases' ids. **VR, the words, under R9-R1 (a): the SERVED words.** - **What reads the facet.** Since S7, three things the expanded tile's cases assert come from the display facet: - a verb row's label is `display.act(role)`; - the possibles wheel's promotability matches `display.registerState(...)`; - the packet flyout's labels and its ordered front matter are the facet's artifact axis. - **Where the display comes from.** The page hands every one of them the served display: `wheel.js` puts `display: vocab` in each action env and passes `vocab` to `packetGroups`. So the two harnesses whose cases assert those words or values, the expand harness and the staged harness, are handed the display the GOVERNED host serves. They install it with `readDisplay({ display: cases.display })`, as #43's harnesses do. - The helper `_served_display()` reads the host from the conftest's `governed_host()` (T020), through `display_profile.host_display`'s injected-profile seam. - Nothing is registered, and no conftest or host-plane file is touched. - Composed, the host is openxFactory's composite (`OpenxFactoryProfile`, `host_facet: declared`, `host_profile: openxfactory-engineering`). A probe measured the manifest read this way as equal to the one served when the governed host is registered. - A case that supplies no env keeps none. - **The respelled words, as measured:** | assertion | was | served | |---|---|---| | the staged tile's propose row | `▶ draft proposal` | `▶ Propose` | | the four commission verbs | `▲ promote to staging`, `✻ research brief`, `✦ derive possibles`, `◀ demote` | `▲ Promote to staging`, `✻ Research brief`, `✦ Derive possibles`, `◀ Demote` | | the packet flyout's group labels | `packet`, `spec deltas` | `packet files`, `Spec deltas` | - **The values.** The served `register_state` values (`latent` is the captured role) keep the live-captured possibles fixtures promotable. The served packet order (`proposal.md`, `design.md`, `tasks.md`) keeps the review-order claim. **OQ-R9-3, after T095 (#46).** `test_landed_parses_a_real_archived_change_delta` read `REPO_ROOT.parent.parent / "openxFactory"`, which a runner's layout never holds, so it skipped there. It now reads the composed tree through T095's `openxfactory_root()` (OQ-R9-4) and skips only where the run is not composed. Composed it passes, reading this run's own openxFactory checkout. The skip's mutant is not killable: lone, the module stops at collection on `doc_health` before the case runs. ### The falsifier, composed (R1Q23 (a)), each file alone The composed form: - openXdox-code at the commit named; - openDox installed from openDox-code `main` `84f8ed83` (`pip install -e ".[test]"`, then that commit force-reinstalled); - openxFactory at `92010d3e` (`tests/composed_host_pin.yaml`), with `openDox`, `openXdox` and `openXwallet` initialized recursively and its `scripts/` on `PYTHONPATH`; - the colour variables unset and `TERM=dumb`; - each run: `python -m pytest -q -p no:cacheprovider -rfE --junitxml=… <file> < /dev/null` from the checkout root; - Python 3.12, pytest 8.4.2, Node v24.21.0. T022's placement script is not on `main` (#41 is open). Run from #41's bytes, it refuses at `main` for want of its `.gitignore` line, so nothing was placed. It places only the session runbook, which none of these files reads. **Base, `main` `fc1115d6`:** ``` tests/test_wheel_model.py rc=1 77 failed, 3 passed tests/test_grouping.py rc=1 7 failed, 4 passed tests/test_project_aggregates.py rc=1 4 failed, 8 passed ``` - 88 failed: the map's 87, every one of them on `ERR_MODULE_NOT_FOUND … display.js`, plus `test_wheel_model.py::test_landed_parses_a_real_archived_change_delta`. - That 88th node reads `REPO_ROOT.parent.parent / "openxFactory"`. It runs only because the local clone's layout happens to hold an openxFactory checkout two levels up; on a runner it skips (OQ-R9-3). **Head, `c16c05eb`** (the T097 commits `b401bc0e` and `9a0704a6`, with `main` merged in): ``` tests/test_wheel_model.py rc=0 80 passed tests/test_grouping.py rc=0 11 passed tests/test_project_aggregates.py rc=0 12 passed ``` The 87 read 0 red. The 88th now reads the composed tree (below) and passes; it no longer skips on a runner. ### Mutants (each applied alone, run composed, file restored byte-identical) | mutant | result | |---|---| | no `display.js` copy, `test_wheel_model.py` | killed: 77 failed | | no `display.js` copy, `test_grouping.py` | killed: 7 failed | | no `display.js` copy, `test_project_aggregates.py` | killed: 4 failed | | the neutral display in place of the served one (`"display": None`) | killed: 8 failed (the word and value nodes) | | no `package.json`, each of the three files | killed with node's module-syntax detection off (`NODE_OPTIONS=--no-experimental-detect-module`): 77, 7 and 4 failed. The unmutated head passes in that mode (80, 11, 12). Node 24's detection otherwise reads `display.js` as a module unaided. | | the expand harness hands the display to a case with NO env too | survives, and it is equivalent: with no env, `actionsFor` offers only rows with no `visible` predicate, and every one of those carries a literal label | ### The unchanged set, composed, base `fc1115d6` against head `c16c05eb` Node-for-node identical: 974 nodes at both, the same 10 failed at both, and `diff` empty. - **12.5's 16 governed suites:** 14 green at both (`branch_session` 132, `doxbench_mutation_boundary` 80, `doxbench_share` 34, `gate_loop_views` 74, `gateway_provenance` 24, `hosted_actor` 5, `session_commits` 22, `session_confinement` 53, `session_gates` 81, `session_lifecycle` 53, `session_records` 29, `session_transaction` 57, `session_verbs` 65, `staging_workbench` 83 passed); failing identically at both (open slices own these, not this one): `session_notebook` 1 and `session_runbook` 9. - **F5.2's 5:** `generator` 45, `session_snapshot` 23, `snapshot_determinism` 6, `snapshot_registry` 40 and `snapshot_validation_launch` 9 passed, at both. - **`test_column_contributions_governed.py`:** 9 passed, at both. - **Run lone** (no openxFactory): `tests/test_declared_exclusion.py` 207 passed at both. Each of the three files still stops at collection on its declared reason, `ModuleNotFoundError: No module named 'doc_health'`, at both. - **The lone CI form:** `validate.yml` collects the suite less the declared exclusion, and all three files are declared. So its floors cannot move. ### Files changed - `tests/test_wheel_model.py` - `tests/test_grouping.py` - `tests/test_project_aggregates.py` ### CI and review at `c16c05eb` - `validate`: run 37554225217, success. - Copilot: review 5440546734 at `c16c05eb`, "Needs a closer look", **Findings: None**. Its one note is the merge of current `main` and the composed run after it, which the next section covers. 0 review threads, 0 unresolved. Earlier heads read the same: `b3c2d04e` and `489081e2`, Findings: None, 0 threads. ### NOT done - Nothing in this slice's scope. - `main` has moved past the measured base `fc1115d6`. The branch is not merged again here: the holder's landing loop brings each lane-3 PR up to date (update-branch) before it lands. This PR changes only the three declared test files above, and none of the commits since `fc1115d6` touches them. 🤖 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 T101 (#48), T098 (#51), T103 (#49), T096 (#53), T100 (#54), T099 (#47), T021's follow-on (#55), T102 (#52), T022 (#41, landed as aefd109), T097 (#50) and T096's follow-on (#56). Conflicted: scripts/composed_placements.py and tests/test_composed_placements.py, both T022's files, which this branch carried from #41 through #44. Main's landed side is kept for both. tests/declared_exclusion.yaml merged clean (T101's entry edits beside T073's header and open_until). Lane: openXfactory-3 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…nd T096's follow-on #56 landed) Two files conflicted (add/add), both T022's, which this branch had carried from #41's head 1f2d685: scripts/composed_placements.py and tests/test_composed_placements.py. Main's side was taken for both, as it was for .gitignore (auto-merged to main's). After this merge the tree differs from main only in T029's two files. Lane: openXfactory-3 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>






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 ofmaine7815ac5. T022's last commit before it is1f592766, the merge ofmainda736090, and T022's three files are byte-identical between the two. The diff againstmainis T022's three files.c40af91b: U-3;7a149b70: the committed tests, under the holder's Files ruling;dc879beb,146ae923,74f3a4fb,1f2d6858and2c86dcca: the review fixes below.42d7ea97(f70bc9c3),c22b5df7(36bbef79),49c1026d(e2340bb6),214f3613(fc1115d6),60c56a47(6f259156) and1f592766(da736090).42d7ea97this branch still carried T021's olderecf0ba9bversions ofsrc/openxdox/gate_console.pyandtests/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 constantRUNBOOK) andtests/test_session_notebook.pyreaddocs/ideation-dashboard-session-runbook.mdat 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.pylinks the runbook, for a composed run, from the composed tree intodocs/. The source isopenDox/spec/docs/ideation-dashboard-session-runbook.mdunder the openxFactory checkout.Before it places anything, it refuses (exit 2), naming the remedy, unless all of these hold:
.git, andgit rev-parse --show-toplevelresolves to the directory itself. So no enclosing repository answers for them, and nocore.worktreepoints the checks elsewhere.specgitlink pins.git hash-object --no-filtersequalsHEAD:<path>. So no index flag, clean filter or EOL normalization can hide an edit..gitignoreitself, read throughgit check-ignore -v. A local exclude alone (.git/info/exclude,core.excludesFile) is refused, and so is a negated pattern.Every git call drops the ambient repository variables (
GIT_DIR,GIT_WORK_TREE,GIT_INDEX_FILEand kin, asopenxdox.domain_corpus_adapterdrops them), so a hook cannot redirect the checks. The script places all or nothing.--removetakes back its own links and nothing else. It needs only the standard library andgit..gitignorenames/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.yamlis 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.mdopenDox/spec/docs/…at the root's pinned spec commit) by T022's script, git-ignored; nothing vendored."), ruled with plan 038 by Brett Heap (#6566013547504).scripts/composed_placements.pyand.gitignore.6016356225, option (a): add the newtests/test_composed_placements.py.check-ignoresource fix (Copilot r4196244480, raised on DRAFT: T073: the composed declarations (ARC-Q2 (a)): every declared entry runs composed, the rail files get the host's rail (plan 038) #45) is ruled into this PR by #6566026275158, item 2.The falsifier, composed
Environment.
51456835, with its submodules initialized recursively andscripts/onPYTHONPATH(R1Q23 (a)).e1e3a3c3pins openDox-specf7ee3c76, the spec checkout's HEAD.34c20641, force-reinstalled with--no-deps.LANG=C.UTF-8, no colour forcing.At the head, the runbook is placed, then removed afterwards, which leaves the checkout clean:
56e1c238(first round)9 failed+1 failedf70bc9c3,36bbef79,6f2591561f592766, placedThe 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_parsertest_the_runbook_names_the_two_required_flags_and_what_they_are_fortest_the_runbook_claims_no_flag_generate_and_open_does_not_havetest_the_runbook_says_what_a_wrong_repo_root_doestest_the_runbook_does_not_call_the_unvalidated_snapshot_routinetest_the_runbook_lists_the_posture_readings_the_page_can_producetest_the_derived_artifact_inventory_names_every_container_the_code_writestest_the_runbook_names_the_pull_the_refusal_namestest_the_runbook_states_what_a_session_may_rewritetest_session_notebook.py:test_the_runbook_names_the_bound_by_its_production_constantsThe 16 suites, each alone (first round, at
c40af91b/7a149b70against main56e1c238):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.pyreads30 passedat1f592766. 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:test_a_spec_checkout_off_the_opendox_roots_pin_is_refusedtest_a_modified_runbook_is_refusedtest_an_edit_a_clean_filter_would_hide_is_refused[clean-filter]test_a_runbook_the_pinned_commit_tracks_as_a_link_is_refusedtest_a_runbook_reached_through_a_linked_directory_is_refusedtest_an_opendox_root_answered_by_an_enclosing_repository_is_refusedtest_a_spec_leg_whose_work_tree_is_configured_elsewhere_is_refusedtest_an_opendox_root_without_a_spec_gitlink_is_refusedtest_an_uninitialized_spec_leg_is_refused_as_not_checked_outtest_a_runbook_the_spec_does_not_track_is_refusedtest_a_destination_the_gitignore_does_not_name_is_refusedtest_a_destination_only_a_local_exclude_names_is_refused[info/exclude]test_a_gitignore_that_negates_the_destination_is_refused.gitignoredrops the runbooktest_a_pinned_unmodified_runbook_is_linked_and_leaves_the_checkout_cleantest_ambient_git_repository_variables_do_not_redirect_the_checks[clean]test_a_file_at_the_destination_is_refused_and_kept[place]test_a_file_at_the_destination_is_refused_and_kept[remove]test_a_link_elsewhere_at_the_destination_is_refused_and_kept[place]test_a_link_elsewhere_at_the_destination_is_refused_and_kept[remove]test_remove_takes_back_its_own_link_and_nothing_elsetest_placing_again_is_a_no_optest_without_git_on_path_it_refusestest_a_modified_runbook_is_refusedThe suite as CI runs it, and the protected suites
pyproject.tomlpindede32b4), at1f592766with nothing placed:selected=1195 passed=1191 skipped=4 failures=0 errors=0. All three pins hold.1f592766's tree ontoda736090, which touches T022's three files: "ok: 0 protected edit(s), each entered and holding".Copilot, by round
At
c40af91b:ecf0ba9b, merged here at8d02a1b3.6016356225), making it fix-now. Fixed in7a149b70(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 DRAFT: T073: the composed declarations (ARC-Q2 (a)): every declared entry runs composed, the rail files get the host's rail (plan 038) #45 (
26fe63e7), on this file: r4196244480,check-ignorehonours local excludes. The holder ruled it into this PR (#6566026275158, item 2).dc879beb: the source must be the root.gitignore, and a negated pattern is refused..git/info/excludeandcore.excludesFilecases (2 failed, 16 passed).dc879bebonce DRAFT: T073: the composed declarations (ARC-Q2 (a)): every declared entry runs composed, the rail files get the host's rail (plan 038) #45 mergesmain.At
dc879beb: r4200975244, ambientGIT_DIR/GIT_WORK_TREEredirect the checks. Fix-now, fixed in146ae923; red before:2 failed, 18 passed.At
146ae923/c22b5df7:assume-unchanged/skip-worktreehide an edit fromgit diff;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-objectapplies clean filters. Fix-now, fixed in1f2d6858(--no-filters). Before it, a hiding clean filter and a CRLF edit undertextboth PLACED the link.At
1f2d6858: r4201912214, a symbolic link in a parent directory. Fix-now, fixed in2c86dcca. Before it, a linkeddocs/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 ofmaine7815ac5after the un-draft, with T022's three files byte-identical to1f592766: review 5440672686, two findings. Both are ACCEPTED LIMITS under 5988818366, for the holder at READY; the PR was un-drafted, so neither is pushed.checkout/docsa link outside,git check-ignoreanswers "pathspec ... is beyond a symbolic link" (exit 128), the ignore guard refuses (exit 2) beforemkdirorsymlink_to, and the outside directory stays empty. Only the message misnames the cause.100644 blob e88f06e6atf7ee3c76. The one-line hardening is agit ls-treemode check, as a follow-on.At the head
57b4f80a(T022's content as at1f592766):validatepass (run 37604221972),SonarCloud Code Analysispass, Copilot success, Sourcery skipped. At1f592766: validate run 37603513031 passed.Not done here
🤖 Generated with Claude Code
Summary by Sourcery
Place the composed-run session runbook from the pinned openDox-spec tree without vendoring it or compromising checkout safety.
New Features:
Enhancements:
Tests:
Chores: