Skip to content

Render a named path pattern as a page listing its matches #373

Open

Description

@dmarx

Problem

Named path patterns (#372, ADR-tmpfdvjo) have one consumer: a chain. A chain needs a pattern that starts and ends on the chain's scheme and has at least one hop, and it shows the matches as steps mixed into its lines. So:

  • A claim-to-claim pattern is hard to see. On dmarx/nucleation, argued_from draws three steps (through ARG-006, ARG-007 and ARG-008) among twelve lines of rests_on steps on Lines of argument. Nothing on the site lists the pattern's matches on their own.
  • Any other shape has nowhere to render. A pattern across schemes, or a single node with WHERE NOT, loads and lowers, but nothing shows it. These are the questions a record most wants a page for:
patterns:
  # theses nothing has objected to yet
  unopposed: (c:CLAIM {role: 'thesis'}) WHERE NOT (c)<-[:objects_to]-(:CLAIM)
  # arguments an Active claim undercuts
  undercut: (g:ARG)<-[:undercuts]-(u:CLAIM {status: 'Active'})
  # arguments with claim premises and no conclusion (where `concludes` is optional)
  unfinished: (i:INF)<-[:premise_of]-(:CLAIM) WHERE NOT (i)-[:concludes]->(:CLAIM)

This is also where the built-in Open list went. It was removed from #372 after it named six complete arguments on nucleation as unfinished: they rest only on grounds, which nucleation's premises field group allows. What a record calls unfinished depends on its schema, so the record states it as a pattern, and this issue gives that pattern a page.

Requirements

  1. Opt-in per pattern. A pattern renders a page only when its declaration asks for one. A record that declares no such page gets byte-identical output (ADR-127's parity rule).
  2. Any pattern in the subset renders: across schemes, zero hops, WHERE NOT, variable length.
  3. One row per match, one column per named node, in the order the nodes appear in the pattern. Anonymous nodes ((:CLAIM)) are matched and not shown, as with a chain's "through". Nodes named only inside WHERE NOT are not columns, since they never bind in a match.
  4. Every cell links to its document from the page's own frame, with its title, as chain pages do.
  5. Deterministic. Rows are sorted by column codes in order, with no clock, so luria index --check can hold the page fresh. Duplicate matches collapse to one row.
  6. An empty result is said, not left blank. A page with no matches reads "No matches", so it never looks like a broken page (DP-15).
  7. The page is generated in the strict sense:
    • it is excluded from the reference scan (Config.is_generated), as chain pages are, or a report listing a retired document turns into unacknowledged-citation findings on a page nobody can annotate;
    • its output path is refused at load if it collides with a chain, scheme or report output.
  8. A report, never a failure. No lint class. A pattern's matches need judgement (CLAUDE.md: the lint takes only checks that are always wrong and mechanically fixable).

Proposed shape (for the ADR to settle)

Declaration: an output: key on the pattern table. It's the key chains already use for "this renders a page here".

patterns:
  unopposed:
    match: (c:CLAIM {role: 'thesis'}) WHERE NOT (c)<-[:objects_to]-(:CLAIM)
    blurb: Theses nothing has objected to yet.
    output: docs/unopposed.md
    title: Unopposed theses          # optional; defaults to the name

The alternatives the ADR should weigh:

  • A reports: family that names patterns. It separates query from view, and one pattern could feed several pages. But it adds a seventh family for something one key does.
  • Fixed placement under docs/reports/patterns/<name>.md. No path to choose, but no control over where the page lands, and reports/ today holds luria's own status reports.

The page. Title, blurb, the pattern in a code span, a count, then the table. An illustrative mock-up (the count is a placeholder):

# Unopposed theses

Theses nothing has objected to yet.

`(c:CLAIM {role: 'thesis'}) WHERE NOT (c)<-[:objects_to]-(:CLAIM)` · N matches

| c (CLAIM) |
|---|
| [CLAIM-004](../record/claims.d/CLAIM-004.md) — … |

Column headers are the variable and its label (g (ARG), u (CLAIM)). Variables are short, so the label carries the meaning. Optional columns: titles could come later if a record wants prose headers; that's not needed for a first version.

The lowering. pattern_rules already derives every match as pat<k>_m(N0, …, Nn) and shows only pattern_edge and pattern_via. Proposal:

  • Show pattern_match(P, (V…)): one atom per match, a tuple of the named, non-repeated node variables in pattern order.
  • Teach logic._python to turn a clingo tuple into a Python tuple. Today it would stringify it.
  • That covers zero-hop patterns as well, which currently derive nothing shown.

Where the code goes. A new luria/pattern_reports.py (keeping reports.py, already 357 lines, to luria's own reports). Its outputs() joins luria index beside chains.outputs(). NamedPattern gains output and title, and config_doc picks them up from the dataclass automatically.

Open questions

  • Should a page group rows by its first column (one heading per claim, matches beneath), or stay one flat table? Flat is simpler and sorts the same.
  • Should acknowledgement directives apply, so a row someone has judged stops being shown? Probably not in a first version: a pattern that is too broad is fixed by narrowing the pattern.
  • Should the site's navigation list pattern pages, or is linking them from the record's own README stub enough?
  • A README badge with the count? Out of scope here.

Out of scope

  • The outline view, a tree walked from a root across schemes, where OPTIONAL MATCH would earn its place.
  • Transcluding document bodies so views compose into a manuscript.
  • Any change to what a pattern can express. This issue renders the subset as it is.

Done when

  • An ADR is filed (Proposed) settling the declaration shape and the open questions above.
  • patterns.X.output renders a page for any pattern in the subset, zero hops included, with links, titles, sorted rows and "No matches" when empty.
  • The page is excluded from the reference scan, and colliding outputs are refused at load.
  • Parity holds: luria's own record, every example and dmarx/nucleation are byte-identical when no pattern declares output.
  • examples/argument-map declares one report (unfinished, which its INF-004 makes non-empty).
  • It has been tried on nucleation with unopposed and undercut before being trusted, and the devlog says what that showed.

Activity

  1. changed the title [-]Render a named path pattern as a report of its matches[/-] [+]Render a named path pattern as a page listing its matches[/+] on Oct 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions