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
2 changes: 2 additions & 0 deletions .github/workflows/trusted-uv-materializer-quality-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ jobs:
tests/test_trusted_uv_download_contract.py \
tests/test_trusted_uv_portability_and_streaming.py \
tests/test_uv_export_isolation_contract.py \
tests/test_uv_flat_lock_publication_boundary.py \
tests/test_uv_redirect_and_coverage_contract.py \
tests/test_uv_redirect_boundary.py \
tests/test_uv_workspace_fail_closed.py \
Expand All @@ -155,6 +156,7 @@ jobs:
tests/test_trusted_uv_download_contract.py \
tests/test_trusted_uv_portability_and_streaming.py \
tests/test_uv_export_isolation_contract.py \
tests/test_uv_flat_lock_publication_boundary.py \
tests/test_uv_redirect_and_coverage_contract.py \
tests/test_uv_redirect_boundary.py \
tests/test_uv_workspace_fail_closed.py \
Expand Down
4 changes: 2 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ Semantic Versioning where the repository publishes a release.

- Parsed `opencode.jsonc` as JSONC (stripping `//` and `/* */` comments outside string literals) in the reasoning-effort guard and its contract tests, instead of raw `json.loads`, which rejected the file the moment it carried its first explanatory comment (added for the `contextual-orchestrator` provider block) with `Expecting property name enclosed in double quotes`. Comment markers inside string values, such as the `$schema` URL, are left untouched.
- Download the pinned `uv` 0.12.1 exporter from the official GitHub Releases URL instead of `releases.astral.sh`, which now returns HTTP 403 and blocks org-wide OpenCode `coverage-evidence`. The SHA-256 pin is unchanged. The opener may follow one hop onto `release-assets.githubusercontent.com` or `objects.githubusercontent.com` and still rejects every other host, userinfo, non-HTTPS scheme, and nondefault port (ContextualWisdomLab/.github#1109).
- Materialized base Python locks only when every package line is an exact SHA-256 pin or a bounded relative `-r`/`--requirement` include. A lone `--require-hashes` directive, a dotted include such as `./lock.txt`, or `-r other-hashes.txt` no longer enters the trusted build context.
- Excluded relative `-r` and `--requirement` referrers from generated flat base-lock publication while retaining bounded include syntax diagnostics and discovering independently complete direct `.txt` children of `requirements` directories.
- Refused a conflict-scope repository root whose immediate parent is a symbolic link, so a swapped parent cannot redirect the canonical worktree after the last-component check (CWE-367).
- Bounded the Strix quality self-test's deterministic timeout fixtures to 3-second process and 5-second fake-sleep budgets so exact-head policy evidence completes inside the existing job limit without changing production Strix scanner timeouts, providers, credentials, or review semantics.
- Allowed commas and ASCII parentheses in the bounded Strix changed-file path policy so legal tracked Packrat fixtures can receive exact-head security analysis, while rejecting raw `..` components before normalization and keeping controls, backslashes, whitespace ambiguity, and shell punctuation fail-closed.
Expand Down Expand Up @@ -68,4 +68,4 @@ Semantic Versioning where the repository publishes a release.
- Added DiskSage operational documentation for the hourly RCA loop, bounded retry cadence, permission model, standalone and MSA reuse, verification, rollback, and APA 7 references.
- Added fast-mlsirm operational documentation for the hourly RCA loop, psychometric scientific gates, Rust ownership, bounded retry cadence, credential isolation, modular reuse, rollback, and APA 7 references.
- Documented the ordinary and conflict repair write-scope parity, ignored-path and symlink inventory, Git-control-file denial, hook suppression, explicit push destination, RED/GREEN evidence, operator response, and local-versus-protected evidence boundary.
- Documented the review-authentication boundary that excludes autonomous writer control-plane paths from review-derived file authority, its test-first Strix security evidence, exact-head coverage contract, and rollback prohibition.
- Documented the review-authentication boundary that excludes autonomous writer control-plane paths from review-derived file authority, its test-first Strix security evidence, exact-head coverage contract, and rollback prohibition.
79 changes: 79 additions & 0 deletions docs/doctoring/trusted-uv-flat-include-isolation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Trusted uv flat-include isolation

## Status

Accepted on 2026-08-18 for generated base Python lock publication.

## Buyer-facing failure

The central coverage lane renames every selected source lock to a generated flat
name such as `requirements-000.txt`. A source requirements file containing a
relative `-r` or `--requirement` directive is valid pip syntax, but pip resolves
the referenced path relative to the generated output location. Publishing only
the referrer can therefore fail a downstream repository before its own tests,
branch coverage, or docstring evidence executes.

## Root cause and decision

The previous implementation conflated two authority boundaries:

- `_is_hash_pinned` answers whether a source file uses bounded requirements
syntax, including a normalized relative include; and
- `base_hash_locks` decides whether one source blob can be copied independently
under a generated flat name.

A bounded relative include may pass the first question while failing the second.
The materializer now keeps bounded-include syntax diagnostics unchanged but uses
`_is_flat_materializable_lock` for publication. That predicate admits only a
non-empty, standalone closure whose logical requirement lines are exact `==`
pins carrying complete SHA-256 hashes. `base_hash_locks` also uses the existing
path-aware candidate predicate, so independently complete direct `.txt` children
such as `requirements/ci.txt` and `service/requirements/package.txt` remain
eligible.

## Security and ownership boundary

No URL, proxy, redirect, package index, caller-controlled header, output path,
review authority, credential, or repository write scope is expanded. The fixed
GitHub Releases uv download and redirect boundary is unchanged. Relative include
publication remains fail-closed until a separately reviewed implementation can
reconstruct the complete immutable include graph, preserve source-directory
identity, rewrite every edge, and prove the resulting closure.

This is a central `.github` materialization correction. Product repositories,
including BandScope, retain ownership of their own requirements, tests, and
runtime behavior. The central workflow must not edit a downstream product merely
to work around a generated-path defect.

## Verification and operator action

The regression suite proves all of the following:

1. both `-r` and `--requirement` referrers are excluded from flat publication;
2. an independently complete referenced lock remains eligible;
3. complete direct `.txt` children of a directory named `requirements` are
discovered; and
4. empty, directive-only, standalone exact-pin, and include-only inputs exercise
both branches of the publication predicate.

Merge requires the focused trusted-uv suite, complete central tests, production
statement and branch coverage at 100%, complete production docstrings, Python
3.10 and current-stable compilation, exact-head security checks, and ordinary
protected-branch review. A downstream repository using nested requirements
should publish one standalone hash-locked closure or wait for a graph-aware
materializer; operators must not manually copy or rename an unresolved include.

## Rollback

Do not restore relative include publication. A rollback would reintroduce a
source-relative edge into a namespace that no longer preserves source location.
Restore only after a graph-aware implementation has equivalent RED fixtures,
immutable edge rewriting, closure verification, and the same security gates.

## APA 7th references

Python Packaging Authority. (2026). *Requirements file format*. pip
documentation. https://pip.pypa.io/en/stable/reference/requirements-file-format/

Python Packaging Authority. (2026). *Secure installs*. pip documentation.
https://pip.pypa.io/en/stable/topics/secure-installs/
24 changes: 20 additions & 4 deletions scripts/ci/materialize_base_python_requirements.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,6 @@ def _is_candidate_lock_name(name: str) -> bool:
)



def _is_candidate_lock_path(path: pathlib.PurePosixPath) -> bool:
"""Return whether one safe tracked path can name a pip requirements lock.

Expand Down Expand Up @@ -242,6 +241,23 @@ def _is_hash_pinned(content: bytes) -> bool:
or _is_bounded_requirement_include(line)
for line in requirement_lines
)


def _is_flat_materializable_lock(content: bytes) -> bool:
"""Return whether content is one standalone exact SHA-256 requirements lock.

Selected sources are renamed to generated flat files. Relative ``-r`` and
``--requirement`` edges therefore lose the source directory that gives them
meaning. Only independent exact package pins cross this publication boundary
until a complete immutable include graph can be reconstructed and rewritten.
"""
lines = _requirement_lines(content)
requirement_lines = [line for line in lines if line != "--require-hashes"]
return bool(requirement_lines) and all(
_is_fully_hash_pinned_requirement(line) for line in requirement_lines
)


def _is_fully_hash_pinned_requirement(line: str) -> bool:
"""Return whether one uv-export line is an exact package pin with SHA-256 hashes."""
fields = re.split(r"\s+(?=--hash=)", line)
Expand Down Expand Up @@ -559,9 +575,9 @@ def base_hash_locks(repo_root: pathlib.Path, base_sha: str) -> list[tuple[str, b
regular_paths = {path for path, _candidate in regular_blobs}
locks: list[tuple[str, bytes]] = []
for path, candidate in regular_blobs:
if _is_candidate_lock_name(candidate.name):
if _is_candidate_lock_path(candidate):
content = _git(repo_root, "show", f"{base_sha}:{path}")
if _is_hash_pinned(content):
if _is_flat_materializable_lock(content):
locks.append((path, content))
elif candidate.name == "uv.lock":
if _uv_pyproject_path(path) not in regular_paths:
Expand Down Expand Up @@ -632,4 +648,4 @@ def main(argv: list[str] | None = None) -> int:


if __name__ == "__main__":
raise SystemExit(main())
raise SystemExit(main())
106 changes: 106 additions & 0 deletions tests/test_uv_flat_lock_publication_boundary.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
"""Regression tests for generated flat Python lock publication."""

from __future__ import annotations

from pathlib import Path

import pytest

from scripts.ci import materialize_base_python_requirements as materializer


def _exact_pin(package_name: str, digest_character: str) -> bytes:
"""Return one standalone exact SHA-256 requirement fixture."""
return (
f"{package_name}==1 --hash=sha256:{digest_character * 64}\n".encode()
)


@pytest.mark.parametrize(
("content", "expected"),
[
(b"", False),
(b"--require-hashes\n", False),
(_exact_pin("standalone-package", "a"), True),
(b"-r requirements-other.txt\n", False),
],
)
def test_flat_materializable_lock_requires_a_standalone_exact_closure(
content: bytes,
expected: bool,
) -> None:
"""Flat publication accepts pins but never unresolved include-only content."""
assert materializer._is_flat_materializable_lock(content) is expected


@pytest.mark.parametrize("directive", ["-r", "--requirement"])
def test_flat_publication_excludes_relative_include_referrers(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
directive: str,
) -> None:
"""A generated flat name cannot preserve a source-relative include edge."""
tree = (
b"100644 blob "
+ (b"0" * 40)
+ b"\trequirements-other.txt\0"
+ b"100644 blob "
+ (b"1" * 40)
+ b"\trequirements.txt\0"
)
target_lock = _exact_pin("target-package", "a")

def fake_git(_repo_root: Path, *args: str) -> bytes:
if args[0] == "ls-tree":
return tree
if args[0] == "show" and args[-1].endswith(":requirements-other.txt"):
return target_lock
if args[0] == "show" and args[-1].endswith(":requirements.txt"):
return f"{directive} requirements-other.txt\n".encode()
raise AssertionError(args)

monkeypatch.setattr(materializer, "_git", fake_git)

assert materializer.base_hash_locks(tmp_path, "a" * 40) == [
("requirements-other.txt", target_lock)
]


def test_flat_publication_discovers_standalone_requirements_directory_locks(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""Path-aware discovery keeps complete direct requirements-directory locks."""
tree = (
b"100644 blob "
+ (b"0" * 40)
+ b"\trequirements/ci.txt\0"
+ b"100644 blob "
+ (b"1" * 40)
+ b"\tservice/requirements/package.txt\0"
+ b"100644 blob "
+ (b"2" * 40)
+ b"\trequirements.txt\0"
)
ci_lock = _exact_pin("ci-package", "a")
service_lock = _exact_pin("service-package", "b")

def fake_git(_repo_root: Path, *args: str) -> bytes:
if args[0] == "ls-tree":
return tree
if args[0] == "show" and args[-1].endswith(":requirements/ci.txt"):
return ci_lock
if args[0] == "show" and args[-1].endswith(
":service/requirements/package.txt"
):
return service_lock
if args[0] == "show" and args[-1].endswith(":requirements.txt"):
return b"-r requirements/ci.txt\n"
raise AssertionError(args)

monkeypatch.setattr(materializer, "_git", fake_git)

assert materializer.base_hash_locks(tmp_path, "a" * 40) == [
("requirements/ci.txt", ci_lock),
("service/requirements/package.txt", service_lock),
]
Loading