docs(auth): document runtime projection operations

This commit is contained in:
User
2026-08-22 01:51:26 +02:00
parent 3d9a9f0675
commit ef7ae7053c
10 changed files with 415 additions and 1 deletions
+4
View File
@@ -61,6 +61,10 @@
`rest_api` acceptance, the 48-hour/two-ETL observation, and revocation of `legacy-shared` are
mandatory before `SURVEY_GO_PROJECT_B`. Current authorization covers read-only survey and
preparation only; old-stack stop and new-stack start remain forbidden.
- **Runtime-projection preparation (2026-08-22):** the source contract and hermetic gates prepare
root-only canonical authentication plus a separate `10001:10001` read-only core projection.
This is implementation preparation and test evidence only. Project A has not been started;
applying its descriptor or any server runtime root still needs explicit authorization.
### Authentication final-review fix round 2 — remediation PASS, release gates remain (2026-08-18)
+11
View File
@@ -68,3 +68,14 @@ values into tickets, logs, or evidence.
Check readiness with `tht auth check`; add `--json` for the machine contract. Use
`tht doctor --json` for the aggregate installation report.
## Projected server installations
This section applies only when a Linux `profile: server` descriptor declares a runtime projection.
The canonical authentication root stays root-owned and is the only authority. The container reads
only the separate read-only runtime projection selected by `CURRENT`; it never falls back to the
canonical files or to a previous generation. Run projected mutations and repairs through the
root-operated `tht` commands documented in the [server guide](server.md), and never edit runtime
files directly.
Mac, Windows, and local direct-file authentication remain unchanged when the projection is absent.
@@ -8,7 +8,13 @@ workspaceRepository:
branch: main
access: ssh
authentication:
configDirectory: "/absolute/path/to/thothii-auth"
# Root-operated source of truth; it is never mounted into core.
configDirectory: "/srv/example/thothii/auth-canonical"
runtimeProjection:
# The only authentication bind exposed to core by the automatic override.
directory: "/srv/example/thothii/auth-runtime"
uid: 10001
gid: 10001
overrides:
- "/absolute/path/to/ThothII/deploy/compose.session-server.yaml.example"
- "/absolute/path/to/ThothII/deploy/compose.git-ssh.yaml"
+67
View File
@@ -88,6 +88,73 @@ Expected: the parent is `operator_uid:10001 750`; source/operator are
`10001:10001 750`; backups are `0:0 700`. Re-run the empty `getent` checks after creation. Do not
make `/srv/thothii` a shared application directory.
## Projected server authentication: canonical root and runtime projection
For a server descriptor that declares `authentication.runtimeProjection`, authentication has two
different roots. The **canonical authentication root** (`authentication.configDirectory`) is the
root-operated source of truth. It and its regular files are `root:root 0700/0600`. The **runtime
projection** is a separate Linux-only tree for the container reader: its root, `generations`, and
generation directories are `10001:10001 0700`; `CURRENT`, `manifest.json`, `auth.yaml`, and (for
local mode) `users.yaml` are `10001:10001 0600`. The publisher assigns the numeric IDs directly;
it does not create a host user or group for 10001.
The runtime projection has only `CURRENT` and `generations/<64-lowercase-hex>/`. `CURRENT` selects
one complete immutable generation. A successful configure, user mutation, restore, or explicit
publish first blocks `CURRENT`, then verifies a new immutable generation, then makes it ready.
The selected generation and up to two predecessor generations are retained; no operator edits a
generation or `CURRENT` directly. A ready projection is usable only when its canonical revision is
equal to the current canonical authentication root. If the runtime projection is blocked, missing,
tampered, or unequal, `start`, `update --check-only`, `auth check`, and `doctor` fail closed before
admission or Compose lifecycle work.
The runtime directory must be an absolute canonical path, distinct from the canonical root, and
must exactly equal `THT_AUTH_RUNTIME_ROOT` in the protected installation environment. The server
profile and numeric UID/GID values are validated before any projected mutation or publication.
The descriptor loader adds `compose.auth-runtime-projection.yaml` automatically when
`runtimeProjection` is present; do not list that file under `overrides`. The automatic override
mounts the runtime projection **read-only and core-only** at `/run/thothii-auth`; the canonical
authentication root is never mounted. No other service receives that mount or
`THT_AUTH_RUNTIME_PROJECTION_ROOT`. The example descriptor uses
`/srv/example/thothii/auth-runtime` only as a replaceable path and contains no credential value.
This source change is prepared and tested only: Project A has not been started. It does not
authorize a raw Compose lifecycle launch, an Nginx change, legacy-stack change, or mutation of
`/srv`. A later manual gate needs separate explicit authorization before applying any descriptor
or runtime root to a server.
### Status, repair, and safe evidence
Use the root-operated installation command; retain only its small redacted JSON result:
```sh
sudo tht --installation "$INSTALLATION" auth status --json
```
`state: "ready"` and `equal: true` are required before a projected server can start. `state:
"blocked"`, `equal: false`, or a command refusal means that the runtime projection is blocked or
cannot be validated. Do not start the stack, inspect YAML, print an environment, or edit `CURRENT`
or a generation. Confirm the protected canonical root is available, then republish it with:
```sh
sudo tht --installation "$INSTALLATION" auth publish
sudo tht --installation "$INSTALLATION" auth status --json
```
`auth publish` reconstructs the selected immutable generation from the canonical root; it never
uses an older runtime generation as authority. If publish fails, leave the projection blocked and
escalate using the sanitized command result plus descriptor path and timestamp only. Do not attach
passwords, hashes, YAML, raw environment output, `nginx -T`, or a secret-bearing diff to evidence.
### Authentication restore
An authentication-bearing restore first publishes a blocked selector, restores canonical
authentication, and publishes a verified candidate generation before any restart. If candidate or
recovery verification fails, the verified recovery checkpoint is republished when possible; an
unverified result remains blocked and prevents start. A restore without authentication entries
does not touch the runtime projection. This is in addition to the normal restore requirement that
browser sessions and pending OIDC state are cleared.
## Firewall and network boundaries
Set `THOTH_SERVER_BIND=127.0.0.1`. Permit inbound TCP 80/443 only to the TLS proxy; port 80 should
@@ -251,6 +251,14 @@ Use `profile: server`, the exact new source root/env/auth root, workspace remote
access, Project A override, and exactly one Git transport override. Do not include the public
session-server overlay in Project A.
For the projected local-auth descriptor, keep `authentication.configDirectory` as the canonical
root and add `runtimeProjection` with a distinct absolute runtime directory plus numeric `uid: 10001`
and `gid: 10001`. The descriptor loader includes the automatic runtime-projection override; do
not list it manually under `overrides`. The canonical root stays `root:root 0700/0600`; the
publisher owns the projection numerically as `10001:10001 0700/0600`. Only the projection is
mounted read-only into core. Do not create a host user/group, edit `CURRENT` or `generations`, or
apply these paths before the separately authorized start gate.
**Step 4: Validate permissions and render**
```bash
@@ -274,6 +282,11 @@ Create a temporary mode-0600 password file using an echo-free prompt, then run:
Remove the temporary input file after success and record that removal. Do not delete generated
`auth.yaml` or `users.yaml`.
Before the later start gate, run the redacted projected status command and require `ready` plus
`equal: true`. A blocked result prevents start. `auth publish` is the only repair path: it rebuilds
from canonical authentication, including after a candidate or recovery restore outcome; it never
promotes a retained runtime generation on its own.
### Task 6: Build and start the clean stack
**Files:**
@@ -35,6 +35,13 @@ than inferring a PASS.
4. Confirm the exact direct `groups` claim for both identities and the mappings `TOT Users → user`
and `TOT Admin → admin`. Confirm extra upstream groups are ignored without warning.
5. For a projected Linux server, before any start gate, collect only the redacted result of
`sudo tht --installation "$INSTALLATION" auth status --json`. Record `state`, generation,
canonical revision, and `equal`; do not retain authentication YAML, user records, hashes, or
environment output. `ready` plus `equal: true` is required. A blocked or unequal result is a
fail-closed condition: do not start, and use `sudo tht --installation "$INSTALLATION" auth
publish` followed by the same status command only after the canonical root is available.
## Matrix
| Scenario | Expected result |
@@ -56,6 +63,7 @@ than inferring a PASS.
| Logout | Cookie expires and the server session is deleted. |
| Provider outage | Live check reports `oidc_discovery_unreachable`; browser login fails closed without exposing credentials. |
| Restore is completed | Sessions and OIDC state are absent; all users must reauthenticate. |
| Projected authentication restore | Candidate generation and any recovery generation are published from the canonical root; a failed verification remains blocked and start is refused. |
## Status at Task 15
@@ -29,6 +29,13 @@ verdi installazione, workspace, DWH, Qdrant, Ollama e preprocessing.
## 1. Stato generale
Prima del gate manuale di avvio, per una descriptor server con runtime projection eseguire solo il
controllo redatto `sudo tht --installation "$INSTALLATION" auth status --json`. Il risultato deve
dire `ready` ed `equal: true`. Se è `blocked`, mancante o diverso dal canonical root, non avviare:
Sol può eseguire `sudo tht --installation "$INSTALLATION" auth publish` e ripetere il controllo,
senza copiare YAML, hash, password, token o environment nel rapporto. Questo documento non
autorizza l'avvio; Project A resta soggetto a un'esplicita autorizzazione separata.
Eseguire:
```bash
+47
View File
@@ -157,4 +157,51 @@ if rg -n -i --pcre2 '(?:unmapped|additional|extra)[^.\r\n]{0,160}groups?[^.\r\n]
exit 1
fi
# Projected server authentication documentation contract.
projection_docs=(
"$root/docs/install/server.md"
"$root/docs/install/authentication-local.md"
"$root/docs/testing/authentication-manual-acceptance.md"
"$root/docs/testing/psd-server-project-a-manual.md"
"$root/docs/plans/2026-08-20-psd-server-project-a-standalone.md"
"$root/PROJECT_STATE.md"
)
projection_corpus=$(mktemp)
trap 'rm -f "$corpus" "$projection_corpus"' EXIT
cat "${projection_docs[@]}" >"$projection_corpus"
projection_required=(
"canonical authentication root" "runtime projection" "CURRENT" "generations"
"root:root 0700/0600" "10001:10001 0700/0600"
"THT_AUTH_RUNTIME_ROOT"
"auth status --json" "auth publish"
"candidate or recovery" "Mac, Windows, and local direct-file authentication"
"explicit authorization"
)
for term in "${projection_required[@]}"; do
rg -Fqi "$term" "$projection_corpus" || {
echo "auth docs smoke: missing runtime-projection term: $term" >&2
exit 1
}
done
if rg -n -i --pcre2 "(?:useradd|groupadd)[^\n]{0,100}10001|10001[^\n]{0,100}(?:useradd|groupadd)" "$projection_corpus"; then
echo "auth docs smoke: runtime projection creates a host 10001 identity" >&2
exit 1
fi
if rg -n -i --pcre2 '(?:^|[.!?]\s+)(?:(?:mount|bind)\s+(?:the\s+)?(?:canonical authentication root|auth-canonical)\b[^\n]{0,120}(?:core|/run/thothii-auth)|(?:core|the core service)\s+(?:mounts?|binds?)\s+(?:the\s+)?(?:canonical authentication root|auth-canonical)\b)|auth-canonical[^\n]{0,120}:/run/thothii-auth' "$projection_corpus"; then
echo "auth docs smoke: canonical authentication is mounted into core" >&2
exit 1
fi
if rg -n -i --pcre2 '(?:(?:operators?\s+)?(?:may|can|should|must)\s+(?!not\b|never\b)(?:edit|write|modify|prune)|^\s*(?:sudo\s+)?(?:vi|vim|nano|sed|cp|mv|rm|tee|printf|echo)\b)[^\n]*(?:CURRENT|generations)' "$projection_corpus"; then
echo "auth docs smoke: runtime projection permits direct selector or generation edits" >&2
exit 1
fi
if rg -n -i --pcre2 '(?:^\s*(?:sudo\s+nginx\s+-T|(?:sudo\s+)?(?:printenv|env)(?:\s|$)|(?:sudo\s+)?(?:diff|cat|less|more|head|tail|yq)\s+[^\n]*(?:secret|auth\.yaml|users\.yaml))|(?:may|can|should|must)\s+(?!not\b|never\b)(?:dump|print|capture|attach|include|run)\b[^\n]*(?:password|auth\.yaml|users\.yaml|YAML|raw environment|nginx\s+-T|secret-bearing diff))' "$projection_corpus"; then
echo "auth docs smoke: runtime projection documents unsafe evidence collection" >&2
exit 1
fi
if rg -n -i --pcre2 'Project A (?:has been|was|is) started|legacy[- ]stack (?:has been|was|is) changed' "$projection_corpus"; then
echo "auth docs smoke: runtime projection claims live Project A or legacy mutation" >&2
exit 1
fi
echo "auth docs smoke: required terms and forbidden wording checks passed"
+166
View File
@@ -0,0 +1,166 @@
#!/usr/bin/env bash
# Hermetic acceptance gate for the server authentication runtime projection.
set -euo pipefail
umask 077
root="$(cd "$(dirname "$0")/.." && pwd -P)"
forbidden_server_root="/$(printf '%s' srv)/"
tmp_base="$(cd "${TMPDIR:-/tmp}" && pwd -P)"
if [[ "$tmp_base/" == "$forbidden_server_root"* ]]; then
echo "runtime projection gate refuses a server temp root" >&2
exit 1
fi
if [[ -n "${DOCKER_HOST:-}" ]]; then
echo "runtime projection gate refuses a remote Docker endpoint" >&2
exit 1
fi
docker_context="$(docker context show)"
docker_endpoint="$(docker context inspect "$docker_context" --format '{{(index .Endpoints "docker").Host}}')"
if [[ "$docker_endpoint" != unix:///* ]]; then
echo "runtime projection gate requires a local Unix Docker endpoint" >&2
exit 1
fi
tmp="$(mktemp -d "$tmp_base/thoth-auth-runtime-projection.XXXXXX")"
chmod 0700 "$tmp"
mkdir -m 0700 "$tmp/go-mod" "$tmp/go-build"
cleanup() {
local cleanup_status=0
if [[ -d "$tmp" ]]; then
docker run --rm --network none -v "$tmp:/cleanup" golang:1.26.5 \
sh -c 'find /cleanup -mindepth 1 -delete' >/dev/null 2>&1 || cleanup_status=$?
if ((cleanup_status == 0)); then
rmdir "$tmp" || cleanup_status=$?
fi
fi
return "$cleanup_status"
}
trap cleanup EXIT
trap 'exit 129' HUP
trap 'exit 130' INT
trap 'exit 143' TERM
project_name_flag="--project"-name
compose_project_var='COMPOSE_PROJECT'_NAME
network_flag="--net"work
compose_pattern='docker[[:space:]]+com''pose'
if rg -n -F -- "$forbidden_server_root" "$0"; then
echo "runtime projection gate must not reference a server path" >&2
exit 1
fi
if rg -n -- "$project_name_flag|$compose_project_var|${compose_pattern}[^[:cntrl:]]*(up|start)|docker[[:space:]]+network" "$0"; then
echo "runtime projection gate has a forbidden project, lifecycle, or network attachment" >&2
exit 1
fi
if rg -n -F -- "$network_flag" "$0" | rg -v -F -e "$network_flag none" -e "$network_flag=none"; then
echo "runtime projection gate permits only an explicitly isolated network option" >&2
exit 1
fi
if rg -n --pcre2 '^[[:space:]]*[A-Za-z_][A-Za-z0-9_]*(?:PASSWORD|SECRET|TOKEN|KEY)[A-Za-z_]*=' "$0"; then
echo "runtime projection gate declares a credential value" >&2
exit 1
fi
node24_source="$(
env -i PATH="$PATH" HOME="$HOME" npm_config_offline=true npm_config_userconfig=/dev/null \
npx -y -p node@24 node -p 'process.execPath'
)"
if [[ "$node24_source" != /* || ! -f "$node24_source" || ! -x "$node24_source" ]]; then
echo "runtime projection gate requires a cached Node 24 runtime" >&2
exit 1
fi
install -m 0755 "$node24_source" "$tmp/node24"
run_case() {
local name="$1"
shift
local output="$tmp/$name.log"
if ! "$@" >"$output" 2>&1; then
echo "$name FAIL (test output suppressed and removed by cleanup)" >&2
return 1
fi
printf '%s PASS\n' "$name"
}
go_test() {
docker run --rm \
-v "$root:/work:ro" \
-v "$tmp/go-mod:/go/pkg/mod" \
-v "$tmp/go-build:/root/.cache/go-build" \
-w /work/tools/tht golang:1.26.5 go test "$@"
}
go_windows_compile() {
docker run --rm \
-v "$root:/work:ro" \
-v "$tmp/go-mod:/go/pkg/mod" \
-v "$tmp/go-build:/root/.cache/go-build" \
-w /work/tools/tht -e GOOS=windows -e GOARCH=amd64 golang:1.26.5 \
go test -c ./cmd/tht -o /tmp/tht.test.exe
}
go_darwin_compile() {
docker run --rm \
-v "$root:/work:ro" \
-v "$tmp/go-mod:/go/pkg/mod" \
-v "$tmp/go-build:/root/.cache/go-build" \
-w /work/tools/tht -e GOOS=darwin -e GOARCH=amd64 golang:1.26.5 \
go test -c ./cmd/tht -o /tmp/tht.test
}
backend_node24() {
docker run --rm --network none \
-e HOME=/tmp \
-v "$root:/work:ro" \
-v "$tmp/node24:/opt/node24:ro" \
-v "$root/backend/vitest.config.ts:/opt/vitest.config.ts:ro" \
-v "$root/backend/node_modules:/opt/node_modules:ro" \
-w /work/backend golang:1.26.5 \
/opt/node24 node_modules/vitest/vitest.mjs run --no-cache \
--config /opt/vitest.config.ts "$@"
}
backend_in_flight() {
backend_node24 test/auth-runtime-projection.test.ts \
-t 'in-flight snapshot authenticates A after selection B and deletion A'
}
backend_direct_file() {
backend_node24 test/config.test.ts \
-t 'keeps the direct auth-file provider when the runtime projection environment is absent'
}
mac_windows_direct_file() {
go_windows_compile
go_darwin_compile
backend_direct_file
}
run_case descriptor_requires_server_uid_gid_and_matching_env \
go_test ./internal/config -run 'TestLoadAcceptsServerRuntimeProjectionAndPlacesAutomaticOverrideBeforeCurrentImage|TestLoadRejectsInvalidRuntimeProjection|TestLoadRejectsRuntimeProjectionEnvironmentWithoutDescriptor' -count=1
run_case compose_mount_is_core_only_read_only_and_noncanonical \
bash "$root/scripts/test-auth-runtime-projection-compose.sh"
run_case local_initial_configure_publishes_equal_ready \
go_test ./internal/setup ./internal/authconfig -run 'TestRunConfigureOnlyPublishesInitialProjectedServerAuthentication|TestRunProjectedMutationPublishesExactLocalAndOIDCSnapshots/local' -count=1
run_case oidc_initial_configure_publishes_equal_ready \
go_test ./internal/authconfig -run 'TestRunProjectedMutationPublishesExactLocalAndOIDCSnapshots/oidc' -count=1
run_case every_user_mutation_blocks_then_publishes \
go_test ./internal/authconfig -run 'TestProjectedAuthMutatorsBlockBeforeCanonicalWriteAndPublishOnlyEqualSnapshots' -count=1
run_case publish_repairs_blocked_from_canonical \
go_test ./internal/authconfig -run 'TestPublishProjectedCanonicalRepairsBlockedStateFromCanonicalOnly|TestProjectedAuthPublishStatusAndCheckFailClosed' -count=1
run_case start_and_doctor_fail_closed_for_missing_blocked_tampered_or_divergent \
go_test ./internal/authprojection ./internal/authconfig ./internal/service ./internal/doctor ./cmd/tht -run 'TestInspectRejectsMissingBlockedMalformedAndTamperedCurrent|TestRequireRuntimeAuthProjectionReadyRejectsMissingBlockedAndDivergentStates|TestStartRefusesProjectedAuthenticationBeforeComposeWhenNotReady|TestRunReportsOneSanitizedRuntimeAuthProjectionFailureBeforeCompose|TestRunUpdateCheckOnlyRefusesProjectedAuthenticationBeforeCompose' -count=1
run_case backend_authenticates_from_one_immutable_generation_after_previous_gc backend_in_flight
run_case auth_restore_publishes_candidate \
go_test ./internal/backup -run 'TestRestoreAuthBearingArchiveBlocksBeforeWritePublishesBeforeRestart' -count=1
run_case failed_candidate_verification_republishes_checkpoint \
go_test ./internal/backup -run 'TestRestoreAuthVerificationFailuresRepublishCheckpointBeforeRecoveryRestart' -count=1
run_case failed_recovery_publish_remains_blocked \
go_test ./internal/backup -run 'TestRestoreAuthCandidatePublicationFailureRecoversThenStaysBlockedWithoutRestart' -count=1
run_case non_auth_restore_never_touches_projection \
go_test ./internal/backup -run 'TestRestoreNonAuthArchiveNeverBeginsProjection' -count=1
run_case mac_windows_local_regression mac_windows_direct_file
run_case secret_redaction \
go_test ./internal/authconfig -run 'TestProjectionCoordinatorErrorsAndLogsNeverContainSyntheticPasswordsOrHashes' -count=1
echo "runtime authentication projection acceptance gate passed."
@@ -1106,4 +1106,89 @@ if (( negative_failures != 0 )); then
exit 1
fi
# Projected server authentication documents must preserve the root-only canonical/runtime split.
projection_example="$root/docs/install/examples/thothii-installation.server.yaml"
for required in \
'runtimeProjection:' \
'directory: "/srv/example/thothii/auth-runtime"' \
'uid: 10001' \
'gid: 10001'; do
grep -Fq -- "$required" "$projection_example" || {
echo "server authentication projection example lacks: $required" >&2
exit 1
}
done
if rg -n -i --pcre2 '(?:password|secret)[[:space:]]*:[[:space:]]*[^<#[:space:]]+' "$projection_example"; then
echo "server authentication projection example contains a credential value" >&2
exit 1
fi
projection_docs=(
"$root/docs/install/server.md"
"$root/docs/install/authentication-local.md"
"$root/docs/testing/authentication-manual-acceptance.md"
"$root/docs/testing/psd-server-project-a-manual.md"
"$root/docs/plans/2026-08-20-psd-server-project-a-standalone.md"
"$root/PROJECT_STATE.md"
)
projection_corpus="$negative_root/projection-corpus.md"
cat "${projection_docs[@]}" >"$projection_corpus"
projection_documentation_is_safe() {
local corpus="$1"
if rg -q -i --pcre2 '(?:useradd|groupadd)[^\n]{0,100}10001|10001[^\n]{0,100}(?:useradd|groupadd)' "$corpus"; then
return 1
fi
if rg -q -i --pcre2 '(?:^|[.!?]\s+)(?:(?:mount|bind)\s+(?:the\s+)?(?:canonical authentication root|auth-canonical)\b[^\n]{0,120}(?:core|/run/thothii-auth)|(?:core|the core service)\s+(?:mounts?|binds?)\s+(?:the\s+)?(?:canonical authentication root|auth-canonical)\b)|auth-canonical[^\n]{0,120}:/run/thothii-auth' "$corpus"; then
return 1
fi
if rg -q -i --pcre2 '(?:(?:operators?\s+)?(?:may|can|should|must)\s+(?!not\b|never\b)(?:edit|write|modify|prune)|^\s*(?:sudo\s+)?(?:vi|vim|nano|sed|cp|mv|rm|tee|printf|echo)\b)[^\n]*(?:CURRENT|generations)' "$corpus"; then
return 1
fi
if rg -q -i --pcre2 '(?:^\s*(?:sudo\s+nginx\s+-T|(?:sudo\s+)?(?:printenv|env)(?:\s|$)|(?:sudo\s+)?(?:diff|cat|less|more|head|tail|yq)\s+[^\n]*(?:secret|auth\.yaml|users\.yaml))|(?:may|can|should|must)\s+(?!not\b|never\b)(?:dump|print|capture|attach|include|run)\b[^\n]*(?:password|auth\.yaml|users\.yaml|YAML|raw environment|nginx\s+-T|secret-bearing diff))' "$corpus"; then
return 1
fi
if rg -q -i --pcre2 'Project A (?:has been|was|is) started|legacy[- ]stack (?:has been|was|is) changed' "$corpus"; then
return 1
fi
return 0
}
for required in \
'canonical authentication root' \
'read-only and core-only' \
'candidate or recovery' \
'runtime projection is blocked' \
'Project A has not been started'; do
if ! rg -Fqi -- "$required" "${projection_docs[@]}"; then
echo "server authentication projection documentation lacks: $required" >&2
exit 1
fi
done
if ! projection_documentation_is_safe "$projection_corpus"; then
echo "server authentication projection documentation contains an unsafe instruction or claim" >&2
exit 1
fi
while IFS='|' read -r fixture_name payload; do
fixture="$negative_root/projection-$fixture_name.md"
cp "$projection_corpus" "$fixture"
printf '\n%s\n' "$payload" >>"$fixture"
if projection_documentation_is_safe "$fixture"; then
echo "server authentication projection documentation accepted unsafe fixture: $fixture_name" >&2
exit 1
fi
done <<'PROJECTION_NEGATIVE_FIXTURES'
host-identity|sudo useradd --system --uid 10001 thothii
canonical-core-mount|Mount the canonical authentication root into core at /run/thothii-auth.
core-subject-canonical-mount|Core mounts the canonical authentication root at /run/thothii-auth.
direct-runtime-edit|Operators may edit CURRENT and prune generations directly.
yaml-dump|Operators may dump auth.yaml and users.yaml into evidence.
nginx-dump|sudo nginx -T
raw-environment|printenv
sudo-raw-environment|sudo printenv
secret-diff|sudo diff auth.yaml auth.yaml.previous
live-claim|Project A has been started.
PROJECTION_NEGATIVE_FIXTURES
echo "unsafe installation-document fixtures rejected passed"