Skip to content

An acknowledgement can carry its own deadline - #247

Merged
dmarx merged 1 commit into
mainfrom
claude/directive-ttl
Sep 13, 2026
Merged

dmarx merged 1 commit into
mainfrom
claude/directive-ttl

Conversation

@dmarx

@dmarx dmarx commented Sep 12, 2026

Copy link
Copy Markdown
Owner

Closes #58 — the date half. The version half is deferred with the reason written down; see below.

Summary

A directive is a promise about the future. Some are permanent — inactive-ok-file: ADR-010 — this page is that history will be true for as long as the page exists. Others are not, and #58 names the shape: reference a proposed decision that you want to circle back to. "Circle back" has a horizon; the acknowledgement doesn't. So it outlives the intent that produced it, silently, because its entire job is to silence a check.

That is the failure directives exist to prevent, reproduced one level up.

<!-- inactive-ok: ADR-028 until 2026-10-01 — revisit when the API settles -->

After that date luria behaves as if the directive were never written. The date is inclusive — good on the 1st, gone on the 2nd, because somebody writing it means the last day it holds.

Three things that make it this shape

It is part of the syntax, not a feature of one directive. until is read by the same parser that reads the name and the scope suffix, so every directive gets it — ADR-008's "a third directive is a name, not a new syntax", kept for a modifier too. A test walks inactive-ok, unexempt, target-ok and url-ok to hold that.

An expired directive is dropped at find. That is the one place directives are read — ten call sites go through it — so dropping it there makes "behaves as if the annotation isn't even there" true everywhere without a single consumer knowing the feature exists. The expiry is lifted out of args on the way, because it is a modifier rather than one of the things the directive names; leaving it in would make every consumer that validates arguments report until as an unknown one.

Inert must not mean invisible. luria lint reports what expired as its own expired-directives finding — file, directive, date, and the author's own reason:

luria: 1 acknowledgement(s) have expired
  docs/adopting.md:1: `inactive-ok: ADR-010` expired 2026-09-01 — check the naming story again after 1.0

Separate from "no longer apply", because they are different facts: a stale acknowledgement means the subject moved under it (the document went Active), an expired one did its job and ran out of the time its author gave it. A check that starts failing again with the acknowledgement still sitting above it is a puzzle, not a report.

A date it can't read leaves the directive live, and says so. until nextweek is a typo. Dropping a suppression over one breaks a build for a reason the failure message would not explain; keeping it and naming the typo says what to fix without breaking anything. The third option — a typo quietly meaning "forever" — is the exact rot this stops.

What's deferred, and why

until 1.0.0, the version-milestone flavour you raised by pointing at #189. Right idea for a deadline that's about a release rather than a calendar, and not shippable yet: it needs to know which version, and luria has no concept of the project's own version at all. luria/__init__.py carries the package's version, not the record's, and an adopter's record isn't luria. #189's own sunset is a prose string for exactly that reason.

Deferred rather than declined — when a project-version source exists, until 1.0.0 parses in the same slot and the only new code is the comparison. The ADR records that so the next person doesn't re-derive it.

A relative duration (for 2w, how you first phrased it) is declined outright, on the anchor: a duration is measured from something, and a comment carries no reliable "written on". Git says when the line last moved, which isn't when the promise was made — a reformat would silently extend every deadline in the repo. The absolute date is the anchor, written where the reader can see it. "Two weeks" stays the way to think about it; a helper that does the arithmetic is a convenience this doesn't need to decide.

Nothing here uses it yet, deliberately

Every acknowledgement currently in this record is permanent — this paragraph is about that decision, this page is that history. Giving one an invented deadline to demonstrate the feature would be writing down a decision nobody made, which is a different thing from the dogfooding #245 needed, where the record genuinely wanted the new behaviour and the old one was broken. The first real use is the next provisional acknowledgement somebody writes.

Changelog

  • record/changelog.d/claude-directive-ttl.md

Devlog

  • No entry — one feature, no archaeology; the reasoning and both deferrals are in the ADR
  • Adds an ADR — the generated views come from CI's docs-generate job

Verification

Test-first, 15 cases in tests/test_directive_ttl.py written against a parser that didn't have the field yet, watched fail, then implemented. They pin the boundary in both directions (live on the date, gone the day after), the partition (find and find_expired never both return the same directive), uniformity across four directive names, and that a malformed expiry keeps the codes it names so whatever it was excusing stays excused.

Fired once on a real file before trusting the report — planted <!-- inactive-ok: ADR-010 until 2026-09-01 — … --> at the top of docs/adopting.md, ran luria lint, got the line quoted above, reverted. The unit tests build their own fixtures; this is the one that proves the corpus sweep reaches a real document.

The clock is injected. find(..., as_of=...) defaults to today and follows adr_pending's existing convention. A test that depends on the wall clock passes until the day it doesn't.

python -m pytest tests -q1116 passed, 33 skipped (1101 + 15), with no existing test modified — a directive without until parses exactly as before, which is the upgrade-safety claim and is pinned directly. luria lint clean.

🤖 Generated with Claude Code

https://claude.ai/code/session_01YSRQzpm4kAWQSaKWD6V9x2


Generated by Claude Code

A directive is a promise about the future. Some of those promises are
permanent -- "this page is that history" will be true for as long as the
page exists. Others are not: #58 asks for "reference a proposed decision
you want to circle back to", and "circle back" has a horizon the
acknowledgement does not. So it outlives the intent that produced it,
silently, because its whole job is to silence a check. That is the
failure directives exist to prevent, one level up.

    <!-- inactive-ok: ADR-028 until 2026-10-01 — revisit when the API settles -->

After that date `find` does not return the directive at all. That
function is the one place directives are read -- ten call sites go
through it -- so dropping it there makes "behaves as if the annotation
isn't even there" true everywhere without a single consumer knowing the
feature exists. `until` is parsed by the same parser that reads the name
and the scope suffix, so it is part of the shape rather than a feature
of one word, and it is lifted out of `args` because it is a modifier and
not one of the things the directive names. The date is inclusive: good
on the 1st, gone on the 2nd.

Two loudness rules follow from what this is for. What expired is
reported, as its own lint finding rather than folded into "no longer
apply" -- those are different facts, and inert must not mean invisible.
And a date that cannot be read leaves the directive LIVE and is
reported: dropping a suppression over a typo breaks a build for a reason
the message would not explain, but a typo silently meaning "forever" is
the exact rot this stops.

Deferred, with the reason recorded: a version milestone (`until 1.0.0`,
the other half of #58) needs a project-version concept luria does not
have -- __init__ carries the package's version, not the record's, and an
adopter's record is not luria. It parses in the same slot when one
exists.

Nothing here uses `until` yet, deliberately: every acknowledgement in
this record is permanent, and inventing a deadline to demo the feature
would be writing down a decision nobody made.

Fired once before trusting it: a planted `until 2026-09-01` on
docs/adopting.md reported "docs/adopting.md:1: `inactive-ok: ADR-010`
expired 2026-09-01 — check the naming story again after 1.0", then
reverted.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YSRQzpm4kAWQSaKWD6V9x2
@dmarx
dmarx merged commit dbd8537 into main Sep 13, 2026
5 checks passed
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.

annotations with TTL

2 participants