T058, the post-render validator in the generate verbs (7.2 part) (plan 034) - #68
Merged
Merged
Conversation
…an 034) tests/fixtures/plain-documents/ carries eight .md documents: three sources (no `stage:` header, two sharing the phrase "rain barrel" so a future topic-based grouping pass has a pair to find) and one document each declaring `stage: grouping`, `stage: candidate`, `stage: selection`, `stage: submission` and `stage: completion` (RULED R1Q13 (a) with (c), openxFactory#656 comment 5850003126). Every document carries the small neutral field set the default adapter will require regardless of station (`title`, `summary`). tests/test_plain_documents_fixture.py is the vocabulary test T050 names as its falsifier: it asserts the fixture carries none of the eight controlled `Status:` words or the change/spec/delta nouns, that it covers all six stations with exactly one document per explicit station, and that at least two (but not all) sources share the topic fixture code will need to group on. Not yet wired into .github/workflows/validate.yml's explicit pytest list: phase2-ahead scope keeps this PR out of that file, the pin files and conftest.py/pyproject.toml/README.md, which the phase-1 chain still edits. This suite runs by node id today. Arc: neutral-product-standalone-operability Lane: openxfactory-4 (openXfactory-4-openDox_extraction) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…erator (plan 034) Box 5.4 of openxFactory's add-neutral-product-standalone-operability: "DECLARE THE GENERATOR SEAM - it does not exist and CorpusAdapter is not it." This declares it, in src/opendox/generator_seam.py, beside domain_profile.register(): - the operation handed over: generate(repo_root, repository, *, source_revision=None, generated_at=None, **inputs) -> the snapshot; - the registration point: register(<SnapshotGenerator>) for a host, and register_default(...) for an entry point, which registers only where nothing is registered; - the conformance a contributed generator must meet. It declares the contract it writes and every further input it reads, its operation takes the seam's call, and it answers a snapshot whose kind is that contract and whose schema_version is an integer. The seam checks the declaration when it is made and each snapshot when it comes back, and it refuses, never drops, an undeclared input. The conformance clause names T053's neutral snapshot kind, "opendox-snapshot" (openDox-spec#16), for openDox's own generator: src/opendox/default_generator.py's GENERATOR. The entry points (cli.build_parser, cli.main, serve.build_server, serve.main) register it where no host has (R1Q10 (a), openxFactory#656 comment 5850003126, in R1Q3 (a)'s pattern). A bare process still refuses, naming the seam and the call. A host replaces the default only before a snapshot has been generated from it. CorpusAdapter stays closed at six members. openDox's own generator refuses until T054 builds its projection. Plan 034 orders T052 before T054, and T054 edits neither cli.py nor serve.py (their single-writer order runs T052, then T055). No verb reaches the seam before T055 routes the generate verbs, which comes after T054. Falsifier: the seam tests, tests/test_generator_seam.py (59 cases). F5.2 is quoted by T059 and T061. Arc: neutral-product-standalone-operability Lane: openxfactory-4 (openXfactory-4-openDox_extraction) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…nce it answers Copilot's review of this PR at 834f8ea raised two findings. Both are addressed here. 1. A declared input the operation cannot do without was accepted when it was declared. SnapshotGenerator bound the seam's call with every declared input given. But generate() passes an input only when its caller has a value for it, so such an operation failed with a raw TypeError the first time the option was unset. The declaration now binds the call twice, once with every declared input given and once with none given, and every call the seam can make lies between the two. Conformance clause 2 now says that each declared input is optional. 2. The default's window closed before its operation ran. generate() recorded a generation from the entry point's default before the call. So a generation that failed shut every host out although it wrote nothing, and that included openDox's own refusal before T054. The record is now made once a conformant snapshot comes back. A generation that fails, or whose answer the seam refuses, records nothing. While a generation from the default is under way, a host's registration is refused, because that snapshot would come back after the swap. The early record gave that property. A count of the generations under way now holds it. The registry's bookkeeping moves under one threading.Lock, because serve.py's ThreadingHTTPServer answers each request on a thread of its own. generate() holds the lock only to resolve and mark, and to record the end, and never across the generator's call. So a generator may register, unregister or generate from inside its own call. Tests: 59 -> 66 cases. Against 834f8ea's seam logic, 8 of the 66 go red: the new declared-input case, the pre-T054 default case, the two wrote-nothing cases, the three under-way cases and the re-entrant case. The concurrency and re-entrancy cases run in a process of their own with a time limit, so a lock held across a call fails them rather than hanging the suite. 13 of 13 mutations are caught. Arc: neutral-product-standalone-operability Lane: openxfactory-4 (openXfactory-4-openDox_extraction) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… (plan 034) tests/fixtures/malformed/ carries two .md documents: notes-bee-boxes.md is an ordinary valid source (non-empty title and summary), and notes-empty-title.md declares `title:` with nothing after the colon -- present in the header, but an empty string once parsed -- while its `summary:` stays ordinary. That is exactly one violation of the neutral snapshot schema T053 adds (opensoft/openDox-spec#16 at cd49eb25): `documents[].title` and `.summary` are each `type: ["string", "null"], minLength: 1` (x-rule `title-and-summary-are-text`, "each non-empty text, or null"). A document that declares no title/summary line at all yields null, which is valid (openDox-spec's own no-front-matter example); an empty string is the one value that is neither null nor non-empty text. Holder decision: T054 copies title/summary verbatim, without coercing an empty declared value to null or excluding the document, so the violation survives unchanged into the generated snapshot. tests/fixtures/malformed/EXPECTED_RULE holds the violated rule's identifier verbatim, `title-and-summary-are-text`, for the future `opendox generate --strict` to name (spec.md AT-R1 scenario 2). T051 carries no falsifier of its own (tasks.md: "used by F7.2"); this PR adds only the fixture and the sentinel file. Arc: neutral-product-standalone-operability Lane: openxfactory-4 (openXfactory-4-openDox_extraction) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
T054's branch stacks on T052's (openDox-code#54, build/034-p2g-t052-generator-seam at 4ca45d2), which was cut from main 80acead. main has since landed T036 (the required check runs the whole suite) and T037 (the margin), so this takes main at 2d11641, and the PR is measured against the whole-suite check it will be judged by. No conflict: T052's five files and main's changes are disjoint. Arc: neutral-product-standalone-operability Lane: openxfactory-4 (openXfactory-4-openDox_extraction) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…9560ee) into T054's branch T054's falsifier runs the neutral projection over T050's tests/fixtures/plain-documents, so this branch carries that fixture until #53 lands on main. The merge adds only #53's files. Once #53 lands, they drop out of this branch's diff against main. Arc: neutral-product-standalone-operability Lane: openxfactory-4 (openXfactory-4-openDox_extraction) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… into T054's branch The holder ruled that T054 copies title and summary without coercing or excluding them, so T051's malformed fixture keeps its one title-and-summary-are-text violation in the snapshot. T054 proves that over T051's tests/fixtures/malformed, so this branch carries that fixture until #56 lands on main. The merge adds only #56's three files. Arc: neutral-product-standalone-operability Lane: openxfactory-4 (openXfactory-4-openDox_extraction) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…usAdapter (plan 034) A checkpoint commit, pushed on the coordinator's usage stop. It is not the finished task: four node-harness cases in tests/test_display_facet.py still carry the governed snapshot values in their fixtures and fail against the new defaults, and T054's own test file is not written yet. What this commit holds: - src/opendox/neutral_projection.py (new): the projection. It writes T053's opendox-snapshot, places a document by its neutral stage: key (reading a value outside the six role keys as a source and reporting it), copies title and summary verbatim, and applies the topic rule and the group rule its docstring names. - src/opendox/default_generator.py: generate() projects the home corpus instead of refusing. NeutralProjectionNotBuilt is retired, and so is its refusal case in tests/test_generator_seam.py. - src/opendox/display_profile.py and web/views/display.js: SNAPSHOT_VALUES' defaults become the neutral snapshot's values. display.js keeps its line count, so the web census row is unchanged. - src/opendox/runtime/local_git_adapter.py: leading_header() is public, NEUTRAL_FIELDS is declared, WorkingTreeCorpus defaults required_fields to it, and the suffix branch of classify reports missing fields when fields are required. Arc: neutral-product-standalone-operability Lane: openxfactory-4 (openXfactory-4-openDox_extraction) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ay-facet cases (plan 034) This finishes the work 25fe752 checkpointed. The holder ruled on two of T054's open questions, relayed by the coordinator: - An entry the adapter cannot classify (a Makefile, an image) is not a document. The projection now leaves it out unread, and nothing mentions it. - The wheel's grouping tile counts a group's edges, and the neutral schema is not widened with a tally. views/wheel-model.js now counts document_edges where a group carries no tallies.document_links, and uses the tally where one exists, as the governed snapshot's groups do. The file keeps its 1358 lines, so its census row is unchanged. tests/test_neutral_projection.py (new) holds T054's falsifier: - in a fresh process with every sibling blocked, the CLI entry point's defaults generate over T050's fixture through the seam, and the result validates against T053's schema with no F5.3 word; - the topic rule groups a copy of AT-R1's repository (b), which has no front matter; - a stage: value outside the six is reported, naming the document, the value and the six keys, and is read as a source. Around them it tests: - the holder's T051 rule (the malformed fixture breaks only its EXPECTED_RULE); - what a document is, the stations, and the anchors; - the default adapter's field set through the entry point; - the neutral display values in Python and in display.js, and the wheel's edge count; - that the projection makes no reach. The schema is a byte-identical copy of openDox-spec#16's at cd49eb25, held to its sha256, until T057 ships the packaged one. tests/test_display_facet.py: four node-harness cases carried the governed snapshot values in their fixtures. Each now reads the value openDox ships (SNAPSHOT_VALUES), and the property each one tests is unchanged. The canvas case's neutral assertion no longer compares against a value the neutral install can never write. It now asserts openDox's own word. Arc: neutral-product-standalone-operability Lane: openxfactory-4 (openXfactory-4-openDox_extraction) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…(plan 034) validate failed at 0f42f67 in one case, test_the_anchors_come_from_the_source_revision_and_the_bytes_repeat. CI's git prints `%cI` for a zero offset as `2026-09-27T12:00:00Z`, and the git this was written against (2.43.0) prints `2026-09-27T12:00:00+00:00`. The projection was right: it records the stamp exactly as git gives it, and the neutral schema admits both spellings. The test compared that stamp against the fixture's date as a string. It now compares the two as instants. The case's first assertion still holds generated_at to the checkout's own `git show` output, so a date read from anywhere else is still caught. Arc: neutral-product-standalone-operability Lane: openxfactory-4 (openXfactory-4-openDox_extraction) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…what is true now (plan 034) Copilot's review of 0f42f67 left three threads. - r4117298988 (neutral_projection.py): a `topics:` header that listed nothing fell back to the derived topics, which contradicted the rule the module's own docstring states. The header is now a declaration even when it is empty. The document carries no topic, nothing is derived for it, and it joins no group. That follows the holder's literal-copy principle for title and summary. New case: test_an_empty_topics_header_declares_no_topic. With the old rule restored, it fails. - r4117299013 and r4117299033 (tests/test_plain_documents_fixture.py, T050's file, merged here from #53): its docstring said T052 and T054 had not landed. It also said the suite was run by node id outside validate's explicit list. T054 makes the first false. The second has been false since T036 (#52), when validate began running the whole suite. Both paragraphs, and the two phrases beside them ("a future topic-based grouping pass", "will require"), now say what is true. Only the docstring 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>
…snapshot (plan 034) Copilot's review of c0747e9 raised two points. - Its overview said digit-adjacent topic names were tokenized wrongly, and they were. The docstring defines a word as a run of three or more letters, but the tokenizer took letter-and-digit runs and then dropped any run holding a digit, so `Q3planning` lost `planning`. Letter runs and digit runs are now separate tokens. New case: test_a_word_is_a_run_of_letters_even_beside_digits. With the old tokenizer restored, it fails. - r4117331489 asks that a supplied source_revision be passed into the CorpusRef. That is not taken, and the answer is on the product's own contract: - the seam defines source_revision as the source anchor to pin, and the CLI's help for --source-revision says "pin the source_revision anchor"; - openXdox's governed generator records a supplied revision and scans the tree it is handed; - the holder ruled for T054 that content comes from the working tree, per Brett's T022 ruling. default_generator's docstring now says so in a paragraph of its own. Only the docstring changes for it. Arc: neutral-product-standalone-operability Lane: openxfactory-4 (openXfactory-4-openDox_extraction) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
T052's branch was cut from main 80acead. main has since landed T036 (the required check runs the whole suite) and T037 (the margin). This takes main at 2d11641, so the PR is measured against the whole-suite check it will be judged by. There is no conflict: T052's five files and main's changes are disjoint. 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#57 at c0747e9 said that the generator seam's "replacement-generator tracking can reject valid registrations". It opened no thread. The claim is real, and there are two cases, both reproduced at 4ca45d2: 1. The count of generations under way was global, not tied to the registration it counted. A generation from a default that unregister() had dropped kept counting against whatever default was registered next. So a host was refused over a fresh default with "a snapshot is being generated from it now", although nothing was. 2. A generation that outlived its registration recorded its late answer against a fresh registration of the same declaration. That shut the fresh window, against unregister()'s own promise that the record of a generation goes with the registration. Now every change of registration goes through one helper: register(), register_default() where it registers, and unregister(). The helper moves a registration serial on and starts the two records afresh. A generation carries the serial it began under. When it ends, it touches the records only if that registration is still current. The generation is not stopped, and its caller still gets its snapshot. Tests: 66 -> 70 cases. The new case test_a_generation_that_outlives_its_registration_touches_no_later_one runs in four variants, in a process of its own. The follower is another default or the same declaration, and the host registers either while the generation runs or after it answers. Three variants are red against 4ca45d2's seam logic, and the fourth guards the new scoping. test_unregister_clears_the_default_and_its_generation now registers the same default afresh before the host tries. 15 of 15 mutations are caught. Arc: neutral-product-standalone-operability Lane: openxfactory-4 (openXfactory-4-openDox_extraction) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
T054's branch (openDox-code#57) stacks on this one and rewrites the last sentence of point 6 of tests/test_generator_seam.py's docstring. The previous commit re-wrapped that same paragraph, so taking this branch would have conflicted there. Point 6 is restored to its 4ca45d2 text, and the new sentence moves to point 5, ONE REGISTRATION, where it belongs. A trial merge of this head into #57's head 5a6fe63 is clean, and 94 seam and projection cases pass in the merged tree. 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 this PR at bce09c5 (r4117421102) found that current() read _registered twice: once for its None check, and once for its answer. A thread that unregistered between the two reads made it answer None after passing the check, against its SnapshotGenerator-or- refusal contract. generate() calls it while holding the seam's lock, so that path was safe, but a bare caller was not. current() now reads the registration into a local once, and answers that local. It still takes no lock of its own, because generate() holds the seam's lock, which is not re-entrant, when it calls it. Test: test_current_answers_the_registration_it_checked forces the interleaving deterministically. A line tracer drops the registration before every line of current() after its first. The test is red against 97b5b01's seam logic ("current() answered None after passing its check") and green here. 16 of 16 mutations are caught, the new M16 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>
…d507c5) into T055's branch T055's branch stacks on T054's (openDox-code#57, build/034-p2p-t054-neutral-projection at 5a6fe63), which stacks on T052's branch at 4ca45d2. T052 has since moved on to 8d507c5: each registration keeps its own records (bce09c5), the docstring point T054 edits (97b5b01), and current() reads the registration once (8d507c5). T055 routes the generate verbs through that seam, so this takes the seam as it now stands. T052's writer trial-merged 8d507c5 into 5a6fe63 without a 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>
… 034) #1144's 7.1, 7.1a, 7.1b and 7.2, with 7.1 as T007's batch G amends it (R1Q11 (a) and R1Q12 (a), openxFactory#656 comment 5850003126). - src/opendox/contracts/ holds the four packaged copies: ideation-workbench, opendox-snapshot, xfactory-workbench-chat-turn and xfactory-workbench-model-catalog. Each is byte for byte openDox-spec's file at cd49eb25, the head of openDox-spec#16 (T053). copies.yaml is the record that pins each copy's sha256. A copy is proved against it before a byte of the copy is read, and a changed, absent or unpinned copy is refused. - src/opendox/validator.py is the validator, new surface at the code leg (7.2). It evaluates JSON Schema 2020-12, exactly the keywords the four use. A refusal names its rule as [<x-rule>]. It implements the neutral snapshot's seven reference rules, and it refuses a copy that uses anything it does not evaluate. Its docstring records why the consumer's script cannot be reused (7.1a), measured at openXdox-code 4610bca5. - tests/test_validator_input_set.py is the falsifier: the packaged-copy digest test and the 7.1b test, with the identity checks. - tests/test_validator.py tests the evaluator over openDox-spec's own 47 examples, which cover all 32 rules, keyword by keyword, and over openDox's own projection of T050's and T051's fixtures. tests/fixtures/spec-examples/ is that corpus. Held, and reported to the holder: the package-data line in pyproject.toml for opendox.contracts. The phase-2 scope rule keeps drafts out of that file. Arc: neutral-product-standalone-operability Lane: openxfactory-4 (openXfactory-4-openDox_extraction) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…an 034) A copy of gate-intent planted under src/opendox/contracts/schemas/ failed the 7.1b test as "assert not True". Each of its four assertions now names what it found: a kind, a kind's copy, a pinned copy, or a carried file. 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) CI's whole suite failed one case, because the two ideation-workbench examples copied from openDox-spec were tracked: tests/test_workbench.py::test_no_workbench_manifest_is_tracked_in_this_repo. The leg's committed-manifest guard, workbench.committed_manifests, refuses a workbench manifest tracked outside examples/, and it is right to. So those two examples leave the corpus. A local run had passed only because it ran before the files were committed. The kind is held instead over the manifests openDox's own workbench.Workbench writes: one of each seed kind, carrying every member route, an exclusion, every action and a notebook binding. An override with no recorded reason is refused as [required] at its member. 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 holder has decided that the package-data line goes in this PR now. openDox-code's phase-1 work has landed, so no phase-1 writer edits pyproject.toml any more. 7.1 settles that the four copies travel as package data, "so `pip install openDox-code` puts them on disk beside the validator". Until now the table named only `web/**`, so a wheel carried opendox/contracts/__init__.py with no record and no copy. Every installed validator then refused, naming the absent record. The new key, "opendox.contracts", names copies.yaml and schemas/*.schema.yaml. It is a separate key, because test_gate_loop_contributed holds the bundle's `opendox = ["web/**"]` verbatim. test_the_package_data_ships_the_record_and_every_copy holds the table to the record: the patterns under opendox.contracts ship exactly the record and every copy it pins. The test fails without the line and passes with it. 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) Copilot's review at ca52182 found a hole in the fail-closed build. A schema holding `type: {}` made set(names) raise TypeError, so a malformed packaged copy could crash the validator's construction when the module promises SchemaNotEvaluable, which is a ValidatorUnavailable. A probe of the same class found more: - `format: {}`, `allOf: 5`, a pattern with an unbounded repetition (OverflowError), and two non-text unknown keys (a mixed sort) also crashed the build. - Twenty-three other malformed values built, then crashed on an instance or judged it wrongly. For example, `uniqueItems: "yes"` read as true, a negative `maxLength` refused every string, and `maximum: nan` passed everything. The build now checks, and refuses as SchemaNotEvaluable: - every evaluated keyword's value against the shape draft 2020-12 gives it (`_SHAPES`, with a test that no evaluated keyword lacks one); - every reference's target and the kind's entry, walked like the document, since a reference can reach a node no walk of the subschemas passes; - an embedded resource (`$id` or `$schema` below the root), which would move where its references resolve; - a subschema that contains itself, which a YAML alias can build and no walk of ends; - a JSON-pointer index that is not a plain decimal, which Python's int() would read as another element (-1, 01). The four packaged copies use none of these forms, and all six kinds build as before. 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 snapshot registry and source, the corpus-root predicate, the snapshot writer and the validator lookup each get a declared seam (src/opendox/projection_seams.py) and an openDox default of their own (default_registry.py, default_projection.py), per R1Q10 (a). The four entry points register the defaults where no host has, in R1Q3 (a)'s pattern. With nothing registered, a seam refuses and names itself and its call (4.2). A host's registration replaces a default until a consumer has read it, and is refused after that. The generate verbs go through the seams. cli.py's _generate_and_write and _gate_snapshot, and the default source's regenerate, call generator_seam.generate() and look the generator up on each call. They write through the registered writer. A snapshot is validated by the validator registered for its own kind. The core /snapshot.json arm's four handlers (_query_key, _read_snapshot, _serve_snapshot, _hosted_entry_refused) and hosted_ref_refused are serve.py's own now, read over the registry seam, so a lone openDox builds a server and answers /snapshot.json, /capabilities and /source/. branch_session's _change_rows goes through the corpus-root seam, and is_rfc3339_datetime is openDox's own (rfc3339.py). The seams do not carry either of them. consumer_reach retires snapshot_registry, snapshot, corpus_root, generator, find_validator, corpus_root_refusal, generate_snapshot, is_rfc3339_datetime, hosted_ref_refused, scanned_roots, and the function/constant stand-ins. LateProjectionRoutes now forwards only _serve_index. workbench.validate_manifest asks the validator lookup for 'ideation-workbench'. The _default_home_factory docstrings in cli.py and serve.py now name NEUTRAL_FIELDS as the adapter's default required_fields. _report prints the neutral kind in place of a project that snapshot does not carry. The validator lookup's default for openDox's own kinds is a stand-in that concludes nothing and names T057. openDox-code#58 carries T057's validator; T058 wires it in. F4.1's scan falls from 19 deferred reaches to 11, all T084's. The census of consumer_reach sites falls from 65 to 29. Whole suite: 2684 passed, 11 skipped. Arc: neutral-product-standalone-operability Lane: openxfactory-4 (openXfactory-4-openDox_extraction) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… is a declaration Taken from Copilot's review of c27eac3: - r4125556296. resolve_within read the dot-directory and dot-file rule only from the URL's spelling. A symlink inside the root that led to a dot-directory, or to a dot-file with no document extension, got past it: link -> .git served /source/link/config, which is .git/config, and a symlink named alias.md served .env. The same rule now also runs on the canonical path, relative to the root, once symlinks are resolved. A symlink to a document is still served. Five cases are added, and dropping the new check makes them fail. openXdox's governed resolve_within has the same gap, and it is flagged to the holder. - r4125556360. data_source_from_options ignored token_env, so --data-source-token-env on its own was dropped in silence. Any declared option is now refused by name, an explicitly empty one included (is not None). The CLI's refusal names the flag. - r4125556420. An extra "by" is gone from the sentence in test_source_core_arm.py, and from the same sentence in resolve_source_path's docstring and in one assertion message. Whole suite: 2689 passed, 11 skipped. Arc: neutral-product-standalone-operability Lane: openxfactory-4 (openXfactory-4-openDox_extraction) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…an 034) Copilot's review at bc3470b found that a copy holding `$ref: "#"`, or two $defs that refer to each other, built, and then is_valid() raised RecursionError. Both are valid draft 2020-12 schemas that no evaluation ends; jsonschema 4.26.0 recurses on them until Python's limit too. The module promises that a copy it cannot evaluate is refused as SchemaNotEvaluable when its validator is built, never a crash in an evaluation. The build now follows every subschema a node applies at its own place in the instance: its reference's target, its allOf, anyOf and oneOf branches, its `not`, and its `if` and `then` when it has both, since the evaluator reads neither alone. A cycle among those is refused, naming its locations. The search is iterative, so it never recurses itself. A recursive schema that moves into the instance before it recurs is still evaluated, such as a tree whose children are items of the node. A copy nested deeper than Python's recursion limit lets the build's walk stop, and it is now refused too, never a RecursionError out of the build. The same review's two other findings, that `enum: []` and `required: []` should be refused, are answered on their threads with evidence and not taken. The draft 2020-12 metaschema gives `enum` no minItems and gives `required` `default: []`; the minItems of 1 was draft-04's. jsonschema treats both as this module does. Arc: neutral-product-standalone-operability Lane: openxfactory-4 (openXfactory-4-openDox_extraction) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…g the active entry clears its key Taken from Copilot's review of ea49c42: - r4125666251. serve.main computed `url=args.data_source_url or (...)`, which turned an explicitly empty --data-source-url into None, and `if args.data_source_github` did the same to an empty GitHub slug. So the registry never saw either. Each option is now tested against None. A GitHub source is composed into the URL only when no URL was given. A slug that cannot be composed, such as an empty one, is refused as "serve refused: --data-source-github: ..." rather than raising ValueError. openDox's own registry then refuses every one by name, and new cases cover the empty URL, a GitHub slug and an empty slug. - The same review's overview (no thread) notes that SnapshotRegistry.drop left the active key pointing at a removed entry. In that state no later register() became active, and a ref-less request met a key with nothing behind it. Dropping the active entry now clears the key. A new case covers it, and removing the clear makes it fail. openXdox's governed registry drops the same way, and that is flagged to the holder. Whole suite: 2690 passed, 11 skipped. Arc: neutral-product-standalone-operability Lane: openxfactory-4 (openXfactory-4-openDox_extraction) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… (plan 034) Copilot's review at 2b32cbf made two findings, and both are taken. The record accepted any well-formed copy id. An edited copies.yaml could add `gate-intent`, with its file beside the four, and verified_bytes() would serve it. So 7.1b's boundary held only in the tests' census. contracts.COPY_IDS now names openDox's four, and a record that names any other copy, or leaves one out, is refused, as a record naming another spec leg already is. A reference's pointer accepted an escape RFC 6901 does not define (`~2`), and read a percent-encoded fragment literally. A reference to `a%20b` named the key `a%20b`, where jsonschema decodes it and resolves `a b`, so the two would judge an instance differently. An invalid escape now makes no pointer, and a percent-encoded reference is refused as a fragment this module does not decode. The four copies use neither form. Arc: neutral-product-standalone-operability Lane: openxfactory-4 (openXfactory-4-openDox_extraction) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…appened Taken from the overview of Copilot's review of bfb9c47. That review had no findings and no threads. _warn_validator_not_found described a filesystem search, "no validator for kind X was reachable from <output> ... or from <checkout>", in both of its cases. When nothing is registered for the kind, the lookup is the process's own registry, and no path could make a validator reachable, so the old text pointed at a remedy that does not exist. The warning now separates the two cases: - Nothing is registered for the kind. The warning names no path, and gives the lookup's refusal whole, on one line. That refusal ends with the call that registers a validator. It used to be cut at its first line, which ended in "at process start with". workbench.validate_manifest made the same cut, and it is corrected there too. - The registered validator reached no verdict. The warning says it was offered both roots to search from, the OUTPUT path first and --repo-root second (T092 defect 8), and then gives the validator's own reason. Two assertions pin the cases apart, and both mutations fail them. Whole suite: 2690 passed, 11 skipped. Arc: neutral-product-standalone-operability Lane: openxfactory-4 (openXfactory-4-openDox_extraction) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…t (plan 034) Copilot's review at 9d2cda1 found, in code this PR had not changed since, that the record's key refusal ran sorted() over a record's keys. So a copies.yaml with a key that is not text raised TypeError, where the module promises CopyRefused. The refusal now orders keys by their repr. An instance fuzz of the same class found a worse case in the evaluator: 115 of 3,000 odd instances crashed a validator. Under additionalProperties false, the report ran sorted() over an instance's extra keys, and YAML can make a key a number or null. Those keys are now ordered by repr too, and a validator judges such an instance rather than raising. Over two seeds of 3,000 instances, all six validators give 0 crashes. A record or copy whose YAML nests past Python's recursion limit escaped all three YAML reads as RecursionError. The record's read, contracts.load() and validator_for() now refuse it, as CopyRefused or ValidatorUnavailable. 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 at openDox-code#66 e357477 (r4146289331). The generate-and-open case looked for "serving until interrupted" only after the interrupt. By then Python's exit flush delivers an unflushed line anyway, so dropping that line's flush=True (cli.py) would still have passed. The case now waits for the line while the child runs, before any request and before the interrupt. Checked with the mutant Copilot names (the URL line flushed, the serving line not): the case now fails, "never printed a line matching ... serving until interrupted". Arc: neutral-product-standalone-operability Lane: openxfactory-4 (openXfactory-4-openDox_extraction) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…t-number, not document-syntax Copilot at openDox-code#68 c7768ed (r4146428769). A manifest with schema_version: .inf (or 1.0000000000000001, or a base-60 spelling) is valid YAML, and {"extra": 1e999} is valid JSON. _exact() refuses the number, but the report said the document "cannot be read as YAML/JSON", which points at a syntax error, not at the numeric policy that refused it. _exact() now raises _Unprovable, and the adapter reports it under a rule of its own, NUMBER_RULE = "document-number": "the document reads as JSON/YAML, but holds a number that cannot be read as written, so no verdict over it would be a verdict over the document: <why>". A document that cannot be read at all (NaN and Infinity literals, a repeated key, a truncated text, bad UTF-8, nesting past the reader) still breaks document-syntax. The number cases are split from the syntax cases, and the controls assert neither rule. Ten cases fail without the change. Arc: neutral-product-standalone-operability Lane: openxfactory-4 (openXfactory-4-openDox_extraction) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
#59 landed as a squash of 0c946f4, and fa14087's tree equals 0c946f4's (empty `git diff 0c946f4 fa14087`). This branch had last merged #59 at 183b40f, and #59 moved after that only in default_registry.py and tests/test_projection_seams.py (`git diff --stat 183b40f 0c946f4`). T056 never touches either file, so the two add/add conflicts take main's side. Proof that the merge carries exactly T056's delta: the stable patch-id of `git diff 183b40f 38761c7` equals the patch-id of `git diff origin/main` against this merge (eee3f01c both times). That delta is four files: cli.py, serve.py, tests/standalone_child.py and tests/test_standalone_generate_path.py. Lane: openxfactory-4 (openXfactory-4-openDox_extraction) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…he runner ignores Measured at a6e953c (this branch with main merged): the whole suite, run as `nohup pytest ... &`, failed cases 3 and 4 of tests/test_standalone_generate_path.py with a TimeoutExpired at the interrupt. The same module passed 6 of 6 in the foreground. The cause is the runner, not the server. POSIX starts an asynchronous command with SIGINT ignored when job control is off (a probe under `nohup ... &` reads signal.getsignal(SIGINT) == 1, SIG_IGN). An ignored signal survives exec, and Python installs its KeyboardInterrupt handler only where SIGINT was not ignored. So every server the harness started ignored the interrupt, and both cases reported how the suite was launched. The estate runs long suites exactly this way, so any lane that runs this module under nohup would read a false red. The fix is in the harness, not the product. A program started with SIGINT ignored should keep it ignored. tests/standalone_child.py's sitecustomize now sets SIGINT back to signal.default_int_handler, which models the case's claim: a Ctrl-C at a terminal. A child that ignores SIGINT itself, after startup, still does, and case 5 still kills it at the deadline. Falsifier: test_a_child_stops_on_the_interrupt_even_when_the_runner_ignores_it builds that runner in process. It ignores SIGINT while the child starts and restores it at once, then requires the child to stop on the interrupt with exit 0 and the parent's handler to be back. Red before the fix (TimeoutExpired at 10 s, in a foreground run). Green after. The module under `nohup ... &` went from 2 failed / 4 passed to 7 passed. Mutant M14 (the reset removed) is killed by the new case, and mutants are 14/14 killed. Lane: openxfactory-4 (openXfactory-4-openDox_extraction) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
brettheap
changed the base branch from
build/034-p2r-t056-standalone-generate-end-to-end
to
main
September 30, 2026 18:03
Copilot at 149d729 (r4147767447): the case checked each role key as a word anywhere in the notice. `candidate` is in the document's own name, candidate-toolshed-rebuild.md, and `source` is in "read as a source". So a notice whose parenthesized list left both out still passed. Measured: mutant M15, the list rendered without candidate and source, SURVIVED the old case (1 passed). The case now requires the whole list, "(source, grouping, candidate, selection, submission, completion)", as one substring of the notice. M15 is killed (1 failed), and so are M5, M6 and M12, the other stage mutants. The module still passes 7 of 7. 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
Sep 30, 2026
…ult_registry.resolve_source) (#70) ## What this is A small follow-up to T055 (#59, landed as `fa140875`). It takes the one finding Copilot's review of #59 at `0c946f4e` raised as "previously missed", after the last push that could take it. ### The finding, quoted Copilot review overview on #59 at `0c946f4e` (review `5368674478`), medium, "Avoid unstable second lookup during source path confinement", `src/opendox/default_registry.py:444`: > `resolve_source()` performs a second lookup by `(repository, ref)` after the caller has already resolved an entry. A concurrent refresh can replace that key between the two operations, so the returned path can be confined under a different entry's `source_root` than the entry whose metadata/listed paths the caller is using (for example, `serve_project._resolved_listed_edit_entry`). Expose an entry-based confinement operation or make the caller pass the resolved entry so lookup, validation, and path confinement use one stable entry. It is real. #59 already closed the same pattern in `serve.py`'s `/source` arm (Copilot r4136585695 and r4136863569): that arm resolves the entry once and confines to that entry's own root with `resolve_source_path(Path(root), rest)`. `serve_project._resolved_listed_edit_entry` was the one caller left. ## What changed - `src/opendox/serve_project.py`: `_resolved_listed_edit_entry` resolves the entry ONCE and confines the path to THAT entry's own root through the registry seam's declared `resolve_within` (read as `projection_seams.registry.current()` inside the function). The listed-path check already read the entry in hand, and the editor is launched over `entry.source_root`, so the lookup, the validation and the confinement are now one entry. An entry with no root serves nothing, as before. - The caller needs no method the seam does not declare. `resolve_source` is on no seam's list (`REGISTRY_CALLABLES`), so a contributed registry is never asked for one. That is why the fix confines the entry in hand rather than adding an entry-based method to openDox's own registry: a host's registry would not carry it. - `src/opendox/default_registry.py`: `SnapshotRegistry.resolve_source` keeps its behaviour (one lookup, confined to that entry). Its docstring now says it is for a caller that holds only a pair, and why a caller that already holds an entry must not ask again by its pair. - `tests/test_edit_action_one_entry.py` (new, 8 cases). No module-level proxy is bound in `serve_project.py`: `tests/test_projection_seams.py::test_no_proxy_over_a_seam_is_read_at_import_time` pins the exact set of modules that bind one (`serve.py`, `serve_workbench.py`), and this PR does not edit that file. ## Every caller of the two-step path `git grep resolve_source -- src` at `fa140875` finds the definition and exactly one production caller, `serve_project.py:111`. The other registry lookups in `src/` are one resolution each: `serve.py` `_serve_snapshot` and `_serve_source` (already one entry), `serve_workbench.py` (`resolve` then `resolve_within(entry.source_root, ...)`, three sites), and `branch_session.py` (stamping an entry after a register, no confinement). `_keyed_source`'s `registry.get(*parsed)` is a parse-time existence check whose result is a key, and `_serve_source` then resolves that key once. The new test `test_no_module_asks_a_registry_for_a_path_by_a_pair` holds that set empty, so a future caller has to be argued for. ## Evidence **Red at main, green after.** The new module against `fa140875`'s sources (`serve_project.py` and `default_registry.py` as on main): ``` FAILED test_the_edit_arm_asks_the_registry_once_and_never_for_a_path_by_a_pair FAILED test_no_module_asks_a_registry_for_a_path_by_a_pair FAILED test_a_host_registry_needs_only_what_the_seam_declares FAILED test_a_refresh_that_replaces_the_key_cannot_take_the_file_the_route_refuses FAILED test_a_refresh_that_replaces_the_key_cannot_take_the_file_the_route_accepts 5 failed, 3 passed ``` With this PR: `8 passed`. The three that pass at main are the control (a listed file opens with no refresh), confinement kept, and "no root serves nothing", which pin what must NOT change. The race is tested at the ROUTE, both ways, with a real server, a real `POST /actions/edit` and the console token. A registry whose key is replaced right after the route's first `resolve` (as a refresh on another thread would) lands the replacement between the resolution and the confinement (asserted): - Entry's root has NO file, the replacement's root has it. At main the route took the file from the replacement's root, read the first entry's listing, and started the editor over the first entry's root: `200` and an editor over a file that is not there. Now: `404 document_unavailable`, no editor. - Entry's root has the file, the replacement's root has none. At main the route refused a file its own entry holds (`404`). Now: `200`, and the editor is started over that entry's root. **Mutants of the fix, all killed** (the new module only, `serve_project.py` restored after each): | mutant | killed by | |---|---| | M1 the second lookup again (`registry.resolve_source(repository, ref, path)`, i.e. main) | one-lookup count, the no-module-asks scan, the host registry, both race cases | | M2 confine by `Path(root) / path`, no containment rule | `test_the_entrys_own_root_still_confines_what_the_route_accepts` | | M3 the no-root check dropped | host-registry and no-root cases | | M4 the listed-path check dropped | the confinement case (an unlisted file inside the root) | | M5 the listing read through a second lookup | the one-lookup count (the race cases cannot see it, as both entries share a snapshot) | **T056's module against this fix.** Fetched #66's head `38761c76` read-only into a scratch worktree, merged main (`fa140875`; the two add/add conflicts, `default_registry.py` and `tests/test_projection_seams.py`, resolved to main's blobs, as T056's own diff does not touch either), cherry-picked this commit on top, and ran `tests/test_standalone_generate_path.py` (its children run from that worktree's `src` via `PYTHONPATH`): ``` tests/test_standalone_generate_path.py + tests/test_edit_action_one_entry.py: 14 passed control, this commit reverted: tests/test_standalone_generate_path.py: 6 passed ``` That module's requests are `GET /source/notes-toolshed-inventory.md` (200, byte-equal) and `GET /source/.git/config` (404). They go through `serve.py`'s `/source` arm (`resolve_source_path`), which #59 already moved off `resolve_source`, not through `serve_project`, so this PR leaves them as they were. The module passes identically with and without this commit. **Whole suite** at `5666505b`, `LANG=C.UTF-8`, run in the foreground with a throwaway `postgres:16` and `OPENDOX_TEST_DATABASE_URL` set as CI sets it: ``` python -m pytest -q tests 2379 passed, 11 skipped python -m pytest -q tests_runtime 607 passed total 2986 passed, 11 skipped, 0 failed ``` #59's CI at `0c946f4e` was `selected=2989 passed=2978 skipped=11`. This PR adds 8 cases: 2997 selected, 2986 passed, 11 skipped. ## Overlap with open PRs None. `gh pr diff --name-only` on #60 to #69, read against each PR's own merge-base (#66 and #68 carry #59's commits, which lists `default_registry.py` spuriously): their own diffs touch neither `serve_project.py` nor `default_registry.py`. #66's own `serve.py` change is one flushed `print` near line 2216, outside the `/source` arm. ## Review rounds - **Copilot at `0471f8c7`**: "Approval recommended", no findings, 0 threads. The SonarCloud quality gate failed there on 4.9% duplication on new code (required at most 3%): the new test module carried a copy of `test_projection_seams.py`'s autouse isolation fixture and `git` helper. - **`5666505b`**: imports both instead of copying them (the `tests/test_doxbench_*.py` precedent), a test-only change. The autouse fixture still applies to all eight cases (eight SETUPs under `--setup-show`), the eight cases pass, and M1 to M5 are still killed. - **Copilot at `5666505b`**: "Approval recommended", no findings, 0 threads. `validate` and SonarCloud ("Quality Gate passed") green at `5666505b`. Arc: neutral-product-standalone-operability 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>
#66 now carries main fa14087 (T055 landed as #59), the harness's SIGINT reset (149d729) and the whole-list stage-notice check (a7bda06). This branch last merged #66 at 38761c7, the merge-base, and the merge is clean. T058's F7.2 case builds its child through tests/standalone_child.py, so it takes the reset too. Lane: openxfactory-4 (openXfactory-4-openDox_extraction) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
main now carries #70 (75bd8705, resolve_source confined to the resolved entry) and #66's squash (a23e422). This branch already held #66's final head, a7bda06, at 923f30d9. So main brings only #70's three files (default_registry.py, serve_project.py and tests/test_edit_action_one_entry.py), and T058 touches none of them. The merge is clean. Proof that the merge carries exactly T058's delta: the stable patch-id of `git diff a7bda06 923f30d9` equals the patch-id of `git diff origin/main` against this merge. That delta is seven files. Lane: openxfactory-4 (openXfactory-4-openDox_extraction) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…d '...'" Copilot at 5322efe (r4148179148): document-syntax's message read "which is how a 'opendox-snapshot' document is written". Used as an adjective, the kind takes the wrong article, and it reads awkwardly in the CLI's relay. The message now reads "which is how a document of kind 'opendox-snapshot' is written", and the same for 'ideation-workbench'. Red before: the two exact-message cases, updated first, failed 6 of 6 parametrized runs against the old wording. Green after: 189 passed across tests/test_post_render_validator.py and tests/test_projection_seams.py. Mutant M29, the old wording restored, is killed (6 failed). Lane: openxfactory-4 (openXfactory-4-openDox_extraction) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
There was a problem hiding this comment.
Copilot review overview
🔵 Needs a closer look
The new parsing and validation policy changes command outcomes and manifest handling across two document formats, warranting final human review.
Review effort: Balanced
Findings: None
Resolved since last review (1)
brettheap
marked this pull request as ready for review
September 30, 2026 19:06
Contributor
Author
|
READY at 1678ccd — Lane: openxfactory-4 (openXfactory-4-openDox_extraction) |
There was a problem hiding this comment.
Sorry @brettheap, you've used your own review budget of 250,000 diff characters for the last 7 days.
You can request another review in 1 day and 19 hours by commenting @sourcery-ai review. Upgrade to get a review now.
Contributor
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.






Lane: openxfactory-4 (openXfactory-4-openDox_extraction)
Plan 034 T058 [US2] [oDc], the post-render validator in the generate verbs (#1144's 7.2, in part), from
specs/034-opendox-standalone-operation/tasks.mdat openxFactorymain91e4685f:Claimed on openxFactory#656 in comment
5901343950, with T056.Was stacked on #66, and is now on
main, with its predecessors landeda7bda066at923f30d8.a23e4224, after T055 follow-up: confine to the resolved entry, no second lookup (default_registry.resolve_source) #70 (75bd8703, theresolve_sourcefollow-up to T055, serve and generate standalone (5.5, 4.3 part) (plan 034) #59).mainis merged in at5322efee. It brought only T055 follow-up: confine to the resolved entry, no second lookup (default_registry.resolve_source) #70's three files,default_registry.py,serve_project.pyandtests/test_edit_action_one_entry.py, and T058 touches none of them.git diff a7bda066 923f30d8equals the patch-id ofgit diff main 5322efee:f302a8aaboth times. The same id held atcd6b33cbagainst38761c76.e94ab323,3351f6a7and753ffa19, because T058 wires theopendox.validatorthat T057, openDox's own validator and its input set (7.1, 7.1a, 7.1b, 7.2) (plan 034) #58 ships. T057, openDox's own validator and its input set (7.1, 7.1a, 7.1b, 7.2) (plan 034) #58 has since landed as8ec08e91, which reaches this branch through T055, serve and generate standalone (5.5, 4.3 part) (plan 034) #59 and T056, the standalone generate path, end to end (5.1 part) (plan 034) #66 (03825eb5, no content change: T057's files here equalmain's byte for byte).3d0b6d66(six files, listed below), and28241a4b,69ca0e2e,1ec7d2c9,c7768ed5andcd6b33cb(Copilot's findings, below).8e7da4a2, so the merge was clean and brought only T057, openDox's own validator and its input set (7.1, 7.1a, 7.1b, 7.2) (plan 034) #58's own 55 files.a691e4e4), T057, openDox's own validator and its input set (7.1, 7.1a, 7.1b, 7.2) (plan 034) #58 (8ec08e91), T055, serve and generate standalone (5.5, 4.3 part) (plan 034) #59 (fa140875) and T056, the standalone generate path, end to end (5.1 part) (plan 034) #66 (a23e4224) have all landed.main. It was retargeted before T056, the standalone generate path, end to end (5.1 part) (plan 034) #66 landed, since a--delete-branchlanding of T056, the standalone generate path, end to end (5.1 part) (plan 034) #66 would have closed it. The diff againstmainis T058's own seven files again.What T058 changes
src/opendox/default_projection.py: openDox's own validator takes the stand-in's place. The stand-in,OwnValidatorNotBuilt, answered every validation "unavailable, T057 is not here".One adapter per own kind.
OwnValidator(kind)keeps the lookup's protocol,validate(path, *, strict, search_from)→projection_seams.ValidationResult.VALIDATORSholds one for each ofOWN_KINDS:opendox-snapshotandideation-workbench.projection_seams.register_defaults()registers each under its kind (a one-line change).OWN_KINDSis not widened. The doxBench wire kinds reachopendox.validatorthrough their own seam (T085).It reads the document as its kind is written.
safe_load, asworkbench.pyreads it.opendox.validator.validator_for(kind)judges the document against the packaged copy, whichopendox.contractsproves against its recorded digest on every call.Three outcomes, which
cli._validatealready turns into consequences:validatedvalidation: opendox-snapshot: 0 violations, by opendox.validator, over its packaged copy opendox-snapshot (sha256 f9e3e111af1d), exit 0not-conformant, rc 1; stdout is one[<rule>] <where>: <detail>line per violation, then a count--strict)not-conformant, ruledocument-syntaxnot-conformant, ruledocument-numberValidatorUnavailable(a copy failing its identity check, or not evaluable),UnknownKind, or a document that cannot be readvalidator-unavailable, with the reason--strictstrictandsearch_fromchange nothing in the adapter. openDox's validator has no warnings to harden, and it searches for nothing, since its schemas are package data.dependency_remedyisNone: no subprocess runs.No such file or directory" holds by construction.--strictstill means what the verb's help says, throughcli._validate.The workbench manifest's two validator rules, carried (for the holder).
recipe.pinnedkeyword is also inrecipe.checked;recipe.new_candidatesdocument is already a member or excluded.validate-ideation-dashboard-contracts.py,check_workbench_rules) checked both.opendox.validatorchecks the schema only, as T057, openDox's own validator and its input set (7.1, 7.1a, 7.1b, 7.2) (plan 034) #58's body says.validate_manifestto openDox's validator must not drop them. T055 routed it to the stand-in, so they fall due here.ideation-workbench, under the script's identifiers,workbench-pinned-not-checkedandworkbench-candidate-overlap. They are judged beside the schema, and are total over any shape.opendox.validatoritself, that is a move within T057, openDox's own validator and its input set (7.1, 7.1a, 7.1b, 7.2) (plan 034) #58's module, and the ids stay.The schema copy.
tests/test_neutral_projection.pyreads the neutral contract from the packaged copy, throughopendox.contracts.verified_bytes("opendox-snapshot").tests/fixtures/opendox-snapshot.schema.yamland itsSCHEMA_SHA256are removed. The bytes were identical (f9e3e111…584aboth), so no case's verdict moved.contracts.load()'s YAML read, and that the tree carries one copy.The stand-in's cases in
tests/test_projection_seams.py, as T055's hand-off listed them:test_the_validator_stand_in_concludes_nothing_and_names_T057test_openDoxs_own_validator_is_bound_to_each_own_kindtest_openDoxs_own_kind_meets_the_stand_in_and_strict_makes_it_fataltest_openDoxs_own_kind_meets_openDoxs_own_validator(a bare snapshot is now REJECTED, naming[envelope-keys]), andtest_openDoxs_own_validator_unavailable_is_skipped_and_strict_makes_it_fatal(a copy refused byopendox.contracts: skipped, then fatal under--strict)test_a_manifest_is_validated_by_the_validator_for_its_kind[required]Two identity asserts also move from
default_projection.VALIDATORtoVALIDATORS[kind].tests/test_post_render_validator.py(new, 29 cases):python -m opendox.cli generate --strict, with the siblings refused by T056'stests/standalone_child.py;generate-and-open --no-open --no-serve --strictover both fixtures;--no-validate;ValidatorUnavailable/SchemaNotEvaluable, and a copy tampered belowopendox.contracts;strict/search_frominert;workbench.save(validate=True)keeping a valid manifest and unwinding a broken one.F7.2, failing before and passing after
#1144's F7.2, verbatim: a fresh venv,
pip install .(package data on disk, a non-editable install), both fixtures as fresh repositories,generate --stricttwice, the rule grep, and the negative grep asserted as exit status 1.3b13f141: T055, serve and generate standalone (5.5, 4.3 part) (plan 034) #59 + T056, the standalone generate path, end to end (5.1 part) (plan 034) #66 + T057, openDox's own validator and its input set (7.1, 7.1a, 7.1b, 7.2) (plan 034) #58, with the stand-in), it exits 1 on the good fixture's--strictrun:cd6b33cb, and again at5322efee, aftermainwas merged in, it exits 0. The malformed run's stderr:EXPECTED_RULEistitle-and-summary-are-text, and noNo such file or directoryappears.tests/test_post_render_validator.py: at the base, with the stand-in, 26 failed and 2 passed. The two that pass hold what T058 does not change:--no-validate, andEXPECTED_RULEbeing one of the contract's rules. At3d0b6d66, 29 passed. At this head1678ccd0, with Copilot's later rounds, 50 passed, and also 50 under--noconftest.Mutation check: 29 of 29 killed, re-run at this head
Each mutant was applied, the named cases were run, and the sources were restored and checked by sha256.
validatedunavailableValidatorUnavailableread as a pass--strictcaseexcludedkind, not its own--strictdoes not make an unavailable validator fatal (cli.py)--no-validatedoes not skip (cli.py)OSErrorfrom the lookup escapeskindagain (cli.py)1e999and YAML.inf/.nancases1.0000000000000001and1.5e-400cases0.1,2.50, …)The repository's own checks
The whole suite ran locally as CI runs it:
CI=true,LANG=C.UTF-8, PostgreSQL 16,-e ".[runtime,test]"with the constraints file, at the committed head, with a clean tree.3b13f1413d0b6d66(T058's commit)09cd1e8a(#66's42a08a31merged in)28241a4b(Copilot's two findings)21e4723f(#66's5a26532eand #58's3351f6a7merged in)69ca0e2e(#66'se939c31fmerged in, and Copilot's second round)80153754(Copilot's third round, and #58's753ffa19merged in)c7768ed5(#66'se3574774merged in, and Copilot's fourth round)cd6b33cb(#66's38761c76merged in, and Copilot's fifth round)923f30d8(#66's final heada7bda066merged in), run undernohup … &5322efee(mainmerged in, with #66 and #70 landed), run undernohup … &1678ccd0(Copilot's sixth round: the syntax message's wording), run undernohup … &3d0b6d66shows +32 added (29 in the new file, 3 intest_projection_seams.py) and 2 removed (the two stand-in cases above, replaced). At09cd1e8ait shows +33: the extra case is T056, the standalone generate path, end to end (5.1 part) (plan 034) #66's own.923f30d8, againstcd6b33cb, it shows +2: T056, the standalone generate path, end to end (5.1 part) (plan 034) #66'stest_a_child_stops_on_the_interrupt_even_when_the_runner_ignores_itand T055, serve and generate standalone (5.5, 4.3 part) (plan 034) #59'stest_a_regenerate_promotes_no_session_and_moves_no_active_key. At5322efee, against923f30d8, it shows +8, all from T055 follow-up: confine to the resolved entry, no second lookup (default_registry.resolve_source) #70'stests/test_edit_action_one_entry.py. At this head, against5322efee, it shows 0 added, 0 removed and 0 changed.923f30d8and5322efee(28 of 28 killed both times), and at this head, where it is 29 of 29.opendox.validatorand PyYAML when a validation runs, and names no sibling.conftest.py,pyproject.tomlor pin is touched.Files (T058's own commits)
src/opendox/default_projection.py:OwnValidator,VALIDATORS,SYNTAX_RULE,NUMBER_RULE,WORKBENCH_RULES. The stand-in is removed and the docstring rewritten.src/opendox/projection_seams.py:register_defaults()registersVALIDATORS[kind].src/opendox/cli.py(69ca0e2e):_written_kindrefuses a key given twice, and_validatefails on it.These seven files are the whole of the PR's diff now that #58 has landed.
tests/test_projection_seams.py: the stand-in cases above.tests/test_neutral_projection.py: it reads the packaged copy.tests/fixtures/opendox-snapshot.schema.yaml: removed.tests/test_post_render_validator.py: new. It is a created file, with no carve-manifest row (RULED OQ-C).Copilot
3d0b6d66, "Needs a closer look", with two findings. Both are fixed in28241a4b, answered with evidence, and resolved:validator_for()as aPermissionErrortraceback.opendox.contractsconverts only a missing file.copies.yamlat mode 000:generate --strictexited 1 with the traceback.OSErrorfrom the lookup as validator-unavailable, and the verb warns, or fails under--strict, in its own words.test_a_packaged_file_that_cannot_be_read_is_unavailable_not_a_traceback._names()was quadratic, and the schema bounds none of the three lists. Membership is now a set's. New case:test_the_rules_read_a_long_list_in_linear_time, which took 17.5 s before the fix and 0.01 s after.21e4723f, "Needs a closer look", with four findings, each answered with evidence and resolved:kindwith plainjson.loads, which keeps the last of two keys. So"kind": "opendox-snapshot", "kind": "unknown"found no validator, and an ordinary run exited 0.69ca0e2e:cli._written_kindrefuses a key given twice, and the verb fails whatever--strictsays. New case:test_a_kind_given_twice_chooses_no_validator_and_fails.1e999reads asinfwithoutparse_constant. Fixed in69ca0e2e(parse_float=_finite), with two new not-JSON cases.69ca0e2e: each is cut at 80 characters. New case:test_a_rules_detail_cuts_a_long_name.validator.py, T057, openDox's own validator and its input set (7.1, 7.1a, 7.1b, 7.2) (plan 034) #58's file):_closeconsuming earlier siblings' canons atcount == 0. Not reproduced: the slice isdone[len(done) - count:], which is empty at 0, and_canon([1, []])and_canon([[], 1])are distinct (a2:n1:1a0:,a2:a0:n1:1). No change here; the note went to T057, openDox's own validator and its input set (7.1, 7.1a, 7.1b, 7.2) (plan 034) #58's owner.opendox.contractsrefusing an unreadable file, has since landed in T057, openDox's own validator and its input set (7.1, 7.1a, 7.1b, 7.2) (plan 034) #58 as2b8ad24, merged in here. With it, a record at mode 000 givescould not run: opendox.contracts has copies.yaml, and it cannot be read (PermissionError: Permission denied)and no traceback. The adapter's ownOSErrorguard stays as defense in depth.)69ca0e2e, one finding, answered with evidence and resolved:float()rounds1.0000000000000001to1.0, which meetsconst: 1. A manifest so written read as "0 violations", and jsonschema 4.26 reads the snapshot case the same way.numbertype, so rather than carry decimals through T057, openDox's own validator and its input set (7.1, 7.1a, 7.1b, 7.2) (plan 034) #58's validator,1ec7d2c9refuses a float literal that is not finite, or not equal to the shortest spelling of the float read from it, underdocument-syntax. The same proof applies to the manifest's YAML floats.0.1,2.50,1E2and every float openDox's writer writes read as written.80153754, one finding, answered with evidence and resolved:0:1.0000000000000001) is read and rounded by PyYAML, butDecimalcannot parse it, so the proof let it through, and the manifest read as "0 violations".c7768ed5: a spelling the proof cannot compare is refused, underdocument-syntax.c7768ed5, one finding, answered with evidence and resolved:cd6b33cb: such a number breaks a rule of its own,document-number.document-syntaxstays for a document that cannot be read at all.cd6b33cb, "Needs a closer look", with nothing open.5322efee, "Needs a closer look", with one finding, answered with evidence and resolved:document-syntax's message read "a 'opendox-snapshot' document", which takes the wrong article.1678ccd0: it now reads "a document of kind 'opendox-snapshot'".1678ccd0, a review is re-requested through the reviewer API.For the holder
opendox.validatorinstead.cli._report_non_conformance, T055's, unchanged). A snapshot breaking more than 19 rules shows the count line and the last 19. That is enough for F7.2's single rule, and not changed here.Arc: neutral-product-standalone-operability
Lane: openxfactory-4 (openXfactory-4-openDox_extraction)
🤖 Generated with Claude Code