pal-enterprises

pal-enterprises forgejo

Notes

Architecture 2
  • Architecture: Rails App (Shared Pattern)

    Common deployment architecture for Rails applications on the pal-e platform. Currently implemented by landscaping-assistant (production) and palinks (in development). New Rails apps SHOULD follow this pattern unless there is a documented reason to diverge.

    Diagram

    graph TB
        subgraph "Developer Machine"
            DEV[Local dev serverbin/dev]
        end
    
        subgraph "Forgejo"
            REPO[App repoldraney/app-name]
            WP[Woodpecker CI]
        end
    
        subgraph "Harbor Registry"
            IMG[harbor.tail5b443a.ts.netproject/app-name:sha]
        end
    
        subgraph "k3s Cluster -- Home Lab"
            ARGO[ArgoCD]
            subgraph "App Namespace"
                POD[Rails Pod]
                SVC[ClusterIP Service]
                MON[ServiceMonitor]
            end
            subgraph "Shared Services"
                CNPG[CNPG PostgreSQL 17]
                KC[Keycloak OIDC]
                MINIO[MinIO S3]
                PROM[Prometheus]
                GRAF[Grafana]
                LOKI[Loki + Promtail]
            end
        end
    
        subgraph "Hetzner Edge"
            CADDY[Caddy reverse proxy178.156.129.142]
        end
    
        subgraph "GoDaddy"
            DNS[A record -> edge IP]
        end
    
        subgraph "Internet"
            USER[End user]
        end
    
        DEV -->|git push| REPO
        REPO -->|webhook| WP
        WP -->|test + build + push| IMG
        IMG -->|Image Updater| ARGO
        ARGO -->|kustomize apply| POD
        POD --> CNPG
        POD -.->|where integrated| KC
        POD -.->|where needed| MINIO
        MON --> PROM
        PROM --> GRAF
        POD -->|lograge JSON| LOKI
        SVC -->|Tailscale mesh| CADDY
        DNS --> CADDY
        USER -->|HTTPS| CADDY
    

    Components

    Component Purpose Notes
    Rails Pod Application server Rails 8.1, Hotwire (Turbo + Stimulus). Monolithic -- no separate frontend, Rails serves HTML directly. Importmap for JS, no build pipeline.
    CNPG PostgreSQL 17 Primary database CloudNativePG operator on k3s. Shared cluster, per-app databases. Handles failover, backups, WAL archiving.
    Keycloak OIDC Authentication (opt-in) Authorization Code + PKCE via OmniAuth. OAuth 2.1 compliant. NOT all apps have this -- landscaping-assistant: yes, palinks: planned.
    MinIO S3 Object storage (opt-in) ActiveStorage with S3-compatible backend. Local disk in dev. Only for apps with file/image uploads -- landscaping-assistant: yes, palinks: no.
    Woodpecker CI CI pipeline Triggered by Forgejo webhook. Stages: test (rspec + postgres service), build (multi-stage Docker), push to Harbor.
    Harbor Registry Container image store harbor.tail5b443a.ts.net. Images tagged with full commit SHA. Trivy vulnerability scanning.
    ArgoCD + Image Updater Continuous deployment Image Updater detects new SHA tags, writes to kustomize overlay in pal-e-deployments. ArgoCD syncs to cluster.
    ServiceMonitor Metrics scraping Prometheus scrapes /metrics endpoint exposed by yabeda-rails + yabeda-prometheus.
    Grafana + Loki Observability Grafana dashboards for metrics. Loki + Promtail for log aggregation. Structured JSON logs via lograge.
    Caddy (Hetzner edge) Public reverse proxy + TLS 178.156.129.142, CPX11 Ashburn. Auto-TLS for custom domains. Connected to home lab via Tailscale mesh (tail5b443a.ts.net).
    GoDaddy DNS Domain management A records pointing custom domains to Hetzner edge IP. Managed by godaddy-tofu provider via pal-e-platform/terraform/.
    Kustomize overlays Deployment manifests Live in pal-e-deployments repo. Follow convention-kustomize-overlay pattern. Image tags updated by ArgoCD Image Updater.

    Deployment Pipeline

    1. Push to Forgejo -- triggers Woodpecker CI webhook
    2. CI: Test -- bundle exec rspec against PostgreSQL service container (version 17, matching prod)
    3. CI: Build -- multi-stage Docker build
    4. CI: Push -- push image to Harbor (harbor.tail5b443a.ts.net/project/app-name:<commit-sha>)
    5. ArgoCD Image Updater -- detects new tag (regexp ^[0-9a-f]{7,40}$), writes SHA to kustomize overlay
    6. ArgoCD Sync -- applies updated kustomization to k3s cluster
    7. Auto-migration -- rails db:migrate runs on deploy

    Public Domain Routing

    graph LR
        GD[GoDaddy DNSA record] -->|domain.app -> 178.156.129.142| HZ[Hetzner EdgeCaddy + auto-TLS]
        HZ -->|Tailscale mesh| K3S[k3s ServiceClusterIP]
        K3S --> POD[Rails Pod]
    

    Three steps to a public domain: (1) godaddy-tofu provider creates A record pointing to 178.156.129.142, (2) Caddy on Hetzner edge terminates TLS and reverse-proxies via Tailscale, (3) k3s ClusterIP service routes to Rails pod.

    Variations by App

    Concern landscaping-assistant palinks
    Auth Keycloak (Auth Code + PKCE, OmniAuth). Five-role model. Session-based. Planned, not yet integrated.
    Object storage ActiveStorage + MinIO for photo uploads. Not needed (link app, no file uploads).
    Mobile Turbo Native iOS build. Dev funnel at landscaping-dev.tail5b443a.ts.net. Web-only.
    Observability depth Full Grafana ecosystem: 26 dashboards, golden signals, blackbox probes, DORA metrics. ServiceMonitor deployed, metrics parity achieved.
    Domain-specific services Nominatim geocoding. None.
    Test suite 230+ RSpec specs (model, request, system). Capybara + Cuprite. In development.
    Custom domain landscaping-assistant.app (when configured). palinks.app (DNS pending godaddy-tofu integration).

    Key Decisions

    • Monolithic Rails over microservices -- single deployable unit per app. No API gateway complexity. Frontend and backend in one repo, one process.
    • Hotwire over SPA -- server-rendered HTML with progressive enhancement via Turbo Frames/Streams. No JS build pipeline (importmap). Turbo Native wraps the same app for mobile.
    • CNPG shared cluster -- one CloudNativePG operator instance, separate databases per app. Operator manages failover, backups, WAL archiving. CI postgres version pinned to match prod (17).
    • Keycloak opt-in, not mandatory -- apps that need auth use Authorization Code + PKCE (OAuth 2.1 compliant). ROPC/Direct Access Grants rejected as deprecated (RFC 9700). Apps without auth needs skip Keycloak entirely.
    • MinIO opt-in -- only apps with file/image storage use ActiveStorage + MinIO. S3-compatible API provides portability. Dev uses local disk.
    • Hetzner edge for public ingress -- Caddy handles TLS termination on a public IP. Tailscale mesh avoids exposing the home lab directly. Single edge node at 178.156.129.142 serves all custom domains.
    • Commit-SHA image tags -- immutable, traceable. ArgoCD Image Updater automates tag detection and kustomize overlay updates.
    • RSpec + Capybara + Cuprite for testing -- system specs run headless Chromium via CDP. No Selenium dependency.
    • Structured logging via lograge -- JSON output collected by Promtail into Loki. No unstructured Rails default logs in production.
    • project-palinks -- palinks project page
    • project-landscaping-assistant -- landscaping-assistant project page
    • arch-godaddy-tofu -- DNS provider architecture (manages A records for custom domains)
    • convention-kustomize-overlay -- deployment overlay pattern used by pal-e-deployments
    • convention-pipeline-stages -- CI pipeline stage convention
    • convention-frontend-css -- CSS conventions for frontend work
  • Architecture: pal-enterprises Rails App arch-rails-app-pal-enterprises

    Architecture: pal-enterprises Rails App

    Single Rails 8.1 app serving the platform front door.

    Components

    • PagesController -- public landing page at /
    • ContactsController -- stateless contact form, delivers via Action Mailer
    • SessionsController -- Keycloak OIDC login/logout/callback
    • DashboardController -- authenticated tool grid, session-gated

    Auth Flow

    sequenceDiagram
        participant V as Visitor
        participant R as Rails App
        participant K as Keycloak (pal-e realm)
        
        V->>R: GET /
        R-->>V: Landing page (public)
        V->>R: Click "Sign In"
        R->>K: Redirect to /auth (OIDC)
        K-->>V: Login form
        V->>K: Credentials
        K->>R: Callback with auth code
        R->>K: Exchange code for tokens
        K-->>R: ID token + access token
        R-->>V: Redirect to /dashboard (session set)
    

    Deployment

    graph TB
        TS[Tailscale Ingress] --> SVC[k8s Service :3000]
        SVC --> POD[Rails Pod - ruby:3.4-slim]
        POD --> |hostPath| SRC[~/pal-enterprises]
        POD --> |PVC| GEMS[gem-cache]
        POD --> |tcp:5432| PG[CNPG pal-e-postgres-rw]
        POD --> |https| KC[Keycloak]
    

    Key Decisions

    • No local user table -- Keycloak sub/name/email stored in session only
    • Contact form is stateless -- email delivery, no DB persistence
    • omniauth-openid-connect gem for OIDC
    • Tailwind CSS, Hotwire (Turbo + Stimulus)
    • project-pal-enterprises -- project page
    • sop-keycloak-client-creation -- SOP for registering the OIDC client
    • arch-deployment-pal-enterprises -- deployment architecture (TBD)
Doc 11
  • Validation: T5 — Woodpecker CI with test gates

    Issue: ldraney/pal-enterprises#19

    Date: 2026-05-10

    Verdict: PASS

    Evidence

    • Pipeline #26 (main branch): all 4 steps green — clone, test, build-and-push, update-kustomize-tag
    • Test gates validated: bundle-audit (0 CVEs), brakeman (0 warnings), rubocop (0 offenses)
    • Docker image pushed to harbor.tail5b443a.ts.net/pal-enterprises/app:d8900c1e
    • Kustomize tag updated in pal-e-deployments, ArgoCD picks up new image

    Issues resolved during validation

    • Woodpecker agents cannot pull from Harbor cluster-internal URL — switched to external Tailscale URL
    • Harbor pal-e project made public for unauthenticated base image pulls
    • harbor-portal-proxy missing client_max_body_size (413 on push) — fixed ConfigMap
    • omniauth 1.9.2 → 2.1.4 (CVE-2015-9284)
    • Kaniko layer caching added for faster subsequent builds

    PRs merged

    • #22: Add Woodpecker CI pipeline with test gates
    • #24: Fix CI pipeline: use Docker Hub image + fix security audit
    • #25: Fix kaniko insecure pull (interim, superseded by #26)
    • #26: Switch kaniko to external Harbor URL
    • #27: Add kaniko layer caching
  • Ticket

    pal-enterprises#18 — Migrate Dockerfile from Debian ruby:slim to Arch Linux base image. PRs: #20 (migration), #21 (tag fix).

    Environment

    Local Docker build with external Harbor URL substitution. Image: pal-enterprises:arch-test.

    Checks

    # Criterion How to Verify Result Evidence
    1 Dockerfile builds successfully docker build with Arch base PASS Build completed, all stages successful including asset precompilation
    2 Ruby version docker run ... ruby --version PASS ruby 3.4.8 (2025-12-17) +PRISM [x86_64-linux]
    3 Rails boots rails runner "puts 'ok'" PASS Rails runner works (no DB needed for boot test)
    4 jemalloc preloaded Check LD_PRELOAD and lib presence PASS LD_PRELOAD=/usr/lib/libjemalloc.so, file present
    5 Non-root user whoami in container PASS USER=rails
    6 No Debian artifacts which apt-get PASS apt-get not found
    7 .ruby-version removed File check PASS Deleted in PR #20
    8 k8s/dev.yaml removed File check PASS Deleted in PR #20

    Verdict

    PASS — Dockerfile builds on Arch base, Rails boots, jemalloc loaded, non-root user, no Debian remnants. Full /up health check deferred to post-deploy (requires Postgres).

    Discovered Issues

    • Missing :build tag: The ruby-arch pipeline only pushes :latest (which is the build stage). The Dockerfile originally referenced :build which doesn't exist. Fixed in PR #21 by using :latest for both stages. Follow-up: update ruby-arch pipeline to push separate :base and :build tags via Kaniko --target.
  • Ticket

    pal-e-platform#363 — Fix gem bin PATH in ruby-arch base image.

    Environment

    Rebuilt image via Woodpecker pipeline 554 (manual trigger). Image: harbor.tail5b443a.ts.net/pal-e/ruby-arch:latest.

    Checks

    # Criterion How to Verify Result Evidence
    1 bundler in PATH docker run ... bundler --version PASS 4.0.11, located at /usr/sbin/bundler
    2 bundle in PATH docker run ... bundle --version PASS 4.0.11, located at /usr/sbin/bundle

    Verdict

    PASS

  • Validation: T3 Arch Linux Ruby Base Image validation-360-2026-05-10

    Ticket

    pal-e-platform#360 — Create shared Arch Linux Ruby base image in Harbor with weekly rebuild pipeline.

    Environment

    Harbor project pal-e (id=42). Woodpecker pipeline 547 (manual trigger on main). Image: harbor.tail5b443a.ts.net/pal-e/ruby-arch:latest.

    Post-merge infra steps performed

    • Created pal-e Harbor project (HTTP 201)
    • Created Harbor robot robot$pal-e+pal-e-ci with push/pull permissions
    • Added harbor_username and harbor_password repo secrets (manual+cron events)
    • Moved pipelines to .woodpecker/ for multi-pipeline discovery (PR #362, merged)
    • Updated all 25 repo secrets to allow manual+cron events (Woodpecker validates secrets across all discovered pipelines)
    • Created Woodpecker cron ruby-arch-weekly-rebuild (Sunday 6am UTC)

    Checks

    # Criterion How to Verify Result Evidence
    1 Harbor project exists Harbor API query PASS pal-e project created, id=42
    2 Pipeline builds and pushes image Woodpecker pipeline 547 manual trigger PASS All steps success: clone, build-and-push
    3 Image exists in Harbor Harbor API: /projects/pal-e/repositories PASS pal-e/ruby-arch:latest, 366MB, 1 artifact
    4 Ruby works docker run ... ruby --version PASS ruby 3.4.8 (2025-12-17)
    5 Bundler installed docker run ... gem list bundler PASS bundler 4.0.11 installed; executable at /root/.local/share/gem/ruby/3.4.0/bin/bundler
    6 jemalloc preloaded docker run ... ruby -e "puts :ok" (LD_PRELOAD set in image ENV) PASS Runs without error, jemalloc loaded
    7 Weekly cron configured Woodpecker cron list PASS ruby-arch-weekly-rebuild, schedule 0 6 * * 0, branch main

    Verdict

    PASS — image builds, pushes, and runs correctly. All components verified: Ruby 3.4.8, Bundler 4.0.11, jemalloc preload, weekly cron.

    Discovered Issues

    • Gem bin not in PATH: Arch's Ruby puts gem install binaries in /root/.local/share/gem/ruby/3.4.0/bin/ rather than /usr/bin/. The bundler command isn't in PATH by default. Fix: add ENV PATH="/root/.local/share/gem/ruby/3.4.0/bin:${PATH}" to the Dockerfile, or use gem install --no-user-install bundler. Minor — downstream Dockerfiles can work around it.
    • Multi-pipeline secret validation: Woodpecker validates secrets from ALL discovered .woodpecker/*.yaml files against the event type, even for pipelines whose when conditions exclude that event. Required updating all 25 existing secrets to allow manual+cron events. This is a Woodpecker quirk worth documenting.
  • Ticket

    pal-e-services#75 — Add dev redirect URI to Keycloak pal-enterprises client and create ArgoCD Application resource.

    Environment

    Prod cluster via tofu apply -var-file=k3s.tfvars in ~/pal-e-services/terraform. Keycloak realm: pal-enterprises. ArgoCD namespace: argocd.

    Checks

    # Criterion How to Verify Result Evidence
    1 Keycloak client has dev redirect URI Keycloak Admin API query for pal-enterprises client redirectUris PASS Both URIs present: pal-enterprises.tail5b443a.ts.net/auth/keycloak/callback and pal-enterprises-dev.tail5b443a.ts.net/auth/keycloak/callback
    2 Keycloak client has dev web origin Keycloak Admin API query for webOrigins PASS Both origins present: prod and pal-enterprises-dev.tail5b443a.ts.net
    3 ArgoCD Application created and synced kubectl get application -n argocd pal-enterprises PASS Status: Synced/Progressing (Progressing expected — no image built yet)
    4 Prod app unaffected curl -sf https://pal-enterprises.tail5b443a.ts.net/up PASS Returns 200 with green health check page. Existing pod still Running.
    5 tofu plan shows no drift tofu plan -var-file=k3s.tfvars after apply PASS Apply completed: 1 added, 14 changed, 0 destroyed. All T1-scoped resources reconciled.

    Verdict

    PASS — all checks green. Keycloak client correctly configured with both prod and dev redirect URIs. ArgoCD app created and synced. Prod unaffected. ImagePullBackOff on new pod is expected — no CI-built image exists yet (separate ticket scope).

    Discovered Issues

    None. The 13 harbor-creds label drift changes were pre-existing ArgoCD label reconciliation, not introduced by T1.

  • Architecture: Harbor arch-harbor

    Architecture: Harbor

    Harbor is the private container registry for the pal-e platform, deployed via Helm in pal-e-platform/terraform/modules/harbor/.

    Image Strategy

    Shared Arch Linux base images in the pal-e Harbor project, consumed by per-app image builds:

    • pal-e/ruby-arch:latest — Arch + Ruby + bundler + jemalloc + postgresql-libs (weekly rebuild)
    • pal-enterprises/app:{SHA} — app image, FROM ruby-arch base

    Registry URLs

    • Internal (CI): harbor.harbor.svc.cluster.local
    • External: harbor.tail5b443a.ts.net

    Weekly Base Image Rebuild

    A Woodpecker cron pipeline in pal-e-platform rebuilds the Arch base image weekly. Arch's rolling release means each rebuild picks up the latest Ruby, system libs, and security patches automatically — eliminating manual version bump tickets.

  • Architecture: Keycloak arch-keycloak

    Architecture: Keycloak

    Keycloak provides OIDC authentication for pal-enterprises. Single realm (pal-enterprises), single client (pal-enterprises), non-public with PKCE.

    Client Pattern

    Single client with multiple redirect URIs for dev/prod separation (same pattern as westside-admin, playme2k):

    • Prod: https://pal-enterprises.tail5b443a.ts.net/auth/keycloak/callback
    • Dev: https://pal-enterprises-dev.tail5b443a.ts.net/auth/keycloak/callback

    Config Location

    • Client definition: pal-e-services/terraform/k3s.tfvars (lines 238-257)
    • App config: pal-enterprises/config/initializers/omniauth.rb
    • Managed via Terraform (IaC), not Keycloak admin console

    Future: Client Board Access

    Per arch-multi-tenant, client users will get a project_slug user attribute mapped via OIDC protocol mapper. This enables scoped board access through the pal-enterprises proxy pattern.

  • Story: Infrastructure Overhaul story-infra-overhaul

    Story: Infrastructure Overhaul

    Migrate pal-enterprises from Debian-based Ruby images to Arch Linux rolling-release base images, establish proper dev/prod deployment separation, and add CI quality gates.

    Motivation

    Every Ruby version bump is a manual ticket. Dev environment points to prod URL with hardcoded secrets. No CI test gates. ArgoCD app not wired up. This story fixes the entire pipeline from image → build → deploy → dev.

    Tickets

    Wave Ticket Repo Pts Issue
    1 T1: Keycloak dev redirect + ArgoCD pal-e-services 3 #75
    1 T3: Arch Ruby base image in Harbor pal-e-platform 5 #360
    2 T2: Dev overlay + local mount pal-e-deployments 3 #158
    2 T4: Dockerfile migration to Arch pal-enterprises 3 #18
    3 T5: CI pipeline with test gates pal-enterprises 3 #19

    Outcome

    • Weekly Arch base image rebuild → no manual Ruby/gem version tickets
    • Prod via ArgoCD, dev via local mount at pal-enterprises-dev.tail5b443a.ts.net
    • CI runs brakeman + rubocop + bundle-audit on every PR
    • Keycloak accepts both prod and dev OAuth callbacks

    Tickets Killed

    • #14 (Ruby 4.0 upgrade) — unnecessary, Arch rolling release handles this
    • Future Ruby/gem version bump tickets — eliminated as a class of work
  • Ticket

    ldraney/pal-e-deployments#156 — Kustomize overlay: overlays/pal-enterprises/prod/

    Shipped: PR #157 merged (f980c45).

    Environment

    Prod cluster. Namespace: pal-enterprises.

    Checks

    # Criterion How to Verify Result Evidence
    1 Overlay directory exists ls overlays/pal-enterprises/prod/ PASS Contains kustomization.yaml + deployment-patch.yaml
    2 Image set correctly Inspect kustomization.yaml PASS harbor.tail5b443a.ts.net/pal-enterprises/app:latest
    3 Port consistency Check containerPort, service targetPort, probes PASS All port 3000
    4 Secrets by reference Grep for inline values PASS All via pal-enterprises-secrets secretRef
    5 App running kubectl get pods -n pal-enterprises PASS 1/1 Ready, deployment Available
    6 Health check /up endpoint PASS Returns 200

    Discovered Issues

    Minor: in-cluster service-to-service calls via pal-enterprises:3000 return 403 due to Rails Host Authorization. Not blocking — only affects hypothetical internal service consumers. Fix: config.hosts << "pal-enterprises" if needed later.

    Verdict

    PASS — all checks green. Ready for done.

  • Ticket

    ldraney/pal-e-platform#357 — NetworkPolicy: allow pal-enterprises → Postgres + Keycloak

    Shipped: PR #359 merged (b2aea40), tofu apply completed.

    Environment

    Prod cluster. Namespaces: pal-enterprises, postgres, keycloak.

    Checks

    # Criterion How to Verify Result Evidence
    1 NetworkPolicy allows pal-enterprises → Postgres kubectl get networkpolicy -n postgres -o yaml PASS pal-enterprises namespace in ingress allowlist (line 152 of network-policies.tf)
    2 NetworkPolicy allows pal-enterprises → Keycloak kubectl get networkpolicy -n keycloak -o yaml PASS pal-enterprises namespace in ingress allowlist (line 176 of network-policies.tf)
    3 DB connectivity works rails runner 'puts ActiveRecord::Base.connection.execute("SELECT 1").to_a' PASS Returned {"test" => 1}
    4 No connection errors in logs kubectl logs -n pal-enterprises deployment/pal-enterprises grep for error/fail/refuse PASS Zero matches

    Verdict

    PASS — all checks green. Ready for done.

  • Architecture: Multi-Tenant Client Board Access

    Decision

    Option B — pal-enterprises proxy pattern. Clients access their project board through pal-enterprises, which fetches data from pal-e-docs internally and renders a read-only view. pal-e-docs remains an internal ops tool with no client-facing auth.

    Decided: 2026-05-10. Spike: ldraney/pal-enterprises#13

    Options Evaluated

    Option A — pal-e-docs native scoping (rejected): Would require adding multi-tenant RBAC to pal-e-docs. The API has zero auth — list_boards() returns all boards, list_board_items() has no scoping. Changes the fundamental nature of pal-e-docs from internal ops tool to client-facing platform. High effort, high risk, wrong separation of concerns.

    Option B — pal-enterprises proxy (selected): pal-enterprises already owns client auth (Keycloak OIDC). A new controller fetches board data via pal-e-docs API (internal, service-to-service), renders read-only view filtered to client's project. ~100 lines of Rails code, no changes to pal-e-docs. Lucas controls what clients see — can filter labels, hide ops-only items.

    Option C — Hybrid (not needed): No advantage over pure proxy since pal-e-docs doesn't need client auth for any other reason.

    Keycloak Mapping Strategy

    • Add project_slug user attribute to client users in Keycloak (e.g. project_slug: "westside")
    • Configure a protocol mapper on the pal-enterprises OIDC client to include project_slug in token claims
    • pal-enterprises reads the claim, fetches list_board_items(board_slug: "board-{project_slug}")
    • Same attribute can gate Forgejo repo access later via Keycloak group/role mapping

    Implementation Shape

    1. Keycloak: Add project_slug user attribute + OIDC protocol mapper
    2. pal-enterprises: ClientBoardController — reads claim, calls pal-e-docs API, renders read-only view
    3. pal-enterprises: Board view template using existing CSS design system (columns, cards, status indicators)
    4. No write endpoints — pure display, no forms, no mutations

    Follow-up ticket: ldraney/pal-enterprises#17

    Read-Only Enforcement

    • pal-enterprises only exposes GET routes for the board view
    • pal-e-docs API calls are server-side only — client browser never contacts pal-e-docs
    • No client-side JS that could be exploited to make write calls
    • ldraney/pal-enterprises#13 — spike that produced this decision
    • ldraney/pal-enterprises#17 — follow-up feature ticket
    • ldraney/pal-enterprises#9 — owner dashboard (Lucas sees all clients)
Review 14
  • Verdict: READY

    Template Completeness

    • [x] Type -- present (Infra)
    • [x] Lineage -- present (Plan: infra overhaul, Ticket 3 of 5)
    • [x] Repo -- present (ldraney/pal-e-platform)
    • [x] User Story -- present
    • [x] Context -- present, thorough
    • [x] File Targets -- present (2 creates, 1 conditional modify)
    • [x] Dockerfile Shape -- present with verified package names
    • [x] Pipeline Shape -- present
    • [x] Acceptance Criteria -- present (11 items)
    • [x] Test Expectations -- present (4 items)
    • [x] Dependencies -- present (added since r1: T2 and T4 as downstream dependents)
    • [x] Constraints -- present
    • [x] Checklist -- present (added since r1: PR opened, Dockerfile builds, image pushed, cron configured, no unrelated changes)
    • [x] Related -- present with cross-repo issue references

    All template sections are complete. Every item flagged as missing in review-1201-2026-05-10 has been addressed.

    Traceability

    • [x] story:infra-overhaul label -- present on board item
    • [x] story note verified -- story-infra-overhaul note exists in pal-e-docs (created since r1). Has Motivation, Tickets, Outcome, and Tickets Killed sections.
    • [x] arch:harbor label -- present on board item
    • [x] arch note verified -- arch-harbor note exists in pal-e-docs (created since r1). Has Image Strategy, Registry URLs, and Weekly Base Image Rebuild sections.
    • [x] Forgejo issue -- ldraney/pal-e-platform#360, state: open

    Minor gap (non-blocking): The project-pal-enterprises user-stories table does not yet have an infra-overhaul row. Current entries: landing-page, sso-gateway, tool-dashboard, client-portal. The story note exists as a standalone note, which is sufficient for traceability. Adding the row to the project page is a housekeeping task, not a scope blocker.

    File Targets

    • [x] docker/ruby-arch/Dockerfile -- to be CREATED. Directory does not exist yet (expected). Dockerfile shape provided inline with verified Arch package names (ruby, jemalloc, postgresql-libs, libyaml, base-devel, git, pkgconf). Package corrections documented: ruby-bundler replaced with gem install, libpq removed, pkg-config corrected to pkgconf.
    • [x] docker/ruby-arch/.woodpecker.yaml -- to be CREATED. Pipeline shape described inline. Note: Woodpecker does not auto-discover yaml files in subdirectories. The existing pal-e-platform uses a single root .woodpecker.yaml. The agent will need to either (a) place this as a separate pipeline config in a .woodpecker/ directory at repo root, or (b) add steps to the root pipeline with path-based triggers. The issue body describes the intent clearly enough for an agent to resolve this at implementation time.
    • [x] terraform/modules/harbor/main.tf -- VERIFIED EXISTS (575 lines). Contains Harbor Helm release, OIDC config, portal CSS proxy. No Harbor project resource exists yet. The issue correctly identifies this as a conditional modify to add the pal-e Harbor project.

    Repo Placement

    OK. Issue filed on ldraney/pal-e-platform. All file targets (docker/, terraform/) are pal-e-platform paths. Board item is on board-pal-enterprises which is correct since this infra supports the pal-enterprises app. No cross-repo mismatch.

    Dependencies

    Dependencies now documented in the issue body (fixed since r1):

    • Upstream: None -- T3 can start immediately. Wave 1 parallel with T1.
    • Downstream: T2 (pal-e-deployments#158, dev overlay) and T4 (pal-enterprises#18, Dockerfile migration) both depend on this base image existing in Harbor.

    Board state confirms: T1 (#1200), T2 (#1197), T3 (#1201), T4 (#1198), T5 (#1199) are all in backlog. No blocking items in in_progress.

    Acceptance Criteria

    11 ACs are specific and testable. Key improvements since r1:

    • Harbor project creation AC added (was missing)
    • Kaniko + pacman compatibility AC added (was missing)
    • Manual pipeline trigger AC added

    All criteria are verifiable by an agent post-implementation. Test commands are concrete (docker run, gem install pg, LD_PRELOAD verification).

    Blast Radius

    Low. Creates new files only. The existing pal-enterprises Dockerfile (ruby:3.4.8-slim Debian) is untouched. No existing pipelines are modified. Future consumers (pal-enterprises, westside-ror, pal-e-ror, westside-docs) will be migrated by T4 separately. The existing pal-enterprises .woodpecker.yaml uses Kaniko (plugin-kaniko:2.3.0) for app builds -- this pipeline would add a new, separate build pipeline for the base image.

    Decomposition Assessment

    3 file targets in 1 repo. 11 ACs + 4 test expectations exceeds the >5 AC threshold numerically, but the work is cohesive: write one Dockerfile, write one pipeline config, optionally add one Terraform resource. All three files serve a single purpose (build and push a base image). Estimated agent work is within 5 minutes -- Dockerfile and pipeline shapes are provided inline. No decomposition needed.

    Refinement Fixes Verified (from r1)

    • [x] Dockerfile package names corrected (ruby-bundler to gem install, libpq removed, pkg-config to pkgconf)
    • [x] Dependencies section added (T2 and T4 as downstream dependents)
    • [x] Missing ACs added (Harbor project creation, Kaniko+pacman compatibility)
    • [x] story-infra-overhaul note created
    • [x] arch-harbor note created
    • [x] Checklist section added

    Recommendations

    No action needed. Ticket is ready for implementation.

    Housekeeping (non-blocking): Add infra-overhaul row to project-pal-enterprises user-stories table for completeness.

  • Verdict: APPROVED

    Re-review after refinement. All four NEEDS_REFINEMENT items from review-1200-2026-05-10 have been addressed. One minor project-page housekeeping item remains but does not block the ticket.

    Template Completeness

    • [x] Type -- "Infra" (maps to Task base template)
    • [x] Lineage -- "Plan: pal-enterprises infrastructure overhaul (Ticket 1 of 5). Story: story-infra-overhaul."
    • [x] Repo -- ldraney/pal-e-services
    • [x] User Story -- developer dev-environment auth flow
    • [x] Context -- clear motivation, references arch-keycloak note
    • [x] File Targets -- terraform/k3s.tfvars with line references
    • [x] Acceptance Criteria -- 5 items, all concrete
    • [x] Test Expectations -- 2 items, verifiable
    • [x] Constraints -- 3 items, clear
    • [x] Checklist -- FIXED: now present with infra-appropriate items (tofu plan reviewed, tofu apply succeeds)
    • [x] Dependencies -- added: "None -- can start immediately. Wave 1 parallel with T3."
    • [x] Related -- FIXED: broken sop-keycloak-client-creation removed, real references added (story-infra-overhaul, arch-keycloak, pal-e-deployments#156, pal-e-platform#360, pal-e-deployments#158)

    Traceability

    • [x] story:infra-overhaul label -- present on board item
    • [x] story note verified -- story-infra-overhaul note exists in pal-e-docs (id: 1727), tags: active, story. Contains ticket table with all 5 tickets, wave assignments, and outcomes.
    • [ ] story entry on project page -- infra-overhaul is NOT listed in project-pal-enterprises user-stories table (only landing-page, sso-gateway, tool-dashboard, client-portal). This is a project-page housekeeping issue, not a ticket blocker. The backing story note exists and is well-structured.
    • [x] arch:keycloak label -- present on board item
    • [x] arch note verified -- arch-keycloak note exists in pal-e-docs (id: 1728), tags: architecture, active. Documents client pattern, config location (k3s.tfvars lines 238-257), and future multi-tenant plans.
    • [x] Forgejo issue -- ldraney/pal-e-services#75, open

    File Targets

    • [x] terraform/k3s.tfvars (lines 238-257) -- verified: pal-enterprises client block exists at exactly lines 238-257. Contains valid_redirect_uris (line 246-248) and web_origins (line 249-251) as described. Currently only has prod URI. File is gitignored (*.tfvars in .gitignore), which is correct for secrets.
    • [x] services block (lines 369-376) -- verified: pal-enterprises ArgoCD service entry already exists with forgejo_repo, image_repo, port 3000, funnel true, source_repo, and source_path pointing to overlays/pal-enterprises/prod. tofu apply will create the ArgoCD Application resource from this existing config.

    Repo Placement

    OK. Issue filed on ldraney/pal-e-services which houses terraform/. Both keycloak client config and ArgoCD app creation live in this repo's terraform state. No cross-repo work needed.

    Dependencies

    • Upstream (done): pal-e-deployments#156 (prod overlay) -- board item #1184, column: done. Prerequisite for ArgoCD app source_path. Confirmed merged.
    • Downstream: T2 (board item #1197, pal-e-deployments#158, dev overlay) -- depends on Keycloak redirect being configured first.
    • Parallel: T3 (board item #1201, pal-e-platform#360, Arch base image) -- Wave 1 parallel, no dependency.
    • Related: Board item #1192 (Keycloak upgrade to 26.x) -- shares arch:keycloak label but independent work in pal-e-platform.
    • Dependencies are now documented in the issue body. Previously undocumented.

    Acceptance Criteria

    5 criteria, all concrete and agent-verifiable:

    • AC1-2: Verifiable by inspecting tfvars after edit
    • AC3: Verifiable by running tofu apply (requires local access)
    • AC4: Verifiable via kubectl command (provided in AC)
    • AC5: Verifiable via Keycloak admin console (requires browser/API access)

    AC3-5 require runtime access, expected for infra tickets. No missing criteria.

    Blast Radius

    • Adding redirect URIs is additive -- no existing URIs modified or removed.
    • ArgoCD app creation is additive -- new application entry, does not modify existing ones.
    • Test expectation #2 constrains: "No other Keycloak clients or services affected."
    • tofu plan step will verify blast radius before apply.
    • Checked all other clients in k3s.tfvars -- westside-admin pattern (multi-URI) is well-established, pal-enterprises follows it.

    Decomposition Assessment

    1 file target, 1 repo, 5 AC, estimated agent work under 5 minutes. No decomposition needed.

    Previous Review Resolution

    Previous Recommendation Status
    [BODY] Add Checklist section FIXED -- Checklist added with infra-appropriate items
    [SCOPE] Create story-infra-overhaul note FIXED -- note exists (id: 1727) with ticket table and outcomes
    [SCOPE] Create arch-keycloak note FIXED -- note exists (id: 1728) with client pattern and config location
    [BODY] Fix broken sop-keycloak-client-creation reference FIXED -- removed, replaced with actual references

    Recommendations

    • [SCOPE] Add infra-overhaul row to project-pal-enterprises user-stories table -- story note exists but is not registered on the project page. Non-blocking; does not affect this ticket's executability.

    No blocking issues. Ticket is ready for execution.

  • Verdict: NEEDS_REFINEMENT

    Template Completeness

    • [x] Type -- present (Infra)
    • [x] Lineage -- present
    • [x] Repo -- present (ldraney/pal-e-platform)
    • [x] User Story -- present
    • [x] Context -- present, thorough
    • [x] File Targets -- present (2 creates, 1 conditional modify)
    • [x] Acceptance Criteria -- present (7 items)
    • [x] Test Expectations -- present (4 items)
    • [x] Constraints -- present
    • [x] Related -- present
    • [x] Dockerfile Shape -- present (bonus: inline code reference)
    • [x] Pipeline Shape -- present (bonus: inline description)
    • [ ] Checklist -- missing (PR opened, tests pass, no unrelated changes)

    Traceability

    • [x] story:infra-overhaul label -- present on board item
    • [ ] story note MISSING -- [SCOPE] The infra-overhaul story is not listed in the project-pal-enterprises user-stories table. Current stories: landing-page, sso-gateway, tool-dashboard, client-portal. Add infra-overhaul row.
    • [x] arch:harbor label -- present on board item
    • [ ] arch note MISSING -- [SCOPE] No arch-harbor note found in pal-e-docs. Create architecture note for the Harbor component.
    • [x] Forgejo issue -- https://forgejo.tail5b443a.ts.net/ldraney/pal-e-platform/issues/360, state: open

    File Targets

    • [x] docker/ruby-arch/Dockerfile -- to be CREATED in pal-e-platform. Directory does not exist yet (expected for new file). Dockerfile shape provided inline.
    • [x] docker/ruby-arch/.woodpecker.yaml -- to be CREATED in pal-e-platform. Pipeline shape described inline.
    • [x] terraform/modules/harbor/main.tf -- VERIFIED EXISTS in pal-e-platform. Currently defines Harbor Helm release, OIDC config, and portal proxy. No Harbor project resource exists yet -- the issue correctly notes "add pal-e Harbor project (if not exists)".

    Repo Placement

    OK. Issue is filed on ldraney/pal-e-platform and all file targets (docker/, terraform/) are pal-e-platform paths. The board item is on board-pal-enterprises which is correct since this infra work supports the pal-enterprises app. No cross-repo issues.

    Dependencies

    The issue is Ticket 3 of a 5-ticket infra overhaul sequence (T1-T5). Dependencies found but NOT documented in the issue body:

    • T4 (Dockerfile migration to Arch base, #1198) -- directly depends on T3. Cannot migrate app Dockerfiles to Arch base until the base image exists in Harbor.
    • T2 (Dev overlay with Arch base, #1197) -- depends on T3. Dev overlay references "Arch base" which is this image.
    • T1 (Keycloak dev redirect, #1200) -- independent, no dependency on T3.
    • T5 (Woodpecker CI with test gates, #1199) -- loosely related. CI pipeline conventions from T5 may affect T3's pipeline yaml, but T3 should be done first.

    [BODY] Add a Dependencies section listing T4 and T2 as downstream dependents.

    Acceptance Criteria

    7 AC items are specific and testable. 4 test expectations are concrete with runnable commands. However:

    • The Constraints section flags a potential Kaniko compatibility issue with pacman -Syu but this is not reflected in the AC. If Kaniko cannot run pacman, the entire approach changes (buildah or docker-in-docker). This should be validated first or added as AC.
    • No AC for Harbor project creation via Terraform (the pal-e project in Harbor).

    Blast Radius

    Low immediate blast radius -- this creates new files only. The existing pal-enterprises Dockerfile (ruby:3.4.8-slim Debian base) is untouched by this ticket. Future consumers listed in the issue (pal-enterprises, westside-ror, pal-e-ror, westside-docs) will be migrated by T4 separately. No existing pipelines are modified.

    The existing .woodpecker.yaml in pal-enterprises uses Kaniko for builds (plugin-kaniko:2.3.0). The pal-e-platform .woodpecker.yaml uses OpenTofu, not container builds. The new pipeline in docker/ruby-arch/.woodpecker.yaml would be a new Woodpecker pipeline definition -- verify whether Woodpecker auto-discovers yaml files in subdirectories or if this needs explicit registration.

    Decomposition Assessment

    3 file targets in 1 repo. 7 AC + 4 test expectations. Estimated agent work is within 5 minutes -- the Dockerfile and pipeline yaml are straightforward with shapes provided. No decomposition needed.

    Recommendations

    • [SCOPE] Add infra-overhaul user story entry to project-pal-enterprises user-stories table.
    • [SCOPE] Create architecture note arch-harbor for the Harbor component in pal-e-docs.
    • [BODY] Add Checklist section (PR opened, tests pass, no unrelated changes).
    • [BODY] Add Dependencies section documenting T4 and T2 as downstream dependents.
    • [BODY] Add AC for Harbor project creation: "[ ] Harbor project pal-e exists (created via Terraform if needed)."
    • [BODY] Add AC or spike note for Kaniko + pacman compatibility: "[ ] Verify pacman -Syu works inside Kaniko build (or document alternative build tool)."
  • Verdict: NEEDS_REFINEMENT

    Template Completeness

    • [x] Type -- "Infra" (maps to Task base template)
    • [x] Lineage -- "Plan: pal-enterprises infrastructure overhaul (Ticket 1 of 5)"
    • [x] Repo -- ldraney/pal-e-services
    • [x] User Story -- developer dev-environment auth flow
    • [x] Context -- clear motivation, references existing pattern
    • [x] File Targets -- terraform/k3s.tfvars with line references
    • [x] Acceptance Criteria -- 5 items, all concrete
    • [x] Test Expectations -- 2 items, verifiable
    • [x] Constraints -- 3 items, clear
    • [x] Related -- 3 references with context
    • [ ] Checklist -- MISSING (PR opened, tests pass, no unrelated changes)

    Traceability

    • [x] story:infra-overhaul label -- present on board item
    • [ ] story note MISSING -- [SCOPE] "infra-overhaul" is not listed in project-pal-enterprises user-stories section. Only landing-page, sso-gateway, tool-dashboard, client-portal exist. Create user story entry on project-pal-enterprises.
    • [x] arch:keycloak label -- present on board item
    • [ ] arch note MISSING -- [SCOPE] No arch-keycloak note found in pal-e-docs. Create architecture note arch-keycloak for the Keycloak component.
    • [x] Forgejo issue -- https://forgejo.tail5b443a.ts.net/ldraney/pal-e-services/issues/75, open

    File Targets

    • [x] terraform/k3s.tfvars -- file is gitignored (*.tfvars in .gitignore), which is correct for secrets. Structure verified via k3s.tfvars.example. The keycloak_clients variable type confirms valid_redirect_uris (list(string)) and web_origins (list(string)) fields exist.
    • [x] Line references (238-257) -- cannot verify exact lines from repo (gitignored), but the pattern is confirmed: westside-admin client in k3s.tfvars.example shows the established multi-URI pattern with both prod and dev redirect URIs.

    Note: Since k3s.tfvars is not tracked in git, the agent will need local filesystem access to the pal-e-services terraform directory to make this change. This is an operational constraint, not a scope issue.

    Repo Placement

    OK. Issue is filed on ldraney/pal-e-services, which houses the terraform/ directory. The keycloak client config and ArgoCD app creation both live in this repo's terraform state. No cross-repo work needed.

    Dependencies

    • Upstream (done): pal-e-deployments#156 (prod overlay) -- closed, already merged. This is a prerequisite for the ArgoCD app to have a source_path to point to.
    • Downstream: T2 (board item #1197, dev overlay) depends on Keycloak redirect being configured first, since the dev environment needs auth to work.
    • Related: T5 (board item #1199, Woodpecker CI / pal-enterprises#7) -- independent, no dependency.
    • Related: Board item #1192 (Keycloak upgrade to 26.x) -- shares arch:keycloak label but is independent work in pal-e-platform.
    • Not documented in scope: The ArgoCD app creation requires a services entry in k3s.tfvars (per services.tf pattern). The issue mentions ArgoCD app but does not list the services block as a file target. However, since k3s.tfvars is a single file, this is covered by the existing file target.

    Acceptance Criteria

    5 criteria, all concrete and agent-verifiable:

    • AC1-2: Verifiable by inspecting tfvars after edit
    • AC3: Verifiable by running tofu apply (requires local access)
    • AC4: Verifiable via kubectl command (provided in AC)
    • AC5: Verifiable via Keycloak admin console (requires browser/API access)

    AC3 and AC5 require runtime access, not just code changes. This is expected for infra tickets.

    Blast Radius

    • Test expectation #2 explicitly constrains: "No other Keycloak clients or services affected."
    • The tofu plan step (Test Expectation #1) will verify blast radius before apply.
    • The westside-admin pattern is well-established -- adding redirect URIs to an existing client is a safe, additive change.
    • The ArgoCD app creation is also additive -- it creates a new application entry, does not modify existing ones.
    • No downstream consumers affected beyond the pal-enterprises app itself.

    Decomposition Assessment

    1 file target, 1 repo, 5 AC, estimated agent work under 5 minutes. No decomposition needed.

    Broken Reference

    The issue's Related section references sop-keycloak-client-creation but this note does not exist in pal-e-docs. This is informational -- it does not block the ticket but the SOP should either be created or the reference removed.

    Recommendations

    • [BODY] Add missing Checklist section (PR opened, tests pass, no unrelated changes)
    • [SCOPE] Create user story entry "infra-overhaul" on project-pal-enterprises user-stories section -- this story is used by 5 tickets (T1-T5) on this board
    • [SCOPE] Create architecture note arch-keycloak for the Keycloak component -- referenced by this ticket and board item #1192
    • [BODY] Fix or remove broken reference to sop-keycloak-client-creation in Related section
  • Verdict: APPROVED

    Re-review after refinement. Previous verdict was NEEDS_REFINEMENT due to missing arch-rails-app note. That note now exists (slug: arch-rails-app, tagged architecture,active, project pal-enterprises). All prior findings remain valid. No new issues found.

    Template Completeness

    • [x] Type -- present (Infra; non-standard but maps to base template-issue)
    • [x] Lineage -- present
    • [x] Repo -- present
    • [x] User Story -- present
    • [x] Context -- present
    • [x] File Targets -- present
    • [x] Acceptance Criteria -- present
    • [x] Test Expectations -- present
    • [x] Constraints -- present
    • [x] Checklist -- present
    • [x] Related -- present

    Traceability

    • [x] story:sso-gateway label -- "Single login grants access to all platform tools"
    • [x] story note verified -- found in project-pal-enterprises user-stories section (key: sso-gateway, role: Authenticated user)
    • [x] arch:rails-app label -- Rails application component
    • [x] arch note verified -- arch-rails-app exists in pal-e-docs (slug: arch-rails-app, tagged architecture,active, project pal-enterprises)
    • [x] Forgejo issue -- https://forgejo.tail5b443a.ts.net/ldraney/pal-enterprises/issues/7, open

    File Targets

    • [x] .woodpecker.yaml (to create) -- verified: does not exist yet, correct
    • [x] Dockerfile (NOT to touch) -- verified: exists at repo root, Rails production build with Thruster, EXPOSE 80
    • [x] k8s/dev.yaml (NOT to touch) -- verified: exists, dev-only manifest

    Repo Placement

    OK. Issue filed on ldraney/pal-enterprises, fix is in the same repo. Single-repo change.

    Dependencies

    • Board item #1183 (NetworkPolicy: allow pal-enterprises to Postgres + Keycloak) -- in todo column. Not a blocker for CI pipeline itself, but required for the deployed app to function.
    • Board item #1184 (Kustomize overlay: overlays/pal-enterprises/prod/) -- in todo column. Not a blocker for CI, but required for ArgoCD to deploy the image CI produces.
    • Manual gates documented in checklist: Woodpecker UI activation and Harbor secrets. These are prerequisites for pipeline execution but not for the file creation PR.
    • Issue states Harbor project must exist first (created by tofu apply) -- this is a pre-existing dependency, correctly documented.

    Acceptance Criteria

    3 ACs, all testable by an agent or via Woodpecker MCP:

    • .woodpecker.yaml exists in repo root -- file existence check, trivially verifiable
    • Uses internal Harbor URL (harbor-core.harbor.svc.cluster.local) per SOP -- grep verifiable. Note: existing sibling pipelines (e.g. westside-admin) use harbor.harbor.svc.cluster.local. The issue follows the SOP, but the implementing agent should confirm which internal URL is correct for this cluster.
    • Pushes pal-enterprises/app:{SHA} tag on merge to main -- verifiable via pipeline output and Harbor API

    Test expectations are clear and actionable. Run command references Woodpecker MCP tool.

    Blast Radius

    Low. Single new file creation (.woodpecker.yaml). No existing code is modified. No downstream consumers affected. Sibling repos (westside-admin, platform-validation, etc.) have their own independent pipelines -- no shared CI config.

    Decomposition Assessment

    1 file target, 3 acceptance criteria, single repo. Well under the 5-minute rule. No decomposition needed.

    Recommendations

    No action needed.

    Re-Review History

    • 2026-05-09 (v1): NEEDS_REFINEMENT -- [SCOPE] Create architecture note arch-rails-app.
    • 2026-05-09 (v2): APPROVED -- arch-rails-app note confirmed to exist (slug: arch-rails-app, tags: architecture,active, project: pal-enterprises). Previous search_notes query returned empty but list_notes with tag/project filter and direct get_note both confirm the note exists. All traceability legs complete.
  • Verdict: READY

    Re-review after three refinements applied. Previous verdict was NEEDS_REFINEMENT.

    Template Completeness

    • [x] Type -- Spike
    • [x] Lineage -- Standalone, with session context
    • [x] Repo -- Multiple: ldraney/pal-enterprises, pal-e-docs, pal-e-services
    • [x] Question -- Clear either/or framing (native multi-tenancy vs proxy vs hybrid)
    • [x] What to Explore -- Six bullet areas covering API surface, two option paths, Keycloak mapping, Forgejo access, read-only enforcement
    • [x] Success Criteria -- Four items, properly framed for a spike (decision + follow-up tickets)
    • [x] Time-box -- "1 session"
    • [x] Related -- References project page, related issues (#12, #9), and Keycloak infra dependencies (#357, #358)

    All required spike template sections present and complete.

    Traceability

    • [x] story:client-portal label -- present on board item
    • [x] story note verified -- found in project-pal-enterprises user-stories table (key=client-portal, Role=Client, Success Metric="Client can view their project board and active work via read-only agency link")
    • [x] arch:multi-tenant label -- present on board item
    • [x] arch note verified -- arch-multi-tenant note exists in pal-e-docs (placeholder, content pending spike completion). Tags: architecture, active. Project: pal-enterprises.
    • [x] Forgejo issue -- https://forgejo.tail5b443a.ts.net/ldraney/pal-enterprises/issues/13, state: open

    All three traceability legs verified. Previous gaps (missing story note, missing arch note) have been resolved.

    File Targets

    N/A -- Spike type. No file targets expected or present. Correct per template.

    Repo Placement

    OK. Issue is filed on ldraney/pal-enterprises. Issue body correctly identifies this as a multi-repo investigation (pal-enterprises, pal-e-docs, pal-e-services). For a spike, filing on the primary consumer repo is appropriate.

    Dependencies

    • #12 (board item 1189) -- "Create pal-enterprises-docs RoR repo" (5pt feature, backlog). Soft dependency -- spike decision shapes #12 implementation. Documented in Related.
    • #9 (board item 1187) -- "Owner dashboard: lead pipeline + client management" (5pt feature, backlog). Admin counterpart to client portal. Soft dependency. Documented in Related.
    • pal-e-platform #357 (board item 1183) -- "NetworkPolicy: allow pal-enterprises to Postgres + Keycloak" (2pt infra, todo). Prerequisite for Keycloak testing. Now documented in Related.
    • pal-e-platform #358 (board item 1192) -- Keycloak infra (3pt, backlog). Related to Keycloak mapping exploration. Now documented in Related.

    All dependencies now documented in the issue body's Related section. Previous gap (#357, #358 missing from Related) has been resolved.

    Acceptance Criteria

    The spike uses "Success Criteria" (correct for spike type). All four criteria are investigation-oriented and agent-verifiable:

    • "Architecture decision documented" -- check for decision note artifact
    • "Keycloak role mapping strategy defined" -- check for documented strategy
    • "Follow-up feature ticket(s) created" -- check for new Forgejo issues
    • "Or: no action if simpler alternative discovered" -- check for closing comment

    Blast Radius

    Acceptable for a spike. Investigation only, no code changes. Follow-up tickets should scope blast radius carefully (pal-e-docs API changes, Keycloak realm config, Forgejo auth integration).

    Decomposition Assessment

    Spike type, time-boxed to 1 session. No file targets, no code changes. 4 success criteria, all investigation-oriented. Single agent can complete in one pass. No decomposition needed.

    Refinements Applied (since previous review)

    • [x] [SCOPE] story:client-portal added to project-pal-enterprises user-stories table -- VERIFIED
    • [x] [SCOPE] arch-multi-tenant architecture note created as placeholder -- VERIFIED (slug: arch-multi-tenant, note_type: doc, tags: architecture/active)
    • [x] [BODY] Related section updated to include #357 and #358 (Keycloak infra dependencies) -- VERIFIED

    Recommendations

    No action needed. All previous refinements verified. Ticket is ready for next_up.

  • Verdict: READY

    Template Completeness

    • [x] Type -- Task
    • [x] Lineage -- Standalone, discovered during production validation
    • [x] Repo -- ldraney/pal-enterprises
    • [x] User Story
    • [x] Context
    • [x] File Targets -- present (template suggests "Scope" for Task type, but File Targets is appropriate here since specific files are named)
    • [x] Acceptance Criteria -- 3 criteria
    • [x] Test Expectations -- 3 test commands
    • [x] Constraints
    • [x] Checklist
    • [x] Related

    Traceability

    • [x] story:sso-gateway label -- found in project-pal-enterprises user-stories section
    • [x] story note verified -- sso-gateway entry exists in project-pal-enterprises user-stories table
    • [ ] arch:rails-app label -- arch note MISSING -- [SCOPE] Create architecture note arch-rails-app for component rails-app
    • [x] Forgejo issue -- https://forgejo.tail5b443a.ts.net/ldraney/pal-enterprises/issues/11, open

    File Targets

    • [x] .gitignore -- verified: file exists. .claude/ is already present on line 34. .claude-no-enforce is NOT currently in .gitignore (so removal is a no-op).
    • [x] .claude-no-enforce -- verified: file exists (empty, 0 bytes), needs deletion.

    Note: Two of the three acceptance criteria are already satisfied in the current codebase state:

    • .claude/ is already in .gitignore (line 34)
    • .claude-no-enforce is not in .gitignore (never was)

    The only actual work is deleting the .claude-no-enforce file. This does not invalidate the ticket -- the AC and test expectations will still pass after deletion -- but the agent should be aware only one change is needed.

    Repo Placement

    OK. Issue is filed on ldraney/pal-enterprises and all file targets are in that repo. Single-repo scope.

    Dependencies

    No blocking dependencies found. All other board items are in backlog, todo, or done. This ticket is independent and can proceed without waiting on anything. No other tickets depend on this one.

    Acceptance Criteria

    All three criteria are machine-verifiable with simple shell commands. Test expectations map 1:1 to acceptance criteria. The grep and test commands are correct and will produce the expected results after the single deletion.

    Blast Radius

    Minimal. No references to .claude-no-enforce found anywhere in the codebase (searched .rb, .yml, .yaml, .json, .sh, .md files). The .dockerignore does not reference it either. Deleting the file has no downstream effects beyond re-enabling SOP enforcement hooks, which is the intent.

    Decomposition Assessment

    No decomposition needed. 1 file to delete, 0 files to edit (both gitignore changes are already done). Well under the 5-minute rule. 3 acceptance criteria, single repo.

    Recommendation

    No action needed. Scope is clean and the ticket is ready for implementation. The arch note gap is a platform-wide concern, not a blocker for this ticket.

    • [SCOPE] Create architecture note arch-rails-app for component rails-app (platform-wide gap, not specific to this ticket)
  • Verdict: NEEDS_REFINEMENT

    Template Completeness

    • [x] Type -- Feature
    • [x] Lineage -- Standalone
    • [x] Repo -- ldraney/pal-enterprises
    • [x] User Story -- present, well-formed
    • [x] Context -- present, adequate
    • [x] File Targets -- 4 files listed
    • [x] Acceptance Criteria -- 5 criteria
    • [x] Test Expectations -- present
    • [x] Constraints -- present
    • [x] Checklist -- present
    • [x] Related -- present

    Traceability

    • [x] story:sso-gateway label -- Single login grants access to all platform tools
    • [x] story note verified -- found in project-pal-enterprises user-stories section (row: sso-gateway, role: Authenticated user)
    • [x] arch:rails-app label -- Rails application component
    • [ ] arch note MISSING -- [SCOPE] Create architecture note arch-rails-app for component rails-app
    • [x] Forgejo issue -- https://forgejo.tail5b443a.ts.net/ldraney/pal-enterprises/issues/8, open

    File Targets

    • [x] app/controllers/docs_controller.rb -- to be created, does not exist yet (correct)
    • [x] app/views/docs/ -- to be created, directory does not exist yet (correct)
    • [x] config/routes.rb -- verified exists, no /docs route present yet (correct)
    • [x] Gemfile -- verified exists, no markdown gem present yet (correct)
    • [x] docs/ content files -- verified: README.md, onboarding.md, architecture.md, dashboards.md all present
    • [ ] docs/ mermaid content -- NOTE: no docs currently contain mermaid diagrams, so AC #3 (mermaid rendering) cannot be visually validated against existing content. Consider adding a sample mermaid block to one doc file, or noting this limitation.

    Repo Placement

    OK. Issue filed on ldraney/pal-enterprises, all file targets are in the same repo. Single-repo scope.

    Dependencies

    • No blocking dependencies on the board. This ticket is independent of other backlog items.
    • Auth infrastructure (Keycloak OIDC, require_login pattern) already exists and is in done column (Phase 3: Keycloak OIDC authentication, item #1180).
    • No test directory exists (test/ and spec/ both missing). The Test Expectations section references bin/rails test but no test infrastructure is set up. The implementing agent will need to create the test directory and test helper as part of the work, or this should be a separate prerequisite ticket.

    Acceptance Criteria

    • [x] AC1: /docs renders docs/README.md as index -- verifiable via integration test
    • [x] AC2: /docs/:slug renders individual docs -- verifiable via integration test
    • [ ] AC3: Mermaid diagrams render -- NOT verifiable against current docs content (no mermaid blocks exist). Agent can verify mermaid.js is included in the view, but cannot confirm visual rendering without test content.
    • [x] AC4: Route behind Keycloak auth -- verifiable via redirect test for unauthenticated user
    • [x] AC5: Navigation between docs -- verifiable via link presence in rendered HTML

    Missing AC: No acceptance criterion for path traversal protection. A slug like ../../etc/passwd or ../config/secrets must be rejected. This is a security-critical gap for a controller that reads files from the filesystem.

    Blast Radius

    • No existing markdown rendering or file-serving patterns in the app. This is net-new functionality.
    • Security risk: the docs controller will read files from disk based on user-supplied slugs. Path traversal protection is not mentioned in Constraints or AC. The implementing agent must sanitize the slug parameter to prevent directory traversal attacks.
    • No downstream consumers affected -- this is a new route.

    Decomposition Assessment

    4 file targets in 1 repo, 5 acceptance criteria. Fits within a single agent pass (estimated ~5 minutes). No decomposition needed.

    Recommendations

    • [BODY] Add acceptance criterion: "Requests for slugs containing path traversal sequences (e.g., ../) return 404 or 400, not file contents outside docs/"
    • [BODY] Add constraint: "Sanitize slug parameter to prevent directory traversal -- reject any slug containing .., /, or characters outside [a-z0-9_-]"
    • [BODY] Note in Test Expectations that test/ directory does not exist yet and must be created (or rely on Rails generator defaults)
    • [SCOPE] Create architecture note arch-rails-app for component rails-app
    • [SCOPE] The convention convention-client-project-structure referenced in Context and Related does not exist in pal-e-docs. Create it or update the reference.
  • Verdict: NEEDS_REFINEMENT

    Template Completeness

    • [x] Type -- Feature
    • [x] Lineage -- "Decomposed from #1 -- Phase 2 of 4. Depends on #2 (scaffold)."
    • [x] Repo -- present but wrong (see Repo Placement below)
    • [x] User Story
    • [x] Context
    • [x] File Targets
    • [x] Acceptance Criteria
    • [x] Test Expectations
    • [x] Constraints
    • [x] Checklist
    • [x] Related

    All required sections are present per template-issue-feature.

    Traceability

    • [x] story:landing-page label -- "Visitor: Can understand services offered and submit contact form"
    • [x] story note verified -- found in project-pal-enterprises user-stories section
    • [ ] arch:rails-app label -- present on board item
    • [ ] arch note MISSING -- [SCOPE] Create architecture note arch-rails-app for the Rails application component
    • [x] Forgejo issue -- ldraney/pal-enterprises#3, state: open

    File Targets

    Major discrepancy: the issue lists files "to create" but several already exist from a prior implementation pass. The existing implementation uses a DB-backed Lead model, which contradicts the issue's "no database model" constraint.

    • [ ] app/controllers/contacts_controller.rb -- ISSUE: Listed as "to create" but already exists. Uses Lead model (DB-backed), contradicts spec's "no database model" constraint.
    • [ ] app/views/contacts/new.html.erb -- ISSUE: Listed as "to create" but already exists.
    • [ ] app/views/contacts/thank_you.html.erb -- Does not exist. Controller redirects to root_path instead of rendering a thank_you page. Spec and implementation disagree.
    • [ ] app/mailers/contact_mailer.rb -- Does not exist. Only application_mailer.rb present. This is genuinely needed.
    • [ ] app/views/contact_mailer/new_inquiry.html.erb -- Does not exist. Genuinely needed.
    • [x] config/routes.rb -- Verified: already has resources :contacts, only: [:new, :create]
    • [x] app/views/pages/home.html.erb -- Verified: already renders the contact form partial inline.
    • [x] app/views/layouts/application.html.erb -- Verified: already has "Contact" nav link at line 23.

    Additional file not in spec: app/views/contacts/_form.html.erb (partial) and app/models/lead.rb (DB model with validations) already exist.

    Repo Placement

    MISMATCH. The issue body says forgejo_admin/pal-enterprises but the actual repo is ldraney/pal-enterprises. The forgejo_admin/pal-enterprises path returns no data from the API. The board item's Forgejo URL also uses the forgejo_admin path. This needs correction.

    Dependencies

    • Phase 1 (#1178, "Rails scaffold, landing page, health check") is in done -- dependency satisfied.
    • Phase 3 (#1180, "Keycloak OIDC authentication") is in done -- no blocker.
    • Board item #1188 ("Remove Tailwind, implement plain CSS design system") is in backlog. The current issue spec references Tailwind styling ("contact form with Tailwind styling"). If #1188 lands first, Tailwind references become invalid. If this ticket lands first, the Tailwind work will need to update these views. Order dependency is undocumented.
    • No items currently block this ticket.

    Acceptance Criteria

    Mixed testability:

    • "Contact form at /contact renders with all fields" -- testable, but spec says name/email/message_type/message_body while codebase has name/email/message (no message_type select). Criteria ambiguous about which fields.
    • "Form validates presence of name, email, and message" -- testable. Already implemented in Lead model.
    • "Successful submission delivers email to admin" -- testable, but mailer does not exist yet. This is the real remaining work.
    • "Turbo-powered submit shows confirmation without full page reload" -- testable but NOT implemented. No turbo_frame or turbo_stream usage in the contact form.
    • "Invalid submission re-renders form with errors" -- testable. Already implemented.
    • "Landing page links to contact form" -- testable. Already implemented (form rendered inline on home page, plus nav link).

    Missing AC: The board item title says "business name, name, logo, email + calendar redirect" but none of these extra fields (business_name, logo upload, calendar redirect) appear in the Forgejo issue or the codebase. The board title and spec are misaligned.

    Blast Radius

    Low. The contact form is self-contained. Adding Action Mailer touches SMTP configuration which could affect any future mailers but has no current downstream consumers. The Tailwind dependency is the main cross-cutting concern (see Dependencies).

    Decomposition Assessment

    6 acceptance criteria (borderline), but all within a single repo and a small number of files. The real remaining work is: (1) add mailer, (2) add message_type select, (3) add Turbo submit, (4) decide DB vs stateless. This fits in a single agent pass if the scope contradictions are resolved first. No decomposition needed.

    Recommendations

    • [BODY] Fix Repo field: forgejo_admin/pal-enterprises should be ldraney/pal-enterprises.
    • [BODY] Update File Targets to reflect current codebase state: mark existing files as "to modify" not "to create." Add app/models/lead.rb and app/views/contacts/_form.html.erb to the target list.
    • [BODY] Resolve DB vs stateless contradiction: the Constraints section says "No database model -- contact form is stateless" but the codebase already has a Lead model backed by a leads table. Either update the constraint to accept the DB model, or rewrite the controller to use a plain PORO + mailer.
    • [BODY] Resolve board title vs issue scope mismatch: board says "business name, name, logo, email + calendar redirect" but issue spec has "name, email, message type, message body" with no logo upload or calendar redirect. Either update the board title or add the missing fields to the issue.
    • [BODY] Add message_type select dropdown to file targets and form spec (currently missing from implementation).
    • [BODY] Clarify Tailwind dependency: note that views use Tailwind classes, and document ordering relative to board item #1188 (Tailwind removal).
    • [SCOPE] Create architecture note arch-rails-app for the Rails application component.
  • Verdict: NEEDS_REFINEMENT

    Template Completeness

    • [x] Type -- Feature
    • [x] Lineage -- Decomposed from #1, depends on #4
    • [x] Repo -- forgejo_admin/pal-enterprises
    • [x] User Story -- present, well-formed
    • [x] Context -- present, clear motivation
    • [x] File Targets -- present, lists creates and modifies
    • [x] Acceptance Criteria -- 6 criteria listed
    • [x] Test Expectations -- 2 unit tests + run command
    • [x] Constraints -- present
    • [x] Checklist -- present
    • [x] Related -- present

    All required template sections present. Template is complete.

    Traceability

    • [x] story:tool-dashboard label -- "Dashboard shows all tools with live status"
    • [x] story note verified -- found in project-pal-enterprises user-stories section (key: tool-dashboard, role: Authenticated user)
    • [x] arch:rails-app label -- Rails application component
    • [ ] arch note MISSING -- [SCOPE] Create architecture note arch-rails-app for component rails-app. No matching note found in pal-e-docs.
    • [x] Forgejo issue -- ldraney/pal-enterprises#5, open

    File Targets

    Files to create (per issue):

    • [x] app/controllers/dashboard_controller.rb -- verified: EXISTS already. Contains before_action :require_login and index action. Issue says "create" but file already exists from prior work.
    • [x] app/views/dashboard/index.html.erb -- verified: EXISTS already. Contains a basic dashboard view with user info and sign-out link. However, does NOT contain the tool card grid specified in the issue.
    • [ ] app/controllers/concerns/authentication.rb -- ISSUE: File does NOT exist. The concerns directory contains only .keep. Auth methods (require_login, current_user, logged_in?) are defined directly in ApplicationController instead. The issue should be updated to reflect this -- either extract to a concern as planned, or document that the methods live in ApplicationController.

    Files to modify (per issue):

    • [x] config/routes.rb -- verified: EXISTS. Already contains get "dashboard", to: "dashboard#index".
    • [x] app/controllers/sessions_controller.rb -- verified: EXISTS. Already redirects to dashboard_path after login.

    Key finding: Most of the plumbing (controller, route, session redirect) already exists from the Phase 3 work. The main remaining work is the tool card grid in the dashboard view. The issue's file targets are partially stale.

    Repo Placement

    Issue filed on forgejo_admin/pal-enterprises but that repo returns HTTP 301 -- it was transferred to ldraney/pal-enterprises. The board item's forgejo_issue_url still points to the old org. The actual issue lives at ldraney/pal-enterprises#5. The issue body's ### Repo section also says forgejo_admin/pal-enterprises which is stale. Single-repo fix, no cross-repo concerns.

    Dependencies

    • Phase 3 (Keycloak OIDC auth, board item #1180) -- in done column. Dependency satisfied.
    • No blocking items found in in_progress column.
    • No items currently depend on this ticket.
    • Dependencies are documented in the issue Lineage section ("Depends on #4").

    Acceptance Criteria

    • [x] "Dashboard renders at /dashboard with tool card grid" -- testable, but current view has NO tool cards. This is the main work item.
    • [x] "Each card shows tool name, description, status, and link" -- testable. 10 tools listed in context. "Status" is ambiguous -- does it mean live health check or static label?
    • [x] "Unauthenticated access to /dashboard redirects to /" -- MINOR: current implementation redirects to /login not /. Either the AC or the implementation needs alignment.
    • [x] "Dashboard shows logged-in user's name" -- testable, already implemented in current view.
    • [x] "Successful login redirects to /dashboard" -- testable, already implemented in SessionsController.
    • [x] "All platform tools listed with correct Tailscale URLs" -- testable but the 10 tool URLs are not specified in the issue. Agent would need to discover them.

    5 of 6 AC are verifiable by an agent. The "status" field on tool cards is ambiguous.

    Blast Radius

    Low blast radius. Changes are confined to the dashboard view and controller. No other controllers reference dashboard. No downstream consumers. The auth guard pattern (before_action :require_login) is already established and tested via Phase 3.

    Decomposition Assessment

    File count: 1-3 files in 1 repo. AC count: 6. Estimated agent work: under 5 minutes -- mostly building the tool card grid HTML. No decomposition needed.

    Recommendations

    • [BODY] Update file targets: most files already exist. Mark dashboard_controller.rb, routes.rb, and sessions_controller.rb as already complete. Primary remaining work is the tool card grid in app/views/dashboard/index.html.erb.
    • [BODY] Remove or update app/controllers/concerns/authentication.rb file target. Auth methods are in ApplicationController directly. Either extract to concern as planned or update the issue to reflect reality.
    • [BODY] Fix repo reference: forgejo_admin/pal-enterprises -> ldraney/pal-enterprises in the ### Repo section.
    • [BODY] Clarify AC: "redirects to /" -- current code redirects to /login. Update AC to match desired behavior.
    • [BODY] Clarify AC: "status" on tool cards -- specify whether this means a static label or live health check.
    • [BODY] Add Tailscale URLs for the 10 tools so the agent doesn't need to discover them.
    • [SCOPE] Create architecture note arch-rails-app for the rails-app component.
  • Verdict: NEEDS_REFINEMENT

    Template Completeness

    • [x] Type — Feature
    • [x] Lineage — Standalone, discovered during scaffold
    • [x] Repo — ldraney/pal-enterprises
    • [x] User Story — present, well-formed
    • [x] Context — thorough, includes version table
    • [x] File Targets — present with modify/don't-touch lists
    • [x] Acceptance Criteria — 5 criteria
    • [x] Test Expectations — 5 items with run command
    • [x] Constraints — 4 constraints listed
    • [x] Checklist — present
    • [x] Related — present

    All required sections for the Feature template are present.

    Traceability

    • [x] story:infra-upgrades label — present on board item
    • [ ] story note MISSING — [SCOPE] The project-pal-enterprises user-stories section has entries for landing-page, sso-gateway, and tool-dashboard, but no entry for infra-upgrades. Create user story entry on project-pal-enterprises.
    • [x] arch:keycloak label — present on board item
    • [ ] arch note MISSING — [SCOPE] No arch-keycloak note found in pal-e-docs. Create architecture note arch-keycloak for the Keycloak component.
    • [x] Forgejo issue — https://forgejo.tail5b443a.ts.net/ldraney/pal-enterprises/issues/6, state: open

    File Targets

    • [x] .ruby-version — verified: exists, currently contains ruby-3.4.8
    • [x] Gemfile — verified: exists, contains omniauth stack (omniauth_openid_connect, omniauth-rails_csrf_protection)
    • [x] Gemfile.lock — verified: exists, shows omniauth 1.9.2 (matches issue's "Current" column)
    • [x] k8s/dev.yaml — verified: exists, shows image: ruby:3.4-slim at line 47
    • [x] Dockerfile — verified: exists, shows ARG RUBY_VERSION=3.4.8 and ruby:$RUBY_VERSION-slim
    • [x] pal-e-platform: terraform/modules/keycloak/main.tf — verified: exists, shows image = "quay.io/keycloak/keycloak:26.0.7" at line 100
    • [x] pal-e-services: terraform/k3s.tfvars — verified: exists, contains keycloak realm and client config

    All file targets verified against the codebase. Paths are accurate and contents match what the issue claims.

    Repo Placement

    The Forgejo issue is filed on ldraney/pal-enterprises, which is the primary repo. However, the issue scope explicitly touches THREE repos:

    • ldraney/pal-enterprises — Ruby version, Gemfile, Dockerfile, k8s/dev.yaml
    • ldraney/pal-e-platform — Keycloak image tag in terraform
    • ldraney/pal-e-services — potential realm schema changes in terraform

    [DECOMPOSE] — Three repos affected. The Keycloak upgrade (pal-e-platform) and the Ruby/OmniAuth upgrade (pal-enterprises) are independent work streams that should be separate tickets. A single agent pass cannot PR across three repos.

    Dependencies

    • Keycloak upgrade (26.0.7 to 26.6.1) must be applied and verified BEFORE the OIDC auth flow can be re-tested with the new OmniAuth version.
    • The constraint "Host (archbox) Ruby also needs updating — coordinate with westside-ror and pal-e-ror" introduces an undocumented coordination dependency. Both westside-ror and pal-e-ror are currently on Ruby 3.4.8 with the same image patterns.
    • No blocking items found on the board — no other items are currently in_progress.

    Acceptance Criteria

    AC #1-3 are verifiable for the pal-enterprises repo. AC #4 (Keycloak admin console version) requires the pal-e-platform change to be deployed first — this is a cross-repo dependency. AC #5 (test existing westside-app and pal-e-app auth flows) is a blast-radius validation that belongs on the Keycloak upgrade ticket, not the Ruby/OmniAuth ticket.

    Missing AC: No acceptance criterion for verifying gem compatibility with Ruby 4.0 (the Constraints section mentions it but there is no testable AC).

    Blast Radius

    • Keycloak upgrade affects 8+ OIDC clients: westside-app, westside-spa, mcd-tracker-app, mcd-tracker-ios, westside-ai-bot, playme2k, pal-e-app, westside-admin. All use the same Keycloak instance. The issue only mentions testing westside-app and pal-e-app, but all clients need validation.
    • Sibling Ruby apps on same version: westside-ror and pal-e-ror both use Ruby 3.4.8 with identical Dockerfile and k8s/dev.yaml patterns. The Constraints section notes "coordinate with westside-ror and pal-e-ror" but the scope does not include those repos.
    • Ruby 4.0 is a major version jump: This carries significant gem compatibility risk. The issue acknowledges this in Constraints but does not provide a verification step or fallback plan.

    Decomposition Assessment

    NEEDS DECOMPOSITION — This ticket violates the 5-minute rule on multiple axes:

    • 3+ repos: pal-enterprises, pal-e-platform, pal-e-services (3 repos)
    • 5 AC + 5 test expectations: 10 verifiable items total
    • Independent work streams: Ruby/OmniAuth upgrade and Keycloak upgrade are logically separate, with different risk profiles and blast radii

    Recommended decomposition:

    1. Sub-ticket 1: Upgrade Keycloak 26.0.7 to 26.6.1 (pal-e-platform) — terraform image tag change, review upgrade guide, validate all OIDC clients
    2. Sub-ticket 2: Upgrade Ruby 3.4.8 to 4.0.2 + OmniAuth 2.x (pal-enterprises) — .ruby-version, Gemfile, Dockerfile, k8s/dev.yaml, gem compatibility verification
    3. Sub-ticket 3 (optional): Coordinate Ruby 4.0 across sibling apps — westside-ror, pal-e-ror alignment

    Recommendation

    • [SCOPE] Create user story entry "infra-upgrades" on project-pal-enterprises user-stories section.
    • [SCOPE] Create architecture note arch-keycloak for the Keycloak component.
    • [DECOMPOSE] 5 AC + 5 test items across 3 repos. Split into: (1) Keycloak upgrade on pal-e-platform, (2) Ruby/OmniAuth upgrade on pal-enterprises. Route to skill-decompose-ticket.
    • [BODY] Add AC for gem compatibility verification: "When I run bundle install on Ruby 4.0, all gems resolve without errors."
    • [BODY] AC #5 (test existing auth flows) should move to the Keycloak upgrade sub-ticket since it validates the Keycloak change, not the Ruby change.
    • [LABEL] Consider adding arch:rails-app label since the Ruby/OmniAuth upgrade is primarily a rails-app concern.
  • Verdict: NEEDS_REFINEMENT

    Template Completeness

    • [x] Type
    • [x] Lineage
    • [x] Repo
    • [x] User Story
    • [x] Context
    • [x] File Targets
    • [x] Acceptance Criteria
    • [x] Test Expectations
    • [x] Constraints
    • [x] Checklist
    • [x] Related

    All required sections present per template-issue-feature.

    Traceability

    • [x] story:sso-gateway label -- "Single login grants access to all platform tools"
    • [x] story note verified -- sso-gateway entry found in project-pal-enterprises user-stories table (Story Note column reads "TBD" but table entry exists)
    • [x] arch:rails-app label -- Rails application component
    • [ ] arch note MISSING -- [SCOPE] Create architecture note arch-rails-app for the Rails application component. Searched pal-e-docs for "arch-rails-app", no matching note found.
    • [x] Forgejo issue -- ldraney/pal-enterprises#10, state: open

    File Targets

    Files to modify (all verified to exist):

    • [x] Gemfile -- verified: contains gem "tailwindcss-rails" at line 18
    • [x] app/assets/stylesheets/application.css -- verified: exists, will be rewritten with design system
    • [x] app/views/layouts/application.html.erb -- verified: contains ~15 Tailwind utility classes (bg-gray-50, text-indigo-600, shadow, etc.)
    • [x] app/views/pages/home.html.erb -- verified: contains ~20 Tailwind utility classes across hero, cards, and headings
    • [x] app/views/contacts/new.html.erb -- verified: contains Tailwind classes (max-w-lg, text-3xl, etc.)
    • [x] app/views/contacts/_form.html.erb -- verified: heavily Tailwind-styled form with ~25 utility classes
    • [x] app/views/sessions/new.html.erb -- verified: Tailwind classes on Keycloak sign-in button and layout
    • [x] app/views/dashboard/index.html.erb -- verified: grid layout, cards, borders all use Tailwind utilities
    • [x] Procfile.dev -- verified: contains css: bin/rails tailwindcss:watch at line 2
    • [x] README.md -- verified: exists

    Files to delete (all verified to exist):

    • [x] app/assets/tailwind/application.css -- verified: contains @import "tailwindcss"
    • [x] app/assets/builds/tailwind.css -- verified: contains compiled Tailwind v4.2.4 output

    Files to NOT touch (verified no changes needed):

    • [x] Dockerfile -- no tailwind references found
    • [x] config/ -- no tailwind references found

    Repo Placement

    OK. Issue filed on ldraney/pal-enterprises, all file targets are within the same repo. Single-repo change, no cross-repo concerns.

    Dependencies

    No blocking dependencies found on the board. This ticket is in backlog alongside other backlog items. The ticket references the ror-css-guide repo as a CSS conventions reference -- the agent will need to read that repo during implementation. No board items are blocked by this ticket, and this ticket is not blocked by any in-progress items.

    Acceptance Criteria

    9 acceptance criteria, all verifiable:

    • [x] "Zero Tailwind dependencies" -- grep-verifiable
    • [x] "All colors use var(--token)" -- grep-verifiable (no hardcoded hex outside :root)
    • [x] "CSS comments mark component boundaries" -- grep-verifiable
    • [x] "Mobile-first layout at 390px, desktop breakpoint at 600px" -- visually verifiable
    • [x] "Production Docker build serves styled pages" -- build-verifiable
    • [x] "Flash messages styled for notice and alert" -- visually verifiable
    • [x] "Landing page has hero, feature cards, and contact form" -- visually verifiable
    • [x] "Login page renders the Keycloak sign-in button" -- visually verifiable
    • [x] "Dashboard page renders styled" -- visually verifiable

    All criteria are testable. The Docker build command is provided in Test Expectations.

    Blast Radius

    Low. This is a single Rails app with no downstream CSS consumers. No sibling services share these stylesheets. The layout and view changes are self-contained. The only external reference is the ror-css-guide conventions repo, which is read-only input.

    Decomposition Assessment

    10 file targets in 1 repo, 9 acceptance criteria. This technically exceeds the 5-minute rule thresholds (>5 AC). However, the changes are tightly coupled -- removing Tailwind and replacing with semantic CSS is a single atomic operation. Decomposing would create artificial boundaries (e.g., "remove Tailwind" vs "add CSS" would conflict on every file). The work is mechanical: strip utility classes, write semantic classes, build one CSS file. Estimated agent time: 5-10 minutes. No decomposition recommended despite the size, given the tight coupling.

    Recommendations

    • [SCOPE] Create architecture note arch-rails-app for the Rails application component. The arch:rails-app label is used on 9 board items but has no backing architecture note in pal-e-docs.
  • Verdict: NEEDS_REFINEMENT

    Template Completeness

    • [x] Type
    • [x] Lineage
    • [x] Repo
    • [x] User Story
    • [x] Context
    • [x] File Targets
    • [x] Acceptance Criteria
    • [x] Test Expectations
    • [x] Constraints
    • [x] Checklist
    • [x] Related

    All required sections present per template-issue-feature.

    Traceability

    • [x] story:sso-gateway label — SSO gateway story
    • [x] story note verified — found in project-pal-enterprises user-stories section (row: sso-gateway, role: Authenticated user, metric: Single login grants access to all platform tools)
    • [x] arch:rails-app label — Rails app architecture component
    • [ ] arch note MISSING — [SCOPE] Create architecture note arch-rails-app for component rails-app. Note: this ticket actually modifies Terraform in pal-e-platform, not the Rails app itself. Consider whether arch:platform-infra would be more accurate.
    • [x] Forgejo issue — ldraney/pal-e-platform#357, open

    File Targets

    • [x] terraform/network-policies.tf — verified: file exists (10KB), Postgres allowlist at lines 158-180 (netpol_postgres), Keycloak allowlist at lines 134-156 (netpol_keycloak). Pattern is clear: add from entry with namespaceSelector matching pal-enterprises. No existing reference to pal-enterprises in the file.

    Repo Placement

    Correct. Issue filed on ldraney/pal-e-platform, file target is terraform/network-policies.tf in that repo.

    Dependencies

    No items in in_progress or next_up on board-pal-enterprises. Related infra items on the board:

    • #1184 — Kustomize overlay (backlog, independent)
    • #1185 — Woodpecker CI pipeline (backlog, independent)

    Constraint documented: must be merged before first prod deploy. No blocking dependencies.

    Acceptance Criteria

    3 criteria, all verifiable by an agent:

    • pal-enterprises namespace in Postgres NetworkPolicy — verifiable by reading diff
    • pal-enterprises namespace in Keycloak NetworkPolicy — verifiable by reading diff
    • tofu plan -lock=false shows clean diff — valid command, but requires cluster access. Agent can verify the HCL syntax is correct; live plan requires infra credentials.

    Blast Radius

    Additive-only change. Existing pattern shows other services already in both allowlists (e.g., basketball-api in both Postgres and Keycloak, westside-ai-assistant in Keycloak). No downstream consumers affected. No risk of breaking existing policies.

    Decomposition Assessment

    1 file target, 1 repo, 3 acceptance criteria. Estimated agent time well under 5 minutes. No decomposition needed.

    Recommendation

    • [SCOPE] Create architecture note arch-rails-app for the Rails app component. This note is referenced by the arch:rails-app label on this and 8 other board items but does not exist in pal-e-docs.
  • Verdict: NEEDS_REFINEMENT

    Template Completeness

    • [x] Type
    • [x] Lineage
    • [x] Repo
    • [x] User Story
    • [x] Context
    • [x] File Targets
    • [x] Acceptance Criteria
    • [x] Test Expectations
    • [x] Constraints
    • [x] Checklist
    • [x] Related

    All required sections for a Feature issue are present and well-structured.

    Traceability

    • [x] story:sso-gateway label — verified in project-pal-enterprises user-stories table (Role: Authenticated user, Metric: Single login grants access to all platform tools)
    • [x] story:landing-page label — verified in project-pal-enterprises user-stories table (Role: Visitor, Metric: Can understand services offered and submit contact form)
    • [ ] arch:rails-app label — arch note MISSING — [SCOPE] Create architecture note arch-rails-app for component rails-app
    • [x] Forgejo issue — https://forgejo.tail5b443a.ts.net/forgejo_admin/pal-enterprises/issues/1, state: open
    • [ ] sop-keycloak-client-creation — referenced in Context and Constraints but does not exist in pal-e-docs — [SCOPE] Create SOP note sop-keycloak-client-creation

    File Targets

    This is a greenfield scaffold — all 14 file targets are new files to create. No existing files to verify against. File paths are well-structured and follow Rails conventions. The explicit "Files NOT relevant" section (no migrations) is a good scope boundary.

    • [x] Gemfile — standard Rails dependency file
    • [x] config/database.yml — proper for CNPG connection
    • [x] config/environments/development.rb — Rails convention
    • [x] config/initializers/omniauth.rb — correct location for OmniAuth config
    • [x] config/routes.rb — standard Rails routing
    • [x] app/controllers/pages_controller.rb — valid path
    • [x] app/controllers/contacts_controller.rb — valid path
    • [x] app/controllers/sessions_controller.rb — valid path
    • [x] app/controllers/dashboard_controller.rb — valid path
    • [x] app/views/pages/home.html.erb — valid path
    • [x] app/views/contacts/new.html.erb — valid path
    • [x] app/views/dashboard/index.html.erb — valid path
    • [x] k8s/dev.yaml — follows westside-ror pattern
    • [x] app/mailers/contact_mailer.rb — valid path

    Repo Placement

    OK — issue is filed on forgejo_admin/pal-enterprises and all work targets the same repo. No cross-repo concerns.

    Dependencies

    • Keycloak pal-e realm must exist (it does, per Context)
    • New OIDC client pal-enterprises must be registered — depends on sop-keycloak-client-creation which does not exist yet
    • CNPG cluster must allow connections from user ldraney
    • Postgres namespace NetworkPolicy must be updated to allow pal-enterprises
    • No blocking items on the board — only one other item (repo placeholder #1177)
    • References westside-ror k8s pattern — should verify that repo/pattern is accessible to implementing agent

    Acceptance Criteria

    10 acceptance criteria. Each is testable by an agent in principle:

    • AC 1-3: Landing page and contact form — verifiable via request specs or system tests
    • AC 4-5: Keycloak flow — requires mock or real Keycloak; integration test mentioned in Test Expectations covers this
    • AC 6-7: Dashboard access and sign-out — verifiable via controller tests
    • AC 8: Auth guard — covered by unit test expectation
    • AC 9: k8s deployment — requires cluster access, not automatable in unit tests but verifiable post-deploy
    • AC 10: Health check — verifiable via request spec

    Criteria are well-written and specific. However, 10 AC across 14 files is too much for a single pass.

    Blast Radius

    • pal-e-hub — this replaces it. Decommission plan not mentioned but acceptable for a greenfield ticket (decommission would be a separate ticket)
    • Postgres NetworkPolicy change could affect other services if done incorrectly — bounded risk
    • Keycloak client registration is a platform-wide SSO concern — if misconfigured, could affect existing pal-e-hub flows

    Decomposition Assessment

    NEEDS DECOMPOSITION — This ticket violates all three thresholds of the 5-minute rule:

    • 14 file targets (threshold: >3)
    • 10 acceptance criteria (threshold: >5)
    • Estimated agent work: 20-30 minutes across Rails scaffold, Keycloak integration, mailer setup, k8s deployment, and tests

    Suggested decomposition:

    1. Phase 1: Rails scaffold + landing page — Gemfile, routes, pages_controller, home view, health check, basic k8s/dev.yaml
    2. Phase 2: Contact form + mailer — contacts_controller, contact view, contact_mailer, email delivery
    3. Phase 3: Keycloak SSO integration — omniauth initializer, sessions_controller, auth callback, session management
    4. Phase 4: Authenticated dashboard — dashboard_controller, dashboard view, auth guard, sign-out

    Recommendations

    • [SCOPE] Create architecture note arch-rails-app for component rails-app
    • [SCOPE] Create SOP note sop-keycloak-client-creation (referenced in issue body but does not exist)
    • [DECOMPOSE] 14 files, 10 AC — route to skill-decompose-ticket for automated sub-ticket creation
Project Page 1
  • pal-enterprises project-pal-enterprises

    Vision

    The front door to the pal-e platform. A single Rails app that serves two audiences: visitors see a landing page with services overview and a contact form; authenticated users land on a dashboard linking every platform tool. This replaces pal-e-hub and becomes the SSO entry point — log in once here, and you're logged into everything.

    User Stories

    Key Story Note Role Success Metric
    landing-page TBD Visitor Can understand services offered and submit contact form
    sso-gateway TBD Authenticated user Single login grants access to all platform tools
    tool-dashboard TBD Authenticated user Dashboard shows all tools with live status
    client-portal TBD Client Client can view their project board and active work via read-only agency link

    Architecture

    Architecture notes TBD:

    • Domain Model — arch-domain-pal-enterprises
    • Data Flow — arch-dataflow-pal-enterprises
    • Deployment — arch-deployment-pal-enterprises

    Key decisions:

    • Keycloak OIDC via omniauth-openid-connect gem, pal-e realm
    • No local user table — Keycloak is the auth source of truth
    • Contact form is stateless — email delivery only, no DB storage
    • Replaces pal-e-hub (SvelteKit) with Rails-native auth flow

    Board

    See board-pal-enterprises

    Status

    Bootstrapping. Repo created on Forgejo. README committed. Issue #1 filed. No code yet.

    Milestones

    None yet.

    Repos

    Repo Platform Role Status
    pal-enterprises Forgejo Application Bootstrapping
Convention 1
  • Convention: Client Project Structure convention-client-project-structure

    Convention: Client Project Structure

    Every client on the pal-e platform gets four surfaces. pal-enterprises itself dogfoods the same pattern.

    Rule

    Every client gets four surfaces: (1) a login on pal-enterprises with a role-aware dashboard, and three URLs linked from that dashboard: (2) [business]-docs — a separate RoR repo rendering project documentation, user stories, and mermaid diagrams (plain CSS via ror-css-guide, no Tailwind), (3) [business]-agency — read-only access to their pal-e-docs project page showing kanban, tickets, and Forgejo issues, and (4) [business]-app — a RoR monolith serving the public landing page, business owner admin view, and customer dashboard. Docs are built FIRST — they are the contract between Lucas, AI, and the client. Agency comes second. App comes third.

    Rationale

    Docs-first means alignment before code. Before any parallel work begins, Lucas, AI agents, and the client reach shared understanding of user stories, system architecture, and project scope through the docs app. The agency surface gives the client ongoing visibility into work in progress without needing to learn developer tools. The app is a monolith because Rails makes role-gated views natural — admin and customer experiences in one repo, not separate deployments. pal-enterprises follows the identical pattern one level up: it IS the SSO gateway and dashboard for Lucas's own business, the same way each client's surfaces serve theirs.

    Examples

    Correct Incorrect Why
    Build westside-docs first as a separate RoR repo with plain CSS. Align on user stories and diagrams before coding the app. Jump straight into building the Westside app and add docs later. Docs are the contract. Without alignment, parallel work drifts.
    westside-agency links to the Westside project page in pal-e-docs. Marcus sees kanban and tickets read-only. Build a custom admin dashboard for Marcus to track project progress. pal-e-docs already has the project page and kanban. No need to rebuild it — just give scoped access.
    westside-app is one RoR repo. Marcus logs in → admin view. Parents log in → their dashboard. Public visitors → landing page. Separate westside-admin and westside-app deployments. Rails monolith with role-gated views. One repo, one deployment, multiple experiences.

    Dogfooding — pal-enterprises:

    Surface pal-enterprises (Lucas) Client (e.g., Westside / Marcus)
    SSO gateway pal-enterprises.app — prospective clients land here, submit contact form, book appointment Client logs into pal-enterprises, sees their dashboard
    docs pal-enterprises-docs — platform vision, architecture, what pal-enterprises will be westside-docs — program docs, user stories, diagrams
    agency pal-e-agency — pal-enterprises project page in pal-e-docs westside-agency — Westside project page in pal-e-docs
    app pal-enterprises.app itself (the SSO gateway IS the app) westside-app — public landing + admin + customer views

    Contact form (pal-enterprises): Mandatory fields are business name, owner's name, logo, and email. No self-serve registration — submission redirects to a calendar page to book an appointment with Lucas. Accounts are provisioned only after the appointment.

    Docs app conventions:

    • Separate RoR repo per client — fresh Rails project
    • No Tailwind — uses ~/ror-css-guide (plain CSS, design tokens, mobile-first)
    • Renders markdown + mermaid diagrams
    • Behind Keycloak auth via pal-enterprises SSO
    • Client has access — this is the shared understanding, not internal docs

    Kanban philosophy: A kanban session begins and ends the same day. All its tickets complete in one focused session. This avoids the overhead of context re-startup. Kanban boards in pal-e-docs are focused work sessions, not open-ended backlogs.

    Enforcement

    SOP-enforced — service-onboarding-sop includes the four-surface deployment as part of client provisioning. Docs-first build order is convention only — depends on discipline.

    • service-onboarding-sop — the procedure that provisions surfaces
    • project-pal-enterprises — the platform front door (dogfooding instance)
    • project-westside-basketball — reference client implementation
    • convention-kustomize-overlay — how each surface's deployment overlay is structured
Board 1