Repository navigation
openDox + openXdox: two open-source layers, an installable app with users, projects and a database; domain descendants pin openXdox (Brett's ruling 2026-09-04) #656
Description
Ruling — Brett Heap, 2026-09-04, in session (lane openxfactory-opendox), recorded verbatim
"we do not have a place to store projects. If i want to start a new project in a new repo, and make some specs, we have no good place to store my projects. I think we need to make this an app that installs and is hosted with a db. we should have users and projects and can expand the feature set."
"openDox is a dead project. it has almost empty repo. I think someone started with idea and stopped after a few days. lets use that name as the core opensource repo. we have two layers of opensource openDox and openXdox. The openXdox is openDox tuned for use with openXfactory. we will make openDox work to just manage documents and ideas. it will keep the integration with git and notebook lm etc and have all tools that help for document management and ideation. then openXdox will integrate with openXfactory."
Earlier in the same sitting: "We then further pin that down to medxDox and CodeXdox for use in those domain factories. If I install MedxFacotry, then I get a medXdox install running in the installed tenand with its own db."
What the ruling settles
- Two open-source layers, two repositories.
opensoft/openDox— the neutral core: a hosted, installable app with its own database; users and projects; document and idea management; git and NotebookLM integration and the document-management/ideation tooling.opensoft/openXdox— openDox tuned for openxFactory: the corpus reader, doc-health and lifecycle integration, gates, the apply lane. Domain descendants (MedxDox,codexDox, …) pin openXdox in the<Domainx><Product>form; a domain-factory install brings its own descendant instance with its own database in the tenant. - The name
openDoxis taken deliberately.docs/openxdox-naming.md(ratified 2026-08-13) rejectedopenDoxas "taken"; Brett has ruled the existing use dead (a near-empty repo abandoned after days) and accepted the name for the core. The record owes an Amendment 3 carried by the change that creates the repository: it must state the collision facts as measured today — a GitHub organizationopendoxexists, and small repos namedopendoxexist including one active 2026 project in the adjacent agent-written-docs space — and Brett's acceptance of them. - A departure from the wallet precedent, ruled.
split-openxwallet-repokept the integration seam inside openxFactory; here the integration layer is its own open-source repository (openXdox). The seam openxFactory keeps is the corpus itself and its governance; what moves to openXdox is the code that reads it.
Why this is filed as an issue
It is the governing object for the lane's claims (Rule 1) and the place the ruling lives outside a chat transcript. The engineering record it supersedes in part: openxdox-install-app-provisioning (staged 2026-08-14, gate met, exit unraised) and codexFactory #89 (standalone migration, open since 2026-08-25) — both fold into the topic this issue commissions.
What the lane does next
- Brainstorm doc(s) under
ideation/brainstorm/capturing the two-layer topology, the app-with-database model (users, projects, documents, ideas), the tenant install story, and the openDox/openXdox boundary — the only place contradiction is legal. - A staging topic under
ideation/staging/(opendox-two-layer-product) with the settled claims from the ruling and the OPEN questions Brett has not yet ruled: what the database is authoritative for versus git; the runtime shape (the Hermes install's FastAPI + Postgres pattern is the in-house precedent); whether a domain install always brings its own instance and database or may point at a shared one; the corpus-adapter boundary that breaks today's two-waydoc_health↔ideation_dashboardimport cycle; sequencing against the five activeideation-dashboardchanges (two with open archive gates); what of the current 80K-LOC dashboard belongs to openDox versus openXdox. - Then an OpenSpec change (contract/boundary changes go through OpenSpec before Speckit) — on the
split-openxwallet-reposhape, adapted for a two-repo extraction that cannot be byte-identical.
Refs: docs/openxdox-naming.md, ideation/staging/openxdox-install-app-provisioning/, openspec/changes/archive/2026-08-28-split-openxwallet-repo/, codexFactory #89, the 2026-09-04 read-only review (lane openxfactory-smalls).
Lane: openxfactory-opendox
Activity
brettheap commentedon Sep 4, 2026
CLAIMED — lane openxfactory-opendox, session 01X7e6BQZysoULChFmDmgG1Z, 2026-09-04T15:19Z, for the brainstorm doc(s) + staging topic
opendox-two-layer-product(ideation only; no contract, code or naming-record edit in this claim — those come with the OpenSpec change). Sibling check: no PR or branch carries opendox/two-layer; codexFactory #89 and staging topicopenxdox-install-app-provisioningare the prior records and are folded, not duplicated. Stale after 4h with no PR.Lane: openxfactory-opendox
brettheap commentedon Sep 4, 2026
RULING Q1 — Brett Heap, 2026-09-04T15:24Z, in session (lane openxfactory-opendox), on the question "what does the openDox database own as the source of truth, versus git?": the database owns identity and coordination; git owns governed artifacts. Users, memberships, projects, the project-to-repository mapping, sessions and unsaved drafts live in the openDox database. Specs, changes, ideation documents and contracts stay in git, read from repositories and written back only through the apply lane. Every existing gate (doc-health, OpenSpec, the PR checks) therefore stays valid; the database is disposable relative to the corpus. Rejected: documents in the database with git as an export; and the hybrid (ideas in the DB until promoted).
Lane: openxfactory-opendox
brettheap commentedon Sep 4, 2026
RULING Q2 — Brett Heap, 2026-09-04T15:31Z, in session (lane openxfactory-opendox), on the runtime shape: reuse the Hermes install pattern — FastAPI + Postgres, deployed the way
xFactory-Hermes-Installis (live on AKS since 2026-07-19), OIDC through the Keycloak broker being adopted in QA; the OpsxFactorydoxworkload set (auth, dashboard, intent-inbox, token-minter) is the deployment shape it grows into. Rejected: bolting a database onto today's stdlibserve.pymonolith; a new full-stack platform.Lane: openxfactory-opendox
brettheap commentedon Sep 4, 2026
RULING Q3 — Brett Heap, 2026-09-04T15:32Z, in session (lane openxfactory-opendox), on instance topology: one instance and one database per tenant, always. Every domain-factory install brings its own descendant instance (
MedxDox,codexDox, …) and its own database inside the tenant, whether Opensoft operates it (the runbook's Case A) or the tenant does (Case B). No cross-tenant data ever shares a store. Rejected: a shared multi-tenant openDox with row-level isolation; a per-tenant default with a pooled option for operator-hosted tenants.Lane: openxfactory-opendox
brettheap commentedon Sep 4, 2026
RULING Q4 — Brett Heap, 2026-09-04T15:34Z, in session (lane openxfactory-opendox), on the seam: openDox defines a corpus-adapter interface; openXdox implements it. openDox declares how documents are listed, read, written back and checked, with no knowledge of OpenSpec or doc-health; openXdox implements that interface over openxFactory's corpus and check families. The two back-imports from
scripts/doc_health/intoideation_dashboard.boundary(derive_possibles.py,ideation_readiness.py) move into a small neutral module both sides depend on. The dependency points ONE way: openXdox depends on openDox, never the reverse. Rejected: openDox pinning doc-health as a library (inverts the layering); openXdox as a tuned copy with no shared interface (divergence, fixes land twice).Lane: openxfactory-opendox
brettheap commentedon Sep 4, 2026
DIRECTION Q5 — Brett Heap, 2026-09-04T15:48Z, in session (lane openxfactory-opendox), on the module split, verbatim:
What this settles (a THREE-layer test, not a two-way split):
MedxDox,LedgerxDox,AdxDox,codexDox) hold the domain-specific mapping.The per-module assignment is therefore design work under this test, carried by the brainstorm set and the staging topic, not ruled here module by module.
Lane: openxfactory-opendox
Encode Brett's four rulings and the Q5 three-layer direction: the dom…
Take both Copilot findings: [sic] on the transcript's own spellings, …
Stage the openDox/openXdox two-layer product: the brainstorm packet, …
brettheap commentedon Sep 4, 2026
RULING C1 — Brett Heap, 2026-09-04T17:46Z, in session (lane openxfactory-opendox), on the recorded conflict "which openDox is dead": the collision is accepted knowingly; the repository is
opensoft/openDox. Amendment 3 todocs/openxdox-naming.mdrecords the measured facts — six GitHub repositories carry the name, and the GitHub organizationopendoxbelongs to an unrelated Amazon-analytics project — and Brett's acceptance of them; no claim is made on the organization name; the brand lives underopensoft. Which specific abandoned repository prompted the word "dead" is immaterial to the record. Rejected: citing a specific dead repo as the basis; reconsidering the name (theopenXnotesfallback stays documented, unused).Lane: openxfactory-opendox
brettheap commentedon Sep 4, 2026
RULING C2 — Brett Heap, 2026-09-04T17:47Z, in session (lane openxfactory-opendox), on the recorded conflict "what is openXdox": openXdox is the domain-mapping core, parameterized. It holds what every domain factory shares — typed artifact kinds, a governed lifecycle engine (statuses, gates, roles, evidence) and the dispatch/apply lane — parameterized by a domain profile that a descendant supplies. Engineering vocabulary ("requirement", "OpenSpec change", the doc-health check families) belongs to the engineering descendant
codexDox, or stays in openxFactory as its own adapter over the corpus-adapter interface; a clinician usingMedxDoxnever sees the word "requirement". This resolves the founding words "openDox tuned for openxFactory" as: tuned for the xFactory FAMILY's way of mapping domains, not for openxFactory's engineering corpus specifically. Rejected: openXdox as today's dashboard minus the pull-ups (every descendant would inherit engineering vocabulary); one repo with two packages (a boundary that is only a package line).Lane: openxfactory-opendox
brettheap commentedon Sep 4, 2026
RULING C3 — Brett Heap, 2026-09-04T17:48Z, in session (lane openxfactory-opendox), on the recorded conflict "the standalone student under Q1": standalone openDox creates and manages a plain local git repository per project. Documents are always git-backed; commits are the write path; a remote can be attached later. Q1 holds unchanged (the database never holds documents), and moving a student or lab-assistant project into a governed factory is a push, not a migration. Rejected: a loose-documents mode with the database holding content until a repository is attached; requiring a repository before the first save.
Lane: openxfactory-opendox
brettheap commentedon Sep 4, 2026
RULING Q6 — Brett Heap, 2026-09-04T17:49Z, in session (lane openxfactory-opendox), on sequencing against the five active
ideation-dashboardchanges: freeze the dashboard now and carve immediately. The five active changes (add-composed-view-authoring,add-doxchat-model-intake,add-lens-document-selection,add-nightly-dashboard-refresh,retire-doxbench-chat-turn-v1) stop where they stand in openxFactory; their live deltas and open tasks (20 across the two with open archive gates) are RE-HOMED into the extraction change and the new repositories as part of the carve. No new dashboard change opens in openxFactory. The lane's recommendation (let the five finish in place, carve after the two gated ones archive) was put and NOT taken; the cost accepted is re-homing 20 open tasks and the never-green nightly-refresh lane mid-flight.Lane: openxfactory-opendox
brettheap commentedon Sep 4, 2026
RULING Q7 — Brett Heap, 2026-09-04T17:51Z, in session (lane openxfactory-opendox), on ownership, visibility and license:
opensoftowns both repositories; both are PUBLIC from day one under Apache-2.0, matching the three public product repos (openChart,openPractice,openRepoShape). Descendants (MedxDox,codexDox, …) follow their domain repositories' visibility. Rejected: openXdox private until the descendants prove the boundary; MIT or AGPL-3.0.All questions the staging topic carried are now ruled: Q1–Q7 and conflicts C1–C3. The topic's exit path (an OpenSpec change on the
split-openxwallet-reposhape, adapted to a two-repo, non-byte-identical extraction that also re-homes the five activeideation-dashboardchanges per Q6) is unblocked.CLAIMED — lane openxfactory-opendox, session 01TF54UA1RBYrVC8E677z6zm, 2026-09-04T17:51Z, for (a) recording these dispositions in the staging fragment + INDEX row (doc slice, now) and (b) authoring the OpenSpec change that exits the topic (proposal/design/tasks/deltas; Amendment 3 to the naming record rides with it). No repository is created and no code moves in (a) or (b); creation and the carve are that change's realization after ratification. Stale after 4h with no PR.
Lane: openxfactory-opendox
1044 remaining items
brettheap commentedon Oct 9, 2026
Lane: openxfactory-4 (openXfactory-4-openDox_extraction)
HOLDER RULINGS — lane openxfactory-4, plan 038, 2026-10-09T01:10:20Z: lane 3's REVIEW-W1 T041 NAMES points (b) to (e), at
f9cc4b0c(lane3-to-lane4.log, 01:09:45Z)check_findingadmits any category name for a pathless finding. CLOSE the set. A pathless finding'sidentity.categorymust be an engine category,identity-collisionoruncited-disappearance's original kind under option (D) (6069024023item 2), which is itself one of those. Packs never raise pathless findings: T045 refuses an empty-path pack finding (6072086385item 3).entryis bounded at 200 characters, not 40. ACCEPTED as written. A per-entry category's entry is also itspack_id, so the 40-character name rule already binds it throughmake_finding. Onlyentry-refusedcarries a raw, possibly malformed entry, and 200 is its bound.ENGINE_FIELDSmixes the ignoredidwith the refused fields. SPLIT it by behaviour into two named sets, each with its rule in the docstring: a pack'sidis ignored, andpack_id,pack_versionandbaseline_classare the engine's. Behaviour does not change; this is a clarity fix.no-sandboxaccepts an entry, and per-entry raising is unruled.no-sandboxtakesentry "", and a non-empty entry is REFUSED, as formanifest-refusedanddispositions-refused(lane 3's MINOR athealth_contract.py:865). A run raises ONE such finding, never one per entry:data-model.md:382says "ONE install-level finding … says why" (R2Q16 (a)). Only the per-entry categories andentry-refusedtake a non-empty entry.T041 applies (b), (d) and (e) with lane 3's two MINORs and two NITs in its next push, before DRAFT-CLEAN, each with a test row and a killed mutant where it adds a guard.
brettheap commentedon Oct 9, 2026
Lane: openxfactory-4 (openXfactory-4-openDox_extraction)
CLAIMED — lane openxfactory-4, plan 038 T002e (the fifth bookkeeping batch), opensoft/openxFactory, 2026-10-09T02:18:51Z
specs/038-opendox-document-tool-self-maintenance/only. Noopenspec/changes/path, and noArc:line. It encodes, with no new decision:a020b34a(T071) and T002d: plan 038's fourth bookkeeping batch, ticks T012 and T013, the two early-wave words, and the holder's wave-1 and wave-2 rulings (plan 038) #1279 →e514f9e0(batch 4);6072086385item 4, the NUL seam (T041, T042, T044 and the finding contract);6072197564, NAMES (b) to (e): the closed pathless category set, the 200-characterentrybound, andno-sandboxtakingentry ""once per run. (d) is a code-only clarity fix and is recorded only if the plan names the field set.brettheap commentedon Oct 9, 2026
Lane: openxfactory-4 (openXfactory-4-openDox_extraction)
HOLDER RULING — lane openxfactory-4, plan 038, 2026-10-09T02:34:27Z: the pathless
entryrule, refining6072197564(e) on lane 3's REVIEW-W1 T041 MINOR athealth_contract.py:693(c620dacd)A pathless identity's
entryis fixed by its category, exactly as the engine builds it:ENTRY_CATEGORIES:fetch-failed,digest-mismatch,declaration-refused,pack-crashed,pack-timed-out,pack-bound-hit,pack-output-refused) REQUIRE a non-empty entry, the valid entry's id (6072086385item 1).no-sandbox,manifest-refused,dispositions-refused) andidentity-collisionREQUIREentry "".disappearance_identity()always gives a collision original the entry"".entry-refusedtakes either: the refused entry as written, bounded at 200 characters, or""when it has none.check_findingrefuses any other form, so it admits exactly whatengine_findingbuilds. T041 adds one test row per case, before its push.brettheap commentedon Oct 9, 2026
Lane: openxfactory-4 (openXfactory-4-openDox_extraction)
RECORD — Brett Heap's word on #99 (T042), by interactive multiple choice, 2026-10-09T02:44:18Z
Brett said "land #99 when green". The holder then asked how that fits his earlier word
6035546708("Write now, land after T027") and plan decision N-6 (a), and Brett chose "Green now, land after T027 (Recommended)":mainand gets new reviews at its final head before READY.Not chosen: landing #99 ahead of T027, or landing it while T017 and T027 exclude it by commit.
brettheap commentedon Oct 9, 2026
Lane: openxfactory-4 (openXfactory-4-openDox_extraction)
RECORD — Brett Heap's word on #97 (T041), 2026-10-09T02:46:40Z
Brett said "land #97 when green". #97 is T041, in phase 5's wave 1, under the same order as #99 (
6035546708, N-6 (a)). The holder applies Brett's choice for #99 (6073187085, "Green now, land after T027 (Recommended)") to #97 likewise:health_contract.py:693(ruled in6073087924), the full suite, CI and SonarCloud, Copilot and Codex at the head, 0 threads, and the live body.mainand gets new reviews at its final head, and the holder lands it.If Brett wants #97 ahead of T027, his word changes this.
brettheap commentedon Oct 9, 2026
Lane: openxfactory-4 (openXfactory-4-openDox_extraction)
HOLDER RULING — lane openxfactory-4, plan 038, 2026-10-09T13:28:47Z: lane openxfactory-4's PR opensoft/openDox-code#99 (T042), Copilot's "previously missed" item at
fbf9e77e(review5465167781): a cyclic value makes_carries_nul(src/opendox/runtime/health_store.py:149) raiseRecursionErrorinstead of the store'sRefusedErrorFIX-NOW under
5988818366, not an accepted limit. The walk regressed a refusal the store already had.json.dumps, with its defaultcheck_circular, raisesValueError: Circular reference detected, and_json_textturns that intoRefusedError._refuse_nul, which6072086385item 4 put in front of the dump, recurses without bound. A one-line payload (d = {}; d["k"] = d) now escapes asRecursionError, which is not the store's documented refusal.6073187085).The required behaviour.
RecursionErrornever escapes the store. A cyclic value in any field the store accepts (identity, evidence, locator,pack_pins, sandbox) is aRefusedErrorthat names the field, raised before any statement and quoting nothing. So is a value nested deeper than the walk can follow. T042 chooses the means: a cycle-safe walk, the dump before the walk, or translatingRecursionErrorat that one shared boundary.Gates, before T042's DRAFT-CLEAN:
6073187085).The body records the fix, citing this comment. It does not go under Stated limits.
brettheap commentedon Oct 9, 2026
Lane: openxfactory-4 (openXfactory-4-openDox_extraction)
HOLDER RULING — lane openxfactory-4, plan 038, 2026-10-09T13:40:55Z: lane 3's REVIEW-W1 T041 (EARLY) MINOR at
src/opendox/health_contract.py:706-707(_entry_breaks_form, the per-entry branch), at #974cc065adFIX (lane 3's first option).
6073087924saidcheck_findingadmits exactly whatengine_findingbuilds. That stands as the rule, and the code is brought to it:ENTRY_CATEGORIES),check_findingrefuses an entry that is empty, the reservedopendox(contracts/health-packs-manifest.md:24), or not a 1-to-40-character name (the pack-id name rule). One test row each.6072197564(c) assumed the 40-character rule binds throughmake_finding. It does not for a re-raise, whosepack_idisopendox. For a per-entry category's entry, the rule binds through this check.T041 applies it before its DRAFT-CLEAN. PHASE B branches from the head that line names.
brettheap commentedon Oct 9, 2026
Lane: openxfactory-4 (openXfactory-4-openDox_extraction)
HOLDER RULINGS — lane openxfactory-4, plan 038, 2026-10-09T17:40:36Z: four items owed since the 13:45Z pause
1.
refused-patchjoinsENGINE_KINDS(lane 3's 13:40:37Z ask, from T049's prep at #974cc065ad). T041 adds it (T049's recommendation).refused_patchandreason" (T049's entry; data-model.md:209,:223,:369), and only the engine raises it. OutsideENGINE_KINDS, T045 would let a pack declare the kind and forge engine-style refusals.uncited-disappearance, not a category. So it stays out ofCATEGORIESand the pathless set (6072197564(b)).2. T015's two limits (lane 3's R1-T015 EARLY line at #96
e7ee97b8). Both are ACCEPTED LIMITS under5988818366, stated in #96's Stated limits. No push.ssh://token@hostand the scp-liketoken@host:pathkeep the token. That slot is the ssh login name, whichredact_destinationspares by design, as the neutral port does. Redacting it would erase the routinegit@hostdisplay, and a token there is the host port's own misconfiguration (operator-only).3.
pathand U+0000 (Codex P14230671177on #1283).6072086385item 4's principle, "every string a finding carries", coverspath. T041 already refuses it:_pathrefuses C0 and C1 controls and lone surrogates, as the pinned schema does.contracts/health-finding.md'spathrow and its bounds state that bound. T002e encodes it.pathif it lacks one.pathamong them), asRefusedErrornaming the column, before any statement, never a driverDataError. This is the same defence in depth as_json_text. One test row (U+0000 inpath) and its mutant.4.
entry-refused's raw entry versus the pinned schema (Copilot4230698859on #1283). The pinned schema wins. "The refused entry as written" (6072197564(c),6073087924) holds only where the entry conforms.identity.entryforentry-refusedis:""when the entry has no id;[a-z0-9-]+of at most 200 characters;sha256-followed by the SHA-256 hex of the id's canonical text. This covers a malformed or over-long id, a non-string id, and one carrying U+0000. It is never truncated and never a schema exception.This is
6069507373item 7's rule (a string over the bound becomes its SHA-256 hex) in the only form a closed{category, entry}identity allows.check_findingalready admits the form.entry-refusedrow.brettheap commentedon Oct 9, 2026
Lane: openxfactory-4 (openXfactory-4-openDox_extraction)
HOLDER RULING — lane openxfactory-4, plan 038, 2026-10-09T20:12:58Z: item 5, the exact canonical text behind
entry-refused's digest form (6086098003item 4). Copilot thread4232974858on #1283 at1d413693asks for it in the contract now. It is ruled here, so T002e encodes it and T041 builds it to the letter.json.dumps(id, sort_keys=True, separators=(",", ":"), ensure_ascii=True, allow_nan=True, default=str).str().repr(), so the rule is total and never raises.identity.entryissha256-followed by the lowercase hexadecimal SHA-256 of that text's ASCII bytes: 71 characters, inside the schema's[a-z0-9-]pattern and the 200-character bound.""for an entry with no id, and the id as written when it is a string matching[a-z0-9-]+of at most 200 characters.repr()fallback), beside item 4's rows.brettheap commentedon Oct 9, 2026
Lane: openxfactory-4 (openXfactory-4-openDox_extraction)
HOLDER RULING — lane openxfactory-4, plan 038, 2026-10-09T20:29:32Z: item 6, Copilot's three items on #1283 at
163ed13cabout60884846434234200786(date versus string): ACCEPTED LIMIT under5988818366.default=str, a YAML date or timestamp id digests like a malformed string id that spells itsstr(). A conforming string such as2026-10-09is kept as written, so it never collides.identity-collision.entry-refusedrow.4234200849(a non-ASCIIrepr()): FIX. The fallback isascii(id), Python'srepr()with every non-ASCII character escaped, so the canonical text is always ASCII and the digest never raises.ascii(id)in place ofrepr(id).6082100803not encoded: ENCODE it in the same push. ForENTRY_CATEGORIES,check_findingrefuses an entry that is empty, the reservedopendox, or not a 1-to-40-character name. It corrects6072197564(c)'s premise, because a re-raise'spack_idisopendox.brettheap commentedon Oct 9, 2026
Lane: openxfactory-4 (openXfactory-4-openDox_extraction)
HOLDER RULING — lane openxfactory-4, plan 038, 2026-10-09T21:20:15Z: item 7, Copilot thread
4234624749on #97 at471b7231(T041): non-string mapping keys inrefused_entry()'s canonical textACCEPTED LIMIT under
5988818366, the same class as6088732352(a). No code change.json.dumpsspells a non-string mapping key in its JSON form, so{1: "a"}and{"1": "a"}share a digest, and so doTrue/"true"andNone/"null".identity-collision.entry-refusedlimit is widened (T002e encodes it with item 6): the canonical text is not injective across YAML value types. A date or timestamp and itsstr(), and a non-string mapping key and its JSON spelling, can share a digest.identity-collisionreports any such collision.471b7231once its suite exits 0 andvalidateis green. Copilot's "Changes recommended" at that head stands only for this ruled limit, and Archive add-register-edit-lane: first unattended fulfilment observed #97 is re-reviewed at its final head after T027.brettheap commentedon Oct 9, 2026
Lane: openxfactory-4 (openXfactory-4-openDox_extraction)
HOLDER RULING — lane openxfactory-4, plan 038, 2026-10-09T22:45:14Z: item 8, lane 3's REVIEW-W1 T041 (EARLY) NO-GO at #97
471b7231(health_contract.py:526-532,refused_entry()), refining6088484643and6088732352(b)(a) MAJOR: FIX, as lane 3 proposes.
yaml.safe_loadyields as an integer past Python's 4300-digit limit (id: 0xplus 4000fs, a long0bliteral, the same nested in a list, or inside a mixed-key mapping) makesjson.dumpsraiseValueError, and theascii()fallback raises it again. That breaks "never raises".ValueErroras well asRecursionError. An id that neither spelling can write takes ONE fixed text,<past the int-digit or recursion limit>, which replaces the recursion-only text.identity-collisionreports.sha256-entry and never raising.(b) MINOR: FIX.
default=strspells a YAML!!setid by itsstr(), which follows hash order, so the digest varies withPYTHONHASHSEED, against "deterministic".defaulthook spells asetorfrozensetas the list of its members sorted by each member'sascii()text. Everything else it meets is still spelled bystr().ascii()fallback (inside a mapping JSON cannot spell) keeps Python's order. That is an ACCEPTED LIMIT: at worst that refused entry's finding is raised again each run.PYTHONHASHSEEDvalues, in subprocesses, and gets one entry.Then: T041 applies (a) and (b), red first, with their mutants, and pushes. Its DRAFT-CLEAN waits for lane 3's delta at the new head. T002e encodes item 8 in the contract's
entry-refusedrow (the fixed text, the set clause and both limits).Carried forward for T046 and T057, from lane 3's same line: T041 prints nothing, and pack strings pass through verbatim, with only NUL and surrogates refused. So T046's CLI and
list --json, and T057's HTTP and view, must escape at their print boundary under one trust model, swept with the fixed credential corpus.brettheap commentedon Oct 10, 2026
Lane: openxfactory-4 (openXfactory-4-openDox_extraction)
HOLDER RULING — lane openxfactory-4, plan 038, 2026-10-10T23:32Z: item 9, lane 3's REVIEW-W1 T041 (EARLY) GO at #97
f924db0dwith 2 NITs, refining item 8 (6090537166)NIT 1: FIX, in T041's after-T027 round.
repr()instead ofascii()survives (tests/test_health_contract.py:1394-1395, code atsrc/opendox/health_contract.py:502), because the only set row has ASCII-only members. For the members e-acute andf,repr()sortsffirst andascii()sorts e-acute first, so the digests differ.!!setrow with a non-ASCII member that pins the ruledascii()order, killing that mutant.mainafter T027 and gets new reviews at its final head (6073211034). It does not hold DRAFT-CLEAN atf924db0d, since lane 3 rated it non-blocking. Lane 3's FORMAL GO atf924db0dstands, and its delta at Archive add-register-edit-lane: first unattended fulfilment observed #97's final head covers the row.NIT 2: ACCEPTED LIMIT, with no code change.
PYTHONINTMAXSTRDIGITS=640, an id of0xplus 3000fs takes the fixed text<past the int-digit or recursion limit>, while at the default it is spelled out, so its digest differs between the two interpreters.5988818366).entry-refusedrow, beside item 8's two limits. T041's body names it under its stated limits.Also from lane 3's line: the CI failure on #97 at
471b7231,tests/test_console_token_delivery.py::test_a_server_outlives_no_killed_run, is not from plan 038's T041. The file is byte-identical on #97, onmainc94878beand at the merge base. It came in withmain's32943cb(#84) and is a race in the test: it reads/proc/<pid>/statbut catches onlyFileNotFoundError, so a child reaped mid-read raisesProcessLookupError. The fix (catch both) belongs onmainas a separate follow-on, not in #97. Until then, a red from this node is re-run with this cause cited.brettheap commentedon Oct 11, 2026
Lane: openxfactory-4 (openXfactory-4-openDox_extraction)
HOLDER RULING — lane openxfactory-4, plan 038, 2026-10-11T00:5xZ: item 10, Copilot's two "previously missed" points on T016's #100 at
99c4e079(review of 2026-10-10T23:30:21Z), which T016 proposed as accepted limitsRuled: FIX, in T016's current round (the round that fixes lane 3's MAJOR), not an accepted limit.
landing_refusal()permits the land andland_branch()SUBMITS through the instrument, yet both confirmation issuers ask the human to confirm a merge commit ontomain:confirm_at_terminal()(src/opendox/landing_confirm.py:342, called atcli_branch_actions.py:474);confirmQuestion()(src/opendox/web/views/branch-actions.js:220, T016's new code), on the server-nonce path whereactions.landis also true under an instrument.mainchanged when only a submission happened. It is not a bypass, but it is a simple, reachable correctness defect on a consent surface, so the convergence rule (5988818366) gives no reason to defer it.landing_refusal()uses: a merge commit ontomainon the standalone path, and a submission to the host's instrument on the governed path. Governance-neutral wording is acceptable only if it misdescribes neither path. The capability's binding (branch and head), its single use and the static two-issuer check stay as they are.src/opendox/landing_confirm.pyis admitted into T016's Files for this wording only (the prompt text, plus the parameter that carries the operation kind). Lane 3's scope check reads it against this item.contracts/cli-http-submit-land.md, andtasks.mdT016's Files line) in its next bookkeeping push.brettheap commentedon Oct 11, 2026
Lane: openxfactory-4 (openXfactory-4-openDox_extraction)
HOLDER RULING — lane openxfactory-4, plan 038, 2026-10-11T16:1xZ: item 11, the two observations T016 raised while fixing lane 3's MAJOR (the print-boundary secret leak) on #100
Ruled: both are ACCEPTED LIMITS, recorded in T016's body v4. Neither changes T016's Files.
x-access-token:VALUEin prose unredacted. The redactor removes the credential from anx-access-token:URL, which is the form git and the host actually emit. A barex-access-token:pair in free text is outside the form it recognises. This is the class already accepted for T015 under6086098003item 2, and it is accepted here on the same terms. T016's print-boundary fix stands as written: every Landed field and every refusal code is vetted against its known shape, and a hostile lander carrying the fixed credential corpus is driven through every print path. A later widening of the shared redactor is a follow-on, not part of release 2.submission_objectacceptsstrsubclasses. Only in-process code can construct astrsubclass; JSON parsing, git output and the host's answers all yield exactstr. The lander seam's trust model is hostile DATA from a trusted code path: an injected lander's answers are vetted before they are printed, but its code is opendox's own. A caller able to pass a crafted subclass already runs inside the process and needs no seam. Under the convergence rule (5988818366), an adversary reachable only from inside the process is an accepted limit, not a fix-now.submission_objectis T015's landed code, outside T016's Files, and stays as it is.6086098003item 2, they are recorded in the PR body and nowhere in plan 038's text. Lane 3's delta review at T016's new head reads both against this item.