create_app org-only rule: scope, the real special cases, and how to remove them #274

Open
opened 2026-09-06 00:21:48 +00:00 by plat · 0 comments
Owner

The rule

create_app refuses a user owner (mcp/src/server.ts, rule 1b, "apps must be owned by an organization"). It landed in mcp v0.34.0 (a313acf, 2026-07-21, "per-org registry credential; apps are org-owned (rb-7)"). The wiki states it in Build-Your-First-App, Users-Teams-and-Access, Security-Model and Connect-Your-Agent.

Why it exists (mechanical, not policy)

The release workflow needs a credential that can push an image and a chart into the owner's package namespace and nothing else. Forgejo grants write:package in exactly one way: an org team (registry-writers, units_map: {repo.packages: write}) holding a restricted bot (registry-bot-<org>). Under a user namespace there is no delegation primitive at all. The commit that added the rule verified the alternatives on a live Forgejo: the Actions token has no package scope under any username (401 ×5), a site admin cannot mint a token for another user (Sudo 401, no admin endpoint), and repo collaboration confers no package access (401). Before the rule, every app's REGISTRY_TOKEN was the admin PAT verbatim.

So the org-only rule is Forgejo's shape reflected faithfully. It is not the special case.

The actual special cases

  1. The plat-user exemption in rule 1b (platformOwned): apps owned by the plat user (tally, rabbit-hole, clicker, mig-go/django/php) skip the check because they were seeded with a REGISTRY_TOKEN at genesis. They predate the open-platform org.
  2. ~20 grandfathered user-owned apps (trevato/portfolio, trevato/empires, tdobbs/fccid, vespo92/im-present, hudsonqi/dog, micavacado/client-intake-demo, …). They deploy today because create_app is the only caller of the rule; releases and domain changes never re-hit it. They are frozen: any create_app re-run (database, bucket, allowedHosts, any future flag) throws. Nothing rotated their REGISTRY_TOKEN, so they very likely still carry the pre-rule admin-scoped credential the rule was written to eliminate.
  3. Pre-July overlays label their namespace plat.sh/owner: plat while plat.sh/owner-id/owner-kind correctly name the user (e.g. clusters/local/apps/trevato/portfolio/prod/manifests.yaml). Cosmetic, but list_apps/lifecycle tooling reads that label.

Options

  • A. Make the rule universal (recommended). Move the plat-owned apps into open-platform (already the direction of the org rebrand), then delete the platformOwned branch. Migrate each grandfathered user app into a personal org (<user>-apps, the wiki's own convention) or archive it, and rotate its REGISTRY_TOKEN to that org's bot. After that the code has one rule and zero exemptions: an app's owner is an org.
  • B. User-namespace parity. Blocked on a Forgejo capability that does not exist (package-scoped delegation for personal namespaces). Anything else re-widens the credential.
  • C. Platform-owned registry namespace. Images and charts under one platform org with a per-app bot; the credential boundary moves from owner to app. Correct, but a larger change to release.yml, the pull secrets and the overlay renderer, and it separates the artifact from the owner.

Tonight

forge (a bespoke, stateful app with a raw-TCP SSH surface) was deployed under the org trevato-apps. Its chart claims Traefik's ssh entrypoint through an IngressRouteTCP in its own namespace, with a NetworkPolicy admitting the edge on port 2222; that entrypoint was scaffolded for Forgejo's git-over-ssh but never wired (no IngressRouteTCP existed in the cluster). Two follow-ups this surfaced:

  • The generated allow-ingress-traefik NetworkPolicy admits the edge on 8080/80 only; an app with another port must add its own policy. Worth a comment in the policy.
  • A plain git push of a repo that already carries charts/app/Chart.yaml is classified fresh by the dispatcher and gets a builder. The only opt-outs are original_url (a real Forgejo migration) or a non-0.1.0 release. A "bring my own app" path that does not summon a builder would be cleaner than racing a release object into place.
## The rule `create_app` refuses a user owner (`mcp/src/server.ts`, rule 1b, "apps must be owned by an organization"). It landed in mcp v0.34.0 (`a313acf`, 2026-07-21, "per-org registry credential; apps are org-owned (rb-7)"). The wiki states it in Build-Your-First-App, Users-Teams-and-Access, Security-Model and Connect-Your-Agent. ## Why it exists (mechanical, not policy) The release workflow needs a credential that can push an image and a chart into the owner's package namespace and nothing else. Forgejo grants `write:package` in exactly one way: an org team (`registry-writers`, `units_map: {repo.packages: write}`) holding a restricted bot (`registry-bot-<org>`). Under a user namespace there is no delegation primitive at all. The commit that added the rule verified the alternatives on a live Forgejo: the Actions token has no package scope under any username (401 ×5), a site admin cannot mint a token for another user (Sudo 401, no admin endpoint), and repo collaboration confers no package access (401). Before the rule, every app's `REGISTRY_TOKEN` was the admin PAT verbatim. So the org-only rule is Forgejo's shape reflected faithfully. It is not the special case. ## The actual special cases 1. **The `plat`-user exemption** in rule 1b (`platformOwned`): apps owned by the `plat` user (tally, rabbit-hole, clicker, mig-go/django/php) skip the check because they were seeded with a `REGISTRY_TOKEN` at genesis. They predate the `open-platform` org. 2. **~20 grandfathered user-owned apps** (trevato/portfolio, trevato/empires, tdobbs/fccid, vespo92/im-present, hudsonqi/dog, micavacado/client-intake-demo, …). They deploy today because `create_app` is the only caller of the rule; releases and domain changes never re-hit it. They are frozen: any `create_app` re-run (database, bucket, allowedHosts, any future flag) throws. Nothing rotated their `REGISTRY_TOKEN`, so they very likely still carry the pre-rule admin-scoped credential the rule was written to eliminate. 3. Pre-July overlays label their namespace `plat.sh/owner: plat` while `plat.sh/owner-id`/`owner-kind` correctly name the user (e.g. `clusters/local/apps/trevato/portfolio/prod/manifests.yaml`). Cosmetic, but `list_apps`/lifecycle tooling reads that label. ## Options - **A. Make the rule universal (recommended).** Move the `plat`-owned apps into `open-platform` (already the direction of the org rebrand), then delete the `platformOwned` branch. Migrate each grandfathered user app into a personal org (`<user>-apps`, the wiki's own convention) or archive it, and rotate its `REGISTRY_TOKEN` to that org's bot. After that the code has one rule and zero exemptions: an app's owner is an org. - **B. User-namespace parity.** Blocked on a Forgejo capability that does not exist (package-scoped delegation for personal namespaces). Anything else re-widens the credential. - **C. Platform-owned registry namespace.** Images and charts under one platform org with a per-app bot; the credential boundary moves from owner to app. Correct, but a larger change to release.yml, the pull secrets and the overlay renderer, and it separates the artifact from the owner. ## Tonight `forge` (a bespoke, stateful app with a raw-TCP SSH surface) was deployed under the org `trevato-apps`. Its chart claims Traefik's `ssh` entrypoint through an `IngressRouteTCP` in its own namespace, with a NetworkPolicy admitting the edge on port 2222; that entrypoint was scaffolded for Forgejo's git-over-ssh but never wired (no `IngressRouteTCP` existed in the cluster). Two follow-ups this surfaced: - The generated `allow-ingress-traefik` NetworkPolicy admits the edge on 8080/80 only; an app with another port must add its own policy. Worth a comment in the policy. - A plain `git push` of a repo that already carries `charts/app/Chart.yaml` is classified `fresh` by the dispatcher and gets a builder. The only opt-outs are `original_url` (a real Forgejo migration) or a non-0.1.0 release. A "bring my own app" path that does not summon a builder would be cleaner than racing a release object into place.
Sign in to join this conversation.
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
open-platform/mitosis#274
No description provided.