feat(aosguard): refuse an issue filed with no priority or autonomy label #1311

Closed
coilyco-ops wants to merge 1 commit from aos/1105-require-issue-labels into main
Owner

Closes #1105. Needs umbra#322, released as umbra v0.172.0 and locked here.

The gap, measured before the change

$ aosguard ops forgejo issue create <owner> <repo> --title probe --body probe --dry-run
body:
    body: probe
    title: probe          <- no labels key, accepted, exit 0

What now happens

issue create becomes an action shadow whose --labels is required and must carry one priority/* and one autonomy/*. Verified against the built binary:

$ ... --title t --body b                             # no labels at all
aosguard: missing required flag --labels                                  EXIT=1
$ ... --title t --body b --labels autonomy/headless   # priority missing
aosguard: --labels: no priority label: add --labels priority/P2 (P0 highest, P4 lowest)   EXIT=1
$ ... --title t --body b --labels priority/P2         # autonomy missing
aosguard: --labels: no autonomy label: add --labels autonomy/headless (or autonomy/live-collab, autonomy/async-consult, autonomy/epic)   EXIT=1

Nothing is created by any of those runs. The check runs while the flags bind, before the create request is assembled, so this is not the response-side reporting the acceptance rejects.

Labels are names now, not ids

--labels priority/P2, not --labels 199. The names are identical across every org and repo the coily* gate reaches and the ids are not, so a name needs no per-org table and no lookup, and a caller stops running org-label list before every filing - the friction the director measured eleven times in one day on this issue.

POST /issues declares items: {type: integer}, so ids only. The labels are therefore applied by a second call to the labels sub-collection, whose schema declares the id-or-name union. The policy check precedes both calls, so a violating call creates nothing.

The two-taxonomy problem dissolved rather than being worked around

The issue asks for the rule to cover coilysiren/inbox's unprefixed labels or be scoped around them. Neither is needed. coilysiren/inbox#392 landed on 2026-08-27, and the live label sets confirm both inbox and website now carry priority/P0..P4 and autonomy/*, all exclusive=true, with the bare P0-P4 / headless / consult / interactive labels gone and IRL migrated to role/human. One rule covers every repo this surface writes, and no per-repo exemption exists to go stale - which was the shape #1206 and #1186 warn about.

The shadow is narrower than the leaf it replaces

An action argument bound to an input that was not supplied fails the whole call (umbra ResolveArg: "$x is not set (an optional input that was not supplied)"). So every input here is required, and the leaf's optional --assignee, --assignees, --closed, --due_date, --milestone and --ref are not carried. Set a milestone or assignee with issue edit after filing.

This is a real reduction and it is recorded in the verb's own describe, which is the operator-facing surface and the generated skill's text. A dedicated docs/ page was written and then dropped: the repo is at the 40-doc ceiling of the large band, and documentation-bands.md is explicit that splitting to clear a cap trades one violation for another. Follow-up filed as #1312.

Acceptance

  • refuses a call carrying no priority or no autonomy label - yes, all three cases above
  • the refusal names which of the two is missing - yes
  • works for inbox's taxonomy, or is scoped so it does not block it - yes, and without an exemption, because inbox migrated
  • refusal is before the write - yes, at bind time, proven upstream by tests that fail if anything reaches the wire
  • AOSguard and the MCP guardfiles agree, or the difference is recorded - deploy#580 is still open and now has a working shape to copy; the separate-copies decision Kai already made is recorded in the describe

Verification

Locked to umbra v0.172.0 and built with the released specgen 0.172.0 from the tap, not a local binary. The flow was proved live end to end: probe issue #1310 filed, both label names applied by the second call, $issue.number threaded correctly, labels confirmed present on re-read, then closed. pre-commit run --all-files passes.

Closes #1105. Needs umbra#322, released as umbra v0.172.0 and locked here. ## The gap, measured before the change ``` $ aosguard ops forgejo issue create <owner> <repo> --title probe --body probe --dry-run body: body: probe title: probe <- no labels key, accepted, exit 0 ``` ## What now happens `issue create` becomes an action shadow whose `--labels` is required and must carry one `priority/*` and one `autonomy/*`. Verified against the built binary: ``` $ ... --title t --body b # no labels at all aosguard: missing required flag --labels EXIT=1 $ ... --title t --body b --labels autonomy/headless # priority missing aosguard: --labels: no priority label: add --labels priority/P2 (P0 highest, P4 lowest) EXIT=1 $ ... --title t --body b --labels priority/P2 # autonomy missing aosguard: --labels: no autonomy label: add --labels autonomy/headless (or autonomy/live-collab, autonomy/async-consult, autonomy/epic) EXIT=1 ``` Nothing is created by any of those runs. The check runs while the flags bind, before the create request is assembled, so this is **not** the response-side reporting the acceptance rejects. ## Labels are names now, not ids `--labels priority/P2`, not `--labels 199`. The names are identical across every org and repo the `coily*` gate reaches and the ids are not, so a name needs no per-org table and no lookup, and a caller stops running `org-label list` before every filing - the friction the director measured eleven times in one day on this issue. `POST /issues` declares `items: {type: integer}`, so ids only. The labels are therefore applied by a second call to the labels sub-collection, whose schema declares the id-or-name union. The policy check precedes both calls, so a violating call creates nothing. ## The two-taxonomy problem dissolved rather than being worked around The issue asks for the rule to cover `coilysiren/inbox`'s unprefixed labels or be scoped around them. Neither is needed. coilysiren/inbox#392 landed on 2026-08-27, and the live label sets confirm both `inbox` and `website` now carry `priority/P0..P4` and `autonomy/*`, all `exclusive=true`, with the bare `P0`-`P4` / `headless` / `consult` / `interactive` labels gone and `IRL` migrated to `role/human`. One rule covers every repo this surface writes, and **no per-repo exemption exists to go stale** - which was the shape #1206 and #1186 warn about. ## The shadow is narrower than the leaf it replaces An action argument bound to an input that was not supplied fails the whole call (`umbra` `ResolveArg`: *"$x is not set (an optional input that was not supplied)"*). So every input here is required, and the leaf's optional `--assignee`, `--assignees`, `--closed`, `--due_date`, `--milestone` and `--ref` are not carried. Set a milestone or assignee with `issue edit` after filing. This is a real reduction and it is recorded in the verb's own `describe`, which is the operator-facing surface and the generated skill's text. A dedicated `docs/` page was written and then dropped: the repo is at the 40-doc ceiling of the `large` band, and `documentation-bands.md` is explicit that splitting to clear a cap trades one violation for another. Follow-up filed as #1312. ## Acceptance * refuses a call carrying no priority or no autonomy label - **yes**, all three cases above * the refusal names which of the two is missing - **yes** * works for inbox's taxonomy, or is scoped so it does not block it - **yes**, and without an exemption, because inbox migrated * refusal is before the write - **yes**, at bind time, proven upstream by tests that fail if anything reaches the wire * AOSguard and the MCP guardfiles agree, or the difference is recorded - deploy#580 is still open and now has a working shape to copy; the separate-copies decision Kai already made is recorded in the `describe` ## Verification Locked to umbra v0.172.0 and built with the released `specgen` 0.172.0 from the tap, not a local binary. The flow was proved live end to end: probe issue #1310 filed, both label names applied by the second call, `$issue.number` threaded correctly, labels confirmed present on re-read, then closed. `pre-commit run --all-files` passes.
feat(aosguard): refuse an issue filed with no priority or autonomy label
Some checks failed
ci / aos-eval-tests (pull_request) Successful in 7s
ci / ward-doctor (pull_request) Successful in 5s
ci / aos-cli-tests (pull_request) Successful in 25s
ci / gate (pull_request) Failing after 17s
3f3627fadc
Closes #1105. Needs umbra#322, released as umbra v0.172.0.

## The gap, measured before the change

    $ aosguard ops forgejo issue create <owner> <repo> --title probe --body probe --dry-run
    body:
        body: probe
        title: probe          <- no labels key, accepted, exit 0

An unlabelled issue is the fail-closed default that reaches no queue.

## What now happens

`issue create` becomes an action shadow whose `--labels` is required and must
carry one `priority/*` and one `autonomy/*`. The refusal names the missing axis
and exits 1:

    $ ... --title t --body b --labels autonomy/headless
    aosguard: --labels: no priority label: add --labels priority/P2 (P0 highest, P4 lowest)

Nothing is created by that run. The check runs while the flags bind, before the
create request is assembled, so this is not the response-side reporting the
acceptance rejects.

## Labels are names now, not ids

`--labels priority/P2`, not `--labels 199`. The names are identical across every
org and repo the `coily*` gate reaches and the ids are not, so a name needs no
per-org table and no lookup, and a caller stops running `org-label list` before
every filing. `POST /issues` declares ids only, so the labels are applied by a
second call to the labels sub-collection, whose schema declares the id-or-name
union. The policy check precedes both calls.

## The two-taxonomy problem dissolved rather than being worked around

The issue asks for the rule to cover `coilysiren/inbox`'s unprefixed labels or
be scoped around them. Neither is needed: coilysiren/inbox#392 landed and both
inbox and website now carry `priority/*` and `autonomy/*`, verified against the
live label sets. One rule covers every repo this surface writes, and no
per-repo exemption exists to go stale.

## The shadow is narrower than the leaf it replaces

An action argument bound to an input that was not supplied fails the whole call
(umbra `ResolveArg`), so every input here is required and the leaf's optional
`--assignee`, `--assignees`, `--closed`, `--due_date`, `--milestone` and `--ref`
are not carried. Set a milestone or assignee with `issue edit` after filing.
This is recorded in the verb's own `describe` rather than a new `docs/` page,
because the repo is at its 40-doc budget ceiling.

## Verification

Locked to umbra v0.172.0 and built. Three refusals checked for message and exit
code, and the flow proved live end to end: probe issue #1310 filed, both label
names applied by the second call, `$issue.number` threaded correctly, then
closed. `pre-commit run --all-files` passes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Kai Siren <coilysiren@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
Agent-Role: platform
Author
Owner

Closed in favour of two landings, because one merge cannot go green here.

The guardfile using umbra's new matches cannot build inside the :release CI image, which bakes specgen at ARG SPECGEN_VERSION and only bootstraps the pinned version when none is on PATH. So the pin has to land and republish the image first.

  • #1316 - the specgen/umbra v0.172.0 pin, on its own.
  • The guardfile shadow follows once #1316's image is published.

The work itself is unchanged and already verified end to end: probe issue #1310 was filed through the shadow, both label names applied, then closed. Nothing is lost by the split.

Closed in favour of two landings, because one merge cannot go green here. The guardfile using umbra's new `matches` cannot build inside the `:release` CI image, which bakes specgen at `ARG SPECGEN_VERSION` and only bootstraps the pinned version when none is on PATH. So the pin has to land and republish the image first. * #1316 - the specgen/umbra v0.172.0 pin, on its own. * The guardfile shadow follows once #1316's image is published. The work itself is unchanged and already verified end to end: probe issue #1310 was filed through the shadow, both label names applied, then closed. Nothing is lost by the split.
Author
Owner

Closed in favour of two landings, because one merge cannot go green here.

The guardfile using umbra's new matches cannot build inside the :release CI image, which bakes specgen at ARG SPECGEN_VERSION and only bootstraps the pinned version when none is on PATH. Measured:

FAILED tests/test_aos_cli_release.py::test_specgen_pin_is_owned_by_the_dependency_lock
ERROR  tests/test_aosguard.py::... - Command '['specgen', ..., 'build', ...]' returned non-zero exit status 1
1 failed, 710 passed, 2 skipped, 11 errors

So the pin lands first and republishes the image, then the guardfile change goes green against it.

  • #1316 - the specgen/umbra v0.172.0 pin, on its own.
  • The guardfile shadow follows once #1316's image is published.

The work itself is unchanged and already verified end to end: probe issue #1310 was filed through the shadow, both label names applied by the second call, $issue.number threaded, then closed. Nothing is lost by the split.

Closed in favour of two landings, because one merge cannot go green here. The guardfile using umbra's new `matches` cannot build inside the `:release` CI image, which bakes specgen at `ARG SPECGEN_VERSION` and only bootstraps the pinned version when none is on PATH. Measured: ``` FAILED tests/test_aos_cli_release.py::test_specgen_pin_is_owned_by_the_dependency_lock ERROR tests/test_aosguard.py::... - Command '['specgen', ..., 'build', ...]' returned non-zero exit status 1 1 failed, 710 passed, 2 skipped, 11 errors ``` So the pin lands first and republishes the image, then the guardfile change goes green against it. * #1316 - the specgen/umbra v0.172.0 pin, on its own. * The guardfile shadow follows once #1316's image is published. The work itself is unchanged and already verified end to end: probe issue #1310 was filed through the shadow, both label names applied by the second call, `$issue.number` threaded, then closed. Nothing is lost by the split.
coilyco-ops closed this pull request 2026-08-27 04:13:24 +00:00
Some checks failed
ci / aos-eval-tests (pull_request) Successful in 7s
ci / ward-doctor (pull_request) Successful in 5s
ci / aos-cli-tests (pull_request) Successful in 25s
ci / gate (pull_request) Failing after 17s

Pull request closed

Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
coilyco-flight-deck/agentic-os!1311
No description provided.