feat(gate): session-gate.sh - R3/R4/R6 checks before a session starts
Order 20260926T070037Z-680859139 (.claude). A read-only, zero-model-call check any session starter can call first: exit 0 allows, exit 2 denies with one stderr line naming the rule and what is missing. - R4: PLAN.md holds exactly one gate line, <!-- gate: gjenfortelling=<time>; dekning=komplett; r1-proeve=<time|->; foerste-maaling=<time|->; retning=<name> --> - R3: the Logg table's Bevegelse (ja|nei) and Kvote (N%) columns. Deny at >= 10% of the weekly quota without movement since the last row that moved. - R6: cap 1 live session per repo (claude agents --json --cwd <repo>) until r1-proeve and foerste-maaling are both times, then 2. Night 23:00-07:00 or --night needs r1-proeve. Fail-closed: unmeasured rows, a missing claude binary, a non-JSON listing, duplicate or unknown keys and usage errors all deny. Measured: claude agents --cwd matches the directory a session was STARTED in, not its printed cwd, so --repo must be the main checkout root. Chose NOT to call it from board.sh --dispatch, because a dispatch line is generated before the start (a live count would be stale), and live-session reading is the process axis board.sh refuses by design. session-gate-selftest.sh: 0 -> 97 checks. It was red 91 of 95 before the script existed, then 97/97 green. Ten mutants all die. It is wired into npm test as the sixth suite, and the README badge goes from 1006 to 1103. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
6c9b17277c
commit
02d4df2dfb
5 changed files with 794 additions and 10 deletions
56
CLAUDE.md
56
CLAUDE.md
|
|
@ -1533,6 +1533,47 @@ marketplace plugin. Three components, one boundary:
|
|||
repo is macOS-only (BSD `stat`), and a case-sensitive APFS volume is the
|
||||
remaining gap.
|
||||
|
||||
- **Session gate (`scripts/session-gate.sh`):** may a new session start in a
|
||||
repo? Exit 0 = allow, 2 = deny with ONE stderr line naming the rule. Zero
|
||||
model calls, writes nothing. Pinned by `session-gate-selftest.sh` (97 checks).
|
||||
Order 20260926T070037Z-680859139 (`.claude`), turning three decisions from
|
||||
the 2026-09-25 retro into a mechanism: R4 (one `<!-- gate: ... -->` line in
|
||||
PLAN.md with the operator's yes to the retelling as a time and
|
||||
`dekning=komplett`), R3 (the Logg table's Bevegelse/Kvote columns; deny at
|
||||
>= 10% of the weekly quota without movement since the last row that moved)
|
||||
and R6 (cap 1 live session per repo until `r1-proeve` AND `foerste-maaling`
|
||||
are times, then 2; night 23:00-07:00 or `--night` needs `r1-proeve`).
|
||||
|
||||
**Fail-closed everywhere, and "unmeasured" is never zero.** A Logg row whose
|
||||
Kvote is not a percent or whose Bevegelse does not start with `ja`/`nei`
|
||||
denies. So do a missing `claude` binary, an agents listing that is not a
|
||||
JSON array, two gate lines, two Logg sections, a list item inside Logg,
|
||||
unknown or repeated gate keys, and an unusable `SESSION_GATE_NOW`. A usage
|
||||
error denies too. Only `--kind read` skips the live count, because only
|
||||
build sessions are capped. PLAN.md is read from DISK (it is local-only
|
||||
wherever the remote is public) and its name matched byte for byte, the
|
||||
same glob board.sh's PLAN column uses.
|
||||
|
||||
**`claude agents --json --cwd` filters on the directory a session was
|
||||
STARTED in, not on the `cwd` field it prints.** Measured 2026-09-26 on the
|
||||
building session itself. It started in this repo's root and then entered a
|
||||
worktree. `--cwd <root>` returned it; `--cwd <that worktree>` returned `[]`.
|
||||
So `--repo` must be the main checkout root, and a caller passing a worktree
|
||||
path would under-count. A path that does not exist also returns `[]` with
|
||||
exit 0, which is why the repo is resolved before the query. Every listed
|
||||
session counts, interactive or background, whatever its state, because the
|
||||
listing cannot tell build from read.
|
||||
|
||||
**`board.sh --dispatch` deliberately does NOT call it.** A dispatch line is
|
||||
generated before the start, often long before, so a live count taken then is
|
||||
stale when the session starts. And a live-session reading is the process
|
||||
axis board.sh refuses structurally (no `pgrep`/`lsof`, FLY is never
|
||||
liveness). The first caller is `.claude`'s budget hook, wired by the PM.
|
||||
**The caveat is part of the contract and stands in `--help`:** the gate
|
||||
checks that fields are filled in, not that they were thought through. The
|
||||
retelling is the only control with real content, because the operator reads
|
||||
it.
|
||||
|
||||
- **Skills (`skills/coord-send/`, `skills/board/`, `skills/route/`, `skills/dispatch/`):** natural-language front
|
||||
doors mapping user intent to engine invocations. No mailbox logic lives here
|
||||
either. `board` additionally owns the *ranking* — which repo wins and why —
|
||||
|
|
@ -1602,8 +1643,9 @@ obligations in another repo.
|
|||
`bash scripts/coord-selftest.sh` must exit 0 (266/266),
|
||||
`bash scripts/board-selftest.sh` must exit 0 (497/497),
|
||||
`bash scripts/route-selftest.sh` must exit 0 (73/73),
|
||||
`bash scripts/orders-selftest.sh` must exit 0 (116/116) and
|
||||
`bash scripts/state-line-guard-selftest.sh` must exit 0 (54/54).
|
||||
`bash scripts/orders-selftest.sh` must exit 0 (116/116),
|
||||
`bash scripts/state-line-guard-selftest.sh` must exit 0 (54/54) and
|
||||
`bash scripts/session-gate-selftest.sh` must exit 0 (97/97).
|
||||
- English for all code, docs, and commit messages (public repo). Norwegian
|
||||
trigger aliases in the skill description are deliberate.
|
||||
- **README's Install block is FENCED (```bash) while the rest of the README
|
||||
|
|
@ -1653,9 +1695,10 @@ obligations in another repo.
|
|||
## Commands
|
||||
|
||||
- Test: `bash scripts/coord-selftest.sh`, `bash scripts/board-selftest.sh`,
|
||||
`bash scripts/route-selftest.sh`, `bash scripts/orders-selftest.sh` and
|
||||
`bash scripts/state-line-guard-selftest.sh` (or `npm test`, the Node wrapper
|
||||
around all five plus the hook tests and the README-number check).
|
||||
`bash scripts/route-selftest.sh`, `bash scripts/orders-selftest.sh`,
|
||||
`bash scripts/state-line-guard-selftest.sh` and
|
||||
`bash scripts/session-gate-selftest.sh` (or `npm test`, the Node wrapper
|
||||
around all six plus the hook tests and the README-number check).
|
||||
`tests/tracked-terms.test.mjs` fails when any tracked path or line matches
|
||||
a term in the untracked `tests/excluded-terms.local.md`, and SKIPS, loudly,
|
||||
when that file is absent - the term list itself is never committed.
|
||||
|
|
@ -1682,6 +1725,9 @@ obligations in another repo.
|
|||
strong --reversibility cheap --scope local --rationale smoke` (read-only;
|
||||
needs a non-empty `/tmp/x.prompt`. Use `--target-pane yes` in a smoke test:
|
||||
it produces no plan file, so nothing can be handed to `morning` by accident)
|
||||
- Session gate smoke test: `bash scripts/session-gate.sh --repo <dir>`
|
||||
(read-only; exit 0 allow, 2 deny with one line; `--kind read` skips the
|
||||
live-session count)
|
||||
- Route smoke test: `bash scripts/route.sh --path known --verification strong
|
||||
--reversibility cheap --scope local --rationale x` (writes nothing, instant)
|
||||
- Sweep smoke test: `bash scripts/coord-sweep.sh` (dry-run is the default, so
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue