Skip to content

feat: extract the assessment into a shared, headless surface (4.1.0) - #14

Merged
Ali Bahaloo (alibahaloo) merged 1 commit into
mainfrom
chore/shared-assessment-engine
Jul 27, 2026
Merged

feat: extract the assessment into a shared, headless surface (4.1.0)#14
Ali Bahaloo (alibahaloo) merged 1 commit into
mainfrom
chore/shared-assessment-engine

Conversation

@alibahaloo

Copy link
Copy Markdown
Collaborator

Why

The verdict rules, the Cisco client and the run orchestration are currently reachable only through analyzer.run() — which resolves credentials from the per-user config file, discovers an inventory in the working folder, prints to a terminal, writes an HTML file, and exits the process.

That makes the assessment unusable by anything that isn't a terminal. A second consumer would have to reimplement the rules instead, and a reimplementation of Constitution IV is a second, divergent answer about the same firewall — the one outcome this tool exists to prevent.

This is the prerequisite for caia-web, which will depend on this package at a pinned tag and import the assessment rather than fork it.

What changed

New — caia/assessment.py. analyze(inventory, client) is the whole check with no user interface attached. It takes a list of per-device dicts (whatever produced them) plus a psirt.Client the caller owns. It prints nothing, writes nothing, never exits the process, and never consults the working folder. _load_catalogues, _run_checks and _assemble_devices moved here unchanged.

Together with matching, psirt and inventory, this is now the package's documented shared surface — see the updated table in the README's maintainers section.

Changed — caia/analyzer.py. Keeps the terminal run flow and supplies its output through the new on_plan and on_progress callbacks. The printed run is byte-identical to before.

Changed — caia/inventory.py. No longer calls sys.exit when openpyxl is missing; raises ImportError instead. A library must not terminate its host process on import.

Two properties worth reviewing closely

Both are about not letting an access failure look like a clean or merely-unassessable fleet:

  • A catalogue fetch that fails raises, rather than returning an inventory of Needs review. With empty catalogues every device would resolve to MODEL_UNMAPPED — a report that reads as a data-quality problem when the real cause was a rejected credential. analyzer.run() catches and dies exactly as it did before.
  • A failure once the catalogues are in hand returns partial_cause with every answer already obtained. A run is never thrown away, and devices never asked about are Needs review, never Not impacted.

Also

analyze() accepts pre-built catalogues, so a long-running consumer can hold them rather than spend requests re-fetching what never changes. Not used by the CLI.

Behaviour

No change for caia users: same prompts, same progress output, same report, same exit codes.

Tests

  • 246 existing tests pass untouchedtest_api_flow already covers the engine end-to-end through run(), so it is the regression gate for this refactor.
  • 25 added in tests/test_assessment.py for the library path: an in-memory inventory, callbacks, supplied catalogues, both failure modes, input not mutated, and nothing printed.
Ran 271 tests in 0.187s
OK

Smoke-tested against a real 70-device inventory: loads 70 devices across 3 families, collapsing to 33 distinct questions.

Version

VERSION4.1.0. Minor, not patch: this adds public API. Tag 4.1.0 after merge so caia-web has something to pin.

🤖 Generated with Claude Code

The verdict rules, the Cisco client and the run orchestration were reachable
only through `analyzer.run()`, which resolves credentials from the per-user
config file, discovers an inventory in the working folder, prints to a
terminal, writes an HTML file and exits the process. A second consumer could
not call the assessment without inheriting all of that, so it would have had
to reimplement the rules — and a reimplementation of Constitution IV is a
second, divergent answer about the same firewall.

Add `caia/assessment.py` holding `analyze(inventory, client)`: the whole check
with no user interface attached. It takes a list of per-device dicts (whatever
produced them) and a `psirt.Client` the caller owns; it prints nothing, writes
nothing, never exits the process and never consults the working folder.
`_load_catalogues`, `_run_checks` and `_assemble_devices` move there unchanged.

Together with `matching`, `psirt` and `inventory`, this is now the package's
documented shared surface. `analyzer` keeps the terminal run flow and supplies
its output through the new `on_plan` and `on_progress` callbacks, so the
printed run is byte-identical to before.

Two properties are deliberate and covered by tests:

- A catalogue fetch that fails raises, rather than returning an inventory of
  Needs review. Access failure must not read as a data-quality problem.
- A failure once the catalogues are in hand still returns `partial_cause` with
  every answer already obtained, so a run is never thrown away.

`analyze` also accepts pre-built catalogues, so a long-running consumer can
hold them rather than spend requests re-fetching what never changes.

`inventory` no longer calls `sys.exit` when openpyxl is missing; it raises
ImportError. A library must not terminate its host process on import.

No behaviour change for `caia` users: same prompts, same output, same report,
same exit codes. 246 existing tests pass untouched; 25 added for the library
path.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@alibahaloo Ali Bahaloo (alibahaloo) added the enhancement New feature or request label Jul 27, 2026
@alibahaloo
Ali Bahaloo (alibahaloo) merged commit 3992488 into main Jul 27, 2026
2 checks passed
@alibahaloo
Ali Bahaloo (alibahaloo) deleted the chore/shared-assessment-engine branch July 27, 2026 19:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant