Skip to content

T026, U-7: the allow-list for 12.5's governed suites, with R-1 (a)'s span kind (plan 038, H-2) - #43

Merged
brettheap merged 11 commits into
mainfrom
r2/t026-allow-list
Oct 6, 2026
Merged

brettheap merged 11 commits into
mainfrom
r2/t026-allow-list

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

Arc: neutral-product-standalone-operability

T026, U-7: the allow-list for 12.5's governed suites, with R-1 (a)'s span kind (plan 038)

The After set has landed:

What this PR does

32 entries are appended to tests/protected_suite_respellings.yaml, giving 51 in all. Each is one edit inside its named test or span, with its exact old and new text, and the entries for each suite are chained by blob.

suite entries what
test_session_gates.py 3 CF-4: cmd_gate_* read from openxdox.cli_gate
test_session_verbs.py 5 CF-4: cmd_gate_* 4, and Group W openxdox.generator.live_topic_health
test_session_confinement.py 3 CF-4: serve_projection.hosted_index
test_doxbench_share.py 2 CF-4: each swept module at its one home; the record schema read through openxdox.contracts.verified_bytes, digest-checked against copies.yaml (Copilot's finding)
test_staging_workbench.py 19 see below

The 19 staging entries, in chain order (allow-list entries 33 to 51):

  • 33: the 'cluster' node, respelled to group neighbourhood.
  • 34, 37, 38 and 39: CF-4 Group S2, the four slice-S5 spellings of the create and session columns.
  • 35 and 36: R-1 (a)'s two in-test relocations, :1291 and :1333. Each route is one export const …_ROUTE of swb-session.js, and neither the view nor the model names one.
  • 40 to 49, R-1 (a)'s six spans (seven span: entries) and three in-test respellings:
    • _CREATE_HARNESS (two entries), _SESSION_HARNESS and _HOSTILE_HARNESS: each imports its route from the column RULED Q3 moved it to (CREATE_ROUTE from swb-create.js, sessionRoute from swb-session.js) and the reader from display.js. Each installs setDisplay(readDisplay({ display: cases.display })) before anything derives, as the shell does. BRAINSTORM_AREA and STAGING_AREA become the model's capturedArea() and organizedArea().
    • _run_create, _run_session and _hostile_descriptors: each copies the sibling files its harness imports (and what they import), with a package.json that makes node read them as modules, as test_session_confinement.py's harness does. Each hands its harness /capabilities["display"], by the statement serve.build_server() makes, replayed as test_gate_loop_views.py's _served_display replays it.
    • Three in-test respellings, flagged below.
  • 50 and 51: the docstrings of the two relocated tests now say what their assertions hold (Copilot's findings).

scripts/protected_suites.py, R-1 (a)'s span kind. An admitted entry may name, in place of test, a span: one module-level constant, or one module-level helper function (its decorators included). The entry's old text must lie inside that statement before the landing, and its new text inside it at the landing. Three further rules apply:

  • Counted fail-closed. The span's name must have exactly one binding in the module, counting every binding form in every scope: assignment, augmented or annotated assignment, for or with target, walrus, del, def or class, an import or a star import, an except … as alias, a match capture, a type parameter and a parameter.
  • Alone on its lines. No other statement may share a line with the span.
  • Never a span: a class, an import, a test, two targets, an annotation alone, or an added name. Only an admitted entry may name a span.

The module's usage block and help now give 12.5's call with --chains, as batch Q has it.

tests/test_protected_suite_check.py: 33 span cases (87 in the module).

FLAGGED for the holder's word: three in-test respellings beyond the plan's 17

These are CF-4's kind. T026's measurement reported them at comment 6016415390 (point 2), and comment 6016648451 ruled that point. They close the last three of the suite's nodes, which the served display alone does not reach. Each is entered as a respelling naming that history, and each stands or falls on your word:

  1. test_gate_off_descriptor_is_the_real_cli_invocation: cli.cmd_gate_create_document becomes cli_gate.cmd_gate_create_document, imported in the test from openxdox.cli_gate. The old spelling raised AttributeError.
  2. test_gate_off_session_affordances_are_the_real_cli_invocations: cli.cmd_gate_edit_document, cmd_gate_open_pr and cmd_gate_abandon_session become cli_gate.…, likewise.
  3. test_the_notebook_refresh_is_a_descriptor_in_BOTH_gate_postures: REPO_ROOT / "scripts" / "sync-notebooklm-books.py" becomes the one directory on the import path that holds the script, which is openxFactory's scripts/ in the composed run (R1Q23 (a)). A run that finds it in none, or in more than one, fails. The old path raised FileNotFoundError.

No assertion is weakened in any of the three, and every argument and flag assertion stays.

T020's two files, edited once #39 landed (commit 242889b)

T020's registration has to cover tests/test_staging_workbench.py. Without it every harness helper refuses ProfileNotRegistered, as measured below. The edit is to T020's tests/conftest.py and to the scan in tests/test_host_plane.py:

  • "test_staging_workbench.py" joins GOVERNED_HOST_SUITES, with a comment giving the reason, kept as a block of its own apart from T021's lines;
  • the scan also recognises a gate verb spelled as a command line (_GATE_COMMAND = re.compile(r"""["']gate ([a-z][a-z-]*)""")), the way this suite's descriptors spell it. Measured over the 16 governed suites, only test_staging_workbench.py matches it and not the argument-vector spelling.

T021 (#40) also edits tests/conftest.py. Whichever of the two lands second merges main.

The falsifier

The oracle, in 12.5's call as main now has it (--chains), at a simulated squash landing of this head. The landing is one local commit on main f70bc9c carrying exactly this branch's tree and the Arc: trailer, and it is never pushed. The inputs are 12.5's own:

  • the governed set by its git grep: 16, the same 16;
  • the Arc: landings since e28930bf: 14, T020's, T024's, T023's and T021's among them.
admitted: c41063d6aaad tests/test_gate_loop_views.py, by entry 1 of tests/protected_suite_respellings.yaml
admitted: 839492d905f4 tests/test_gate_loop_views.py, by entry 2 of tests/protected_suite_respellings.yaml
admitted: 56e1c238681a tests/test_branch_session.py, by entry 18 of tests/protected_suite_respellings.yaml
admitted: 2f7003912aa7 tests/test_doxbench_share.py, by entries 31, 32, in that order, of tests/protected_suite_respellings.yaml
admitted: 2f7003912aa7 tests/test_session_confinement.py, by entries 28, 29, 30, in that order, of tests/protected_suite_respellings.yaml
admitted: 2f7003912aa7 tests/test_session_gates.py, by entries 20, 21, 22, in that order, of tests/protected_suite_respellings.yaml
admitted: 2f7003912aa7 tests/test_session_verbs.py, by entries 23, 24, 25, 26, 27, in that order, of tests/protected_suite_respellings.yaml
admitted: 2f7003912aa7 tests/test_staging_workbench.py, by entries 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, in that order, of tests/protected_suite_respellings.yaml
ok: 8 protected edit(s), each entered and holding

rc=0. In the pre-batch-Q form, which passes no --chains, a landing of this branch reads FAIL: the arc edited protected suites outside the reviewed allow-list: tests/test_doxbench_share.py, … with rc=1. That is why batch Q was ruled.

The oracle on mutants of the real list (--chains), each refused:

A a chain step's before_blob broken: rc=2
    FAIL: tests/protected_suite_respellings.yaml breaks its own rules, so nothing is subtracted: entry 44: its before_blob is not the after_blob of the entry before it for tests/test_staging_workbench.py, so the two do not chain
B the last staging step dropped: rc=1
    refused:  f7ebecbcc37e tests/test_staging_workbench.py: entries 33-50: tests/test_staging_workbench.py at the landing is 66d16b72080ccbaece5be1aef81446aaa01500bc, not the chain's 54852fa5250b2c4d0630088154aa4bac6517bffe; entries 3…
    FAIL: the arc edited protected suites outside the reviewed allow-list: tests/test_staging_workbench.py
C a span entry names another span: rc=1
    refused:  f7ebecbcc37e tests/test_staging_workbench.py: entries 33-51: step 10: the entry's old text is not inside _run_session before the landing; entries 34-51: tests/test_staging_workbench.py before the landing is 655afc8b57224…
    FAIL: the arc edited protected suites outside the reviewed allow-list: tests/test_staging_workbench.py
D a span entry names a test instead: rc=1
    refused:  f7ebecbcc37e tests/test_staging_workbench.py: entries 33-51: step 10: the entry's old text is not inside test_toggle_keyword_is_order_preserving_and_idempotent before the landing; entries 34-51: tests/test_staging_workbe…
    FAIL: the arc edited protected suites outside the reviewed allow-list: tests/test_staging_workbench.py
E a respelling entry names a span: rc=2
    FAIL: tests/protected_suite_respellings.yaml breaks its own rules, so nothing is subtracted: entry 43: names a span, and only an admitted entry may (R-1 (a)); a respelling entry names its test

R-1 (a)'s span guards: 22 mutants of scripts/protected_suites.py, each run against the committed tests/test_protected_suite_check.py. 21 are killed. M4 survives because it is equivalent: a nested binder also lies inside its compound statement's lines, so the shared-line guard refuses it too. M22, which removes both guards, is killed.

M1 a respelling may name a span: killed (first: test_a_span_entry_that_breaks_the_rules_subtracts_nothing[respelling])
M2 a span may be spelled as a test: killed (first: test_a_span_entry_that_breaks_the_rules_subtracts_nothing[a-test])
M3 a name bound twice may be a span: killed (first: test_what_is_never_a_span_is_refused[shadowed])
M4 a block-only binding may be a span: SURVIVED
M5 a del is not a binding: killed (first: test_a_second_binding_in_any_form_or_scope_is_never_a_span[del])
M6 an except alias is not a binding: killed (first: test_a_second_binding_in_any_form_or_scope_is_never_a_span[except-alias])
M7 a match capture is not a binding: killed (first: test_a_second_binding_in_any_form_or_scope_is_never_a_span[match-capture])
M8 a star import is not a binding: killed (first: test_a_second_binding_in_any_form_or_scope_is_never_a_span[star-import])
M9 a parameter is not a binding: killed (first: test_a_second_binding_in_any_form_or_scope_is_never_a_span[parameter])
M10 a class may be a span: killed (first: test_what_is_never_a_span_is_refused[class])
M11 a helper function is never a span: killed (first: test_an_edit_inside_a_named_helper_function_is_admitted)
M12 two targets may be a span: killed (first: test_what_is_never_a_span_is_refused[two-targets])
M13 an annotation alone may be a span: killed (first: test_what_is_never_a_span_is_refused[annotation-only])
M14 a helper's decorators lie outside it: killed (first: test_a_helpers_decorator_is_inside_its_span)
M15 a shared line is allowed: killed (first: test_a_span_that_shares_a_line_with_another_statement_is_refused)
M16 old outside the span is not checked: killed (first: test_an_old_text_outside_the_span_is_refused)
M17 new outside the span is not checked: killed (first: test_a_new_text_that_leaves_the_span_is_refused)
M18 an added span is not refused: killed (first: test_what_is_never_a_span_is_refused[class])
M19 the span's first line is outside it: killed (first: test_an_edit_spanning_a_whole_named_constant_is_admitted)
M20 a span entry keeps the test key: killed (first: test_this_repositorys_allow_list_keeps_its_rules)
M21 span entries take the test path: killed (first: test_an_edit_spanning_a_whole_named_constant_is_admitted)
M22 M4 and M15 together: killed (first: test_what_is_never_a_span_is_refused[block-only])
M0 unmutated: 87 passed in 9.05s

The served display, measured in the test process (Brett's (2)+(3): "INFERRED … MEASURE it"). This was an uncommitted probe test, run composed at 3eead0fe (242889b on the integration of that time; the helpers' statement is unchanged since):

  • with no host registered, the helpers' statement refuses ProfileNotRegistered;
  • under the governed host (OpenxFactoryProfile), it reads host_facet: declared and host_profile: openxfactory-engineering, with areas captured: ideation/brainstorm/ and organized: ideation/staging/;
  • that result equals the display of a live GET /capabilities from serve.build_server() under the same registration: EQUAL: True, 3163 bytes of sorted JSON.

The nodes, composed, in R1Q23 (a)'s form:

run 16 governed suites test_host_plane.py test_protected_suite_check.py
without this PR (main f70bc9c) 842 nodes: 800 passed, 42 red (staging 29, verbs 5, confinement 3, gates 3, share 2) 9 passed 54 passed
with this head 9c29855 (0478b5f1) 842 nodes: 842 passed 9 passed 87 passed
earlier, this PR WITHOUT the edit to T020's files (at 856047a, on main 56e1c23 with #41 and #42 merged) test_staging_workbench.py: 37 failed, 46 passed, every failure ProfileNotRegistered 9 passed n/a

42 nodes go red to green, none goes green to red, and none is still red.

CI at the head 9c29855

  • Checks: validate success (run 37517336710), SonarCloud success, copilot-pull-request-reviewer success.
  • CI's own pytest step, run alone (CI's install, openDox at its pin dede32b4): selected=1146 passed=1142 skipped=4 failures=0 errors=0, against the floors MIN_SELECTED 1099 and MIN_PASSED 1095 and EXPECT_SKIPPED 4. The holder ruled (lane log, 13:57Z) that T029 raises the floors once, and T020 to T026 leave them.
  • Copilot: reviewed at 9c29855, "Needs a closer look" (a governance-sensitive validator and a large chained allow-list), Findings: None. 9 threads over its reviews, each answered with its fixing commit and resolved; 0 unresolved.

Files changed

scripts/protected_suites.py, tests/protected_suite_respellings.yaml, tests/test_session_gates.py, tests/test_session_verbs.py, tests/test_session_confinement.py, tests/test_doxbench_share.py, tests/test_staging_workbench.py (T026's seven), plus three files the ruling at comment 6016648451 adds to T026's Files:

What remains, and its trigger

  • The holder's word on the three flagged respellings. Nothing else in this PR waits.
  • If main moves before landing, the lander merges it, never by rebase.

🤖 Generated with Claude Code

…038)

R-1 (a), ruled by Brett Heap with plan 038 (opensoft/openxFactory#656
comment 6013547504, "Admitted edit kind (Recommended)") and recorded at
12.5's falsifier by T005's batch Q: an admitted entry may name a SPAN, one
module-level constant of a protected suite, in place of its test.
scripts/protected_suites.py's _inside_the_test rule is amended to admit
that kind, and nothing wider:

* the entry's old text lies inside that one constant's statement before
  the landing, and its new text inside it at the landing;
* a span is one module-level assignment to one bare name that nothing else
  in module scope binds, so a function, a class, an import or a second
  binding is never one, and no span is ever added;
* only an admitted entry names a span, and an entry naming both a test
  and a span is refused.

Refs opensoft/openxFactory#656 (CLAIMED 6015602254).

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

Adds R-1 (a)'s span-kind support to the protected-suite allow-list, allowing admitted edits inside pre-existing module-level constants while enforcing strict schema, naming, AST scope, and before/after containment checks.

Flow diagram for validating an admitted span edit

flowchart TD
    A[Allow-list entry] --> B[_check_keys]
    B --> C{admitted entry names span?}
    C -->|No| D[_check_spellings validates test]
    C -->|Yes| E[_check_spellings validates module-level name]
    E --> F[_span_lines parses before text]
    F --> G{Exactly one module-level assignment?}
    G -->|No| H[Reject entry]
    G -->|Yes| I[_inside_the_span checks old text]
    I --> J[_inside_the_span checks new text]
    J --> K{Both contained in span?}
    K -->|No| H
    K -->|Yes| L[Admit edit]
Loading

File-Level Changes

Change Details Files
Extend protected-suite allow-list entries to admit edits within existing module-level constants via a new span target.
  • Document the span semantics and validation rules for admitted entries.
  • Add schema/key validation so admitted entries use either test or span, while respelling entries remain test-based.
  • Validate span names and resolve them to exactly one module-scope assignment, excluding functions, classes, imports, duplicate bindings, and multi-target assignments.
  • Verify span edits are contained within the named constant both before and after the landing, without allowing the span itself to be added.
scripts/protected_suites.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

brettheap and others added 2 commits October 6, 2026 12:43
…ach with its respelling (plan 038)

Eighteen edits to five of 12.5's governed suites, one per test, each
entered in tests/protected_suite_respellings.yaml with its exact old and
new text and chained by blob in the order it applies:

* the 17 that CF-4 (H-2) admits, respelling names the split-opendox carve
  moved before the arc: cmd_gate_* 7 (test_session_gates.py 3,
  test_session_verbs.py 4: the verbs now read from openxdox.cli_gate),
  hosted_index 3 (test_session_confinement.py: serve_projection's), share
  paths 2 (test_doxbench_share.py: each swept module at its one home, the
  record schema at the packaged copy), Group W 1 (test_session_verbs.py:
  openxdox.generator) and Group S2 4 (test_staging_workbench.py: the S5
  spellings of the create and session columns);
* test_staged_scope_adds_cluster_neighbourhood_section, R-1 (a)'s in-test
  entry: the label is the neutral grouping word, since S7 spells labels in
  the registered domain's vocabulary and that probe sets none.

No assertion is weakened: each entry's review says what stays. The file's
header now names the CF-4 respellings and R-1 (a)'s span kind.

Refs opensoft/openxFactory#656 (CLAIMED 6015602254; questions 6016415390).

Lane: openXfactory-3

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
R-1 (a), ruled with plan 038 (opensoft/openxFactory#656 comment
6013547504), names the two route claims of tests/test_staging_workbench.py
among its admitted edits. RULED Q3 (comment 5642758731) moved the session
routes from the pure model into the session column, swb-session.js, the
binding that POSTs them, so each claim is relocated with them and entered
as an admitted edit with its reason:

* test_the_session_surface_added_no_transport_to_the_view_or_the_model:
  each route is one constant of the session column, and neither the view
  nor the model names it; the view reaches the column the gate column
  contributes (the S5 respelling beside it);
* test_session_transport_uses_the_injected_fetcher_spelling: every route
  literal in the transport is its one export const declaration, each
  route declared once; the model arrives as ctx.model (the S5 respelling
  beside it).

The staging suite's entries chain again by blob, now seven.

Refs opensoft/openxFactory#656 (CLAIMED 6015602254; questions 6016415390).

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

Span validation has confinement gaps, lacks tests and manifest entries, and leaves contradictory test documentation.

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

Open (6)
What changed in this PR

Implements plan 038’s protected-suite allow-list updates and repairs governed tests after the openDox/openXdox split.

Changes:

  • Adds module-level span allow-list validation.
  • Records governed test respellings and relocated ownership assertions.
  • Updates tests for relocated CLI, projection, schema, and web modules.
File Description
scripts/​protected_suites.py Adds span validation.
tests/​protected_suite_respellings.yaml Records governed edits.
tests/​test_staging_workbench.py Updates web ownership assertions.
tests/​test_session_verbs.py Respells relocated CLI/generator references.
tests/​test_session_gates.py Respells gate verb references.
tests/​test_session_confinement.py Uses the relocated projection module.
tests/​test_doxbench_share.py Locates split modules and packaged schema.

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

Comment thread scripts/protected_suites.py Outdated
Comment thread scripts/protected_suites.py Outdated
Comment thread scripts/protected_suites.py Outdated
Comment thread tests/protected_suite_respellings.yaml Outdated
Comment thread tests/test_staging_workbench.py
Comment thread tests/test_staging_workbench.py
…'s cases (plan 038)

Brett Heap's ruling at opensoft/openxFactory#656 comment 6016648451
("Widen the spans, served display (Recommended)") widens R-1 (a)'s named
spans to six: _CREATE_HARNESS, _SESSION_HARNESS and _HOSTILE_HARNESS, and
the helpers _run_create, _run_session and _hostile_descriptors. The helpers
copy the model's siblings and hand each harness the SERVED governed display.

* scripts/protected_suites.py: a span may now be one module-level helper
  function as well as one module-level constant. A function's span starts at
  its first decorator, as a test's does. A class, an import, a test or a name
  bound twice is still never a span, and no span is added.
* tests/test_staging_workbench.py, ten entries chained after the seven:
  - the three helpers copy display.js and the siblings the harness imports
    (swb-create.js or swb-session.js, with the helpers.js and dispose.js
    they import), with a package.json that makes node read them as modules,
    and hand the harness /capabilities["display"], by the statement
    serve.build_server() makes under the registered host;
  - the three harnesses install it (setDisplay(readDisplay(...))) before
    anything derives, as the shell does, and import CREATE_ROUTE and
    sessionRoute from the columns RULED Q3 moved them to;
  - three in-test respellings of the CF-4 kind, which T026's measurement
    found outside the seventeen (comment 6016415390 point 2): the gate
    verbs read from openxdox.cli_gate in two tests, and the sync script
    read at its one home on the import path.
* tests/test_protected_suite_check.py: the span kind's cases (T026's Files
  gain it, the holder's answer on the same ruling).
* tests/protected_suite_respellings.yaml: the header names the six spans,
  the helper-function form, and batch Q's --chains for 12.5's call.

T020's registration of tests/test_staging_workbench.py (its
tests/conftest.py and the scan in tests/test_host_plane.py) is not in this
commit: it is made after #39 lands.

Refs opensoft/openxFactory#656 (CLAIMED 6015602254; questions 6016415390;
ruling 6016648451).

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

Span uniqueness misses valid Python binding forms, and chain documentation still contradicts the governing 12.5 ruling.

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

Open (4)
Resolved since last review (3)

Comment thread tests/test_protected_suite_check.py Outdated
…alone on their lines, the route docstrings, --chains for 12.5 (plan 038)

Copilot's reviews at 5fb475f and 856047a, each finding taken:

* scripts/protected_suites.py, a span's one binding is now counted
  FAIL-CLOSED (review comment 4195507077): every binding form, in every
  scope of the module (an assignment, augmented or annotated assignment,
  for or with target, walrus or del; a function or class; an import or a
  star import; an except alias; a match capture; a type parameter; a
  parameter). A name bound twice anywhere is never one span.
* A span holds its lines alone (review comment 4195507191): where another
  module-level statement starts or ends on the span's lines
  (HARNESS = "..."; OTHER = 1), the name is not a span, because an entry's
  whole-line text would change that statement too.
* --chains is F5.2's and, from batch Q, 12.5's (review comment
  4196188614): Brett Heap's ruling at opensoft/openxFactory#656 comment
  6016648451. The module's usage block, the chain paragraph, check()'s
  docstring and the option's help say so, and the no-flag case is renamed
  test_a_chain_is_refused_in_a_call_without_chains, its assertions kept.
* tests/test_staging_workbench.py, two admitted entries chained after the
  seventeen (review comments 4195507364 and 4195507428): the docstrings of
  the two R-1 (a) relocations now place the session routes in
  swb-session.js, as their assertions do.
* tests/test_protected_suite_check.py: the eight binding forms, the shared
  line and its control, two targets and an annotation without a value.

Refs opensoft/openxFactory#656 (CLAIMED 6015602254; ruling 6016648451).

Lane: openXfactory-3

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
brettheap and others added 2 commits October 6, 2026 16:13
…its scan reads a gate command line (plan 038)

Brett Heap's ruling at opensoft/openxFactory#656 comment 6016648451 has
T026 make this edit to T020's two files once #39
landed (c6d15b2):

* tests/conftest.py: test_staging_workbench.py joins GOVERNED_HOST_SUITES.
  It parses its gate-off CLI descriptors with the real parser, and its node
  harnesses run in the display /capabilities serves, which openDox reads
  off the registered host. With no host, every harness helper refuses
  ProfileNotRegistered and the descriptors read invalid choice: 'gate'.
* tests/test_host_plane.py: the scan that holds the list to the governed
  suites also reads a gate verb spelled as a command line (a string that
  opens with gate and the verb), as this suite's descriptors spell it.
  Over the 16 governed suites only test_staging_workbench.py matches it
  and not the argument-vector spelling.

Refs opensoft/openxFactory#656 (CLAIMED 6015602254; ruling 6016648451).

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.

Comment thread tests/test_staging_workbench.py

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 governance-sensitive validator and large chained allow-list require final human validation despite passing CI.

Review effort: Balanced
Findings: None

Resolved since last review (1)

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 schema test bypasses the repository’s mandatory packaged-copy digest verification.

Review effort: Balanced
Findings: 1 Medium severity

Open (1)

Comment thread tests/test_doxbench_share.py Outdated
brettheap and others added 2 commits October 6, 2026 17:45
…fied_bytes (plan 038)

Copilot's finding on #43 at 3226d5c (review comment 4198603899): the
packaged copies' contract is that a copy is read only through
openxdox.contracts.verified_bytes() or verified_path(), each of which
checks its sha256 against copies.yaml before it is used. The CF-4 entry in
tests/test_doxbench_share.py (test_the_action_is_in_the_pinned_schema_enum)
built the copy's path beside gate_console and read it directly, so it could
pass on a modified copy that a runtime consumer would refuse.

The entry's new text now reads the schema with
contracts.verified_bytes("gate-action-record"). Its assertion is unchanged.
The suite's chain is regenerated: entry 32's text, and the suite's final
blob.

Refs opensoft/openxFactory#656 (CLAIMED 6015602254; ruling 6016648451).

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

The governance-sensitive AST validator and large historical blob chain warrant final human review.

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

🔵 Needs a closer look

This draft changes a governance boundary and a large blob-chained allow-list while three entries still await holder approval.

Review effort: Balanced
Findings: None

@brettheap brettheap changed the title DRAFT: T026, U-7: the allow-list for 12.5's governed suites, with R-1 (a)'s span kind (plan 038, H-2) T026, U-7: the allow-list for 12.5's governed suites, with R-1 (a)'s span kind (plan 038, H-2) Oct 6, 2026
@brettheap
brettheap marked this pull request as ready for review October 6, 2026 22:23
@sourcery-ai

sourcery-ai Bot commented Oct 6, 2026

Copy link
Copy Markdown

Sorry @brettheap, your pull request is larger than the review limit of 150,000 diff characters

@brettheap

Copy link
Copy Markdown
Contributor Author

READY at 9c29855

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

Holder, at READY: T026 (U-7), the allow-list for 12.5's governed suites, with R-1 (a)'s span kind. Lane openXfactory-3's READY-CANDIDATE is 6026548899.

  • The three flagged in-test respellings are ALREADY ADMITTED under CF-4 by holder ruling 6020859092:

    • cli_gate in the two gate-off descriptor tests;
    • the sync script's one home on the import path in the notebook-refresh test.

    T026's count moves from the plan's 17 to 20. T002 batch 2 records it.

  • Gates at the head:

    • CLEAN against main f70bc9c3, as this ruleset requires;
    • checks: validate (run 37517336710) and SonarCloud succeed, and Sourcery is skipped;
    • Copilot at 9c29855c raises no findings;
    • 9 threads, 0 unresolved;
    • 0 closing references and 0 keywords.
  • The falsifier: --chains reads "ok: 8 protected edit(s), each entered and holding"; composed, the 16 governed suites go 800/842 → 842/842 with 0 regressed; the lone CI form reads 1146/1142/4.

  • Next: openXdox-code's ruleset is strict. After this lands, each queued PR merges main.

@brettheap
brettheap merged commit 36bbef7 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-06T22:35:47Z, PR #43 → 36bbef7 (opensoft/openXdox-code main; plain gate)

Brett: plan 038 RULED incl. lane openxfactory-4 lands every PR (#656 6013547504); Start release 2 (#656 6001702967)

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
Lane: openXfactory-3

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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 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 to opensoft/openxFactory that referenced this pull request Oct 7, 2026
…e identity response shape and the single-writer chains (plan 038) (#1257)

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

**T002, second bookkeeping batch, plan 038: T094 and the slice entries T095 to T104, the identity response shape, the single-writer chains, and the ticks.** Bookkeeping, so this PR carries no `Arc:` line. It touches no `openspec/changes/` path, so no Rule 6 window applies. It began stacked on #1251's head; #1251 has landed (`1837ea75`) and `main` is merged in (`543f6ce1`), so the diff against `main` is this batch alone.

## From plan 038's `tasks.md`, T002

- **Task**: T002, "Release-2 base, and the re-measure at the ruling" (landed in #1251; this is its second batch of bookkeeping, the brief of the holder's `T002b`).
- **Realizes**: none (bookkeeping; FR-025). **Falsifier**: T002 names none. Its gates are the brief's, quoted under "Review and gates".
- **Ruled / Decisions**: none of its own. Every item is a ruling already posted on `#656`, or a landing already merged, encoded as worded and cited by comment id or merge commit.

## The items

| # | Where | What changed | Record |
|---|---|---|---|
| 1 | tasks.md: T094, T095 to T104, T073 | T094 ticked, with its result quoted: 436 composed reds over 51 files, outside 12.5's set (Base 2; 533 at Base 1). The `T095+` placeholder becomes ten entries, T095 to T104, each with its slice name, Files, After and Falsifier from R2-INV-R9's outline and the holder's rulings, `Lane: 3 (openXfactory-3)`. T073's After and Files name them. The map's means, the rulings (R9-R1 (a), R9-R2 (a), W1 to W7) and the OQ defaults are recorded once, above the entries | map `R2-INV-R9.md`; `6021830607`; claim `6021862323`; rulings `6021830531` |
| 2 | T095, T101, T104, T097 | T095: the two confirmed departures from OQ-R9-1 (`plane_host_for` untouched, an autouse fixture registers the declared host; `DECLARED_HOST_SUITES` guarded structurally). T101: `tests/declared_exclusion.yaml` for ONE entry (reasons `[doc_health]`, note dropped, the `only` list loses the file, count stays 66). T104: `tests/test_binding_stylesheets.py`, the pin 572 to 574. T097: its served-display respelling is R9-R1 (a) applied | `6022291206`; the T097 line is `6023517122` (the holder confirmed the citation, `6026622117`) |
| 3 | plan.md: the single-writer table; tasks.md Files and After lines | W7's chains: `tests/conftest.py` T020, then T021 and T026 in landing order, then T095, T073, T074; `tests/test_host_plane.py` T020, T026, T095; `declared_exclusion.yaml` T101, T073, T074; the four T074 test files T103 then T074; the census T025, T104, T015, T016, T057 (the row splits, so `test_web_boundary.py` keeps its own chain). T015's and T074's After gain T104 and T103 | `6021830531`, `6022291206` |
| 4 | tasks.md: T072, T074, T075 falsifiers | The four folds of #1253, in the arc change's own wording: T072 asserts that ONLY CR, LF and CRLF split rows; T074's seam tests assert that an empty-seam read names the seam AND its registrar; T075's host-wiring test shows a leg declaring only SOME of the four seams refused, and a leg declaring the six and none of the four accepted; T074's protected-suite oracle computes 12.5's set as 12.5 does (`git grep -l -e 'open-pr' -e 'open_pr' -e 'FakePullRequests'`), from the pre-arc and the final tree, the floor of 16 on each, and takes the union | openxFactory#1253 to `fc4fa0ff`; Copilot `5427373153` |
| 5 | spec.md FR-011; tasks.md T041 | FR-011's JSON shape gains `identity`, emitted and bounded as openDox-spec's finding schema bounds it (no `excerpt`, `text`, `content` or `quote` key, no number, strings of at most 200 characters, `{category, entry}` for a pathless finding, `{collided_id}` for a collision). T041's falsifier gains a response-shape test asserting exactly that bound. The coverage row for FR-011 names T041. The serialized-size cap was already T041's at the base (T041's text says so, and T042 "stores what T041 bounds"), so nothing moved | holder `6018624750` |
| 6 | tasks.md ticks | T011, T014, T021, T023, T025, T026 and T060 ticked, T070's tick gains #1253, and T002's own landing is recorded. Each merge commit was verified with `gh pr view <n> -R <repo> --json mergeCommit` (listed below) | the landings |
| 7 | tasks.md: T072 | A Files line (`src/opendox/lines.py`, `tests/test_lines.py`); the Ruled line gains `6023517122`: `head_sha` (a), its own public `head_sha` parity-tested against `serve._head_of`, `serve.py` untouched; the line split (a), `doxbench_defaults.py` untouched. Folding either private helper is recorded as unscheduled and optional, and is not scheduled | holder `6023517122` |
| 8 | contracts/health-finding.md:91 | The sentence reads: a stored `identity` makes a cross-run collision between STORED findings detectable; an exception-only finding is never stored; whether the engine reports it remains T046's concern. Nothing else on the line changed | holder `6018624750`; Copilot `5433292692` |
| 9 | plan.md; tasks.md | Copilot's three items at #1251's head `1eb8e9eb`, accepted by the holder at its landing: (a) the pin and landing-order row for T073 names T005 and T095 to T104 as prerequisites, and `T095+` reads T095 to T104 at the critical path, the lane table and the revision note; (b) the combined `conftest.py` and `test_host_plane.py` row splits, and the cross-lane summary gains T026 (and the other chains of item 3); (c) the same split in T020's, T021's and T026's Files lines | holder replies `4201056969`, `4201057311`, `4201057577`; READY note `6026651962` |

### The ticks, verified

| task | pull request | merge commit | merged |
|---|---|---|---|
| T011 | opensoft/openDox-code#92 | `09b8d44a` | 2026-10-06T19:15:55Z |
| T014 | opensoft/openDox-code#93 | `a7a1c4d3` | 2026-10-06T22:23:06Z |
| T021 | opensoft/openXdox-code#40 | `f70bc9c3` | 2026-10-06T19:07:10Z |
| T023 | opensoft/openXdox-code#42 | `d45a0939` | 2026-10-06T17:45:33Z |
| T025 | opensoft/openDox-code#91 | `84f8ed83` | 2026-10-06T17:33:31Z |
| T026 | opensoft/openXdox-code#43 | `36bbef79` | 2026-10-06T22:35:40Z |
| T060 | opensoft/openDox#20, the tag `dox-v1.2` (tag object `58538ff3`, on `6a9f4902`), opensoft/openDox#21 | `6a9f4902`, `c7f75845` | 17:46:59Z, tag 17:58:45Z, 19:02:39Z |
| T070 | openxFactory#1253 (T070b, added to its tick) | `fc4fa0ff` | 2026-10-06T18:56:42Z |
| T002 | openxFactory#1251 (its own landing) | `1837ea75` | 2026-10-06T22:31:42Z |

- **T021** landed with Copilot r4199337845 an ACCEPTED LIMIT (holder READY note `6023492215`); T021's entry records it.
- **T025's** entry says its 34 nodes are "quoted at T029"; that stays T029's, and the entry says so.
- **T011's** server node is T014's, as its own Falsifier says.

**Skipped, because already present at the base:** T020's and T024's ticks (#1251 item 13) and T004's tick (#1245, `260234b5`), all confirmed with `git show <commit>:<tasks.md> | grep`.

**T071 is NOT ticked.** Brett's word, "Ratify it (Recommended)" (`#656` `6023375303`), is recorded on T071's entry, but the change's ratification-record PR has not landed (no such PR is open or merged on `opensoft/openxFactory` at this writing). The box stays open.

## Readings the holder accepted (`6026622117`), disclosed as the brief requires

- **W7 omits T026 from the openXdox-code chains.** The single-writer table keeps T026 where its own ruling (`6016648451`) puts it, and lane 3's claim (`6021862323`) says T026's PR lands before T095. So the conftest chain reads T020, then T021 and T026 in landing order, then T095, T073, T074; and `test_host_plane.py` reads T020, T026, T095, as W7 gives it with T026 added.
- **R9-R2's last sentence** ("If the re-scoped test runs alone, it leaves the declaration, and the count moves with it, as the declaration's own rules require") is recorded in T100 as worded. `declared_exclusion.yaml` is NOT added to T100's Files: the holder's file list at `6022291206` names it for T101 only.
- **Edits outside tasks.md**, ruled in scope as each item names its place: plan.md (items 3, 9), spec.md FR-011 (item 5) and `contracts/health-finding.md:91` (item 8).

## Other notes on where an item needed a reading

- **Counting.** The task count reads 90 rows: the placeholder `T095+` is replaced by T095 to T104 (81 - 1 + 10).
- **Plan 034's task numbers.** The entries cite plan 034's T103 (#80, in T099's `Host` header) and plan 034's T086 (W2, in the rulings) as "plan 034's", because plan 038 now has its own T103.
- **OQ-R9-3.** The map did not measure the respelled gate-console and wheel-model skips; T097 and T103 say so, and the map's 127 passed and 5 skipped is quoted as the measurement.
- **Counts of slices** are copied in the map's own form (for example "16 nodes, and +5 on R9-R1 (a)"), not summed.
- **T095's chain after T026.** T095's After names T021 and T026, both landed.
- **Not mirrored from the arc's 4.1:** T075's falsifier gains the some-of-four refusal and the six-and-none acceptance, which #1253 folded. The arc's two older refusals (part-way through the seams; at the column call) are not in T075, since #1253 did not add them.
- **T046 and T057** carry no response-shape falsifier for `identity`: item 5 names T041's.

## Review and gates

- **`python3 scripts/validate-openspec-cli-pin.py --all`**, in the clone named `openxFactory`, at `691648c6`: exit 0, `Totals: 113 passed, 1 failed (114 items)`, `0 UNDISPOSITIONED failures`. The one failure is the dispositioned `add-chain-attestation` finding, the same on `main`.
- **openxFactory pytest.** The full local run was stopped at about 44% at a coordinator drain, so it is NOT a result and is not claimed. The repository's own gate, `pytest-suite` in CI at `691648c6`, completed `SUCCESS` (2026-10-06T23:32:38Z to 23:58:56Z).
- **Doc-health**, `scripts/doc-health.py --single-repo . --as-of 2026-10-07`, branch `691648c6` against `origin/main` `0992369a` in the same clone: the two reports are byte-identical (sha256 `ce17aadd6d96e484170e2ec4784e44972e33c7d1a6c65830ea14a1035f2ae976`; `Findings: 31 critical, 26 error, 69 warning, 20 info. New regressions vs previous report: 0.`). So no new error.
- **CI at `691648c6`**: all 14 check runs are `SUCCESS` (rollup `SUCCESS`): `pytest-suite`, `merge-master-approval` (the red inherited from `main` since #1252 was cleared by #1138), `openspec-cli-pin`, `doc-health-py314`, `openxdox-consumer-gate`, `wallet-validation`, `clearing-dispatch-gate`, `signed-execution-chain-gate`, `former-id-arrival-gate`, `release-tag-gate`, `openreposhape-pin`, the two `lane-line` runs and SonarCloud.
- **`main`** has moved to `0992369a` since the last merge (`543f6ce1`). GitHub reports this PR `MERGEABLE`, and the ruleset is not strict, so `main` is not merged in again (a merge would restart the 27-minute gate).

| Copilot at | verdict | findings | answered |
|---|---|---|---|
| `691648c6` (final; review `5435686665`) | Changes recommended | 1 (thread `4201443690`): T041's falsifier cannot verify what T046's `health list --json` and T057's HTTP route emit; the review summary adds that the new T095 to T104 collide with plan 034's task numbers cited unqualified at `spec.md:662`, `spec.md:1222` and `clarify-questions.md:437` | accepted limit (#656 `5988818366`): plan-record accuracy, not a bypass, and the T041 placement is the holder's own instruction (item 5). Answered (`4201487553`) and resolved |

1 thread, 0 unresolved. The holder ruled both points YES at `6027706377` and has them encoded in a THIRD batch, not here: the `identity` falsifier split across T041, T046 and T057, and the plan 034 references qualified. This PR therefore carries T041's wording as the brief gave it.

## Files changed

- `specs/038-opendox-document-tool-self-maintenance/tasks.md`, `plan.md`, `spec.md`
- `specs/038-opendox-document-tool-self-maintenance/contracts/health-finding.md`

## What is NOT done

- T071 stays open (its ratification record has not landed). No box of #1144 is ticked, and no task other than those listed is ticked.
- No scenario or spec delta beyond FR-011's JSON-shape bullet is touched, and nothing is under `openspec/changes/`. `README.md` is untouched.
- The T095 to T104 entries record the slices; none of their work is done here.

🤖 Generated with [Claude Code](https://claude.com/claude-code)


Lane: openxfactory-4 (openXfactory-4-openDox_extraction)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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
… 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
…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