From c81eecf826ef1629e35fb946dff4c5e4c5c3f60c Mon Sep 17 00:00:00 2001 From: OpenCode Date: Wed, 8 Jul 2026 20:20:01 +0700 Subject: [PATCH] Add 10 infrastructure files (tests, policies, proposals) Closes #393, Closes #394, Closes #396, Closes #397, Closes #398, Closes #399, Closes #400, Closes #406, Closes #407, Closes #410 --- docs/evidence/CHECKLIST_DEPENDENCY_UPDATE.md | 16 +++++++ docs/evidence/GUIDE_PRIVACY_THREAT_MODEL.md | 16 +++++++ .../GUIDE_STRUCTURED_LOGGING_OPTION.md | 23 +++++++++ docs/evidence/POLICY_DEPENDENCY_RISK.md | 21 ++++++++ docs/evidence/POLICY_HASH_ALGORITHM.md | 20 ++++++++ docs/evidence/POLICY_SAFE_ARCHIVE.md | 16 +++++++ docs/evidence/PROPOSAL_CLI_COMPLETION.md | 25 ++++++++++ tests/test_manifest_completeness.py | 23 +++++++++ tests/test_path_traversal.py | 20 ++++++++ tests/test_policies_batch.py | 17 +++++++ tests/test_stdout_stderr.py | 48 +++++++++++++++++++ 11 files changed, 245 insertions(+) create mode 100644 docs/evidence/CHECKLIST_DEPENDENCY_UPDATE.md create mode 100644 docs/evidence/GUIDE_PRIVACY_THREAT_MODEL.md create mode 100644 docs/evidence/GUIDE_STRUCTURED_LOGGING_OPTION.md create mode 100644 docs/evidence/POLICY_DEPENDENCY_RISK.md create mode 100644 docs/evidence/POLICY_HASH_ALGORITHM.md create mode 100644 docs/evidence/POLICY_SAFE_ARCHIVE.md create mode 100644 docs/evidence/PROPOSAL_CLI_COMPLETION.md create mode 100644 tests/test_manifest_completeness.py create mode 100644 tests/test_path_traversal.py create mode 100644 tests/test_policies_batch.py create mode 100644 tests/test_stdout_stderr.py diff --git a/docs/evidence/CHECKLIST_DEPENDENCY_UPDATE.md b/docs/evidence/CHECKLIST_DEPENDENCY_UPDATE.md new file mode 100644 index 00000000..199cbdfc --- /dev/null +++ b/docs/evidence/CHECKLIST_DEPENDENCY_UPDATE.md @@ -0,0 +1,16 @@ +# Dependency Update Checklist + +Steps for updating a dependency. + +- [ ] Check the new version's changelog for breaking changes. +- [ ] Update the version in pyproject.toml. +- [ ] Run `pip install -e ".[dev]"` to install the update. +- [ ] Run `python3 -m pytest -q` to verify nothing is broken. +- [ ] If tests fail, fix compatibility issues. +- [ ] Update any documentation that references the old version. +- [ ] If the dependency has a security fix, note it in the PR. + +## Frequency +- Security updates: apply within 1 week. +- Minor updates: apply within 1 month. +- Major updates: evaluate on a case-by-case basis. diff --git a/docs/evidence/GUIDE_PRIVACY_THREAT_MODEL.md b/docs/evidence/GUIDE_PRIVACY_THREAT_MODEL.md new file mode 100644 index 00000000..941a3d24 --- /dev/null +++ b/docs/evidence/GUIDE_PRIVACY_THREAT_MODEL.md @@ -0,0 +1,16 @@ +# Artifact Privacy Threat Model + +Potential privacy threats related to pipeline artifacts. + +| Threat | Risk | Mitigation | +|--------|:----:|------------| +| Candidate sequences in logs | Low | Logs are local-only | +| Lab results contain PII | Medium | Lab partner agreement, data return schema excludes PII | +| Evidence certificates shared without context | Low | All certs include disclaimer | +| Manifest reveals internal paths | Low | Paths are relative, no user info | + +## Principles +- Pipeline outputs should not contain PII. +- If PII is present, it must be clearly labeled and handled per data governance policy. +- All artifacts include a disclaimer stating they are computational predictions. +- Artifacts shared externally should be reviewed for sensitive content. diff --git a/docs/evidence/GUIDE_STRUCTURED_LOGGING_OPTION.md b/docs/evidence/GUIDE_STRUCTURED_LOGGING_OPTION.md new file mode 100644 index 00000000..36c77040 --- /dev/null +++ b/docs/evidence/GUIDE_STRUCTURED_LOGGING_OPTION.md @@ -0,0 +1,23 @@ +# Structured Logging Option + +How to enable structured logging. + +## Current State +Pipeline uses print statements and subprocess output. There is no +structured logging framework. + +## Recommendation +Add Python's `logging` module with structured formatting: +```python +import logging +logging.basicConfig( + format="%(asctime)s %(levelname)s %(name)s:%(lineno)d %(message)s", + level=logging.INFO, +) +``` + +## Benefits +- Consistent log format across all modules. +- Log levels (DEBUG, INFO, WARNING, ERROR) enable filtering. +- Structured format enables log parsing and analysis. +- No external dependencies required. diff --git a/docs/evidence/POLICY_DEPENDENCY_RISK.md b/docs/evidence/POLICY_DEPENDENCY_RISK.md new file mode 100644 index 00000000..675c88a4 --- /dev/null +++ b/docs/evidence/POLICY_DEPENDENCY_RISK.md @@ -0,0 +1,21 @@ +# Dependency Risk Review Policy + +How to review dependency risks. + +## Risk Levels +| Level | Criteria | Review Required | +|:-----:|----------|:---------------:| +| Low | Well-known library, stable API, permissive license | None | +| Medium | Less common library, breaking changes possible | Maintainer | +| High | New dependency, restrictive license, security-sensitive | Project lead | + +## Required Checks +- License compatibility with Apache 2.0 +- Known security vulnerabilities +- Maintenance status (recent releases, issue response) +- Number of transitive dependencies + +## Prohibited +- Dependencies with GPL or AGPL licenses (unless absolutely necessary and reviewed) +- Dependencies with known critical vulnerabilities +- Unmaintained dependencies (no release in > 2 years) diff --git a/docs/evidence/POLICY_HASH_ALGORITHM.md b/docs/evidence/POLICY_HASH_ALGORITHM.md new file mode 100644 index 00000000..14d06cd3 --- /dev/null +++ b/docs/evidence/POLICY_HASH_ALGORITHM.md @@ -0,0 +1,20 @@ +# Hash Algorithm Policy + +Hash algorithms used in the pipeline. + +## Current Algorithms +| Algorithm | Used For | Strength | +|-----------|----------|:--------:| +| SHA-256 | Evidence certificates, manifests, chain of custody | Strong | +| SHA-256 | Input file hashing for reproducibility | Strong | + +## Requirements +- All hashes must use SHA-256 or stronger. +- MD5 and SHA-1 are forbidden for security-sensitive uses. +- Hash algorithms must be documented in the relevant schema. +- Hash values must be hex-encoded, lowercase. + +## Verification +- Hash verification is built into chain-of-custody scripts. +- Run `scripts/verify_lab_peptides.py` to verify sequence hashes. +- Manifests include hashes for all input files. diff --git a/docs/evidence/POLICY_SAFE_ARCHIVE.md b/docs/evidence/POLICY_SAFE_ARCHIVE.md new file mode 100644 index 00000000..f7528bdf --- /dev/null +++ b/docs/evidence/POLICY_SAFE_ARCHIVE.md @@ -0,0 +1,16 @@ +# Safe Archive Extraction Policy + +How to safely extract archives in the pipeline. + +## Rules +- Only extract archives from trusted sources. +- Validate archive contents before extraction. +- Prevent path traversal attacks (no `../` in extracted paths). +- Set a maximum file size and count. +- Extract to a dedicated directory, not to an existing directory tree. +- Verify extracted file hashes against expected values when available. + +## Implementation +- The `build_lab_batch_pack.py` script creates ZIP archives. +- Extraction should use `zipfile.ZipFile.extractall()` with path validation. +- Path traversal is prevented by checking that all extracted paths are within the target directory. diff --git a/docs/evidence/PROPOSAL_CLI_COMPLETION.md b/docs/evidence/PROPOSAL_CLI_COMPLETION.md new file mode 100644 index 00000000..76dde708 --- /dev/null +++ b/docs/evidence/PROPOSAL_CLI_COMPLETION.md @@ -0,0 +1,25 @@ +# CLI Completion Support Proposal + +Adding shell completion support for the CLI. + +## Motivation +Shell completion improves developer experience by showing available +commands, flags, and values. + +## Approach +Use Python's `argparse` with `argcomplete` for shell completion. + +## Required Changes +1. Add `argcomplete` as an optional dependency. +2. Add shell completion entry point in `pyproject.toml`. +3. Register argparse parsers with `argcomplete.autocomplete()`. +4. Document how to enable completion in the shell. + +## Completion Types +- Command names: `rank`, `validate`, `bench`, etc. +- Flag names: `--candidates`, `--out`, `--config`, etc. +- File paths: for `--candidates`, `--out` flags. +- Enum values: for `--ranking-mode` (ensemble, expert). + +## Status +Proposed — not yet implemented. diff --git a/tests/test_manifest_completeness.py b/tests/test_manifest_completeness.py new file mode 100644 index 00000000..8b110e6a --- /dev/null +++ b/tests/test_manifest_completeness.py @@ -0,0 +1,23 @@ +"""Tests for archive manifest completeness.""" +import json +from pathlib import Path + + +def test_run_manifest_has_required_keys(): + """Verify run_manifest.schema.json has required fields.""" + schema = json.loads(Path("schemas/run_manifest.schema.json").read_text()) + required = {"run_id", "pipeline_version", "config_hash", "generated_at", "inputs", "outputs"} + if "required" in schema: + assert required.issubset(set(schema["required"])), "Missing required fields in schema" + + +def test_candidate_schema_has_required_keys(): + schema = json.loads(Path("schemas/candidate.schema.json").read_text()) + assert "candidate_id" in str(schema) + assert "scores" in str(schema) + + +def test_lab_result_schema_has_required_keys(): + schema = json.loads(Path("schemas/lab_result.schema.json").read_text()) + for field in ["candidate_id", "assay_type", "result_value", "result_unit"]: + assert field in str(schema), f"Missing {field} from lab_result schema" diff --git a/tests/test_path_traversal.py b/tests/test_path_traversal.py new file mode 100644 index 00000000..3f110032 --- /dev/null +++ b/tests/test_path_traversal.py @@ -0,0 +1,20 @@ +"""Tests for path traversal in archive builders.""" +import zipfile +import tempfile +from pathlib import Path + + +def test_zip_path_traversal_detected(): + """Verify that path traversal via ../ in zip entries can be detected.""" + with tempfile.TemporaryDirectory() as tmp: + malicious_zip = Path(tmp) / "malicious.zip" + with zipfile.ZipFile(malicious_zip, "w") as zf: + zf.writestr("../../etc/passwd", "malicious") + detected = False + with zipfile.ZipFile(malicious_zip, "r") as zf: + for name in zf.namelist(): + resolved = (Path(tmp) / name).resolve() + if not str(resolved).startswith(str(Path(tmp).resolve())): + detected = True + assert detected, "Path traversal was not detected" + assert "../../" in name, "Path traversal pattern should contain ../" diff --git a/tests/test_policies_batch.py b/tests/test_policies_batch.py new file mode 100644 index 00000000..68f6885b --- /dev/null +++ b/tests/test_policies_batch.py @@ -0,0 +1,17 @@ +"""Tests for policies and infrastructure batch.""" +from pathlib import Path + +FILES = [ + "CHECKLIST_DEPENDENCY_UPDATE.md", + "GUIDE_PRIVACY_THREAT_MODEL.md", + "POLICY_DEPENDENCY_RISK.md", + "POLICY_HASH_ALGORITHM.md", + "POLICY_SAFE_ARCHIVE.md", + "GUIDE_STRUCTURED_LOGGING_OPTION.md", + "PROPOSAL_CLI_COMPLETION.md", +] + + +def test_all_exist(): + for f in FILES: + assert Path("docs/evidence", f).exists(), f"Missing: {f}" diff --git a/tests/test_stdout_stderr.py b/tests/test_stdout_stderr.py new file mode 100644 index 00000000..264a28fc --- /dev/null +++ b/tests/test_stdout_stderr.py @@ -0,0 +1,48 @@ +"""Tests that CLI commands properly separate stdout and stderr.""" +import subprocess +import sys + +import pytest + + +def _run_cmd(*args: str) -> subprocess.CompletedProcess: + return subprocess.run( + [sys.executable, "-m", "openamp_foundry.cli", *args], + capture_output=True, text=True, + env={"PYTHONPATH": "src"}, + ) + + +def test_rank_output_goes_to_stdout(): + r = _run_cmd("rank", "--help") + assert r.returncode == 0 + assert len(r.stdout) > 0 + + +def test_validate_output_goes_to_stdout(): + r = _run_cmd("validate", "--help") + assert r.returncode == 0 + assert len(r.stdout) > 0 + + +def test_bench_help_goes_to_stdout(): + r = _run_cmd("bench", "--help") + assert r.returncode == 0 + assert len(r.stdout) > 0 + + +def test_missing_file_error_goes_to_stderr(): + r = _run_cmd("rank", "--candidates", "/nonexistent.csv", "--out", "/tmp/x.jsonl") + assert r.returncode != 0 + assert len(r.stderr) > 0 or "Error" in r.stdout + + +@pytest.mark.parametrize("cmd", [ + ["rank", "--help"], + ["validate", "--help"], + ["bench", "--help"], +]) +def test_help_uses_stdout(cmd): + r = _run_cmd(*cmd) + assert r.returncode == 0 + assert len(r.stdout) > 0