8.2.23 Phase 4 amended: the Python interface rebuilt on a Case and a Session, agreed 30 October 2026
8.2.23 Phase 4 amended: the Python interface rebuilt on a Case and a Session, agreed 30 October 2026¶
Rulings by the author on 30 October 2026, after WP57's plan was written and before it was
implemented. The author read the plan's Python interface (D6 to D8, after W4 and W7) as bolted onto
Case rather than built under it, and asked for a design session. It found that no caller in src/
used Case: the command line, the shell, the sweep runner, reproduction and the validation code all
called run_case or run_document. Seventeen questions were put through AskUserQuestion in six
calls: sixteen while the design was settled, and the seventeenth at /np-plan amend 4, where IF-01
was found to conflict with Y8. On the author's request, the public surface was audited against what the
examples, notebooks and session scripts import. The author took the recommendation on every question
but two: a Session runs a case, not the Case itself (Y4), and the interface stays in WP57 with no
plan cap (Y11). The letter series continues at Y.
| # | Decision | Consequence | Clause changed, and when |
|---|---|---|---|
| Y1 | A Case is the case document, built from public typed blocks (UT-10). One immutable class is both the parsed case file and what Python builds: CaseDocument merges into Case. The block models of the case schema are public in nanopnp.case, named without the Spec suffix (Geometry, Pore, Electrolyte, Mesh, Membrane, …); the presets Case.cylinder and Case.from_structure are classmethods that assemble them |
One name for one thing, the schema the only source of its defaults; an editor completes and checks a block. The case file's keys and its identifier nanopnp/case/v0.5 do not move; the generated JSON Schema's definition titles do |
IF-01's NOTE on the interface, in this commit; WP57 |
| Y2 | A Case is edited only by copy: with_ returns an edited case and takes a leaf name, a dotted path, or a whole block as a typed block or a mapping, an absent block created; the whole case is validated once per edit |
A rule that spans blocks still runs on every edit, and no holder of a case sees it change. REV-145's section edits are this | IF-01's NOTE on the interface, in this commit; WP57 |
| Y3 | A case loaded from a file remembers it while unedited. Case.load, Case.loads, case.dump and case.dumps replace the four functions; a loaded case keeps its file's path and text, and a run of a case still equal to what was read records them as nanopnp run does today; an edited case is recorded as a case in memory |
Manifests, run directories and reproduce of command-line runs are unchanged (FR-25, §5.3.3) |
IF-01's NOTE on the interface, in this commit; WP57 |
| Y4 | A Session runs cases, and every caller runs through it. A Session holds where artefacts go (the store, whether a run writes its directory, the workspace and its overwrite switch) and which stages exist (its own stages, Y10). Each run takes its own controls: session.run(case, upto=, only=, progress=, cancel=, on_stage=, on_solve=, on_artefact=). session.sweep(case, axis=, values=) is the one sweep and session.open(directory) reads a recorded run back. The command line, the shell, the sweep runner and reproduce run through it |
A notebook and the command line walk one path, and a run control lives in one place. Amends W4's Case.run clause |
IF-01's NOTE on the interface and §5.3.2's workspace sentence, in this commit; WP57 |
| Y5 | Each job has one public object. run_case, run_document, resolve, load_case, loads_case, dump_case, dumps_case, CaseDocument, plan_from_document, run_plan, Stage and registered_stages leave PUBLIC; Session, Artefact (read only: what a run holds and a stage of one's own receives) and nanopnp.case enter. Store, RunResult, orient_pore, Progress, CancelToken, CancelFlag, Cancelled, the three hook protocols and the physics model names stay |
Each break is listed in CHANGELOG.md with its migration (F4); test_public_api.py records the surface |
IF-01's NOTE on the interface, in this commit; WP57 |
| Y6 | A figure is drawn from what was returned: RunResult gains plot_field and plot_cutline beside figure, and a sweep's result plot_iv; nanopnp.plot's data helpers stay internal |
Notebooks 02 to 06 import no internal module to draw | IF-01's NOTE on the interface, in this commit; WP57 |
| Y7 | Case.from_structure takes a path or a shipped structure's name, and orient=, which orients it as prepared_pore does; structure_file and prepared_pore stay internal |
Notebooks 01, 02 and 06 build a structure's case in one call | IF-01's NOTE on the interface, in this commit; WP57 |
| Y8 | A stage is invoked and described through the Session. session.run(case, upto=, only=True) runs one stage alone, its inputs from the store (FR-27), and session.stages describes every stage the session walks, built-in and its own: name, number, inputs and summary keys. Stage, registered_stages and describe are internal |
IF-01's "separately importable, invocable object" becomes separately invocable and introspectable; IF-02's command line and IF-09's shell stand over the IF-01 interface | IF-01, IF-02, IF-09 and its NOTE on the browser shell, in this commit |
| Y9 | Physics models stay registered for the whole process. A model's name is written in the case file and checked when the case is loaded, before any Session exists; a stage is never named in the case file, so it belongs to a Session | The API reference states the difference. Nothing changes for the shipped models | §5.5's NOTE on a stage of one's own, in this commit |
| Y10 | A stage of one's own is registered on a Session by session.register_stage(function, *, name=None), keyed by the function's code and the hashes of the artefacts it reads, and nothing else |
Registering again on that Session replaces it; another Session never sees it. Amends W7's public call | §5.5 and its NOTE on a stage of one's own, in this commit; WP57 |
| Y11 | WP57 carries the interface, with no plan cap. Its plan has no word or decision cap and keeps its compaction checkpoints. It is re-planned by /np-plan wp 57 on its open PR, which carries this amendment |
Amends X5's cap | §8.4.4, in this commit; WP57's plan |