From aa7bdce5fa37052b8df36191a2123568946d2031 Mon Sep 17 00:00:00 2001 From: Claude Bot Date: Thu, 2 Jul 2026 07:17:18 +0800 Subject: [PATCH 1/2] fix: Roadmap project table lists `wasmagent-ops` as a public product (#53) Closes #53 --- .github/workflows/docs-ci.yml | 14 ++ .../generate_project_table.cpython-312.pyc | Bin 0 -> 10415 bytes scripts/generate_project_table.py | 29 +++- ...est_generate_project_table.cpython-312.pyc | Bin 0 -> 6679 bytes tests/test_generate_project_table.py | 137 ++++++++++++++++++ 5 files changed, 179 insertions(+), 1 deletion(-) create mode 100644 scripts/__pycache__/generate_project_table.cpython-312.pyc create mode 100644 tests/__pycache__/test_generate_project_table.cpython-312.pyc create mode 100644 tests/test_generate_project_table.py diff --git a/.github/workflows/docs-ci.yml b/.github/workflows/docs-ci.yml index 023b36a..d6e2ca4 100644 --- a/.github/workflows/docs-ci.yml +++ b/.github/workflows/docs-ci.yml @@ -37,3 +37,17 @@ jobs: run: python -m pip install --disable-pip-version-check pyyaml - name: README project table matches repos.yml run: python scripts/generate_project_table.py --check README.md + + project-table-unit-tests: + name: Project table generator unit tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.x" + - name: Install PyYAML + run: python -m pip install --disable-pip-version-check pyyaml + - name: Run generator unit tests + run: python3 -m unittest discover -s tests diff --git a/scripts/__pycache__/generate_project_table.cpython-312.pyc b/scripts/__pycache__/generate_project_table.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b845951b2c000f3060d2bd0f7bec2d9a76776bc9 GIT binary patch literal 10415 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 a<0A!K<|XcIFZ1uchlaOX|D%J2^8Wy8%@eKw literal 0 HcmV?d00001 diff --git a/scripts/generate_project_table.py b/scripts/generate_project_table.py index 4cf463a..d8b1298 100755 --- a/scripts/generate_project_table.py +++ b/scripts/generate_project_table.py @@ -58,9 +58,36 @@ def load_repositories(manifest_path: Path = MANIFEST) -> list[dict]: def public_repositories(manifest_path: Path = MANIFEST) -> list[dict]: - """Return only repositories that should appear in the public table.""" + """Return only repositories that should appear in the public table. + + Repositories are first filtered by the ``public_product`` flag, then two + defence-in-depth guards keep internal tooling out of the public table even + when that flag is misconfigured: + + 1. Category-based: a repository whose ``category`` is ``internal-tool`` is + never surfaced. ``category`` is the org-wide metadata that distinguishes + internal tools from public products, so a contradictory + ``public_product: true`` on an internal-tool repo is treated as a hard + error rather than silently rendered as a public product row. + 2. Name-based: ``FORBIDDEN_PUBLIC`` hard-codes known-sensitive internal + repos that must never appear regardless of any other field. + """ repos = load_repositories(manifest_path) surfaced = [r for r in repos if r.get("public_product") is True] + + # Guard 1 (category-based): internal tooling must never be surfaced as a + # public product, even if public_product was set to true by mistake. This + # is self-maintaining -- new internal tools are excluded by their category + # without needing to be added to FORBIDDEN_PUBLIC. + for repo in surfaced: + if repo.get("category") == "internal-tool": + raise ValueError( + f"{repo.get('name')!r} has category 'internal-tool' but is " + f"flagged public_product: true in {manifest_path.name}; " + f"internal tooling must set public_product: false" + ) + + # Guard 2 (name-based defence-in-depth) for known-sensitive repos. names = {r["name"] for r in surfaced} for forbidden in FORBIDDEN_PUBLIC: if forbidden in names: diff --git a/tests/__pycache__/test_generate_project_table.cpython-312.pyc b/tests/__pycache__/test_generate_project_table.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5695bda10602a377b662d58536fbd8f3ff600643 GIT binary patch literal 6679 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 A9smFU literal 0 HcmV?d00001 diff --git a/tests/test_generate_project_table.py b/tests/test_generate_project_table.py new file mode 100644 index 0000000..d974648 --- /dev/null +++ b/tests/test_generate_project_table.py @@ -0,0 +1,137 @@ +"""Tests for the WasmAgent public project-table generator. + +Stdlib-only (``unittest``) so no extra dependencies are required. Run with:: + + python3 -m unittest discover -s tests + # or + python3 tests/test_generate_project_table.py +""" +from __future__ import annotations + +import sys +import tempfile +import unittest +from pathlib import Path + +# Make the generator importable regardless of the current working directory. +SCRIPTS_DIR = Path(__file__).resolve().parent.parent / "scripts" +sys.path.insert(0, str(SCRIPTS_DIR)) + +import generate_project_table as gpt # noqa: E402 + + +def _write_manifest(text: str) -> Path: + """Write ``text`` to a temporary manifest file and return its path.""" + handle = tempfile.NamedTemporaryFile( + mode="w", suffix=".yml", delete=False, encoding="utf-8" + ) + handle.write(text) + handle.flush() + handle.close() + return Path(handle.name) + + +PUBLIC = """\ +schema_version: 1 +repositories: + - name: wasmagent-js + url: https://github.com/WasmAgent/wasmagent-js + category: public-product + public_product: true + purpose: Core JS/TS runtime and MCP server +""" + + +class PublicRepositoriesTests(unittest.TestCase): + def test_surfaces_public_products(self): + path = _write_manifest(PUBLIC) + surfaced = gpt.public_repositories(path) + self.assertEqual([r["name"] for r in surfaced], ["wasmagent-js"]) + + def test_excludes_internal_tool_with_public_product_false(self): + """An internal-tool repo with public_product: false is omitted.""" + path = _write_manifest( + PUBLIC + + """\ + - name: wasmagent-ops + url: https://github.com/WasmAgent/wasmagent-ops + category: internal-tool + public_product: false + purpose: Internal operations and automation tooling +""" + ) + surfaced = gpt.public_repositories(path) + self.assertEqual([r["name"] for r in surfaced], ["wasmagent-js"]) + + def test_rejects_internal_tool_misflagged_as_public(self): + """A category=internal-tool repo with public_product: true is a hard + error (category-based defence-in-depth, wasmagent#53).""" + path = _write_manifest( + PUBLIC + + """\ + - name: wasmagent-ops + url: https://github.com/WasmAgent/wasmagent-ops + category: internal-tool + public_product: true + purpose: Internal operations and automation tooling +""" + ) + with self.assertRaises(ValueError) as ctx: + gpt.public_repositories(path) + self.assertIn("internal-tool", str(ctx.exception)) + self.assertIn("wasmagent-ops", str(ctx.exception)) + + def test_rejects_unknown_internal_tool_misflagged_as_public(self): + """The category guard is self-maintaining: an internal tool that is + NOT in FORBIDDEN_PUBLIC is still rejected by its category.""" + path = _write_manifest( + PUBLIC + + """\ + - name: some-future-ops-bot + url: https://github.com/WasmAgent/some-future-ops-bot + category: internal-tool + public_product: true + purpose: Another internal tool +""" + ) + with self.assertRaises(ValueError): + gpt.public_repositories(path) + + def test_rejects_forbidden_public_name_even_without_category(self): + """FORBIDDEN_PUBLIC is a name-based backstop independent of category.""" + path = _write_manifest( + PUBLIC + + """\ + - name: claude-bot + url: https://github.com/WasmAgent/claude-bot + category: public-product + public_product: true + purpose: Should not be surfaced +""" + ) + with self.assertRaises(ValueError) as ctx: + gpt.public_repositories(path) + self.assertIn("claude-bot", str(ctx.exception)) + + +class RenderTableTests(unittest.TestCase): + def test_render_excludes_internal_tools(self): + repos = gpt.public_repositories( + _write_manifest( + PUBLIC + + """\ + - name: wasmagent-ops + url: https://github.com/WasmAgent/wasmagent-ops + category: internal-tool + public_product: false + purpose: Internal operations and automation tooling +""" + ) + ) + table = gpt.render_table(repos) + self.assertIn("wasmagent-js", table) + self.assertNotIn("wasmagent-ops", table) + + +if __name__ == "__main__": + unittest.main() From c82fc8d609a36fb91edf520c3bc7228efbaca9d5 Mon Sep 17 00:00:00 2001 From: Claude Bot Date: Thu, 2 Jul 2026 07:17:56 +0800 Subject: [PATCH 2/2] fix: address review findings (#53) --- .gitignore | 5 +++++ .../generate_project_table.cpython-312.pyc | Bin 10415 -> 0 bytes .../test_generate_project_table.cpython-312.pyc | Bin 6679 -> 0 bytes 3 files changed, 5 insertions(+) delete mode 100644 scripts/__pycache__/generate_project_table.cpython-312.pyc delete mode 100644 tests/__pycache__/test_generate_project_table.cpython-312.pyc diff --git a/.gitignore b/.gitignore index fafff2e..ebe4d7f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,7 @@ .DS_Store Thumbs.db + +# Python bytecode +__pycache__/ +*.py[cod] +*.pyo diff --git a/scripts/__pycache__/generate_project_table.cpython-312.pyc b/scripts/__pycache__/generate_project_table.cpython-312.pyc deleted file mode 100644 index b845951b2c000f3060d2bd0f7bec2d9a76776bc9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10415 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 a<0A!K<|XcIFZ1uchlaOX|D%J2^8Wy8%@eKw diff --git a/tests/__pycache__/test_generate_project_table.cpython-312.pyc b/tests/__pycache__/test_generate_project_table.cpython-312.pyc deleted file mode 100644 index 5695bda10602a377b662d58536fbd8f3ff600643..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6679 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 A9smFU