Skip to content

Phase 18: Library API analysis & design (design-only) - #23

Closed
cemililik wants to merge 2 commits into
developmentfrom
closure/faz18-library-api-design
Closed

cemililik wants to merge 2 commits into
developmentfrom
closure/faz18-library-api-design

Conversation

@cemililik

@cemililik cemililik commented May 2, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Design-only PR. Ships the Phase 18 deliverable from the closure plan: a 470-line analysis document (`docs/analysis/code_reviews/library-api-design-202605021414.md`) that pins down ForgeLM's library API surface so Phase 19 can implement it without re-deriving decisions.

What this resolves

ForgeLM today is a de facto library — `from forgelm import ForgeTrainer` works because of an existing `getattr` shim — but nothing has documented:

  • which symbols are stable (semver-protected) vs experimental vs internal
  • the lazy-import invariant (`import forgelm` must not pull `torch`)
  • how `forgelm/cli/init.py`'s 153-line eager-import facade gets migrated (Wave 1 round-5 carry-over)
  • the `py.typed` + `mypy --strict` contract for downstream type checking
  • the `api_version` separation from `version`
  • which integration tests Phase 19 must ship
  • which docs / README / notebook Phase 19 must produce

The design answers all of these. Five open questions resolved with explicit decisions (see §10).

Document structure

§ Topic
1 Current public surface audit (forgelm/init.py)
2 Stability tiers (stable / experimental / internal) with per-symbol mapping
3 Type contract (py.typed + mypy --strict scope)
4 Lazy-import discipline (incl. cli/init.py migration)
5 Versioned API contract (`version` vs `api_version`)
6 Integration test strategy (9 named test cases)
7 Documentation surface (reference + guide + notebook)
8 Cross-cutting: logging, config-from-dict, errors, concurrency
9 16-row Phase 19 task plan
10 Open questions + resolutions
11 Out of scope (deliberate)
12 Sign-off checklist (all boxes ticked)

Test plan

  • Document is 496 lines (≥400-line acceptance threshold)
  • No code changes; `pytest tests/` not regressed (no need to run — design only)
  • No ruff target (markdown only)

Risk

None. Pure design document. No runtime / API / dependency surface touched.

Closes

  • `closure-plan-202604300906.md` §7 Phase 18

Unblocks

  • Phase 19 (Library API implementation) — task plan ready to execute

🤖 Generated with Claude Code

Summary by Sourcery

Documentation:

  • Add a comprehensive library API analysis and design document covering public surface, stability tiers, typing contract, lazy-import rules, versioning, integration tests, and documentation plan.

Closure plan §18 deliverable. Design-only PR; no code changes.

The document pins down what has been a *de facto* library API for
several minor releases — `from forgelm import ForgeTrainer` works,
but until now nothing told a downstream consumer which symbols are
stable, which are experimental, and which are internal. Phase 19
implements the design without re-deriving any decisions.

12 sections + 16-row implementation plan covering:

- Stable/experimental/internal tier split + per-symbol mapping
- `py.typed` + `mypy --strict` contract for the public surface
- Lazy-import discipline (`import forgelm` MUST NOT pull torch)
  + the regression test that pins it
- Wave 1 round-5 carry-over resolved: `forgelm/cli/__init__.py`
  lazy `__getattr__` + `_SYMBOL_TO_MODULE` pattern, monkeypatch-safe
- Two-segment `__api_version__` separated from `__version__` so
  CLI patch releases do not inflate the library contract version
- Integration test cases Phase 19 ships
  (`tests/test_library_api.py` + `tests/test_cli_lazy_imports.py`)
- Documentation deliverables (reference, guide, notebook, README)
- Cross-cutting: logging hygiene, config-from-dict, error
  propagation at the library boundary, thread/process safety
- Five open questions resolved

Closes: closure-plan-202604300906.md §7 Phase 18
Unblocks: Phase 19 (Library API implementation)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@sourcery-ai

sourcery-ai Bot commented May 2, 2026 •

Copy link
Copy Markdown

Reviewer's Guide

Design-only PR that adds a detailed 480+ line ForgeLM Library API analysis/design document and a matching CHANGELOG entry, defining the future public library surface, stability tiers, lazy-import rules, type-checking contract, API versioning, integration tests, and documentation/tasks for the upcoming implementation phase, without touching any runtime code.

Sequence diagram for planned lazy import of ForgeTrainer via forgelm.getattr

sequenceDiagram
    actor User
    participant Py as PythonImporter
    participant FM as forgelm
    participant TR as forgelm.trainer
    participant TC as torch

    User->>Py: import forgelm
    Py->>FM: load module forgelm
    note over FM: Execute lightweight imports only<br/>Do not import torch or trainer
    Py-->>User: forgelm module object

    User->>FM: access ForgeTrainer
    FM->>FM: __getattr__("ForgeTrainer")
    FM->>TR: import forgelm.trainer
    TR->>TC: import torch (and other heavy deps)
    TR-->>FM: ForgeTrainer class
    FM-->>User: ForgeTrainer reference

    User->>FM: dir(forgelm)
    FM->>FM: __dir__ returns stable and experimental symbols
    FM-->>User: list including ForgeTrainer
Loading

Class diagram for planned ForgeLM library API surface

classDiagram
    direction LR

    class forgelm {
      <<package>>
    }

    class ForgeConfig {
      <<stable>>
      +fields...  
      +validate()  
    }

    class ForgeTrainer {
      <<stable>>
      +ForgeTrainer(config: ForgeConfig)
      +train() TrainResult
    }

    class TrainResult {
      <<stable>>
      +success bool
      +metrics dict
      +artifacts_path str
    }

    class BenchmarkResult {
      <<stable>>
      +task_name str
      +scores dict
    }

    class AuditReport {
      <<stable>>
      +total_samples int
      +issues dict
      +croissant dict
    }

    class VerifyResult {
      <<stable>>
      +valid bool
      +reason str
    }

    class AuditLogger {
      <<stable>>
      +AuditLogger(output_dir: str, run_id: str)
      +log_event(event: str, fields: dict)
    }

    class WebhookNotifier {
      <<experimental>>
      +WebhookNotifier(config: object)
      +notify_event(event: str, payload: dict)
    }

    class ConfigError {
      <<stable>>
      +message str
    }

    class LibraryFunctions {
      <<facade>>
      +load_config(path: str) ForgeConfig
      +prepare_dataset(path: str) object
      +get_model_and_tokenizer(config: ForgeConfig) tuple
      +run_benchmark(config: ForgeConfig) BenchmarkResult
      +audit_dataset(source: str, options: dict) AuditReport
      +verify_audit_log(path: str, require_hmac: bool) VerifyResult
      +detect_pii(text: str) list
      +mask_pii(text: str) str
      +detect_secrets(text: str) list
      +mask_secrets(text: str) str
      +compute_simhash(text: str) int
    }

    class Versioning {
      <<meta>>
      +__version__ str
      +__api_version__ str
    }

    forgelm o-- ForgeConfig
    forgelm o-- ForgeTrainer
    forgelm o-- TrainResult
    forgelm o-- BenchmarkResult
    forgelm o-- AuditReport
    forgelm o-- VerifyResult
    forgelm o-- AuditLogger
    forgelm o-- WebhookNotifier
    forgelm o-- LibraryFunctions
    forgelm o-- Versioning
    forgelm o-- ConfigError

    ForgeTrainer --> ForgeConfig
    ForgeTrainer --> TrainResult
    LibraryFunctions --> ForgeConfig
    LibraryFunctions --> TrainResult
    LibraryFunctions --> BenchmarkResult
    LibraryFunctions --> AuditReport
    LibraryFunctions --> VerifyResult
    AuditLogger --> AuditReport
    Versioning --> forgelm
Loading

Flow diagram for Phase 19 ForgeLM library API implementation tasks

flowchart TD
    A["Start Phase 19<br/>Implement library API"] --> B["Add forgelm/py.typed<br/>(PEP 561 marker)"]
    B --> C["Create forgelm/api.py<br/>with __api_version__"]
    C --> D["Extend forgelm.__init__<br/>__getattr__ and __all__<br/>for new symbols"]
    D --> E["Implement __dir__ for forgelm"]
    E --> F["Port forgelm.cli.__init__<br/>to lazy __getattr__ facade"]
    F --> G["Add tests/test_library_api.py<br/>(stable journeys + lazy imports)"]
    G --> H["Add tests/test_cli_lazy_imports.py"]
    H --> I["Wire mypy --strict CI step<br/>for public surface"]
    I --> J["Write docs/reference/library_api.md"]
    J --> K["Write docs/guides/library_api.md<br/>and example notebook"]
    K --> L["Update README, CHANGELOG,<br/>release standards"]
    L --> M["Run full test + lint matrix<br/>and complete Phase 19"]
Loading

File-Level Changes

Change Details Files
Document the future ForgeLM library API surface, stability tiers, and lazy-import strategy for Phase 19 implementation.
  • Audit current public symbols exposed from forgelm/init.py and enumerate additional desired top-level exports (e.g., audit_dataset, verify_audit_log, AuditLogger, PII/secrets helpers, WebhookNotifier).
  • Define stable vs experimental vs internal API tiers with per-symbol mapping and semver expectations, including deprecation rules.
  • Specify strict lazy-import requirements (import forgelm must not load heavy deps like torch) and standardize on a getattr + dir pattern for package and CLI facades.
docs/analysis/code_reviews/library-api-design-202605021414.md
Define the type-checking, API versioning, integration testing, and documentation contracts for the library API.
  • Set the py.typed + mypy --strict scope for the public surface, including how to type heavy-dependency return types via TYPE_CHECKING and string annotations.
  • Introduce a separate api_version concept (planned for forgelm.api) distinct from version, with rules for when each must be bumped and optional CI checks for breaking changes.
  • Lay out a 9-case integration test suite and a 16-task Phase 19 implementation plan, plus required docs (reference, guides, notebook, README and changelog updates) and cross-cutting rules for logging, config-from-dict, error handling, and concurrency.
docs/analysis/code_reviews/library-api-design-202605021414.md
Record the design in the project CHANGELOG as a Wave 2a / Phase 18 deliverable.
  • Add a changelog entry describing the new Library API analysis & design document, summarizing the decisions about public symbols, lazy-import invariant, typing contract, CLI migration, and required integration suite, and clarifying that this PR is design-only with no code changes.
CHANGELOG.md

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

@coderabbitai

coderabbitai Bot commented May 2, 2026 •

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f2308176-1b56-464a-a2cf-d4c86500976e

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch closure/faz18-library-api-design

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@qodo-code-review

Copy link
Copy Markdown

Review Summary by Qodo

Phase 18: Library API analysis & design document (design-only)

📝 Documentation

Grey Divider

Walkthroughs

Description
• Delivers Phase 18 design document pinning ForgeLM's library API surface
• Establishes three stability tiers (stable/experimental/internal) with per-symbol mapping
• Documents lazy-import invariant ensuring import forgelm does not load torch
• Specifies type-checking contract (py.typed + mypy --strict on public surface)
• Defines 16-row Phase 19 implementation task plan with acceptance criteria
Diagram
flowchart LR
  A["Phase 18<br/>Design Document"] -->|"Pins API surface"| B["Stability Tiers<br/>Stable/Experimental/Internal"]
  A -->|"Enforces lazy imports"| C["Lazy-Import<br/>Invariant"]
  A -->|"Type safety"| D["py.typed +<br/>mypy --strict"]
  A -->|"Versioning"| E["__api_version__<br/>vs __version__"]
  A -->|"Implementation spec"| F["Phase 19<br/>Task Plan"]
  B -->|"9 test cases"| G["Integration Tests"]
  C -->|"Regression test"| G
  D -->|"CI enforcement"| G
Loading

Grey Divider

File Changes

1. CHANGELOG.md 📝 Documentation +16/-0

Document Phase 18 library API design in changelog

• Added "Wave 2a / Phase 18 — Library API design" section documenting the new design deliverable
• Lists all newly exposed public symbols (audit_dataset, verify_audit_log, AuditLogger,
 VerifyResult, AuditReport, WebhookNotifier, PII/secrets utilities)
• Explains the lazy-import invariant and py.typed + mypy --strict contract
• References the 16-row Phase 19 task plan

CHANGELOG.md


2. docs/analysis/code_reviews/library-api-design-202605021414.md 📝 Documentation +480/-0

Library API analysis & design specification document

• New 480-line design document establishing ForgeLM's library API contract
• §1: Audits current public surface (13 symbols via __all__ + __getattr__)
• §2: Defines three stability tiers with per-symbol mapping (13 stable, 7 experimental, internal via
 underscore)
• §3: Specifies py.typed marker + mypy --strict enforcement on public surface only
• §4: Documents lazy-import invariant (import forgelm must not load torch) with __getattr__ pattern
 and Wave 1 round-5 CLI facade migration
• §5: Introduces __api_version__ (two-segment) separate from __version__ for library contract
 tracking
• §6: Enumerates 9 integration test cases covering stable surface end-to-end
• §7: Lists documentation deliverables (reference, guide, notebook, README updates)
• §8: Covers cross-cutting concerns (logging hygiene, config-from-dict, error handling,
 thread/process safety)
• §9: Provides precise 16-row Phase 19 implementation task plan with file changes and acceptance
 criteria
• §10: Resolves 5 open questions (forgelm/api.py existence, auto-generation, mypy scope,
 __api_version__ automation, WebhookNotifier tier)
• §11: Explicitly lists out-of-scope items with rationale
• §12: Sign-off checklist confirming all Phase 18 acceptance criteria met

docs/analysis/code_reviews/library-api-design-202605021414.md


Grey Divider

Qodo Logo

@qodo-code-review

qodo-code-review Bot commented May 2, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (1) 📎 Requirement gaps (0)

Grey Divider


Action required

1. Lazy-import test misstated ☑ 🐞 Bug ≡ Correctness
Description
The design doc states tests/test_lazy_imports.py asserts import forgelm does not import torch,
but the current test file only checks import forgelm.trainer and import forgelm.model. This can
mislead Phase 19 implementers about which invariant is already pinned by tests and what must remain
green.
Code

docs/analysis/code_reviews/library-api-design-202605021414.md[R144-156]

+### 4.1 The invariant: `import forgelm` does not load torch
+
+The single most important lazy-import rule:
+
+> Importing `forgelm` (or any of its public re-export modules) **must not** import `torch`, `transformers`, `trl`, `datasets`, or any other heavy ML dep.  Heavy deps are loaded only when the consumer accesses a symbol that genuinely needs them.
+
+This rule exists because:
+- CI runners that only check our config (`ruff`, the lightweight `pytest` subset) don't need `torch`.
+- The CLI entry point `python -m forgelm.cli --help` must respond instantly.
+- `forgelm doctor` (Phase 34) is meant to run on a brand-new machine *before* `torch` is even installed, so it cannot crash on `import forgelm`.
+
+There is a regression test that pins this: `tests/test_lazy_imports.py` runs `python -c "import forgelm; assert 'torch' not in sys.modules"` in a subprocess.  Phase 19 keeps that test green.
+
Evidence
The new doc claims an import forgelm lazy-import subprocess assertion, but the existing regression
tests only cover submodule imports (forgelm.trainer / forgelm.model) and never import the
package root in the subprocess checks.

docs/analysis/code_reviews/library-api-design-202605021414.md[144-156]
tests/test_lazy_imports.py[19-42]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`docs/analysis/code_reviews/library-api-design-202605021414.md` claims the existing regression test `tests/test_lazy_imports.py` runs a subprocess that imports the package root (`import forgelm`) and asserts `torch` is not loaded. In the current repo, that test module instead checks `import forgelm.trainer` and `import forgelm.model`.
### Issue Context
This doc is the Phase 18 deliverable and is intended to be the Phase 19 implementation spec. It should accurately describe what is already enforced by tests vs what Phase 19 must add.
### Fix Focus Areas
- docs/analysis/code_reviews/library-api-design-202605021414.md[144-156]
- tests/test_lazy_imports.py[19-42]
### Expected change
Update the design doc text to match the current test behavior (trainer/model), or explicitly call out that Phase 19 must add a new `import forgelm` lazy-import regression test (and adjust the wording accordingly).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. verify_audit_log spec mismatch ☑ 🐞 Bug ≡ Correctness
Description
The design doc defines the stable verify_audit_log signature without the existing hmac_secret
parameter and also documents different unreadable-file behavior than the current implementation. If
Phase 19 follows the doc as written, it risks an API/behavior breaking change for existing callers
of forgelm.compliance.verify_audit_log.
Code

docs/analysis/code_reviews/library-api-design-202605021414.md[R68-72]

+| `forgelm.load_config(path: str) -> ForgeConfig` | YAML loader, used in every CLI invocation. |
+| `forgelm.audit_dataset(source: str, **kwargs) -> AuditReport` | Called from notebooks + CI gates. |
+| `forgelm.verify_audit_log(path: str, *, require_hmac: bool = False) -> VerifyResult` | CI gate; `VerifyResult` is part of the contract. |
+| `forgelm.AuditLogger(output_dir: str, run_id: str = None)` | Used by integrators to emit Article 12 records from their own pipelines. |
+| `forgelm.AuditLogger.log_event(event: str, **fields)` | Append-only contract. |
Evidence
In the design doc, the stable-tier signature omits hmac_secret. In the actual code,
verify_audit_log already accepts hmac_secret, and read failures are converted into a failing
VerifyResult rather than raising OSError, conflicting with the doc’s stated contract.

docs/analysis/code_reviews/library-api-design-202605021414.md[68-72]
docs/analysis/code_reviews/library-api-design-202605021414.md[366-371]
forgelm/compliance.py[1233-1239]
forgelm/compliance.py[1204-1218]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The Phase 18 design document specifies a stable API contract for `verify_audit_log` that does not match the current implementation:
- Doc signature omits `hmac_secret`.
- Doc says unreadable files raise `OSError`, but current code returns `VerifyResult(valid=False, reason=...)` for read errors.
### Issue Context
Since this document is meant to be the implementation spec for Phase 19, discrepancies here can cause Phase 19 to unintentionally introduce breaking changes.
### Fix Focus Areas
- docs/analysis/code_reviews/library-api-design-202605021414.md[68-72]
- docs/analysis/code_reviews/library-api-design-202605021414.md[366-371]
- forgelm/compliance.py[1204-1218]
- forgelm/compliance.py[1233-1239]
### Expected change
Update the design doc to either:
1) Match the current `verify_audit_log` signature and failure behavior, OR
2) Explicitly state that Phase 19 will change the signature/exception behavior (and treat that as a deliberate contract change with clear migration notes).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

3. py.typed status contradicts later 📘 Rule violation ⚙ Maintainability
Description
The new design doc states that ForgeLM already ships forgelm/py.typed, but later lists adding
forgelm/py.typed as a Phase 19 task. This inconsistency can mislead implementers and creates
documentation drift about the current code/package state.
Code

docs/analysis/code_reviews/library-api-design-202605021414.md[R104-106]

+## 3. Type contract — `py.typed` + PEP 561
+
+ForgeLM ships `forgelm/py.typed` (zero-byte marker) so installers / `mypy` / IDEs know to use the inline type hints.  Without the marker, downstream `mypy --strict` runs treat every `forgelm.*` import as `Any` and lose all signal.
Evidence
PR Compliance ID 9 requires documentation claims to match reality; the same document contains
conflicting statements about whether forgelm/py.typed already exists vs. is a future task.

CLAUDE.md
docs/analysis/code_reviews/library-api-design-202605021414.md[104-107]
docs/analysis/code_reviews/library-api-design-202605021414.md[393-396]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The design document contradicts itself about `forgelm/py.typed` (claims it already ships it, but later treats it as a Phase 19 addition), creating documentation drift.
## Issue Context
This PR is described as design-only; the document should clearly distinguish current state vs. Phase 19 planned work.
## Fix Focus Areas
- docs/analysis/code_reviews/library-api-design-202605021414.md[104-107]
- docs/analysis/code_reviews/library-api-design-202605021414.md[393-396]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


4. compute_minhash export inconsistent 🐞 Bug ⚙ Maintainability
Description
The design doc lists compute_minhash as an experimental symbol, but the Phase 19 implementation
task list and the __getattr__ sketch omit it, and the CHANGELOG blurb only enumerates
compute_simhash. This creates ambiguity about whether Phase 19 must add forgelm.compute_minhash
at the package root.
Code

docs/analysis/code_reviews/library-api-design-202605021414.md[R389-399]

+## 9. Phase 19 task plan (what implementation actually does)
+
+This section is the implementation spec for the next phase.  It is intentionally precise so a Phase 19 contributor doesn't have to re-derive any of the above.
+
+| # | Task | Files | Acceptance |
+|---|---|---|---|
+| 1 | Add `forgelm/py.typed` (zero-byte marker) | `forgelm/py.typed` (new) | `pip install . && python -c "import forgelm; help(forgelm)"` shows hints. |
+| 2 | Create `forgelm/api.py` with `__api_version__ = "0.5"` | `forgelm/api.py` (new) | `from forgelm.api import __api_version__` works. |
+| 3 | Extend `forgelm/__init__.py` `__getattr__` with the §4.2 entries (`audit_dataset`, `verify_audit_log`, `AuditLogger`, `VerifyResult`, `AuditReport`, `WebhookNotifier`, `detect_pii`, `mask_pii`, `detect_secrets`, `mask_secrets`, `compute_simhash`) | `forgelm/__init__.py` | Each name is reachable; `import forgelm` does not import torch (see §4.1 regression test). |
+| 4 | Add `__all__` entries for every name introduced in §3 | `forgelm/__init__.py` | `dir(forgelm)` lists them. |
+| 5 | Add `__dir__()` returning sorted `__all__` | `forgelm/__init__.py` | IDE auto-complete works on a fresh import. |
Evidence
compute_minhash exists and is publicly exported from forgelm.data_audit today, and the design
doc acknowledges it as a symbol worth exposing. However, the doc’s own implementation sketch/task
plan only includes compute_simhash, and the new CHANGELOG entry’s symbol enumeration also omits
compute_minhash, so the spec is internally inconsistent.

docs/analysis/code_reviews/library-api-design-202605021414.md[84-92]
docs/analysis/code_reviews/library-api-design-202605021414.md[161-197]
docs/analysis/code_reviews/library-api-design-202605021414.md[393-399]
CHANGELOG.md[16-30]
forgelm/data_audit/init.py[54-60]
forgelm/data_audit/init.py[144-170]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The design doc is inconsistent about whether `compute_minhash` should be re-exported at the package root (`forgelm.compute_minhash`): it’s listed as experimental, but omitted from both the `__getattr__` example and Phase 19 task list, and it’s not included in the new CHANGELOG symbol enumeration.
### Issue Context
`compute_minhash` already exists and is exported under `forgelm.data_audit`. The design doc should clearly decide whether Phase 19 also re-exports it from `forgelm`.
### Fix Focus Areas
- docs/analysis/code_reviews/library-api-design-202605021414.md[84-92]
- docs/analysis/code_reviews/library-api-design-202605021414.md[161-197]
- docs/analysis/code_reviews/library-api-design-202605021414.md[393-399]
- CHANGELOG.md[16-30]
- forgelm/data_audit/__init__.py[54-60]
### Expected change
Either add `compute_minhash` everywhere it should appear (utility list, `__getattr__` sketch, Phase 19 task #3, CHANGELOG blurb), or remove it from the experimental list if it is not intended to be part of the package-root surface.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. Wrong missing-extra exception 🐞 Bug ≡ Correctness
Description
The design doc states audit_dataset() raises OptionalDependencyError for missing extras, but the
current audit implementation raises plain ImportError for missing optional backends (e.g.,
MinHash/datasketch). If Phase 19 intends to standardize on OptionalDependencyError, that should be
explicitly called out as a behavior change in the spec.
Code

docs/analysis/code_reviews/library-api-design-202605021414.md[R366-371]

+| Library symbol | Errors propagated as |
+|---|---|
+| `ForgeTrainer.train()` | `ConfigError` (validation), `RuntimeError` (CUDA / training-loop), `OSError` (I/O), all preserving original traceback. |
+| `audit_dataset()` | `ValueError` (invalid args), `OSError` (I/O), `OptionalDependencyError` (missing extra). |
+| `verify_audit_log()` | Returns `VerifyResult(valid=False, reason=...)` for chain failures (not an exception); raises `OSError` only for unreadable files. |
+| `AuditLogger.log_event()` | `OSError` on write failure (caller decides whether to retry or abort). |
Evidence
The doc’s library-boundary error table names OptionalDependencyError for audit_dataset() missing
extras, but forgelm.data_audit currently enforces missing MinHash support by raising ImportError
directly in _require_datasketch().

docs/analysis/code_reviews/library-api-design-202605021414.md[366-371]
forgelm/data_audit/_minhash.py[18-23]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The Phase 18 design doc specifies `audit_dataset()` propagates `OptionalDependencyError` when optional extras are missing, but the current `forgelm.data_audit` implementation raises `ImportError` for at least the MinHash backend.
### Issue Context
This matters for the stable library contract: callers may already be catching `ImportError`. If Phase 19 changes this to `OptionalDependencyError`, it’s a behavioral change that should be deliberate and documented.
### Fix Focus Areas
- docs/analysis/code_reviews/library-api-design-202605021414.md[366-371]
- forgelm/data_audit/_minhash.py[18-23]
### Expected change
Update the design doc to match current behavior (`ImportError`), or add an explicit Phase 19 task to introduce/standardize `OptionalDependencyError` for data-audit optional deps (including any necessary wrapping at the library boundary).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

@sourcery-ai sourcery-ai 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.

Hey - I've found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="CHANGELOG.md" line_range="27-29" />
<code_context>
+  `AuditReport`, `WebhookNotifier`, `detect_pii` / `mask_pii` / `detect_secrets` /
+  `mask_secrets` / `compute_simhash`); spells out the `py.typed` + `mypy --strict`
+  contract for the public surface; resolves the Wave 1 round-5 carry-over
+  (lazy `__getattr__` migration for `forgelm/cli/__init__.py`); spec'es the
+  `tests/test_library_api.py` integration suite Phase 19 must ship.  No code
+  changes in this PR — design only; Phase 19 implements.
</code_context>
<issue_to_address>
**issue (typo):** The word "spec'es" looks like a typo; consider using "specs" or "specifies" instead.

Using a standard term here (e.g., “specifies” or “specs”) will read more clearly than the nonstandard contraction “spec’es,” which comes across as a typo.

```suggestion
  contract for the public surface; resolves the Wave 1 round-5 carry-over
  (lazy `__getattr__` migration for `forgelm/cli/__init__.py`); specifies the
  `tests/test_library_api.py` integration suite Phase 19 must ship.  No code
```
</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 CHANGELOG.md
@codacy-production

codacy-production Bot commented May 2, 2026 •

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request introduces a comprehensive design document for the ForgeLM Library API, establishing stability tiers, a strict type contract, and a lazy-import discipline to ensure heavy ML dependencies are only loaded when needed. Feedback focuses on improving the implementation of the lazy-import mechanism by suggesting dictionary-based lookups and caching to enhance performance and maintainability. Additionally, it was noted that explicit type declarations within TYPE_CHECKING blocks are necessary to ensure downstream mypy --strict compatibility for lazy-loaded symbols.


| Surface | Type-hint coverage required |
|---|---|
| Stable symbols (§2.1) | 100% — every parameter + return. `mypy --strict forgelm/__init__.py` clean. |

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

To achieve the goal of mypy --strict cleanliness for downstream consumers, the design should explicitly require that all lazy-loaded symbols are declared in forgelm/__init__.py using a if TYPE_CHECKING: block. Without these declarations, mypy will not be able to resolve the types of attributes accessed via __getattr__, leading to Any types or errors in consumer code.

Comment on lines +162 to +197
def __getattr__(name: str):
if name == "audit_dataset":
from .data_audit import audit_dataset as _v
return _v
if name == "verify_audit_log":
from .compliance import verify_audit_log as _v
return _v
if name == "AuditLogger":
from .compliance import AuditLogger as _v
return _v
if name == "VerifyResult":
from .compliance import VerifyResult as _v
return _v
if name == "AuditReport":
from .data_audit import AuditReport as _v
return _v
if name == "WebhookNotifier":
from .webhook import WebhookNotifier as _v
return _v
if name == "detect_pii":
from .data_audit import detect_pii as _v
return _v
if name == "mask_pii":
from .data_audit import mask_pii as _v
return _v
if name == "detect_secrets":
from .data_audit import detect_secrets as _v
return _v
if name == "mask_secrets":
from .data_audit import mask_secrets as _v
return _v
if name == "compute_simhash":
from .data_audit import compute_simhash as _v
return _v
# ... existing entries (ForgeTrainer, get_model_and_tokenizer, ...) ...
raise AttributeError(name)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

This if/elif chain is repetitive and less maintainable than the dictionary-based lookup proposed for the CLI in Section 4.3. Additionally, the implementation lacks caching; every access will re-trigger the import logic. It is recommended to refactor this to use a mapping dictionary and ensure that resolved symbols are set on the module (e.g., via globals()[name] = value) to optimize subsequent accesses.

Comment on lines +224 to +230
def __getattr__(name: str):
target_module = _SYMBOL_TO_MODULE.get(name)
if target_module is None:
raise AttributeError(f"module 'forgelm.cli' has no attribute {name!r}")
import importlib
submodule = importlib.import_module(target_module, package=__name__)
return getattr(submodule, name)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The implementation of __getattr__ should cache the resolved attribute in the module's namespace. As noted in the "monkeypatch resolution" section (line 236), __getattr__ is only bypassed once the name is set on the module. The current sketch is missing the assignment step that would enable this bypass and improve performance for subsequent accesses.

Suggested change
def __getattr__(name: str):
target_module = _SYMBOL_TO_MODULE.get(name)
if target_module is None:
raise AttributeError(f"module 'forgelm.cli' has no attribute {name!r}")
import importlib
submodule = importlib.import_module(target_module, package=__name__)
return getattr(submodule, name)
def __getattr__(name: str):
target_module = _SYMBOL_TO_MODULE.get(name)
if target_module is None:
raise AttributeError(f"module 'forgelm.cli' has no attribute {name!r}")
import importlib
submodule = importlib.import_module(target_module, package=__name__)
value = getattr(submodule, name)
globals()[name] = value
return value

Comment thread docs/analysis/code_reviews/library-api-design-202605021414.md
Comment thread docs/analysis/code_reviews/library-api-design-202605021414.md
Closes the three HIGH findings from the multi-agent review and the
six MEDIUMs that have a clean doc-only fix:

- F-23-01 (HIGH): rewrote §4.1 ¶2 to acknowledge the existing
  test_lazy_imports.py only covers forgelm.trainer / forgelm.model;
  bare `import forgelm` is verified manually today and Phase 19 task
  #7 closes the gap with a real subprocess regression test.
- F-23-02 (HIGH): added a TYPE_CHECKING import block to the §4.2
  __getattr__ sketch so mypy --strict sees real signatures (not Any),
  and replaced the if/elif chain with a _LAZY_SYMBOLS dict + caching
  via globals()[name] = value (gemini-code-assist comments addressed
  in the same edit: maintainability + per-access caching).
- F-23-03 (HIGH): replaced the "raise AttributeError on forgelm._http"
  test with a stronger and import-side-effect-immune assertion
  ("_http" not in dir(forgelm) and not in __all__).  Documented in §2.3
  that internal = absent from public surface, not import-protected.
- F-23-04 (MEDIUM): §12 sign-off note honestly acknowledges that no
  existing test imports the new stable names and Phase 19 task #7 ships
  the coverage; the table claim was aspirational.
- F-23-05 (MEDIUM): §4.3 reframes the cli/__init__.py lazy migration as
  hygiene, not perf; explicitly notes _dispatch eagerly imports six
  submodules so a real CLI invocation defeats the laziness anyway.
- F-23-06 (MEDIUM): §5.2 names the CI trigger (release-* branch push)
  and explicitly documents the script as a no-op for v0.5.5 because
  there is no prior __api_version__-bearing baseline.
- F-23-07 (MEDIUM): added a tier-completeness audit table covering
  every top-level module + sub-package (Stable / Experimental /
  Internal) so reach-ins to forgelm.safety / forgelm.judge /
  forgelm.ingestion are not silently load-bearing.
- F-23-08 (MEDIUM): replaced the §2.1 audit_dataset signature
  (`**kwargs` placeholder) and verify_audit_log signature (missing
  hmac_secret) with the real signatures audited from
  forgelm/data_audit/_orchestrator.py and forgelm/compliance.py.
- BOT (sourcery): "spec'es" typo in CHANGELOG.md replaced with
  "specifies".

Reviewer recommendation said "pre-Phase-19 must land before Phase 19
starts"; that's exactly what this commit does — design + Phase 19
implementation plan are now consistent with the audited live code.

Closes (review IDs): F-23-01 .. F-23-08 + sourcery-typo

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@sonarqubecloud

sonarqubecloud Bot commented May 2, 2026

Copy link
Copy Markdown

cemililik added a commit that referenced this pull request May 2, 2026
Closes the three CRITICAL findings + four HIGH findings from the
multi-agent review and the bot comments that have a clean doc-only fix.

CRITICAL fixes:

- F-24-01 (CRITICAL): added §3.1 "Prior-state acknowledgement" that
  resolves the staging_ttl_days collision with the already-shipped
  EvaluationConfig.staging_ttl_days. Phase 21 deprecates the
  evaluation field via DeprecationWarning + alias-forward to the new
  retention.staging_ttl_days; removal targeted v0.7.0 per
  docs/standards/release.md cadence.
- F-24-02 (CRITICAL): same §3.1 entry resolves the closure-plan
  §15.5 GH-23 conflict between top-level retention.* (this design)
  and ingestion.retention.raw_documents.ttl_days (legacy GDPR docs).
  Top-level retention.* wins; Phase 21 updates the user-manual
  gdpr.md page in the same PR; closure-plan GH-23 entry will be
  amended when Phase 21 ships.
- F-24-03 (CRITICAL): added §5.4 "Personal-data minimisation across
  every audit-event field" — per-field classification + hash policy
  for category-3 (personal-data-bearing) fields. target_id in row
  mode is hashed via SHA-256(salt + value); error_message is routed
  through the existing _http._mask_secrets_in_text + PII regex mask
  helpers; everything else is genuinely operational. Phase 21 ships
  test_target_id_hashed_in_audit_event regression.

HIGH fixes:

- F-24-04 (HIGH): §4.2 flag table revised — line-number fallback for
  --row-id REFUSED; directory mode REFUSED; multi-row matches in a
  single file aborted unless --row-matches=all opt-in; new --yes flag
  required for unattended use (interactive without --yes prompts on
  TTY, exits EXIT_CONFIG_ERROR on non-TTY).
- F-24-05 (HIGH): §4.3 exit codes aligned with the project-wide 0/1/2/3/4
  contract; --check-policy returns code 3 (EXIT_EVAL_FAILURE) when
  violations exist (was: always 0); code 4 (EXIT_AWAITING_APPROVAL)
  for the interactive-confirm path.
- F-24-06 (HIGH): §4.4 atomicity rewritten with strict event ordering
  (data.erasure_requested FIRST before any deletion, then rewrite,
  then data.erasure_completed). Step-5 and step-6 failure recovery
  paths spelled out with named regression test
  test_partial_commit_emits_failed_event.
- F-24-08 (HIGH): §6 marketing-claim revision now carries an explicit
  scope-limitation paragraph naming what the operator remains
  responsible for (replicas, downstream consumers, webhook receivers,
  legal-hold copies). New data.erasure_warning_external_copies event
  fires when a webhook target is configured.

MEDIUM fixes (selected high-value):

- F-24-09 (MEDIUM): §3.2 audit_log_retention_days docstring rewritten
  with accurate Article 12(2) (6-month floor) + Article 11 (10-year
  high-risk) + GDPR Article 30 (≥3y) reasoning; default 1825d
  justified as middle-of-the-road horizon.
- F-24-11 (MEDIUM): §5.1 audit-event catalog grew from 3 to 6 events —
  data.erasure_warning_memorisation, _warning_synthetic_data_present,
  _warning_external_copies are now properly specified (previously
  referenced 3× without spec).

BOT fixes (gemini, qodo, sourcery):

- gemini-mtime: §3.3 new section "mtime caveat (and the in-band age
  field)" — retention check prefers the audit-log timestamp where
  available (HMAC-signed, can't be touch'd), falls back to mtime with
  age_source notation in the JSON output.
- qodo-line-count: §12 sign-off updated with actual line count (445
  vs claimed ≈460); v2 additions enumerated.
- sourcery-typo: "spec'es" → "specifies" in design intro + CHANGELOG.

DEFERRED (not in this PR):

- F-24-07 (HIGH) full §8 file-map enumeration (16 → 20 files): folded
  into Phase 21 implementation PR description per its closer scope.
- F-24-10 (MEDIUM) acquire_run_lock spec + Windows fallback: still
  Phase 22 ISO/SOC 2 design scope per closure-plan §15.5 Round-5
  carry-over; Phase 20 references it but does not specify it.
- F-24-12 (MEDIUM) TR mirror file map: Phase 30 final doc sweep
  scope; Phase 21 ships EN side first.
- F-24-13 (MEDIUM) distributed-corpus deletion: explicitly out of
  scope per §11; already documented.
- F-24-14 (MEDIUM): --yes flag now in §4.2 table; no-tty behaviour
  spelled out.

Closes (review IDs): F-24-01..06, 08, 09, 11 + gemini-mtime,
qodo-line-count, sourcery-typo

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@cemililik

Copy link
Copy Markdown
Collaborator Author

Wave 2a Round-1 multi-agent review — fixes pushed

All CRITICAL + HIGH findings from the multi-agent review (and the relevant inline bot comments) have been addressed in the latest commit on this branch. See the commit message for the per-finding mapping (review IDs F-23-*, gemini bot IDs, qodo bot IDs, sourcery bot IDs).

Per-PR test counts:

ruff format/check clean across all five PRs. Cross-PR XPR-01 concern (audit-log parser proliferation) closed in PR #25 via the new forgelm/cli/subcommands/_audit_log_reader.py shared module that _approve.py and _approvals.py now both delegate to (Phase 21 forgelm purge will use the same module).

Items deferred to follow-up PRs (with rationale) are listed under DEFERRED in each commit message.

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 2, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

@cemililik Thanks for the detailed Wave 2a Round-1 summary! Noted on the per-PR test counts and the XPR-01 resolution via the new shared _audit_log_reader.py module. Kicking off a fresh review of PR #23 now to verify the design document updates against the addressed findings.

[review]

@cemililik

Copy link
Copy Markdown
Collaborator Author

Superseded by #28 (consolidated Wave 2a integration). All commits from this branch are merged into closure/wave2a-integration and the Wave-2a Round-1 review fixes ride on top there. Closing without merge to keep history single-stream.

@cemililik cemililik closed this May 4, 2026
cemililik added a commit that referenced this pull request May 4, 2026
Wave 2a: Phase 17/18/20/34/37 — consolidated integration (supersedes #23-#27)
@cemililik
cemililik deleted the closure/faz18-library-api-design branch May 10, 2026 11:35
cemililik added a commit that referenced this pull request Jun 15, 2026
…PR#63-review)

verify_integrity iterated manifest['artifacts'] unchecked: null/int raised an uncaught TypeError (traceback, not exit 1) and str/dict false-passed as 'All 0 artifacts present' (exit 0). Add an explicit container guard returning valid=False (exit 1) rather than CodeRabbit's silent coercion. CodeRabbit #23.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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