commit 7e5e049bf3d2cf6b400848c25d4d4a89b6cd4895
parent 73f16412a4fe3cf8ad02c9502c2690115bf73433
Author: triesap <tyson@radroots.org>
Date: Tue, 8 Sep 2026 19:35:06 +0000
tera: bind ffi provenance to its application producer
- separate the exact foundation lock from the owned native producer contract
- identify staged producer inputs without a self-referential commit marker
- record target feature graphs and Rust and Apple toolchains under extbuild
- reject dirty or mismatched source while preserving installed provenance
Diffstat:
11 files changed, 764 insertions(+), 0 deletions(-)
diff --git a/AGENTS.md b/AGENTS.md
@@ -20,6 +20,11 @@ This file applies to the complete standalone iOS app repository. A closer
generated project/source inputs, and the package locks are machine evidence.
Do not hand-edit generated bindings, XCFramework contents, provenance, SBOM,
project output, or API snapshots.
+- `RadrootsFFI/producer.toml` separately governs the owned Tera producer.
+ Stage its declared source inputs before `make ffi-source-write`; check with
+ `make ffi-source-check`. Source records identify a staged input tree and exact
+ build tuple under extbuild output. They do not establish installed artifacts
+ or remote release qualification; installed provenance remains separate.
- Human specifications, decisions, migration history, runbooks, and
qualification evidence are parent-owned under `docs/oss/ios_app/**`. They
are absent from a standalone clone and must never become a build, test,
diff --git a/Makefile b/Makefile
@@ -2,12 +2,14 @@ SHELL := /bin/bash
.SHELLFLAGS := -eu -o pipefail -c
FFI_ROOT := RadrootsFFI
+FFI_TARGET ?= aarch64-apple-ios
SIMULATOR_NAME ?= iPhone 17 Pro
SIMULATOR_DESTINATION := platform=iOS Simulator,name=$(SIMULATOR_NAME)
.NOTPARALLEL:
.PHONY: all doctor bootstrap persona-verifier-bootstrap ffi-bootstrap artifact-check package-contract-check \
+ ffi-source-write ffi-source-check \
swift-quality maintainability-check \
linux-shared-rust \
package-resolve package-build package-test project xcodegen xcode-resolve \
@@ -28,6 +30,12 @@ persona-verifier-bootstrap: doctor
artifact-check: doctor
cargo extbuild run -- $(FFI_ROOT)/scripts/verify-installed-artifacts.sh
+ffi-source-write: doctor
+ cargo extbuild run -- scripts/ffi-provenance.sh write --target '$(FFI_TARGET)'
+
+ffi-source-check: doctor
+ cargo extbuild run -- scripts/ffi-provenance.sh check --target '$(FFI_TARGET)'
+
package-contract-check: doctor
cargo extbuild run -- scripts/verify-package-contract.sh
diff --git a/README.md b/README.md
@@ -20,6 +20,13 @@ preserves the current five creation families, Today/Add tabs, installed identity
and persisted operation formats. Each moved package replaces its old source
only with verified history, compatibility and generated-artifact evidence.
+`RadrootsFFI/producer.toml` separately governs the owned Tera FFI producer.
+After staging its source inputs, `make ffi-source-write ffi-source-check`
+captures and checks the exact source tree, foundation lock, target, features and
+toolchains under extbuild output. Select a supported target with `FFI_TARGET`.
+This is local source evidence; the installed native artifacts retain their
+existing source lock until the native cutover.
+
## Requirements
- macOS with Xcode and an iOS 18-or-newer simulator
diff --git a/RadrootsFFI/producer.toml b/RadrootsFFI/producer.toml
@@ -0,0 +1,27 @@
+schema = "tera.native-producer.v1"
+repository = "https://github.com/radrootslabs/tera"
+foundation_lock = "radroots.lib.source-lock.v1.toml"
+source_inputs = [
+ "Cargo.toml", "Cargo.lock", "rust-toolchain.toml", "Makefile",
+ "core", "scripts", "RadrootsFFI/Makefile", "RadrootsFFI/scripts",
+ "RadrootsFFI/producer.toml", "radroots.lib.source-lock.v1.toml",
+]
+
+[ffi]
+package = "tera_ffi"
+default_features = true
+features = []
+config = "core/crates/tera_ffi/uniffi.toml"
+
+[generator]
+package = "tera_bindgen"
+default_features = true
+features = []
+
+[build]
+rust_version = "1.97.1"
+profile = "release"
+ios_deployment_target = "18.0"
+source_date_epoch = 1787871027
+host = "aarch64-apple-darwin"
+targets = ["aarch64-apple-ios", "aarch64-apple-ios-sim", "aarch64-apple-darwin"]
diff --git a/rust-toolchain.toml b/rust-toolchain.toml
@@ -0,0 +1,3 @@
+[toolchain]
+channel = "1.97.1"
+profile = "minimal"
diff --git a/scripts/ffi-provenance.sh b/scripts/ffi-provenance.sh
@@ -0,0 +1,6 @@
+#!/bin/sh
+set -eu
+
+repo_root=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd)
+exec uv run --offline --frozen --project "$repo_root/scripts/persona-verifier" \
+ python "$repo_root/scripts/ffi_provenance.py" --repo-root "$repo_root" "$@"
diff --git a/scripts/ffi_provenance.py b/scripts/ffi_provenance.py
@@ -0,0 +1,172 @@
+"""Generate or check staged producer evidence, separate from installed artifacts."""
+
+from __future__ import annotations
+
+import argparse
+import hashlib
+import json
+import os
+import sys
+import tempfile
+from pathlib import Path
+from typing import Any
+
+import ffi_source as source
+import package_contract as contract
+
+
+def capture(root: Path, target: str) -> dict[str, Any]:
+ source.reject_build_overrides()
+ source.reject_cargo_configuration(root)
+ config = source.producer_contract(root)
+ build = config["build"]
+ if target not in build["targets"]:
+ raise source.ProvenanceError("producer target is not governed")
+ snapshot = source.source_snapshot(root, config["source_inputs"])
+ rustc = source.command(root, ["rustc", "-Vv"]).decode().strip()
+ if f"release: {build['rust_version']}\n" not in rustc + "\n":
+ raise source.ProvenanceError(
+ "active Rust compiler differs from producer contract"
+ )
+ if f"host: {build['host']}\n" not in rustc + "\n":
+ raise source.ProvenanceError("active Rust host differs from producer contract")
+ result = {
+ "schema": "tera.producer-source.v1",
+ "repository": config["repository"],
+ "source": snapshot,
+ "foundation": contract._read_toml(root / config["foundation_lock"]),
+ "cargo_lock_sha256": hashlib.sha256(
+ source.read_source(root, "Cargo.lock")
+ ).hexdigest(),
+ "build": {
+ **config["ffi"],
+ "target": target,
+ "profile": build["profile"],
+ "ios_deployment_target": build["ios_deployment_target"],
+ "source_date_epoch": build["source_date_epoch"],
+ "rustc": rustc,
+ "apple_toolchain": apple_toolchain(root),
+ "feature_graph": source.feature_graph(
+ root, config["ffi"]["package"], target
+ ),
+ },
+ "generator": {
+ **config["generator"],
+ "target": build["host"],
+ "profile": "dev",
+ "feature_graph": source.feature_graph(
+ root, config["generator"]["package"], build["host"]
+ ),
+ },
+ "disposition": "local_staged_source_only",
+ }
+ if source.source_snapshot(root, config["source_inputs"]) != snapshot:
+ raise source.ProvenanceError("producer source changed during capture")
+ return result
+
+
+def apple_toolchain(root: Path) -> dict[str, str]:
+ commands = {
+ "xcode": ["xcodebuild", "-version"],
+ "swift": ["xcrun", "swiftc", "--version"],
+ "swiftformat": ["swiftformat", "--version"],
+ "iphoneos_sdk": ["xcrun", "--sdk", "iphoneos", "--show-sdk-build-version"],
+ "iphonesimulator_sdk": [
+ "xcrun",
+ "--sdk",
+ "iphonesimulator",
+ "--show-sdk-build-version",
+ ],
+ }
+ return {
+ name: source.command(root, argv).decode().strip()
+ for name, argv in commands.items()
+ }
+
+
+def encoded(value: dict[str, Any]) -> bytes:
+ return (json.dumps(value, sort_keys=True, indent=2) + "\n").encode()
+
+
+def verify_record(actual: bytes, expected: dict[str, Any]) -> None:
+ if actual != encoded(expected):
+ raise source.ProvenanceError(
+ "producer provenance differs from the exact source/build tuple"
+ )
+
+
+def output_path(root: Path, target: str, requested: str | None) -> Path:
+ external = os.environ.get("EXT_BUILD_PROJECT_DIR")
+ if not external or not os.environ.get("EXT_BUILD_RUN_ACTIVE"):
+ raise source.ProvenanceError("producer provenance requires extbuild")
+ base = Path(external).resolve()
+ path = (
+ Path(requested)
+ if requested
+ else base / "target/tera_ffi/source" / f"{target}.json"
+ )
+ path = path.absolute()
+ if not path.is_relative_to(base) or path.is_relative_to(root):
+ raise source.ProvenanceError("producer evidence must use external build output")
+ if path.resolve() != path or path.is_symlink():
+ raise source.ProvenanceError("producer evidence output contains a symlink")
+ return path
+
+
+def write_atomic(path: Path, data: bytes) -> None:
+ path.parent.mkdir(parents=True, exist_ok=True)
+ temporary: str | None = None
+ try:
+ with tempfile.NamedTemporaryFile(dir=path.parent, delete=False) as handle:
+ temporary = handle.name
+ handle.write(data)
+ handle.flush()
+ os.fsync(handle.fileno())
+ os.replace(temporary, path)
+ temporary = None
+ finally:
+ if temporary:
+ Path(temporary).unlink(missing_ok=True)
+
+
+def main() -> int:
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument("mode", choices=("contract-check", "write", "check"))
+ parser.add_argument("--target")
+ parser.add_argument(
+ "--repo-root", type=Path, default=Path(__file__).resolve().parent.parent
+ )
+ parser.add_argument("--output")
+ args = parser.parse_args()
+ try:
+ root = args.repo_root.resolve()
+ if args.mode == "contract-check":
+ source.producer_contract(root)
+ print(
+ "FFI producer contract verified; installed artifacts remain separately governed"
+ )
+ return 0
+ if not args.target:
+ raise source.ProvenanceError("producer target is required")
+ path = output_path(root, args.target, args.output)
+ record = capture(root, args.target)
+ if args.mode == "write":
+ write_atomic(path, encoded(record))
+ verify_record(contract._read_regular(path), record)
+ except (
+ source.ProvenanceError,
+ contract.PackageContractError,
+ OSError,
+ ValueError,
+ KeyError,
+ ) as error:
+ print(f"FFI producer provenance: {error}", file=sys.stderr)
+ return 1
+ print(
+ f"FFI producer source {args.mode}: {args.target}; tree={record['source']['tree']}; local staged source only"
+ )
+ return 0
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
diff --git a/scripts/ffi_source.py b/scripts/ffi_source.py
@@ -0,0 +1,349 @@
+"""Bounded source capture for the application-owned native producer."""
+
+from __future__ import annotations
+
+import hashlib
+import os
+import re
+import stat
+import subprocess
+from pathlib import Path
+from typing import Any
+
+import package_contract as contract
+
+MAX_BYTES = 2 * 1024 * 1024
+MAX_INPUTS = 2048
+INPUTS = [
+ "Cargo.toml",
+ "Cargo.lock",
+ "rust-toolchain.toml",
+ "Makefile",
+ "core",
+ "scripts",
+ "RadrootsFFI/Makefile",
+ "RadrootsFFI/scripts",
+ "RadrootsFFI/producer.toml",
+ "radroots.lib.source-lock.v1.toml",
+]
+PRODUCER_PATH = "RadrootsFFI/producer.toml"
+
+
+class ProvenanceError(Exception):
+ """A source-free, fail-closed provenance rejection."""
+
+
+def command(root: Path, argv: list[str], data: bytes | None = None) -> bytes:
+ if argv[0] == "git":
+ argv = ["git", "--no-replace-objects", *argv[1:]]
+ try:
+ result = subprocess.run(
+ argv, cwd=root, input=data, capture_output=True, check=False, timeout=120
+ )
+ except (OSError, subprocess.TimeoutExpired) as error:
+ raise ProvenanceError("producer inspection command unavailable") from error
+ if result.returncode or len(result.stdout) > MAX_BYTES:
+ raise ProvenanceError("producer inspection command failed or exceeded bound")
+ return result.stdout
+
+
+def read_source(root: Path, relative: str) -> bytes:
+ path = Path(relative)
+ if path.is_absolute() or str(path) != relative or ".." in path.parts:
+ raise ProvenanceError("producer input path is invalid")
+ current = root
+ for part in path.parts:
+ current = current / part
+ if current.is_symlink():
+ raise ProvenanceError("producer input contains a symlink")
+ return contract._read_regular(current, maximum=MAX_BYTES)
+
+
+def producer_contract(root: Path) -> dict[str, Any]:
+ value = contract._read_toml(root / PRODUCER_PATH)
+ expected = {
+ "schema",
+ "repository",
+ "foundation_lock",
+ "source_inputs",
+ "ffi",
+ "generator",
+ "build",
+ }
+ contract._exact(set(value), expected, "producer contract fields")
+ contract._exact(value["schema"], "tera.native-producer.v1", "producer schema")
+ contract._exact(
+ value["repository"],
+ "https://github.com/radrootslabs/tera",
+ "producer repository",
+ )
+ contract._exact(value["source_inputs"], INPUTS, "producer input inventory")
+ contract._exact(
+ value["foundation_lock"],
+ "radroots.lib.source-lock.v1.toml",
+ "foundation lock path",
+ )
+ contract._exact(
+ value["ffi"],
+ {
+ "package": "tera_ffi",
+ "default_features": True,
+ "features": [],
+ "config": "core/crates/tera_ffi/uniffi.toml",
+ },
+ "FFI producer selection",
+ )
+ contract._exact(
+ value["generator"],
+ {
+ "package": "tera_bindgen",
+ "default_features": True,
+ "features": [],
+ },
+ "generator selection",
+ )
+ validate_build(value["build"])
+ cargo = contract._read_toml(root / "Cargo.toml")
+ lock = contract._read_toml(root / value["foundation_lock"])
+ validate_foundation(cargo, lock)
+ toolchain = contract._read_toml(root / "rust-toolchain.toml")
+ contract._exact(
+ toolchain,
+ {
+ "toolchain": {
+ "channel": value["build"]["rust_version"],
+ "profile": "minimal",
+ }
+ },
+ "producer toolchain",
+ )
+ return value
+
+
+def validate_build(value: Any) -> None:
+ value = contract._mapping(value, "producer build")
+ contract._exact(
+ set(value),
+ {
+ "rust_version",
+ "profile",
+ "ios_deployment_target",
+ "source_date_epoch",
+ "host",
+ "targets",
+ },
+ "producer build fields",
+ )
+ contract._exact(value["rust_version"], "1.97.1", "producer Rust version")
+ contract._exact(value["profile"], "release", "producer build profile")
+ contract._exact(
+ value["ios_deployment_target"], "18.0", "producer deployment target"
+ )
+ contract._exact(value["host"], "aarch64-apple-darwin", "producer host")
+ contract._exact(
+ value["targets"],
+ [
+ "aarch64-apple-ios",
+ "aarch64-apple-ios-sim",
+ "aarch64-apple-darwin",
+ ],
+ "producer targets",
+ )
+ if type(value["source_date_epoch"]) is not int or value["source_date_epoch"] <= 0:
+ raise ProvenanceError("producer source epoch is invalid")
+
+
+def validate_foundation(cargo: dict[str, Any], lock: dict[str, Any]) -> None:
+ contract._exact(
+ lock.get("schema"), "radroots.lib.source-lock.v1", "foundation schema"
+ )
+ contract._exact(lock.get("repository"), contract.LIB_REMOTE, "foundation remote")
+ if not contract.GIT_REVISION.fullmatch(str(lock.get("revision", ""))):
+ raise ProvenanceError("foundation revision is invalid")
+ for field in (
+ "workspace_catalog_sha256",
+ "source_archive_sha256",
+ "lockfile_sha256",
+ ):
+ if not contract.SHA256.fullmatch(str(lock.get(field, ""))):
+ raise ProvenanceError("foundation digest is invalid")
+ dependencies = cargo["workspace"]["dependencies"]
+ selected = {
+ name: value
+ for name, value in dependencies.items()
+ if name.startswith("radroots_")
+ }
+ if not selected:
+ raise ProvenanceError("foundation dependency inventory is empty")
+ for value in selected.values():
+ value = contract._mapping(value, "foundation Cargo dependency")
+ contract._exact(value.get("git"), lock["repository"], "foundation Cargo remote")
+ contract._exact(value.get("rev"), lock["revision"], "foundation Cargo revision")
+ contract._exact(
+ value.get("version"), "=" + lock["version"], "foundation Cargo version"
+ )
+
+
+def source_snapshot(root: Path, inputs: list[str]) -> dict[str, Any]:
+ actual_root = (
+ command(root, ["git", "rev-parse", "--show-toplevel"]).decode().strip()
+ )
+ if actual_root != str(root):
+ raise ProvenanceError("producer source must use its own repository root")
+ if command(
+ root, ["git", "ls-files", "--others", "--exclude-standard", "-z", "--", *inputs]
+ ):
+ raise ProvenanceError("producer inputs contain untracked source")
+ reject_ignored_source(root, inputs)
+ raw = command(root, ["git", "ls-files", "--stage", "-z", "--", *inputs])
+ rows = raw.rstrip(b"\0").split(b"\0") if raw else []
+ if not rows or len(rows) > MAX_INPUTS:
+ raise ProvenanceError("producer input inventory exceeds bounds")
+ files: dict[str, Any] = {}
+ tree: dict[str, Any] = {}
+ for row in rows:
+ relative, entry = staged_file(root, row)
+ files[relative] = entry
+ insert_tree(tree, relative, entry["mode"], entry["git_blob"])
+ require_input_inventory(inputs, files)
+ return {"policy": "staged_inputs", "tree": tree_identity(tree), "files": files}
+
+
+def require_input_inventory(inputs: list[str], files: dict[str, Any]) -> None:
+ for relative in inputs:
+ if not any(
+ name == relative or name.startswith(relative + "/") for name in files
+ ):
+ raise ProvenanceError("required producer input is missing from index")
+
+
+def reject_ignored_source(root: Path, inputs: list[str]) -> None:
+ ignored = command(
+ root,
+ [
+ "git",
+ "ls-files",
+ "--others",
+ "--ignored",
+ "--exclude-standard",
+ "-z",
+ "--",
+ *inputs,
+ ":(exclude)scripts/persona-verifier/.venv/**",
+ ":(exclude,glob)**/__pycache__/**",
+ ],
+ )
+ if any(is_input(path, inputs) for path in ignored.decode().split("\0") if path):
+ raise ProvenanceError(
+ "producer inputs contain ignored source outside tool caches"
+ )
+
+
+def is_input(relative: str, inputs: list[str]) -> bool:
+ return any(relative == name or relative.startswith(name + "/") for name in inputs)
+
+
+def staged_file(root: Path, row: bytes) -> tuple[str, dict[str, Any]]:
+ header, encoded_path = row.split(b"\t", 1)
+ mode, oid, stage = header.decode("ascii").split()
+ relative = encoded_path.decode("utf-8")
+ if stage != "0" or mode not in ("100644", "100755"):
+ raise ProvenanceError("producer input has unresolved or unsupported mode")
+ data = read_source(root, relative)
+ blob = command(root, ["git", "cat-file", "blob", oid])
+ actual_mode = (
+ "100755" if (root / relative).stat().st_mode & stat.S_IXUSR else "100644"
+ )
+ if data != blob or mode != actual_mode:
+ raise ProvenanceError("producer worktree differs from staged source")
+ return relative, {
+ "mode": mode,
+ "git_blob": oid,
+ "bytes": len(data),
+ "sha256": hashlib.sha256(data).hexdigest(),
+ }
+
+
+def reject_cargo_configuration(root: Path) -> None:
+ locations = [path / ".cargo" for path in (root, *root.parents)]
+ locations.append(Path(os.environ.get("CARGO_HOME", str(Path.home() / ".cargo"))))
+ for location in locations:
+ for name in ("config", "config.toml"):
+ path = location / name
+ if path.exists():
+ value = contract._read_toml(path)
+ if {"build", "target", "env", "profile", "patch", "unstable"} & set(
+ value
+ ):
+ raise ProvenanceError(
+ "ungoverned Cargo configuration affects producer build"
+ )
+
+
+def insert_tree(tree: dict[str, Any], relative: str, mode: str, oid: str) -> None:
+ parts = relative.split("/")
+ for part in parts[:-1]:
+ tree = tree.setdefault(part, {})
+ tree[parts[-1]] = (mode, oid)
+
+
+def tree_identity(tree: dict[str, Any]) -> str:
+ rows = []
+ for name, value in tree.items():
+ if isinstance(value, dict):
+ mode, oid, key = "40000", tree_identity(value), name.encode() + b"/"
+ else:
+ mode, oid = value
+ key = name.encode()
+ rows.append((key, f"{mode} {name}".encode() + b"\0" + bytes.fromhex(oid)))
+ body = b"".join(row for _, row in sorted(rows))
+ return hashlib.sha1(
+ b"tree " + str(len(body)).encode() + b"\0" + body, usedforsecurity=False
+ ).hexdigest()
+
+
+def reject_build_overrides() -> None:
+ forbidden = (
+ "RUSTFLAGS",
+ "RUSTC",
+ "RUSTC_WRAPPER",
+ "RUSTC_WORKSPACE_WRAPPER",
+ "CARGO_ENCODED_RUSTFLAGS",
+ "GIT_INDEX_FILE",
+ "GIT_DIR",
+ "GIT_WORK_TREE",
+ "GIT_OBJECT_DIRECTORY",
+ "GIT_ALTERNATE_OBJECT_DIRECTORIES",
+ "GIT_CONFIG_COUNT",
+ "GIT_CONFIG_PARAMETERS",
+ )
+ if any(os.environ.get(name) for name in forbidden):
+ raise ProvenanceError("ungoverned Rust build override is active")
+ if any(
+ re.fullmatch(r"CARGO_(BUILD_.*|TARGET_.*_(RUSTFLAGS|LINKER|RUNNER))", name)
+ for name in os.environ
+ ):
+ raise ProvenanceError("ungoverned Cargo build override is active")
+
+
+def feature_graph(root: Path, package: str, target: str) -> list[str]:
+ raw = command(
+ root,
+ [
+ "cargo",
+ "tree",
+ "--locked",
+ "--offline",
+ "-p",
+ package,
+ "--target",
+ target,
+ "--edges",
+ "normal,build",
+ "--prefix",
+ "none",
+ "--format",
+ "{p}|{f}",
+ ],
+ ).decode()
+ return sorted(set(raw.replace(str(root), "<producer-root>").splitlines()))
diff --git a/scripts/swift-quality.sh b/scripts/swift-quality.sh
@@ -20,6 +20,9 @@ readonly -a MAINTAINABILITY_RULES=(
type_body_length
)
readonly -a PYTHON_QUALITY_PATHS=(
+ scripts/ffi_source.py
+ scripts/ffi_provenance.py
+ scripts/test_ffi_provenance.py
scripts/maintainability_ratchet.py
scripts/package_contract.py
scripts/test_maintainability_ratchet.py
diff --git a/scripts/test_ffi_provenance.py b/scripts/test_ffi_provenance.py
@@ -0,0 +1,181 @@
+from __future__ import annotations
+
+import copy
+import os
+import subprocess
+import sys
+import tempfile
+import unittest
+from pathlib import Path
+from unittest.mock import patch
+
+SCRIPTS = Path(__file__).resolve().parent
+if str(SCRIPTS) not in sys.path:
+ sys.path.insert(0, str(SCRIPTS))
+
+import ffi_provenance as provenance # noqa: E402
+import ffi_source as source # noqa: E402
+import package_contract as contract # noqa: E402
+
+
+class ProducerSourceTests(unittest.TestCase):
+ def setUp(self) -> None:
+ self.temporary = tempfile.TemporaryDirectory()
+ self.addCleanup(self.temporary.cleanup)
+ self.root = Path(self.temporary.name).resolve()
+ self.git("init", "--quiet")
+ (self.root / "core").mkdir()
+ (self.root / "core/lib.rs").write_text("pub fn value() -> u8 { 1 }\n")
+ (self.root / "Cargo.lock").write_text("# synthetic source-capture fixture\n")
+ self.git("add", "core", "Cargo.lock")
+ self.inputs = ["core", "Cargo.lock"]
+
+ def git(self, *args: str) -> str:
+ return subprocess.check_output(
+ ["git", *args], cwd=self.root, text=True, stderr=subprocess.DEVNULL
+ ).strip()
+
+ def snapshot(self) -> dict:
+ return source.source_snapshot(self.root, self.inputs)
+
+ def test_tree_is_real_and_deterministic_without_an_introducing_commit(self) -> None:
+ first = self.snapshot()
+ self.assertEqual(first, self.snapshot())
+ self.assertEqual(self.git("write-tree"), first["tree"])
+ self.assertEqual(self.git("cat-file", "-t", first["tree"]), "tree")
+ # Generated evidence never changes its own source tree identity.
+ (self.root / "provenance.json").write_bytes(provenance.encoded(first))
+ self.git("add", "provenance.json")
+ self.assertEqual(first, self.snapshot())
+
+ def test_worktree_change_requires_an_explicit_staged_transaction(self) -> None:
+ first = self.snapshot()
+ (self.root / "core/lib.rs").write_text("pub fn value() -> u8 { 2 }\n")
+ with self.assertRaisesRegex(source.ProvenanceError, "differs from staged"):
+ self.snapshot()
+ self.git("add", "core/lib.rs")
+ self.assertNotEqual(first["tree"], self.snapshot()["tree"])
+
+ def test_untracked_and_missing_source_fail_closed(self) -> None:
+ (self.root / "core/extra.rs").write_text("// untracked input\n")
+ with self.assertRaisesRegex(source.ProvenanceError, "untracked"):
+ self.snapshot()
+ (self.root / "core/extra.rs").unlink()
+ (self.root / "Cargo.lock").unlink()
+ self.git("add", "-u", "Cargo.lock")
+ with self.assertRaisesRegex(source.ProvenanceError, "missing from index"):
+ self.snapshot()
+
+ def test_symlink_and_file_mode_changes_are_rejected(self) -> None:
+ path = self.root / "core/lib.rs"
+ path.chmod(0o755)
+ with self.assertRaisesRegex(source.ProvenanceError, "differs from staged"):
+ self.snapshot()
+ path.unlink()
+ path.symlink_to("../Cargo.lock")
+ with self.assertRaisesRegex(source.ProvenanceError, "symlink"):
+ self.snapshot()
+ self.git("add", "core/lib.rs")
+ with self.assertRaisesRegex(source.ProvenanceError, "unsupported mode"):
+ self.snapshot()
+
+ def test_source_byte_bound_prevents_unbounded_capture(self) -> None:
+ (self.root / "core/lib.rs").write_bytes(b"x" * (source.MAX_BYTES + 1))
+ with self.assertRaisesRegex(contract.PackageContractError, "byte limit"):
+ self.snapshot()
+
+ def test_gitignore_cannot_hide_a_producer_input(self) -> None:
+ (self.root / ".gitignore").write_text("core/hidden.rs\n")
+ (self.root / "core/hidden.rs").write_text("// ignored source input\n")
+ with self.assertRaisesRegex(source.ProvenanceError, "ignored source"):
+ self.snapshot()
+
+ def test_provenance_rejects_every_source_or_build_tuple_mutation(self) -> None:
+ expected = {
+ "source": self.snapshot(),
+ "cargo_lock_sha256": "a" * 64,
+ "foundation": {"revision": "b" * 40},
+ "build": {
+ "target": "aarch64-apple-ios",
+ "rustc": "rustc 1.97.1",
+ "features": [],
+ "feature_graph": ["tera_core|mobile-social"],
+ },
+ }
+ provenance.verify_record(provenance.encoded(expected), expected)
+ for section, key, value in (
+ ("source", "tree", "c" * 40),
+ ("foundation", "revision", "d" * 40),
+ ("build", "target", "aarch64-apple-ios-sim"),
+ ("build", "rustc", "rustc 1.96.0"),
+ ("build", "features", ["extra"]),
+ ("build", "feature_graph", []),
+ ):
+ with self.subTest(section=section, key=key):
+ changed = copy.deepcopy(expected)
+ changed[section][key] = value
+ with self.assertRaisesRegex(
+ source.ProvenanceError, "exact source/build tuple"
+ ):
+ provenance.verify_record(provenance.encoded(changed), expected)
+ changed = {**expected, "cargo_lock_sha256": "f" * 64}
+ with self.assertRaises(source.ProvenanceError):
+ provenance.verify_record(provenance.encoded(changed), expected)
+
+ def test_output_rejects_repository_paths_escapes_and_symlinks(self) -> None:
+ external = self.root / "external"
+ external.mkdir()
+ repository = self.root / "repository"
+ repository.mkdir()
+ with patch.dict(
+ os.environ,
+ {
+ "EXT_BUILD_RUN_ACTIVE": "1",
+ "EXT_BUILD_PROJECT_DIR": str(external),
+ },
+ ):
+ for output in (repository / "evidence.json", external / "../escape.json"):
+ with self.assertRaises(source.ProvenanceError):
+ provenance.output_path(repository, "aarch64-apple-ios", str(output))
+ (external / "link").symlink_to(repository, target_is_directory=True)
+ with self.assertRaises(source.ProvenanceError):
+ provenance.output_path(
+ repository, "aarch64-apple-ios", str(external / "link/value.json")
+ )
+
+ def test_ungoverned_build_flags_are_rejected(self) -> None:
+ for key in (
+ "RUSTFLAGS",
+ "RUSTC_WRAPPER",
+ "CARGO_BUILD_RUSTFLAGS",
+ "CARGO_TARGET_AARCH64_APPLE_IOS_LINKER",
+ ):
+ with self.subTest(key=key), patch.dict(os.environ, {key: "synthetic"}):
+ with self.assertRaisesRegex(source.ProvenanceError, "ungoverned"):
+ source.reject_build_overrides()
+
+ def test_local_cargo_build_configuration_cannot_escape_source_identity(
+ self,
+ ) -> None:
+ (self.root / ".cargo").mkdir()
+ config = self.root / ".cargo/config.toml"
+ config.write_text('[build]\nrustflags = ["--cfg", "unrecorded"]\n')
+ with self.assertRaisesRegex(source.ProvenanceError, "Cargo configuration"):
+ source.reject_cargo_configuration(self.root)
+
+ def test_current_contract_separates_foundation_and_application(self) -> None:
+ config = source.producer_contract(SCRIPTS.parent)
+ self.assertEqual(config["ffi"]["package"], "tera_ffi")
+ self.assertEqual(config["repository"], "https://github.com/radrootslabs/tera")
+ cargo = contract._read_toml(SCRIPTS.parent / "Cargo.toml")
+ lock = contract._read_toml(SCRIPTS.parent / config["foundation_lock"])
+ changed = copy.deepcopy(cargo)
+ changed["workspace"]["dependencies"]["radroots_sdk"]["rev"] = "f" * 40
+ with self.assertRaisesRegex(
+ contract.PackageContractError, "foundation Cargo revision"
+ ):
+ source.validate_foundation(changed, lock)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/scripts/verify-package-contract.sh b/scripts/verify-package-contract.sh
@@ -7,11 +7,14 @@ python_project="$repo_root/scripts/persona-verifier"
uv run --project "$python_project" --offline --frozen \
python "$repo_root/scripts/package_contract.py" --repo-root "$repo_root"
+sh "$repo_root/scripts/ffi-provenance.sh" contract-check
+
(
cd "$repo_root"
uv run --project "$python_project" --offline --frozen \
python -m unittest \
scripts/test_package_contract.py \
+ scripts/test_ffi_provenance.py \
scripts/test_local_social_fixture.py
)