aosguard org-label create is a second writer to a resource infrastructure declares, and it has already produced an undeclared label #1380

Open
opened 2026-08-29 05:22:53 +00:00 by coilyco-ops · 4 comments
Owner

Filed by Portia (director seat). Found because Kai asked whether the infrastructure repo already manages org labels. It does, and I had not opened it before advising on this surface.

The two writers

Declarative, and authoritative: coilyco-flight-deck/infrastructure holds config/forgejo-label-taxonomy.yaml and drives it onto all three orgs with scripts/forgejo-label-taxonomy-sync.py via just forgejo-label-taxonomy. Create, read, update, never delete. Renames are edits in place so issues keep their labels. Its docstring is explicit about why it exists:

Before that file the authority was live Forgejo org state in coilyco-gaming, which this script copied outward: a label change was an API call with no diff, no review, and no commit. That is how the role/* values went on naming a roster that no longer existed without anything noticing.

And about strictness:

Every field an entry declares is driven to its declared value, so a label edited by hand in the web UI is corrected rather than accepted.

Imperative, and unaware of the above: aosguard ops forgejo-admin org-label create / edit / delete, running on a separate principal (the admin PAT at /forgejo/admin-token) rather than as coilyco-ops, which the sync notes is a member and not an owner and so cannot apply.

Two write paths, two principals, one resource. The imperative one can mint org labels the declarative one will never own.

It has already happened

state/ambient was created 2026-08-29 at ids 481, 482, 483 across the three orgs, color=0e8a16, exclusive=true, through aosguard ops forgejo-admin org-label create. It is not declared in forgejo-label-taxonomy.yaml.

Consequence, from the script's own behaviour: the converge reports it as undeclared, left alone and never touches it. So its colour and description have no owner. It cannot drift back to a declared value because there is no declared value, and it will sit in every converge report as a stray indefinitely.

Nothing is broken. Nothing carries the label yet. This is exactly the moment to fix it cleanly.

The immediate fix, which is not this issue's build

Declare state/ambient in config/forgejo-label-taxonomy.yaml and run the sync. That is a reviewed diff in infrastructure, and it resolves a question that has been open on #1364 all evening: the colour stops being an org-label edit call and becomes a line in a file someone reviews. 0e8a16 sits a few shades from role/*'s 70c24a, which is the reason the question came up; 006b75 was the recommendation and the decision is Kai's, in a PR, where it belongs.

That belongs to whoever owns infrastructure, not here.

What this issue asks for

The guardfile question: should aosguard ops forgejo-admin org-label create exist at all, given the resource is declared elsewhere?

Options, and I am not picking for the platform seat:

  • Remove the create and delete leaves, keep get and list. Writes go through infrastructure. Cleanest against the config-placement rule, and it removes an operator's ability to fix something urgently.
  • Keep them and make the describe say so - name the taxonomy file as the source of truth and the verb as a break-glass path. Cheap, and it depends on an operator reading a describe.
  • Keep them and add a check that refuses a name absent from the declared taxonomy. Strongest, most work, and needs the guardfile to reach a file in another repository, which the config-placement rule points the wrong way for.

org-label edit is the interesting middle case: the converge already corrects hand edits on declared labels, so edit is only reachable in practice on undeclared ones, which is the state this issue is about.

The class

coilysiren/inbox#484 collects controls that read as present and are not. This is the inverse and worth naming as its own shape: a control that is present, correct, and bypassable through a second door nobody remembered was there. The taxonomy sync is well built and its docstring anticipates hand-editing in the web UI. It does not anticipate another guarded verb on a different principal writing the same resource.

Worth a sweep for other resources with a declarative owner in infrastructure and an imperative verb in aosguard pointed at the same thing.

Refs coilysiren/inbox#484, agentic-os#1364, #1375, #1377, agent-compose#342

Filed by Portia (director seat). Found because Kai asked whether the infrastructure repo already manages org labels. It does, and I had not opened it before advising on this surface. ## The two writers **Declarative, and authoritative:** `coilyco-flight-deck/infrastructure` holds `config/forgejo-label-taxonomy.yaml` and drives it onto all three orgs with `scripts/forgejo-label-taxonomy-sync.py` via `just forgejo-label-taxonomy`. Create, read, update, never delete. Renames are edits in place so issues keep their labels. Its docstring is explicit about why it exists: > Before that file the authority was live Forgejo org state in coilyco-gaming, which this script copied outward: a label change was an API call with no diff, no review, and no commit. That is how the `role/*` values went on naming a roster that no longer existed without anything noticing. And about strictness: > Every field an entry declares is driven to its declared value, so **a label edited by hand in the web UI is corrected rather than accepted.** **Imperative, and unaware of the above:** `aosguard ops forgejo-admin org-label create / edit / delete`, running on a separate principal (the admin PAT at `/forgejo/admin-token`) rather than as `coilyco-ops`, which the sync notes is a member and not an owner and so cannot apply. Two write paths, two principals, one resource. The imperative one can mint org labels the declarative one will never own. ## It has already happened `state/ambient` was created 2026-08-29 at ids 481, 482, 483 across the three orgs, `color=0e8a16`, `exclusive=true`, through `aosguard ops forgejo-admin org-label create`. It is **not declared** in `forgejo-label-taxonomy.yaml`. Consequence, from the script's own behaviour: the converge reports it as `undeclared, left alone` and never touches it. So its colour and description have **no owner**. It cannot drift back to a declared value because there is no declared value, and it will sit in every converge report as a stray indefinitely. Nothing is broken. Nothing carries the label yet. This is exactly the moment to fix it cleanly. ## The immediate fix, which is not this issue's build **Declare `state/ambient` in `config/forgejo-label-taxonomy.yaml` and run the sync.** That is a reviewed diff in `infrastructure`, and it resolves a question that has been open on `#1364` all evening: the colour stops being an `org-label edit` call and becomes a line in a file someone reviews. `0e8a16` sits a few shades from `role/*`'s `70c24a`, which is the reason the question came up; `006b75` was the recommendation and the decision is Kai's, in a PR, where it belongs. That belongs to whoever owns `infrastructure`, not here. ## What this issue asks for The guardfile question: **should `aosguard ops forgejo-admin org-label create` exist at all**, given the resource is declared elsewhere? Options, and I am not picking for the platform seat: * **Remove the create and delete leaves**, keep `get` and `list`. Writes go through `infrastructure`. Cleanest against the config-placement rule, and it removes an operator's ability to fix something urgently. * **Keep them and make the describe say so** - name the taxonomy file as the source of truth and the verb as a break-glass path. Cheap, and it depends on an operator reading a describe. * **Keep them and add a check** that refuses a name absent from the declared taxonomy. Strongest, most work, and needs the guardfile to reach a file in another repository, which the config-placement rule points the wrong way for. `org-label edit` is the interesting middle case: the converge already corrects hand edits on declared labels, so `edit` is only reachable in practice on undeclared ones, which is the state this issue is about. ## The class `coilysiren/inbox#484` collects controls that read as present and are not. This is the inverse and worth naming as its own shape: **a control that is present, correct, and bypassable through a second door nobody remembered was there.** The taxonomy sync is well built and its docstring anticipates hand-editing in the web UI. It does not anticipate another guarded verb on a different principal writing the same resource. Worth a sweep for other resources with a declarative owner in `infrastructure` and an imperative verb in aosguard pointed at the same thing. Refs `coilysiren/inbox#484`, `agentic-os#1364`, `#1375`, `#1377`, `agent-compose#342`
Author
Owner

Decided by Kai, 2026-08-29, and it inverts what I recommended. Her words:

the infra repo script should use aosguard. if it gets out of sync, we just run the infra repo script again. adding a label isn't some grand mistake.

What that changes

I had recommended removing create and edit from aosguard org-label, leaving the declaration as the only writer, on the reasoning that one writer means a label that exists is a label somebody declared. That is the wrong trade here.

The decision is the opposite direction and it collapses the two writers rather than deleting one: forgejo-label-taxonomy-sync.py should drive its writes through aosguard instead of its own API client. One code path, the declaration still authoritative, and the guarded surface is the thing that touches Forgejo.

Drift stops being a problem to prevent and becomes a problem to re-converge. An undeclared label is not a breach, it is a label that will be reconciled the next time the sync runs, exactly like any other config this estate converges rather than guards.

Why the original framing overweighted it

This issue is titled around a second writer having "already produced an undeclared label". Measured against Kai's answer, that event is not evidence of a dangerous capability. It is evidence that the two writers were not the same code, so nothing reconciled them. Making the script use the guarded verb fixes the cause; deleting the verb would have fixed the symptom by removing a capability that has a legitimate use.

The work

  • infrastructure: scripts/forgejo-label-taxonomy-sync.py writes through aosguard ops forgejo org-label create / edit rather than a direct API client.
  • agentic-os: nothing to remove. org-label create and edit stay.
  • Re-running just forgejo-label-taxonomy is the reconciliation step, and should be stated as such wherever the drift concern is documented.

One thing to check while doing it

org-label writes go through the same body-flag plumbing as issue-label, which is where #1047 lives: repeated --labels flags silently apply nothing and exit 0, and add cannot apply an exclusive label at all. A sync script driving aosguard needs to read back what it wrote rather than trusting the exit code, at least until #1047 lands. That is the same discipline the triage that produced this comment had to adopt tonight.

**Decided by Kai, 2026-08-29, and it inverts what I recommended.** Her words: > the infra repo script should use aosguard. if it gets out of sync, we just run the infra repo script again. adding a label isn't some grand mistake. ## What that changes I had recommended removing `create` and `edit` from `aosguard org-label`, leaving the declaration as the only writer, on the reasoning that one writer means a label that exists is a label somebody declared. That is the wrong trade here. The decision is the opposite direction and it collapses the two writers rather than deleting one: **`forgejo-label-taxonomy-sync.py` should drive its writes through `aosguard` instead of its own API client.** One code path, the declaration still authoritative, and the guarded surface is the thing that touches Forgejo. Drift stops being a problem to prevent and becomes a problem to re-converge. An undeclared label is not a breach, it is a label that will be reconciled the next time the sync runs, exactly like any other config this estate converges rather than guards. ## Why the original framing overweighted it This issue is titled around a second writer having "already produced an undeclared label". Measured against Kai's answer, that event is not evidence of a dangerous capability. It is evidence that the two writers were not the same code, so nothing reconciled them. Making the script use the guarded verb fixes the cause; deleting the verb would have fixed the symptom by removing a capability that has a legitimate use. ## The work * `infrastructure`: `scripts/forgejo-label-taxonomy-sync.py` writes through `aosguard ops forgejo org-label create` / `edit` rather than a direct API client. * `agentic-os`: nothing to remove. `org-label create` and `edit` stay. * Re-running `just forgejo-label-taxonomy` is the reconciliation step, and should be stated as such wherever the drift concern is documented. ## One thing to check while doing it `org-label` writes go through the same body-flag plumbing as `issue-label`, which is where `#1047` lives: repeated `--labels` flags silently apply nothing and exit 0, and `add` cannot apply an exclusive label at all. A sync script driving `aosguard` needs to read back what it wrote rather than trusting the exit code, at least until `#1047` lands. That is the same discipline the triage that produced this comment had to adopt tonight.
Author
Owner

Correcting the last paragraph of my comment above. I told whoever implements this that a sync script driving aosguard must read back what it wrote rather than trusting the exit code, "at least until #1047 lands", and cited repeated --labels flags silently applying nothing and add being unable to set an exclusive label.

Both of those were my own shell quoting, not the verb. Measured properly since: repeated flags apply, exclusive labels apply through add, and --dry-run renders an identical correct body either way. Full retraction is on #1047.

So the caution I attached to this work does not apply. forgejo-label-taxonomy-sync.py can drive aosguard org-label create and edit on the ordinary contract, and does not need a defensive read-back loop bolted on for a defect that is not there.

The rest of the comment stands: the decision is to collapse the two writers by making the infrastructure script use the guarded verb, drift is re-converged by re-running just forgejo-label-taxonomy rather than prevented, and nothing gets removed from aosguard.

Read-back is still reasonable engineering for a sync script that claims convergence, since it is what makes the run's own report trustworthy. It is just not a workaround for a broken tool, and it should not be justified by one.

Correcting the last paragraph of my comment above. I told whoever implements this that a sync script driving `aosguard` must read back what it wrote rather than trusting the exit code, "at least until `#1047` lands", and cited repeated `--labels` flags silently applying nothing and `add` being unable to set an exclusive label. **Both of those were my own shell quoting, not the verb.** Measured properly since: repeated flags apply, exclusive labels apply through `add`, and `--dry-run` renders an identical correct body either way. Full retraction is on `#1047`. So the caution I attached to this work does not apply. `forgejo-label-taxonomy-sync.py` can drive `aosguard org-label create` and `edit` on the ordinary contract, and does not need a defensive read-back loop bolted on for a defect that is not there. The rest of the comment stands: the decision is to collapse the two writers by making the infrastructure script use the guarded verb, drift is re-converged by re-running `just forgejo-label-taxonomy` rather than prevented, and nothing gets removed from `aosguard`. Read-back is still reasonable engineering for a sync script that claims convergence, since it is what makes the run's own report trustworthy. It is just not a workaround for a broken tool, and it should not be justified by one.
Author
Owner

Measured the current drift, read-only, 2026-08-29. Two things changed since filing.

The state/ambient instance this issue cites is closed. It is declared in config/forgejo-label-taxonomy.yaml now, at color: "0e8a16", exclusive: true, matching live id 481. So the specific example is no longer live drift.

The general drift is real, and it is not what the issue predicted. Declared taxonomy against all three live orgs:

coilyco-flight-deck   declared 18, live 22
  live but undeclared: coherence-core, qa-fixture, role/exec, role/qa
coilyco-bridge        declared 18, live 20
  live but undeclared: role/exec, role/qa
coilyco-gaming        declared 18, live 21
  live but undeclared: c#, role/exec, role/qa

Nothing declared is absent, so the sync is applying everything it owns. The whole gap is the other direction: five labels the declarative source does not own, sitting as undeclared, left alone.

role/exec and role/qa are in all three orgs, which makes them the interesting pair. Neither names a seat on the current roster (platform, sysadmin, science, frontend, gamedev, director, advocate). They are the same divergence #1377 recorded from the other side. coherence-core, qa-fixture and c# are single-org strays.

Kai's call on this was that drift is not a crisis - the infra script should use aosguard, and re-running the sync is the answer when it gets out of sync. That holds: nothing here is broken, and the sync would not remove these anyway, since it leaves undeclared labels alone by design.

What I did not do, and why. Deciding whether role/exec and role/qa get declared or deleted is a taxonomy decision about which seats exist, not a platform one. Same for the three strays. I measured it and left the infrastructure checkout untouched - no worktree, no edits.

The remaining half of this issue, making the infra sync script go through aosguard rather than the API directly, is still open.

Measured the current drift, read-only, 2026-08-29. Two things changed since filing. **The `state/ambient` instance this issue cites is closed.** It is declared in `config/forgejo-label-taxonomy.yaml` now, at `color: "0e8a16"`, `exclusive: true`, matching live id 481. So the specific example is no longer live drift. **The general drift is real, and it is not what the issue predicted.** Declared taxonomy against all three live orgs: ``` coilyco-flight-deck declared 18, live 22 live but undeclared: coherence-core, qa-fixture, role/exec, role/qa coilyco-bridge declared 18, live 20 live but undeclared: role/exec, role/qa coilyco-gaming declared 18, live 21 live but undeclared: c#, role/exec, role/qa ``` Nothing declared is absent, so the sync is applying everything it owns. The whole gap is the other direction: five labels the declarative source does not own, sitting as `undeclared, left alone`. `role/exec` and `role/qa` are in **all three** orgs, which makes them the interesting pair. Neither names a seat on the current roster (platform, sysadmin, science, frontend, gamedev, director, advocate). They are the same divergence #1377 recorded from the other side. `coherence-core`, `qa-fixture` and `c#` are single-org strays. Kai's call on this was that drift is not a crisis - the infra script should use aosguard, and re-running the sync is the answer when it gets out of sync. That holds: nothing here is broken, and the sync would not remove these anyway, since it leaves undeclared labels alone by design. **What I did not do, and why.** Deciding whether `role/exec` and `role/qa` get declared or deleted is a taxonomy decision about which seats exist, not a platform one. Same for the three strays. I measured it and left the infrastructure checkout untouched - no worktree, no edits. The remaining half of this issue, making the infra sync script go through aosguard rather than the API directly, is still open.
Author
Owner

Checked the second half too, and it is also already done.

I wrote above that "the script-uses-aosguard half is untouched." That was wrong - I had not opened the script. It routes every call through aosguard, on both surfaces:

READ = "forgejo"
WRITE = "forgejo-admin"

def guard(surface: str, *argv: str) -> str:
    proc = subprocess.run(["aosguard", "ops", surface, *argv], ...)

Zero raw HTTP: no requests, no urllib, no curl. Landed in 5d714b5 (infrastructure#935, 2026-08-25), which fixed guard to honour the surface its caller passes.

So Kai's call - "the infra repo script should use aosguard" - is satisfied, and the read/write split even matches the privilege ladder: reads on the bot's forgejo surface, writes on the admin PAT's forgejo-admin.

What is genuinely left

Only the taxonomy question, which is not mine:

role/exec, role/qa            undeclared in all three orgs
coherence-core, qa-fixture    undeclared in coilyco-flight-deck
c#                            undeclared in coilyco-gaming

Nothing declared is missing, so the sync applies everything it owns. These five sit as undeclared, left alone, which is the sync's designed behaviour rather than a failure.

role/exec and role/qa are the interesting pair: neither names a seat on the current roster (platform, sysadmin, science, frontend, gamedev, director, advocate), and #1377 recorded the same divergence from the other side.

Recommendation

Both halves of this issue's ask are complete. What remains is one decision - declare those five or delete them - and that is a call about which seats exist rather than about a second writer. Worth closing this and carrying the five into a taxonomy issue, or closing it outright if Kai's "adding a label isn't some grand mistake" already settles them as acceptable.

I made no edits to the infrastructure checkout.

Checked the second half too, and it is also already done. I wrote above that "the script-uses-aosguard half is untouched." That was wrong - I had not opened the script. It routes **every** call through aosguard, on both surfaces: ```python READ = "forgejo" WRITE = "forgejo-admin" def guard(surface: str, *argv: str) -> str: proc = subprocess.run(["aosguard", "ops", surface, *argv], ...) ``` Zero raw HTTP: no `requests`, no `urllib`, no `curl`. Landed in `5d714b5` (infrastructure#935, 2026-08-25), which fixed `guard` to honour the surface its caller passes. So Kai's call - "the infra repo script should use aosguard" - is satisfied, and the read/write split even matches the privilege ladder: reads on the bot's `forgejo` surface, writes on the admin PAT's `forgejo-admin`. ## What is genuinely left Only the taxonomy question, which is not mine: ``` role/exec, role/qa undeclared in all three orgs coherence-core, qa-fixture undeclared in coilyco-flight-deck c# undeclared in coilyco-gaming ``` Nothing declared is missing, so the sync applies everything it owns. These five sit as `undeclared, left alone`, which is the sync's designed behaviour rather than a failure. `role/exec` and `role/qa` are the interesting pair: neither names a seat on the current roster (platform, sysadmin, science, frontend, gamedev, director, advocate), and #1377 recorded the same divergence from the other side. ## Recommendation Both halves of this issue's ask are complete. What remains is one decision - declare those five or delete them - and that is a call about which seats exist rather than about a second writer. Worth closing this and carrying the five into a taxonomy issue, or closing it outright if Kai's "adding a label isn't some grand mistake" already settles them as acceptable. I made no edits to the infrastructure checkout.
Sign in to join this conversation.
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#1380
No description provided.