Skip to content

T062, phase 2's openDox root pin: code → 047bb4fa (plan 034, T090 steps 1–2) - #16

Merged
brettheap merged 1 commit into
mainfrom
t062-phase2-root-pin-047bb4fa
Sep 30, 2026
Merged

brettheap merged 1 commit into
mainfrom
t062-phase2-root-pin-047bb4fa

Conversation

@brettheap

@brettheap brettheap commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Plan 034, task T062, slice P2-D (the openDox root pin, phase 2). The plan is
specs/034-opendox-standalone-operation/tasks.md at opensoft/openxFactory main
39f19145. This PR realizes box 9.5 (part) of the ratified
add-neutral-product-standalone-operability, as T090 steps 1-2.

Arc: neutral-product-standalone-operability

  • Claim: openxFactory#656 comment 5917907106.
  • Landing word: Brett's "land phase 2 when green", as this lane's phase-2 claims quote it (for example #656 comment 5901343950). The holder's lander merges; this writer does not.

What changes -- THE PIN THIS PR DELIVERS IS 047bb4fa394f3e1bf42466062a67ef18e99f8d6a

ONE commit (ef7e83f7, this PR's head) moves exactly two things, as T039 (#13) did:

file before after
code (gitlink) 2d116415159b721b55613fe20a159afc46202d1f 047bb4fa394f3e1bf42466062a67ef18e99f8d6a
contracts/code-pin.yaml commit: 2d116415159b721b55613fe20a159afc46202d1f 047bb4fa394f3e1bf42466062a67ef18e99f8d6a
contracts/code-pin.yaml digests.tree_sha256 322f8105d15cf01582dae8771fce2cc6ca9120e5b6bca2750898e872cb6a26e2 97536dbe7454ac062cc8fd3e34fd15a32eee52213f0292042b243f7e5c699a02

Diffstat: 2 files, 3 insertions, 3 deletions. The spec leg and contracts/spec-pin.yaml are untouched.

No workflow file moves. No .github/workflows/*.yml references the code leg by
@<sha>. Checked here with grep -rnE "openDox-code[^ ]*@[0-9a-f]{40}" .github, which finds
nothing; the only 40-hex uses: pin in the tree is actions/create-github-app-token@fee1f7d6...,
an unrelated action. validate-pins.py's own workflow check (WORKFLOW_REF_RE) raises no finding.

Dependencies (T062's After: line) -- ALL LANDED

T062 is After: T054-T058 (every phase-2 openDox-code landing), T039 (the root pin's single-writer order),
and comes after T053's spec pin and bundle in this root.

task state landed as
T053 (spec pin and bundle, this root) landed openDox-spec#16 -> f7ee3c76; openDox#14 -> 52005213 (tag dox-v1.1, tag object 851a28e9); openDox#15 -> 66758438
T039 (phase 1's root pin, single-writer order) landed openDox#13 -> 663ac683
T054 landed openDox-code#57 -> a691e4e4 (mergedAt 2026-09-30T00:32:48Z)
T057 landed openDox-code#58 -> 8ec08e91 (2026-09-30T15:01:12Z)
T055 landed openDox-code#59 -> fa140875 (2026-09-30T17:12:43Z)
T055 follow-up landed openDox-code#70 -> 75bd8703 (2026-09-30T18:19:46Z)
T056 landed openDox-code#66 -> a23e4224 (2026-09-30T18:28:21Z)
T058 landed openDox-code#68 -> 047bb4fa (mergedAt 2026-09-30T19:07:35Z)

047bb4fa394f3e1bf42466062a67ef18e99f8d6a is openDox-code main's tip now that T058 has landed
(gh api repos/opensoft/openDox-code/commits/main --jq .sha at this PR's opening), and is the ONLY commit
this PR pins. Its first-parent history above the phase-1 pin 2d116415 is exactly nine landings, every
phase-2 openDox-code task: T050 dc3765dd (#53), T051 86647320 (#56), T052 fa8862cc (#54), T054, T057,
T055 with its follow-up, T056 and T058 (git log --first-parent 2d116415..047bb4fa in the code submodule).
2d116415 is an ancestor of 047bb4fa, so this is an ancestor move.

Falsifier

make pins in the openDox root, at this PR's head (ef7e83f7, pinning 047bb4fa):

$ make pins
python3 scripts/validate-pins.py
  ok  spec: gitlink == contracts/spec-pin.yaml commit f7ee3c763b3a
  ok  spec: tree digest recomputes (e81f8530d93e…)
  ok  code: gitlink == contracts/code-pin.yaml commit 047bb4fa394f
  ok  code: tree digest recomputes (97536dbe7454…)
  ok  contracts/shape-pin.yaml: 10 copied shape file(s) match their digests
pins ok

The repo's own make validate (naming + manifest + lockstep pins) also passes at this head, exit 0:

$ make validate
python3 scripts/validate-repository-naming.py --project project.yaml
  openDox                          neutral-product/assembly   also_matches project-leg/assembly
  openDox-spec                     project-leg/spec
  openDox-code                     project-leg/code
python3 scripts/validate-manifest.py
manifest ok: openDox (opendox), 3 legs
python3 scripts/validate-pins.py
  ok  spec: gitlink == contracts/spec-pin.yaml commit f7ee3c763b3a
  ok  spec: tree digest recomputes (e81f8530d93e…)
  ok  code: gitlink == contracts/code-pin.yaml commit 047bb4fa394f
  ok  code: tree digest recomputes (97536dbe7454…)
  ok  contracts/shape-pin.yaml: 10 copied shape file(s) match their digests
pins ok

Before the commit, with the gitlink not yet recorded, the same make pins reported
FINDING pin-gitlink-mismatch (recorded gitlink 2d116415 vs pin file 047bb4fa), exit 2, with the digest
line already ok. That is the lockstep rule working, and is why the gitlink and the pin file are one commit.

The digest, and how it was recomputed independently

97536dbe7454ac062cc8fd3e34fd15a32eee52213f0292042b243f7e5c699a02 is sorted-ls-tree-r-v1 over
047bb4fa394f3e1bf42466062a67ef18e99f8d6a (299 records), the definition in contracts/code-pin.yaml's own
header. Four methods, none of them copied from another, gave the same value:

method result
A. standalone Python that does NOT import repo_shape (split git ls-tree -r -z on NUL, drop empties, sort bytewise, sha256 of each record plus \n), in the code submodule's own object store 97536dbe...9a02, 299 records
B. shell only: git ls-tree -r -z <sha> | tr '\0' '\n' | LC_ALL=C sort | sed '/^$/d' | sha256sum 97536dbe...9a02
C. the repo's own scripts/repo_shape.py::tree_digest(Path('code'), <sha>), the function validate-pins.py recomputes with 97536dbe...9a02
D. the forge's own data: gh api repos/opensoft/openDox-code/git/trees/<sha>?recursive=1 (not truncated), tree rows dropped, mode type sha TAB path rows sorted and hashed, no clone involved 97536dbe...9a02, 299 records

Sanity check on the method first. Before trusting methods A and B on the new commit, they were run on the
commit the repo already trusts, 2d116415159b721b55613fe20a159afc46202d1f, and both reproduced the existing
contracts/code-pin.yaml value 322f8105d15cf01582dae8771fce2cc6ca9120e5b6bca2750898e872cb6a26e2 exactly
(219 records). Only then were they applied to 047bb4fa.

Not this PR

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

🤖 Generated with Claude Code

Summary by Sourcery

Update the openDox root to pin the completed phase-2 code leg at commit 047bb4fa.

Enhancements:

  • Advance the openDox code leg root pin to the landed phase-2 commit and update its recorded tree digest.

Tests:

  • Verify the updated gitlink and code pin remain synchronized and that repository pin and validation checks pass.

…anded; plan 034)

Move the `code` gitlink and `contracts/code-pin.yaml` (`commit:` and
`digests.tree_sha256`) to openDox-code's main, 047bb4fa, which carries
every phase-2 openDox-code landing: T050 (dc3765dd), T051 (86647320),
T052 (fa8862cc), T054 (a691e4e4), T057 (8ec08e91), T055 (fa140875) and
its follow-up #70 (75bd8703), T056 (a23e4224) and T058 (047bb4fa).

This follows T053's spec pin and bundle in this root (openDox#14 ->
5200521, the dox-v1.1 tag, then #15 -> 6675843) and T039 (#13 ->
663ac68). No `.github/workflows/*.yml` references the code leg by
`@<sha>`, so none moves.

Falsifier: `make pins` in the openDox root -- pins ok.

Arc: neutral-product-standalone-operability
Lane: openxfactory-4 (openXfactory-4-openDox_extraction)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings September 30, 2026 19:11
@sourcery-ai

sourcery-ai Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Reviewer's guide (collapsed on small PRs)

Reviewer's Guide

This PR performs the phase-2 openDox root pin update by advancing the code gitlink and synchronized contract pin from 2d116415 to 047bb4fa, updating the verified tree digest, and confirming the lockstep and repository validation checks pass without changing the spec leg or bundle.

Flow diagram for the synchronized openDox code pin update

flowchart LR
    A[openDox code gitlink<br/>2d116415] -->|advance to| B[openDox-code phase-2 landing<br/>047bb4fa]
    C[contracts/code-pin.yaml<br/>commit and tree_sha256] -->|update in lockstep| D[commit: 047bb4fa<br/>tree_sha256: 97536dbe]
    B --> E[make pins]
    D --> E
    E --> F[ pins ok ]
Loading

File-Level Changes

Change Details Files
Advance the openDox code leg to the completed phase-2 openDox-code landing while preserving the repository’s lockstep pin invariant.
  • Update the code submodule gitlink to openDox-code commit 047bb4fa.
  • Update contracts/code-pin.yaml to the same commit and its recomputed tree digest.
  • Leave the spec leg, manifest, workflows, bundle, and changelog unchanged.
code
contracts/code-pin.yaml
Validate that the new code pin is internally consistent and reproducible.
  • Run make pins to verify gitlink/contract synchronization and the 97536dbe tree digest.
  • Run make validate to confirm repository naming, manifest, and pin checks pass.
  • Independently recompute the digest using local Git, the repository validator, and forge tree data.
scripts/validate-pins.py
scripts/repo_shape.py
Makefile

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

@sonarqubecloud

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

🟢 Approval recommended

The gitlink and contract pin are synchronized, satisfy the documented task, and introduce no unresolved issues.

Review effort: Balanced
Findings: None

What changed in this PR

Advances the openDox assembly’s code leg to the completed phase-2 revision required by T062/T090.

Changes:

  • Updates the code gitlink to 047bb4fa.
  • Synchronizes the contract pin and tree digest.
File Description
code Advances the code submodule revision.
contracts/​code-pin.yaml Updates the matching commit and tree digest.

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

@brettheap
brettheap marked this pull request as ready for review September 30, 2026 21:41
@brettheap

Copy link
Copy Markdown
Contributor Author

READY at ef7e83f — Lane: openxfactory-4 (openXfactory-4-openDox_extraction)

@sourcery-ai sourcery-ai Bot 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.

Hey - I've reviewed your changes and they look great!

Sourcery assessment

Approved.


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

@brettheap
brettheap merged commit d509829 into main Sep 30, 2026
4 checks passed
@brettheap

Copy link
Copy Markdown
Contributor Author

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

LANDED — lane openxfactory-4, 2026-09-30T21:42:50Z, PR #16 → d509829 (opensoft/openDox main; plain gate)

Brett: land the phase 1 PRs when green

brettheap added a commit to opensoft/openXdox-code that referenced this pull request Sep 30, 2026
…t (plan 034 T059)

T062 (opensoft/openDox#16) landed as d5098297. It pins openDox-code
047bb4fa394f3e1bf42466062a67ef18e99f8d6a, the head of openDox-code main
after T058 (#68). So this leg's `opendox @` pin moves from 814516b7 (the
head of openDox-code#59) to that commit. The pin crosses, first-parent: T057
(#58, 8ec08e9), T055 (#59, fa14087) and its follow-up (#70, 75bd870), T056
(#66, a23e422) and T058 (#68, 047bb4f). The repository has no lock or
constraints file, so pyproject.toml is the one place the pin lives.

One seam moved. T058 replaced default_projection.VALIDATOR, one stand-in for
all of openDox's own kinds, with VALIDATORS, one validator per own kind. So
tests/test_projection_contributions.py reads VALIDATORS[kind] in its two
cases. That file is T059's own and not a protected suite.

The pin comments were re-measured at 047bb4fa, and each measurement holds:
* ViewBinding still takes exports and styles (dataclasses.fields);
* _back_import_census() returns the same three rows as the ratchet table;
* openDox's NEUTRAL_MODULES is still thirteen, with STILL_REACHING empty;
* the vendored stylesheet helpers and GATE_EXCLUSIVE are byte-unchanged in
  tests/test_binding_stylesheets.py.

The whole suite, alone at the new pin, gives 989 passed, 4 skipped and 1
deselected (CI's command), the triple unchanged.

Lane: openxfactory-4 (openXfactory-4-openDox_extraction)
Arc: neutral-product-standalone-operability
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
brettheap added a commit to opensoft/openXdox-code that referenced this pull request Sep 30, 2026
…e through openDox's seams (5.4a) (plan 034) (#35)

Lane: openxfactory-4 (openXfactory-4-openDox_extraction)
Arc: neutral-product-standalone-operability

**DRAFT. T062 has landed, and this PR is pinned to its commit.**
- **T062** landed as opensoft/openDox#16, merge commit `d5098297`. It pins openDox-code `047bb4fa` (`047bb4fa394f3e1bf42466062a67ef18e99f8d6a`), the head of openDox-code `main` after T058.
- `pyproject.toml`'s `opendox @` pin now names that commit, at `25414afc`. Before that it named `814516b7`, openDox-code#59's head while that PR was a draft.
- The comments that name the pin were each re-measured at `047bb4fa`. They are in `src/openxdox/view_extensions.py`, `tests/test_dependency_direction.py`, `tests/test_gate_loop_probes.py`, `tests/integration/test_assembled_bundle.py` and `validate.yml`.
- **T055** landed as openDox-code#59 (`fa140875`). Between the two pins sit T057 (#58, `8ec08e91`), T055's follow-up (#70, `75bd8703`), T056 (#66, `a23e4224`) and T058 (#68, `047bb4fa`).
- **The holder retargets T061 (openXdox-code#36, stacked on this branch) to `main` before this PR goes READY.** A `--delete-branch` landing would close it.

## What this is

This PR carries plan 034's **T059** (`specs/034-opendox-standalone-operation/tasks.md`, read at openxFactory `main` `91e4685f`), slice P2-K: **5.4a, openXdox contributes its governed generator, registry and source through openDox's seams**. It is the governed half of R1Q10 (a).
- **Realizes**: 5.4a, and 9.5 (step 3, part).
- **Ruled**: R1Q6 (d), R1Q7 (a), `5817152735`; R1Q10 (a), R1Q23 (a), `5850003126`; R1Q26 (a), `5851950767`.
- **After**: T052 (landed, openDox-code#54 → `fa8862cc`), T055 (landed, openDox-code#59 → `fa140875`), T060 (landed, openXdox-code#34 → `c41063d6`), T062 (landed, openDox#16 → `d5098297`), T067 and T007's batches C and I (landed; batch I is openxFactory#1180 → `8421603a`), and T040 (landed, openXdox-code#29), for the ratchet's single-writer order.

openXdox keeps `generator.py`, `snapshot.py`, `snapshot_registry.py`, `completeness.py` and `corpus_root.py`. The new `openxdox.projection_contributions` registers what they provide at openDox's seams:

| seam | openXdox's contribution |
|---|---|
| `opendox.generator_seam` | `GENERATOR`, a `SnapshotGenerator` over `generator.generate_snapshot`, declaring its two inputs (`project_register_source`, `possibles_source`) |
| `projection_seams.registry` | the `snapshot_registry` module |
| `projection_seams.corpus_root` | `CORPUS_ROOT`: `corpus_scan_defect`, `corpus_root_refusal`, `change_rows` and `SCANNED_ROOTS` |
| `projection_seams.writer` | the `snapshot` module (`write_snapshot`) |
| `projection_seams.validators` | `VALIDATOR`, for the three governed kinds (`ideation-dashboard-snapshot`, `-index`, `gate-action-record`). openDox's own kinds keep openDox's validator. |

`generator`, `corpus_root` and `completeness` import openxFactory's `doc_health` at module level, and a lone checkout does not carry it (R1Q6 (d); the direction arc is T008). So the contributions reach those modules late, when they are used. Registering reaches no module that needs `doc_health`, and a subprocess case holds that with `doc_health` blocked.

`register()` is explicit, idempotent, and all or none. A seam's refusal (`GeneratorAlreadyRegistered`, `SeamAlreadyRegistered`) reaches the caller unchanged, after every seam that call wrote has been taken back, in reverse. `is_registered()` and `unregister()` complete the API.

## The holder's rulings on this PR

Each of these is the holder's reading, given on this PR's questions.

1. **Where the contributions register: (c), not (a).**
   - `openxdox.projection_contributions.register()` is an explicit call.
   - `openxdox.domain_profile.register()` calls it too, so openXdox standalone gets its governed projection from its own registration path.
   - `domain_profile.load()` stays free of side effects. Registering from `load()` was option (a), and it was ruled out: it would fire in every process that only reads a profile (the verifiers, the NotebookLM sync, tests).
   - **This revises the holder decision in openDox-code#54's body (2026-09-27).** That decision read: "T059 registers openXdox's generator from openXdox's own profile-registration path, the hook openxFactory already calls. So T064's line stays true, and T064 needs no host line." It rested on a misread. openxFactory's `scripts/opendox_host.register_openxfactory()` never calls `openxdox.domain_profile.register()`: it calls `openxdox.domain_profile.load(PROFILE_PATH)` and registers the composite with openDox alone (`opendox.domain_profile.register(composite)`).
   - **So T064 owes one host line:** `openxdox.projection_contributions.register()` in `register_openxfactory()`. T064's line *"It composes openXdox's profile, so none is expected"* no longer holds. `scripts/opendox_host.py` is on F11.1's host surface, so its guard admits the change.
2. **The home corpus in the test harness: (c).** openDox's `authoring.create_scaffold` asks the registered home corpus which fields it obliges (`scaffold_lead_fields()`, T054). With nothing registered it refuses, as #1144's 4.1a has every seam do:

   > "A process in which no entry point was built and nothing registered anything — an import, a test, a library caller — still refuses with 4.2's ADAPTER_NOT_REGISTERED, so the default is a registration the entry point makes and never a fallback inside the seam."

   That rules out (a), a fallback inside the seam, and (b) would amend a ruled falsifier environment. So `tests/conftest.py` registers the same factory openxFactory's host registers, `corpus_adapter_openxfactory.home_corpus`, found where F5.2's environment composes openxFactory's `scripts/` on `PYTHONPATH`. Where it is absent, it registers nothing, and a lone checkout refuses as 4.1a says.
   - **`tests/conftest.py` is not a protected suite.** It matches neither F5.2's glob (`tests/test_generator.py`, `tests/test_snapshot*.py`, `tests/test_session_snapshot.py`) nor 12.5's computed set (`git grep -l -e open-pr -e open_pr -e FakePullRequests -- 'tests/test_*.py'`). So it has no allow-list entry.
   - **The declared-exclusion accounting is unchanged by it.** Every suite that reaches the home corpus also reaches `doc_health`, so each is already declared. The lone whole suite is green with nothing registered.
   - **The cases are green again**, measured in composition (below). At `e3ef506a` there were 69. At `814516b7` there are 71, the same 69 plus the 2 T054's final head routes through the home corpus. All 71 pass here.
3. **The respelling: accepted** as an R1Q7 (a) entry citing T055's `r4136863569`. The expected call in `tests/test_session_snapshot.py::test_the_session_source_read_is_the_existing_confinement_mechanism` is respelled from `self.source.registry.resolve_source(` to `resolve_source_path(Path(root), rest)`, the route's one entry point, which applies the registered registry's `resolve_within`. The second assertion is unchanged.
4. **The consumer-schema reds: (a).** In F5.2's environment, seven cases are red at BOTH pins because the consumer's schemas are not in a lone tree: `test_generator.py` 4, `test_snapshot_determinism.py` 1 and `test_snapshot_registry.py` 2. They are deselected in T059's F5.2 run with R1Q25 (b)'s reason. A supplementary run with a composed `CONTRACTS_DIR` shows all seven green (below). `test_snapshot_validation_launch.py`'s 5 reds are C3's, so they are T061's.
5. **The exclusion entry: accepted.** T059 removes `tests/test_seam_assembly_beside_gate_and_projection.py` from `tests/declared_exclusion.yaml`, taking it from 67 to 66 files. The file was declared under `doc_health` only because `serve_projection` reached `doc_health` through `snapshot_registry` at import. `snapshot_registry`'s one read of `doc_health` (`pin_sentinels`) now happens where the sentinel is written, so the file passes alone in a lone checkout, and T041's `test_declared_exclusion` would refuse it as declared. **T059 joins the exclusion file's single-writer chain between T044 and T061:** T044 → T059 → T061.
6. **Three pre-arc reds in `tests/test_session_snapshot.py`: (a)**, ruled after this PR opened. They are deselected in T059's F5.2 run with the reason "red at both pins; pre-arc carve residue; not the arc's", and this PR does not edit them. They stay open for F5.2 whole (T061/T063).
   - `test_a_new_serve_process_re_registers_the_session_at_startup`: `opendox.serve` has no `SNAPSHOT_INDEX_ROUTE`. The constant moved to `openxdox.serve_projection` in split-opendox § 2.4 PR-3 (#761).
   - `test_a_session_key_is_validated_against_the_roster_before_url_composition`: the node probe copies only `repo-selector-model.js`, which imports `./display.js` (since openDox-code#21, § 3.4 S7).
   - `test_the_hosted_session_arrival_path_is_recorded_and_not_built`: `_handle_refresh_action` is not on openXdox's serve surface. It moved to openxFactory's `serve_openxfactory_lanes.py` in § 2.4 PR 3.

## What changes

| path | what changes |
|---|---|
| `src/openxdox/projection_contributions.py` | **Created.** The contributions, as above. |
| `src/openxdox/domain_profile.py` | `register()` also registers the contributions, after its own refusals and before the profile is held, so a refused contribution leaves no profile registered. `unregister()`'s docstring says why the contributions stay (below). |
| `src/openxdox/snapshot_registry.py` | Reads `pin_sentinels` only where it writes the sentinel, for an entry with no revision. Seals gaps 1, 2 and 4 below. `index_document` and `compose_aggregate` each take one reading. |
| `src/openxdox/snapshot.py` | Seals gap 3 below. `SnapshotNotWritable` is a `ProjectionSeamError` and a `ValueError`. |
| `scripts/protected_suites.py` | **Created.** Batch C's subtraction (below). |
| `tests/test_projection_contributions.py` | **Created**, 22 cases. |
| `tests/test_governed_registry_and_writer.py` | **Created**, 27 cases, one or more per gap. |
| `tests/test_protected_suite_check.py` | **Created**, 43 cases, over scratch git histories. |
| `tests/test_gate_loop_views.py` | **Protected (12.5).** Batch I's second admitted edit, entered. |
| `tests/test_session_snapshot.py` | **Protected (F5.2).** The respelling, entered. |
| `tests/protected_suite_respellings.yaml` | Entries 2 and 3. The header now says who reads the file and states the named-test condition. |
| `tests/conftest.py` | The home corpus, as ruled (2 above). |
| `tests/test_generated_at_anchor.py` | Patched `snapshot._locate_validator`, which openDox's generate verb no longer reaches. It now patches the registered validator's `locate()`. |
| `tests/test_dependency_direction.py` | The ratchet is lowered for T055's reaches: `cli` and `serve` leave `OPENDOX_BACK_IMPORTS`, and `branch_session` goes from (0, 7) to (0, 2). `DOC_HEALTH_SURFACE` records `snapshot_registry` as (0, 1). |
| `tests/declared_exclusion.yaml`, `tests/test_declared_exclusion.py`, the four seam files | The exclusion entry leaves (5 above), and the prose follows. |
| `pyproject.toml` | The pin: `2d116415` → `047bb4fa`, T062's commit. In the drafts it named `814516b7`, #59's head. |
| `.github/workflows/validate.yml` | Floors raised to CI's reading, with the reason. The prose that named the old pin or the declared file is updated. |
| `src/openxdox/view_extensions.py`, `tests/test_gate_loop_probes.py`, `tests/integration/test_assembled_bundle.py` | Comments that name the pin follow it, each measured at `814516b7` and again at `047bb4fa`, where each claim still holds. `ViewBinding` still has `styles` and `exports`. `web/`, `view_extension.py` and the copied stylesheet helpers are unchanged from `2d116415`. |
| `tests/test_projection_contributions.py` (at `047bb4fa`) | One seam moved in T058: `default_projection.VALIDATOR`, one stand-in for all of openDox's own kinds, became `VALIDATORS`, one validator per own kind. The two cases that name it read `VALIDATORS[kind]`. This file is this PR's own, not a protected suite. |
| `src/openxdox/projection_contributions.py` (at `aa0a2c5d`) | A refused registration gives back each unread default it replaced, as the default it was, instead of leaving the seam empty (Copilot, r4149710491). |

**A kept decision on teardown.** Copilot asked whether `domain_profile.unregister()` should release the contributions. It does not. The contributions belong to the process, not the profile:
- a host registers them without this module too (T064);
- openDox's seams refuse a governed registration once a default has been read.

So a teardown that took them back could not be undone wherever anything was served from a default in between, and a test fixture that takes the profile away for one case could not put it back. `projection_contributions.unregister()` is their teardown. The docstring says so, and a case pins both halves.

## The four gaps

openDox's own defaults sealed each of these in openDox-code#59's review rounds, and #59's body names them as openXdox-code's to seal before T059 registers this leg's mechanisms.

1. **`resolve_within`'s symlink escape (r4125556296).** The hidden-name rule was applied only to the path as the URL spelled it, so `link -> .git` served `/source/link/config`, and `notes.md -> .env` served `.env`. The rule is now applied to the canonical path too. The source root is resolved under the same guard, so a root that is a symlink loop refuses instead of raising (Copilot).
2. **`drop` left a dangling active key.** Dropping the active entry now clears the key. Left set, it also kept every later entry from becoming active.
3. **The writer.**
   - It wrote in place (r4126138808). It now writes an exclusive temporary sibling, keeps the target's permission bits, fsyncs, and moves the sibling over the target in one `os.replace`. A failed move leaves the old snapshot and no sibling.
   - `canonical_json` wrote `NaN` and `Infinity` (r4125900060). A value JSON cannot carry is now refused, with nothing written.
4. **`SnapshotRegistry` read without its lock (r4136863481).** Every read now holds it, and the index and an aggregate's composition are each one reading (Copilot, r4139816732).

## The protected edits, and batch C's wiring

**Both edits are entered in `tests/protected_suite_respellings.yaml`, naming this PR:**

| entry | suite and test | edit | blobs |
|---|---|---|---|
| 2 | `tests/test_gate_loop_views.py::test_the_overlay_changes_four_words_and_the_named_absence_and_nothing_else` | admitted (R1Q26 (a), batch I): the test expects the `values` block's six leaves beside the five it pinned | `a56906c6` → `a5ce00cc`, chained on entry 1 |
| 3 | `tests/test_session_snapshot.py::test_the_session_source_read_is_the_existing_confinement_mechanism` | respelling (R1Q7 (a), T055's r4136863569) | `b5e1be02` → `f78c4452` |

Each is its own commit on this branch (`81a01cd`, `614f79f`), so each entry also holds at its commit.

**`scripts/protected_suites.py` is batch C's subtraction** (RULED R1Q7 (a), `5817152735`). It is the last step of F5.2 and of 12.5's falsifier, in place of the inline intersection each ended with:

```
python3 scripts/protected_suites.py --landings="$(cat "$W/x-arc.txt")" --suites="$(cat "$W/gen-suites.txt")"   # F5.2
python3 scripts/protected_suites.py --landings="$(cat "$W/x-arc.txt")" --suites="$(cat "$W/governed.txt")"     # 12.5
```

- The falsifier still lists the landings and the protected suites itself.
- The script admits a touched suite only where one entry holds at that landing. An entry holds when:
  - the before and after blobs are the entry's;
  - `old` occurs once, and replacing it with `new` gives the after text byte for byte;
  - `old` starts a line, as it ends one;
  - both lie inside the named test.
- Each entry admits one landing. The landings are taken oldest first, so a later landing that repeats a spent entry's edit is refused.
- Renames are not detected, so a protected suite renamed away counts as a deletion.
- It exits 2, subtracting nothing, when its input or the allow-list breaks the rules, or when the checkout does not hold a landing's history.
- The lists are passed as values, never as paths: SonarCloud's first analysis flagged a path read from `argv`, S8707.

## Evidence

Every run here used `LANG=C.UTF-8`, in the foreground.

**AT THE FINAL PIN, `047bb4fa`, head `aa0a2c5d`.**
- **The required check (9.2).** Run `36782773982` reads `triple: selected=993 passed=989 skipped=4 failures=0 errors=0`, which is the same triple as at `814516b7`. So the floors, 993 and 989, already sit on CI's reading at margin 0, and do not move.
- **F5.2 in its environment**, on a simulated squash landing of this head on `main`, with `OPENDOX_CODE` at `047bb4fa`, `OPENXFACTORY` at `91e4685f` and `ARC_BASE` at `e28930bf`, with the same deselects as below:
  - `test_generator` 41 passed, 4 deselected;
  - `test_session_snapshot` 20 passed, 3 deselected;
  - `test_snapshot` 15 passed, 2 deselected;
  - `test_snapshot_determinism` 5 passed, 1 deselected;
  - `test_snapshot_registry` 38 passed, 2 deselected;
  - `test_snapshot_validation_launch` 5 failed, 4 passed. These are T061's, as below.
  - `test_snapshot_validator_home` 12 passed.
  - The arc check admits `tests/test_session_snapshot.py` by entry 3, then prints `ok: 1 protected edit(s)`.
  - 12.5's call admits `tests/test_gate_loop_views.py` by entries 1 and 2, and exits 0.
- **The supplementary run with `CONTRACTS_DIR` composed**, and no both-pins deselect: `test_generator` 45/45, `test_snapshot_determinism` 6/6 and `test_snapshot_registry` 40/40. So the seven reds at both pins are still only the consumer's schemas.
- **Whole suite alone (CI's command):** 989 passed, 4 skipped, 1 deselected.
- **Mutants:** 22 of 22 killed at `aa0a2c5d`. That is the 21 below, plus "a displaced unread default is not given back", which fails 1 case.

The sections below were measured at `4feb8009` with openDox-code at `814516b7`, the drafts' pin, and are kept as they were.

**The required check (9.2), CI's reading.** Run `36741174322` at `4feb8009`:
```
triple: selected=993 passed=989 skipped=4 failures=0 errors=0
pins:   selected>=993 (margin 0) passed>=989 (margin 0) skipped==4
```
`main` `c41063d6` reads 896/892/4. Compared test by test:
- added 98: `test_protected_suite_check` 43, `test_governed_registry_and_writer` 27, `test_projection_contributions` 22, and `test_seam_assembly_beside_gate_and_projection` 6;
- removed 1: `test_declared_exclusion`'s case for the seam-assembly file;
- changed outcome 0, and the same four skips.

The floors are raised to 993 and 989 (T060's 11 and T059's 97), with `EXPECT_SKIPPED` 4, margin zero. Each later case moved them again, in its own commit, from CI's reading of the head before.

**F5.2 in its environment**, as batch G amends it. That is a fresh venv, `pip install ".[test]"`, then `--force-reinstall --no-deps "$OPENDOX_CODE"`, with `PYTHONPATH="$OPENXFACTORY/scripts"`. `OPENDOX_CODE` is openDox-code `814516b7`, `OPENXFACTORY` is openxFactory `91e4685f`, and `ARC_BASE` is `e28930bf`. The deselects are the task's two `test_snapshot.py` schema cases plus rulings 4 and 6.

At this head (`4feb8009`):
```
tests/test_generator.py                   rc=0  41 passed, 4 deselected
tests/test_session_snapshot.py            rc=0  20 passed, 3 deselected
tests/test_snapshot.py                    rc=0  15 passed, 2 deselected
tests/test_snapshot_determinism.py        rc=0  5 passed, 1 deselected
tests/test_snapshot_registry.py           rc=0  38 passed, 2 deselected
tests/test_snapshot_validation_launch.py  rc=1  5 failed, 4 passed        <- C3's, T061's (ruling 4)
tests/test_snapshot_validator_home.py     rc=0  12 passed
landings: 29
admitted: 614f79f tests/test_session_snapshot.py, by entry 3 of tests/protected_suite_respellings.yaml
ok: 1 protected edit(s), each entered and holding
```

The same block, **before**:
- at `main` `c41063d6` with `OPENDOX_CODE` at `814516b7` (the realized openDox, pins only): `test_session_snapshot.py` 20 failed (`SeamNotRegistered`, adapter-not-registered), `test_snapshot_validation_launch.py` 9 failed;
- at `main` with openDox `2d116415` (the old pin): `test_session_snapshot.py` 3 failed (ruling 6's three), `test_snapshot_validation_launch.py` 6 failed.

The five `validation_launch` reds left here are:
- `test_the_default_shaped_launch_validates_from_the_repo_root`
- `test_a_run_dir_beside_a_checkout_still_uses_that_one_first`
- `test_the_dependency_warning_carries_the_pip_remedy_and_clears_the_corpus`
- `test_a_non_conformant_snapshot_still_blocks_and_blames_the_snapshot`
- `test_the_classifier_reads_the_exit_code_not_the_dependency_sentence`

**Supplementary, with a composed `CONTRACTS_DIR`** (ruling 4). The directory holds openXdox-spec `f088b09`'s three schemas, openDox-spec `f7ee3c7`'s three and openxFactory `91e4685f`'s four. The run does not deselect ruling 4's seven:
```
tests/test_generator.py             rc=0  45 passed
tests/test_snapshot_determinism.py  rc=0  6 passed
tests/test_snapshot_registry.py     rc=0  40 passed
```
The rest reads as above, and ruling 6's three stay red.

**12.5's set, and a simulated landing.** Over 12.5's 16 governed suites, the check admits `81a01cd` by entry 2 and `c41063d6` (#34) by entry 1. A squash landing of this branch on `main` was simulated with `git commit-tree HEAD^{tree} -p origin/main`, carrying the `Arc:` line. It is admitted by entry 3 (the F5.2 set) and by entry 2 (the 12.5 set). **With `main`'s allow-list** (entry 1 only), the same landing is refused on both paths:
- `tests/test_gate_loop_views.py`: entry 1's before blob is `7321b04d`, and the suite before the landing is `a56906c6`;
- `tests/test_session_snapshot.py`: "no entry names this suite".

**The 23 protected suites in composition** (openxFactory `scripts/` on `PYTHONPATH`, each suite alone), compared test by test with `main` at the old pin:

| state | red where `main` at the old pin was green | green where it was red |
|---|---|---|
| `main` with openDox `814516b7` (pins only) | 150 (130 `SeamNotRegistered`) | 0 |
| this head | **0** | 3 |
| this head with the home registration removed from `conftest.py` | 71, all adapter-not-registered | 0 |

**Mutants**, at this head. Each ran against its suites, and the file was restored and checked clean after each. All 21 were killed:

| mutant | killed by |
|---|---|
| take-back removed | 3 failed |
| `_holds` always true / reads nothing | 11 / 8 failed |
| `domain_profile.register` without the contributions | 5 failed |
| `resolve_within` without the canonical check | 2 failed |
| `drop` keeps the active key | 2 failed |
| `get` without the lock | 1 failed |
| `allow_nan=True` | 3 failed |
| write in place | 2 failed |
| no mode copy | 1 failed |
| `pin_sentinels` back at module level | collection error, plus the ratchet |
| the check skips each of: the before blob, the after blob, the exact text, occurs-once, old inside the test, new inside the test, input shapes, chaining | 1 to 3 failed each |
| the facet's `values` block loses one governed word | 2 failed (the protected facet and overlay tests) |
| the installed openDox route confines beside `resolve_source_path` | 1 failed (the respelled assertion) |

More checks were each shown red before the commit that added them and green after:
- the one-window index and aggregate;
- the rename;
- `schema_version: 1.0`;
- the single-use entry and input order, plus `edit: []` and a sequence key;
- a source root that is a symlink loop;
- a landing the checkout does not hold (exit 2);
- an `old` text that is the tail of a line;
- a versioned entry indexed without `doc_health`, where a revisionless one still fails on it.

Under a global `commit.gpgsign=true`, `tests/test_protected_suite_check.py` read 16 passed and 19 errors before its fixture disabled signing, and 37 passed after.

## For the tasks that follow

- **T061** (stacked on this branch):
  - F5.2's five `validation_launch` reds above are its to clear;
  - it removes `tests/test_snapshot.py`'s exclusion entry, next in the exclusion file's single-writer chain after this PR;
  - it is next in the `pyproject.toml` pin's single-writer order.
- **T062.** Landed (openDox#16 → `d5098297`). The pin names its commit, `047bb4fa` (see the top).
- **T063.** F5.2 whole runs again there. Ruling 6's three `test_session_snapshot.py` cases stay open for F5.2 whole.
- **T064 (openxFactory):**
  - the one host line, `openxdox.projection_contributions.register()` in `scripts/opendox_host.register_openxfactory()` (ruling 1);
  - `created:` admissions under `openxdox_code` in `docs/opendox-carve-admissions.yaml` for the files this PR creates: `src/openxdox/projection_contributions.py`, `scripts/protected_suites.py`, `tests/test_projection_contributions.py`, `tests/test_governed_registry_and_writer.py` and `tests/test_protected_suite_check.py`. Also `tests/protected_suite_respellings.yaml`, which #34 created.
- **T066.** Unchanged by this PR. openxFactory's facet still has to compose the `values` block. #34's note on a page that fetches no `/capabilities` still stands: this PR serves nothing new there.
- **T086.** The ratchet goes from here to (0, 0). The eleven remaining reaches are T084's.

## Review

- **Copilot.** It reviewed each head from `f0f7f41c` on. Every thread is answered, and every finding in its overviews is either taken or answered in this body. The code findings were taken:
  - `c34c0de6`: one-window registry reads, renames, the schema integer, and unsigned fixtures;
  - `11c02cf4`: single-use entries, malformed keys and edits, and the teardown rule stated;
  - `62a1b0d4`: the root under the guard, and the seam-assembly docstring;
  - `e7860056`: missing history is exit 2;
  - `f70ed5d1`: `write_snapshot`'s refusal order, stated;
  - `9d315537`: `old` starts a line;
  - `a1b75f3a`: `doc_health` read only for the sentinel;
  - `1885aee1`, `f22c73b0`, `0fdd65c1` and `4feb8009`: the floors.

  - `aa0a2c5d`: after the re-pin, Copilot's review at `25414afc` found that a refused registration dropped the unread defaults it had replaced, instead of giving them back. That finding was taken.

  At `4feb8009` its review read *"Changes recommended"*, with **Findings: None**, and gave the gate as its only reason: T055/T062 had not landed and the provisional pin had not been replaced. Both are now done. Its review at `aa0a2c5d` reads *"Needs a closer look"*, with **Findings: None**, and asked for this description to state the final pin and gate, which it now does.

  Two threads named allow-list entries as missing. They were left on `f0f7f41c`, and the entries were added at `619684c9`; the replies quote the check admitting them. The pin thread is this PR's stated gate. There are 0 unresolved threads, including r4149710491 at `25414afc`.
- **SonarCloud.** The first analysis failed the gate on Security Rating C (S8707). It passes from `7815c2da` on, including at `aa0a2c5d`.

🤖 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 5, 2026
…ps 1–2) (#18)

Plan 034, task **T087**, the openDox root pin, phase 3. The plan is
`specs/034-opendox-standalone-operation/tasks.md` at opensoft/openxFactory `main`. This PR realizes box
**9.5 (part)** of the ratified `add-neutral-product-standalone-operability`, as T090 steps 1-2.

Arc: neutral-product-standalone-operability

- **Claim:** openxFactory#656 comment [`5992552627`](opensoft/openxFactory#656 (comment)).
- **Landing word:** the holder's lander merges; this writer does not.

## What changes -- THE PIN THIS PR DELIVERS IS `dede32b4b6f3d0f147d599776f83628c5af8ff3d`

ONE commit (`f451f68c`, this PR's head), made by openRepoShape's `scripts/bump-leg.py` (the tool CONTRIBUTING.md names), moves exactly two things, as T062 (#16) did:

| file | before | after |
|---|---|---|
| `code` (gitlink) | `047bb4fa394f3e1bf42466062a67ef18e99f8d6a` | `dede32b4b6f3d0f147d599776f83628c5af8ff3d` |
| `contracts/code-pin.yaml` `commit:` | `047bb4fa394f3e1bf42466062a67ef18e99f8d6a` | `dede32b4b6f3d0f147d599776f83628c5af8ff3d` |
| `contracts/code-pin.yaml` `digests.tree_sha256` | `97536dbe7454ac062cc8fd3e34fd15a32eee52213f0292042b243f7e5c699a02` | `c2672463b4d8cf834026e2faae23478105d9e98650fcc07d971c19e387e7e0a7` |

Diffstat: 2 files changed, 3 insertions(+), 3 deletions(-). The `spec` leg and `contracts/spec-pin.yaml` are untouched.

**No workflow file moves.** No `.github/workflows/*.yml` references the code leg by `@<sha>`: `bump-leg.py` reported
"no `@<sha>` reference names this leg", and `grep -rnE "openDox-code[^ ]*@[0-9a-f]{40}" .github` finds nothing.

## The commit pinned

`dede32b4b6f3d0f147d599776f83628c5af8ff3d` is the commit openDox-code#79's squash landed: T101's version bump to 0.1.0, the last phase-3 openDox-code
landing that changes the shipped package, right after openDox-code#78's release workflow. T099 publishes exactly the commit this root
pins (RULED `5963162921`), and the release workflow's build job refuses a commit this root does not pin.
`047bb4fa` is an ancestor of `dede32b4`, so this is an ancestor move. Its first-parent history above the phase-2 pin is
22 landings (`git log --first-parent 047bb4fa..dede32b4` in the `code` submodule):

| commit | landing |
|---|---|
| `dede32b` | T099 release step: version 0.1.0 (plan 034) (openDox-code#79) |
| `d59f3f2` | T099, publish openDox to PyPI by trusted publishing (10.3's install line) (plan 034) (openDox-code#78) |
| `651c35f` | T100 follow-on: the trust store's review findings (plan 034) (openDox-code#86) |
| `32943cb` | T104, the console token travels in the opened URL, not /capabilities (plan 034) (openDox-code#84) |
| `38d3350` | T100, 16.3a: a served repository's bindings are trusted per machine (plan 034) (openDox-code#82) |
| `ca9e1bd` | T082, 16.5: every other surface works with no model (plan 034) (openDox-code#76) |
| `c4b55cc` | T102 follow-on: the chat rail reads a thread only with a branch session (plan 034) (openDox-code#85) |
| `0116293` | T102, the staging workbench offers the editors and the chat rail by scope (plan 034) (openDox-code#81) |
| `390e2c2` | T103, every loopback route checks the Host (DNS rebinding) (plan 034) (openDox-code#80) |
| `e49b17c` | T084, 4.3: the last deferred reaches through declared seams; consumer_reach retired (plan 034) (openDox-code#77) |
| `90ac703` | T073, 13.4a — /capabilities gains an install block, read from the serving process's own settings (plan 034) (openDox-code#72) |
| `8e37782` | Broker-path credential hardening (follows T080): a minted token keeps a built-in credential's rules (plan 034) (openDox-code#64) |
| `9197ccd` | T075, 10.2 and 10.2a — an installed entry point serves all 42 bundle files; intent-feed.js is not owed (plan 034) (openDox-code#73) |
| `5e7ab00` | T072, 13.1: the bundled PostgreSQL server, the local install's own child (plan 034) (openDox-code#69) |
| `1130e99` | T080, 16.3: the credential stays a reference, and a raw key is refused (plan 034) (openDox-code#63) |
| `9a49040` | T081, 16.4: "no model configured" is a state, shown before any turn (plan 034) (openDox-code#74) |
| `2fc714d` | T079, 16.2: a model field the provider receives (plan 034) (openDox-code#62) |
| `66ff725` | T070, 13.4-13.6: OPENDOX_INSTALL_MODE and generate-and-open --local (plan 034) (openDox-code#67) |
| `8a98e31` | T078, 16.1: the OpenAI-compatible dialect joins DIALECTS (plan 034) (openDox-code#61) |
| `7ff434d` | T071, 13.2 and 13.3: load_settings refuses a non-PostgreSQL DSN and a collapsed DSN pair (plan 034) (openDox-code#60) |
| `d0d3cee` | T088, the lens's two seed actions are offered only where a binding answers them (R1Q19 (a)) (plan 034) (openDox-code#65) |
| `2680eb5` | T085, the standalone doxBench defaults for T027's seams, and T058's two workbench rules move into opendox.validator (plan 034) (openDox-code#71) |

T087's `After:` line is T074, T077, T083, T084, T085, T088, T100, T101, T062 and T007 batch P. The openDox-code ones
are in the table above. T074, T077 and T083 are runs recorded in openxFactory (openxFactory#1224, openxFactory#1223 and openxFactory#1231). T062 landed as
openDox#16 -> `d5098297`. T007 batch P landed as openxFactory#1230. The chat rail's follow-on (openDox-code#85) and T100's
follow-on (openDox-code#86) are in the table too.

## Falsifier

`make pins` at this PR's head (`f451f68c`):

```
$ make pins
  ok  spec: gitlink == contracts/spec-pin.yaml commit f7ee3c763b3a
  ok  spec: tree digest recomputes (e81f8530d93e…)
  ok  code: gitlink == contracts/code-pin.yaml commit dede32b4b6f3
  ok  code: tree digest recomputes (c2672463b4d8…)
  ok  contracts/shape-pin.yaml: 10 copied shape file(s) match their digests
pins ok
```

`make validate` (naming, manifest, lockstep pins; what CI runs) also passes, exit 0:

```
$ make validate
python3 scripts/validate-repository-naming.py --project project.yaml
  openDox                          neutral-product/assembly   also_matches project-leg/assembly
  openDox-spec                     project-leg/spec
  openDox-code                     project-leg/code
python3 scripts/validate-manifest.py
manifest ok: openDox (opendox), 3 legs
python3 scripts/validate-pins.py
  ok  spec: gitlink == contracts/spec-pin.yaml commit f7ee3c763b3a
  ok  spec: tree digest recomputes (e81f8530d93e…)
  ok  code: gitlink == contracts/code-pin.yaml commit dede32b4b6f3
  ok  code: tree digest recomputes (c2672463b4d8…)
  ok  contracts/shape-pin.yaml: 10 copied shape file(s) match their digests
pins ok
```

Each half-move is refused, measured in a copy of this tree:
- the pin file moved and the gitlink not gives `FINDING pin-gitlink-mismatch`, exit 1;
- the gitlink moved and the pin file not gives the same finding, exit 1;
- an altered `tree_sha256` gives `FINDING pin-digest-mismatch`, exit 1.

A fresh `git clone` of this branch with `git submodule update --init --recursive`, CI's own step, passes the three CI
steps (naming, manifest, pins), each with exit 0.

## The digest, recomputed independently

`c2672463b4d8cf834026e2faae23478105d9e98650fcc07d971c19e387e7e0a7` is `sorted-ls-tree-r-v1` over `dede32b4b6f3d0f147d599776f83628c5af8ff3d` (329 records). Four methods gave the same value:

| method | result |
|---|---|
| A. standalone Python, NOT importing `repo_shape`, in the `code` submodule's own object store | `c2672463...e0a7`, 329 records |
| B. shell only: `git ls-tree -r -z <sha> \| tr '\0' '\n' \| sed '/^$/d' \| LC_ALL=C sort \| sha256sum` | the same |
| C. `bump-leg.py`, which uses `repo_shape.tree_digest`, and `validate-pins.py`'s recompute | the same |
| D. the forge's data: `gh api repos/opensoft/openDox-code/git/trees/<sha>?recursive=1` (not truncated), tree rows dropped, `mode type sha TAB path` sorted and hashed | the same, 329 records |

**Sanity check first.** Method A reproduced the trusted value for `047bb4fa`, `97536dbe7454ac062cc8fd3e34fd15a32eee52213f0292042b243f7e5c699a02` (299 records),
before it was applied to `dede32b4`.

## Not this PR

- `contracts/CHANGELOG.md` is not edited. A code-pin move carries no CHANGELOG line (T062's reason: the `code` leg
  carries no contract bytes).
- No bundle is cut and no tag moves. T099 publishes the PyPI release from this pin; that is T099's.
- The `spec` leg, `contracts/spec-pin.yaml`, `contracts/manifest.yaml` and every workflow file are untouched.
- The consumer side is not this PR's. T086 (openXdox-code#37) moves its `opendox @` pin to this same openDox-code
  commit. T094 (T090 steps 5-6) then moves the openXdox root, and openxFactory's pin pairs and host wiring with it.

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

🤖 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