exlab_wizard.validator.engine#
Validator engine. Backend Spec §4.4.4, §8.1, §11.7, §11.8.
The engine is the single component that implements the rules in §8.1 and runs in two modes against the same rule set:
Creation-time mode (
Validator.validate_creation()) – input is a resolved destination path, a resolved variable map, and the post-render content of files about to be written. Output is a flat list ofFindinginstances. The controller raises aValidationErrorcontaining this list when any hard-tier finding fires (§8 bullet “Validation”). This mode does not touch the disk; it dispatches to the pure rule-check helpers inexlab_wizard.validator.rules.Audit mode (
Validator.audit()) – walks a directory subtree under the managedlocal_root(andstaging_rootwhen orchestrator mode is on; §11.8). Output is a flat list ofFindinginstances sorted by(tier desc, rule, offending_path). Readscreation.jsonper directory viamsgspec.json.decode; bounded text-file content scans perValidatorConfig.content_scan_max_mibandValidatorConfig.content_scan_extensions; binary files always skipped via the 8-KiB null-byte sniff (§8.1.1).Validator.query_problems()– public read-only alias forValidator.audit()that satisfies the §11.8 problem-query contract. Does not mutatecreation.json, does not write log entries, does not initiate sync.
Performance commitments (§4.5, §11.8):
The directory walk uses
os.scandir(NOTpathlib.Path.rglob).DirEntry.is_dir()/is_file()are cached from the iteration, so the walk avoids per-entrystat()syscalls.creation.jsonis decoded viamsgspec.json.decode.Regex patterns are pre-compiled at module load (
constants/patterns.py).Pattern matching uses stdlib
reonly (nohyperscan,ripgrep) so the §11.8 determinism contract holds across hosts.
Determinism (§11.8). The same inputs always produce the same finding
list in the same order. The constructor accepts a
ValidatorConfig so the per-lab
content-scan tuning (size cap, extension list) is captured as part of
the input contract; if no config is supplied the engine uses the
documented defaults from §9.
Sort order. The engine returns findings sorted by (tier, rule,
offending_path). tier is sorted with "hard" before "soft"
(matching the §11.8 contract that hard-tier findings appear first in
the Problems tab). rule and offending_path are sorted
lexicographically. The ordering is total: two findings with identical
(tier, rule, offending_path) are equal under the comparator, but
the underlying list keeps insertion order via a stable sort.
Classes
Audit every configured equipment + staging when orchestrator on. |
|
Audit one equipment subtree. |
|
Audit one |
|
|
Input bundle for creation-time validation. |
|
Run §8.1 rules in creation-time and audit modes. |
- class exlab_wizard.validator.engine.AuditScopeAll[source]#
Bases:
TypedDictAudit every configured equipment + staging when orchestrator on.
Spec §11.8. The
valuefield is omitted; the constantkindof"all"is the discriminator.
- class exlab_wizard.validator.engine.AuditScopeEquipment[source]#
Bases:
TypedDictAudit one equipment subtree. Spec §11.8.
The
valueis the equipment ID (matched against the configuredequipment[].idlist); the engine resolves it to the equipment’slocal_rootvia the equipment-config map handed to the constructor.
- class exlab_wizard.validator.engine.AuditScopeProject[source]#
Bases:
TypedDictAudit one
<equipment>/<project>subtree. Spec §11.8.The
valueis an absolute project-level directory path. Useful for the per-project Problems tab view (Frontend §3.8).
- class exlab_wizard.validator.engine.CreationValidationInput(proposed_path, variables=<factory>, file_names=(), file_contents=<factory>, run_kind='experimental', template_required_field_ids=(), config_required_field_ids=(), readme_fields=<factory>)[source]#
Bases:
objectInput bundle for creation-time validation. Backend Spec §8.1, §11.8.
All fields are positional-or-keyword. The dataclass is
frozenso callers cannot mutate the bundle between dispatch passes; this matches the determinism contract (§11.8).- proposed_path#
The destination path the creation controller is about to write to. Used to derive the per-segment lists for the path-segment rules. Accepts
/and\as separators (the splitter handles both).
- variables#
The resolved Copier variable dict. Keys are the template question IDs (lower-snake) and values are the resolved values. Reserved for downstream rules; not directly consumed by the §8.1 rule set today.
- file_names#
File names that will be written into the destination. Bare names without directory components.
- file_contents#
Post-render content for files about to be written (text only; binaries excluded by the caller). Keys are the same names as
file_namesfor the entries that have content.
- run_kind#
"experimental"or"test"; mirrors thecreation.jsonrun_kindvalue.
- template_required_field_ids#
README field ids the template marks required (parsed from
copier.yml_exlab_*metadata).
- config_required_field_ids#
README field ids
config.yamlreadme.defaultsmarks required.
- readme_fields#
The merged readme_fields_json dict the controller is about to write. Used by the missing-required-field rule.
- Parameters:
- class exlab_wizard.validator.engine.Validator(validator_config=None, *, equipment_roots=None, staging_root=None)[source]#
Bases:
objectRun §8.1 rules in creation-time and audit modes. Backend Spec §11.8.
The constructor accepts a
ValidatorConfig– the §9validatorblock – so callers can tune the content-scan size cap and extension list. The default constructs a freshValidatorConfigwith the §9 defaults (content_scan_max_mib=5and the canonical extension list).Audit-mode callers also pass the equipment-roots map (mapping
equipment_id -> absolute equipment directory) and an optionalstaging_root. These default to empty when audit mode is not in use; creation-time-only callers can omit them.- Parameters:
- audit(scope)[source]#
Walk a directory subtree and return all findings.
Backend Spec §11.8. Uses
os.scandir(NOTpathlib.rglob) per Backend §4.5. Readscreation.jsonviamsgspec.json.decode(..., type=CreationJson)where present. Bounded text-file content scan viacontent_scan_max_mibandcontent_scan_extensions. Binary files are always skipped via the 8-KiB null-byte sniff (§8.1.1).scopeis one of:{"kind": "equipment_id", "value": "<id>"}– one equipment subtree (resolved via the equipment-roots map handed to the constructor).{"kind": "project_path", "value": "<absolute path>"}– one project subtree.{"kind": "all"}– every configured equipment plus the staging root when orchestrator is on.
Returns a
Findinglist sorted by(tier desc, rule, offending_path). The list is deterministic across repeated calls with the same fixture: a contract pinned bytest_validator_determinism.py.- Parameters:
scope (
AuditScopeEquipment|AuditScopeProject|AuditScopeAll)- Return type:
- property config: ValidatorConfig#
The
ValidatorConfigthis engine instance was built with.Exposed read-only so audit-mode helpers (Agent C) can consult the same content-scan limits as the creation-time pass.
- classmethod from_config(config)[source]#
Build a
Validatorfrom the fullconfig.yamlmodel.Projects the relevant fields out of
exlab_wizard.config.models.Configso the engine is not coupled to the entire config schema. Used by the FastAPI lifespan when wiring the audit task.
- query_problems(scope)[source]#
Public read-only alias for
audit().Backend Spec §11.8. Read-only: does not mutate
creation.json, does not write log entries, does not initiate sync. The GUI’s per-row actions (mark-as-known, override) call dedicated mutation endpoints rather than this method.- Parameters:
scope (
AuditScopeEquipment|AuditScopeProject|AuditScopeAll)- Return type:
- validate_creation(params)[source]#
Run every §8.1 creation-time rule against
params.Returns a flat list of
Findinginstances sorted by(tier, rule, offending_path)with hard-tier findings first.Dispatch order (each helper returns
list[dict]in the rules-module contract; the engine stamps each dict with the commonFindingfields the helper does not know):check_unresolved_placeholder– against path segments, file names, and the file contents map. Markdown front-matter extraction happens inside the rule helper.check_illegal_filesystem_character– against path segments and file names.check_reserved_filesystem_name– against file names (Windows reserved-name set; case-insensitive).check_mode_prefix_mismatch– against the leaf and parent of the proposed path, with the declaredrun_kind.check_missing_required_field– against the merged readme_fields dict and the union of required IDs from the template + config layers.check_malformed_yaml_front_matter– againstfile_contents['README.md']if present.
The orphan rule (§8.1.4) is not dispatched here – it is an audit-mode rule by spec. The mode-prefix mismatch rule is the only one of the seven that consults
run_kind; everything else is structural.- Parameters:
params (
CreationValidationInput)- Return type: