(chore) add associate tasks

This commit is contained in:
Story Crater Bot
2026-08-18 20:41:34 -07:00
parent 3a03a6afb6
commit c42040319f
75 changed files with 909 additions and 236 deletions
+15
View File
@@ -0,0 +1,15 @@
# 0.1 — Resolve GitOps repo ownership
Phase: 0 — Unblock
Depends on: [00-decisions.md](00-decisions.md) decision #1
- [ ] Resolve which GitOps repo owns the portfolio; delete or correct the losing manifest
Cluster evidence (2026-08-18, `kubectl get applications -n argocd`): `portfolio` and `auth-infra` (from this repo's `infra/argocd-apps.yaml`) do not exist in-cluster. `homarr` — also defined in this repo's manifest — does exist, but its live source is 100% `homelab-root` (`github.com/Riotpiaole/riotpiao.homelab.com`, `project: homelab`), not this repo. Points toward homelab-root as authoritative, but decision is still open — needs explicit sign-off, not inferred.
## Verify
```bash
kubectl get application portfolio -n argocd # shows one, correct, source
```
+15
View File
@@ -0,0 +1,15 @@
# 0.2 — Fix NXDOMAIN repoURL/image reference
Phase: 0 — Unblock
Depends on: [0.1](0.1-gitops-repo.md)
- [ ] Point `infra/argocd-apps.yaml` `repoURL` and image reference at real hostnames (fix NXDOMAIN)
Root cause confirmed (2026-08-18): `*.riotpiao.homelab.com` is fully retired, replaced by `*.riotpiao.com`. `infra/argocd-apps.yaml` still points at `forgejo.riotpiao.homelab.com` (dead domain); `forgejo.riotpiao.com` is live and returns 200. Fix is a straight domain swap in `infra/argocd-apps.yaml` (`portfolio` and `auth-infra` Application `spec.source.repoURL`) — not open diagnosis anymore. `portfolio` namespace still does not exist in-cluster until this + [0.1](0.1-gitops-repo.md) land.
## Verify
```bash
kubectl get pods -n portfolio # 2/2 Running
```
+11
View File
@@ -0,0 +1,11 @@
# 0.3 — Replace :latest with commit-SHA tag
Phase: 0 — Unblock
- [ ] Replace `:latest` tag with commit-SHA tag in `infra/portfolio/base/deployment.yaml`; keep `imagePullPolicy: IfNotPresent` (correct once tags are immutable)
## Verify
```bash
# new commit -> new tag -> Argo rolls out automatically, no manual `kubectl set image`
```
+19
View File
@@ -0,0 +1,19 @@
# 0.4 — Diagnose forgejo-gitea stuck Init:0/3
Phase: 0 — Unblock
- [x] Diagnose `forgejo-gitea` stuck `Init:0/3` (3h+) — read init container logs before changing anything
Resolved — `forgejo-gitea` pod Running 1/1 (confirmed via `kubectl get pods -n cicd`, 2026-08-18).
## Command
```bash
kubectl logs -n cicd <forgejo-gitea-pod> -c <init-container-name>
```
## Verify
```bash
kubectl get pods -n cicd # forgejo-gitea 1/1 Running
```
+15
View File
@@ -0,0 +1,15 @@
# 0.5 — Diagnose apex 403
Phase: 0 — Unblock
Depends on: [00-decisions.md](00-decisions.md) decision #2
- [ ] Diagnose apex 403 — check Cloudflare tunnel Public Hostnames list and WAF event log
Partial evidence (2026-08-18): `kubectl get ingress -A` has zero rules for bare host `riotpiao.com` — every ingress is a subdomain (22 hosts, all `*.riotpiao.com` per the `*.riotpiao.homelab.com` retirement). No in-cluster origin exists for the apex today. Leans toward "no origin configured" as the cause, but Cloudflare tunnel Public Hostnames list + WAF event log still needed to confirm the tunnel-side config (not visible from `kubectl`).
## Verify
```bash
curl -sS -o /dev/null -w '%{http_code}\n' https://riotpiao.com # 200
```
+11
View File
@@ -0,0 +1,11 @@
# 0.6 — Add Vitest + Testing Library + msw
Phase: 0 — Unblock
- [ ] Add Vitest + Testing Library + `msw`; add `test` and `test:watch` scripts to `package.json`
## Verify
```bash
pnpm test # runner executes, 0 tests, exit 0
```
+12
View File
@@ -0,0 +1,12 @@
# 0.6a — Add Playwright (hard requirement)
Phase: 0 — Unblock
- [ ] Add Playwright (`pnpm create playwright`); add `test:e2e` script to `package.json`; wire into CI as a hard gate (build fails if `test:e2e` fails)
- [ ] Playwright is a hard requirement, not optional, for every phase that ships a UI surface (Phase 2, 3, 4, 5) — no surface merges without a passing e2e spec
## Verify
```bash
pnpm test:e2e # runner executes, 0 tests, exit 0
```
+10
View File
@@ -0,0 +1,10 @@
# 0.7 — Triage unrelated cluster issues
Phase: 0 — Unblock
- [ ] Triage unrelated cluster issues: `sms` Application Degraded (`macos-bluebubbles` Pending 3h), `longhorn-config` OutOfSync
- Unrelated to atlas, but delivery tree (Phase 3) will render both red on day one — fix or explicitly accept as known-red
Status (2026-08-18, `kubectl get application <name> -n argocd`):
- `longhorn-config` — Synced / Healthy. Resolved.
- `sms` — Synced / Degraded. Still open.
+6 -2
View File
@@ -6,7 +6,9 @@ All five must resolve before Phase 1 starts. Source: ADR-0001 review notes.
Two roots exist: Two roots exist:
- `homelab-root``[email protected]:Riotpiaole/riotpiao.homelab.com.git`, path `k8s/argocd/apps` (31 child Applications, this is the live one) - `homelab-root``[email protected]:Riotpiaole/riotpiao.homelab.com.git`, path `k8s/argocd/apps` (31 child Applications, this is the live one)
- This repo's `infra/argocd-apps.yaml``forgejo.riotpiao.homelab.com` (NXDOMAIN, `portfolio`/`auth-infra` Applications don't exist in cluster) - This repo's `infra/argocd-apps.yaml``forgejo.riotpiao.homelab.com` (`portfolio`/`auth-infra` Applications don't exist in cluster)
**Update (2026-08-18):** `*.riotpiao.homelab.com` is fully retired — confirmed NXDOMAIN across the board (`argocd.riotpiao.homelab.com`, `forgejo.riotpiao.homelab.com`, etc.), not just the forgejo subdomain. Live ingress hosts are all `*.riotpiao.com` now (`forgejo.riotpiao.com` → 200, `argocd.riotpiao.com`, `vault.riotpiao.com`, etc. — 22 hosts total). `infra/argocd-apps.yaml`'s `repoURL: https://forgejo.riotpiao.homelab.com/...` in this repo is pointed at the dead domain; the working equivalent is `forgejo.riotpiao.com`. This doesn't answer which repo is authoritative, but it means 0.2's NXDOMAIN fix is now a known, mechanical domain swap rather than an open diagnosis.
**Decision needed:** GitHub or Forgejo. Blocks Phase 0.10.3. **Decision needed:** GitHub or Forgejo. Blocks Phase 0.10.3.
@@ -14,6 +16,8 @@ Two roots exist:
`riotpiao.com` resolves via Cloudflare (172.67.196.33 / 104.21.60.115) but returns HTTP 403 at the edge, no origin headers. `riotpiao.com` resolves via Cloudflare (172.67.196.33 / 104.21.60.115) but returns HTTP 403 at the edge, no origin headers.
**Update (2026-08-18):** `kubectl get ingress -A` shows zero ingress objects with host exactly `riotpiao.com` (bare apex) — every rule is a subdomain (`argocd.`, `forgejo.`, `grafana.`, ...). No in-cluster origin is configured for the apex at all, which is consistent with (though not proof of) "no origin configured" as the 403 cause. Still need the Cloudflare tunnel Public Hostnames list / WAF event log to confirm whether the tunnel even has an apex entry, since that's Cloudflare-side config not visible from `kubectl`.
**Decision needed:** tunnel route missing, WAF rule, or no origin configured at all. Check Cloudflare tunnel Public Hostnames list + WAF event log (event log names the blocking rule). Blocks Phase 0.5. **Decision needed:** tunnel route missing, WAF rule, or no origin configured at all. Check Cloudflare tunnel Public Hostnames list + WAF event log (event log names the blocking rule). Blocks Phase 0.5.
## 3. Is homarr still wanted? ## 3. Is homarr still wanted?
@@ -34,7 +38,7 @@ This repo (`riotpiao`) or the homelab repo. Follows from decision 1 — whicheve
--- ---
Once answered, update this file with the decisions taken (date + rationale) before starting [01-phase0-unblock.md](01-phase0-unblock.md). Once answered, update this file with the decisions taken (date + rationale) before starting Phase 0 tasks in [INDEX.md](INDEX.md).
## Hard requirement: Playwright ## Hard requirement: Playwright
-44
View File
@@ -1,44 +0,0 @@
# Phase 0 — Unblock
Blocking. Nothing ships until this lands. Requires [00-decisions.md](00-decisions.md) #1 and #2 answered first.
## Tasks
- [ ] **0.1** Resolve which GitOps repo owns the portfolio; delete or correct the losing manifest
- Depends on: decision #1
- Verify: `kubectl get application portfolio -n argocd` shows one, correct, source
- [ ] **0.2** Point `infra/argocd-apps.yaml` `repoURL` and image reference at real hostnames (fix NXDOMAIN)
- Verify: `kubectl get pods -n portfolio``2/2 Running`
- [ ] **0.3** Replace `:latest` tag with commit-SHA tag in `infra/portfolio/base/deployment.yaml`; keep `imagePullPolicy: IfNotPresent` (correct once tags are immutable)
- Verify: new commit → new tag → Argo rolls out automatically, no manual `kubectl set image`
- [ ] **0.4** Diagnose `forgejo-gitea` stuck `Init:0/3` (3h+) — read init container logs before changing anything
- Command: `kubectl logs -n cicd <forgejo-gitea-pod> -c <init-container-name>`
- Verify: pod reaches `Running`, image builds succeed
- [ ] **0.5** Diagnose apex 403 — check Cloudflare tunnel Public Hostnames list and WAF event log
- Depends on: decision #2
- Verify: `curl -sS -o /dev/null -w '%{http_code}\n' https://riotpiao.com``200`
- [ ] **0.6** Add Vitest + Testing Library + `msw`; add `test` and `test:watch` scripts to `package.json`
- Verify: `pnpm test` → runner executes, 0 tests, exit 0
- [ ] **0.6a** Add Playwright (`pnpm create playwright`); add `test:e2e` script to `package.json`; wire into CI as a hard gate (build fails if `test:e2e` fails)
- Playwright is a hard requirement, not optional, for every phase that ships a UI surface (Phase 2, 3, 4, 5) — no surface merges without a passing e2e spec
- Verify: `pnpm test:e2e` → runner executes, 0 tests, exit 0
- [ ] **0.7** Triage unrelated cluster issues: `sms` Application Degraded (`macos-bluebubbles` Pending 3h), `longhorn-config` OutOfSync
- Unrelated to atlas, but delivery tree (Phase 3) will render both red on day one — fix or explicitly accept as known-red
## Phase verify
```bash
kubectl get pods -n portfolio # 2/2 Running
curl -sS -o /dev/null -w '%{http_code}\n' https://riotpiao.com # 200
pnpm test # runner executes, 0 tests, exit 0
pnpm test:e2e # runner executes, 0 tests, exit 0
```
Next: [02-phase1-atlas-core.md](02-phase1-atlas-core.md)
-35
View File
@@ -1,35 +0,0 @@
# Phase 1 — atlas core
~4 days. Every surface depends on this. RED → GREEN → REFACTOR.
## RED (write tests first, confirm they fail)
- [ ] `redact_test.go` — golden test: serialized snapshot contains none of the denied fields, run against a fixture captured from the real cluster
- [ ] `rbac_test.go` — atlas ServiceAccount receives 403 on `get secrets` in every namespace
- [ ] `snapshot_test.go` — an informer event produces the expected delta
## GREEN
- [ ] ClusterRole: verbs `get,list,watch` only, explicit resource list — no `secrets`, no `*`, no wildcard apiGroups
- [ ] client-go informers: kube API (nodes, namespaces, workloads), Argo CD `Application` CRs
- [ ] Reducer: informer events → in-memory snapshot, redacted **at write time**
- [ ] DTO construction — allowlist only. Emitted: name, namespace, kind, phase, ready counts, restart count, age, node name, health status, sync status, sync wave, explicit label subset. Never emitted: container env/args, image digests/tags, `spec.source.repoURL`, `spec.source.path`, annotations, pod IPs, cluster IPs, Secret names, `status.conditions[].message`, node internal IPs
- [ ] Redis publish (snapshot deltas → `kmsvc-redis-master.sqs:6379`)
- [ ] NetworkPolicy on atlas: egress restricted to kube API, `prometheus-operated.monitoring`, `reasoning-predictor.llm-serving`, `kmsvc-redis-master.sqs`; ingress from `ingress-nginx` only
- [ ] Container hardening: `runAsNonRoot`, read-only root filesystem, all capabilities dropped, `seccompProfile: RuntimeDefault`
## REFACTOR
- [ ] Run `simplify` skill pass on reducer/DTO code
- [ ] Confirm no `_ =` on errors, no naked returns, every upstream call carries a `context.Context` (go-error-handling, go-context skills)
## Verify
```bash
kubectl auth can-i get secrets --as=system:serviceaccount:portfolio:atlas # no
go test ./... -run TestRedact -v
go test ./... -run TestRBAC -v
go test ./... -run TestSnapshot -v
```
Next: [03-phase2-topology.md](03-phase2-topology.md)
-31
View File
@@ -1,31 +0,0 @@
# Phase 2 — Surface B: cluster topology
~3 days. Proves the snapshot + SSE pipeline end to end.
## RED
- [ ] `stream_test.go` — SSE emits a delta within 5s of a pod state change
- [ ] `topology.test.tsx` — graph re-renders on delta without a full reload
- [ ] `topology.e2e.ts` (Playwright, hard requirement) — load `/topology`, assert graph nodes render, delete a pod, assert node count updates without reload
## GREEN
- [ ] `GET /api/topology` — nodes, namespaces, workload summaries; envelope `{"data": {}, "meta": {"snapshotAge", "generation"}}`; capped 256 KB, `meta.truncated: true` on overflow, never a silent drop
- [ ] `GET /api/stream` — SSE, session cookie, 2 concurrent/IP, `topology` event type; keepalive comment frame every 30s; `Last-Event-ID` supported for resumable deltas
- [ ] Frontend: React Flow, force layout, node → namespace → workload
- [ ] Security headers on all responses: CSP (no `unsafe-inline`), `X-Content-Type-Options: nosniff`, `Referrer-Policy: no-referrer`, HSTS; CORS same-origin only
- [ ] Rate limiting Tier 1 (Cloudflare edge — WAF, Bot Fight Mode, per-IP rules) + Tier 2 (Kong `rate-limiting`, `policy: redis`, generous profile for topology)
## REFACTOR
- [ ] `simplify` pass on SSE handler + React Flow wiring
## Verify
```bash
# delete a pod, observe graph update in <5s without reloading
kubectl delete pod <name> -n <ns>
pnpm test:e2e topology.e2e.ts # required, must pass before merge
```
Next: [04-phase3-delivery.md](04-phase3-delivery.md)
-33
View File
@@ -1,33 +0,0 @@
# Phase 3 — Surface E: delivery tree
~3 days. Zero new data sources, zero new attack surface, highest signal — reads as platform engineering, not hobby.
## RED
- [ ] `delivery_test.go` — apps group correctly by `sync-wave`; Secret names absent from output (kind+count only); `repoURL` absent from output
- [ ] `delivery.test.tsx` — 550-node tree renders under frame budget with virtualization on
- [ ] `delivery.e2e.ts` (Playwright, hard requirement) — load `/delivery`, click app node, assert side panel opens with virtualized resource tree, assert no Secret names appear in DOM
## GREEN
- [ ] `GET /api/delivery` — Argo apps, wave-grouped (0→8), resource children lazy; capped 256 KB
- [ ] `GET /api/delivery/{app}/resources` — cursor-paginated at 100 items (prometheus alone has 68 resources today)
- [ ] Frontend: React Flow, wave columns left→right from `sync-wave` annotations. Click app → side panel with `react-arborist` virtualized resource tree, lazy-loaded children
- [ ] Live sync animation `OutOfSync → Syncing → Synced` driven by Application watch (reuse Phase 2 SSE `/api/stream`, add `delivery` event type)
- [ ] Redaction check specific to this surface: 21 `Secret` resources appear in Argo trees today — render kind+count only, never names (includes `sops-secrets`); `status.conditions[].message` echoes raw errors with internal hostnames — emit condition **type** only
## REFACTOR
- [ ] `simplify` pass on wave-grouping + virtualized tree code
## Verify
```bash
# trigger an Argo sync, observe wave-ordered animation
argocd app sync homelab-root
pnpm test:e2e delivery.e2e.ts # required, must pass before merge
```
Also in scope for this phase: delete fabricated stats in `app/page.tsx` ("40% CPU reduction", "99.2% uptime", "Mission-critical", "60% latency cut") — wire each card to a real number from `/api/topology` or `/api/delivery`, or remove the claim. Five of six landing cards link to routes that don't exist (`/infrastructure`, `/systems`, `/llm`, `/kafka`, `/opensource`) — fix or remove.
Next: [05-phase4-terminal.md](05-phase4-terminal.md)
-32
View File
@@ -1,32 +0,0 @@
# Phase 4 — Surface C: terminal
~2 days. First surface accepting user input — injection (A03) is the primary risk here.
## RED
- [ ] `exec_parse_test.go` — fuzz corpus: every non-allowlisted input rejects and performs zero upstream calls
- [ ] `exec_test.go` — unknown namespace rejects on snapshot membership, not regex
- [ ] `terminal.e2e.ts` (Playwright, hard requirement) — load terminal UI, submit `get nodes`, assert rendered output; submit an injection payload, assert rejection surfaces in UI, zero upstream call
## GREEN
- [ ] `POST /api/exec` — session cookie, 20/min/session; input parses to a closed command enum, anything unmatched rejected before any lookup
- [ ] Command set: `get nodes`, `get pods <ns>`, `get apps`, `top nodes`, `describe pod <ns> <name>`, `help`
- [ ] Namespace and resource-name arguments validated by **set membership against current snapshot**, not regex/escaping
- [ ] No shell, no `exec`, no `kubectl` binary in the container image
- [ ] Frontend: reuse [components/InteractiveTerminal.tsx](../components/InteractiveTerminal.tsx), wire to `/api/exec`
- [ ] Structured logging: every rejected `/api/exec` input logged
## REFACTOR
- [ ] `simplify` pass on the enum parser
## Verify
```bash
# attempt injection payloads against /api/exec; all rejected, all logged
curl -X POST https://riotpiao.com/api/exec -d '{"cmd":"get pods; rm -rf /"}'
pnpm test:e2e terminal.e2e.ts # required, must pass before merge
```
Next: [06-phase5-chat.md](06-phase5-chat.md)
-48
View File
@@ -1,48 +0,0 @@
# Phase 5 — Surface D: chat + rate limiter
~5 days, one PR. Highest risk, highest cost — ships last, ships with its limiter, never after.
Depends on [00-decisions.md](00-decisions.md) #4 (is chat in scope for v1).
## RED
- [ ] `ratelimit_test.go` — 7th concurrent chat queues rather than reaching vLLM
- [ ] `disconnect_test.go` — client abort cancels the upstream request
- [ ] `budget_test.go` — 13th message in 24h returns `429` with `Retry-After`
- [ ] `injection_test.go` — snapshot content cannot alter system-prompt behaviour
- [ ] `context_test.go` — history truncation keeps total tokens under 16384
- [ ] `chat.e2e.ts` (Playwright, hard requirement) — load chat UI, send message, assert streamed tokens render, assert queue position renders under load, assert reasoning block is collapsible
## GREEN
- [ ] `POST /api/chat` — session + Turnstile, 12/day/session, 6 global concurrent, SSE token stream
- [ ] Rate limiting Tier 3 (atlas): global chat semaphore = 6 (2 of 8 GPU slots kept as operator headroom); queue depth 20 then reject `429`; per-session budget 12 msg/24h; per-request timeout 120s hard server-side; disconnect cancels upstream immediately (`req.Context()` threaded to vLLM request)
- [ ] Queue position streamed as SSE `{"type":"queue","position":N}`
- [ ] Prompt injection defenses: system prompt is compile-time constant, unreachable by user input; cluster snapshot digest injected in a delimited block explicitly labelled untrusted data; user message always last; **no tool-calling** — model reads pre-built digest, cannot query anything; `max_tokens: 1500` cap
- [ ] Context budget (16384 total): system prompt ~300, snapshot digest capped at 2000, `max_tokens` 1500, ~12500 for history, truncated oldest-first
- [ ] Chat SSE events: `{"type":"reasoning"|"content"|"queue"|"done"|"error"}` — render `reasoning_content` in collapsible block (this is the demo)
- [ ] Frontend: new chat component — collapsible reasoning block, queue position, streaming tokens
- [ ] Prometheus metrics: `atlas_chat_concurrent`, `atlas_chat_queue_depth`, `atlas_ratelimit_rejections_total`, `atlas_snapshot_age_seconds`
- [ ] Alertmanager rule: chat queue saturated > 5 min, snapshot age > 60s
- [ ] `pnpm audit` + `govulncheck` in CI, fail build on high severity
## REFACTOR
- [ ] `simplify` pass on rate limiter + SSE chat handler
## Verify
```bash
# load test at 20 concurrent clients
# GPU sequence usage never exceeds 6, no upstream 5xx, queue drains
pnpm test:e2e chat.e2e.ts # required, must pass before merge
```
## Done — atlas v1 shipped
Success criteria (from PLAN-atlas.md):
- [ ] Protected services redirect unauthenticated to Authentik (if still applicable post decision #3)
- [ ] Fabricated stats gone, real numbers or removed
- [ ] `riotpiao.com` is the only public hostname (I1 held)
- [ ] Golden redaction test passes against live-cluster fixture
- [ ] Load test: 20 concurrent clients, GPU usage ≤ 6, no 5xx, queue drains
+12
View File
@@ -0,0 +1,12 @@
# 1.1 — redact_test.go (RED)
Phase: 1 — Atlas Core
Stage: RED
- [ ] Golden test: serialized snapshot contains none of the denied fields, run against a fixture captured from the real cluster
## Verify
```bash
go test ./... -run TestRedact -v
```
+6
View File
@@ -0,0 +1,6 @@
# 1.10 — Container hardening (GREEN)
Phase: 1 — Atlas Core
Stage: GREEN
- [ ] Container hardening: `runAsNonRoot`, read-only root filesystem, all capabilities dropped, `seccompProfile: RuntimeDefault`
+8
View File
@@ -0,0 +1,8 @@
# 1.11 — simplify skill pass (REFACTOR)
Phase: 1 — Atlas Core
Stage: REFACTOR
Depends on: [1.4](1.4-clusterrole.md), [1.5](1.5-informers.md), [1.6](1.6-reducer.md), [1.7](1.7-dto-allowlist.md), [1.8](1.8-redis-publish.md)
- [ ] Run `simplify` skill pass on reducer/DTO code
+16
View File
@@ -0,0 +1,16 @@
# 1.12 — Go error-handling / context hygiene (REFACTOR)
Phase: 1 — Atlas Core
Stage: REFACTOR
Depends on: [1.11](1.11-simplify-pass.md)
- [ ] Confirm no `_ =` on errors, no naked returns, every upstream call carries a `context.Context` (go-error-handling, go-context skills)
## Verify
```bash
go test ./... -run TestRedact -v
go test ./... -run TestRBAC -v
go test ./... -run TestSnapshot -v
```
+13
View File
@@ -0,0 +1,13 @@
# 1.2 — rbac_test.go (RED)
Phase: 1 — Atlas Core
Stage: RED
- [ ] atlas ServiceAccount receives 403 on `get secrets` in every namespace
## Verify
```bash
kubectl auth can-i get secrets --as=system:serviceaccount:portfolio:atlas # no
go test ./... -run TestRBAC -v
```
+12
View File
@@ -0,0 +1,12 @@
# 1.3 — snapshot_test.go (RED)
Phase: 1 — Atlas Core
Stage: RED
- [ ] An informer event produces the expected delta
## Verify
```bash
go test ./... -run TestSnapshot -v
```
+14
View File
@@ -0,0 +1,14 @@
# 1.4 — ClusterRole (GREEN)
Phase: 1 — Atlas Core
Stage: GREEN
Depends on: [1.2](1.2-rbac-test.md)
- [ ] ClusterRole: verbs `get,list,watch` only, explicit resource list — no `secrets`, no `*`, no wildcard apiGroups
## Verify
```bash
kubectl auth can-i get secrets --as=system:serviceaccount:portfolio:atlas # no
```
+14
View File
@@ -0,0 +1,14 @@
# 1.5 — client-go informers (GREEN)
Phase: 1 — Atlas Core
Stage: GREEN
Depends on: [1.3](1.3-snapshot-test.md)
- [ ] client-go informers: kube API (nodes, namespaces, workloads), Argo CD `Application` CRs
## Verify
```bash
go test ./... -run TestSnapshot -v
```
+14
View File
@@ -0,0 +1,14 @@
# 1.6 — Reducer: informer events -> snapshot (GREEN)
Phase: 1 — Atlas Core
Stage: GREEN
Depends on: [1.5](1.5-informers.md)
- [ ] Reducer: informer events -> in-memory snapshot, redacted **at write time**
## Verify
```bash
go test ./... -run TestSnapshot -v
```
+16
View File
@@ -0,0 +1,16 @@
# 1.7 — DTO construction, allowlist only (GREEN)
Phase: 1 — Atlas Core
Stage: GREEN
Depends on: [1.1](1.1-redact-test.md), [1.6](1.6-reducer.md)
- [ ] DTO construction — allowlist only.
- Emitted: name, namespace, kind, phase, ready counts, restart count, age, node name, health status, sync status, sync wave, explicit label subset
- Never emitted: container env/args, image digests/tags, `spec.source.repoURL`, `spec.source.path`, annotations, pod IPs, cluster IPs, Secret names, `status.conditions[].message`, node internal IPs
## Verify
```bash
go test ./... -run TestRedact -v
```
+8
View File
@@ -0,0 +1,8 @@
# 1.8 — Redis publish (GREEN)
Phase: 1 — Atlas Core
Stage: GREEN
Depends on: [1.6](1.6-reducer.md)
- [ ] Redis publish (snapshot deltas -> `kmsvc-redis-master.sqs:6379`)
+6
View File
@@ -0,0 +1,6 @@
# 1.9 — NetworkPolicy on atlas (GREEN)
Phase: 1 — Atlas Core
Stage: GREEN
- [ ] NetworkPolicy on atlas: egress restricted to kube API, `prometheus-operated.monitoring`, `reasoning-predictor.llm-serving`, `kmsvc-redis-master.sqs`; ingress from `ingress-nginx` only
+12
View File
@@ -0,0 +1,12 @@
# 2.1 — stream_test.go (RED)
Phase: 2 — Topology (Surface B)
Stage: RED
- [ ] SSE emits a delta within 5s of a pod state change
## Verify
```bash
go test ./... -run TestStream -v
```
+12
View File
@@ -0,0 +1,12 @@
# 2.2 — topology.test.tsx (RED)
Phase: 2 — Topology (Surface B)
Stage: RED
- [ ] Graph re-renders on delta without a full reload
## Verify
```bash
pnpm test topology.test.tsx
```
+12
View File
@@ -0,0 +1,12 @@
# 2.3 — topology.e2e.ts (RED, Playwright — hard requirement)
Phase: 2 — Topology (Surface B)
Stage: RED
- [ ] Load `/topology`, assert graph nodes render, delete a pod, assert node count updates without reload
## Verify
```bash
pnpm test:e2e topology.e2e.ts # required, must pass before merge
```
+8
View File
@@ -0,0 +1,8 @@
# 2.4 — GET /api/topology (GREEN)
Phase: 2 — Topology (Surface B)
Stage: GREEN
Depends on: [1.7](1.7-dto-allowlist.md)
- [ ] `GET /api/topology` — nodes, namespaces, workload summaries; envelope `{"data": {}, "meta": {"snapshotAge", "generation"}}`; capped 256 KB, `meta.truncated: true` on overflow, never a silent drop
+14
View File
@@ -0,0 +1,14 @@
# 2.5 — GET /api/stream (GREEN)
Phase: 2 — Topology (Surface B)
Stage: GREEN
Depends on: [2.1](2.1-stream-test.md), [1.8](1.8-redis-publish.md)
- [ ] `GET /api/stream` — SSE, session cookie, 2 concurrent/IP, `topology` event type; keepalive comment frame every 30s; `Last-Event-ID` supported for resumable deltas
## Verify
```bash
go test ./... -run TestStream -v
```
+15
View File
@@ -0,0 +1,15 @@
# 2.6 — Frontend: React Flow topology graph (GREEN)
Phase: 2 — Topology (Surface B)
Stage: GREEN
Depends on: [2.2](2.2-topology-component-test.md), [2.4](2.4-api-topology.md), [2.5](2.5-api-stream.md)
- [ ] Frontend: React Flow, force layout, node -> namespace -> workload
## Verify
```bash
# delete a pod, observe graph update in <5s without reloading
kubectl delete pod <name> -n <ns>
```
+6
View File
@@ -0,0 +1,6 @@
# 2.7 — Security headers (GREEN)
Phase: 2 — Topology (Surface B)
Stage: GREEN
- [ ] Security headers on all responses: CSP (no `unsafe-inline`), `X-Content-Type-Options: nosniff`, `Referrer-Policy: no-referrer`, HSTS; CORS same-origin only
+6
View File
@@ -0,0 +1,6 @@
# 2.8 — Rate limiting Tier 1 + Tier 2 (GREEN)
Phase: 2 — Topology (Surface B)
Stage: GREEN
- [ ] Rate limiting Tier 1 (Cloudflare edge — WAF, Bot Fight Mode, per-IP rules) + Tier 2 (Kong `rate-limiting`, `policy: redis`, generous profile for topology)
+15
View File
@@ -0,0 +1,15 @@
# 2.9 — simplify skill pass (REFACTOR)
Phase: 2 — Topology (Surface B)
Stage: REFACTOR
Depends on: [2.5](2.5-api-stream.md), [2.6](2.6-frontend-react-flow.md)
- [ ] `simplify` pass on SSE handler + React Flow wiring
## Verify
```bash
kubectl delete pod <name> -n <ns> # graph updates in <5s without reload
pnpm test:e2e topology.e2e.ts # required, must pass before merge
```
+12
View File
@@ -0,0 +1,12 @@
# 3.1 — delivery_test.go (RED)
Phase: 3 — Delivery Tree (Surface E)
Stage: RED
- [ ] Apps group correctly by `sync-wave`; Secret names absent from output (kind+count only); `repoURL` absent from output
## Verify
```bash
go test ./... -run TestDelivery -v
```
+7
View File
@@ -0,0 +1,7 @@
# 3.10 — Landing page: remove fabricated stats and dead links
Phase: 3 — Delivery Tree (Surface E)
Stage: GREEN
- [ ] Delete fabricated stats in `app/page.tsx` ("40% CPU reduction", "99.2% uptime", "Mission-critical", "60% latency cut") — wire each card to a real number from `/api/topology` or `/api/delivery`, or remove the claim
- [ ] Five of six landing cards link to routes that don't exist (`/infrastructure`, `/systems`, `/llm`, `/kafka`, `/opensource`) — fix or remove
+12
View File
@@ -0,0 +1,12 @@
# 3.2 — delivery.test.tsx (RED)
Phase: 3 — Delivery Tree (Surface E)
Stage: RED
- [ ] 550-node tree renders under frame budget with virtualization on
## Verify
```bash
pnpm test delivery.test.tsx
```
+12
View File
@@ -0,0 +1,12 @@
# 3.3 — delivery.e2e.ts (RED, Playwright — hard requirement)
Phase: 3 — Delivery Tree (Surface E)
Stage: RED
- [ ] Load `/delivery`, click app node, assert side panel opens with virtualized resource tree, assert no Secret names appear in DOM
## Verify
```bash
pnpm test:e2e delivery.e2e.ts # required, must pass before merge
```
+8
View File
@@ -0,0 +1,8 @@
# 3.4 — GET /api/delivery (GREEN)
Phase: 3 — Delivery Tree (Surface E)
Stage: GREEN
Depends on: [3.1](3.1-delivery-test.md)
- [ ] `GET /api/delivery` — Argo apps, wave-grouped (0->8), resource children lazy; capped 256 KB
+8
View File
@@ -0,0 +1,8 @@
# 3.5 — GET /api/delivery/{app}/resources (GREEN)
Phase: 3 — Delivery Tree (Surface E)
Stage: GREEN
Depends on: [3.4](3.4-api-delivery.md)
- [ ] `GET /api/delivery/{app}/resources` — cursor-paginated at 100 items (prometheus alone has 68 resources today)
+8
View File
@@ -0,0 +1,8 @@
# 3.6 — Frontend: wave columns + resource tree (GREEN)
Phase: 3 — Delivery Tree (Surface E)
Stage: GREEN
Depends on: [3.2](3.2-delivery-component-test.md), [3.4](3.4-api-delivery.md), [3.5](3.5-api-delivery-resources.md)
- [ ] Frontend: React Flow, wave columns left->right from `sync-wave` annotations. Click app -> side panel with `react-arborist` virtualized resource tree, lazy-loaded children
+15
View File
@@ -0,0 +1,15 @@
# 3.7 — Live sync animation (GREEN)
Phase: 3 — Delivery Tree (Surface E)
Stage: GREEN
Depends on: [2.5](2.5-api-stream.md), [3.6](3.6-frontend-wave-columns.md)
- [ ] Live sync animation `OutOfSync -> Syncing -> Synced` driven by Application watch (reuse Phase 2 SSE `/api/stream`, add `delivery` event type)
## Verify
```bash
# trigger an Argo sync, observe wave-ordered animation
argocd app sync homelab-root
```
+14
View File
@@ -0,0 +1,14 @@
# 3.8 — Redaction check specific to delivery surface (GREEN)
Phase: 3 — Delivery Tree (Surface E)
Stage: GREEN
Depends on: [3.4](3.4-api-delivery.md)
- [ ] 21 `Secret` resources appear in Argo trees today — render kind+count only, never names (includes `sops-secrets`); `status.conditions[].message` echoes raw errors with internal hostnames — emit condition **type** only
## Verify
```bash
go test ./... -run TestDelivery -v
```
+15
View File
@@ -0,0 +1,15 @@
# 3.9 — simplify skill pass (REFACTOR)
Phase: 3 — Delivery Tree (Surface E)
Stage: REFACTOR
Depends on: [3.6](3.6-frontend-wave-columns.md), [3.7](3.7-live-sync-animation.md)
- [ ] `simplify` pass on wave-grouping + virtualized tree code
## Verify
```bash
argocd app sync homelab-root # wave-ordered animation
pnpm test:e2e delivery.e2e.ts # required, must pass before merge
```
+12
View File
@@ -0,0 +1,12 @@
# 4.1 — exec_parse_test.go (RED)
Phase: 4 — Terminal (Surface C)
Stage: RED
- [ ] Fuzz corpus: every non-allowlisted input rejects and performs zero upstream calls
## Verify
```bash
go test ./... -run TestExecParse -v
```
+15
View File
@@ -0,0 +1,15 @@
# 4.10 — simplify skill pass (REFACTOR)
Phase: 4 — Terminal (Surface C)
Stage: REFACTOR
Depends on: [4.5](4.5-command-set.md), [4.6](4.6-namespace-validation.md)
- [ ] `simplify` pass on the enum parser
## Verify
```bash
curl -X POST https://riotpiao.com/api/exec -d '{"cmd":"get pods; rm -rf /"}'
pnpm test:e2e terminal.e2e.ts # required, must pass before merge
```
+12
View File
@@ -0,0 +1,12 @@
# 4.2 — exec_test.go (RED)
Phase: 4 — Terminal (Surface C)
Stage: RED
- [ ] Unknown namespace rejects on snapshot membership, not regex
## Verify
```bash
go test ./... -run TestExec -v
```
+12
View File
@@ -0,0 +1,12 @@
# 4.3 — terminal.e2e.ts (RED, Playwright — hard requirement)
Phase: 4 — Terminal (Surface C)
Stage: RED
- [ ] Load terminal UI, submit `get nodes`, assert rendered output; submit an injection payload, assert rejection surfaces in UI, zero upstream call
## Verify
```bash
pnpm test:e2e terminal.e2e.ts # required, must pass before merge
```
+14
View File
@@ -0,0 +1,14 @@
# 4.4 — POST /api/exec (GREEN)
Phase: 4 — Terminal (Surface C)
Stage: GREEN
Depends on: [4.1](4.1-exec-parse-test.md), [4.2](4.2-exec-test.md)
- [ ] `POST /api/exec` — session cookie, 20/min/session; input parses to a closed command enum, anything unmatched rejected before any lookup
## Verify
```bash
curl -X POST https://riotpiao.com/api/exec -d '{"cmd":"get pods; rm -rf /"}'
```
+8
View File
@@ -0,0 +1,8 @@
# 4.5 — Command set (GREEN)
Phase: 4 — Terminal (Surface C)
Stage: GREEN
Depends on: [4.4](4.4-api-exec.md)
- [ ] Command set: `get nodes`, `get pods <ns>`, `get apps`, `top nodes`, `describe pod <ns> <name>`, `help`
+14
View File
@@ -0,0 +1,14 @@
# 4.6 — Namespace/resource-name validation by snapshot membership (GREEN)
Phase: 4 — Terminal (Surface C)
Stage: GREEN
Depends on: [4.2](4.2-exec-test.md), [1.7](1.7-dto-allowlist.md)
- [ ] Namespace and resource-name arguments validated by **set membership against current snapshot**, not regex/escaping
## Verify
```bash
go test ./... -run TestExec -v
```
+6
View File
@@ -0,0 +1,6 @@
# 4.7 — No shell, no exec, no kubectl binary in image (GREEN)
Phase: 4 — Terminal (Surface C)
Stage: GREEN
- [ ] No shell, no `exec`, no `kubectl` binary in the container image
+8
View File
@@ -0,0 +1,8 @@
# 4.8 — Frontend: wire InteractiveTerminal to /api/exec (GREEN)
Phase: 4 — Terminal (Surface C)
Stage: GREEN
Depends on: [4.4](4.4-api-exec.md)
- [ ] Frontend: reuse [components/InteractiveTerminal.tsx](../components/InteractiveTerminal.tsx), wire to `/api/exec`
+15
View File
@@ -0,0 +1,15 @@
# 4.9 — Structured logging of rejections (GREEN)
Phase: 4 — Terminal (Surface C)
Stage: GREEN
Depends on: [4.4](4.4-api-exec.md)
- [ ] Structured logging: every rejected `/api/exec` input logged
## Verify
```bash
# attempt injection payloads against /api/exec; all rejected, all logged
curl -X POST https://riotpiao.com/api/exec -d '{"cmd":"get pods; rm -rf /"}'
```
+14
View File
@@ -0,0 +1,14 @@
# 5.1 — ratelimit_test.go (RED)
Phase: 5 — Chat + Rate Limiter (Surface D)
Stage: RED
Depends on: [00-decisions.md](00-decisions.md) decision #4
- [ ] 7th concurrent chat queues rather than reaching vLLM
## Verify
```bash
go test ./... -run TestRateLimit -v
```
+14
View File
@@ -0,0 +1,14 @@
# 5.10 — Prompt injection defenses (GREEN)
Phase: 5 — Chat + Rate Limiter (Surface D)
Stage: GREEN
Depends on: [5.4](5.4-injection-test.md)
- [ ] System prompt is compile-time constant, unreachable by user input; cluster snapshot digest injected in a delimited block explicitly labelled untrusted data; user message always last; **no tool-calling** — model reads pre-built digest, cannot query anything; `max_tokens: 1500` cap
## Verify
```bash
go test ./... -run TestInjection -v
```
+14
View File
@@ -0,0 +1,14 @@
# 5.11 — Context budget (GREEN)
Phase: 5 — Chat + Rate Limiter (Surface D)
Stage: GREEN
Depends on: [5.5](5.5-context-test.md)
- [ ] Context budget (16384 total): system prompt ~300, snapshot digest capped at 2000, `max_tokens` 1500, ~12500 for history, truncated oldest-first
## Verify
```bash
go test ./... -run TestContext -v
```
+8
View File
@@ -0,0 +1,8 @@
# 5.12 — Chat SSE event types (GREEN)
Phase: 5 — Chat + Rate Limiter (Surface D)
Stage: GREEN
Depends on: [5.7](5.7-api-chat.md)
- [ ] Chat SSE events: `{"type":"reasoning"|"content"|"queue"|"done"|"error"}` — render `reasoning_content` in collapsible block (this is the demo)
+8
View File
@@ -0,0 +1,8 @@
# 5.13 — Frontend: chat component (GREEN)
Phase: 5 — Chat + Rate Limiter (Surface D)
Stage: GREEN
Depends on: [5.6](5.6-chat-e2e.md), [5.12](5.12-chat-sse-events.md)
- [ ] Frontend: new chat component — collapsible reasoning block, queue position, streaming tokens
+8
View File
@@ -0,0 +1,8 @@
# 5.14 — Prometheus metrics (GREEN)
Phase: 5 — Chat + Rate Limiter (Surface D)
Stage: GREEN
Depends on: [5.8](5.8-rate-limiting-tier3.md)
- [ ] Prometheus metrics: `atlas_chat_concurrent`, `atlas_chat_queue_depth`, `atlas_ratelimit_rejections_total`, `atlas_snapshot_age_seconds`
+8
View File
@@ -0,0 +1,8 @@
# 5.15 — Alertmanager rule (GREEN)
Phase: 5 — Chat + Rate Limiter (Surface D)
Stage: GREEN
Depends on: [5.14](5.14-prometheus-metrics.md)
- [ ] Alertmanager rule: chat queue saturated > 5 min, snapshot age > 60s
+6
View File
@@ -0,0 +1,6 @@
# 5.16 — pnpm audit + govulncheck CI gate (GREEN)
Phase: 5 — Chat + Rate Limiter (Surface D)
Stage: GREEN
- [ ] `pnpm audit` + `govulncheck` in CI, fail build on high severity
+25
View File
@@ -0,0 +1,25 @@
# 5.17 — simplify skill pass (REFACTOR)
Phase: 5 — Chat + Rate Limiter (Surface D)
Stage: REFACTOR
Depends on: [5.8](5.8-rate-limiting-tier3.md), [5.13](5.13-frontend-chat-component.md)
- [ ] `simplify` pass on rate limiter + SSE chat handler
## Verify
```bash
# load test at 20 concurrent clients
# GPU sequence usage never exceeds 6, no upstream 5xx, queue drains
pnpm test:e2e chat.e2e.ts # required, must pass before merge
```
## Done — atlas v1 shipped
Success criteria (from PLAN-atlas.md):
- [ ] Protected services redirect unauthenticated to Authentik (if still applicable post decision #3)
- [ ] Fabricated stats gone, real numbers or removed
- [ ] `riotpiao.com` is the only public hostname (I1 held)
- [ ] Golden redaction test passes against live-cluster fixture
- [ ] Load test: 20 concurrent clients, GPU usage <= 6, no 5xx, queue drains
+12
View File
@@ -0,0 +1,12 @@
# 5.2 — disconnect_test.go (RED)
Phase: 5 — Chat + Rate Limiter (Surface D)
Stage: RED
- [ ] Client abort cancels the upstream request
## Verify
```bash
go test ./... -run TestDisconnect -v
```
+12
View File
@@ -0,0 +1,12 @@
# 5.3 — budget_test.go (RED)
Phase: 5 — Chat + Rate Limiter (Surface D)
Stage: RED
- [ ] 13th message in 24h returns `429` with `Retry-After`
## Verify
```bash
go test ./... -run TestBudget -v
```
+12
View File
@@ -0,0 +1,12 @@
# 5.4 — injection_test.go (RED)
Phase: 5 — Chat + Rate Limiter (Surface D)
Stage: RED
- [ ] Snapshot content cannot alter system-prompt behaviour
## Verify
```bash
go test ./... -run TestInjection -v
```
+12
View File
@@ -0,0 +1,12 @@
# 5.5 — context_test.go (RED)
Phase: 5 — Chat + Rate Limiter (Surface D)
Stage: RED
- [ ] History truncation keeps total tokens under 16384
## Verify
```bash
go test ./... -run TestContext -v
```
+12
View File
@@ -0,0 +1,12 @@
# 5.6 — chat.e2e.ts (RED, Playwright — hard requirement)
Phase: 5 — Chat + Rate Limiter (Surface D)
Stage: RED
- [ ] Load chat UI, send message, assert streamed tokens render, assert queue position renders under load, assert reasoning block is collapsible
## Verify
```bash
pnpm test:e2e chat.e2e.ts # required, must pass before merge
```
+8
View File
@@ -0,0 +1,8 @@
# 5.7 — POST /api/chat (GREEN)
Phase: 5 — Chat + Rate Limiter (Surface D)
Stage: GREEN
Depends on: [5.1](5.1-ratelimit-test.md), [5.2](5.2-disconnect-test.md), [5.3](5.3-budget-test.md)
- [ ] `POST /api/chat` — session + Turnstile, 12/day/session, 6 global concurrent, SSE token stream
+15
View File
@@ -0,0 +1,15 @@
# 5.8 — Rate limiting Tier 3 (atlas) (GREEN)
Phase: 5 — Chat + Rate Limiter (Surface D)
Stage: GREEN
Depends on: [5.7](5.7-api-chat.md)
- [ ] Global chat semaphore = 6 (2 of 8 GPU slots kept as operator headroom); queue depth 20 then reject `429`; per-session budget 12 msg/24h; per-request timeout 120s hard server-side; disconnect cancels upstream immediately (`req.Context()` threaded to vLLM request)
## Verify
```bash
# load test at 20 concurrent clients
# GPU sequence usage never exceeds 6, no upstream 5xx, queue drains
```
+8
View File
@@ -0,0 +1,8 @@
# 5.9 — Queue position streamed as SSE (GREEN)
Phase: 5 — Chat + Rate Limiter (Surface D)
Stage: GREEN
Depends on: [5.8](5.8-rate-limiting-tier3.md)
- [ ] Queue position streamed as SSE `{"type":"queue","position":N}`
+121
View File
@@ -0,0 +1,121 @@
# atlas — Task Board
Source of truth: [docs/PLAN-atlas.md](../docs/PLAN-atlas.md), [docs/adr/ADR-0001-atlas-cluster-visualization.md](../docs/adr/ADR-0001-atlas-cluster-visualization.md)
Blocking decisions (must answer before Phase 1): [00-decisions.md](00-decisions.md)
Rules carried from the ADR:
- I1: `riotpiao.com` is the only public hostname, ever
- I2: browser never talks to an internal API directly — atlas is the only origin
- I3: redaction is allowlist-only, enforced by DTO construction
- I4: no free-form string reaches an internal system (closed enum, snapshot-membership validation)
- I5: GPU chat concurrency capped at 6 of 8 sequence slots, disconnect cancels upstream immediately
- TDD: RED (tests named before code) -> GREEN (minimal code) -> REFACTOR, per phase
- Playwright e2e is a hard requirement for every UI-shipping phase (2, 3, 4, 5) — no surface merges without a passing spec
## 0 — Unblock
| Task | Description |
|---|---|
| [0.1](0.1-gitops-repo.md) | Resolve GitOps repo ownership |
| [0.2](0.2-fix-nxdomain.md) | Fix NXDOMAIN repoURL/image reference |
| [0.3](0.3-immutable-tags.md) | Replace :latest with commit-SHA tag |
| [0.4](0.4-forgejo-init-stuck.md) | Diagnose forgejo-gitea stuck Init:0/3 — done |
| [0.5](0.5-apex-403.md) | Diagnose apex 403 |
| [0.6](0.6-vitest-setup.md) | Add Vitest + Testing Library + msw |
| [0.6a](0.6a-playwright-setup.md) | Add Playwright (hard requirement) |
| [0.7](0.7-triage-unrelated.md) | Triage unrelated cluster issues |
## 1 — Atlas Core
| Task | Description |
|---|---|
| [1.1](1.1-redact-test.md) | redact_test.go (RED) |
| [1.2](1.2-rbac-test.md) | rbac_test.go (RED) |
| [1.3](1.3-snapshot-test.md) | snapshot_test.go (RED) |
| [1.4](1.4-clusterrole.md) | ClusterRole (GREEN) |
| [1.5](1.5-informers.md) | client-go informers (GREEN) |
| [1.6](1.6-reducer.md) | Reducer: informer events -> snapshot (GREEN) |
| [1.7](1.7-dto-allowlist.md) | DTO construction, allowlist only (GREEN) |
| [1.8](1.8-redis-publish.md) | Redis publish (GREEN) |
| [1.9](1.9-networkpolicy.md) | NetworkPolicy on atlas (GREEN) |
| [1.10](1.10-container-hardening.md) | Container hardening (GREEN) |
| [1.11](1.11-simplify-pass.md) | simplify skill pass (REFACTOR) |
| [1.12](1.12-go-hygiene.md) | Go error-handling / context hygiene (REFACTOR) |
## 2 — Topology (Surface B)
| Task | Description |
|---|---|
| [2.1](2.1-stream-test.md) | stream_test.go (RED) |
| [2.2](2.2-topology-component-test.md) | topology.test.tsx (RED) |
| [2.3](2.3-topology-e2e.md) | topology.e2e.ts (RED, Playwright — hard requirement) |
| [2.4](2.4-api-topology.md) | GET /api/topology (GREEN) |
| [2.5](2.5-api-stream.md) | GET /api/stream (GREEN) |
| [2.6](2.6-frontend-react-flow.md) | Frontend: React Flow topology graph (GREEN) |
| [2.7](2.7-security-headers.md) | Security headers (GREEN) |
| [2.8](2.8-rate-limiting-tier1-2.md) | Rate limiting Tier 1 + Tier 2 (GREEN) |
| [2.9](2.9-simplify-pass.md) | simplify skill pass (REFACTOR) |
## 3 — Delivery Tree (Surface E)
| Task | Description |
|---|---|
| [3.1](3.1-delivery-test.md) | delivery_test.go (RED) |
| [3.2](3.2-delivery-component-test.md) | delivery.test.tsx (RED) |
| [3.3](3.3-delivery-e2e.md) | delivery.e2e.ts (RED, Playwright — hard requirement) |
| [3.4](3.4-api-delivery.md) | GET /api/delivery (GREEN) |
| [3.5](3.5-api-delivery-resources.md) | GET /api/delivery/{app}/resources (GREEN) |
| [3.6](3.6-frontend-wave-columns.md) | Frontend: wave columns + resource tree (GREEN) |
| [3.7](3.7-live-sync-animation.md) | Live sync animation (GREEN) |
| [3.8](3.8-redaction-check.md) | Redaction check specific to delivery surface (GREEN) |
| [3.9](3.9-simplify-pass.md) | simplify skill pass (REFACTOR) |
| [3.10](3.10-landing-page-cleanup.md) | Landing page: remove fabricated stats and dead links (GREEN) |
## 4 — Terminal (Surface C)
| Task | Description |
|---|---|
| [4.1](4.1-exec-parse-test.md) | exec_parse_test.go (RED) |
| [4.2](4.2-exec-test.md) | exec_test.go (RED) |
| [4.3](4.3-terminal-e2e.md) | terminal.e2e.ts (RED, Playwright — hard requirement) |
| [4.4](4.4-api-exec.md) | POST /api/exec (GREEN) |
| [4.5](4.5-command-set.md) | Command set (GREEN) |
| [4.6](4.6-namespace-validation.md) | Namespace/resource-name validation by snapshot membership (GREEN) |
| [4.7](4.7-no-shell-in-image.md) | No shell, no exec, no kubectl binary in image (GREEN) |
| [4.8](4.8-frontend-terminal-wiring.md) | Frontend: wire InteractiveTerminal to /api/exec (GREEN) |
| [4.9](4.9-structured-logging.md) | Structured logging of rejections (GREEN) |
| [4.10](4.10-simplify-pass.md) | simplify skill pass (REFACTOR) |
## 5 — Chat + Rate Limiter (Surface D)
| Task | Description |
|---|---|
| [5.1](5.1-ratelimit-test.md) | ratelimit_test.go (RED) |
| [5.2](5.2-disconnect-test.md) | disconnect_test.go (RED) |
| [5.3](5.3-budget-test.md) | budget_test.go (RED) |
| [5.4](5.4-injection-test.md) | injection_test.go (RED) |
| [5.5](5.5-context-test.md) | context_test.go (RED) |
| [5.6](5.6-chat-e2e.md) | chat.e2e.ts (RED, Playwright — hard requirement) |
| [5.7](5.7-api-chat.md) | POST /api/chat (GREEN) |
| [5.8](5.8-rate-limiting-tier3.md) | Rate limiting Tier 3 (atlas) (GREEN) |
| [5.9](5.9-queue-position-sse.md) | Queue position streamed as SSE (GREEN) |
| [5.10](5.10-prompt-injection-defenses.md) | Prompt injection defenses (GREEN) |
| [5.11](5.11-context-budget.md) | Context budget (GREEN) |
| [5.12](5.12-chat-sse-events.md) | Chat SSE event types (GREEN) |
| [5.13](5.13-frontend-chat-component.md) | Frontend: chat component (GREEN) |
| [5.14](5.14-prometheus-metrics.md) | Prometheus metrics (GREEN) |
| [5.15](5.15-alertmanager-rule.md) | Alertmanager rule (GREEN) |
| [5.16](5.16-audit-ci-gate.md) | pnpm audit + govulncheck CI gate (GREEN) |
| [5.17](5.17-simplify-pass.md) | simplify skill pass (REFACTOR) |
## Progress
Not started, Phase 0 partially unblocked. 5 decisions still block Phase 1 — see [00-decisions.md](00-decisions.md).
Status as of 2026-08-18:
- 0.4 (forgejo-gitea Init stuck) — resolved, pod Running 1/1
- 0.7 (`longhorn-config` OutOfSync) — resolved, now Synced/Healthy; `sms` Degraded still open
- `*.riotpiao.homelab.com` fully retired, replaced by `*.riotpiao.com` — confirmed root cause for 0.2 (dead-domain `repoURL`, not a broken forgejo pod)
- 0.1/0.2 (portfolio GitOps repo, dead-domain repoURL) — still open, `application portfolio` not found in cluster, fix is now a known domain swap
- 0.5 (apex 403) — still open, `curl riotpiao.com` returns 403; no in-cluster ingress exists for bare apex, still need Cloudflare-side confirmation
+12 -11
View File
@@ -6,19 +6,20 @@ Supersedes: [PLAN.md](../PLAN.md), [IMPLEMENTATION.md](../IMPLEMENTATION.md) (Ho
## Status ## Status
Not started. **5 decisions block Phase 1** — see [00-decisions.md](00-decisions.md). Not started, Phase 0 partially unblocked. **5 decisions block Phase 1** — see [00-decisions.md](00-decisions.md).
## Phases ## Task board
| File | Phase | Est. | Task board lives in [INDEX.md](INDEX.md) — one row per task, one file per task (`<id>-<slug>.md`), agent-harness format. That file is the source of truth for execution order; this README is a human overview only.
|---|---|---|
| [00-decisions.md](00-decisions.md) | Blocking decisions (must answer before Phase 1) | — | | Phase | Est. |
| [01-phase0-unblock.md](01-phase0-unblock.md) | Phase 0 — unblock deployment | — | |---|---|
| [02-phase1-atlas-core.md](02-phase1-atlas-core.md) | Phase 1 — atlas core (RBAC, informers, redaction) | ~4d | | 0 — unblock deployment | — |
| [03-phase2-topology.md](03-phase2-topology.md) | Phase 2 — Surface B: cluster topology | ~3d | | 1 — atlas core (RBAC, informers, redaction) | ~4d |
| [04-phase3-delivery.md](04-phase3-delivery.md) | Phase 3 — Surface E: delivery tree | ~3d | | 2 — Surface B: cluster topology | ~3d |
| [05-phase4-terminal.md](05-phase4-terminal.md) | Phase 4 — Surface C: terminal | ~2d | | 3 — Surface E: delivery tree | ~3d |
| [06-phase5-chat.md](06-phase5-chat.md) | Phase 5 — Surface D: chat + rate limiter | ~5d | | 4 — Surface C: terminal | ~2d |
| 5 — Surface D: chat + rate limiter | ~5d |
**Total: ~17 working days.** **Total: ~17 working days.**