OPER-167
OPER: admission unknown_feature_id should tell the filer which feature_ids are valid
Band
SMALL
Suggested
Justin Cooke
Body
---
feature_id: FEAT-studio-planning-validation
filing_template_version: v1
decision_memo: docs/pdt/2026.09.09-observation-blind-feature-id.md
blocked_by:
---
## Context
On 2026.09.09 the auto-update-behind gate ticket was filed with `feature_id: FEAT-fleet-gate-auto-update-behind`. Admission rejected it with:
```
Admission rejected (unknown_feature_id): feature_id=FEAT-fleet-gate-auto-update-behind is not declared in the feature-map.md of any admission-enabled product owned by this team
```
The rejection is correct policy: admission requires the feature_id to be declared in a `products/*/feature-map.md`. The failure was **discoverability**. The rejection body names the missing id, but not the id set that would have been accepted. The filer (human or agent) has to shell out to `gh api search/code?q=repo:...+filename:feature-map.md` and grep for `FEAT-` to construct a valid guess, then re-file.
The filing agent, mid-run, cannot do this without extra tool calls. On this session it produced two rejected tickets before landing on `FEAT-studio-dispatcher`.
## Root cause
The admission gate at `apps/web/src/lib/admission/*` (per OPER-D2 lineage) knows the set of admission-enabled products and their declared feature_ids because it enumerates them to check membership. That enumerated set is thrown away instead of surfaced in the rejection.
## Acceptance Criteria
1. When admission rejects with `unknown_feature_id`, the rejection body includes the top N (bounded at 20) feature_ids the requesting team owns across admission-enabled products, grouped by product. Example format:
```
Admission rejected (unknown_feature_id): feature_id=FEAT-x-y-z is not declared.
Valid feature_ids for team operant (studio):
FEAT-studio-agent-workers
FEAT-studio-audit
FEAT-studio-dispatcher
...
```
2. If the team owns 0 admission-enabled products, the rejection body says so explicitly rather than showing an empty list.
3. If the team owns >20 feature_ids, the body shows the first 20 plus a `... and N more; see products/<product>/feature-map.md` line.
4. `ticket-filing.ts` (OPER-146 output) reads the admission rejection body and, on `unknown_feature_id`, surfaces the valid set to the caller so an agent can pick and retry without a second round trip to the code-search API.
5. Self-test: filing a ticket with `feature_id: FEAT-clearly-fake-id` returns the enumerated list, and re-filing with the first id in that list is accepted.
## Verification
- `bash scripts/admission-unknown-feature-id-selftest.sh` passes.
- Manual repro: `curl -X POST /api/studio/operant/tasks -d '{... feature_id: FEAT-fake ...}'` returns 200 with the ticket flagged admission-rejected AND the response body contains the enumerated valid list.
## Notes
- Class: `Blind-Feature-ID-Guess`. Pair with OPER-146 (ticket-filing helper) so the helper consumes the improved rejection body.
- Do not weaken the feature_id requirement while fixing this; a rejection is still a rejection, we are only making it self-correcting.
Attachments
Loading attachments…
Comments
Loading comments…