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
+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' \