|
|
|
@@ -207,7 +207,7 @@ for name, port in (("qdrant", "6333"), ("embedding", "11434")):
|
|
|
|
|
if "devices" in str(services["embedding"]):
|
|
|
|
|
raise SystemExit("base embedding service must stay CPU-first")
|
|
|
|
|
volumes = set(doc["volumes"])
|
|
|
|
|
for required in ("qdrant-data", "embedding-models"):
|
|
|
|
|
for required in ("qdrant-data", "embedding-models", "workspace-secrets"):
|
|
|
|
|
if required not in volumes:
|
|
|
|
|
raise SystemExit(f"missing volume {required}")
|
|
|
|
|
model_init = services["embedding-model-init"]
|
|
|
|
@@ -360,18 +360,16 @@ required_contract_phrases = [
|
|
|
|
|
"A custom endpoint requires",
|
|
|
|
|
"HTTP endpoint additionally requires",
|
|
|
|
|
"page size cannot exceed 1000",
|
|
|
|
|
"Public docs, exports, and rendered YAML never expose file contents.",
|
|
|
|
|
"Public docs, APIs, and rendered YAML never expose file contents.",
|
|
|
|
|
"THT_WORKSPACE_SECRET_ROOTS",
|
|
|
|
|
"readable regular file",
|
|
|
|
|
"strictly below",
|
|
|
|
|
"Content-only revision",
|
|
|
|
|
"read-only Evidence summary",
|
|
|
|
|
"excludes Evidence bytes",
|
|
|
|
|
"`schema_version` value `1`",
|
|
|
|
|
"It is authoritative for workspace ID,\nname, description, and display order.",
|
|
|
|
|
"The descriptor at `<id>/workspace.yaml` must match the\ncatalog metadata exactly.",
|
|
|
|
|
"Catalog-only entries without `<id>/workspace.yaml` are valid bootstrap slots and surface as\n`configuration_required`.",
|
|
|
|
|
"The API never writes `thoth-workspaces.yaml` or `<id>/evidence/**`.",
|
|
|
|
|
"catalog-only entries are invalid and reject the complete candidate revision.",
|
|
|
|
|
"The API never writes `thoth-workspaces.yaml`,\n`<id>/workspace.yaml`, `<id>/schema/**`, or `<id>/evidence/**`.",
|
|
|
|
|
]
|
|
|
|
|
normalized_contract = normalize_space(contract)
|
|
|
|
|
for phrase in required_contract_phrases:
|
|
|
|
@@ -399,22 +397,10 @@ for forbidden in (
|
|
|
|
|
if forbidden in active_public:
|
|
|
|
|
raise SystemExit("old registry layout text found")
|
|
|
|
|
|
|
|
|
|
legacy_docs = re.compile(r"(?:^|\n)\s*(?:<id>|[a-z0-9-]+)/(?:(?:contract\.env\.example|README\.md))")
|
|
|
|
|
if "workspace-docs/<id>/{contract.env.example,README.md}" not in all_public:
|
|
|
|
|
raise SystemExit("generated docs path invalid")
|
|
|
|
|
for pattern in (
|
|
|
|
|
r"(?<!workspace-docs/)<id>/README\.md",
|
|
|
|
|
r"(?<!workspace-docs/)<id>/contract\.env\.example",
|
|
|
|
|
r"(?<!workspace-docs/)example/README\.md",
|
|
|
|
|
r"(?<!workspace-docs/)example/contract\.env\.example",
|
|
|
|
|
):
|
|
|
|
|
if re.search(pattern, contract):
|
|
|
|
|
raise SystemExit("generated docs path invalid")
|
|
|
|
|
required_tree_lines = [
|
|
|
|
|
"registry.git/", "├── thoth-workspaces.yaml", "├── example/", "│ ├── workspace.yaml",
|
|
|
|
|
"│ └── evidence/...", "├── another/", "│ └── workspace.yaml", "└── workspace-docs/",
|
|
|
|
|
" ├── example/{contract.env.example,README.md}",
|
|
|
|
|
" └── another/{contract.env.example,README.md}",
|
|
|
|
|
"workspace-repository.git/", "├── thoth-workspaces.yaml", "├── example/",
|
|
|
|
|
"│ ├── workspace.yaml", "│ └── evidence/...", "└── another/",
|
|
|
|
|
" └── workspace.yaml",
|
|
|
|
|
]
|
|
|
|
|
if any(line not in contract for line in required_tree_lines):
|
|
|
|
|
raise SystemExit("missing canonical Evidence layout")
|
|
|
|
@@ -440,15 +426,19 @@ if not all(token in revision_text for token in ("same 40-hex Git commit", "catal
|
|
|
|
|
raise SystemExit("missing same-revision ownership")
|
|
|
|
|
if "Evidence-only commit" not in relationships.get("Content-only revision", "") or "revision.commit" not in relationships.get("Content-only revision", ""):
|
|
|
|
|
raise SystemExit("missing content-only revision identity")
|
|
|
|
|
if "docs-only" not in relationships.get("Docs-only sync commit", "").lower() or "workspace-docs/**" not in relationships.get("Docs-only sync commit", ""):
|
|
|
|
|
raise SystemExit("missing docs-only sync rule")
|
|
|
|
|
repository_consumer = relationships.get("Repository consumer", "")
|
|
|
|
|
if not all(token in repository_consumer for token in ("complete candidate", "atomically activates", "never edits, commits, or pushes")):
|
|
|
|
|
raise SystemExit("missing read-only repository-consumer rule")
|
|
|
|
|
runtime_secrets = relationships.get("Runtime secrets", "")
|
|
|
|
|
if not all(token in runtime_secrets for token in ("configured/missing status only", "runtime lease")):
|
|
|
|
|
raise SystemExit("missing runtime-secret lifecycle rule")
|
|
|
|
|
p11 = relationships.get("P1.1", "")
|
|
|
|
|
p6 = relationships.get("P6", "")
|
|
|
|
|
if not all(token in p11 for token in ("lexical URI `<id>/evidence`", "Git tree", "same commit", "does not recursively inspect nested symlinks", "out of scope for P1.1")):
|
|
|
|
|
raise SystemExit("missing P1.1 lexical/tree ownership")
|
|
|
|
|
if not all(token in p6 for token in ("commit-addressed materialization", "realpath", "recursive containment", "nested-symlink", "race")):
|
|
|
|
|
raise SystemExit("missing P6 materialization ownership")
|
|
|
|
|
no_scope = "P1.1 performs no acquisition, extraction, preprocessing/indexing, embeddings, Qdrant writes, `ACTIVE` publication, retention, or GC."
|
|
|
|
|
no_scope = "P1.1 performs no acquisition, extraction, preprocessing/indexing, embeddings, Qdrant writes, active-snapshot retention, or GC."
|
|
|
|
|
p1_adverbs = r"(?:\s+(?:also|then|now|directly|itself))*"
|
|
|
|
|
p1_base_operation = r"""(?:
|
|
|
|
|
acquire|materialize|extract|preprocess|index|retain|
|
|
|
|
@@ -513,56 +503,38 @@ if len(automated) != 1 or len(manual) != 1:
|
|
|
|
|
raise SystemExit("separate automated/manual states missing")
|
|
|
|
|
|
|
|
|
|
flow_tokens = [
|
|
|
|
|
"Clone the one shared registry",
|
|
|
|
|
"Create a local workspace",
|
|
|
|
|
"thoth-workspaces.yaml",
|
|
|
|
|
"<id>/workspace.yaml",
|
|
|
|
|
"<id>/evidence/**",
|
|
|
|
|
"configuration_required",
|
|
|
|
|
"The API may create `<id>/workspace.yaml` only when the catalog slot already exists and no Git",
|
|
|
|
|
"After bootstrap, existing descriptors change only through curator Git commit/push and",
|
|
|
|
|
"The API never writes `thoth-workspaces.yaml` or `<id>/evidence/**`.",
|
|
|
|
|
"workspace-docs/<id>/contract.env.example",
|
|
|
|
|
"workspace-docs/<id>/README.md",
|
|
|
|
|
"Evidence `*_FILE` files outside Git",
|
|
|
|
|
"THT_WORKSPACE_SECRET_ROOTS",
|
|
|
|
|
"`*_SOURCE` paths",
|
|
|
|
|
"tht config check -c <path>",
|
|
|
|
|
"P2/P6 later performs preprocessing and materialization",
|
|
|
|
|
"<workspace-id>/workspace.yaml",
|
|
|
|
|
"commit",
|
|
|
|
|
"push",
|
|
|
|
|
"ThothII",
|
|
|
|
|
"Update workspace repository",
|
|
|
|
|
"workspace-secrets",
|
|
|
|
|
"Validate workspace",
|
|
|
|
|
"Test connections",
|
|
|
|
|
]
|
|
|
|
|
for guide in (local_path, server_path):
|
|
|
|
|
text = guide.read_text()
|
|
|
|
|
match = re.search(
|
|
|
|
|
r"^## Curator flow for shared-registry Evidence\s*$\n(.*?)(?=^## |\Z)",
|
|
|
|
|
r"^## Prepare and publish a workspace source\s*$\n(.*?)(?=^## |\Z)",
|
|
|
|
|
text,
|
|
|
|
|
re.MULTILINE | re.DOTALL,
|
|
|
|
|
)
|
|
|
|
|
if not match:
|
|
|
|
|
raise SystemExit(f"{guide.name}: missing curator flow")
|
|
|
|
|
section = match.group(1)
|
|
|
|
|
raise SystemExit(f"{guide.name}: missing workspace source flow")
|
|
|
|
|
section = text
|
|
|
|
|
positions = [section.find(token) for token in flow_tokens]
|
|
|
|
|
if any(position < 0 for position in positions):
|
|
|
|
|
raise SystemExit(f"{guide.name}: curator flow missing registry rule")
|
|
|
|
|
if positions != sorted(positions):
|
|
|
|
|
raise SystemExit(f"{guide.name}: curator flow out of order")
|
|
|
|
|
|
|
|
|
|
for guide in (local_path, server_path):
|
|
|
|
|
guide_text = guide.read_text()
|
|
|
|
|
if "authoritative for workspace ID, name, description, and\ndisplay order" not in guide_text:
|
|
|
|
|
raise SystemExit("missing catalog authority")
|
|
|
|
|
if "The API may create `<id>/workspace.yaml` only when the catalog slot already exists" not in guide_text:
|
|
|
|
|
raise SystemExit("missing bootstrap create-once rule")
|
|
|
|
|
if "After bootstrap, existing descriptors change only through curator Git commit/push and\n" not in guide_text:
|
|
|
|
|
raise SystemExit("missing existing-descriptor curator ownership")
|
|
|
|
|
|
|
|
|
|
readme_required = [
|
|
|
|
|
"thoth-workspaces.yaml",
|
|
|
|
|
"<id>/workspace.yaml",
|
|
|
|
|
"<id>/evidence/**",
|
|
|
|
|
"workspace-docs/<id>/{contract.env.example,README.md}",
|
|
|
|
|
"authoritative for workspace ID, name, description, and\ndisplay order",
|
|
|
|
|
"configuration_required",
|
|
|
|
|
"existing descriptors remain curator-owned and change only through curator Git commit,\npush, and installation pull.",
|
|
|
|
|
"The API never writes `thoth-workspaces.yaml` or `<id>/evidence/**`;",
|
|
|
|
|
"the complete candidate is rejected",
|
|
|
|
|
"ThothII\nnever writes any workspace repository content.",
|
|
|
|
|
"docs/migrations/p1-to-p1-1-registry-layout.md",
|
|
|
|
|
]
|
|
|
|
|
normalized_readme = normalize_space(readme)
|
|
|
|
@@ -1702,23 +1674,24 @@ verify_manual() {
|
|
|
|
|
if [[ "$profile" == local ]]; then
|
|
|
|
|
headings=(
|
|
|
|
|
"Prerequisites"
|
|
|
|
|
"Git remote: SSH and HTTPS"
|
|
|
|
|
"Shared Git values, local bindings, and secret files"
|
|
|
|
|
"Direct PostgreSQL, REST, and SSH tunnel bindings"
|
|
|
|
|
"Bootstrap, first pull, and diagnostics"
|
|
|
|
|
"Publish, update, backup, outage recovery, and rollback"
|
|
|
|
|
"Prepare and publish a workspace source"
|
|
|
|
|
"Configure the remote Git repository"
|
|
|
|
|
"Start and update the installation"
|
|
|
|
|
"Complete runtime secrets in Workspace management"
|
|
|
|
|
"Validation and activation behavior"
|
|
|
|
|
"Backup, rotation, and recovery"
|
|
|
|
|
"Troubleshooting"
|
|
|
|
|
)
|
|
|
|
|
else
|
|
|
|
|
headings=(
|
|
|
|
|
"Service account, storage, and firewall"
|
|
|
|
|
"Gitea and remote Git setup"
|
|
|
|
|
"Git credentials, CA, SSH key, and known-hosts mounts"
|
|
|
|
|
"Shared Git values, local bindings, and secret files"
|
|
|
|
|
"Direct PostgreSQL, REST, and SSH tunnel bindings"
|
|
|
|
|
"Same-origin reverse proxy, bootstrap, and health"
|
|
|
|
|
"Pull, publish, upgrade, backup, and recovery"
|
|
|
|
|
"Troubleshooting and snapshot rollback"
|
|
|
|
|
"Prepare and publish a workspace source"
|
|
|
|
|
"Configure the remote Git repository"
|
|
|
|
|
"Start and update the installation"
|
|
|
|
|
"Complete runtime secrets in Workspace management"
|
|
|
|
|
"Validation and activation behavior"
|
|
|
|
|
"Backup, rotation, and recovery"
|
|
|
|
|
"Troubleshooting"
|
|
|
|
|
)
|
|
|
|
|
fi
|
|
|
|
|
for heading in "${headings[@]}"; do
|
|
|
|
@@ -1731,9 +1704,10 @@ verify_manual() {
|
|
|
|
|
if [[ "$profile" == local ]]; then
|
|
|
|
|
expected_steps=(
|
|
|
|
|
'export THT_SOURCE_ROOT=/absolute/path/to/ThothII'
|
|
|
|
|
'--env-file "$THT_OPERATOR_ENV"'
|
|
|
|
|
"-f \"\$THT_SOURCE_ROOT/compose.yaml\" -f \"\$THT_SOURCE_ROOT/deploy/compose.$profile.yaml\""
|
|
|
|
|
'"$THT_SOURCE_ROOT/scripts/generate-connector-secrets-override.sh"'
|
|
|
|
|
'thothii-installation.yaml'
|
|
|
|
|
'workspaceRepository'
|
|
|
|
|
'"$THTCTL" --installation "$INSTALLATION" start'
|
|
|
|
|
'"$THTCTL" --installation "$INSTALLATION" doctor'
|
|
|
|
|
)
|
|
|
|
|
else
|
|
|
|
|
expected_steps=(
|
|
|
|
@@ -1761,6 +1735,82 @@ verify_manual() {
|
|
|
|
|
echo "$profile manual canonical base+override references passed"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
verify_read_only_workspace_runtime_contract() {
|
|
|
|
|
python3 - "$root" <<'PY'
|
|
|
|
|
import pathlib, sys, yaml
|
|
|
|
|
|
|
|
|
|
root = pathlib.Path(sys.argv[1])
|
|
|
|
|
compose = yaml.safe_load((root / "compose.yaml").read_text())
|
|
|
|
|
services = compose["services"]
|
|
|
|
|
core = services["core"]
|
|
|
|
|
maintenance = services["workspace-maintenance"]
|
|
|
|
|
environment = core["environment"]
|
|
|
|
|
|
|
|
|
|
for forbidden in ("THT_WORKSPACE_GIT_AUTHOR_NAME", "THT_WORKSPACE_GIT_AUTHOR_EMAIL"):
|
|
|
|
|
if forbidden in environment:
|
|
|
|
|
raise SystemExit(f"compose retains Git write identity: {forbidden}")
|
|
|
|
|
for key, value in {
|
|
|
|
|
"THT_WORKSPACE_SECRET_STORE_ROOT": "/data/workspace-secrets",
|
|
|
|
|
"THT_WORKSPACE_SECRET_RUNTIME_ROOT": "/tmp/thothii-workspace-secrets",
|
|
|
|
|
}.items():
|
|
|
|
|
if environment.get(key) != value or maintenance["environment"].get(key) != value:
|
|
|
|
|
raise SystemExit(f"workspace secret setting missing from core/maintenance: {key}")
|
|
|
|
|
if "workspace-secrets" not in compose["volumes"]:
|
|
|
|
|
raise SystemExit("workspace-secrets persistent volume is missing")
|
|
|
|
|
if not any("workspace-secrets:/data/workspace-secrets" in str(value) for value in core["volumes"]):
|
|
|
|
|
raise SystemExit("core does not persist the workspace secret vault")
|
|
|
|
|
if not any(mount.get("source") == "workspace-secrets" and mount.get("target") == "/data/workspace-secrets"
|
|
|
|
|
for mount in maintenance["volumes"] if isinstance(mount, dict)):
|
|
|
|
|
raise SystemExit("workspace-maintenance cannot use the encrypted workspace vault")
|
|
|
|
|
|
|
|
|
|
checked = [
|
|
|
|
|
root / "docs/install/local-workspace-registry.md",
|
|
|
|
|
root / "docs/install/server-workspace-registry.md",
|
|
|
|
|
root / "docs/guida-utente.md",
|
|
|
|
|
root / "deploy/workspace-registry.env.example",
|
|
|
|
|
root / "deploy/psd/operator.env.example",
|
|
|
|
|
root / "docs/install/examples/thothii-installation.local.yaml",
|
|
|
|
|
root / "docs/install/examples/thothii-installation.server.yaml",
|
|
|
|
|
]
|
|
|
|
|
joined = "\n".join(path.read_text() for path in checked)
|
|
|
|
|
for forbidden in (
|
|
|
|
|
"THT_WORKSPACE_GIT_AUTHOR_NAME",
|
|
|
|
|
"THT_WORKSPACE_GIT_AUTHOR_EMAIL",
|
|
|
|
|
"connector-secrets.local.yaml",
|
|
|
|
|
"connector-secrets.server.yaml",
|
|
|
|
|
"THT_WORKSPACE_BINDINGS_ENV_FILE",
|
|
|
|
|
"POST /workspaces/publish",
|
|
|
|
|
"POST /workspaces/import",
|
|
|
|
|
"Import workspace bundle",
|
|
|
|
|
):
|
|
|
|
|
if forbidden in joined:
|
|
|
|
|
raise SystemExit(f"active workspace documentation retains obsolete contract: {forbidden}")
|
|
|
|
|
for required in (
|
|
|
|
|
"GitHub, GitLab, or Gitea",
|
|
|
|
|
"read-only consumer",
|
|
|
|
|
"workspace-secrets",
|
|
|
|
|
"write-only",
|
|
|
|
|
"previous active revision",
|
|
|
|
|
):
|
|
|
|
|
if required.lower() not in joined.lower():
|
|
|
|
|
raise SystemExit(f"active workspace documentation lacks required concept: {required}")
|
|
|
|
|
|
|
|
|
|
ui = (root / "frontend/src/shell/WorkspaceManager.tsx").read_text()
|
|
|
|
|
for required in (
|
|
|
|
|
"Create a local workspace",
|
|
|
|
|
"Update workspace repository",
|
|
|
|
|
"No workspace selection is required",
|
|
|
|
|
"Temporary files are deleted after the test",
|
|
|
|
|
):
|
|
|
|
|
if required not in ui:
|
|
|
|
|
raise SystemExit(f"Workspace management lacks required explanation: {required}")
|
|
|
|
|
for forbidden in ("Import bundle", "Export bundle", "localStorage"):
|
|
|
|
|
if forbidden in ui:
|
|
|
|
|
raise SystemExit(f"Workspace management retains obsolete behavior: {forbidden}")
|
|
|
|
|
PY
|
|
|
|
|
echo "read-only workspace repository and encrypted runtime-secret contract passed"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
verify_local_installation_example() {
|
|
|
|
|
local example="$root/docs/install/examples/thothii-installation.local.yaml"
|
|
|
|
|
[[ -f "$example" ]] || {
|
|
|
|
@@ -1768,7 +1818,7 @@ verify_local_installation_example() {
|
|
|
|
|
return 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
local fixture source_copy operator_dir copied_example connector_override env_file
|
|
|
|
|
local fixture source_copy operator_dir copied_example env_file
|
|
|
|
|
fixture="$(mktemp -d "${TMPDIR%/}/thoth local install.XXXXXX")"
|
|
|
|
|
trap 'rm -rf "$fixture"' RETURN
|
|
|
|
|
[[ "$fixture" == *" "* ]] || {
|
|
|
|
@@ -1788,27 +1838,15 @@ verify_local_installation_example() {
|
|
|
|
|
write_private "$operator_dir/thothii.secrets" 'THT_MODEL_API_KEY=fixture-local-model-key'
|
|
|
|
|
write_private "$operator_dir/git-ssh-key" 'fixture-local-ssh-key'
|
|
|
|
|
write_private "$operator_dir/git-known-hosts" 'fixture-local-known-hosts'
|
|
|
|
|
write_private "$operator_dir/dwh-password" 'fixture-local-dwh-password'
|
|
|
|
|
printf '%s\n' \
|
|
|
|
|
'THT_WS_NORTH_STAR_RESEARCH_DWH_TRANSPORT=postgres_direct' \
|
|
|
|
|
'THT_WS_NORTH_STAR_RESEARCH_DWH_PASSWORD_FILE=/run/secrets/north-star-research-dwh-password' \
|
|
|
|
|
>"$operator_dir/workspace-bindings.env"
|
|
|
|
|
env_file="$source_copy/deploy/env/local.env"
|
|
|
|
|
mkdir -p "$source_copy/deploy/env"
|
|
|
|
|
printf '%s\n' \
|
|
|
|
|
'THT_WORKSPACE_GIT_REMOTE=ssh://git@git.example.invalid/platform/thoth-workspaces.git' \
|
|
|
|
|
"PI_AUTH_FILE=$operator_dir/pi-auth.json" \
|
|
|
|
|
"THT_SECRETS_FILE=$operator_dir/thothii.secrets" \
|
|
|
|
|
"THT_WORKSPACE_BINDINGS_ENV_FILE=$operator_dir/workspace-bindings.env" \
|
|
|
|
|
"THT_WORKSPACE_GIT_SSH_KEY_FILE=$operator_dir/git-ssh-key" \
|
|
|
|
|
"THT_WORKSPACE_GIT_KNOWN_HOSTS_FILE=$operator_dir/git-known-hosts" \
|
|
|
|
|
"THT_WS_NORTH_STAR_RESEARCH_DWH_PASSWORD_SOURCE=$operator_dir/dwh-password" \
|
|
|
|
|
>"$env_file"
|
|
|
|
|
connector_override="$operator_dir/connector-secrets.local.yaml"
|
|
|
|
|
"$root/scripts/generate-connector-secrets-override.sh" \
|
|
|
|
|
--bindings-env "$operator_dir/workspace-bindings.env" \
|
|
|
|
|
--operator-env "$env_file" \
|
|
|
|
|
--output "$connector_override" >/dev/null
|
|
|
|
|
|
|
|
|
|
copied_example="$fixture/thothii-installation.yaml"
|
|
|
|
|
local contents
|
|
|
|
@@ -1827,7 +1865,7 @@ verify_local_installation_example() {
|
|
|
|
|
echo "local installation example does not resolve its required fields" >&2
|
|
|
|
|
return 1
|
|
|
|
|
}
|
|
|
|
|
[[ "${#overrides[@]}" -eq 2 && "${overrides[1]}" == "$connector_override" ]] || {
|
|
|
|
|
[[ "${#overrides[@]}" -eq 1 && "${overrides[0]}" == "$source_copy/deploy/compose.git-ssh.yaml" ]] || {
|
|
|
|
|
echo "local installation example does not select the expected optional overrides" >&2
|
|
|
|
|
return 1
|
|
|
|
|
}
|
|
|
|
@@ -1863,7 +1901,7 @@ verify_server_installation_example() {
|
|
|
|
|
return 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
local fixture source_copy operator_dir copied_example connector_override env_file backup_root
|
|
|
|
|
local fixture source_copy operator_dir copied_example env_file backup_root
|
|
|
|
|
fixture="$(mktemp -d "${TMPDIR%/}/thoth server install.XXXXXX")"
|
|
|
|
|
trap 'rm -rf "$fixture"' RETURN
|
|
|
|
|
[[ "$fixture" == *" "* ]] || {
|
|
|
|
@@ -1874,7 +1912,7 @@ verify_server_installation_example() {
|
|
|
|
|
operator_dir="$fixture/server operator files"
|
|
|
|
|
backup_root="$fixture/server backups"
|
|
|
|
|
mkdir -p "$source_copy/deploy/pi" "$source_copy/deploy/workspaces" \
|
|
|
|
|
"$operator_dir/data" "$operator_dir/pi-state" "$operator_dir/workspace-registry" "$backup_root"
|
|
|
|
|
"$operator_dir/data/workspace-secrets" "$operator_dir/pi-state" "$operator_dir/workspace-registry" "$backup_root"
|
|
|
|
|
"$root/scripts/prepare-server-pi-state.sh" \
|
|
|
|
|
"$operator_dir/pi-state" "$(id -u)" "$(id -g)" >/dev/null
|
|
|
|
|
cp "$root/compose.yaml" "$source_copy/compose.yaml"
|
|
|
|
@@ -1891,14 +1929,9 @@ verify_server_installation_example() {
|
|
|
|
|
write_private "$operator_dir/thothii.secrets" 'THT_MODEL_API_KEY=fixture-server-model-key'
|
|
|
|
|
write_private "$operator_dir/git-ssh-key" 'fixture-server-ssh-key'
|
|
|
|
|
write_private "$operator_dir/git-known-hosts" 'fixture-server-known-hosts'
|
|
|
|
|
write_private "$operator_dir/dwh-password" 'fixture-server-dwh-password'
|
|
|
|
|
write_private "$operator_dir/session-runtime-password" 'fixture-server-session-runtime-password'
|
|
|
|
|
write_private "$operator_dir/session-migrator-password" 'fixture-server-session-migrator-password'
|
|
|
|
|
write_private "$operator_dir/session-ca.pem" 'fixture-server-session-ca'
|
|
|
|
|
printf '%s\n' \
|
|
|
|
|
'THT_WS_NORTH_STAR_RESEARCH_DWH_TRANSPORT=postgres_direct' \
|
|
|
|
|
'THT_WS_NORTH_STAR_RESEARCH_DWH_PASSWORD_FILE=/run/secrets/north-star-research-dwh-password' \
|
|
|
|
|
>"$operator_dir/workspace-bindings.env"
|
|
|
|
|
env_file="$operator_dir/server.env"
|
|
|
|
|
printf '%s\n' \
|
|
|
|
|
'THOTH_SERVER_BIND=127.0.0.1' \
|
|
|
|
@@ -1907,10 +1940,8 @@ verify_server_installation_example() {
|
|
|
|
|
'THT_WORKSPACE_GIT_BRANCH=main' \
|
|
|
|
|
"PI_AUTH_FILE=$operator_dir/pi-auth.json" \
|
|
|
|
|
"THT_SECRETS_FILE=$operator_dir/thothii.secrets" \
|
|
|
|
|
"THT_WORKSPACE_BINDINGS_ENV_FILE=$operator_dir/workspace-bindings.env" \
|
|
|
|
|
"THT_WORKSPACE_GIT_SSH_KEY_FILE=$operator_dir/git-ssh-key" \
|
|
|
|
|
"THT_WORKSPACE_GIT_KNOWN_HOSTS_FILE=$operator_dir/git-known-hosts" \
|
|
|
|
|
"THT_WS_NORTH_STAR_RESEARCH_DWH_PASSWORD_SOURCE=$operator_dir/dwh-password" \
|
|
|
|
|
"THT_DATA_ROOT=$operator_dir/data" \
|
|
|
|
|
"THT_PI_STATE_ROOT=$operator_dir/pi-state" \
|
|
|
|
|
"THT_WORKSPACE_REGISTRY_ROOT=$operator_dir/workspace-registry" \
|
|
|
|
@@ -1925,11 +1956,6 @@ verify_server_installation_example() {
|
|
|
|
|
"THT_SESSION_MIGRATOR_PASSWORD_SOURCE=$operator_dir/session-migrator-password" \
|
|
|
|
|
"THT_SESSION_CA_SOURCE=$operator_dir/session-ca.pem" \
|
|
|
|
|
>"$env_file"
|
|
|
|
|
connector_override="$operator_dir/connector-secrets.server.yaml"
|
|
|
|
|
"$root/scripts/generate-connector-secrets-override.sh" \
|
|
|
|
|
--bindings-env "$operator_dir/workspace-bindings.env" \
|
|
|
|
|
--operator-env "$env_file" \
|
|
|
|
|
--output "$connector_override" >/dev/null
|
|
|
|
|
|
|
|
|
|
copied_example="$fixture/thothii-installation.yaml"
|
|
|
|
|
local contents
|
|
|
|
@@ -1948,8 +1974,8 @@ verify_server_installation_example() {
|
|
|
|
|
echo "server installation example does not resolve its required fields" >&2
|
|
|
|
|
return 1
|
|
|
|
|
}
|
|
|
|
|
[[ "${#overrides[@]}" -eq 3 && "${overrides[0]}" == "$source_copy/deploy/compose.session-server.yaml.example" \
|
|
|
|
|
&& "${overrides[2]}" == "$connector_override" ]] || {
|
|
|
|
|
[[ "${#overrides[@]}" -eq 2 && "${overrides[0]}" == "$source_copy/deploy/compose.session-server.yaml.example" \
|
|
|
|
|
&& "${overrides[1]}" == "$source_copy/deploy/compose.git-ssh.yaml" ]] || {
|
|
|
|
|
echo "server installation example does not select the expected optional overrides" >&2
|
|
|
|
|
return 1
|
|
|
|
|
}
|
|
|
|
@@ -2053,34 +2079,25 @@ write_private() {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
verify_compose_fixtures() {
|
|
|
|
|
local fixture connector_override profile rendered
|
|
|
|
|
local fixture profile rendered
|
|
|
|
|
fixture="$(mktemp -d "${TMPDIR%/}/thoth-install-fixtures.XXXXXX")"
|
|
|
|
|
trap 'rm -rf "$fixture"' RETURN
|
|
|
|
|
mkdir -p "$fixture/data" "$fixture/pi-state" "$fixture/workspace-registry"
|
|
|
|
|
mkdir -p "$fixture/data/workspace-secrets" "$fixture/pi-state" "$fixture/workspace-registry"
|
|
|
|
|
|
|
|
|
|
write_private "$fixture/pi-auth.json" '{"zai":{"type":"api_key","key":"fixture-native-auth-key"}}'
|
|
|
|
|
write_private "$fixture/thothii.secrets" 'THT_MODEL_API_KEY=fixture-model-api-key'
|
|
|
|
|
write_private "$fixture/git-ssh-key" 'fixture-git-ssh-key'
|
|
|
|
|
write_private "$fixture/git-known-hosts" 'fixture-git-known-hosts'
|
|
|
|
|
write_private "$fixture/dwh-password" 'fixture-dwh-password'
|
|
|
|
|
write_private "$fixture/session-runtime-password" 'fixture-session-runtime-password'
|
|
|
|
|
write_private "$fixture/session-migrator-password" 'fixture-session-migrator-password'
|
|
|
|
|
write_private "$fixture/session-ca.pem" 'fixture-session-ca'
|
|
|
|
|
cp "$root/deploy/workspaces/server-sessions.yaml.example" "$fixture/server-sessions.yaml"
|
|
|
|
|
|
|
|
|
|
printf '%s\n' \
|
|
|
|
|
'THT_WS_NORTH_STAR_RESEARCH_DWH_TRANSPORT=postgres_direct' \
|
|
|
|
|
'THT_WS_NORTH_STAR_RESEARCH_DWH_PASSWORD_FILE=/run/secrets/north-star-research-dwh-password' \
|
|
|
|
|
>"$fixture/workspace-bindings.env"
|
|
|
|
|
|
|
|
|
|
printf '%s\n' \
|
|
|
|
|
'THT_WORKSPACE_GIT_REMOTE=ssh://git@git.example.invalid/platform/thoth-workspaces.git' \
|
|
|
|
|
"PI_AUTH_FILE=$fixture/pi-auth.json" \
|
|
|
|
|
"THT_SECRETS_FILE=$fixture/thothii.secrets" \
|
|
|
|
|
"THT_WORKSPACE_BINDINGS_ENV_FILE=$fixture/workspace-bindings.env" \
|
|
|
|
|
"THT_WORKSPACE_GIT_SSH_KEY_FILE=$fixture/git-ssh-key" \
|
|
|
|
|
"THT_WORKSPACE_GIT_KNOWN_HOSTS_FILE=$fixture/git-known-hosts" \
|
|
|
|
|
"THT_WS_NORTH_STAR_RESEARCH_DWH_PASSWORD_SOURCE=$fixture/dwh-password" \
|
|
|
|
|
"THT_DATA_ROOT=$fixture/data" \
|
|
|
|
|
"THT_PI_STATE_ROOT=$fixture/pi-state" \
|
|
|
|
|
"THT_WORKSPACE_REGISTRY_ROOT=$fixture/workspace-registry" \
|
|
|
|
@@ -2094,12 +2111,6 @@ verify_compose_fixtures() {
|
|
|
|
|
"THT_SESSION_CA_SOURCE=$fixture/session-ca.pem" \
|
|
|
|
|
>"$fixture/operator.env"
|
|
|
|
|
|
|
|
|
|
connector_override="$fixture/connector-secrets.local.yaml"
|
|
|
|
|
"$root/scripts/generate-connector-secrets-override.sh" \
|
|
|
|
|
--bindings-env "$fixture/workspace-bindings.env" \
|
|
|
|
|
--operator-env "$fixture/operator.env" \
|
|
|
|
|
--output "$connector_override" >/dev/null
|
|
|
|
|
|
|
|
|
|
for profile in local server; do
|
|
|
|
|
rendered="$fixture/$profile.json"
|
|
|
|
|
files=(
|
|
|
|
@@ -2111,7 +2122,6 @@ verify_compose_fixtures() {
|
|
|
|
|
fi
|
|
|
|
|
files+=(
|
|
|
|
|
-f "$root/deploy/compose.git-ssh.yaml"
|
|
|
|
|
-f "$connector_override"
|
|
|
|
|
)
|
|
|
|
|
"$root/scripts/compose-with-preflight.sh" --env-file "$fixture/operator.env" \
|
|
|
|
|
"${files[@]}" config --format json >"$rendered"
|
|
|
|
@@ -2133,15 +2143,9 @@ for (const target of [
|
|
|
|
|
throw new Error(profile + ": missing read-only Pi mount " + target);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for (const [name, value] of Object.entries({
|
|
|
|
|
THT_WS_NORTH_STAR_RESEARCH_DWH_PASSWORD_FILE: "/run/secrets/north-star-research-dwh-password",
|
|
|
|
|
})) {
|
|
|
|
|
if (core.environment?.[name] !== value) throw new Error(profile + ": missing binding " + name);
|
|
|
|
|
}
|
|
|
|
|
const secretTargets = new Set((core.secrets || []).map((secret) => secret.target));
|
|
|
|
|
for (const target of [
|
|
|
|
|
"thothii.secrets",
|
|
|
|
|
"north-star-research-dwh-password",
|
|
|
|
|
]) {
|
|
|
|
|
if (!secretTargets.has(target)) throw new Error(profile + ": missing secret target " + target);
|
|
|
|
|
}
|
|
|
|
@@ -2156,7 +2160,7 @@ if ((config.services.frontend.secrets || []).length !== 0) {
|
|
|
|
|
const rendered = JSON.stringify(config);
|
|
|
|
|
for (const value of [
|
|
|
|
|
"fixture-native-auth-key", "fixture-model-api-key", "fixture-git-ssh-key",
|
|
|
|
|
"fixture-git-known-hosts", "fixture-dwh-password",
|
|
|
|
|
"fixture-git-known-hosts",
|
|
|
|
|
"fixture-session-runtime-password", "fixture-session-migrator-password", "fixture-session-ca",
|
|
|
|
|
]) {
|
|
|
|
|
if (rendered.includes(value)) throw new Error(profile + ": rendered Compose leaked " + value);
|
|
|
|
@@ -2178,6 +2182,7 @@ case "$mode" in
|
|
|
|
|
[[ $# -eq 1 ]] || { echo "usage: $0 --fixtures-only" >&2; exit 2; }
|
|
|
|
|
verify_internal_semantic_infrastructure_docs
|
|
|
|
|
echo "internal semantic infrastructure documentation contract passed"
|
|
|
|
|
verify_read_only_workspace_runtime_contract
|
|
|
|
|
verify_workspace_evidence_contract
|
|
|
|
|
verify_local_guide
|
|
|
|
|
verify_windows_line_endings_guide
|
|
|
|
@@ -2197,6 +2202,7 @@ case "$mode" in
|
|
|
|
|
|| { echo "usage: $0 --profile {local|server}" >&2; exit 2; }
|
|
|
|
|
if [[ "$profile" == local ]]; then
|
|
|
|
|
verify_internal_semantic_infrastructure_docs
|
|
|
|
|
verify_read_only_workspace_runtime_contract
|
|
|
|
|
verify_workspace_evidence_contract
|
|
|
|
|
verify_local_guide
|
|
|
|
|
verify_windows_line_endings_guide
|
|
|
|
@@ -2204,6 +2210,7 @@ case "$mode" in
|
|
|
|
|
verify_local_installation_example
|
|
|
|
|
else
|
|
|
|
|
verify_internal_semantic_infrastructure_docs
|
|
|
|
|
verify_read_only_workspace_runtime_contract
|
|
|
|
|
verify_workspace_evidence_contract
|
|
|
|
|
verify_server_guide
|
|
|
|
|
verify_reverse_proxy_nginx_guide
|
|
|
|
|