feat: isolate and enable repository creation #1718

Merged
binjovi-bot merged 5 commits from feat/enable-repository-creation into trunk 2026-09-13 10:41:47 +00:00
Owner

Summary

create_repository shipped in PR #1716 (v0.1.706) but is inert: nothing
configures the site-admin Forgejo credential it needs, and nothing runs
the isolated worker it's designed for. This PR wires the isolated side.

  • A new BINJOVI_RUNTIME=repo-creator mode configures only a narrow
    service_operation_worker (claim_opts: [only: [{"forgejo", "repository","create"}]]) with a site-admin Forgejo credential read
    from its own mounted token. It needs no Kubernetes RBAC:
    RepositoryIntake only ever queues a create for a name outside the
    static catalog, so legacy_names/1 always resolves to [] and
    ServiceLegacyClient short-circuits to "absent" without ever making
    a request.
  • The main release's service_operation_worker now excludes that same
    {"forgejo","repository","create"} tuple, so it never attempts an
    operation it structurally cannot hold the credential for.
  • ServiceOperationWorker.run_once/1 threads the new :claim_opts
    through to ServiceOperations.claim/3.
  • ReleaseManifests.agent_release_manifest/2 gains the patch block that
    keeps the new binjovi-repo-creator Deployment's image current on
    every release, alongside every other companion agent — confirmed by
    reading ArgoRelease.publish/3 directly: it fully replaces the
    ArgoCD Application's spec.source on every release from exactly this
    function's output, so a new companion Deployment needs a block here to
    keep receiving new images, not a patch in the seanfarm-side bootstrap
    seed (that file is a one-time disaster-recovery snapshot, pinned to an
    old commit — adding an orphan patch there for a resource that doesn't
    exist in that commit's tree would break kustomize's build for a
    from-scratch rebuild).
  • New Deployment (manifests/repo-creator.yaml): its own ServiceAccount
    with automountServiceAccountToken: false and no Role or RoleBinding,
    an explicit openbao-identity projected volume for the one
    audience-scoped token OpenBao's own Kubernetes-auth method validates
    independently, and a mount for its own forgejo-repo-creator Secret
    (delivered by the companion sean/seanfarm PR). Its
    CiliumNetworkPolicy allows only Forgejo, Postgres, OpenBao and DNS
    egress — no kube-apiserver.
  • BINJOVI_MCP_POLICY grants binjovi.repository.create to the
    existing binding.
  • A new guard in tests/runtime-deployment-contract.sh asserts the
    repo-creator credential is reachable from no other Deployment, the
    same way the codeberg credential is already guarded there.

Companion PR: sean/seanfarm#697 (the ForgejoUser/ForgejoToken/RBAC chain
that delivers the credential this release mounts). That PR should merge
and deploy first — this Deployment's Secret volume has nothing to mount
until it does.

Test plan

  • make check (full local equivalent, including postgres) — green,
    except one pre-existing, unrelated failure in evidence_api_test.exs
    (confirmed via git stash against unmodified trunk; last touched by
    an old, unrelated commit)
  • kubectl kustomize manifests/ renders cleanly; new Deployment and
    CiliumNetworkPolicy verified in the output
  • Live smoke test: create_repository MCP call creates a disposable
    repo and its push-mirror (after sean/seanfarm#697 is live)

https://claude.ai/code/session_01JGe4pyhQ36AhgDfPJry4d3

## Summary `create_repository` shipped in PR #1716 (v0.1.706) but is inert: nothing configures the site-admin Forgejo credential it needs, and nothing runs the isolated worker it's designed for. This PR wires the isolated side. - A new `BINJOVI_RUNTIME=repo-creator` mode configures only a narrow `service_operation_worker` (`claim_opts: [only: [{"forgejo", "repository","create"}]]`) with a site-admin Forgejo credential read from its own mounted token. It needs no Kubernetes RBAC: `RepositoryIntake` only ever queues a create for a name outside the static catalog, so `legacy_names/1` always resolves to `[]` and `ServiceLegacyClient` short-circuits to `"absent"` without ever making a request. - The main release's `service_operation_worker` now excludes that same `{"forgejo","repository","create"}` tuple, so it never attempts an operation it structurally cannot hold the credential for. - `ServiceOperationWorker.run_once/1` threads the new `:claim_opts` through to `ServiceOperations.claim/3`. - `ReleaseManifests.agent_release_manifest/2` gains the patch block that keeps the new `binjovi-repo-creator` Deployment's image current on every release, alongside every other companion agent — confirmed by reading `ArgoRelease.publish/3` directly: it fully replaces the ArgoCD Application's `spec.source` on every release from exactly this function's output, so a new companion Deployment needs a block here to keep receiving new images, not a patch in the seanfarm-side bootstrap seed (that file is a one-time disaster-recovery snapshot, pinned to an old commit — adding an orphan patch there for a resource that doesn't exist in that commit's tree would break kustomize's build for a from-scratch rebuild). - New Deployment (`manifests/repo-creator.yaml`): its own ServiceAccount with `automountServiceAccountToken: false` and no Role or RoleBinding, an explicit `openbao-identity` projected volume for the one audience-scoped token OpenBao's own Kubernetes-auth method validates independently, and a mount for its own `forgejo-repo-creator` Secret (delivered by the companion `sean/seanfarm` PR). Its CiliumNetworkPolicy allows only Forgejo, Postgres, OpenBao and DNS egress — no kube-apiserver. - `BINJOVI_MCP_POLICY` grants `binjovi.repository.create` to the existing binding. - A new guard in `tests/runtime-deployment-contract.sh` asserts the repo-creator credential is reachable from no other Deployment, the same way the codeberg credential is already guarded there. Companion PR: sean/seanfarm#697 (the ForgejoUser/ForgejoToken/RBAC chain that delivers the credential this release mounts). That PR should merge and deploy first — this Deployment's Secret volume has nothing to mount until it does. ## Test plan - [x] `make check` (full local equivalent, including postgres) — green, except one pre-existing, unrelated failure in `evidence_api_test.exs` (confirmed via `git stash` against unmodified trunk; last touched by an old, unrelated commit) - [x] `kubectl kustomize manifests/` renders cleanly; new Deployment and CiliumNetworkPolicy verified in the output - [ ] Live smoke test: `create_repository` MCP call creates a disposable repo and its push-mirror (after sean/seanfarm#697 is live) https://claude.ai/code/session_01JGe4pyhQ36AhgDfPJry4d3
Repository creation is inert in production: nothing configures the
site-admin Forgejo credential it needs, and nothing runs the isolated
worker it's designed for. This wires the isolated side:

- A new BINJOVI_RUNTIME=repo-creator mode configures only a narrow
  service_operation_worker (claim_opts: only [{"forgejo","repository",
  "create"}]) with a site-admin credential read from its own mounted
  token. It needs no Kubernetes RBAC: RepositoryIntake only ever queues
  a create for a name outside the static catalog, so legacy_names/1
  always resolves to [], and ServiceLegacyClient short-circuits to
  "absent" without a Kubernetes API call.
- The main release's service_operation_worker now excludes that same
  {"forgejo","repository","create"} tuple, so it never attempts an
  operation it structurally cannot hold the credential for.
- ServiceOperationWorker.run_once/1 threads the new :claim_opts through
  to ServiceOperations.claim/3.
- ReleaseManifests.agent_release_manifest/2 gains the patch block that
  keeps the new binjovi-repo-creator Deployment's image current on
  every release, alongside every other companion agent.

Claude-Session: https://claude.ai/code/session_01JGe4pyhQ36AhgDfPJry4d3
The isolated repo-creator runtime needs a Deployment to run in: its own
ServiceAccount with automountServiceAccountToken: false and no Role or
RoleBinding, an explicit openbao-identity projected volume for the one
audience-scoped token OpenBao's Kubernetes-auth method validates on its
own, and a mount for its own forgejo-repo-creator Secret. Its
CiliumNetworkPolicy allows only Forgejo, Postgres, OpenBao and DNS
egress -- no kube-apiserver.

serve_http is now explicitly off for BINJOVI_RUNTIME=repo-creator:
binjovi_api is still part of the same binjovi release (its top-level
permanent app), but this lane never serves a request.

Claude-Session: https://claude.ai/code/session_01JGe4pyhQ36AhgDfPJry4d3
The isolated Deployment mounts binjovi-forgejo-repo-creator, projected
by its own ClusterSecretStore (seanfarm-side) from a ForgejoToken this
credential never crosses with any other release's.

A new guard in runtime-deployment-contract.sh asserts that secret name
never appears in any other Deployment's manifest, matching the existing
codeberg-credential isolation check.

Claude-Session: https://claude.ai/code/session_01JGe4pyhQ36AhgDfPJry4d3
feat(manifests): grant create_repository through the MCP policy
Some checks failed
binjovi/ci Binjovi failed the frozen plan
2cb5503198
Appends binjovi.repository.create to the existing binding's permissions
array in BINJOVI_MCP_POLICY, so the create_repository MCP tool is
actually reachable by the identity that already holds every other
release/deploy permission.

Claude-Session: https://claude.ai/code/session_01JGe4pyhQ36AhgDfPJry4d3
fix(api): stop the evidence API test's clock from rotting
All checks were successful
binjovi/ci Binjovi completed the frozen plan
5126f7a6ec
evidence_api_test.exs seeded its Release/Deploy fixtures at a fixed
past date, ~U[2026-09-08 10:00:00Z]. Unlike every sibling test file,
this one has no clock to override: the endpoint it exercises calls
Activity.snapshot/1 with no opts, so it always reads a real
DateTime.utc_now/0 for its 5-day rolling activity window. Once the
fixed seed date fell more than 5 days behind actual wall-clock time,
the endpoint silently dropped the seeded record and the test's
target["deployment_id"] assertion started failing.

This blocked the whole build (make test-api-unit), which blocks every
release for the project -- confirmed independent of this PR's changes
by reproducing it against unmodified trunk. Anchoring the seed
timestamp to DateTime.utc_now() instead fixes it for good.

Claude-Session: https://claude.ai/code/session_01JGe4pyhQ36AhgDfPJry4d3
binjovi-bot deleted branch feat/enable-repository-creation 2026-09-13 10:41:47 +00:00
Sign in to join this conversation.
No reviewers
No labels
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
sean/binjovi!1718
No description provided.