Component map¶
This page is for someone joining the project, reviewing a proposed fork, or trying to answer “where does this conclusion come from?” It maps the product by responsibility rather than by directory listing.
One scan, end to end¶
CLI / UI entrypoint
→ auth context
→ collection plan
→ collectors
→ normalized evidence
→ evaluators
→ quality / uncertainty policy
→ rollup + ranking
→ report view model
→ HTML / JSON / Markdown
The system is local-first and read-only. A live scan uses the operator's credentials to read Microsoft APIs. A dry-run uses deterministic fixtures. The report is an artifact written to disk; there is no hosted control plane in this repository.
Runtime layers¶
| Layer | Responsibility | Primary locations |
|---|---|---|
| CLI | Parses commands, flags, output paths, exit codes, and interactive prompts. | src/licenselens/cli.py, src/licenselens/ui/ |
| Authentication | Builds device-code, client-secret, Azure CLI, OIDC, and dry-run contexts without embedding secrets in reports. | src/licenselens/auth.py, src/licenselens/graph.py |
| Planning | Resolves profiles, workloads, packs, tiers, backend preferences, and required evidence. | src/licenselens/engine/planner.py, src/licenselens/engine/runtime_specs.py |
| Collection | Performs read-only Graph, ARM, MDE, DNS, Secure Score, and PowerShell-bridge reads. | src/licenselens/collectors/, src/licenselens/engine/runner_collect.py |
| Evidence registry | Declares source keys, permissions, defaults, collector bindings, and runtime dependencies. | src/licenselens/engine/_registry_source_meta.py, src/licenselens/engine/_registry_defaults.py, src/licenselens/collectors/bindings/ |
| Evaluation | Applies pure, deterministic check predicates to normalized evidence. | src/licenselens/evaluators/, src/licenselens/engine/evaluate.py |
| Quality policy | Demotes incomplete, proxy, manual, truncated, failed, or otherwise insufficient evidence so it cannot become false certainty. | src/licenselens/engine/quality.py, src/licenselens/engine/limitations_policy.py, src/licenselens/models.py |
| Rollup and ranking | Separates entitlement, assessment completeness, implementation state, headline percentage, and prioritized moves. | src/licenselens/engine/rollup.py, src/licenselens/engine/rank.py |
| View model | Converts ScanResult into stable presentation payloads: posture, capabilities, moves, findings, detection realization, provenance. |
src/licenselens/report/viewmodel.py |
| Renderers | Produce offline HTML, bundle assets, JSON, Markdown, and optional ZIP output. | src/licenselens/report/, templates/report.html.j2, templates/report_app/ |
| Catalog | Defines entitlements, capabilities, checks, profiles, telemetry expectations, and flagship metadata. | catalog/, checks/ |
Data contracts to preserve¶
ScanResult¶
The portable result object is the boundary between scanning and reporting. It contains tenant-safe identity/provenance, owned capabilities, findings, capability outcomes, warnings, limitations, packs, moves, and rollup data.
If a new field is needed, prefer an additive schema change. Do not make the renderer infer security state from raw collector payloads.
Finding¶
A finding is one check's conclusion. Important fields include:
check_id— stable identity; never rename casually.status—gap,partial,ok,not_licensed,error, orskipped.evaluation_mode— direct, proxy, manual, or fallback mode.confidenceandlimitations— what the evidence permits.evidenceanddata_sources— inspectable support.- customer-facing title, summary, remediation, next step, and admin link.
A finding is not proof of effectiveness. A direct configuration read can support a narrower claim than “the control is effective.”
Capability outcome and rollup¶
Capability outcomes connect owned entitlements to a capability-level state. The rollup is intentionally not a raw finding count. It keeps:
The headline percentage is generated from the rollup and its sentence, not hardcoded in a template.
Report responsibilities¶
The report should remain a presentation of the result, not a second assessment engine.
src/licenselens/report/viewmodel.pydecides which customer-safe values are available.templates/report.html.j2is the single-file renderer.templates/report_app/entry.html.j2is the bundled renderer entrypoint.templates/report/v2/contains shared macros and partials for moves, capabilities, detection realization, finding belief blocks, and technical reference.templates/report/v2/_v2_styles.css.j2is the shared single-file foundation.templates/report_app/v2/app.cssis the bundle stylesheet.templates/report_app/v2/app.jsowns client-side filtering, sorting, pagination, exports, and capability/finding cross-filtering.
The narrative order is:
- posture and implication;
- prioritized decisions;
- capability/entitlement context;
- finding evidence;
- technical reference and provenance.
If a new detail does not answer one of those reader questions, it belongs in technical disclosure or documentation, not in the main reading path.
Adding a check safely¶
A new check crosses more seams than its YAML file suggests:
- entitlement/capability mapping;
- check definition and expected state;
- collector/source metadata;
- runtime dependency and binding;
- evaluator implementation;
- uncertainty/quality behavior;
- reference and permission documentation;
- positive, negative, missing, API-error, and relevant truncation tests;
- generated reference docs and demo/sample anchors;
- report/action-plan wording.
Follow Adding a check and run the validators named in maturity-goal.md. Do not begin by adding a card to the report.
Debugging a surprising result¶
Trace one check_id through the layers:
report row
→ viewmodel `_finding_entry`
→ `ScanResult.findings[]`
→ evaluator binding
→ evaluator
→ normalized evidence key
→ collector
→ API/backend response
For a capability headline, trace:
headline sentence
→ `CapabilityRollup.realized_sentence`
→ `engine/rollup.py`
→ capability outcomes
→ findings + entitlement state
The right fix is usually at the first layer where the evidence becomes ambiguous, not in the final template.
Verification commands¶
uv run pytest -q -m "not browser"
uv run ruff check src tests scripts
uv run ruff format --check src tests scripts
uv run python scripts/validate_flagship_tests.py
uv run python scripts/validate_flagship_meta.py
uv run python scripts/validate_coverage_manifest.py
uv run python scripts/validate_sku_catalog.py
uv run mkdocs build --strict
For report changes, also run the browser suite and regenerate the committed sample assets when the repository's design gate requires it.