From 4cd929f8de0a211b61d7f81d5e469fa1311b532d Mon Sep 17 00:00:00 2001 From: Diogo Santos Date: Mon, 10 Aug 2026 21:53:02 +0100 Subject: [PATCH 1/8] chore(packaging): separate user extras from maintainer groups --- pyproject.toml | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 224e63b..fe63354 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,17 +36,34 @@ Changelog = "https://github.com/dgenio/intentflow/blob/main/CHANGELOG.md" intentflow = "intentflow.cli:main" [project.optional-dependencies] +# User-facing capabilities only. Maintainer test/docs/audit tooling lives in +# PEP 735 dependency groups below and is not part of the published dependency +# contract. +llm = ["anthropic>=0.40"] +openai = ["openai>=1.40"] +sign = ["cryptography>=42"] +# Backward-compatible source-install shims. Historical contributor/docs commands +# may still reference these extras; keep them harmless while new documentation +# uses PEP 735 groups. They intentionally publish no maintainer dependencies. +dev = [] +docs = [] +audit = [] + +[dependency-groups] dev = [ "pytest>=7", "jsonschema>=4.18", "cryptography>=42", "tomli>=2; python_version<'3.11'", ] -llm = ["anthropic>=0.40"] -openai = ["openai>=1.40"] -sign = ["cryptography>=42"] -docs = ["mkdocs>=1.6", "mkdocs-material>=9.5"] -audit = ["pip-audit>=2.7", "cyclonedx-bom>=4.0"] +docs = [ + "mkdocs>=1.6", + "mkdocs-material>=9.5", +] +audit = [ + "pip-audit>=2.7", + "cyclonedx-bom>=4.0", +] [tool.setuptools.packages.find] include = ["intentflow*"] From b52a15e6341a5fbb71315d46e1347cbdcc46f70c Mon Sep 17 00:00:00 2001 From: Diogo Santos Date: Mon, 10 Aug 2026 21:53:21 +0100 Subject: [PATCH 2/8] ci: install maintainer test tooling from PEP 735 group --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e95cee2..05bb2e6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,7 +29,7 @@ jobs: - name: Install test toolchain run: | python -m pip install --upgrade pip - pip install -e ".[dev]" + python -m pip install -e . --group dev - name: Test # Existing tests intentionally import examples.* from the repository # checkout. `python -m pytest` puts the checkout root on sys.path; From ad2a0aa09e2ae4264fe000b5a49c56e460a3105c Mon Sep 17 00:00:00 2001 From: Diogo Santos Date: Mon, 10 Aug 2026 21:53:35 +0100 Subject: [PATCH 3/8] ci: use PEP 735 dev group in Python-next canary --- .github/workflows/python-next.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/python-next.yml b/.github/workflows/python-next.yml index 6635380..8296322 100644 --- a/.github/workflows/python-next.yml +++ b/.github/workflows/python-next.yml @@ -26,6 +26,6 @@ jobs: - name: Install run: | python -m pip install --upgrade pip - pip install -e ".[dev]" + python -m pip install -e . --group dev - name: Test - run: pytest -q + run: python -m pytest -q From 7d634b831d1293db4275cd593f68350a70704fb9 Mon Sep 17 00:00:00 2001 From: Diogo Santos Date: Mon, 10 Aug 2026 21:53:48 +0100 Subject: [PATCH 4/8] ci: install docs tooling from PEP 735 group --- .github/workflows/docs.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index e5de365..1d7b295 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -27,7 +27,10 @@ jobs: - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: "3.12" - - run: pip install ".[docs]" + - name: Install package and docs toolchain + run: | + python -m pip install --upgrade pip + python -m pip install . --group docs - run: mkdocs build - uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3 with: From 1a9de3cac353eeef6d240f4ff3b88e062c2b1e8b Mon Sep 17 00:00:00 2001 From: Diogo Santos Date: Mon, 10 Aug 2026 21:54:02 +0100 Subject: [PATCH 5/8] ci: install audit tooling from PEP 735 group --- .github/workflows/security-audit.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/security-audit.yml b/.github/workflows/security-audit.yml index d97bcf0..546e838 100644 --- a/.github/workflows/security-audit.yml +++ b/.github/workflows/security-audit.yml @@ -2,8 +2,8 @@ name: security-audit # Standalone supply-chain scan (#79). Deliberately scoped to vulnerability # auditing only — the test matrix / lint / format / type checks are owned by a -# separate CI workflow (#22). This runs pip-audit over the installed extras so a -# known-vulnerable dependency in an optional SDK is surfaced early. +# separate CI workflow (#22). This runs pip-audit over the installed functional +# extras so a known-vulnerable optional provider/signing dependency is surfaced. on: push: @@ -24,10 +24,10 @@ jobs: - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: "3.12" - - name: Install package with all extras and the audit tool + - name: Install package with user extras and audit toolchain run: | python -m pip install --upgrade pip - pip install ".[dev,llm,openai,sign,audit]" + python -m pip install ".[llm,openai,sign]" --group audit - name: Run pip-audit # Advisory mode first (does not fail the build) so we establish a clean # baseline before making it blocking. Flip to remove `|| true` once green. @@ -43,7 +43,7 @@ jobs: - name: Generate a CycloneDX SBOM run: | python -m pip install --upgrade pip - pip install ".[audit]" + python -m pip install ".[llm,openai,sign]" --group audit cyclonedx-py environment --output-format json --outfile sbom.json || \ cyclonedx-py --help - name: Upload SBOM artifact From 051b4c246f9006a4b2c925bd4ea16283a40e5140 Mon Sep 17 00:00:00 2001 From: Diogo Santos Date: Mon, 10 Aug 2026 21:54:23 +0100 Subject: [PATCH 6/8] docs: separate contributor setup from published extras --- CONTRIBUTING.md | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 29a6727..7079677 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -9,14 +9,26 @@ contract reviewers hold PRs to. ```bash git clone https://github.com/dgenio/intentflow cd intentflow -pip install -e ".[dev]" +python -m pip install --upgrade pip +python -m pip install -e . --group dev python -m pytest ``` -The runtime core has **zero third-party dependencies**; provider SDKs, signing, -schema validation, docs, and audit tooling are optional extras -(`.[llm]`, `.[openai]`, `.[sign]`, `.[docs]`, `.[audit]`). `.[dev]` pulls in -`pytest`, `jsonschema`, and `cryptography` so the whole suite runs. +The runtime core has **zero third-party dependencies**. Published functional +extras are only capabilities downstream users may intentionally install: +`intentflow[llm]` (Anthropic), `intentflow[openai]`, and `intentflow[sign]`. +Maintainer-only test, documentation, and supply-chain tooling lives in PEP 735 +dependency groups instead of wheel optional-dependency metadata: + +```bash +python -m pip install -e . --group dev # tests / schema validation / signing tests +python -m pip install -e . --group docs # MkDocs site tooling +python -m pip install -e . --group audit # pip-audit / CycloneDX tooling +``` + +Empty legacy `dev`, `docs`, and `audit` extras remain temporarily as harmless +source-install compatibility shims; new contributor documentation and CI must +use the dependency groups above. ## Design invariants (read before changing runtime code) From d71f5bbb1e9d726c23fd364e93d027536bd4d28b Mon Sep 17 00:00:00 2001 From: Diogo Santos Date: Mon, 10 Aug 2026 21:54:49 +0100 Subject: [PATCH 7/8] test: guard public extras and registry-first install docs --- tests/test_dependency_policy.py | 45 ++++++++++++++++++++++++++++++--- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/tests/test_dependency_policy.py b/tests/test_dependency_policy.py index 1979eaa..d5b98bb 100644 --- a/tests/test_dependency_policy.py +++ b/tests/test_dependency_policy.py @@ -1,8 +1,9 @@ """Dependency policy tests for IntentFlow's stdlib-only core. The guard itself intentionally uses only the standard library plus pytest from -the dev extra. Runtime dependencies must stay empty; provider SDKs belong -behind optional extras and lazy imports. +the dev dependency group. Runtime dependencies must stay empty; user-facing +provider/signing SDKs belong behind optional extras, while maintainer tooling +belongs in PEP 735 dependency groups. """ from __future__ import annotations @@ -18,12 +19,24 @@ ROOT = Path(__file__).resolve().parents[1] STDLIB_MODULES = set(sys.stdlib_module_names) | {"__future__"} +PUBLIC_FUNCTIONAL_EXTRAS = {"llm", "openai", "sign"} +LEGACY_EMPTY_EXTRAS = {"dev", "docs", "audit"} +MAINTAINER_GROUPS = {"dev", "docs", "audit"} -def _runtime_dependencies(pyproject_text: str) -> list[str]: +def _load_pyproject(pyproject_text: str) -> dict: try: import tomllib except ModuleNotFoundError: # pragma: no cover - Python 3.10 fallback + import tomli as tomllib + + return tomllib.loads(pyproject_text) + + +def _runtime_dependencies(pyproject_text: str) -> list[str]: + try: + payload = _load_pyproject(pyproject_text) + except ModuleNotFoundError: # pragma: no cover - defensive fallback match = re.search( r"(?ms)^\[project\]\s*(.*?)(?:^\[|\Z)", pyproject_text, @@ -39,7 +52,6 @@ def _runtime_dependencies(pyproject_text: str) -> list[str]: return [] return [item.strip().strip("\"'") for item in raw.split(",") if item.strip()] - payload = tomllib.loads(pyproject_text) return list(payload["project"].get("dependencies", [])) @@ -86,6 +98,31 @@ def test_core_runtime_dependencies_stay_empty() -> None: assert deps == [] +def test_published_extras_only_expose_user_capabilities() -> None: + payload = _load_pyproject((ROOT / "pyproject.toml").read_text()) + extras = payload["project"].get("optional-dependencies", {}) + assert set(extras) == PUBLIC_FUNCTIONAL_EXTRAS | LEGACY_EMPTY_EXTRAS + assert all(extras[name] for name in PUBLIC_FUNCTIONAL_EXTRAS) + assert all(extras[name] == [] for name in LEGACY_EMPTY_EXTRAS) + + groups = payload.get("dependency-groups", {}) + assert set(groups) == MAINTAINER_GROUPS + assert all(groups[name] for name in MAINTAINER_GROUPS) + + +def test_public_install_section_is_registry_first() -> None: + readme = (ROOT / "README.md").read_text() + install = readme.split("## Install", 1)[1].split("## Quickstart", 1)[0] + assert "pip install intentflow" in install + assert 'pip install "intentflow[openai]"' in install + assert 'pip install "intentflow[llm]"' in install + assert 'pip install "intentflow[sign]"' in install + assert "pip install -e" not in install + assert "intentflow[dev]" not in install + assert "intentflow[docs]" not in install + assert "intentflow[audit]" not in install + + def test_intentflow_modules_only_import_stdlib_or_intentflow_at_top_level() -> None: violations = { path.relative_to(ROOT).as_posix(): sorted( From 2982eed4e6431ea5c5aab9d35940e8cd2d10cf59 Mon Sep 17 00:00:00 2001 From: Diogo Santos Date: Mon, 10 Aug 2026 21:55:44 +0100 Subject: [PATCH 8/8] docs: make published package the canonical install path --- README.md | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 379a7c5..6ec1dad 100644 --- a/README.md +++ b/README.md @@ -37,14 +37,24 @@ intentflow audit examples/opensource_triage.iflow result.json # NONCONFORMANT ## Install +Install the published package from PyPI. The core has zero runtime dependencies: + ```bash -pip install -e . # core: zero runtime dependencies -pip install -e ".[dev]" # + pytest, jsonschema, cryptography -pip install -e ".[openai]" # + OpenAI-compatible backend -pip install -e ".[llm]" # + Anthropic backend -pip install -e ".[docs]" # + MkDocs (build the docs site) +pip install intentflow ``` +Install only the user-facing capability you need: + +```bash +pip install "intentflow[openai]" # OpenAI-compatible backend +pip install "intentflow[llm]" # Anthropic backend +pip install "intentflow[sign]" # signed traces / verification support +``` + +Editable installs, tests, docs tooling, and supply-chain audit tooling are +maintainer workflows and live in [CONTRIBUTING.md](CONTRIBUTING.md), not in the +public install path. + Or run it in a container without touching your Python — see [Run in Docker](#run-in-docker).