diff --git a/docs/plans/2026-08-18-thothii-authentication-acceptance-and-psd-deployment.md b/docs/plans/2026-08-18-thothii-authentication-acceptance-and-psd-deployment.md new file mode 100644 index 00000000..ef0bc511 --- /dev/null +++ b/docs/plans/2026-08-18-thothii-authentication-acceptance-and-psd-deployment.md @@ -0,0 +1,403 @@ +# ThothII Authentication Acceptance and PSD Deployment Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to execute this plan task-by-task. + +**Goal:** Validate local and OIDC authentication on macOS, deploy the exact feat/thoth-auth candidate to the Aritmolab/PSD server before merging it into main, and complete end-to-end acceptance with remote Authentik. + +**Architecture:** Test the candidate first as a standalone local installation. Then install the same immutable Git revision on the existing PSD installation with the installation-aware thothctl lifecycle, leaving main untouched. Authentik provides OIDC login and a mandatory direct groups claim; ThothII maps exact external groups to roles and validates mapped groups through the Authentik catalog API. + +**Tech Stack:** macOS, Docker Desktop, Docker Compose, tht/thothctl, local Argon2id authentication, generic OIDC Authorization Code + PKCE, Authentik, PSD workspace registry, reverse proxy/TLS. + +--- + +## Scope and release rules + +Do not merge feat/thoth-auth into main until every mandatory gate in Task 9 is PASS and the PSD owner accepts the evidence. + +Capture one candidate revision and reuse it everywhere: + +```bash +export CANDIDATE_SHA="$(git rev-parse HEAD)" +git fetch origin feat/thoth-auth +test "$CANDIDATE_SHA" = "$(git rev-parse origin/feat/thoth-auth)" +git show -s --format='%H%n%P%n%s' "$CANDIDATE_SHA" +git status --short --untracked-files=all +``` + +Never deploy a moving branch name without checking its resolved SHA. Never put passwords, OIDC client secrets, Authentik API tokens, cookies, authorization headers, raw ID tokens, or password hashes in Git, shell history, screenshots, logs, or evidence. + +Use protected operator values for , , , , , , , and . + +The Authentik contract is mandatory: a direct non-empty JSON array claim named groups; exact groups TOT Users and TOT Admin; mappings TOT Users -> user and TOT Admin -> admin; and a separate group-view-only API service account exposed only as THT_AUTHENTIK_API_TOKEN. Extra upstream groups are valid and silently ignored. + +## Task 0: Freeze the candidate and collect approvals + +**Files:** None; record results in the acceptance report in Task 9. + +Run from the candidate worktree: + +```bash +git diff --check +go test ./... -count=1 +go test -race ./... +go vet ./... +go build ./... +``` + +Expected: all commands pass, the candidate is pushed, and existing evidence is bound to the same SHA. A historical result from another revision is not evidence for this run. + +Before touching PSD, obtain the maintenance window, server access, public URL, Authentik provider details, protected secret locations, test identities for ordinary/admin/unmapped users, and permission to test PSD DWH/Evidence connections. + +## Task 1: Prepare and start the local macOS installation + +**Files:** + +- Read: docs/install/local.md +- Read: docs/install/authentication-local.md +- Read: docs/testing/authentication-manual-acceptance.md +- Use: an untracked local installation descriptor and protected secret/password files + +**Step 1: Verify prerequisites** + +```bash +docker version +docker compose version +bash scripts/verify-line-endings.sh +``` + +Expected: Docker Desktop and Compose are available and line-ending validation passes. + +**Step 2: Build and configure** + +```bash +bash scripts/build-local.sh +bash scripts/build-thothctl.sh +tht setup --profile local +``` + +For an existing installation, do not overwrite data; run tht --installation update --check-only instead of setup. + +**Step 3: Start and inspect** + +```bash +tht --installation start --build +tht --installation status +tht --installation doctor --json +curl --fail http://127.0.0.1:8080/health +curl --fail http://127.0.0.1:8787/health +``` + +Expected: core, frontend, qdrant, embedding, and the completed model initializer are healthy; doctor includes authentication after configuration and before services. + +## Task 2: Configure and test local login + +**Files:** + +- Read: docs/install/authentication-local.md +- Modify only protected installation state through tht auth configure and tht auth user + +**Step 1: Bootstrap the administrator** + +```bash +tht --installation auth configure \ + --mode local --public-url http://127.0.0.1:8080 \ + --admin-user --admin-display-name \ + --password-file +``` + +Remove the temporary password file immediately. Expected: non-secret auth.yaml is created and the user store contains Argon2id hashes, never plaintext passwords. + +**Step 2: Add and inspect a normal user** + +```bash +tht --installation auth user add --role user --display-name --password-file +tht --installation auth status --json +tht --installation auth check --json +``` + +Expected: pristine redacted JSON and no credential, hash, or session secret in output. + +**Step 3: Test browser authorization** + +At http://127.0.0.1:8080, in a private browser profile: + +1. Verify unauthenticated access reaches login and protected routes are denied. +2. Log in as the normal user and verify application/session routes work. +3. Verify Pi Management and other admin-only operations return HTTP 403 or are not exposed. +4. Log out and verify the session is invalidated. +5. Log in as the administrator and verify admin-only routes work. + +Expected: ordinary users authenticate without receiving admin permissions; administrators receive the configured admin permission set. + +**Step 4: Test account failure paths** + +Use auth user disable, enable, set-password, and logout-all user --yes on the test user. Test a wrong password and refresh the old browser session after logout-all. + +Expected: generic safe failures, disabled login rejection, re-enabled login success, and forced reauthentication. The last enabled administrator cannot be disabled or demoted. + +## Task 3: Test remembered sessions and local recovery + +**Files:** + +- Read: docs/architecture/authentication.md, Browser sessions +- Read: docs/install/authentication-local.md, Session behavior and recovery + +**Step 1: Test browser restart** + +Log in as the normal user with Remember me, close the browser completely, reopen it, and revisit the application. + +Expected: the session survives within the 7-day idle / 30-day absolute limits. Do not record the cookie. + +**Step 2: Test ThothII restart** + +```bash +tht --installation stop +tht --installation start +``` + +Expected: the remembered session remains valid after backend restart. + +**Step 3: Test invalidation** + +Change the test user password or role, and separately run auth user logout-all user --yes. Refresh after each operation. + +Expected: affected sessions are rejected and reauthentication is required; configuration revision changes invalidate all sessions. + +Go/no-go: do not proceed to PSD if local login, role separation, logout, or remembered-session behavior fails. + +## Task 4: Snapshot the current PSD installation + +**Files:** + +- Read: docs/install/server.md +- Read: docs/install/server-workspace-registry.md +- Use: protected server operator and backup locations + +**Step 1: Capture live state** + +```bash +THTCTL= +INSTALLATION= +"$THTCTL" --installation "$INSTALLATION" status +"$THTCTL" --installation "$INSTALLATION" doctor +"$THTCTL" --installation "$INSTALLATION" pi status +"$THTCTL" --installation "$INSTALLATION" pi doctor +git -C /srv/thothii/source/ThothII status --short --untracked-files=all +git -C /srv/thothii/source/ThothII rev-parse HEAD +``` + +Save the live SHA as and capture image identities, workspace registry status, and maintenance/recovery state. Stop if the checkout is dirty or recovery is pending. + +**Step 2: Drain and back up** + +Announce maintenance, close the reverse proxy or show its maintenance page, drain active work, and stop through thothctl. Create the protected, checksummed backup specified in docs/install/server.md, including runtime trees and PSD PostgreSQL/session data where applicable. Back up credentials separately. Never run docker compose down --volumes. + +**Step 3: Check preconditions** + +```bash +git -C /srv/thothii/source/ThothII config --local core.autocrlf false +bash /srv/thothii/source/ThothII/scripts/verify-line-endings.sh +"$THTCTL" --installation "$INSTALLATION" update --check-only +``` + +Expected: descriptor, protected secrets, Pi-state mount, workspace repository binding, and Compose render remain valid before source changes. + +## Task 5: Deploy the feature revision to PSD without merging main + +**Files:** + +- Server source checkout: /srv/thothii/source/ThothII +- Server operator binary: protected THTCTL path +- Server installation descriptor and secret files: unchanged paths unless a reviewed auth update is required + +**Step 1: Select the exact candidate** + +```bash +git -C /srv/thothii/source/ThothII fetch origin feat/thoth-auth +git -C /srv/thothii/source/ThothII switch --detach +test "$(git -C /srv/thothii/source/ThothII rev-parse HEAD)" = "" +git -C /srv/thothii/source/ThothII status --short --untracked-files=all +``` + +Do not merge or rebase main. The running installation is intentionally based on the detached feature revision until acceptance completes. + +**Step 2: Build candidate artifacts** + +```bash +cd /srv/thothii/source/ThothII +bash scripts/build-local.sh +THT_THOTHCTL_OUTPUT_DIRECTORY=/srv/thothii/operator/build-output bash scripts/build-thothctl.sh +``` + +Install the architecture-appropriate candidate thothctl only after its build succeeds. Keep the old operator binary recoverable. + +**Step 3: Start and verify the candidate** + +```bash +"$THTCTL" --installation "$INSTALLATION" update --check-only +"$THTCTL" --installation "$INSTALLATION" start --build +"$THTCTL" --installation "$INSTALLATION" status +"$THTCTL" --installation "$INSTALLATION" doctor --json +curl --fail http://127.0.0.1:8080/health +"$THTCTL" --installation "$INSTALLATION" pi doctor +"$THTCTL" --installation "$INSTALLATION" pi test +``` + +Expected: candidate frontend/core and internal services are healthy, no data volume was replaced, and the candidate SHA is recorded. Liveness alone is not release approval. + +## Task 6: Configure and validate remote Authentik + +**Files:** + +- Modify protected server authentication state through tht auth configure +- Modify protected secret entries THT_OIDC_CLIENT_SECRET and THT_AUTHENTIK_API_TOKEN +- Read: docs/install/authentik.md and docs/install/authentication-oidc.md + +**Step 1: Verify Authentik** + +Verify the OAuth2/OIDC callback exactly /api/auth/oidc/callback, scopes openid/profile/email, direct groups array mapping, exact groups TOT Users and TOT Admin, and a separate group-view-only catalog service account. Inspect a disposable identity without copying its token. + +**Step 2: Configure the ThothII mapping** + +```bash +tht --installation "$INSTALLATION" auth configure \ + --mode oidc --public-url \ + --issuer --client-id \ + --authentik-base-url \ + --user-group 'TOT Users' --admin-group 'TOT Admin' +``` + +Secrets are read from protected files, never command-line arguments. Confirm the non-secret mapping is: + +```yaml +authorization: + groupRoles: + TOT Users: [user] + TOT Admin: [admin] +``` + +**Step 3: Run static and live checks** + +```bash +tht --installation "$INSTALLATION" auth status --json +tht --installation "$INSTALLATION" auth check --json +tht --installation "$INSTALLATION" auth check --interactive +tht --installation "$INSTALLATION" doctor --json +``` + +Expected: configuration, discovery, issuer, JWKS, client-secret access, catalog access, and exact existence of every mapped group pass. Doctor lists authentication after configuration and before services. No output contains credentials or bearer tokens. + +A missing mapped group must fail with redacted oidc_mapped_group_missing. Unmapped groups produce neither error nor warning. Missing, indirect, malformed, or overage-style groups claims fail closed. + +**Step 4: Reload if required** + +If configuration requires process reload: + +```bash +"$THTCTL" --installation "$INSTALLATION" pi restart --yes --drain +``` + +Repeat authentication, doctor, and health checks. Do not substitute raw Compose commands. + +## Task 7: Test PSD browser login and authorization + +**Files:** + +- Read: docs/testing/authentication-manual-acceptance.md +- Evidence: redacted report from Task 9 + +**Step 1: Ordinary user** + +In a private profile, authenticate with an identity in TOT Users but not TOT Admin. Verify callback success, application/session routes, denial of Pi Management/admin operations, opaque HttpOnly ThothII cookie, no bearer token in Web Storage, and logout invalidation. + +**Step 2: Administrator** + +Authenticate with TOT Admin. Verify Pi Management and allowed workspace-management operations. Access must derive from the exact mapped group, not a client-supplied header or browser-local flag. + +**Step 3: Unmapped and malformed groups** + +Authenticate with a valid token containing no mapped group. Expected: login may complete, but protected operations return 403 with no warning. Use a disposable provider mapping that omits or corrupts groups; expected: generic HTTP 401 oidc_callback_failed, with no internal claim details exposed. + +**Step 4: Provider outage/group drift** + +During a controlled window, make discovery/JWKS unavailable or rename a mapped group, run the CLI check, and restore it immediately. Expected: redacted fail-closed diagnostics followed by a successful check after restoration. Do not leave production broken. + +## Task 8: Test PSD workspace validation and real connections + +**Files:** + +- Read: docs/install/server-workspace-registry.md +- Use: authenticated PSD browser sessions + +**Step 1: Validate as administrator** + +In Workspace Management, select and run Validate workspace source. Expected: Git descriptor, Evidence/catalog invariants, and complete authentication readiness pass; result is redacted and identifies the workspace revision. + +**Step 2: Test connections** + +Run Test workspace connections for the selected workspace. Expected: protected DWH/Evidence secrets are used, status is redacted, and workspace source is unchanged. + +**Step 3: Test ordinary-user authorization** + +Log in as TOT Users. Confirm inspection follows ordinary permissions while validation, secret mutation, and connection tests remain unavailable unless explicitly granted. + +**Step 4: Run a harmless end-to-end smoke** + +As an authorized PSD user, create or resume one harmless known-good session: + +```text +browser login -> same-origin API -> workspace readiness -> Pi/core -> result -> logout +``` + +Do not run mutating production queries. Preserve only a session ID and redacted outcome if approved. + +## Task 9: Close acceptance, rollback if needed, and decide merge readiness + +**Files:** + +- Create: docs/testing/evidence/2026-08-18-thothii-authentication-psd-acceptance.md or the approved external evidence location +- Read: docs/install/server.md and docs/contracts/thothctl-pi.md + +**Step 1: Mandatory gates** + +| Gate | Required evidence | +|---|---| +| Candidate identity | Local and server SHA exactly match pushed feat/thoth-auth | +| Local startup | macOS Compose, doctor, health, and Pi smoke pass | +| Local auth | Bootstrap, ordinary/admin roles, logout, bad password, disable/enable, logout-all pass | +| Local session | Remembered session survives browser and ThothII restart; revisions invalidate it | +| Server safety | Old SHA/image/status captured; backup checksummed; maintenance/drain completed | +| Candidate deploy | Server candidate status/doctor/health pass | +| Authentik | Direct groups claim, issuer/JWKS, secrets, catalog, and mapped groups pass | +| OIDC authorization | ordinary, admin, unmapped, malformed, logout, and outage cases pass | +| Workspace integration | Validate workspace source and test connections pass | +| PSD smoke | One harmless known-good session completes | +| Hygiene | No secrets, tokens, cookies, hashes, or raw claims in evidence | + +**Step 2: Write the redacted report** + +Include candidate SHA, old SHA, timestamps, commands, browser cases, redacted diagnostic/HTTP codes, Authentik issuer/client/group names, workspace ID/revision, backup/checksum location, rollback decision, and unrelated CI failures. Never include secret values, raw tokens, cookies, or hashes. + +**Step 3: Roll back a failed candidate** + +1. Keep the proxy closed and preserve .thothctl// recovery state. +2. Do not use thothctl pi rollback as the whole-application rollback; it addresses only Pi lifecycle images. +3. Stop with thothctl. +4. Return the source checkout to , rebuild old application/operator artifacts, and start through the same descriptor. +5. Run update --check-only, status, doctor, health, Pi smoke, workspace diagnostics, and one harmless session. +6. For ambiguous recovery, leave maintenance active and follow pi maintenance status / pi maintenance recover --yes. Never delete volumes, selectors, or recovery files to force progress. + +Expected: the previous application serves again with prior data and workspace state intact. Record the failure and do not merge. + +**Step 4: Reopen traffic** + +After every gate passes, restore the reverse proxy, repeat one unauthenticated redirect and one authorized public login, and confirm only the proxy is externally reachable. + +**Step 5: Merge decision** + +Merge only after PSD owner acceptance, exact-SHA evidence, no unresolved auth/workspace/provider/deployment gate, and an accepted rollback path. If the merge creates a new commit, repeat Tasks 0, 5, 6, and 7 against the merge SHA. + +## Handoff checklist + +Deliver the redacted report, local result/SHA, PSD candidate SHA/images, Authentik provider and group mapping confirmation, workspace validation/connection results, backup/rollback status, and an explicit READY TO MERGE or NOT READY TO MERGE decision. +