diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 3fda60a..fb62705 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -272,3 +272,20 @@ jobs:
${{ runner.temp }}/caboodle-phase2/anticipated-question.log
${{ runner.temp }}/caboodle-phase2/empty-question.log
${{ runner.temp }}/caboodle-phase2/*.log
+
+ repository-pendant-source:
+ name: Repository pendant source controls
+ runs-on: ubuntu-latest
+ timeout-minutes: 5
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-python@v5
+ with:
+ python-version: '3.12'
+ - name: Install pinned RDF test parser
+ run: python3 -m pip install rdflib==7.6.0
+ - name: Test copied pointer and publication boundaries
+ run: |
+ python3 scripts/test-repository-share-query.py
+ python3 scripts/test-repository-knowledge.py
+ python3 scripts/test-repository-share-publication.py
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index de96b37..782524b 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -194,3 +194,22 @@ jobs:
done
echo 'Registry visibility is UNKNOWN; publication is not accepted.' >&2
exit 1
+
+ repository-pendant:
+ name: Verify repository pendant
+ needs: [release-please, publish]
+ if: always() && needs.publish.result == 'success'
+ runs-on: ubuntu-latest
+ timeout-minutes: 45
+ permissions:
+ contents: read
+ env:
+ RELEASE_TAG: ${{ startsWith(github.ref, 'refs/tags/') && github.ref_name || needs.release-please.outputs.tag_name }}
+ GH_TOKEN: ${{ github.token }}
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ ref: ${{ env.RELEASE_TAG }}
+ fetch-depth: 0
+ - name: Verify the bounded public pointer corpus
+ run: python3 scripts/verify-repository-share.py "$RELEASE_TAG" "$(git rev-parse HEAD)"
diff --git a/docs/knowledge/alignment-status.json b/docs/knowledge/alignment-status.json
new file mode 100644
index 0000000..4bbdbd3
--- /dev/null
+++ b/docs/knowledge/alignment-status.json
@@ -0,0 +1,8 @@
+{
+ "status": "no-approved-mappings",
+ "approved_record_count": 0,
+ "carrier_sha256": "f74d895765cf2975b23c6a095b3c09a992865950f9199a7e53f9d2042679db10",
+ "scope": "scbrown/tambo repository pendant source",
+ "basis": "No verified approved artifact or pairs supplied at source review; empty carrier is explicit. This is not a global absence claim.",
+ "curation_contract": "https://github.com/scbrown/quipu/blob/main/docs/design/cross-graph-alignment.md"
+}
diff --git a/docs/knowledge/alignments.ttl b/docs/knowledge/alignments.ttl
new file mode 100644
index 0000000..db35baa
--- /dev/null
+++ b/docs/knowledge/alignments.ttl
@@ -0,0 +1,6 @@
+# SSSOM/RDF carrier: explicitly no approved Chaski cross-stack mappings.
+# This is an empty mapping graph, not an absent/corrupt input or a claim that
+# no mappings exist anywhere. Candidate records require the existing curator
+# path before publication as accepted mappings. Dependencies remain dependencies.
+@prefix sssom: .
+@prefix owl: .
diff --git a/docs/knowledge/repository-knowledge.json b/docs/knowledge/repository-knowledge.json
new file mode 100644
index 0000000..2fd475f
--- /dev/null
+++ b/docs/knowledge/repository-knowledge.json
@@ -0,0 +1,102 @@
+{
+ "source_revision": "ccdfa3a2ed8747228b486f6ee97f4c291aadac26",
+ "reviewed_at": "2026-10-21T06:26:04.583642Z",
+ "max_age_seconds": 2592000,
+ "records": [
+ {
+ "kind": "idea",
+ "source": "README.md",
+ "subject": "http://aegis.gastown.local/ontology/doc/tambo/README.md",
+ "summary": "Tambo installs and verifies the knowledge-tool stack from a reviewable plan. The transition retains the caboodle command and existing plan/state paths; publication and installed verification remain separate.",
+ "source_sha256": "8bb36e408f903090f2f78a481dcb3a2214b45feea2071a6a77285a0168d6b19c",
+ "type": "Concept"
+ },
+ {
+ "kind": "idea",
+ "source": "docs/book/src/architecture.md",
+ "subject": "http://aegis.gastown.local/ontology/doc/tambo/docs%2Fbook%2Fsrc%2Farchitecture.md",
+ "summary": "The architecture separates plan, apply and verify. Guided intended-use answers become reviewable plan data and executable reader-path contracts rather than implicit permission to install.",
+ "source_sha256": "21fb4ce1e2827a14a6550a5154c7b9f15e92435dd265721ab4d4ed6f819ba97e",
+ "type": "Concept"
+ },
+ {
+ "kind": "interface",
+ "source": "src/main.rs",
+ "subject": "http://aegis.gastown.local/ontology/code/tambo/src%2Fmain.rs",
+ "summary": "The Clap command tree defines quickstart, plan, apply, verify and doctor, with explicit plan/state paths and opt-in remote credential provisioning.",
+ "source_sha256": "1062af6157fec196ffe42ea259c8714573bcdb0964e00d1ab9b96e40abb1c1da",
+ "properties": {
+ "interfaceKind": "CLI",
+ "publicName": "tambo and compatible caboodle command tree"
+ }
+ },
+ {
+ "kind": "interface",
+ "source": "src/model.rs",
+ "subject": "http://aegis.gastown.local/ontology/code/tambo/src%2Fmodel.rs",
+ "summary": "The serialized Plan schema defines profiles, ordered tools, optional shares and destination database, intended-use contracts and host exclusions. Unknown plan fields are rejected.",
+ "source_sha256": "8cde1e7890d525cb5f38f8ff5bebccb69a0489699435dc68d6a843618f9f8932",
+ "properties": {
+ "interfaceKind": "file-format",
+ "publicName": "caboodle-plan.toml Plan and JSON State"
+ }
+ },
+ {
+ "kind": "interface",
+ "source": "src/adapter.rs",
+ "subject": "http://aegis.gastown.local/ontology/code/tambo/src%2Fadapter.rs",
+ "summary": "The Adapter trait separates desired version, installation, version read-back and functional verification. Built-in adapters integrate the selected stack tools; package adapters may bind verification to PATH executables.",
+ "source_sha256": "7ff5434afe2a2fbcdaf600341127447316498c6f211ff804621e9f6f77b9b343",
+ "properties": {
+ "interfaceKind": "library",
+ "publicName": "caboodle::adapter::Adapter"
+ },
+ "depends_on": [
+ "http://aegis.gastown.local/ontology/repo_quipu"
+ ]
+ },
+ {
+ "kind": "decision",
+ "source": "Cargo.toml",
+ "subject": "http://aegis.gastown.local/ontology/code/tambo/Cargo.toml",
+ "summary": "The package and primary command are tambo, while the Rust library import name remains caboodle and a legacy caboodle binary is explicitly declared for compatibility.",
+ "source_sha256": "7e31dcd35cb941031963d783120fc1653cf113914b5b0fe473760c08b898f752",
+ "type": "DecisionRecord",
+ "properties": {
+ "status": "active",
+ "supersessionStatus": "not-recorded-in-source"
+ }
+ },
+ {
+ "kind": "start-here",
+ "source": "README.md",
+ "subject": "http://aegis.gastown.local/ontology/doc/tambo/README.md",
+ "summary": "Start with the local single-user quickstart and its verified-tool success checks before selecting remote service mode.",
+ "source_sha256": "8bb36e408f903090f2f78a481dcb3a2214b45feea2071a6a77285a0168d6b19c",
+ "properties": {
+ "startHereOrder": 1,
+ "glossaryTerm": "https://scbrown.github.io/quechua/ns#Tool"
+ }
+ },
+ {
+ "kind": "start-here",
+ "source": "docs/book/src/configuration.md",
+ "subject": "http://aegis.gastown.local/ontology/doc/tambo/docs%2Fbook%2Fsrc%2Fconfiguration.md",
+ "summary": "Read the TOML plan, JSON progress state and explicit remote credential boundaries before changing installation targets or endpoint configuration.",
+ "source_sha256": "45754e285dd180793bc1c1daf866d5d8455512d96aa4dbec4050e90cc8115fda",
+ "properties": {
+ "startHereOrder": 2
+ }
+ },
+ {
+ "kind": "start-here",
+ "source": "Cargo.toml",
+ "subject": "http://aegis.gastown.local/ontology/code/tambo/Cargo.toml",
+ "summary": "Read the declared library and binary names before integrating the crate or changing compatibility assumptions.",
+ "source_sha256": "7e31dcd35cb941031963d783120fc1653cf113914b5b0fe473760c08b898f752",
+ "properties": {
+ "startHereOrder": 3
+ }
+ }
+ ]
+}
diff --git a/docs/knowledge/repository-knowledge.shapes.ttl b/docs/knowledge/repository-knowledge.shapes.ttl
new file mode 100644
index 0000000..8a79384
--- /dev/null
+++ b/docs/knowledge/repository-knowledge.shapes.ttl
@@ -0,0 +1,53 @@
+@prefix a: .
+@prefix sh: .
+@prefix rdfs: .
+@prefix xsd: .
+@prefix sssom: .
+@prefix owl: .
+@prefix prov: .
+
+# This profile constrains only explicitly curated repository knowledge. It
+# does not turn every imported Concept or CodeModule into a Bobbin record.
+a:TamboKnowledgeProfile a sh:NodeShape ;
+ sh:targetSubjectsOf a:knowledgeKind ;
+ sh:property [ sh:path rdfs:label ; sh:minCount 1 ; ] ;
+ sh:property [ sh:path rdfs:comment ; sh:minCount 1 ; sh:datatype xsd:string ; ] ;
+ sh:property [ sh:path a:knowledgeKind ; sh:minCount 1 ;
+ sh:in ( "idea" "interface" "decision" "start-here" ) ; ] ;
+ sh:property [ sh:path a:filePath ; sh:minCount 1 ; sh:datatype xsd:string ; ] ;
+ sh:property [ sh:path a:reviewAfter ; sh:minCount 1 ; sh:maxCount 1 ; sh:datatype xsd:dateTime ; ] ;
+ sh:property [ sh:path a:glossaryTerm ; sh:nodeKind sh:IRI ; ] ;
+ sh:property [ sh:path a:sourceHash ; sh:minCount 1 ; sh:maxCount 1 ; sh:pattern "^[0-9a-f]{64}$" ; ] ;
+ # maxAge is seconds since the explicit source review, not an eviction rule.
+ sh:property [ sh:path a:maxAge ; sh:minCount 1 ; sh:maxCount 1 ; sh:datatype xsd:integer ; sh:minExclusive 0 ; ] ;
+ sh:property [ sh:path prov:wasDerivedFrom ; sh:minCount 1 ; sh:nodeKind sh:IRI ; ] .
+
+a:TamboInterfaceProfile a sh:NodeShape ;
+ sh:targetSubjectsOf a:interfaceKind ;
+ sh:property [ sh:path rdfs:label ; sh:minCount 1 ; ] ;
+ sh:property [ sh:path a:interfaceKind ; sh:minCount 1 ; sh:maxCount 1 ;
+ sh:in ( "CLI" "MCP" "REST" "config" "file-format" "library" ) ; ] ;
+ sh:property [ sh:path a:publicName ; sh:minCount 1 ; sh:datatype xsd:string ; ] ;
+ sh:property [ sh:path a:depends_on ; sh:nodeKind sh:IRI ; ] .
+
+a:TamboDecisionProfile a sh:NodeShape ;
+ sh:targetSubjectsOf a:supersessionStatus ;
+ sh:property [ sh:path rdfs:label ; sh:minCount 1 ; ] ;
+ sh:property [ sh:path a:status ; sh:minCount 1 ; sh:maxCount 1 ; sh:in ( "active" "superseded" "proposed" ) ; ] ;
+ sh:property [ sh:path a:supersessionStatus ; sh:minCount 1 ; sh:maxCount 1 ;
+ sh:in ( "not-recorded-in-source" "recorded" ) ; ] .
+
+# Existing SSSOM/RDF is a record, not a second identity-assertion format. The
+# native align decide/apply path remains the only way to derive identity knots.
+a:TamboMappingRecordProfile a sh:NodeShape ;
+ sh:targetSubjectsOf sssom:subject_id ;
+ sh:property [ sh:path rdfs:label ; sh:minCount 1 ; ] ;
+ sh:property [ sh:path sssom:subject_id ; sh:minCount 1 ; sh:maxCount 1 ; sh:nodeKind sh:IRI ; ] ;
+ sh:property [ sh:path sssom:object_id ; sh:minCount 1 ; sh:maxCount 1 ; sh:nodeKind sh:IRI ; ] ;
+ sh:property [ sh:path sssom:predicate_id ; sh:minCount 1 ; sh:maxCount 1 ; sh:nodeKind sh:IRI ; ] ;
+ sh:property [ sh:path sssom:mapping_justification ; sh:minCount 1 ; sh:maxCount 1 ; sh:nodeKind sh:IRI ; ] .
+
+a:TamboNoHandAuthoredIdentity a sh:NodeShape ;
+ sh:targetSubjectsOf owl:sameAs ;
+ sh:property [ sh:path rdfs:label ; sh:minCount 1 ; ] ;
+ sh:property [ sh:path owl:sameAs ; sh:maxCount 0 ; ] .
diff --git a/docs/knowledge/stored-queries.json b/docs/knowledge/stored-queries.json
new file mode 100644
index 0000000..cccecde
--- /dev/null
+++ b/docs/knowledge/stored-queries.json
@@ -0,0 +1,32 @@
+[
+ {
+ "name": "repository_ideas",
+ "description": "Repository-owned repository ideas",
+ "template": "PREFIX a: PREFIX rdfs: SELECT * WHERE { ?s a a:Concept; rdfs:comment ?summary . FILTER(STRSTARTS(STR(?s), \"http://aegis.gastown.local/ontology/doc/tambo/\") || STRSTARTS(STR(?s), \"http://aegis.gastown.local/ontology/code/tambo/\")) } ORDER BY ?s LIMIT 100",
+ "params": []
+ },
+ {
+ "name": "repository_interfaces",
+ "description": "Repository-owned repository interfaces",
+ "template": "PREFIX a: PREFIX rdfs: SELECT * WHERE { ?s a:interfaceKind ?kind; a:publicName ?name . FILTER(STRSTARTS(STR(?s), \"http://aegis.gastown.local/ontology/doc/tambo/\") || STRSTARTS(STR(?s), \"http://aegis.gastown.local/ontology/code/tambo/\")) } ORDER BY ?s LIMIT 100",
+ "params": []
+ },
+ {
+ "name": "repository_decisions",
+ "description": "Repository-owned repository decisions",
+ "template": "PREFIX a: PREFIX rdfs: SELECT * WHERE { ?s a a:DecisionRecord; a:status ?status . OPTIONAL { ?s ?successor } FILTER(STRSTARTS(STR(?s), \"http://aegis.gastown.local/ontology/doc/tambo/\") || STRSTARTS(STR(?s), \"http://aegis.gastown.local/ontology/code/tambo/\")) } ORDER BY ?s LIMIT 100",
+ "params": []
+ },
+ {
+ "name": "repository_start_here",
+ "description": "Repository-owned repository start here",
+ "template": "PREFIX a: PREFIX rdfs: SELECT * WHERE { ?s a:startHereOrder ?order . FILTER(STRSTARTS(STR(?s), \"http://aegis.gastown.local/ontology/doc/tambo/\") || STRSTARTS(STR(?s), \"http://aegis.gastown.local/ontology/code/tambo/\")) } ORDER BY ?s LIMIT 100",
+ "params": []
+ },
+ {
+ "name": "repository_freshness",
+ "description": "Repository-owned repository freshness",
+ "template": "PREFIX a: PREFIX rdfs: SELECT * WHERE { ?s a:reviewAfter ?reviewAfter; a:maxAge ?maxAgeSeconds . FILTER(STRSTARTS(STR(?s), \"http://aegis.gastown.local/ontology/doc/tambo/\") || STRSTARTS(STR(?s), \"http://aegis.gastown.local/ontology/code/tambo/\")) } ORDER BY ?s LIMIT 100",
+ "params": []
+ }
+]
diff --git a/queries/repository-share-tambo.rq b/queries/repository-share-tambo.rq
new file mode 100644
index 0000000..15de2cf
--- /dev/null
+++ b/queries/repository-share-tambo.rq
@@ -0,0 +1,76 @@
+PREFIX aegis:
+PREFIX rdf:
+PREFIX prov:
+PREFIX dct:
+PREFIX sssom:
+PREFIX owl:
+PREFIX rdfs:
+
+# Public repository knowledge only. Bobbin publishes its replaceable snapshot
+# into ROOT, so graph/group selectors cannot see it. Keep this predicate list
+# aligned with Bobbin's generate_chunk_turtle output: selecting every outgoing
+# edge also pulls private operational literals into a public release artifact.
+CONSTRUCT { ?s ?p ?o }
+WHERE {
+ {
+ ?s ?p ?o .
+ FILTER(
+ STRSTARTS(STR(?s), "http://aegis.gastown.local/ontology/code/tambo/") ||
+ STRSTARTS(STR(?s), "http://aegis.gastown.local/ontology/chunk/tambo/") ||
+ STRSTARTS(STR(?s), "http://aegis.gastown.local/ontology/doc/tambo/")
+ )
+ VALUES ?p {
+ rdf:type rdfs:label
+ aegis:filePath aegis:repo aegis:language
+ aegis:inDocument aegis:chunkOrder aegis:nextChunk aegis:mentions
+ aegis:name aegis:definedIn aegis:symbolKind
+ aegis:heading aegis:headingDepth
+ rdfs:comment rdfs:seeAlso prov:wasDerivedFrom dct:modified dct:isReplacedBy
+ aegis:knowledgeKind aegis:interfaceKind aegis:publicName aegis:status
+ aegis:supersessionStatus aegis:startHereOrder aegis:glossaryTerm
+ aegis:reviewAfter aegis:maxAge aegis:depends_on aegis:sourceHash
+ }
+ # Bobbin emits weak mention names as strings. Quipu may additionally
+ # resolve those names to IRIs under the same predicate in its local store;
+ # those derived edges do not satisfy the shipped string-valued shape.
+ FILTER(?p != aegis:mentions || isLiteral(?o))
+ }
+ UNION
+ {
+ ?s ?p ?o .
+ { ?s a sssom:MappingSet } UNION { ?s a owl:Axiom }
+ VALUES ?p {
+ rdf:type rdfs:label prov:wasDerivedFrom
+ sssom:mappings sssom:subject_id sssom:object_id sssom:predicate_id
+ sssom:subject_label sssom:object_label sssom:mapping_justification
+ sssom:predicate_modifier sssom:confidence sssom:author_id
+ }
+ }
+ UNION
+ {
+ VALUES ?s {
+ aegis:repo_caboodle
+ aegis:repo-embedded-knowledge-databases-directive-2026-09-03
+ }
+ ?s ?p ?o .
+ }
+ # Versioned crew memory is internal source, including references attached to
+ # otherwise public context nodes. Internal backups use a separate explicit scope.
+ FILTER(!STRSTARTS(STR(?s), "urn:quipu:memory:"))
+ FILTER(!STRSTARTS(STR(?p), "https://quipu.dev/memory#"))
+ FILTER(!STRSTARTS(STR(?o), "urn:quipu:memory:"))
+ # Work tracking identifiers are not public repository knowledge. Filter
+ # both endpoints and literal mentions instead of renaming entity identity.
+ FILTER(!REGEX(STR(?s), "(^|[^A-Za-z0-9_])aegis-[A-Za-z0-9]"))
+ FILTER(!REGEX(STR(?o), "(^|[^A-Za-z0-9_])aegis-[A-Za-z0-9]"))
+ # Exclude the whole node when a required label contains an internal ID.
+ # Dropping only its label would leave a typed, SHACL-invalid fragment.
+ MINUS {
+ ?s ?internalPredicate ?internalValue .
+ FILTER(REGEX(STR(?internalValue), "(^|[^A-Za-z0-9_])aegis-[A-Za-z0-9]"))
+ }
+ MINUS {
+ ?o ?targetPredicate ?targetValue .
+ FILTER(REGEX(STR(?targetValue), "(^|[^A-Za-z0-9_])aegis-[A-Za-z0-9]"))
+ }
+}
diff --git a/scripts/build-repository-knowledge.py b/scripts/build-repository-knowledge.py
new file mode 100644
index 0000000..15fd3de
--- /dev/null
+++ b/scripts/build-repository-knowledge.py
@@ -0,0 +1,106 @@
+#!/usr/bin/env python3
+"""Render reviewed summaries onto existing indexed pointers, never new identities."""
+
+import datetime
+import hashlib
+import json
+from pathlib import Path
+import re
+from urllib.parse import quote
+
+A = "http://aegis.gastown.local/ontology/"
+RDF = "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+RDFS = "http://www.w3.org/2000/01/rdf-schema#"
+PROV = "http://www.w3.org/ns/prov#"
+XSD = "http://www.w3.org/2001/XMLSchema#"
+KINDS = {"idea", "interface", "decision", "start-here"}
+PROPERTIES = {"interfaceKind", "publicName", "status", "supersessionStatus", "startHereOrder", "glossaryTerm"}
+
+
+def render(source, revision, lookup):
+ source = Path(source)
+ if not re.fullmatch("[0-9a-f]{40}", revision):
+ raise ValueError("release revision must be explicit")
+ registry = json.loads((source / "docs/knowledge/repository-knowledge.json").read_text())
+ reviewed = datetime.datetime.fromisoformat(registry["reviewed_at"].replace("Z", "+00:00"))
+ age = registry["max_age_seconds"]
+ if reviewed.tzinfo is None or type(age) is not int or age <= 0:
+ raise ValueError("freshness requires an explicit UTC review time and positive seconds")
+ after = (reviewed + datetime.timedelta(seconds=age)).isoformat().replace("+00:00", "Z")
+ records = registry["records"]
+ if {record["kind"] for record in records} != KINDS:
+ raise ValueError("ideas/interfaces/decisions/start-here inventory incomplete")
+ lines, seen = [], set()
+
+ def fact(subject, predicate, obj, iri=False, datatype=None):
+ if iri:
+ if not re.fullmatch(r'[^\s<>"{}|\\^`]+:[^\s<>"{}|\\^`]*', obj):
+ raise ValueError("invalid referenced IRI")
+ value = "<" + obj + ">"
+ else:
+ value = json.dumps(str(obj), ensure_ascii=False)
+ if datatype:
+ value += "^^<" + datatype + ">"
+ lines.append("<" + subject + "> <" + predicate + "> " + value + " .")
+
+ for record in records:
+ path = record["source"]
+ witness = source / path
+ if (
+ Path(path).is_absolute()
+ or ".." in Path(path).parts
+ or not witness.resolve().is_relative_to(source.resolve())
+ or hashlib.sha256(witness.read_bytes()).hexdigest() != record["source_sha256"]
+ ):
+ raise ValueError("source witness changed: " + path)
+ subject = record["subject"]
+ bases = (A + "doc/tambo/" + quote(path, safe=""), A + "code/tambo/" + quote(path, safe=""))
+ if not any(subject == base or subject.startswith(base + "#") for base in bases):
+ raise ValueError("subject is not the source-backed indexed pointer")
+ # lookup must use the actual index: a deterministic-looking guessed IRI
+ # is not an identity proof. Never mint another node when it is absent.
+ label = lookup(subject)
+ if not isinstance(label, str) or not label:
+ raise ValueError("source pointer absent or ambiguous in actual index: " + subject)
+ if (subject, record["kind"]) in seen:
+ raise ValueError("duplicate knowledge record")
+ seen.add((subject, record["kind"]))
+ if record.get("type") not in ("Concept", "DecisionRecord", None):
+ raise ValueError("unsupported knowledge type")
+ fact(subject, RDFS + "label", label)
+ fact(subject, A + "filePath", path)
+ if record.get("type"):
+ fact(subject, RDF + "type", A + record["type"], iri=True)
+ summary = record["summary"]
+ if (
+ not summary.strip()
+ or len(summary) > 1000
+ or re.search(r"\b[\w-]+\.(?:svc|lan)\b|/home/|\baegis-[A-Za-z0-9]", summary)
+ ):
+ raise ValueError("unreviewed or private summary")
+ fact(subject, RDFS + "comment", summary)
+ fact(subject, A + "knowledgeKind", record["kind"])
+ fact(subject, A + "sourceHash", record["source_sha256"])
+ fact(subject, A + "reviewAfter", after, datatype=XSD + "dateTime")
+ fact(subject, A + "maxAge", age, datatype=XSD + "integer")
+ fact(subject, "http://purl.org/dc/terms/modified", registry["reviewed_at"], datatype=XSD + "dateTime")
+ fact(
+ subject,
+ PROV + "wasDerivedFrom",
+ "https://github.com/scbrown/tambo/blob/" + revision + "/" + path,
+ iri=True,
+ )
+ for key, value in record.get("properties", {}).items():
+ if key not in PROPERTIES:
+ raise ValueError("unknown content property")
+ if key == "startHereOrder":
+ if not str(value).isdigit() or int(value) <= 0:
+ raise ValueError("start-here order must be a positive integer")
+ fact(subject, A + key, int(value), datatype=XSD + "integer")
+ else:
+ fact(subject, A + key, value, iri=(key == "glossaryTerm"))
+ for dependency in record.get("depends_on", []):
+ fact(subject, A + "depends_on", dependency, iri=True)
+ for term in record.get("see_also", []):
+ fact(subject, RDFS + "seeAlso", term, iri=True)
+ return "\n".join(sorted(set(lines))) + "\n"
diff --git a/scripts/build-repository-share.py b/scripts/build-repository-share.py
new file mode 100644
index 0000000..9a90a18
--- /dev/null
+++ b/scripts/build-repository-share.py
@@ -0,0 +1,367 @@
+"""Build one outward stack share using the existing Quipu/Bobbin boundary.
+
+Source and tooling are separate fresh, tag-verified checkouts. Index configuration,
+live policy, databases and receiver probes stay in the private working directory.
+Only the verified share directory leaves this function.
+"""
+
+import hashlib
+import json
+import os
+from pathlib import Path
+import re
+import shutil
+import sqlite3
+import io
+import tarfile
+import subprocess
+import sys
+
+# Release source must stay clean when modules load.
+sys.dont_write_bytecode = True
+
+
+def isolated_environment(private):
+ """Native workers must not inherit a host endpoint or credential catalogue."""
+ private = Path(private)
+ env = dict(os.environ)
+ for key in list(env):
+ if key in {
+ "BOBBIN_SERVER",
+ "QUIPU_SERVER",
+ "QUIPU_DB",
+ "QUIPU_AUTH_TOKEN",
+ "QUIPU_AUTH_TOKEN_FILE",
+ "GH_TOKEN",
+ "GITHUB_TOKEN",
+ } or key.startswith("BOBBIN_QUIPU_"):
+ env.pop(key)
+ for key, name in (
+ ("HOME", "native-home"),
+ ("XDG_CONFIG_HOME", "native-config"),
+ ("XDG_DATA_HOME", "native-data"),
+ ("XDG_CACHE_HOME", "native-cache"),
+ ):
+ directory = private / name
+ directory.mkdir(mode=0o700, parents=True, exist_ok=True)
+ env[key] = str(directory)
+ return env
+
+
+def command(args, *, env=None, cwd=None):
+ if env is None and cwd is not None:
+ env = isolated_environment(cwd)
+ return subprocess.run([str(v) for v in args], check=True, capture_output=True, text=True, env=env, cwd=cwd).stdout
+
+
+def local_mcp(binary, database, calls):
+ """Short-lived native process over a private DB; no HTTP or live graph."""
+ import selectors
+
+ requests = [
+ {
+ "jsonrpc": "2.0",
+ "id": 0,
+ "method": "initialize",
+ "params": {
+ "protocolVersion": "2024-11-05",
+ "capabilities": {},
+ "clientInfo": {"name": "repository-pendant-build", "version": "1"},
+ },
+ },
+ {"jsonrpc": "2.0", "method": "notifications/initialized"},
+ ]
+ requests.extend(
+ {"jsonrpc": "2.0", "id": number, "method": "tools/call", "params": {"name": name, "arguments": arguments}}
+ for number, (name, arguments) in enumerate(calls, 1)
+ )
+ values = []
+ with subprocess.Popen(
+ [str(binary), "mcp", "--db", str(database)],
+ stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ text=True,
+ cwd=Path(database).parent,
+ env=isolated_environment(Path(database).parent),
+ ) as process:
+ selector = selectors.DefaultSelector()
+ selector.register(process.stdout, selectors.EVENT_READ)
+ try:
+ for request in requests:
+ process.stdin.write(json.dumps(request) + "\n")
+ process.stdin.flush()
+ if "id" not in request:
+ continue
+ if not selector.select(timeout=30):
+ raise ValueError("native MCP response timeout")
+ response = json.loads(process.stdout.readline())
+ if (
+ response.get("id") != request["id"]
+ or "result" not in response
+ or "error" in response
+ or response["result"].get("isError", False)
+ ):
+ raise ValueError("native MCP refused operation: " + json.dumps(response))
+ if request["id"]:
+ content = response["result"].get("content", [])
+ if len(content) != 1 or content[0].get("type") != "text":
+ raise ValueError("unexpected native MCP result envelope")
+ values.append(json.loads(content[0]["text"]))
+ finally:
+ selector.close()
+ process.stdin.close()
+ try:
+ process.wait(timeout=10)
+ except subprocess.TimeoutExpired:
+ process.terminate()
+ process.wait(timeout=10)
+ if process.returncode:
+ raise ValueError("native MCP process failed")
+ return values
+
+
+def register_queries(binary, database, definitions):
+ if not isinstance(definitions, list) or not definitions:
+ raise ValueError("explicit repository stored query definitions required")
+ for definition in definitions:
+ if not isinstance(definition, dict) or definition.get("action", "load") != "load":
+ raise ValueError("stored query must be an explicit load definition")
+ results = local_mcp(binary, database, [("quipu_queries", {**d, "action": "load"}) for d in definitions])
+ if [result.get("loaded") for result in results] != [d["name"] for d in definitions]:
+ raise ValueError("native registry did not return the exact loaded query names")
+
+
+def indexed_labels(binary, database, subjects):
+ if any(not re.fullmatch(r'https?://[^\s<>"{}|\\^`]+', subject) for subject in subjects):
+ raise ValueError("invalid indexed subject IRI")
+ query = (
+ "SELECT ?s ?label WHERE { VALUES ?s { "
+ + " ".join("<" + s + ">" for s in subjects)
+ + "} ?s ?label }"
+ )
+ (result,) = local_mcp(binary, database, [("quipu_query", {"query": query, "verbose": True})])
+ if result.get("truncated") is not False:
+ raise ValueError("indexed pointer lookup was incomplete")
+ labels = {}
+ for row in result["rows"]:
+ subject, label = row["s"], row["label"]
+ if isinstance(subject, dict):
+ subject = subject["value"]
+ if isinstance(label, dict):
+ label = label["value"]
+ if subject in labels and labels[subject] != label:
+ raise ValueError("ambiguous indexed pointer label")
+ labels[subject] = label
+ return labels
+
+
+def materialize_release(source, revision, destination):
+ """Index only exact tracked release bytes in a new private source tree."""
+ destination = Path(destination)
+ destination.mkdir(mode=0o700)
+ archive = subprocess.run(
+ ["git", "-C", str(source), "archive", "--format=tar", revision], check=True, capture_output=True
+ ).stdout
+ with tarfile.open(fileobj=io.BytesIO(archive), mode="r:") as bundle:
+ for member in bundle.getmembers():
+ relative = Path(member.name)
+ if relative.is_absolute() or ".." in relative.parts or not (member.isfile() or member.isdir()):
+ raise ValueError("unsupported tracked release member")
+ target = destination / relative
+ if member.isdir():
+ target.mkdir(parents=True, exist_ok=True)
+ else:
+ target.parent.mkdir(parents=True, exist_ok=True)
+ with bundle.extractfile(member) as original, target.open("xb") as copied:
+ shutil.copyfileobj(original, copied)
+ return destination
+
+
+def alignment_carrier(source):
+ """An explicit empty reviewed carrier differs from missing or corrupt data.
+
+ Accepted nonempty mapping transport awaits an actual curated artifact. Do
+ not interpret a dependency, lexical match, or candidate as an identity.
+ """
+ source = Path(source)
+ data = (source / "docs/knowledge/alignments.ttl").read_bytes()
+ status = json.loads((source / "docs/knowledge/alignment-status.json").read_text())
+ if (
+ status.get("status") != "no-approved-mappings"
+ or type(status.get("approved_record_count")) is not int
+ or status["approved_record_count"] != 0
+ or status.get("carrier_sha256") != hashlib.sha256(data).hexdigest()
+ ):
+ raise ValueError("unreviewed or corrupt alignment carrier")
+ # Only declarations/comments are permitted in the explicit-empty profile.
+ # The native data validator below still parses the complete Turtle input.
+ for line in data.decode().splitlines():
+ if (
+ line.strip()
+ and not line.lstrip().startswith("#")
+ and line.strip()
+ not in {"@prefix sssom: .", "@prefix owl: ."}
+ ):
+ raise ValueError("empty alignment profile contains mapping data")
+ return data.decode()
+
+
+def build(revision, source, tooling, binary, bobbin, output):
+ repository = "scbrown/tambo"
+ source, tooling, output = map(Path, (source, tooling, output))
+ if output.exists():
+ raise ValueError("repository share output already exists")
+ if not re.fullmatch(r"scbrown/[a-z][a-z0-9-]*", repository):
+ raise ValueError("invalid repository identity")
+ slug = repository.split("/")[1]
+ if (
+ not re.fullmatch(r"[0-9a-f]{40}", revision)
+ or command(["git", "-C", source, "rev-parse", "HEAD"]).strip() != revision
+ or command(["git", "-C", source, "status", "--porcelain"]).strip()
+ ):
+ raise ValueError("repository source is not the exact clean release")
+ paths = command(["git", "-C", source, "ls-files", "-z"]).split("\0")
+ control = next((p for p in ("README.md", "README.rst") if p in paths), None)
+ if control is None:
+ raise ValueError("release lacks a supported public corpus positive control")
+ private = output.parent / "build-private"
+ private.mkdir(mode=0o700)
+
+ def run(args, *, env=None):
+ return command(args, env=env, cwd=private)
+
+ policy = private / "policy.ttl"
+ run(["python3", tooling / "scripts/private-share-policy.py", policy], env=dict(os.environ))
+ corpus = materialize_release(source, revision, private / "source")
+ (corpus / ".bobbin").mkdir(exist_ok=True)
+ index_database = private / "indexed-graph.db"
+ # Bobbin resolves embedded graph state at --source, not at the vector
+ # index root. Explicit private configuration avoids global-store fallback.
+ (corpus / ".bobbin/config.toml").write_text("[quipu]\nstore_path = " + json.dumps(str(index_database)) + "\n")
+ index = private / "index"
+ (index / ".bobbin").mkdir(parents=True)
+ (index / ".bobbin/config.toml").write_text("""quipu_push_chunks = true
+
+[index]
+include = [
+ "**/*.rs", "**/*.md", "**/*.rst", "**/*.toml", "**/*.yml", "**/*.yaml", "**/*.py",
+ "**/*.sh", "**/*.js", "**/*.mjs", "**/*.ts", "**/*.tsx", "**/*.json", "**/*.ttl", "**/*.go",
+]
+exclude = [
+ "**/target/**", "**/.git/**", "**/.bobbin/**", "**/.agents/**", "**/.claude/**", "**/.codex/**",
+ "**/.mcp.json", "**/CLAUDE.local.md", "**/node_modules/**", "**/book/book/**", "**/vendor/**",
+]
+use_gitignore = true
+entities = false
+
+[embedding]
+model = "all-MiniLM-L6-v2"
+batch_size = 64
+""")
+ env = isolated_environment(private)
+ # Keep default inference concurrency bounded; a caller may supply its measured limit.
+ env.setdefault("BOBBIN_ONNX_THREADS", "1")
+ run([bobbin, "index", index, "--source", corpus, "--repo", slug, "--force", "--skip-calibrate", "--json"], env=env)
+ database = private / "producer.db"
+ if not index_database.is_file():
+ raise ValueError("indexer did not produce the repository graph")
+ with sqlite3.connect("file:" + str(index_database) + "?mode=ro", uri=True) as original:
+ with sqlite3.connect(database) as target:
+ original.backup(target)
+ shapes = sorted((tooling / "shapes").glob("*.ttl"))
+ if not shapes:
+ raise ValueError("verified engine source has no receiver shapes")
+ for shape in shapes:
+ run([binary, "shapes", "load", shape.stem, shape, "--db", database])
+ run(
+ [
+ binary,
+ "shapes",
+ "load",
+ "publication-policy",
+ tooling / "docs/knowledge/publication-policy.shapes.ttl",
+ "--db",
+ database,
+ ]
+ )
+ run([binary, "knot", tooling / "docs/knowledge/publication-policy.ttl", "--db", database])
+ run([binary, "knot", policy, "--graph", "urn:quipu:private-release-policy", "--db", database])
+ context = private / "context.ttl"
+ context.write_text(f'''@prefix aegis: .
+@prefix rdfs: .
+aegis:repo_caboodle a aegis:GitRepo ; rdfs:label "repo_caboodle" ;
+ aegis:repositorySha "{revision}" .
+aegis:repo-embedded-knowledge-databases-directive-2026-09-03 a aegis:Directive ;
+ rdfs:label "Repository-embedded knowledge databases" ;
+ aegis:issuedBy "Stiwi" ; aegis:applies_to aegis:repo_caboodle .
+''')
+ run([binary, "knot", context, "--db", database])
+ # Curated repository content and validating shapes are repo-owned. Refuse
+ # an incomplete source proposal rather than minting a pointer-only stub.
+ registry_path = source / "docs/knowledge/repository-knowledge.json"
+ knowledge = private / "repository-knowledge.ttl"
+ constraints = source / "docs/knowledge/repository-knowledge.shapes.ttl"
+ queries = source / "docs/knowledge/stored-queries.json"
+ alignments = source / "docs/knowledge/alignments.ttl"
+ for required in (registry_path, constraints, queries, alignments):
+ if not required.is_file() or not required.stat().st_size:
+ raise ValueError("required repository knowledge member missing: " + required.name)
+ import importlib.util
+
+ spec = importlib.util.spec_from_file_location(
+ "repository_knowledge", source / "scripts/build-repository-knowledge.py"
+ )
+ generator = importlib.util.module_from_spec(spec)
+ spec.loader.exec_module(generator)
+ records = json.loads(registry_path.read_text())["records"]
+ labels = indexed_labels(binary, database, sorted({record["subject"] for record in records}))
+ knowledge.write_text(
+ generator.render(source, revision, lambda subject: labels.get(subject)) + "\n" + alignment_carrier(source)
+ )
+ run([binary, "validate", "--shapes", constraints, "--data", knowledge])
+ run([binary, "shapes", "load", "repository-knowledge", constraints, "--db", database])
+ run([binary, "knot", knowledge, "--db", database])
+ register_queries(binary, database, json.loads(queries.read_text()))
+ query = (source / "queries/repository-share-tambo.rq").read_text()
+ stage = private / "share"
+ run([binary, "share", "--construct", query, "--turtle", "--output", stage, "--db", database])
+ if not (stage / "shapes.ttl").is_file() or (stage / "shapes.ttl").stat().st_size == 0:
+ raise ValueError("repository share omitted receiver shapes")
+ receiver = private / "receiver.db"
+ run([binary, "shapes", "load", "repository-share", stage / "shapes.ttl", "--db", receiver])
+ imported = json.loads(run([binary, "import", stage, "--db", receiver]))
+ if (
+ imported.get("outcome") != "staged"
+ or imported.get("promotion", {}).get("eligible") is not True
+ or type(imported.get("triples", {}).get("accepted")) is not int
+ or imported["triples"]["accepted"] <= 3
+ ):
+ raise ValueError("repository receiver did not admit a non-stub corpus")
+ run([binary, "import", "promote", imported["share_id"], "--db", receiver])
+ positive = run(
+ [
+ binary,
+ "query",
+ "SELECT ?s WHERE { ?s " + json.dumps(control) + " } LIMIT 1",
+ "--db",
+ receiver,
+ ]
+ )
+ if re.search(r"\n1 results\s*$", positive) is None:
+ raise ValueError("repository corpus positive control missing after import")
+ run(["python3", tooling / "scripts/verify-repository-share-receiver.py", binary, stage, receiver, private])
+ if run(["git", "-C", source, "status", "--porcelain"]).strip():
+ raise ValueError("indexing changed the exact repository source")
+ shutil.move(stage, output)
+ return {"triples": imported["triples"]["accepted"], "positive_file": control}
+
+
+if __name__ == "__main__":
+ import sys
+
+ if len(sys.argv) != 7:
+ raise SystemExit(
+ "usage: build-repository-share.py "
+ )
+ build(*sys.argv[1:])
diff --git a/scripts/publish-repository-share.sh b/scripts/publish-repository-share.sh
new file mode 100755
index 0000000..3b0f0f9
--- /dev/null
+++ b/scripts/publish-repository-share.sh
@@ -0,0 +1,5 @@
+#!/usr/bin/env bash
+# Repo-owned job, invoked only by the managed trusted-host release dispatcher.
+set -euo pipefail
+umask 077
+exec python3 "$(dirname -- "${BASH_SOURCE[0]}")/publish_repository_share.py" "$@"
diff --git a/scripts/publish_repository_share.py b/scripts/publish_repository_share.py
new file mode 100644
index 0000000..36da825
--- /dev/null
+++ b/scripts/publish_repository_share.py
@@ -0,0 +1,193 @@
+#!/usr/bin/env python3
+"""Tambo's owned trusted release job; hosted workflows only verify its output."""
+
+import gzip
+import hashlib
+import importlib.util
+import json
+import os
+from pathlib import Path
+import re
+import shutil
+import subprocess
+import sys
+import tarfile
+import tempfile
+
+# Release source must stay clean when modules load.
+sys.dont_write_bytecode = True
+
+REPOSITORY = "scbrown/tambo"
+MEMBERS = {"export.nt", "export.ttl", "payload.nq", "manifest.json", "manifest.ttl", "shapes.ttl", "queries.ttl"}
+
+
+def command(args):
+ return subprocess.run([str(v) for v in args], check=True, capture_output=True, text=True).stdout
+
+
+def digest(path):
+ result = hashlib.sha256()
+ with path.open("rb") as handle:
+ for block in iter(lambda: handle.read(1 << 20), b""):
+ result.update(block)
+ return result.hexdigest()
+
+
+def check_share(stage):
+ files = list(stage.iterdir())
+ if any(not p.is_file() or p.is_symlink() or p.name not in MEMBERS for p in files):
+ raise ValueError("unexpected or non-regular share member")
+ for name in ("export.nt", "manifest.json", "manifest.ttl", "shapes.ttl", "queries.ttl"):
+ if not (stage / name).is_file() or not (stage / name).stat().st_size:
+ raise ValueError("required nonempty share member missing: " + name)
+ recipe = json.loads((stage / "manifest.json").read_text()).get("embedding")
+ if (
+ not isinstance(recipe, dict)
+ or not isinstance(recipe.get("model"), str)
+ or not recipe["model"]
+ or Path(recipe["model"]).name != recipe["model"]
+ or type(recipe.get("dimension")) is not int
+ or recipe["dimension"] <= 0
+ or any(not re.fullmatch("[0-9a-f]{64}", recipe.get(k, "")) for k in ("model_sha256", "tokenizer_sha256"))
+ ):
+ raise ValueError("share omitted a provider-byte embedding recipe")
+ # This repository pack is pointer-only. Curated idea summaries use
+ # rdfs:comment; source bodies and line spans are never release predicates.
+ export = (stage / "export.nt").read_text()
+ for predicate in ("content", "startLine", "endLine"):
+ if "" in export:
+ raise ValueError("share duplicates source text or line-span data")
+ return recipe
+
+
+def package(stage, archive):
+ check_share(stage)
+ with archive.open("xb") as raw:
+ with gzip.GzipFile(filename="", fileobj=raw, mode="wb", mtime=0) as compressed:
+ with tarfile.open(fileobj=compressed, mode="w") as tar:
+ for path in sorted(stage.iterdir()):
+ info = tar.gettarinfo(str(path), arcname=path.name)
+ info.uid = info.gid = 0
+ info.uname = info.gname = ""
+ info.mtime = 0
+ info.mode = 0o600
+ with path.open("rb") as handle:
+ tar.addfile(info, handle)
+
+
+def held():
+ path = Path(os.environ.get("QUIPU_SHARE_HOLD_FILE", str(Path.home() / ".quipu-share-hold")))
+ try:
+ path.lstat()
+ except FileNotFoundError:
+ return False
+ except OSError as error:
+ raise ValueError("cannot inspect publication hold") from error
+ return True
+
+
+def publish(tag, revision, output):
+ if held():
+ raise ValueError("publication held before any lookup/build")
+ if not re.fullmatch(r"v\d+\.\d+\.\d+(?:[.-][A-Za-z0-9.-]+)?", tag):
+ raise ValueError("unexpected Tambo release tag")
+ if not re.fullmatch("[0-9a-f]{40}", revision):
+ raise ValueError("invalid release revision")
+ source = Path(__file__).resolve().parents[1]
+ if (
+ command(["git", "-C", source, "rev-parse", "HEAD"]).strip() != revision
+ or command(["git", "-C", source, "rev-parse", "refs/tags/" + tag + "^{commit}"]).strip() != revision
+ or command(["git", "-C", source, "status", "--porcelain"]).strip()
+ ):
+ raise ValueError("publisher source is not the exact clean release")
+ output = Path(output)
+ if output.exists():
+ raise ValueError("retained output exists; reconcile before retrying")
+ binary = Path(os.environ["QUIPU_SHARE_ENGINE_BIN"]).resolve(strict=True)
+ tooling = Path(os.environ["QUIPU_SHARE_ENGINE_SOURCE"]).resolve(strict=True)
+ bobbin = Path(os.environ["QUIPU_SHARE_BOBBIN"]).resolve(strict=True)
+ engine_revision = os.environ["QUIPU_SHARE_ENGINE_REVISION"]
+ if (
+ not re.fullmatch("[0-9a-f]{40}", engine_revision)
+ or command(["git", "-C", tooling, "rev-parse", "HEAD"]).strip() != engine_revision
+ or command(["git", "-C", tooling, "status", "--porcelain"]).strip()
+ or digest(binary) != os.environ["QUIPU_SHARE_ENGINE_BIN_SHA256"]
+ or digest(binary.with_name("quipu-server")) != os.environ["QUIPU_SHARE_ENGINE_SERVER_SHA256"]
+ or digest(bobbin) != os.environ["QUIPU_SHARE_BOBBIN_SHA256"]
+ ):
+ raise ValueError("trusted engine/source/indexer pin mismatch")
+ output.parent.mkdir(parents=True, exist_ok=True, mode=0o700)
+ with tempfile.TemporaryDirectory(prefix="prepare-", dir=output.parent) as temp:
+ private = Path(temp)
+ stage = private / "share"
+ spec = importlib.util.spec_from_file_location(
+ "bobbin_share_build", source / "scripts/build-repository-share.py"
+ )
+ builder = importlib.util.module_from_spec(spec)
+ spec.loader.exec_module(builder)
+ builder.build(revision, source, tooling, binary, bobbin, stage)
+ archive_name = "tambo-" + tag + "-repository.pendant.tar.gz"
+ prepared = private / "assets"
+ prepared.mkdir(mode=0o700)
+ archive = prepared / archive_name
+ package(stage, archive)
+ (prepared / (archive_name + ".sha256")).write_text(digest(archive) + " " + archive_name + "\n")
+ proof = {
+ "schema": "quipu.repository-share-producer/v1",
+ "source_repository": REPOSITORY,
+ "source_revision": revision,
+ "release_tag": tag,
+ "archive_sha256": digest(archive),
+ "quipu_sha256": digest(binary),
+ "engine_source_revision": engine_revision,
+ "bobbin_sha256": digest(bobbin),
+ "quipu_server_sha256": digest(binary.with_name("quipu-server")),
+ "destination": "outward",
+ }
+ (prepared / (archive_name + ".provenance.json")).write_text(json.dumps(proof, sort_keys=True, indent=2) + "\n")
+ names = sorted(p.name for p in prepared.iterdir())
+ if held():
+ raise ValueError("publication held after preparation")
+ available = json.loads(command(["gh", "release", "view", tag, "--repo", REPOSITORY, "--json", "assets"]))
+ present = {a["name"] for a in available["assets"]} & set(names)
+ if present and present != set(names):
+ raise ValueError("partial publication; explicit reconciliation required")
+ # Retain before the one-way upload. No --clobber and no automatic retry
+ # from an uncertain write. The managed dispatcher also retains STARTED.
+ shutil.copytree(prepared, output)
+ if not present:
+ if held():
+ raise ValueError("publication held immediately before upload")
+ command(["gh", "release", "upload", tag, "--repo", REPOSITORY, *(output / name for name in names)])
+ downloaded = private / "downloaded"
+ downloaded.mkdir()
+ command(
+ [
+ "gh",
+ "release",
+ "download",
+ tag,
+ "--repo",
+ REPOSITORY,
+ "--dir",
+ downloaded,
+ *(arg for name in names for arg in ("--pattern", name)),
+ ]
+ )
+ for name in names:
+ if (downloaded / name).read_bytes() != (output / name).read_bytes():
+ raise ValueError("existing or newly published asset differs; never overwrite")
+ receipt = {
+ **proof,
+ "published_and_read_back": True,
+ "assets": names,
+ "asset_sha256": {name: digest(downloaded / name) for name in names},
+ }
+ (output / "delivery.json").write_text(json.dumps(receipt, indent=2) + "\n")
+ return receipt
+
+
+if __name__ == "__main__":
+ if len(sys.argv) != 5 or sys.argv[1] != "--trusted-release":
+ raise SystemExit("usage: publish-repository-share.sh --trusted-release ")
+ print(json.dumps(publish(sys.argv[2], sys.argv[3], sys.argv[4])))
diff --git a/scripts/test-repository-knowledge.py b/scripts/test-repository-knowledge.py
new file mode 100644
index 0000000..9fd3cb6
--- /dev/null
+++ b/scripts/test-repository-knowledge.py
@@ -0,0 +1,182 @@
+#!/usr/bin/env python3
+"""Curated-source refusal controls. Lookup fixtures are not index coverage proof.
+
+# arming: ci; release contract invokes this script.
+"""
+
+import importlib.util
+import json
+import os
+from pathlib import Path
+import shutil
+import subprocess
+import tempfile
+import unittest
+import sys
+
+# Release source must stay clean when modules load.
+sys.dont_write_bytecode = True
+
+ROOT = Path(__file__).resolve().parents[1]
+spec = importlib.util.spec_from_file_location("knowledge", ROOT / "scripts/build-repository-knowledge.py")
+module = importlib.util.module_from_spec(spec)
+spec.loader.exec_module(module)
+
+
+builder_spec = importlib.util.spec_from_file_location("share_builder", ROOT / "scripts/build-repository-share.py")
+builder = importlib.util.module_from_spec(builder_spec)
+builder_spec.loader.exec_module(builder)
+
+
+class ReleaseSnapshot(unittest.TestCase):
+ def test_real_git_archive_excludes_untracked_key_and_preserves_source(self):
+ with tempfile.TemporaryDirectory() as folder:
+ root = Path(folder)
+ source = root / "source"
+ source.mkdir()
+ subprocess.run(["git", "init", "-q", str(source)], check=True)
+ (source / "README.md").write_text("tracked pointer source\n")
+ subprocess.run(["git", "-C", str(source), "add", "README.md"], check=True)
+ subprocess.run(
+ [
+ "git",
+ "-C",
+ str(source),
+ "-c",
+ "user.name=fixture",
+ "-c",
+ "user.email=fixture@example.org",
+ "commit",
+ "-qm",
+ "fixture",
+ ],
+ check=True,
+ )
+ (source / "private.key").write_text("synthetic untracked sentinel")
+ revision = subprocess.check_output(["git", "-C", str(source), "rev-parse", "HEAD"], text=True).strip()
+ copied = builder.materialize_release(source, revision, root / "copied")
+ self.assertEqual((copied / "README.md").read_bytes(), (source / "README.md").read_bytes())
+ self.assertFalse((copied / "private.key").exists())
+ self.assertEqual((source / "private.key").read_text(), "synthetic untracked sentinel")
+ self.assertFalse((source / ".bobbin").exists())
+
+
+class AlignmentCarrier(unittest.TestCase):
+ def test_explicit_empty_positive_missing_corrupt_and_nonempty_negative(self):
+ with tempfile.TemporaryDirectory() as folder:
+ root = Path(folder)
+ target = root / "docs/knowledge"
+ target.mkdir(parents=True)
+ for name in ("alignments.ttl", "alignment-status.json"):
+ shutil.copyfile(ROOT / "docs/knowledge" / name, target / name)
+ self.assertIn("@prefix sssom:", builder.alignment_carrier(root))
+ data = target / "alignments.ttl"
+ original = data.read_text()
+ data.unlink()
+ with self.assertRaises(FileNotFoundError):
+ builder.alignment_carrier(root)
+ data.write_text(original + " .\n")
+ with self.assertRaisesRegex(ValueError, "corrupt"):
+ builder.alignment_carrier(root)
+ import hashlib
+
+ status = json.loads((target / "alignment-status.json").read_text())
+ status["carrier_sha256"] = hashlib.sha256(data.read_bytes()).hexdigest()
+ (target / "alignment-status.json").write_text(json.dumps(status))
+ with self.assertRaisesRegex(ValueError, "mapping data"):
+ builder.alignment_carrier(root)
+ data.write_text("@prefix owl: . .\n")
+ status["carrier_sha256"] = hashlib.sha256(data.read_bytes()).hexdigest()
+ (target / "alignment-status.json").write_text(json.dumps(status))
+ with self.assertRaisesRegex(ValueError, "mapping data"):
+ builder.alignment_carrier(root)
+
+
+class Knowledge(unittest.TestCase):
+ def setUp(self):
+ self.temp = tempfile.TemporaryDirectory()
+ self.addCleanup(self.temp.cleanup)
+ self.root = Path(self.temp.name)
+ self.registry = json.loads((ROOT / "docs/knowledge/repository-knowledge.json").read_text())
+ for source in {r["source"] for r in self.registry["records"]}:
+ target = self.root / source
+ target.parent.mkdir(parents=True, exist_ok=True)
+ shutil.copyfile(ROOT / source, target)
+ (self.root / "docs/knowledge").mkdir(parents=True)
+ self.save()
+
+ def save(self):
+ (self.root / "docs/knowledge/repository-knowledge.json").write_text(json.dumps(self.registry))
+
+ def render(self, lookup=lambda _: "actual-index-label-fixture"):
+ return module.render(self.root, "1" * 40, lookup)
+
+ def test_source_bound_nonempty_content(self):
+ text = self.render()
+ self.assertIn("2592000", text)
+ self.assertIn("reviewAfter", text)
+ self.assertIn("glossaryTerm> ", text)
+ self.assertIn('knowledgeKind> "idea"', text)
+ self.assertIn('interfaceKind> "file-format"', text)
+ self.assertIn('supersessionStatus> "not-recorded-in-source"', text)
+ self.assertIn('startHereOrder> "3"^^', text)
+ self.assertNotIn("/content>", text)
+ self.assertNotIn("owl#sameAs", text)
+
+ def test_absent_or_ambiguous_index_pointer_refuses(self):
+ for value in (None, False, ""):
+ with self.subTest(value=value), self.assertRaisesRegex(ValueError, "absent or ambiguous"):
+ self.render(lambda _: value)
+
+ def test_changed_source_refuses(self):
+ path = self.root / self.registry["records"][0]["source"]
+ path.write_text(path.read_text() + "\nchanged\n")
+ with self.assertRaisesRegex(ValueError, "witness changed"):
+ self.render()
+
+ def test_guessed_identity_refuses(self):
+ self.registry["records"][0]["subject"] = "https://example.org/guessed"
+ self.save()
+ with self.assertRaisesRegex(ValueError, "source-backed"):
+ self.render()
+
+ def test_missing_required_content_refuses(self):
+ self.registry["records"] = [r for r in self.registry["records"] if r["kind"] != "decision"]
+ self.save()
+ with self.assertRaisesRegex(ValueError, "inventory incomplete"):
+ self.render()
+
+ def test_bad_freshness_and_private_summary_refuse(self):
+ self.registry["max_age_seconds"] = 0
+ self.save()
+ with self.assertRaisesRegex(ValueError, "freshness"):
+ self.render()
+ self.registry["max_age_seconds"] = 30
+ self.registry["records"][0]["summary"] = "read /home/private/file"
+ self.save()
+ with self.assertRaisesRegex(ValueError, "private summary"):
+ self.render()
+
+ @unittest.skipUnless(os.environ.get("QUIPU_TEST_BIN"), "native schema control requires explicit test artifact")
+ def test_actual_native_schema_positive_and_missing_freshness_negative(self):
+ data = self.root / "curated.ttl"
+ data.write_text(self.render())
+ args = [
+ os.environ["QUIPU_TEST_BIN"],
+ "validate",
+ "--shapes",
+ str(ROOT / "docs/knowledge/repository-knowledge.shapes.ttl"),
+ "--data",
+ str(data),
+ "--format",
+ "json",
+ ]
+ positive = subprocess.run(args, capture_output=True, text=True, cwd=self.root)
+ self.assertEqual(positive.returncode, 0, positive.stderr + positive.stdout)
+ data.write_text("\n".join(line for line in data.read_text().splitlines() if "/reviewAfter>" not in line))
+ negative = subprocess.run(args, capture_output=True, text=True, cwd=self.root)
+ self.assertNotEqual(negative.returncode, 0, negative.stdout)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/scripts/test-repository-share-publication.py b/scripts/test-repository-share-publication.py
new file mode 100644
index 0000000..5514b8d
--- /dev/null
+++ b/scripts/test-repository-share-publication.py
@@ -0,0 +1,218 @@
+#!/usr/bin/env python3
+"""Packaging/readback refusal controls; synthetic data is not native acceptance.
+
+# arming: ci; release asset publication contract invokes this script.
+"""
+
+import importlib.util
+import json
+from pathlib import Path
+import tempfile
+import unittest
+import sys
+import os
+from unittest.mock import patch
+
+# Release source must stay clean when modules load.
+sys.dont_write_bytecode = True
+
+ROOT = Path(__file__).resolve().parent
+
+
+def load(name, path):
+ spec = importlib.util.spec_from_file_location(name, path)
+ module = importlib.util.module_from_spec(spec)
+ spec.loader.exec_module(module)
+ return module
+
+
+publisher = load("publisher_test", ROOT / "publish_repository_share.py")
+verifier = load("verifier_test", ROOT / "verify-repository-share.py")
+
+
+class Publication(unittest.TestCase):
+ def setUp(self):
+ self.temp = tempfile.TemporaryDirectory()
+ self.addCleanup(self.temp.cleanup)
+ self.root = Path(self.temp.name)
+ self.stage = self.root / "stage"
+ self.stage.mkdir()
+ recipe = {"model": "model.onnx", "model_sha256": "a" * 64, "tokenizer_sha256": "b" * 64, "dimension": 384}
+ (self.stage / "manifest.json").write_text(json.dumps({"embedding": recipe}))
+ for name in ("export.nt", "manifest.ttl", "shapes.ttl", "queries.ttl"):
+ (self.stage / name).write_text("# synthetic fixture only\n")
+ self.archive = self.root / "tambo-v1.2.3-repository.pendant.tar.gz"
+ self.revision = "1" * 40
+
+ def prepare(self):
+ publisher.package(self.stage, self.archive)
+ sha = publisher.digest(self.archive)
+ self.archive.with_name(self.archive.name + ".sha256").write_text(sha + " " + self.archive.name + "\n")
+ self.proof = {
+ "schema": "quipu.repository-share-producer/v1",
+ "source_repository": "scbrown/tambo",
+ "source_revision": self.revision,
+ "release_tag": "v1.2.3",
+ "archive_sha256": sha,
+ "destination": "outward",
+ "quipu_sha256": "2" * 64,
+ "quipu_server_sha256": "5" * 64,
+ "bobbin_sha256": "3" * 64,
+ "engine_source_revision": "4" * 40,
+ }
+ self.proof_path = self.archive.with_name(self.archive.name + ".provenance.json")
+ self.proof_path.write_text(json.dumps(self.proof))
+
+ def test_deterministic_archive_and_exact_readback(self):
+ self.prepare()
+ other = self.root / "second.tar.gz"
+ publisher.package(self.stage, other)
+ self.assertEqual(self.archive.read_bytes(), other.read_bytes())
+ self.assertEqual(verifier.verify(self.root, "v1.2.3", self.revision)["source_revision"], self.revision)
+
+ def test_required_members_and_recipe(self):
+ (self.stage / "queries.ttl").unlink()
+ with self.assertRaisesRegex(ValueError, "required nonempty"):
+ publisher.check_share(self.stage)
+ (self.stage / "queries.ttl").write_text("# query")
+ (self.stage / "manifest.json").write_text("{}")
+ with self.assertRaisesRegex(ValueError, "provider-byte"):
+ publisher.check_share(self.stage)
+
+ def test_text_line_span_and_private_members_refuse(self):
+ for predicate in ("content", "startLine", "endLine"):
+ (self.stage / "export.nt").write_text(
+ " "body" .\n'
+ )
+ with self.subTest(predicate=predicate), self.assertRaisesRegex(ValueError, "duplicates source"):
+ publisher.check_share(self.stage)
+ (self.stage / "export.nt").write_text("# empty synthetic")
+ (self.stage / "policy-token").write_text("not a real token")
+ with self.assertRaisesRegex(ValueError, "unexpected"):
+ publisher.check_share(self.stage)
+
+ def test_foreign_repository_and_wrong_source_refuse(self):
+ self.prepare()
+ for field, value in [
+ ("source_repository", "scbrown/quipu"),
+ ("source_revision", "f" * 40),
+ ("release_tag", "v9.9.9"),
+ ("archive_sha256", "f" * 64),
+ ]:
+ self.proof_path.write_text(json.dumps({**self.proof, field: value}))
+ with self.subTest(field=field), self.assertRaisesRegex(ValueError, "provenance mismatch"):
+ verifier.verify(self.root, "v1.2.3", self.revision)
+
+ def test_checksum_refuses(self):
+ self.prepare()
+ self.archive.with_name(self.archive.name + ".sha256").write_text("wrong")
+ with self.assertRaisesRegex(ValueError, "checksum"):
+ verifier.verify(self.root, "v1.2.3", self.revision)
+
+ def test_verifier_argument_refusal_leaves_checkout_clean(self):
+ import shutil
+ import subprocess
+
+ source = self.root / "clean-source"
+ source.mkdir()
+ for name in ("verify-repository-share.py", "publish_repository_share.py"):
+ shutil.copyfile(ROOT / name, source / name)
+ # Isolated startup ignores inherited Python environment workarounds.
+ result = subprocess.run(
+ [sys.executable, "-I", str(source / "verify-repository-share.py")],
+ capture_output=True, text=True, cwd=source,
+ )
+ self.assertEqual(result.returncode, 1)
+ self.assertIn("usage:", result.stderr)
+ self.assertEqual(list(source.rglob("*.pyc")), [])
+
+ def test_hold_absent_and_dangling_symlink_before_any_build(self):
+ hold = self.root / "hold"
+ with patch.dict(publisher.os.environ, {"QUIPU_SHARE_HOLD_FILE": str(hold)}):
+ self.assertFalse(publisher.held())
+ hold.symlink_to(self.root / "missing-target")
+ self.assertTrue(publisher.held())
+ with self.assertRaisesRegex(ValueError, "held before"):
+ publisher.publish("invalid-tag", "invalid-revision", self.root / "output")
+ self.assertFalse((self.root / "output").exists())
+
+ def test_unreadable_hold_fails_closed(self):
+ with patch.object(Path, "lstat", side_effect=PermissionError("synthetic denied")):
+ with self.assertRaisesRegex(ValueError, "cannot inspect publication hold"):
+ publisher.held()
+
+ def host_environment(self):
+ host = self.root / "synthetic-host"
+ host.mkdir()
+ (host / "config-marker").write_text("fixture only")
+ return {
+ "HOME": str(host), "XDG_CONFIG_HOME": str(host),
+ "XDG_DATA_HOME": str(host), "XDG_CACHE_HOME": str(host),
+ **{key: "synthetic-fixture" for key in (
+ "BOBBIN_SERVER", "BOBBIN_QUIPU_REMOTE", "BOBBIN_QUIPU_TOKEN",
+ "QUIPU_SERVER", "QUIPU_DB", "QUIPU_AUTH_TOKEN",
+ "QUIPU_AUTH_TOKEN_FILE", "GH_TOKEN", "GITHUB_TOKEN",
+ )},
+ }
+
+ def test_command_isolates_host_configuration_and_policy_exception(self):
+ builder = load("builder_environment_test", ROOT / "build-repository-share.py")
+ private = self.root / "private"
+ private.mkdir()
+ probe = (
+ "import json,os;from pathlib import Path;"
+ "print(json.dumps({'host_config':(Path.home()/'config-marker').exists(),"
+ "'inherited':[k for k in os.environ if k in "
+ "['BOBBIN_SERVER','QUIPU_SERVER','QUIPU_DB','QUIPU_AUTH_TOKEN',"
+ "'QUIPU_AUTH_TOKEN_FILE','GH_TOKEN','GITHUB_TOKEN'] or k.startswith('BOBBIN_QUIPU_')],"
+ "'homes':[os.environ[k] for k in ['HOME','XDG_CONFIG_HOME','XDG_DATA_HOME','XDG_CACHE_HOME']]}))"
+ )
+ with patch.dict(os.environ, self.host_environment()):
+ # The explicit policy-helper exception still sees synthetic host config.
+ control = json.loads(builder.command([sys.executable, "-I", "-c", probe], env=dict(os.environ), cwd=private))
+ self.assertTrue(control["host_config"])
+ self.assertTrue(control["inherited"])
+ result = json.loads(builder.command([sys.executable, "-I", "-c", probe], cwd=private))
+ self.assertFalse(result["host_config"])
+ self.assertEqual(result["inherited"], [])
+ self.assertEqual(len(set(result["homes"])), 4)
+ for directory in result["homes"]:
+ self.assertTrue(Path(directory).is_relative_to(private))
+ self.assertTrue(Path(directory).is_dir())
+
+ def test_mcp_child_isolates_host_configuration(self):
+ builder = load("builder_mcp_environment_test", ROOT / "build-repository-share.py")
+ child = self.root / "fixture-mcp"
+ child.write_text(
+ "#!" + sys.executable + "\n"
+ "import json,os,sys\nfrom pathlib import Path\n"
+ "for line in sys.stdin:\n"
+ " r=json.loads(line)\n"
+ " if 'id' not in r:continue\n"
+ " isolated=not (Path.home()/'config-marker').exists() and not any(k in os.environ for k in ['QUIPU_SERVER','GH_TOKEN','BOBBIN_QUIPU_TOKEN'])\n"
+ " result={} if r['id']==0 else {'content':[{'type':'text','text':json.dumps({'isolated':isolated})}]}\n"
+ " print(json.dumps({'jsonrpc':'2.0','id':r['id'],'result':result}),flush=True)\n"
+ )
+ child.chmod(0o700)
+ with patch.dict(os.environ, self.host_environment()):
+ (result,) = builder.local_mcp(child, self.root / "fixture.db", [("fixture", {})])
+ self.assertEqual(result, {"isolated": True})
+
+ def test_wait_is_bounded(self):
+ calls, pauses = [], []
+
+ class Result:
+ returncode = 1
+
+ def run(args, **kwargs):
+ calls.append(args)
+ return Result()
+
+ with self.assertRaisesRegex(ValueError, "bounded"):
+ verifier.wait("v1.2.3", self.revision, attempts=2, pause=pauses.append, run=run)
+ self.assertEqual(len(calls), 2)
+ self.assertEqual(pauses, [30])
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/scripts/test-repository-share-query.py b/scripts/test-repository-share-query.py
new file mode 100644
index 0000000..0c442d5
--- /dev/null
+++ b/scripts/test-repository-share-query.py
@@ -0,0 +1,108 @@
+#!/usr/bin/env python3
+"""Private corpus controls for Tambo's copied pointer-only release query.
+
+# arming: agent-invoked; run before publishing the repository-pendant source.
+"""
+
+from pathlib import Path
+import unittest
+from rdflib import Graph, Literal, Namespace, RDF, RDFS, URIRef
+
+A = Namespace("http://aegis.gastown.local/ontology/")
+CODE = Namespace(str(A) + "code/tambo/")
+DOC = Namespace(str(A) + "doc/tambo/")
+QUERY = (Path(__file__).resolve().parents[1] / "queries/repository-share-tambo.rq").read_text()
+
+
+class RepositoryQuery(unittest.TestCase):
+ def test_pointer_corpus_and_private_boundaries(self):
+ source = Graph()
+ doc, chunk = DOC["README.md"], DOC["README.md/chunk/0"]
+ expected = {
+ (doc, RDF.type, A.Document),
+ (doc, A.filePath, Literal("README.md")),
+ (chunk, RDF.type, A.Chunk),
+ (chunk, A.inDocument, doc),
+ (chunk, A.chunkOrder, Literal(0)),
+ (chunk, A.mentions, Literal("Store")),
+ (CODE["store.rs"], A.filePath, Literal("src/store.rs")),
+ }
+ for triple in expected:
+ source.add(triple)
+ blocked = {
+ (chunk, A.content, Literal("body must not be exported")),
+ (chunk, A.startLine, Literal(1)),
+ (chunk, A.endLine, Literal(30)),
+ (chunk, A.mentions, URIRef("https://example.org/resolved")),
+ (DOC["private"], RDFS.label, Literal("aegis-private123")),
+ (DOC["private"], RDF.type, A.Document),
+ (URIRef(str(A) + "doc/other/README.md"), A.filePath, Literal("foreign")),
+ (URIRef("urn:quipu:memory:private"), RDFS.label, Literal("memory")),
+ }
+ for triple in blocked:
+ source.add(triple)
+ result = set(source.query(QUERY).graph)
+ self.assertEqual(result, expected)
+
+ def test_ideas_and_sssom_records_survive_without_identity_knots(self):
+ source = Graph()
+ S = Namespace("https://w3id.org/sssom/")
+ OWL = Namespace("http://www.w3.org/2002/07/owl#")
+ record = URIRef("https://example.org/fixture/mapping")
+ pointer = DOC["guide.md#idea"]
+ other = URIRef("https://example.org/other/idea")
+ expected = {
+ (pointer, RDF.type, A.Concept),
+ (pointer, RDFS.comment, Literal("Curated high-level idea")),
+ (pointer, A.reviewAfter, Literal("2026-11-01T00:00:00Z")),
+ (record, RDF.type, OWL.Axiom),
+ (record, S.subject_id, pointer),
+ (record, S.object_id, other),
+ (record, S.predicate_id, URIRef("http://www.w3.org/2004/02/skos/core#closeMatch")),
+ (record, S.mapping_justification, URIRef("https://w3id.org/semapv/vocab/LexicalMatching")),
+ }
+ for triple in expected:
+ source.add(triple)
+ source.add((pointer, OWL.sameAs, other))
+ self.assertEqual(set(source.query(QUERY).graph), expected)
+
+ def test_actual_anonymous_chunk_lane_and_foreign_text_negatives(self):
+ source = Graph()
+ chunk = URIRef(str(A) + "chunk/tambo/README.md#C1")
+ expected = {
+ (chunk, RDF.type, A.Chunk),
+ (chunk, A.filePath, Literal("README.md")),
+ (chunk, RDFS.label, Literal("anonymous pointer")),
+ }
+ blocked = {
+ (chunk, A.content, Literal("private source body")),
+ (chunk, A.startLine, Literal(1)),
+ (chunk, A.endLine, Literal(9)),
+ (URIRef(str(A) + "chunk/foreign/file.py#C1"), RDF.type, A.Chunk),
+ (URIRef(str(A) + "chunk/tambo-other/file.py#C1"), RDF.type, A.Chunk),
+ }
+ for triple in expected | blocked:
+ source.add(triple)
+ self.assertEqual(set(source.query(QUERY).graph), expected)
+
+ def test_existing_repository_identity_survives_without_migration(self):
+ source = Graph()
+ expected = {
+ (A.repo_caboodle, RDF.type, A.GitRepo),
+ (A.repo_caboodle, RDFS.label, Literal("repo_caboodle")),
+ }
+ for triple in expected:
+ source.add(triple)
+ source.add((A.repo_tambo, RDF.type, A.GitRepo))
+ self.assertEqual(set(source.query(QUERY).graph), expected)
+
+ def test_empty_is_empty_with_populated_positive(self):
+ self.assertEqual(len(Graph().query(QUERY).graph), 0)
+ source = Graph()
+ triple = (DOC["README.md"], A.filePath, Literal("README.md"))
+ source.add(triple)
+ self.assertEqual(set(source.query(QUERY).graph), {triple})
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/scripts/verify-repository-share.py b/scripts/verify-repository-share.py
new file mode 100644
index 0000000..fcd01fa
--- /dev/null
+++ b/scripts/verify-repository-share.py
@@ -0,0 +1,104 @@
+#!/usr/bin/env python3
+"""Bounded hosted readback; build policy and credentials stay on trusted host."""
+
+import hashlib
+import importlib.util
+import json
+from pathlib import Path
+import re
+import subprocess
+import sys
+import tarfile
+import tempfile
+import time
+
+# Release source must stay clean when modules load.
+sys.dont_write_bytecode = True
+
+spec = importlib.util.spec_from_file_location("publisher", Path(__file__).with_name("publish_repository_share.py"))
+publisher = importlib.util.module_from_spec(spec)
+spec.loader.exec_module(publisher)
+
+
+def verify(folder, tag, revision):
+ if not re.fullmatch(r"v\d+\.\d+\.\d+(?:[.-][A-Za-z0-9.-]+)?", tag) or not re.fullmatch("[0-9a-f]{40}", revision):
+ raise ValueError("invalid release identity")
+ archive = folder / ("tambo-" + tag + "-repository.pendant.tar.gz")
+ sha = hashlib.sha256(archive.read_bytes()).hexdigest()
+ if archive.with_name(archive.name + ".sha256").read_text() != sha + " " + archive.name + "\n":
+ raise ValueError("archive checksum mismatch")
+ proof = json.loads(archive.with_name(archive.name + ".provenance.json").read_text())
+ expected = {
+ "schema": "quipu.repository-share-producer/v1",
+ "source_repository": "scbrown/tambo",
+ "source_revision": revision,
+ "release_tag": tag,
+ "archive_sha256": sha,
+ "destination": "outward",
+ }
+ if any(proof.get(k) != v for k, v in expected.items()):
+ raise ValueError("repository/source/tag/provenance mismatch")
+ for key in ("quipu_sha256", "quipu_server_sha256", "bobbin_sha256"):
+ if not re.fullmatch("[0-9a-f]{64}", proof.get(key, "")):
+ raise ValueError("missing engine/indexer digest")
+ if not re.fullmatch("[0-9a-f]{40}", proof.get("engine_source_revision", "")):
+ raise ValueError("missing engine source pin")
+ with tempfile.TemporaryDirectory(prefix="verify-pendant-") as temp:
+ stage = Path(temp)
+ with tarfile.open(archive) as tar:
+ members = tar.getmembers()
+ if len(members) != len({m.name for m in members}) or any(
+ not m.isfile() or m.name not in publisher.MEMBERS for m in members
+ ):
+ raise ValueError("unexpected, duplicate, or unsafe archive member")
+ # Every member is a flat allowlisted regular file; no path, link or device extraction.
+ for member in members:
+ (stage / member.name).write_bytes(tar.extractfile(member).read())
+ publisher.check_share(stage)
+ return expected
+
+
+def wait(tag, revision, attempts=80, pause=time.sleep, run=subprocess.run):
+ if not re.fullmatch(r"v\d+\.\d+\.\d+(?:[.-][A-Za-z0-9.-]+)?", tag) or not re.fullmatch("[0-9a-f]{40}", revision):
+ raise ValueError("invalid release identity before lookup")
+ stem = "tambo-" + tag + "-repository.pendant.tar.gz"
+ last_read = "not attempted"
+ for number in range(attempts):
+ with tempfile.TemporaryDirectory(prefix="repository-pendant-readback-") as temp:
+ folder = Path(temp)
+ try:
+ result = run(
+ [
+ "gh",
+ "release",
+ "download",
+ tag,
+ "--repo",
+ "scbrown/tambo",
+ "--dir",
+ temp,
+ "--pattern",
+ stem,
+ "--pattern",
+ stem + ".sha256",
+ "--pattern",
+ stem + ".provenance.json",
+ ],
+ capture_output=True,
+ text=True,
+ timeout=45,
+ )
+ last_read = "exit=" + str(result.returncode)
+ if result.returncode == 0:
+ return verify(folder, tag, revision)
+ except subprocess.TimeoutExpired:
+ last_read = "read timeout"
+ if number + 1 < attempts:
+ pause(30)
+ raise ValueError("repository pendant unverified after bounded trusted-producer wait; " + last_read)
+
+
+if __name__ == "__main__":
+ if len(sys.argv) != 3:
+ raise SystemExit("usage: verify-repository-share.py ")
+ print(json.dumps(wait(sys.argv[1], sys.argv[2])))