# GitOps: config-as-code with `.tinyforge.yml`

A **dockerfile** or **static** workload can read part of its deploy config from a
`.tinyforge.yml` file in its own repo. Tinyforge fetches the file, shows you how it
differs from the live config (**drift**), and applies it when you click **Sync** — so the
repo becomes the source of truth for the declared fields.

This is opt-in per workload and **manual-sync only** in v1: nothing is applied automatically
on deploy, and a sync never runs without an explicit admin action.

## Enabling it

1. Open the workload (Apps → your app).
2. In the **GitOps** panel, toggle it on. The default file path is `.tinyforge.yml` at the
   repo root; change it if your file lives elsewhere (e.g. `deploy/.tinyforge.yml`).
3. Add a `.tinyforge.yml` to the repo (schema below) and push.
4. The panel shows the parsed file and any drift vs. the live config. Click **Sync now** to
   apply the repo's values to the workload.

Only **dockerfile** and **static** sources are eligible — they're the git-backed sources.
`image` and `compose` workloads don't show the panel.

## `.tinyforge.yml` schema (v1)

```yaml
version: 1            # required, must be 1
deploy:
  # dockerfile only:
  port: 8080          # container port the app listens on
  healthcheck: /healthz   # HTTP path probed before a blue-green cutover ("" to disable)
  # dockerfile + static:
  deploy_strategy: blue-green   # "" | recreate | blue-green
```

Notes:

- **Only the fields above are honored.** Unknown keys are rejected with an error (so a typo
  surfaces instead of being silently ignored).
- Fields you omit are **left untouched** — the file overlays only what it declares; it never
  clears the rest of your config.
- The file is **source-aware**: a `static` workload only honors `deploy_strategy` (a static
  site has no port/healthcheck); `port`/`healthcheck` in a static site's file are ignored.
- `deploy_strategy: ""` and `recreate` are equivalent (both are the default for dockerfile
  and static), so they never show as drift against each other.

## What `.tinyforge.yml` does **not** contain

- **No repo location** (provider / owner / repo / branch) and **no access token** — those
  stay in Tinyforge's encrypted database. This is deliberate: it keeps credentials out of
  your repo. (You need the repo coords to find the file in the first place, so they can't
  live in it.)

## Drift and sync

- **Drift** is computed only over the fields the file declares, after normalization (so a
  defaulted strategy or a YAML-int vs stored-number difference isn't a false positive).
- **Sync** fetches the file, merges the declared fields onto a copy of the live config,
  **validates the merged result** with the source's own rules, and only persists it if it
  passes — a bad file is rejected as a whole and never leaves a partial config. The sync is
  recorded to the workload's activity log (not the deploy ledger — it changes config, it
  isn't a deploy).
- While GitOps is enabled, the edit form shows a banner noting which fields the repo manages;
  editing them in the UI works, but the next Sync overwrites them with the repo's values.

## Not in v1 (planned)

These are intentionally out of scope for the first version; the design leaves clean seams
for them:

- **`env` and `faces` (public subdomains)** — they live in separate stores and (for `env`)
  would re-introduce a secrets-in-repo risk; deferred to a typed multi-target apply.
- **Auto-apply on deploy** — applying the repo config automatically on every push. v1 keeps
  a human in the loop with the drift view + manual Sync. When added, it will read the file
  at the exact deployed commit (a source-plugin concern), not at dispatch time.
- **Multi-workload reconcile** — one repo declaring/creating/deleting many workloads
  (the full Flux/Argo model). v1 is per-workload, config-only, with no create/delete.
- **`image` / `compose` sources** — not git-backed / overlapping config surface.
