Skip to content

Chains may walk a named path pattern (#371) - #372

Merged
dmarx merged 7 commits into
mainfrom
claude/beautiful-cray-sr1fx2
Oct 13, 2026
Merged

dmarx merged 7 commits into
mainfrom
claude/beautiful-cray-sr1fx2

Conversation

@dmarx

@dmarx dmarx commented Oct 13, 2026 •

Copy link
Copy Markdown
Owner

Closes #371. Decision: ADR-tmpfdvjo, A chain may walk a named path pattern (Proposed).

What it does

A record may declare named path patterns in Cypher's pattern syntax, and a chain may walk one wherever it walks a field:

patterns:
  follows_from: (b:CLAIM)<-[:concludes]-(i:INF)<-[:premise_of]-(a:CLAIM)
chains:
  argument: {scheme: CLAIM, relation: follows_from, output: docs/argument.md}
  • Meaning. Node labels are schemes and edge types are reference fields. A hop holds when the relation does, as held/4 reads it (converse side included).
  • The subset. One label per node (or a bare back-reference), | between fields, variable length (*, *2.., *..3), property maps, and WHERE NOT followed by a fixed path.
    • MATCH, RETURN, OPTIONAL MATCH, undirected or named edges, shortest paths and writes are refused by name.
  • Engine. luria parses patterns with its own lark grammar (luria/patterns.lark) and lowers them to clingo over the record's facts (luria/pattern_rules.py), so the logic core stays the one engine. Every program is stratified, so there is one answer set.
  • Load-time checks (consistency.lp):
    • labels are schemes;
    • each hop is declared on the node it leaves and can land on the next label;
    • closures stay within one scheme;
    • properties are fields the scheme can hold;
    • no pattern shares a reference's name;
    • a chain's pattern starts and ends on the chain's scheme and has a hop.
  • On the chain page:
    • a step reads like a field: the first node nests under the last;
    • named inner nodes render as "— through INF-004";
    • other parents are named by the pattern ("— also follows from CLAIM-002", via Name a chain step's other parents by the relation that holds them #359);
    • a chain invariant covers pattern steps, and cycles are the existing finding;
    • a partial match is no step, and luria derives nothing from it.

Removed during review: built-in "Open"

An earlier commit listed partial matches that couldn't be completed under Open on each chain page. Tried on dmarx/nucleation, it named six arguments, all of them complete. They rest only on grounds, which nucleation's premises field group allows. Whether a partial match is unfinished depends on the schema, so a record states it as a pattern of its own:

unfinished: (i:INF)<-[:premise_of]-(:CLAIM) WHERE NOT (i)-[:concludes]->(:CLAIM)

Rendering such a pattern as a list of its matches is #373.

Files

  • New: luria/patterns.py, luria/patterns.lark, luria/pattern_rules.py, examples/argument-map/.
  • Changed:
    • config.py: the patterns family, NamedPattern;
    • consistency.py and .lp;
    • chains.py;
    • invariants.py and .lp;
    • logic/__init__.py: derive(extra=..., families=...) for generated programs;
    • facts.py: pattern/1;
    • config_doc.py;
    • tools/wheel_smoke.py: proves the wheel carries the grammar;
    • pyproject.toml: lark>=1.2,<2.
  • Record: ADR-tmpfdvjo, the 2026-10-13 devlog (design path, Cypher→Datalog tool survey, what building and the nucleation trial corrected), a changelog fragment, and a paragraph in docs/project-memory.md.

Verification

  • python -m pytest tests -q after merging main (with Name a chain step's other parents by the relation that holds them #359): 1769 passed, 33 skipped.
  • luria lint: clean at the root (which includes examples/*) and in examples/argument-map on its own.
  • dmarx/nucleation, unchanged: all 625 files luria index writes are byte-identical between main and this branch, and so is luria lint with lint.network: never set in both copies.
  • dmarx/nucleation, with a pattern: argued_from: (c:CLAIM)<-[:concludes]-(g:ARG)-[:rests_on]->(p:CLAIM) WHERE NOT (g {status: 'Rejected'}) on its argument chain draws the steps through ARG-006, ARG-007 and ARG-008, each "through" its argument. They were invisible to a chain over rests_on.
  • examples/argument-map: steps render "through" their inference, and uncontested.md drops the step OBJ-001 challenges.
  • Wheel built and tools/wheel_smoke.py passes; luria/patterns.lark is in the wheel.

Merge with #359

#359 merged first. The conflict in chains._load and _line_block was resolved by keeping both sides: #359's per-edge relation tracking runs beside the pattern-aware _edges, and a step's tail carries "through" before #359's _also. A pattern has no label, so its name reads in that place, and tests/test_pattern_chains.py pins it.

🤖 Generated with Claude Code

https://claude.ai/code/session_016xS6nL8ZZpTh1jMSvaTRks

Proposed. A scheme declares a composed relation as the reference fields
it follows in order; a chain names it where it names a field. Read-only,
single-scheme legs, steps name their intermediate, partial paths shown.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016xS6nL8ZZpTh1jMSvaTRks
Chains walk a named Cypher-syntax path pattern, parsed by GraphGlot and
lowered to clingo over held/4, replacing the composed field list. Devlog
records the design path and the Cypher-to-Datalog tool survey.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016xS6nL8ZZpTh1jMSvaTRks
GraphGlot moves to the rejected alternatives: luria writes the lowering
either way, and a 15-line grammar accepts only the subset. Firing a
scratch grammar corrected the node-label rule (a bare back reference is
allowed); the devlog carries the grammar and its known gaps.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016xS6nL8ZZpTh1jMSvaTRks
A `patterns:` family declares paths in Cypher's pattern syntax. luria's own
lark grammar accepts only the subset and names the constructs it refuses;
pattern_rules lowers a parse to clingo over held/4, so the logic core stays
the one engine. Labels, hops, properties and chain endpoints are checked at
load in consistency.lp. A chain names a pattern in `relation` or `sibling`;
its page shows what each step went through and lists paths that stop
partway, and a chain invariant covers pattern steps.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016xS6nL8ZZpTh1jMSvaTRks
examples/argument-map fires the feature on a real shape: two chains over
patterns through inference documents, one dropping challenged steps with
WHERE NOT, both listing an unfinished inference as open. The ADR now
matches the implementation (reading direction, open ends from both ends,
closures within one scheme, the name-clash rule); the devlog records what
building it corrected; the changelog fragment and project-memory describe
the feature.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016xS6nL8ZZpTh1jMSvaTRks
Tried on dmarx/nucleation, the Open list named six complete arguments:
they rest only on `grounds`, which the record's field group allows.
Whether a partial match is unfinished depends on the schema, so a record
states it as a pattern of its own; rendering one is #373. pattern_open,
its rules, the page section and their tests go. The ADR records the trial
as the reason, and the devlog records parity on nucleation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016xS6nL8ZZpTh1jMSvaTRks
Conflict in chains.py resolved by keeping both sides: #359's per-edge
relation tracking beside the pattern-aware `_edges`, and a step's tail
carries "through" before #359's `_also`. A pattern has no label, so its
name reads there ("also follows from"); the chain test pins it, and the
ADR and devlog say #359 merged first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016xS6nL8ZZpTh1jMSvaTRks
@dmarx
dmarx merged commit d0cc49a into main Oct 13, 2026
6 checks passed
dmarx added a commit to dmarx/nucleation that referenced this pull request Oct 13, 2026
The `argument` chain walks `argued_from` beside `rests_on`: a claim and the
claims an argument that is not Rejected draws it from, through the ARG's
`concludes` and `rests_on`. Steps through ARG-006, ARG-007 and ARG-008 now
render, each naming its argument. CI moves to luria 1.1.0, which carries
path patterns (dmarx/luria#372) and names a step's other parents by their
relation (dmarx/luria#359).


Claude-Session: https://claude.ai/code/session_016xS6nL8ZZpTh1jMSvaTRks

Co-authored-by: Claude <noreply@anthropic.com>
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.

A chain should be able to walk a named path pattern, not only a reference field

2 participants