27 KiB
DWH REST Per-Installation Authentication 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: Build, document, and safely activate a reusable server-side dwh-auth service that gives every remote ThothII installation an independently revocable /dwh/ API key without changing portable ThothII connector behavior.
Architecture: A standalone Linux Go binary uses only the standard library, stores versioned credential digests in protected atomic files, and serves one fail-closed verification endpoint over a Unix socket. Host Nginx uses that endpoint through auth_request; PSD runs the binary as an independent hardened systemd service, while the local ThothII server continues to use postgres_direct.
Tech Stack: Go 1.26.0/toolchain 1.26.5, standard library only, Linux systemd, Nginx auth_request, Bash verification scripts, Markdown documentation, Git.
Global Constraints
- Preserve the portable REST contract: successful clients continue to send exactly one
X-API-Keyheader. - Do not modify
postgres_directorssh_tunnel, and do not add DWH commands to portabletht. - Do not add
dwh-authto ThothII Compose ortht start/stop. - Build Linux amd64/arm64 only; Mac and Windows do not need this binary.
- Use only the Go standard library; do not add SQLite, CGO, or runtime packages.
- New keys use
thtdwh_v1.<16-character-base64url-key-id>.<43-character-base64url-secret>with 256 random secret bits. - Permit one temporary unversioned
legacy_rawrecord with public IDlegacy-shared; revoke it after Mac migration. - Store SHA-256 digests and non-secret metadata only; never emit digests through CLI or HTTP.
- Default to no expiry; optional expiry is RFC3339 UTC and fail-closed.
- Read/write real keys only through absolute protected files. Never put keys in argv, env, Git, logs, JSON output, or ordinary config.
- Credential failures are generic
401; service/registry/integrity failures become503. - PSD uses independent
systemdplus a Unix socket. Stopping ThothII must not stop DWH auth. - Do not preserve/migrate legacy test sessions, Qdrant indexes, or Ollama caches.
- Do not stop or modify the legacy ThothII stack in this plan.
- Do not mutate PSD Nginx,
systemd, registry paths, or real keys before Task 9 authorization. - When delegated, use Luna for deterministic execution and Terra for review. The primary executor owns secret-bearing production steps and reports sanitized results.
- Keep unrelated
brain/changes and other dirty content out of every commit.
Task 1: Freeze credential and record contracts
Files:
- Create:
tools/dwh-auth/go.mod - Create:
tools/dwh-auth/internal/credential/credential.go - Create:
tools/dwh-auth/internal/credential/credential_test.go - Create:
tools/dwh-auth/internal/record/record.go - Create:
tools/dwh-auth/internal/record/record_test.go
Interfaces:
-
Consumes: Go standard-library randomness, hashing, constant-time comparison, base64url, JSON, and time.
-
Produces:
credential.Generate(io.Reader) (Material, error),credential.VerifyV1([]byte, record.Digest) (string, bool),credential.VerifyLegacy([]byte, record.Digest) bool,record.Validate(Record) error, and the record JSON schema. -
Step 1: Add the isolated module
module github.com/aritmolab/thothii/tools/dwh-auth
go 1.26.0
toolchain go1.26.5
- Step 2: Write failing format and validation tests
Freeze:
const Prefix = "thtdwh_v1"
const KeyIDEncodedLength = 16
const SecretEncodedLength = 43
const MaxHeaderBytes = 128
type Material struct {
Value []byte
KeyID string
Digest record.Digest
}
Tests use a deterministic 44-byte reader and assert exact segments/lengths, 32 decoded secret bytes, valid digest, changed-byte rejection, comma/duplicate rejection, padding/whitespace rejection, and 129-byte rejection. Legacy tests hash opaque raw values without v1 parsing. Record tests cover every invalid enum, identifier, timestamp, digest, description, expiry, and revocation combination.
- Step 3: Prove RED
cd tools/dwh-auth
go test ./internal/credential ./internal/record -count=1
Expected: non-zero because implementations are absent.
- Step 4: Implement the exact record schema
type Kind string
const (
SchemaVersion = 1
KindV1 Kind = "per_installation_v1"
KindLegacyRaw Kind = "legacy_raw"
LegacyKeyID = "legacy-shared"
)
type Digest [32]byte
type Record struct {
SchemaVersion int `json:"schema_version"`
Kind Kind `json:"credential_kind"`
KeyID string `json:"key_id"`
InstallationID string `json:"installation_id"`
Description string `json:"description,omitempty"`
SecretSHA256 string `json:"secret_sha256"`
CreatedAt time.Time `json:"created_at"`
ExpiresAt *time.Time `json:"expires_at,omitempty"`
RevokedAt *time.Time `json:"revoked_at,omitempty"`
RevocationReason string `json:"revocation_reason,omitempty"`
}
Require schema 1; exact kind/key relationship; installation ID
[a-z0-9][a-z0-9-]{0,62}; metadata at most 160 UTF-8 characters without controls;
canonical 32-byte base64url digest; UTC timestamps; expiry after creation; paired revocation fields.
- Step 5: Implement generation and verification
Read 12 random key-ID bytes and 32 secret bytes, use base64.RawURLEncoding, emit the 70-byte
canonical value, hash the complete value, and compare via subtle.ConstantTimeCompare.
Legacy accepts 1–128 opaque bytes without ASCII controls and hashes the entire raw value.
- Step 6: Prove GREEN and standard-library-only
cd tools/dwh-auth
gofmt -w internal/credential internal/record
go test ./internal/credential ./internal/record -count=1
go list -deps ./... | grep -v '^github.com/aritmolab/thothii/tools/dwh-auth' | grep '\.'
Expected: tests pass; dependency scan exits 1 with no third-party path.
- Step 7: Commit
git add tools/dwh-auth/go.mod tools/dwh-auth/internal/credential tools/dwh-auth/internal/record
git commit -m "feat: define DWH installation credentials"
Task 2: Implement the protected atomic registry
Files:
- Create:
tools/dwh-auth/internal/securefile/securefile_linux.go - Create:
tools/dwh-auth/internal/securefile/securefile_linux_test.go - Create:
tools/dwh-auth/internal/registry/store.go - Create:
tools/dwh-auth/internal/registry/store_test.go
Interfaces:
-
Consumes: Task 1 records and key IDs.
-
Produces:
registry.Open,Store.Add,Store.Find,Store.FindLegacy,Store.List,Store.Revoke, andStore.Check. -
Step 1: Write failing security tests
Cover regular protected files; symlinked roots/directories/records/secret files; unsafe modes;
unknown/duplicate/trailing JSON; filename mismatch; partial files; concurrent reads; and revoked
state winning when both records exist. Use t.TempDir and Linux build tags.
- Step 2: Prove RED
cd tools/dwh-auth
go test ./internal/securefile ./internal/registry -count=1
- Step 3: Implement no-follow bounded reads/writes
Use syscall.Open with O_NOFOLLOW|O_CLOEXEC, compare Fstat/Lstat, require regular files,
reject group/world write, cap record reads at 4096 bytes, and detect size changes. Secret input is
0600; generated output uses O_CREAT|O_EXCL and 0600.
- Step 4: Implement state contracts
type State string
const (
StateActive State = "active"
StateRevoked State = "revoked"
)
type PublicRecord struct {
Kind record.Kind `json:"credential_kind"`
KeyID string `json:"key_id"`
InstallationID string `json:"installation_id"`
Description string `json:"description,omitempty"`
CreatedAt time.Time `json:"created_at"`
ExpiresAt *time.Time `json:"expires_at,omitempty"`
State State `json:"state"`
RevokedAt *time.Time `json:"revoked_at,omitempty"`
RevocationReason string `json:"revocation_reason,omitempty"`
}
Add uses exclusive temp, canonical JSON plus newline, record mode 0640, sync, rename, and
directory sync. Revoke publishes revoked state before removing active state. Find checks
revoked first.
FindLegacy allows at most one legacy_raw; multiple records are an integrity fault.
- Step 5: Prove GREEN
cd tools/dwh-auth
gofmt -w internal/securefile internal/registry
go test -race ./internal/securefile ./internal/registry -count=1
- Step 6: Commit
git add tools/dwh-auth/internal/securefile tools/dwh-auth/internal/registry
git commit -m "feat: add protected DWH credential registry"
Task 3: Add the secret-safe administrative CLI
Files:
- Create:
tools/dwh-auth/internal/command/command.go - Create:
tools/dwh-auth/internal/command/command_test.go - Create:
tools/dwh-auth/cmd/dwh-auth/main.go
Interfaces:
-
Consumes: Tasks 1–2.
-
Produces:
command.Run(context.Context, []string, io.Reader, io.Writer, io.Writer) int. -
Step 1: Write failing CLI tests
Freeze exits 0 success, 2 unsafe invocation, 3 not found, 4 integrity/filesystem.
Prove create writes once to new 0600; import reads 0600; no overwrite; list/status omit digest;
revoke requires reason; relative/secret-valued flags fail; sentinel secret never enters output.
- Step 2: Prove RED
cd tools/dwh-auth
go test ./internal/command -count=1
- Step 3: Implement only this grammar
dwh-auth --registry-root ABS key create --installation-id ID [--description TEXT] [--expires-at RFC3339] --output ABS
dwh-auth --registry-root ABS key import --legacy-raw --installation-id legacy-shared --from-file ABS
dwh-auth --registry-root ABS key list [--json]
dwh-auth --registry-root ABS key status --key-id ID [--json]
dwh-auth --registry-root ABS key revoke --key-id ID --reason TEXT
dwh-auth --registry-root ABS check [--json]
dwh-auth serve --registry-root ABS --socket ABS
No aliases, env fallback, interactive secret, or plaintext output. Create prints only
created key_id=<id> installation_id=<id> output=<path>.
- Step 4: Implement failure rollback
Create writes/syncs protected output before record publication and removes output if publication fails. Import never modifies its source. Cleanup uncertainty returns exit 4 and path only.
- Step 5: Prove GREEN
cd tools/dwh-auth
gofmt -w cmd internal/command
go test -race ./internal/command -count=1
go test ./... -count=1
- Step 6: Commit
git add tools/dwh-auth/cmd tools/dwh-auth/internal/command
git commit -m "feat: add DWH credential administration CLI"
Task 4: Implement Unix-socket verification
Files:
- Create:
tools/dwh-auth/internal/service/service.go - Create:
tools/dwh-auth/internal/service/service_test.go - Modify:
tools/dwh-auth/internal/command/command.go - Modify:
tools/dwh-auth/internal/command/command_test.go
Interfaces:
-
Consumes: Tasks 1–3.
-
Produces:
service.New(*registry.Store, *log.Logger, func() time.Time) http.Handler,service.ListenAndServe(context.Context, Config) error, andGET /verify. -
Step 1: Write failing decision tests
Valid v1=204/key ID; legacy=204/legacy ID; absent/duplicate/malformed/unknown/changed/expired/ revoked=401; corrupt/unsafe/multiple-legacy=503; other path=404; other method=405. Bodies empty. Logs contain timestamp, decision code, parsed public ID only—never secret/digest/description/query.
- Step 2: Prove RED
cd tools/dwh-auth
go test ./internal/service -count=1
- Step 3: Implement handler/listener
Require exactly one header value and 128-byte bound. Try strict v1 then reserved legacy for non-v1.
Require canonical absolute socket/registry paths, refuse non-socket collision, remove only a proven
owned stale socket, chmod 0660, and stop on context cancellation.
- Step 4: Wire
servefail-closed
Dispatch serve before administrative write access. Open registry read-only, run Check, and
refuse startup on malformed or unsafe state. A well-formed expired record remains valid registry
data but always authenticates as denied.
- Step 5: Verify
cd tools/dwh-auth
gofmt -w cmd internal/command internal/service
go test -race ./... -count=1
go vet ./...
- Step 6: Commit
git add tools/dwh-auth/cmd tools/dwh-auth/internal/command tools/dwh-auth/internal/service
git commit -m "feat: serve DWH authentication over Unix socket"
Task 5: Add reproducible packaging and hardened templates
Files:
- Create:
docker/dwh-auth.Dockerfile - Create:
scripts/build-dwh-auth.sh - Create:
scripts/test-dwh-auth-build-contract.sh - Create:
deploy/dwh-auth/dwh-auth.service - Create:
deploy/dwh-auth/dwh-auth.tmpfiles.conf - Create:
deploy/dwh-auth/nginx-http.conf.example - Create:
deploy/dwh-auth/nginx-dwh-location.conf.example
Interfaces:
-
Consumes: Task 4 binary.
-
Produces: static Linux amd64/arm64 outputs and generic deployment templates.
-
Step 1: Write failing contract
Require the exact pinned builder digest from docker/tht.Dockerfile, no require in go.mod,
non-empty ELF outputs, and service directives User=dwh-auth, Group=www-data,
SupplementaryGroups=dwh-auth, NoNewPrivileges=true, ProtectSystem=strict,
ProtectHome=true, RestrictAddressFamilies=AF_UNIX, empty CapabilityBoundingSet, UMask=0007.
Tmpfiles creates root/active/revoked as root:dwh-auth mode 2750.
- Step 2: Prove RED
bash scripts/test-dwh-auth-build-contract.sh
- Step 3: Add static builds
Docker runs tests, then builds only linux/amd64 and linux/arm64 with CGO_ENABLED=0, -trimpath,
-ldflags='-s -w'. Build script accepts --output ABSOLUTE_DIRECTORY, rejects unsafe paths, and
defaults to dist/dwh-auth.
- Step 4: Add systemd/storage templates
ExecStart=/usr/local/sbin/dwh-auth serve --registry-root /var/lib/dwh-auth --socket /run/dwh-auth/verify.sock
Service writes only RuntimeDirectory=dwh-auth, reads registry, and has no secret environment.
- Step 5: Add Nginx templates
HTTP map/zone rate key contains only remote address plus parsed public ID, never secret; rate is
20/s. Location uses Unix auth, GET /verify, no body, uses a trailing-slash upstream so public /dwh/rpc/ping?x reaches PostgREST as /rpc/ping?x, clears X-API-Key before PostgREST, burst
100, keeps the public /dwh/ authorization boundary while stripping that prefix upstream, maps auth infrastructure failure to 503.
- Step 6: Prove GREEN
bash -n scripts/build-dwh-auth.sh
bash -n scripts/test-dwh-auth-build-contract.sh
bash scripts/test-dwh-auth-build-contract.sh
Expected final output: dwh-auth build and deployment contract passed.
- Step 7: Commit
git add docker/dwh-auth.Dockerfile scripts/build-dwh-auth.sh scripts/test-dwh-auth-build-contract.sh deploy/dwh-auth
git commit -m "build: package standalone DWH authentication service"
Task 6: Gate Nginx and CI behavior
Files:
- Create:
scripts/test-dwh-auth-nginx-integration.sh - Create:
scripts/test-dwh-auth-nginx-contract.sh - Modify:
.github/workflows/deployment.yml
Interfaces:
-
Consumes: Tasks 1–5.
-
Produces: structural/runtime Nginx gates and
dwh-auth-linuxCI. -
Step 1: Write failing structural checks
Require effective (non-comment) directives:
auth_request /_check_dwh_key;
proxy_method GET;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_set_header X-API-Key $http_x_api_key;
proxy_set_header X-API-Key "";
error_page 500 =503 @dwh_auth_unavailable;
Negative fixtures remove each and test public verifier, TCP authenticator, PostgREST bypass, full secret in rate key, and failure mapped to success.
- Step 2: Prove RED
bash scripts/test-dwh-auth-nginx-contract.sh
- Step 3: Implement runtime smoke
Use one temp root, synthetic v1/legacy keys, temp auth/Nginx Unix sockets, and a static marker behind auth. Assert v1=200/body unchanged, legacy=200, invalid=401, revoked=401, expired=401, stopped authenticator=503. Trap only recorded PIDs and exact temp root.
- Step 4: Add CI job
Checkout without credentials, Go 1.26.5 keyed by tools/dwh-auth/go.mod, install Ubuntu
nginx-light, then:
(cd tools/dwh-auth && go test -race ./... -count=1 && go vet ./...)
bash scripts/test-dwh-auth-build-contract.sh
bash scripts/test-dwh-auth-nginx-contract.sh
bash scripts/test-dwh-auth-nginx-integration.sh
- Step 5: Run locally to GREEN
Expected: all exit 0; output includes case/status only.
- Step 6: Commit
git add scripts/test-dwh-auth-nginx-integration.sh scripts/test-dwh-auth-nginx-contract.sh .github/workflows/deployment.yml
git commit -m "test: gate DWH authentication integration"
Task 7: Write and verify operator documentation
Files:
- Create:
docs/install/dwh-auth-server.md - Create:
docs/install/dwh-auth-client-enrollment.md - Create:
docs/install/dwh-auth-tls.md - Create:
docs/operations/psd-dwh-auth-rollout.md - Create:
docs/testing/dwh-auth-manual-acceptance.md - Create:
docs/testing/evidence/psd-dwh-auth-rollout-report-template.md - Create:
scripts/verify-dwh-auth-docs.sh - Create:
scripts/test-verify-dwh-auth-docs.sh - Modify:
docs/install/local-workspace-registry.md - Modify:
docs/install/server-workspace-registry.md - Modify:
docs/install/psd-workspace-setup.md - Modify:
docs/guida-utente.md - Modify:
docs/index.md - Modify:
mkdocs.yml
Interfaces:
-
Consumes: Tasks 1–6.
-
Produces: generic server/client/TLS manuals, PSD runbook, manual acceptance, sanitized evidence, navigation, docs gates.
-
Step 1: Write failing docs fixtures
Require prerequisites/install/issue/deliver/GUI/headless/rotate/revoke/TLS/fingerprint/renewal/
rollback/401/503/evidence/uninstall. Reject key/digest literals, curl -k, disabled TLS, secrets
in env/argv, world-readable files, raw Nginx capture, and Compose coupling.
- Step 2: Prove RED
bash scripts/test-verify-dwh-auth-docs.sh
- Step 3: Write generic manuals
Document exact paths, identities/modes, safe commands, one key per installation, rotation
generations, optional expiry, protected delivery, GUI vault, headless API_KEY_FILE, /rpc/ping,
positive/negative checks, registry backup, revocation, troubleshooting.
- Step 4: Write TLS/PSD/evidence documents
Document current self-issued .it certificate and missing .com coverage; TLS_CA_FILE;
openssl x509 -noout -fingerprint -sha256; out-of-band confirmation; renewal order; never bypass
verification. PSD runbook mirrors Tasks 9–10 and excludes legacy test sessions/indexes/caches.
Evidence contains public IDs, paths, modes, timestamps, checksums, statuses, owners, approvals only.
- Step 5: Link existing manuals
Link local/server workspace docs, PSD setup, user guide, docs index, MkDocs. Explicitly state
postgres_direct and ssh_tunnel do not use these keys.
- Step 6: Prove GREEN
bash scripts/test-verify-dwh-auth-docs.sh
bash scripts/verify-dwh-auth-docs.sh
bash scripts/test-verify-workspace-install-docs.sh
bash scripts/auth-docs-smoke.sh
- Step 7: Commit
git add docs/install/dwh-auth-server.md docs/install/dwh-auth-client-enrollment.md docs/install/dwh-auth-tls.md docs/operations/psd-dwh-auth-rollout.md docs/testing/dwh-auth-manual-acceptance.md docs/testing/evidence/psd-dwh-auth-rollout-report-template.md scripts/verify-dwh-auth-docs.sh scripts/test-verify-dwh-auth-docs.sh docs/install/local-workspace-registry.md docs/install/server-workspace-registry.md docs/install/psd-workspace-setup.md docs/guida-utente.md docs/index.md mkdocs.yml
git commit -m "docs: explain per-installation DWH access"
Task 8: Run source and portability checkpoint
Files:
- Modify only after a focused test fails: files from Tasks 1–7.
- Record locally:
.artifacts/dwh-auth/source-verification.md.
Interfaces:
-
Consumes: complete candidate.
-
Produces: reviewed clean SHA eligible for PSD mutation gate.
-
Step 1: Run focused gates
(cd tools/dwh-auth && go test -race ./... -count=1 && go vet ./...)
bash scripts/test-dwh-auth-build-contract.sh
bash scripts/test-dwh-auth-nginx-contract.sh
bash scripts/test-dwh-auth-nginx-integration.sh
bash scripts/test-verify-dwh-auth-docs.sh
- Step 2: Run portability regressions
bash scripts/test-default-compose.sh
bash scripts/test-unified-compose.sh
bash scripts/test-no-deployment-coupling-scope.sh
bash scripts/test-no-deployment-coupling.sh
bash scripts/test-compose-secret-policy.sh
bash scripts/test-verify-workspace-install-docs.sh
(cd tools/tht && go test ./... -count=1)
Expected: all pass; Compose unchanged; tht has no DWH-key command; Mac/Windows need no new binary.
- Step 3: Check scope and leaks
git diff --check
git status --short
git log --oneline --decorate -8
rg -n --hidden --glob '!.git/**' --glob '!docs/superpowers/**' 'legacy-shared\.[A-Za-z0-9_-]|thtdwh_v1\.[A-Za-z0-9_-]{16}\.[A-Za-z0-9_-]{43}' .
Expected: whitespace clean; only intended evidence untracked; secret scan exits 1; reviewed commits.
- Step 4: Terra review
Give Terra design, plan, diff, tests. Require requirement matrix and severity-ranked findings. Resolve Important/Critical with focused tests and commits; record Minor.
- Step 5: Freeze
git rev-parse HEAD
git status --porcelain=v1
Expected: full SHA recorded; execution worktree clean. Do not push/merge/install here.
Task 9: Install on PSD without changing public Nginx
Files/objects:
- Install:
/usr/local/sbin/dwh-auth - Install:
/etc/systemd/system/dwh-auth.service - Install:
/usr/lib/tmpfiles.d/dwh-auth.conf - Create:
/var/lib/dwh-auth/active/,/var/lib/dwh-auth/revoked/,/run/dwh-auth/verify.sock - Protect:
/root/dwh-auth-provision/ - Evidence: owner-approved protected rollout directory
Interfaces:
-
Consumes: frozen Task 8 SHA/binary.
-
Produces: healthy local authenticator with legacy and Mac identities, disconnected from Nginx.
-
Step 1: Stop for explicit mutation authorization
Present SHA, results, exact targets, rollback, legacy-stack non-impact. Without explicit approval,
stop with Activity 1 IN_DISCUSSION.
- Step 2: Verify preconditions read-only
Require x86_64, Nginx group www-data, free target names, systemd metadata, current nginx -t,
current /dwh/ diagnostic, and regular root-owned 0600
/root/dwh-auth-provision/legacy-shared.key. Never print/hash that key.
- Step 3: Build/fingerprint frozen binary
bash scripts/build-dwh-auth.sh --output /tmp/dwh-auth-release
sha256sum /tmp/dwh-auth-release/dwh-auth-linux-amd64
Record binary/source SHA only.
- Step 4: Install service
Create no-login group/user; install binary 0755; install unit/tmpfiles 0644; run
systemd-tmpfiles --create dwh-auth.conf; verify owners/modes. Do not start until check passes.
- Step 5: Import legacy and issue Mac key
Import fixed protected legacy file with --legacy-raw --installation-id legacy-shared. Create
non-expiring psd-mac-primary in root-owned 0600
/root/dwh-auth-provision/psd-mac-primary.key. Capture public metadata only.
- Step 6: Start/test disconnected service
Run check, systemd-analyze verify, daemon-reload, enable/start, verify socket. Via protected curl
configs test new=204, legacy=204, random=401, absent=401 on Unix socket. Check bounded journal for
absence of both keys.
- Step 7: Record checkpoint
Retain checksums, public IDs/modes, hardening/socket metadata, statuses, rollback. Do not touch Nginx.
Task 10: Roll out dual-key Nginx and close Activity 1
Files/objects:
- Create:
/etc/nginx/conf.d/dwh-auth-rate-limit.conf - Modify:
/etc/nginx/sites-available/policlinicosandonato - Preserve: protected pre-change copies
- Update: Mac vault or protected
API_KEY_FILE - Update:
docs/operations/psd-server-survey-remediation-checklist.md - Complete: protected rollout evidence template
Interfaces:
-
Consumes: Task 9 checkpoint, both protected keys, CA/fingerprint, explicit Nginx approval.
-
Produces: per-installation
/dwh/, revoked legacy, verified Mac, Activity 1 PASS. -
Step 1: Stop for separate Nginx/client gate
Present files, backups, reload/rollback, delivery channel, observation duration, expected 204/401/503. Obtain explicit approval distinct from Task 9.
- Step 2: Prepare candidate
Create timestamped root-owned 0600 backups. Change only DWH auth plus rate map/zone. Preserve
upstream http://127.0.0.1:3001/; keep vector locations byte-identical; clear API key before
PostgREST; include no literal key.
- Step 3: Validate before reload
Run the structural checker and a secret scan that emits only PASS/FAIL metadata, install candidates,
and run sudo nginx -t. Never run or retain a raw diff, nginx -T, or configuration dump: a legacy
Nginx file can contain the exposed key. On failure restore backups before reload and record sanitized FAIL.
- Step 4: Reload/prove dual-key
Through real .it HTTPS and approved CA test /dwh/rpc/ping via file header curl protetti 0600:
pre-revoke v1=2xx, legacy=2xx, random=401, missing=401. Roll back on response/TLS/unrelated
health change.
- Step 5: Deliver/configure Mac
Use approved protected channel. Verify CA fingerprint, configure vault or headless API_KEY_FILE,
run Validate workspace source, Test workspace connections, /rpc/ping. Record public IDs, fingerprint confirmation,
timestamp, result only.
- Step 6: Observe/revoke legacy
After approved window, revoke legacy-shared with reason shared-credential-rotation. New Mac
still succeeds; legacy returns 401. Bounded logs contain no key/digest.
- Step 7: Verify rollback scope
Prove Nginx backups/commands readable. Do not preserve/migrate/restore/stop legacy ThothII data or stack.
- Step 8: Close only Activity 1
Complete sanitized evidence. Set PASS only with new success, legacy 401, service/Nginx PASS, clean
logs, rollback, owner acceptance. Advance Current activity to 2; overall remains SURVEY_NO_GO.
- Step 9: Commit non-secret status
git add docs/operations/psd-server-survey-remediation-checklist.md PROJECT_STATE.md
git commit -m "docs: record PSD DWH credential rotation"
Expected: public IDs/dates/evidence references/statuses only; no keys, digests, certificate body, connection string, raw log, or protected evidence.
Final execution boundary
Task 10 closes only Activity 1. It does not authorize Project A, stop legacy ThothII, or install the
new application. Resume Activity 2 and remaining blockers before a fresh SURVEY_GO.