Skip to content

programme: the close-time promote-or-kill rule is unobserved, and its only test asserts the wording #3

Description

@vjsrinath

What the rule is

commands/handoff.md:100"Do not mark it closed while <docsRoot>/programmes/<SLUG>/deferred.md has open rows." Each must be promoted to the repo-wide deferred-work.md with its fix shape intact, or killed with a reason recorded in the archive. templates/deferred.md:6 states the same obligation from the other side.

This is the rule that stops repo-wide items being archived alive when a programme's directory becomes history. It is the transition the whole two-file split exists to protect.

The gap

The rule is executed by a model reading handoff.md. Nothing observes whether it happened.

All three of these close a programme, and none consults deferred.md:

  • running /programme:handoff and skipping step 6
  • editing INDEX.md's status column directly
  • closing on the user's say-so mid-session, without running /programme:handoff at all

The resulting state — INDEX.md saying closed while that programme's deferred.md holds open rows — is one the two files jointly forbid and neither reports.

The only test asserts the wording

tests/test-templates.sh:84:

assert_contains "$D" "cannot be closed" "deferred: promotion obligation"

That checks the sentence is present in the template. It passes identically whether or not any close ever honoured it.

This is not a criticism of the assertion — you cannot unit-test whether a model followed a prose instruction. The point is that there is no other observation either.

The asymmetry is visible in the suite

implemented as how it is tested
shell — lib.sh + the three hooks 62 behavioural assertions across 4 files
model instructions — 4 commands, all templates 76 assert_contains against markdown text

The plugin already spends a hook on a softer obligation: hooks/stale-ledger.sh fires when a session changed code and the ledger did not move. That is a judgement call, and it is deliberately non-blocking and latched to once per session. The close rule gets no hook.

Field evidence — n=2, one repo, one author

Two programmes in the same repo closed over their own open rows, four days apart, by different mechanisms:

  • 2026-08-22 — a check was run and was wrong. grep -c '^### ' read 2 while 48 rows existed: older rows were table rows and only the two newest were ### headings. Found 6 days later.
  • 2026-08-26 — no check was run. The close commit does not touch deferred.md at all. Found 4 days later.

Worth noting the first one's shape: a hand-rolled check written against the file's newest formatting convention and blind to its oldest. Whatever ends up observing this should not be a grep for a heading style.

Deliberately not proposing a fix

The mechanism is a design call and the obvious candidates each cost something — a blocking gate on INDEX.md edits is intrusive, a Stop-hook detector fires only after the close commit lands, and a stricter row schema trades expressiveness for checkability.

One caution from the same field data. My rows outgrew the template badly: the shipped example row is one line, while my median row is 27 lines and the longest is 212. That is my repo being an outlier, not the template being wrong — and a fix sized against those numbers would be overfitted to a single user. The gap reported here is the one that holds regardless of row size, row format, or programme scale.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions