Skip to content

feat: sentinel review — the archive owner's verdict, recorded on the file - #71

Merged
khaneight merged 1 commit into
mainfrom
feat/review-gate
Aug 27, 2026
Merged

feat: sentinel review — the archive owner's verdict, recorded on the file#71
khaneight merged 1 commit into
mainfrom
feat/review-gate

Conversation

@khaneight

Copy link
Copy Markdown
Owner

Stacked on #70. The permission system: approve, reject, request changes, comment.

$ sentinel review
Waiting on you — 1 item(s)

  [trait] distrusts-abstraction
      Treats an abstraction as unearned until something concrete has failed.
      unconfirmed reading of the author · persona/distrusts-abstraction.md

$ sentinel review distrusts-abstraction --reject \
    --note "too strong — I distrust *unearned* abstraction, not abstraction"
rejected — persona/distrusts-abstraction.md (2026-08-25)
  status is now `rejected`

No skill invokes it. It joins export in NOT_FOR_AGENTS: an agent that
can approve its own work has a permission system in name only.

Where verdicts live, and why they append

In the document's own frontmatter. A verdict recorded in meta/ comes apart
from the thing it was about the first time a file is renamed by hand.

They append rather than overwrite, because the history is the point — changes
requested → note → approved three weeks later
is the useful record, and a
single mutable field keeps only the last word. The operative verdict is the
latest that decided something; comment deliberately isn't one, so a remark
left on approved work does not un-approve it.

Three refusals

  • A verdict signed by nobody. With no --by, SENTINEL_REVIEWER or USER
    it stops rather than defaulting. A record attributed to a placeholder looks
    exactly like somebody agreeing, which is the one outcome this whole mechanism
    exists to prevent.
  • An ambiguous name. A trait and an article can share a slug. Recording a
    verdict on the wrong document is worse than recording none, so it names both
    and stops.
  • A partial view. The unreadable file could be the one the name really
    meant — or the one that made it ambiguous.

The append is textual

Like mv's citation rewriting, and for the same reason: the block is a file the
user also opens by hand, and a serde round trip would reorder its keys and strip
its comments to add four lines. Tested against a comment above title:, an
inline tags: [a, b], a key after the list, and an indented review: nested
under something else.

The entry itself is serialised, so a note containing colons, quotes and
newlines is YAML's problem rather than this function's. A review: already
written as a populated inline list is refused with instructions rather than
mangled.

Traits carry their standing twice

status: is what a reader sees at the top of the file; review: is the history
behind it. Deriving one silently from the other would mean a file that reads
proposed while the archive treats it as affirmed — so both are written
together and verdict-disagrees-with-status reports them drifting. Articles get
no such treatment: their status is maturity, and stable means finished,
not signed.

CLAUDE.md's budget: 12,000 → 14,000

Raised after compressing, not instead of it. The repository file absorbed a
second document schema, a permission system and a ladder rung while growing 325
bytes net: mv/rm reasoning and the case-sensitivity recipe moved to
docs/design-notes.md, the command reference lost what --help already says,
and "derived sets" and "one source of truth" turned out to be one invariant
written twice. The reasoning is recorded on the constant, with an instruction to
compress first and say what came out before raising it again.

484 tests (+25), clippy and fmt clean.

🤖 Generated with Claude Code

…file

Every other command is the tool's opinion. This one is the user's, and it
is the only writer of `review:`. No skill invokes it: an agent that can
approve its own work has a permission system in name only, so `review`
joins `export` in the deliberately-not-agent-reachable list.

Verdicts live in the document's own frontmatter, articles and persona
traits alike. A verdict recorded in `meta/` comes apart from the thing it
was about the first time a file is renamed by hand. They append, because
the history is the point — "changes requested, then a note, then approved
three weeks later" is the useful record, and a single mutable field keeps
only the last word. The operative verdict is the latest that *decided*
something; `comment` is deliberately not one, so a remark on approved
work does not un-approve it.

Three things it refuses to do. It will not attribute a verdict to a
default: with no `--by`, `SENTINEL_REVIEWER` or `USER` it stops, because
a record signed by nobody looks exactly like somebody agreeing. It will
not resolve an ambiguous name — a verdict on the wrong document is worse
than none. And it will not decide from a partial view, since the
unreadable file could be the one the name really meant.

The append is textual and confined to the frontmatter block, like
`mv`'s citation rewriting and for the same reason: the block is a file
the user also opens by hand, and a serde round trip would reorder its
keys and strip its comments to add four lines. The entry itself is
serialised, so quoting a note with colons, quotes and newlines is YAML's
problem rather than this function's. A `review:` already written as a
populated inline list is refused with instructions rather than mangled.

A persona trait carries its standing twice — `status:` is what a reader
sees at the top of the file, `review:` is the history behind it — so both
are written together and `verdict-disagrees-with-status` reports them
drifting apart. `/sentinel-improve` gains the repairs, and each one fixes
a malformed record rather than supplying a missing one: a bad date can be
recovered from context, a missing `by` never can.

CLAUDE.md's context budget rises 12,000 → 14,000, after compressing
rather than instead of it. The repository file absorbed a second document
schema, a permission system and a ladder rung while growing 325 bytes:
`mv`/`rm` reasoning and the case-sensitivity recipe moved to
docs/design-notes.md, the command reference lost what `--help` already
says, and "derived sets" and "one source of truth" turned out to be one
invariant written twice.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@khaneight
khaneight changed the base branch from feat/learn-rung to main August 27, 2026 02:18
@khaneight
khaneight merged commit e0ce4bf into main Aug 27, 2026
6 checks passed
@khaneight
khaneight deleted the feat/review-gate branch August 27, 2026 02:33
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.

1 participant