Conversation
…#144) Add pdf::PDFBlockingThreadGuard, a runtime thread-affinity check a blocking service adapter opens with so it refuses to run on the registered interactive (canvas) thread instead of silently stalling pointer and frame handling. Wire it into PreflightEngine::run(), the one blocking implementation already reached through PDFJobScheduler, and register EditorHost's owning thread as the interactive thread at construction. Add PDFJobKind to PDFJobTraceEvent so job traces identify async work by type, and document the interactive-thread boundary (what pointer/frame callbacks may do directly vs. what must go through a submitted job) in docs/JOB_SCHEDULER.md. Wiring an actual UI trigger for interactive preflight runs, and correlating PDFJobTraceEvent timing against InteractionTraceRecorder's slow-frame attribution, remain open follow-up work for #144. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xnr9TZYfGr1nMgBeh16CSb
Add pdfinteraction::evaluateTraceContracts() and its TraceContract/TracePhase enums (LoopLibInteraction/sources/interactiontracecontract.h/.cpp): the fixed-order pass/fail evaluator issue #146 AC7 asks for. Given an ordered QList<TraceContractCheck>, it returns the first unsatisfied contract and the phase responsible, matching scripts/ci/check_interaction_traces.py's CONTRACTS/PHASES tuples and docs/schemas/interaction-trace-report.schema.json exactly, so a future harness that supplies the nine checks in order gets AC7 for free. phaseForStage() is docs/INTERACTION_CONTRACT.md's TraceStage-to-phase attribution table given a type. The evaluator takes an already-assembled checklist rather than an InteractionTraceRecorder or a live replay, so it is fully testable with synthetic checks -- no InteractionController, no scheduler, no hit-test dispatch -- and is covered by the new UnitTestsInteractionTraceContract target. The prior gh-146 work (scenario/report schemas, the nine-scenario corpus, and check_interaction_traces.py) validates the corpus as data but has no test binary to produce a run; this evaluator is the piece that decides a run's verdict once one exists. Replaying a scenario through InteractionController, applying its cost model, and assembling a full report run remain open, as UnitTestsInteractionTraces/UnitTestsInteractionTracesPresent in docs/INTERACTION_CONTRACT.md's two-lanes table. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xnr9TZYfGr1nMgBeh16CSb
…readGuard docs/generated/phase5-widgets-inventory.json went stale when UnitTestsBlockingThreadGuard landed (issue #144 commit): it's a target directly in UnitTests/CMakeLists.txt, which scripts/generate_phase5_widgets_evidence.py scans, but the catalog was never regenerated for it. CI's policy job caught the drift -- test_verify_phase5_widgets_contract.py's crlf-currency check and its hardcoded target count (70) both went red against the actual count (71). Regenerate via scripts/generate_phase5_widgets_evidence.py --write and bump the test's hardcoded count to match. (scripts/generate-architecture-catalogs.py's separate architecture-catalog.json was already regenerated for this target in the #144 commit; this is the sibling widgets-surface catalog that step didn't cover.) Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xnr9TZYfGr1nMgBeh16CSb
CI status on
|
#144 — async interactive-thread boundary
Reading
dev, the job-scheduler side of #144's contract (revision + cancellation token per job, stale-result discard, non-blocking delivery back through a relay, deterministic cancel/close) was already built and tested viaPDFJobScheduler/IJobSubmitter. The one acceptance criterion with no runtime enforcement was the thread-affinity assertion itself (AC5) — nothing stopped a blocking implementation from being called on the interactive thread, it just happened not to be.pdf::PDFBlockingThreadGuard(LoopLibCore/sources/pdfblockingthreadguard.h/.cpp): a small, opt-in, global thread-affinity check. A host with an interactive canvas registers its owning thread once (registerInteractiveThread()); a blocking service adapter opens withassertOffInteractiveThread(name)and folds afalsereturn into its own typed error instead of doing the blocking work.PreflightEngine::run()and intoEditorHost's constructor.PDFJobTraceEventnow carriesPDFJobKind(issue AC7, first half).docs/JOB_SCHEDULER.mdgets a new "Interactive-thread boundary" section (issue AC8).UnitTestsBlockingThreadGuard(new), aUnitTestsPreflightEnginecase, and an extendedUnitTestsJobSchedulerassertion.Still open on #144, called out so reviewers don't read this as a full close: no real UI trigger yet for an interactive preflight run (
PreflightController::beginRun()is only called from tests), and AC7's second half (correlatingPDFJobTraceEventtiming againstInteractionTraceRecorder's slow-frame attribution) is a real design decision left as follow-up. OCR/AI adapters remain out of scope perdocs/JOB_SCHEDULER.md's own migration table.#146 — interaction-trace contract evaluator
Prior work already on
dev(scenario/report JSON schemas, a nine-scenario corpus underUnitTests/testdata/interaction-traces/, andscripts/ci/check_interaction_traces.py) validates the corpus as data but has no C++ test binary to produce a run —UnitTestsInteractionTraces/UnitTestsInteractionTracesPresent, named indocs/INTERACTION_CONTRACT.md's two-lanes table, don't exist yet.This adds the piece of that harness a future replay engine can be built around without risking a large, unverifiable change in this sandboxed environment (no Qt/CMake toolchain to build against):
pdfinteraction::evaluateTraceContracts()and itsTraceContract/TracePhaseenums (LoopLibInteraction/sources/interactiontracecontract.h/.cpp) — the fixed-order pass/fail evaluator issue [0.2.0] Automate interaction-performance regression traces #146 AC7 asks for. Given an orderedQList<TraceContractCheck>, it returns the first unsatisfied contract and the responsible phase, matchingscripts/ci/check_interaction_traces.py'sCONTRACTS/PHASEStuples anddocs/schemas/interaction-trace-report.schema.jsonexactly.phaseForStage()givesdocs/INTERACTION_CONTRACT.md'sTraceStage-to-phase attribution table a type.InteractionController, no scheduler, no hit-test dispatch — via the newUnitTestsInteractionTraceContracttarget.docs/INTERACTION_CONTRACT.mddocuments the module and is explicit that the actual scenario-replay/report-assembly harness remains open.Still open on #146: replaying a corpus scenario through
InteractionController, applying its cost model, counting hit-test candidates and async-job overlap, and assembling a full reportrun(of which this evaluator decides only thepassed/first_violated_contract/responsible_phase/failure_excerptfields).Proof
This session has no Qt/CMake toolchain, so
scripts/agent/check-change.py --base origin/dev --head-branch 0.2.2could only run the source-level checks locally:changelog,source_integrity,architecture_catalog,policy_adapters— passclang-format --dry-run --Werroron every changed file — passbuild:*/clang_tidy/focused_tests— incomplete locally (nobuild/directory); CI supplies these.Regenerated
docs/generated/architecture-catalog.json(scripts/generate-architecture-catalogs.py --write) andagent-policy.json's derived adapters (scripts/agent/generate-adapters.py --write) to pick up the new test targets.🤖 Generated with Claude Code
https://claude.ai/code/session_01Xnr9TZYfGr1nMgBeh16CSb