Skip to content

T100, 16.3a: a served repository's bindings are trusted per machine (plan 034) - #82

Merged
brettheap merged 61 commits into
mainfrom
build/034-p3-t100-binding-trust
Oct 4, 2026
Merged

brettheap merged 61 commits into
mainfrom
build/034-p3-t100-binding-trust

Conversation

@brettheap

@brettheap brettheap commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

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

Arc: neutral-product-standalone-operability

Plan 034 T100. #1144 box 16.3a: a served repository's bindings are trusted per machine. RULED by Brett Heap on 2026-10-02 in openxFactory#656 comment 5962785556, item 2: "Trust per machine (Recommended)". The text this conforms to is T007 batch M (openxFactory#1219, merged as cc775fea): box 16.3a and F16.1's batch M block. What openxFactory registers when it hosts openDox was RULED on 5970369724; that registration is T094's (below).

Phase 3. The base is main. T100's After line (T080, openDox-code#64 and T084) is met: #64 landed as 8e377823, and #77 (T084) as e49b17c3.

Nothing was rebased. The diff against main is T100's 12 files only.

The defect

The entry points read the bindings document of the repository they SERVE (declared_model_port_factory over bindings_path(checkout_root)). A binding written into that file by hand, or arriving with a clone, needed no approval. The adversarial review of 2026-10-02 showed two things:

  • A committed broker_argv of ["/bin/sh", "-c", "id > $PWD/pwned"] ran on the first chat turn.
  • A committed env: reference sent an unrelated secret of the operator's to the file's endpoint as a bearer token (repo_binding_exfil.py).

The first two cases of tests/test_model_binding_trust.py are those two findings, run as the review ran them. On main 8e377823 without this change, they fail for the defect's own reasons:

AssertionError: the endpoint was contacted, and was sent ['Bearer cloud-secret-NOT-A-MODEL-KEY-7d41e9a2c0b85f36']
AssertionError: the repository's program ran: uid=1000(brett) gid=1000(brett) groups=1000(brett),1001(docker-host)

The rule, and where it is enforced

A binding read from the served repository runs a broker, resolves a credential reference (env:, keyring: or a broker's), or contacts its endpoint ONLY after the operator has trusted THAT EXACT binding on THIS machine. The auth kind none is included, because it still sends chat content to the endpoint the file chose.

  • src/opendox/doxbench_trust.py (new, standard library only).

    • The key is (the repository root's resolved path, the binding id, sha256 of the binding's canonical full record). The canonical record is as_record(), every field, sorted, compact, ASCII. So any edit untrusts the binding, and so does the same file under another root.
    • The store is one private file, model-binding-trust.json, in openDox's state directory (OPENDOX_STATE_DIR, through T072, 13.1: the bundled PostgreSQL server, the local install's own child (plan 034) #69's config.state_dir). It holds no credential and is written owner-only, through an exclusive, no-follow temporary file and a rename.
    • Its checks. It is read only once it passes the checks T072, 13.1: the bundled PostgreSQL server, the local install's own child (plan 034) #69's bundle makes of its own tree: no symbolic link, owned by this user, writable by no one else, and every directory above it this user's or root's, sticky where another user can write it. A store that fails a check trusts nothing, and the refusal names it. The store and its lock file are opened without waiting (O_NONBLOCK), so a FIFO in either place is refused by the type check on the opened descriptor rather than waited on.
    • Its size. The store writes nothing larger than it reads (MAX_TRUST_STORE_BYTES). A record that would outgrow it is refused before anything is replaced, so every trust already held stays held.
    • Its lock. Every record holds an exclusive lock on model-binding-trust.lock, beside the store, across its read, its change and its replace. So two processes recording at once keep both trusts, and neither restores a form the other replaced. The lock file gets the same link and permission checks as the store, and is then set to exactly 0600, whatever the umask. Where no lock can be taken, the record is refused by name and nothing is written. Readers take no lock: the replace is atomic.
    • A state directory equal to the served root, or nested under it, is refused naming OPENDOX_STATE_DIR, before anything is written. So is one that cannot be resolved, such as a link loop.
    • A platform without the primitives the store needs has nothing trusted, with a refusal that names the platform and the gaps (unsupported_platform(), following T072, 13.1: the bundled PostgreSQL server, the local install's own child (plan 034) #69's form). The primitives are os.getuid, O_DIRECTORY, O_NOFOLLOW, O_NONBLOCK, fchmod, mkdir with dir_fd, and fcntl.flock. So every verdict reads untrusted rather than raising.
    • A link to nothing on the way to the store, whoever owns it, is refused by name. So is any OSError the store's tree raises that no check named: it becomes a TrustStoreRefused naming the store and the system's short word for it, in record and in verdict.
  • A binding the model catalog refuses is never trusted (unservable_because). Its id or its label is one the catalog cannot list, so no turn could use it. The check builds the same catalog the factory declares (brokered_catalog), and it runs BEFORE any policy is asked:

    • verdict_for refuses such a binding, even under a host policy that trusts every binding or with a store entry recorded earlier. The start declares the refusing port over an empty catalog and never fails.
    • recorded_for refuses such a binding, so add, edit and trust record nothing and write nothing.
    • Its refusals print no trust command, because trust cannot repair it. They name the actual remedy instead (REMEDY_UNSERVABLE): correct the binding with opendox model-binding edit (to keep the id) or remove then add (to change the id). Each of those records trust for the binding it writes.
  • Every answer a policy gives is held to the binding AND the root asked about.

    • verdict_for passes on a verdict for this root that admits exactly this binding, or an untrusted one for exactly this record at this root. Anything else becomes an untrusted verdict for THIS binding at THIS root:

      • a verdict for another binding, trusted or not;
      • another form of this one;
      • a verdict minted for another repository root;
      • a non-verdict;
      • a policy that raised.

      So every refusal names the right binding, root and command, and the per-repository key holds against a host policy too.

    • recorded_for refuses by name (TrustNotRecorded) a policy that declines to record, records another binding or root, or raises. It names what a policy raised, never its words. Only openDox's own store's TrustStoreRefused passes through as written, and only from exactly MachineTrust, not a host's subclass. A host policy's refusal of any class, BindingRefused included, is named by its class alone.

  • doxbench_install. The factory asks verdict_for about the first approved binding (trust_gated_model_port_factory).

    • Trusted: the brokered port, handed the verdict.
    • Untrusted: UntrustedBindingPort. Its catalog lists the binding available: false, every dispatch is refused by name, and the factory writes a notice on stderr naming the id and the command that trusts it.
    • A checkout with no bindings never asks the policy, so it never touches the state directory.
  • In depth, doxbench_provider. These all refuse a binding that no verdict covers:

    • every broker operation (mint, hand_off_credential, revoke, list_references), in _broker_operation, before the argv is built and before either runner's branch;
    • the built-in resolver, after its two route assertions and before its first read;
    • the port's dispatch and catalog.

    A verdict covers only the id and digest it was given for.

  • cli_model_binding.

    • add and edit record trust for the binding they write. They record it FIRST, so a store or a policy that refuses leaves nothing written and nothing printed as "trusted".

    • trust <id> (positional, no --yes) prints what it trusts, then records trust for exactly that record: the broker argv it would run, the endpoint, the auth kind and the credential REFERENCE, never the credential. It resolves no reference, spawns nothing and contacts nothing.

    • The command every refusal, notice and list prints is opendox model-binding trust --repo-root ROOT [--bindings DOCUMENT] ID. It is printed only from operands a POSIX shell reads back exactly (trust_command):

      • an id the catalog accepts: ASCII letters, digits, ., _ and -, beginning with a letter or a digit, so no shell expands it and no option parser reads it as an option;
      • paths quoted by shlex.quote, and only where every character is printable.

      A root that is not printable is never printed: the command names it ., to be run from that repository's root. --bindings appears where the binding was read from a document given by one (list --bindings, or the factory's bindings_path). Run as printed, the command trusts exactly the binding it names.

    • set-credential is refused before its broker spawns when the binding is untrusted, and it leaves the binding untrusted. It re-trusts a TRUSTED binding after rewriting its reference.

    • list reads the bindings document ONCE, and derives everything it prints from that reading. It adds two lines per binding:

      • trust: trusted, or why not and the command that trusts it;
      • console: the one binding a console serving this repository declares, by the factory's own rule. A pending declaration is passed over, an unreadable declarations document declares nothing pending, and the console declares the first of the rest. Where list was given another document, the line says the console does not read it.
    • Every value a repository wrote is printed in a JSON string's form, by list, trust, the refusals and the notice. A newline or \x1b[2J in a field cannot forge or hide a line.

  • serve_workbench.

    • A turn on an untrusted binding is refused model_unavailable with a FIXED sentence (UNTRUSTED_TURN_MESSAGE) saying how to trust it. The catalog's shape is closed, so the reason travels in the refusal, the notice and list. Where the catalog cannot list the binding, the sentence is UNSERVABLE_TURN_MESSAGE instead, which names the remedy and no command that trusts (turn_message_for). Both fit within the released failure envelope's 500-character message bound.
    • The console's model approval answers APPROVAL_NOTICE ("becomes an available catalog entry") only where the registered trust policy admits the binding it approved. That is a governed host's approval, or a binding this machine trusts. Otherwise it answers APPROVED_UNTRUSTED_NOTICE, a fixed sentence saying the binding is not yet trusted and how to trust it. A binding the catalog cannot list is checked first, under any policy and without reading a store, and answers APPROVED_UNSERVABLE_NOTICE, which names the remedy and no command that trusts. Approval asks only a policy that is already registered, so where nothing is registered it registers nothing and reads no store. It reads the registration once, under the seam's lock (registered_verdict_for), so a host that unregisters meanwhile never has the default installed in its place.
    • The console intake follows batch M. Its hand-off runs a broker the served repository's ideation/dashboard/model-declarations.yaml names, and that broker belongs to no binding. So it asks the registered policy its OWN question, intake_verdict_for, which no binding's trust can answer.
      • openDox's strict default (MachineTrust.intake_verdict) always says no, refused by name (intake_refused, reason INTAKE_BROKER_UNTRUSTED, a fixed sentence). That happens before any byte of the body is read, and the body is drained unread.
      • A host admits the intake only through its own intake_verdict, an optional third callable on the seam. A policy without one admits no intake.
      • So a repository that declares a binding with the intake's exact fields, and gets it trusted, gains nothing. No command trusts an intake declaration's broker.
  • The chat rail (web/views/doxbench-chat.js). It has its own visible line, UNTRUSTED_BINDING_REMEDY (Python twin doxbench_trust.UNTRUSTED_BINDING_REMEDY), beside T081, 16.4: "no model configured" is a state, shown before any turn (plan 034) #74's no-model line.

    • It shows when the catalog has answered, is not empty, and offers nothing available, and it is announced once.
    • It names "opendox model-binding list --repo-root <repository>", which shows whether each binding is trusted, and "opendox model-binding trust --repo-root <repository> <id>". It also says that a binding already trusted, which a provider's refusal also leaves unavailable, is unavailable for the reason the console printed when its provider refused.
    • --repo-root is in every quoted command. --repo-root is required by every model-binding verb. Each command a fixed sentence quotes (the refused turn's, the rail's and the approval's) is parsed by the real parser in a test, with its placeholders filled in.
    • T081, 16.4: "no model configured" is a state, shown before any turn (plan 034) #74's no-model line stays hidden, because a model IS configured.
    • The sentence says "where it would connect" rather than naming a credential, because test_doxbench_privacy.py bans that word from both chat modules.
  • Bindings stay committable. The bindings document is unchanged, and no trust is ever read from it.

Whose rule: the neutral default, and the seam

doxbench_trust is a policy seam (register / register_default / current / policy / unregister), with the same window rule as projection_seams: the default is replaceable until it is read, a host over a host is refused, and the same registration again is a no-op. A policy carries verdict and record, and optionally intake_verdict. openDox's strict per-machine store is the NEUTRAL default.

Why the default is registered lazily, which departs from R1Q10 (a)'s entry-point registration (accepted by the holder): the CONSUMERS register it, the first time one asks and only where nothing is registered yet. Those consumers are declared_model_port_factory, the model-binding verbs and the intake hand-off. The console's approval is not one of them: it asks only a policy that is already registered. So this PR adds no hunk to cli.py or serve.py, which stay out of the phase-3 single-writer order. It also fails closed: a bare process is held to the strict default too. A host's registration at process start wins.

How this relates to 4.2's seam tests. Each seam module's tests enumerate and test that module's own seams: tests/test_projection_seams.py, tests/test_doxbench_seams.py (the doxBench validators and rail), and #77's tests/test_column_seams.py (SEAMS = (cs.gate, cs.scope, cs.kickoff, cs.register)). No test enumerates every seam of the package, so none needs this one added. doxbench_trust.current() refuses by naming its own seam and the registration call (TrustPolicyNotRegistered), as 4.2's discipline asks, and tests/test_model_binding_trust.py holds that. tests/test_consumer_reach.py derives its record over the whole package and passes with the new module, which imports no sibling.

What T094 must register

RULED by Brett Heap on openxFactory#656 comment 5970369724, "Governance approval (Recommended)": openxFactory registers its own policy, GovernedBindingTrust, as a sixth seams() entry with an undo. Under it:

  1. A binding whose declaration the governance flow APPROVED is trusted.
  2. A binding a repository declared that is still PENDING is refused.
  3. A binding with NO declaration is trusted: the operator's own, or the console intake's new binding while its broker runs.
  4. Where the declarations document cannot be read, nothing is admitted.
  5. record() writes nothing.
  6. intake_verdict must answer too, as the policy answers for a binding with no declaration. The console intake asks that question and no other, so without it the governed host's intake would be refused. With it, the intake stays as it is today, which the ruling keeps.

_GovernedHostPolicy in tests/test_model_binding_trust.py is that policy as a test-local stand-in. The tests that compose it in process:

  • test_a_governed_host_policy_keeps_the_governed_flow[approved|undeclared]: the factory resolves the brokered port, and a turn reaches the listener, exactly as before this change.
  • test_a_governed_host_policy_keeps_the_console_intake: the governed intake runs its broker.
  • test_a_governed_host_policy_refuses_a_pending_declaration: the pending and unreadable cases, and recorded_for refusing a policy that declines.

In the same checkout, the strict default refuses each of these until trust, and always refuses the intake.

Evidence

Run Tree Result
Red: the final test file with no other change main 8e377823 14 failed, 1 passed (test_the_edits_cover_every_field_of_the_record), 1 xfailed, 56 errors (ImportError: cannot import name 'doxbench_trust'). The two defect cases fail as quoted above.
Red: each Copilot round's new cases, before their fixes the code before each fix Round 1: 8 failed. Round 2: 7 failed. Round 3: 3 failed. Round 4: the 4 new cases failed. One example: the two-process case kept only first-binding.
Red: round 8's cases, before their fix 42c98f9d 2 failed, 1 passed. The store's verdict waited on a FIFO, and the platform check did not name O_NONBLOCK. The lock file's FIFO case passes there too, because Linux opens a FIFO read-write without waiting.
Red: round 7's cases, before their fix 735d0c14 2 failed. Under umask 0777 the second record was refused ("the lock file cannot be opened"). The approval installed MachineTrust after a host's teardown and answered APPROVAL_NOTICE from its store.
Red: round 6's cases, before their fix e05c475c 7 failed, 9 passed. Both sites the thread named told the operator to trust a binding past the catalog's bounds: the approval's availability, and a served turn's message.
Red: round 5's cases and the self-pass's, before their fixes 7012cda3 32 failed, 5 passed. Three hostile repository names made CANARY when sh ran their printed command: a newline, a terminal escape and a non-ASCII character, each followed by $(touch CANARY). The factory raised InvalidCatalogEntryError on a trusted binding past the catalog's bounds, and record raised FileNotFoundError through a link to nothing.
Green: the trust module, with the census, chat-configuration and deploy-shape modules 735d0c14 (the merge's tree, run just before it was committed); the trust module alone again at adb19f1e 336 passed, nothing skipped or xfailed; 135 passed at adb19f1e
Whole suite (tests + tests_runtime), in a venv installed by CI's own command (pip install --only-binary :all: -c constraints-cpython312-linux.txt -e ".[runtime,test]") head adb19f1e 3945 passed, 177 skipped, 0 failed. The 177 are the database-backed runtime cases, which need CI's PostgreSQL service (OPENDOX_TEST_DATABASE_URL). #76 reads the same skips on its own local run.
The 10 local reds of earlier rounds a6c2a844 (the 10 alone), then the head (in the whole suite above) They were this machine's first venv: it was installed without the test extra, so it lacked the local extra's pixeltable-pgserver, and generate-and-open with --local refused ("the local install's PostgreSQL server is not installed"). In the CI-command venv, all 10 pass, with a short TMPDIR (~/.local/state/t1) and with the long one (~/.local/state/t100-tmp) alike. The socket-path length was not the cause here.
Mutants (mutate_t100.py, one textual edit each) head adb19f1e 94 of 94 killed, in one run (mutants-run-17).
openxFactory's existing tests (tests/ideation-dashboard -m "not postgres", in a clone named openxFactory, TMPDIR holding the basetemp) openxFactory main 1f670bc3, openDox code leg at main 8e377823, then at T100 7a04590d 4 failed, 1477 passed both times; the failure sets diff IDENTICAL (all 4 pre-existing). openxFactory injects its own model_port_factory and commits no bindings document, so its governed flow never reaches the gate before T094. The later rounds change only the store, how verdicts are held, and the rail's sentence.

The mutants killed:

  • The digest: each of the ten fields, each by its own hand-edit case.
  • The root key: ignored, or not resolved.
  • The factory gate: by-passed, or accepting a verdict for another binding.
  • The in-depth refusals: the broker operations, the resolver, dispatch and catalog; and a verdict admitting by id alone.
  • The store:
    • accepting a writable file;
    • following a link to its file;
    • skipping the link check or the tree check;
    • being written with mode 0666;
    • following a temporary-file link planted in the race window.
  • The CLI:
    • add and edit recording no trust, or writing before recording;
    • set-credential skipping the gate, or not re-trusting what it rewrote;
    • trust recording nothing or printing nothing;
    • list, the disclosure and the refusals printing raw values;
    • list saying nothing of trust.
  • The intake and the state directory: the intake gate; the state-directory check, skipped or blind to nesting.
  • The default and the notice: the lazy default never registered; a silent factory notice.
  • The rail line: shown beside an available model, for an empty catalog, or where a host offers intake; or never announced.
  • Copilot round 1:
    • recording without the cross-process lock, or never taking it;
    • the lock file opened through a link, or its owner and mode unchecked;
    • a declined record taken as trust;
    • a policy's failure to record escaping;
    • an untrusted verdict for another binding passed through;
    • the intake asking the binding question;
    • the per-machine store admitting the intake.
  • Copilot rounds 2 and 3:
    • the platform check skipped, or forgetting the file lock;
    • an unresolvable state directory escaping;
    • the rail line still claiming list says why;
    • a verdict for another root passed through;
    • a host policy's refusal text passing through, of either class;
    • openDox's own store's refusal sanitized too.
  • Copilot round 4:
    • a host's MachineTrust subclass passing its refusal text;
    • the writer outgrowing the read bound.
    • Round 4's "a dashed id printed where an option is read" is RETIRED with the -- it mutated: no id the catalog accepts begins with -, and no command is printed for one that does.
  • Copilot round 5 and the self-pass:
    • a printed path not shell-quoted;
    • an unprintable path printed in a command;
    • an id the catalog refuses printed in a command;
    • a binding trust cannot repair told to trust;
    • a verdict trusting, or a record recording, a binding the catalog refuses;
    • servability judged by the id alone;
    • list judging a second reading;
    • list's command, the factory's notice and the refusing port each omitting the document read;
    • a link to nothing gone through;
    • a system refusal escaping record or verdict raw;
    • the console line ignoring pending declarations or the document listed, or naming the last approved binding;
    • an approval always saying available, or registering and reading the default;
    • the turn message's list command or the rail's trust command dropping --repo-root.
  • Copilot round 6:
    • an approval of a binding the catalog refuses saying to trust it;
    • a turn on such a binding saying to trust it;
    • a refused turn naming no cause.
  • Copilot round 7:
    • the lock file keeping the umask's mode;
    • an approval reading the seam twice;
    • the registered verdict registering the default.
  • Copilot round 8:
    • the store waiting on a FIFO in its place;
    • the platform check forgetting the nonblocking open.

The F16.1 batch M cases each serve a fresh git init with a fresh OPENDOX_STATE_DIR. They run over three bindings in turn:

  • a broker that writes a marker file;
  • an env: reference whose environment records every name read;
  • a keyring: reference whose stand-in backend records every lookup.

The listener records every request. One test per case of the block.

set-credential on an env: or keyring: binding is refused by the refusal it already had, "names no broker", which also comes before any read or spawn. Naming trust there would point at a command that cannot make the verb work.

Review

Every Copilot finding was accepted and fixed with a case that failed first and its mutant, then answered on its thread and resolved:

  • Round 1, at 1ef4c71d, fixed in 7a04590d:
    • r4173513738: a policy that declines to record;
    • r4173513761: the lock across processes;
    • r4173513782: the intake's own question;
    • r4173513795: a verdict for another binding.
  • Round 2, at 7a04590d, fixed in e4b145d1:
    • r4173876800: a platform without the store's primitives;
    • r4173876823: an unresolvable state directory;
    • r4173876849: what the rail line says list shows.
  • Round 3, at 8270dffc, fixed in e4b145d1:
    • r4174310794: a verdict for another root;
    • review 5402101086's "previously missed" item: a host policy's refusal text.
  • Round 4, at cb691b18, fixed in 24a1c25e:
    • r4174632006: only the exact MachineTrust passes its refusal;
    • r4174632060: the printed trust command runs as printed;
    • r4174632086: the write bound.
  • Round 5, at 7012cda3, fixed in db77c4ea:
    • r4174783197: a printed command a shell reads back exactly;
    • r4174783250: list reads its bindings once;
    • r4174783280: a binding the catalog refuses is never trusted, and never fails the start;
    • r4174783301: a link to nothing, and any unnamed OSError, refused by name.
  • Round 6, at e05c475c, fixed in a6c2a844:
    • r4175203889: a binding the catalog cannot list is told its remedy, at the approval and in a refused turn, and never to trust it.
  • Round 7, at 735d0c14, fixed in 42c98f9d:
    • r4177946237: the lock file is 0600 whatever the umask;
    • r4177946288: the approval reads the trust seam once.
  • Round 8, at 42c98f9d, fixed in adb19f1e:
    • r4178064601: the store and its lock file are opened without waiting on a FIFO.

The adversarial self-pass, in the same commit. It covered two things:

  • Every command printed for a human to paste. That is each refusal, the notice and list, plus each command a fixed sentence quotes. Hostile values in every interpolated operand were run through sh, bash and zsh.
  • The trust-state machine, for whether what is printed, what is stored and what is enforced agree: pending, approved, undeclared, unreadable declarations, and a stale record.

It found three gaps, each now fixed with its case and mutant:

  • list did not say which binding a console declares;
  • an approval said "available" for a binding the strict default still refuses;
  • the fixed sentences quoted model-binding commands without the required --repo-root.

SonarCloud. The quality gate's one failure was python:S5332: the trust disclosure spelled a plain-HTTP URL scheme, and now says "plain HTTP". The two functions over the cognitive-complexity bound (_unsafe_because, _refuse_an_unsafe_tree) are split into named parts, with the same rules. The gate passes from 7a04590d on.

CI's triple

EXPECT_SKIPPED is 11, main's own value. It moved to 14 for three strict-xfail cases, each waiting on a draft and naming it:

It stepped back by one, with its reason in the workflow, as each draft reached this branch (1ef4c71d, 32646871, 78d1e904). All three cases now run and pass. The floors are not moved.

The web census's class-A total is re-derived from the merged tree at every merge of main. It is 18526 at the head: main's 18486 plus this change's 40 lines in views/doxbench-chat.js (1890 to 1930). Round 5 changed that file within one line, so its count holds, and #76 touched no web file. The floors are #76's re-pin (3977 / 3966, T082). T100 moves neither, since it only adds cases. The merge kept both reasons for EXPECT_SKIPPED holding at 11, T082's and T100's. #84 (T104) also moves EXPECT_SKIPPED and class A. Whichever of the two lands second re-derives both values from its own merged tree.

#77, and other notes

Gates

  • pyflakes is clean over the changed modules.
  • tests/test_provider_boundary.py passes. No module outside doxbench_provider spells its banned needles.
  • No closing keyword appears in any commit message on this branch or in this body.

🤖 Generated with Claude Code

brettheap and others added 30 commits September 28, 2026 18:49
`openai-chat-v1` joins doxbench_binding.DIALECTS as the second member,
after `xfactory-prompt-v1`, which stays first. Its request is the
chat-completions grammar (`model`, `messages`, the assembled prompt as
one message in the user role), and its answer is read at
`choices[0].message.content`. Both are spoken by one arm in
doxbench_provider.py alone (`_DIALECT_ARMS`), beside the prompt
grammar's arm, which sends the bytes it always sent. An unknown dialect
is still refused when a binding is declared, and a test holds the arm
table's keys equal to the vocabulary.

Falsifier: F16.1's dialect assertion, `"openai-chat-v1" in b.DIALECTS`.
Ruled: R1Q22 (a), openxFactory#656 comment 5817152735. doxbench_binding.py
is a moved_verbatim row, and editing it needs no declared-edit act.

Drafted ahead of T063 under Brett's phase-3 word ("Only the independent
ones"). It does not land before T063.

Arc: neutral-product-standalone-operability
Lane: openxfactory-4 (openXfactory-4-openDox_extraction)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The binding record gains `model`, the model name the provider receives
as the request's model, in either dialect. It sits after `dialect`, with
the route it belongs to, so BINDING_FIELDS grows from nine to ten, and
still no field can hold a secret. `model-binding add|edit --model` sets
it, and `model-binding list` shows it.

A binding that declares no model keeps the meaning it had: the request
names the catalog handle, the binding's `id`, byte for byte. So `model`
is keyword-only and defaults to None, and a stored record may leave it
out (OPTIONAL_BINDING_FIELDS). Every construction written before the
field existed builds the binding it built, and every nine-field document
reads. The console's intake route in serve_workbench.py builds a binding
without a model, and it keeps working unedited. The catalog handle stays
the binding's id, and `model` is not an argv placeholder.

Falsifier: F16.1's field assertion, `"model" in b.BINDING_FIELDS`.
Ruled: R1Q22 (a), openxFactory#656 comment 5817152735.

Drafted ahead of T063 under Brett's phase-3 word ("Only the independent
ones"). It does not land before T063.

Arc: neutral-product-standalone-operability
Lane: openxfactory-4 (openXfactory-4-openDox_extraction)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…lan 034)

A key inside the endpoint URL is refused when a binding is declared, by
the product's own detector, runtime/local_git_adapter's
carries_a_credential. It runs before the scheme check, so no refusal
repeats the URL. The refusal is one fixed sentence. A key in an extra
field is refused as an unknown key, as it always was.

Each record now has ONE resolver, and credential_source() says which:
- a BROKER, for any other reference, as before;
- the BUILT-IN RESOLVER, for `env:NAME` and `keyring:SERVICE/USERNAME`
  references (R1Q17 (b)). doxbench_provider reads the reference at call
  time, once per request, and keeps nothing: no mint, no ledger event,
  no retry on a 401. Such a record needs no broker, and a broker_argv
  given beside it is refused. That refusal is the plan's fail-closed
  reading (analyze round 2, V2-21), recorded as standing in
  openxFactory#656 comment 5851950767; no answer rules it. The OS
  keyring is read through the `keyring` package, imported at call time.
  It is not a dependency: without it, a keyring reference refuses with a
  fixed sentence. Two fixed diagnostics join (eight to ten);
- NONE, for an endpoint that takes no credential: the auth kind `none`
  (R1Q18 (a)), under which credential_ref and broker_argv are forbidden.
  It joins AUTH_KINDS after api_key and oauth, so AUTH_KINDS[0] is
  unchanged, and its requests carry no authorization header.

The record parses a reference's form, once, for both sides, and never
reads what it names. A stored record may leave out the fields its
resolver forbids or does not need. Each read-back and each removal
states the custody sentence that is true of its resolver. The operator
door takes `--auth-kind none`, an omitted `--credential-ref` and an
empty broker invocation, and `set-credential` refuses a binding no
broker answers without reading its standard input.

Falsifier: F16.1's three refusals and its control, plus tests of the
resolver and of `none`.
Ruled: R1Q22 (a), openxFactory#656 comment 5817152735; R1Q17 (b) and
R1Q18 (a), comment 5850003126.
After: T079, and T007 (batch H), which has NOT landed on openxFactory
main (e369cb25): #1144's 16.3 has no addendum yet. This is built to plan
034's text.

Drafted ahead of T063 under Brett's phase-3 word ("Only the independent
ones"). It does not land before T063.

Arc: neutral-product-standalone-operability
Lane: openxfactory-4 (openXfactory-4-openDox_extraction)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…arCloud)

SonarCloud's analysis of openDox-code#61 flagged five of T078's new test
lines: four `pytest.raises` blocks whose envelope was built inside the
block (S5778), and one composite assertion (S9073). The envelope is now
built before each block and the assertion is split in two. No assertion
changes meaning, and the case count is unchanged.

Arc: neutral-product-standalone-operability
Lane: openxfactory-4 (openXfactory-4-openDox_extraction)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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's overview of openDox-code#62 noted that doxbench_intake.py's
module docstring still called the binding a "closed nine-field record"
and the intake declaration "not a tenth field on it". T079 made the
record ten fields, so both phrases were false.

The paragraph now names each count by its tuple, as it already did for
the catalog entry, and records that the binding grew once, by `model`
(#1144 box 16.2). The declaration is "not a field on it". This is a
docstring only: no code, no test and no behaviour changes.

Arc: neutral-product-standalone-operability
Lane: openxfactory-4 (openXfactory-4-openDox_extraction)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…o T080's

Arc: neutral-product-standalone-operability
Lane: openxfactory-4 (openXfactory-4-openDox_extraction)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…rer can carry

Two remarks in Copilot's overview of openDox-code#63, both measured here.

The endpoint reached the credential detector unbounded. The detector is
quadratic in a parameter name's length, and an endpoint arrives from the
command line or from the console's intake route. The binding now refuses
an endpoint longer than runtime/config.MAX_REMOTE_URL_CHARS before the
detector is asked. That is the product's own URL bound, which the
repository act applies to a remote for the same reason. It is read when
the binding is declared, and the refusal is a fixed sentence that
repeats nothing of the endpoint.

A resolved credential could be one no header carries. The resolver
checked only for CR, LF and NUL. Measured against urllib:
- a character outside latin-1 failed while the header was encoded, as
  DIAG_PROVIDER_UNREACHABLE, which names the wrong party, and the
  UnicodeEncodeError it chained held the whole header, credential
  included;
- any other non-ASCII character, or an embedded space, was sent.
A resolved value must now be non-empty printable ASCII with no
whitespace, or it refuses with DIAG_REFERENCE_UNRESOLVED before any
provider is contacted.

SonarCloud's findings on the same PR:
- built_in_reference_parts returns one shape for both forms, a
  BuiltInReference named tuple (S8495);
- the variable-name pattern reads [A-Za-z_]\w* under re.ASCII (S6353);
- the broad except carries a bare noqa code, with its reason on the line
  above (S7632);
- each pytest.raises block in T080's tests holds one throwing call
  (S5778), and two composite assertions are split (S9073).

Sixteen new cases: the bound (past it, at it, read from config), six
unpresentable values and a printable-ASCII control, a real-socket case
for a value outside latin-1, three unusable keyring answers, and two
non-ASCII variable names. Five mutants of the fix were each killed.

Arc: neutral-product-standalone-operability
Lane: openxfactory-4 (openXfactory-4-openDox_extraction)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…rCloud)

SonarCloud's analysis of openDox-code#63 at f92fca4 flagged one line of
the new real-socket test: it reset the stand-in handler's class-level
record by assignment (S8997). The test now takes monkeypatch, so the
record is restored when the test ends.

The test file's four new non-ASCII literals are written as \u escapes,
so the source shows which code point each case uses. The string values
are unchanged, and the case count is unchanged.

Arc: neutral-product-standalone-operability
Lane: openxfactory-4 (openXfactory-4-openDox_extraction)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… pinned

With `none` in AUTH_KINDS, the console's intake flow offers a subset of
the vocabulary: api_key and oauth, the kinds a broker enrols. A `none`
binding holds no credential, so the flow, which exists to hand one to a
broker, has nothing to collect for it. The operator declares one with
`model-binding add --auth-kind none` instead.

doxbench_intake.auth_kind_disclosure's docstring now says so, and a new
test pins the relationship, in the vocabulary's order. The disclosure's
code is unchanged. The docstring sits outside P3-B's row; the PR body
flags it.

The test named "this_processs_own_environment" is renamed to
"the_serving_process_environment".

Arc: neutral-product-standalone-operability
Lane: openxfactory-4 (openXfactory-4-openDox_extraction)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Brett Heap ruled on the question openDox-code#63 raised, on 2026-09-28,
choosing "Refuse unless loopback (Recommended)"; the lane's holder
relayed the ruling. A credential the built-in resolver reads (an env: or
keyring: reference) is a long-lived key, so it is sent only over
https://, or over http:// to 127.0.0.1, ::1 or localhost. Any other
http:// endpoint is refused before resolution.

- doxbench_binding.is_a_private_route is the one predicate. It matches
  the endpoint as written, case-blind, against https:// or http:// to
  one of LOOPBACK_HOSTS with an optional port. A URL parser's reading is
  not used, because it and the HTTP client read
  "http://evil.example\@localhost/" as two different hosts.
- The record refuses a built-in reference on any other route when it is
  declared, from the command line or a stored record, with one fixed
  sentence, ENDPOINT_NOT_PRIVATE, that repeats nothing of the endpoint.
- The built-in resolver asks the same predicate before it reads
  anything. No declared binding reaches that check, so what does is a
  programming error, raised as an AssertionError with nothing read, as
  the resolver already does for a broker's reference.
- The broker path is left as it is, as the ruling says: a minted token
  may still be declared over plain http:// to any host. The auth kind
  none presents no credential and keeps its route too.

32 new cases, including IPv6 loopback, the lookalike host
"localhost.evil.com" and mixed-case schemes. Seven mutants of the rule
were each killed.

Arc: neutral-product-standalone-operability
Lane: openxfactory-4 (openXfactory-4-openDox_extraction)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…helpers (SonarCloud)

SonarCloud's analysis of openDox-code#63 at 4abc6d4 measured the
binding's __post_init__ at a cognitive complexity of 17, over the 15
allowed (S3776). The endpoint's own checks (the length bound, the key
detector and the scheme, in that order) move into
_require_a_declarable_endpoint, and the loopback rule into
_require_a_private_route. The order of every check is unchanged, and so
is every refusal. The whole suite and the seven mutants of the rule give
the same results as at 4abc6d4.

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's review of openDox-code#63 at 4abc6d4 (high severity): the
default urllib opener follows redirects, and it re-sends every header
but the content ones to the Location. Measured with two loopback
servers: a POST answered 301, 302 or 303 reached the redirect's target
as a GET that still carried "Authorization: Bearer ...". So a private
endpoint could hand a long-lived key to any host and any scheme, which
the loopback ruling of 2026-09-28 forbids.

A request that carries a credential the built-in resolver read now uses
_open_without_redirects. Its _DeclineRedirects handler closes the
redirect's answer unread and declines, and the port answers with a new
fixed diagnostic, DIAG_PROVIDER_REDIRECTED. FIXED_DIAGNOSTICS goes from
ten to eleven. An injected opener is still used as given. The auth kind
none sends no credential, and the broker path keeps the default opener,
as the ruling leaves that path.

Five new cases over real sockets (301, 302, 303, 307 and 308). The
second server hears nothing. Two mutants were each killed: the opener
swap removed, and the redirect declined silently.

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's review of openDox-code#63 at d240fd5 (high severity):
_post_to_provider took the credential as a raw string. At main the
provider-call frame held a MintedToken, whose repr redacts. A
traceback keeps its frames, and an error reporter that records a
frame's locals records them by their repr, so the raw string was one
repr away from a log.

- The credential now travels as _PresentedCredential, whose repr and
  str say nothing, on both paths. The minted token is wrapped at its
  two call sites and the built-in value as it is read. So every frame
  of this module that carries a credential to the provider holds only
  the wrapper, as it held a MintedToken at main.
- A refusal of a request that carried a built-in credential is raised
  afresh, outside every handler, with no cause and no context. Measured
  with a refused connection: urllib's own frames (do_open.headers,
  _send_request.headers, send.data and others) hold the bearer in
  their locals, and a chained cause keeps those frames.
- A value refused as unpresentable is deleted from the reading frame
  before the refusal is raised. It can still be most of a key.

The broker path keeps its chained cause, and so keeps urllib's frames,
as the 2026-09-28 ruling leaves that path. The PR body records that.

Three new cases: over a real refused socket, for an unpresentable
value, and for the broker path's frame. Each walks every frame the
refusal keeps, through its causes and contexts. Three mutants were
each killed: the wrapper's repr disclosing, the chained cause kept,
and the unpresentable value kept.

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's review of openDox-code#63 at 1b0fb3f: the opener for a
built-in credential still installed urllib's ProxyHandler, which reads
the environment's proxies. Measured: with http_proxy set, a request
addressed to 127.0.0.1 went to the proxy, credential header and all. A
plain-http route is private only because it stays on this host, so the
loopback ruling of 2026-09-28 is broken by any proxy.

_open_without_redirects becomes _open_for_a_built_in_credential. It
still declines every redirect, and a plain-http request now goes direct
through ProxyHandler({}), whatever the environment names. An https://
request may still use the environment's proxy, because a proxy reaches
it only by CONNECT and the credential stays inside TLS. The broker path
keeps the default opener, as the ruling leaves that path.

One new case over real sockets, with a stand-in proxy that hears
nothing. With the bypass removed, the stand-in proxy answered the turn,
so the mutant was killed.

Arc: neutral-product-standalone-operability
Lane: openxfactory-4 (openXfactory-4-openDox_extraction)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ures keep no context

Two remarks in Copilot's overview of openDox-code#63 at 286655f. Its
threads were none.

- "Broker-returned reserved references can raise uncaught errors." T080
  reserves the env: and keyring: forms for the built-in resolver. A
  broker whose intake answer carried a reference in one of them would
  make the next declaration of the record see two resolvers. The
  console's intake route replaces the reference outside the handler
  that catches a refused binding, so there it would be an uncaught
  error. hand_off_credential now refuses such an answer as malformed
  (DIAG_BROKER_MALFORMED), which both entry points already catch.
  serve_workbench.py is left alone.
- "Keyring failures retain exception context." The resolver raised its
  keyring refusal inside the handler, "from None", which hides the
  backend's error but keeps it as __context__, together with the
  backend's frames. The refusal is now raised after the handler, with
  no context.

One new case with two real broker scripts, and one assertion added to
the failing-backend case. Two mutants were each killed.

Arc: neutral-product-standalone-operability
Lane: openxfactory-4 (openXfactory-4-openDox_extraction)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…T080)

A broker binding over plain http:// to any host but this one is now refused
when it is declared, with the one ENDPOINT_NOT_PRIVATE sentence a built-in
credential earns on the same route. `mint` asks the same predicate before it
asks the broker for anything, as the built-in resolver does before it reads,
so a binding forced past the record still mints nothing. Only the auth kind
`none`, which presents no credential, keeps a route that is not private.

This is the first of the four broker-path gaps openDox-code#63 listed for
Brett. His word of 2026-09-29, given in-session on #63's closing question:
"Yes, separate phase-3 draft (Recommended)". The T080 ruling it extends is
recorded at openxFactory#656 comment 5880893901.

ENDPOINT_NOT_PRIVATE now names both credentials. T080's two route lists
become shared parametrize marks, so the broker cases reuse them and T080's
node ids do not change. T080's case that pinned the old scope (a broker
binding declared on these routes) keeps only its `none` half, over all nine.

Tests: 17 broker cases and 9 `none` cases added, 2 removed. With T080's head
3f14bb9 as the source, the 11 gap cases fail (DID NOT RAISE) and the 15
controls pass.

Arc: neutral-product-standalone-operability
Lane: openxfactory-4 (openXfactory-4-openDox_extraction)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…hains nothing

Gaps 2 and 3 of the four broker-path gaps openDox-code#63 listed for Brett,
closed with T080's own rules for a built-in credential (his word of
2026-09-29, given in-session).

One port method, `_call_provider`, now makes every provider request, on both
paths. A request that carries a credential (a broker's minted token, or what
the built-in resolver read) goes through `_open_with_a_credential`, which is
T080's opener renamed. It declines every redirect, and over plain http:// it
uses no proxy. Its refusal is raised afresh, with no cause and no context.
The auth kind `none` presents nothing, so it keeps the default opener and
its refusal's chain, as before.

The broker branch of `dispatch` answers an expiry outside every handler. The
one re-mint and the one paid retry of the 2026-08-26 ruling do what they
did, and a refusal raised by either keeps no context.
DIAG_PROVIDER_REDIRECTED and the redirect handler now name both credentials.

Measured at T080's head 3f14bb9, over real sockets and a real broker child:
- a POST answered 301, 302 or 303 reached the redirect's target as a GET
  that carried the token;
- with http_proxy set, a request to 127.0.0.1 went to the proxy with it;
- a refused connection chained urllib's URLError, and do_open.headers,
  _send_request.headers, _send_output.msg, send.data and request.headers
  held the token.

Tests: 12 broker cases. With 3f14bb9 as the source all 12 fail, and T080's
two refactored cases pass. T080's redirect and proxy cases now share their
scaffolds with the broker's (`_a_provider_that_redirects`,
`_an_environment_proxy`). The proxy scaffold drops urlopen's cached global
opener, so the proxy environment is read the way a process started with it
reads it. The ENDPOINT_NOT_PRIVATE sentence is rewrapped, with the same text.

Arc: neutral-product-standalone-operability
Lane: openxfactory-4 (openXfactory-4-openDox_extraction)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…keeps it

Gap 4 of the four broker-path gaps openDox-code#63 listed for Brett, closed
with T080's own rule for a built-in credential (his word of 2026-09-29,
given in-session).

`mint` now asks `_presentable`, the built-in resolver's own test, of the
token: non-empty printable ASCII with no whitespace. Any other token is a
malformed answer (DIAG_BROKER_MALFORMED). It is refused before the port
holds it or any provider is contacted, so the catalog reads unavailable and
no mint is recorded. At T080's head 3f14bb9:
- a token outside latin-1 failed inside urllib as DIAG_PROVIDER_UNREACHABLE,
  which names the wrong party, with do_open.headers and putheader.values
  holding it;
- one with a space or another non-ASCII character was sent as it was.

The refusal keeps no frame that holds the token, as the built-in resolver's
refusal of an unpresentable value keeps none. `mint` reads the answer
through `_minted_token` and raises any refusal again, afresh, after the
answer has left its frame. That covers every refusal of the mint answer,
which goes one step past the fourth gap's letter: a malformed answer beside
a good token also kept it at 3f14bb9 (mint.answer, mint.document,
_answer_document.text and _answer_document.document).

Not changed: the broker runner's own refusals (a non-zero exit, an answer
past the bound, a timeout) still keep what a misbehaving broker wrote. The
runner is shared by all four broker operations and sits outside the
provider call the ruling names. The PR flags it for Brett.

Tests: 15 cases. With 3f14bb9 as the source the 13 gap cases fail, and
the 2 controls pass (every printable ASCII character but the space is
presented unchanged; the declared answer mints).

Arc: neutral-product-standalone-operability
Lane: openxfactory-4 (openXfactory-4-openDox_extraction)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The two unpresentable-token literals outside ASCII are written as
backslash-u escapes, as the rest of this module writes them, so the
source adds no non-ASCII character. The gap-1 control and the operator
door's case each gain a line saying what they hold. The `none` case says
how T080 once pinned its scope there (two routes, with a broker binding).
No test changes what it runs.

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's review of openDox-code#64 at af457e6 (thread 4133295457): the
docstring's "301, 302 or 303" wrapped so that it read as repeating 303, and
"a 307 or 308 read as the provider refusing" was hard to parse. It now says
that at T080's head urllib followed only the 301, 302 and 303 codes, with a
GET that still carried the token, and refused a 307 or a 308 with
DIAG_PROVIDER_REFUSED, as the base probe measured. No test changes what it
runs.

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's review of openDox-code#64 at a2c838a (thread 4133360345):
`_parse_expires_at` let an integer past a float's range escape as an
OverflowError, and took NaN and the infinities as an expiry. `mint`
re-raises only a BrokerRefused afresh, so the OverflowError escaped with the
answer still in its frames. That defeats this PR's rule that no refusal of a
mint keeps the answer. A NaN or infinite expiry also minted a token that
would never expire, or would always have expired.

An expiry must now be a finite number, or an ISO-8601 instant as before.
Anything else is a malformed answer (DIAG_BROKER_MALFORMED), which `mint`
raises again holding nothing. The same measurement found one more escape of
the same class: arrays nested past the recursion limit fit inside the 64 KiB
answer bound (30,000 of them in 60 KB), and json.loads' RecursionError
escaped `_answer_document`. That is now malformed too, for all four broker
operations.

Tests: 5 cases join the malformed-answer case (an expiry past a float, NaN,
Infinity, -Infinity, and an answer nested past the limit), and each answer
is held inside the runner's bound. With 3f14bb9 as the source all 8 of that
case's params fail. Three mutants are killed: the finite check removed (4
fail), the overflow left to float() (1), and RecursionError not caught (1).

Arc: neutral-product-standalone-operability
Lane: openxfactory-4 (openXfactory-4-openDox_extraction)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Brett Heap's word of 2026-09-29 (openxFactory#656, comment 5901112350):
"Yes, add to #64". When a broker misbehaves (it exits non-zero, answers
past the size bound, or times out after writing), the shared runner's
refusal carries no broker output, no cause and no context, for all four
operations. The refusal names the operation and the failure class, and
never the bytes.

What was measured at 788d764, with a broker that wrote a fake token
first:
- exit non-zero, or past the bound: the token stayed in the runner's
  frame (answer, and the child's _fileobj2output). Past the bound read
  as MALFORMED.
- timeout: the refusal chained the TimeoutExpired, whose output and
  whose frames inside subprocess held the token.
- an answer that is not UTF-8: a UnicodeDecodeError escaped holding the
  token, and no refusal was raised at all.
- no refusal named its operation, and two of the sentences said "so no
  token could be minted" for intake, revoke and list too.

The runner's work moves to _run_broker, which returns an answer or a
sentence and raises no refusal of its own. subprocess_broker_runner
raises the refusal after that call returns, so it is outside every
handler and in a frame that never held the child. A decode error is
caught, and so is a decode error while a refused child is reaped.

The four operations now ask through one wrapper, _broker_operation. It
raises each refusal again, afresh, with the operation named, so an
injected runner is covered too. BrokerRefused takes
operation=, but only from OPERATIONS and only beside a broker's sentence
(BROKER_DIAGNOSTICS). Its message reads "broker <operation>: <sentence>",
and .diagnostic is unchanged. The broker's sentences no longer name an
operation. An answer past the bound has its own sentence,
DIAG_BROKER_OVERSIZE, so there are twelve fixed diagnostics.

Tests: 36 new cases. They cover the shared runner for five misbehaviours,
each of the four operations for each misbehaviour, a broker that cannot
be started, an injected runner, the operation vocabulary, and the
operator's set-credential door. With 788d764 as the source, 37 cases
fail: the 36 new ones except the one guard, plus the two tests that were
updated. 14 new mutants are killed, and all 16 earlier ones are still
killed.

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's review of #64 at 25788f9 (high): communicate() read all of a
broker's output before MAX_BROKER_ANSWER_BYTES was checked. So the bound
limited nothing in memory, and a broker that wrote without end was read
until the timeout and refused as a timeout.

_run_broker now reads the answer on a reader thread, at most one byte past
the bound. A broker that writes that byte is refused with
DIAG_BROKER_OVERSIZE and killed at once. The thread starts before the
credential is written, so the answer drains while intake's stdin is
streamed. The runner then waits for the child's exit within what is left
of the timeout. The answer is decoded as UTF-8 (JSON's encoding; the
locale's before), and an answer that is not UTF-8 is malformed. Every
refusal is still raised by subprocess_broker_runner after _run_broker has
returned, so it keeps no cause, no context and nothing the broker wrote.

Tests: a broker that writes without end is refused at the bound, well
inside a 5 s timeout. At 25788f9 it read until the timeout (5.1 s
measured), and that is the one case that fails there. A sixth
misbehaviour, a broker that closes its output and then hangs, is refused
as a timeout by the runner and by each operation. With 788d764 as the
source, all six new cases fail too (43 of the 44 runner cases). The
runner's mutants are rewritten for the new code. All 31 mutants are
killed, among them the whole output read as at 25788f9, and no deadline
on the child's exit.

Arc: neutral-product-standalone-operability
Lane: openxfactory-4 (openXfactory-4-openDox_extraction)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
b847ef3's reader is a daemon thread. When the credential's own source
failed mid-copy (the operator's input, which the ruling does not reach),
the error escaped with the child still running and the reader blocked on
it, and the interpreter aborted at exit ("Fatal Python error:
_enter_buffered_busy"), measured 3 of 3 times.

_run_broker now reaps the child before any other exception goes on, and
drops what the child wrote. The work after the reader starts moves to
_answer_of, unchanged. The runner's tests also assert that no refusal
keeps a frame holding the child. That kills three mutants that the reap
had made equivalent: a refusal raised where it is seen, for the exit, the
bound and the timeout.

Tests: the failing source in a child interpreter exits 1 with its own
UnicodeDecodeError and no fatal error. At b847ef3 it aborts. In this
process, with a broker that wrote first, what escapes keeps nothing the
broker wrote. All 33 mutants are killed.

Arc: neutral-product-standalone-operability
Lane: openxfactory-4 (openXfactory-4-openDox_extraction)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…boundedly

Copilot's review of #64 at b847ef3: a descendant that inherits the
broker's standard output kept the pipe open after the broker was killed,
so the reader, and the refusal, waited forever. That was measured at
e3eec6b: a broker with such a descendant and a 0.5 s timeout was still
not refused after 10 s.

The broker now starts in its own process group (process_group=0; the
session and terminal stay this process's). A refusal kills the whole
group, and waits at most BROKER_REAP_SECONDS (2 s) for the reader. A
descendant that left the group is left to the daemon reader, and the
refusal does not wait on it. The reader reads the descriptor with
os.read, so a reader still blocked at interpreter exit holds no buffered
lock that finalization needs.

Tests: a descendant in the group, refused within the timeout plus the
grace, and one that left it (setsid), refused within the grace plus a
margin. Both hang at e3eec6b. The failing-source case now also asserts
that the broker is not left running. All 36 mutants are killed, among
them the broker killed alone, no group of its own, and an unbounded wait
for the reader.

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's review of #64 at a603a03: a descendant that left the broker's
process group left the timed-out reader thread blocked, with its pipe
descriptor, behind every refusal, and that reader could still add to
what had been read after received was cleared.

The reader thread is gone. One selector loop in the calling thread
streams the credential to the broker's stdin in PIPE_BUF writes and
reads the answer, as communicate does on POSIX, under one deadline that
now covers the credential's streaming too. A refusal kills the broker's
process group and closes this process's ends of both pipes. So a
descendant that left the group costs no thread and no descriptor, and
nothing can add to the answer once the loop stops. The answer is read
straight into received, so no other local holds it. BROKER_REAP_SECONDS
is gone.

Measured at a603a03:
- a broker whose descendant left the group was refused only after the
  2 s reap grace (2.5 s with a 0.5 s timeout), and left its reader behind;
- a broker that never read a 1 MB credential held the refusal past 10 s,
  because the timeout began only after the credential was written.

Tests: the descendant case now also asserts that no thread and no
descriptor is left behind, and that an in-group descendant is killed. A
broker that reads 5000 bytes of a 1 MB credential is refused at its
0.5 s timeout. The failing-source case now lets the answer be read first.
That exposed a local, chunk, holding the answer in the escaping
traceback, and it is removed. All 35 mutants are killed.

Arc: neutral-product-standalone-operability
Lane: openxfactory-4 (openXfactory-4-openDox_extraction)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
openDox-code main moved from 2d11641 to 047bb4f with phase 2's nine
landings, T050 to T058. None of them changes a file this branch
changes, and the merge has no conflict.

Arc: neutral-product-standalone-operability
Lane: openxfactory-4 (openXfactory-4-openDox_extraction)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
T078's branch now carries openDox-code main 047bb4f, phase 2's nine
landings. Neither those landings nor T078's merge change a file this
branch changes, and the merge has no conflict.

Arc: neutral-product-standalone-operability
Lane: openxfactory-4 (openXfactory-4-openDox_extraction)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
T079's branch now carries T078's merge of openDox-code main 047bb4f,
phase 2's nine landings. None of them changes a file this branch
changes, and the merge has no conflict.

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 October 3, 2026 20:40

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

Printed remediation commands permit shell substitution, and additional trust-state consistency failures remain.

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

Open (4)
Resolved since last review (3)

Comment thread src/opendox/doxbench_trust.py Outdated
Comment thread src/opendox/cli_model_binding.py Outdated
Comment thread src/opendox/doxbench_install.py
Comment thread src/opendox/doxbench_trust.py
… bindings never trusted, one list reading, dangling links refused (plan 034)

Copilot at openDox-code#82, review at 7012cda, and the adversarial
self-pass the holder asked for before this push.

- r4174783197 (high). A printed trust command fell back to a JSON-quoted
  operand for a non-printable or non-ASCII value, and a shell still runs
  `$(...)` and backticks inside double quotes. At 7012cda a repository
  directory named with a newline, a terminal escape or a non-ASCII
  character plus `$(touch CANARY)` made CANARY when its printed command
  was pasted (red run). Now a printed command carries only an id the
  model catalog accepts (no shell or option parser acts on its
  characters) and paths quoted by shlex.quote where every character is
  printable. A non-printable root is named `.`, to be run from the
  repository's root, and `--bindings` is named where the binding was read
  from a document given by one. An id the catalog refuses gets no command.
  The `--` logic is gone: no printable id begins with `-`.
- r4174783280. A binding whose id or label the catalog refuses was
  trusted by `trust`, and the next start failed in brokered_catalog.
  doxbench_trust.unservable_because now judges the very catalog the
  factory declares, before any policy is asked: verdict_for refuses such a
  binding (a host policy and an old store entry included), recorded_for
  refuses it, so `add`, `edit` and `trust` write nothing, and the start
  declares the refusing port over an empty catalog.
- r4174783250. `list` reads the bindings document once, and its
  disclosure, trust lines and console lines are all of that reading.
- r4174783301. A link this user owns that points at nothing, as the state
  directory or above it, is refused by name, and any OSError the store's
  tree raises that no check named is a TrustStoreRefused naming the store,
  in `record` and in `verdict`.
- The trust-state walk (pending, approved, undeclared, unreadable
  declarations, a stale record). `list` gains a console line naming the
  one binding a console serving the repository declares, by the factory's
  own rule. The console's model approval says the binding is available
  only where the registered policy admits it, and otherwise
  APPROVED_UNTRUSTED_NOTICE. The fixed sentences that quote a command
  (the refused turn's, the rail's, the approval's) now name the required
  `--repo-root`, each parsed by the real parser in a test. The rail's
  JavaScript twin changes on its one line, so the census is unchanged.

Every fix has a case that fails at 7012cda (32 failed) and a mutant.

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 3, 2026
…on (plan 034) (#85)

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

Arc: neutral-product-standalone-operability

Plan 034 (`specs/034-opendox-standalone-operation/`): a **T102 follow-on**, under no task of its own. T102 (#81) landed as `0116293a`, so its After line is met. Claimed on openxFactory#656 in [`5973692022`](opensoft/openxFactory#656 (comment)). **DRAFT.** The holder posts READY, and the landers merge.

**The finding, F1 of the holder's T096 dry run:**

> Switching the rail's loaded document reads /workbench/thread. A standalone plane has no branch session, so it answers 403 `thread_capability_unavailable`, and Chromium logs one console error per switch, which is undeclared.

**The ruling,** [`5973854291`](opensoft/openxFactory#656 (comment)) on openxFactory#656:

> **Ruling (i): fix the product.** The rail wires its thread read only when `workbenchGate.session` exists, the same condition as its refusal transport. A composed host, which has a branch session, keeps reading threads as today. A separate openDox-code PR carries it (a T102 follow-on, under no task of its own). It lands before T087 and before T101's version bump, because it changes the shipped bundle.

Option (ii), declaring the 403 in T096's oracle, was rejected.

## The change

`src/opendox/web/app.js` gains `doxbenchThreadSeam(session, consoleTokenOf, injectedFetch)`, beside the thread loader it chooses. The doxBench bundle's `thread` is now `doxbenchThreadSeam(workbenchGate.session, () => caps?.console_token)`. That is the same `workbenchGate.session` whose presence picks the Save transport over `refusalTransport()`.
- **With a session column** (a composed host), it is `createDoxBenchThreadLoader`, unchanged: the same route, query, header, and reading of a refusal.
- **With none** (a standalone plane), it is a seam that answers `null` and sends nothing.

**Why not an absent seam.** The ruling's words, "wires its thread read only when…", could be read as leaving `thread` off the bundle. That would regress the rail. `switchThread` (`views/doxbench-chat.js`) reads an absent `thread` as the pre-§11 rail, which returns early and keeps the previous document's transcript across a switch. Carrying one document's turns into another's next request is the defect the switch exists to close. So a plane with no session column still gets a seam, and it answers "no readable thread" itself. Its `null` is the answer the 403 produced, so the rail still adopts the empty transcript, without the request or its console error. No file other than `app.js` changes in the product.

## Tests

**`tests/test_thread_read_by_session.py` (new), 7 cases:**
1. **The seam, under node, from `app.js`'s own text.** The module cannot be imported whole, because it boots the page at its last line. The harness takes the loader, the seam and the two constants they read, and runs them with an injected fetch.
   - No session column (`null` or `undefined`): a function that answers `null` and sends nothing.
   - A session column: one request to `/workbench/thread?repository=fixture&ref=main&tile_kind=cluster&tile_id=g1&document=b.md`, with the console-token header and the thread answered. A 403 still reads as `null`.
2. **The switch, in the real shell.** This reuses T102's shell harness (`mount`, `expand`, the DOM instrument) up to its first scenario. A document is loaded through its docs tile, then the rail's loaded-document selector (`select.doxchat-loaded`) is switched to every other entry once.
   - **Standalone** (the null columns, `editing by scope`): each switch asks the seam, and no switch sends a thread request.
   - **A composed host** (the contributed create and session columns, the gate live): each switch sends one thread request, with the usual query.
3. **The composition.** `app.js` composes the seam from `workbenchGate.session`, the bare loader is no longer wired directly, and the rail still adopts the empty transcript on a `null` answer.

**`tests/test_workbench_edit_by_scope.py`:** the shell harness's `mount` takes an optional `thread`, and `_stage` can write extra modules beside the views. Both are additive, and the module's 44 cases are unchanged.

**`tests/fixtures/web_boundary_census.yaml`:** the `app.js` row is re-measured (`1725 -> 1752`) with a provenance sentence, and the totals are re-derived.

**Mutants**, at `c7b2635e`. Each is applied to `app.js` in the committed tree, and an anchor that is not found aborts the run. Then the new module, `test_doxbench_thread_switch.py` and `test_workbench_edit_by_scope.py` run. All four are killed, each by a named failure.

| Mutant | Result |
|---|---|
| the guard dropped: every plane reads the thread (the F1 defect) | 2 failed |
| no seam at all without a session column (the pre-§11 rail) | 3 failed |
| the bare loader wired again, past the seam | 1 failed |
| the guard inverted: a composed host loses its thread read | 4 failed |

`c7b2635e` exists because the second mutant first ERRORED the seam cases (the harness called the `null`) rather than failing them. The harness now records a seam that is not a function, and the switch's counter hands no seam down as none.

**The whole suite**, locally, with `LANG=C.UTF-8 python -m pytest -q` and the basetemp under `~/.local/state`, run under nohup and polled in the foreground:
- at the head `c7b2635e`: `3776 passed, 177 skipped`, 0 failed;
- at `9959f89a`: the same.

The skip count does not move, and `validate.yml` is not edited.

## AT-R1, the browser half

**At the head, with the T096 prep harness** (`run-pass.sh` and `t096_drive.py`, as for #81). The integration is the head itself, because main `0116293a` is its base. The venv was reinstalled from it (117 installed files compared, 0 differ). Pass a (`plain-documents`) and pass b (plain notes, no front matter) each passed all 15 checks, with `FAILED CHECKS: []`.
- Step 7 passes: the no-model note shows, the turn is refused 403 `model_capability_unavailable`, and both editors are typed.
- Zero `pageerror`, nothing undeclared, and no 5xx. The pill reads `editing by scope`.

That harness does not switch the rail's document. So F1 is measured with the dry run's own browser half, which does.

**F1, before and after, with the dry run's own browser half.** These are `run-at-r1.sh` and `at_r1_browser.py` from the holder's t096-dry evidence, byte-identical copies (sha256 `b4b654ba697a…` and `b719aa17101d…`), run with `HALVES=browser`. That browser half reads the console token from T104's private copy, so it ran on #84's head. Its driver switches `select.doxchat-loaded` through every held buffer, and does not declare the thread 403.

| Commit (local, never pushed) | Pass | Rail switches | Thread reads | Thread 403s | Undeclared console errors | Verdict |
|---|---|---|---|---|---|---|
| `d4b99436`: #84's head, main without this fix | (a) | 1 | 1 | 1 | 1 | FAIL, "nothing undeclared" |
| `d4b99436` | (b) | 4 | 4 | 4 | 4 | FAIL, "nothing undeclared" |
| `78816a4e`: `d4b99436` plus this PR's four files, nothing else | (a) | 1 | 0 | 0 | 0 | PASS |
| `78816a4e` | (b) | 4 | 0 | 0 | 0 | PASS |

So, on a standalone plane, the browser logs 0 console errors on a switch.

## Not in this PR

- **#82** (T100) touches `views/doxbench-chat.js`, and this PR does not.
- **#84** (T104) merged into `78816a4e` with one conflict: the census's class-A total. It was re-derived. Whichever of the two lands second re-derives it the same way.

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>
brettheap and others added 2 commits October 3, 2026 22:18
A plain merge: the branch holds none of #85's commits. One conflict, the
web census's class-A total: main's 18486 (18459 + #85's 27 lines in
app.js) against this branch's 18499 (18459 + T100's 40 lines in
views/doxbench-chat.js). RE-DERIVED from the merged tree's rows, every
one of which matches its file: 18526 = 18459 + 27 + 40.
EXPECT_SKIPPED is untouched by #85 and stays main's 11.

Arc: neutral-product-standalone-operability
Lane: openxfactory-4 (openXfactory-4-openDox_extraction)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…034)

Two of round 5's mutants survived at db77c4e, each a defence the cases
did not yet reach:

- "an id the catalog refuses is printed in a command": the refusals of a
  catalog-refused id carried REASON_UNSERVABLE, so `command_safe_id` was
  never the only guard. The hostile-id case now also asks the provider's
  own refusals (`require_admitted` with no verdict, and with a verdict
  for another binding) and `trust_command` itself: no command for such an
  id on any path.
- "a binding trust cannot repair is told to trust": the over-long-label
  case checked the reason but not the absence of a command. Its refused
  turn, factory notice and `list` now print REMEDY_UNSERVABLE and no
  trust command, though the id itself is a valid one.

Both mutants are killed with these cases (mutants-run-13).

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 October 3, 2026 22:31

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

Unservable bindings receive remediation commands that cannot succeed in approval and turn responses.

Review effort: Balanced
Findings: 1 Medium severity

Open (1)
Resolved since last review (4)

Comment thread src/opendox/serve_workbench.py Outdated
brettheap and others added 2 commits October 4, 2026 13:15
…ever to trust (plan 034)

Copilot at openDox-code#82, review at e05c475 (r4175203889). A binding
whose id or label the model catalog refuses is never trusted: verdict_for
refuses it and recorded_for never records it. But two fixed sentences
still sent the operator to `trust`, a command that can never succeed for
it:

- the console's model approval answered APPROVED_UNTRUSTED_NOTICE for an
  approved binding past the catalog's bounds;
- a served turn on such a binding (its refusing port lists nothing) was
  refused with UNTRUSTED_TURN_MESSAGE.

Each now names the actual remedy and no command that trusts:
APPROVED_UNSERVABLE_NOTICE, judged first under any policy and reading no
store, and UNSERVABLE_TURN_MESSAGE, chosen by
doxbench_trust.turn_message_for from the refusing port's verdict, which is
held within the released failure envelope's 500-character message bound.
REMEDY_UNSERVABLE, which the refusals, the notice and `list` print, names
the verbs that correct a binding, `edit` (the same id) or `remove` then
`add` (a new id), each of which records trust for what it writes.

Every case fails first at e05c475 (7 failed), and each change has a
mutant.

Arc: neutral-product-standalone-operability
Lane: openxfactory-4 (openXfactory-4-openDox_extraction)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A plain merge: the branch holds none of #76's commits. One conflict, in
validate.yml's triple, and only in its comments: #76 re-pinned the floors
(3977 / 3966) and wrote why EXPECT_SKIPPED holds at 11 for T082; T100's
block wrote why it holds at 11 for T100. Both reasons are kept, T082's
first, and T100's last sentence now names T082's floors in place of the
superseded 3327 / 3316 reading. EXPECT_SKIPPED stays 11.

The web census is untouched by #76 (no file under src/opendox/web/), so
class A stays 18526, which tests/test_web_boundary.py re-derives from
the rows. tests/test_chat_model_configuration.py merged cleanly.

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 October 4, 2026 13:46

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

Lock-file permissions, platform capability checks, and an approval-policy race remain unresolved.

Review effort: Balanced
Findings: 2 Medium severity

Open (2)
Resolved since last review (1)

Comment thread src/opendox/doxbench_trust.py
Comment thread src/opendox/serve_workbench.py Outdated
…val reads the trust seam once (plan 034)

Copilot at openDox-code#82, review at 735d0c1.

- r4177946237. `os.open`'s mode is filtered by the umask. Under 0777 the
  lock file was born 000: the first record went through the descriptor it
  had open, and every later record was refused ("cannot be opened"), so
  the store was unusable. Once its owner and mode are judged, the lock
  file is set to exactly 0600 through that descriptor, as `_write`
  already sets the store's temporary file.
- r4177946288. The approval asked `is_registered()` and then
  `verdict_for()`, which reads the seam twice. A host that unregistered
  between the two reads would have had openDox's default installed in its
  place by an act that promised not to, and the default's store's answer
  given as the host's. `doxbench_trust.registered_verdict_for` reads the
  registration once, under the seam's lock (`_registered_now`), registers
  nothing, and gives that policy's verdict alone. `verdict_for` and it
  share one judging path (`_judged`), with the catalog check first.

Both cases fail at 735d0c1, and each change has a mutant.

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 October 4, 2026 14:23

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

Trust-store reads can block indefinitely on a FIFO before validating that the path is a regular file.

Review effort: Balanced
Findings: 1 High severity

Open (1)
Resolved since last review (2)

Comment thread src/opendox/doxbench_trust.py
…on a FIFO (plan 034)

Copilot at openDox-code#82, review at 42c98f9 (r4178064601). A
read-only open of a FIFO blocks until a writer comes. With a FIFO in the
store's place, `list`, the start and every verdict waited forever,
before the descriptor's type check could refuse it. The store and its
lock file are now opened O_NONBLOCK, so a FIFO is refused by the type
check on the descriptor that was opened, never by a second look at the
path. A regular file reads the same either way. O_NONBLOCK joins the
primitives the platform check names.

The case fails at 42c98f9: the store's verdict waited on the FIFO (red
run). The lock file's case passes there too, because Linux opens a FIFO
read-write without waiting; it holds the refusal on platforms that wait.

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 October 4, 2026 15:35
@sonarqubecloud

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

The large security-sensitive trust store, policy seam, and provider enforcement boundary warrant final human review.

Review effort: Balanced
Findings: None

Resolved since last review (1)

@brettheap
brettheap marked this pull request as ready for review October 4, 2026 15:45
@brettheap

Copy link
Copy Markdown
Contributor Author

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

Holder: T100 (16.3a): a served repository's bindings are trusted per machine. Its After line is met, and the head merges main ca9e1bd5.

  • Tests at the head.
    • Full suite: 3945 passed, 177 skipped, 0 failed, in a venv built with CI's own install command. The 177 are DB-backed runtime cases that need CI's PostgreSQL service.
    • Mutants: 94 of 94 killed.
    • validate and Sonar pass.
  • Copilot at the head: "Needs a closer look", findings none. It asks for a final human review of the security-sensitive trust store and policy seam. 0 threads are open, and 7 rounds' findings are fixed, answered and resolved: shell-quoted remediation commands, trust-state consistency, lock-file permissions, platform checks, an approval race, impossible-remedy text, and FIFO-safe store opening.
  • The holder's judgment. The closer look is an independent adversarial review the holder commissioned for this PR. Anything it substantiates is fixed in a T100 follow-on openDox-code PR that lands BEFORE T087. No release ships before that pin, because T099 publishes only the commit the openDox root pins. Landing now unblocks T083.

@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.

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

@brettheap
brettheap merged commit 38d3350 into main Oct 4, 2026
4 checks passed
@brettheap

Copy link
Copy Markdown
Contributor Author

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

LANDED — lane openxfactory-4, 2026-10-04T15:48:11Z, PR #82 → 38d3350 (opensoft/openDox-code main; plain gate)

Brett: land phase 1 / phase 2 PRs when green

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