diff --git a/docs/install/local-workspace-registry.md b/docs/install/local-workspace-registry.md index 5bc7f6cb..65b41806 100644 --- a/docs/install/local-workspace-registry.md +++ b/docs/install/local-workspace-registry.md @@ -36,7 +36,7 @@ authentication method. ## Git remote: SSH and HTTPS Create one private repository such as `thoth-workspaces.git`. It contains canonical workspace -definitions and generated artifacts only: +definitions, curated Evidence content, and generated artifacts: ```text registry.git/ diff --git a/scripts/test-verify-workspace-install-docs.sh b/scripts/test-verify-workspace-install-docs.sh index df5f6701..f53399f1 100755 --- a/scripts/test-verify-workspace-install-docs.sh +++ b/scripts/test-verify-workspace-install-docs.sh @@ -525,6 +525,12 @@ elif mutation == "s3-token-boundary-omitted": ) elif mutation == "credential-literal": changed = original + "\nTHT_WS_STATIC_S3_EVIDENCE_SECRET_KEY=AKIAEXAMPLECREDENTIAL\n" +elif mutation == "credential-literal-public-prose": + changed = original + "\nPublic credential example: AKIAABCDEFGHIJKLMNOP\n" +elif mutation == "credential-literal-public-yaml": + document = yaml.safe_load(original) + document["public_credential_example"] = "AKIAABCDEFGHIJKLMNOP" + changed = yaml.safe_dump(document, sort_keys=False) elif mutation == "signed-query-example": signed_query = "https://evidence.example.invalid/report" + "?X-Amz-Signature=unsafe" changed = original + f"\nTHT_EVIDENCE_URI={signed_query}\n" @@ -538,6 +544,23 @@ elif mutation == "p1-scope-inversion": "P1 materializes, extracts, and indexes Evidence before publication.", 1, ) +elif mutation.startswith("p1-append-"): + claims = { + "p1-append-acquisition": "P1 owns Evidence acquisition.", + "p1-append-materialization": "P1 owns Evidence materialization.", + "p1-append-extraction": "P1 owns Evidence extraction.", + "p1-append-preprocessing": "P1 owns Evidence preprocessing.", + "p1-append-embeddings": "P1 owns Evidence embeddings.", + "p1-append-qdrant-writes": "P1 owns Evidence Qdrant writes.", + "p1-append-indexing": "P1 owns Evidence indexing.", + "p1-append-active": "P1 owns Evidence `ACTIVE` publication.", + "p1-append-retention": "P1 owns Evidence retention.", + "p1-append-gc": "P1 owns Evidence GC.", + } + claim = claims.get(mutation) + if claim is None: + raise SystemExit(f"unknown P1 append mutation: {mutation}") + changed = original + f"\n{claim}\n" elif mutation == "config-ordering": changed = original.replace( "tht config check -c ", "tht -c config check", 1 @@ -963,6 +986,12 @@ expect_evidence_fixture_rejected \ expect_evidence_fixture_rejected \ "credential literal in public bindings" docs/install/examples/workspace-bindings.env.example credential-literal \ "credential literal forbidden" +expect_evidence_fixture_rejected \ + "credential literal in public prose" docs/contracts/workspace-evidence-v3.md credential-literal-public-prose \ + "credential literal forbidden" +expect_evidence_fixture_rejected \ + "credential literal in public YAML" deploy/workspaces/example.yaml credential-literal-public-yaml \ + "credential literal forbidden" expect_evidence_fixture_rejected \ "signed query in public bindings" docs/install/examples/workspace-bindings.env.example signed-query-example \ "query-bearing public URI forbidden" @@ -972,6 +1001,36 @@ expect_evidence_fixture_rejected \ expect_evidence_fixture_rejected \ "P1 Evidence scope inversion" docs/contracts/workspace-evidence-v3.md p1-scope-inversion \ "P1 scope violation" +expect_evidence_fixture_rejected \ + "appended P1 acquisition claim" docs/contracts/workspace-evidence-v3.md p1-append-acquisition \ + "P1 scope violation" +expect_evidence_fixture_rejected \ + "appended P1 materialization claim" docs/contracts/workspace-evidence-v3.md p1-append-materialization \ + "P1 scope violation" +expect_evidence_fixture_rejected \ + "appended P1 extraction claim" docs/contracts/workspace-evidence-v3.md p1-append-extraction \ + "P1 scope violation" +expect_evidence_fixture_rejected \ + "appended P1 preprocessing claim" docs/contracts/workspace-evidence-v3.md p1-append-preprocessing \ + "P1 scope violation" +expect_evidence_fixture_rejected \ + "appended P1 embeddings claim" docs/contracts/workspace-evidence-v3.md p1-append-embeddings \ + "P1 scope violation" +expect_evidence_fixture_rejected \ + "appended P1 Qdrant writes claim" docs/contracts/workspace-evidence-v3.md p1-append-qdrant-writes \ + "P1 scope violation" +expect_evidence_fixture_rejected \ + "appended P1 indexing claim" docs/contracts/workspace-evidence-v3.md p1-append-indexing \ + "P1 scope violation" +expect_evidence_fixture_rejected \ + "appended P1 ACTIVE publication claim" docs/contracts/workspace-evidence-v3.md p1-append-active \ + "P1 scope violation" +expect_evidence_fixture_rejected \ + "appended P1 retention claim" docs/contracts/workspace-evidence-v3.md p1-append-retention \ + "P1 scope violation" +expect_evidence_fixture_rejected \ + "appended P1 garbage collection claim" docs/contracts/workspace-evidence-v3.md p1-append-gc \ + "P1 scope violation" expect_evidence_fixture_rejected \ "config check option reordered" docs/contracts/workspace-evidence-v3.md config-ordering \ "exact config-check ordering missing" diff --git a/scripts/verify-workspace-install-docs.sh b/scripts/verify-workspace-install-docs.sh index 819e23ab..4c4d5a73 100755 --- a/scripts/verify-workspace-install-docs.sh +++ b/scripts/verify-workspace-install-docs.sh @@ -247,14 +247,18 @@ contract_path = base / "docs/contracts/workspace-evidence-v3.md" local_path = base / "docs/install/local-workspace-registry.md" server_path = base / "docs/install/server-workspace-registry.md" bindings_path = base / "docs/install/examples/workspace-bindings.env.example" -paths = [contract_path, local_path, server_path, bindings_path] +descriptor_paths = [ + base / "deploy/workspaces/example.yaml", + base / "deploy/workspaces/psd.yaml.example", +] +paths = [contract_path, local_path, server_path, bindings_path, *descriptor_paths] for path in paths: if not path.is_file(): raise SystemExit(f"missing workspace Evidence contract input: {path.relative_to(base)}") # Generic descriptors must publish an explicit, ID-derived canonical filesystem contract. -for relative in ("deploy/workspaces/example.yaml", "deploy/workspaces/psd.yaml.example"): - path = base / relative +for path in descriptor_paths: + relative = path.relative_to(base).as_posix() document = yaml.safe_load(path.read_text()) workspace_id = document["workspace"]["id"] evidence = document.get("evidence") @@ -402,7 +406,25 @@ if not all(token in p1 for token in ("lexical URI", "Git tree", "same commit", " 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 performs no acquisition, extraction, preprocessing/indexing, embeddings, Qdrant writes, `ACTIVE` publication, retention, or GC." -if no_scope not in contract or re.search(r"P1\s+(?:materializes|extracts|indexes)", contract, re.IGNORECASE): +positive_p1_operation = re.compile( + r"""\bP1\b(?:\s+(?:also|then|now|directly|itself))*\s+(?: + (?:will\s+|must\s+|may\s+|can\s+)?(?: + acquires?|materializes?|extracts?|preprocesses?|indexes?|retains?| + (?:creates?|generates?)\s+embeddings?| + writes?\s+(?:embeddings?\s+)?to\s+Qdrant| + publishes?\s+`?ACTIVE\b`?| + garbage[- ]collects?| + (?:runs?|performs?)\s+(?:retention|GC|garbage[ -]collection) + )| + (?:owns?|handles?|performs?|is\s+responsible\s+for)\s+(?:Evidence\s+)?(?: + acquisition|materialization|extraction|preprocessing|embeddings?| + Qdrant\s+writes?|indexing|`?ACTIVE`?\s+publication|retention|GC| + garbage[ -]collection + ) + )\b""", + re.IGNORECASE | re.VERBOSE, +) +if no_scope not in contract or positive_p1_operation.search(contract): raise SystemExit("P1 scope violation") installation_rows = {row[0]: row[1:] for row in table_for("Installation files")} @@ -454,6 +476,15 @@ for guide in (local_path, server_path): if any(position < 0 for position in positions) or positions != sorted(positions): raise SystemExit(f"{guide.name}: curator flow out of order") +# Public prose, YAML, and examples may name credential variables and describe forbidden shapes, +# but they must never contain a high-confidence access-key literal. Identifier-aware boundaries +# avoid treating a legitimate variable name as a credential value. +aws_access_key = re.compile( + r"(?