(chore) init commit and add tasks
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
# Blocking Decisions
|
||||
|
||||
All five must resolve before Phase 1 starts. Source: ADR-0001 review notes.
|
||||
|
||||
## 1. Which GitOps repo owns the portfolio?
|
||||
|
||||
Two roots exist:
|
||||
- `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)
|
||||
|
||||
**Decision needed:** GitHub or Forgejo. Blocks Phase 0.1–0.3.
|
||||
|
||||
## 2. Apex 403 cause
|
||||
|
||||
`riotpiao.com` resolves via Cloudflare (172.67.196.33 / 104.21.60.115) but returns HTTP 403 at the edge, no origin headers.
|
||||
|
||||
**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?
|
||||
|
||||
Deployed and healthy, but shipped under the abandoned Homarr+Terraform plan.
|
||||
|
||||
**Decision needed:** keep, or decommission via GitOps (remove Application, let Argo prune).
|
||||
|
||||
## 4. Does chat (Surface D) stay in v1?
|
||||
|
||||
8-slot GPU ceiling (`--max-num-seqs=4` × 2 replicas) means chat queues under real traffic — a visible "please wait" is a worse first impression than no chat.
|
||||
|
||||
**Decision needed:** ship B+E+C first and treat D as separate follow-up, or commit to D in v1 with the queue UX as-is.
|
||||
|
||||
## 5. Where does atlas live?
|
||||
|
||||
This repo (`riotpiao`) or the homelab repo. Follows from decision 1 — whichever repo is authoritative for the portfolio's GitOps should also own atlas's manifests.
|
||||
|
||||
---
|
||||
|
||||
Once answered, update this file with the decisions taken (date + rationale) before starting [01-phase0-unblock.md](01-phase0-unblock.md).
|
||||
@@ -0,0 +1,39 @@
|
||||
# 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.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
|
||||
```
|
||||
|
||||
Next: [02-phase1-atlas-core.md](02-phase1-atlas-core.md)
|
||||
@@ -0,0 +1,35 @@
|
||||
# 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)
|
||||
@@ -0,0 +1,29 @@
|
||||
# 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
|
||||
|
||||
## 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>
|
||||
```
|
||||
|
||||
Next: [04-phase3-delivery.md](04-phase3-delivery.md)
|
||||
@@ -0,0 +1,31 @@
|
||||
# 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
|
||||
|
||||
## 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
|
||||
```
|
||||
|
||||
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)
|
||||
@@ -0,0 +1,30 @@
|
||||
# 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
|
||||
|
||||
## 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 /"}'
|
||||
```
|
||||
|
||||
Next: [06-phase5-chat.md](06-phase5-chat.md)
|
||||
@@ -0,0 +1,46 @@
|
||||
# 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
|
||||
|
||||
## 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
|
||||
```
|
||||
|
||||
## 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,32 @@
|
||||
# atlas — Task Breakdown
|
||||
|
||||
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)
|
||||
|
||||
Supersedes: [PLAN.md](../PLAN.md), [IMPLEMENTATION.md](../IMPLEMENTATION.md) (Homarr + Terraform — abandoned)
|
||||
|
||||
## Status
|
||||
|
||||
Not started. **5 decisions block Phase 1** — see [00-decisions.md](00-decisions.md).
|
||||
|
||||
## Phases
|
||||
|
||||
| File | Phase | Est. |
|
||||
|---|---|---|
|
||||
| [00-decisions.md](00-decisions.md) | Blocking decisions (must answer before Phase 1) | — |
|
||||
| [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 |
|
||||
| [03-phase2-topology.md](03-phase2-topology.md) | Phase 2 — Surface B: cluster topology | ~3d |
|
||||
| [04-phase3-delivery.md](04-phase3-delivery.md) | Phase 3 — Surface E: delivery tree | ~3d |
|
||||
| [05-phase4-terminal.md](05-phase4-terminal.md) | Phase 4 — Surface C: terminal | ~2d |
|
||||
| [06-phase5-chat.md](06-phase5-chat.md) | Phase 5 — Surface D: chat + rate limiter | ~5d |
|
||||
|
||||
**Total: ~17 working days.**
|
||||
|
||||
## 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
|
||||
Reference in New Issue
Block a user