Skip to content

Add lint.inactive_ok: pattern rules for citations of retired documents that need no acknowledgement - #378

Open
dmarx wants to merge 2 commits into
mainfrom
claude/laughing-brahmagupta-17c985
Open

dmarx wants to merge 2 commits into
mainfrom
claude/laughing-brahmagupta-17c985

Conversation

@dmarx

@dmarx dmarx commented Oct 21, 2026 •

Copy link
Copy Markdown
Owner

Why

In nucleation, the QUESTION scheme has active: Open. When an Open parent question is split, its answered part becomes a child question with status Answered (nucleation's ADR-049). The parent then cites that child, and the retired-citations check warns on every such citation, because any status other than active counts as retired. The owner's rule is that an Open question citing an Answered one is legitimate. A citation from anywhere else should still warn: it either still treats the question as open, or it should cite the CLAIM or THEORY that answers it. Today the only remedy is an inactive-ok comment at each site.

What

lint.inactive_ok is the config-level form of the inactive-ok directive. It is a list of rules written in the existing pattern syntax (patterns.lark), with a reserved hop type cites:

lint:
  inactive_ok:
    - match: "(:QUESTION {status: 'Open'})-[:cites]->(:QUESTION {status: 'Answered'})"
      reason: a split parent cites the Answered child split off it

A citation of a retired document is sanctioned, and needs no acknowledgement, when the document it appears in matches the source node and the cited document matches the target node. Properties match any front-matter field (status, role, warrant and so on), as they do in patterns.X. The match: value has to be quoted in YAML, because an unquoted { starts a mapping.

Refused at load, each naming lint.inactive_ok[N]:

  • a value that is not a list, an entry that is not a mapping, or keys other than match and reason;
  • a missing or blank reason, mandatory for the same reason directive reasons are;
  • zero hops or more than one;
  • a hop typed anything but cites alone;
  • variable length, or WHERE NOT;
  • an unlabelled node;
  • an undeclared scheme, a property that scheme can't hold, or MATCH and similar keywords.

Labels and properties are checked by the same code that checks patterns.X. The grammar is not forked: patterns.parse and check_patterns each take an optional argument naming where the pattern was declared.

Dead rules (DP-015). A rule that sanctions nothing is reported as a stale-directives row, with its pattern and reason. An inactive-ok comment that a rule has made redundant is reported stale, with a message naming the rule.

Internals. A matching site emits sanctioned(I), and retired(I) in acknowledgements.lp now requires not sanctioned(I). Citation gains sanctioned and a loud property, which flagged(), ack.py and adr_pending.py use to decide what is unacknowledged. Sanctioned sites are not counted as acknowledged, because they aren't suppressions. The export's excused column keeps its meaning, "a directive acknowledges it".

History of this PR

The first commit added a per-scheme citable_from: {Answered: [QUESTION:Open]}. The owner asked for something more general, since status words are specific to one scheme and that form could only express status. The second commit replaces it with the pattern rules above. The ADR records citable_from as a rejected alternative, alongside:

  • active as a list, which would also silence a document still citing a now-Answered question as open;
  • per-site annotations only, which is the status quo;
  • a global "counts as in force" flag per status.

Record

  • ADR ADR-tmpfu1s6, numbered at merge.
  • A changelog fragment.
  • A devlog entry recording the guard being fired on a real case (DP-006). On a scratch copy of nucleation with the rule above:
    • the 13 split parents' inactive-ok comments went stale, each naming lint.inactive_ok[0];
    • unacknowledged rows stayed at 480;
    • 34 sites moved from "acknowledged" to "sanctioned".
  • config_doc gains a section, so the configuration reference documents the key.

Tests

  • tests/test_inactive_ok_rules.py, 27 tests; the 26 written first failed before the change.
  • Full suite: 1881 passed, 33 skipped.
  • luria lint gives the same finding classes and counts as main.

🤖 Generated with Claude Code

https://claude.ai/code/session_01KC8NN4NtUf8sd5unC6dpFV

A scheme may now declare which citers may cite one of its retired
statuses without an acknowledgement:

    citable_from:
      Answered: [QUESTION:Open]

A site whose citer document matches a declared (status, prefix, status)
triple is `sanctioned`: acknowledgements.lp no longer reads it as
retired, so it needs no inactive-ok and one written over it is reported
stale, with the reason. Citation.loud is the single test the flagged
report, `luria ack` and the pending report now use. The loader refuses
an unknown cited status, the scheme's own `active` word, an undeclared
citer scheme and an unknown citer status (consistency.lp), and malformed
shapes (config.py).

Fired on a copy of nucleation: its 13 parent-question inactive-ok
annotations go stale and no other finding changes.

Record: ADR-tmpfu1s6, changelog fragment, devlog entry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KC8NN4NtUf8sd5unC6dpFV
Review asked for a generic shape: the per-scheme `citable_from` could
only express status, and status words are scheme-specific. A citation
that needs no acknowledgement is now declared once, as the config-level
form of the `inactive-ok` directive:

    lint:
      inactive_ok:
        - match: "(:QUESTION {status: 'Open'})-[:cites]->(:QUESTION {status: 'Answered'})"
          reason: a split parent cites the Answered child split off it

The pattern is read by the existing pattern grammar over a reserved
`cites` edge; labels and properties go through check_patterns, which
(with patterns.parse) now takes where the pattern was declared. The
loader refuses, by lint.inactive_ok[N], anything but one `cites` hop,
variable length, WHERE NOT, unknown schemes or fields, unlabelled nodes,
and a missing reason. Matching sites still emit sanctioned(I); a
directive made redundant names the rule, and a rule that sanctions no
citation is a stale-directives row with its reason (DP-015).

Scheme.citable_from and its four consistency refusals are removed.
Fired on a copy of nucleation: the 13 parent annotations go stale,
naming lint.inactive_ok[0]; unacknowledged rows unchanged (480).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KC8NN4NtUf8sd5unC6dpFV
@dmarx dmarx changed the title Add per-scheme citable_from: let named citers cite a retired status without acknowledgement Add lint.inactive_ok: pattern rules for citations of retired documents that need no acknowledgement Oct 21, 2026

This branch has not been deployed

No deployments
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