Skip to content

DRAFT (phase 3, after T063): T095, AT-R1's HTTP half: an acceptance harness in its own CI job, with no database service (plan 034) - #75

Draft
brettheap wants to merge 26 commits into
mainfrom
build/034-acc-t095-at-r1-http-half
Draft

brettheap wants to merge 26 commits into
mainfrom
build/034-acc-t095-at-r1-http-half

Conversation

@brettheap

@brettheap brettheap commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Plan 034, task T095, slice acceptance (AT-R1, the HTTP half), phase 3. The plan is specs/034-opendox-standalone-operation/tasks.md at opensoft/openxFactory main 80217a92, § "Acceptance: AT-R1". It realizes FR-011's HTTP half.

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

  • Claim: openxFactory#656 comment 5961416730.
  • Rulings: 5961364221 item 2 (T095 is drafted now and does not go READY before T063 lands); 5850003126 (R1Q10 (a), R1Q12 (a), R1Q13 (a) with (c), R1Q15 (b), R1Q16 (iii) and (iv)).
  • State: DRAFT. It lands after T089 and T104 (openDox-code#84), per the holder. The holder posts READY and the landers merge; this writer does neither.

On this PR, acceptance is EXPECTED red; validate stays green

Most of the stack the harness drives has landed. main 390e2c28 (2026-10-03) carries:

Still a draft: #84 (T104, the console token via the opened URL).

This head reads the console token the way T104 delivers it (section 6 below). At this branch (main plus the harness), the acceptance job therefore fails by design, at [a.capabilities carries no console token], until T104 lands (quoted below). Against a local integration that includes #84, the same harness passes. The validate job runs the whole suite as before, and is green.

Making acceptance a required check is a ruleset change for the repository's owner (docs/branch-protection.md). This PR does not touch the ruleset.

What changes (four files, nothing else)

file change
acceptance/at_r1_http.py NEW. The harness. Standard library only. Not a pytest module: it sits outside tests/ and tests_runtime/, so testpaths never collects it and #1144's F9.1 is unchanged (FR-006's "no exclusion" holds).
.github/workflows/validate.yml A new acceptance job, appended. It has no services, runs no pytest and installs nothing itself: one step, python3 acceptance/at_r1_http.py. The validate job and its triple are untouched.
tests_runtime/test_deploy_shape.py Required, because test_the_workflow_declares_the_required_job_and_its_steps pinned sorted(jobs) == ["validate"]. It now admits ["acceptance", "validate"]. A new case holds acceptance to no service or container, no pytest run, no pip install step, and one harness step outside testpaths. And test_every_install_of_this_package_reads_one_dependency_lock now also requires that the harness passes the lock to pip.
tests/test_at_r1_http_harness.py NEW, from Copilot's review at b440d12b. It tests the harness's DECISIONS in process, as tests/test_smoke_signals.py tests the browser half's oracle. It loads the harness from its file, against an in-process loopback server, so nothing is installed or started. 140 cases: the lexer; import specifiers against routes; a module-graph cycle; a dynamic refusal against a static import of the same path; the media types of served modules and stylesheets; modules and stylesheets from outside the plane; bare module specifiers; nineteen catalog shapes; and T104's delivery: the opener's location and privacy (a FIFO included, which is never read); its refresh as a browser follows it; its forward, which must open the console page on a host the plane answers on, with no encoded copy of the token outside the fragment; its token as the page accepts it; its record; the token absent from /capabilities; the stop's exit status; and the after-stop checks. 64 mutants of the harness's decisions are each killed by their case (below).

The two test changes add 141 cases to SELECTED and PASSED, and none skips. The floors permit a rise, and no floor is edited, as with every phase-2 landing since T037. CI's validate at f29b4ddd printed triple: selected=3764 passed=3753 skipped=11 failures=0 errors=0.

Overlap, checked before editing: gh pr diff --name-only on every open openDox-code PR (#60, #61, #62, #63, #64, #65, #67, #69, #71, #72, #73, #74). None touches .github/workflows/validate.yml, tests_runtime/test_deploy_shape.py or acceptance/. Re-checked at 2026-10-03T16:1xZ over every open PR: #76 (T082) and #82 (T100) now edit validate.yml, but only the validate job's triple pins (around line 257). This PR only appends a job after the last line of the file (@@ -299,3 +299,40 @@), so the two never touch the same lines. No open PR touches the other three files.

The documented command, and how the harness runs it

T007 batch H's 10.3 addendum (openspec/changes/add-neutral-product-standalone-operability/tasks.md, 10.3, "AMENDED — T007 Batch H (5850003126; Ruled R1Q15 (b), R1Q16 (iii))"), verbatim:

The command the root's README.md documents is the standalone install and its one start: pip install "opendox[local]", then opendox generate-and-open --local …. The flag selects the local mode explicitly (13.4 as this batch amends it), and the local extra carries the bundled server that mode starts (13.1 as this batch amends it). After the install, that start is the single command requirement 10's second scenario has a user run. The entry point is still the code leg's console script, and no Makefile target is added, for the reason above. Carried out by T070 and T076.

The harness holds both lines as constants (DOCUMENTED_INSTALL, DOCUMENTED_START, the … being U+2026 after one space). Compared mechanically with T076's README (opensoft/openDox#17 at 7eef03b4):

DOCUMENTED_INSTALL = 'pip install "opendox[local]"' (6f63616c5d22 tail bytes): README lines [32]
DOCUMENTED_START = 'opendox generate-and-open --local …' (616c20e280a6 tail bytes): README lines [33]
  • The start line. The harness runs opendox generate-and-open --local --repo-root <repo> --repository fixture --no-open --port <free port>. That is quickstart.md § 3's filled form, with --port given a port found free at run time and checked free on 127.0.0.1 and ::1 just before the start. Before it starts anything, it asserts that its argv begins with exactly the tokens of DOCUMENTED_START before the … (start.documented-command). The … is the verb's own arguments, as the README says, and every flag above is one the README names. opendox is the fresh venv's console script, resolved on the child's PATH and asserted to lie inside that venv; argv[0] stays the literal opendox.
  • The install line. No release of opendox is published (PyPI answers 404 for opendox; the release-channel question is with Brett). So the harness installs the same distribution with the same extra from the checkout: pip install "<checkout>[local]", from a copy of this checkout's tracked files. That is exactly the substitution the README makes for its first line (pip install "./code[local]", run from the root). It installs through constraints-cpython312-linux.txt where the lock's own interpreter runs it (CPython 3.12 on Linux, as in the job). The lock pins versions and adds no package, so the install is still opendox[local] and nothing else (AT-R1 step 2).

What the harness asserts, in order (AT-R1 steps 1-4, and the route answers behind 5-8)

Each assertion has an id. The run stops at the first that fails and prints AT-R1 HTTP half: FAIL [<id>]: <why>, then exits 1. --keep-going reports every failure instead, and still exits 1. A harness that breaks before a verdict exits 2, never 0.

  1. Install. A fresh venv, then opendox[local] (R1Q16 (iii)). Then install declares the local extra, read from the installed distribution's metadata.

  2. The clean machine, ASSERTED (clean.*), in a fresh OPENDOX_STATE_DIR created empty for the run:

    • none of openxdox, ideation_dashboard, doc_health and corpus_adapter_openxfactory is importable in the venv (python -I, from an empty directory);
    • omp is not on the PATH, nor is the product's own doxbench_bridge.HARNESS_COMMAND;
    • no identity broker (openprofiler-broker) is on the PATH, and no issuer setting is set;
    • nothing listens on 127.0.0.1:5432 or [::1]:5432;
    • no PostgreSQL Unix socket answers in a shared directory. The harness reads every listening .s.PGSQL.* socket the kernel lists in /proc/net/unix, wherever it lies, plus the distribution sockets as a fallback. One in a directory other users can traverse, as libpq's defaults (/tmp, /var/run/postgresql) are, must not answer. One in a private 0700 directory (another install's own) is noted;
    • no DSN or PG* setting reaches a child;
    • neither repository holds a binding at the product's own doxbench_binding.DEFAULT_BINDINGS_RELPATH;
    • no bundled-server process exists before the start.
      Every child runs with GIT_*, XF_*, OPENDOX_*, PG*, DATABASE_URL and PYTHONPATH dropped, a fresh HOME, and TMPDIR inside the scratch directory.
  3. Two plain repositories, each a fresh git init (spec.md AT-R1 step 3): (a) tests/fixtures/plain-documents, and (b) quickstart.md § 2's three notes, byte for byte, with no front matter. Each gets git config user.name/user.email, which the served actor is read from.

  4. The documented start, once per repository (start.*): the port is free, the argv matches, the console script lies in the venv, the server is ready within 120 s, and the process that answered is still the one it launched.

  5. Fetches.

    • / must be text/html with an <html> element.
    • /snapshot.json must be a JSON object, non-empty, and neutral per F5.3: none of #1144's fourteen declared words (5.5's falsifier list, verbatim) in any string value.
    • The grouping station, named by /capabilities' own display facet (fields.grouping.field), must be filled, so a grouping tile can open the chat pane (R1Q13 (a) with (c); this fails, and does not skip).
    • /capabilities must give install.mode == "local", and must carry no console_token. A standalone plane hands its token to no loopback caller (T104, RULED openxFactory#656 5963851934, adversarial review 2's M5).
  6. The console token, as the user's browser is handed it (T104). The start prints console <file URL>, the PATH of a private opener, and never the token. The harness reads that opener itself, with the standard library, and imports nothing from the product (opendox.console_access.read_private_copy is the product's own reader, and it is not used). The checks:

    • the start printed the opener (console opener printed);
    • it is <OPENDOX_STATE_DIR>/console/<port>.html, outside the served repository;
    • it is private: this user's regular file, mode exactly 0600, one link, in a directory no one else can enter. It is opened with O_NOFOLLOW | O_NONBLOCK and read only if fstat calls it a regular file, so a FIFO can never hang the run;
    • it has exactly one meta-refresh that a browser follows (the HTML standard's declarative refresh steps: a delay that does not start with a digit or . aborts it). It goes to this plane's console page (/ or /index.html), on a loopback host the launched plane answers on (served_loopback_hosts probes 127.0.0.1 and ::1), with no backslash, whitespace or user information. #console_token=<token> is in the FRAGMENT, and never in the query or the path, raw or in any percent-decoding;
    • that token is one the page accepts ([A-Za-z0-9_-]{16,512}, T104's takeDeliveredConsoleToken);
    • its JSON record (<script type="application/json" id="opendox-console">, kind opendox-console-access v1) describes that same forward: its port, its token, its opened_url, and its page_url.

    No failure message quotes the token.

  7. The model catalog. /workbench/model-catalog must refuse a caller with no token (4xx). With the opener's token in X-XF-Console-Token, it must answer 200 in the envelope the chat rail adopts (views/doxbench-chat-model.js, adoptCatalog):

    • schema_version is any JSON number equal to 1 (so 1, 1.0 and 1e0), never true and never "1";
    • kind is workbench-model-catalog;
    • models is an array, with no entry whose available is true (16.4).

    The harness also asserts that the served bundle names both this route and this kind, so neither constant can drift from the bundle.

  8. Every route the panes can request answers, and none answers 5xx or drops the connection (derivation below).

  9. Stop and look. The harness sends SIGTERM to the entry point alone, as kill would, never to its process group, and waits up to 90 s. Then:

    • the entry point must exit 0 (stop exits 0), as tests_runtime/test_bundled_postgres.py requires of the same stop;
    • it scans /proc for any live process whose executable lies in the venv's pixeltable_pgserver package, or whose command line names the fresh state directory, and there must be none (R1Q16 (iv)). That is checked at the operating system, not by the product's report;
    • the opener must be gone (stop removes the console opener);
    • the token must appear nowhere in either output stream of the whole serve (console token never printed).

Cleanup runs however the run ends: it stops any server it started and kills any bundled-server process the scan finds, but only AFTER the verdict is taken, so a leftover still fails the run. Then it removes the scratch and state directories.

How the route list is derived (from the served JS, not by hand)

derive_bundle reads the bundle from the running server:

  1. It fetches /, then follows the page's <script type="module"> and stylesheet links.
  2. In every module, a small JS lexer strips comments and regular-expression literals and yields each string literal with the code before it. That code tells an import … from "x", an export … from "x" or an import "x" from an ordinary string.
  3. It follows every static specifier, every literal dynamic import("x"), and every view-binding module /capabilities declares, transitively. Each module is fetched from the server under test once, and judged once per way it is imported. A static import must answer 200, even of a path some other module imports dynamically, because a failed one is a module-load pageerror, which AT-R1 step 8 forbids and no declaration can excuse. A dynamic import may be refused, but not with a 5xx. Standalone, /views/intent-feed.js answers 404 (10.2a: not owed), and views/intent-binding.js degrades. A module the server DOES serve, statically or dynamically imported, must be served as JavaScript ([bundle.module-type <path>]: a JavaScript MIME type essence, as tests_runtime/test_served_bundle.py's JAVASCRIPT_TYPES lists them). A linked stylesheet must be text/css ([bundle.sheet-type <path>]). Both comparisons ignore parameters such as charset, and case. A BARE module specifier (import "child.js"), which a browser with no import map refuses, is [bundle.bare <specifier>]. A module or stylesheet from OUTSIDE the plane (https://…, //host/…, data:) is [bundle.external <url>], and is never fetched from loopback by its path: a clean machine with only openDox installed cannot be assumed to reach it.
  4. In every module of that graph, every string literal that is a same-origin path (/ or ./ followed by a letter, with no whitespace, not a .js/.css path) is a route the bundle can request. At the integration below that is 38 modules and 18 routes.

A static read cannot tell a load-time request from an on-click one. So the harness requests ALL of them, a superset of the wheel's, the lens's and the chat rail's load-time reads; T096's browser run observes the load-time set itself. The nine /actions/… routes get a GET too. A GET there finds no handler (each answers 404), so it never executes an action.

Two kinds of route take values the panes fill from the snapshot, and the harness fills them the same way:

  • A prefix ending in / (/source/). The wheel, the viewer and the workbench's source loader complete it with a document path, so it is also requested once per document the snapshot lists, both plain and keyed by the workbench's key (/source/fixture%40main/<doc>).

  • The thread read (/workbench/thread). It is also requested with the exact query the chat rail sends when it opens on a document (views/staging-workbench.js:2925, loadThread, called at views/doxbench-chat.js:1276-1281):

    • repository and ref come from the workbench's key, which with no snapshot index is the snapshot's repository at main (app.js, sourceKeyFor);
    • tile_kind is cluster (a grouping tile's kind, views/wheel-model.js: "clusters -> "cluster"");
    • tile_id is the first grouping tile, and document is its first member.

    The query-less form alone would stop at the 400 parameter check, before the reach that crashes.

Every request carries the console token the opener delivered, as the doxBench transports do, so a guarded read answers from its handler and not from the console check.

Falsifier: the harness itself

At this branch, main 90ac7033 plus the harness (what this PR's own acceptance job runs): FAIL, named

== r32-r8-on-pr-tree rc=1 held=32
AT-R1 HTTP half: FAIL [a.capabilities carries no console token]: /capabilities publishes `console_token` to any loopback caller; a standalone plane delivers it only through the 0600 opener file (T104, adversarial review 2's M5)
      1 failed, 32 held

The expected red moved as the stack landed:

Against a LOCAL integration of the stack (never pushed): PASS with T104, and without it, FAIL where T104 is missing

Both integrations live in this writer's own clone, with no upstream. The live heads were read at 2026-10-03T21:xxZ, after #77 (T084) and #80 (T103) landed:

tree what it is
b0a8ec88, the integration WITHOUT T104 main e49b17c3 + #80 b9025b6c, #80's landed head. Its tree is exactly main 390e2c28's (git diff is empty)
d05c266e, the integration WITH T104 b0a8ec88 + #84 c979747a (git merge --no-ff, clean)

#84 at c979747a conflicts with main 390e2c28 itself in 14 files, as a history effect of #80's squash landing: the same content merges clean through #80's landed head. That main merge is #84's writer's.

The harness at 27479495, and then at this PR's head 33841d4a, whose change since only tightens the bundle walk and the opener's read:

== r40-2747-on-live8-t104-failfast rc=0 held=302
AT-R1 HTTP half: PASS (302 assertions held)
== r41-2747-on-live8-t104-keepgoing rc=0 held=302
AT-R1 HTTP half: PASS (302 assertions held)
== r42-2747-on-live8-keepgoing rc=1 held=276
== r43-r13-on-live8-t104-failfast rc=0 held=302
AT-R1 HTTP half: PASS (302 assertions held)
== r44-r13-on-live8-t104-keepgoing rc=0 held=302
AT-R1 HTTP half: PASS (302 assertions held)
== r45-r13-on-main390e-keepgoing rc=1 held=276
AT-R1 HTTP half: FAIL [a.capabilities carries no console token]: /capabilities publishes `console_token` to any loopback caller; a standalone plane delivers it only through the 0600 opener file (T104, adversarial review 2's M5)
      also FAIL [a.console opener printed]: the start printed no `console <file URL>` line naming the opener, so a user has no way to open the console page
      also FAIL [a.catalog answers]: /workbench/model-catalog with no console token to present (step 6 found none) answers HTTP 403: ...console_required...
      also FAIL [b.capabilities carries no console token]: ...
      also FAIL [b.console opener printed]: ...
      also FAIL [b.catalog answers]: ...
      6 failed, 276 held

r42 printed the same six failures as r45. r43 to r45 ran the working tree that 64dc06f5 committed; cmp is identical, and the merge of main after it changed no harness file. The same verdicts held at every earlier integration:

The pushed harness from before the token change (4dcb4221) stops at the integration with T104's first local commit, [a.capabilities console_token] (r16), which is the reason for this change.

Verdict at the integration with T104: AT-R1's HTTP half passes, with 302 assertions held in both passes. That covers:

  • the install, the clean machine and both repositories;
  • the documented start: ready, with the process the harness launched;
  • / as HTML;
  • the neutral, non-empty snapshot with a grouping tile: repository (a) has 8 documents and 2 clusters; repository (b), with no front matter, has 3 documents and 2 clusters;
  • install.mode == local, and no console_token on /capabilities;
  • the opener: printed, at <OPENDOX_STATE_DIR>/console/<port>.html, private, forwarding with the token in its fragment, and with a record that agrees;
  • the catalog: it refuses a caller with no token, and with the token answers in the envelope the chat rail adopts, with no available entry;
  • 38 modules, none bare and none from outside the plane, every one served as JavaScript and every static import 200, /views/intent-feed.js refused as 10.2a's not-owed dynamic import, and /styles.css served as text/css;
  • all 18 derived routes and every parameterized form below 500;
  • the stop: exit status 0 within the bound, no bundled PostgreSQL process left, the opener removed, and the token in neither output stream.

Pass (a)'s route answers at d05c266e (r44). Every answer is the same as at 16d1053b, a945ef01, 365e4e9b and 887e72f5 before it, by a diff of the runs' GET lines:

/actions/{dtn-seed,edit,notebook,refresh,staging-seed}            HTTP 404
/actions/workbench/{chat-turn,document-abstract,model-approval,model-intake}  HTTP 404
/capabilities HTTP 200         /logout HTTP 404           /snapshot-index.json HTTP 404
/snapshot.json HTTP 200        /source/ HTTP 404          /source/<each of 8 documents> HTTP 200
/source/fixture%40main/<each of 8 documents> HTTP 200
/workbench/model-catalog HTTP 200    /workbench/model-intake HTTP 200    /workbench/thread HTTP 400
/project-register.json HTTP 404
/workbench/thread?repository=fixture&ref=main&tile_kind=cluster&tile_id=grouping-compost-corner&document=grouping-compost-corner.md HTTP 403

Before T084: two product gaps, both T084's, both removed by #77

The same harness against the integration without #77 (e7187f06: main + #69 f66e5f82, #72 1b0c3a63, #73 71d24af6, #71 83213eb2, #74 9061b22a, #65 c0a97648) held 198 and failed 4. The 4 were these two gaps, each hit in both repositories:

== r6-integ-keepgoing rc=1 held=198
AT-R1 HTTP half: FAIL [a.route /project-register.json]: GET /project-register.json answers no answer (RemoteDisconnected: ...)
      also FAIL [a.route /workbench/thread?repository=fixture&ref=main&tile_kind=cluster&tile_id=grouping-compost-corner&document=grouping-compost-corner.md]: ... no answer (RemoteDisconnected: ...)
      also FAIL [b.route /project-register.json]: ... no answer (RemoteDisconnected: ...)
      also FAIL [b.route /workbench/thread?repository=fixture&ref=main&tile_kind=cluster&tile_id=budget-roadmap&document=budget.md]: ... no answer (RemoteDisconnected: ...)
      4 failed, 198 held
  1. GET /project-register.json dropped the connection. src/opendox/serve_project.py:271 (at main 047bb4fa) did from openxdox.gate_console import DEFAULT_RECORDS_DIR (and :272, from openxdox.kickoff import …), so it raised ModuleNotFoundError: No module named 'openxdox'. The page requests this route on every load (app.js:1004 → views/repo-selector.js:138-139). It is batch L's named crash site (5920216845).
    Removed by T084, 4.3: the last deferred reaches through declared seams; consumer_reach retired (plan 034) #77. The handler now reads column_seams.gate and column_seams.kickoff. openDox's default discovers no register, so the route answers the existing structured 404 "no project register", and the picker hides.
  2. The chat rail's thread read dropped the connection. src/opendox/serve_workbench.py:560 at the pre-T084 integration (:548 at main) did from openxdox import doxbench_scope, which is reached once the five query fields are present. The rail requests it when it opens on a document (views/staging-workbench.js:2925). It was not among batch L's three named crash sites; the holder relayed it to T084's writer.
    Removed by T084, 4.3: the last deferred reaches through declared seams; consumer_reach retired (plan 034) #77. The handler now uses openDox's own scope type. With no live session on that scope, it answers 403 with the no-live-session absence body.

No from openxdox import is left in either file at d05c266e, now that #77 has landed on main, and the runs with #77 found no new gap.

The assertions bite: eleven product mutants, four workflow mutants and sixty-four decision mutants, all killed

Product mutants, each run through the full harness: m1, m2, m4 and m5 at b4fc637f, and m3 again at b440d12b. The only harness change since b4fc637f is main()'s handling of an error while preparing. m1 and m2 were merged from main when #69 was at fedfa75d and #72 at 20032d02. m3 to m5 are working-tree edits of the integration as it was then, 8e4203d8 (before T084):

mutant tree killed at
m1, no T081 integration without #74 (25f1f6af) [a.catalog offers no available entry]: "no model is configured, yet the catalog offers ['omp-local']"
m2, no T073 integration without #72 (f3957515) [a.capabilities install.mode == local]: "the served install block is None"
m3, the server is never stopped bundle.BundledServer.stop() returns at once, and no parent-death signal [a.stop leaves no bundled PostgreSQL process]: "still running after the entry point stopped: pid 599656: …/pixeltable_pgserver/pginstall/bin/postgres -D …" (and the harness's cleanup then removed it)
m4, a governance word in the fixture title: A slow leak at the rain barrel, ratified [a.snapshot neutral (F5.3)]: "openxFactory's vocabulary leaked into the neutral snapshot: ['ratified']"
m5, a missing module src/opendox/web/views/lens-model.js deleted [a.bundle.module /views/lens-model.js]: "imported statically by /app.js, answers HTTP 404"

T104's delivery, each a working-tree edit of the integration WITH #84, a945ef01, run through the full harness at d53a7378 (r33):

mutant edit killed at
m6, a readable opener console_access.PRIVATE_MODE = 0o644 [a.console opener is private]: "… has mode 644, not 600"
m7, the token in the query opened_url joins with ? instead of # [a.console opener forwards with the token in its fragment]: "the opener's forward carries the token in its QUERY …"
m8, the opener outlives the server remove_private_copy returns at once [a.stop removes the console opener]: "… is still there after the server stopped …"
m9, the token back on /capabilities build_server publishes it on every plane [a.capabilities carries no console token]
m10, the token printed generate-and-open prints token <token> [a.console token never printed]
m11, an unguarded catalog the catalog's console check removed [a.catalog refuses a caller without the console token]: "… answers HTTP 200 …"

The harness's own decisions, each mutated in acceptance/at_r1_http.py and run against tests/test_at_r1_http_harness.py, every one killed:

  • at d53a7378 (evidence/decision_mutants_d53a7378.py in this writer's work directory), 25 of 25: each of T104's checks (12), the media types (5), the opener's record (6), and the catalog's number (2);
  • at 5636eb8d, 6 more: the browser-divergence check, the user-information check, the guarded split of the forward, the unquoted message, the guarded split of the printed location, and the record's redaction;
  • at 486e426e, 7 more: the refresh delay, the optional url keyword, the quote truncation, and the token shape (no check, no upper bound, search for fullmatch, and a lower bound of 1);
  • at 142d1352, 3 more: no console-page check, an empty path refused, and / not a console page;
  • at 27479495, 8 more: the origin discarded, the external module and stylesheet checks each dropped, the served-host check, localhost never served, no decoding, one decoding round, and any stop status;
  • at 64dc06f5, 5 more: a bare specifier resolved, a bare one not judged, any /-containing specifier read as relative, a blocking open of the opener, and no regular-file check (killed by a FIFO that a gone writer filled with a page). Five earlier mutants (static-after-dynamic, the catalog's shapes and its envelope) were killed at f0e0ffe1 and 4dcb4221. One proposed mutant, admitting a string schema_version, is equivalent, because no string equals 1 in Python.

Workflow mutants, against the three affected cases of tests_runtime/test_deploy_shape.py:

mutant killed by
acceptance gains a postgres service test_the_acceptance_job_runs_the_harness_on_a_clean_machine
the harness drops -c <lock> test_every_install_of_this_package_reads_one_dependency_lock
acceptance chains && python -m pytest -q test_the_acceptance_job_runs_the_harness_on_a_clean_machine
acceptance gains its own pip install . step both cases

Restored: 3 passed. Locally, tests_runtime/test_deploy_shape.py and tests/test_triple_pin.py give 115 passed.

How the harness is run, and where the bundled server's state lives

The harness takes no path and no port (only --keep and --keep-going; see the SonarCloud round below). It installs the checkout its own file is in. Its scratch space and its OPENDOX_STATE_DIR are fresh tempfile directories, so TMPDIR chooses where they go. To measure the integration and each mutant, this writer copied the harness, untracked, into that tree's acceptance/ and ran it there.

  • In CI, in the runner's temporary directory (/tmp/odx-XXXXXXXX), which is sticky.
  • The socket path is capped. The bundled server's socket is <OPENDOX_STATE_DIR>/postgres/run/.s.PGSQL.5432, and Linux takes at most 107 bytes (runtime/config.py, UNIX_SOCKET_PATH_MAX). So the harness's state directory is a short odx-XXXXXXXX directly under TMPDIR. A run whose socket could not fit is refused with exit 2, naming TMPDIR, before anything is installed.
  • The local runs above used TMPDIR=~/.local/state/opendox-t095. This writer's work directory resolves through a world-writable, non-sticky mount, and the product rightly refuses a state directory below it: "… is writable by every user and is not sticky (mode 777) …". That directory was empty after every run, and no bundled PostgreSQL started by these runs was left running.

Review rounds

  • SonarCloud (advisory; the gate failed at 1c064bb3 on Security Rating D, and passes at every head since bbdeb9ec). Fixed in bbdeb9ec:
    • pythonsecurity:S8703, S8705, S8707: the --port, --scratch, --state-base and --checkout options flowed into a socket, the server's argv and the filesystem. They are gone.
    • python:S5443: the /tmp/.s.PGSQL.5432 probe is replaced by the distribution sockets.
    • python:S3776: the lexer is a class, and the long functions are split one per step.
    • python:S9073: the acceptance job's two assertions are split.
  • Copilot, fourteen rounds. Every finding was real and was fixed with evidence: each fixed head reports a named FAIL, and the reviewed head reproduces the defect. Every thread is resolved.
    • At 1c064bb3, fixed in 32ef3e8c:

      • r4170450448: a module refused as a dynamic import hid a later static import of the same path.
      • r4170450491: a malformed 200 catalog raised a harness error instead of a named failure.
    • At bbdeb9ec: r4170537382, the same static-after-dynamic case. Already covered by 32ef3e8c, and answered there.

    • At 32ef3e8c, fixed in b4fc637f:

      • r4170537350: a server on /tmp's socket alone, with TCP off, passed the fixed socket list.
      • r4170567257: a non-list documents value passed as "non-empty".
      • A "previously missed" item: a grouping value with no tile holding an id and a member document passed, and silently dropped the rail's thread read.
    • At b4fc637f, "Needs a closer look" with no new finding. It noted that the PR needs integrated verification with the dependent product changes: that is the integration above, now including T084. Its "previously missed" item, fixed in b440d12b: an OSError in prepare() escaped main() with exit 1. This head returns 2; at b4fc637f, an ENOSPC from tempfile.mkdtemp escaped.

    • At b440d12b, "Needs a closer look" with no findings. Its "previously missed" item, fixed in f0e0ffe1: the module-graph verdict had no collected regression test, and on this branch the acceptance job stops before derive_bundle. The new tests/test_at_r1_http_harness.py above answers it.

    • At f0e0ffe1, "Needs a closer look" with no findings. Its "previously missed" item, fixed in 4dcb4221: a 200 catalog of {"models": []} passed, though adoptCatalog adopts only schema_version === 1 and kind === "workbench-model-catalog", and shows any other catalog as unreadable. The envelope is now a named check, and the served bundle must name the kind. Seven refused envelopes are tested, and relaxing the int check fails the true case.

    • At 4dcb4221, "Needs a closer look" with no findings.

    • At 1c0ff975, fixed in f29b4ddd: r4173473346, the envelope check refused "schema_version": 1.0 and 1e0, which JavaScript's === 1 adopts. It now admits any JSON number equal to 1, and never true or "1".

    • At f29b4ddd ("Changes recommended"), fixed in de8b2274:

      • r4173769822: a module served 200 as text/plain or text/html passed;
      • r4173769844: so did a stylesheet.

      Both now require their type.

    • At d53a7378 ("Changes recommended"), fixed in 5636eb8d:

      • r4173842763: a forward holding a backslash or user information passed, though a browser opens another host;
      • r4173842794: a refused destination's message quoted a URL that could hold the token;
      • r4173842805 and r4173842811: an unparseable printed location or refresh URL raised a harness error instead of a named failure.
    • At 5636eb8d ("Changes recommended"), fixed in 486e426e:

      • r4173894317: a refresh whose delay a browser aborts (invalid;url=, -1;url=) still yielded a token;
      • r4173894352: a token that takeDeliveredConsoleToken discards (short) passed.
    • At 486e426e, "Needs a closer look" with no findings. Its "previously missed" item, fixed in 142d1352: with an agreeing record, a forward to /missing.html or /snapshot.json passed. The forward must now open / or /index.html.

    • At 142d1352 ("Changes recommended"), fixed in 27479495:

      • r4174355680: a percent-encoded copy of the token in the query passed;
      • previously missed: a forward to [::1] passed, though the plane listens on 127.0.0.1 alone;
      • previously missed: an import from https://… was fetched as its local path and passed;
      • previously missed: any stop exit status passed.
    • At 27479495 ("Changes recommended"), fixed in 64dc06f5:

      • r4174411680: a bare specifier (import "child.js") passed when /child.js was served;
      • previously missed: a FIFO opener hung the harness under --keep-going.

Not done here

  • No ruleset change: making acceptance required is the owner's.
  • No fix to any other writer's PR. The two gaps above are reported.
  • No README edit: the openDox root's README is T076's (openDox#17).

🤖 Generated with Claude Code

Summary by Sourcery

Add an isolated AT-R1 HTTP acceptance check that validates standalone openDox operation and secure console access in CI.

New Features:

  • Add an AT-R1 HTTP acceptance harness that installs openDox in an isolated environment, exercises standalone operation on clean plain repositories, validates served routes and console-token delivery, and verifies clean shutdown.
  • Add a dedicated CI acceptance job that runs the harness without database services or pytest.

Enhancements:

  • Expand deployment-shape checks to enforce the acceptance job’s isolation, single harness command, test-path exclusion, and dependency-lock usage.
  • Add in-process tests covering the harness’s bundle traversal, route validation, catalog checks, console-token security, and shutdown decisions.

CI:

  • Run the standalone HTTP acceptance checks in their own 20-minute GitHub Actions job alongside the existing validation suite.

Tests:

  • Add comprehensive unit coverage for acceptance-harness decisions and malformed or insecure server responses.

…n 034)

acceptance/at_r1_http.py is release 1's acceptance test, the HTTP half
(FR-011; spec.md section AT-R1, steps 1-4 and the route answers behind
steps 5-8). It is not a pytest module and sits outside tests/ and
tests_runtime/, so testpaths and F9.1 are unchanged. It:

- installs openDox alone into a fresh venv as opendox[local] (R1Q16 (iii)),
  from a copy of this checkout's tracked files, through the dependency lock;
- asserts the clean machine in a fresh, short OPENDOX_STATE_DIR: the four
  siblings not importable, no omp, no identity broker, no database on the
  default port or socket and no DSN, and no model binding;
- copies both plain repositories into fresh git inits with a git identity;
- runs the documented opendox generate-and-open --local ... (T007 batch H's
  10.3 addendum, as the openDox root README documents it) on loopback,
  checking its argv against that line;
- fetches /, /snapshot.json (non-empty, neutral per F5.3, a grouping tile)
  and /capabilities (install.mode == local);
- fetches the model catalog with the console token: no available entry;
- fetches every route the served bundle names, derived from the running
  server's module graph, and none may answer 5xx or drop the connection;
- stops the entry point with SIGTERM and asserts no bundled PostgreSQL
  process is left (R1Q16 (iv)).
It exits 1 on the first failed assertion, naming it, and 2 on a harness
error.

.github/workflows/validate.yml gains the acceptance job, which has no
database service, because the harness asserts a clean machine. It runs
no pytest and installs nothing itself. Making it a required check is a
ruleset change for the repository's owner.

tests_runtime/test_deploy_shape.py admits that one other job, holds it to
no service, no pytest and no pip step, and extends the one-lock rule to
the harness's install.

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 2, 2026 22:41
@sourcery-ai

sourcery-ai Bot commented Oct 2, 2026

Copy link
Copy Markdown

Reviewer's Guide

Introduces a dedicated, intentionally database-free CI acceptance job backed by a comprehensive standard-library AT-R1 HTTP harness, while extending workflow-shape tests to enforce its isolation, execution contract, and dependency-lock usage; the existing validate suite remains unchanged.

Sequence diagram for the AT-R1 HTTP acceptance harness

sequenceDiagram
    participant CI as acceptance job
    participant H as at_r1_http.py
    participant V as Fresh venv
    participant O as opendox
    participant R as Plain Git repository
    participant S as HTTP server

    CI->>H: Run python3 acceptance/at_r1_http.py
    H->>V: Create venv and install checkout[local] with lock
    H->>H: Assert clean machine and no database service
    H->>R: Create fresh repository and commit documents
    H->>O: Start opendox generate-and-open --local
    O-->>S: Serve repository
    H->>S: Fetch /, /snapshot.json, /capabilities
    H->>S: Fetch model catalog and derived bundle routes
    H->>O: Send SIGTERM
    H->>H: Assert no bundled PostgreSQL process remains
    alt Assertion fails
        H-->>CI: Exit 1 with named failure
    else All assertions hold
        H-->>CI: Exit 0
    end
Loading

Flow diagram for AT-R1 harness assertions

flowchart TD
    START["Start harness"] --> INSTALL["Install opendox[local] in fresh venv"]
    INSTALL --> CLEAN["Assert clean machine: no sibling tools, broker, DB, or bindings"]
    CLEAN --> REPOS["Create two plain Git repositories"]
    REPOS --> STARTAPP["Run documented local start"]
    STARTAPP --> CORE["Check HTML, neutral snapshot, grouping station, and local mode"]
    CORE --> CATALOG["Check model catalog has no available model"]
    CATALOG --> DERIVE["Derive module graph and request bundle routes"]
    DERIVE --> STOP["SIGTERM entry point and scan for leftover bundled PostgreSQL"]
    STOP --> RESULT{Assertions pass?}
    RESULT -->|Yes| PASS["PASS"]
    RESULT -->|No| FAIL["FAIL with assertion id"]
Loading

File-Level Changes

Change Details Files
Add a standalone AT-R1 HTTP acceptance harness that installs and exercises openDox on a clean Linux environment.
  • Build a tracked checkout into a fresh virtual environment and install the local extra through the CPython 3.12 dependency lock.
  • Assert isolation from sibling packages, external harnesses, identity services, database services, inherited environment settings, and model bindings.
  • Create two plain Git repositories and run the documented local server command against each.
  • Validate HTML, snapshot neutrality and grouping data, local install capabilities, no-model catalog behavior, derived frontend module and route availability, and clean shutdown without bundled PostgreSQL leftovers.
  • Provide fail-fast or keep-going diagnostics, Linux/process checks, cleanup, and explicit exit codes.
acceptance/at_r1_http.py
Run the HTTP acceptance harness in a dedicated CI job isolated from the database-backed validation job.
  • Add an Ubuntu Python 3.12 acceptance job with checkout and setup steps.
  • Run only the standard-library harness command, with no services, container, pytest invocation, or job-level package installation.
  • Leave the existing validate job and its required-check behavior unchanged.
.github/workflows/validate.yml
Pin the new workflow shape and dependency-lock usage in deployment-shape tests.
  • Allow exactly validate and acceptance workflow jobs.
  • Verify acceptance has no database service or container, does not run pytest or pip install, and invokes the harness once outside pytest testpaths.
  • Verify the harness installation references and passes the CPython 3.12 Linux constraints file.
tests_runtime/test_deploy_shape.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

The harness can miss broken static imports and misclassify malformed catalog responses as internal errors.

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

Open (2)
What changed in this PR

Adds the HTTP half of standalone openDox acceptance testing, separate from the existing validation suite. Acceptance is intentionally expected to fail until dependent features land.

Changes:

  • Adds a clean-install harness covering HTTP responses and shutdown.
  • Runs acceptance in a separate CI job without database services.
  • Extends workflow-shape tests to enforce isolation and dependency-lock usage.
File Description
tests_runtime/​test_deploy_shape.py Enforces acceptance-job configuration and lock usage.
acceptance/​at_r1_http.py Implements standalone HTTP acceptance checks.
.github/​workflows/​validate.yml Adds the isolated acceptance job.

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

Comment thread acceptance/at_r1_http.py Outdated
Comment thread acceptance/at_r1_http.py Outdated
… (SonarCloud)

SonarCloud's quality gate on openDox-code#75 failed on Security Rating D
for four findings in acceptance/at_r1_http.py, and flagged five
cognitive-complexity smells and one composite assertion.

- pythonsecurity:S8703, S8705, S8707: --port, --scratch, --state-base and
  --checkout flowed into a socket, the server's argv and the filesystem.
  The harness now takes no path and no port: it installs the checkout its
  own file is in (copy it into another tree to measure that tree), its
  scratch and state directories are fresh tempfile directories (TMPDIR
  chooses where), and the port is a free one found at run time. Only
  --keep and --keep-going remain.
- python:S5443: the /tmp/.s.PGSQL.5432 probe is gone. The clean-machine
  check probes the distribution sockets (/var/run/postgresql and
  /run/postgresql) beside TCP 5432 on both stacks; the bundled server
  never listens in either place.
- python:S3776: js_strings becomes the JsStrings lexer class, and
  serve_one, main and derive_bundle are split into one function per step.
  Behaviour is unchanged: the same 89 and 198 assertions hold at the
  local integration, the same falsifier fires at main, and the same five
  product mutants are killed.
- python:S9073: the acceptance job's service and container assertions
  are separate in tests_runtime/test_deploy_shape.py.

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 2, 2026 22:58

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

The harness can miss broken static imports and an existing PostgreSQL socket under /tmp.

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

Open (4)

Comment thread acceptance/at_r1_http.py
Comment thread acceptance/at_r1_http.py Outdated
…usal, and a malformed catalog is a named failure (Copilot)

Copilot's review of openDox-code#75 at 1c064bb:

- r4170450448: a module first reached by a dynamic import and refused
  was marked seen, so a later STATIC import of the same path was never
  judged, and a page that cannot load passed. derive_bundle now fetches
  and scans each path once but judges every (path, static) pair, so the
  static import must still answer 200. Proved over a crafted bundle
  (app.js imports missing.js dynamically, child.js statically): the
  reviewed head reports no failure; this head reports
  [t.bundle.module /missing.js].
- r4170450491: a 200 catalog of [], null or {"models": 1} raised inside
  the harness (exit 2) instead of failing a named check. Every nested
  read of the product's JSON (the catalog, /capabilities' install, display
  and views blocks, the snapshot's documents and groups) now goes through
  as_object / as_list, so each such answer fails [catalog is a catalog]
  or its own check, with no exception.

The local integration's verdict is unchanged: 89 held fail-fast, and
198 held with 4 failed under --keep-going, all at T084's two crash sites.

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 2, 2026 23:04

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

Snapshot checks can accept unusable document or grouping values and silently skip required HTTP requests.

Review effort: Balanced
Findings: 2 Medium severity

Open (2)
Resolved since last review (3)
Previously missed (1)

In code that hasn't changed since last review

Medium severity Accepts invalid grouping without required document

acceptance/​at_r1_http.py:1038

This truthiness check accepts both a non-list grouping value and a list such as [{"id":"topic","document_edges":[]}]. In either case, thread_query() returns None, so requests_for() silently omits the parameterized thread read. The query-less request stops at parameter validation and cannot expose the handler failure this harness is intended to catch. Require a grouping tile with an id and a member document before accepting this check.

Comment thread acceptance/at_r1_http.py Outdated
… of documents, a grouping tile with a member (Copilot)

Copilot's review of openDox-code#75 at 32ef3e8:

- r4170537350: a server listening only on /tmp's socket, with TCP off,
  passed the clean-machine check's fixed list of directories. The check
  now reads every listening `.s.PGSQL.*` socket from /proc/net/unix,
  wherever it lies, and requires that none answers in a directory other
  users can traverse, as every libpq default can (/tmp,
  /var/run/postgresql). A socket in a private (0700) directory is another
  install's own, reachable only through an explicit setting that no child
  inherits, so it is noted, not failed. No directory is hardcoded, so
  SonarCloud's S5443 stays clear. Proved with a listening fake socket: a
  shared directory fails [clean.database socket ...] here and passed at
  32ef3e8; a 0700 directory is noted.
- r4170567257: a truthy non-list documents value passed [snapshot
  non-empty]; it must now be a non-empty list.
- previously missed: [snapshot fills the grouping station] accepted a
  grouping value with no tile holding an id and a member document, which
  silently dropped the rail's parameterized thread read. It now requires
  exactly what that read is built from.

The local integration's verdict is unchanged: 89 held fail-fast, and
198 held with 4 failed under --keep-going, all at T084's two crash sites.

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 2, 2026 23:16

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 draft needs integrated verification with dependent product changes, and preparation-error handling remains unresolved.

Review effort: Balanced
Findings: None

Resolved since last review (2)
Previously missed (1)

In code that hasn't changed since last review

Medium severity Handle preparation errors with documented ERROR status 2

acceptance/​at_r1_http.py:1283

Filesystem errors from prepare() bypass this handler. For example, tempfile.mkdtemp() raising OSError(ENOSPC) escapes main() and exits with status 1, instead of the documented harness ERROR and status 2. This makes an infrastructure failure look like a product acceptance failure. Catch unexpected preparation exceptions and report status 2, as the later execution block does.

…t 2 (Copilot)

Copilot's review of openDox-code#75 at b4fc637 ("previously missed"):
an OSError from prepare(), such as ENOSPC under tempfile.mkdtemp,
escaped main() and exited 1, so an infrastructure failure read as a
product acceptance failure. main() now catches any unexpected exception
from prepare() and reports `AT-R1 HTTP half: ERROR` with exit 2, as the
execution block already does. Proved with mkdtemp patched to raise
ENOSPC: this head returns 2, while at b4fc637 the OSError escapes
main().

At the local integration with T084 (openDox-code#77 at b1db196) merged
in, the harness now PASSES: 202 assertions held, fail-fast and
--keep-going alike.

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 2, 2026 23: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

🔵 Needs a closer look

The substantial harness lacks automated graph-verdict regression coverage and a passing end-to-end result against the complete feature stack.

Review effort: Balanced
Findings: None

Previously missed (1)

In code that hasn't changed since last review

Medium severity Add behavioral regression tests for module-graph verdict

acceptance/​at_r1_http.py:661

The module-graph verdict has no automated behavioral regression tests. The new deployment tests inspect configuration and source text, while this branch's acceptance run stops at the missing local extra before reaching derive_bundle. Add collected, in-process tests for traversal cycles, route extraction, and the already-fixed case where a dynamic 404 precedes a static import of the same URL. Mocking get would exercise these decisions without installing or starting the product, while keeping the harness itself outside pytest collection. tests/test_smoke_signals.py provides a similar pattern for testing acceptance decision logic.

…s tests (Copilot)

Copilot's review of openDox-code#75 at b440d12 ("previously missed"):
the module-graph verdict had no collected regression test, and on this
branch the acceptance job stops at the missing `local` extra, before
derive_bundle ever runs.

tests/test_at_r1_http_harness.py loads acceptance/at_r1_http.py from its
file and tests its decisions in process, as tests/test_smoke_signals.py
tests the browser half's oracle. An in-process loopback server stands in
for the product (the pattern test_model_provider_broker.py uses), so
nothing is installed or started. 13 cases:

- the lexer skips comments and regular expressions;
- an import specifier is told from a route literal, and a route resolves
  against the page;
- the module graph terminates on a cycle and counts each module once;
- a dynamic refusal does not hide a static import of the same path
  (r4170450448), and a dynamic refusal alone is not a failure (10.2a);
- a malformed 200 catalog is a named failure, never an exception
  (r4170450491), in seven shapes;
- the documented start is the README line less its ellipsis.

Four mutants of the harness, each killed by its case: a path judged once
by path alone, every `/` read as a division, the catalog read without a
shape check, and a stylesheet path taken for a route. The harness stays
outside testpaths, so F9.1 is unchanged. tests/: 2450 passed, 11
skipped, locally.

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 2, 2026 23:52

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 catalog check accepts envelopes the chat rail rejects, allowing a false acceptance result.

Review effort: Balanced
Findings: None

Previously missed (1)

In code that hasn't changed since last review

Medium severity HTTP check accepts catalogs with invalid envelope metadata

acceptance/​at_r1_http.py:1106

An HTTP 200 response containing {"models": []} passes every catalog check, even if kind and schema_version are missing or incorrect. However, adoptCatalog requires schema_version === 1 and kind === "workbench-model-catalog" (src/opendox/web/views/doxbench-chat-model.js:228–235); the chat rail treats a rejected envelope as unreadable. This lets the HTTP acceptance check pass a catalog the UI cannot use. Require both envelope fields in this named check, update the successful catalog fixtures to use valid envelopes, and add missing/wrong kind and version cases.

…dopts (Copilot)

Copilot's review of openDox-code#75 at f0e0ffe ("previously missed"):
a 200 catalog of {"models": []} passed every catalog check, though the
chat rail's adoptCatalog (views/doxbench-chat-model.js) adopts only
schema_version === 1 and kind === "workbench-model-catalog", and shows
any other catalog as unreadable, never as its no-model state.

- A named check, [catalog envelope is the one the chat rail adopts],
  requires schema_version to be the integer 1 (as JavaScript's === 1,
  so no true and no "1") and kind == "workbench-model-catalog".
- So that constant cannot drift from the bundle, the module-graph walk
  also collects every string literal, and [bundle names the catalog kind]
  requires the served bundle to name it, as [bundle names the catalog
  route] already does for the route.
- tests/test_at_r1_http_harness.py: the passing catalogs carry a valid
  envelope, and seven envelopes adoptCatalog refuses are added: missing
  kind, missing version, version 2, true, "1", and a different kind.
  Relaxing the int check fails the `true` case.

At the local integration with T084 (#77 at b1db196), the product's real
catalog passes: 206 assertions held, 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>
Copilot AI balanced review requested due to automatic review settings October 3, 2026 00:01

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 encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

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

Console-opener validation can pass delivery that the browser rejects.

Review effort: Balanced
Findings: 2 High severity

Open (2)
Resolved since last review (4)

Comment thread acceptance/at_r1_http.py Outdated
Comment thread acceptance/at_r1_http.py
Copilot AI balanced review requested due to automatic review settings October 3, 2026 18:20
…page read them (plan 034)

Copilot review of openDox-code#75 at 5636eb8:
- r4173894317: the refresh delay was ignored, so `content="invalid;url=…"`
  or `"-1;url=…"` yielded a token, though a browser aborts that refresh
  and leaves the user on the opener. `refresh_target` now follows the
  HTML standard's shared declarative refresh steps for the forms a refresh
  takes. The delay must start with an ASCII digit or `.`. A `,` or `;`
  separator and the `url =` keyword, in any case and spacing, are
  optional. A quoted URL ends at its closing quote. A refresh with no URL
  part opens no console.
- r4173894352: any non-empty fragment value was taken as the token, but
  T104's `takeDeliveredConsoleToken` (web/views/notebook.js) discards a
  value that is not `[A-Za-z0-9_-]{16,512}`. The harness could then
  authenticate where the user's page cannot. The forward's token must now
  match that shape exactly (CONSOLE_TOKEN_SHAPE), and the message does
  not quote the value.

The tests add 14 cases. Four refresh forms a browser follows: comma and
spaces, a fractional delay, no `url` keyword, and a quote that truncates.
Four delays that abort the refresh: a word, a negative, an empty delay,
and a bare `url=`. Four tokens the page discards: too short, too long,
dots, and an escaped `+`. Two tokens at the page's bounds, 16 and 512
characters. Seven mutants are each killed.

At the local integration with T104 (#84 cb89954), the real opener passes
(r37: PASS, 302 assertions held).

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 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 harness accepts non-console opener destinations, allowing a false acceptance result.

Review effort: Balanced
Findings: None

Resolved since last review (6)
Previously missed (1)

In code that hasn't changed since last review

Medium severity Destination validation accepts non-console paths

acceptance/​at_r1_http.py:1429

The destination check validates the authority but not the page path. With a matching JSON record, both /missing.html#console_token=… and /snapshot.json#console_token=… pass every opener assertion. Later checks request / and the catalog independently, so acceptance can pass while the user's browser opens a missing page or JSON instead of the console. Require / or /index.html, and add regression cases where both the forward and record name a non-console path.

Copilot AI balanced review requested due to automatic review settings October 3, 2026 18:31
Copilot review of openDox-code#75 at 486e426, previously missed: the
destination check validated the authority but not the path. With a
record that agreed, `/missing.html#console_token=…` or
`/snapshot.json#console_token=…` passed every opener assertion, while the
user's browser would open a missing page or JSON instead of the console.
The forward's path must now be one of CONSOLE_PAGES, `/` or the
`/index.html` that T104's entry points open. An empty path is `/`, as a
browser reads it. The message does not quote the path.

The tests add 5 cases: `/missing.html`, `/snapshot.json` and
`//index.html` are refused; `/` and an empty path are accepted. Three
mutants are each killed: no path check, an empty path refused, and `/`
not a console page.

At the local integration with T104 (#84 cb89954), the real opener passes
(r38: PASS, 302 assertions held).

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 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

The harness can falsely accept broken console delivery, external dependencies, unsuccessful shutdowns, and encoded token leaks.

Review effort: Balanced
Findings: 1 High severity

Open (1)
Previously missed (3)

In code that hasn't changed since last review

Medium severity Reject IPv6 opener targets when launch binds only to IPv4

acceptance/​at_r1_http.py:220

launch() uses the default IPv4-only bind (127.0.0.1), but this allows an opener targeting [::1]:<port>. With an agreeing record, that opener passes delivery validation even though it cannot reach the launched server. Later catalog and route probes use IPv4 directly, so they do not catch the broken destination. Restrict opener hosts to those supported by this launch and add an IPv6-forward regression case.

Medium severity Preserve origins and reject unsupported external dependencies

acceptance/​at_r1_http.py:677

Discarding the origin makes external dependencies look local. For example, a static import of https://unreachable.invalid/app.js resolves here to /app.js. The walker can reuse the successful local response and pass, although the browser cannot load the external module. Script roots and stylesheets use the same resolver. Preserve the origin during resolution and report a named acceptance failure for unsupported external dependencies rather than fetching their paths from loopback. Add regression cases for external imports and page links.

Medium severity Require zero shutdown status for a passing lifecycle test

acceptance/​at_r1_http.py:1641

Any exit status passes this check, including 1 or -SIGTERM. If shutdown fails after removing the opener and stopping PostgreSQL, the harness can still report PASS. The existing lifecycle test requires status zero (tests_runtime/test_bundled_postgres.py:440). Require rc == 0 here and add a decision test for a nonzero shutdown status.

Comment thread acceptance/at_r1_http.py Outdated
Copilot AI balanced review requested due to automatic review settings October 3, 2026 18:46
… a failed stop are named failures (plan 034)

Copilot review of openDox-code#75 at 142d135:
- r4174355680: the duplicate-token check searched the raw query, so
  `?extra=<percent-encoded token>#console_token=<token>` passed, though
  the request line still carries a recoverable copy. The path and query
  are now also searched in every percent-decoding (`_decodings`, with
  `+` read as a space and not, until nothing new appears).
- previously missed: a forward to `[::1]` passed, though the launched
  plane listens on 127.0.0.1 alone. The forward's host must now be one
  the plane answers on. `served_loopback_hosts` probes 127.0.0.1 and ::1
  at the server's port, and adds `localhost` where one answers.
- previously missed: `_resolve` discarded the origin, so a static import
  of `https://unreachable.invalid/app.js` was fetched as `/app.js` from
  loopback and passed. It now keeps any URL that leaves the plane whole.
  A module or stylesheet from outside the plane is a named failure,
  `[bundle.external <url>]`, and is never fetched from loopback.
- previously missed: any exit status passed the stop. It must now be 0,
  as tests_runtime/test_bundled_postgres.py requires of the same stop:
  `[stop exits 0]`.

The tests add 19 cases: an encoded query, a double-encoded query and an
encoded path; an unserved `[::1]`; a served `[::1]`; the host probe;
three imports from outside the plane (static https, dynamic
protocol-relative, a data: URL); page links from outside the plane;
and four stop statuses (0, 1, -15, timeout). Eight mutants are each
killed.

At the local integration with T104 (#84 cb89954), the real plane passes
(r39: PASS, 302 assertions held, `[a.stop exits 0]` and `[b.stop exits 0]`
among them).

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 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

The harness can accept browser-invalid imports and hang while reading a rejected FIFO opener.

Review effort: Balanced
Findings: 1 High severity

Open (1)
Resolved since last review (1)
Previously missed (1)

In code that hasn't changed since last review

Medium severity Prevent FIFO hangs during file opening

acceptance/​at_r1_http.py:1407

With --keep-going, a FIFO opener fails the privacy check but is still passed to this blocking os.open. If it has no writer, the harness hangs instead of reporting its verdict or reaching cleanup. A bounded probe confirms the hang after the non-regular-file rejection. Open nonblocking and check that the opened descriptor is a regular file before reading; add a FIFO regression case.

Comment thread acceptance/at_r1_http.py Outdated
brettheap and others added 2 commits October 3, 2026 19:52
…ures (plan 034)

Copilot review of openDox-code#75 at 2747949:
- r4174411680: a bare specifier such as `import "child.js"` resolved to
  /child.js and passed when that file was served. A browser with no
  import map refuses it: the HTML standard's "resolve a module
  specifier" accepts only a URL, or a specifier that starts with `/`,
  `./` or `../`. A bare specifier, static or dynamic, is now
  `[bundle.bare <specifier>]`, and is never fetched by its path
  (`_specifier`, BARE_PREFIX).
- previously missed: under --keep-going, a FIFO at the opener's path
  failed the privacy check but was still opened with a blocking
  os.open. With no writer, that hung the harness before its verdict
  and its cleanup. `_read_without_following` now opens with O_NONBLOCK
  and reads only what fstat calls a regular file.

The tests add 10 cases:
- four bare specifiers (static, dynamic, export-from and a package
  name), each with the file served;
- three relative specifiers that still resolve (./, ../ and /);
- a FIFO opener, which must fail as not private and not delivering,
  within a bounded wait;
- a FIFO that a gone writer has filled with a page, which must be
  refused and not read.
Five mutants are each killed: a bare specifier resolved, a bare one
not judged, any slash read as relative, a blocking open, and no
regular-file check.

At the local integration with T104 (#84 c979747, on a tree identical
to main 390e2c2), the real plane passes (r43 fail-fast and r44
--keep-going: PASS, 302 assertions held). Without T104, main's own tree
fails in 6 checks, all where T104 is missing (r45).

Arc: neutral-product-standalone-operability
Lane: openxfactory-4 (openXfactory-4-openDox_extraction)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Brings in #77 (T084) and #80 (T103). No file this branch edits changed
on main; the merge is clean.

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 19:53

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

Harness diagnostics can expose console tokens, and malformed snapshot entries can bypass document-specific acceptance checks.

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

Open (2)
Resolved since last review (1)

Comment thread acceptance/at_r1_http.py Outdated
Comment thread acceptance/at_r1_http.py
Copilot AI balanced review requested due to automatic review settings October 3, 2026 20:07
…ment names its path (plan 034)

Copilot review of openDox-code#75 at 33841d4:
- r4174621486: `Server.said()` put the tail of the entry point's stdout
  and stderr into start and route failures. If the entry point printed
  its console token and then failed, the harness published that token
  in its own log before the after-stop check could catch it. A
  diagnostic now names where the output is (both files), says why it
  is not echoed, and points at --keep. `printed()` still reads all of
  it for the after-stop check.
- r4174621535: `documents: [{}]` passed `snapshot non-empty`, while
  `requests_for` dropped every entry without a path and so skipped
  every document's source reads. New check, [snapshot documents each
  name a path]: every entry must be an object with a non-empty string
  `path`.

The tests add 7 cases: six document lists (named; an empty object; an
empty path; a numeric path; a string entry; one of two pathless) and a
diagnostic that must name both files and quote no token, while
printed() still holds the token. Four mutants are each killed: the
output echoed, the path check dropped, an empty path admitted, and a
path of any type admitted.

At the local integration with T104 (#84 c979747, on a tree identical
to main 390e2c2), the real plane passes (r46 fail-fast and r47
--keep-going: PASS, 304 assertions held, the two new ones among them).

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

sonarqubecloud Bot commented Oct 3, 2026

Copy link
Copy Markdown

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Verified false-positive acceptance checks remain in console-opener navigation and directory privacy validation.

Review effort: Balanced
Findings: 2 High severity

Open (2)
Resolved since last review (2)

Comment thread acceptance/at_r1_http.py Outdated
Comment thread acceptance/at_r1_http.py Outdated
brettheap and others added 3 commits October 3, 2026 20:45
…, and no line quotes a token (plan 034)

Copilot review of openDox-code#75 at 4809b3d:
- r4174671390: a refresh inside `<template>` or `<noscript>` was counted
  as the opener's forward, though a browser that runs scripts never acts
  on it. `_RefreshContents` now reads only LIVE elements: nothing inside
  a template, a noscript or a raw-text or escapable raw-text element
  (title, style, script, textarea, xmp, iframe, noembed, noframes,
  plaintext), and, failing closed, select and frameset. The first of a
  repeated attribute is read, `http-equiv` is `refresh` exactly, and
  `/>` closes only a void element.
- r4174671426: the privacy check stopped at `console/`. The opener's whole
  path is now judged by T104's own rules for that tree: the state
  directory is this user's and no one else can write it; every directory
  above it, as written and as resolved, is this user's or root's, sticky
  where others can write it; every symbolic link on the way is this
  user's or root's. `prepare()` refuses a TMPDIR that fails them (exit 2),
  so the verdict judges what the product made.

Carried from r4174621486 to every diagnostic: `Verdict` redacts every
console token the run has seen (the opener's forwards and record, and a
token `/capabilities` publishes) from every id, reason, note and the
report; the printed opener path is quoted only where it is the expected
one; a record nested past the JSON parser's depth is a named failure.

Tests: 68 new cases (215 harness cases; 326 with deploy-shape). Decision
mutants lv1-lv9, tr1-tr8, rd1-rd9: 25 of 26 killed at the named cases;
lv2 survived the first textarea case (this Python already parses textarea
as text) and is answered by the noscript and iframe cases.

At the local integration with T104 (#84 60bace0, which merges main
390e2c2), the real plane passes (r48 fail-fast: PASS, 304 assertions
held).

Arc: neutral-product-standalone-operability
Lane: openxfactory-4 (openXfactory-4-openDox_extraction)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… by name nor by value, and console/ is 0700 exactly (plan 034)

T007 batch N (openxFactory bdd0f586; RULED 5963851934) amends T095: "As
quickstart § 3 does, it asserts that the raw `/capabilities` payload
carries the console token neither by name, at any depth, nor by value".

- By name: `[capabilities carries no console token]` (the id CI's
  by-design failure already names) now fails where `console_token` is
  anywhere in the RAW payload, as § 3 checks it, or in any key of the
  parsed one, however deep or however escaped. It read a top-level key
  only.
- By value: `[capabilities carries the opener's token nowhere]`, run
  once the opener has handed the harness the token: the token is
  nowhere in the raw payload, nor in any key or string value of the
  parsed one, under whatever name. Neither is quoted.
- The opener's directory is 0700 exactly, as § 3 checks it (it was "no
  bits for others"). The catalog header is sent in process, so the token
  is on no command line, as § 3 asks.

Tests: 15 new cases (230 harness cases; 341 with deploy-shape).
Mutants rp1-rp6 and dm1 are each killed at the named cases. Product
mutant m12 (#84 d4b9943 with the token under `session_hint`) fails
`[a.capabilities carries the opener's token nowhere]` (r51), and prints
no token.

Runs: with T104 (#84 d4b9943, which merges main 0116293), r49
--keep-going: PASS, 306 assertions held. Without it (main 0116293), r50
--keep-going: the known 6 failures, all where T104 is missing.

Arc: neutral-product-standalone-operability
Lane: openxfactory-4 (openXfactory-4-openDox_extraction)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Brings in #81 (T102). No file this branch edits changed on main; the
merge is clean.

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 21:51

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

Verified token exposure in diagnostics and unhandled JSON nesting errors remain unresolved.

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

Open (2)
Resolved since last review (2)

Comment thread acceptance/at_r1_http.py
install_block = as_object(caps.get("install"))
verdict.check(f"{label}.capabilities install.mode == local",
install_block.get("mode") == "local",
f"the served install block is {caps.get('install')!r}")
Comment thread acceptance/at_r1_http.py
self.error = error

def json(self):
return json.loads(self.body.decode("utf-8"))
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