Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ jobs:
command: |
nix develop --command just setup
nix develop --command just check
nix develop --command just substrate-boundary-check
nix develop --command just build
push-cache: 'false'

Expand Down
6 changes: 6 additions & 0 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,12 @@ lane-reap pr:
conformance:
cd {{ root }} && bash scripts/check-conformance.sh

# Substrate-boundary conformance: blahaj code-reach must be a named,
# provenance-carrying interface (config/substrate-boundary-allowlist.json),
# not an ad-hoc bleed (TIN-2423 / ledger item 30).
substrate-boundary-check:
cd {{ root }} && python3 scripts/validate-substrate-boundary.py

# ─────────────────────────────────────────────
# Flywheel (cache-first; executor opt-in; see docs/CI-SCHEMA.md §5)
# Env knob: FLYWHEEL=local|cache|executor|auto (default auto)
Expand Down
22 changes: 22 additions & 0 deletions config/substrate-boundary-allowlist.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"$comment": "TIN-2423 / ledger item 30: named-interface allowlist for the substrate-boundary validator. Every entry carries provenance (TIN-2398 guardrail #2). A blahaj-reach hit under an allowlisted path_prefix is a NAMED INTERFACE, not a bleed.",
"schema_version": 1,
"allowed": [
{
"path_prefix": "scripts/lane-dispatch.py",
"interface": "ADR-007 generic receiver dispatch: constructs + POSTs the `<spoke>-lane-env` repository_dispatch payload (docs/schemas/blahaj-dispatch.schema.json) to tinyland-inc/blahaj. This is the spoke-side half of the generic PR-env provisioning contract, invoked via `just lane-dispatch` / `just lane-reap` and by .github/workflows/lane-env.yml (ci-templates spoke-lane-env.yml@v2).",
"provenance": {
"decision": "ledger item 30 (generic deploy-machinery / dynamic-spoke deployment edge) + blahaj ADR-007 generic receiver contract",
"date": "2026-07-03"
}
},
{
"path_prefix": "tinyland.repo.json",
"interface": "Declared `gitops_receiver` manifest field naming tinyland-inc/blahaj as the repository_dispatch receiver, per the tinyland-repo-manifest schema and docs/CI-SCHEMA.md §4/§9 (the wire, not blahaj's implementation).",
"provenance": {
"decision": "ledger item 30 (generic deploy-machinery / dynamic-spoke deployment edge) + blahaj ADR-007 generic receiver contract",
"date": "2026-07-03"
}
}
]
}
13 changes: 13 additions & 0 deletions scripts/check-conformance.sh
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,19 @@ else
man "AGENTS.md cites the scaffold tag the repo conforms to (pre-D3 PR7 OK)"
fi

# 13. Substrate-boundary conformance (TIN-2423 / ledger item 30): code-surface
# reach into the blahaj substrate must go through a named, provenance-carrying
# interface (config/substrate-boundary-allowlist.json), not an ad-hoc bleed.
if [[ -f scripts/validate-substrate-boundary.py ]]; then
if python3 scripts/validate-substrate-boundary.py >/dev/null 2>&1; then
ok "substrate-boundary: blahaj reach is allowlisted or absent (TIN-2423)"
else
no "substrate-boundary: un-allowlisted blahaj reach — run just substrate-boundary-check for details"
fi
else
no "scripts/validate-substrate-boundary.py is missing"
fi

echo
echo "summary: ${pass} pass, ${fail} fail, ${manual} manual"
if (( fail > 0 )); then exit 1; fi
Expand Down
143 changes: 143 additions & 0 deletions scripts/validate-substrate-boundary.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
#!/usr/bin/env python3
"""Substrate-boundary conformance (TIN-2423 / ledger item 30, consumer-side).

Asserts ADR-008 "logical replaceability" as a checked invariant instead of
prose: this repo's CODE surfaces may reach the blahaj substrate ONLY through
named interfaces recorded (with provenance) in
config/substrate-boundary-allowlist.json. Docs (*.md) are exempt — the
invariant is about code reach, not prose mentions.

Flagged reach classes (TIN-2423 item 1):
repo-ref a tinyland-inc/blahaj reference (module source, workflow
dispatch target, checkout) in a code surface
path-reach a sibling/home-relative filesystem reach into a blahaj
checkout (../blahaj, ~/git/blahaj, /git/blahaj)
state-key an OpenTofu backend key under the blahaj/ state prefix

Exit 0 = conformant (allowlisted hits are reported, not failed).
Exit 1 = un-allowlisted reach (the boundary bleed class of TIN-2398/2406).
"""
from __future__ import annotations

import json
import re
import subprocess
import sys
from pathlib import Path

REPO = Path(__file__).resolve().parent.parent
SELF = Path(__file__).resolve().relative_to(REPO)
ALLOWLIST = REPO / "config" / "substrate-boundary-allowlist.json"

# Code surfaces (git-tracked). Markdown is exempt everywhere.
CODE_GLOBS = [
"infra/tofu/**", "infra/kustomize/**", ".github/workflows/**", "scripts/**",
"Justfile", "flake.nix", "MODULE.bazel", "**/*.bzl", "**/BUILD.bazel",
"tinyland.repo.json",
]

PATTERNS = {
"repo-ref": re.compile(r"tinyland-inc/blahaj"),
"path-reach": re.compile(r"(\.\./|~/git/|/git/)blahaj\b"),
"state-key": re.compile(r"key\s*=\s*\"blahaj/"),
}


def tracked_code_files() -> list[Path]:
out = subprocess.run(
["git", "ls-files", "--"] + CODE_GLOBS,
cwd=REPO, capture_output=True, text=True, check=True,
).stdout.splitlines()
return [
Path(p)
for p in sorted(set(out))
if not p.endswith(".md") and Path(p) != SELF
]


def load_allowlist() -> list[dict]:
data = json.loads(ALLOWLIST.read_text())
entries = data["allowed"]
for e in entries:
for field in ("path_prefix", "interface", "provenance"):
if field not in e:
raise SystemExit(
f"allowlist entry missing {field!r} (provenance-carrying "
f"allowlists are the contract, TIN-2398 guardrail #2): {e}"
)
for field in ("decision", "date"):
if field not in e["provenance"]:
raise SystemExit(f"allowlist provenance missing {field!r}: {e}")
return entries


def scan(files: list[Path], allowed: list[dict]):
violations, allowed_hits = [], []
for rel in files:
path = REPO / rel
try:
text = path.read_text(errors="replace")
except (OSError, IsADirectoryError):
continue
for lineno, line in enumerate(text.splitlines(), 1):
for kind, rx in PATTERNS.items():
if not rx.search(line):
continue
entry = next(
(e for e in allowed if str(rel).startswith(e["path_prefix"])),
None,
)
record = (str(rel), lineno, kind, line.strip()[:120])
(allowed_hits if entry else violations).append(record)
return violations, allowed_hits


def self_test() -> None:
cases = {
"repo-ref": 'uses: tinyland-inc/blahaj/.github/workflows/x.yml@main',
"repo-ref-dispatch": 'gh api repos/tinyland-inc/blahaj/dispatches -f event_type=x',
"path-reach": 'source = "../blahaj/tofu/modules/thing"',
"path-reach-home": 'cd ~/git/blahaj && just apply',
"state-key": 'key = "blahaj/mail/terraform.tfstate"',
}
for name, sample in cases.items():
if not any(rx.search(sample) for rx in PATTERNS.values()):
raise SystemExit(f"self-test FAILED: {name!r} not detected")
clean = 'key = "tinyland-infra/attic/terraform.tfstate" # fine'
if any(rx.search(clean) for rx in PATTERNS.values()):
raise SystemExit("self-test FAILED: false positive on clean line")
# allowlist suppression
allowed = [{"path_prefix": "tofu/modules/spoke-blahaj-app-install/",
"interface": "t", "provenance": {"decision": "t", "date": "t"}}]
v, a = [], []
entry = next((e for e in allowed
if "tofu/modules/spoke-blahaj-app-install/main.tf".startswith(e["path_prefix"])), None)
(a if entry else v).append("x")
if v or not a:
raise SystemExit("self-test FAILED: allowlist suppression broken")
print("substrate-boundary self-test passed")


def main() -> int:
if "--self-test" in sys.argv:
self_test()
return 0
allowed = load_allowlist()
violations, allowed_hits = scan(tracked_code_files(), allowed)
for rel, lineno, kind, frag in allowed_hits:
print(f"allowed [{kind}] {rel}:{lineno}: {frag}")
if violations:
print(f"\nsubstrate-boundary FAILED: {len(violations)} un-allowlisted "
f"blahaj reach(es) — consume the substrate via a named interface "
f"or add a provenance-carrying allowlist entry (TIN-2423):",
file=sys.stderr)
for rel, lineno, kind, frag in violations:
print(f" [{kind}] {rel}:{lineno}: {frag}", file=sys.stderr)
return 1
print(f"substrate-boundary validation passed "
f"({len(allowed_hits)} allowlisted hit(s), 0 violations)")
return 0


if __name__ == "__main__":
raise SystemExit(main())
Loading