(chore) add associate tasks
This commit is contained in:
@@ -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
|
||||||
|
```
|
||||||
@@ -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
|
||||||
|
```
|
||||||
@@ -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`
|
||||||
|
```
|
||||||
@@ -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
|
||||||
|
```
|
||||||
@@ -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
|
||||||
|
```
|
||||||
@@ -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
|
||||||
|
```
|
||||||
@@ -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
|
||||||
|
```
|
||||||
@@ -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,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.1–0.3.
|
**Decision needed:** GitHub or Forgejo. Blocks Phase 0.1–0.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
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
|
||||||
@@ -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)
|
|
||||||
@@ -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)
|
|
||||||
@@ -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)
|
|
||||||
@@ -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)
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
||||||
|
```
|
||||||
@@ -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`
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
|
```
|
||||||
@@ -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
|
||||||
|
```
|
||||||
@@ -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
|
||||||
|
```
|
||||||
@@ -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
|
||||||
|
```
|
||||||
@@ -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
|
||||||
|
```
|
||||||
@@ -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
|
||||||
|
```
|
||||||
@@ -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
|
||||||
|
```
|
||||||
@@ -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`)
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
|
```
|
||||||
@@ -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
|
||||||
|
```
|
||||||
@@ -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
|
||||||
|
```
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
|
```
|
||||||
@@ -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>
|
||||||
|
```
|
||||||
@@ -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
|
||||||
@@ -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)
|
||||||
@@ -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
|
||||||
|
```
|
||||||
@@ -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
|
||||||
|
```
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
|
```
|
||||||
@@ -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
|
||||||
|
```
|
||||||
@@ -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
|
||||||
@@ -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)
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
|
```
|
||||||
@@ -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
|
||||||
|
```
|
||||||
@@ -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
|
||||||
|
```
|
||||||
@@ -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
|
||||||
|
```
|
||||||
@@ -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
|
||||||
|
```
|
||||||
@@ -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
|
||||||
|
```
|
||||||
@@ -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
|
||||||
|
```
|
||||||
@@ -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 /"}'
|
||||||
|
```
|
||||||
@@ -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`
|
||||||
@@ -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
|
||||||
|
```
|
||||||
@@ -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
|
||||||
@@ -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`
|
||||||
@@ -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 /"}'
|
||||||
|
```
|
||||||
@@ -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
|
||||||
|
```
|
||||||
@@ -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
|
||||||
|
```
|
||||||
@@ -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
|
||||||
|
```
|
||||||
@@ -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)
|
||||||
@@ -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
|
||||||
@@ -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`
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
|
```
|
||||||
@@ -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
|
||||||
|
```
|
||||||
@@ -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
|
||||||
|
```
|
||||||
@@ -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
|
||||||
|
```
|
||||||
@@ -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
|
||||||
|
```
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
|
```
|
||||||
@@ -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
@@ -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
@@ -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.**
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user