Design Doc · Implementation Scope · CI Vertical Slice

Mise as a First-Class Forge Component CI scope approved

2026-08-22 repo: ForgeGraph @ feat/mise-toolchains status: CI slice implemented and under review

4Phases
13Tasks
~3dEst. effort
1New table

Overview

Forge apps and repos already declare their toolchains — this repo alone carries .nvmrc, flake.nix, and forge-ci.toml — but ForgeGraph ignores all of them. This delivery slice makes mise (the TOML dev-environment manager) first-class for native CI: repos get a parsed, viewable Dev Environment panel; Linux agents gain a mise execution path with explicit precedence against Nix; and completed CI builds record which tools they ran with as append-only delivery evidence.

Problem & Status Quo

The pain, in one line: environment setup is tribal knowledge. The repo knows what tools it needs; Forge doesn't.

Premises Confirmed

1
The repo's mise.toml is the single source of truth — Forge mirrors it, never forks it. No shadow copy of tool versions in the DB. If UI editing comes later, it commits back to the file; it never maintains a parallel config.
2
Explicit precedence: nix > mise > manifest. A flake.nix wins (flakes are strictly more total: system deps + tools). Otherwise mise.toml. Otherwise legacy forge-ci.toml. Behavior is never undefined.
3
mise install on nodes stays inside the existing trust boundary. It executes plugin/tool code declared by whoever can push to the repo — same trust class as running forge-ci.toml builds or flakes today. Explicit, not accidental.
4
This PR excludes: node/systemd app preparation, editing mise config from the UI, executing mise tasks from the UI, managing [env] secrets through Forge, and anything on CF Workers deploys. This is the CI vertical slice.
Scope decision, 2026-08-22: implementation review narrowed this PR to native CI. Node/systemd preparation, prep-time evidence callbacks, structured per-tool install logs, a real fresh-container integration test, and persistent runtime caches are explicit follow-ups rather than silent omissions.

Approaches Considered

A — Parse-on-read + agent hook Minimal viable

No new tables. Server-side lib parses mise config from git for display; agent gains a mise branch in its prep path. Effort S · Risk low. Kills the manual-setup pain but leaves no record of what tools a given build actually used.

C — A + evidence snapshots Recommended

Everything in A, plus one small append-only table written atomically with a CI run's terminal report: this build ran node 22.6 + pnpm 9 with mise 2026.x at sha …. Reproducibility evidence for completed reports without B's sync machinery. Effort M (~3d) · Risk low.

B — First-class toolchain projection Ideal architecture

Push-webhook-synced repo_toolchain table, server-resolved precedence encoded once, agent consumes resolved specs from the hub, edit-in-UI commits back. The long-game home for this feature — but heavy machinery before validating that anyone looks at the panel. Effort L · Risk medium. C preserves A's shape, so upgrading to B later wastes no work.

Node agent (Go)

ForgeGraph web/API

Repo (source of truth)

git fetch @sha

same rule, Go twin

mise.toml present

POST run evidence

mise.toml / .mise.toml

parseMiseConfig
(TS lib)

Dev Environment panel
repos + apps

build_toolchain
append-only evidence

Precedence gate
nix > mise > manifest

mise install + exec

Toolchain snapshot
callback

Mise data flow — one source of truth in the repo, mirrored for display, executed by the agent, and snapshotted when the agent delivers a terminal report.
Diagram source (mermaid)
flowchart LR
  subgraph REPO["Repo (source of truth)"]
    MT["mise.toml / .mise.toml"]
  end
  subgraph SERVER["ForgeGraph web/API"]
    P["parseMiseConfig\n(TS lib)"]
    UI["Dev Environment panel\nrepos + apps"]
    EV[("build_toolchain\nappend-only evidence")]
  end
  subgraph AGENT["Node agent (Go)"]
    PRE["Precedence gate\nnix > mise > manifest"]
    MI["mise install + exec"]
    SNAP["Toolchain snapshot\ncallback"]
  end
  MT -->|"git fetch @sha"| P
  P --> UI
  P -.->|"same rule, Go twin"| PRE
  PRE -->|"mise.toml present"| MI
  MI --> SNAP
  SNAP -->|"POST run evidence"| EV

Goals & Non-goals

Goals

  • Fresh clone → correct toolchain with zero human steps (agent runs mise install).
  • Dev environment visible on repo and app pages: tools + versions, task names, env key names.
  • Every CI build that delivers a terminal report records its resolved toolchain as queryable evidence.
  • Deterministic precedence: nix > mise > manifest, identical rule in Go and TS.

Non-goals

  • Editing mise config from the UI (future: commit-back flow).
  • Running mise tasks from the UI.
  • Managing [env] values/secrets through Forge.
  • CF Workers deploy paths; interactive dev shells on nodes.
  • Replacing the nixci path — flakes remain first in line.

Phase 1 — Shared detection & parsing (TS) Done

One library owns detection + parsing so both the API surface and the UI read from the same place.

TaskFilesVerificationStatus
Detect config paths in order: mise.toml, .mise.toml, .config/mise/config.toml; ignore *.local.toml / mise.local.tomlapps/web/src/lib/mise.tsunit tests over fixture trees incl. local-file exclusionDone
Parse TOML into a typed shape: tools, task metadata, env key names only, and settingsapps/web/src/lib/mise.tsgolden fixture tests; env values never cross the parser boundaryDone
Expose precedence helper resolveEnvStrategy(files) → "nix" | "mise" | "manifest" mirroring the Go ruleapps/web/src/lib/mise.tsshared table-driven fixture testsDone

Phase 2 — Dev Environment panel (UI) Done

A compact panel on repo detail pages, linked from app pages when the app's repo has a mise config. Read-only mirror of the file at the current default-branch head.

TaskFilesVerificationStatus
"Dev Environment" section on /repos/[id]: strategy badge, tools, tasks, and env key namesapps/web/src/app/repos/[id]/component and parser testsDone
Link the selected mise config into the existing code viewer with TOML highlightingexisting repository code viewergenerated URL targets the selected config and refDone
App page cross-link to the linked repository's Dev Environment panel, which reports the detected Nix, mise, or manifest strategyapp detail pagetypecheck and route rendering coverageDone
Security note: [env] can carry secrets. The UI renders env key names only, never values — enforce this in the parser shape itself (envKeys: string[]), not by convention in components.

Phase 3 — Agent mise path (Go) CI slice done

The agent's prep gate becomes three-way instead of two-way. New package keeps nixci untouched.

TaskFilesVerificationStatus
Precedence gate: flake.nix → nixci; else mise config → mise-managed manifest CI; else legacy manifest CIagent/internal/agentci/, agent/internal/miseenv/Go tests mirror the shared precedence fixtureDone
Install a pinned, architecture-specific mise binary with SHA-256 verification and cache it across runsagent/internal/miseenv/install.goidempotency and checksum testsDone
Run mise install after worktree sync and execute manifest commands through mise exec inside bubblewrapagent/internal/miseenv/, agent/internal/manifestci/unit-level fake-manager integration; real container follow-upDone
Emit structured per-tool installation log recordsmiseenv packagelog assertionsFollow-up

Phase 4 — Toolchain evidence snapshots Terminal evidence done

The part that makes this feel like ForgeGraph rather than a config viewer: builds become reproducible-on-paper.

TaskFilesVerificationStatus
Append-only build_toolchains table keyed by build with repository, commit, strategy, manager version, tools, and timestamppackages/db/src/schema/build.ts, migration 0088additive migration verified on betaDone
Persist the frozen snapshot atomically with the agent's terminal CI report/api/agent/ci-reportroute tests cover attribution, idempotency, and terminal compatibilityDone
Build detail page shows the frozen toolchain snapshot as "Ran with" evidenceCI run detail componentcomponent testsDone

Risks & Mitigations

RiskSeverityMitigation
mise install downloads arbitrary plugin/tool code — supply-chain surfaceMediumSame trust class as existing repo-declared builds; pinned mise binary with architecture-specific SHA-256 verification; lockfile respected when present. Structured per-tool install records remain an explicit follow-up.
Precedence rule drifts between Go and TS implementationsMediumIdentical table-driven test fixtures committed to both sides; a shared fixture JSON file referenced by both test suites
Repos with stale/broken mise configs now fail CI where they previously "worked" (via ambient node state)LowPrep failure produces a clear named error ("mise install failed: …"); ambient fallback explicitly rejected — silent drift is the bug we're killing
Panel shows nothing for repos without mise — feels half-builtLowStrategy badge always renders (Nix / Mise / Legacy manifest) so the absence is informative, not empty

Verification

Open Questions & Follow-ups

Node/systemd preparation: extend the same precedence and mise manager into non-CI node app deployment as a separate vertical slice.
Prep-time evidence: add a dedicated authenticated callback so the resolved snapshot survives an agent crash between preparation and the terminal report.
Operational depth: add structured per-tool install records, a real Linux+bubblewrap fresh-container test, and a persistent node-local runtime/download cache with safe per-job isolation.

The Assignment

Next concrete action: land the verified CI slice, then pick up node deployment and prep-time evidence as separately reviewable follow-ups.

What I noticed

You described the friction precisely: not "we need a config format" but "setup is manual every time." And when given the wedge options you picked the slice that includes evidence — consistent with how ForgeGraph has evolved (attestations, health evidence, delivery readiness). The tell that this is the right shape: your own repo is the messiest case (three competing mechanisms), and you chose to make precedence explicit rather than hope it never matters.