# muninn-gate WG-internal HTTP front door for the Muninn ingestion queue (Worldtree #377). Path-addressed submit, list/status, cancel/retry, watcher-liveness health. Upstream: `vh/muninn-gate` (no Dockerfile there — this stack owns containerization). ## Where it runs **corviduo-dev (10.250.50.152)**, co-located with the worldtree-personal muninn watcher (`worldtree-personal-worldtree-muninn-1`). infra-ops-managed stack, separate from the worldtree CI/CD compose. Bind: `10.250.50.152:8090`, `network_mode: host`. ## Key wiring (why it's shaped this way) - **`ingestion_root: /data/state/ingestion`** — the `worldtree-personal_worldtree-state` volume, mounted at `/data/state`. Byte-identical path to the watcher's view; the dispatch records absolute paths and both processes read each other's. Acceptance: `/health` → `watcher.running: true` **proves** this byte-identity (gate is reading the heartbeat the watcher writes). `no_heartbeat` with the watcher up = root mismatch. - **`user: "1000:1000"`** — the ingestion dir is `vh:vh 0755`; the gate must run as uid 1000 to write the queue (non-root, least-privilege). Overrides the image user. - **staging** — `/mnt/muninn-staging/mimir-inbox`, same absolute path bound `:ro` in BOTH the gate and the watcher (worldtree-dev's b162). Currently a LOCAL placeholder dir; becomes the shared mount when mimir-inbox (the writer) lands. **No gate-side check can tell a real share from an empty dir** (docker fabricates a missing bind source) — the guard is operational: confirm the host mount exists before wiring/ repointing a bind. Repoint needs a restart (config is read once at boot). - **health probe uses `/ping`, not `/health`** — `/health` is always-200 by design (watcher-down is report *content*, not a status code), so it must never be the liveness probe or it would restart the wrong container. ## Config (single-writer, infra-ops) Real config with bearer-key secrets lives on the server at `/opt/docker/conf/muninn-gate/muninn-gate.yaml` (`1000:1000`, `0600`, gitignored). Redacted schema: `conf/muninn-gate.example.yaml`. Schema is CLOSED — any unknown field is a boot failure. Keys are flat-scope (`read | submit | control`, no inheritance); `name` is the non-secret `submitted_by` identity. ## Build + deploy Image builds out-of-band — the Gitea read token (for `muninn-dispatch==0.1.4` from the internal index) rides as a BuildKit secret, never a layer: ```bash DOCKER_BUILDKIT=1 docker build --secret id=gitea_pw,src= -t muninn-gate:0.0.14 . docker compose up -d # after installing the config to /opt/docker/conf/muninn-gate/ ``` ## Status Booted + healthy 2026-07-31; `/ping` + `/health` (watcher:running:true, ingestion_root_writable:true) verified. **SUBMIT deferred** — returns `not_found` against the placeholder staging until the real shared mount + mimir-inbox writer exist; then repoint + the one-file path-agreement probe + the real acceptance run.