Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
8344a2b
test(coverage): require requirements directory lock discovery
seonghobae Aug 5, 2026
28bd2c9
ci: stage bounded requirements-directory lock repair
seonghobae Aug 5, 2026
9d24c19
ci(coverage): harden requirements lock repair
seonghobae Aug 5, 2026
18cfd57
ci: add bounded requirements-directory lock transformer
seonghobae Aug 5, 2026
1587f60
ci: finalize requirements-directory locks on PR reopen
seonghobae Aug 5, 2026
be31e48
ci: correct RED lineage delta contract
seonghobae Aug 5, 2026
5426934
ci: repair requirements-directory locks from exact RED lineage
seonghobae Aug 6, 2026
26add0f
ci: remove redundant reopen branch writer
seonghobae Aug 6, 2026
dd6729d
ci: retrigger focused requirements-directory repair
seonghobae Aug 6, 2026
7f6a084
chore(ci): verify PR 785 repair without workflow-file push
seonghobae Aug 6, 2026
ed33253
fix(coverage): materialize requirements directory locks
seonghobae Aug 6, 2026
bca94a7
test(coverage): reject unpinned requirements-directory locks
seonghobae Aug 6, 2026
5a16611
ci: add PR 785 global hash directive repair
seonghobae Aug 6, 2026
2860ce0
ci: trigger PR 785 hash directive repair
seonghobae Aug 6, 2026
7be7b6d
chore(coverage): remove branch-writing repair workflow
seonghobae Aug 7, 2026
e2c2dae
chore(coverage): remove repair trigger artifact
seonghobae Aug 7, 2026
574b199
ci: add bounded PR 785 hash boundary applier
seonghobae Aug 7, 2026
ca74488
ci: trigger bounded PR 785 hash repair
seonghobae Aug 7, 2026
cc49028
test(coverage): define global hash directive trust boundary
seonghobae Aug 7, 2026
e4c8237
fix(coverage): reject unpinned global-hash locks
seonghobae Aug 7, 2026
080fb77
chore(ci): remove PR 785 repair trigger
seonghobae Aug 7, 2026
74c210c
chore(ci): remove PR 785 repair workflow
seonghobae Aug 7, 2026
7d669fb
docs(coverage): document global hash directive boundary
seonghobae Aug 7, 2026
23d1bce
ci: add pointer-based PR 785 finalizer
seonghobae Aug 7, 2026
cd4e5f8
ci: trigger pointer-based PR 785 finalizer
seonghobae Aug 7, 2026
bbeeab2
test(coverage): reject unpinned global-hash requirements
github-actions[bot] Aug 7, 2026
b3f5f7e
Merge branch 'main' into fix/coverage-materialize-requirements-direct…
opencode-agent[bot] Aug 7, 2026
7de73e7
docs(coverage): preserve current-main changelog for requirements locks
seonghobae Aug 9, 2026
d45faed
docs(coverage): isolate requirements-lock changelog hunk
seonghobae Aug 9, 2026
4914e12
test(coverage): reject unsafe requirements candidates before material…
seonghobae Aug 9, 2026
a9f03f9
fix(coverage): validate trusted requirement lines before materialization
seonghobae Aug 10, 2026
17a9b60
test(coverage): cover bounded requirements include grammar
seonghobae Aug 10, 2026
cac1bc8
docs(coverage): specify trusted requirements grammar and proof boundary
seonghobae Aug 10, 2026
dcc5391
docs(coverage): record trusted requirements grammar hardening
seonghobae Aug 10, 2026
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
4 changes: 4 additions & 0 deletions .github/workflows/trusted-uv-materializer-quality-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ on:
- "scripts/ci/materialize_base_python_requirements.py"
- "tests/conftest.py"
- "tests/test_materialize*.py"
- "tests/test_requirements_directory_lock_materialization.py"
- "tests/test_trusted_uv*.py"
- "tests/test_uv*.py"
- "tests/test_repository_branch_coverage_*.py"
Expand All @@ -20,6 +21,7 @@ on:
- "scripts/ci/materialize_base_python_requirements.py"
- "tests/conftest.py"
- "tests/test_materialize*.py"
- "tests/test_requirements_directory_lock_materialization.py"
- "tests/test_trusted_uv*.py"
- "tests/test_uv*.py"
- "tests/test_repository_branch_coverage_*.py"
Expand Down Expand Up @@ -126,6 +128,7 @@ jobs:
python -m coverage run -m pytest \
tests/test_materialize_base_python_requirements.py \
tests/test_materialize_uv_export_hash_contract.py \
tests/test_requirements_directory_lock_materialization.py \
tests/test_trusted_uv_download_contract.py \
tests/test_trusted_uv_portability_and_streaming.py \
tests/test_uv_export_isolation_contract.py \
Expand All @@ -152,6 +155,7 @@ jobs:
scripts/ci/materialize_base_python_requirements.py \
tests/test_materialize_base_python_requirements.py \
tests/test_materialize_uv_export_hash_contract.py \
tests/test_requirements_directory_lock_materialization.py \
tests/test_trusted_uv_download_contract.py \
tests/test_trusted_uv_portability_and_streaming.py \
tests/test_uv_export_isolation_contract.py \
Expand Down
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,18 @@ Semantic Versioning where the repository publishes a release.

### Added

- Added a trusted pull-request comment router for `@cwl-noema-review` and review-only `@opencode-agent` dispatches, with an organization sweep, exact-head receipts, repository allowlisting, fixed runners, immutable checkout pins, and a permanent 100% statement/branch/docstring quality gate.
- Added exact-base `uv.lock` materialization that reconstructs standalone nested projects with a checksum-pinned official `uv` exporter, isolated frozen/offline execution, strict exact-pin and SHA-256 output validation, and complete Python 3.10/3.14 quality evidence.

### Fixed

- 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.
- Bound each review-agent invocation key to the wrapper's complete canonical payload, including the base branch and requesting actor; altered fields with a valid-format key now fail before durable-leader election or forwarding, and wrapper write permission is job-scoped.
- Bound both trusted-uv quality jobs to `github.event.pull_request.head.sha` and added a permanent two-checkout regression contract so exact-head compatibility, coverage, docstring, and compilation claims cannot silently measure GitHub's generated pull-request merge revision.
- Made Strix treat only a single LiteLLM provider-error line containing NVIDIA NIM context and model-catalog 404 evidence as cross-model fallback evidence, rejecting cross-line signal assembly and provider-like target source literals; moved the public default to Nemotron 3 Super 120B and added a second NVIDIA hosted candidate before GitHub Models without neutralizing reported vulnerabilities.
- Hardened base-owned Python requirements materialization so candidate package lines require exact `==` pins with complete SHA-256 hashes and requirement includes use only bounded relative paths; range pins, malformed digests, pip option lines, URL/absolute/traversing/home/query/fragment/backslash/option-like includes, and include lines carrying extra inline options are rejected before the trusted build context.

### Changed

- Materialize complete hash-pinned `requirements/ci.txt` and other direct `requirements/*.txt` base-owned closures so isolated OpenCode coverage imports repository runtime dependencies without trusting pull-request metadata or broadening network access.
91 changes: 91 additions & 0 deletions docs/doctoring/trusted-requirements-directory-lock-discovery.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# Trusted requirements-directory lock discovery

## Decision

The central OpenCode coverage image materializes dependency closures only from
regular files in the authenticated pull-request base commit. In addition to the
conventional `requirements*.txt` and `requirements.lock` names, it recognizes a
`.txt` file that is a **direct child** of a directory named `requirements`, such
as `requirements/ci.txt` or `services/scoring_service/requirements/package.txt`.

The path rule grants candidate status only. A global `--require-hashes`
directive is not trust evidence by itself. Every non-directive logical line must
be either:

- an exact package `==` requirement with one or more complete 64-hex SHA-256
`--hash=` values; or
- a two-token `-r` / `--requirement` include naming a bounded relative path.

The include grammar rejects absolute paths, `..` traversal, URL/scheme syntax,
query or fragment syntax, shell-home expansion, Windows-style separators,
option-like targets, and additional inline options or hashes. Package lines
using ranges such as `>=`, truncated/non-SHA-256-looking hash values, index or
other option lines, local/direct references, and other syntax that merely
contains a `--hash=` substring do not gain trusted candidate status.

This parser is a **pre-materialization eligibility boundary**, not a dependency
solver. The exact trusted source path is recorded in the manifest and the
existing installer separately preflights every candidate as an independently
installable `pip --require-hashes` closure. That second proof remains mandatory:
pip's hash-checking mode intentionally fails when a requirement participating in
the installation is not fully hashed. Syntax qualification therefore cannot
substitute for dependency-closure proof.

Unpinned notes, directive-only files, input files, deeper descendants, symbolic
links, pull-request-only files, malformed Git tree entries, and unsafe include
syntax remain excluded from the networked coverage image.

## Operational reason

Concrete environment locks are frequently organized below a `requirements`
directory and use role names such as `ci.txt` or `package.txt`. Ignoring those
safe base-owned locks leaves isolated coverage without runtime dependencies even
when the repository maintains a complete generated closure. The resulting import
failure measures the coverage image rather than the changed production code.

Conversely, treating the presence of the substring `--hash=` as trust evidence
would let a range requirement, malformed digest, pip option, or path/URL include
cross the materialization boundary. The accepted design therefore combines
base-commit provenance, a narrow grammar, and an independent pip closure
preflight rather than relying on file names or hash-looking text alone.

## Verification

- A failing contract first proved that `requirements/ci.txt` was undiscoverable.
- A later RED security contract proved that range requirements, malformed
digests, pip option lines, absolute/traversing includes, and includes carrying
extra inline options could be materialized by the earlier substring test.
- Direct `requirements/*.txt` and nested-service equivalents remain eligible.
- A deeper `requirements/nested/ci.txt` path and unrelated `docs/ci.txt` remain
ineligible.
- Exact `==` package pins with complete SHA-256 hashes are accepted; `>=` and
malformed/truncated hash forms are rejected.
- Bounded relative includes such as `--requirement other.txt` and
`-r ./locks/other.txt` are accepted; URL, absolute, traversal, home-expansion,
query/fragment, backslash, and option-like forms are rejected.
- A global `--require-hashes` directive combined with an unpinned requirement is
rejected rather than promoted into the networked coverage image.
- Only qualifying base-owned candidates are emitted from realistic temporary Git
bases; unpinned `.in`, note, and hostile direct-child files remain absent.
- Exact-head Python 3.14 quality requires the focused suite, complete central
suite, 100% production statement and branch coverage, 100% public docstrings,
compilation, and security/supply-chain workflows. Python 3.10 compatibility
remains a separate minimum-runtime contract.

## References

Python Packaging Authority. (2026). *Install requires vs requirements files*.
Python Packaging User Guide. Retrieved August 10, 2026, from
https://packaging.python.org/en/latest/discussions/install-requires-vs-requirements/

Python Packaging Authority. (2026). *Repeatable installs*. pip documentation.
Retrieved August 10, 2026, from
https://pip.pypa.io/en/latest/topics/repeatable-installs/

Python Packaging Authority. (2026). *Requirements file format*. pip
26.1.2 documentation. Retrieved August 10, 2026, from
https://pip.pypa.io/en/stable/reference/requirements-file-format/

Python Packaging Authority. (2026). *Secure installs*. pip 26.1.2
documentation. Retrieved August 10, 2026, from
https://pip.pypa.io/en/stable/topics/secure-installs/
80 changes: 63 additions & 17 deletions scripts/ci/materialize_base_python_requirements.py
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,21 @@ 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.

In addition to conventional ``requirements*.txt`` names, repositories often
keep concrete environment closures as direct children such as
``requirements/ci.txt`` or ``service/requirements/package.txt``. Only direct
``.txt`` children of a directory named ``requirements`` gain this path-based
eligibility; content must still pass the independent complete hash-pin
validation before it reaches the trusted image build context.
"""
return _is_candidate_lock_name(path.name) or (
path.suffix == ".txt" and path.parent.name == "requirements"
)


def _requirement_lines(content: bytes) -> list[str]:
"""Return logical requirement lines, joining backslash line-continuations.

Expand All @@ -106,24 +121,31 @@ def _requirement_lines(content: bytes) -> list[str]:
return lines


def _is_hash_pinned(content: bytes) -> bool:
"""Return whether content carries hash pins and is safe to preflight.

Discovery is content-based rather than name-based so hash-pinned locks in any
location (a service subdirectory, ``requirements-dev.txt``,
``requirements-test.txt``) can be considered for offline coverage, while an
unpinned or PR-mutable requirements file is still excluded from the networked
build context. Hash syntax cannot prove that a file includes every transitive
dependency, so the trusted image installer separately preflights every
candidate as an independent ``--require-hashes`` closure. An empty file
carries no installable dependency and is not materialized.
def _is_bounded_requirement_include(line: str) -> bool:
"""Return whether one requirements include names a bounded relative file.

Includes are accepted only as a two-token ``-r``/``--requirement`` form.
Absolute paths, traversal, URLs, option-like targets, shell/Windows path
separators, fragments, queries, and extra inline options or hashes are
rejected before a base-owned file can enter the trusted build context.
The downstream installer still proves that the candidate is an independently
complete hash closure; this predicate grants syntax eligibility only.
"""
lines = _requirement_lines(content)
if not lines:
fields = line.split()
if len(fields) != 2 or fields[0] not in {"-r", "--requirement"}:
return False
target = fields[1]
if (
target.startswith(("-", "~"))
or "\\" in target
or ":" in target
or "?" in target
or "#" in target
):
return False
return any(line == "--require-hashes" for line in lines) or all(
"--hash=" in line or line.startswith(("-r ", "--requirement "))
for line in lines
include_path = pathlib.PurePosixPath(target)
return bool(include_path.parts) and not include_path.is_absolute() and (
".." not in include_path.parts
)


Expand All @@ -138,6 +160,30 @@ def _is_fully_hash_pinned_requirement(line: str) -> bool:
return all(UV_SHA256_HASH_RE.fullmatch(hash_value) for hash_value in hashes)


def _is_hash_pinned(content: bytes) -> bool:
"""Return whether content carries only trusted pins or bounded includes.

Discovery is content-based rather than name-based so exact hash-pinned locks
in service subdirectories and role-specific requirements files can be
considered for offline coverage. Candidate syntax is deliberately stricter
than a substring search: each package line must be an exact ``==`` pin with
one or more complete SHA-256 hashes, or a bounded relative requirements
include. A global ``--require-hashes`` directive is not trust evidence by
itself. The downstream installer separately preflights every candidate as an
independent ``pip --require-hashes`` closure, so syntax eligibility never
substitutes for dependency-closure proof.
"""
lines = _requirement_lines(content)
requirement_lines = [line for line in lines if line != "--require-hashes"]
if not requirement_lines:
return False
return all(
_is_fully_hash_pinned_requirement(line)
or _is_bounded_requirement_include(line)
for line in requirement_lines
)


def _is_fully_hash_pinned_export(content: bytes) -> bool:
"""Return whether every emitted uv requirement is exactly SHA-256 pinned.

Expand Down Expand Up @@ -459,7 +505,7 @@ 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):
locks.append((path, content))
Expand Down
2 changes: 1 addition & 1 deletion tests/test_materialize_base_python_requirements.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ def test_lock_name_candidates_are_pip_requirements_files() -> None:
def test_hash_pin_detection_includes_pinned_and_excludes_unpinned_or_empty() -> None:
"""Only fully hash-pinned, non-empty lock content is materialized."""
assert not materializer._is_hash_pinned(b"# comment only\n\n")
assert materializer._is_hash_pinned(b"--require-hashes\ndemo==1\n")
assert not materializer._is_hash_pinned(b"--require-hashes\ndemo==1\n")
assert materializer._is_hash_pinned(b"demo==1 --hash=sha256:" + b"a" * 64 + b"\n")
assert materializer._is_hash_pinned(b"-r other-hashes.txt\n")
assert not materializer._is_hash_pinned(b"untrusted==1\n")
Expand Down
Loading
Loading