docs: make schema v3 the only workspace contract

This commit is contained in:
2026-08-11 00:00:54 +02:00
parent edef085fea
commit bb17d6d435
7 changed files with 326 additions and 55 deletions
+15 -10
View File
@@ -35,8 +35,12 @@
cache in `embedding-models`, and `embedding-model-init` blocks `core` until
`qwen3-embedding:0.6b` is present.
- **Semantic contract.** Internal semantic indexing is fixed to `qwen3-embedding:0.6b`,
`1024` dimensions, and cosine distance. Schema-v3 descriptors are operational; schema-v1/v2 descriptors remain `migration_required` until an explicit reviewed migration writes schema version 3. One workspace owns one Qdrant collection, and schema, Evidence, and Memory records
coexist inside that collection with payload `kind` separation.
`1024` dimensions, and cosine distance. Schema v3 is the only accepted workspace descriptor
format. Schema v1 and v2 descriptors are rejected while a candidate snapshot is validated, so
activation or a pull fails atomically and leaves the prior valid snapshot active; there is no
in-product migrator or automatic conversion. One workspace owns one Qdrant collection, and
schema, Evidence, and Memory records coexist inside that collection with payload `kind`
separation.
- **Final review runtime barriers.** Operational routes, retained session pins, and runtime
rendering now require schema version 3 before resolving bindings, readiness, diagnostics, or
Pi. Session admission verifies the exact internal Qdrant collection (dimensions, cosine
@@ -53,11 +57,13 @@
archives exactly one labeled `<project>_qdrant-data` volume and preserves the prior `qdrant`
running state. `./scripts/vector-restore.sh --project-name <name> --input <file>
--confirm-project <name>` requires the exact repeated project confirmation, validates manifest
and archive safety before stopping `qdrant`, stages rollback content, restores in place, and
restarts `qdrant` only if it was previously running. Restore does not migrate legacy workspace
descriptors, rename collections, or repair a semantic-index incompatibility. Backup and restore
share one atomic Docker-daemon lock per Compose project/Qdrant volume; contenders fail before
volume resolution, and cleanup removes the lock only when its ownership labels still match.
and archive safety before stopping `qdrant`, stages rollback content, restores semantic storage
in place, and restarts `qdrant` only if it was previously running. Recovery requires the registry
to already hold a reviewed v3 descriptor revision compatible with the restored collection; the
helper does not restore descriptors, rename collections, or repair a semantic-index
incompatibility. Backup and restore share one atomic Docker-daemon lock per Compose
project/Qdrant volume; contenders fail before volume resolution, and cleanup removes the lock
only when its ownership labels still match.
- **Verification recorded for Task 13 final audit.** On Apple M4 Pro
(`Darwin 25.5.0`, Docker Server `29.6.2 linux/arm64`), harness pytest passed
**827 passed / 4 deselected**; backend Vitest passed **477/477** plus TypeScript and build;
@@ -85,9 +91,8 @@
Windows Docker Desktop startup were not manually executed in this run.
- **Task 13 known limitations.** Broad harness Ruff remains existing unrelated debt
(**220 errors**); touched harness files were verified Ruff-clean. The final active-reference
audit remains non-empty only in categorized legacy parser/migration compatibility, legacy
descriptor/config fixtures, deterministic negative guards, retained off-repository migration
SQL, L2 legacy fixtures, gitignored task notes, and historical reference notes. No active
audit remains non-empty only in deterministic negative guards, retained off-repository migration
SQL, L2 compatibility fixtures, gitignored task notes, and historical reference notes. No active
schema-v3 operator manual or supported runtime deployment path retains external vector or
embedding endpoint coupling.
- **Final review fix verification.** Backend Vitest passed **477/477** plus TypeScript and build;
+12 -9
View File
@@ -58,7 +58,7 @@ diagnostics are exposed by `tht doctor` and do not prevent the UI from starting.
Workspace descriptors are shared through a validated Git repository while endpoint bindings and
secret files remain installation-local. Use the [local Mac/PC installation manual](docs/install/local-workspace-registry.md)
for Docker Desktop or a local engine, and the [server installation manual](docs/install/server-workspace-registry.md)
for the Gitea, reverse-proxy, backup, migration, and recovery workflow. The isolated deployment
for the Gitea, reverse-proxy, backup, upgrade, and recovery workflow. The isolated deployment
exercise is `./scripts/workspace-registry-smoke.sh`; both manuals are checked with
`./scripts/verify-workspace-install-docs.sh --profile local` or `--profile server`.
@@ -70,10 +70,12 @@ every revision referenced by an open, closed, or failed unarchived session. It r
single local installation list or from a server administrator's complete session list, never from
a remote user's partial list.
Schema-v3 is the operational descriptor contract. Schema-v1/v2 descriptors remain
`migration_required` until an explicit reviewed migration writes schema version 3. One workspace
owns one Qdrant collection; schema, Evidence, and Memory records share that collection and stay
separated by indexed payload `kind`.
Schema v3 is the only accepted workspace descriptor format. Schema v1 and v2 descriptors are
rejected while a candidate snapshot is validated, so activation or a pull fails atomically and the
prior valid snapshot remains active. There is no in-product migrator or automatic conversion. A
repository must already contain reviewed v3 descriptors. One workspace owns one Qdrant collection;
schema, Evidence, and Memory records share that collection and stay separated by indexed payload
`kind`.
Connector `ssh_tunnel` bindings are diagnostic-only in this release: their bounded probe always
cleans up the loopback forward and returns `workspace_not_activatable`; session creation is rejected
@@ -228,10 +230,11 @@ Compose project name by passing `--confirm-project`:
The restore script stops `qdrant`, validates the exact labeled target, stages the current volume
contents for rollback, extracts the requested archive into the volume, and then returns the
service to its prior running state. After restore, run the backend health checks and a known
retrieval query before reopening write traffic. Restore does not migrate schema-v1/v2 workspace
descriptors, does not rename collections, and does not reconcile an incompatible collection
contract; those remain explicit reviewed recovery steps outside the helper.
service to its prior running state. It restores semantic storage only. Before reopening write
traffic, the workspace registry must already be at a reviewed v3 descriptor revision compatible
with the restored collection; then run backend health checks and a known retrieval query. The
helper does not restore descriptors, rename collections, or reconcile an incompatible collection
contract.
## Production trust boundary and secrets
+8 -12
View File
@@ -213,8 +213,11 @@ Use `POST /workspace-registry/pull` to fetch later revisions. Run workspace diag
required DWH bindings are mounted. Schema-v3 diagnostics probe the internal Qdrant/Ollama
services through backend config; ordinary diagnostics are read-only.
Schema-v3 is the only operational descriptor format. Schema-v1/v2 descriptors remain
`migration_required` until an explicit reviewed migration writes schema version 3. One workspace owns one Qdrant collection; schema, Evidence, and Memory records share that collection and remain
Schema v3 is the only accepted workspace descriptor format. Schema v1 and v2 descriptors are
rejected while the candidate snapshot is validated, so bootstrap activation or a pull fails
atomically and leaves the prior active snapshot unchanged. There is no in-product migrator or
automatic conversion. The repository must already contain reviewed v3 descriptors. One workspace
owns one Qdrant collection; schema, Evidence, and Memory records share that collection and remain
isolated by payload `kind`.
## Semantic index ownership contract
@@ -223,16 +226,9 @@ isolated by payload `kind`.
| --- | --- | --- |
| Workspace semantic index | Each workspace reserves a single Qdrant collection. | Schema, Evidence, and Memory stay in that one collection and remain isolated by payload `kind`. |
To migrate an existing legacy descriptor, create/clone an empty private remote, set the absolute
`THT_SOURCE_ROOT`, transform with absolute paths, review the schema-v1 result, explicitly produce
the reviewed schema-v3 contract, then commit/push. The transformer never imports `${ENV}` values
or secrets.
```sh
THT_SOURCE_ROOT=/absolute/path/to/ThothII
npm --prefix "$THT_SOURCE_ROOT/backend" run build
node "$THT_SOURCE_ROOT/backend/dist/workspaces/migrate-legacy.js" --input /absolute/path/legacy.yaml --output /absolute/path/thoth-workspaces
```
If source material needs conversion, perform it outside ThothII in a separate reviewed process.
Commit only the resulting reviewed v3 descriptors. That external process must not import `${ENV}`
values, secret values, certificates, keys, or secret files into the repository.
## Publish, update, backup, outage recovery, and rollback
+16 -13
View File
@@ -60,9 +60,9 @@ use `ssh://git@git.example.invalid/platform/thoth-workspaces.git`. For HTTPS, cr
machine credential in the secret manager and mount the Gitea/private CA separately. Never use a
Gitea admin credential in the application.
Bootstrap an empty remote from a temporary review clone: migrate legacy descriptors, review their
schema-v3 identity and generated artifacts, commit, and push `main`. The running server is not an
authoring environment for migration.
Bootstrap an empty remote from a temporary review clone only after its canonical v3 descriptors
and generated public artifacts have been reviewed; commit and push `main`. The running server is
not a descriptor authoring or conversion environment.
## Curator flow for shared-registry Evidence
@@ -258,14 +258,16 @@ For upgrades, record active status/head, finish active work, use the documented
--check-only`, deploy the compatible image through `thothctl`, verify health/status, then resume
proxy traffic.
For legacy descriptor migration, use a temporary review clone and the legacy transformer with absolute paths.
Its schema-v1 output is `migration_required`; explicitly supply collection identity, diagnostics,
and the reviewed v3 contract before commit. Never import `${ENV}` values or
copy secret files.
Schema v3 is the only accepted workspace descriptor format. Schema v1 and v2 descriptors are
rejected while the candidate snapshot is validated, so initial activation or a pull fails
atomically and leaves the prior active snapshot unchanged. There is no in-product migrator or
automatic conversion. The repository must already contain reviewed v3 descriptors. One workspace
owns one Qdrant collection; schema, Evidence, and Memory records share it and stay separated by
payload `kind`.
Schema-v3 is the only operational descriptor contract. Schema-v1/v2 descriptors remain
`migration_required` until an explicit reviewed migration writes version 3. One workspace owns one Qdrant collection; schema, Evidence, and Memory records share it and stay separated by payload
`kind`.
If source material needs conversion, perform it outside ThothII in a separate reviewed process.
Commit only the resulting reviewed v3 descriptors. That external process must not import `${ENV}`
values, secret values, certificates, keys, or secret files into the repository.
## Semantic index ownership contract
@@ -312,9 +314,10 @@ Use the repository helpers for Qdrant backup/restore:
Qdrant backup/restore targets exactly one labeled `qdrant-data` volume for the named Compose
project. Restore requires the exact repeated project confirmation, validates the archive before
stopping `qdrant`, stages rollback content, and restores in place only for that project-scoped
volume. It does not migrate schema-v1/v2 workspaces, rename collections, or resolve semantic-index
incompatibilities.
stopping `qdrant`, stages rollback content, and restores semantic storage in place only for that
project-scoped volume. Before recovery, the registry must already contain a reviewed v3 descriptor
revision compatible with the restored collection. The helper does not restore descriptors, rename
collections, or resolve semantic-index incompatibilities.
The Ollama model cache is a recoverable local cache, not the canonical semantic source of truth.
You may back up `embedding-models` for faster offline recovery, but a cache loss is recoverable by
+4 -3
View File
@@ -7,9 +7,10 @@ response body belongs in the descriptor, generated `.env.example` files, or diag
## Scope and safety rules
- The operational descriptor is schema version 3.
- Schema-v1/v2 descriptors are readable only and remain `migration_required` until an explicit
reviewed migration writes schema version 3.
- Schema v3 is the only accepted workspace descriptor format.
- Schema v1 and v2 descriptors are rejected before diagnostics run. There is no in-product
migrator or automatic conversion; the Git repository must already contain reviewed v3
descriptors.
- One workspace owns one Qdrant collection.
- Qdrant and Ollama are internal services. Operators do not bind external vector or embedding
transports for active manuals or supported diagnostics.
+138 -2
View File
@@ -214,7 +214,7 @@ cat >"$project_state_positive" <<'EOF'
## Internal Qdrant + Ollama semantic infrastructure — LIVE 2026-08-08
- Schema-v3 descriptors are operational and v1/v2 remain `migration_required`.
- Schema v3 is the only accepted workspace descriptor format. Schema v1 and v2 descriptors are rejected before activation or diagnostics.
- One workspace owns one Qdrant collection.
- Only DWH and LLM remain external runtime application endpoints.
- The internal stack includes `qdrant`, `embedding`, and `embedding-model-init`.
@@ -223,10 +223,146 @@ cat >"$project_state_positive" <<'EOF'
### Historical snapshot — previous deployment
- Older notes intentionally live only here.
- Schema-v2 descriptors were operational and returned `migration_required`.
- Operators used `migrate-legacy` in this superseded workflow.
EOF
verify_project_state_current_contract "$project_state_positive" positive-project-state >/dev/null
project_state_unrelated_migration="$negative_root/project-state-unrelated-migration.md"
python3 - "$project_state_positive" "$project_state_unrelated_migration" <<'PY'
import pathlib, sys
source = pathlib.Path(sys.argv[1]).read_text()
old = "rejected before activation or diagnostics.\n- One workspace"
new = (
"rejected before activation or diagnostics. The unrelated session database\n"
" upgrade may report `migration_required`.\n- One workspace"
)
if source.count(old) != 1:
raise SystemExit("PROJECT_STATE positive fixture insertion point not found")
pathlib.Path(sys.argv[2]).write_text(source.replace(old, new, 1))
PY
verify_project_state_current_contract "$project_state_unrelated_migration" \
unrelated-current-project-state >/dev/null
generic_unrelated_migration="$negative_root/generic-unrelated-migration.md"
python3 - "$root/docs/install/local-workspace-registry.md" "$generic_unrelated_migration" <<'PY'
import pathlib, sys
source = pathlib.Path(sys.argv[1]).read_text()
old = "isolated by payload `kind`.\n\n## Semantic index ownership contract"
new = (
"isolated by payload `kind`. The unrelated session database\n"
"upgrade may report `migration_required`.\n\n## Semantic index ownership contract"
)
if source.count(old) != 1:
raise SystemExit("manual positive fixture insertion point not found")
pathlib.Path(sys.argv[2]).write_text(source.replace(old, new, 1))
PY
verify_workspace_descriptor_doc_contract "$generic_unrelated_migration" \
unrelated-current-manual >/dev/null
assert_schema_contract_rejected() {
local source="$1" label="$2" expected="$3" output="$negative_root/schema-contract-output"
set +e
verify_workspace_descriptor_doc_contract "$source" "$label" >"$output" 2>&1
local status=$?
set -e
if [[ $status -eq 0 ]] || ! grep -Fq "$expected" "$output"; then
echo "$label fixture was not rejected correctly" >&2
cat "$output" >&2
exit 1
fi
}
project_state_migration_required="$negative_root/project-state-migration-required.md"
python3 - "$project_state_positive" "$project_state_migration_required" <<'PY'
import pathlib, sys
source = pathlib.Path(sys.argv[1]).read_text()
marker = source.index("## Historical snapshots")
pathlib.Path(sys.argv[2]).write_text(
source[:marker]
+ "- Schema v1 and v2 descriptors remain\n"
+ " `migration_required`.\n\n"
+ source[marker:]
)
PY
set +e
verify_project_state_current_contract "$project_state_migration_required" current-migration-required >"$project_state_output" 2>&1
project_state_status=$?
set -e
if [[ $project_state_status -eq 0 ]] || ! grep -Fq "migration_required" "$project_state_output"; then
echo "current PROJECT_STATE migration_required fixture was not rejected correctly" >&2
cat "$project_state_output" >&2
exit 1
fi
project_state_readable="$negative_root/project-state-readable.md"
python3 - "$project_state_positive" "$project_state_readable" <<'PY'
import pathlib, sys
source = pathlib.Path(sys.argv[1]).read_text()
source = source.replace(
"Schema v1 and v2 descriptors are rejected before activation or diagnostics.",
"Schema v1 and v2 descriptors remain readable for operational use.",
1,
)
pathlib.Path(sys.argv[2]).write_text(source)
PY
set +e
verify_project_state_current_contract "$project_state_readable" current-readable-v1-v2 >"$project_state_output" 2>&1
project_state_status=$?
set -e
if [[ $project_state_status -eq 0 ]] || ! grep -Eq "rejected|readable|operational" "$project_state_output"; then
echo "current PROJECT_STATE v1/v2 readability fixture was not rejected correctly" >&2
cat "$project_state_output" >&2
exit 1
fi
manual_migration_required="$negative_root/manual-migration-required.md"
cp "$root/docs/install/local-workspace-registry.md" "$manual_migration_required"
printf '\nSchema v1 and v2 descriptors remain\n`migration_required`.\n' \
>>"$manual_migration_required"
assert_schema_contract_rejected "$manual_migration_required" manual-migration-required "migration_required"
manual_migrate_legacy="$negative_root/manual-migrate-legacy.md"
cp "$root/docs/install/local-workspace-registry.md" "$manual_migrate_legacy"
printf '\nRun `node backend/dist/workspaces/migrate-legacy.js` before activation.\n' >>"$manual_migrate_legacy"
assert_schema_contract_rejected "$manual_migrate_legacy" manual-migrate-legacy "migrate-legacy"
manual_legacy_procedure="$negative_root/manual-legacy-procedure.md"
cp "$root/docs/install/server-workspace-registry.md" "$manual_legacy_procedure"
printf '\nMigrate legacy descriptors in a temporary review clone before activation.\n' >>"$manual_legacy_procedure"
assert_schema_contract_rejected "$manual_legacy_procedure" manual-legacy-procedure "legacy descriptor procedure"
manual_missing_v3_only="$negative_root/manual-missing-v3-only.md"
python3 - "$root/docs/install/local-workspace-registry.md" "$manual_missing_v3_only" <<'PY'
import pathlib, re, sys
text = pathlib.Path(sys.argv[1]).read_text()
text = re.sub(
r"Schema v3 is the only accepted workspace descriptor format\.",
"Schema v3 is accepted as a workspace descriptor format.",
text,
count=1,
flags=re.IGNORECASE,
)
pathlib.Path(sys.argv[2]).write_text(text)
PY
assert_schema_contract_rejected "$manual_missing_v3_only" manual-missing-v3-only "v3-only acceptance"
diagnostics_readable="$negative_root/diagnostics-readable-v1-v2.md"
python3 - "$root/docs/workspace-diagnostic-protocol.md" "$diagnostics_readable" <<'PY'
import pathlib, re, sys
text = pathlib.Path(sys.argv[1]).read_text()
text, count = re.subn(
r"Schema v1 and v2 descriptors are\s+rejected before diagnostics run\.",
"Schema v1 and v2 descriptors remain readable for operational diagnostics.",
text,
count=1,
)
if count != 1:
raise SystemExit("diagnostic rejection sentence not found")
pathlib.Path(sys.argv[2]).write_text(text)
PY
assert_schema_contract_rejected "$diagnostics_readable" diagnostics-readable-v1-v2 "v1/v2 rejection"
local_manual_paraphrase="$negative_root/local-manual-paraphrase.md"
cp "$root/docs/install/local-workspace-registry.md" "$local_manual_paraphrase"
python3 - "$local_manual_paraphrase" <<'PY'
+133 -6
View File
@@ -105,6 +105,109 @@ for token in tokens:
PY
}
verify_descriptor_migration_required_context() {
local source="$1" label="$2" scope="${3:-all}"
python3 - "$source" "$label" "$scope" <<'PY'
import pathlib, re, sys
text = pathlib.Path(sys.argv[1]).read_text()
label = sys.argv[2]
scope = sys.argv[3]
if scope == "current":
marker = re.search(r"^## Historical snapshots\b", text, re.MULTILINE)
if not marker:
raise SystemExit(f"{label}: missing Historical snapshots boundary")
text = text[:marker.start()]
elif scope != "all":
raise SystemExit(f"{label}: invalid migration_required verifier scope: {scope}")
blocks = []
current = []
def flush():
if current:
blocks.append(" ".join(current))
current.clear()
boundary = re.compile(r"^(?:#{1,6}\s+|[-*+]\s+|\d+[.)]\s+|>\s+|```|\|)")
for raw_line in text.splitlines():
line = raw_line.strip()
if not line:
flush()
continue
if boundary.match(line):
flush()
line = re.sub(r"^(?:#{1,6}\s+|[-*+]\s+|\d+[.)]\s+|>\s+)", "", line)
current.append(line)
flush()
migration_context = re.compile(
r"(?:\bworkspace(?:\s+[a-z0-9_-]+){0,3}\s+descriptors?\b|"
r"\bschema(?:[- ]?v?|\s+version\s*)[12]\b|"
r"\bv1\s*(?:/|and|or)\s*v2\b)",
re.IGNORECASE,
)
for block in blocks:
normalized = re.sub(r"\s+", " ", block).strip()
clauses = (part.strip() for part in re.split(r"(?<=[.!?;])\s+", normalized))
for clause in clauses:
if "migration_required" in clause.lower() and migration_context.search(clause):
raise SystemExit(f"{label}: contains forbidden descriptor migration_required support")
PY
}
verify_workspace_descriptor_doc_contract() {
local source="$1" label="$2"
verify_descriptor_migration_required_context "$source" "$label" || return 1
python3 - "$source" "$label" <<'PY'
import pathlib, re, sys
text = pathlib.Path(sys.argv[1]).read_text()
label = sys.argv[2]
if not re.search(
r"schema[- ]v?3\s+is\s+the\s+only\s+accepted\s+workspace\s+descriptor",
text,
re.IGNORECASE,
):
raise SystemExit(f"{label}: missing explicit v3-only acceptance contract")
legacy_subject = (
r"(?:schema[- ]v?1\s*(?:/|and|or)\s*(?:schema[- ]?)?v?2|"
r"schema[- ]v?1/v2|v1/v2)\s+descriptors?"
)
if not re.search(
legacy_subject + r".{0,100}\brejected\b",
text,
re.IGNORECASE | re.DOTALL,
):
raise SystemExit(f"{label}: missing explicit v1/v2 rejection contract")
forbidden_literals = {
"migrate-legacy": "migrate-legacy",
"legacy transformer": "deleted legacy transformer instruction",
"backend/dist/workspaces/migrate-legacy.js": "deleted transformer path",
}
for token, description in forbidden_literals.items():
if token in text.lower():
raise SystemExit(f"{label}: contains forbidden {description}")
if re.search(
r"(?:\bmigrat(?:e|ing)\s+legacy\s+descriptors?\b|\blegacy\s+descriptor\s+migration\b)",
text,
re.IGNORECASE,
):
raise SystemExit(f"{label}: contains forbidden legacy descriptor procedure")
legacy_support = [
legacy_subject + r".{0,100}\b(?:remain|are|stay)\b.{0,40}\b(?:readable|operational|supported|accepted)\b",
r"schema[- ]v?[12]\s+descriptors?.{0,100}\b(?:remain|are|stay)\b.{0,40}\b(?:readable|operational|supported|accepted)\b",
]
for pattern in legacy_support:
if re.search(pattern, text, re.IGNORECASE | re.DOTALL):
raise SystemExit(f"{label}: v1/v2 rejection contradicted by readable or operational support")
PY
}
verify_markdown_table_relationships() {
local source="$1" label="$2" heading="$3" spec_json="$4"
python3 - "$source" "$label" "$heading" "$spec_json" <<'PY'
@@ -588,6 +691,7 @@ verify_vector_helper_interfaces() {
verify_project_state_current_contract() {
local source="${1:-$root/PROJECT_STATE.md}"
local label="${2:-PROJECT_STATE.md}"
verify_descriptor_migration_required_context "$source" "$label current section" current || return 1
python3 - "$source" "$label" <<'PY'
import pathlib, re, sys
text = pathlib.Path(sys.argv[1]).read_text()
@@ -599,10 +703,30 @@ current = text[:marker.start()]
historical = text[marker.start():]
if not re.search(r"Internal Qdrant \+ Ollama semantic infrastructure", current, re.MULTILINE):
raise SystemExit(f"{label}: current section missing internal semantic snapshot heading")
if not re.search(r"Schema-v3 descriptors are operational", current, re.MULTILINE):
raise SystemExit(f"{label}: current section must say schema-v3 is operational")
if "migration_required" not in current:
raise SystemExit(f"{label}: current section must mention migration_required")
if "migrate-legacy" in current.lower():
raise SystemExit(f"{label}: current section contains forbidden migrate-legacy instruction")
if not re.search(
r"schema[- ]v?3\s+is\s+the\s+only\s+accepted\s+workspace\s+descriptor",
current,
re.IGNORECASE,
):
raise SystemExit(f"{label}: current section lacks explicit v3-only acceptance contract")
legacy_subject = (
r"(?:schema[- ]v?1\s*(?:/|and|or)\s*(?:schema[- ]?)?v?2|"
r"schema[- ]v?1/v2|v1/v2)\s+descriptors?"
)
if not re.search(
legacy_subject + r".{0,100}\brejected\b",
current,
re.IGNORECASE | re.DOTALL,
):
raise SystemExit(f"{label}: current section lacks explicit v1/v2 rejection contract")
for pattern in [
legacy_subject + r".{0,100}\b(?:remain|are|stay)\b.{0,40}\b(?:readable|operational|supported|accepted)\b",
r"schema[- ]v?[12]\s+descriptors?.{0,100}\b(?:remain|are|stay)\b.{0,40}\b(?:readable|operational|supported|accepted)\b",
]:
if re.search(pattern, current, re.IGNORECASE | re.DOTALL):
raise SystemExit(f"{label}: current section contains contradictory active text: v1/v2 readable or operational support")
if not re.search(r"\b(one|single)\b.*\bworkspace\b.*\b(one|single)\b.*\bQdrant\b.*\bcollection\b", current, re.IGNORECASE | re.DOTALL):
raise SystemExit(f"{label}: current section must describe one-workspace/one-collection ownership")
if not re.search(r"\bDWH\b", current) or not re.search(r"\bLLM\b", current):
@@ -640,6 +764,10 @@ verify_internal_semantic_infrastructure_docs() {
verify_workspace_descriptor_semantic_contract "$root/deploy/workspaces/psd.yaml.example" "psd workspace example" || return 1
verify_vector_helper_interfaces || return 1
verify_project_state_current_contract "$root/PROJECT_STATE.md" "PROJECT_STATE.md" || return 1
verify_workspace_descriptor_doc_contract "$readme" "README" || return 1
verify_workspace_descriptor_doc_contract "$local_manual" "local workspace manual" || return 1
verify_workspace_descriptor_doc_contract "$server_manual" "server workspace manual" || return 1
verify_workspace_descriptor_doc_contract "$diagnostics" "workspace diagnostic protocol" || return 1
local ownership_spec semantic_index_spec compact_spec
ownership_spec='{"rows":[
@@ -670,13 +798,12 @@ verify_internal_semantic_infrastructure_docs() {
require_pattern "$agents" "AGENTS.md" 'DWH and LLM remain external configuration endpoints' || return 1
for manual in "$local_manual" "$server_manual"; do
require_pattern "$manual" "$(basename "$manual")" 'qwen3-embedding:0\.6b' || return 1
require_pattern "$manual" "$(basename "$manual")" 'migration_required' || return 1
done
require_pattern "$local_manual" "local workspace manual" 'CPU-first' || return 1
require_pattern "$local_manual" "local workspace manual" 'THOTH_ENABLE_EMBEDDING_GPU=1' || return 1
require_pattern "$server_manual" "server workspace manual" 'Qdrant backup/restore' || return 1
require_pattern "$compact_manual" "four-context install note" '1024 dimensioni' || return 1
require_pattern "$diagnostics" "workspace diagnostic protocol" 'schema version 3' || return 1
require_pattern "$diagnostics" "workspace diagnostic protocol" 'Schema v1 and v2 descriptors.+rejected before diagnostics' || return 1
require_pattern "$diagnostics" "workspace diagnostic protocol" 'semantic_index_incompatible' || return 1
require_absent "$diagnostics" "workspace diagnostic protocol" \
'engine: pgvector' \