Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
98d5c67
ledger: record a reason on each supersession
NovusEdge Oct 2, 2026
049e5aa
ledger: add review lines, folded in on read
NovusEdge Oct 2, 2026
d091602
support: derive whether each record's grounds still stand
NovusEdge Oct 2, 2026
45aa9aa
ledger: block a prerequisite that resolves back onto its dependent
NovusEdge Oct 2, 2026
c411603
cli: --supersede-reason, docket review, and relabelling by correction
NovusEdge Oct 2, 2026
c9cd4b7
completion: restore the space before -a in the fish show/review line
NovusEdge Oct 2, 2026
1fb62f5
context, show, graph: surface records that owe review or lost their g…
NovusEdge Oct 2, 2026
1b9cf3a
context: keep unsupported records available as prerequisites
NovusEdge Oct 2, 2026
f04b4da
docs: supersession reasons, support status and review
NovusEdge Oct 2, 2026
9956de9
support: flag circular support instead of losing it
NovusEdge Oct 2, 2026
55d7239
support: let a review clear a circular flag; reword prerequisite docs
NovusEdge Oct 2, 2026
9ac310d
context: resolve prerequisites to their chain head in blocked lines; …
NovusEdge Oct 2, 2026
796bac5
cli: count reviews apart in check; no revise hint on questions; compl…
NovusEdge Oct 2, 2026
d757943
tests: a retired prerequisite blocks only when reversed
NovusEdge Oct 2, 2026
dd1d659
docs: prerequisites follow the chain head; derived support fields; re…
NovusEdge Oct 2, 2026
0fc0489
ledger: record the circular-support, digest and prerequisite decisions
NovusEdge Oct 2, 2026
d24045f
support: a review acknowledges only the grounds the record owns
NovusEdge Oct 2, 2026
657226b
context: --since prints a changed record once, not again as newly fla…
NovusEdge Oct 2, 2026
dd29c2e
ledger: correct the forward-cycle comment to the blocking rule
NovusEdge Oct 2, 2026
11a272e
ledger: record the review-scope decision and correct the digest ratio…
NovusEdge Oct 2, 2026
6a1a978
support: a pin on an owned cause no longer lifts the flag or block it…
NovusEdge Oct 2, 2026
97b5c46
support: a circular head the greatest fixed point also flags carries …
NovusEdge Oct 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .docket/ledger.jsonl
Original file line number Diff line number Diff line change
Expand Up @@ -138,3 +138,8 @@
{"schema":2,"kind":"claim","id":"c138","text":"Treating retirement as withdrawal, the any-surviving-set rule would retract 27 of 147 current reasoned records across eleven project ledgers, and most of those retirements restate or refine the ground rather than withdraw it.","state":"accepted","ts":"2026-10-02T13:56:44+00:00","author":"claude-code","session":"","branch":"main","scope":["docs/definitions.md","docs/north-star.md","docs/outcome-formalism.md"],"rationale":"About 22 of the 27 rest on a ground superseded by a restatement or refinement: docket d38 by d88, stoat d25 by d38, nimblefox d10 by d89. Three rest on c62, which was disputed and corrected by c105, the defeat the rule exists for. Two rest on unassessed claims. nimblefox c90 explicitly records that its dependents survived d69's replacement. Across 600 claims and decisions no record is rejected or revoked; grounds leave only by supersession or dispute.","supports":[["d137"]],"depends_on":[],"answers":[],"supersedes":[],"evidence":[{"ref":"docket list --superseded --json across docket, tessera, nimblefox-metaarch, hippocampus, relex-context, NovusEdge.github.io, locus-core, stoat, aiben, bot.sales, ocloak (2026-10-02)"}],"revisit":"When a ledger starts using rejected or revoked states, or when supersession gains a typed reason","cost_if_wrong":"Shipping automatic retraction on this reading would retract mostly sound records and train users to ignore it.","pinned":false}
{"schema":2,"kind":"question","id":"q139","text":"When a supporting record is superseded, should its dependents lose that ground, keep it through the replacement, or be flagged for review?","state":"open","ts":"2026-10-02T13:56:52+00:00","author":"claude-code","session":"","branch":"main","scope":["docs/definitions.md","docs/north-star.md","docs/outcome-formalism.md"],"rationale":"Supersession records replacement, and the formal model only has withdrawal. Passing support to the replacement silently keeps dependents of a changed ground, as with nimblefox d69 to d97, where the substance changed. Withdrawing it retracts dependents of a mere restatement. Dispute is the one observed withdrawal.","supports":[["c138"]],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"Phase 4 retraction picks one reading by default, and either default is wrong for a large share of records.","pinned":false}
{"schema":2,"kind":"decision","id":"d140","text":"Each supersession carries a reason that decides what records citing the retired record keep.","state":"adopted","ts":"2026-10-02T14:48:54+00:00","author":"claude-code","session":"","branch":"main","scope":["docs/definitions.md","docs/outcome-formalism.md","experiments/lean-outcomes/**","docket/ledger.py"],"rationale":"Recorders already write the reason as free text in supersession headlines, and without it about 22 of the 27 survey flags would be restatements, which is the volume that produces rubber-stamped review. reverse gives supersession a way to withdraw, which the disputed-then-corrected case needed. The revise default makes the self-reported reason fail toward review. Lean section 7 checks strict within clean within live and the three per-reason outcomes.","supports":[["c138","d137"]],"depends_on":[],"answers":["q139"],"supersedes":[],"evidence":[],"revisit":"When a reviewer finds a restate that changed substance, or when flags pile up unreviewed","cost_if_wrong":"A schema field and derived flag state ship to every ledger; a wrong reason vocabulary means a migration of recorded reasons.","pinned":false,"choice":"Three reasons. restate: the commitment is unchanged; dependents resolve forward to the chain head and stay clean. revise: the substance changed; dependents resolve forward and are flagged for review. reverse: the retired record was wrong; dependents lose that ground and hold only through another complete set. A missing reason, including on every existing record, reads as revise.","alternatives":["No reason field: every supersession resolves forward and flags dependents for review.","Treat every supersession as withdrawal, so dependents lose the ground.","Resolve forward silently with no flag."],"decided_by":"user"}
{"schema":2,"kind":"decision","id":"d141","text":"Support that is circular and never grounded is flagged for review, not lost.","state":"adopted","ts":"2026-10-02T17:35:04+00:00","author":"claude-code","session":"","branch":"feat/supersession-reasons","scope":["docket/support.py","docs/definitions.md","experiments/lean-outcomes/**"],"rationale":"This repository's ledger has a cycle created only by forward resolution (c105 cites c104, c104 cites c62, c62 resolves to c105). The least fixed point made five records unsupported although nothing was withdrawn. The well-founded reading treats ungrounded support as unknown, and unknown maps to review.","supports":[["d140"]],"depends_on":[],"answers":[],"supersedes":[],"evidence":[{"ref":"tests/test_support_smoke.py"}],"revisit":"","cost_if_wrong":"Genuinely self-justifying records read as owing review rather than as lost.","pinned":false,"choice":"Evaluate a least and a greatest fixed point; a record where they differ is flagged with because: circular. A reversal, rejection or revocation inside the cycle still removes the ground. A later rising pass lets a review of the cycle's frontier clear the records above it.","alternatives":["Least fixed point only: circular support is unsupported."],"decided_by":"user"}
{"schema":2,"kind":"decision","id":"d142","text":"The revision digest excludes the derived support fields.","state":"adopted","ts":"2026-10-02T17:35:04+00:00","author":"claude-code","session":"","branch":"feat/supersession-reasons","scope":["docket/context_model.py"],"rationale":"The fields are a pure function of the hashed history, so the digest loses nothing, and --since tokens minted before the upgrade keep matching.","supports":[["d140"]],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"A change in evaluation code alone never moves the digest, so a resumed session is not told statuses moved after an upgrade.","pinned":false,"choice":"context_model._canonical_history drops support, review_owed and lost_grounds before hashing.","alternatives":["Hash them and regenerate every golden briefing, invalidating --since tokens on upgrade."],"decided_by":""}
{"schema":2,"kind":"decision","id":"d143","text":"An unsupported prerequisite does not block a decision; only a reversal or a rejected or revoked chain head blocks.","state":"adopted","ts":"2026-10-02T17:35:05+00:00","author":"claude-code","session":"","branch":"feat/supersession-reasons","scope":["docket/context_select.py","docket/context_delta.py","docket/ledger.py"],"rationale":"Applicability and support are separate axes: a decision's prerequisite gate follows the commitment chain, and a record's own grounds are reported on the record. Mixing them printed blocked lines beside applicable: true.","supports":[["d140"]],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"A decision resting on a claim whose grounds are gone shows no blocked line; the claim's own lost line carries the signal.","pinned":false,"choice":"Prerequisites resolve forward like supports; context_select._available keeps ignoring support status, and only the --since delta counts a newly unsupported record as no longer available.","alternatives":["Treat an unsupported record as unavailable everywhere, including briefing blocking and admission."],"decided_by":""}
{"schema":2,"kind":"correction","id":"d142.1","corrects":"d142","fields":{"rationale":"The fields are a pure function of the hashed history, so the digest loses nothing. A --since token minted before the upgrade keeps matching unless a decision's applicability changed under the new prerequisite rule; applicable and blocked_by are still hashed, so that ledger falls back to a full briefing."},"reason":"overstated token survival; applicability fields are still hashed","ts":"2026-10-02T20:00:35+00:00","author":"claude-code","session":"","branch":"feat/supersession-reasons"}
{"schema":2,"kind":"decision","id":"d144","text":"A review acknowledges only grounds whose problem the reviewed record owns.","state":"adopted","ts":"2026-10-02T20:00:35+00:00","author":"claude-code","session":"","branch":"feat/supersession-reasons","scope":["docket/support.py","docket/reviews.py","skills/docket/SKILL.md"],"rationale":"A pin is keyed on (ground, head); for a ground never superseded the head never moves, so pinning an inherited flag waived every later revision or block underneath it. Inherited flags clear on their own once the frontier record is reviewed.","supports":[["d140"]],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"A record resting on a blocked decision cannot be acknowledged from above; its owner must fix the decision.","pinned":false,"choice":"docket review pins revised, unassessed, disputed and circular grounds; a ground flagged because it is itself a flagged record, or a blocked decision, is cleared by reviewing or fixing that ground, and a review that would pin only those is refused with the grounds to look at.","alternatives":["Pin every owed ground, whatever its cause.","Store the cause in each pin and lift only while it still matches."],"decided_by":""}
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- A supersession can carry `--supersede-reason restate|revise|reverse`, and every claim and decision derives `support` (`clean`, `flagged` or `unsupported`) with `review_owed` and `lost_grounds`. A citation follows supersessions to the head of its chain: a restatement keeps the citing record clean, a revision flags it, a reversal removes the ground. `unassessed`, `disputed` and blocked grounds flag; `rejected` and `revoked` grounds are lost. Support that is circular and never grounded is flagged with `because: circular` rather than lost.
- `docket review ID [--note TEXT]` acknowledges a record's own revised, unassessed, disputed or circular grounds against the heads it was reviewed against. A later revision of the same chain raises the flag again. A flag inherited from a flagged or blocked ground clears by reviewing or fixing that ground; `docket review` refuses a record that owes only those, and review the frontier first.
- `docket correct ID --supersede-reason VALUE` relabels a supersession. `--where` takes `is:flagged` and `is:unsupported`. `show --json` and `list --json` carry `support`, `review_owed`, `lost_grounds` and `reviews`.

### Changed

- A prerequisite in `depends_on` now follows restatements and revisions to the head of its chain instead of blocking the decision; only a reversal, or a rejected or revoked head, blocks. A `--since` token minted before the upgrade still matches unless a decision's applicability changed under the new prerequisite rule; then it prints a full briefing.
- Every supersession recorded before this release reads as `revise`, so records citing a superseded record owe review after upgrading. Relabel restatements with `docket correct ID --supersede-reason restate`.
- A ledger with any `supersede_reason`, a correction of one, or a review line cannot be read by docket 0.20.x or older. Upgrade every machine and plugin cache that reads the ledger first.

## [0.20.2] - 2026-10-02

### Fixed
Expand Down
6 changes: 5 additions & 1 deletion docket/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@
from docket.cli.graph import cmd_graph
from docket.cli.query import cmd_filter_ids, cmd_list, cmd_show, cmd_where
from docket.cli.record import cmd_claim, cmd_decision, cmd_question
from docket.cli.review import add_review_parser
from docket.cli.selfupdate import cmd_update, cmd_update_fetch
from docket.ledger import KINDS, STATES, LedgerError
from docket.support import REASONS
from docket.where import WhereError


Expand All @@ -38,6 +40,7 @@ def _add_shared_args(p: argparse.ArgumentParser) -> None:
p.add_argument("--depends-on", default="", metavar="CSV")
p.add_argument("--answers", default="", metavar="CSV")
p.add_argument("--supersedes", default="", metavar="CSV")
p.add_argument("--supersede-reason", choices=REASONS, help="restate, revise or reverse")
p.add_argument("--evidence", action="append", default=[])
p.add_argument("--revisit", default="")
p.add_argument("--cost", default="")
Expand All @@ -55,7 +58,7 @@ def main(argv: list[str] | None = None) -> int:
sub = p.add_subparsers(
dest="cmd",
metavar=(
"{claim,decision,question,correct,list,show,graph,context,where,check,"
"{claim,decision,question,correct,review,list,show,graph,context,where,check,"
"rebase,migrate,init,feature,completion,update}"
),
)
Expand All @@ -81,6 +84,7 @@ def main(argv: list[str] | None = None) -> int:
qu.set_defaults(func=cmd_question)

add_correct_parser(sub)
add_review_parser(sub)

ls = sub.add_parser("list", help="list records")
ls.add_argument("--kind", choices=KINDS)
Expand Down
10 changes: 8 additions & 2 deletions docket/cli/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ def cmd_check(args: argparse.Namespace) -> int:
highest = 0
records = 0
correction_lines = 0
review_lines = 0
for number, line in enumerate(lines, 1):
if not line.strip():
continue
Expand All @@ -152,8 +153,11 @@ def cmd_check(args: argparse.Namespace) -> int:
except json.JSONDecodeError as exc:
faults.append(f"line {number}: invalid JSON: {exc.msg}")
continue
if isinstance(record, dict) and record.get("kind") == "correction":
correction_lines += 1
if isinstance(record, dict) and record.get("kind") in ("correction", "review"):
if record["kind"] == "review":
review_lines += 1
else:
correction_lines += 1
# Kept out of seen: a feature that names a correction id names
# nothing a brief can attach, and the feature check reports it.
try:
Expand Down Expand Up @@ -195,6 +199,8 @@ def cmd_check(args: argparse.Namespace) -> int:
summary = f"{records} record{'s' if records != 1 else ''}"
if correction_lines:
summary += f" and {correction_lines} correction{'s' if correction_lines != 1 else ''}"
if review_lines:
summary += f" and {review_lines} review{'s' if review_lines != 1 else ''}"
print(f"docket: {path} reads cleanly, {summary}")
else:
print(f"docket: {path} has {len(faults)} fault{'s' if len(faults) != 1 else ''}")
Expand Down
13 changes: 10 additions & 3 deletions docket/cli/completion.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
"--depends-on",
"--answers",
"--supersedes",
"--supersede-reason",
"--note",
"--evidence",
"--revisit",
"--cost",
Expand Down Expand Up @@ -54,6 +56,7 @@
"decision",
"question",
"correct",
"review",
"list",
"show",
"graph",
Expand Down Expand Up @@ -120,7 +123,8 @@
fi
case "$prev" in
--state) COMPREPLY=($(compgen -W "unassessed accepted disputed rejected adopted revoked open resolved" -- "$cur")); return ;;
--supports|--depends-on|--answers|--supersedes|show)
--supersede-reason) COMPREPLY=($(compgen -W "restate revise reverse" -- "$cur")); return ;;
--supports|--depends-on|--answers|--supersedes|show|review)
COMPREPLY=($(compgen -W "$(docket list --oneline 2>/dev/null | awk '{{print $1}}')" -- "$cur")); return ;;
completion) COMPREPLY=($(compgen -W "bash zsh fish" -- "$cur")); return ;;
esac
Expand All @@ -146,7 +150,7 @@
'*::arg:->args'

case $words[1] in
show) _docket_ids ;;
show|review) _docket_ids ;;
completion) _values 'shell' bash zsh fish ;;
feature)
case $words[2] in
Expand All @@ -171,6 +175,7 @@
'--depends-on[decision prerequisites]:id:_docket_ids' \\
'--answers[question ids]:id:_docket_ids' \\
'--supersedes[retired ids]:id:_docket_ids' \\
'--supersede-reason[why it supersedes]:reason:(restate revise reverse)' \\
'--cost[cost if wrong]:cost:'
;;
esac
Expand All @@ -179,7 +184,9 @@
_FISH_COMPLETION = f"""\
set -l docket_cmds {" ".join(_COMPLETION_CMDS)}
complete -c docket -n "not __fish_seen_subcommand_from $docket_cmds" -a "$docket_cmds"
complete -c docket -n "__fish_seen_subcommand_from show" -a "(docket list --oneline 2>/dev/null | awk '{{print \\$1}}')"
complete -c docket -n "__fish_seen_subcommand_from claim decision question correct" -l supersede-reason -a "restate revise reverse"
complete -c docket -n "__fish_seen_subcommand_from review" -l note -r
complete -c docket -n "__fish_seen_subcommand_from show review" -a "(docket list --oneline 2>/dev/null | awk '{{print \\$1}}')"
complete -c docket -n "__fish_seen_subcommand_from claim decision question" -l supports -a "(docket list --oneline 2>/dev/null | awk '{{print \\$1}}')"
complete -c docket -n "__fish_seen_subcommand_from claim decision question" -l supersedes -a "(docket list --oneline 2>/dev/null | awk '{{print \\$1}}')"
complete -c docket -n "__fish_seen_subcommand_from claim decision" -l state -a "unassessed accepted disputed rejected adopted revoked"
Expand Down
6 changes: 6 additions & 0 deletions docket/cli/correct.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from docket import corrections, env
from docket.ledger import ID_RE, LedgerError, append, parse_evidence
from docket.support import REASONS

# Flags that change what a record commits to. Accepted by the parser only so
# the refusal can name supersession instead of argparse's generic error.
Expand Down Expand Up @@ -40,6 +41,8 @@ def _fields(args: argparse.Namespace) -> dict:
fields["alternatives"] = args.alternative
if args.decided_by is not None:
fields["decided_by"] = args.decided_by
if args.supersede_reason is not None:
fields["supersede_reason"] = args.supersede_reason
for name in args.clear:
if name in fields:
raise LedgerError(f"docket: --clear {name} and {CLEAR_FLAGS[name]} conflict")
Expand Down Expand Up @@ -95,6 +98,9 @@ def add_correct_parser(sub) -> None:
pin.add_argument("--unpin", action="store_true")
co.add_argument("--alternative", action="append", default=[])
co.add_argument("--decided-by")
co.add_argument(
"--supersede-reason", choices=REASONS, help="relabel why this record superseded another"
)
co.add_argument(
"--clear", action="append", default=[], choices=CLEARABLE, help="set a list field to empty"
)
Expand Down
9 changes: 8 additions & 1 deletion docket/cli/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from pathlib import Path
from typing import Any

from docket import ROOT, env, where
from docket import ROOT, env, support, where
from docket.cli.term import (
_DIM,
_GRAPH_GLYPHS,
Expand Down Expand Up @@ -68,6 +68,9 @@ def _node_info(e: dict, retired: dict[str, str]) -> dict:
"applicable": e.get("applicable"),
"blocked_by": e.get("blocked_by", []),
"decided_by": e.get("decided_by", ""),
"support": e.get("support"),
"review_owed": e.get("review_owed", []),
"lost_grounds": e.get("lost_grounds", []),
}


Expand All @@ -78,6 +81,10 @@ def _formula(sets: list[list[str]]) -> str:
def _blocked_text(info: dict) -> str:
if info.get("kind") == "decision" and info.get("applicable") is False:
return "! blocked by " + (", ".join(info.get("blocked_by", [])) or "prerequisites")
if support.surfaced(info, "unsupported"):
return "! lost " + ", ".join(o["ground"] for o in info["lost_grounds"])
if support.surfaced(info, "flagged"):
return "? review " + ", ".join(o["ground"] for o in info["review_owed"])
return ""


Expand Down
Loading
Loading