Repository navigation
Render a named path pattern as a page listing its matches #373
Description
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_fromdraws three steps (through ARG-006, ARG-007 and ARG-008) among twelve lines ofrests_onsteps 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
- 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).
- Any pattern in the subset renders: across schemes, zero hops,
WHERE NOT, variable length. - 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 insideWHERE NOTare not columns, since they never bind in a match. - Every cell links to its document from the page's own frame, with its title, as chain pages do.
- Deterministic. Rows are sorted by column codes in order, with no clock, so
luria index --checkcan hold the page fresh. Duplicate matches collapse to one row. - 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).
- 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.
- it is excluded from the reference scan (
- 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 nameThe 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, andreports/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._pythonto 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 MATCHwould 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.outputrenders 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-mapdeclares one report (unfinished, which its INF-004 makes non-empty).- It has been tried on nucleation with
unopposedandundercutbefore being trusted, and the devlog says what that showed.
Activity
Remove built-in open ends from pattern chains (#373)
Chains may walk a named path pattern (#372)
[-]Render a named path pattern as a report of its matches[/-][+]Render a named path pattern as a page listing its matches[/+]A named path pattern may render a page of its matches (#373)