Known issues & documented failure routes

Repo-wide register. This page is a register of known, deliberately-deferred behaviors and documented failure routes: behaviors we have discovered and understood but are (for now) choosing not to prevent, plus quirks worth keeping in mind. Each entry carries a stable KI-NN id — a permanent identifier (e.g. KI-01) that is referenced from code, tests, and docs (e.g. “pass-through per KI-01”) so a behavior can be pointed to from a single canonical place.

Scope. Entries describe bugs, tripping hazards, codebase/convention oddities, and in general issues that need dedicated development work (refactoring, cleanup, new features) or a standing decision. Small transient issues do not belong here — e.g. doc-sweep leftovers or other nits are fixed directly rather than registered (keeps the register lean).

Conventions:

  • IDs are permanent and never reused. Add new issues with the next free KI-NN; do not renumber existing ones.

  • Status vocabulary: accepted = known, intentionally not prevented now; planned-fix = slated for a named branch; watch = quirk to keep in mind, no action yet; implemented = addressed by a shipped change (kept for history); blocker = must be resolved before the relevant branch can merge.

  • Severity vocabulary (impact if encountered, orthogonal to status): critical = silent wrong/empty output or data loss; major = breaks a real flow or compounds technical debt; minor = works but fragile / incorrect-in-edge-cases; nit = cleanup. A nit is registered only when fixing it now is genuinely out of scope — otherwise fix it directly instead of filing it.

  • Entry headings carry status and severity: `status · severity`.

  • Each entry states: what, evidence (file:line where applicable), current decision, revisit trigger. Reference related issues by id.


KI-01 — Malformed fixed cell computes a wrong pattern silently accepted · critical

What. A metrically symmetry-illegal cell (e.g. cubic phase with a≠b≠c) that is not refined is used verbatim: GSAS-II computes peak positions from the raw, non-symmetric reciprocal metric tensor and returns a “successful” refinement with no error or warning.

Evidence. Verified against the pinned GSAS-II: a distorted cubic cell with cell-refine off → d-spacings follow the distorted metric (Rwp 65.8%, reported as a successful refinement).

Decision. Pass-through, do not prevent. Preventing symmetry-illegal cell values is the future recipe builder’s job. This behavior is also arguably useful — e.g. deliberately simulating symmetry-breaking/strain effects with a fixed distorted cell.

Revisit. When a recipe builder exists, or if a cell-vs-symmetry metric validation option is added. If added, it should be opt-in / warn so the legitimate symmetry-breaking-simulation use survives.


KI-02 — Oblique (monoclinic/triclinic) per-parameter cell holds are limited implemented (0.26), limitation inherent · minor

What. Holding a reciprocal-metric A-term equals holding a direct cell parameter only for orthogonal cells. For monoclinic, only the unique-axis length is independently holdable; a, c, and the oblique angle are coupled. For triclinic, no direct parameter is individually holdable.

Evidence. Verified against the pinned GSAS-II: holding A2 in a monoclinic cell → c still moves (6.000→6.032).

Decision (implemented in schema 0.26). PowderLine models the cell as Laue-class DOF-groups (coupled oblique parameters form a single group) and applies group-OR: a group refines if any member is requested, and only whole unrefined groups are held (powderline/constraints.py, cell_dof_groups). PowderLine does not validate or reject mixed flags within a group — it is the engine/translator; symmetry-consistent flags are the upstream recipe builder’s responsibility. Consequence for a malformed recipe: a false flag inside a group with a true member is not honored (deterministic, documented here). Not a GSAS-II limitation we can work around: a direct-parameter hold on an oblique cell is a nonlinear constraint outside GSAS-II’s linear constraint system.

Revisit. Transparency (which groups refined together) arrives with the provenance work. True independent oblique direct-parameter holds would require a fundamentally different (nonlinear) constraint approach — unlikely to change.


KI-03 — Site symmetry & multiplicity are derived, and failures are swallowed planned-fix · major

What. PowderLine derives each atom’s site symmetry and multiplicity at load via G2spc.SytSym, inside a bare except Exception: pass, defaulting to "" and 1 on failure. The recipe never states these, and a derivation failure is invisible.

Evidence. kicker.py:1170-1176.

Decision. Candidate for dedicated work: make site symmetry and multiplicity explicit, validated recipe fields, with an immediate safety patch to stop swallowing the exception. Improves resilience and decouples structural interpretation from GSAS-II for future multi-engine support.

Revisit. At minimum, stop swallowing the exception when the surrounding area is next touched.


KI-04 — Recipe→output provenance is implicit; no round-trip planned-fix · major

What. Outputs are CSV/txt keyed by GSAS-II parameter names. Which parameters were actually varied vs held-by-user vs held-by-symmetry vs frozen-by-limit is implicit (inferred from absence in varyList). No recipe-shaped output exists, so sequential/multi-step refinement needs external output→input mapping.

Evidence. GSAS-II outputs are keyed by parameter name only; per-parameter disposition is inferred from absence in varyList, with no recipe-shaped output.

Decision. Add explicit per-parameter disposition and a JSON recipe-style output enabling round-trip / sequential refinement. Deferred to dedicated work.

Revisit. When designing sequential-refinement support, or when a downstream data-management consumer needs machine-readable provenance.


KI-05 — GSAS-II refinement failures & constraint warnings don’t raise planned-fix · major

What. proj.refine() does not raise on refinement failure (singular matrix, etc.); constraint cascades/conflicts and frozen-variable notices go to stdout / Rvals['msg'] only. PowderLine currently inspects none of these, inferring success solely from hist.residuals['wR'] being non-None.

Evidence. Verified against GSAS-II internals: proj.refine() does not raise on failure, and constraint/frozen-variable notices go to stdout / Rvals['msg'] only, which PowderLine does not currently inspect.

Decision. Surface diagnostics via proj.data structures + captured console text, without adopting do_refinements. Minimal failure-detection may land early; fuller provenance later.

Revisit. Bundle with the provenance work.


KI-06 — GSAS-II parameter-limit (min/max) semantics are clamp-and-freeze watch · minor

What. GSAS-II enforces parmMin/parmMaxDict not as in-loop box constraints but by resetting out-of-range variables to the limit after a refine and freezing them from the next one. Frozen state persists in the .gpx. Coordinate limits must target Ax/Ay/Az even though the varied variable is dAx/dAy/dAz.

Evidence. Verified against GSAS-II internals (parmMin/parmMaxDict clamp-and-freeze semantics; coordinate limits target Ax/Ay/Az while the varied variable is dAx/dAy/dAz).

Decision. min/max deferred to the general constraints & limits work. When implemented, document the semantics honestly and surface frozen variables; do not imply hard bounds.

Revisit. With the general constraints & limits work.


KI-07 — Space-group setting (hexagonal vs rhombohedral, origin choice) is unvalidated watch · critical

What. R -3 c (hexagonal axes) and R -3 c R (rhombohedral axes) are both accepted but imply different cell parameterizations and A-term equivalence sets. Two-origin space groups default to origin 2; a recipe with origin-1 coordinates under such a symbol is silently wrong (no CIF symops exist to trigger GSAS-II’s auto-shift, since PowderLine is file-less).

Evidence. Verified against GSAS-II internals: two-origin space groups default to origin 2, and the CIF-only auto-shift never fires because PowderLine is file-less (no CIF symops).

Decision. Candidate for the convention-validation work: at minimum echo the inferred Laue class / setting back to the user. Origin-1 detection is hard without symmetry operators and better handled in the recipe builder.

Revisit. Convention-validation branch, or recipe-builder work.


KI-08 — Sphinx build emits reST warnings from RecipeModel docstring fixed · nit

What. A clean pixi run docs (which runs sphinx-build -W --keep-going, so this did already fail the build) printed 266 warnings (re-measured 2026-08-27), all pre-existing or cosmetic:

  • ~230 autodoc cross-reference warnings (py:class/py:obj reference target not found) for pydantic internals — PlainSerializer, ConfigDict, and the fully-expanded Annotated[..., PlainSerializer(lambda ...)] field types in schema.py that autodoc can’t resolve (the renderer additionally mis-splits the PlainSerializer(func=..., return_type=..., when_used=...) repr into several bogus sub-references);

  • ~15 docutils WARNING/ERROR lines from the RecipeModel docstring, whose embedded markdown-style ```json migration examples don’t parse as reST (unexpected indentation, inline-literal start without end);

  • ~19 myst warnings (header-level jumps in known_issues.md, and slug-style anchor cross-refs in cross-platform-guide.md’s table of contents);

  • ~12 Pygments highlighting-failure warnings from ```json fences in DEVELOPMENT.md/TROUBLESHOOTING.md containing ///# comments and ... ellipses (illustrative, not strictly valid JSON);

  • one stray pandas.core.frame.DataFrame cross-reference (pandas’ own intersphinx inventory only indexes the public pandas.DataFrame path);

  • one html_static_path entry '_static' does not exist warning.

Evidence. Reproduced by pixi run docs-clean && pixi run docs.

Decision. Fixed:

  • added docs/_static/.gitkeep so the configured html_static_path exists;

  • reworded the RecipeModel docstring to use a proper .. code-block:: javascript directive and double-backtick literals instead of markdown fences/single-backticks;

  • relabeled the illustrative, comment-bearing json fences as javascript (tolerant of // comments and ...) in DEVELOPMENT.md and TROUBLESHOOTING.md;

  • fixed a handful of docstrings in kicker.py whose Returns: sections (e.g. is_template_file, extract_refined_params_from_project, calculate_cell_esds_from_A_matrix, _extract_fit_profile) were misparsed by Napoleon as bogus name (type): pairs; docs/conf.py nitpick_ignore_regex/nitpick_ignore for the unresolvable pydantic-PlainSerializer and pandas.core.frame.DataFrame noise; fixed the pydandtic intersphinx key typo; set myst_heading_anchors = 4 so cross-platform-guide.md’s TOC anchors resolve; and promoted known_issues.md’s ### KI-NN headers to ## (the file has no other H2, so H1→H3 was a level skip).

Revisit. Closed; kept for history.


KI-09 — Withdrawn

Filed during the PR #23 review; withdrawn 2026-07-16 (the reported behavior was intentional, not a defect). Id retained so it is never reused.


KI-10 — stop_server() force-kill escalation raises on Windows and is untested implemented · minor

What. stop_server() escalates to os.kill(pid, signal.SIGKILL) when the server has not exited 5 s after SIGTERM. signal.SIGKILL does not exist on Windows, so the escalation branch raises AttributeError there instead of force-killing. The branch is rarely reached on Windows (Python maps os.kill(pid, SIGTERM) to TerminateProcess, a hard kill, so the graceful loop usually succeeds), and no test on any platform drives the timeout path — the unit tests cover only the graceful sequence (tests/test_gsas_server_unit.py, alive_states = iter([True, False])).

Evidence. src/powderline/gsas_server.py:371; tests/test_gsas_server_unit.py:157.

Decision. Fixed: the escalation now uses getattr(signal, "SIGKILL", signal.SIGTERM) — on Windows, os.kill(pid, SIGTERM) maps to TerminateProcess, the correct hard kill — and unit tests drive the escalation path (tests/test_gsas_server_unit.py).

Revisit. Closed; kept for history.


KI-11 — Server discovery is split between PID file and port probe; an orphaned server is unmanageable watch · minor

What. gsas-server status|stop trust the PID file (<tempdir>/powderline_gsas_server.pid), while GSASClient trusts an HTTP /health probe on the port-file/default port (19471). If the PID file is lost (crash, tempdir cleanup, or the starting process group being killed) the server keeps serving: clients silently use it while status reports “not running” and stop cannot stop it (there is no HTTP shutdown endpoint). A related latent bug in the auto-start path — the child env joined PYTHONPATH with a hardcoded ':' instead of os.pathsep (wrong separator on Windows; benign in the pixi env, where powderline is pip-installed) — has since been fixed; the discovery/lifecycle mismatch below remains open.

Evidence. Reproduced live: /health on 19471 answered (uptime_seconds≈13261) with no .pid/.port files present and status reporting “not running”. src/powderline/gsas_server.py:332-347 (is_server_running = PID file), src/powderline/gsas_client.py:40-49 (is_server_available = port probe), gsas_client.py:180 (the former hardcoded ':', now os.pathsep).

Decision. File for a server-lifecycle robustness pass: align status/ stop with the client’s probe (e.g. status also checks /health; add an HTTP shutdown endpoint, or have stop fall back to the PID reported by /health).

Revisit. Next branch touching gsas_server.py/gsas_client.py.


KI-12 — Server mode assumes a shared filesystem; output files should travel in-band planned-fix · major

What. The GSAS-II server writes output files into output_dir in its own filesystem view and returns only result data over HTTP. A server without a shared view (another cluster node; a sandbox/container with a private /tmp) therefore cannot deliver the file outputs at all. The client’s output-visibility guard (stat-compare of fit_profile.txt before/after the run) detects the mismatch and falls back to in-process execution — a mitigation, not a fix, and it keeps a (cheap) filesystem check in the client.

Evidence. gsas_server.py security notes (server resolves output_dir in its own view); gsas_client.py _server_output_visible / _submit_to_server_guarded; tests/test_gsas_client_visibility.py. mp-simulate’s .chi export already consumes the in-band fit_profile dict, showing the in-band path works.

Decision. Planned fix in a future server-protocol branch: the server runs in a server-local scratch directory and returns all output artifacts in-band (gpx/lst as base64/text, CSVs as text) and the client materializes the files locally. This dissolves the divergent-view failure instead of detecting it, deletes the guard, and makes cross-node servers (HPC: client on a login/worker node, GSAS-II server elsewhere, no shared filesystem) a supported topology. Needs protocol versioning for already-running servers.

Revisit. Next branch touching the server protocol, or when the HPC deployment work starts.