Triage Classification
inspectah automatically classifies every discovered item (package, config file, service, container workload, kernel parameter, etc.) into a triage bucket. The bucket determines the item’s default inclusion in the generated Containerfile and signals how much human attention it needs.
Classification works differently depending on whether you scanned a single host or merged multiple hosts into a aggregate snapshot.
Single-host classifications
When inspectah scans one host, every item receives one of three buckets:
| Bucket | Meaning | Default action |
|---|---|---|
| Baseline | Already present in the base image. No migration action needed. | Excluded from Containerfile |
| Site | User-installed, user-configured, or otherwise intentionally changed. This is your customization. | Included in Containerfile |
| Investigate | Unclear origin or ambiguous state. Needs human review before deciding. | Varies: packages excluded by default, config files included (flagged) |
How each bucket is assigned
Baseline items matched one of these conditions:
- Package name appears in the base image manifest (
PackageBaselineMatch) - Config file content is identical to the RPM-shipped default (
ConfigDefault,ConfigBaselineMatch) - Service state matches the base image (
ServiceBaselineMatch) - Kernel parameter matches the base image (
SysctlBaselineMatch) - Tuned profile matches the base image (
TunedBaselineMatch)
Site items show intentional customization:
- Package was added by the user and has a known repository source (
PackageUserAdded) - Package version was upgraded from the base image version (
PackageVersionChangedwith upgrade direction) - Config file was modified from the RPM default (
ConfigModified) - Config file is unowned – not shipped by any RPM (
ConfigUnowned) - Config file is orphaned – its owning RPM was removed (
ConfigOrphaned) - Service was changed to a non-default state (
ServiceNonDefaultState) - Service has a drop-in override (
ServiceDropInPresent) - Quadlet container unit deployed by the user (
QuadletUserDeployed) - Kernel parameter has a file-backed override (
SysctlFileBackedOverride) - Tuned profile is non-default (
TunedNonDefaultProfile) or custom (TunedCustomProfile)
Investigate items need human judgment:
- Package has no known repository source (
PackageNoRepoSource) - Package was locally installed from an RPM file (
PackageLocalInstall) - Package version was downgraded (
PackageVersionChangedwith downgrade direction) - Service has no owning package (
ServiceUnknownOrigin) - Flatpak has incomplete provenance data (
FlatpakIncompleteProvenance) - Kernel parameter has no baseline for comparison (
SysctlNoBaseline) - Tuned subsystem is in an unusual state (
TunedUnusualState)
Annotations
Items may carry additional annotations alongside their bucket:
| Annotation | Meaning |
|---|---|
SensitivePath | Item lives in a security-sensitive directory (e.g., /etc/ssh/, /etc/pki/) |
FirstBootProvisioned | Item was provisioned during first boot (e.g., Flatpak from a manifest) |
RequiresProjectedPackage | Config file belongs to a package that must also be included |
RuntimeOnlyObservation | Item reflects runtime state, not a persistent configuration |
Annotations do not change the bucket. They add context for review.
Aggregate classifications
When you merge multiple host snapshots into an aggregate, items receive a aggregate bucket instead. Aggregate classification layers prevalence (how many hosts have this item) on top of the single-host classification.
| Bucket | Meaning | Prevalence | Default action |
|---|---|---|---|
| Universal | Present on all hosts. Consensus across the aggregate. | count = total | Included in Containerfile (same items may also be excluded by baseline subtraction) |
| Partial | Present on some hosts, absent on others. Role-specific. | count >= half of total | Excluded from Containerfile |
| Divergent | Present on fewer than half the hosts. Unusual or role-specific. | count < half of total | Excluded from Containerfile |
| Investigate | Divergent-zone item that appears on all hosts but with unclear provenance. Needs review. | count = total, but zone is Divergent | Included in Containerfile (flagged) |
Prevalence zones
Aggregate classification is built on prevalence zones, which are computed from the ratio of hosts that have an item to the total host count:
| Zone | Condition | Maps to aggregate bucket |
|---|---|---|
| Consensus | count = total (present on every host) | Universal |
| NearConsensus | count * 2 >= total (present on at least half) | Partial |
| Divergent | count * 2 < total (present on fewer than half) | Divergent |
A special case: if an item is in the Divergent zone but its prevalence count equals the total host count, it is promoted to Investigate instead of Divergent. This catches items that exist everywhere but were flagged as divergent due to configuration differences.
Aggregate-to-single-host mapping
For filtering and counting, aggregate buckets map back to single-host equivalents:
| Aggregate bucket | Single-host equivalent |
|---|---|
| Universal | Baseline |
| Partial | Site |
| Divergent | Investigate |
| Investigate | Investigate |
This mapping is used when the UI needs to show aggregate counts across both single-host and aggregate views.
The Partial vs. Divergent distinction
This is the most important distinction in aggregate classification. Both describe items that vary across hosts, but they mean different things:
| Partial | Divergent | |
|---|---|---|
| What varies | Presence – the item exists on some hosts but not others | Presence – the item exists on fewer than half the hosts |
| Prevalence | At least half the aggregate has it | Fewer than half the aggregate has it |
| Typical cause | Role-based deployment (web servers vs. database servers) | One-off installation, test host, or legacy outlier |
| Default action | Excluded from Containerfile | Excluded from Containerfile |
Examples
Partial – httpd on web servers: You have 10 hosts. 6 are web servers with httpd installed, 4 are database servers without it. httpd appears on 6/10 hosts (NearConsensus zone), so it gets the Partial bucket. It is excluded from the Containerfile by default (not universal), but you can manually include it if this represents a legitimate role-based package that should be in the target image.
Divergent – debugging tool on one host: You have 10 hosts. 1 host has strace installed for a debugging session that was never cleaned up. strace appears on 1/10 hosts (Divergent zone), so it gets the Divergent bucket. It is excluded from the Containerfile because it is not part of the standard deployment.
Partial – sshd_config with different AllowUsers: You have 10 hosts. 7 have a modified /etc/ssh/sshd_config with site-specific AllowUsers lines. The config file appears on 7/10 hosts (NearConsensus zone), so it gets Partial. In aggregate mode, the refine UI lets you pick which variant of the file to include.
Divergent – custom repo on a test host: You have 10 hosts. 2 have an EPEL repository configured. The repo config appears on 2/10 hosts (Divergent zone), so it gets Divergent. It is excluded by default but you can manually include it if you want EPEL in your target image.
Section types
Triage classification applies across all inspected sections:
| Section | Item type | Common Baseline reasons | Common Site reasons |
|---|---|---|---|
| Packages | RPM packages | In base image manifest | User-added, version upgraded |
| Config Files | Files under /etc/ | RPM default, baseline match | Modified, unowned, orphaned |
| Services | systemd units | Default state | Non-default state, drop-in present |
| Containers | Quadlet units, Flatpaks | (rare) Present in base image | User-deployed |
| Kernel/Boot | sysctl, loaded modules | Baseline match | File-backed override |
| Scheduled Tasks | cron jobs, systemd timers | (rare) | User-created |
| Non-RPM Software | Binaries outside RPM | (none) | Always Site |
Include/exclude and the Containerfile
Unified include-default model
All 25 toggleable item types default to include: true at the schema level. This is the unified include-default model: items start included and are narrowed during triage classification and aggregate aggregation, not expanded from an excluded default. The include field uses a default_true serde helper so that snapshots from older schema versions deserialize correctly.
After classification, items are excluded in two ways:
- Baseline subtraction – items matching the base image are excluded during the scan pipeline.
- Aggregate prevalence narrowing – in aggregate mode, items below the consensus threshold are excluded during the aggregate merge pass.
The net effect after classification:
| Bucket | Effective include | Rationale |
|---|---|---|
| Baseline | No | Excluded by baseline subtraction |
| Site | Yes | User customization to reproduce |
| Investigate | Varies by section | Packages: excluded by default. Config files: included. |
| Universal | Yes | Aggregate consensus item |
| Partial | No | Excluded by prevalence narrowing |
| Divergent | No | Excluded by prevalence narrowing |
Locked items
Some items carry a locked: true flag that prevents toggling in the refine UI (both web and TUI). Locked items display their include/exclude state but the toggle is disabled. A reason string explains why the item is locked (e.g., “Baseline package” or “Required dependency”).
Locked items are set during classification and aggregate merge. They represent non-negotiable decisions – items where toggling would produce an invalid or broken Containerfile.
Users can override any unlocked item’s include/exclude state in the refine UI. The Containerfile is regenerated from the current include states when exported.
What “included” means in the Containerfile
The Containerfile action depends on the item type:
| Item type | Containerfile action |
|---|---|
| RPM package | RUN dnf install -y <name> |
| Config file | COPY <path> <path> (from materialized config tree) |
| Service enable/disable | RUN systemctl enable/disable <unit> |
| Service drop-in | COPY <drop-in-path> <drop-in-path> |
| Quadlet unit | COPY <unit-path> <unit-path> |
| Sysctl override | COPY <sysctl-conf> <sysctl-conf> |
| Kernel module | COPY <modules-load.d-conf> <modules-load.d-conf> |
| Firewall zone | RUN firewall-offline-cmd directives |
| Scheduled task (timer) | COPY <timer-unit> <timer-unit> |
Section promotion
Section promotion is the mechanism by which items that would normally be invisible (classified as Baseline, or excluded by aggregate intersection) can be surfaced for review or inclusion.
In the refine UI, users can:
- Include a Baseline item to add it to the Containerfile (e.g., pinning a specific package version even though it matches the base image)
- Exclude a Site or Investigate item to remove it from the Containerfile (e.g., dropping a debugging package you do not want in the target image)
- Select a variant in aggregate mode when multiple hosts have different versions of the same config file
These user decisions override the automatic classification. The triage bucket itself does not change – only the include/exclude flag is toggled. This preserves the classification rationale while giving users full control over the migration output.
When a section contains items that the user promoted (changed from excluded to included), the export tarball includes the corresponding file roots so the Containerfile’s COPY directives have matching source files.