Conversation
GitHub Actions should only run from the active default branch now that the repo has been renamed to main/dev. This change limits the conda workflow triggers to main for both push and pull_request events, avoiding stale runs from legacy branch names. Constraint: Branch rename already completed on GitHub Rejected: Rely on default-branch behavior alone | too implicit for explicit CI routing Confidence: high Scope-risk: narrow Directive: Keep workflow branch filters in sync with future branch renames Tested: Reviewed workflow diff and verified no other .github/workflows branch selectors remain Not-tested: Live GitHub Actions run after push
The old ignore file predates the current tree: it listed the generated Cython sources under quest/lib/, which is gone, and nothing else. Replace it with rules covering the surfaces that actually exist now - Python builds and caches, the Node/Next.js frontend, pixi's materialised .pixi/ environment (pixi.toml and pixi.lock are committed instead), and the usual editor and OS noise. Also ignore simulation output. simulate_project writes jobs/<uuid>/ under the working directory whenever save_outputs is true, so anything run from the repo root leaves a directory behind; that had grown to 2292 runs and 32 GB. The scratch files left by manual runs and the molecule viewer are ignored with it.
okf/ is the durable, agent-readable knowledge layer that sits beside the code: plain markdown with YAML frontmatter, grouped as architecture/, subsystems/, workflows/, specs/ and references/ under a root index.md, plus an append-only log.md and a session handover. It records what is not derivable from the source - what actually runs during a simulation, the project-JSON contract every surface shares, the physics and its approximations, the assessment backlog and where the architecture is heading, and the defects that are known but not yet fixed. CLAUDE.md points at it and states the working practices that keep it in step with the tree: it is updated in the same change as the code, never in a later pass. No source changes.
quest/lib/ was a vendored slice of an older application - a genealogy mixin, a math package with its own linalg, an I/O layer that reimplemented PDB parsing, and the simulation buried under tools/dye_diffusion/. Almost none of it was reachable from the library entry points. What survives moves to quest/core/ under names that say what it is: simulation.py (was core.py), structure.py, pdb.py, av.py, imp_av.py, photon.py, dye_diffusion.py, io.py, fps_json.py. The Qt-era class hierarchy, the genealogy mixin and the unused math package are gone. Two changes here alter what the science computes: - Radii come from IMP. read_pdb already decorates every particle with a radius, so the reader takes IMP.core.XYZR().get_radius() directly instead of consulting a private element table. That table (quest/lib/common.py, 271 lines) is deleted, and with it the last reason the two accessible-volume backends disagreed - fed the same atoms with the same radii they now agree to a 0.90 point ratio with centroids within 0.5 A, where before it was 0.78 and was misread as an algorithmic difference. IMP's CHARMM radii are ~19% larger than the Bondi-ish values QuEst carried, so accessible volumes shrink and some sites that were labelable no longer are. - imp_av.py takes the atom array rather than a filename, so coarse graining can no longer be silently discarded and mmCIF works wherever the reader works. It had shipped a Model.get_particles() call that does not exist: the backend had no test coverage at all, because AV_BACKENDS starts with LabelLib and nothing ever entered that branch. quest/imp_tricks.py splices the imp-tricks checkout onto sys.path deliberately without installing it, since that source tree is what is changing. Tests are re-baselined for the new radii rather than loosened; each replaced site was chosen to satisfy its own test's assertion, not just to be labelable. conftest.py pins NUMBA_NUM_THREADS and NUMBA_THREADING_LAYER, because a warm numba cache otherwise hides compile-time failures in the jitted kernels.
Logic that lived in a surface cannot be reached from the others, and that
is how the GUI came to ship different quenching chemistry from the CLI for
as long as nobody looked. This adds the layer that makes that structurally
impossible.
- backend/dispatcher.py registers QuEst's capabilities as named RPC
methods behind a single envelope, {ok: true, result} or
{ok: false, error}, so callers never sniff for a field to tell success
from failure. It is deliberately small: a host application can pass its
own dispatcher to register_services() instead, since registration only
needs register(name, handler).
- backend/services.py registers the methods themselves. manifest.json
declares them, along with the state schema, so a host can learn what
QuEst offers without importing it.
- project.py owns the project-JSON contract - the one document the CLI,
the web backend, the GUI and notebooks all exchange.
- jobs.py owns the run directory; scan.py the labelling scan;
structure_fetch.py fetching a structure by PDB ID; structure_info.py
the chain, residue and site inventory a caller needs before it can
choose an attachment site.
The CLI is rewritten as a thin wrapper over these methods rather than a
second implementation, which is most of its diff.
Parameter help text was written three times - in the Qt form, in the web
UI, and in doc/parameters.md - so the three drifted, and a parameter could
mean one thing in a tooltip and another in the docs.
quest/settings/parameter_catalog.json is now the single description. Both
doc/parameters.md and the Qt form's view spec are generated from it, and
the web API serves it, so a parameter is edited in one place and the
change reaches every surface at once. Never hand-edit the generated files;
regenerate with doc/generate_parameter_docs.py and
quest.gui.generate_view_spec.
Because the description exists once, translating it is tractable:
quest/i18n.py plus locales/{en,de,fr}.json give English, German and French
across the docs, the desktop form and the web UI together. The tests
assert what makes that safe - a translation is complete, structurally
identical to English, and never changes what the software computes. Locale
files are cached per process, so a running uvicorn must be restarted after
editing one.
quest/settings/dye_repository.json replaces the dye presets that were
tucked under quest/lib/tools/dye_diffusion/, and the loader fills in the
stickiness fields a preset may omit so every entry is complete before it
reaches the simulation.
Every .ui file is gone. The desktop form is quest/gui/quest.view.json, generated from the parameter catalog by generate_view_spec.py and rendered by ChiSurf's AutoForm, with de/fr spec variants produced from the same locale files as the docs. Adding a parameter to the catalog therefore adds it to the form; it can no longer be forgotten there. The form is bound to ProjectFormModel, whose state *is* a project dict. That is the point of the rewrite: the GUI edits the same document the CLI loads, so the two cannot drift the way they did when the GUI shipped its own quenching chemistry. chisurf_host.py reaches a ChiSurf checkout to borrow AutoForm. Where ChiSurf is absent QuEst still runs on the in-tree fallback, and nothing outside quest.gui imports it - Qt stays an optional dependency, which is what lets the CLI, the API and the web backend run in environments with no Qt binding at all. This removes the last of quest/lib/: the widget layer, the plot classes, the Qt resource module and the icon .qrc. The window icon survives as a plain .ico file. Note for running the tests: -p no:pytest-qt is required only where there is no Qt binding, because the plugin aborts collection there. In an environment that has one the flag is harmful - it removes the qtbot fixture these tests need.
A browser surface over the same service layer as the CLI and the GUI: FastAPI in webui/backend calls the registered methods and serves the parameter catalog, so labels, tooltips and translations come from the catalog rather than being written a second time in TypeScript. The frontend covers simulation, labelling, scanning and an advanced view, with an NGL structure viewer. NGL is vendored under public/vendor rather than pulled from a CDN so the desktop bundle works offline. webui/desktop wraps the two in Electron and prepares a Python environment for them. Two things that cost time and are easy to hit again: uvicorn does not reload, so it must be restarted before a browser run - and since locale files are cached per process, editing one has no effect until it is. The frontend needs Node >= 20; Next refuses to start on 18. The browser suite is marked and deselected by default. Chromium's renderer dies once a single pytest process has also imported IMP, numba and FastAPI and executed the notebooks; the suite passes on its own and fails at the end of a combined run, so it gets its own process. Deselecting says that out loud, where silently skipping would not.
…ples The old notebooks were snapshots of a working directory - stored outputs, duplicate copies with '(2)' and '(1)' in their names, and imports of modules that no longer exist. None of them executed. Four remain, numbered by what they teach: donor quenching, a FRET pair, the ET distance distribution, and diffusion-modulated FRET. quest_notebook.py holds the setup they share so each notebook is about the physics rather than about paths and imports. examples/ is the same material as plain scripts, for readers who do not want a kernel. Both are executed by the test suite rather than trusted, so an example that stops working fails CI. The notebook suite is marked and deselected by default - it spawns kernels, so it needs its own process.
pixi.toml declares four environments and the tasks that run in each: default (library, CLI, web backend - test, test-web, serve), desktop (the above plus Qt - gui, test-gui), frontend (the Node toolchain - dev, build, typecheck) and build (rattler-build - build-pkg). Python is a pixi dependency like any other, so a contributor needs pixi and nothing else, and CI runs the same task names a developer does rather than a parallel set of shell steps. The conda package is built by rattler-build from rattler-recipe/recipe.yaml; conda-recipe/meta.yaml and the conda mambabuild workflow it drove are removed rather than kept alongside, since two recipes would only diverge. pyproject.toml follows the tree: quest.gui replaces quest.quest_gui as the GUI entry point, the web dependencies become a 'web' extra, and package data no longer ships *.ui - there are none - while shipping the .ico that replaced the Qt resource module. The numpy>=2.0 floor is relaxed to plain numpy: the installed IMP extension modules are compiled against numpy 1.x and abort under 2.x, so the declared floor was promising something the stack could not deliver. The pytest markers and the default deselection of the browser and notebook suites are declared here too, with the reason in a comment.
The handover opened by saying everything was uncommitted, which is no longer true of quest - it is nine commits on main, listed in log.md with what each carries. imp-tricks is still dirty, and still blocks decision 4. Also records what staging turned up: the size jobs/ had reached, the duplicate 148l.pdb fixture, and the stale quest/core_new/ snapshot the index was holding from an abandoned rename. Verified after the split: 350 passed / 4 skipped in arm64, 313 passed / 25 skipped in base plus the web backend - both unchanged from the pre-commit baseline.
Decision 1 of four taken 2026-07-27. AV_BACKENDS becomes
("imp_bff", "labellib"); LabelLib stays as the fallback for platforms
without a compiled IMP.bff, Windows above all. The test class that
existed solely to assert LabelLib was first is gone - it did its job by
making the promotion deliberate.
Promoting it surfaced three defects that had been invisible for as long
as nothing entered the branch. None of them announced itself:
- The density grid was transposed. IMP numbers voxels with x fastest, so
reshaping the flat tile values C-order into (nx, ny, nz) exchanges x
and z. An accessible volume is globular enough that the transpose still
overlapped the truth by 83% of its voxels, so the point count, the
bounding box and the total volume were all right and only the shape was
mirrored. Downstream the contact volume landed on the wrong side of the
protein: quenchers in reach were not seen and donors came out
unquenched.
- The grid origin was one voxel out. IMP sizes the map itself and the
attachment atom is on the middle voxel only when the edge length is odd
- it is 86 on the reference site. The origin now comes from the PathMap
header. Relatedly _points_from_centred_grid uses the integer offset
(ng - 1) // 2 so it agrees with _center_grid_indices; the float form
put the point cloud half a voxel off the grid the quenching kernels
index into, for even ng only.
- It was not thread-safe. A pooled scan - which the web backend runs -
died in SWIG with 'XYZR_setup_particle' receiving a particle name,
the same failure quest/core/pdb.py already locks the parse against.
_IMP_BUILD_LOCK guards construction and extraction but deliberately not
av.resample(): resample is 636 ms of a 662 ms build and the SWIG work
is 22 ms, so locking the compute would cost a threaded scan nearly all
its parallelism to guard the phase that was not failing.
The regression test for the first two is that AV(av_backend='imp_bff')
must reproduce build_imp_accessible_volume() exactly as a set. Comparing
counts or centroids passes with the grid transposed.
This changes what QuEst computes, by more than the 10% volume difference
between the backends. Over all 151 CB sites of 148l chain E under the
reference project, LabelLib leaves 43 labelable and 21 quenching;
IMP.bff leaves 147 and 60. Most of that is not the algorithm but
allowed_sphere_radius - IMP's is floored at 2.0 while LabelLib gets the
project's 0.5 - which is what decision 3 is about.
QUENCHED_SITES is re-picked from sites that quench under *both* backends.
Picking them from the IMP numbers alone turned the entire base
environment red: it has no compiled IMP.bff, so it runs LabelLib, and
this file tests seeded reproducibility rather than AV chemistry.
Verified: 350 passed / 4 skipped in arm64, 313 / 25 in base plus the web
backend.
Decision 2 of four. A bound ligand occludes the dye, so the reader passes no selector and takes IMP's default - everything but waters and hydrogens. _atom_record_selector is deleted. On 148l that is 1363 atoms against 1322; the extra 41 are FGA, API, DAL, MUB, NAG and BME. Both candidates were measured over all 151 CB sites of 148l chain E, because the atom count does not decide this: ATOM records only (before) 1322 148/151 labelable mean AV 9485 A^3 no selector (IMP default) 1363 150/151 mean AV 9320 A^3 NonHydrogen (waters in) 1503 148/151 mean AV 7900 A^3 Waters cost 17% of every accessible volume, and crystallographic solvent is mobile on the timescale a tethered dye explores - treating it as a rigid wall claims more than the physics supports. Rejected. Admitting ligands cost 1.7% of mean volume and gained two labelable sites. More obstacles cannot open a pocket; what changes is the grid IMP sizes around the site, so a search that previously started nowhere can succeed. Two latent defects surfaced the moment HETATM records arrived, both being code that had only ever seen standard amino acids: - to_coarse raised KeyError: 'FGA'. Residues with no coarse template are now kept whole - coarse-graining reduces an amino acid to backbone plus CB and a bound sugar has no such reduction. Dropping them would have reinstated this same stripping one layer down. - Every hetero atom was named 'HET:'. IMP names them 'HET: C1 ' and the atom_name field is |U5, so the real name was truncated away and all of them collided on one string. Atom names are how the quenching chemistry and the coarse templates identify atoms, so this was not cosmetic. A third defect is recorded but deliberately not fixed here: coarse-grain residue matching ignores the chain, so in any multi-chain structure one chain's side chains are folded into another's CB. Fixing it moves coarse-grained numbers and belongs in its own change. Verified: 351 passed / 4 skipped in arm64, 314 / 25 in base plus the web backend.
Decision 3 of four. DEFAULT_ALLOWED_SPHERE_RADIUS replaces QuEst's 0.5 default and the separate 2.0 floor IMP's path was clamped to. 2.1 A is carbon's CHARMM radius: the attachment atom is a CB in every default workflow, and the sphere that lets the linker escape it should be the size of that atom. It also sits just above the cliff below which IMP's path search starts nowhere and returns an empty volume without saying so. The clamp survives for callers who explicitly ask for less, but it now warns instead of substituting silently. A caller asking for 0.5 and getting 2.0 never learned the number it reasoned about was not the number that ran. Effect, measured over all 151 CB sites of 148l chain E: IMP.bff goes from 147 to 151 labelable sites and from 60 to 67 quenching ones. It also corrects a claim made when IMP.bff was promoted. That commit said most of the 43-vs-147 labelability gap between the backends was this parameter rather than the algorithm. It is not: raising the default from 0.5 to 2.1 left LabelLib at exactly 43/151, because calculate1R and calculate3R accept a linkersphere argument and never pass it on - ll.dyeDensityAV1 has no such parameter, and the wrapper zeroes the attachment atom's own vdW radius instead. The real cause is that LabelLib frees only the attachment atom, so a CB hemmed in by CHARMM-sized neighbours has no route out, while IMP ignores every obstacle inside the sphere. Only the IMP.bff backend honours this parameter at all, and both the constant and the AV concept now say so. Verified: 351 passed / 4 skipped in arm64, 314 / 25 in base plus the web backend.
TestTheUpstreamAvMatchesQuEsts is the precondition for DUP-01 - deleting imp_av.py and the AV class in favour of IMP.bff.av.compute.compute_av - and it only ever compared the LabelLib implementations, because upstream's imp_bff branch raised on every input. That branch is fixed in imp-tricks (936a17c, b95c000), so the fixture is parametrised over both and the path QuEst actually defaults to is pinned too. Equality is to 1e-4 A rather than bit-exact, and the reason is worth keeping in the test: on the imp_bff path the two sides reach the same points by different arithmetic - QuEst rebuilds its cloud from the density grid in float64, upstream takes IMP's get_xyz_density(), which has been through float32. They agree to ~2e-6 A, a millionth of the 0.5 A voxel. A real disagreement is a whole voxel, five orders of magnitude larger. DUP-01 is no longer blocked on correctness, only on packaging: QuEst still reaches imp-tricks through a path shim rather than a declared dependency. Verified: 353 passed / 4 skipped in arm64, 314 / 27 in base plus the web backend.
Decision 4 - imp-tricks as a declared dependency - is blocked on packaging rather than correctness. Both of its AV backends now work and are pinned against QuEst's, but imp-tricks is version 0.1.0 on a private GitLab, absent from PyPI and conda-forge, and the two fixes are committed locally and unpushed. Declaring the dependency would make QuEst uninstallable for anyone who cannot resolve that name and would fail the packaging job in CI, so the path shim stays until it is published. Also corrects the state table. The previous handover recorded 16 passing notebook and example tests; that total only ever came from running the two files separately. As one selection, pytest tests -m notebooks gives 6 failed / 10 passed, from a dynamic-loader conflict in the child process - pre-existing, verified in a worktree at 1807e72, and now written up rather than carried forward as a number that no single command produces. The browser and ChiSurf rows are marked explicitly as not re-run since the AV changes, instead of being left to read as current.
Decision 4. It was blocked on imp-tricks being unpublished; the owner has
cleared that ("nobody installs quest, will make imp-tricks public later"),
so the declaration lands now and publication catches up later.
Declared in the two manifests that describe the *shipped* package —
rattler-recipe/recipe.yaml and pixi.toml — and deliberately not in
pyproject.toml. That is the convention `imp` already follows here: neither
is pip-installable, so a pip install of QuEst has never produced a working
science stack and this changes nothing about that.
The cost, stated rather than hidden: imp-tricks is on a private GitLab and
on no public channel, so the `py` solve-group no longer solves and all four
CI jobs go red — not just packaging. The constraint is `*`, so publication
alone fixes it, with no further change here. Local work is unaffected: the
arm64 environment reaches the checkout through quest/imp_tricks.py.
QuEst's own _density2points is deleted; density2points is now an adapter over IMP.bff.av._kernels.density2points, which selects exactly the same voxels (checked on 5x7x9, 8x8x8 and 11x11x11 grids) and additionally keeps the per-voxel weight this signature discards. Two preconditions the plan did not mention, both created rather than worked around: Upstream's kernels were bare @nb.njit. QuEst's carried cache=True and nogil=True, and nogil is what keeps a threaded site scan -- the web backend -- from serialising on the GIL inside the loop that visits every voxel. Contributed upstream as imp-tricks a49e17c, so this is a swap and not a regression. Upstream enumerates ix outermost where QuEst enumerated iz, so the point cloud comes back permuted. That is safe only because nothing reads AV.points positionally -- verified against every consumer: the walk runs on the density grid, _ran_dist samples at random, Rmp averages, and the acceptor cloud is averaged in full. The only visible effect is .xyz row order. TestDensityToPoints asserted a sequence and now compares sorted. The import is lazy on purpose. At module scope it failed test_importing_the_domain_does_not_import_qt and test_the_model_needs_no_qt, via the documented dylib conflict rather than an ImportError: both probe the domain layer in a subprocess, and importing IMP.bff.av reaches IMP. arm64 353 passed / 4 skipped; base + web backend 314 passed / 27 skipped. Both match the pre-change baseline exactly.
RDAMean and RDAMeanE now aggregate IMP.bff.av._kernels.random_distances and IMP.bff.distance_metrics.av_pair_statistics; QuEst's _ran_dist is deleted. On 148l E15/E90 the two agree inside their own sampling noise: <R_DA> 55.164 +- 0.037 against 55.171 +- 0.032 A, <R_DA>_E 54.467 +- 0.022 against 54.473 +- 0.029. It is 5x slower -- 3.4 ms a call against 0.68 ms -- and that is taken knowingly rather than missed. QuEst's kernel was parallel=True; upstream's is sequential because it seeds, and numba's per-thread RNG cannot be seeded reproducibly, so determinism and parallelism genuinely conflict in this kernel. A further 0.87 ms is widening (N,3) clouds to the (N,4) weighted points upstream takes; QuEst's grids are binary, so those weights are all ones. Acceptable only because this runs once per simulation, about 0.4 s across a 151-site scan. It must not be adopted on a per-frame path. In exchange the result is reproducible, which it was not: the old kernel drew from an unseeded per-thread RNG and gave a different answer every call. The seed is a parameter now. dRmp stays local. av_pair_statistics returns r_mp as a copy of r_da_mean by its own admission, and R_mp cannot be recovered from a sample of pair distances at all -- 47.65 A here against 51.53 A there. Only indices 0 and 2 are read, and dRmp's docstring says why. What guarded these before was one assertion that a distance lay between 10 and 100 A, so TestTheAdoptedDistanceKernelsBehave is new. It uses E55/E124: it first used E15/E90, the pair the spec quotes, and errored under LabelLib, which labels 43 of 151 CB sites and not that one. arm64 356 passed / 4 skipped; base + web backend 317 passed / 27 skipped; 16 GUI tests. Baselines were 353 and 314, plus the 3 new tests.
simulation_type="AV3" reached calculate3R, which was deleted in 7ecf209, so it raised NameError: name 'calculate3R' is not defined -- from eight frames inside a running simulation, naming nothing a caller could act on. simulation_type is a project field a scan can set, so this was reachable from configuration alone. Nothing caught it because no test set the field, and reachability analysis cannot see a value-driven branch. Neither backend has ever implemented AV3 here: the IMP.bff path ignores simulation_type outright, and calculate1R drives ll.dyeDensityAV1. So the honest fix is to say so rather than to grow a three-radius model. Validated first thing in AV.__init__, before the structure is read. okf/subsystems/accessible-volume.md said AV3 "only takes effect on the LabelLib path". It took effect on neither; corrected there. arm64 358 passed / 4 skipped; base + web backend 319 passed / 27 skipped.
The largest deletion QuEst had left. quest/core/imp_av.py (329 lines) and calculate1R (69) are gone; AV.__init__ makes a single compute_av call with an explicit backend=, so the choice is never implicit. QuEst keeps the application half -- site selection, the cubic-grid and binarisation adapter, and AV's derived grids. The volumes are verified unchanged, which the suite alone would not show: 148l E36/E55/E118 under both backends give identical grid shape, identical binary density voxel-for-voxel, and identical point counts -- 147011 / 164783 / 67889 (IMP.bff) and 172964 / 200283 / 91585 (LabelLib). Adopting compute_av would have re-introduced the SWIG race QuEst fixed on 2026-07-27: it had no thread safety at all. The lock is contributed upstream instead (imp-tricks 9cd5c13), keeping the construction-locked / resample-unlocked split, because wrapping the whole call would cost a threaded scan 96 % of its parallelism. The two grid-origin conventions are preserved rather than unified. The IMP path offsets by the integer (ng-1)//2, matching every grid kernel; the LabelLib path places its cloud on the float corner. For an even ng -- 92 here -- those differ by half a voxel, so on that backend AV.points sits 0.25 A per axis off its own density grid. That is a real defect, but unifying it moves LabelLib results and the correct convention is a judgement call, so it is recorded in okf/references/known-issues.md for its own change rather than altered inside a deduplication. Tests that reached the deleted module are rewritten against AV itself. The one asserting on the *source text* of build_imp_accessible_volume now asserts on compute_av's signature: it cannot re-read a file because it is never given a path. arm64 358 passed / 4 skipped; base + web backend 319 / 27; 16 GUI tests -- all identical to the pre-change counts.
The handover now reads as the state after decision 4 and the deduplication rather than before them: all four decisions closed, DUP-01/DUP-04 and half of DUP-05 done, CI deliberately red until imp-tricks is published. Three things worth a fresh session's attention are recorded rather than left in the diff. That none of the three adoptions was a drop-in -- each needed an upstream fix first (thread safety, njit annotations) or a knowing trade (5x on the RDA sampling). That the DUP-* numbers this file used did not match specs/assessment.md, which is authoritative, so two commit messages are mislabelled. And that a concurrency or grid test which has never been seen to fail is not evidence -- that has now cost time three separate times here.
`simulate_project` wrote `Path.cwd()/jobs/<uuid>` whenever `save_outputs` was true, and it was true by default. Every library call, notebook and forgetful test therefore left a run behind: 2870 directories, 39 GB, in this checkout. `save_outputs` now defaults to False, and an opt-in run with no `project_dir` writes to the per-user artefact root rather than the working directory. The web backend stops passing REPO_ROOT -- that is what steered browser runs into the tree -- and reads its job list from the same root, so the list no longer depends on where uvicorn was started. `quest jobs clean` drives `jobs.purge_jobs`, which reports without deleting unless told otherwise. Two suite tests asserted the old default. One of them would have gone vacuous rather than red: it compared a "full" run against a "fast" one, and both would now have been fast. Also fixes `pytest tests -m notebooks`, which has given 6 failed / 10 passed as one selection since 2026-07-26 and now gives 16 passed. The recorded diagnosis was wrong twice -- it is neither base-versus-arm64 boost skew (it reproduces with base retired) nor an ordering effect between the two test files (they pass together). Importing ChiSurf sets DYLD_LIBRARY_PATH=<mambaforge>/lib into os.environ; that is inert in-process but every subprocess inherits it, so an arm64 child resolves libboost_filesystem against base's copy and `import IMP` dies. It happens during collection, in the one module that calls autoform_available() at import, which is what made it look like ordering. Contained here with a collection hook; the real fix belongs in ChiSurf. And deletes quest/utils.py, which nothing imported: a coverage hook writing .coverage.* into the working directory, a set_search_paths left over from the sys.path injection LAY-08 removed, and unittest monkey-patching for Python 2.6.
…se used to The mambaforge base interpreter is retired. arm64 gains fastapi, uvicorn, python-multipart, pytest-asyncio, httpx and playwright, so one command now covers library, Qt GUI and web backend: 404 passed, 1 skipped, against 358 in arm64 and 319 in base separately. playwright has to come from pip. conda-forge's `playwright` is the Node package; installing it leaves `import playwright` failing. Four backend tests broke, correctly: they monkeypatched REPO_ROOT, which the job routes stopped consulting when artefacts moved out of the checkout. They set QUEST_JOBS_DIR now, and REPO_ROOT is gone. Retiring base removes what enforced "keep the optional dependencies optional". That rule held because base genuinely lacked Qt and a compiled IMP.bff, so every fallback ran for free. arm64 has all three, so nothing would enter those branches again -- which is the exact condition under which imp_av.py once shipped a Model.get_particles() call that does not exist. tests/test_optional_dependencies.py simulates the absence instead: backend resolution with each capability flag forced false, and `import quest.core` in a subprocess with PyQt5, qtpy, LabelLib or IMP blocked at import. Verified by adding a module-scope `import IMP` to quest/core/av.py and watching it go red. Base was not measured to be broken; its backend suite gave 16 passed on the day it was retired. Recorded as an owner ruling, not as a measurement.
…ites
A green suite does not tell you an accessible volume is right. The AV grid was
once transposed x<->z and still overlapped the truth by 83 % of its voxels --
point count, bounding box and volume all correct, only the shape mirrored, so
the contact volume landed on the wrong side of the protein and quenching
silently vanished. Nothing in the suite noticed.
tests/baselines/{labellib,imp_bff}.json record what this tree computes for 148l
chain E sites 96/139/129 under each backend: grid edge, point count, occupied
voxels, x0, cloud centroid, per-axis extent, then the seeded simulation, the
E55-E124 distance metrics, one FRET run, and coarse-graining on both 148l.pdb
and mc4r_dimer.cif. generate.py rebuilds them after clearing the numba cache;
test_baselines.py asserts against them.
Centroid and extent are asserted separately from the sizes. A cloud shifted half
a voxel has the same point count, the same volume and the same bounding box --
only the centroid moves, and that is the defect the next phase fixes.
Verified by defeating it: adding 0.5*dg to the LabelLib x0 turns 7 tests red --
three centroids, three simulations, the FRET channel -- and leaves every imp_bff
case and every size check green.
The baselines caught two of their own generation bugs first. The initial run
produced identical simulation blocks for both backends while their volumes
differed by 7000 points, because simulate_project reads QUEST_AV_BACKEND from
the environment and ignores the backend passed to AV(); there is a canary test
for that now. And tests/__init__.py was briefly added so the generator could be
imported -- that makes tests/ a package and changes pytest's import mode for
every file in it, so it is loaded by path instead.
Also runs the two suites handover.md had italicised as unverified across two
sessions of AV changes: browser e2e 26 passed (under arm64 for the first time),
ChiSurf quenching_estimator 1 passed.
Five call sites converted between Angstrom and a voxel index, and they did not
agree. `grid_center_index` is now the single map: voxel i of a grid anchored at
x0 sits at x0 + (i - npm)*dg with npm = (ng-1)//2, and the inverse is
floor((p - x0)/dg) + npm.
The integer-versus-float choice was recorded as a genuine judgement call. It is
not one. On the IMP path x0 = grid_origin + npm*dg, so voxel i is at
grid_origin + i*dg by construction; the integer offset is the map that inverts
that, and integer voxel indexing cannot express the float corner. LabelLib
anchored x0 on the attachment atom, which is on a node only for odd ng.
Writing the test found a second and larger defect. After unifying the offsets,
asserting that the trajectory sampler and the kernels agree probe-for-probe
still failed: _center_grid_indices used int((p - r0)/dg), and int() truncates
toward zero, so a centre on the negative side of the anchor rounded up while
every other map rounds down -- one voxel per axis for half the grid, on both
backends, independent of parity. Its mirror was in _sample_grid_at_trajectory,
where np.trunc maps [-1, 0) to 0, so a dye one voxel below the grid was treated
as inside and read voxel 0.
The measurement that settles it: run every AV.points point through the kernels'
index formula and ask whether it lands on an occupied voxel of AV.density.
Before 37478/37859 = 98.99 % on LabelLib, 100 % on IMP; after, 100 % on both.
Numbers move on both backends and are re-baselined. Point clouds and all
distance metrics are unchanged -- only the registration of the stamped grids.
Corroboration: the two backends now agree far better, donor QY at E96 going from
0.089 vs 0.458 (5.1x apart) to 0.073 vs 0.085 (1.2x).
Three tests broke correctly and none was relaxed. test_grid_kernels' reference
implementation had copied the kernel's int(), so it was a self-comparison and
structurally could not see this; it derives the index from the specification
now. test_api asserts a tyrosine is in reach, and which sites qualify changed
wholesale -- rescanned residues 2..164 under both backends, intersection {97,
160}, old fixture E36 in neither. test_core_api required an overlap between two
stickiness spheres that no longer falls inside the contact volume; that factors
multiply is now asserted directly instead.
Also corrects the documented numba cache clearing: conftest imports ChiSurf,
which sets NUMBA_CACHE_DIR=~/.chisurf/cache, so under pytest numba reads and
writes there and not beside the source. 193 kernels were cached in it.
A PDB residue number restarts per chain, so `res_id` names a set of residues in any multi-chain structure. Structure.residue_dict and residue_ids keyed on it, so on tests/data/mc4r_dimer.cif -- where all 534 numbers occur in both chains -- each entry held two residues' atoms, whichever chain came second overwrote the first atom name by atom name, and move_center_of_mass folded both chains' side chains into a single CB. known-issues proposed matching (chain, res_id) inside move_center_of_mass. That function was a symptom: residue_dict was itself keyed on res_id, so the CA/CB/C/N/H lookup tables, the sequence, the dihedrals and which CB a labelling site attaches to all inherited the collapse. l_residue was also filled in residue_dict order while move_center_of_mass indexed it by position in residue_ids -- two independently constructed orderings that agreed only because both iterated a set() of small integers. The reader already held IMP's Chain, Residue.get_index() and get_insertion_code() and wrote only a bare res_id. It now assigns a residue_index per distinct (chain, res_id, insertion_code) in file order and keeps the insertion code as a column; everything groups on that. The domain still passes plain numpy arrays, so no IMP object crosses into it and the lazy-import constraint stands. Exactly one number moves, and it is the whole baseline diff: the dimer's CB centroid, [-1.400, 2.220, 1.264] -> [0.733, -0.419, -0.851]. 148l.pdb is byte-identical -- three chains, no colliding residue number, which is what makes it the control. Verified by reverting the three groupings to res_id and watching 3 of the 7 new tests go red. Closes DUP-06, which was filed "low -- a hazard that has not bitten yet".
fret.kappa2 is now an explicit project field -- template, parameter catalog, German and French, Qt form, and validated to [0, 4] by validate_project before a structure is even resolved. A project carrying kappa2 = 9 would otherwise have multiplied the transfer rate by 13.5 and surfaced only as an implausible efficiency. The default changes no number by construction: a published Forster radius is already quoted at the isotropic dynamic average, so 2/3 is neutral, and a test asserts the rate arrays are bit-identical with and without the parameter. The rate carries kappa2/(2/3), the literature's 1.5*kappa2. DUP-03 said fret_rate_trace belongs with IMP.bff.cgdye.analysis.fret, "which additionally handles kappa2". Both halves are wrong. calculate_fret_exact takes an (nd x na) distance matrix plus transition matrices for both dyes, forms np.kron(p_d, p_a) and solves a Markov kinetic ensemble -- a rotamer-library formulation, O((nd*na)^2), against QuEst's ~1e5-point clouds. It is not a slower drop-in, it is a different model at a different scale. And it does not compute kappa2, it accepts a kappa2 matrix. The direction is reversed: QuEst's trajectory kernel is the general one and should go upstream, like dRmp. kappa2 cannot be modelled in the current dye model at all -- a structureless point in a volume has no transition dipole to orient, and the upstream kernel that does compute it takes the two dipole vectors as input. OBJ-01 item 6 closes as chosen and documented, not as modelled, and the objective and the tests both say so in as many words. Also records OBJ-02's decision: the host route. QuEst contributes a model, ChiSurf's optimiser drives it. Nothing implemented, but it already constrains the facade work -- an optimiser calling simulate thousands of times must not pay a numpy-to-list conversion per iteration.
… question subav is now a thin adapter over IMP.bff.av._kernels.split_av_acv and the 93-line _subav kernel is deleted; dRmp delegates to IMP.bff.distance_metrics.mean_position_distance, which was contributed from it. Neither was a drop-in, and both needed fixing upstream first (imp-tricks 0.2.0, 3dbefc8). split_av_acv carried the float corner and the truncate-toward-zero defects this tree was fixed for two commits ago, so adopting it as written would have reintroduced both wholesale; and it returned float64 masks, 12.5 MB per site at ng=92 against 1.6 MB. av_pair_statistics returned <R_DA> under the name R_mp, and its test asserted r_mp == 50.0, passing only because of the bug. All numbers unchanged: the kernel-equivalence tests and both baselines pass untouched. That is the point of verifying a kernel before adopting it rather than after. HAS_IMP_BFF now asks both halves of the real question -- the compiled IMP.bff.AV class and an importable IMP.bff.av.compute_av that drives it. hasattr(IMP.bff, "AV") was the compiled half only, so an environment with a compiled IMP.bff but no imp-tricks reported the backend available and failed on first use. compute_av is imported rather than probed, because IMP.bff.av is a namespace subpackage spliced in at runtime. Closes DUP-02. Also corrects environment.md, which claimed `import quest` bridges an imp-tricks checkout and named a module DUP-01 deleted. It does not: quest.core, quest.core.av and quest.api do. That error had already cost something -- "fix the IMP.cgmol import" entered the plan as an upstream defect, from a probe that imported quest.imp_tricks without calling enable_imp_tricks(). There was no defect.
Phases 0-7 are done and every row of the state table was measured today; the two italicised 'unverified' rows are gone. Records what is left in dependency order, what still needs a decision, and four traps learned or corrected today -- the numba cache location, a reference implementation that copied the code under test, the fifth test found pinning the bug it should have caught, and a probe that blamed the wrong repository.
- Replace custom web-like QSS dark theme with standard native Fusion/OS Qt style in quest/gui/theme.py - Refactor InTreeCollapsiblePanel in quest/gui/autoform_panel.py to use native checkable QGroupBoxes - Update pyqtgraph plot canvases in quest/gui/plots.py to standard clean light desktop rendering - Standardize toolbars, menus, and action buttons across QuEstWindow and TransientDecayGenerator
…or Web UI and PyQt - Add shared tour definition in quest/settings/tour.json and tour loader in quest/tour.py - Add /api/tour and /api/demo-project endpoints in FastAPI backend - Integrate 148L demo mode project loading in Web UI AppLayout when Tour button is clicked - Add Demo Mode button and GuidedTour controller in PyQt desktop workspace (using chisurf.gui.widgets.tools.guided_tour or InTreeGuidedTour) - All unit tests passing (5/5)
… in ChiMolStructureWidget
…en no structure is loaded
…ain project format
…'Guide' and 'Help' action buttons
`.gitignore` carried the standard Python-packaging block, in which `lib/` is unanchored. It therefore matched `webui/frontend/src/lib/` as well, and three frontend sources were never committed: autoformPaths.ts spec `attr` -> nested project path (the 2026-08-09 fix) parameterCatalog.ts projectHelpers.ts The web UI imports all three, so a clean clone could not build the frontend — the working tree was the only copy. Anchoring `/lib/` and `/lib64/` keeps the Python build directories ignored (neither exists today) without reaching into subdirectories that happen to be named `lib`.
…face IMP's selector already drops waters, but nothing guaranteed it: a caller that passes a different selector, a future parser, or the ChiMol viewer (which loads with keep_water=True) each reached a surface with `HOH`/`WAT`/`TIP3`/`SOL` atoms in it. Waters then showed up as labelable residues, and a dye could be attached to one. `_WATER_RESIDUE_NAMES` names the residues, `strip_water_pdb_text` filters raw PDB text by columns 18-20 so a viewer can be handed a clean file, and `_strip_waters` runs after the IMP parse. `structure_metadata` filters PDB text through the same function and skips water rows while parsing CIF, so the pdb_text it returns to the browser viewer carries none either. Baselines move accordingly: 148L atom_count 1525 -> 1385, chain E residues 291 -> 164. Both assertions updated rather than relaxed. tests/test_water_stripping.py covers the filter directly. Suite: 695 passed, 1 skipped (library + Qt + web backend).
…d requests Two defects in the ZMQ server, plus the test coverage that would have caught them. `process_message` returned the dispatcher's envelope untouched, so any handler returning a native object (a numpy array, a DecaySimulationResult) raised inside `json.dumps` and the caller got nothing back. The transport is the boundary the contract names for `to_payload`, so it converts there — and only when the envelope actually carries a `result`, because a failure envelope has no result and should not be given a null one. `serve_one` no longer lets an unhandled error kill the loop without a reply. `invalid_request` joins ERROR_CODES rather than being folded into `operation_failed`. A malformed envelope — unparsable payload, or no method named — is the caller's fault, and a host reacts to it differently from an operation that ran and failed. `service_error` validates codes against the table, which is why the two call sites had been silently downgraded. New tests: test_rpc_methods.py (every registered method through the in-process transport), test_zmq_rpc.py (every read-only method over a real REQ/REP socket, plus the wire protocol), test_api_facade.py (every public verb on quest.api). Suite: 695 passed, 1 skipped.
…ny more Both presentation surfaces now render one declarative view spec: the desktop renders quest.view.json through ChiSurf's AutoForm, the web renders the same spec from /api/autoform-spec through components/autoform/. Adding a parameter is a one-place edit in parameter_catalog.json instead of three hand-synchronised surfaces — the web was the one that fell behind (fret.kappa2 shipped with two of three wired). AppLayout drops its three-way ternary and the ~540-line legacy block; only the JSON overlay toggle remains. The scan residue picker and Calculate Scan button moved to a Scan Settings card in app/scan/page.tsx so that workflow survives. The migration exposed a real bug: a view-spec `attr` is the flat ProjectFormModel attribute (attachment_chain, fret_enabled, fret_R0), not the project path (attachment.chain, fret.enabled, fret.R0_matrix[0][1]). The web read and wrote the flat keys, which the backend rejects — FRET never seeded its acceptor and ran as a one-dye donor simulation that looked like it worked. autoformPaths.ts maps every attr to its project path; fret.enabled routes through setFretEnabled (which seeds the acceptor dye and R0) and fret.R0_matrix.0.1 through setForsterRadius, a list cell setNested cannot index. test_autoform_blueprint.py fails if the catalog grows a parameter the map does not cover. Browser e2e rewritten for AutoForm selectors: _use_legacy_form is gone, _autoform_field/_autoform_input/_autoform_check locate fields by label, and _server_is_up waits 10 s (a cold Next.js dev server takes >3 s, which had been silently skipping the whole suite). Browser e2e: 54 passed (measured 2026-08-09, both servers up).
Picking a labeling site meant typing a chain and a residue number into a form while looking at the structure beside it. Now a click on the 3D structure or the sequence bar sets the attachment, on the desktop and in the browser alike — the divergence AGENTS.md warns about is the reason both halves are in one commit. Desktop: ChiMolStructureWidget embeds ChiSurf's SequenceDock next to MolView rather than carrying a second sequence widget (okf/subsystems/chisurf-widgets.md records the mapping of what to embed instead of rebuild), and donor/acceptor toggle buttons say which attachment the next click sets. Web: ProteinViewer wires NGL's picking proxy to the same onResidueSelect the sequence bar calls. ProjectFormModel gains auto_attach_to_structure, run when a project is loaded: the template's default site (chain A, residue 1) frequently does not exist in the structure just opened, which left a project that validates and cannot run. It only overwrites an attachment that is missing or invalid, prefers CB/CA/OG/SG and never selects a non-standard residue — a water could previously be chosen. structure_label is a derived read-only property so the dock's structure display and the file picker are no longer two controls bound to `pdb`; the drift guard test_each_scalar_field_appears_exactly_once passes again. View specs regenerated for all three locales, and the web.guide key the Guide button references added to en/de/fr. Suite: 695 passed, 1 skipped.
…he RPC fixes The bundle described the AutoForm migration and the desktop viewer work as landed while the tree held all of it uncommitted, and its headline sentence said everything was committed and clean. Both are true now; the handover opens with what actually changed and the library baseline is re-measured at 695 passed, 1 skipped. Adds the trap that cost the most here: an ignored file never shows in `git status`, so a clean status is not evidence that the tree is committed. `git status --ignored=matching --untracked-files=all` is the check. CLAUDE.md was renamed to AGENTS.md (the rename itself rode along in 74d7421); its content update states the rule that a feature reaching only one of the two surfaces is a bug. okf_validate: 0 errors, 0 warnings; 20 with --check-paths (was 21).
The raw transcript of the session that produced the ChiMol viewer and guided tour work. It is not a knowledge artefact — okf/ is — but it is the only record of the reasoning behind commits cd0d058..d3dc195, so it is kept rather than discarded. Checked for credentials before committing; the only "token" matches are CSS design tokens.
Step 2 of the change-tracking loop had been skipped while both surfaces changed. webui.md still described a hand-written React form, a labeling page carrying the FRET panel, a backend importing quest.cli's private helpers (LAY-01, removed in phase 8), and data-testid="tour-btn"; its route list was five routes short and its "Design language" section appeared twice verbatim. Corrected against the tree, not the prose: the generated sidebar form and why lib/autoformPaths.ts is load-bearing, the two fields that bypass setNested, click-to-select on both surfaces, and the browser-suite locators with the two mechanical traps (10 s _server_is_up, hidden checkbox behind a styled toggle). gui.md gains structure_label, auto_attach_to_structure and the ChiMolStructureWidget embedding, and loses two sentences that still assumed the retired base environment — one of them recommending -p no:pytest-qt, which the handover lists as a trap. One finding is a gap rather than a stale sentence: LAY-07 is closed and quest/tasks.py owns the one mechanism, but ProjectFormModel.run_simulation still runs on the GUI thread instead of registering a job, so a desktop run blocks the window and cannot be cancelled. Recorded as current state. okf_validate: 0 errors, 0 warnings; 20 with --check-paths.
…26-08-06
They were flagged because the refactor moved everything they describe and only
their paths had been corrected, never their prose. Read against the code, with
every symbol resolved by hasattr rather than by eye:
core-api.md clean; DecaySimulationResult's fields match the
documented list in both directions
structure-io.md clean, including its removal claims — every name it
says is gone was checked to be absent. Its water
paragraph described one filter where there are three
simulation-pipeline.md clean; gained the implicit stage 0, a water-free
structure
surfaces.md the one that had rotted: Qt is imported in nine files,
not "exactly three", all still under quest/gui/. The
rule survived, the count did not
Also corrected in surfaces.md: two sentences asserting the checked environment
has no PyQt5, untrue since the base environment was retired.
The lesson is which concepts got flagged. Three of the four were fine; the two
that were actually stale (webui.md, gui.md) were not on the list, because
nothing structural had moved under them — the surfaces simply changed behaviour
while their concepts sat still. Only the first kind of rot is visible to a path
checker.
okf_validate: 0 errors, 0 warnings; 20 with --check-paths.
LAY-07 closed on 2026-07-28 by building quest/tasks.py — one observable, cooperatively cancelable mechanism for long work. The desktop went on calling simulate_project on the GUI thread anyway, so the surface that motivated the mechanism was the one not using it: the window froze for the whole run and nothing could stop it. ProjectFormModel gains start_simulation (registers a job, spawns the worker, returns the job_id), wait_simulation(timeout), poll_simulation and cancel_simulation. run_simulation is those four joined, so headless callers keep the synchronous contract — including raising — and there is still one code path rather than two that can drift. The worker never notifies. A listener redraws widgets, and touching a widget off the GUI thread is undefined, so the worker only stores its outcome and poll_simulation applies it; test_the_worker_never_notifies records the thread every listener fires on. TransientDecayGenerator.update_all pumps the event queue between joins and ignores re-entrant clicks, which pumping makes possible, and a Cancel button is enabled for the duration. Two limits are in the code, not left to be discovered: the trajectory kernels are njit(nogil=True) so the GUI thread does get the interpreter back during the dominant cost, but the AV grid kernels are parallel=True without nogil and still hold it — a smaller freeze, not none; and cancelling a single simulation cannot interrupt it, because _simulate_traj is one njit call and a jitted kernel cannot poll a flag, so Cancel discards the result rather than stopping the work. A scan, which checks between sites, does stop. Clicking the run button on a broken project no longer raises out of the Qt slot; the failure is reported in the status line. Suite: 706 passed, 1 skipped (11 new tests).
… anything `default` and `desktop` could not solve, so `test` and `gui` had been red for long enough to train people to ignore the light. Two causes, not the one that was known: - imp-tricks is on no public channel (owner ruling: it stays local). It is now a feature *no environment uses* — anything requiring it would take pixi lock down with it — one word in [environments] from being switched on the day it is published. - labellib is not on conda-forge either, and on the owner's channel the `main` label stops at the 2020 builds: py37-py39, pinning numpy <2 against this project's numpy >=2.0. The numpy-2 builds are on the channel's `dev` label, for linux-64, osx-arm64 and win-64 only. [feature.py] points there and restricts itself to those three platforms, which is exactly the CI matrix. pixi lock now solves all four environments. The cost is measured, not assumed. Both AV backends dispatch through IMP.bff.av, so "LabelLib is the fallback" never meant an AV could be computed without imp-tricks: with IMP_TRICKS_SRC pointed at nothing, 98 tests failed and 8 errored, including the labellib baselines. Turning that into signal takes two mechanisms in conftest, because the failures arrive two ways — hookwrapper hooks rewrite QuEst's own sentinel message into a skip (most of the suite, no annotations), and @pytest.mark.needs_av marks the tests that swallow the error and assert on a status instead. Neither converts anything where imp-tricks is present, so a real regression cannot be laundered into a skip. Without imp-tricks: 550 passed, 141 skipped, 0 failed. Twelve of those skips are over-skip from marking by module rather than by test, recorded as such. With it, unchanged: 706 passed, 1 skipped. What CI covers now is the contract, the project schema, the RPC envelopes, i18n, the view spec and the form model. What it still cannot cover is the simulation path; publishing imp-tricks remains the only thing that would.
… check The two surfaces draw the same structure with two different engines — chimol on the desktop, NGL in the browser — which is the two-implementations shape the AutoForm migration removed from the forms. ChiSurf's chimol → WebGPU work exists to close it with one WGSL source and a driver each side. Checked 2026-08-10 and not yet actionable: the desktop WGSL renderer runs, the browser/JS driver has not been started. Points at ChiSurf's agent board and okf/plugins/chimol-web.md rather than restating a status that will go stale here.
…rt is done Upstream deleted chimol's OpenGL renderer (chisurf 76df2fcfd), so the browser half is next and its handover says it has not been started. Writing down what NGL actually does in ProteinViewer.tsx — loadFile, three representations, a selection language, autoView, resize, and atom picking — states the first consumer's requirements before the driver is designed rather than after. Picking is called out because it is the one upstream still lists as GL-only, and it is the feature QuEst's click-to-select depends on.
…t core Found while checking ChiSurf's agent board for the chimol browser work. ChiSurf ruled on 2026-08-10 (a2ff49665) that the labelling plugin — the LabelStructure widget that owns the fps.json payload this row is about — belongs in ChiMOL: placing a dye on a structure is structural work done while looking at the structure. It stays in modelling/ only until ChiMOL hardens. That resolves the "where" this row was holding open (neither chisurf/core nor here) and leaves only "when". It also names a constraint QuEst should not break: the fps_json_payload getter/setter seam is what the eventual move rides on.
Found on ChiSurf's board while checking the chimol browser work. IMP.bff's C++ fps.json reader (AV::set_av_parameter, AV.cpp:208-210) overwrites radius2 and radius3 with radius1, so every three-radius AV built through it is silently a one-radius AV. The fix belongs in imp.bff. QuEst is unaffected, checked rather than assumed: it passes dye_radii straight into IMP.bff.av.compute_av from Python and never reaches that setter, and it is AV1-only, so radius2/radius3 reach no kernel that reads them. Recorded anyway because quest/core/fps_json.py *writes* those two fields: a reader carrying this bug discards them without saying so, and the symptom would look like a QuEst export bug.
…view ChiSurf classified its av.py (the LabelLib/IMP backend selection) and its fps.json round-trip to move into imp.bff, on the same rule this spec states. Its own PRD counts three accessible-volume paths landing in one repository as a result; QuEst's resolve_av_backend is a fourth copy of that same choice. Recorded as a watch item, not work: QuEst already dispatches through IMP.bff.av.compute_av, so its selector only adds the QUEST_AV_BACKEND preference plumbing and its messages. When PRD-97 stage 2 lands, the question is whether QuEst keeps a selector or passes backend= through. Until then, do not add a fifth path.
FPSIMP renamed CLAUDE.md to AGENTS.md today, so the family convention is uniform: a root AGENTS.md in every repository, no per-tool instruction file. AGENTS.md now links ../fpsimp/AGENTS.md and FPSIMP's okf-conventions.md -- the canonical copy of the rule set both bundles share -- and says which validator copy is canonical. One passage in programme-2026-07.md named the root CLAUDE.md; it names AGENTS.md now and dates the old name, since the reversal it records is about the environments and not the file.
Owner's rule, 2026-08-11: a simple method reachable only through a complex package gets transcribed and A/B'd against the reference, not depended on. Recorded here because it reads as a reversal of QuEst's standing 'prefer depending on IMP.bff' instruction if met without the scoping. It reopens nothing: the question is asked when a dependency would be added, and QuEst already depends on IMP.bff for compute_av, so the five smaller kernels adopted beside it are settled. Also notes that imp-tricks handed IMP.bff to ../imp.bff on 2026-08-10, so quest/hosts/imp_tricks.py names a checkout that no longer carries it.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Trigger build of CS