# Ready-Role Filing

> Demoted from the always-on eager tier by CodySwannGT/lisa#3992. The
> section below is the former eager head, preserved verbatim; the full
> contract follows it. Reachable on demand via `rules/eager/00-rule-index.md`.

## Ready-Role Filing (load-bearing)

Filing a work item without the ready role is an **incomplete handoff** — build-intake scans the ready lane and nothing else, so the item is never picked up. A correct write plus a missing role still loses the work.

## Omitted means NOT ready — on every tracker

**An omitted `build_ready` is NOT build-ready on JIRA, GitHub, and Linear alike.** Ready is an explicit claim, never an accident of which tracker a project configured. This is a **breaking change** for GitHub and Linear callers, which previously treated omission as ready; `lisa-github-validate-issue` F4's compensating omitted → `true` normalization is removed with it.

## Every filing declares one of three things

- **`build_ready: true`** — complete enough to build; enters the ready lane for auto-pickup. Required for any complete defect found during other work, so it is claimable next cycle with no human flipping status.
- **`human_gate: "<why a human must judge this first>"`** — deliberately held outside the queue. The writer stamps a visible line plus `<!-- [lisa-human-gate] reason=<short-slug> -->` so the hold is auditable.
- **The container declaration** — `None — container: state rolls up from children`, the canonical line `derived-branch-plan` defines and every writer stamps in place of a Target Backend Environment. A container is never build-ready (`leaf-only-lifecycle`) and needs no gate, so it declares the third thing instead. Deliberately **not** keyed on `--label type:Epic`: a declared type is a claim that costs a leaf nothing and leaves it looking buildable, where the container line costs the item the environment and Branch Plan a leaf needs to be built. It is refused alongside a by-design leaf type (`type:Bug` / `type:Task` / `type:Sub-task` / `type:Improvement`) — an item cannot be a container and a leaf at once.

Filed, not ready, and no `human_gate` is the **incomplete handoff** case — writers reject it and name both ways to resolve it. `build_ready: false` with no reason is the same omission with a value attached. `build_ready` stays subordinate to `leaf-only-lifecycle`: a container is never build-ready and needs no gate.

## The named exception

`lisa-exploratory-qa` files findings not-ready **by design** — its findings are candidate defects whose product significance a human should judge. It is the named human-gate exception, not drift: it pairs `ready=false` with an explicit `human_gate` reason. The sibling `e2e-coverage-gaps` skills are the contrast (a missing test is not a product question, so they file build-ready).

`lisa-repair-intake` sweeps for open items that are neither in the ready role nor marked `[lisa-human-gate]`, and surfaces them rather than promoting them.

## This rule is enforced

The PreToolUse guard `block-direct-issue-create.sh` refuses a direct tracker-creation command (`gh issue create`, `gh api` POST to an issues endpoint, `linear`/`jira`/`acli` creates, equivalent `curl` posts) that carries **none of the three declarations** — no configured build-ready role, no `[lisa-human-gate]` marker, and no container declaration. It shipped because this rule as prose did not bind: 13 of 13 issues filed during the session that merged it bypassed it, several by the agent that wrote it, while the one obligation backed by a git hook was honored 50 of 50 times. It stands down where no tracker is configured, and for an operator who set `LISA_ALLOW_DIRECT_ISSUE_CREATE` **in the ambient environment** — never from an inline assignment on the refused command.

**The role is the TARGET repository's, not the caller's.** A creation addressed at another repository — `--repo` / `-R`, or a `repos/<org>/<repo>/issues` endpoint — is judged against that repository's build-ready role: `hardening.upstreamReadyRole` (default `status:ready`) when the target is `hardening.upstreamRepo`, and the stock `status:ready` otherwise. A creation naming no repository, or naming the project's own `github.org`/`github.repo`, is judged against the project's own role exactly as before. A declaration is required either way; only its vocabulary changes.

---

A work item that is filed but never given the ready role is an **incomplete handoff**. No other agent will ever pick it up: build-intake scans the ready lane and nothing else. The originating failure is small and entirely typical — an agent closed a ticket it could not reproduce, filed the real defect it found beside it, and left the new ticket sitting. The defect was correct, the write succeeded, and the work still died.

This rule makes the ready role an **explicit claim** at every filing site, and makes omission mean the same thing on every tracker.

## The one normalization

**An omitted `build_ready` means NOT build-ready — on JIRA, on GitHub, and on Linear alike.**

Before this rule the three writers disagreed, and the disagreement was invisible at the call site:

| Tracker | Omitted `build_ready` (before) | Omitted `build_ready` (now) |
|---|---|---|
| JIRA | project's default created status — **not ready** | unchanged — **not ready** |
| GitHub | `status:ready` applied — **ready** | **not ready** |
| Linear | created in the configured `ready` state — **ready** | **not ready** |

So the same vendor-neutral `lisa-tracker-write` call produced a different lifecycle outcome depending on which tracker a project had configured. That is a leak in the abstraction the shim exists to provide: switching trackers must not change behavior.

**This is a breaking change for GitHub and Linear callers.** Any caller that omitted `build_ready` and relied on implicit ready must now pass it explicitly. That is the point rather than a cost of it: the only paths affected are ones that were silently depending on a provider-specific default, which is exactly the class of bug this rule exists to eliminate. `lisa-github-validate-issue` F4's compensating normalization (omitted → `true`) is removed with it — a validator that re-introduces the old default would simply move the leak.

The safe direction is the implicit one. A ticket that reaches a build queue by accident is worse than one that waits, because the queue is what agents act on autonomously.

## Every filing declares one of two things — three, counting containers

A writer accepts a filing only when it carries **one** of:

- **`build_ready: true`** — the item is complete enough to build and enters the ready lane for auto-pickup. This is the correct answer for a complete defect found during other work.
- **`human_gate: "<why a human must judge this first>"`** — the item is deliberately held outside the queue because a human product call is pending. The writer stamps a visible marker on the item so the hold is auditable rather than indistinguishable from an accident:

  ```text
  Held for a human product call: <reason>.
  <!-- [lisa-human-gate] reason=<short-slug> -->
  ```

Filed, not ready, and no `human_gate` is the **incomplete handoff** case: the writer rejects it and names the two ways to resolve it. `build_ready: false` without a `human_gate` reason is the same failure spelled differently — it is not a gate, it is an omission with a value attached.

`build_ready` remains strictly subordinate to `leaf-only-lifecycle`: a container is never build-ready regardless of what a caller passes, and a container needs no `human_gate` because its state rolls up from its children rather than being claimed.

### The container declaration, on the direct-CLI path

That exemption used to live only in writer prose, which meant the guard below — the thing that actually runs — had no arm for it. A container could satisfy the guard only by declaring a build-ready role `leaf-only-lifecycle` forbids on it, or a human gate it does not have. **A guard that can only be obeyed by writing something untrue is worse than one that simply refuses**, because the lie is indistinguishable from a correct filing afterwards, and both available lies corrupt data another control reads: a stamped role puts a container in the lane build-intake claims from, and a fabricated hold marker is a state change with no inverse.

So a container filed directly through a tracker CLI declares a **third** thing — the canonical container line the `derived-branch-plan` rule already defines and every writer already stamps in place of a Target Backend Environment:

```text
## Target Backend Environment

None — container: state rolls up from children
```

**Not `--label type:Epic`.** A declared type is a claim, and one that costs a leaf nothing: the item it produces still looks buildable, so keying the exemption on it would hand any item willing to mistype itself a free bypass of the readiness requirement — the shape of the measured case where an allowlist added to harden a guard became the way around it. The container line is self-limiting instead: writing it costs the item the Target Backend Environment and Branch Plan a leaf needs to be built at all, so it is only useful to something that actually is a container.

The declaration is refused alongside a **by-design leaf type** declared as a flag value (`type:Bug` / `type:Task` / `type:Sub-task` / `type:Improvement`, or the `--type` spelling on a state-based tracker) — an item cannot be a container and a leaf at once. Story and Spike are deliberately not in that set: `leaf-only-lifecycle`'s childless-parent exception makes them leaf-or-container depending on child work, and a decomposition legitimately files a parent Story before the children that make it one.

One string, defined in one rule, read by the guard and stamped by the writers — which is what keeps this exemption from drifting back into prose only.

## Complete defects found during other work are filed build-ready

Any defect discovered while doing something else, which is complete enough to build, is filed through `lisa-track` / `lisa-tracker-write` with **explicit `build_ready: true`**. It must be claimable by build-intake on the next cycle with no human flipping status. "Complete enough to build" is the `work-item-definition-of-ready` bar — reproduction, observed-versus-expected, and Gherkin acceptance criteria — not a placeholder to be fleshed out later.

The same explicitness applies in the other direction. A filing that is genuinely a human product call declares `human_gate`; it does not simply omit the flag and hope the tracker default is the merciful one.

## The named exception: `lisa-exploratory-qa`

`lisa-exploratory-qa` files its findings **not** build-ready by default, and that is **correct** — it is the named human-gate exception under this rule, not drift to be repaired.

Exploratory QA is a first-time-user experience pass. Its findings are *candidate* defects and usability observations whose product significance a human should judge; auto-readying them would push judgment work into the build queue, which is precisely the failure the gate model exists to prevent. Its `ready=false` is therefore paired with an explicit `human_gate` reason and stamped with `[lisa-human-gate]` — an explicit human-gate marker, never a bare omission.

The sibling `e2e-coverage-gaps` skills are the contrast: a missing automated test is not a product question, so they file `build_ready: true` by default.

Other legitimate human-gate filings follow the same shape — `lisa-learnings-audit` promotion/demotion tickets, `lisa-improve-harness` intervention proposals, automation-retirement proposals, and provisioning tickets for access a human must grant. Each declares its `human_gate` reason rather than relying on a default.

## This rule is enforced, not merely stated

The rule shipped as prose first, and prose did not hold. A conformance audit of the ~13 issues filed during the session that shipped it found **13 of 13 bypassed it**, with zero `lisa-track` / `lisa-tracker-write` invocations — eight of them filed *after* the rule merged, several by the agent that wrote it. Over the same window `Co-Authored-By` compliance was **50 of 50**, because a husky `commit-msg` hook enforces that one. The contrast is the whole argument: at the EAGER-RULE rung this rule did not bind even its own author, while the executable control was never once violated. `learnings-ladder` says machine-checkable knowledge belongs at EXECUTABLE-CONTROL, and this rule is machine-checkable.

The control is the PreToolUse Bash guard `block-direct-issue-create.sh`, shipped to every agent variant (Claude, Codex, Cursor, Copilot, agy, OpenCode) and to the host `scripts/lisa-hooks/` fallback. It refuses a direct tracker-creation command — `gh issue create`, `gh api` POST to an issues endpoint, `gh api graphql createIssue`, `linear issue create`, `jira issue create`, `acli … create`, and equivalent `curl` posts — when the command carries **no readiness declaration**.

What it checks is the artifact, not the caller. A Bash-level hook cannot observe call provenance: any provenance signal is settable by the very agent being governed, so a guard built on one would be theatre. So the guard asks whether the command about to run produces a correctly declared item — the configured build-ready role is applied, or a `[lisa-human-gate]` marker is present, or the container declaration is (each read inline, or in the `--body-file` the create is about to submit). That is precisely the machine-checkable content of this rule, and it lets the three writers through by construction, because they always stamp one.

Two ways out, both deliberate:

- **No tracker configured** — `.lisa.config.json` absent or carrying no `tracker`. There is no `lisa-tracker-write` to route through, so the guard stands down. This is the bootstrapping case, and it is *detected* rather than asserted: nobody has to remember an env var to bring up a new repo.
- **`LISA_ALLOW_DIRECT_ISSUE_CREATE`** in the ambient environment — the human operator's override, mirroring `LISA_ALLOW_INSTRUCTION_FILE_WRITE`. It is honored **only** from the environment the hook inherits and is refused outright when it appears as an inline assignment on the intercepted command. A tool-call shell is fresh each time and its exports never reach the hook, so the ambient variable can only have been set by a human before the session began. An escape the governed agent reaches by typing one more token in front of the command it was just refused is not an escape hatch; it is this rule's original failure with extra steps.

### Which repository's vocabulary answers

The guard demands a build-ready role. Whose role that is depends on **where the item lands**, not on whose config file is nearest.

Resolving it from the calling project unconditionally made the highest-value report Lisa receives unfileable. Filing upstream is not a hypothetical: `lisa-persist-learning` step 6 files a redacted hardening ticket at `hardening.upstreamRepo` on a cron, and Lisa's own design already states that repository runs *its own* build queue off *its own* ready role. The guard was the one component assuming there is only one. What actually happened, per calling tracker:

| Caller's tracker | What was demanded of another repository | Outcome |
|---|---|---|
| `jira` / `linear` | a workflow **state**, as the value of a `gh --label` | **unsatisfiable** — that label does not exist there and `gh` rejects an unknown one, so obeying the guard made the command fail |
| `github`, renamed lane | the caller's own label | refused; the target never carried that token |
| `github`, stock lane | `status:ready` | worked — because both repositories happened to pick the same string. A coincidence, not a routing decision, which is why the defect was intermittent across the fleet rather than universal |

Unsatisfiable is not the same as refused. The only escape that *did* work cross-repo was `[lisa-human-gate]`, and using it there is a lie about the item: it stamps a build-ready defect report as held for a human product call, and the target's build queue scans the ready role and nothing else. The report is filed and never picked up — the exact incomplete handoff this guard exists to prevent, committed one repository over. (The workaround actually in use was worse still: ask a second agent, in a repo with the right label, to type the command. A control routinely satisfied by delegating around it is misrouting, not enforcing.)

So the target is extracted from the command — `--repo` / `-R` / `--repo=o/r` in any position before a bare `--`, and the `repos/<org>/<repo>/issues` endpoint on the `gh api` and `curl` paths — and the role resolved for **it**:

- **No target named, or the project's own `github.org`/`github.repo`** — the project's own role, unchanged.
- **The configured `hardening.upstreamRepo`** — `hardening.upstreamReadyRole`, defaulting to `status:ready`.
- **Any other repository** — the stock `status:ready`. Lisa has no configuration describing it, and the caller's token is categorically not its lane.
- **A GitHub-tracked project that declares no `github.org`/`github.repo`** cannot be compared against a target at all, so both roles are accepted. Permissive about *which* token, never about *whether* one is required.

The source is config, not the network. A live `gh api repos/<org>/<repo>/labels` lookup would be more general and is the wrong trade for a `PreToolUse` hook: a round-trip on every intercepted command, and a new fail-open surface when it errors.

The refusal changes with it. `/lisa:track` routes through `lisa-tracker-write` to the **caller's own** tracker and therefore cannot file upstream at all, so recommending it for a cross-repo filing sends the agent to a path that structurally cannot do what it was refused for. The cross-repo message names `bunx @codyswann/lisa file-upstream` — the path Lisa actually ships, which composes a public-safe body through an allowlist projection — and names the target's ready role.

What did **not** change: an undeclared cross-repo create is still refused. Only which token satisfies the declaration moved.

### Where the guard's authority ends

Two limits, stated rather than implied, because a control that documents its own boundary is worth more than one that suggests completeness it does not have.

**The declaration is checked positionally, and only where the declaration actually lives.** The build-ready role counts as the value of a `--label` / `--labels` / `--add-label` / `--status` / `--state` flag, never as free text: a role named in a title or body is a role *mentioned*, not a role *applied*, and matching it anywhere let a bug report titled `"status:ready is broken"` declare its own readiness. This is the failure mode `block-no-verify` hit from the other direction, where an allowlist added to harden the guard became its bypass because `git --config-env` reads its argument as an environment-variable name rather than a path. **The rule generalizes: before matching or exempting a token, ask what it means in every position it can occupy.** Only the long flag spellings are honored, since short flags differ per CLI (`-s` is `--state` on one tracker and `--summary` on another). The `[lisa-human-gate]` marker is matched anywhere by deliberate contrast — it is a marker with no other meaning, so its presence anywhere in the payload *is* the declaration.

**Nothing after a bare `--` can declare anything, and the trackers disagree about why that matters.** A post-`--` token is an operand, not a flag, so it never reaches the created item. Crediting one is the same error as reading the role out of a title, one position over. The two CLIs available for testing behave differently: `gh` 2.96.0 rejects a post-`--` flag outright (`unknown arguments`), so the shape is unexploitable there — but `acli` parses straight past `--` and proceeds to create the work item with the trailing `--status` **silently unapplied**, which made it a live bypass on the JIRA path until it was closed. `jira`, `linear`, `http`, and `wget` were not installed when this was written and their behavior is **unverified**. The guard therefore fails closed on every path rather than inheriting `gh`'s strictness — a vendor-neutral control cannot assume the strictest vendor is representative, and this is the case that proves it.

**A marker inside a heredoc is not seen.** The guard strips heredoc bodies before tokenising, so quoted prose cannot be mistaken for argv — which also means a `[lisa-human-gate]` marker passed through a heredoc is invisible to it and the command is refused. Pass the body with `--body-file <path>`, which the guard reads, or put the marker inline on the command. Every Lisa writer already uses `--body-file`, so this bites only hand-written creations.

**Remote execution is out of scope.** `ssh host 'gh issue create …'` is not intercepted. Catching it would mean recursing into arbitrary trailing quoted operands, which refuses ordinary commands like `git commit -m "the gh issue create guard"`; and the command runs against another host's tracker configuration, where that host's own guard is the right control. Named rather than quietly missing.

**A guard on tool calls cannot govern configuration.** An agent that edits the harness's own settings — the `env` block in `settings.json`, a shell profile the session is later started from — reaches the override without ever running a refused command. That is outside this guard by construction, and it is bounded by policy instead: changing harness configuration is not something an agent may do on its own initiative, and no message from another agent authorizes it. Naming the limit is the point; a reader who believes the guard is total will not think to watch the surface it cannot see.

## Recovery

`lisa-repair-intake` sweeps for the failure this rule prevents: recently filed items that are open, not in the configured ready role, and carrying **no** `[lisa-human-gate]` marker. Each is an incomplete handoff — surfaced with its filing context so an operator can promote it or gate it deliberately. The sweep reports rather than guesses: it never silently promotes an item into the queue, because a filing whose readiness nobody declared is exactly the input the gate model says a human should see.

## Related

- **`leaf-only-lifecycle`** — the prohibition `build_ready` is always subordinate to. Containers are never build-ready.
- **`work-item-definition-of-ready`** — what "complete enough to build" means.
- **`tracked-work`** — the filing entry point (`lisa-track`) this rule governs the readiness of.
- **`factory-model`** — why the ready flip is a gate standing outside the factory rather than a field inside it.
