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
108 changes: 108 additions & 0 deletions docs/decisions/2026-10-09-architecture-inventory-boundary.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# Architecture inventory input custody

The GPT R3 read at ddb7a2a9419dac5047bb73df85b30c95bc0c98b8 found two
blocking source-policy regressions. Verdict SHA-256
9c4ed3c41e4e678981b75a71a398df373d0c6798a3d6b5ddb5403f85a1f94e59
was read in full. The fixes narrow frozen-input eligibility and carry the
existing independent reader into the remaining complete-build inventory leg.

The original frozen-artifact tripwire reproduced its one failure in a genuine
worktree before the change. Two unused input permissions were removed.
`scripts/local_pages_policy_grants.py` now derives a proposal from full-SHA
Git tree and registration metadata and the pinned test's eligibility
declaration, excluding frozen and shared protected paths. It prints proposals
for review; it never writes runtime permissions from discovered input files,
opens the referenced artifact bodies or pins executable grants as descriptions.
The receipt grant set was independently reviewed at
`c945ea1f011e4c7e69a0b5f19052717b2af9d46e`. Its exact union narrows 4650 to
4648; no other receipt is added or removed. Native proposal regeneration at
the landing rebase reports 4725 eligible paths for current main; that proposal
does not expand the independently reviewed pinned receipt grant set.

The complete builder's independent reader now reaches inventory content,
metadata and cache signatures. Hashes and parsed metadata use one captured
approved payload. Exact canonical approval precedes content/digest access,
and shared checks reject protected lexical and canonical aliases. The later
CC read at 64809272 identified an availability regression: ordinary unknown
names had become fatal to all pages. Those discoveries now produce aggregate
counts by source root and kind before public projection, without target
stat/resolve/open/hash or invented presence. Individual unapproved names and
paths never enter served inventory, custody or detail documents, and cannot
add themselves to permissions. Protected inputs still refuse the
Architecture observation; the optional composer adapter retains its last
page or shows an UNREPORTED placeholder while other pages refresh. Cache
signatures outside the serving root include unknown names, so installing,
removing or renaming an unapproved entry changes the observation without
accessing its content or publishing its name. Complete-builder synthetic
regressions cover an ungranted unit, skill and Codex agent, retain zero forbidden
opens and metadata calls, and scan every served output. Publication prunes old
generated detail HTML from the fixed architecture/layers namespace before its
receipt replacement; cache reuse applies the same retained-output check.

There are 132 independent exact inventory content paths: 90 pinned repo selectors,
the fixed sanitized automation projection and 41 reviewed installed skill
targets. User content is restricted to exact `SKILL.md` assets. A separate
94-path metadata-only role covers user agent/service registrations; it cannot
authorize file body access. Existing native descriptor-relative no-follow
directory traversal and stat observe those metadata targets without reading
client configuration or service Environment values. Newly installed assets
require a new independent policy review before content capture, never a
runtime-derived wildcard grant. File presence or hash equality still does not
establish wiring, execution or acceptance.

A checked-in collection of independently reviewed bindings reproduces
the 41 skill, 94 metadata and one design grant, including 33 exact aliases.
The binding source is re-pinned to reachable rebased commit
`994ab9b279c8915cbdb637e1786aaa41d1306e57`; its reviewed binding bytes remain
equal to the CC-read snapshot at `c225909e50128ef5730765bb842006a27404743b`.
Public data contains no user directory listings or unapproved names. Additional
runtime names remain unapproved rather than expanding permissions. A CI test
compares repository grants with the current committed `git ls-files` selector.
The authorized landing rebase onto main
`3c01bdddc66896f8e36f9f21d452710808a9557e` preserves the final Fleet implementation
and sanitization, retains the two reviewed workflow grants and adds the
committed host-name scanner selected by native grant regeneration. Repository
grant regeneration uses the rebased Git tree, without runtime discovery.
Adoption role attribution separately reuses the same native no-follow reader
for fixed co-op metadata paths and reviewed parking/capacity filenames;
refusals leave measured call counts unattributed and report safe source errors.
No raw co-op payload or user configuration body is read during derivation.
Directory-listing errors report UNREPORTED coverage; unapproved rows never
inherit catalog, MCP or receipt evidence by matching an approved component's
name. Optional archive errors, subprocess time limits and deeply nested role
registries cannot halt the other page refreshes. These failures retain only
exception categories, preserving the published Adoption snapshot's counts.

The supported source primitives are reused from the native reader at
ddb7a2a9419dac5047bb73df85b30c95bc0c98b8 and installed CPython 3.13.16,
full source pin cbc944f4bc59639a444dd971c737788ba2283a91:
[os descriptor interfaces](https://github.com/python/cpython/blob/cbc944f4bc59639a444dd971c737788ba2283a91/Doc/library/os.rst),
[pathlib](https://github.com/python/cpython/blob/cbc944f4bc59639a444dd971c737788ba2283a91/Lib/pathlib/_local.py),
and [buffered I/O](https://github.com/python/cpython/blob/cbc944f4bc59639a444dd971c737788ba2283a91/Modules/_io/bufferedio.c).
The same installed version defines the optional adapter's
[archive exceptions](https://github.com/python/cpython/blob/cbc944f4bc59639a444dd971c737788ba2283a91/Lib/tarfile.py),
[subprocess time limits](https://github.com/python/cpython/blob/cbc944f4bc59639a444dd971c737788ba2283a91/Lib/subprocess.py)
and [JSON parser](https://github.com/python/cpython/blob/cbc944f4bc59639a444dd971c737788ba2283a91/Lib/json/decoder.py).
Native installed Git supplies immutable `ls-tree`/`show` metadata, rather than
another repository walker or archive parser. The reader captures 64 KiB chunks
within its original bound; a small approved payload cannot trigger a caller's
large-bound buffer allocation. Scope-limit errors remain failures, with no
read-limit widening.

Alternatives rejected: expanding subtree/wildcard permissions from discovery
would preserve the reported bypass; adding descriptive pins for frozen input
grants would contradict the tripwire's eligibility rule; hashing raw user
service/agent configuration would read content outside the approved metadata
scope. Replacing the reader would duplicate maintained native no-follow and
descriptor semantics. The selected seam instead passes the existing reader
and adds reviewed exact roles. A supported upstream API with the same custody
and smaller maintained surface, or a reproduced counterexample under these
fixtures, would overturn this integration choice.

Measured checks are local integration with synthetic repository/state/user
fixtures, scope/tripwire validation and bounded native filesystem calls.
Final command/exit/head/UTC receipts, CC Claude/co-op GPT review, exact-head CI
and the one authorized landing-turn rebase remain separate gates. Previous
production byte/inventory/unmapped counts are not promoted to this changed
source policy. No provider, market-data pull, live-service promotion or native
Darwin acceptance is inferred.
148 changes: 148 additions & 0 deletions scripts/local_pages_policy_grants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
"""Derive exact Architecture grants from pinned Git metadata, never input bodies.

Reference: native source_policy.ArchitectureReads at
ddb7a2a9419dac5047bb73df85b30c95bc0c98b8; installed Git ls-tree/show and
CPython3.13.16 cbc944f4bc59639a444dd971c737788ba2283a91 ast/json/pathlib.
This prints a proposal for review. It does not modify policy or discover a
runtime file into permission. User grants come only from reviewed bindings.
"""

from __future__ import annotations

import argparse
import ast
import importlib.util
import json
from pathlib import Path, PurePosixPath
import re
import subprocess


ROOT = Path(__file__).resolve().parents[1]
SCRIPT_SUFFIXES = {".py", ".sh", ".js", ".mjs", ".cjs", ".ts"}
FROZEN_RULE = "tests/test_frozen_macos_variant_no_use.py"


def receipt_paths(metadata: dict, frozen_prefixes: tuple[str, ...], protected) -> list[str]:
"""Use registered path metadata, excluding protected and frozen eligibility."""
selected = set()
for collection in ("files", "receipts"):
rows = metadata.get(collection, [])
if not isinstance(rows, list):
raise ValueError("registered file metadata must be a list")
for row in rows:
relative = row.get("path") if isinstance(row, dict) else None
if not isinstance(relative, str) or not relative.endswith(".json"):
continue
path = PurePosixPath(relative)
if path.is_absolute() or ".." in path.parts or "\\" in relative or protected(relative):
continue
if any(path == PurePosixPath(prefix) or PurePosixPath(prefix) in path.parents for prefix in frozen_prefixes):
continue
selected.add(relative)
return sorted(selected)


def inventory_paths(tracked: list[str], catalogs: dict) -> list[str]:
"""Only the reviewed selector's pinned paths become a proposed exact grant."""
selected = {"adoption/skills/manifest.json", "adoption/manifest.json", "manifests/stack.json",
"catalogs/landscape/manifest.json", "tools/local-pages/architecture_mapping.json"}
for value in catalogs.values():
if not isinstance(value, str) or not value.startswith("catalogs/landscape/") or ".." in PurePosixPath(value).parts or not value.endswith(".json"):
raise ValueError("canonical catalog metadata path is not confined")
selected.add(value)
for relative in tracked:
path = PurePosixPath(relative)
if path.parent == PurePosixPath("scripts") and path.suffix in SCRIPT_SUFFIXES:
selected.add(relative)
elif path.parent == PurePosixPath(".github/workflows") and path.suffix in {".yml", ".yaml"}:
selected.add(relative)
elif relative.startswith("adoption/agents/") and len(path.parts) <= 5:
if path.suffix in {".md", ".toml"} and path.name != "AGENTS.md" or path.name.endswith("manifest.json") or path.name == "SHA256SUMS":
selected.add(relative)
elif relative.startswith(".claude/skills/") and path.name == "SKILL.md":
selected.add(relative)
return sorted(selected)


def user_inventory_grants(snapshot: dict) -> dict:
"""Reproduce grants directly from independently reviewed relative bindings.

Binding targets are independently reviewed in the retained data file; no
directory observation, resolver, stat or input body is used by derivation.
"""
if not isinstance(snapshot, dict) or snapshot.get("schema") != "architecture-user-bindings/1":
raise ValueError("user grant derivation requires reviewed bindings")
if any(key in snapshot for key in ("directories", "names", "unapproved_names")):
raise ValueError("user grant source must exclude directory observations and unapproved names")
rows = snapshot.get("reviewed_bindings")
if not isinstance(rows, list):
raise ValueError("reviewed bindings must be an explicit list")
policy_spec = importlib.util.spec_from_file_location("user_grant_policy_reference", ROOT / "tools/local-pages/source_policy.py")
policy = importlib.util.module_from_spec(policy_spec)
policy_spec.loader.exec_module(policy)
roles = {"architecture_inventory": set(), "architecture_inventory_metadata": set(), "architecture_design": set()}
aliases, seen, targets = {}, set(), {}
for row in rows:
if not isinstance(row, dict) or row.get("role") not in roles:
raise ValueError("user binding requires a reviewed role")
source, target = row.get("source"), row.get("target")
for relative in (source, target):
policy._relative(relative)
role = row["role"]
binding = (role, source, target)
if binding in seen:
raise ValueError("duplicate reviewed user binding")
seen.add(binding)
if source in targets and targets[source] != target:
raise ValueError("reviewed user binding has conflicting targets")
targets[source] = target
if role != "architecture_inventory_metadata" and any(PurePosixPath(path).name != "SKILL.md" for path in (source, target)):
raise ValueError("user content binding must name an exact skill asset")
roles[role].add(target)
if source != target:
aliases[source] = {"root": "user", "path": source, "target_root": "user", "target_path": target}
return {**{role: [{"root": "user", "path": path} for path in sorted(paths)] for role, paths in roles.items()},
"architecture_inventory_aliases": sorted(aliases.values(), key=lambda row: (row["path"], row["target_path"]))}


def proposal(root: Path, pin: str) -> dict:
if not re.fullmatch(r"[a-f0-9]{40}", pin):
raise ValueError("grant derivation requires a full Git source SHA")
def git(*args):
return subprocess.run(["git", "-C", str(root), *args], check=True, capture_output=True).stdout
policy_spec = importlib.util.spec_from_file_location("policy_grant_reference", root / "tools/local-pages/source_policy.py")
policy = importlib.util.module_from_spec(policy_spec)
policy_spec.loader.exec_module(policy)
# The tripwire's own pinned eligibility declaration is metadata. No frozen
# artifact body is read and no descriptive-pin exception is created here.
syntax = ast.parse(git("show", pin + ":" + FROZEN_RULE).decode("utf-8"))
prefixes = [ast.literal_eval(node.value) for node in syntax.body
if isinstance(node, ast.Assign) and any(isinstance(target, ast.Name) and target.id == "ARTIFACT" for target in node.targets)]
if len(prefixes) != 1 or not isinstance(prefixes[0], str):
raise ValueError("frozen eligibility metadata is not the reviewed declaration")
metadata = json.loads(git("show", pin + ":manifests/evidence.json"))
catalogs = json.loads(git("show", pin + ":catalogs/landscape/manifest.json"))["catalogs"]
paths = git("ls-tree", "-r", "--name-only", pin).decode("utf-8").splitlines()
receipts = receipt_paths(metadata, tuple(prefixes), policy.protected_path)
inventory = [relative for relative in inventory_paths(paths, catalogs) if not policy.protected_path(relative)]
return {"schema": "architecture-grant-proposal/1", "source_pin": pin,
"frozen_rule_source": FROZEN_RULE, "receipt_count": len(receipts),
"architecture_receipt": [{"root": "repo", "path": relative} for relative in receipts],
"architecture_inventory": [{"root": "repo", "path": relative} for relative in inventory],
"limits": ["Proposal only; runtime discovery never grants permission.",
"User targets require independent exact review; unit and agent configurations are metadata only.",
"Referenced registered receipt or frozen-artifact bodies are never read by derivation."]}


def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--root", type=Path, default=ROOT)
parser.add_argument("--pin", required=True)
args = parser.parse_args()
print(json.dumps(proposal(args.root, args.pin), sort_keys=True))
return 0


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