# W22 — the `flag` verb (Request #24)

## Scope

`CONTEXT.md` declares the shell's capability vocabulary as "create, view, list, state, tag,
**flag**, link, dispatch, watch, comment, milestone, help". There is no `flag` verb: absent
from `[shell.capabilities]`, from `help`, from the verb engine. A spec/implementation
divergence, not a design question — the property was specced and never built. Build it, and
guard the vocabulary with a test so the next omission is found by CI rather than by needing it.

## What is already true (verified before planning)

- The label plumbing exists: `flag_label`, `FLAG_NAMES = ["needs-operator", "needs-routing"]`,
  both `flag: *` labels in `RESERVED_LABELS` (`src/labels.rs`).
- `tag` correctly refuses the reserved `flag:` namespace (`src/verbs.rs`, REQ-VERB-TAG) — so
  it is not, and must not become, the clearing path.
- The greenlit gate already reads `flag: NEEDS-OPERATOR`, on both the Request refusal and the
  Milestone cascade refusal (`src/verbs.rs:485-532`).
- **A clearing path does exist and #24 does not know it:** the Discord Bot post carries a
  Needs Operator toggle button (`src/daemon/run.rs:843`). #24's "Nothing removes one" is true
  of the *shell verb surface* only. The Request body needs that correction.

## Open design questions, decided

**1. Does the state rule bind both directions?** `CONTEXT.md`: "It exists only in **Backlog**
and **Eval**". The invariant is about where the flag *exists*, so the two directions are not
symmetric: **setting** `needs-operator` outside Backlog/Eval violates it and is refused;
**clearing** it anywhere restores it and is always legal. `needs-routing` carries no state
restriction in `CONTEXT.md` and gets none.

This is also the defect behind the live example. The Discord button today refuses the *whole*
toggle outside Backlog/Eval — so #21, closed **DONE** still wearing `flag: NEEDS-OPERATOR`,
cannot be cleared by the one path that exists. The button is fixed in the same pass, through
the same helper: refuse the setting direction, allow the clearing direction.

**2. Is clearing `needs-operator` restricted to the Operator?** No — alchemy cannot express
it. `requester` is an spt endpoint id, and the Discord button is reachable by anyone in the
guild; there is no Operator identity anywhere in this codebase to check against. Inventing one
is a separate design with a separate Request, not a rider on this one.

What the concern actually asks for is that a route-around be *visible*, and that is
affordable: clearing `needs-operator` writes an audit comment naming the requester (the
`dispatch` precedent). Setting is not audited — asking a question is nobody's privilege — and
neither is `needs-routing`. The Discord button is not audited either: the actor there is the
Operator by construction, which is the whole trace.

**3. The wider audit #24 asks for.** Every other verb `CONTEXT.md` names exists. The drift runs
the other way too: `unwatch`, `sweep`, `release`, `bind-repo` and `daemon` ship in
`[shell.capabilities]` and are absent from CONTEXT's enumeration. Both directions are the same
defect — an enumeration nothing checks. Fix the sentence and make it a test.

## Tasks

- **T1** — `traceable-reqs.toml`: add `REQ-VERB-FLAG` (doc/impl/unit) and `REQ-VOCAB-PARITY`
  (doc/unit) to the active group.
- **T2** — `CONTEXT.md`: document the `flag` verb under the agent surface (grammar, the
  asymmetric state rule, no operator restriction, the audit comment), and correct the
  vocabulary sentence so it names the shipped set and marks create/comment as the
  `shell send` pair. `<!-- [doc->REQ-VERB-FLAG] -->`, `<!-- [doc->REQ-VOCAB-PARITY] -->`.
- **T3** — `src/verbs.rs`: `flag <ref> +needs-operator -needs-routing`, the `tag` token grammar
  (`+add` / `-remove` / bare-add) restricted to `FLAG_NAMES`, refusing an unknown flag **by
  name** rather than silently creating a label. Reuses `write_ref` (no foreign writes). Answers
  with the resulting flag set. Wire it into `handle_with_attachments` and `help_text()`.
- **T4** — `manifest.toml`: `flag = { args = ["ref", "ops"] }`. Version bump (new capability).
- **T5** — share the state rule: one helper both `flag` and the Discord button call, and fix
  the button so the clearing direction is no longer refused outside Backlog/Eval.
- **T6** — tests: set/clear round-trip, bare-add, unknown flag refused by name, reserved-prefix
  input refused, the asymmetric state rule both directions, the audit comment fires on
  `needs-operator` clear and only there, foreign ref refused; button regression for the
  clearing direction; vocabulary parity test (CONTEXT list == capabilities ∪ {create, comment}).
- **T7** — repair the live example: clear #21's stale flag with the shipped binary. That is the
  acceptance proof, not a side errand.
- **T8** — release publish per `docs/RELEASE-RUNBOOK.md` (CHANGELOG, tag, `adapter.spt`,
  `SHA256SUMS`, `gh release`), then relink the live shells onto it.

## Gate

`cargo build` clean · `cargo test` green · `traceable-reqs check` exit 0 · the live #21 flag
cleared through the released binary.
