feat(infra_manifest): producer-level shared-infra droplets topology

Add optional `droplets` array for producer-root manifests (@quinn/.infra.yaml):
one shared services droplet (all forges + npm/pypi/swift registries + DNS + edge)
+ a dedicated MCP droplet, instead of one droplet per producer. v0.3.0->0.4.0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Natalie 2026-06-29 14:16:22 -04:00
parent d1629a7545
commit 11fc015686

View file

@ -1,12 +1,12 @@
apiVersion: conventions/v1
version: 0.3.0
version: 0.4.0
updated: "2026-06-29"
name: infra_manifest
title: Per-project infra manifest (.infra.yaml)
title: Infra manifest (.infra.yaml — per-project + producer-level shared infra)
scope: general
status: draft
summary: Every deployable project declares its infrastructure in a root .infra.yaml; `service.host` must be a host in net-tools mesh-hosts.json, so the infra-net reconciler can build the live picture. A project with a distinct non-prod deployment may add a sibling .infra.dev.yaml (same schema, environment:dev) — e.g. a local operator instance on a mac host. The reconciler reads every .infra*.yaml. A future infra-apply renders the DO parts.
appliesTo: ["@applications/*", "@projects/@cocottetech", "@projects/@magic-civilization"]
summary: "Every deployable project declares its infrastructure in a root .infra.yaml (single `service`); `service.host` must be a host in net-tools mesh-hosts.json. A PRODUCER root may also carry a .infra.yaml describing shared-infra TOPOLOGY via `droplets` — physical hosts each running many co-located services (e.g. @quinn/.infra.yaml — one services droplet for all forges + registries + DNS + edge, plus an MCP droplet). The infra-net reconciler reads every .infra*.yaml; a future infra-apply renders the DO parts."
appliesTo: ["@ct/**", "@mc/**", "@quinn/**", "@*/.infra.yaml"]
rules:
- id: own_db
level: must
@ -21,6 +21,10 @@ rules:
- id: host_in_mesh
level: must
text: "`service.host` is a host name from net-tools mesh-hosts.json (lime, fennel, redroid, …) — the infra-net reconciler validates this and regenerates the mesh-hosts services map from all .infra.yaml."
- id: shared_infra_topology
level: should
text: "Shared metal owned by the operator is declared once at the producer root (@quinn/.infra.yaml) via `droplets` — each droplet lists the co-located services it runs (forges, npm/pypi/swift registries, DNS, reverse-proxy, MCP). Logical per-producer forges (ct/mc/quinn) co-locate on one services droplet rather than one droplet each; tag each service with its `producer`. On provision, register each droplet's `hosts` in mesh-hosts.json."
rationale: One services droplet (forges + registries + DNS + edge) + one MCP droplet is cheaper and simpler than a droplet per producer, while keeping forges logically per-producer.
- id: env_variants
level: should
text: "Default manifest is `.infra.yaml` (prod, environment defaults to prod). A distinct non-prod deployment lives in a sibling `.infra.<env>.yaml` (currently only `.infra.dev.yaml`) with the same schema + `environment` set. One project may thus appear as multiple services (e.g. prod on a DO droplet + a local mac instance). Keep run-only/access config (passcodes, bind addresses) out of the manifest — it is not mesh infra."
@ -63,3 +67,27 @@ providesFile:
type: array
items: { type: string }
description: Other services consumed over HTTP.
droplets:
type: array
description: "Producer-level shared-infra topology: physical droplets each hosting MANY co-located services. Used by a producer-root manifest (e.g. @quinn/.infra.yaml) that owns shared metal — distinct from a single project's `service`. Logical per-producer endpoints (ct-forge/mc-forge/quinn-forge) may co-locate on one droplet."
items:
type: object
additionalProperties: false
required: [name, services]
properties:
name: { type: string }
role: { type: string }
provider: { type: string, enum: [digitalocean, mac, bare-metal, local] }
hosts: { type: array, items: { type: string }, description: "mesh-hosts.json names this droplet registers on provision." }
services:
type: array
items:
type: object
additionalProperties: false
required: [name, kind]
properties:
name: { type: string }
kind: { type: string, description: "forgejo | npm-registry | pypi-registry | swiftpm-registry | dns | reverse-proxy | mcp | ..." }
producer: { type: string, description: "Which producer this service belongs to (ct/mc/quinn), when shared host serves multiple." }
port: { type: integer }
domain: { type: string }