Skip to content

Introduce typed Program core and safety catalogue - #6

Merged
leynos merged 3 commits into
mainfrom
terragon/implement-typed-command-core-vd9eer
Dec 2, 2025
Merged

Introduce typed Program core and safety catalogue#6
leynos merged 3 commits into
mainfrom
terragon/implement-typed-command-core-vd9eer

Conversation

@leynos

@leynos leynos commented Dec 2, 2025

Copy link
Copy Markdown
Owner

Summary

  • Introduces a typed Program NewType and a curated ProgramCatalogue with a default allowlist to gate command execution safely
  • Unknown programs are blocked by default via UnknownProgramError
  • Exposes project metadata to downstream services through visible_settings()
  • Adds unit and behavioural tests and updates documentation to reflect the safety model

Changes

  • cuprum/program.py
    • Add Program as a NewType: Program = NewType("Program", str)
  • cuprum/catalogue.py (new module)
    • Implement UnknownProgramError, ProjectSettings, ProgramEntry, and ProgramCatalogue
    • Define core constants: CORE_OPS_PROJECT, ECHO, LS, DOC_TOOL
    • Build a default catalogue DEFAULT_CATALOGUE with DEFAULT_PROJECTS
    • Provide visible_settings() to surface project metadata without mutation
    • Enforce safety by guarding unknown executables and duplicates
  • cuprum/init.py
    • Re-export catalogue and program symbols for public API:
      • CORE_OPS_PROJECT, DEFAULT_CATALOGUE, DEFAULT_PROJECTS, DOC_TOOL, ECHO, LS, ProgramCatalogue, ProgramEntry, ProjectSettings, UnknownProgramError, Program
  • Tests
    • unit tests: cuprum/unittests/test_catalogue.py cover NewType behaviour, default allowlist, safety errors, and metadata exposure
    • behavioural tests: tests/behaviour/test_catalogue_behaviour.py and feature file tests/features/catalogue.feature verify unknown programs are blocked and metadata is exposed
  • Documentation
    • docs/cuprum-design.md: add section describing catalogue metadata and safety model
    • docs/users-guide.md: explain how to use the curated catalogue and read metadata
    • docs/roadmap.md: reflect completion of the typed command core step
  • Dependencies
    • pyproject.toml: add pytest-bdd to dev dependencies
  • Lockfile
    • uv.lock updated to reflect new/test dependencies

Usage example

  • Access the default safe catalogue and look up a program:

    • from cuprum.catalogue import DEFAULT_CATALOGUE, ECHO
    • from cuprum.program import Program
    • entry = DEFAULT_CATALOGUE.lookup(ECHO)
    • print(entry.program) # echoes "echo"
    • print(entry.project.name) # e.g., "core-ops"
  • Unknown programs are rejected:

    • DEFAULT_CATALOGUE.lookup(Program("unknown-tool")) # raises UnknownProgramError

Why this matters

  • Provides a concrete, typed command surface that is safety-first by default
  • Makes the set of allowed commands explicit and discoverable via a catalogue
  • Enables downstream components to fetch noise rules and documentation links without mutating the catalogue

Testing

  • Unit tests verify typed Program behaviour and catalogue logic
  • Behavioural tests assert unknown programs are blocked and metadata is exposed
  • Documentation updated to reflect the safety model

Next steps

  • Optional: extend the default catalogue with additional projects/programs as needed while maintaining safety guarantees
  • Consider adding runtime policy hooks to adjust noise rules dynamically if required

🌿 Generated by Terry


ℹ️ Tag @terragon-labs to ask questions and address PR feedback

📎 Task: https://www.terragonlabs.com/task/c6f484cc-6f2b-418e-8025-9723e201d830

Summary by Sourcery

Introduce a typed program representation and a curated, metadata-rich catalogue that safely gates command execution via an allowlist.

New Features:

  • Add a Program NewType and a ProgramCatalogue with project-scoped metadata, including noise rules and documentation locations, plus a default catalogue of curated programs.
  • Expose catalogue project metadata via visible_settings() for downstream consumers while keeping the catalogue immutable.
  • Re-export core catalogue and program symbols from the top-level package for use as the public API.

Build:

  • Add pytest-bdd to the development dependency group and refresh the lockfile.

Documentation:

  • Extend the user guide and design docs to describe the program catalogue, metadata model, and safety guarantees, and update the roadmap to mark the typed command core as complete.

Tests:

  • Add unit tests for the catalogue allowlist, UnknownProgramError behaviour, and metadata exposure, plus BDD-style behavioural tests and feature scenarios for catalogue safety and visibility.

…adata

Introduces a new `Program` NewType for typed executables and a `ProgramCatalogue` class
that maintains a curated allowlist of allowed programs grouped by `ProjectSettings`.

The catalogue includes functionality to lookup programs, enforce allowlist restrictions
(by raising `UnknownProgramError` for unknown executables), and expose project metadata
such as noise rules and documentation locations for downstream services.

Adds unit and behavioral tests covering the catalogue usage and integration with pytest-bdd.
Updates documentation accordingly to describe the new catalogue feature and usage guidance.
Also updates dependencies to include `pytest-bdd` for behavioral tests.

This implementation enhances safety by blocking unknown commands by default and improves
code maintainability and clarity by centralizing executable definitions with associated metadata.

Co-authored-by: terragon-labs[bot] <terragon-labs[bot]@users.noreply.github.com>
@coderabbitai

coderabbitai Bot commented Dec 2, 2025

Copy link
Copy Markdown

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced a curated programme catalogue system that validates and manages executable allowlists.
    • Unknown programmes are now blocked by default for enhanced safety.
    • Pre-curated programmes (echo, ls, documentation tool) available for immediate use.
    • Project metadata (documentation locations and noise rules) now accessible for downstream services.
  • Documentation

    • Updated design documentation and user guide with catalogue usage examples and configuration guidance.

✏️ Tip: You can customize this high-level summary in your review settings.

Walkthrough

Introduce a typed Program NewType and a ProgramCatalogue with ProjectSettings and ProgramEntry; add curated program constants and a default catalogue; export these symbols at package level; add unit and BDD tests and update documentation and dev dependencies.

Changes

Cohort / File(s) Summary
Program type definition
cuprum/program.py
Add Program = typ.NewType("Program", str) and export it via __all__.
Catalogue core implementation
cuprum/catalogue.py
Add UnknownProgramError, ProjectSettings, ProgramEntry, ProgramCatalogue, _coerce_program(); define CORE_OPS_PROJECT, DOCUMENTATION_PROJECT, ECHO, LS, DOC_TOOL, DEFAULT_PROJECTS, DEFAULT_CATALOGUE; implement indexing, lookup, allowlist checks and visible_settings(); export public symbols.
Public API expansion
cuprum/__init__.py
Re-export catalogue symbols and Program; extend __all__ to include programme and catalogue-related public names.
Unit tests
cuprum/unittests/test_catalogue.py, cuprum/unittests/test_public_api.py
Add tests for Program typing, catalogue allowlist and visibility, UnknownProgramError behaviour, hashing/equality, and package re-exports.
Behaviour-driven tests
tests/behaviour/test_catalogue_behaviour.py, tests/features/catalogue.feature
Add BDD scenarios, fixtures and step definitions validating unknown programme rejection, metadata exposure and curated lookup acceptance; add feature file.
Documentation
docs/cuprum-design.md, docs/users-guide.md, docs/roadmap.md
Document ProgramCatalogue, ProjectSettings, DEFAULT_CATALOGUE, UnknownProgramError, visible_settings(), public safe vs dynamic command surface; add examples and mark roadmap progress.
Dev dependency
pyproject.toml
Add pytest-bdd to the dev dependency group.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Inspect catalogue indexing and duplicate/ownership guards in _index_projects() and _index_programmes().
  • Verify resolve() and project resolution handle missing/unknown entries and raise UnknownProgramError consistently.
  • Confirm visible_settings() returns immutable views that cannot mutate internal state.
  • Check public re-exports in cuprum/__init__.py for naming consistency and accidental shadowing.
  • Review unit and BDD tests for coverage of edge cases (duplicate projects/programmes, string vs Program coercion).

Poem

A catalogue now guards the gate,
With curated tools arranged by fate,
No rogue command shall slip inside,
Each programme named, each project spied,
Docs and rules aligned — the guard’s delight. 🛡️📚

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description check ✅ Passed The description comprehensively relates to the changeset, detailing the typed Program NewType, ProgramCatalogue, safety mechanisms via UnknownProgramError, and metadata exposure through visible_settings().
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Title check ✅ Passed The title directly reflects the primary change: introducing a typed Program core with a safety catalogue, which aligns with the main features (Program NewType, ProgramCatalogue, UnknownProgramError) added in this pull request.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch terragon/implement-typed-command-core-vd9eer

Comment @coderabbitai help to get the list of available commands and usage tips.

@sourcery-ai

sourcery-ai Bot commented Dec 2, 2025

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Implements a typed Program abstraction and a curated ProgramCatalogue with a default allowlist that blocks unknown executables, exposes project metadata via a read-only API, wires the new catalogue into the public cuprum API, and adds tests, docs, and dependency updates to support the new safety model.

Sequence diagram for ProgramCatalogue.lookup and safety checks

sequenceDiagram
    actor Developer
    participant Catalogue as ProgramCatalogue
    participant Helper as _coerce_program
    participant Map as _program_to_project
    participant Entry as ProgramEntry
    participant Error as UnknownProgramError

    Developer->>Catalogue: lookup(raw_program)
    Catalogue->>Helper: _coerce_program(raw_program)
    Helper-->>Catalogue: program_value
    Catalogue->>Map: get(program_value)
    Map-->>Catalogue: project_or_none
    alt program not found
        Catalogue->>Error: construct with message
        Catalogue-->>Developer: raise UnknownProgramError
    else program found
        Catalogue->>Entry: construct(program_value, project)
        Entry-->>Catalogue: entry
        Catalogue-->>Developer: ProgramEntry
    end
Loading

Sequence diagram for visible_settings metadata exposure

sequenceDiagram
    actor DownstreamService
    participant Catalogue as ProgramCatalogue

    DownstreamService->>Catalogue: visible_settings()
    Catalogue-->>DownstreamService: MappingProxyType(dict(_projects))
    DownstreamService->>DownstreamService: iterate projects
    DownstreamService->>DownstreamService: read documentation_locations, noise_rules
Loading

Class diagram for typed Program core and ProgramCatalogue

classDiagram
    class Program {
    }

    class UnknownProgramError {
      <<exception>>
    }

    class ProjectSettings {
      +str name
      +tuple~Program~ programs
      +tuple~str~ documentation_locations
      +tuple~str~ noise_rules
      +owns(program: Program): bool
    }

    class ProgramEntry {
      +Program program
      +ProjectSettings project
      +project_name: str
    }

    class ProgramCatalogue {
      -dict~str, ProjectSettings~ _projects
      -dict~Program, ProjectSettings~ _program_to_project
      -frozenset~Program~ _allowlist
      +ProgramCatalogue(projects: Iterable~ProjectSettings~)
      +allowlist: frozenset~Program~
      +is_allowed(program: Program): bool
      +lookup(program: Program | str): ProgramEntry
      +project_for(program: Program | str): ProjectSettings
      +visible_settings(): Mapping~str, ProjectSettings~
      +_index_projects(projects: Iterable~ProjectSettings~) dict~str, ProjectSettings~
      +_index_programs(projects: dict~str, ProjectSettings~) dict~Program, ProjectSettings~
    }

    class DEFAULTS {
      <<module level constants>>
      +str CORE_OPS_PROJECT
      +str DOCUMENTATION_PROJECT
      +Program ECHO
      +Program LS
      +Program DOC_TOOL
      +tuple~ProjectSettings~ DEFAULT_PROJECTS
      +ProgramCatalogue DEFAULT_CATALOGUE
    }

    ProgramCatalogue --> ProgramEntry : returns
    ProgramCatalogue --> ProjectSettings : indexes
    ProgramCatalogue --> UnknownProgramError : raises
    ProgramEntry --> ProjectSettings : project
    ProjectSettings --> Program : owns
    DEFAULTS --> ProjectSettings : constructs
    DEFAULTS --> ProgramCatalogue : constructs
    DEFAULTS --> Program : constructs
Loading

File-Level Changes

Change Details Files
Introduce a typed Program NewType and use it as the core executable identifier.
  • Define Program as a NewType wrapper around str to represent curated executables.
  • Ensure Program is exported via the cuprum package public API for downstream use.
cuprum/program.py
cuprum/__init__.py
Add a curated ProgramCatalogue with project-scoped metadata and a default allowlist enforcing safety constraints.
  • Define ProjectSettings and ProgramEntry dataclasses to model project metadata and resolved catalogue entries.
  • Implement ProgramCatalogue with allowlist, lookup, project_for, and visible_settings methods, including internal indexing that rejects duplicate projects and duplicate program ownership.
  • Introduce UnknownProgramError for unknown lookups and a _coerce_program helper that normalises str inputs to Program.
  • Define default constants (CORE_OPS_PROJECT, DOCUMENTATION_PROJECT, ECHO, LS, DOC_TOOL), build DEFAULT_PROJECTS, and instantiate DEFAULT_CATALOGUE as the default allowlisted catalogue.
  • Export catalogue-related symbols via all for use by external callers.
cuprum/catalogue.py
cuprum/__init__.py
Extend tests to cover the new catalogue behaviour, metadata exposure, and safety guarantees.
  • Add unit tests for Program NewType behaviour, default allowlist contents, unknown-program rejection, metadata visibility, and safe extension of the catalogue.
  • Add BDD-style behavioural tests and feature scenarios asserting that unknown programs are blocked and project metadata is visible via visible_settings().
cuprum/unittests/test_catalogue.py
tests/behaviour/test_catalogue_behaviour.py
tests/features/catalogue.feature
Update documentation to describe the catalogue safety model and usage, and mark the roadmap step as complete.
  • Extend the user guide with a section on the program catalogue, including examples for looking up programs and defining project-specific catalogues.
  • Add design documentation for catalogue metadata, noise rules, documentation locations, and visible_settings().
  • Update the roadmap to mark the typed command core and curated catalogue step as completed.
docs/users-guide.md
docs/cuprum-design.md
docs/roadmap.md
Adjust project configuration to support behaviour-driven tests and capture dependency changes.
  • Add pytest-bdd to the dev dependency group for BDD tests.
  • Regenerate the uv.lock lockfile to include the new test dependency.
pyproject.toml
uv.lock

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@leynos
leynos marked this pull request as ready for review December 2, 2025 01:16

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey there - I've reviewed your changes and found some issues that need to be addressed.

  • In visible_settings() you wrap a new dict(self._projects) in MappingProxyType, which is an unnecessary copy given that _projects is only populated at construction time; consider returning MappingProxyType(self._projects) instead for a cheaper, still read‑only view.
  • DOCUMENTATION_PROJECT is defined in catalogue.py but not exported via __all__ or the top‑level cuprum package and is not referenced elsewhere in this diff; consider either exporting it consistently like CORE_OPS_PROJECT or inlining/removing it if you don’t expect callers to depend on it.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `visible_settings()` you wrap a new `dict(self._projects)` in `MappingProxyType`, which is an unnecessary copy given that `_projects` is only populated at construction time; consider returning `MappingProxyType(self._projects)` instead for a cheaper, still read‑only view.
- `DOCUMENTATION_PROJECT` is defined in `catalogue.py` but not exported via `__all__` or the top‑level `cuprum` package and is not referenced elsewhere in this diff; consider either exporting it consistently like `CORE_OPS_PROJECT` or inlining/removing it if you don’t expect callers to depend on it.

## Individual Comments

### Comment 1
<location> `cuprum/catalogue.py:57-59` </location>
<code_context>
+        """Return the curated allowlist of programs."""
+        return self._allowlist
+
+    def is_allowed(self, program: Program) -> bool:
+        """Return True when the program is part of the default allowlist."""
+        return program in self._allowlist
+
+    def lookup(self, program: Program | str) -> ProgramEntry:
</code_context>

<issue_to_address>
**suggestion:** Consider accepting `Program | str` in `is_allowed` for consistency with `lookup`.

This asymmetry means callers who only need a boolean allowlist check must depend on the `Program` NewType or wrap strings themselves. Allowing `Program | str` here and delegating to `_coerce_program` would improve ergonomics and keep the public API consistent.

```suggestion
    def is_allowed(self, program: Program | str) -> bool:
        """Return True when the program is part of the default allowlist."""
        program_value = _coerce_program(program)
        return program_value in self._allowlist
```
</issue_to_address>

### Comment 2
<location> `cuprum/unittests/test_catalogue.py:31` </location>
<code_context>
+    assert CORE_OPS_PROJECT in DEFAULT_CATALOGUE.visible_settings()
+
+
+def test_unknown_programs_are_blocked_by_default() -> None:
+    """Unknown executables are rejected to maintain safety by default."""
+    with pytest.raises(UnknownProgramError):
</code_context>

<issue_to_address>
**issue (testing):** Add unit tests for duplicate projects/programs to exercise catalogue safety guards.

The catalogue has explicit guards against duplicate project names and duplicate program ownership (`_index_projects` and `_index_programs` raise `ValueError`), but there are no tests covering them. Please add tests such as:

- Constructing `ProgramCatalogue` with two `ProjectSettings` sharing the same `name` raises `ValueError` with a clear message.
- Constructing `ProgramCatalogue` where two different projects list the same `Program` in `programs` raises `ValueError` and identifies the original owner.

This will help ensure the uniqueness constraints remain enforced across refactors.
</issue_to_address>

### Comment 3
<location> `cuprum/catalogue.py:129` </location>
<code_context>
+    ),
+)
+
+DEFAULT_CATALOGUE = ProgramCatalogue(projects=DEFAULT_PROJECTS)
+
+__all__ = [
</code_context>

<issue_to_address>
**issue (review_instructions):** Add unit and behavioural tests that exercise ProgramCatalogue, ProjectSettings, ProgramEntry, and UnknownProgramError.

Add unit tests under `cuprum/unittests/` that cover at least:
- successful lookups of curated programs (e.g. `ECHO`, `LS`, `DOC_TOOL`) and their owning `ProjectSettings`;
- `is_allowed`/`allowlist` behaviour;
- `visible_settings()` returning the expected metadata and being read‑only;
- duplicate project names and duplicate program ownership raising `ValueError`;
- `UnknownProgramError` when looking up a program not in the allowlist.

Add behavioural tests under `tests/behaviour` (and corresponding `.feature` coverage if you are using BDD) that demonstrate the externally visible behaviour of this catalogue: unknown executables being rejected by default, curated programs being accepted with the right metadata, and downstream components being able to consume `visible_settings()`.

Ensure the newly added empty test files are populated with concrete test cases so this feature is fully covered at both the unit and behavioural levels.

<details>
<summary>Review instructions:</summary>

**Path patterns:** `**/*`

**Instructions:**
For any new feature or change to an existing feature, both behavioural *and* unit tests are required.

</details>
</issue_to_address>

### Comment 4
<location> `cuprum/program.py:7` </location>
<code_context>
+
+import typing as typ
+
+Program = typ.NewType("Program", str)
+
+__all__ = ["Program"]
</code_context>

<issue_to_address>
**issue (review_instructions):** Add tests covering the Program NewType usage and coercion behaviour.

Add unit tests that exercise `Program` as a typed wrapper around `str`, including interactions via `_coerce_program` and use as dictionary keys in the catalogue. Verify that equality, hashing, and membership checks behave as intended when mixing `Program` instances and plain strings (where supported) so regressions in the typed command core are caught.

Also extend the behavioural tests for the catalogue so that end‑to‑end scenarios assert that commands are passed around as `Program` values rather than raw strings, fulfilling the requirement for behavioural coverage of this new typed core.

<details>
<summary>Review instructions:</summary>

**Path patterns:** `**/*`

**Instructions:**
For any new feature or change to an existing feature, both behavioural *and* unit tests are required.

</details>
</issue_to_address>

### Comment 5
<location> `cuprum/__init__.py:21` </location>
<code_context>
+
 PACKAGE_NAME = "cuprum"

+__all__ = [
+    "CORE_OPS_PROJECT",
+    "DEFAULT_CATALOGUE",
</code_context>

<issue_to_address>
**issue (review_instructions):** Cover the newly exposed public API symbols with tests to guard their behaviour and availability.

Add unit tests that import the new symbols from `cuprum`’s top‑level package (`DEFAULT_CATALOGUE`, `DEFAULT_PROJECTS`, `CORE_OPS_PROJECT`, `DOC_TOOL`, `ECHO`, `LS`, `Program`, `ProgramCatalogue`, `ProgramEntry`, `ProjectSettings`, `UnknownProgramError`) and assert they are present and wired to the expected underlying objects.

Add behavioural tests that exercise the public surface via `import cuprum as c` (or similar) and use only this re‑exported API to perform catalogue lookups and allowlist checks. This will ensure that the newly added public surface is covered in both unit and behavioural suites, as required.

<details>
<summary>Review instructions:</summary>

**Path patterns:** `**/*`

**Instructions:**
For any new feature or change to an existing feature, both behavioural *and* unit tests are required.

</details>
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread cuprum/catalogue.py Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
cuprum/__init__.py (1)

1-1: Expand the module docstring to meet documentation requirements.

Per coding guidelines, module docstrings must explain purpose, utility, and usage with example calls if appropriate. The current docstring is insufficient.

-"""cuprum package."""
+"""
+cuprum package.
+
+Provides a typed programme catalogue system for managing curated, allowlisted
+executables. Re-exports core types and the default catalogue for convenience.
+
+Example
+-------
+>>> from cuprum import DEFAULT_CATALOGUE, ECHO
+>>> entry = DEFAULT_CATALOGUE.lookup(ECHO)
+>>> entry.project_name
+'core-ops'
+"""
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 293a0d5 and 13b3d65.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (10)
  • cuprum/__init__.py (1 hunks)
  • cuprum/catalogue.py (1 hunks)
  • cuprum/program.py (1 hunks)
  • cuprum/unittests/test_catalogue.py (1 hunks)
  • docs/cuprum-design.md (1 hunks)
  • docs/roadmap.md (1 hunks)
  • docs/users-guide.md (1 hunks)
  • pyproject.toml (1 hunks)
  • tests/behaviour/test_catalogue_behaviour.py (1 hunks)
  • tests/features/catalogue.feature (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.py

⚙️ CodeRabbit configuration file

**/*.py: - Keep C90 / mccabe complexity ≤ 9

  • Follow single responsibility and CQRS (command/query segregation)
  • Prefer structural pattern matching to
  • Prefer structural pattern matching over isinstance() or imperative decomposition.
  • Docstrings must follow the numpy style guide. Use a single-line summary for private functions and methods, and full structured docs for all public interfaces.
  • Move conditionals with >2 branches to predicate/helper functions
  • Avoid eval, exec, pickle, monkey-patching, ctypes, unsafe shell
  • Every module must begin with a triple-quoted docstring explaining its purpose, utility, and usage, including example calls if appropriate.
  • Comments and docs must follow en-GB-oxendict (-ize / -yse / -our) spelling and grammar
  • Lint suppressions:
    • Blanket # noqa, file-level skips, and categories are forbidden
    • Only narrow in-line disables (# noqa: XYZ) are permitted, and must be accompanied by FIXME: or a ticket link, and used only as a last resort.
  • Use pytest fixtures for shared setup (conftest.py or fixtures/)
  • Replace duplicate tests with @pytest.mark.parametrize
  • Prefer pytest-mock or unittest.mock for stubs/mocks
  • Use assert …, "message" over bare asserts
  • Reflect all API/behaviour changes in docs/ and update roadmap on completion
  • Files must not exceed 400 logical lines:
    • Decompose large modules into subpackages
    • Split large match/case or dispatch tables by domain and collocate with targets if appropriate
    • Move bulky data (fixtures, templates) to external files for parsing at runtime
  • Mutable defaults and shadowed built-ins are forbidden
  • All code must have clear type hints using modern style (A | B, list[str], class Foo[A]:, type Bar = int, etc.), with ABC imports drawn from the correct stdlib module.
  • All path manipulation must be performed using pathlib for cross platform safety. Do not use string manipulation for extraction of path components or filename ele...

Files:

  • cuprum/catalogue.py
  • cuprum/unittests/test_catalogue.py
  • tests/behaviour/test_catalogue_behaviour.py
  • cuprum/__init__.py
  • cuprum/program.py
**/*.md

⚙️ CodeRabbit configuration file

**/*.md: * Avoid 2nd person or 1st person pronouns ("I", "you", "we")

  • Use en-GB-oxendict (-ize / -yse / -our) spelling and grammar
  • Headings must not be wrapped.
  • Documents must start with a level 1 heading
  • Headings must correctly increase or decrease by no more than one level at a time
  • Use GitHub-flavoured Markdown style for footnotes and endnotes.
  • Numbered footnotes must be numbered by order of appearance in the document.

Files:

  • docs/roadmap.md
  • docs/users-guide.md
  • docs/cuprum-design.md
🧬 Code graph analysis (3)
cuprum/unittests/test_catalogue.py (1)
cuprum/catalogue.py (7)
  • ProgramCatalogue (43-104)
  • ProjectSettings (17-27)
  • UnknownProgramError (12-13)
  • allowlist (53-55)
  • visible_settings (74-76)
  • lookup (61-68)
  • is_allowed (57-59)
tests/behaviour/test_catalogue_behaviour.py (1)
cuprum/catalogue.py (5)
  • ProgramCatalogue (43-104)
  • ProjectSettings (17-27)
  • UnknownProgramError (12-13)
  • lookup (61-68)
  • visible_settings (74-76)
cuprum/__init__.py (1)
cuprum/catalogue.py (4)
  • ProgramCatalogue (43-104)
  • ProgramEntry (31-40)
  • ProjectSettings (17-27)
  • UnknownProgramError (12-13)
🪛 LanguageTool
docs/cuprum-design.md

[uncategorized] ~236-~236: Loose punctuation mark.
Context: ...ttingsrecord that holds: -programs: curated Program` values owned by the p...

(UNLIKELY_OPENING_PUNCTUATION)

🔍 Remote MCP Ref

Summary of Additional Context

Based on the available documentation and tools, here's the relevant context for reviewing this PR:

Python NewType Pattern

The Program = NewType("Program", str) approach used in this PR is a standard Python typing pattern that allows the static type checker to treat the new type as a distinct subtype while maintaining runtime identity with the base type. At runtime, Derived(some_value) does not create overhead beyond a regular function call, and some_value is Derived(some_value) is always true at runtime.

Key implications for review:

  • The implementation provides compile-time safety without runtime performance penalties
  • Values of type str cannot be used in places expecting Program, helping prevent logic errors with minimal runtime cost
  • This is the appropriate choice for the cuprum use case

pytest-bdd Testing Framework

The PR's addition of pytest-bdd as a dev dependency enables behavior-driven testing through pytest, which aligns with the new behavioral test files (tests/features/catalogue.feature and tests/behaviour/test_catalogue_behaviour.py) included in the PR.

Design Alignment

The PR implements a catalogue/allowlist pattern with:

  • Strong typing via NewType for program identifiers
  • Metadata coupling through ProjectSettings (containing programs, documentation_locations, and noise_rules)
  • Default-deny security model via UnknownProgramError
  • Read-only metadata exposure through visible_settings()

This is a foundational safety layer that enforces command allowlisting at the type system level, complemented by runtime validation through exception handling.,

⏰ Context from checks skipped due to timeout of 120000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Sourcery review
🔇 Additional comments (13)
docs/roadmap.md (1)

15-20: Keep typed command core task marked as complete

The checked task accurately reflects the implemented Program NewType, curated catalogue, and tests, so no change is required.

docs/cuprum-design.md (1)

231-246: Retain catalogue metadata description as written

The new subsection accurately describes ProgramCatalogue, ProjectSettings, the default allowlist, UnknownProgramError, and visible_settings() without over‑specifying implementation details, so no change is required.

pyproject.toml (1)

11-18: Add pytest-bdd to dev dependencies to back behaviour tests

The pytest-bdd entry in the dev group matches the new BDD tests and is consistent with the existing dependency style; keep this addition.

Run this script to confirm the dependency is wired through configuration and lockfile:

#!/bin/bash
set -euo pipefail

echo "Searching for pytest-bdd in pyproject.toml"
rg -n "pytest-bdd" pyproject.toml || echo "pytest-bdd not declared in pyproject.toml"

echo "Checking for pytest-bdd in uv.lock (if present)"
if [ -f uv.lock ]; then
  rg -n "pytest-bdd" uv.lock || echo "pytest-bdd not found in uv.lock"
else
  echo "uv.lock not present at repository root"
fi
tests/features/catalogue.feature (1)

1-11: Retain BDD scenarios for blocking unknown programmes and exposing metadata

The feature file clearly specifies the default‑deny behaviour and metadata exposure contract for the catalogue, matching the behavioural tests; no change is required.

tests/behaviour/test_catalogue_behaviour.py (1)

1-77: Keep behaviour tests wiring for catalogue scenarios

The pytest‑bdd scenarios and step implementations correctly exercise unknown‑programme rejection and project metadata visibility via visible_settings, with clear fixture and type usage; retain this structure.

cuprum/__init__.py (1)

5-34: LGTM!

Imports are well-organised, and __all__ is alphabetically sorted with all re-exported symbols correctly included.

cuprum/catalogue.py (7)

3-9: LGTM!

Imports are minimal and correctly organised. The typ alias for typing is consistent.


12-13: LGTM!

Inheriting from LookupError is semantically appropriate for "programme not found" scenarios.


16-27: LGTM!

Frozen dataclass with slots is appropriate for immutable metadata. Using tuples for collection fields prevents mutation.


30-40: LGTM!

Clean immutable dataclass with a convenient project_name property.


43-50: LGTM!

Constructor correctly indexes projects and programmes, building the allowlist from the index keys.


114-127: LGTM!

Default project definitions are well-structured with immutable tuples for programmes, documentation locations, and noise rules.


131-142: LGTM!

Public exports are correctly scoped and alphabetically sorted. Private helper _coerce_program is correctly excluded.

Comment thread cuprum/catalogue.py Outdated
Comment thread cuprum/catalogue.py Outdated
Comment thread cuprum/catalogue.py
Comment thread cuprum/catalogue.py Outdated
Comment thread cuprum/program.py Outdated
Comment thread cuprum/unittests/test_catalogue.py
Comment thread docs/users-guide.md
- Allow ProgramCatalogue methods to accept both Program and str types for convenience.
- Added _coerce_program helper function to normalize input.
- Updated is_allowed() and lookup() to support both types.
- Improved ProjectSettings and ProgramCatalogue with cache and consistency checks.
- Added tests for these improvements and public API coverage.
- Updated documentation and examples to use top-level imports from cuprum.

This enhancement improves ergonomics and type safety of program catalogue lookups and checks.

Co-authored-by: terragon-labs[bot] <terragon-labs[bot]@users.noreply.github.com>
@leynos

leynos commented Dec 2, 2025

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Dec 2, 2025

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (1)
docs/users-guide.md (1)

5-20: Align narrative import guidance with the public API surface

Update the narrative bullet so it matches the recommended top-level imports used in the examples and avoids binding readers to the internal catalogue module path.

-- Import curated programs from `cuprum.catalogue` (for example `ECHO`, `LS`).
+- Import curated programs from `cuprum` (for example `ECHO`, `LS`).

This keeps documentation aligned with the cuprum package re‑exports.

Also applies to: 24-40

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 13b3d65 and 5d3a55c.

📒 Files selected for processing (8)
  • cuprum/__init__.py (1 hunks)
  • cuprum/catalogue.py (1 hunks)
  • cuprum/program.py (1 hunks)
  • cuprum/unittests/test_catalogue.py (1 hunks)
  • cuprum/unittests/test_public_api.py (1 hunks)
  • docs/users-guide.md (1 hunks)
  • tests/behaviour/test_catalogue_behaviour.py (1 hunks)
  • tests/features/catalogue.feature (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.md

⚙️ CodeRabbit configuration file

**/*.md: * Avoid 2nd person or 1st person pronouns ("I", "you", "we")

  • Use en-GB-oxendict (-ize / -yse / -our) spelling and grammar
  • Headings must not be wrapped.
  • Documents must start with a level 1 heading
  • Headings must correctly increase or decrease by no more than one level at a time
  • Use GitHub-flavoured Markdown style for footnotes and endnotes.
  • Numbered footnotes must be numbered by order of appearance in the document.

Files:

  • docs/users-guide.md
**/*.py

⚙️ CodeRabbit configuration file

**/*.py: - Keep C90 / mccabe complexity ≤ 9

  • Follow single responsibility and CQRS (command/query segregation)
  • Prefer structural pattern matching to
  • Prefer structural pattern matching over isinstance() or imperative decomposition.
  • Docstrings must follow the numpy style guide. Use a single-line summary for private functions and methods, and full structured docs for all public interfaces.
  • Move conditionals with >2 branches to predicate/helper functions
  • Avoid eval, exec, pickle, monkey-patching, ctypes, unsafe shell
  • Every module must begin with a triple-quoted docstring explaining its purpose, utility, and usage, including example calls if appropriate.
  • Comments and docs must follow en-GB-oxendict (-ize / -yse / -our) spelling and grammar
  • Lint suppressions:
    • Blanket # noqa, file-level skips, and categories are forbidden
    • Only narrow in-line disables (# noqa: XYZ) are permitted, and must be accompanied by FIXME: or a ticket link, and used only as a last resort.
  • Use pytest fixtures for shared setup (conftest.py or fixtures/)
  • Replace duplicate tests with @pytest.mark.parametrize
  • Prefer pytest-mock or unittest.mock for stubs/mocks
  • Use assert …, "message" over bare asserts
  • Reflect all API/behaviour changes in docs/ and update roadmap on completion
  • Files must not exceed 400 logical lines:
    • Decompose large modules into subpackages
    • Split large match/case or dispatch tables by domain and collocate with targets if appropriate
    • Move bulky data (fixtures, templates) to external files for parsing at runtime
  • Mutable defaults and shadowed built-ins are forbidden
  • All code must have clear type hints using modern style (A | B, list[str], class Foo[A]:, type Bar = int, etc.), with ABC imports drawn from the correct stdlib module.
  • All path manipulation must be performed using pathlib for cross platform safety. Do not use string manipulation for extraction of path components or filename ele...

Files:

  • cuprum/program.py
  • cuprum/unittests/test_public_api.py
  • tests/behaviour/test_catalogue_behaviour.py
  • cuprum/catalogue.py
  • cuprum/__init__.py
  • cuprum/unittests/test_catalogue.py
🧬 Code graph analysis (3)
cuprum/unittests/test_public_api.py (1)
cuprum/catalogue.py (7)
  • ProgramCatalogue (56-119)
  • ProgramEntry (44-53)
  • ProjectSettings (30-40)
  • UnknownProgramError (25-26)
  • lookup (76-83)
  • project_name (51-53)
  • is_allowed (71-74)
tests/behaviour/test_catalogue_behaviour.py (1)
cuprum/catalogue.py (8)
  • ProgramCatalogue (56-119)
  • ProgramEntry (44-53)
  • ProjectSettings (30-40)
  • UnknownProgramError (25-26)
  • lookup (76-83)
  • visible_settings (89-91)
  • project_name (51-53)
  • is_allowed (71-74)
cuprum/unittests/test_catalogue.py (1)
cuprum/catalogue.py (7)
  • ProgramCatalogue (56-119)
  • ProjectSettings (30-40)
  • UnknownProgramError (25-26)
  • allowlist (67-69)
  • visible_settings (89-91)
  • is_allowed (71-74)
  • lookup (76-83)
🔍 Remote MCP Ref

Summary of additional concrete facts found in the PR changes (relevant for review)

  • Program is a nominal NewType alias: Program = NewType("Program", str).
  • Catalogue normalization: _coerce_program(raw) returns Program(raw) (no transformation).
  • Unknown programs raise UnknownProgramError from lookup(); lookup() uses the program→project map and raises with message "Program '' is not in the catalogue allowlist". visible_settings() returns an immutable MappingProxyType of projects. allowlist is a frozenset of Program values.
  • Duplicate guards:
    • _index_projects raises ValueError if a project name is registered more than once.
    • _index_programs raises ValueError if the same Program is owned by multiple projects (message includes owner).
  • ProjectSettings is a frozen dataclass with fields: name, programs (tuple[Program,...]), documentation_locations (tuple[str,...]), noise_rules (tuple[str,...]); has owns(program) helper. ProgramEntry is a frozen dataclass holding program and its ProjectSettings and exposes project_name property.
  • Default constants and catalogue contents:
    • CORE_OPS_PROJECT = "core-ops", DOCUMENTATION_PROJECT = "docs".
    • ECHO = Program("echo"), LS = Program("ls"), DOC_TOOL = Program("mdbook").
    • DEFAULT_PROJECTS contains:
      • core-ops: programs (ECHO, LS), documentation_locations ("docs/users-guide.md#program-catalogue"), noise_rules (r"^progress:", r"^note:")
      • docs: programs (DOC_TOOL), documentation_locations ("https://docs.example.invalid/cuprum/catalogue"), noise_rules (r"^[INFO]")
    • DEFAULT_CATALOGUE = ProgramCatalogue(projects=DEFAULT_PROJECTS).
  • cuprum/init.py re-exports: CORE_OPS_PROJECT, DEFAULT_CATALOGUE, DEFAULT_PROJECTS, DOCUMENTATION_PROJECT, DOC_TOOL, ECHO, LS, PACKAGE_NAME, Program, ProgramCatalogue, ProgramEntry, ProjectSettings, UnknownProgramError (i.e., catalogue + Program are publicly exposed at package level).
  • PR page metadata (author branch/PR #/title) available at the PR URL.

Sources

  • cuprum/catalogue.py (catalogue implementation, constants, defaults)
  • cuprum/program.py (Program NewType)
  • cuprum/init.py (public re-exports)
  • PR page: #6 (PR metadata)
⏰ Context from checks skipped due to timeout of 120000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Sourcery review
🔇 Additional comments (4)
cuprum/program.py (1)

1-17: Keep Program NewType and docstring as implemented

Retain this nominal Program alias and the concise example; the public surface and typing behaviour align with the catalogue usage and tests.

tests/features/catalogue.feature (1)

1-17: Keep feature scenarios as written

Retain these three scenarios; they map cleanly onto the step definitions and exercise the key safety and metadata behaviours of the catalogue.

cuprum/__init__.py (1)

1-12: Keep expanded package docstring and re-exports

Retain this package‑level documentation and the explicit __all__; the public API surface for catalogue usage is clear and matches the tests and docs.

Also applies to: 16-29, 33-47

cuprum/catalogue.py (1)

1-158: Approve catalogue core, safety checks, and defaults

Keep this implementation as-is: the coercion helper, duplicate guards, allowlist semantics, visible settings cache, and default projects all align with the intended safety guarantees and are well covered by the tests.

Comment thread cuprum/unittests/test_catalogue.py
Comment thread cuprum/unittests/test_public_api.py Outdated
Comment thread tests/behaviour/test_catalogue_behaviour.py
…ory messages

Improved test clarity by adding detailed assertion messages in
unit and behaviour tests for catalogue and public API. This
includes more descriptive failure messages for duplicate project
and program detection, public export availability, and behaviour
verification in downstream services. Also fixed a docs import path.

Co-authored-by: terragon-labs[bot] <terragon-labs[bot]@users.noreply.github.com>
@leynos leynos changed the title Implement typed Program core with safety catalogue Introduce typed Program core and safety catalogue Dec 2, 2025
@leynos
leynos merged commit d7c5f1d into main Dec 2, 2025
4 checks passed
@leynos
leynos deleted the terragon/implement-typed-command-core-vd9eer branch December 2, 2025 12:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant