symfonic.diagnostics.inspection¶
inspection ¶
Offline migration inspection (T4.3.4, REQ-S4.3).
symfonic doctor audits a running configuration: it constructs an agent
and asks the process about itself. This package answers the questions a
migration asks instead, and answers them from text alone -- raw configuration,
a directory of source, and a mapping of environment variables.
Nine questions, one pass:
============================ =================================================
legacy-configuration which supplied keys are legacy spellings, and the
native target each one feeds
legacy-pin whether a legacy generation override is declared,
and whether this release honors or rejects it
capability-conflict which cross-capability rules the configuration
breaks -- reported, not raised
missing-extra which optional distributions the configuration
commits to that this install lacks
lifecycle-risk which state a restart, a replica, or a swept
directory will lose
implicit-default which consequential values arrived by default
rather than by decision
deprecated-import which imported symbols have a documented new owner
platform-security where the production auth gate will not fire
architecture-violation which imports reach past what the package declares
============================ =================================================
Usage::
from symfonic.diagnostics.inspection import (
InspectionContext, inspect_migration, render_migration_report,
)
context = InspectionContext(config=raw, project_root=Path("."))
inspection = inspect_migration(context)
print(render_migration_report(inspection, context))
or, from a shell, symfonic doctor --offline ..
InspectionContext
dataclass
¶
InspectionContext(config: Mapping[str, Any] = dict(), package_version: str | None = None, project_root: Path | None = None, environ: Mapping[str, str] = dict(), available_roots: frozenset[str] | None = None, framework_root: Path | None = None, max_source_files: int = 5000)
The inputs one offline inspection may read.
Every field is injectable so a caller -- a test, a CI job, a reviewer inspecting somebody else's checkout -- gets the same answer the adopter gets, without depending on the inspecting process's own environment.
for_target
classmethod
¶
for_target(*, project_root: Path | None = None, config: Mapping[str, Any] | None = None, environ: Mapping[str, str] | None = None, package_version: str | None = None) -> InspectionContext
Build a context describing the inspected deployment.
environ defaults to empty rather than to os.environ. The
replaced from_process did the latter, which quietly turned every
platform-security answer into a statement about the machine running the
inspection -- and the caller has no way to notice, because a laptop with
no production marker set produces exactly the silence a clean production
deployment would.
Source code in src/symfonic/diagnostics/inspection/context.py
resolved_framework_root ¶
The directory containing the symfonic package.
src/ in a checkout, site-packages/ in an installed environment;
only the symfonic subtree under it is ever scanned.
Source code in src/symfonic/diagnostics/inspection/context.py
resolved_roots ¶
Installed optional import roots, probed once if not supplied.
sources ¶
Every readable *.py under the project root, as (path, text).
Paths are project-relative POSIX strings because they end up in a report somebody reads; absolute paths from the inspecting machine would be noise in a document that outlives the run.
Source code in src/symfonic/diagnostics/inspection/context.py
InspectionFinding
dataclass
¶
InspectionFinding(code: str, category: str, severity: Severity, subject: str, detail: str, migration_action: str, location: str | None = None)
One offline observation about an adopter's project.
sort_key
property
¶
Group by category, then most severe first, then stably by name.
as_check_result ¶
Render as a :class:CheckResult so doctor treats it like any check.
Source code in src/symfonic/diagnostics/inspection/model.py
MigrationInspection
dataclass
¶
MigrationInspection(findings: tuple[InspectionFinding, ...] = (), inspected: tuple[str, ...] = (), skipped: tuple[tuple[str, str], ...] = ())
Everything one offline pass established.
inspected and skipped together cover every category, always. An
empty findings list is ambiguous on its own -- it means "clean" for an
inspected category and "never looked" for a skipped one, and conflating
those two is how a migration tool earns undeserved trust.
inspect_migration ¶
Answer all migration questions, reading nothing but text.
Source code in src/symfonic/diagnostics/inspection/runner.py
render_migration_report ¶
Return the Markdown migration inspection report for inspection.