Skip to content

T095: the composed harness for the declared integration tests (plan 038, T094's R2-INV-R9) - #46

Merged
brettheap merged 12 commits into
mainfrom
r2/t095-composed-conftest
Oct 6, 2026
Merged

brettheap merged 12 commits into
mainfrom
r2/t095-composed-conftest

Conversation

@brettheap

@brettheap brettheap commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

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 (DRAFT: T029, U-9: the composed run, permanent: composed.yml and its openxFactory pin (plan 038) #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 f70bc9c3: TOTAL over 66 files: 2001 passed, 496 failed, 72 errors, 2 skipped; 568 red
head c5d07c6b: 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 c6982e34 (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

Summary by Sourcery

Compose the declared integration-test harness while preserving lone-checkout behavior and enforcing safe, structurally governed test selection.

New Features:

  • Add a composed-only pytest harness for declared integration suites, providing governed host registration, host seam setup, composed contract sources, the openxFactory root, and local-install coverage.

Bug Fixes:

  • Prevent composed contract farms from being created inside the checkout or from using a reach sourced from another tree.
  • Preserve and restore existing host seam registrations while excluding openDox defaults from restoration.

Enhancements:

  • Extend the launch-suite fixture to select all suites requiring a local installation through a shared policy.
  • Add structural safeguards and host-plane tests covering suite declarations, composed-only behavior, seam handling, contract-farm composition, and safety refusals.

Tests:

  • Add comprehensive tests for the composed harness interfaces, isolation behavior, contract-farm contents, seam registration, and checkout-boundary protections.

brettheap and others added 7 commits October 6, 2026 10:39
…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>
…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>
…38; T094's map R2-INV-R9)

The composed conftest T094's map names (R2-INV-R9, slice T095), under the
holder's rulings on #656 comment 6021830531 (OQ-R9-1, OQ-R9-2 and OQ-R9-4
at their defaults; W2). 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 tests/test_declared_exclusion.py's evidence are
unchanged.

The interface the other slices code against, in tests/conftest.py:
- HR: DECLARED_HOST_SUITES (17 declared files), declared_host_for(), and
  the autouse fixture that registers the governed host for them, set up
  inside the stand-in host's plane;
- SR: HOST_SEAM_SUITES, HEALTH_CHECK_SEAM, BINDING_TRUST_SEAM,
  openxfactory_seams() and a_hosts_seam(): openxFactory's health check
  for test_authoring_agent.py and its binding-trust policy for
  test_doxchat_model_intake.py, read off opendox_host.seams() and put
  back after each test;
- CS: CONTRACTS_DIR, the contract family's composed farm (55 schemas
  through carved_reach.sources_under, 139 examples from the three owners
  less openDox's opendox-snapshot-*), built outside the checkout at
  import and exported in the environment; build_contracts_farm() and
  compose_the_contracts_farm();
- OQ-R9-4: openxfactory_root();
- LI (W2): LOCAL_INSTALL_SUITES and runs_a_local_install(), which T086's
  launch-suite fixture now reads.

tests/test_host_plane.py holds each list to its rules (declared entries,
outside 12.5's governed set, test_column_contributions_governed.py and
any file registering openXdox's columns kept off the declared-host list)
and each piece to its behaviour, in a lone checkout.

Lane: openXfactory-3

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@sourcery-ai

sourcery-ai Bot commented Oct 6, 2026

Copy link
Copy Markdown

Reviewer's Guide

The PR replaces checkout-relative receipt-schema loading with explicit owner-aware schema sources and adds a composed-only pytest harness that assembles the governed host, host seams, contract farm, and local-install behavior for declared integration suites, with extensive lone/composed isolation and invariant coverage.

Sequence diagram for owner-aware contract schema validation

sequenceDiagram
    participant Caller
    participant GateConsole
    participant PackagedContracts
    participant HostSchemaSource
    participant JSONSchema

    Caller->>GateConsole: validate_gate_action_record(record)
    GateConsole->>PackagedContracts: verified_bytes(GATE_ACTION_RECORD_SCHEMA)
    PackagedContracts-->>GateConsole: schema_text
    GateConsole->>JSONSchema: _schema_errors(record, schema_text)
    JSONSchema-->>GateConsole: validation errors or success

    Caller->>GateConsole: validate_demotion_execution_receipt(receipt)
    GateConsole->>HostSchemaSource: read registered receipt schema
    HostSchemaSource-->>GateConsole: schema_text
    GateConsole->>JSONSchema: _schema_errors(receipt, schema_text)
    JSONSchema-->>GateConsole: validation errors or success
Loading

File-Level Changes

Change Details Files
Make contract-schema validation owner-aware and fail closed on schema problems.
  • Validate packaged gate-action records through the verified packaged copy.
  • Add explicit host registration, single-source enforcement, and teardown for demotion-receipt schemas.
  • Parse, validate, and apply schemas while converting malformed or unusable schemas to GateRefused.
src/openxdox/gate_console.py
tests/test_gate_console_schema_source.py
Introduce a composed-only integration-test harness that supplies host behavior, seams, contract inputs, and local-install execution where declared.
  • Apply the governed host to the declared host-suite allowlist while preserving the stand-in host lifecycle.
  • Load and restore health-check and binding-trust seams from the composed host's seam table.
  • Build an external temporary contracts/examples farm, expose CONTRACTS_DIR, and reject conflicting or cross-tree inputs.
  • Extend local-install handling to the renderer and repository-root suites and expose the composed openxFactory root.
tests/conftest.py
Add focused invariants and behavioral tests for the harness composition rules.
  • Verify composed lists remain declared, non-governed, and inert in lone checkouts.
  • Exercise seam registration/restoration, host-source selection, farm symlink layout, cleanup, and cross-tree rejection.
  • Check fixture ordering and ensure the harness does not interfere with suites that provide their own columns.
tests/test_host_plane.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

T095's conftest chain is T020 -> T021 -> T095 -> T073 -> T074, so this branch
carries T021 until it lands. Neither tests/conftest.py nor
tests/test_host_plane.py moves in this merge.

Lane: openXfactory-3

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

The cross-repository composed harness remains draft and its full falsifier and CI validation have not run.

Review effort: Balanced
Findings: None

What changed in this PR

Adds the T095 composed integration-test harness and stacked T021 schema-ownership support.

Changes:

  • Supplies composed-only hosts, seams, contract farms, and local-install behavior.
  • Validates schemas from their explicit packaged or host-owned sources.
  • Adds focused harness and schema-source tests.
File Description
tests/​conftest.py Implements the composed harness and schema registration.
tests/​test_host_plane.py Tests harness composition and isolation.
src/​openxdox/​gate_console.py Resolves and safely validates owner-provided schemas.
tests/​test_gate_console_schema_source.py Tests schema ownership and refusal behavior.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

…test.raises

SonarCloud at 70ed40c read five code smells in tests/test_host_plane.py
(python:S9073 at three asserts, python:S5778 at two pytest.raises blocks).
Each assertion is split, and each pytest.raises block now holds one call
that may raise. What the cases assert is unchanged: 27 passed lone.

Lane: openXfactory-3

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

Schema-source re-registration does not honor its documented idempotency guarantee.

Review effort: Balanced
Findings: None

Previously missed (1)

In code that hasn't changed since last review

Medium severity Avoid schema revalidation for already-registered sources

src/​openxdox/​gate_console.py:2417

The documented idempotent path still revalidates the schema file before recognizing the same source. If an already-registered source is removed or temporarily unmounted, registering that same resolved directory raises ContractSchemaSourceRefused instead of being a no-op, so repeated host startup is not actually idempotent. Return immediately when the resolved directory is already registered, and validate the file only for a new registration.

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>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

The contracts farm can be created inside the checkout when the configured temporary directory is within it.

Review effort: Balanced
Findings: 1 Medium severity

Open (1)

Comment thread tests/conftest.py
… farm

Copilot r4200978505 at c5d07c6: mkdtemp() honours TMPDIR and an explicit
under, so a temporary root beneath the checkout would build the contracts
farm inside the working tree, against the harness's own invariant.
compose_the_contracts_farm() now resolves the parent first and refuses one
inside the checkout, before anything is created or set; a new case covers
both an explicit under and TMPDIR.

Lane: openXfactory-3

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

Cross-repository composition and import-time environment wiring require final human validation against the pinned assembled tree.

Review effort: Balanced
Findings: None

Resolved since last review (1)

@sonarqubecloud

sonarqubecloud Bot commented Oct 6, 2026

Copy link
Copy Markdown

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Snapshot-example filtering incorrectly excludes matching files owned by openxFactory or openXdox.

Review effort: Balanced
Findings: 1 Medium severity

Open (1)

Comment thread tests/conftest.py
@brettheap

Copy link
Copy Markdown
Contributor Author

READY (holder, manual landing) at bfcab6b

Lane: openxfactory-4 (openXfactory-4-openDox_extraction)

Holder, at READY: T095, the composed harness for the declared integration tests. Lane openXfactory-3's READY-CANDIDATE is 6026609526 at c6982e34. Landing on Brett's word "land #46 when green".

@brettheap
brettheap merged commit e2340bb into main Oct 6, 2026
4 checks passed
@brettheap

Copy link
Copy Markdown
Contributor Author

Lane: openxfactory-4 (openXfactory-4-openDox_extraction)

LANDED — lane openxfactory-4, 2026-10-06T23:33:15Z, PR #46 → e2340bb (opensoft/openXdox-code main; plain gate)

Landed on Brett's word "land #46 when green". Copilot 4201411760 (owner-wide opendox-snapshot-* exclusion) is an ACCEPTED LIMIT under 5988818366; its fix rides T073 (#45). #48, #49, #51 and #52 merge main next.

brettheap added a commit that referenced this pull request Oct 6, 2026
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>
brettheap added a commit that referenced this pull request Oct 6, 2026
…hot-* examples (Copilot r4201411760 on #46; holder r4201421102)

T095's farm (build_contracts_farm) skipped every example named
opendox-snapshot-* whichever owner kept it, while the farm's own contract
leaves out only openDox's. The condition is now scoped to the opendox_spec
owner (CONTRACT_EXAMPLE_LEFT_OUT_OWNER), so a same-prefix example kept by
openxFactory or openXdox-spec is linked like any other. As the holder
measured, none exists today, so the composed farm is unchanged.

tests/test_host_plane.py gains one farm case beside T095's: a same-prefix
example from openxFactory and one from openXdox-spec are linked, and
openDox-spec's two are not. It is red on the owner-wide condition and green
on the scoped one. It also kills the mutants that scope the exclusion to
the wrong owner or drop it, the latter beside T095's existing farm case.

The fix rides T073 as the holder ruled (r4201421102, under
openxFactory#656 5988818366). T073's files gain tests/test_host_plane.py
for this one case.

Lane: openXfactory-3

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
brettheap added a commit that referenced this pull request Oct 6, 2026
Lane: openXfactory-3

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
brettheap added a commit that referenced this pull request Oct 6, 2026
Lane: openXfactory-3

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
brettheap added a commit that referenced this pull request Oct 6, 2026
Lane: openXfactory-3

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
brettheap pushed a commit that referenced this pull request Oct 6, 2026
Lane: openXfactory-3

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
brettheap added a commit that referenced this pull request Oct 6, 2026
brettheap added a commit that referenced this pull request Oct 6, 2026
…; 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>
brettheap added a commit that referenced this pull request Oct 7, 2026
…erved facet (plan 038, T094's R2-INV-R9) (#51)

Lane: openxfactory-4 (openXfactory-4-openDox_extraction)
Authored by lane openXfactory-3, delegated by lane openxfactory-4 (#656 6021862323)

Arc: neutral-product-standalone-operability

## T098: display harnesses (A), and the server's scope labels from the served facet (plan 038)

This is a slice of T094's map R2-INV-R9 (opensoft/openxFactory#656 `6021830607`). `tasks.md` has no entry for it yet: lane 4 records the T095 to T104 entries after T002 (#1251). Until then, its contract is the map's slice-outline row plus the holder's rulings, `6021830531`.

- **Realizes:** 21 of the composed reds outside 12.5's set in the declared exclusion, toward requirement 9's declared composed tests (T073, F9.1):
  - canvas: DJ 8 and DJ + HR 1;
  - explorer: DJ 5 and CP 2;
  - the 5 nodes held by R9-R1: canvas 2, explorer 2, scope 1.
- **Falsifier (the outline row):** the composed run of the three files, each alone, at `main` + T095, takes the named 21 to 0 red. These read unchanged:
  - 12.5's 16 governed suites;
  - F5.2's 5 suites;
  - `test_column_contributions_governed.py` (9 passed).

  `tests/test_declared_exclusion.py` reads green when run lone.
- **Ruled:**
  - **R9-R1 (a):** `doxbench_scope.py`'s labels and notes derive from the facet the browser reads, and every pin that asserts words is respelled through VR to the SERVED words.
  - **R9-W1 (A″):** the harnesses copy `display.js` beside the module they copy, as #43 does.
  - Both are in `6021830531`. They conform to the ruled overlay (`5784683830`, `5801057769`) and to the served-display rule for the staging suite (`6016648451`).
- **After:** T095 (#46), LANDED as `e2340bb6`. The served display needs the governed host that T095's `DECLARED_HOST_SUITES` registers for these three files when the run is composed. This branch merges `main` `e2340bb6`, and every number below is measured over it.

## What changes

**`src/openxdox/doxbench_scope.py`** is this slice's under R9-R1 (a), as claim `6021862323` names it.
- **The labels now come from the facet.** The section labels and notes were one domain's words ("cluster documents", "topic folder documents"). They are now built from the facet by `_section_meta(_served_display())` once per `resolve_scope` call.
  - The six sentences are word for word the ones openDox's staging model has built since § 3.4 slice S7 (`views/staging-workbench-model.js` `SECTION_META`).
  - Keys, flags and paths are unchanged.
- **Where the words come from.** `_served_display()` is the `display` block that `/capabilities` serves: `display_manifest(host_display())`.
  - It is read at call time, never at import and never cached, as `opendox.canvas_drafts` reads its one shared sentence.
- **Decision: no host means neutral words.** With no host registered there is no served page. The browser model given no facet speaks openDox's neutral words, so the server answers with those too rather than refusing.
  - The words label sections of an authorization answer and never decide it.
  - A library or test caller with no host therefore keeps its paths.
- **Who reads the labels.** Server-side, no module reads a section's label or note except the parity test: no prompt and no wire field carries them.

**`tests/test_doxbench_scope.py`**
- The browser harness copies `display.js` beside the two models and writes a `package.json` with `"type": "module"` (DJ). It installs the display through `setDisplay(readDisplay(...))`, as the shell does at mount.
- The parity test hands the browser the served display (VR).
- **Two new cases**, because the parity test alone cannot catch one thing. The composite serves openDox's neutral stage words for these three roles, since openXdox overlays only `completion` and the `values`. So parity cannot tell a server that reads the facet from one that always speaks neutral words. The new cases can:
  - **A host that declares its own stage words.** It declares openxFactory's pre-S7 words. The server labels read "cluster documents", "topic folder documents" and so on, and match the browser byte for byte.
  - **No host.** The server reads the neutral words, which equal the browser's with no facet.

**`tests/test_canvas.py`**
- `_run_node` copies `display.js` and writes the `package.json` (DJ). It hands the harness the served display, so every canvas derivation runs under the words the page is served (VR).
- The two gap pins now read the kind canvas-model.js emits under every host, `unsupported-candidate`, where they read `unsupported-possible` before (R9-R1).
- `test_js_and_python_draft_constants_agree` also cross-checks the seed state of a new draft. Both halves read it off one facet: the composer plan's `state` against `canvas_drafts.register_state("captured")`. This assertion is what makes the served-display hand-off observable (mutant H4 below).

**`tests/test_explorer_viewer.py`**
- **DJ:** each explorer harness copies `display.js` and writes the `package.json`.
- **VR:** `_EXPLORER_HARNESS` is handed the served display.
- **R9-R1, the served words.** The two label pins read the words the page is served, from the composite's artifact axis:
  - groups `proposal.md` and `tasks.md`;
  - classification `proposal.md`, `design.md`, `tasks.md`, `Spec deltas`, `Supporting docs`, `other`.
- **CP: the import-free pin**, respelled to S7's design:
  - no import names the viewer;
  - the only module imported is `./display.js`;
  - import statements spread over several lines are read whole.
- **CP: the wheel pin**, respelled to `opts.wheelKey === SOURCE`, with `SOURCE` held to be `STAGE_ROLES[0]`.

## The falsifier (composed, each file alone; quoted)

**The composed form, R1Q23 (a):**
- openxFactory `92010d3e` (`tests/composed_host_pin.yaml`), with `openDox`, `openXdox` and `openXwallet` initialized recursively, and its `scripts/` on `PYTHONPATH`;
- openDox installed from openDox-code `main` `84f8ed83` (force-reinstalled);
- the colour variables unset and `TERM=dumb`;
- per file: `python -m pytest -q -p no:cacheprovider -rfE --junitxml=<file>.xml tests/<file>.py < /dev/null`.

T022's `scripts/composed_placements.py` (taken from #44's tree) refuses to place in these checkouts: its `.gitignore` entry is not on `main` until #41 lands. None of these files reads the runbook it places. Every checkout read clean after every run.

**Measured over real `main`** (`e2340bb6`, carrying T095 #46 and T026 #43): the base is `main` itself, and the head is this PR's `c935f2a0` (this branch merged with `main`).

| run | test_canvas.py | test_explorer_viewer.py | test_doxbench_scope.py | named reds |
|---|---|---|---|---|
| base: `main` `e2340bb6` | 10 passed, 11 failed | 15 passed, 9 failed | 34 passed, 1 failed | **21** |
| head: `c935f2a0` | **21 passed, 0 failed** | **24 passed, 0 failed** | **37 passed, 0 failed** | **0** |

Before T095 landed, the same 21 were measured at a local integration that was never pushed (`main` `f70bc9c3` + #46 `c2192e3` + this branch): 21 red at its base and 0 at its head. At `main` `d45a0939`, before T095 existed, these files read 19, 9 and 1 red.

The 21 nodes that flipped:
- **canvas (11):** `test_member_pane_is_exactly_the_topics_document_edges`, `test_downstream_artifacts_are_in_the_lineage_strip_never_members`, `test_evidence_board_carries_section_ref_and_passage_hash`, `test_missing_cluster_degrades_to_null`, `test_gap_unclaimed_member`, `test_gap_unsupported_possible`, `test_gap_both_kinds_on_one_cluster`, `test_option_set_grouping`, `test_js_and_python_draft_constants_agree`, `test_esc_escapes_markup`, `test_model_passes_prose_through_as_data_not_html`;
- **explorer (9):** `test_explorer_js_exists_and_is_importable_standalone`, `test_staged_tile_lists_the_topic_folder_with_headers`, `test_aggregate_folder_entry_keeps_its_member_source_key`, `test_aggregate_folder_entry_disambiguates_same_repository_refs`, `test_aggregate_wheel_navigation_threads_the_owning_member_key`, `test_proposal_tile_groups_proposal_and_tasks`, `test_realized_tile_lists_the_archived_change_folder`, `test_unresolvable_tile_and_kind_degrade_to_null`, `test_change_file_classification`;
- **scope (1):** `test_server_and_actual_browser_projection_are_byte_shape_equivalent`.

Two nodes are new: `test_the_server_labels_follow_the_facet_a_host_declares` and `test_with_no_host_registered_the_labels_are_openDoxs_neutral_words`. No node regressed.

**The unchanged set** was compared node for node, base against head over `main`: **974 nodes, 10 red at both, 0 nodes changed outcome.**

| set | base | head |
|---|---|---|
| 12.5's 16 governed suites | 832 passed, 10 failed (pre-existing: `session_runbook` 9 and `session_notebook` 1, which read the runbook T022 places) | identical, file for file |
| F5.2's 5 suites | 123 passed | identical, file for file |
| `test_column_contributions_governed.py` | 9 passed | 9 passed |

## Mutants

Each mutant was applied at the integration (`main` `f70bc9c3` + T095 `c2192e3` + this branch), its file run alone composed, and the file restored byte for byte. **11 of 12 killed:**

| mutant | site | killed by |
|---|---|---|
| S1 | src: ignores the host's facet (always neutral) | `test_the_server_labels_follow_the_facet_a_host_declares` |
| S2 | src: no-host fallback dropped | `test_with_no_host_registered_the_labels_are_openDoxs_neutral_words` |
| S3 | src: members label hard-coded "cluster documents" | parity (served) + no-host |
| S4 | src: folder label one/many swapped | parity + declaring host + no-host |
| S5 | src: neighbourhood note drops a facet word | declaring host |
| H1 | scope harness: no `display.js` | all three browser cases |
| H2 | scope harness: browser ignores the display | declaring host |
| H3 | canvas: no `display.js` | the 11 canvas Node cases |
| H4 | canvas: neutral display instead of served | `test_js_and_python_draft_constants_agree` |
| H5 | explorer: no `display.js` | 7 explorer Node cases |
| H6 | explorer: neutral display instead of served | `test_proposal_tile_groups_proposal_and_tasks`, `test_change_file_classification` |
| H7 | scope harness: no `package.json` | **SURVIVES** (37 passed) |

**H7 is an equivalent mutant on the measured runtime.** Node v24 detects module syntax in a `.js` file with no `"type"` declared, so `package.json` changes nothing there. It is kept for runtimes without that detection, as #43's harness keeps it. T097 measured the same survivor.

## The lone CI form (`validate.yml`'s exact step, at `c935f2a0`)

`python -m pytest -q --junitxml=pytest-report.xml --deselect "$LEFT_OUT"`, run locally, read:

```
SELECTED=1165 PASSED=1161 SKIPPED=4 FAILURES=0 ERRORS=0   (floors MIN_SELECTED=1099 MIN_PASSED=1095 EXPECT_SKIPPED=4)
1161 passed, 4 skipped, 1 deselected in 169.43s
```

- `tests/test_declared_exclusion.py` run alone: rc 0. Each of the three files still fails lone on its declared reason (`doc_health`, at collection).
- The floors are untouched; only T029 raises them.

## CI and Copilot at `c935f2a0`

- **`validate`:** success, run `37548181742`. The triple is `selected=1165 passed=1161 skipped=4 failures=0 errors=0`, with pins `selected>=1099` (margin 66), `passed>=1095` (margin 66) and `skipped==4`.
- **SonarCloud Code Analysis:** success.
- **Copilot**, review `5435780547` at `c935f2a0`:
  - **"Needs a closer look", Findings: None.** Its stated reason is that "final composed validation after integration remains unverified". This PR's CI cannot run the composed form until `composed.yml` lands (T029 #44, then T073 #45). The composed validation over real `main` `e2340bb6` is the local run quoted above: 21 to 0 red, and the unchanged set identical node for node.
  - The earlier review at `588d4c15` (`5435186246`) gave the same verdict, with no findings, for the then-unmerged #46.
  - 0 review threads, 0 unresolved.

## Files changed

- `src/openxdox/doxbench_scope.py`
- `tests/test_canvas.py`
- `tests/test_explorer_viewer.py`
- `tests/test_doxbench_scope.py`

No protected suite is edited: none of the four is in 12.5's 16 or F5.2's 7. No allow-list entry is needed, and `tests/declared_exclusion.yaml` is untouched.

## Not done

- **Composed CI:** `composed.yml` is not on `main` yet (T029 #44, then T073 #45), so this PR's CI is `validate` and SonarCloud. The composed numbers above are local runs, quoted.

🤖 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>
brettheap added a commit that referenced this pull request Oct 7, 2026
…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>
brettheap added a commit that referenced this pull request Oct 7, 2026
Lane: openXfactory-3

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
brettheap added a commit that referenced this pull request Oct 7, 2026
…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>
brettheap added a commit that referenced this pull request Oct 7, 2026
…n 038, T094's map R2-INV-R9) (#54)

Lane: openxfactory-4 (openXfactory-4-openDox_extraction)
Authored by lane openXfactory-3, delegated by lane openxfactory-4 (#656 6021862323)

Arc: neutral-product-standalone-operability

## T100: the repo selector's declared tests, respelled to the carve (plan 038, T094's map R2-INV-R9)

**Contract.** There is no `tasks.md` entry yet. Lane 4 records T095 to T104 after T002. The contract is T094's slice-outline row for T100 and the holder's rulings on the map, opensoft/openxFactory#656 comment 6021830531.
- **Realizes:** the requirement 9 declared composed tests (T073, F9.1), for `tests/test_repo_selector.py`. It owns 24 composed reds, and its 7 host-registration nodes arrive with T095.
- **Falsifier:** the composed run of the file, alone, goes from red to green. The unchanged set stays node-for-node unchanged, and `tests/test_declared_exclusion.py` stays green lone.
- **After:** T095 (#46), LANDED as `e2340bb6`. main is merged in.

**Ruled (#656 6021830531), as applied here:**
- **R9-W1 (A″):** the three Node harnesses copy `display.js` beside the model, with `package.json`, as T026's harnesses do. 17 nodes.
- **R9-R1 (a):** the sparse-station node is handed the display `/capabilities` serves under the registered host, and asserts the served words. 1 node.
- **MN:** `SNAPSHOT_INDEX_ROUTE` is read from `openxdox.serve_projection`. 2 nodes.
- **R9-W4:** the plain-script and module subprocess tests register the host from THIS checkout before `opendox.serve` runs. 2 nodes. The child, in order:
  1. puts this checkout's `src/` first and imports openXdox's governed columns from it;
  2. checks that `openxdox` resolves to this checkout;
  3. registers openXdox's governed projection (`projection_contributions.register()`, the call openxFactory's host makes, plan 034 T064);
  4. builds openxFactory's composite with `sys.path` saved and put back;
  5. runs T020's `_refuse_an_openxdox_from_elsewhere`, the very function (its source, run in the child);
  6. registers the composite.
- **R9-R2 (a):** re-scoped to openDox with no host (`python -m opendox.serve`, a PyYAML blocker as its only path entry). The refresh is refused as `unknown_action`, and no `yaml` is loaded. 1 node.
- **R9-W3:** `test_serve_module_uses_no_relative_imports` is RETIRED. 1 node.

**Two notes for the holder (measured; no ruling taken):**
1. **R9-R2: openDox's serve tries `import yaml` at START, not on a POST.**
   - Measured: `build_server()`, then `_probe_nlm`, then `real_notebook_adapter`, then `opendox.workbench:52`, which imports PyYAML.
   - The blocker refuses it, and the probe reads that as no notebook support.
   - None of the three POSTs tries it. So the blocker counts every import attempt, and the test asserts that the count after the POSTs equals the count once the serve is up.
   - Mutant M11 (below) shows the count catches a POST that reaches for `yaml`.
2. **R9-W3's scope: the regex retires together with its self-test.**
   - The pin was the scan, the pattern it scanned with (`RELATIVE_IMPORT_RE`), and that pattern's 7-case self-test (`test_the_relative_import_guard_catches_every_dot_and_segment_depth`). The file's own comment named the three as one pin.
   - All three are retired: 1 red node and 7 green.
   - Say if you want the self-test kept.

**R9-R2's lone consequence: none.**
- Run alone lone, the re-scoped node does not run. The file still fails at COLLECTION on `doc_health` (`openxdox.generator`): `ModuleNotFoundError: No module named 'doc_health'`, `1 error in 0.39s`.
- So `tests/declared_exclusion.yaml` is unchanged, and the entry keeps `[doc_health]`.

## Falsifier, quoted

**The composed form (R1Q23 (a)):**
- openxFactory `92010d3e` (`tests/composed_host_pin.yaml`), with `openDox`, `openXdox` and `openXwallet` recursive, and its `scripts/` on `PYTHONPATH`;
- openDox-code `84f8ed83`, installed;
- T022's runbook placement made before the run and removed after;
- the colour variables unset, with `TERM=dumb`;
- each file run ALONE: `python -m pytest -q -p no:cacheprovider -rfE --junitxml=… <file>`.

Measured over REAL main, after T095 (#46) landed:
- **base:** main `e2340bb6`;
- **head:** this PR's head `ba0c3446`, which is main `e2340bb6` plus this PR's one file.

| | `tests/test_repo_selector.py` |
|---|---|
| base, main `e2340bb6` | `18 passed, 24 failed` |
| head `ba0c3446` | `34 passed, 0 failed` |

The 24 at the base are DJ 17, DJ+VR 1, MN 2, W4 2, W3 1 and R9-R2 1. T095's 7 host-registration nodes are already green there. 42 nodes become 34: W3 retires 1 red and 7 green.

**Unchanged set, composed, each file alone, at base and head: node-for-node identical, 974 nodes, 0 red at both.**
- 12.5's 16 governed suites: the same 16 at both.
- F5.2's 5: `test_generator` 45, `test_session_snapshot` 23, `test_snapshot_determinism` 6, `test_snapshot_registry` 40, `test_snapshot_validation_launch` 9, all passed.
- `test_column_contributions_governed.py`: `9 passed`.
- Over all 23 files: base `992 passed, 24 failed`; head `1008 passed, 0 failed`.

**Lone (CI venv, openDox `dede32b4` by the pin), at `ba0c3446`:**
- `tests/test_declared_exclusion.py`: `207 passed`.
- The R9-R2 node alone: `1 error`, `ModuleNotFoundError: No module named 'doc_health'` at collection. Its entry stays as it is.
- The CI form (`python -m pytest -q --deselect "$LEFT_OUT"`): `1161 passed, 4 skipped, 1 deselected`. That is selected 1165, against the floors 1099 / 1095 / 4. The floors are untouched.

## Mutants (one exact edit each, the file run composed alone)

Measured at the earlier integration of main `f70bc9c3` + #46 `c2192e3e`. This PR's file is byte-identical at `ba0c3446`.

| id | mutation | result |
|---|---|---|
| M1 | `display.js` not copied | KILLED, 18 red |
| M2 | no `package.json` | survived: equivalent on Node 24, whose syntax detection reads `display.js` as a module; kept for parity with T026's harnesses |
| M3 | the harness ignores the display it is handed | KILLED |
| M4 | `sparseNotice` not given the display | survived: equivalent at this pin, since the served words for the four empty stations are openDox's neutral ones (the ruled overlay changes only `completion` and the values). M3 guards the hand-off |
| M5 | this checkout's `src/` not put first | KILLED, both W4 nodes. Locally, the editable install is another clone; in CI it is the checkout itself, so there this edit is equivalent |
| M6 | openXdox's columns not imported from this checkout first | KILLED, both W4 nodes, by T020's guard |
| M7 | M6, with the guard call removed too | survived: the control, showing that the guard is what kills M6 |
| M8 | the governed projection not registered | KILLED, both W4 nodes |
| M9 | `sys.path` not put back | survived: no `openxdox` import after the composite is read reaches the pinned leg in these two tests. The in-process harness has the same posture |
| M10 | the composite not registered | KILLED, both W4 nodes |
| M11 | R9-R2 on loopback, with the notebook asserts dropped, so a POST reaches `yaml` | KILLED, by the attempt count |

## Files changed
- `tests/test_repo_selector.py` (only).

## Gates at `ba0c3446`
- **CI:**
  - `validate` passed (run 37548690247): `1161 passed, 4 skipped, 1 deselected`, and `triple: selected=1165 passed=1161 skipped=4 failures=0 errors=0` against the floors 1099 / 1095 / 4.
  - SonarCloud Code Analysis passed.
- **Copilot:** review 5435813621 at `ba0c3446` reads "Findings: None". Its "Needs a closer look" note concerns the then-unlanded #46, which has since LANDED as `e2340bb6` and is merged into this head. The earlier review, 5435251251 at `0c0f8bc8`, also read "Findings: None".
- **Review threads:** 0, so 0 unresolved.

## What remains
- Nothing for the writer. The PR is final and frozen.
- It is behind main `fc1115d6`. Lane 4's autoland loop runs update-branch and re-requests Copilot.
- `main` since `e2340bb6` (#48 T101, #51 T098) does not touch `tests/test_repo_selector.py`.

🤖 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>
brettheap added a commit that referenced this pull request Oct 7, 2026
Lane: openXfactory-3

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
brettheap added a commit that referenced this pull request Oct 7, 2026
…enxfactory_root() (OQ-R9-3, after T095)

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 (T094's OQ-R9-3; the map's default respells it
so it runs). T095 (#46) now provides openxfactory_root(), composed-only
(OQ-R9-4), so the case reads the composed openxFactory tree's archive
and skips only where the run is not composed.

Lane: openXfactory-3

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
brettheap added a commit that referenced this pull request Oct 7, 2026
… 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>
brettheap added a commit that referenced this pull request Oct 7, 2026
… the arc's rulings (plan 038, T094's R2-INV-R9) (#52)

Lane: openxfactory-4 (openXfactory-4-openDox_extraction)
Authored by lane openXfactory-3, delegated by lane openxfactory-4 (#656 6021862323)
Arc: neutral-product-standalone-operability

**T102, the small texts** (plan 038, slice T102 of T094's repair map, `R2-INV-R9`). T095 (#46) has landed, so every node of this slice now clears at `main`.

**Realizes:** requirement 9's declared composed tests (T073, F9.1): the 21 composed reds that `R2-INV-R9` assigns to its T102 row. There is no `tasks.md` entry yet (lane 4 records T095 to T104 after T002).

**Ruled:** the map's rulings (#656 `6021830531`: OQ-R9-1 to OQ-R9-4 at their defaults, W7's file chains), and #656 `6026275158` item 3 (the no-bypass scan, below). **Decisions:** none of my own.

## What changes (seven test files, no conftest edit, no new test)

| file | nodes | means | the edit |
|---|---|---|---|
| `tests/test_source_dot_directories.py` | 13 | HH | the raw client sends ONE `Host` line (`skip_host=True`); openDox-code `390e2c2` (plan 034 T103) refuses a second as `403 invalid_host` |
| `tests/test_session_document_ownership.py` | 1 | PP | `gate_routes.py` is read at its carved home (`carved_module_path`) |
| `tests/test_readiness_gate.py` | 1 | MN | `cmd_gate_propose` is read from `openxdox.cli_gate`, where the carve put it |
| `tests/test_gate_failure_diagnostics.py` | 1 | MN | the fresh interpreter imports `opendox.workbench` (and needs no path to find it) |
| `tests/test_authoring_agent.py` | 1 | PP | `sync-notebooklm-books.py` is openxFactory's own tool, read beside the composed host module (`openxfactory_host()`) |
| `tests/test_wheel_verbs_cli.py` | 2 | MN (+ T095's HR) | the `derive-possibles` stub replaces `generator_seam.generate` (plan 034 T055), the call the CLI makes now |
| `tests/test_notebook_action.py` | 2 | CP (one also needs T095's HR) | the exact-equality capabilities pin pops the additive `views` and `display` keys and checks their kinds; the gate flag is asserted with a gate route binding (plan 034 T084) and without one |

Beyond the 21 nodes, per #656 `6026275158` item 3: **the no-bypass scan in `test_readiness_gate.py::test_no_parameter_or_keyword_can_bypass_the_gate` now reads the sources of BOTH `opendox.cli` and `openxdox.cli_gate`.** It read only `opendox.cli`, while the `propose` arguments live in `cli_gate`, so a bypass flag added there passed it. M14 and M15 below show the widened scan kills it and the old scan did not.

## A correction to the brief (kept for the record)

The brief said 19 of the 21 nodes clear at `main` before T095. Measured at `main` `f70bc9c3` (before T095 landed) it was 18: a third node, `test_notebook_action.py::test_capabilities_route_reports_available_when_nlm_present`, also needed T095's declared-host list, because its pinned `gate: True` and `refresh: True` are the host's route bindings. T095 has landed since, and at `main` `fc1115d6` below all 21 clear, those three included.

## The falsifier (composed R1Q23 (a), each file alone, red first)

Environment: openxFactory `92010d3e` (the commit `tests/composed_host_pin.yaml` names) with `openDox`, `openXdox` and `openXwallet` initialized recursively, its `scripts/` on `PYTHONPATH`; openDox installed from openDox-code `84f8ed83`; T022's placement run first and removed afterwards (both checkouts read clean after); colour variables unset, `TERM=dumb`; Python 3.12.3, pytest 8.4.2, Node v24.21.0.

**At `main` `fc1115d6`** (the 7 files: 21 red, exactly this slice's), then at this head `0a5dfc30` (a merge of `fc1115d6`):

```
base: 114 passed, 21 failed       head: 135 passed, 0 failed
source_dot_directories      14p 13f   ->  27p  0f
session_document_ownership  26p  1f   ->  27p  0f
readiness_gate              10p  1f   ->  11p  0f
wheel_verbs_cli             13p  2f   ->  15p  0f
notebook_action             25p  2f   ->  27p  0f
authoring_agent             18p  1f   ->  19p  0f
gate_failure_diagnostics     8p  1f   ->   9p  0f
```

21 nodes cleared, none turned red.

**The unchanged set**, at `main` `fc1115d6` and at this head, node for node: 24 files, 1004 nodes, 1004 passed, 0 red on BOTH sides, 0 nodes differing. It is 12.5's 16 governed suites, F5.2's 7 (its 5 declared files among them) and `test_column_contributions_governed.py` (9 passed).

**The lone form**, from a clean clone with CI's own install command (openDox at its pin):
- `tests/test_declared_exclusion.py` alone: 207 passed. Each of the seven files still fails alone only on `doc_health`.
- the whole suite as `validate.yml` runs it: 1161 passed, 4 skipped, 1 deselected; selected 1165, passed 1161, skipped 4, failures 0, errors 0, against the floors 1099, 1095 and `skipped == 4`. The floors are untouched, and no test is added or removed.

## Mutants (each is a mutated copy in a scratch tree, reverted after; each killed for its own reason)

| id | mutant | result |
|---|---|---|
| M1 | HH: `skip_host=False` again | 13 red (`403 == 200`) |
| M2 | `gate_routes.py` gains `_unwind_first_edit` | red |
| M3 | the PP read names another module's file | red |
| M4 | `cmd_gate_propose` no longer calls `console.propose` | red |
| M5 | the child interpreter's slug differs | red |
| M6 | wrong script name for the sync tool | red |
| M7 | no composed host | red, with the guard's own message |
| M8 | the stub names the old target `cli.generate_snapshot` | 2 red |
| M9 | the stub's snapshot carries no cluster | red |
| M10 | the gated call is handed no route binding | red |
| M11 | an unrouted plane asserted to offer the gate | red |
| M12 | `views` not popped | red |
| M13 | `display` checked against the wrong kind | red |
| M14 | a `--force` argument added to `cli_gate`'s `propose` | red under the widened scan, naming `openxdox.cli_gate` |
| M15 | the same flag against the OLD scan (the file as of `8935da8`) | PASSES: the old scan let it through |

## CI and review at head `0a5dfc30`

- `validate` green (run 37552892745): `1161 passed, 4 skipped, 1 deselected`; the triple reads selected 1165, passed 1161, skipped 4, failures 0, errors 0, against the floors 1099, 1095 and `skipped == 4` (margin 66).
- SonarCloud: Quality Gate passed.
- Copilot (reviewer API, review at this exact head): "Approval recommended", findings none, 0 unresolved threads. Its review of the previous head `b307c6ee` read "Changes recommended" with one finding, that the body still described the pre-T095 head; the body was fixed, and that thread was answered and resolved.

## NOT done

- Nothing in this slice is open. `main` has moved since (`da736090` at the last read), and the green loop's update-branch merge picks it up; the other slices' files are disjoint from these seven.

🤖 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>
brettheap added a commit that referenced this pull request Oct 7, 2026
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants