dmarx / dmarx/luria

Declared transitions: a record has no idea how a document reached its status

Open
#192 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Python
Stars
21
Forks
2
Avg merge
6h 33m
Merged PRs (30d)
91

Description

Out of the discussion on #188. The most interesting part of the state-machine framing, and the part luria has nothing for at all.

## What is missing

Every check reads the *current* value of `status:`. Nothing knows how a document got there. So all of these are indistinguishable to the lint:

- `Proposed` → `Active` — the ordinary promotion
- `Active` → `Superseded` — the ordinary retirement
- `Rejected` → `Active` — **a decision the project turned down, quietly reinstated by editing one word**

The last one is the reason to care. DP-3 says retire by changing status, never by deleting, and `Superseded` names its successor — the record is built so that reversals leave a trace. But a status edit leaves no trace at all: the word changes and the history of the claim is in git, where no check is looking.

A scheme cannot currently say *"a rejected decision is not reinstated by editing it; it is superseded by a new one."*

## Sketch

```toml
[luria.schemes.ADR.transitions]
Proposed = ["Active", "Deferred", "Rejected"]
Deferred = ["Active", "Proposed", "Rejected"]
Active = ["Superseded", "Rejected"]
Superseded = [] # terminal
Rejected = [] # terminal: reopen by filing, not by editing
```

An undeclared `transitions` table means every transition is fine, which is every project today.

## The machinery mostly exists

This does not need a new way to see the past. #180 already established reading the last committed state as a baseline — `git grep -l` at HEAD, narrowed to the documents that declare the field in question, because reading every blob is too expensive. "What did this field say before this change" is a question the code already knows how to ask, and the same narrowing applies.

The pieces:

- the baseline reader from `relations.py`, generalized off relation fields;
- the declared vocabulary (#181) — transitions are only checkable because the values are now a closed set the scheme names;
- the same warn-first posture and acknowledgement directive as the other status findings, since a deliberate reinstatement is a real thing a person might mean.

## Decisions this needs first

**Is a record allowed to constrain its own history?** Every other check is a statement about the current tree. This one says a *legal* document is illegal because of what it used to be — which is a different kind of claim, and arguably the first one luria would make. Worth a decision document rather than an implementation.

**What is the baseline?** HEAD is the obvious answer and is what #180 uses, with a known sharp edge: a change committed before the check ran reads as "nothing changed". For relation completion that is self-correcting; for transitions it means a reinstatement committed in one step is invisible. Walking further back is `git log -S` territory and expensive.

**Does it survive a migration?** `luria migrate` rewrites codes and moves documents between schemes (ADR-040). A scheme rename would make every document look like it transitioned from nothing.

## Not to be confused with

- **#191** (`forbidden_when`) — which *combinations* are legal, at one moment. Combinatorial, cheap, no history.
- **#188** (correlated facets) — a distribution over the corpus, unpreventable by construction.

This one is the only one of the three that needs the past.

Contributor guide

Open the contributing guide

Research direction

Start with the baseline reader in relations.py and the last-committed-state behavior established by #180; then review the declared vocabulary from #181 and migration behavior in ADR-040. Resolve whether history constraints, the HEAD baseline, and migrations are supported before implementation. Done means an agreed transition design, likely recorded in a decision document.

Written by the indexing model from the issue text.

Assessment

Tech stack
git, python
Domain
devtools, tooling
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Needs clarification
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.