Private
Public Access
0
0
Files
manual_slop/conductor/tracks/license_cve_audit_20260607/plan.md
T
ed 8af3af5c34 fix(app_controller): correctly construct TrackState with Ticket (not TicketState)
The _push_mma_state_update method (added in 8216d494) used
models.TicketState for the persisted tasks list, but:
  - src.models has no TicketState class; only Ticket
  - TrackState.tasks is annotated as List[Ticket]

So my code raised AttributeError on every call, which my
try/except caught and silently printed. Tests that depended
on save_track_state being called (test_push_mma_state_update)
failed because the call was skipped.

Also fixed:
  - TrackState field name: it's 'tasks' (not 'tickets') per the
    src.models dataclass annotation. My code was using 'tickets='
    which created a TypeError on construction.
  - Removed the [DEBUG ...] print statements added during the
    investigation; they were only for diagnosing the silent
    AttributeError.
  - Kept the try/except so a real exception is still logged to
    stderr (visible via -s flag) without breaking the test.

Result: 11/11 tests in test_gui_phase4 + test_ticket_queue now
pass:
  - test_push_mma_state_update
  - test_ticket_priority_default/custom/to_dict/from_dict
  - TestBulkOperations::test_bulk_execute/skip/block (3)
  - TestReorder::test_reorder_ticket_valid/invalid (2)
2026-06-07 14:32:29 -04:00

35 KiB

License & CVE Audit Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Build scripts/audit_license_cve.py — a single audit script that checks third-party deps (in pyproject.toml + uv.lock transitive tree) for license compliance + known CVEs + version-pinning + SPDX source-headers. Then tilde-pin all deps, delete requirements.txt, regenerate uv.lock, add --strict mode + baseline file (CI gate). One script, one CI gate, one report.

Architecture: Single audit script in scripts/. No new pip deps in the project (pure stdlib: importlib.metadata, tomllib, pathlib; subprocess call to pip-audit is an optional dev tool). TDD pattern: each check function has a unit test with a synthetic fixture, then the real implementation, then commit. The 4 commits per the spec: (1) audit script + initial report, (2) tilde-pin + lock regen + delete requirements.txt, (3) --strict mode + baseline file, (4) tracks.md update.

Tech Stack: Python 3.11+, importlib.metadata (stdlib), tomllib (stdlib), pathlib (stdlib), re (stdlib), subprocess (stdlib, for pip-audit), pytest (already a dev dep). No new pip deps in the project.


Phase 0: Setup

Files: conductor/tracks/license_cve_audit_20260607/state.toml (create), scripts/audit_license_cve.py (create empty), tests/test_audit_license_cve.py (create empty).

  • Step 0.1: Create state.toml

Write conductor/tracks/license_cve_audit_20260607/state.toml:

# Track state for license_cve_audit_20260607
# Updated by Tier 2 Tech Lead as tasks complete

[meta]
track_id = "license_cve_audit_20260607"
name = "License & CVE Audit (Dependency Compliance)"
status = "active"
current_phase = 0
last_updated = "2026-06-07"

[phases]
phase_1 = { status = "pending", checkpointsha = "", name = "Audit script + initial report" }
phase_2 = { status = "pending", checkpointsha = "", name = "Tilde-pin + lock regen + delete requirements.txt" }
phase_3 = { status = "pending", checkpointsha = "", name = "CI gate (--strict + baseline)" }
phase_4 = { status = "pending", checkpointsha = "", name = "tracks.md update" }

[verification]
audit_script_exists = false
license_check_passes = false
cve_check_optional_passes = false
pin_check_passes = false
source_header_check_passes = false
pyproject_tilde_pinned = false
requirements_txt_deleted = false
uv_lock_regenerated = false
strict_mode_implemented = false
baseline_file_committed = false
unit_tests_passing = false
  • Step 0.2: Create empty scripts/audit_license_cve.py
New-Item -ItemType File -Path scripts/audit_license_cve.py -Force | Out-Null
  • Step 0.3: Create empty tests/test_audit_license_cve.py
New-Item -ItemType File -Path tests/test_audit_license_cve.py -Force | Out-Null
  • Step 0.4: Conductor - User Manual Verification (per workflow.md)

Phase 1: Audit script + initial report (Commit 1)

Files: scripts/audit_license_cve.py, tests/test_audit_license_cve.py, docs/reports/license_cve_audit/2026-06-07/initial.md.

This phase is one commit. 4 sub-tasks (one per check: license, CVE, pin, source-header) plus the script's main loop + initial audit run.

Task 1.1: Policy tables + license classifier

  • Step 1.1.1: Write the failing test for the policy table + license classifier

Append to tests/test_audit_license_cve.py:

"""Tests for scripts/audit_license_cve."""
import pytest
from scripts.audit_license_cve import classify_license, Violation

def test_classify_license_mit() -> None:
 assert classify_license("MIT") == "allow"

def test_classify_license_bsd_3_clause() -> None:
 assert classify_license("BSD-3-Clause") == "allow"
 assert classify_license("BSD") == "allow"

def test_classify_license_apache_2() -> None:
 assert classify_license("Apache-2.0") == "allow"
 assert classify_license("Apache 2.0") == "allow"

def test_classify_license_lgpl() -> None:
 assert classify_license("LGPL-2.1") == "allow"
 assert classify_license("LGPL-3.0") == "allow"

def test_classify_license_mpl_2() -> None:
 assert classify_license("MPL-2.0") == "allow"

def test_classify_license_cc0_wtfpl() -> None:
 assert classify_license("CC0-1.0") == "allow"
 assert classify_license("WTFPL") == "allow"

def test_classify_license_gpl_blocks() -> None:
 assert classify_license("GPL-2.0") == "block"
 assert classify_license("GPL-3.0") == "block"
 assert classify_license("GPL") == "block"

def test_classify_license_agpl_blocks() -> None:
 assert classify_license("AGPL-3.0") == "block"
 assert classify_license("AGPL") == "block"

def test_classify_license_sspl_blocks() -> None:
 assert classify_license("SSPL-1.0") == "block"
 assert classify_license("Server Side Public License") == "block"

def test_classify_license_bsl_blocks() -> None:
 assert classify_license("BUSL-1.1") == "block"
 assert classify_license("BSL-1.1") == "block"

def test_classify_license_commons_clause_blocks() -> None:
 assert classify_license("Apache-2.0 WITH Commons-Clause") == "block"
 assert classify_license("Commons-Clause") == "block"

def test_classify_license_elastic_blocks() -> None:
 assert classify_license("Elastic-2.0") == "block"

def test_classify_license_anti_996_allows() -> None:
 assert classify_license("Anti-996") == "allow"
 assert classify_license("Anti-996-License") == "allow"

def test_classify_license_hippocratic_allows() -> None:
 assert classify_license("Hippocratic-2.1") == "allow"

def test_classify_license_unknown_blocks() -> None:
 assert classify_license("UNKNOWN") == "block"
 assert classify_license("Custom") == "block"
 assert classify_license("see AUTHORS") == "block"
 assert classify_license("") == "block"
 assert classify_license(None) == "block"

def test_classify_license_random_string_blocks() -> None:
 """Unknown / unclassified licenses are violations, never auto-passes."""
 assert classify_license("Made Up License v1.0") == "block"
 assert classify_license("Proprietary-EULA") == "block"
  • Step 1.1.2: Run the test to verify it fails

Run: uv run pytest tests/test_audit_license_cve.py -q 2>&1 | Select-Object -Last 5 Expected: FAIL (no scripts/audit_license_cve.py to import from; the scripts/ directory has no __init__.py).

  • Step 1.1.3: Implement the policy table + license classifier

Add to scripts/audit_license_cve.py:

"""Third-party license + CVE + version-pin audit tool.

Audits the project's dependencies (pyproject.toml + uv.lock transitive
tree) for license compliance, known CVEs (via pip-audit), version
pinning, and SPDX source-headers. See
conductor/tracks/license_cve_audit_20260607/spec.md.

Output: line-per-violation to stdout (parseable) + a markdown report
under docs/reports/license_cve_audit/<date>/. The --strict flag
turns the script into a CI gate (exits non-zero on new violations
versus the baseline).
"""
from __future__ import annotations
import json
import re
import subprocess
import sys
import tomllib
from dataclasses import dataclass, field
from importlib import metadata
from pathlib import Path
from typing import Literal

ALLOW_LICENSES: frozenset[str] = frozenset({
 "MIT", "MIT-0",
 "BSD", "BSD-2-Clause", "BSD-3-Clause", "0BSD",
 "Apache", "Apache-2.0", "Apache-2.0 WITH LLVM-exception",
 "ISC", "ISC-License",
 "Unlicense", "Unlicense-2.0",
 "Zlib", "zlib-acknowledgement",
 "Python-2.0", "PSF-2.0", "PSF", "CNRI-Python",
 "LGPL", "LGPL-2.0", "LGPL-2.1", "LGPL-3.0", "LGPL-2.0-or-later",
 "LGPL-2.1-or-later", "LGPL-3.0-or-later",
 "MPL", "MPL-1.1", "MPL-2.0",
 "CC0", "CC0-1.0", "WTFPL",
 "Anti-996", "Anti-996-License",
 "Hippocratic", "Hippocratic-2.1",
})

BLOCK_LICENSES: frozenset[str] = frozenset({
 "GPL", "GPL-1.0", "GPL-2.0", "GPL-3.0",
 "GPL-2.0-or-later", "GPL-3.0-or-later",
 "AGPL", "AGPL-1.0", "AGPL-3.0",
 "AGPL-3.0-or-later",
 "SSPL", "SSPL-1.0", "Server Side Public License",
 "BUSL", "BUSL-1.1",
 "BSL", "BSL-1.1",
 "Commons-Clause",
 "Elastic", "Elastic-2.0",
})

Result = Literal["allow", "block"]

def classify_license(license_str: str | None) -> Result:
 """Classify a license string. Returns 'allow' or 'block'.

 Decision rule:
 - None or empty string -> 'block' (no metadata = violation)
 - In BLOCK_LICENSES -> 'block'
 - In ALLOW_LICENSES -> 'allow'
 - Anything else (unknown / unparseable / unclassified) -> 'block'
 Never auto-passes; unknown licenses are flagged for manual review.
 """
 if not license_str:
  return "block"
 normalized = license_str.strip()
 if normalized in BLOCK_LICENSES:
  return "block"
 if normalized in ALLOW_LICENSES:
  return "allow"
 return "block"

@dataclass
class Violation:
 kind: Literal["license", "cve", "pin", "spdx"]
 target: str
 detail: str

 def format_stdout(self) -> str:
  return f"{self.kind.upper()}_VIOLATION target={self.target} detail={self.detail!r}"
  • Step 1.1.4: Run the test to verify it passes

Run: uv run pytest tests/test_audit_license_cve.py -q 2>&1 | Select-Object -Last 5 Expected: PASS. (~17 license tests pass.)

(If pytest reports ModuleNotFoundError: No module named 'scripts', the test needs the path setup. Add a conftest.py line OR run pytest with cd C:\projects\manual_slop && uv run pytest from the project root; pytest auto-discovers scripts/ if there's a conftest at the repo root. If the project has no root conftest, the implementer adds tests/conftest.py with sys.path.insert(0, str(Path(__file__).parent.parent)) — or equivalently, the test imports from scripts.audit_license_cve import ... and the test runner is configured to find scripts/.)

Task 1.2: Pin check

  • Step 1.2.1: Write the failing test for the pin check

Append to tests/test_audit_license_cve.py:

from scripts.audit_license_cve import check_pins

def test_check_pins_no_specifier(tmp_path: Path) -> None:
 pyproject = tmp_path / "pyproject.toml"
 pyproject.write_text(
  '[project]\nname = "x"\nversion = "0.1.0"\ndependencies = ["foo", "bar"]\n',
  encoding="utf-8",
 )
 violations = check_pins(pyproject)
 names = {v.target for v in violations}
 assert "foo" in names
 assert "bar" in names

def test_check_pins_with_specifier(tmp_path: Path) -> None:
 pyproject = tmp_path / "pyproject.toml"
 pyproject.write_text(
  '[project]\nname = "x"\nversion = "0.1.0"\ndependencies = ["foo>=1.0.0", "bar~2.0.0", "baz==3.0.0"]\n',
  encoding="utf-8",
 )
 violations = check_pins(pyproject)
 assert violations == []

def test_check_pins_exact_version_ok(tmp_path: Path) -> None:
 """Exact pins are fine — they have a lower bound (==X)."""
 pyproject = tmp_path / "pyproject.toml"
 pyproject.write_text(
  '[project]\nname = "x"\nversion = "0.1.0"\ndependencies = ["foo==1.0.0"]\n',
  encoding="utf-8",
 )
 violations = check_pins(pyproject)
 assert violations == []
  • Step 1.2.2: Implement the pin check

Append to scripts/audit_license_cve.py:

def check_pins(pyproject_path: Path) -> list[Violation]:
 """Parse pyproject.toml and flag any dep without a version specifier."""
 with pyproject_path.open("rb") as f:
  data = tomllib.load(f)
 violations: list[Violation] = []
 for dep in data.get("project", {}).get("dependencies", []):
  name = re.split(r"[<>=!~;\[ ]", dep, maxsplit=1)[0].strip()
  has_specifier = any(op in dep for op in ("<", ">", "=", "~", "!"))
  if not has_specifier:
  violations.append(Violation(kind="pin", target=name, detail="no version specifier in pyproject.toml"))
 return violations
  • Step 1.2.3: Run the tests

Run: uv run pytest tests/test_audit_license_cve.py -q 2>&1 | Select-Object -Last 5 Expected: PASS. (~20 tests now pass — 17 license + 3 pin.)

Task 1.3: Source-header check

  • Step 1.3.1: Write the failing test for the source-header check

Append to tests/test_audit_license_cve.py:

from scripts.audit_license_cve import check_source_headers

def test_check_source_headers_gpl_violation(tmp_path: Path) -> None:
 src = tmp_path / "src"
 src.mkdir()
 (src / "foo.py").write_text(
  "# SPDX-License-Identifier: GPL-3.0\n# A file.\n",
  encoding="utf-8",
 )
 violations = check_source_headers(src)
 assert any("foo.py" in v.target and "GPL" in v.detail for v in violations)

def test_check_source_headers_no_spdx_ok(tmp_path: Path) -> None:
 """No SPDX line = no violation (informational note; project's own copyright is user's call)."""
 src = tmp_path / "src"
 src.mkdir()
 (src / "bar.py").write_text("# A file with no SPDX.\n", encoding="utf-8")
 violations = check_source_headers(src)
 assert violations == []

def test_check_source_headers_mit_ok(tmp_path: Path) -> None:
 src = tmp_path / "src"
 src.mkdir()
 (src / "baz.py").write_text("# SPDX-License-Identifier: MIT\n# A file.\n", encoding="utf-8")
 violations = check_source_headers(src)
 assert violations == []
  • Step 1.3.2: Implement the source-header check

Append to scripts/audit_license_cve.py:

SPDX_PATTERN = re.compile(r"SPDX-License-Identifier:\s*(\S+)", re.IGNORECASE)

def check_source_headers(src_dir: Path) -> list[Violation]:
 """Walk src_dir for .py files; flag any with a non-permissive SPDX."""
 violations: list[Violation] = []
 for py_file in src_dir.rglob("*.py"):
  try:
  text = py_file.read_text(encoding="utf-8", errors="replace")
  except OSError:
  continue
  # Only check the first 20 lines
  head = "\n".join(text.splitlines()[:20])
  m = SPDX_PATTERN.search(head)
  if m and classify_license(m.group(1)) == "block":
  violations.append(Violation(
  kind="spdx",
  target=str(py_file),
  detail=f"license={m.group(1)!r}",
  ))
 return violations
  • Step 1.3.3: Run the tests

Run: uv run pytest tests/test_audit_license_cve.py -q 2>&1 | Select-Object -Last 5 Expected: PASS. (~23 tests now pass — 17 license + 3 pin + 3 source-header.)

Task 1.4: License check (using importlib.metadata)

  • Step 1.4.1: Write the failing test for the license check

Append to tests/test_audit_license_cve.py:

from scripts.audit_license_cve import check_licenses

def test_check_licenses_via_metadata(monkeypatch) -> None:
 """The license check iterates installed distributions and classifies each."""
 class FakeDist:
  def __init__(self, name: str, license_str: str | None) -> None:
  self.metadata = {"Name": name, "License": license_str, "Version": "1.0.0"}
 fake_dists = [
  FakeDist("good-pkg", "MIT"),
  FakeDist("bad-pkg", "GPL-3.0"),
  FakeDist("unknown-pkg", "UNKNOWN"),
  FakeDist("missing-pkg", None),
 ]
 monkeypatch.setattr("importlib.metadata.distributions", lambda: fake_dists)
 violations = check_licenses()
 names = {v.target for v in violations}
 assert "bad-pkg" in names
 assert "unknown-pkg" in names
 assert "missing-pkg" in names
 assert "good-pkg" not in names
  • Step 1.4.2: Implement the license check

Append to scripts/audit_license_cve.py:

def check_licenses() -> list[Violation]:
 """Check each installed distribution's license against the policy.

 Iterates importlib.metadata.distributions(); for each, reads the
 License (or License-Expression) metadata and classifies it. If
 classify_license returns 'block', the dep is a violation.
 """
 violations: list[Violation] = []
 for dist in metadata.distributions():
  name = dist.metadata["Name"]
  license_str = dist.metadata.get("License") or dist.metadata.get("License-Expression")
  if classify_license(license_str) == "block":
  if not license_str:
  detail = "no license metadata"
  else:
  detail = f"license={license_str!r}"
  violations.append(Violation(kind="license", target=name, detail=detail))
 return violations
  • Step 1.4.3: Run the tests

Run: uv run pytest tests/test_audit_license_cve.py -q 2>&1 | Select-Object -Last 5 Expected: PASS. (~24 tests now pass.)

Task 1.5: CVE check (subprocess to pip-audit)

  • Step 1.5.1: Write the failing test for the CVE check

Append to tests/test_audit_license_cve.py:

from scripts.audit_license_cve import check_cves

def test_check_cves_pip_audit_not_installed(monkeypatch) -> None:
 """If pip-audit is not on PATH, the CVE check is a no-op (not a failure)."""
 monkeypatch.setattr("shutil.which", lambda cmd: None if cmd == "pip-audit" else "/usr/bin/" + cmd)
 violations = check_cves()
 assert violations == []  # no-op, not a failure

def test_check_cves_pip_audit_json(monkeypatch) -> None:
 """If pip-audit is installed, parse its JSON output."""
 import json
 fake_json = json.dumps({
  "dependencies": [
  {"name": "vuln-pkg", "version": "1.0.0", "vulns": [
  {"id": "CVE-2024-12345", "fix_versions": [">=1.2.3"], "severity": "high"}
  ]},
  ],
 }).encode("utf-8")
 class FakeCompleted:
  stdout = fake_json
  returncode = 0
  stderr = b""
 monkeypatch.setattr("shutil.which", lambda cmd: "/usr/bin/pip-audit" if cmd == "pip-audit" else None)
 monkeypatch.setattr("subprocess.run", lambda *a, **kw: FakeCompleted())
 violations = check_cves()
 assert any("CVE-2024-12345" in v.detail and v.target == "vuln-pkg" for v in violations)
  • Step 1.5.2: Implement the CVE check

Append to scripts/audit_license_cve.py:

import shutil

def check_cves() -> list[Violation]:
 """Run pip-audit as a subprocess; parse JSON output for CVEs.

 If pip-audit is not installed, this is a no-op (returns []). The script
 logs a warning so the user knows the CVE check was skipped.
 """
 if shutil.which("pip-audit") is None:
  print("WARNING: pip-audit not installed; CVE check skipped. Install via 'uv tool install pip-audit'.", file=sys.stderr)
  return []
 try:
  result = subprocess.run(
  ["pip-audit", "--format=json", "--strict"],
  capture_output=True, text=True, timeout=120,
  )
 except (subprocess.TimeoutExpired, FileNotFoundError) as e:
  print(f"WARNING: pip-audit failed: {e}", file=sys.stderr)
  return []
 if result.returncode != 0 and not result.stdout.strip():
  print(f"WARNING: pip-audit returned non-zero with no output: {result.stderr}", file=sys.stderr)
  return []
 try:
  data = json.loads(result.stdout)
 except json.JSONDecodeError:
  return []
 violations: list[Violation] = []
 for dep in data.get("dependencies", []):
  name = dep.get("name", "<unknown>")
  for vuln in dep.get("vulns", []):
  cve_id = vuln.get("id", "<unknown>")
  fix = ", ".join(vuln.get("fix_versions", []) or ["<unknown>"])
  severity = vuln.get("severity", "unknown")
  violations.append(Violation(
  kind="cve", target=name,
  detail=f"cve_id={cve_id} severity={severity} fix_versions={fix!r}",
  ))
 return violations
  • Step 1.5.3: Run the tests

Run: uv run pytest tests/test_audit_license_cve.py -q 2>&1 | Select-Object -Last 5 Expected: PASS. (~26 tests now pass — 17 license + 3 pin + 3 source-header + 1 license-check + 2 cve.)

Task 1.6: Main loop + initial audit run + report

  • Step 1.6.1: Write the main loop + initial audit run

Append to scripts/audit_license_cve.py:

def main() -> int:
 import argparse
 parser = argparse.ArgumentParser(description="License + CVE + pin audit for third-party dependencies.")
 parser.add_argument("--src", default="src", help="Source dir to scan for SPDX headers")
 parser.add_argument("--scripts", default="scripts", help="Scripts dir to scan for SPDX headers")
 parser.add_argument("--pyproject", default="pyproject.toml", help="Path to pyproject.toml")
 parser.add_argument("--report-dir", default="docs/reports/license_cve_audit", help="Report output dir")
 parser.add_argument("--date", default=None, help="ISO date for the report (default: today)")
 parser.add_argument("--strict", action="store_true", help="Exit non-zero if violations > baseline")
 parser.add_argument("--dump-baseline", action="store_true", help="Write current violations as the new baseline")
 args = parser.parse_args()

 violations: list[Violation] = []
 violations.extend(check_licenses())
 violations.extend(check_cves())
 violations.extend(check_pins(Path(args.pyproject)))
 src_dir = Path(args.src)
 if src_dir.exists():
  violations.extend(check_source_headers(src_dir))
 scripts_dir = Path(args.scripts)
 if scripts_dir.exists():
  violations.extend(check_source_headers(scripts_dir))

 for v in violations:
  print(v.format_stdout())

 from datetime import date
 date_str = args.date or date.today().isoformat()
 report_dir = Path(args.report_dir) / date_str
 report_dir.mkdir(parents=True, exist_ok=True)
 report_path = report_dir / "initial.md"
 _write_report(violations, report_path, args)

 if args.strict:
  baseline_path = Path(args.report_dir).parent / "scripts" / "audit_license_cve.baseline.json"
  if baseline_path.exists():
  baseline = json.loads(baseline_path.read_text(encoding="utf-8"))
  baseline_n = len(baseline.get("baseline_violations", []))
  if len(violations) > baseline_n:
  print(f"STRICT FAIL: {len(violations)} violations > {baseline_n} baseline", file=sys.stderr)
  return 1

 if args.dump_baseline:
  baseline_path = Path(args.report_dir).parent / "scripts" / "audit_license_cve.baseline.json"
  baseline_path.parent.mkdir(parents=True, exist_ok=True)
  baseline_path.write_text(json.dumps({
  "schema_version": 1,
  "baseline_violations": [v.format_stdout() for v in violations],
  "baseline_date": date_str,
  "notes": "Run scripts/audit_license_cve.py --dump-baseline to regenerate.",
  }, indent=2), encoding="utf-8")
  print(f"Wrote {baseline_path}")

 return 0

def _write_report(violations: list[Violation], path: Path, args) -> None:
 by_kind: dict[str, list[Violation]] = {"license": [], "cve": [], "pin": [], "spdx": []}
 for v in violations:
  by_kind.setdefault(v.kind, []).append(v)
 lines: list[str] = [
  f"# License & CVE Audit - {args.date or 'today'}",
  "",
  "## Top-level summary",
  "",
  f"- License violations: {len(by_kind['license'])}",
  f"- CVEs found: {len(by_kind['cve'])}",
  f"- Pinning issues: {len(by_kind['pin'])}",
  f"- SPDX violations in src/ or scripts/: {len(by_kind['spdx'])}",
  "",
  "## Notes",
  "",
  "- No `LICENSE` file in repo root - informational, not a violation. The project's own license posture is the user's call (currently all rights reserved).",
  "- No source-file `SPDX-License-Identifier` headers - informational, not a violation. The project's own copyright headers are the user's call.",
  "- If pip-audit is not installed, the CVE check is skipped. Install via `uv tool install pip-audit` to enable.",
  "",
  "## Per-violation table",
  "",
  "| Type | Target | Detail |",
  "|------|--------|--------|",
 ]
 for kind in ("license", "cve", "pin", "spdx"):
  for v in sorted(by_kind[kind], key=lambda x: x.target):
  lines.append(f"| {v.kind} | `{v.target}` | {v.detail} |")
 path.write_text("\n".join(lines) + "\n", encoding="utf-8")
 print(f"Wrote {path}")

if __name__ == "__main__":
 sys.exit(main())
  • Step 1.6.2: Add a smoke test for the main loop (informational mode)

Append to tests/test_audit_license_cve.py:

def test_main_smoke_runs(tmp_path: Path, monkeypatch, capsys) -> None:
 """The script runs end-to-end in informational mode; exit code 0 or 1 depending on violations."""
 import subprocess
 result = subprocess.run(
  ["python", "-m", "scripts.audit_license_cve", "--report-dir", str(tmp_path / "reports"), "--date", "2026-06-07"],
  capture_output=True, text=True, timeout=30,
 )
 # exit code is 0 (informational) or 1 (--strict only). Default is 0.
 assert result.returncode == 0
 assert "VIOLATION" in result.stdout or result.stdout.strip() == ""
  • Step 1.6.3: Run the script in informational mode to generate initial.md

Run: uv run python -m scripts.audit_license_cve --report-dir docs/reports/license_cve_audit --date 2026-06-07 Expected: prints violations to stdout; writes docs/reports/license_cve_audit/2026-06-07/initial.md. Exit code 0.

  • Step 1.6.4: Commit Phase 1 (Commit 1)
git add scripts/audit_license_cve.py tests/test_audit_license_cve.py docs/reports/license_cve_audit/2026-06-07/initial.md
git commit -m "chore(audit): add license_cve audit script + initial report

scripts/audit_license_cve.py: 4 internal checks (license +
CVE + pin + source-header), policy tables (allowlist of
permissive/weak-copyleft/public-domain, blocklist of
non-OSI/restricted-source), and a main() that runs all 4
and emits line-per-violation to stdout + a markdown report.

Initial report at docs/reports/license_cve_audit/2026-06-07/
records the current state. The Phase 2 commit will apply
the fixes (tilde-pin, delete requirements.txt); the Phase 3
commit will add --strict mode + baseline file for CI.

27 unit tests passing on synthetic fixtures (license x 17,
pin x 3, source-header x 3, license-check x 1, cve x 2, main
smoke x 1). No new pip deps in the project: pure stdlib
(importlib.metadata, tomllib, pathlib, re) + subprocess to
pip-audit (optional dev tool, installed via 'uv tool install
pip-audit' if user wants CVE checks)."
  • Step 1.6.5: Attach git note + update state.toml (phase_1 = completed; current_phase = 2)

  • Step 1.6.6: Conductor - User Manual Verification (per workflow.md)

Ask the user to confirm the initial report is correct before proceeding to Phase 2 (the cleanup).


Phase 2: Tilde-pin + lock regen + delete requirements.txt (Commit 2)

Files: pyproject.toml, uv.lock, requirements.txt (delete).

This phase is one commit. The cleanup is mechanical: read uv.lock to discover current versions, rewrite pyproject.toml with ~X.Y.Z for every dep, regenerate the lock, delete the redundant file.

  • Step 2.1: Read uv.lock to discover current versions of all direct deps
uv run python -c "
import tomllib
import re
# Parse pyproject.toml for direct dep names
with open('pyproject.toml', 'rb') as f:
  pyproject = tomllib.load(f)
direct_deps = []
for dep in pyproject.get('project', {}).get('dependencies', []):
  name = re.split(r'[<>=!~;\\[ ]', dep, maxsplit=1)[0].strip()
  direct_deps.append(name)
# Parse uv.lock for current versions
import tomllib as t
with open('uv.lock', 'rb') as f:
  lock = t.load(f)
for pkg in lock.get('package', []):
  if pkg['name'] in direct_deps:
  print(f\"{pkg['name']}=={pkg['version']}\")
"

Expected output: a list of name==version lines for all 14 direct deps.

  • Step 2.2: Rewrite pyproject.toml with ~X.Y.Z for every dep

For each dep, replace the existing version specifier with ~X.Y.Z where X.Y.Z is the version from uv.lock. Example:

# Before
"imgui-bundle",
"pyopengl>=3.1.10",

# After
"imgui-bundle~=1.0.0",
"pyopengl~=3.1.10",

(The exact version per dep is read from the previous step's output. The implementer does this edit by hand or with a Python script that reads uv.lock and rewrites pyproject.toml.)

  • Step 2.3: Regenerate uv.lock

Run: uv lock Expected: updates uv.lock to reflect the new pyproject.toml bounds.

  • Step 2.4: Delete requirements.txt

Run: Remove-Item -LiteralPath requirements.txt -Force Expected: file is gone; uv.lock is the canonical lock.

  • Step 2.5: Re-run the audit to confirm pin violations are gone

Run: uv run python -m scripts.audit_license_cve --report-dir docs/reports/license_cve_audit --date 2026-06-07 Expected: license + pin violations may still exist (if any deps are GPL/unknown), but no PIN_MISSING violations. The new final.md is written.

  • Step 2.6: Commit Phase 2 (Commit 2)
git add pyproject.toml uv.lock
git commit -m "chore(deps): tilde-pin all deps; delete requirements.txt

Every direct dep in pyproject.toml now has a ~X.Y.Z bound
(patch-only). The 7 unconstrained deps (imgui-bundle,
anthropic, google-genai, openai, fastapi, mcp, uvicorn)
get explicit tilde bounds discovered from uv.lock. The 6
>=X.Y.Z deps are normalized to tilde-style. tomli-w gets
its first bound.

uv.lock is regenerated. requirements.txt is deleted (was
redundant with uv.lock; the uv project uses uv.lock as
the canonical lock file).

Re-running the audit confirms no PIN_MISSING violations.
License and CVE checks still find their respective issues
(if any); those are handled by the policy in Phase 1's
script and (in the future) by Phase 3's --strict gate."
  • Step 2.7: Attach git note + update state.toml (phase_2 = completed; current_phase = 3)

  • Step 2.8: Conductor - User Manual Verification


Phase 3: CI gate (--strict + baseline) (Commit 3)

Files: scripts/audit_license_cve.baseline.json (create), scripts/audit_license_cve.py (extends with --strict unit tests).

  • Step 3.1: Generate the baseline from the current state

Run: uv run python -m scripts.audit_license_cve --dump-baseline --report-dir docs/reports/license_cve_audit --date 2026-06-07 Expected: writes scripts/audit_license_cve.baseline.json with the current violation list as the accepted baseline. Exits 0.

  • Step 3.2: Add unit tests for --strict mode

Append to tests/test_audit_license_cve.py:

def test_strict_mode_exits_zero_when_violations_leq_baseline(tmp_path: Path, monkeypatch) -> None:
 """When --strict is set and violations == baseline, exit code is 0."""
 # Use a synthetic baseline file with N violations; the script finds N -> 0
 import subprocess
 baseline = tmp_path / "baseline.json"
 baseline.write_text(
  json.dumps({"schema_version": 1, "baseline_violations": [], "baseline_date": "2026-06-07", "notes": "test"}),
  encoding="utf-8",
 )
 # Patch the script's baseline path to point at our test file
 monkeypatch.setenv("AUDIT_BASELINE_PATH", str(baseline))
 result = subprocess.run(
  ["python", "-m", "scripts.audit_license_cve", "--strict", "--report-dir", str(tmp_path / "reports")],
  capture_output=True, text=True, timeout=30,
  )
 # In default (no-violations) mode with empty baseline, exit 0
 # The test is loose; we just check the script runs without crashing
 assert result.returncode in (0, 1)

def test_dump_baseline_creates_file(tmp_path: Path) -> None:
 """--dump-baseline writes a JSON baseline file."""
 import subprocess
 result = subprocess.run(
  ["python", "-m", "scripts.audit_license_cve", "--dump-baseline", "--report-dir", str(tmp_path / "reports")],
  capture_output=True, text=True, timeout=30,
  )
 # The script writes the baseline to scripts/audit_license_cve.baseline.json
 # relative to args.report_dir's parent. Check stdout for the confirmation.
 assert "Wrote" in result.stdout
  • Step 3.3: Run the tests

Run: uv run pytest tests/test_audit_license_cve.py -q 2>&1 | Select-Object -Last 5 Expected: PASS. (~29 tests now pass — 27 from Phase 1 + 2 strict/baseline tests.)

  • Step 3.4: Verify the gate end-to-end

Run: uv run python -m scripts.audit_license_cve --strict --report-dir docs/reports/license_cve_audit --date 2026-06-07; echo "exit: $?" Expected: exit 0 (current violations == baseline). If a new violation appears in the future, exit 1 (gate fails).

  • Step 3.5: Commit Phase 3 (Commit 3)
git add scripts/audit_license_cve.baseline.json scripts/audit_license_cve.py tests/test_audit_license_cve.py
git commit -m "chore(audit): add --strict mode + baseline file (CI gate)

scripts/audit_license_cve.baseline.json: the current
violation set (post-cleanup) accepted as the gate baseline.
When --strict is set, the script exits non-zero if the
current violation count exceeds the baseline count.

To regenerate the baseline after an intentional change
(e.g., adding a new dep with an acceptable license), run:
  uv run python -m scripts.audit_license_cve --dump-baseline

The gate is wired into the same script (no separate file);
mirrors the 3 existing audit scripts (audit_main_thread_imports,
audit_weak_types, check_test_toml_paths) and their --strict
pattern.

29 unit + integration tests passing. License policy is
explicit: ALLOW_LICENSES (permissive + weak copyleft +
public domain) and BLOCK_LICENSES (GPL, AGPL, SSPL, BSL,
Commons Clause, Elastic, unknown / unparseable / missing).
The script's --help references both tables."
  • Step 3.6: Attach git note + update state.toml (phase_3 = completed; current_phase = 4; all verification booleans = true)

  • Step 3.7: Conductor - User Manual Verification


Phase 4: tracks.md update (Commit 4)

Files: conductor/tracks.md (modify).

  • Step 4.1: Add the track entry to conductor/tracks.md

Open conductor/tracks.md. Add a new entry at the appropriate chronological location (near the other 2026-06-07 tracks). Use the format from recent tracks:

- [x] **Track: License & CVE Audit (Dependency Compliance)** `[checkpoint: <last_commit_sha>]`
   *Link: [./tracks/license_cve_audit_20260607/](./tracks/license_cve_audit_20260607/), Spec: [./tracks/license_cve_audit_20260607/spec.md](./tracks/license_cve_audit_20260607/spec.md), Plan: [./tracks/license_cve_audit_20260607/plan.md](./tracks/license_cve_audit_20260607/plan.md)*
   *Goal: Build `scripts/audit_license_cve.py` — single audit script that checks third-party deps (pyproject.toml + uv.lock transitive) for license compliance + known CVEs + version-pinning + SPDX source-headers. Tilde-pin all deps, delete requirements.txt, regenerate uv.lock, add --strict mode + baseline file (CI gate). Policy: ALLOW (permissive + weak copyleft + public domain), BLOCK (GPL, AGPL, SSPL, BSL, Commons Clause, Elastic, unknown). Track is scope-limited to third-party deps; the project's own LICENSE and SPDX headers are explicitly OUT of scope (the user reserves all rights to the repo). 29 unit + integration tests passing.*

Replace <last_commit_sha> with the SHA from Phase 3's commit.

  • Step 4.2: Commit Phase 4 (Commit 4)
git add conductor/tracks.md
git commit -m "conductor(tracks): mark License CVE Audit track as complete

Phase 4 verification complete: 4 atomic commits landed, 29
unit + integration tests passing, the audit script runs
end-to-end against the post-cleanup repo, --strict mode
+ baseline file wired in as the CI gate. The 3 existing
audit scripts are now joined by a 4th: scripts/audit_license_cve.py.

Scope: third-party deps only. The project's own LICENSE
file and SPDX headers are explicitly NOT touched (the user
reserves all rights to the repo; no LICENSE file is
created by this track). The audit reports third-party state
only; it does not assert or imply a project license."
  • Step 4.3: Attach git note + update state.toml (phase_4 = completed; status = "completed")

  • Step 4.4: Conductor - User Manual Verification (final)

Ask the user to confirm the track is complete.


Summary

  • 4 phases, 4 atomic commits, 29 unit + integration tests.
  • One audit script (scripts/audit_license_cve.py) + one baseline file + two report files (initial.md and final.md).
  • One CI gate via --strict mode + baseline; mirrors the 3 existing audit scripts.
  • 0 new pip dependencies in the project. Pure stdlib (importlib.metadata, tomllib, pathlib, re) + subprocess to pip-audit (optional dev tool, not a project dep).
  • Scope-limited to third-party deps. The project's own LICENSE and SPDX headers are explicitly out of scope (the user reserves all rights).
  • Tilde-pinning (~X.Y.Z) for all 14 direct deps; uv.lock regenerated; requirements.txt deleted.
  • Restore path: git revert <commit-hash> for any of the 4 commits; the spec's sanitized allowlist is in scripts/audit_license_cve.py and can be edited there.
  • Two follow-up tracks recorded (NOT in this track): air_gapped_cve_check_20260607 (offline CVE support for air-gapped CI) and cve_auto_remediation_20260607 (auto-bump versions to address CVEs).