Skip to content

DRAFT (phase 3, after T063): T088, the lens's two seed actions are offered only where a binding answers them (R1Q19 (a)) (plan 034) - #65

Draft
brettheap wants to merge 2 commits into
mainfrom
build/034-p3l-t088-lens-seed-actions-by-binding
Draft

brettheap wants to merge 2 commits into
mainfrom
build/034-p3l-t088-lens-seed-actions-by-binding

Conversation

@brettheap

@brettheap brettheap commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

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

Arc: neutral-product-standalone-operability

DRAFT. This PR never goes READY before T063 (the phase-2 checkpoint) has landed and the holder says so. It is authored ahead under Brett's phase-3 draft-ahead word, "Install chain + lens (Recommended)" (openxFactory#656 comment 5901112350, 2026-09-29). Claim: 5901192386.

T088 (plan 034, slice P3-L): the lens's two seed actions

The task, from specs/034-opendox-standalone-operation/tasks.md at openxFactory main 91e4685f:

The lens's two seed actions are offered only where a binding answers them (R1Q19 (a)). Standalone, no binding answers /actions/dtn-seed or /actions/staging-seed, so neither control is offered. lens.js stays the web census's one declared ? row. Moving the two controls into a view extension that openxFactory contributes, which retires the row, is R1Q19's (b), for later and outside release 1.

  • Realizes: none of the 69; this is the precondition for AT-R1 step 6.
  • Falsifier: AT-R1 step 6 (spec.md): "#tab-lens renders the bullseye with the corpus's documents as dots, and not the text 'nothing on the radar'. Neither of its two openxFactory seed actions is offered, since no binding answers them (R1Q19 (a))."
  • Ruled: R1Q22 (a), 5817152735; R1Q19 (a), 5850003126. No #1144 line changes (R1Q19 amends none), so there is no batch amendment to carry.
  • After: T063, T069. T069 is done. T063 has not landed, which is why this is a draft.

What changed

src/opendox/web/views/lens.js, and the census's own bookkeeping for that file. Nothing else in src/.

  • The capability that says a binding answers is not new server code. /capabilities already carries views.contributed_routes, which serve.build_server() builds from the very route_bindings table its POST dispatch consults (route_extension.match(self.route_bindings, "POST", path)). The lens reads its answer off the payload the shell has already fetched, so there is no second fetch and no new field, and nothing that can drift from the dispatcher.
  • bindingAnswers(capabilities, method, path) (exported) mirrors RouteBinding.matches: method, then path, exact or under a prefix, a GET binding answering HEAD too. It fails closed on anything it cannot read (no payload, the probe's fallback, no views), and on the WHOLE manifest, as manifestRoutes() does: every entry is first held to what RouteBinding.__post_init__ accepts (a known method, a pattern rooted at a slash with no query or fragment, a boolean is_prefix, a prefix ending in a slash), and one malformed entry leaves every route unanswered (Copilot's round-1 finding, taken in d716ded). Unlike manifestRoutes() it never throws, because it gates a control.
  • The register seed (draft seed, a drill row on a set two or more repositories share): ctx.onSeed is null unless the DTN route is answered.
  • The staging seed (draft staging seed, the pick bar): ctx.pickDoc is null unless the staging route is answered. Every selection site (the matrix's checkbox column and select-all, the clickable dots, the picked mark, the pick bar) was already guarded by ctx.pickDoc, and test_the_matrix_selection_is_the_seeds_only_input says the column "exists to feed ONE action". So the selection goes with the button, and a standalone lens never says "tick documents to draft from them". The matrix then draws no empty gutter (and its empty-row colspan follows), and the drill note stops naming a seed that is not there.
  • Both controls carry a data-seed-action hook (dtn-seed, staging-seed) for the browser half (T096) to assert absence by, without matching on a label that changes (re-draft).
  • Census: views/lens.js loc 1495 to 1577 and the ? class total. The row stays ? (the two route literals and their route_ownership_exceptions entry are untouched). The two until lines named "a future ruling"; that ruling has now been made in part, so they name R1Q19 (b).
  • tests/test_display_facet.py's lens render test drove the lens with no capability payload and read the pick bar's words. It now asks the lens as a host that answers both routes (5 added lines plus 1 changed). The standalone lens is the new file's.

The falsifier, before and after

tests/test_lens_seed_actions.py (new, 9 cases) drives the REAL views/lens.js under node with the /capabilities payload a serve publishes, built with the real route_extension.collect_bindings and view_extension.view_manifest. Both vocabularies are read, because the two controls live in different ones.

Before (the new file against main fa8862cc): 7 failed, 1 passed. The AT-R1 step 6 case fails on the behaviour, not on a missing symbol:

tests/test_lens_seed_actions.py:316: AssertionError: ('keywords', 'a seed action is offered where no binding answers it')
E   assert ['staging-seed'] == []
FAILED ...::test_a_standalone_lens_draws_its_radar_and_offers_neither_seed_action
FAILED ...::test_the_same_lens_offers_both_where_a_host_contributes_both_routes
FAILED ...::test_each_seed_action_is_offered_on_its_own_route_alone[contributed0-True-False]
FAILED ...::test_each_seed_action_is_offered_on_its_own_route_alone[contributed1-False-True]
FAILED ...::test_a_payload_that_cannot_say_a_binding_answers_reads_as_none_answering
FAILED ...::test_a_binding_answers_only_the_method_and_the_path_it_declared
FAILED ...::test_the_lens_answers_exactly_what_the_servers_dispatcher_answers
7 failed, 1 passed in 0.73s

(The one that passes is the source ratchet that the manifest and the dispatcher name the same table.)

After (this branch, d716ded): 9 passed in 0.43s. Against the round-0 lens.js (196e827) the two cases added for Copilot's finding fail (2 failed, 7 passed), so they test what they claim.

What each case asserts:

  1. Standalone: the radar draws its documents as dots, "nothing on the radar" is absent, no seed control is offered by label OR by hook, and no pick bar, checkbox, clickable dot, pick column or word "seed" survives, in both vocabularies.
  2. A host contributing both routes gets both controls (register seed only on the shared row), so "never offer them" cannot pass.
  3. Each control is offered on its own route alone.
  4. Fail closed on every unreadable payload shape, including one bad entry beside a good one (either order).
  5. Method and path exactness, and prefixes.
  6. bindingAnswers over the published manifest equals route_extension.match() across GET, HEAD and POST and exact and prefix routes.
  7. The lens's notion of a well-formed manifest entry is RouteBinding's own: over 21 candidate entries, on both sides of every clause and including JSON shapes that were never constructed, a list holding one beside a good seed route answers yes exactly when RouteBinding accepts it.
  8. serve.py names the same table for the manifest and the POST arm (build_server() cannot yet run in a lone checkout, so this is read as text).

Mutants killed (each applied to lens.js alone, the new file run, then restored): staging gate removed; register gate removed; fail open on a payload with no manifest; method ignored; prefix and exact inverted; register seed asking the staging route; GET no longer answering HEAD; pick column drawn with no selection; drill note always naming the seed; is_prefix boolean check dropped; gate reading the wrong caps object; one bad entry no longer poisoning the list; each of the rooted-pattern, no-query-or-fragment and prefix-ends-in-slash clauses dropped; the methods table widened. 16 of 16 killed, 0 survivors.

Measured

LANG=C.UTF-8, the whole suite, in the foreground: main 2393 passed, 177 skipped; this branch (d716ded) 2402 passed, 177 skipped. The skip sets are identical node for node (measured at 196e827, +1 case since). The 177 are the database-backed cases (tests_runtime, and others that need a service) that CI runs against its PostgreSQL service. Neighbours: test_web_boundary.py, test_display_facet.py, test_bullseye_widget.py, test_lens_labels_at_scale.py and test_view_registry.py pass unchanged apart from the one respell above.

Overlap, and what this deliberately does not touch

🤖 Generated with Claude Code

Summary by Sourcery

Gate the lens’s seed actions and their supporting selection UI on the host bindings that answer those actions.

New Features:

  • Offer the lens’s register and staging seed actions only when the host contributes bindings for their routes.
  • Expose route-binding capability checks so the lens can independently gate actions and related selection controls.

Bug Fixes:

  • Prevent standalone lenses from displaying seed-related controls, selection affordances, or instructions for unavailable actions.

Enhancements:

  • Keep route capability detection aligned with server dispatch semantics and fail closed for missing or malformed capability data.
  • Add seed-action hooks for browser-level verification and update census bookkeeping for the transitional lens entry.

Tests:

  • Add Node-backed coverage for standalone and host-contributed lens behavior, route matching, malformed capability payloads, and parity with server dispatch.
  • Update the display-facet lens test to render with contributed seed routes.

…swers them (plan 034)

R1Q19 (a), openxFactory#656 comment 5850003126. The lens's `draft seed`
(a drill row's register seed) and `draft staging seed` (the pick bar) call
two routes that only a host's binding answers. A standalone install has no
host, so neither control is offered there. `lens.js` stays the web census's
one declared `?` row; moving the controls into a view extension that
openxFactory contributes is R1Q19 (b), later and outside release 1.

"A binding answers it" is read off what the shell already fetches.
`/capabilities` carries `views.contributed_routes`, which `serve.build_server()`
builds from the same `route_bindings` table its POST dispatch consults. The new
`bindingAnswers(capabilities, method, path)` mirrors `RouteBinding.matches` and
fails closed on a payload it cannot read. No `serve.py` change, no new field.

The staging seed's selection goes with the button. Every selection site (the
matrix's checkbox column, the clickable dots, the `picked` mark, the pick bar)
is guarded by `ctx.pickDoc`, and the matrix's own test says the column exists
"to feed ONE action", so `pickDoc` is null where the staging seed is not
offered. The matrix draws no empty gutter then, and the drill note stops
naming a seed that is not there. Both controls carry `data-seed-action` for
the browser half (T096, AT-R1 step 6) to read.

Falsifier, AT-R1 step 6, as far as a unit test can drive it
(`tests/test_lens_seed_actions.py`, real `views/lens.js` under node): with the
payload a standalone serve publishes the radar draws its documents as dots, the
empty-radar text is absent and no seed control is offered, and with a host that
contributes the routes both are. The lens's answer is held to
`route_extension.match()` across every method, exact and prefix routes.

Census: `views/lens.js` loc 1495 -> 1560 and the `?` total; the two `until`
lines now name R1Q19 (b). `tests/test_display_facet.py`'s lens render test asks
the lens as a host that answers both routes (it reads the pick bar's words).

Measured with LANG=C.UTF-8: 2393 passed / 177 skipped on main, 2401 passed /
177 skipped here (the same skips node for node; the 177 are the database-backed
cases that CI runs against its service).

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

sourcery-ai Bot commented Sep 29, 2026

Copy link
Copy Markdown

Reviewer's Guide

The PR makes lens seed actions capability-driven: standalone installations hide both seed controls and all staging-selection affordances, while hosts contribute only the actions backed by their route bindings. It implements a fail-closed client-side mirror of server route matching, adds comprehensive Node-backed and consistency tests, and updates the affected display test and census metadata.

Sequence diagram for capability-driven lens seed actions

sequenceDiagram
    participant Shell
    participant Lens as lens.js
    participant Capabilities as capabilities payload
    participant Binding as route binding manifest
    Shell->>Lens: renderLens(root, snapshot, opts)
    Lens->>Capabilities: read views.contributed_routes
    Lens->>Binding: bindingAnswers(caps, POST, seed route)
    Binding-->>Lens: answered or false
    alt seed route is answered
        Lens->>Lens: render corresponding seed control
    else route is absent or unreadable
        Lens->>Lens: omit seed control
    end
    Lens-->>Shell: render lens with only supported actions
Loading

Flow diagram for standalone lens affordance gating

flowchart TD
    A[Read capabilities payload] --> B{POST /actions/dtn-seed answered?}
    A --> C{POST /actions/staging-seed answered?}
    B -->|yes| D[Offer register seed control]
    B -->|no| E[Omit register seed control]
    C -->|yes| F[Offer staging seed and selection affordances]
    C -->|no| G[Omit staging seed, pick bar, checkboxes, clickable selection]
Loading

File-Level Changes

Change Details Files
Gate both lens seed actions and their supporting selection UI on whether the host contributes matching routes.
  • Added fail-closed route matching against capabilities.views.contributed_routes, including method, exact/prefix paths, and GET-to-HEAD behavior.
  • Offer the register seed only for a contributed DTN route and the staging seed only for a contributed staging route.
  • Remove staging-only controls and explanatory seed text when the staging action is unavailable; adjust matrix columns and empty-row spans accordingly.
  • Added data-seed-action hooks for browser-level assertions.
src/opendox/web/views/lens.js
Add end-to-end coverage proving standalone and host-contributed lens behavior matches server dispatch semantics.
  • Added Node-backed tests covering standalone absence, both-route presence, independent route gating, malformed payloads, path/method matching, prefixes, and GET/HEAD behavior.
  • Build test capabilities payloads through the real route and view-manifest helpers and compare lens answers with route_extension.match().
  • Add a source-level check that the capabilities manifest and POST dispatcher use the same route_bindings table.
tests/test_lens_seed_actions.py
Update lens rendering coverage and census bookkeeping for the new conditional behavior.
  • Provide contributed-route capabilities in the existing display-facet lens test so its expected seed vocabulary remains valid.
  • Update the lens census LOC total and explain that the transitional row now remains until R1Q19(b).
tests/test_display_facet.py
tests/fixtures/web_boundary_census.yaml

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

Malformed capability manifests can still enable seed controls, and the stated T063 prerequisite remains unmet.

Review effort: Balanced
Findings: 1 Medium severity

Open (1)
What changed in this PR

Gates lens seed actions and related selection UI on contributed route capabilities.

Changes:

  • Adds fail-closed route capability matching.
  • Hides unavailable seed controls and selection UI.
  • Adds Node-based coverage and updates census bookkeeping.
File Description
src/​opendox/​web/​views/​lens.js Gates seed actions and selection controls.
tests/​test_lens_seed_actions.py Tests route matching and rendered controls.
tests/​test_display_facet.py Supplies seed capabilities to the render test.
tests/​fixtures/​web_boundary_census.yaml Updates lens census metadata.

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

Comment thread src/opendox/web/views/lens.js
…pilot review, round 1)

`bindingAnswers` tested `routes.some(...)`, so a manifest such as
`[validSeedRoute, null]` answered yes, and an entry like
`{method: "POST", pattern: "", is_prefix: true}`, which `RouteBinding` refuses
and which prefix-matches every path, could answer for a route nobody
contributed. `manifestRoutes()` refuses the whole payload for one bad entry;
this now does the same, quietly, because it gates a control and must not throw.

Every entry is first held to what `RouteBinding.__post_init__` accepts: a known
method, a pattern rooted at a slash with no query or fragment, a boolean
`is_prefix`, and a prefix ending in a slash. Any entry that fails leaves every
route unanswered. `route_extension.METHODS` is mirrored as `ROUTE_METHODS`.

Tests: the fail-closed case gains one-bad-entry-beside-a-good-one cases in both
orders, an empty prefix and a missing `is_prefix`, with two controls that must
still answer. A new case holds the lens's notion of a well-formed entry to the
server's own: over 21 candidate entries that sit on both sides of each clause
(including JSON shapes that were never constructed), a list holding one beside
a good seed route answers yes exactly when `RouteBinding` accepts it. The
mutation battery is now 16, each killed (the four well-formedness clauses, the
methods table and the whole-list poisoning are new).

Census: `views/lens.js` loc 1560 -> 1577 and the `?` total.

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

Copy link
Copy Markdown

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot review overview

🔵 Needs a closer look

Final approval must await the explicitly required T063 phase checkpoint.

Review effort: Balanced
Findings: None

Resolved since last review (1)

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