Summary
LoopEditor/editorhost.cpp grew from 899 to 1,639 lines in the preflight/shell change set
(4acb0560) and now owns profile discovery, filesystem watching, menu classification, preflight
preparation, async result routing, report export, workspace state and presentation. It also reimplements
Core's profile preparation instead of calling one shared operation.
This is the "Consider" finding #10 of the hostile-PDF audit of 4acb0560cffef7a49f6e460b7c76326906271959,
split out as its own work item because it has no security impact and touching it inside the hardening
commit would have tripled that change set.
Evidence
Size and responsibility: LoopEditor/editorhost.cpp is 1,639 lines at the audited revision (899
before the preflight work landed). It owns the preflight profile watcher (QFileSystemWatcher +
reloadPreflightProfiles), the preflight worker outcome map, menu/action policy classification, the
workspace request state, and bumpPresentation()-driven presentation concerns.
Duplicated profile preparation. The import → bind → resolve → parse sequence exists in four places:
LoopEditor/editorhost.cpp:673-700 (inside the submitted job work closure)
LoopEditor/editorhost.cpp:824 (report/export path)
PdfTool/pdftoolpreflight.cpp:409
LoopLibCore/sources/preflightengine.cpp:5611
The primitives are pdf::importPreflightProfile, pdf::bindPreflightProfileVariables,
PreflightProfileResolver::resolveExplicitProfile and pdf::PreflightEngine::parseProfile. Three
callers, one sequence, no shared "prepare this profile" operation — so an invariant added to one copy
(e.g. a validation or a diagnostics field) silently does not apply to the others.
Proposed approach
- Add one Core-side operation that turns a profile choice + variable bindings into the resolved,
parsed profile (and its diagnostics), with a single error surface. Core already exports all four
primitives; this is a composition, not new behaviour.
- Migrate
LoopEditor/editorhost.cpp (both sites), PdfTool/pdftoolpreflight.cpp and
LoopLibCore/sources/preflightengine.cpp to it.
- Only then split
EditorHost: move profile discovery + filesystem watching and async result routing
into their own types, leaving the host as wiring and presentation. Do this as a pure move with no
behaviour change, one type per commit.
Acceptance criteria
- One prepared-profile operation exists in
LoopLibCore, and none of the four call sites re-implements
the sequence (a git grep for the four primitives shows a single production composition point).
- No behaviour change:
UnitTestsEditorHost, UnitTestsPreflightEngine, UnitTestsPreflightProfileResolver,
UnitTestsPreflightVerdict, UnitTestsPreflightCorpus and UnitTestsPdfToolContract pass unchanged.
- Any structural split keeps the mapped-module contract intact:
LoopEditor/editorhost.cpp/.h are in
the quick module's path list in agent-policy.json, so check-change.py must still select the same
targets after the move (adjust the policy path list if files are added, and run the proof).
LoopLibCore still must not depend on Widgets, and no Core change may depend on LoopEditor.
Out of scope
- Any change to preflight semantics, verdicts, or the operator-visible behaviour of the preflight run.
- The shell placeholder surface (separate issue).
Summary
LoopEditor/editorhost.cppgrew from 899 to 1,639 lines in the preflight/shell change set(
4acb0560) and now owns profile discovery, filesystem watching, menu classification, preflightpreparation, async result routing, report export, workspace state and presentation. It also reimplements
Core's profile preparation instead of calling one shared operation.
This is the "Consider" finding #10 of the hostile-PDF audit of
4acb0560cffef7a49f6e460b7c76326906271959,split out as its own work item because it has no security impact and touching it inside the hardening
commit would have tripled that change set.
Evidence
Size and responsibility:
LoopEditor/editorhost.cppis 1,639 lines at the audited revision (899before the preflight work landed). It owns the preflight profile watcher (
QFileSystemWatcher+reloadPreflightProfiles), the preflight worker outcome map, menu/action policy classification, theworkspace request state, and
bumpPresentation()-driven presentation concerns.Duplicated profile preparation. The import → bind → resolve → parse sequence exists in four places:
LoopEditor/editorhost.cpp:673-700(inside the submitted job work closure)LoopEditor/editorhost.cpp:824(report/export path)PdfTool/pdftoolpreflight.cpp:409LoopLibCore/sources/preflightengine.cpp:5611The primitives are
pdf::importPreflightProfile,pdf::bindPreflightProfileVariables,PreflightProfileResolver::resolveExplicitProfileandpdf::PreflightEngine::parseProfile. Threecallers, one sequence, no shared "prepare this profile" operation — so an invariant added to one copy
(e.g. a validation or a diagnostics field) silently does not apply to the others.
Proposed approach
parsed profile (and its diagnostics), with a single error surface. Core already exports all four
primitives; this is a composition, not new behaviour.
LoopEditor/editorhost.cpp(both sites),PdfTool/pdftoolpreflight.cppandLoopLibCore/sources/preflightengine.cppto it.EditorHost: move profile discovery + filesystem watching and async result routinginto their own types, leaving the host as wiring and presentation. Do this as a pure move with no
behaviour change, one type per commit.
Acceptance criteria
LoopLibCore, and none of the four call sites re-implementsthe sequence (a
git grepfor the four primitives shows a single production composition point).UnitTestsEditorHost,UnitTestsPreflightEngine,UnitTestsPreflightProfileResolver,UnitTestsPreflightVerdict,UnitTestsPreflightCorpusandUnitTestsPdfToolContractpass unchanged.LoopEditor/editorhost.cpp/.hare inthe
quickmodule's path list inagent-policy.json, socheck-change.pymust still select the sametargets after the move (adjust the policy path list if files are added, and run the proof).
LoopLibCorestill must not depend on Widgets, and no Core change may depend onLoopEditor.Out of scope