docs(init): validate specification — v0.14 ingress bootstrap

---ci---
project: orca
phase: 0
milestone: v0.14
status: specify
---/ci---
This commit is contained in:
Jon Chery
2026-08-10 18:07:55 +00:00
parent 0424f8ce02
commit 5e0b899f1a
5 changed files with 167 additions and 24 deletions
+12 -21
View File
@@ -1,26 +1,17 @@
{ {
"phase": 13, "phase": 0,
"stage": "complete", "stage": "specify",
"milestone": "v0.13", "milestone": "v0.14",
"milestone_slug": "production-hardening-2", "milestone_slug": "ingress-bootstrap",
"phase_role": "final", "phase_role": "pre_execution",
"attempts": 0, "attempts": 0,
"updated_at": "2026-08-10T14:30:00Z", "updated_at": "2026-08-10T18:00:00Z",
"milestone_complete": true, "milestone_complete": false,
"previous_milestone": "v0.12", "previous_milestone": "v0.13",
"phase_count": 14, "phases_shipped": [],
"phases_shipped": ["P0","P1","P2","P3","P4","P5","P6","P7","P8","P9","P10","P11","P12","P13"], "tags_shipped": [],
"tags_shipped": ["v0.12.0","v0.12.1","v0.12.2","v0.12.3","v0.12.4","v0.12.5","v0.12.6","v0.12.7","v0.12.8","v0.12.9","v0.12.10","v0.12.11","v0.12.12"],
"requirements": { "requirements": {
"covered": [149,150,151,152,153,154,155,156,157,158,159,160,161,162,163], "covered": [],
"partial": [] "partial": []
},
"binding_conditions": ["C-39","C-40","C-41","C-42","C-43","C-44","C-45","C-46","C-47","C-48","C-49"],
"load_bearing_rule": "R-022",
"next_milestone": "v1.0",
"ship": {
"tag": "v0.12.13",
"merged_to_milestone": true,
"milestone_release": "v0.13"
} }
} }
+46
View File
@@ -707,6 +707,52 @@ v0.13 closes all critical/high/medium findings (15 new requirements,
14 phases) and delivers the **UAT plan + signoff script** that gates 14 phases) and delivers the **UAT plan + signoff script** that gates
the v1.0.0 cut. the v1.0.0 cut.
### v0.14 Milestone: Ingress Bootstrap Completeness
**Scope**: ensure that linux & proxmox types are properly bootstrapped
with traefik during cluster init or node join. All cluster endpoints are
provisioned as sockets (R-007); routing between jobs and services
depends on traefik being present and properly configured. v0.13 shipped
traefik binary + systemd unit + empty dynamic dir but never wrote the
static config nor applied nft rules — `orca-traefik.service` fails on a
fresh `orca init` and `orca doctor nft` FAILs. v0.14 replaces the
binary+systemd model with a **podman container** running a custom
`orca-traefik` image, and completes the nft SNAT+DNAT ingress stack on
every node type.
**New load-bearing rule**: **R-024** — Traefik runs exclusively as a
podman container, deployed from the `orca-traefik` image published per
release. Every orca-managed ingress surface bootstraps: nft DNAT
(`:443→127.0.0.1:8443`, `:80→127.0.0.1:8080`) + SNAT/MASQUERADE
postrouting + `podman run -d --restart=always --network host -v
/etc/traefik/dynamic:/etc/traefik/dynamic:Z -v
/etc/orca/step-ca-root.crt:/etc/orca/step-ca-root.crt:ro
git.cloudinit.dev/coreci/orca-traefik:<tag>`. No node joins without a
functional podman-traefik ingress data plane.
**Three topologies** (per operator constraints):
1. **Linux**: host → nft → `podman run orca-traefik` (host network)
2. **Proxmox Native**: host → nft → LXC (nesting=1) → `podman run orca-traefik`
3. **Proxmox Floating-IP**: LXC (owns floating IP) → nft (inside LXC) → `podman run orca-traefik`
**Milestone type**: feature (multiple `feat` phases). Tags on v0.13.x
patch line: `v0.13.0` (P0) ... `v0.13.8` (P8 final = v0.14 milestone
release). 9 phases, 9 net-new requirements (REQ-171..REQ-179).
### v0.14 Decisions (D-series, full autonomy)
| ID | Question | Decision | Rationale | Confidence |
|----|----------|----------|-----------|------------|
| D-255 | Traefik deployment model? | **Podman container from custom `orca-traefik` image** | Operator constraint: traefik always deployed as a container. Replaces v0.13 binary+systemd. Image bakes static config. | 0.95 |
| D-256 | Container network mode? | **`--network host`** | Binds 127.0.0.1:8080/8443 directly on host/LXC loopback; nft DNAT targets that. No port publishing complexity. | 0.92 |
| D-257 | TLS cert resolver in image? | **Mounted step-ca root CA, no ACME** | Offline-first (R-001), no external CA. `certificatesResolvers.orca.tls` references `/etc/orca/step-ca-root.crt` (volume-mounted). Graceful degradation if absent. | 0.90 |
| D-258 | Dynamic config volume? | **Mount `/etc/traefik/dynamic` from host** | Zero changes to existing `deployRemote` WriteFile path (`job_dispatch.go:243`). File provider watches it. | 0.95 |
| D-259 | Floating-IP mode: register PVE host too? | **Yes — PVE host as `proxmox` + ingress LXC as `linux`** | PVE host needed in registry for `pct`/`qm` workload dispatch. Both register. | 0.92 |
| D-260 | `--ingress-mode` persistence? | **Store `IngressMode` on `model.Node`** | `doctor ingress` needs to know which check path to run. Schema migration. | 0.90 |
| D-261 | MAC generation when `--mac` omitted? | **Generate random `02:XX:...` in interactive mode; require `--mac` in `--json` mode** | Interactive: generate + confirm. Non-interactive: explicit required (no silent generation). | 0.88 |
| D-262 | Proxmox native nft DNAT target? | **LXC bridge IP (not 127.0.0.1)** | LXC has its own network namespace; 127.0.0.1 on PVE host ≠ LXC loopback. `NftClusterConfig.DNATTarget` field (default `127.0.0.1:8443`; native mode = `<lxc-ip>:8443`). | 0.90 |
| D-263 | LXC podman requirements? | **`--features nesting=1,keyctl=1` + `apt-get install podman`** | Ubuntu 24.04 LXC template has no podman preinstalled. Nesting+keyctl required for podman in unprivileged LXC. | 0.88 |
### v0.13 Decisions (D-series, full autonomy) ### v0.13 Decisions (D-series, full autonomy)
| ID | Question | Decision | Rationale | Confidence | | ID | Question | Decision | Rationale | Confidence |
+52 -1
View File
@@ -362,7 +362,7 @@ production-ready tag. v1.0.0 is gated on the UAT signoff script
- 14 phases (P0 + P01..P12 + P13 final); "no limit on phases" per operator. - 14 phases (P0 + P01..P12 + P13 final); "no limit on phases" per operator.
- P03 (scheduler wiring) and P12 (`--type linux` + UAT) are the `feat` phases; the rest are `fix`/`chore`/`test`/`docs`/`refactor`. Milestone type = feature (at least one `feat`). - P03 (scheduler wiring) and P12 (`--type linux` + UAT) are the `feat` phases; the rest are `fix`/`chore`/`test`/`docs`/`refactor`. Milestone type = feature (at least one `feat`).
- Tags on v0.12.x patch line: `v0.12.0` (P0) ... `v0.12.13` (P13 final = v0.13 milestone release). - Tags on v0.12.x patch line: `v0.12.0` (P0) ... `v0.12.13` (P13 final = v0.13 milestone release).
- v1.0.0 production-ready tag stays deferred for post-v0.13 UAT signoff (operator runs `scripts/uat-signoff.sh`, pastes output back). - v1.0.0 production-ready tag stays deferred for post-v0.13 UAT signoff (operator runs `scripts/uat-signoff.sh`, paste output back).
### Accepted residual risks (documented in threat-model, not fixed) ### Accepted residual risks (documented in threat-model, not fixed)
@@ -374,3 +374,54 @@ production-ready tag. v1.0.0 is gated on the UAT signoff script
- "WASM-first" pillar aspirational — document as "WASM runtime available, process is default" - "WASM-first" pillar aspirational — document as "WASM runtime available, process is default"
- arm64/armv7 release — D-193 deferred; install.sh detection is forward-looking - arm64/armv7 release — D-193 deferred; install.sh detection is forward-looking
- OIDC callback slowloris — loopback, short-lived, single CLI invocation - OIDC callback slowloris — loopback, short-lived, single CLI invocation
## Milestone v0.14: Ingress Bootstrap Completeness
**Scope**: ensure that linux & proxmox types are properly bootstrapped with
traefik during cluster init or node join. All cluster endpoints are
provisioned as sockets (R-007); routing between jobs and services depends on
traefik being present on the host and properly configured. The v0.13 traefik
deployment shipped only a binary + systemd unit + empty dynamic dir — it
never wrote the static config nor applied nft rules, so `orca-traefik.service`
fails to start on a fresh `orca init` and `orca doctor nft` FAILs. v0.14
replaces the binary+systemd model with a **podman container** running a custom
`orca-traefik` image, and completes the nft SNAT+DNAT ingress stack on every
node type.
**New load-bearing rule**:
- **R-024** — Traefik runs exclusively as a podman container, deployed from
the `orca-traefik` image published per release. Every orca-managed ingress
surface bootstraps: nft DNAT (`:443→127.0.0.1:8443`,
`:80→127.0.0.1:8080`) + SNAT/MASQUERADE postrouting + `podman run -d
--restart=always --network host -v /etc/traefik/dynamic:/etc/traefik/dynamic:Z
-v /etc/orca/step-ca-root.crt:/etc/orca/step-ca-root.crt:ro
git.cloudinit.dev/coreci/orca-traefik:<tag>`. No node joins without a
functional podman-traefik ingress data plane. The image's baked static
config is canonical; host-side `traefik.yml` is no longer written.
**Three topologies** (per operator constraints):
- **Linux**: host → nft → `podman run orca-traefik` (host network)
- **Proxmox Native**: host → nft → LXC (nesting=1) → `podman run orca-traefik`
- **Proxmox Floating-IP**: LXC (owns floating IP) → nft (inside LXC) →
`podman run orca-traefik`
| ID | Requirement | Priority | Phase | Status |
|----|-------------|----------|-------|--------|
| REQ-171 | `Dockerfile.traefik` + release pipeline: build + publish `git.cloudinit.dev/coreci/orca-traefik:<version>` alongside the orca image per release; `.coreci.yml` `container-publish-traefik` step; image bakes static `traefik.yml` (entrypoints `websecure` 127.0.0.1:8443, `web` 127.0.0.1:8080, `traefik` 127.0.0.1:8081 + file provider watching `/etc/traefik/dynamic` + `certificatesResolvers.orca.tls` referencing mounted step-ca root CA at `/etc/orca/step-ca-root.crt` + json log/accessLog); graceful degradation if CA file absent | Critical | **v0.14 P1** | pending |
| REQ-172 | Replace `internal/traefik/install.go` binary+systemd install with a podman-container reconciler: `podman pull orca-traefik:<tag>` + `podman run -d --restart=always --network host --name orca-traefik -v /etc/traefik/dynamic:/etc/traefik/dynamic:Z -v /etc/orca/step-ca-root.crt:/etc/orca/step-ca-root.crt:ro <image>`; idempotent (pull+run if absent, start if stopped); works locally + over SSH-push; update all callsites (`init.go`, `linux/bootstrap.go`, `proxmox/bootstrap.go`, `upgrade.go`); remove systemd unit generation | Critical | **v0.14 P2** | pending |
| REQ-173 | nft SNAT+DNAT ruleset render+apply: extend `internal/emitter/nft.go` with postrouting masquerade chain; new `internal/ingress/bootstrap.go` renders `orca.nft` + applies `nft -f` + ensures `/etc/traefik/dynamic` dir + pushes step-ca root CA + invokes podman traefik reconciler; wired into `orca init` (localhost lead) | Critical | **v0.14 P3** | pending |
| REQ-174 | Remote ingress bootstrap via SSH-push for `orca node join --type linux`: push step-ca root CA, render+apply nft remotely, invoke podman traefik reconciler remotely; register node as `linux` | Critical | **v0.14 P4** | pending |
| REQ-175 | Proxmox native ingress mode (`--ingress-mode native`, default): on PVE host, render+apply nft (vmbr-compatible, separate `orca-ingress` table avoids pve-firewall conflict); create unprivileged LXC with `--features nesting=1,keyctl=1` running podman+orca-traefik; push step-ca root CA into LXC; nft DNAT target = LXC bridge IP; register PVE host as `proxmox` node; add `IngressMode` field to `model.Node` + schema migration | Critical | **v0.14 P5** | pending |
| REQ-176 | Proxmox floating-IP mode (`--ingress-mode floating-ip --floating-ip --gateway --mac [--net-prefix]`): `pct create` Ubuntu LXC named `ingress` with `net0 bridge=vmbr0,hwaddr=<mac>,ip=<floating-ip>/<prefix>,gw=<gateway> --features nesting=1,keyctl=1 --onboot 1`; install podman + run orca-traefik inside LXC; apply nft DNAT+SNAT inside LXC; register LXC as managed `linux` node (name=`ingress`, addr=`<floating-ip>:8443`); interactive prompt for params when flags absent + not `--json`; validate IP/MAC/gateway; PVE host also registered as `proxmox` for workload dispatch | Critical | **v0.14 P6** | pending |
| REQ-177 | `orca doctor ingress [--peer]`: verify orca-traefik container running (`podman inspect`), nft DNAT+SNAT applied, `/etc/traefik/dynamic` exists, step-ca root CA mounted; extend `scripts/uat-signoff.sh` with ingress assertions (40 podman_traefik, 41 nft_dnat_snat, 42 linux_worker, 43 proxmox_native_lxc / floating_ip_lxc) | High | **v0.14 P7** | pending |
| REQ-178 | Docs: `docs/cli.md` (`--ingress-mode` + floating-IP flags + `doctor ingress`), `docs/uat.md` (native + floating-IP topologies), `docs/ingress.md` (podman-traefik image + volume mounts + certResolver), `docs/docker.md` (orca-traefik image), `ARCHITECTURE.md` (R-024 + ingress bootstrap section) | High | **v0.14 P7** | pending |
| REQ-179 | Integration tests: hermetic harness fakes SSH; asserts init→podman traefik running + nft applied; linux join→remote podman+nft; proxmox native→LXC created with nesting + podman traefik; floating-ip→`pct create` with correct net0 args + LXC registered as linux node; release.sh builds orca-traefik image (Dockerfile.traefik parses) | Critical | **v0.14 P7** | pending |
### Scope notes (v0.14)
- REQ-171..REQ-179 = 9 net-new requirements (REQ count grows 163 -> 172).
- 9 phases (P0 + P1..P7 + P8 final); feature milestone (multiple `feat` phases).
- Tags on v0.13.x patch line: `v0.13.0` (P0) ... `v0.13.8` (P8 final = v0.14 milestone release).
- Milestone branch: `milestone/v0.14-ingress-bootstrap`.
+54
View File
@@ -675,3 +675,57 @@ CI agent verifies and cuts v1.0.0).
- jobspec `health` prober (v0.13 adds lint warning; enforcement deferred) - jobspec `health` prober (v0.13 adds lint warning; enforcement deferred)
- jobspec `update` rolling/canary controller (v0.13 adds lint warning; enforcement deferred) - jobspec `update` rolling/canary controller (v0.13 adds lint warning; enforcement deferred)
- jobspec `schedule.cron` scheduler loop (v0.13 adds lint warning; enforcement deferred) - jobspec `schedule.cron` scheduler loop (v0.13 adds lint warning; enforcement deferred)
## Milestone v0.14: Ingress Bootstrap Completeness — **IN PROGRESS**
**Scope**: ensure that linux & proxmox types are properly bootstrapped with
traefik during cluster init or node join. All cluster endpoints are
provisioned as sockets (R-007); routing between jobs and services depends on
traefik being present and properly configured. v0.13 shipped traefik binary +
systemd unit + empty dynamic dir but never wrote the static config nor applied
nft rules. v0.14 replaces the binary+systemd model with a **podman container**
running a custom `orca-traefik` image, and completes the nft SNAT+DNAT ingress
stack on every node type.
**New load-bearing rule**: **R-024** — Traefik runs exclusively as a podman
container from the `orca-traefik` image published per release. Every
orca-managed ingress surface bootstraps nft DNAT + SNAT/MASQUERADE +
`podman run --restart=always --network host` with dynamic-config + step-ca
root CA volume mounts.
**Three topologies**:
1. **Linux**: host → nft → `podman run orca-traefik` (host network)
2. **Proxmox Native**: host → nft → LXC (nesting=1) → `podman run orca-traefik`
3. **Proxmox Floating-IP**: LXC (owns floating IP) → nft (inside LXC) → `podman run orca-traefik`
**Milestone type**: feature (multiple `feat` phases). Tags on v0.13.x patch
line: `v0.13.0` (P0) ... `v0.13.8` (P8 final = v0.14 milestone release).
- [ ] Phase 0: Pre-execution (SPECIFY→CLARIFY→RESEARCH→PLAN→GRILL) — tag `v0.13.0`
- [ ] Phase 1: `orca-traefik` container image + release pipeline (REQ-171) — tag `v0.13.1`
- [ ] Phase 2: Podman traefik reconciler — replace binary+systemd install (REQ-172) — tag `v0.13.2`
- [ ] Phase 3: nft SNAT+DNAT + `orca init` ingress bootstrap (REQ-173) — tag `v0.13.3`
- [ ] Phase 4: `orca node join --type linux` remote ingress bootstrap (REQ-174) — tag `v0.13.4`
- [ ] Phase 5: Proxmox native ingress mode — LXC + podman traefik (REQ-175) — tag `v0.13.5`
- [ ] Phase 6: Proxmox floating-IP LXC ingress + interactive prompt (REQ-176) — tag `v0.13.6`
- [ ] Phase 7: `doctor ingress` + docs + integration tests (REQ-177,178,179) — tag `v0.13.7`
- [ ] Phase 8: Final review + ship + audit (milestone release) — tag `v0.13.8` = **v0.14 milestone release**
### Per-phase REQ coverage (v0.14)
- **P1**`Dockerfile.traefik` + release pipeline (REQ-171)
- **P2** — Podman traefik reconciler (REQ-172)
- **P3** — nft SNAT+DNAT + init bootstrap (REQ-173)
- **P4** — Linux node join remote ingress (REQ-174)
- **P5** — Proxmox native ingress — LXC + podman (REQ-175)
- **P6** — Proxmox floating-IP LXC ingress (REQ-176)
- **P7** — doctor ingress + docs + tests (REQ-177,178,179)
- **P8** — Final review + ship + audit
### v0.14 is a continuation milestone, not a direction change
The vision ("minimalist, offline-first, CLI-first orchestration engine")
is unchanged. v0.14 completes the ingress bootstrap that v0.13 left
non-functional (binary installed but no config, no nft applied). The
podman-container model is the operator's constraint; the architecture's
socket+traefik routing design (R-007, R-017) is unchanged.
+3 -2
View File
@@ -5,7 +5,7 @@
"slug": "orca", "slug": "orca",
"name": "Orca", "name": "Orca",
"description": "Offline/CLI-first orchestration engine (Orca) \u2014 Nomad-inspired, far simpler than Kubernetes", "description": "Offline/CLI-first orchestration engine (Orca) \u2014 Nomad-inspired, far simpler than Kubernetes",
"milestone": "v0.13", "milestone": "v0.14",
"phase": 0, "phase": 0,
"milestone_type": "feature", "milestone_type": "feature",
"default_branch": "main", "default_branch": "main",
@@ -185,7 +185,8 @@
"registry": "git.cloudinit.dev", "registry": "git.cloudinit.dev",
"owner": "coreci", "owner": "coreci",
"image": "orca", "image": "orca",
"credential_env": "GITEA_TOKEN" "credential_env": "GITEA_TOKEN",
"images": ["orca", "orca-traefik"]
} }
}, },
"secrets": { "secrets": {