Skip to content

T022, U-3: the runbook placed from the composed tree, git-ignored, nothing vendored (plan 038, P4F-3) - #41

Merged
brettheap merged 18 commits into
mainfrom
r2/t022-composed-runbook-placement
Oct 7, 2026
Merged

brettheap merged 18 commits into
mainfrom
r2/t022-composed-runbook-placement

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 6013608959)

Arc: neutral-product-standalone-operability

T022, U-3: the runbook placed from the composed tree (RP) (plan 038, feature 038-opendox-document-tool-self-maintenance)

After set met: T004 #1245 → 260234b5; T020 #39 → c6d15b2; T021 #40 → f70bc9c.

The branch. The head is 57b4f80a, GitHub's update-branch merge of main e7815ac5. T022's last commit before it is 1f592766, the merge of main da736090, and T022's three files are byte-identical between the two. The diff against main is T022's three files.

  • T022's own commits:
    • c40af91b: U-3;
    • 7a149b70: the committed tests, under the holder's Files ruling;
    • dc879beb, 146ae923, 74f3a4fb, 1f2d6858 and 2c86dcca: the review fixes below.
  • Main came in by merges, never a rebase: 42d7ea97 (f70bc9c3), c22b5df7 (36bbef79), 49c1026d (e2340bb6), 214f3613 (fc1115d6), 60c56a47 (6f259156) and 1f592766 (da736090).
  • At 42d7ea97 this branch still carried T021's older ecf0ba9b versions of src/openxdox/gate_console.py and tests/test_gate_console_schema_source.py. Both conflicts were resolved to main's side, since neither file is T022's.

What changes

tests/test_session_runbook.py (its module constant RUNBOOK) and tests/test_session_notebook.py read docs/ideation-dashboard-session-runbook.md at the checkout root. The carve sent the runbook to openDox-spec, so this checkout has none, and both suites are protected.

  • New scripts/composed_placements.py links the runbook, for a composed run, from the composed tree into docs/. The source is openDox/spec/docs/ideation-dashboard-session-runbook.md under the openxFactory checkout.

    python3 scripts/composed_placements.py --openxfactory "$OPENXFACTORY"            # place
    python3 scripts/composed_placements.py --openxfactory "$OPENXFACTORY" --remove   # take back
    

    Before it places anything, it refuses (exit 2), naming the remedy, unless all of these hold:

    • Each leg is its own work tree. The openDox root and the spec leg each have their own .git, and git rev-parse --show-toplevel resolves to the directory itself. So no enclosing repository answers for them, and no core.worktree points the checks elsewhere.
    • The spec leg is at the pin. Its HEAD is the commit the root's spec gitlink pins.
    • The bytes are the pinned bytes. The runbook is tracked, no component from the spec leg down to the file is a symbolic link, and its raw bytes are the pinned blob: git hash-object --no-filters equals HEAD:<path>. So no index flag, clean filter or EOL normalization can hide an edit.
    • The destination is ignored by the repository-root .gitignore itself, read through git check-ignore -v. A local exclude alone (.git/info/exclude, core.excludesFile) is refused, and so is a negated pattern.
    • Nothing is overwritten. Anything already at the destination must be the script's own link to that same source.

    Every git call drops the ambient repository variables (GIT_DIR, GIT_WORK_TREE, GIT_INDEX_FILE and kin, as openxdox.domain_corpus_adapter drops them), so a hook cannot redirect the checks. The script places all or nothing. --remove takes back its own links and nothing else. It needs only the standard library and git.

  • .gitignore names /docs/ideation-dashboard-session-runbook.md, so the checkout stays clean and nothing placed can be committed.

  • New tests/test_composed_placements.py, 30 cases, holds those guards in the suite CI runs. Each case runs the script in a child interpreter: a copy of this checkout's script, inside a scratch checkout that carries this checkout's own .gitignore. It runs against a scratch openxFactory/openDox/spec tree pinned to commit A, so no case writes into this checkout. No case skips, and the file carries none of 12.5's governed-suite markers.

  • Nothing is vendored. src/openxdox/contracts/copies.yaml is untouched (P4F-3; ADV-10).

The composed workflow (T029) and the documented local composed run call the script. Neither is in this PR.

The contract, from tasks.md

The falsifier, composed

Environment.

  • openxFactory 51456835, with its submodules initialized recursively and scripts/ on PYTHONPATH (R1Q23 (a)).
  • The openDox root e1e3a3c3 pins openDox-spec f7ee3c76, the spec checkout's HEAD.
  • openDox-code 34c20641, force-reinstalled with --no-deps.
  • Python 3.12.3, pytest 8.4.2, LANG=C.UTF-8, no colour forcing.

At the head, the runbook is placed, then removed afterwards, which leaves the checkout clean:

python3 scripts/composed_placements.py --openxfactory "$OPENXFACTORY"
PYTHONPATH="$OPENXFACTORY/scripts" python -m pytest -q -p no:cacheprovider -rA <the 10 runbook nodes, one process per suite file>
at the 10 runbook nodes
main 56e1c238 (first round) 9 failed + 1 failed
main f70bc9c3, 36bbef79, 6f259156 "PASSED nodes: 0 / FAILED-or-ERROR nodes: 10"
head 1f592766, placed "PASSED nodes: 10 / FAILED-or-ERROR nodes: 0"

The cause at main is FileNotFoundError: … '<checkout>/docs/ideation-dashboard-session-runbook.md'.

The 10 nodes:

  • test_session_runbook.py:
    • test_the_runbook_start_command_parses_with_the_real_cli_parser
    • test_the_runbook_names_the_two_required_flags_and_what_they_are_for
    • test_the_runbook_claims_no_flag_generate_and_open_does_not_have
    • test_the_runbook_says_what_a_wrong_repo_root_does
    • test_the_runbook_does_not_call_the_unvalidated_snapshot_routine
    • test_the_runbook_lists_the_posture_readings_the_page_can_produce
    • test_the_derived_artifact_inventory_names_every_container_the_code_writes
    • test_the_runbook_names_the_pull_the_refusal_names
    • test_the_runbook_states_what_a_session_may_rewrite
  • test_session_notebook.py:
    • test_the_runbook_names_the_bound_by_its_production_constants

The 16 suites, each alone (first round, at c40af91b/7a149b70 against main 56e1c238):

  • 82 red of R2-INV-P4F's 174;
  • 0 regressions;
  • node for node the same at 7a149b70.

T022 changes no code under src/. The only change it makes to a composed run is the placement.

The script's guards: 30 cases and 23 mutants

tests/test_composed_placements.py reads 30 passed at 1f592766. Each later finding's cases were red against the script before its fix; the review rounds below quote them.

All 23 mutants were applied to this checkout's script or .gitignore, which the cases copy, and every one is killed by the case that names its guard:

mutant killed by
MA no pin comparison test_a_spec_checkout_off_the_opendox_roots_pin_is_refused
MB content not compared test_a_modified_runbook_is_refused
MW hash through clean filters test_an_edit_a_clean_filter_would_hide_is_refused[clean-filter]
MS no link check test_a_runbook_the_pinned_commit_tracks_as_a_link_is_refused
MX only the last component test_a_runbook_reached_through_a_linked_directory_is_refused
MU root may be any repository test_an_opendox_root_answered_by_an_enclosing_repository_is_refused
MV toplevel not compared test_a_spec_leg_whose_work_tree_is_configured_elsewhere_is_refused
MO no pinned-root check test_an_opendox_root_without_a_spec_gitlink_is_refused
MI spec leg is any directory test_an_uninitialized_spec_leg_is_refused_as_not_checked_out
MC no tracked check test_a_runbook_the_spec_does_not_track_is_refused
MD no ignore check test_a_destination_the_gitignore_does_not_name_is_refused
MP any ignore source test_a_destination_only_a_local_exclude_names_is_refused[info/exclude]
MQ a negation counts as ignored test_a_gitignore_that_negates_the_destination_is_refused
MJ .gitignore drops the runbook test_a_pinned_unmodified_runbook_is_linked_and_leaves_the_checkout_clean
MR ambient git variables kept test_ambient_git_repository_variables_do_not_redirect_the_checks[clean]
ME overwrite a foreign file test_a_file_at_the_destination_is_refused_and_kept[place]
MF remove any file test_a_file_at_the_destination_is_refused_and_kept[remove]
MK place accepts any link test_a_link_elsewhere_at_the_destination_is_refused_and_kept[place]
ML remove takes any link test_a_link_elsewhere_at_the_destination_is_refused_and_kept[remove]
MM remove unlinks nothing test_remove_takes_back_its_own_link_and_nothing_else
MN no already-placed short circuit test_placing_again_is_a_no_op
MG no git crashes test_without_git_on_path_it_refuses
MH place before checking all test_a_modified_runbook_is_refused
mutants: 23/23 killed; files restored

The suite as CI runs it, and the protected suites

  • The required job's own command, in a venv built as CI builds it (openDox at the pyproject.toml pin dede32b4), at 1f592766 with nothing placed: selected=1195 passed=1191 skipped=4 failures=0 errors=0. All three pins hold.
  • The governed set is unchanged at 16.
  • No protected file is edited. T059's oracle, as 12.5 calls it, over a simulated squash of 1f592766's tree onto da736090, which touches T022's three files: "ok: 0 protected edit(s), each entered and holding".

Copilot, by round

  • At c40af91b:

    • r4194596249, "Translate schema validation errors into GateRefused" (T021's code, carried then): fix-now, fixed in T021's ecf0ba9b, merged here at 8d02a1b3.
    • r4194596317, "Add pytest coverage for filesystem-mutating script safeguards": first an accepted limit, put to the holder; the holder ruled (a) (#656 6016356225), making it fix-now. Fixed in 7a149b70 (the committed tests). That commit also fixed a misreport the new tests found: an uninitialized spec leg had been reported at the root's HEAD.
  • At 8d02a1b3: "Findings: None".

  • At 7a149b70: r4195469835, the PR description. Fixed in the body.

  • On DRAFT: T073: the composed declarations (ARC-Q2 (a)): every declared entry runs composed, the rail files get the host's rail (plan 038) #45 (26fe63e7), on this file: r4196244480, check-ignore honours local excludes. The holder ruled it into this PR (#656 6026275158, item 2).

  • At dc879beb: r4200975244, ambient GIT_DIR/GIT_WORK_TREE redirect the checks. Fix-now, fixed in 146ae923; red before: 2 failed, 18 passed.

  • At 146ae923/c22b5df7:

    • r4201032934, assume-unchanged/skip-worktree hide an edit from git diff;
    • r4201424060, a symlinked runbook;
    • r4201032886, repository discovery inherited or redirected.

    All fix-now, fixed in 74f3a4fb. Before it, four of the new cases PLACED the link: both index flags, the link tracked at the pinned commit, and the enclosing repository.

  • At 60c56a47: r4201850292, hash-object applies clean filters. Fix-now, fixed in 1f2d6858 (--no-filters). Before it, a hiding clean filter and a CRLF edit under text both PLACED the link.

  • At 1f2d6858: r4201912214, a symbolic link in a parent directory. Fix-now, fixed in 2c86dcca. Before it, a linked docs/ holding identical bytes PLACED the link.

  • r4200975330 and r4201032964, the PR description: fixed by this body.

  • At the head 1f592766: review 5440606809, no new finding. Its overview reads "Needs a closer look" for one stated reason: "It remains a cross-repository draft stacked on prerequisites that have not landed". The After set above has landed, so this is not a finding.

  • Each fix-now finding was a real bypass with a simple payload, under the convergence rule (#656 5988818366).

  • At 57b4f80a, GitHub's update-branch merge of main e7815ac5 after the un-draft, with T022's three files byte-identical to 1f592766: review 5440672686, two findings. Both are ACCEPTED LIMITS under 5988818366, for the holder at READY; the PR was un-drafted, so neither is pushed.

    • r4205549424, a symlinked destination parent. It is already refused. Measured: with checkout/docs a link outside, git check-ignore answers "pathspec ... is beyond a symbolic link" (exit 128), the ignore guard refuses (exit 2) before mkdir or symlink_to, and the outside directory stays empty. Only the message misnames the cause.
    • r4205549516, the pinned tree mode. The scenario needs a pin that tracks the runbook as a link, but openDox-spec tracks it as 100644 blob e88f06e6 at f7ee3c76. The one-line hardening is a git ls-tree mode check, as a follow-on.

At the head 57b4f80a (T022's content as at 1f592766):

  • Checks: validate pass (run 37604221972), SonarCloud Code Analysis pass, Copilot success, Sourcery skipped. At 1f592766: validate run 37603513031 passed.
  • Review threads: 13, all answered and resolved. 0 unresolved.

Not done here

  • The permanent composed workflow that calls the script on every run is T029 (U-9).
  • The "documented local composed run" is wherever T029 documents it. This PR adds the script's own usage docstring only.

🤖 Generated with Claude Code

Summary by Sourcery

Place the composed-run session runbook from the pinned openDox-spec tree without vendoring it or compromising checkout safety.

New Features:

  • Add a composed-run placement utility that links the pinned openDox-spec session runbook into the checkout and safely removes it afterward.

Enhancements:

  • Validate repository boundaries, submodule pins, tracked and unmodified source bytes, repository-root ignore rules, and destination ownership before changing the working tree.

Tests:

  • Add comprehensive tests covering placement, removal, repository and content validation, ignore behavior, ambient Git configuration, and protection against overwriting unrelated files or links.

Chores:

  • Keep the placed runbook git-ignored and avoid vendoring an additional copy.

brettheap and others added 3 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>
…thing vendored (plan 038)

tests/test_session_runbook.py (its RUNBOOK) and tests/test_session_notebook.py
read docs/ideation-dashboard-session-runbook.md at this checkout, where the
runbook sat before the carve sent it to openDox-spec. The new
scripts/composed_placements.py links it, for a composed run, from the
composed tree (openDox/spec/docs/ under the openxFactory checkout) into
docs/. .gitignore names the placed path, so the checkout stays clean and
nothing placed can be committed.

Before it places anything, the script checks four things and refuses
(exit 2), naming the remedy, if any fails:
  * the spec checkout's HEAD is the commit the openDox root's spec
    gitlink pins;
  * the runbook is tracked there, unmodified;
  * the destination is ignored by .gitignore;
  * anything already at the destination is the script's own link to that
    same source.
It places all or nothing. --remove takes back the script's own links and
nothing else. The composed workflow (T029) and the documented local
composed run both call it.

Composed, the 10 runbook nodes turn green on T021's tree, 12.5's suites
read 82 red of the 174, and no green node turns red.

Refs opensoft/openxFactory#656 (CLAIMED 6013608959).

Lane: openXfactory-3

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 6, 2026 11:11
@sourcery-ai

sourcery-ai Bot commented Oct 6, 2026

Copy link
Copy Markdown

Reviewer's Guide

This PR implements composed-tree operability by safely linking the pinned session runbook into an ignored destination, sourcing contract schemas from their owning distributions or registered host, and executing governed suites with scoped, isolated host integrations; it also adds focused tests for these boundaries.

Sequence diagram for safe composed runbook placement

sequenceDiagram
    participant Runner
    participant Script as composed_placements.py
    participant Factory as openxFactory
    participant Root as openDox
    participant Spec as openDox-spec
    participant Checkout as openXdox checkout

    Runner->>Script: place(openxfactory)
    Script->>Root: rev-parse --verify HEAD:spec
    Root-->>Script: pinned spec commit
    Script->>Spec: rev-parse --verify HEAD
    Spec-->>Script: current HEAD
    Script->>Spec: ls-files --error-unmatch runbook
    Script->>Spec: diff --quiet HEAD -- runbook
    Script->>Checkout: git check-ignore -q runbook
    Script->>Checkout: inspect existing destination
    alt all checks pass
        Script->>Checkout: symlink_to(spec runbook)
        Script-->>Runner: placed runbook
    else guard fails
        Script-->>Runner: PlacementRefused exit 2
    end
    Runner->>Script: remove(openxfactory)
    Script->>Checkout: unlink only matching symlink
Loading

File-Level Changes

Change Details Files
Adds guarded placement of the session runbook from the pinned openDox-spec checkout into the composed repository without vendoring or dirtying the checkout.
  • Adds a standard-library script to validate the pinned submodule commit, tracked and clean source, ignored destination, and existing-link ownership before atomic placement.
  • Supports safe idempotent removal of only links created by the script.
  • Ignores the destination path so composed runs cannot commit the placed document.
scripts/composed_placements.py
.gitignore
Moves contract-schema validation from repository-relative files to owner-controlled sources.
  • Reads the packaged gate-action schema through digest-verified contract copies.
  • Adds explicit host schema registration, single-source enforcement, unregister support, and refusal when the host receipt schema is unavailable or invalid.
  • Updates validation errors and tests to cover packaged-schema integrity, host-source behavior, and the absence of checkout-relative schema reads.
src/openxdox/gate_console.py
tests/test_gate_console_schema_source.py
Updates composed test hosting so governed suites execute with the appropriate openXfactory host integrations while preserving import and registry isolation.
  • Replaces the unconditional stand-in fixture with scoped host-plane registration helpers.
  • Adds governed host discovery, openXdox column construction, composed-host support, path restoration, and rejection of openXdox modules imported from another checkout.
  • Registers the composed host's contract-schema directory when available.
  • Adds tests that derive and verify the governed suite list, host precedence, column composition, and failure isolation.
tests/conftest.py
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

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

Invalid registered schemas can escape the refusal path, and the placement script lacks committed regression coverage.

Review effort: Balanced
Findings: 1 High severity · 1 Medium severity

Open (2)
What changed in this PR

Adds composed-run support across the stacked T020–T022 changes.

Changes:

  • Registers governed hosts for gate-driven suites.
  • Resolves contract schemas from their owning package or host.
  • Places the pinned runbook via a git-ignored symlink.
File Description
.gitignore Ignores the placed runbook.
scripts/​composed_placements.py Validates and manages the runbook symlink.
src/​openxdox/​gate_console.py Adds owner-aware schema resolution.
tests/​conftest.py Configures governed hosts and schema registration.
tests/​test_gate_console_schema_source.py Tests schema ownership and registration.
tests/​test_host_plane.py Tests governed-host selection and isolation.

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

Comment thread src/openxdox/gate_console.py Outdated
Comment thread scripts/composed_placements.py
brettheap and others added 2 commits October 6, 2026 11:20
…Copilot r4194497045)

A registered source's receipt schema was checked only for existence. A
document that is valid YAML but no valid JSON Schema escaped as a raw
exception: an empty file raised AttributeError, `required: 1` raised
TypeError, and an unresolvable $ref raised jsonschema's referencing
error. cli_gate's demotion reports only GateRefused, BoundaryViolation or
OSError as "executed but receipt not written", after files have moved, so
any of these ended the verb with a traceback.

_schema_errors now parses the schema, checks it against the validator's
own meta-schema, and applies it. A failure at any of the three refuses as
GateRefused. _validate_contract_document keeps its line count, so no line
that test_no_hardcoded_status_words.py's PROSE_EXEMPT pins moves. A new
lone case covers the three schemas, and two more mutants are killed.

Refs opensoft/openxFactory#656 (CLAIMED 6013608628).

Lane: openXfactory-3

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Lane: openXfactory-3

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

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

It remains a cross-repository draft stacked on unlanded prerequisite pull requests and host contracts.

Review effort: Balanced
Findings: None

Resolved since last review (2)

…lacements.py (Copilot r4194596317; holder ruling (a))

The holder's ruling on opensoft/openxFactory#656 (comment 6016356225,
option (a)) adds tests/test_composed_placements.py to T022's files. The
script changes the file system, so its guards now live in the suite CI
runs, not in a scratch harness.

Each case runs the script in a child interpreter, as the composed run
calls it. The script under test is a copy of this checkout's script,
inside a scratch checkout that carries this checkout's own .gitignore,
and the tree it reads is a scratch openxFactory/openDox/spec tree pinned
to commit A. No case writes into this checkout. 14 cases cover:
  * placing, with the checkout left clean;
  * the no-op of placing again;
  * --remove;
  * a spec off its pin;
  * a modified runbook;
  * an untracked runbook;
  * an uninitialized spec leg;
  * an uninitialized openDox root;
  * a destination .gitignore does not name;
  * no git;
  * a file, or a link elsewhere, at the destination, kept under both
    place and --remove.
No case skips, and the file carries none of 12.5's governed-suite markers.

Writing the uninitialized-leg case found a misreport in the script, now
fixed. An uninitialized spec leg is an empty directory, so git answered
for the openDox root above it, and the refusal read "spec is at <the
root's HEAD>". The script now asks git about the leg only when the leg
has its own .git, so the refusal reads "is not checked out" with the
remedy. It refused before as well; only the message was wrong.

Refs opensoft/openxFactory#656 (CLAIMED 6013608959).

Lane: openXfactory-3

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

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

It remains a cross-repository draft stacked on unlanded prerequisites, with one PR-description scope discrepancy.

Review effort: Balanced
Findings: 1 Low severity

Open (1)

Comment thread tests/test_composed_placements.py
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
…tes again (plan 038) (#42)

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

Arc: neutral-product-standalone-operability

## T023, U-4: `swb-session.js` spells the model's two names at its call sites again (plan 038, feature `038-opendox-document-tool-self-maintenance`)

After set met: T004 (opensoft/openxFactory#1245) landed as 260234b5 at 2026-10-06T14:31Z.

Base brought up to date with main (T020 #39 c6d15b2, then T024 #38 8b64fae) by two merge commits; CI green at e8a067f; the composed falsifier was measured at c3c6a47, before the merges, and was not re-run (the merges brought only T020's host registration and T024's shim; INFERRED, not measured: the composed falsifier reads the same at the new head).

### Contract, from `tasks.md`

- **Realizes**: 12.5 (part).
- **Falsifier**: the 2 nodes, composed, quoted.
- **Ruled**: R2Q8. (The entry names no Decisions line.)
- **After**: T004.
- **Files**: openXdox-code `src/openxdox/web/views/swb-session.js`, plus ONE new test file, `tests/test_swb_session_transport.py`. The test file is not in the task's `tasks.md` entry: lane openxfactory-4 added it to T023's Files by its ruling on Copilot's first finding on this PR (`lane4-to-lane3.log`, 12:52:51Z, "FIX-NOW, as in T022 (a)"; recorded on #656 in lane 4's latest comment). No protected suite is edited, and the file is not one of the 16 `HOST_PLANE_SUITES`.

### What and why

`submitSession` took its two model calls from locals, `const headersOf = bound ? bound.consoleHeaders : consoleHeaders;` and `const repairWith = ...`, called as `headersOf(caps)` and `repairWith(send, repair)`. That is the `bound`-namespace refinement of Copilot review round 3, which arrived with the S5 carve in #18. `tests/test_session_confinement.py` pins both write transports (`swb-session.js` and `swb-create.js`) to the literal spellings `headers: consoleHeaders(caps)` and `withConsoleRepair(send, repair)`, so two nodes went red in the composed run. The pre-shed openxFactory copy spelled both exactly; `swb-create.js` still does. The trace is lane 3's R2-INV-P4F part, Group J.

The fix shadows the two module-level names inside `submitSession` instead of respelling the call sites:

```js
const MODULE_TRANSPORT = {
  consoleHeaders: (...args) => consoleHeaders(...args),
  withConsoleRepair: (...args) => withConsoleRepair(...args),
};
// inside submitSession:
const { consoleHeaders, withConsoleRepair } = bound || MODULE_TRANSPORT;
//   headers: consoleHeaders(caps),   ...   return withConsoleRepair(send, repair);
```

With a `bound` namespace the calls go to it, as before. With none, `MODULE_TRANSPORT` reaches the CURRENT install at call time, exactly as the two module-level consts do. The comment above it does not quote the pinned needles, because a needle quoted in its own source satisfies itself; each pinned needle occurs once in the file, in code (counted).

### The new test file, `tests/test_swb_session_transport.py`

The pinned spellings are text assertions, so they cannot tell the right model from the wrong one. Two node probes run the shipped module out of the composed bundle and assert on the request that reaches the injected fetcher:

- **bound**: two `firstEditTransport`s over two models that differ in exactly `consoleHeaders` and `withConsoleRepair`, both built before either is called; each request must carry its own model's header (from its own `caps`) and go through its own model's repair policy.
- **unbound**: a mounted session form, submit pressed; the request must carry the header the mounted model's `consoleHeaders` built and go through its `withConsoleRepair`. (The outcome panel the column writes after the request needs the shell's `page-overlay` host, which a stub DOM lacks, so the handler rejects after the fetcher is called; the probe records that and asserts on the request only.)

Both pass at main's own code (checked in the base clone), so they describe the behaviour the respelling preserves. They run in the lone CI form too: no openxFactory module, node only, as `test_gate_loop_probes.py` does.

### The falsifier

**Base measured**: openXdox-code `56e1c238` (origin/main), openDox-code `a9ac96f9` (plain install, `--no-deps`, over openXdox's pin, as R2-INV-P4F did), openxFactory `92010d3e` with `openDox`, `openXdox` and `openXwallet` initialized recursively and its `scripts/` on `PYTHONPATH` (R1Q23 (a)'s form). Python 3.12.3, pytest 8.4.2, the CI install command (`python -m pip install -e ".[test]"`). **Head**: `c3c6a47` (the fix is `f19e05c`; the test file is `c3c6a47`). Run with `FORCE_COLOR`, `CLICOLOR` and `COLORTERM` unset and `TERM=dumb`.

RED at main (`56e1c238`), composed:

```
FAILED tests/test_session_confinement.py::test_the_renderer_presents_the_console_token_from_the_capability_probe
FAILED tests/test_session_confinement.py::test_the_renderer_repairs_a_stale_token_by_re_reading_it_never_by_reloading
2 failed in 0.45s
```

(each: `AssertionError: swb-session.js`, from `'headers: consoleHeaders(caps)' in body` at `:290` and `'withConsoleRepair(send, repair)' in body` at `:383`)

GREEN at head `c3c6a47`, same environment:

```
PASSED tests/test_session_confinement.py::test_the_renderer_presents_the_console_token_from_the_capability_probe
PASSED tests/test_session_confinement.py::test_the_renderer_repairs_a_stale_token_by_re_reading_it_never_by_reloading
2 passed in 0.67s
```

### What else reads `swb-session.js`, and did not move

Every test file in the tree (`tests/test_*.py` and `tests/integration`, named on the command line so the declared exclusion does not drop them), composed as above, run SERIALLY on one idle container with `--continue-on-collection-errors` (`test_gate_console.py` fails to collect in both, for want of `scripts/proposal-support.py`):

| | nodes | failed | errors | passed | skipped |
|---|---|---|---|---|---|
| main `56e1c238` | 3671 | 550 | 94 | 3021 | 6 |
| head `c3c6a47` | 3673 | 548 | 94 | 3025 | 6 |

Compared node by node: the 3671 ids at main all appear at the head, and EXACTLY TWO of them change status, both red to green, the two above. The two extra nodes are the new file's, both passing. Nothing goes green to red.

The suites that read `swb-session.js`, red nodes at main and at the head: `test_gate_loop_probes.py` 0 / 0, `test_renderer.py` 25 / 25, `test_doxbench_save.py` 3 / 3, `test_session_confinement.py` 20 / 18 (the two above), `test_staging_workbench.py` 63 / 63, `test_doxchat_model_intake.py` 10 / 10, `test_doxbench_transport.py` 6 / 6, `test_assembled_bundle.py` 4 / 4, `test_doxbench_scope.py` 1 / 1, `test_gate_loop_views.py` 0 / 0. The three suites the trace names read 28 red on both sides, the same node ids, as the trace's trial found. Earlier pairs, run overlapping and while the container was out of process slots, showed `Resource temporarily unavailable` and `can't start new thread` errors in git-fixture and thread-spawning suites; they were discarded and re-run serially after the container was restarted, which is the pair above.

### Mutants of the fix

Spelling mutants, killed by the pinned nodes (composed, over `test_session_confinement.py`, `test_gate_loop_probes.py`, `test_doxbench_save.py` and `test_renderer.py`, compared with the head's own red set):

| Mutant | Result |
|---|---|
| M1: respell the `headers:` call as `(bound ? bound.consoleHeaders : MODULE_TRANSPORT.consoleHeaders)(caps)` (behaviour kept) | KILLED: both target nodes go red (the stale-token node pins the same headers spelling) |
| M2: respell the return as `(bound ? bound.withConsoleRepair : MODULE_TRANSPORT.withConsoleRepair)(send, repair)` | KILLED: the repairs-a-stale-token node goes red |

Behaviour mutants, which keep every pinned spelling and which only the new file can kill (run composed AND in the lone CI venv, same result in both):

| Mutant | Killed by |
|---|---|
| M3: drop `bound ||`, so the module pair is always used | `test_js_each_built_transport_sends_through_its_own_models_header_and_repair` (1 failed, 1 passed) |
| M4: `MODULE_TRANSPORT.consoleHeaders` calls `withConsoleRepair` | `test_js_a_mounted_form_sends_through_the_mounted_models_header_and_repair` (1 failed, 1 passed) |
| M6: `MODULE_TRANSPORT.withConsoleRepair` calls `consoleHeaders` | `test_js_a_mounted_form_sends_through_the_mounted_models_header_and_repair` (1 failed, 1 passed) |

Before the new file, M3 and M4 survived every committed suite (the finding on the first head).

### Other gates

- **CI form, lone** (`python -m pytest -q --deselect "$LEFT_OUT"` from the root, the CI venv, no `PYTHONPATH`) at `c3c6a47`: `1097 passed, 4 skipped, 1 deselected`; the workflow's triple reads `selected=1101 passed=1097 skipped=4 failures=0 errors=0`, which meets its pins (floors 1099 / 1095, skipped exactly 4, 0 / 0). At `f19e05c` and at main it read 1099 / 1095.
- **Protected suites:** `python3 scripts/protected_suites.py --landings=<both commits> --suites=<the 98 tests/test_*.py at origin/main>` prints `ok: 0 protected edit(s), each entered and holding`, with and without `--chains`. The new file is not in `HOST_PLANE_SUITES`.
- **Already red at main, composed, not from this change:** `tests/test_dependency_direction.py::test_first_attribute_access_refuses_and_names_the_layering` (the T024 writer attributes it to `ideation_dashboard` being importable when composed; I did not trace it); red in both the base and head runs.
- **CI at the head:** `validate` pass and `SonarCloud Code Analysis` pass (2 of 2) at `c3c6a47`.
- **Copilot:** two reviews, one per head. At `f19e05c8` (review 5428398958): 1 finding, that no committed test distinguishes the bound and unbound transports. Fixed by `c3c6a47`; thread answered and resolved. At `c3c6a470` (review 5428827782, COMMENTED, body "Changes recommended"): 2 findings, both on the new file.
  - "the declared one-file scope": this body said one file and "no test is added"; the body is corrected (Files above), the ruling that widened Files is cited, and the thread is answered and resolved.
  - "update the workflow coverage floors": see below. An accepted limit, answered and resolved.
  - 0 unresolved threads at the head. The holder rules on the "Changes recommended" label.
  - Holder ruling #656 `6017860539`: both Copilot findings, the "Changes recommended" label included, are ACCEPTED as they stand; the CI floors are left to T029.
- **At the new head `e8a067f3`** (two update-branch merges of `main`, both merge commits: `3b6cd13b` took T020 #39 `c6d15b27`, then `e8a067f3` took T024 #38 `8b64fae0`): every check-run completed success; Copilot review 5432366190, state COMMENTED, "Approval recommended", `Findings: None` (requested through the reviewer API after about 10 minutes without an auto-review); 3 review threads in all, all resolved, 0 unresolved, none new. Against `c3c6a470` the merges changed only `tests/conftest.py`, `tests/test_host_plane.py` and `scripts/ideation_dashboard/session_git.py`, the files of T020 and T024.
- **SonarCloud:** the `SonarCloud Code Analysis` check passes (quality gate); I did not list individual issues.

### What is NOT done

- **The CI floors are not raised** (`MIN_SELECTED` / `MIN_PASSED` in `.github/workflows/validate.yml`, 1099 / 1095; this PR's head selects 1101 and passes 1097). `validate.yml` is not in T023's Files, plan 038 gives its floors in this repository to no task (T017 and T051 re-pin openDox-code's), and the open sibling PRs that add tests (#39, #40, #41) would collide with this one on the same two lines. Floors are minimums, so the check passes and the margin is two wider than it was. The workflow's own comment says the commit that adds a test SHOULD raise them with the reason; whether and in which PR is for the holder to sequence.
- The other groups of R2-INV-P4F's one-off bucket (H, L, T, W, S) are other tasks.


Arc: neutral-product-standalone-operability
Lane: openxfactory-4 (openXfactory-4-openDox_extraction)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
brettheap and others added 2 commits October 6, 2026 22:05
…to T022

T020 (#39 c6d15b2), T024 (#38 8b64fae), T023 (#42 d45a093) and T021
(#40 f70bc9c) have landed. This branch carried T021's ecf0ba9 versions of
src/openxdox/gate_console.py and tests/test_gate_console_schema_source.py,
and main holds T021's final squash. Both conflicts are resolved to main's
side, since neither file is T022's. Against main the branch now differs only
in T022's three files: .gitignore, scripts/composed_placements.py and
tests/test_composed_placements.py.

Lane: openXfactory-3

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nly by a local exclude (Copilot r4196244480; holder ruling 6026275158)

git check-ignore honours .git/info/exclude and core.excludesFile, so the
guard passed on a machine whose repository .gitignore no longer named the
placement, and the placed link could then be committed from another
checkout. The holder ruled the fix into T022, which owns the script
(opensoft/openxFactory#656, comment 6026275158, item 2); Copilot raised it
on #45, which stacks on this file.

The guard now reads git check-ignore -v, which prints the deciding pattern
as <source>:<line>:<pattern><TAB><path>, with the source relative to the
repository root. It requires that source to be .gitignore. It also refuses
a negated pattern: -v exits 0 for a "!" pattern although the path is not
ignored, which the old -q exit status had covered.

tests/test_composed_placements.py adds four cases:
  * a destination only .git/info/exclude names: refused;
  * a destination only core.excludesFile names: refused;
  * a local exclude beside the .gitignore line still places, because the
    root .gitignore outranks it and stays the source;
  * a .gitignore that negates the destination: refused.
Against the script before this commit, the two local-exclude cases fail
(2 failed, 16 passed). With it, 18 passed. All 17 mutants are killed, each
by its own case, including MP-any-ignore-source (the dropped source check)
and MQ-a-negation-counts-as-ignored.

Refs opensoft/openxFactory#656 (CLAIMED 6013608959).

Lane: openXfactory-3

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Lane: openXfactory-3

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

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

Clean filters can bypass the raw-byte check, and the PR description’s test evidence is outdated.

Review effort: Balanced
Findings: 1 Medium severity · 2 Low severity

Open (3)
Resolved since last review (3)

Comment thread scripts/composed_placements.py Outdated
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>
…ilot r4201850292)

By default git hash-object applies the path's clean filters and EOL
normalization, so a filter in the spec checkout (.git/info/attributes plus a
filter.<name>.clean command, or the text attribute on a CRLF edit) turned
edited bytes back into the pinned blob. Against the script before this
commit, both cases PLACED the link. The check now runs hash-object
--no-filters, so it compares the raw bytes the placed link exposes.

tests/test_composed_placements.py adds
test_an_edit_a_clean_filter_would_hide_is_refused[clean-filter|eol-normalization].
Both cases fail before the fix and pass with it: 29 passed.
MW-hash-through-clean-filters is killed, and all 22 mutants are killed.

Refs opensoft/openxFactory#656 (CLAIMED 6013608959).

Lane: openXfactory-3

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

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

A symlinked source parent can bypass the pinned-document guard, and the PR validation description is stale.

Review effort: Balanced
Findings: 1 High severity · 2 Low severity

Open (3)
Resolved since last review (1)

Comment thread scripts/composed_placements.py Outdated
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 and others added 2 commits October 7, 2026 09:49
Path.is_symlink() looked only at the final component. With spec/docs
replaced by a link to an outside directory holding identical bytes, the
script PLACED a link to that outside file, which the pin does not fix.
Every component from the spec leg down to the file is now checked, and a
link anywhere is refused.

tests/test_composed_placements.py adds
test_a_runbook_reached_through_a_linked_directory_is_refused. It fails
before the fix (placed, rc 0) and passes with it: 30 passed.
MX-only-the-last-component is killed, and all 23 mutants are killed.

Refs opensoft/openxFactory#656 (CLAIMED 6013608959).

Lane: openXfactory-3

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Lane: openXfactory-3

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@brettheap
brettheap requested a balanced review from Copilot October 7, 2026 09:51
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>

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

It remains a cross-repository draft stacked on prerequisites that have not landed.

Review effort: Balanced
Findings: 2 Low severity

Open (2)
Resolved since last review (1)

@brettheap brettheap changed the title DRAFT: T022, U-3: the runbook placed from the composed tree, git-ignored, nothing vendored (plan 038, P4F-3) T022, U-3: the runbook placed from the composed tree, git-ignored, nothing vendored (plan 038, P4F-3) Oct 7, 2026
@brettheap
brettheap marked this pull request as ready for review October 7, 2026 09:57
@sourcery-ai

sourcery-ai Bot commented Oct 7, 2026

Copy link
Copy Markdown

Sorry @brettheap, you've used your own review budget of 250,000 diff characters for the last 7 days.

You can request another review in 2 days and 11 hours by commenting @sourcery-ai review. Upgrade to get a review now.

@sonarqubecloud

sonarqubecloud Bot commented Oct 7, 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

Pinned file-mode validation and destination-parent confinement remain incomplete.

Review effort: Balanced
Findings: 1 High severity · 1 Medium severity

Open (2)
Resolved since last review (2)

Comment thread scripts/composed_placements.py
Comment thread scripts/composed_placements.py
@brettheap

Copy link
Copy Markdown
Contributor Author

READY (holder, manual landing) at 57b4f80

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

Holder, on Brett's word "land lane 3's PRs when green" (2026-10-06). At 57b4f80a the PR is CLEAN with rollup SUCCESS. All 13 threads are resolved and it has 0 closing references. Copilot reads "Changes recommended" for two findings, r4205549424 and r4205549516. The holder ruled both ACCEPTED LIMITS at opensoft/openxFactory#656 comment 6035811524, and the threads cite that ruling. The holder lands the PR by the manual path.

@brettheap
brettheap merged commit aefd109 into main Oct 7, 2026
4 checks passed
@brettheap

Copy link
Copy Markdown
Contributor Author

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

LANDED — lane openxfactory-4, 2026-10-07T10:15:23Z, PR #41 → aefd109 (opensoft/openXdox-code main; plain gate)

holder manual landing; Copilot r4205549424 r4205549516 accepted limits at openxFactory#656 6035811524

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>
brettheap added a commit that referenced this pull request Oct 8, 2026
Brings T101 (#48), T098 (#51), T103 (#49), T096 (#53), T100 (#54), T099
(#47), T021's follow-on (#55), T102 (#52), T022 (#41, landed as aefd109),
T097 (#50) and T096's follow-on (#56).

Conflicted: scripts/composed_placements.py and tests/test_composed_placements.py,
both T022's files, which this branch carried from #41 through #44. Main's
landed side is kept for both. tests/declared_exclusion.yaml merged clean
(T101's entry edits beside T073's header and open_until).

Lane: openXfactory-3

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
brettheap added a commit that referenced this pull request Oct 8, 2026
…nd T096's follow-on #56 landed)

Two files conflicted (add/add), both T022's, which this branch had carried
from #41's head 1f2d685: scripts/composed_placements.py and
tests/test_composed_placements.py. Main's side was taken for both, as it
was for .gitignore (auto-merged to main's). After this merge the tree
differs from main only in T029's two files.

Lane: openXfactory-3

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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