fix(project_create): commit staging namespace directly to infra main
All checks were successful
CI / Lint / Test / Vet (push) Successful in 11s
CI / Mirror to GitHub (push) Successful in 3s

Drops the intermediate `staging/<name>` branch so Flux begins reconciling the
namespace within ~60s of `project_create` instead of waiting on a human PR
merge. Consistent with project-wide trunk-based development.

Rationale: ADR 2026-05-18 in DECISIONS.md.

Closes hyperguild#14 (item 1). Item 2 (GITEA_MCP_TOKEN in SOPS) verified
already-present in infra@408a527 secrets.enc.yaml.

Note: TestRoutingPodEndToEnd is failing on main pre-existing this commit
(context deadline waiting for port 33310 in <5s). Not caused by this change;
project skill tests pass. To track in a separate issue.
This commit is contained in:
Mathias
2026-05-18 17:20:53 +02:00
parent 5950ef5f0f
commit 937355cabe
2 changed files with 26 additions and 7 deletions

View File

@@ -118,3 +118,24 @@ When berget.ai tokens run out, flip `BRAIN_LLM_PRIMARY_URL` to iguana.
**Decision**: The root context-sync generates a `mathias.md` prompt and `mathias.toml` agent config in `~/.vibe/`. This is the one tool that needs a custom adapter path. **Decision**: The root context-sync generates a `mathias.md` prompt and `mathias.toml` agent config in `~/.vibe/`. This is the one tool that needs a custom adapter path.
**Consequences**: Run `vibe --agent mathias` to use your conventions. Other Vibe users on the machine aren't affected. **Consequences**: Run `vibe --agent mathias` to use your conventions. Other Vibe users on the machine aren't affected.
---
## 2026-05-18 — project_create commits staging namespace directly to infra main
**Context:** `project_create` writes a k8s namespace manifest into the infra
repo so Flux brings up a staging environment for the new project. Initial
implementation pushed to a `staging/<name>` branch, which required manual PR
merge before Flux saw the namespace — defeating the "one tool call, project
exists, staging reconciling within 60s" goal.
**Decision:** Option A — commit directly to `main`. `callInfraCommit` passes
`branch: "main"` to gitea-mcp's `file_write_branch`; no PR, no merge step.
**Consequences:** Staging namespace appears in cluster within ~60s of the
`project_create` call. Consistent with project-wide TBD policy (CLAUDE.md):
commit directly to main, every commit deployable. Acceptable because the
manifest is a fresh namespace under `k3s/staging/<name>/` — isolated, low
blast-radius, and Flux will simply recreate it if the file is bad. Manual
review gating was friction for no compensating safety gain on experiment
namespaces.

View File

@@ -97,8 +97,7 @@ func (s *Skill) handleCreate(ctx context.Context, raw json.RawMessage) (json.Raw
// Step 3: commit staging namespace manifest to infra repo. Done before // Step 3: commit staging namespace manifest to infra repo. Done before
// the issue so the staging env is reconciling by the time the issue lands. // the issue so the staging env is reconciling by the time the issue lands.
branch := fmt.Sprintf("staging/%s", args.Name) if err := s.callInfraCommit(ctx, args.Name); err != nil {
if err := s.callInfraCommit(ctx, args.Name, branch); err != nil {
if !isConflict(err) { if !isConflict(err) {
return marshalPartial(res, stepInfraCommit, err) return marshalPartial(res, stepInfraCommit, err)
} }
@@ -171,17 +170,16 @@ func (s *Skill) callMirror(ctx context.Context, name string) error {
}, nil) }, nil)
} }
// callInfraCommit writes the staging namespace manifest into the infra repo // callInfraCommit writes the staging namespace manifest directly to infra
// on a dedicated branch. Flux picks it up after merge. // main. Flux reconciles within ~60s. See DECISIONS.md 2026-05-18.
func (s *Skill) callInfraCommit(ctx context.Context, name, branch string) error { func (s *Skill) callInfraCommit(ctx context.Context, name string) error {
manifest := stagingNamespaceManifest(name, time.Now().UTC().Format(time.RFC3339)) manifest := stagingNamespaceManifest(name, time.Now().UTC().Format(time.RFC3339))
return s.cfg.Client.CallTool(ctx, "file_write_branch", map[string]any{ return s.cfg.Client.CallTool(ctx, "file_write_branch", map[string]any{
"owner": s.cfg.GiteaOwner, "owner": s.cfg.GiteaOwner,
"name": s.cfg.InfraRepo, "name": s.cfg.InfraRepo,
"path": fmt.Sprintf("k3s/staging/%s/namespace.yaml", name), "path": fmt.Sprintf("k3s/staging/%s/namespace.yaml", name),
"content": manifest, "content": manifest,
"branch": branch, "branch": "main",
"base": "main",
"message": fmt.Sprintf("feat(staging): add namespace for %s\n\nGenerated by hyperguild project_create.", name), "message": fmt.Sprintf("feat(staging): add namespace for %s\n\nGenerated by hyperguild project_create.", name),
}, nil) }, nil)
} }