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
14 changes: 9 additions & 5 deletions .github/workflows/audit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,18 @@ permissions:
contents: read
issues: write

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false

jobs:
audit:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v6
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- uses: actions/setup-python@v6
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: '3.11'

Expand All @@ -29,7 +33,7 @@ jobs:
pip install -e ".[config]"

- name: Restore previous audit history
uses: actions/cache@v5
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: |
output/audit-report-*.json
Expand Down Expand Up @@ -144,7 +148,7 @@ jobs:
fi

- name: Save audit history
uses: actions/cache/save@v5
uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: |
output/audit-report-*.json
Expand All @@ -153,7 +157,7 @@ jobs:
key: audit-history-${{ github.repository }}-${{ github.run_number }}

- name: Upload reports
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: audit-reports-${{ github.run_number }}
path: output/
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ jobs:
matrix:
python-version: ["3.11"]
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v6
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,15 @@ jobs:

steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Initialize CodeQL
uses: github/codeql-action/init@v4
uses: github/codeql-action/init@87557b9c84dde89fdd9b10e88954ac2f4248e463 # v4.36.1
with:
languages: ${{ matrix.language }}
queries: security-extended,security-and-quality

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v4
uses: github/codeql-action/analyze@87557b9c84dde89fdd9b10e88954ac2f4248e463 # v4.36.1
with:
category: "/language:${{ matrix.language }}"
10 changes: 5 additions & 5 deletions .github/workflows/pypi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@ jobs:
esac

- name: Checkout release tag
uses: actions/checkout@v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ inputs.ref }}
fetch-depth: 0

- name: Set up Python
uses: actions/setup-python@v6
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "3.11"

Expand All @@ -43,7 +43,7 @@ jobs:
run: python -m twine check dist/*

- name: Upload distributions
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: python-distributions
path: dist/*
Expand All @@ -60,10 +60,10 @@ jobs:

steps:
- name: Download distributions
uses: actions/download-artifact@v7
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
with:
name: python-distributions
path: dist

- name: Publish package distributions to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
6 changes: 3 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ jobs:

steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0 # full history so pip can detect installed version

- name: Set up Python
uses: actions/setup-python@v6
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "3.11"

Expand All @@ -45,7 +45,7 @@ jobs:
run: ls -lh dist/

- name: Create GitHub Release
uses: softprops/action-gh-release@v3
uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0
with:
files: |
dist/*.whl
Expand Down
22 changes: 22 additions & 0 deletions src/portfolio_truth_sources.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

import json
import re
import subprocess
from datetime import datetime, timezone
from pathlib import Path
Expand Down Expand Up @@ -79,6 +80,23 @@
"tests",
}
)
# Directory-name substrings (case-insensitive) marking deliberate non-projects.
# A match skips the directory AND its subtree during discovery, so neither the
# container nor anything nested under it reaches the catalog-completeness gate.
# nogoprjs -> operator-flagged "no-go" projects, never pursued
# smoke-export -> generated AuraForge signed-smoke-export bundles (no real repo)
IGNORE_PROJECT_DIR_TOKENS = frozenset({"nogoprjs", "smoke-export"})
# Transient / generated working directories matched by regex on the dir name —
# e.g. a `<repo>-tmp-<timestamp>` clone left behind by a tooling run.
IGNORE_PROJECT_DIR_PATTERNS: tuple[re.Pattern[str], ...] = (re.compile(r"-tmp-\d+$"),)


def _is_ignored_project_dir(name: str) -> bool:
"""True if a directory name is a transient/non-project artifact to skip."""
lowered = name.lower()
if any(token in lowered for token in IGNORE_PROJECT_DIR_TOKENS):
return True
return any(pattern.search(name) for pattern in IGNORE_PROJECT_DIR_PATTERNS)


def discover_workspace_projects(
Expand All @@ -93,6 +111,8 @@ def discover_workspace_projects(
for child in sorted(workspace_root.iterdir(), key=lambda item: item.name.lower()):
if child.name.startswith(".") or not child.is_dir() or child.is_symlink():
continue
if _is_ignored_project_dir(child.name):
continue
if _is_project_dir(child):
discovered.append(
_inspect_project_dir(child, workspace_root, catalog_data=catalog_data, now=now)
Expand Down Expand Up @@ -162,6 +182,8 @@ def _discover_nested_projects(
for child in sorted(root.iterdir(), key=lambda item: item.name.lower()):
if child.name.startswith(".") or not child.is_dir() or child.is_symlink():
continue
if _is_ignored_project_dir(child.name):
continue
if _is_project_dir(child):
discovered.append(
_inspect_project_dir(child, workspace_root, catalog_data=catalog_data, now=now)
Expand Down
10 changes: 7 additions & 3 deletions tests/test_distribution_policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,9 @@ def test_pypi_workflow_is_manual_trusted_publishing_only() -> None:
assert "push:" not in workflow
assert "environment: pypi" in workflow
assert "id-token: write" in workflow
assert "pypa/gh-action-pypi-publish@release/v1" in workflow
# SHA-pinned to the v1 trusted-publishing release line
assert "pypa/gh-action-pypi-publish@" in workflow
assert "# v1" in workflow
assert "actions/upload-artifact" in workflow
assert "actions/download-artifact" in workflow

Expand All @@ -67,8 +69,10 @@ def test_public_repo_has_code_scanning_workflow_documented() -> None:
workflows_readme = (ROOT / ".github" / "workflows" / "README.md").read_text()
security_model = (ROOT / "docs" / "security-model.md").read_text()

assert "github/codeql-action/init@v4" in workflow
assert "github/codeql-action/analyze@v4" in workflow
# CodeQL actions SHA-pinned to the v4 line
assert "github/codeql-action/init@" in workflow
assert "github/codeql-action/analyze@" in workflow
assert "# v4" in workflow
assert "security-events: write" in workflow
assert "pull_request:" in workflow
assert "schedule:" in workflow
Expand Down
54 changes: 53 additions & 1 deletion tests/test_portfolio_truth_sources.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,13 @@

from __future__ import annotations

from src.portfolio_truth_sources import _dedupe_checkouts_by_origin
from datetime import datetime, timezone

from src.portfolio_truth_sources import (
_dedupe_checkouts_by_origin,
_is_ignored_project_dir,
discover_workspace_projects,
)


def _p(name: str, repo_full_name: str = "", path: str | None = None) -> dict:
Expand Down Expand Up @@ -76,3 +82,49 @@ def test_result_is_sorted_by_name_case_insensitively() -> None:
]
result = _dedupe_checkouts_by_origin(discovered)
assert [p["name"] for p in result] == ["Alpha", "mike", "zeta"]


# --- discovery ignore-list: transient / non-project directories ---
# NoGoPRJs (operator-flagged never-pursued), `*-smoke-export` (generated
# AuraForge bundles), and `*-tmp-<ts>` clones are scratch artifacts, not real
# projects. Discovery must skip them (and their subtrees) so they never reach
# the catalog-completeness gate.


def test_ignore_predicate_matches_transient_dirs() -> None:
assert _is_ignored_project_dir("Misc:NoGoPRJs") # colon form, as on disk
assert _is_ignored_project_dir("NoGoPRJs")
assert _is_ignored_project_dir("auraforge-signed-smoke-export")
assert _is_ignored_project_dir("resume-evolver-tmp-1776063720")


def test_ignore_predicate_keeps_real_projects() -> None:
# guard against over-broad matching: legit names that merely resemble a rule
for name in (
"GithubRepoAuditor",
"ApplyKit-public",
"cost-tracker",
"resume-evolver", # the real repo, sans -tmp-<ts> suffix
"smoke-test-runner", # "smoke" but not "smoke-export"
"tmp-tools", # "tmp" but not the -tmp-<digits> clone pattern
):
assert not _is_ignored_project_dir(name), name


def test_discovery_skips_ignored_subtrees(tmp_path) -> None:
def _project(*parts: str) -> None:
d = tmp_path.joinpath(*parts)
d.mkdir(parents=True)
(d / "README.md").write_text("# fixture")

_project("LegitProject") # real top-level project -> kept
_project("NoGoPRJs", "app") # nested under ignored container -> skipped
_project("auraforge-signed-smoke-export", "foo-plan") # ignored bundle -> skipped
_project("resume-evolver-tmp-1776063720") # top-level tmp clone -> skipped

result = discover_workspace_projects(
tmp_path,
catalog_data={},
now=datetime(2026, 6, 2, tzinfo=timezone.utc),
)
assert {p["name"] for p in result} == {"LegitProject"}