12 KiB
Workspace Install Docs Fixture Self-Containment Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Make the workspace-install documentation verifier pass from a clean environment without inheriting TMPDIR or THT_AUTH_CONFIG_ROOT.
Architecture: Keep the existing Bash verifier and test runner structure. The test runner removes both ambient variables at the production-verifier boundary; the verifier supplies its own safe temporary-root fallback and creates one empty authentication-config directory for each Compose-rendering fixture, then proves the rendered core mount has the exact source, target, and read-only mode.
Tech Stack: Bash (set -euo pipefail), Docker Compose configuration rendering through scripts/compose-with-preflight.sh, inline Node.js JSON assertions, Git.
Global Constraints
- Modify only
scripts/verify-workspace-install-docs.shandscripts/test-verify-workspace-install-docs.shduring implementation. - Do not modify Compose, authentication runtime code, documentation examples, the PSD server, Docker resources, or the legacy stack.
- Every production-verifier
mktempcall must use/tmpwhenTMPDIRis absent and must continue to honor an explicitly providedTMPDIR. - Do not mutate or export the caller's environment.
- Each of the local installation, server installation, and canonical Compose fixtures must own a distinct empty authentication-config directory inside its fixture root.
- Each generated fixture environment must set
THT_AUTH_CONFIG_ROOTexplicitly. - Each Compose fixture must assert the exact authentication mount source, target
/run/thothii-auth, andread_only: trueon the rendered core service. - Temporary cleanup must remain bounded to directories returned by
mktemp. - No command in this plan starts, stops, reloads, builds, or modifies the PSD or legacy server stack.
Task 1: Make the verifier fixtures self-contained
Files:
- Modify:
scripts/test-verify-workspace-install-docs.sh:11 - Modify:
scripts/verify-workspace-install-docs.sh:917,1066,1865-1946,1948-2050,2133-2215 - Test:
scripts/test-verify-workspace-install-docs.sh
Interfaces:
-
Consumes:
compose.yamlrequires host variableTHT_AUTH_CONFIG_ROOTand renders it as the core bind mount target/run/thothii-authwith read-only mode. -
Produces:
scripts/verify-workspace-install-docs.sh --fixtures-onlysucceeds withTMPDIRandTHT_AUTH_CONFIG_ROOTabsent; every generated Compose fixture supplies and verifies its own authentication mount source. -
Step 1: Add the clean-environment regression boundary
Change only the production-verifier invocation near the beginning of scripts/test-verify-workspace-install-docs.sh:
-"$root/scripts/verify-workspace-install-docs.sh" --fixtures-only >"$output"
+env -u TMPDIR -u THT_AUTH_CONFIG_ROOT \
+ "$root/scripts/verify-workspace-install-docs.sh" --fixtures-only >"$output"
- Step 2: Run the regression test and retain the first RED result
Run:
env -u TMPDIR -u THT_AUTH_CONFIG_ROOT \
bash scripts/test-verify-workspace-install-docs.sh
Expected: non-zero exit; stderr contains TMPDIR: unbound variable. This proves the test reaches the production verifier with no ambient temporary root.
- Step 3: Add the production-verifier temporary-root fallback
Replace the five unsafe mktemp calls in scripts/verify-workspace-install-docs.sh; leave the calls that already use ${TMPDIR:-/tmp} unchanged:
- update_fixture="$(mktemp -d "${TMPDIR%/}/thoth-source-update.XXXXXX")"
+ update_fixture="$(mktemp -d "${TMPDIR:-/tmp}/thoth-source-update.XXXXXX")"
- repair_root="$(mktemp -d "${TMPDIR%/}/thoth-crlf-repair.XXXXXX")"
+ repair_root="$(mktemp -d "${TMPDIR:-/tmp}/thoth-crlf-repair.XXXXXX")"
- fixture="$(mktemp -d "${TMPDIR%/}/thoth local install.XXXXXX")"
+ fixture="$(mktemp -d "${TMPDIR:-/tmp}/thoth local install.XXXXXX")"
- fixture="$(mktemp -d "${TMPDIR%/}/thoth server install.XXXXXX")"
+ fixture="$(mktemp -d "${TMPDIR:-/tmp}/thoth server install.XXXXXX")"
- fixture="$(mktemp -d "${TMPDIR%/}/thoth-install-fixtures.XXXXXX")"
+ fixture="$(mktemp -d "${TMPDIR:-/tmp}/thoth-install-fixtures.XXXXXX")"
- Step 4: Run the test and retain the second RED result
Run:
env -u TMPDIR -u THT_AUTH_CONFIG_ROOT \
bash scripts/test-verify-workspace-install-docs.sh
Expected: non-zero exit after the earlier documentation fixtures progress; output contains THT_AUTH_CONFIG_ROOT. This isolates the remaining incomplete Compose-fixture input.
- Step 5: Make the local installation fixture own and verify its auth root
Apply these exact changes inside verify_local_installation_example:
- local fixture source_copy operator_dir copied_example env_file
+ local fixture source_copy operator_dir copied_example env_file auth_config_root
fixture="$(mktemp -d "${TMPDIR:-/tmp}/thoth local install.XXXXXX")"
trap 'rm -rf "$fixture"' RETURN
@@
source_copy="$fixture/ThothII source"
operator_dir="$fixture/operator files"
- mkdir -p "$source_copy/deploy/pi" "$operator_dir"
+ auth_config_root="$operator_dir/auth config"
+ mkdir -p "$source_copy/deploy/pi" "$operator_dir" "$auth_config_root"
@@
"THT_WORKSPACE_GIT_SSH_KEY_FILE=$operator_dir/git-ssh-key" \
"THT_WORKSPACE_GIT_KNOWN_HOSTS_FILE=$operator_dir/git-known-hosts" \
+ "THT_AUTH_CONFIG_ROOT=$auth_config_root" \
>"$env_file"
@@
- node - "$rendered" <<'NODE'
+ node - "$rendered" "$auth_config_root" <<'NODE'
const fs = require("fs");
-const config = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
+const [path, authConfigRoot] = process.argv.slice(2);
+const config = JSON.parse(fs.readFileSync(path, "utf8"));
if (Object.keys(config.services).sort().join(",") !== "core,embedding,embedding-model-init,frontend,qdrant") {
throw new Error("local installation example must render the internal semantic stack");
}
+const authMount = (config.services.core.volumes || []).find(
+ (mount) => mount.target === "/run/thothii-auth",
+);
+if (!authMount || authMount.source !== authConfigRoot || authMount.read_only !== true) {
+ throw new Error("local installation example must mount its fixture auth root read-only");
+}
const output = JSON.stringify(config);
- Step 6: Make the server installation fixture own and verify its auth root
Apply these exact changes inside verify_server_installation_example:
- local fixture source_copy operator_dir copied_example env_file backup_root
+ local fixture source_copy operator_dir copied_example env_file backup_root auth_config_root
fixture="$(mktemp -d "${TMPDIR:-/tmp}/thoth server install.XXXXXX")"
trap 'rm -rf "$fixture"' RETURN
@@
operator_dir="$fixture/server operator files"
backup_root="$fixture/server backups"
+ auth_config_root="$operator_dir/auth config"
mkdir -p "$source_copy/deploy/pi" "$source_copy/deploy/workspaces" \
- "$operator_dir/data/workspace-secrets" "$operator_dir/pi-state" "$operator_dir/workspace-registry" "$backup_root"
+ "$operator_dir/data/workspace-secrets" "$operator_dir/pi-state" "$operator_dir/workspace-registry" \
+ "$auth_config_root" "$backup_root"
@@
"THT_WORKSPACE_REGISTRY_ROOT=$operator_dir/workspace-registry" \
"THT_BACKUP_ROOT=$backup_root" \
+ "THT_AUTH_CONFIG_ROOT=$auth_config_root" \
"THT_SERVER_WORKSPACE_CONFIG=$source_copy/deploy/workspaces/server-sessions.yaml.example" \
@@
- node - "$rendered" <<'NODE'
+ node - "$rendered" "$auth_config_root" <<'NODE'
const fs = require("fs");
-const config = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
+const [path, authConfigRoot] = process.argv.slice(2);
+const config = JSON.parse(fs.readFileSync(path, "utf8"));
if (Object.keys(config.services).sort().join(",") !== "core,embedding,embedding-model-init,frontend,qdrant") {
throw new Error("server installation example must render the internal semantic stack");
}
const core = config.services.core;
const frontend = config.services.frontend;
+const authMount = (core.volumes || []).find((mount) => mount.target === "/run/thothii-auth");
+if (!authMount || authMount.source !== authConfigRoot || authMount.read_only !== true) {
+ throw new Error("server installation example must mount its fixture auth root read-only");
+}
- Step 7: Make the canonical local/server fixture own and verify its auth root
Apply these exact changes inside verify_compose_fixtures:
- local fixture profile rendered
+ local fixture profile rendered auth_config_root
fixture="$(mktemp -d "${TMPDIR:-/tmp}/thoth-install-fixtures.XXXXXX")"
trap 'rm -rf "$fixture"' RETURN
- mkdir -p "$fixture/data/workspace-secrets" "$fixture/pi-state" "$fixture/workspace-registry"
+ auth_config_root="$fixture/auth-config"
+ mkdir -p "$fixture/data/workspace-secrets" "$fixture/pi-state" \
+ "$fixture/workspace-registry" "$auth_config_root"
@@
"THT_PI_STATE_ROOT=$fixture/pi-state" \
"THT_WORKSPACE_REGISTRY_ROOT=$fixture/workspace-registry" \
+ "THT_AUTH_CONFIG_ROOT=$auth_config_root" \
"THT_SERVER_WORKSPACE_CONFIG=$fixture/server-sessions.yaml" \
@@
- node - "$rendered" "$profile" <<'NODE'
+ node - "$rendered" "$profile" "$auth_config_root" <<'NODE'
const fs = require("fs");
-const [path, profile] = process.argv.slice(2);
+const [path, profile, authConfigRoot] = process.argv.slice(2);
const config = JSON.parse(fs.readFileSync(path, "utf8"));
@@
const core = config.services.core;
+const authMount = (core.volumes || []).find((mount) => mount.target === "/run/thothii-auth");
+if (!authMount || authMount.source !== authConfigRoot || authMount.read_only !== true) {
+ throw new Error(profile + ": core must mount its fixture auth root read-only");
+}
for (const target of [
- Step 8: Run the focused regression test to GREEN
Run:
env -u TMPDIR -u THT_AUTH_CONFIG_ROOT \
bash scripts/test-verify-workspace-install-docs.sh
Expected: exit 0 with all existing positive and negative fixture checks accepted; no TMPDIR or THT_AUTH_CONFIG_ROOT error.
- Step 9: Run direct acceptance and syntax checks
Run:
env -u TMPDIR -u THT_AUTH_CONFIG_ROOT \
bash scripts/verify-workspace-install-docs.sh --fixtures-only
bash -n scripts/verify-workspace-install-docs.sh
bash -n scripts/test-verify-workspace-install-docs.sh
bash scripts/auth-docs-smoke.sh
Expected: both verifier commands and both syntax checks exit 0; auth-docs-smoke.sh prints its PASS summary.
- Step 10: Verify scope, whitespace, and absence of unsafe expansions
Run:
git diff --check
git status --short
rg -n '\$\{TMPDIR%/\}' scripts/verify-workspace-install-docs.sh
git diff -- scripts/verify-workspace-install-docs.sh scripts/test-verify-workspace-install-docs.sh
Expected: git diff --check exits 0; git status --short lists only the two implementation scripts; the rg command exits 1 with no matches; the diff contains only the clean-environment regression call, five temporary-root fallbacks, three fixture-owned auth roots/environment entries, and three rendered-mount assertions.
- Step 11: Commit the verified fix
git add scripts/verify-workspace-install-docs.sh scripts/test-verify-workspace-install-docs.sh
git commit -m "test: make install docs fixtures self-contained"
Expected: one commit containing exactly the two implementation files. Do not push, merge, or change the detached-HEAD state.