feat: unify installation model catalog

This commit is contained in:
Codex
2026-09-02 18:45:33 +02:00
parent ae053961a3
commit 7b7927bfe5
169 changed files with 3696 additions and 4572 deletions
+1 -17
View File
@@ -1,5 +1,5 @@
workspace:
schema_version: 3
schema_version: 4
id: local
name: Local
description: Isolated workspace registry smoke fixture.
@@ -15,22 +15,6 @@ dwh:
- postgres_direct
- rest_api
semantic_index:
vector_store:
engine: qdrant
collection: local
dimensions: 1024
distance: cosine
embedding:
provider: ollama_internal
model: qwen3-embedding:0.6b
dimensions: 1024
llm_policy:
default: zai/glm-5.2
allowed:
- zai/glm-5.2
evidence:
source:
type: filesystem
@@ -1,5 +1,5 @@
workspace:
schema_version: 3
schema_version: 4
id: task13-smoke
name: Task 13 Smoke
language: en
@@ -11,22 +11,6 @@ dwh:
supported_transports:
- postgres_direct
semantic_index:
vector_store:
engine: qdrant
collection: task13-smoke
dimensions: 1024
distance: cosine
embedding:
provider: ollama_internal
model: qwen3-embedding:0.6b
dimensions: 1024
llm_policy:
default: local-qwen/task13-smoke
allowed:
- local-qwen/task13-smoke
evidence:
source:
type: filesystem
@@ -1,5 +1,5 @@
workspace:
schema_version: 3
schema_version: 4
id: task13-windows
name: Task 13 Windows
language: en
@@ -14,22 +14,6 @@ dwh:
- postgres_direct
- rest_api
semantic_index:
vector_store:
engine: qdrant
collection: task13-windows
dimensions: 1024
distance: cosine
embedding:
provider: ollama_internal
model: qwen3-embedding:0.6b
dimensions: 1024
llm_policy:
default: zai/glm-5.2
allowed:
- zai/glm-5.2
evidence:
source:
type: filesystem
+22 -29
View File
@@ -86,26 +86,11 @@ for (const name of [
}
if (workspace.workspace?.id !== "task13-smoke") throw new Error("fixture workspace id changed");
if (workspace.semantic_index?.vector_store?.engine !== "qdrant") {
throw new Error("fixture workspace must use qdrant");
}
if (workspace.semantic_index?.vector_store?.collection !== workspace.workspace?.id) {
throw new Error("fixture workspace must dedicate one qdrant collection per workspace id");
}
if (workspace.semantic_index?.vector_store?.dimensions !== 1024) {
throw new Error("fixture workspace qdrant dimension changed");
}
if (workspace.semantic_index?.vector_store?.distance !== "cosine") {
throw new Error("fixture workspace qdrant distance changed");
}
if (workspace.semantic_index?.embedding?.provider !== "ollama_internal") {
throw new Error("fixture workspace must use internal ollama embeddings");
}
if (workspace.semantic_index?.embedding?.model !== "qwen3-embedding:0.6b") {
throw new Error("fixture workspace embedding model changed");
}
if (workspace.semantic_index?.embedding?.dimensions !== 1024) {
throw new Error("fixture workspace embedding dimension changed");
if (workspace.workspace?.schema_version !== 4) throw new Error("fixture workspace must use schema v4");
for (const forbidden of ["semantic_index", "llm_policy"]) {
if (Object.hasOwn(workspace, forbidden)) {
throw new Error(`fixture workspace must not own installation model configuration: ${forbidden}`);
}
}
if (!statSync(bundleSource).isFile()) {
@@ -139,28 +124,36 @@ if (profile === "local") {
throw new Error("server runtime fixture lacks one readable, read-only password-file bind");
}
accessSync(runtimePasswordSourceInput, constants.R_OK);
const piTargets = [
"/home/thoth/.pi/agent/auth.json",
const generatedPiTargets = [
"/home/thoth/.pi/agent/models.json",
"/home/thoth/.pi/agent/settings.json",
] as const;
const piParent = mounts.filter((mount: any) => mount.target === "/home/thoth/.pi");
if (piParent.length !== 1) throw new Error("core lacks exactly one Pi state mount");
for (const target of piTargets) {
for (const target of generatedPiTargets) {
const selected = mounts.filter((mount: any) => mount.target === target);
if (selected.length !== 1 || selected[0].type !== "bind" || !selected[0].read_only) {
throw new Error(`Pi fixture mount is not one generated read-only bind: ${target}`);
}
accessSync(selected[0].source, constants.R_OK);
if (profile === "local") {
if (piParent[0].type !== "volume" || selected.length !== 0) {
throw new Error(`local Pi fixture must come only from the projected state volume: ${target}`);
if (piParent[0].type !== "volume") {
throw new Error(`local Pi parent is not a state volume: ${target}`);
}
} else {
if (selected.length !== 1 || selected[0].type !== "bind" || !selected[0].read_only) {
throw new Error(`Pi fixture mount is not one read-only bind: ${target}`);
}
accessSync(selected[0].source, constants.R_OK);
const hidden = join(piParent[0].source, "agent", basename(target));
if (!statSync(hidden).isFile()) throw new Error(`server parent root lacks ${hidden}`);
}
}
const authTarget = "/home/thoth/.pi/agent/auth.json";
const authMounts = mounts.filter((mount: any) => mount.target === authTarget);
if (profile === "local") {
if (authMounts.length !== 0) {
throw new Error("local Pi auth must be projected into the state volume, not directly mounted");
}
} else if (authMounts.length !== 1 || authMounts[0].type !== "bind" || !authMounts[0].read_only) {
throw new Error("server Pi auth is not one independent read-only bind");
}
for (const [target, localVolume] of [
["/run/thothii-auth", "auth-runtime"],
+60 -107
View File
@@ -1,121 +1,74 @@
#!/usr/bin/env bash
# Fresh Compose flow: mounted Pi policy/auth must produce a selectable, credential-ready provider.
# Base Compose is deliberately model-free; `tht start` appends the generated catalog projection.
set -euo pipefail
root="$(cd "$(dirname "$0")/.." && pwd -P)"
tmp_parent="${TMPDIR:-/tmp}"
tmp="$(mktemp -d "${tmp_parent%/}/thoth-provider-readiness.XXXXXX")"
project="thothii-provider-readiness-$$"
compose=(
docker compose --project-name "$project" --env-file "$tmp/local.env"
-f "$root/compose.yaml" -f "$root/deploy/compose.local.yaml"
)
cleanup() {
"${compose[@]}" down --volumes --remove-orphans >/dev/null 2>&1 || true
rm -rf "$tmp"
}
trap cleanup EXIT HUP INT TERM
trap 'rm -rf "$tmp"' EXIT HUP INT TERM
printf '%s\n' '{"zai":{"type":"api_key","key":"fixture-native-auth-key"}}' >"$tmp/pi-auth.json"
printf '%s\n' '{}' >"$tmp/pi-auth.json"
printf '%s\n' 'THT_MODEL_API_KEY=fixture-model-api-key' >"$tmp/thothii.secrets"
chmod 0600 "$tmp/pi-auth.json" "$tmp/thothii.secrets"
auth_config="$tmp/auth"
mkdir "$auth_config"
chmod 0700 "$auth_config"
printf '%s\n' \
'version: 1' \
'mode: local' \
'publicUrl: http://127.0.0.1:8080' \
'local:' \
' usersFile: users.yaml' \
>"$auth_config/auth.yaml"
node - "$auth_config/users.yaml" <<'NODE'
const { argon2 } = require("node:crypto");
const { writeFileSync } = require("node:fs");
printf '%s\n' 'schemaVersion: 2' >"$tmp/thothii-installation.yaml"
printf '%s' 'fixture-catalog-runtime-password' >"$tmp/catalog-runtime-password"
printf '%s' 'fixture-catalog-migrator-password' >"$tmp/catalog-migrator-password"
mkdir "$tmp/auth"
printf '%s\n' 'mode: local' >"$tmp/auth/auth.yaml"
chmod 0600 "$tmp/pi-auth.json" "$tmp/thothii.secrets" "$tmp/thothii-installation.yaml" \
"$tmp/catalog-runtime-password" "$tmp/catalog-migrator-password" "$tmp/auth/auth.yaml"
chmod 0700 "$tmp/auth"
const message = Buffer.from("fixture-local-password", "utf8");
const nonce = Buffer.from([...Array(16).keys()]);
argon2("argon2id", {
message,
nonce,
memory: 65_536,
parallelism: 1,
tagLength: 32,
passes: 3,
}, (error, digest) => {
message.fill(0);
nonce.fill(0);
if (error || !digest) throw error ?? new Error("fixture password hash failed");
const salt = Buffer.from([...Array(16).keys()]).toString("base64").replaceAll("=", "");
const hash = digest.toString("base64").replaceAll("=", "");
writeFileSync(process.argv[2], [
"version: 1",
"users:",
" - id: 00000000-0000-4000-8000-000000000001",
" username: fixture-user",
" displayName: Fixture user",
` passwordHash: $argon2id$v=19$m=65536,t=3,p=1$${salt}$${hash}`,
" roles:",
" - user",
" enabled: true",
" authRevision: 1",
"",
].join("\\n"), { mode: 0o600 });
});
NODE
chmod 0600 "$auth_config/auth.yaml" "$auth_config/users.yaml"
printf '%s\n' \
'THT_WORKSPACE_GIT_REMOTE=https://git.example.invalid/platform/thoth-workspaces.git' \
"PI_AUTH_FILE=$tmp/pi-auth.json" \
"THT_SECRETS_FILE=$tmp/thothii.secrets" \
"THT_AUTH_CONFIG_ROOT=$auth_config" \
'THOTH_CORE_HTTP_PORT=0' \
'THOTH_HTTP_PORT=0' \
>"$tmp/local.env"
rendered="$tmp/rendered.json"
THT_WORKSPACE_GIT_REMOTE=https://git.example.invalid/platform/thoth-workspaces.git \
PI_AUTH_FILE="$tmp/pi-auth.json" \
THT_SECRETS_FILE="$tmp/thothii.secrets" \
THT_INSTALLATION_CONFIG_SOURCE="$tmp/thothii-installation.yaml" \
THT_CATALOG_RUNTIME_PASSWORD_SOURCE="$tmp/catalog-runtime-password" \
THT_CATALOG_MIGRATOR_PASSWORD_SOURCE="$tmp/catalog-migrator-password" \
THT_AUTH_CONFIG_ROOT="$tmp/auth" \
docker compose --project-directory "$root" \
-f "$root/compose.yaml" -f "$root/deploy/compose.local.yaml" \
config --format json >"$rendered"
"${compose[@]}" up --detach --wait --wait-timeout 90 --build core
core_id="$("${compose[@]}" ps -q core)"
core_address="$("${compose[@]}" port core 8787 | head -n 1)"
"${compose[@]}" exec -T core sh -ceu '
test -r /home/thoth/.pi/agent/auth.json
test -r /home/thoth/.pi/agent/models.json
test -r /home/thoth/.pi/agent/settings.json
test -r /run/secrets/thothii.secrets
'
curl --fail --silent --show-error "http://$core_address/models" >"$tmp/models.json"
node - "$tmp/models.json" <<'NODE'
const fs = require("fs");
const body = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
if (!body.models?.some((model) => model.provider === "zai" && model.id === "glm-5.2")) {
throw new Error("fresh Compose did not expose the mounted Pi-enabled model");
node - "$rendered" <<'NODE'
const { readFileSync } = require("node:fs");
const config = JSON.parse(readFileSync(process.argv[2], "utf8"));
const core = config.services?.core;
if (!core) throw new Error("base Compose lacks core");
for (const name of [
"THT_MODEL_CATALOG_FILE",
"THT_MODEL_CATALOG_REVISION",
"THT_DEFAULT_SESSION_MODEL",
"THT_INTERNAL_EMBEDDING_ID",
"THT_INTERNAL_EMBEDDING_MODEL",
"THT_INTERNAL_EMBEDDING_DIMENSIONS",
]) {
if (Object.hasOwn(core.environment || {}, name)) {
throw new Error(`base Compose invented installation-owned model input ${name}`);
}
}
for (const target of (core.volumes || []).map((mount) => mount.target)) {
if (target === "/run/thothii-model-catalog/catalog.json"
|| target === "/home/thoth/.pi/agent/models.json"
|| target === "/home/thoth/.pi/agent/settings.json") {
throw new Error(`base Compose mounted a generated model adapter: ${target}`);
}
}
const installation = (core.configs || []).filter(
(entry) => entry.target === "/run/thothii-installation/thothii-installation.yaml",
);
if (installation.length !== 1 || installation[0].source !== "thothii_installation_config") {
throw new Error("base Compose lacks the protected installation descriptor mount");
}
NODE
curl --fail --silent --show-error -X PUT \
-H 'content-type: application/json' \
--data '{"provider":"zai","model":"glm-5.2","reasoning":"low"}' \
"http://$core_address/pi-management/config" >"$tmp/configured.json"
curl --fail --silent --show-error \
"http://$core_address/pi-management/status" >"$tmp/status.json"
node - "$tmp/status.json" <<'NODE'
const fs = require("fs");
const body = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
if (!body.ready || body.credentials !== "present") {
throw new Error("mounted Pi provider is not credential-ready");
}
if (body.config?.provider !== "zai" || body.config?.model !== "glm-5.2") {
throw new Error("Pi provider configuration was not persisted");
}
NODE
if grep -Fq 'fixture-model-api-key' "$rendered"; then
echo "rendered base Compose leaked the model key" >&2
exit 1
fi
if [[ -e "$root/deploy/pi/models.json" || -e "$root/deploy/pi/settings.json" ]]; then
echo "legacy authored Pi model sources still exist" >&2
exit 1
fi
inspect="$(docker inspect "$core_id")"
for secret in fixture-native-auth-key fixture-model-api-key; do
if grep -Fq "$secret" <<<"$inspect"; then
echo "container inspection leaked $secret" >&2
exit 1
fi
done
echo "Compose provider-readiness contract passed."
echo "Base Compose model fail-closed contract passed."
+8 -2
View File
@@ -13,9 +13,17 @@ printf '%s\n' '{"zai":{"type":"api_key","key":"fixture-native-auth-key"}}' >"$tm
chmod 0600 "$tmp/pi-auth.json"
printf '%s\n' 'THT_MODEL_API_KEY=fixture-model-api-key' >"$tmp/thothii.secrets"
chmod 0600 "$tmp/thothii.secrets"
printf '%s\n' 'schemaVersion: 2' >"$tmp/thothii-installation.yaml"
chmod 0600 "$tmp/thothii-installation.yaml"
printf '%s' 'fixture-catalog-runtime-password' >"$tmp/catalog-runtime-password"
printf '%s' 'fixture-catalog-migrator-password' >"$tmp/catalog-migrator-password"
chmod 0600 "$tmp/catalog-runtime-password" "$tmp/catalog-migrator-password"
export PI_AUTH_FILE="$tmp/pi-auth.json"
export THT_SECRETS_FILE="$tmp/thothii.secrets"
export THT_INSTALLATION_CONFIG_SOURCE="$tmp/thothii-installation.yaml"
export THT_CATALOG_RUNTIME_PASSWORD_SOURCE="$tmp/catalog-runtime-password"
export THT_CATALOG_MIGRATOR_PASSWORD_SOURCE="$tmp/catalog-migrator-password"
export THT_WORKSPACE_GIT_REMOTE="https://git.example.invalid/thothii/workspaces.git"
# Let Docker assign loopback ports so this isolated contract test never collides with an operator stack.
export THOTH_CORE_HTTP_PORT=0
@@ -66,8 +74,6 @@ docker compose --project-name "$project" -f compose.yaml -f deploy/compose.local
command -v pi >/dev/null
test ! -e /var/run/docker.sock
test -r /home/thoth/.pi/agent/auth.json
test -r /home/thoth/.pi/agent/models.json
test -r /home/thoth/.pi/agent/settings.json
test -r /run/secrets/thothii.secrets
touch /data/.task5-writable
rm /data/.task5-writable
+3 -2
View File
@@ -86,8 +86,6 @@ for (const [key, value] of Object.entries({
THT_AUTH_STATE_ROOT: "/data/auth",
THT_INTERNAL_QDRANT_URL: "http://qdrant:6333",
THT_INTERNAL_EMBEDDING_URL: "http://embedding:11434",
THT_INTERNAL_EMBEDDING_MODEL: "qwen3-embedding:0.6b",
THT_INTERNAL_EMBEDDING_DIMENSIONS: "1024",
THT_CATALOG_DB_HOST: "catalog-db",
THT_CATALOG_DB_NAME: "thothii_catalog",
THT_CATALOG_RUNTIME_USER: "thothii_catalog_runtime",
@@ -95,6 +93,9 @@ for (const [key, value] of Object.entries({
})) {
if (env[key] !== value) throw new Error(`unexpected core ${key}: ${env[key]}`);
}
for (const key of ["THT_INTERNAL_EMBEDDING_ID", "THT_INTERNAL_EMBEDDING_MODEL", "THT_INTERNAL_EMBEDDING_DIMENSIONS"]) {
if (Object.hasOwn(env, key)) throw new Error(`${key} must come only from the generated installation projection`);
}
const authConfigMounts = (core.volumes || []).filter((mount) => mount.target === "/run/thothii-auth");
if (authConfigMounts.length !== 1 || authConfigMounts[0].type !== "bind" || !authConfigMounts[0].read_only) {
throw new Error("core must receive exactly one read-only authentication configuration bind");
+15 -30
View File
@@ -16,20 +16,22 @@ chmod 0600 "$auth_config/auth.yaml"
secrets_file="$tmp/thothii.secrets"
printf '%s\n' 'THT_MODEL_API_KEY=fixture-model-api-key' >"$secrets_file"
chmod 0600 "$secrets_file"
installation_file="$tmp/thothii-installation.yaml"
printf '%s\n' 'schemaVersion: 2' >"$installation_file"
chmod 0600 "$installation_file"
rendered=$(THT_WORKSPACE_GIT_REMOTE=https://git.example.invalid/platform/thoth-workspaces.git \
PI_AUTH_FILE="$auth_file" THT_SECRETS_FILE="$secrets_file" \
THT_AUTH_CONFIG_ROOT="$auth_config" docker compose config)
THT_AUTH_CONFIG_ROOT="$auth_config" THT_INSTALLATION_CONFIG_SOURCE="$installation_file" \
docker compose config)
printf '%s\n' "$rendered" | grep -q "source: $auth_file"
printf '%s\n' "$rendered" | grep -q 'target: /home/thoth/.pi/agent/auth.json'
printf '%s\n' "$rendered" | grep -A4 'target: /home/thoth/.pi/agent/auth.json' \
| grep -q 'read_only: true'
for target in \
/home/thoth/.pi/agent/models.json \
/home/thoth/.pi/agent/settings.json; do
printf '%s\n' "$rendered" | grep -q "target: $target"
printf '%s\n' "$rendered" | grep -A4 "target: $target" | grep -q 'read_only: true'
done
if printf '%s\n' "$rendered" | grep -Eq '/home/thoth/\.pi/agent/(models|settings)\.json'; then
echo "base Compose must not mount model projections without the generated override" >&2
exit 1
fi
printf '%s\n' "$rendered" | grep -q "file: $secrets_file"
printf '%s\n' "$rendered" | grep -q 'target: thothii.secrets'
if grep -Fq 'fixture-model-api-key' <<<"$rendered"; then
@@ -39,12 +41,14 @@ fi
dev_rendered=$(THT_WORKSPACE_GIT_REMOTE=https://git.example.invalid/platform/thoth-workspaces.git \
PI_AUTH_FILE="$auth_file" THT_SECRETS_FILE="$secrets_file" \
THT_AUTH_CONFIG_ROOT="$auth_config" \
THT_AUTH_CONFIG_ROOT="$auth_config" THT_INSTALLATION_CONFIG_SOURCE="$installation_file" \
docker compose --env-file deploy/env/local.env.example -f docker-compose.dev.yml config)
printf '%s\n' "$dev_rendered" | grep -q "source: $auth_file"
printf '%s\n' "$dev_rendered" | grep -q 'target: /home/thoth/.pi/agent/auth.json'
printf '%s\n' "$dev_rendered" | grep -q 'target: /home/thoth/.pi/agent/models.json'
printf '%s\n' "$dev_rendered" | grep -q 'target: /home/thoth/.pi/agent/settings.json'
if printf '%s\n' "$dev_rendered" | grep -Eq '/home/thoth/\.pi/agent/(models|settings)\.json'; then
echo "development Compose must not contain legacy model sources" >&2
exit 1
fi
printf '%s\n' "$dev_rendered" | grep -q "file: $secrets_file"
printf '%s\n' "$dev_rendered" | grep -q 'target: thothii.secrets'
if grep -Fq 'fixture-model-api-key' <<<"$dev_rendered"; then
@@ -52,27 +56,8 @@ if grep -Fq 'fixture-model-api-key' <<<"$dev_rendered"; then
exit 1
fi
python3 - <<'PY'
import json
from pathlib import Path
settings = json.loads(Path("deploy/pi/settings.json").read_text())
assert settings["enabledModels"] == [
"zai/glm-5.3",
"deepseek/deepseek-v4-flash",
"deepseek/deepseek-v4-pro",
"local-qwen/qwen3.6-35b-a3b",
]
models = json.loads(Path("deploy/pi/models.json").read_text())
qwen = models["providers"]["local-qwen"]
assert qwen["api"] == "openai-completions"
assert qwen["models"][0]["id"] == "qwen3.6-35b-a3b"
assert qwen["models"][0]["compat"]["maxTokensField"] == "max_tokens"
assert "aritmolab" not in models["providers"]
PY
if grep -R -n 'registerProvider' harness/.pi/extensions; then
echo "Pi model providers must be declared in deploy/pi/models.json, not in code" >&2
echo "Pi model providers must be declared in the Installation Model Catalog, not in code" >&2
exit 1
fi
+9 -23
View File
@@ -41,8 +41,6 @@ TASK13_AUTHENTIK_API_TOKEN="fixture-authentik-token-$profile"
TASK13_FRONTEND_PORT=18080
TASK13_SESSION_RUNTIME_PASSWORD="$fixture/runtime-password"
TASK13_SESSION_CA="$fixture/session-ca.pem"
TASK13_PI_MODELS="$fixture/models.json"
TASK13_PI_SETTINGS="$fixture/settings.json"
TASK13_LLM_SERVER="$fixture/fake-llm.mjs"
TASK13_LLM_CONTAINER="$TASK13_PROJECT-llm"
TASK13_REMOTE="$fixture/remote.git"
@@ -54,22 +52,8 @@ cp "$root/scripts/fixtures/workspace-registry-task13.yaml" "$workspace"
if [[ "$profile" == local ]]; then
task13_write_fixture_files
node - "$TASK13_PI_MODELS" <<'NODE'
const fs = require("fs");
const models = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
const model = models.providers?.["local-qwen"]?.models?.find(
(candidate) => candidate?.id === "task13-smoke",
);
if (!model || JSON.stringify(model.input) !== JSON.stringify(["text"])) {
throw new Error("Task 13 provider smoke model must declare text input support");
}
const expectedCost = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
if (JSON.stringify(model.cost) !== JSON.stringify(expectedCost)) {
throw new Error("Task 13 provider smoke model must declare complete zero-cost metadata");
}
NODE
task13_write_environment /fixtures/remote.git
compose_files=(-f "$root/compose.yaml" -f "$root/deploy/compose.local.yaml" -f "$TASK13_OVERRIDE")
compose_files=(-f "$root/compose.yaml" -f "$root/deploy/compose.local.yaml" -f "$TASK13_OVERRIDE" -f "$fixture/generated/compose.models.yaml")
else
TASK13_SERVER_DATA="$fixture/Server Data"
TASK13_SERVER_PI_STATE="$fixture/Server Pi State"
@@ -106,6 +90,7 @@ else
-f "$root/deploy/compose.server.yaml"
-f "$root/deploy/compose.session-server.yaml.example"
-f "$TASK13_OVERRIDE"
-f "$fixture/generated/compose.models.yaml"
-f "$root/deploy/compose.auth-runtime-projection.yaml"
)
fi
@@ -188,7 +173,7 @@ NODE
fi
done
for mutation in collection-reuse dimension-change; do
for mutation in workspace-semantic-index workspace-llm-policy; do
mutated="$fixture/$mutation.yaml"
node - "$root/backend/package.json" "$workspace" "$mutated" "$mutation" <<'NODE'
const fs = require("fs");
@@ -197,11 +182,12 @@ const requireFromBackend = createRequire(process.argv[2]);
const yaml = requireFromBackend("yaml");
const [source, destination, mutation] = process.argv.slice(3);
const workspace = yaml.parse(fs.readFileSync(source, "utf8"));
if (mutation === "collection-reuse") {
workspace.semantic_index.vector_store.collection = "shared-semantic";
} else if (mutation === "dimension-change") {
workspace.semantic_index.vector_store.dimensions = 1536;
workspace.semantic_index.embedding.dimensions = 1536;
if (mutation === "workspace-semantic-index") {
workspace.semantic_index = {
vector_store: { engine: "qdrant", collection: "shared-semantic", dimensions: 1536 },
};
} else if (mutation === "workspace-llm-policy") {
workspace.llm_policy = { provider: "openai", model: "gpt-test" };
}
fs.writeFileSync(destination, yaml.stringify(workspace));
NODE
+6 -4
View File
@@ -48,11 +48,12 @@ if (!Object.hasOwn(coreEnv, "THT_LLM_URL")) {
for (const [key, value] of Object.entries({
THT_INTERNAL_QDRANT_URL: "http://qdrant:6333",
THT_INTERNAL_EMBEDDING_URL: "http://embedding:11434",
THT_INTERNAL_EMBEDDING_MODEL: "qwen3-embedding:0.6b",
THT_INTERNAL_EMBEDDING_DIMENSIONS: "1024",
})) {
if (coreEnv[key] !== value) throw new Error(`unexpected core ${key}: ${coreEnv[key]}`);
}
for (const key of ["THT_INTERNAL_EMBEDDING_ID", "THT_INTERNAL_EMBEDDING_MODEL", "THT_INTERNAL_EMBEDDING_DIMENSIONS"]) {
if (Object.hasOwn(coreEnv, key)) throw new Error(`${key} must come only from the generated installation projection`);
}
for (const forbidden of ["THT_VEC_REST_URL", "THT_VEC_WRITE_REST_URL", "THT_OLLAMA_URL"]) {
if (Object.hasOwn(coreEnv, forbidden) && coreEnv[forbidden] !== "") {
throw new Error(`core must not require external semantic binding ${forbidden}`);
@@ -189,13 +190,14 @@ if (!Object.hasOwn(config.services.core.environment || {}, "THT_LLM_URL")) {
for (const [key, value] of Object.entries({
THT_INTERNAL_QDRANT_URL: "http://qdrant:6333",
THT_INTERNAL_EMBEDDING_URL: "http://embedding:11434",
THT_INTERNAL_EMBEDDING_MODEL: "qwen3-embedding:0.6b",
THT_INTERNAL_EMBEDDING_DIMENSIONS: "1024",
})) {
if (config.services.core.environment?.[key] !== value) {
throw new Error(`unexpected core ${key}: ${config.services.core.environment?.[key]}`);
}
}
for (const key of ["THT_INTERNAL_EMBEDDING_ID", "THT_INTERNAL_EMBEDDING_MODEL", "THT_INTERNAL_EMBEDDING_DIMENSIONS"]) {
if (Object.hasOwn(config.services.core.environment || {}, key)) throw new Error(`${key} must come only from the generated installation projection`);
}
for (const serviceName of ["qdrant", "embedding", "embedding-model-init"]) {
if ((config.services[serviceName].ports || []).length !== 0) {
throw new Error(`${serviceName} must not publish a host port`);
+18 -27
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env bash
# Regression tests for the fail-closed schema-v3-only absence gate.
# Regression tests for the fail-closed schema-v4-only absence gate.
set -euo pipefail
project_root="$(cd "$(dirname "$0")/.." && pwd -P)"
@@ -25,7 +25,7 @@ fail() {
}
write_fixture_descriptor() {
local path="$1" workspace_key="${2:-workspace:}" schema_key="${3:- schema_version: 3}"
local path="$1" workspace_key="${2:-workspace:}" schema_key="${3:- schema_version: 4}"
printf '%s\n' "$workspace_key" "$schema_key" >"$path"
cat >>"$path" <<'YAML'
id: fixture-workspace
@@ -36,18 +36,6 @@ dwh:
database: warehouse
schema: public
supported_transports: [postgres_direct]
semantic_index:
vector_store:
engine: qdrant
collection: fixture-workspace
dimensions: 1024
distance: cosine
embedding:
provider: ollama_internal
model: qwen3-embedding:0.6b
dimensions: 1024
llm_policy:
allowed: [fixture/model]
YAML
}
@@ -62,7 +50,7 @@ seed_fixture() {
"$real_git" -C "$fixture" init -q
"$real_git" -C "$fixture" config user.email fixture@example.invalid
"$real_git" -C "$fixture" config user.name Fixture
printf '%s\n' 'export const schemaVersion = 3;' >"$fixture/backend/src/server.ts"
printf '%s\n' 'export const schemaVersion = 4;' >"$fixture/backend/src/server.ts"
printf '%s\n' 'export const spaced = true;' >"$fixture/backend/src/nested dir/file name.ts"
newline_path="$fixture/backend/src/line
break.ts"
@@ -74,7 +62,7 @@ break.ts"
printf '%s\n' '#!/usr/bin/env bash' 'echo operator-smoke' >"$fixture/scripts/workspace-registry-smoke.sh"
write_fixture_descriptor "$fixture/scripts/fixtures/workspace-registry-smoke.yaml"
write_fixture_descriptor "$fixture/scripts/fixtures/workspace-registry-windows.yaml"
printf '%s\n' 'Write-Output "schema v3"' >"$fixture/scripts/test-windows-clone-contract.ps1"
printf '%s\n' 'Write-Output "schema v4"' >"$fixture/scripts/test-windows-clone-contract.ps1"
"$real_git" -C "$fixture" add .
"$real_git" -C "$fixture" commit -qm seed
}
@@ -307,14 +295,14 @@ EOF
commit_fixture powershell-bundle
expect_pass "PowerShell non-workspace bundle"
# Quoted/space/indented YAML keys are real mappings; v3 passes and non-v3 fails.
# Quoted/space/indented YAML keys are real mappings; v4 passes and non-v4 fails.
seed_fixture
write_fixture_descriptor \
"$fixture/deploy/workspaces/example.yaml" \
'"workspace" :' \
" 'schema_version' : 3"
commit_fixture quoted-yaml-v3
expect_pass "quoted and indented workspace v3"
" 'schema_version' : 4"
commit_fixture quoted-yaml-v4
expect_pass "quoted and indented workspace v4"
seed_fixture
cat >"$fixture/deploy/workspaces/example.yaml" <<'EOF'
@@ -325,7 +313,7 @@ commit_fixture quoted-yaml-noncanonical
expect_rejected "quoted workspace noncanonical schema" "deploy/workspaces/example.yaml"
seed_fixture
printf '%s\n' 'workspace: { schema_version: 3 }' >"$fixture/deploy/workspaces/example.yaml"
printf '%s\n' 'workspace: { schema_version: 4 }' >"$fixture/deploy/workspaces/example.yaml"
commit_fixture inline-workspace
expect_rejected "inline workspace mapping" "deploy/workspaces/example.yaml"
@@ -388,8 +376,8 @@ printf '%s\n' 'const revision = { revision: { id: "x", state: "operational" } };
commit_fixture revision-object-state
expect_rejected "bounded revision object state" "backend/scripts/runtime-check.mjs"
# A top-level workspace descriptor has one exact schema_version: 3 key.
for malformed in schema-v1 schema-v2 leading-zero hexadecimal multiline duplicate; do
# A top-level workspace descriptor has one exact schema_version: 4 key.
for malformed in schema-v1 schema-v2 schema-v3 leading-zero hexadecimal multiline duplicate; do
seed_fixture
case "$malformed" in
schema-v1)
@@ -398,17 +386,20 @@ for malformed in schema-v1 schema-v2 leading-zero hexadecimal multiline duplicat
schema-v2)
printf '%s\n' 'workspace:' ' schema_version: 2' >"$fixture/deploy/workspaces/example.yaml"
;;
schema-v3)
printf '%s\n' 'workspace:' ' schema_version: 3' >"$fixture/deploy/workspaces/example.yaml"
;;
leading-zero)
printf '%s\n' 'workspace:' ' schema_version: 02' >"$fixture/deploy/workspaces/example.yaml"
printf '%s\n' 'workspace:' ' schema_version: 04' >"$fixture/deploy/workspaces/example.yaml"
;;
hexadecimal)
printf '%s\n' 'workspace:' ' schema_version: 0x2' >"$fixture/deploy/workspaces/example.yaml"
printf '%s\n' 'workspace:' ' schema_version: 0x4' >"$fixture/deploy/workspaces/example.yaml"
;;
multiline)
printf '%s\n' 'workspace:' ' schema_version: >' ' 3' >"$fixture/deploy/workspaces/example.yaml"
printf '%s\n' 'workspace:' ' schema_version: >' ' 4' >"$fixture/deploy/workspaces/example.yaml"
;;
duplicate)
printf '%s\n' 'workspace:' ' schema_version: 3' ' schema_version: 3' >"$fixture/deploy/workspaces/example.yaml"
printf '%s\n' 'workspace:' ' schema_version: 4' ' schema_version: 4' >"$fixture/deploy/workspaces/example.yaml"
;;
esac
commit_fixture "yaml-$malformed"
@@ -91,8 +91,8 @@ CASES = [
def workspace_region(extra=""):
body = (
"Schema v3 is the only accepted workspace descriptor. "
"Schema v1 and v2 workspace descriptors are rejected before activation."
"Schema v4 is the only accepted workspace descriptor. "
"Schema v1, v2, and v3 workspace descriptors are rejected before activation."
)
if extra:
body += "\n" + extra
@@ -335,16 +335,16 @@ class RegionStructureTests(unittest.TestCase):
def test_noncanonical_contract_sentences_are_rejected(self):
variants = (
workspace_region().replace(
"Schema v3 is the only accepted workspace descriptor.",
"Only schema v3 workspace descriptors are accepted.",
"Schema v4 is the only accepted workspace descriptor.",
"Only schema v4 workspace descriptors are accepted.",
),
workspace_region().replace(
"Schema v3 is the only accepted workspace descriptor.",
"Not Schema v3 is the only accepted workspace descriptor.",
"Schema v4 is the only accepted workspace descriptor.",
"Not Schema v4 is the only accepted workspace descriptor.",
),
workspace_region().replace(
"Schema v1 and v2 workspace descriptors are rejected before activation.",
"Not Schema v1 and v2 workspace descriptors are rejected before activation.",
"Schema v1, v2, and v3 workspace descriptors are rejected before activation.",
"Not Schema v1, v2, and v3 workspace descriptors are rejected before activation.",
),
)
for index, noncanonical in enumerate(variants):
+148 -47
View File
@@ -317,12 +317,14 @@ task13_compose_files() {
-f "$TASK13_ROOT/deploy/compose.server.yaml"
-f "$TASK13_ROOT/deploy/compose.session-server.yaml.example"
-f "$TASK13_OVERRIDE"
-f "${TASK13_INSTALLATION%/*}/generated/compose.models.yaml"
-f "$TASK13_ROOT/deploy/compose.auth-runtime-projection.yaml"
)
else
TASK13_COMPOSE+=(
-f "$TASK13_ROOT/deploy/compose.local.yaml"
-f "$TASK13_OVERRIDE"
-f "${TASK13_INSTALLATION%/*}/generated/compose.models.yaml"
)
fi
if [[ "${TASK13_PROFILE:-local}" == local && -f "$TASK13_CURRENT_IMAGE_OVERRIDE" ]]; then
@@ -414,6 +416,100 @@ EOF
chmod 0600 "$TASK13_SESSION_CA"
}
# Test-only materialization of the same deterministic boundary adapters covered by the Go
# modelprojection tests. Production lifecycle commands always generate these files themselves.
task13_write_model_projection_fixture() {
python3 - "$TASK13_INSTALLATION" "$TASK13_LLM_CONTAINER" <<'PY'
import hashlib
import json
from pathlib import Path
import sys
installation = Path(sys.argv[1])
provider_host = sys.argv[2]
generated = installation.parent / "generated"
(generated / "pi").mkdir(parents=True, exist_ok=True)
catalog = {
"schemaVersion": 1,
"defaultSession": "local-qwen/task13-smoke",
"embedding": {"id": "ollama/qwen3-embedding:0.6b", "dimensions": 1024},
"models": [{
"id": "local-qwen/task13-smoke",
"provider": "local-qwen",
"model": "task13-smoke",
"label": "Task 13 deterministic smoke",
"upstreamModel": "task13-smoke",
"endpoint": {"baseUrl": f"http://{provider_host}:9000/v1"},
"authentication": {"mode": "none"},
"sessionAdapter": {"mode": "openai_compatible"},
"session": {
"reasoning": False,
"input": ["text"],
"cost": {"input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0},
"contextWindow": 4096,
"maxTokens": 64,
},
}],
}
models = {
"providers": {"local-qwen": {
"baseUrl": f"http://{provider_host}:9000/v1",
"api": "openai-completions",
"apiKey": "local",
"models": [{
"id": "task13-smoke", "name": "Task 13 deterministic smoke",
"reasoning": False, "input": ["text"],
"cost": {"input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0},
"contextWindow": 4096, "maxTokens": 64,
}],
}},
}
settings = {"defaultProjectTrust": "always", "enabledModels": ["local-qwen/task13-smoke"]}
serialized = []
for path, value in (
(generated / "catalog.json", catalog),
(generated / "pi/models.json", models),
(generated / "pi/settings.json", settings),
):
raw = json.dumps(value, indent=2, sort_keys=True) + "\n"
path.write_text(raw)
serialized.append(raw.encode())
revision = "sha256:" + hashlib.sha256(b"".join(serialized)).hexdigest()
quote = json.dumps
(generated / "compose.models.yaml").write_text(f"""services:
core:
environment:
THT_MODEL_CATALOG_FILE: /run/thothii-model-catalog/catalog.json
THT_MODEL_CATALOG_REVISION: {quote(revision)}
THT_DEFAULT_SESSION_MODEL: local-qwen/task13-smoke
THT_INTERNAL_EMBEDDING_ID: ollama/qwen3-embedding:0.6b
THT_INTERNAL_EMBEDDING_MODEL: qwen3-embedding:0.6b
THT_INTERNAL_EMBEDDING_DIMENSIONS: '1024'
volumes:
- type: bind
source: {quote(str(generated / 'catalog.json'))}
target: /run/thothii-model-catalog/catalog.json
read_only: true
- type: bind
source: {quote(str(generated / 'pi/models.json'))}
target: /home/thoth/.pi/agent/models.json
read_only: true
- type: bind
source: {quote(str(generated / 'pi/settings.json'))}
target: /home/thoth/.pi/agent/settings.json
read_only: true
workspace-maintenance:
environment:
THT_INTERNAL_EMBEDDING_ID: ollama/qwen3-embedding:0.6b
THT_INTERNAL_EMBEDDING_MODEL: qwen3-embedding:0.6b
THT_INTERNAL_EMBEDDING_DIMENSIONS: '1024'
embedding-model-init:
environment:
OLLAMA_MODEL: qwen3-embedding:0.6b
""")
PY
}
task13_write_fixture_files() {
printf '{}\n' >"$TASK13_PI_AUTH"
printf 'THT_MODEL_API_KEY=%s\n' "$TASK13_SECRET_VALUE" >"$TASK13_SECRETS"
@@ -425,41 +521,6 @@ task13_write_fixture_files() {
chmod 0700 "$TASK13_AUTH_ROOT"
chmod 0600 "$TASK13_SECRETS" "$TASK13_SESSION_RUNTIME_PASSWORD" "$TASK13_AUTH_PASSWORD_FILE"
cat >"$TASK13_PI_MODELS" <<EOF
{
"providers": {
"local-qwen": {
"baseUrl": "http://$TASK13_LLM_CONTAINER:9000/v1",
"api": "openai-completions",
"apiKey": "task13-local-provider",
"models": [
{
"id": "task13-smoke",
"name": "Task 13 deterministic smoke",
"reasoning": false,
"input": ["text"],
"cost": {
"input": 0,
"output": 0,
"cacheRead": 0,
"cacheWrite": 0
},
"contextWindow": 4096,
"maxTokens": 64
}
]
}
}
}
EOF
cat >"$TASK13_PI_SETTINGS" <<'EOF'
{
"defaultProjectTrust": "always",
"enabledModels": ["local-qwen/task13-smoke"]
}
EOF
chmod 0644 "$TASK13_PI_MODELS" "$TASK13_PI_SETTINGS"
cat >"$TASK13_LLM_SERVER" <<'EOF'
import http from "node:http";
@@ -517,8 +578,6 @@ services:
labels:
io.thothii.task13.run: "$TASK13_RUN_ID"
environment:
PI_PROVIDER: local-qwen
PI_MODEL: task13-smoke
PI_THINKING: low
THT_WORKSPACE_INSTALLATION_ID: task13-smoke
THT_WORKSPACE_REGISTRY_ROOT: /data/workspace-registry
@@ -605,15 +664,40 @@ EOF
chmod 0600 "$TASK13_OVERRIDE"
cat >"$TASK13_INSTALLATION" <<EOF
schemaVersion: 2
profile: local
projectDirectory: "$TASK13_ROOT"
envFile: "$TASK13_ENV_FILE"
modelCatalog:
defaults:
session: local-qwen/task13-smoke
embedding:
id: ollama/qwen3-embedding:0.6b
dimensions: 1024
providers:
local-qwen:
endpoint:
baseUrl: http://$TASK13_LLM_CONTAINER:9000/v1
authentication:
mode: none
session:
mode: openai_compatible
models:
task13-smoke:
label: Task 13 deterministic smoke
session:
reasoning: false
input: [text]
cost: {input: 0, output: 0, cacheRead: 0, cacheWrite: 0}
contextWindow: 4096
maxTokens: 64
authentication:
configDirectory: "$TASK13_AUTH_ROOT"
overrides:
- "$TASK13_OVERRIDE"
EOF
chmod 0600 "$TASK13_INSTALLATION"
task13_write_model_projection_fixture
}
task13_write_server_fixture_files() {
@@ -805,9 +889,33 @@ EOF
chmod 0600 "$TASK13_ENV_FILE"
cat >"$TASK13_INSTALLATION" <<EOF
schemaVersion: 2
profile: server
projectDirectory: "$TASK13_ROOT"
envFile: "$TASK13_ENV_FILE"
modelCatalog:
defaults:
session: local-qwen/task13-smoke
embedding:
id: ollama/qwen3-embedding:0.6b
dimensions: 1024
providers:
local-qwen:
endpoint:
baseUrl: http://$TASK13_LLM_CONTAINER:9000/v1
authentication:
mode: none
session:
mode: openai_compatible
models:
task13-smoke:
label: Task 13 deterministic smoke
session:
reasoning: false
input: [text]
cost: {input: 0, output: 0, cacheRead: 0, cacheWrite: 0}
contextWindow: 4096
maxTokens: 64
authentication:
configDirectory: "$TASK13_AUTH_ROOT"
runtimeProjection:
@@ -819,6 +927,7 @@ overrides:
- "$TASK13_OVERRIDE"
EOF
chmod 0600 "$TASK13_INSTALLATION"
task13_write_model_projection_fixture
}
task13_workspace_metadata() {
@@ -1058,23 +1167,17 @@ task13_prepare_local_pi_runtime() {
--user 0:0 \
--entrypoint sh \
--volume "$TASK13_PI_AUTH:/source/auth.json:ro" \
--volume "$TASK13_PI_MODELS:/source/models.json:ro" \
--volume "$TASK13_PI_SETTINGS:/source/settings.json:ro" \
--volume "$TASK13_PI_RUNTIME_VOLUME:/target" \
"$TASK13_CORE_IMAGE" -ceu '
test -f /source/auth.json && test ! -L /source/auth.json
test -f /source/models.json && test ! -L /source/models.json
test -f /source/settings.json && test ! -L /source/settings.json
test -d /target/agent && test ! -L /target/agent
test -z "$(find /target -mindepth 1 -maxdepth 1 ! -name agent -print -quit)"
test -z "$(find /target/agent -mindepth 1 -maxdepth 1 -print -quit)"
cp /source/auth.json /source/models.json /source/settings.json /target/agent/
cp /source/auth.json /target/agent/
chown -R 10001:10001 /target
chmod 0700 /target /target/agent
chmod 0600 /target/agent/auth.json /target/agent/models.json /target/agent/settings.json
chmod 0600 /target/agent/auth.json
test "$(stat -c "%u:%g:%a" /target/agent/auth.json)" = 10001:10001:600
test "$(stat -c "%u:%g:%a" /target/agent/models.json)" = 10001:10001:600
test "$(stat -c "%u:%g:%a" /target/agent/settings.json)" = 10001:10001:600
'
}
@@ -2918,8 +3021,6 @@ task13_initialize() {
TASK13_AUTH_PASSWORD="task13-auth-$TASK13_RUN_ID"
TASK13_OIDC_CLIENT_SECRET="task13-oidc-client-$TASK13_RUN_ID"
TASK13_AUTHENTIK_API_TOKEN="task13-authentik-token-$TASK13_RUN_ID"
TASK13_PI_MODELS="$TASK13_TMP/models.json"
TASK13_PI_SETTINGS="$TASK13_TMP/pi-settings.json"
TASK13_LLM_SERVER="$TASK13_TMP/fake-llm.mjs"
TASK13_OIDC_SERVER="$TASK13_TMP/fake-oidc.mjs"
TASK13_OIDC_CERT="$TASK13_TMP/fake-oidc-cert.pem"
+3 -3
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env bash
# Reproducible schema-v3-only release gate. The Git checkout is the trust root;
# Reproducible schema-v4-only release gate. The Git checkout is the trust root;
# dependencies come from backend/package-lock.json and dist comes from a clean build.
set -euo pipefail
export PYTHONDONTWRITEBYTECODE=1
@@ -12,7 +12,7 @@ export NPM_CONFIG_GLOBALCONFIG=<private-empty-global-config>
/bin/bash scripts/verify-schema-v3-only.sh --bootstrap-trust-only
(cd backend && npm ci --ignore-scripts)
(cd backend && npm run build)
(cd backend && npm run test:schema-v3-verifier)
(cd backend && npm run test:schema-v4-verifier)
/bin/bash scripts/test-verify-schema-v3-only.sh
/bin/bash scripts/verify-schema-v3-only.sh
EOF
@@ -29,6 +29,6 @@ export NPM_CONFIG_GLOBALCONFIG="$release_tmp/npm-globalconfig"
/bin/bash "$root/scripts/verify-schema-v3-only.sh" --bootstrap-trust-only
(cd "$root/backend" && npm ci --ignore-scripts)
(cd "$root/backend" && npm run build)
(cd "$root/backend" && npm run test:schema-v3-verifier)
(cd "$root/backend" && npm run test:schema-v4-verifier)
/bin/bash "$root/scripts/test-verify-schema-v3-only.sh"
/bin/bash "$root/scripts/verify-schema-v3-only.sh"
+6 -5
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env bash
# Fail-closed absence gate for the supported schema-v3-only workspace runtime.
# Fail-closed absence gate for the supported schema-v4-only workspace runtime.
set -euo pipefail
shopt -s nocasematch
@@ -31,7 +31,7 @@ Release trust anchor and order (durable entry point):
checkout and performs an inline clean-checkout assertion before the wrapper.
scripts/verify-schema-v3-only-release.sh then runs npm ci --ignore-scripts from the trusted
backend/package-lock.json; clean build; npm Node verifier test; Bash regression;
and the full schema-v3-only gate, which repeats trust checks.
and the full schema-v4-only gate, which repeats trust checks.
EOF
}
@@ -54,7 +54,7 @@ if ! root="$(cd "$root_argument" 2>/dev/null && pwd -P)"; then
fi
[[ $runtime_only -eq 1 || $bootstrap_trust_only -eq 1 || "$root" == "$script_root" ]] || { echo "full mode is restricted to the canonical repository" >&2; exit 2; }
tmp="$(mktemp -d "${TMPDIR:-/tmp}/thoth-v3-gate.XXXXXX")"
tmp="$(mktemp -d "${TMPDIR:-/tmp}/thoth-v4-gate.XXXXXX")"
trap 'rm -rf "$tmp"' EXIT HUP INT TERM
if git_top="$(git -C "$root" rev-parse --show-toplevel 2>"$tmp/rev-parse")"; then :; else
@@ -88,6 +88,7 @@ is_allowed_match() {
legacy-workspace:scripts/verify-schema-v3-only.sh|legacy-workspace:scripts/test-verify-schema-v3-only.sh|legacy-workspace:backend/scripts/verify-workspace-descriptor-files.mjs|legacy-workspace:backend/scripts/verify-workspace-descriptor-files.test.mjs|legacy-workspace:backend/scripts/revision-state-policy.mjs|legacy-workspace:backend/scripts/revision-state-policy.test.mjs|legacy-workspace:backend/scripts/bash-heredoc.mjs|legacy-workspace:backend/scripts/revision_state_policy.py|legacy-workspace:backend/scripts/test_revision_state_policy.py) return 0 ;;
migration-marker:scripts/verify-schema-v3-only.sh|migration-marker:scripts/test-verify-schema-v3-only.sh|migration-marker:backend/scripts/verify-workspace-descriptor-files.mjs|migration-marker:backend/scripts/verify-workspace-descriptor-files.test.mjs|migration-marker:backend/scripts/revision-state-policy.mjs|migration-marker:backend/scripts/revision-state-policy.test.mjs|migration-marker:backend/scripts/bash-heredoc.mjs|migration-marker:backend/scripts/revision_state_policy.py|migration-marker:backend/scripts/test_revision_state_policy.py) return 0 ;;
migration-marker:scripts/workspace_descriptor_doc_contract.py|migration-marker:scripts/test_workspace_descriptor_doc_contract.py) return 0 ;;
migration-marker:backend/src/workspaces/schema.ts) return 0 ;;
migration-marker:backend/scripts/clean-dist.test.mjs) return 0 ;;
*) return 1 ;;
esac
@@ -204,7 +205,7 @@ bootstrap_files=(
)
if [[ $bootstrap_trust_only -eq 1 ]]; then
require_trusted_files_at "$root" "${bootstrap_files[@]}"
echo "schema-v3-only bootstrap trust passed"
echo "schema-v4-only bootstrap trust passed"
exit 0
fi
@@ -272,4 +273,4 @@ if [[ $runtime_only -eq 0 ]]; then
--document "$root/docs/operations/workspaces.md"
fi
echo "schema-v3-only absence gate passed"
echo "schema-v4-only absence gate passed"
+3 -2
View File
@@ -123,8 +123,9 @@ bindings = pathlib.Path(sys.argv[3]).read_text()
if local.get("profile") != "local" or server.get("profile") != "server":
raise SystemExit("installation examples must retain their local/server profiles")
for document in (local, server):
if "metadataGeneration" not in document or "authentication" not in document:
raise SystemExit("installation example lacks metadata or authentication configuration")
catalog = document.get("modelCatalog")
if not isinstance(catalog, dict) or "providers" not in catalog or "authentication" not in document:
raise SystemExit("installation example lacks model catalog or authentication configuration")
if re.search(r"(?:KEY|PASSWORD|TOKEN|SECRET)=[^\n#<][^\n]*", bindings):
raise SystemExit("workspace bindings example contains a secret value")
PY
+10 -10
View File
@@ -14,9 +14,9 @@ from pathlib import Path
WORKSPACE_REGION = "workspace-descriptor-contract"
NON_WORKSPACE_REGION = "non-workspace-migration"
HISTORICAL_ARCHIVE_H1 = "# Historical archive"
CANONICAL_V3_SENTENCE = "Schema v3 is the only accepted workspace descriptor."
CANONICAL_V4_SENTENCE = "Schema v4 is the only accepted workspace descriptor."
CANONICAL_REJECTION_SENTENCE = (
"Schema v1 and v2 workspace descriptors are rejected before activation."
"Schema v1, v2, and v3 workspace descriptors are rejected before activation."
)
_MARKER_LINE = re.compile(
@@ -34,8 +34,8 @@ _DELETED_TOOL = re.compile(
re.IGNORECASE,
)
_REMAINING_LEGACY_CLAIM = re.compile(
r"\b(?:schema(?:[- ]v?|\s+version\s*)[12]|"
r"schema_version\s*[:=]\s*[12]|version\s*[12]|v[12]|"
r"\b(?:schema(?:[- ]v?|\s+version\s*)[123]|"
r"schema_version\s*[:=]\s*[123]|version\s*[123]|v[123]|"
r"legacy(?:[-\s]+workspace)?[-\s]+descriptors?)\b",
re.IGNORECASE,
)
@@ -48,9 +48,9 @@ _CODE_RESTORE = str.maketrans(
)
_OUTSIDE_LEGACY_REFERENCE = re.compile(
r"\b(?:schema(?:[- ]v?|\s+version\s*)[12]|"
r"schema_version\s*[:=]\s*[12]|"
r"(?:v[12](?:\s*(?:/|and|or)\s*v[12])?)\s+(?:workspace\s+)?descriptors?|"
r"\b(?:schema(?:[- ]v?|\s+version\s*)[123]|"
r"schema_version\s*[:=]\s*[123]|"
r"(?:v[123](?:\s*(?:/|and|or|,)\s*v[123])*)\s+(?:workspace\s+)?descriptors?|"
r"legacy(?:[-\s]+workspace)?[-\s]+descriptors?)\b",
re.IGNORECASE,
)
@@ -355,11 +355,11 @@ def check_document_text(text: str, label: str = "document") -> None:
workspace_prose = _render_prose_markdown(
scan.prose_text[workspace.start : workspace.end]
)
if _exact_sentence_count(workspace_prose, CANONICAL_V3_SENTENCE) != 1:
raise ContractError(f"{label}: workspace contract lacks the canonical v3-only sentence")
if _exact_sentence_count(workspace_prose, CANONICAL_V4_SENTENCE) != 1:
raise ContractError(f"{label}: workspace contract lacks the canonical v4-only sentence")
if _exact_sentence_count(workspace_prose, CANONICAL_REJECTION_SENTENCE) != 1:
raise ContractError(
f"{label}: workspace contract lacks the canonical v1/v2 rejection sentence"
f"{label}: workspace contract lacks the canonical v1/v2/v3 rejection sentence"
)
workspace_visible = _render_markdown(scan.operator_text[workspace.start : workspace.end])