Review: B: westside-admin dev overlay (hostPath + tailnet)
Verdict: NEEDS_REFINEMENT
Reviewer: Dottie. Board item #1143 on
board-westside-admin. Forgejo issue: forgejo_admin/pal-e-deployments#148. The scope is well-shaped (clear file list, real reference overlay, correct security posture rationale), but five concrete, blocking issues will cause the dev pod to fail to come up if shipped as written. Fix in the issue body before advancing to todo.Template Completeness
- [x] Type — Feature
- [x] Lineage — links pal-e-services#73 and notes blocking dependency
- [x] Repo — pal-e-deployments
- [x] User Story — Marcus / dev hot-reload / iteration
- [x] Context — pattern reference, auth model, env vars, port rationale, ingress posture
- [x] File Targets — five files enumerated with content sketch
- [x] Acceptance Criteria — 7 items, all observable
- [x] Test Expectations — kustomize lint + smoke curl
- [x] Constraints — mirror reference, reuse Secret, no package.json edits, FF main pre-branch
- [x] Checklist — present
- [x] Related — pal-e-services#73 cross-reference
Traceability
- [x]
type:featurelabel — present on board item - [x]
arch:dev-overlaylabel — present on board item - [ ]
arch:dev-overlaybacking note MISSING —search_notes("arch-dev-overlay")returns empty.[SCOPE]Createarch-dev-overlaynote (or reusearch-deployment-westside-adminwith a Dev section appended). - [x]
story:marcus-admin-dev-accesslabel — present on board item - [ ]
story:marcus-admin-dev-accessbacking entry MISSING —project-westside-admin#user-storiescontains onlyadmin-row-crud.[SCOPE]Addmarcus-admin-dev-accessrow toproject-westside-adminUser Stories table (Key, Story Note, Role=Marcus, Success Metric). - [x] Forgejo issue valid and open — pal-e-deployments#148.
- [x] Sibling issue valid — pal-e-services#73 (also open, same backing story label).
File Targets
- [x]
overlays/westside-admin/prod/*— verified to exist onorigin/main(5 files, including `westside-admin-secrets.enc.yaml` and a `deployment-patch.yaml` that targets the standard base name `app`). - [x]
overlays/pal-e-dictionary/dev/reference — verified to exist locally onarchboxat/home/ldraney/pal-e-deployments/overlays/pal-e-dictionary/dev/. NOTE: it does not exist onorigin/main(untracked in cwd). Reference is informational only; the dev agent must read the local files to mirror them. - [x]
~/westside-admin/src/hooks.server.ts— verified. Per-request gate present (REQUIRED_ROLE = 'admin',isAuthExcludedexcludes/auth/*+/health, role check at line 264). Auth model claim is accurate: dev pod inherits the gate automatically. - [x] Local
~/westside-adminis 3 commits behindorigin/mainat review time — Constraints section already calls this out (git pull origin main). - [x] Local
~/pal-e-deploymentsis on stale branch75-rename-pal-e-production— Constraints already require FF main first. - [x]
westside-admin-secretsSecret — verified to exist inwestside-adminnamespace. Contains 6 keys:COOKIE_SIGNING_KEY,DATABASE_URL,KEYCLOAK_CLIENT_ID,KEYCLOAK_CLIENT_SECRET,KEYCLOAK_REALM,KEYCLOAK_URL. The issue says DATABASE_URL is "NOT in this Secret" — that is wrong. See Issue 4 below.
Repo Placement
OK — issue correctly filed on
pal-e-deployments. The redirect-URI sibling on pal-e-services is a separate issue (#73), correctly split.Dependencies
- Blocking: pal-e-services#73 — Keycloak client must list
https://westside-admin-dev.tail5b443a.ts.net/auth/callbackas a valid redirect URI. Without it, OIDC callback returns 400. Issue body documents this clearly. Board item A onboard-westside-admin#1142tracks it. - Implicit dependency NOT documented: ArgoCD Application registration. The existing
westside-adminArgoCD Application is sourced atoverlays/westside-admin/prod— NOT the parent directory. Addingoverlays/westside-admin/dev/will be invisible to ArgoCD until a new Application resource (e.g.,westside-admin-dev) is created in the cluster pointing at the dev overlay path. The issue's AC #2 ("After ArgoCD syncs ... pod 1/1 Running") will silently never satisfy because Argo isn't watching the new path.[BODY]Add an explicit Constraints/File-Targets bullet: "Register a new ArgoCD Applicationwestside-admin-devpointing atoverlays/westside-admin/dev(mirror the pattern used forpal-e-dictionary-dev) — OR — explicitly state that this overlay is applied viakubectl apply -kby Lucas and lives outside ArgoCD." Without that decision, the dev agent has no path to satisfy AC #2.
Acceptance Criteria
Seven AC, all observable. Mostly testable. Two concerns:
- AC #2 is not satisfiable as written until the ArgoCD-vs-manual-apply question is resolved (see Dependencies).
- AC #6 (
nslookup westside-admin-dev.tail5b443a.ts.netfrom outside the tailnet does not resolve to a public IP) is testable but flaky — Tailscale magicdns sometimes returns nothing, sometimes returns the tailnet IP, depending on resolver. A more robust assertion: "from a non-tailnet host, an HTTPS request to the hostname connection-refuses or DNS-NXDOMAINs; from a tailnet host, the TLS cert is issued and the SvelteKit dev server responds." Optional polish, not blocking.
Blast Radius
Five hard blockers found. Listing in priority order.
- [BLOCKER 1 — Port 5177 is taken] The issue claims "Next free: 5177" based on observing dictionary=5175, kanban=5176. That observation is stale. Live cluster check at review time:
pal-e-hub/pal-e-hub-devstood up 6 min ago withcontainerPort=5177. Other in-cluster ports in the 5170–5200 range:mcd-tracker-dev=5173,westside-dev=5174,pal-e-dictionary-dev=5175,pal-e-kanban-dev=5176,pal-e-hub-dev=5177,playme2k-dev=5175(own ns, no conflict). Next actually free: 5178.[BODY]Replace every5177in the issue body (Context, deployment.yaml sketch, service.yaml sketch) with5178. Also update the kustomization.yaml header comment to "Vite port 5178". - [BLOCKER 2 — PSA restricted will reject the dev pod] The
westside-adminnamespace haspod-security.kubernetes.io/enforce: restrictedapplied (verified live + present inoverlays/westside-admin/prod/namespace.yamlon origin/main). The referenceoverlays/pal-e-dictionary/dev/deployment.yamlhas no securityContext — and thepal-e-dictionarynamespace has no PSA enforcement, which is why the reference works there. Mirroring "exactly" intowestside-adminwill fail admission with `seccompProfile not set / runAsNonRoot not set`.[BODY]Add to deployment.yaml sketch: pod-levelsecurityContextwithrunAsNonRoot: true,runAsUser: 1000,runAsGroup: 1000,fsGroup: 1000,seccompProfile.type: RuntimeDefault; and container-levelsecurityContextwithallowPrivilegeEscalation: false,readOnlyRootFilesystem: false(Vite needs writes),capabilities.drop: ["ALL"]. Also note:node:22default user is root — image will need an explicit non-root user. Two viable options: (a) setimage: node:22-bookworm-slimand pre-create a writable workdir under/tmp/app, or (b) use the existing precedent: prod'sdeployment-patch.yamlsetsrunAsUser: 1000on the standard base — replicate that. The simpler path (b): copy the prod patch'ssecurityContextstanza verbatim into the new dev deployment. - [BLOCKER 3 — Reference overlay contradicts "tailnet-only" requirement] Constraints say "Mirror
overlays/pal-e-dictionary/dev/structure exactly. Same filenames, same yaml shape, same idioms." The referenceingress.yamlhastailscale.com/funnel: "true". The Context section says "Do not addtailscale.com/funnel: \"true\"". These two instructions directly conflict — a literal mirror produces a public funnel, which the issue explicitly forbids perfeedback_funnel_requires_auth.[BODY]Reword the Constraint to "Mirroroverlays/pal-e-dictionary/dev/structure (filenames + general yaml shape), with two intentional deviations: (a) port 5178 not 5175; (b)ingress.yamlomitstailscale.com/funnel: \"true\"— tailnet-only ingress only." Also note the prod ingress IS funneled — the dev pod is the deviation, and the reasoning (admin-grade data) is sound. - [BLOCKER 4 — DATABASE_URL claim is wrong] Issue says: "
DATABASE_URLis NOT needed for dev — it lives in a separate Secret (admin-app-db-url) used by prod only." Live check:westside-admin-secretscontains 6 keys includingDATABASE_URL. Both Secrets contain it (the prod patch sources DATABASE_URL fromadmin-app-db-url, but the legacy mirrored copy inwestside-admin-secretsstill exists). Practical effect ofenvFrom: [secretRef: westside-admin-secrets]: the dev pod WILL receive a realDATABASE_URL. That is probably fine for Marcus's read paths — but the issue's claim is factually wrong, and a future reader will be misled.[BODY]Reword to: "DATABASE_URLis present inwestside-admin-secrets(legacy mirror). The dev pod will inherit it viaenvFrom; this is acceptable for Marcus's read-only views but the dev pod must NOT be used to exercise write paths until a separate dev DB role is wired (out of scope for this ticket — file follow-up if write testing is needed)." - [BLOCKER 5 — Selector collision risk] Issue says "deployment named distinctly from prod (suggest
westside-admin-dev)". Good. But the prod deployment selector isapp: westside-admin(verified live + in prod kustomization patch). The dev deployment must use a distinct label key/value across all three of:spec.selector.matchLabels,spec.template.metadata.labels, and the Service'sspec.selector. The referencepal-e-dictionaryoverlay usesapp: pal-e-dictionary-dev(distinct from a non-existent prod) — the dev agent could accidentally just useapp: westside-adminif they copy too literally.[BODY]Add an explicit Constraint: "All three label fields (deployment selector, deployment pod template, service selector) must beapp: westside-admin-dev— NOTapp: westside-admin— to avoid selector collision with the prod deployment in the same namespace. Verify withkubectl -n westside-admin get pods -l app=westside-admin-devreturning only the dev pod andkubectl -n westside-admin get pods -l app=westside-adminreturning only the prod pod after kubectl apply." Without this, an over-broad selector returns both pods and the dev Service load-balances 50% of dev traffic to prod (or vice versa).
Decomposition Assessment
Five files in one repo, one agent, ~5 ACs to verify post-merge. Estimated dev-agent time: 25–40 min including kustomize lint + PR + describing apply path. Below the 5-minute rule's per-AC ceiling but well within a single ticket. No decomposition needed. Keep as one ticket.
Recommendation
Verdict NEEDS_REFINEMENT. Before advancing to
todo, apply these to the Forgejo issue body (the single source of truth dev agents read):[BODY]Replace5177with5178everywhere in the issue (Context port table, deployment containerPort, service targetPort, kustomization comment).[BODY]Add securityContext requirement (pod + container level) per Blocker 2; reference prod'sdeployment-patch.yamlsecurityContext as the template to copy.[BODY]Reword the "mirror exactly" Constraint to call out the two intentional deviations (port + no funnel annotation).[BODY]Fix the DATABASE_URL Context paragraph to reflect that the Secret DOES contain it; note dev is read-only-acceptable but flag for follow-up if write testing becomes needed.[BODY]Add explicit Constraint requiringapp: westside-admin-devacross deployment selector, pod template labels, and service selector to avoid selector collision with prod in the same namespace.[BODY]Resolve the ArgoCD-vs-manual-apply question: either add a Constraint/File-Target requiring a new ArgoCDwestside-admin-devApplication resource pointing at the new path, OR explicitly state the dev overlay is hand-applied withkubectl apply -kand lives outside ArgoCD. Reword AC #2 accordingly.[SCOPE]Addmarcus-admin-dev-accessrow toproject-westside-admin#user-storiestable. (Dottie can do this on Ava's instruction once Ava confirms wording.)[SCOPE]Decide whetherarch:dev-overlaydeserves a standalonearch-dev-overlaynote or appends a Dev section toarch-deployment-westside-admin.
Once the six
[BODY] items land in the issue, the ticket is ready for re-review and APPROVED → todo. The two [SCOPE] items are doc-side and don't block dev work but should be tracked so traceability is complete by the time the PR merges.