xtalate 0.1.0.dev0__tar.gz → 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (171) hide show
  1. xtalate-0.2.0/.github/ISSUE_TEMPLATE/bug_incorrect_conversion.yml +50 -0
  2. xtalate-0.2.0/.github/ISSUE_TEMPLATE/bug_parse_failure.yml +54 -0
  3. xtalate-0.2.0/.github/ISSUE_TEMPLATE/config.yml +5 -0
  4. xtalate-0.2.0/.github/ISSUE_TEMPLATE/format_request.yml +48 -0
  5. xtalate-0.2.0/.github/ISSUE_TEMPLATE/general.yml +16 -0
  6. xtalate-0.2.0/.github/PULL_REQUEST_TEMPLATE.md +32 -0
  7. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/.github/workflows/ci.yml +7 -1
  8. xtalate-0.2.0/CHANGELOG.md +364 -0
  9. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/CITATION.cff +2 -2
  10. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/CLAUDE.md +13 -3
  11. xtalate-0.2.0/CONTRIBUTING.md +140 -0
  12. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/PKG-INFO +24 -11
  13. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/README.md +19 -10
  14. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/docs/DECISIONS.md +151 -0
  15. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/docs/MASTER_SPEC.md +17 -7
  16. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/pyproject.toml +22 -1
  17. xtalate-0.2.0/src/xtalate/__init__.py +13 -0
  18. xtalate-0.2.0/src/xtalate/_time.py +20 -0
  19. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/src/xtalate/cli/main.py +135 -22
  20. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/src/xtalate/conversion/__init__.py +3 -0
  21. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/src/xtalate/conversion/engine.py +283 -61
  22. xtalate-0.2.0/src/xtalate/conversion/parse_recovery.py +203 -0
  23. xtalate-0.2.0/src/xtalate/conversion/preflight.py +327 -0
  24. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/src/xtalate/exporters/extxyz.py +8 -5
  25. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/src/xtalate/exporters/poscar.py +39 -4
  26. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/src/xtalate/exporters/xyz.py +10 -1
  27. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/src/xtalate/parsers/_common.py +31 -4
  28. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/src/xtalate/parsers/extxyz.py +32 -8
  29. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/src/xtalate/parsers/poscar.py +240 -22
  30. xtalate-0.2.0/src/xtalate/parsers/xyz.py +357 -0
  31. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/src/xtalate/recovery/__init__.py +10 -2
  32. xtalate-0.2.0/src/xtalate/recovery/engine.py +868 -0
  33. xtalate-0.2.0/src/xtalate/recovery/scenarios.py +172 -0
  34. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/src/xtalate/schema/models.py +24 -0
  35. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/src/xtalate/schema/paths.py +8 -0
  36. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/src/xtalate/sdk/capabilities.py +21 -0
  37. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/src/xtalate/sdk/plugins.py +27 -0
  38. xtalate-0.2.0/src/xtalate/validation/_shared.py +34 -0
  39. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/src/xtalate/validation/engine.py +15 -23
  40. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/src/xtalate/validation/rethreshold.py +5 -18
  41. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/src/xtalate/validation/tolerance.py +77 -3
  42. xtalate-0.2.0/tests/capabilities/test_velocity_mass_rows.py +49 -0
  43. xtalate-0.2.0/tests/cli/test_cli.py +537 -0
  44. xtalate-0.2.0/tests/conversion/test_engine.py +416 -0
  45. xtalate-0.2.0/tests/conversion/test_frame_reduction_completeness.py +226 -0
  46. xtalate-0.2.0/tests/conversion/test_parse_recovery.py +195 -0
  47. xtalate-0.2.0/tests/conversion/test_preflight.py +215 -0
  48. xtalate-0.2.0/tests/conversion/test_velocity_recovery.py +237 -0
  49. xtalate-0.2.0/tests/exporters/test_poscar.py +86 -0
  50. xtalate-0.2.0/tests/golden/ATTRIBUTIONS.md +41 -0
  51. xtalate-0.2.0/tests/golden/_governance.py +371 -0
  52. xtalate-0.2.0/tests/golden/contcar/co-md-restart/CONTCAR +14 -0
  53. xtalate-0.2.0/tests/golden/contcar/co-md-restart/expected.canonical.json +118 -0
  54. xtalate-0.2.0/tests/golden/contcar/co-md-restart/manifest.yaml +19 -0
  55. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/tests/golden/extxyz/co-in-cell/manifest.yaml +1 -0
  56. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/tests/golden/poscar/nacl-primitive/expected.canonical.json +3 -13
  57. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/tests/golden/poscar/nacl-primitive/manifest.yaml +3 -2
  58. xtalate-0.2.0/tests/golden/test_corpus_governance.py +107 -0
  59. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/tests/golden/xyz/water-traj/manifest.yaml +1 -0
  60. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/tests/parsers/test_extxyz.py +28 -0
  61. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/tests/parsers/test_poscar.py +106 -2
  62. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/tests/parsers/test_xyz.py +7 -0
  63. xtalate-0.2.0/tests/property/_generators.py +215 -0
  64. xtalate-0.2.0/tests/property/_properties.py +106 -0
  65. xtalate-0.2.0/tests/property/_strategies.py +188 -0
  66. xtalate-0.2.0/tests/property/test_fabricative_recovery_completeness.py +159 -0
  67. xtalate-0.2.0/tests/property/test_report_completeness.py +161 -0
  68. xtalate-0.2.0/tests/property/test_report_completeness_hypothesis.py +74 -0
  69. xtalate-0.2.0/tests/recovery/test_engine.py +437 -0
  70. xtalate-0.2.0/tests/recovery/test_missing_masses.py +117 -0
  71. xtalate-0.2.0/tests/recovery/test_missing_velocities.py +209 -0
  72. xtalate-0.2.0/tests/recovery/test_physics_maxwell_boltzmann.py +56 -0
  73. xtalate-0.2.0/tests/recovery/test_scenarios.py +128 -0
  74. xtalate-0.2.0/tests/roundtrip/_compare.py +85 -0
  75. xtalate-0.2.0/tests/roundtrip/_matrix.py +163 -0
  76. xtalate-0.2.0/tests/roundtrip/test_matrix_enumeration.py +102 -0
  77. xtalate-0.2.0/tests/roundtrip/test_three_hop.py +86 -0
  78. xtalate-0.2.0/tests/roundtrip/test_two_hop.py +98 -0
  79. {xtalate-0.1.0.dev0/tests/golden/schema → xtalate-0.2.0/tests/schema/fixtures}/poscar_nacl.json +3 -13
  80. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/tests/schema/test_golden_roundtrip.py +5 -3
  81. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/tests/schema/test_models.py +30 -0
  82. xtalate-0.2.0/tests/sdk/__init__.py +0 -0
  83. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/tests/test_smoke.py +1 -1
  84. xtalate-0.2.0/tests/validation/__init__.py +0 -0
  85. xtalate-0.2.0/tests/validation/test_tolerance.py +105 -0
  86. xtalate-0.1.0.dev0/.claude/settings.local.json +0 -39
  87. xtalate-0.1.0.dev0/.import_linter_cache/.gitignore +0 -2
  88. xtalate-0.1.0.dev0/.import_linter_cache/711309a5eafe379c50451bc7ebdb0a6d9c6d28e6.data.json +0 -1
  89. xtalate-0.1.0.dev0/.import_linter_cache/CACHEDIR.TAG +0 -3
  90. xtalate-0.1.0.dev0/.import_linter_cache/xtalate.meta.json +0 -1
  91. xtalate-0.1.0.dev0/CHANGELOG.md +0 -47
  92. xtalate-0.1.0.dev0/src/xtalate/__init__.py +0 -10
  93. xtalate-0.1.0.dev0/src/xtalate/conversion/preflight.py +0 -127
  94. xtalate-0.1.0.dev0/src/xtalate/parsers/xyz.py +0 -286
  95. xtalate-0.1.0.dev0/src/xtalate/recovery/engine.py +0 -321
  96. xtalate-0.1.0.dev0/src/xtalate/recovery/scenarios.py +0 -86
  97. xtalate-0.1.0.dev0/tests/cli/test_cli.py +0 -220
  98. xtalate-0.1.0.dev0/tests/conversion/test_engine.py +0 -193
  99. xtalate-0.1.0.dev0/tests/conversion/test_preflight.py +0 -111
  100. xtalate-0.1.0.dev0/tests/recovery/test_engine.py +0 -175
  101. xtalate-0.1.0.dev0/tests/recovery/test_scenarios.py +0 -35
  102. xtalate-0.1.0.dev0/tests/validation/test_tolerance.py +0 -56
  103. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/.gitignore +0 -0
  104. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/LICENSE +0 -0
  105. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/NOTICE +0 -0
  106. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/docs/ARCHITECTURE_REVIEW.md +0 -0
  107. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/docs/IMPLEMENTATION_PLAN_v0.1.md +0 -0
  108. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/docs/IMPLEMENTATION_PLAN_v0.2.md +0 -0
  109. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/docs/IMPLEMENTATION_PLAN_v0.3.md +0 -0
  110. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/docs/IMPLEMENTATION_PLAN_v0.4.md +0 -0
  111. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/docs/IMPLEMENTATION_PLAN_v0.5.md +0 -0
  112. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/docs/IMPLEMENTATION_PLAN_v0.6.md +0 -0
  113. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/docs/IMPLEMENTATION_PLAN_v0.7.md +0 -0
  114. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/docs/IMPLEMENTATION_PLAN_v1.0.md +0 -0
  115. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/docs/Incremental_Roadmap_v1.0.md +0 -0
  116. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/examples/.gitkeep +0 -0
  117. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/examples/convert_extxyz_to_poscar.py +0 -0
  118. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/src/xtalate/capabilities/__init__.py +0 -0
  119. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/src/xtalate/capabilities/registry.py +0 -0
  120. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/src/xtalate/cli/__init__.py +0 -0
  121. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/src/xtalate/cli/render.py +0 -0
  122. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/src/xtalate/conversion/report.py +0 -0
  123. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/src/xtalate/discovery/__init__.py +0 -0
  124. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/src/xtalate/discovery/engine.py +0 -0
  125. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/src/xtalate/discovery/report.py +0 -0
  126. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/src/xtalate/discovery/sniffer.py +0 -0
  127. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/src/xtalate/exporters/__init__.py +0 -0
  128. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/src/xtalate/parsers/__init__.py +0 -0
  129. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/src/xtalate/py.typed +0 -0
  130. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/src/xtalate/registry.py +0 -0
  131. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/src/xtalate/schema/__init__.py +0 -0
  132. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/src/xtalate/schema/arrays.py +0 -0
  133. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/src/xtalate/schema/elements.py +0 -0
  134. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/src/xtalate/schema/presence.py +0 -0
  135. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/src/xtalate/sdk/__init__.py +0 -0
  136. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/src/xtalate/sdk/results.py +0 -0
  137. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/src/xtalate/validation/__init__.py +0 -0
  138. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/src/xtalate/validation/report.py +0 -0
  139. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/tests/__init__.py +0 -0
  140. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/tests/_dummy_plugins.py +0 -0
  141. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/tests/_format_helpers.py +0 -0
  142. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/tests/capabilities/__init__.py +0 -0
  143. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/tests/capabilities/test_registry.py +0 -0
  144. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/tests/cli/__init__.py +0 -0
  145. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/tests/conversion/__init__.py +0 -0
  146. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/tests/discovery/__init__.py +0 -0
  147. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/tests/discovery/test_engine.py +0 -0
  148. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/tests/discovery/test_sniffer.py +0 -0
  149. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/tests/exporters/__init__.py +0 -0
  150. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/tests/golden/.gitkeep +0 -0
  151. {xtalate-0.1.0.dev0/tests/recovery → xtalate-0.2.0/tests/golden}/__init__.py +0 -0
  152. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/tests/golden/extxyz/co-in-cell/expected.canonical.json +0 -0
  153. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/tests/golden/extxyz/co-in-cell/sample.extxyz +0 -0
  154. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/tests/golden/poscar/nacl-primitive/POSCAR +0 -0
  155. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/tests/golden/xyz/water-traj/expected.canonical.json +0 -0
  156. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/tests/golden/xyz/water-traj/water_traj.xyz +0 -0
  157. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/tests/parsers/__init__.py +0 -0
  158. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/tests/parsers/test_builtins_registration.py +0 -0
  159. {xtalate-0.1.0.dev0/tests/schema → xtalate-0.2.0/tests/property}/__init__.py +0 -0
  160. {xtalate-0.1.0.dev0/tests/sdk → xtalate-0.2.0/tests/recovery}/__init__.py +0 -0
  161. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/tests/roundtrip/.gitkeep +0 -0
  162. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/tests/roundtrip/__init__.py +0 -0
  163. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/tests/roundtrip/test_identity.py +0 -0
  164. {xtalate-0.1.0.dev0/tests/validation → xtalate-0.2.0/tests/schema}/__init__.py +0 -0
  165. {xtalate-0.1.0.dev0/tests/golden/schema → xtalate-0.2.0/tests/schema/fixtures}/xyz_2frame_3atom.json +0 -0
  166. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/tests/schema/test_arrays.py +0 -0
  167. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/tests/schema/test_paths.py +0 -0
  168. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/tests/schema/test_presence.py +0 -0
  169. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/tests/sdk/test_contracts.py +0 -0
  170. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/tests/validation/test_engine.py +0 -0
  171. {xtalate-0.1.0.dev0 → xtalate-0.2.0}/tests/validation/test_rethreshold.py +0 -0
@@ -0,0 +1,50 @@
1
+ name: "Bug: incorrect conversion"
2
+ description: A conversion kept, dropped, transformed, or fabricated something wrong.
3
+ labels: ["bug", "conversion"]
4
+ body:
5
+ - type: markdown
6
+ attributes:
7
+ value: >-
8
+ The Conversion Report and Validation Report are **designed to be the bug report**
9
+ (`docs/MASTER_SPEC.md` Part 4 §2, Part 5 §3). Paste them below — they tell us far more
10
+ than a prose description can.
11
+ - type: textarea
12
+ id: source
13
+ attributes:
14
+ label: Source file (or minimal reproducer)
15
+ description: >-
16
+ Paste the smallest file that reproduces the problem, or attach it. If it can be
17
+ donated to the golden corpus, say so and its license (see the license field below).
18
+ render: text
19
+ validations:
20
+ required: true
21
+ - type: input
22
+ id: target
23
+ attributes:
24
+ label: Target format
25
+ placeholder: "e.g. poscar"
26
+ validations:
27
+ required: true
28
+ - type: textarea
29
+ id: reports
30
+ attributes:
31
+ label: Conversion Report + Validation Report (JSON)
32
+ description: "Output of `xtalate convert ... --json` (and `xtalate validate ... --json` if relevant)."
33
+ render: json
34
+ validations:
35
+ required: true
36
+ - type: textarea
37
+ id: expected_vs_observed
38
+ attributes:
39
+ label: Expected vs observed
40
+ description: What should the report/output have said or done, and what did it actually do?
41
+ validations:
42
+ required: true
43
+ - type: input
44
+ id: versions
45
+ attributes:
46
+ label: Xtalate + schema versions
47
+ description: "`xtalate --version`, and the `schema_version` from the report."
48
+ placeholder: "xtalate 0.2.0, schema 0.1.0"
49
+ validations:
50
+ required: true
@@ -0,0 +1,54 @@
1
+ name: "Bug: parse failure"
2
+ description: A file failed to parse, or parsed with a wrong or missing ParseIssue.
3
+ labels: ["bug", "parser"]
4
+ body:
5
+ - type: textarea
6
+ id: file
7
+ attributes:
8
+ label: File (or minimal reproducer)
9
+ description: Paste or attach the file that fails to parse.
10
+ render: text
11
+ validations:
12
+ required: true
13
+ - type: textarea
14
+ id: error
15
+ attributes:
16
+ label: Full error envelope / ParseIssue list
17
+ description: >-
18
+ The complete error output (`docs/MASTER_SPEC.md` Part 3 §5, Part 6 §6) — codes,
19
+ messages, line numbers. Run with `--json` if using the CLI.
20
+ render: text
21
+ validations:
22
+ required: true
23
+ - type: input
24
+ id: producer
25
+ attributes:
26
+ label: What produced the file?
27
+ placeholder: "e.g. VASP 6.4, ASE 3.23, hand-written"
28
+ validations:
29
+ required: true
30
+ - type: dropdown
31
+ id: donate
32
+ attributes:
33
+ label: Can this file be donated to the golden corpus?
34
+ description: >-
35
+ If yes, we may add it as a regression fixture. This requires an explicit data
36
+ license (see below) — a file without one cannot be admitted.
37
+ options:
38
+ - "No"
39
+ - "Yes — I hold the rights and grant Apache-2.0 / a compatible license"
40
+ - "Yes — it is published open data (I'll give the license + attribution)"
41
+ validations:
42
+ required: true
43
+ - type: input
44
+ id: license
45
+ attributes:
46
+ label: License status of the file (if donating)
47
+ placeholder: "e.g. CC-BY-4.0 (Materials Project), CC0, my own work"
48
+ - type: input
49
+ id: versions
50
+ attributes:
51
+ label: Xtalate + schema versions
52
+ placeholder: "xtalate 0.2.0, schema 0.1.0"
53
+ validations:
54
+ required: true
@@ -0,0 +1,5 @@
1
+ blank_issues_enabled: false
2
+ contact_links:
3
+ - name: Question or discussion
4
+ url: https://github.com/jsong1218/Xtalate/discussions
5
+ about: For questions and open-ended discussion, use Discussions rather than an issue.
@@ -0,0 +1,48 @@
1
+ name: "Format request"
2
+ description: Request support for a new file format.
3
+ labels: ["enhancement", "format-request"]
4
+ body:
5
+ - type: markdown
6
+ attributes:
7
+ value: >-
8
+ A well-scoped format request — especially with example files and a draft capability
9
+ row — is a genuinely valuable contribution even if you never write a line of code.
10
+ - type: input
11
+ id: name
12
+ attributes:
13
+ label: Format name / spec link
14
+ placeholder: "e.g. CIF — https://www.iucr.org/resources/cif/spec"
15
+ validations:
16
+ required: true
17
+ - type: textarea
18
+ id: capability_draft
19
+ attributes:
20
+ label: Which canonical fields can it express? (a draft capability row!)
21
+ description: >-
22
+ Which of the canonical categories (Geometry, Cell, Trajectory, Dynamics, Electronic,
23
+ Metadata) can this format hold, and which can it not? Honest PARTIAL beats optimistic
24
+ FULL — this becomes the format's Capability Matrix row (`docs/MASTER_SPEC.md` Part 3 §3).
25
+ validations:
26
+ required: true
27
+ - type: textarea
28
+ id: examples
29
+ attributes:
30
+ label: Example files + license status
31
+ description: >-
32
+ Links or attachments, each with its data license. Licensed examples can seed the
33
+ golden corpus; unlicensed ones cannot be admitted.
34
+ validations:
35
+ required: true
36
+ - type: dropdown
37
+ id: contribute
38
+ attributes:
39
+ label: Would you be willing to contribute a plugin?
40
+ description: >-
41
+ Heads up: the plugin SDK is not frozen until v1.0 (risk R12), so a plugin may need to
42
+ follow SDK changes between minor versions until then.
43
+ options:
44
+ - "No — requesting only"
45
+ - "Maybe, with guidance"
46
+ - "Yes"
47
+ validations:
48
+ required: true
@@ -0,0 +1,16 @@
1
+ name: "General bug / docs issue"
2
+ description: Anything that doesn't fit the other templates.
3
+ labels: ["triage"]
4
+ body:
5
+ - type: textarea
6
+ id: description
7
+ attributes:
8
+ label: Description
9
+ description: What's the issue? For a docs issue, link the doc/section.
10
+ validations:
11
+ required: true
12
+ - type: input
13
+ id: versions
14
+ attributes:
15
+ label: Xtalate + schema versions (if applicable)
16
+ placeholder: "xtalate 0.2.0, schema 0.1.0"
@@ -0,0 +1,32 @@
1
+ <!-- Thanks for contributing to Xtalate. Keep PRs small and single-purpose. -->
2
+
3
+ ## What & why
4
+
5
+ <!-- What does this change do, and why? For any nontrivial design decision, name at least
6
+ one reasonable rejected alternative — the standard the whole doc set holds itself to. -->
7
+
8
+ ## Rejected alternative (for design changes)
9
+
10
+ <!-- e.g. "Considered threading choices into build_preflight; rejected because it breaks the
11
+ draft's purity (D46)." Delete this section only for pure docs/typo PRs. -->
12
+
13
+ ## Checklist
14
+
15
+ - [ ] **Docs updated or confirmed unaffected.** The docs are the constitution; a behavior
16
+ change and its doc change are one PR (`docs/MASTER_SPEC.md` Part 10 §4.6).
17
+ - [ ] **Golden cases added** for new behavior, each with a licensed `manifest.yaml`
18
+ (`08 §3.2`), and `tests/golden/ATTRIBUTIONS.md` regenerated (`python
19
+ tests/golden/_governance.py`) if any manifest changed.
20
+ - [ ] **No parser defaulting introduced** — the absence convention holds and the
21
+ default-laundering suite passes (P3, Part 3 §2).
22
+ - [ ] **Completeness invariant green** — the runtime assertion and the property suite
23
+ (`tests/property/`) both pass; no field is lost, dropped, or fabricated silently (P1/P4).
24
+ - [ ] **Capability declarations updated** and the Part 3 §3 table sync test is green.
25
+ - [ ] **Rejected alternative named** in the description for design changes.
26
+ - [ ] **Attribution file regenerates cleanly** — the governance suite
27
+ (`tests/golden/test_corpus_governance.py`) is green.
28
+ - [ ] **Contributed files carry license grants** — for any file added under
29
+ `origin.kind: contributed`, I have the right to contribute it and grant it under the
30
+ recorded license (Apache-2.0 or a compatible data license).
31
+ - [ ] **Full local gate passed:** `ruff check .`, `ruff format --check .`, `mypy`,
32
+ `lint-imports`, `pytest`.
@@ -41,8 +41,14 @@ jobs:
41
41
  - name: Mypy
42
42
  run: mypy
43
43
 
44
+ # Promoted to a required check in v0.2 M11 (Part 10 §1 deferral table, "earliest
45
+ # v0.2"): the import-linter contract from M0 confirmed as a blocking gate.
44
46
  - name: Import-graph lint (P2)
45
47
  run: lint-imports
46
48
 
47
- - name: Pytest
49
+ # Pytest enforces the coverage ratchet (--cov-fail-under in pyproject addopts) and the
50
+ # golden-corpus governance suite: manifest schema + license, per-source sha256, the
51
+ # schema-version migration-chain load, and the ATTRIBUTIONS.md regeneration diff
52
+ # (Part 8 §3; v0.2 M11). A silent fixture edit or a lapsed attribution fails here.
53
+ - name: Pytest (unit + golden + governance, with coverage gate)
48
54
  run: pytest
@@ -0,0 +1,364 @@
1
+ # Changelog
2
+
3
+ All notable changes to Xtalate are recorded here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project adheres to
5
+ [Semantic Versioning](https://semver.org/spec/v2.0.0.html). The canonical schema version is
6
+ tracked separately from the package version and reaches `1.0.0` only in the v1.0 release
7
+ (`docs/MASTER_SPEC.md` Part 2 §5); v0.1 objects carry `schema_version = "0.1.0"`.
8
+
9
+ ## [Unreleased]
10
+
11
+ Post-`0.2.0` architectural-review pass: report-semantics and JSON-tolerance correctness fixes,
12
+ golden-corpus governance hardening, a velocity-bearing corpus case, and internal de-duplication.
13
+ Schema stays `0.1.0`; no normative report/field shapes change.
14
+
15
+ ### Added
16
+
17
+ - **A CONTCAR-with-velocities golden case** (`tests/golden/contcar/co-md-restart/`). CONTCAR was a
18
+ round-trip *target* only; this synthetic case gives it a golden *source* with a Cartesian velocity
19
+ block, so velocities now flow through the identity, two-hop, and three-hop round-trip matrices and
20
+ the completeness invariant — previously the M8 velocity block was unit-tested but never exercised
21
+ as system-level round-trip content.
22
+ - **Report-completeness property coverage for the fabricative recovery family**
23
+ (`tests/property/test_fabricative_recovery_completeness.py`). `missing_velocities`
24
+ (`zero_init`/`maxwell_boltzmann`) and the `maxwell_boltzmann → missing_masses` chain now flow
25
+ through the **independently re-derived** M10 properties, not just the runtime completeness
26
+ assertion — closing the gap where the opt-in fabricative path (never in the shared round-trip
27
+ presets) reached only the runtime guard.
28
+
29
+ ### Changed
30
+
31
+ - **Golden-corpus governance hardened (`docs/DECISIONS.md` D54).** Every data file under
32
+ `tests/golden/` must now be claimed by a manifest — an unmanifested source/expectation, or the
33
+ `manifest.yml` misspelling, fails CI rather than silently bypassing the license/hash/schema
34
+ guarantees. Manifests gain a required `expected_sha256`, verified against `expected.canonical.json`
35
+ exactly as `sha256` guards the source. The schema-lag bound is now two-sided (an expectation
36
+ *ahead* of the current schema major is rejected as impossible, not just one too far behind). The
37
+ two schema-serialization fixtures moved from `tests/golden/schema/` to `tests/schema/fixtures/`, so
38
+ the `ATTRIBUTIONS.md` "every file is admitted only with a license" claim is now literally true.
39
+ - **Internal de-duplication (no behavior change).** The validation status-precedence and
40
+ numeric-field→quantity tables are single-sourced in `xtalate.validation._shared`; the derived-path
41
+ exclusion in `xtalate.schema.paths.DERIVED_PATHS`; the UTC-timestamp helper in `xtalate._time`.
42
+ The single-frame reduction (re-index, `custom_per_frame` slice, `trajectory` drop) shared by
43
+ `frame_selection` recovery and `split_all` export is now one `CanonicalObject.single_frame` method,
44
+ so the two paths can never slice a reduced object differently. The M10 property harness deliberately
45
+ keeps its own independent copies (D50).
46
+
47
+ ### Fixed
48
+
49
+ - **A fabricated (`supplied`) field is no longer also listed `preserved` in the D51 flow
50
+ (`docs/DECISIONS.md` D53).** When a `mixed` cell's only cell-bearing frame was dropped by
51
+ `frame_selection` and `missing_lattice` fabricated a replacement, the report listed
52
+ `cell.lattice_vectors`/`cell.pbc` in `preserved` **and** `removed` **and** `supplied` at once — the
53
+ stale pre-flight optimistic-preserve prediction was never struck once recovery falsified it. The
54
+ Conversion Engine now removes any `supplied` path from `preserved` (the two are mutually exclusive
55
+ per path), leaving the honest **removed + supplied** pair D51 always documented.
56
+ `absence_conformance` correspondingly exempts `supplied` paths from its must-be-absent check (a
57
+ fabricated replacement is expected to reappear), applied identically in the runtime guard and,
58
+ independently, in the M10 property re-derivation. Regression coverage in
59
+ `tests/conversion/test_frame_reduction_completeness.py`.
60
+ - **JSON custom tolerance-table files with scientific-notation bounds now parse
61
+ (`docs/DECISIONS.md` D55).** `--tolerance-profile ./table.json` routed through `yaml.safe_load`,
62
+ whose YAML 1.1 float grammar reads dotless `1e-8` as the *string* `"1e-8"`, so a valid JSON table
63
+ (exactly what `json.dumps` emits) failed with a confusing "must be a number". The CLI now parses
64
+ `.json` files with `json.load` and other extensions with `yaml.safe_load`. Regression test in
65
+ `tests/cli/test_cli.py`.
66
+
67
+ ## [0.2.0] — 2026-07-15
68
+
69
+ v0.2 — **"trustworthy core complete."** The full Part 4 §3.3 recovery scenario catalog, the
70
+ POSCAR/CONTCAR velocity block with velocity/mass recovery, the cross-format round-trip matrix
71
+ with custom tolerance tables, the report-completeness property test, and the corpus-governance
72
+ and contributor surface that make outside golden-corpus contributions realistic. Schema
73
+ version is unchanged at `0.1.0`; the four v0.1 formats (XYZ, extXYZ, POSCAR, CONTCAR) are the
74
+ supported set. Pre-1.0, a minor bump may still break — the plugin SDK is not frozen until v1.0
75
+ (risk R12).
76
+
77
+ ### Added
78
+
79
+ - **Golden-corpus governance, contributor surface, and the v0.2 release (v0.2 M11).** The last
80
+ milestone of the break: the governance that makes the golden corpus a corpus a stranger can
81
+ extend without a maintainer in the loop, mechanized so it cannot silently rot.
82
+ - **Manifest governance suite** (`tests/golden/test_corpus_governance.py`, `_governance.py`;
83
+ `docs/MASTER_SPEC.md` Part 8 §3): every `manifest.yaml` is schema-validated (required
84
+ fields incl. `origin.kind`, `origin.license`, `sha256`); a **missing or blank license is a
85
+ hard CI failure** — *no manifest, no license, no merge* (§3.2). The recorded `sha256` of
86
+ every source file is re-verified, so a silent fixture edit is impossible. CC-BY origins
87
+ require an `attribution`, and published-dataset origins require a `source`.
88
+ - **Schema-version sync check** (§3.3): every `expected.canonical.json` loads **through the
89
+ migration chain** (`load_expected_through_migration_chain` — the identity today, the seam
90
+ for real migrations when the schema versions past `0.1.0`), its embedded `schema_version`
91
+ is cross-checked against the manifest's `canonical_schema_version`, and CI fails if any
92
+ manifest lags more than one **major** version behind current.
93
+ - **`tests/golden/ATTRIBUTIONS.md` regenerated from manifests and diffed in CI** (§3.2): the
94
+ aggregate attribution file is *generated* (`python tests/golden/_governance.py`), never
95
+ hand-edited, and the suite fails on any drift — an attribution obligation can never silently
96
+ lapse.
97
+ - **CI gates promoted** (Part 10 §1 deferral table, earliest v0.2): a **coverage ratchet**
98
+ (`--cov-fail-under=91` in `pyproject.toml`, set below the measured ~92.6% branch coverage with slack — a floor
99
+ that rises, never lowers, adding **`pytest-cov`** as a dev dependency), and the M0
100
+ import-linter contract confirmed as a required check.
101
+ - **`CONTRIBUTING.md`** (Part 10 §4.3): the docs-are-the-constitution rule, the non-negotiables
102
+ (absence convention, completeness invariant, glossary), the add-a-format checklist, and the
103
+ Tier 0 dev loop — plus the two honesty clauses (golden-corpus contributions are the invited
104
+ path now; parser contributions are welcome-with-churn-warning until the SDK freezes at v1.0,
105
+ risk R12).
106
+ - **Issue + PR templates** (Part 10 §4.4–4.5, `.github/ISSUE_TEMPLATE/`,
107
+ `.github/PULL_REQUEST_TEMPLATE.md`): the reports-are-the-bug-report intake for incorrect
108
+ conversions and parse failures, a format-request template with a draft capability row, and a
109
+ PR checklist including the **license-grant checkbox** for contributed files.
110
+ - **Release:** version bumped to **0.2.0**; README "what v0.2 does / does not do" scope
111
+ statement updated honestly (the round-trip matrix, custom tolerance tables, and recovery
112
+ catalog now shipped). Tag + PyPI/GitHub publish remain the maintainer's manual step
113
+ (`docs/DECISIONS.md` D52).
114
+ - **Report-completeness property test (v0.2 M10).** The single most important test in the repository
115
+ (`docs/MASTER_SPEC.md` Part 8 §1.2), mechanically enforcing **P1** (no silent loss) and **P4** (no
116
+ misfiled fabrication) over conversions that *have not happened yet* — the test-time generalization
117
+ of the v0.1-M4 runtime completeness assertion.
118
+ - **Two properties, re-derived in test code** (`tests/property/_properties.py`, deliberately not
119
+ importing the production guard so it is an independent check — D50): **Property 1 — the
120
+ completeness invariant** (every source-`present`/`mixed` path appears in `preserved` ∪ `removed`;
121
+ every `supplied` entry names a source-`absent` path traced to a recorded Assumption) and
122
+ **Property 2 — absence conformance** (every `removed` path is absent in the re-parsed output).
123
+ - **Stage-1 generator — parametrized golden mutations** (`tests/property/_generators.py`):
124
+ systematically nulls or populates **each optional canonical field-path** of the worked-example
125
+ goldens (all eight schema categories reached) plus a per-frame `mixed` configuration, yielding 59
126
+ valid Canonical Objects; `tests/property/test_report_completeness.py` drives every `(mutant,
127
+ target)` pair through the real Conversion Engine under the `strict` profile with fixed recovery
128
+ presets, asserting both properties on every report — including refused reports, which must still
129
+ satisfy the completeness invariant. Every mutant re-validates through the model validators, so an
130
+ invariant-violating mutation fails loudly at generation rather than producing a dead test.
131
+ - **Stage-2 generator — hypothesis strategies over randomized objects** (`tests/property/
132
+ _strategies.py`, `test_report_completeness_hypothesis.py`): randomized Canonical Objects with
133
+ independent presence draws across all categories and **shrinking** on failure, exercising the
134
+ field-*combinations* and multi-field `mixed` configs the one-at-a-time sweep cannot. Bounded to
135
+ `max_examples=200` for the PR suite (Part 8 §5); v0.3's nightly workflow hosts the extended budget.
136
+ Adds **`hypothesis`** as a **test-only** dev dependency (`docs/DECISIONS.md` D50) — the
137
+ minimal-dependency posture governs runtime deps, which are unchanged.
138
+ - **Independent-guard proof** (M10 done-means): feeding a tampered report — one `removed` entry
139
+ dropped, or a `supplied` entry's Assumption removed — to the property checker is caught as silent
140
+ loss / silent fabrication, demonstrating the property catches the class of bug the runtime
141
+ assertion does, without the runtime assertion in the loop. A non-vacuity guard asserts the
142
+ stage-1 lattice actually exercises both `removed` and `supplied` across its pairs.
143
+
144
+ - **Cross-format round-trip matrix suites + custom tolerance-table files (v0.2 M9).** v0.1 proved
145
+ *identity* round-trips (`A → Canonical → A`); v0.2 adds the cross-format matrix that catches
146
+ parser/exporter **asymmetry**, plus the deferred tolerance-file feature.
147
+ - **Two-hop suite** (`A → Canonical → B → Canonical′`, `tests/roundtrip/test_two_hop.py`):
148
+ parametrized over **every `(source, target)` pair enumerated from the registry**, driven through
149
+ the real Conversion + Validation engines under the **`strict`** profile, with fabricative/
150
+ selective gaps resolved by fixed recovery presets so Assumption recording is exercised end to end.
151
+ The comparable subspace is **computed from the Capability Matrix at test time, never hand-listed**
152
+ (`tests/roundtrip/_matrix.py`); reusing the Validation Engine's diff is deliberate (D49).
153
+ - **Three-hop return** (`A → B → A`, `test_three_hop.py`): the symmetric-bug catcher over the
154
+ curated high-risk pairs (`xyz↔extxyz`, `poscar↔extxyz`, `poscar↔contcar`), each anchored to its
155
+ golden `expected.canonical.json` and diffed over the matrix-computed subspace by a dedicated
156
+ comparator (`_compare.py`). Velocity round-trips (POSCAR↔extXYZ) join automatically via M8's
157
+ capability rows — the matrix, not a hand-list, governs coverage.
158
+ - **Registry-driven enumeration** (`test_matrix_enumeration.py`): registering a dummy in-test
159
+ format grows the enumerated targets, sources, and pair list — and the comparable-subspace
160
+ machinery answers for it — with zero suite edits, the mechanical guarantee behind **P6**.
161
+ - **Custom tolerance-table files.** `--tolerance-profile FILE` now accepts a YAML/JSON table
162
+ (`ToleranceProfile.from_mapping`) of per-quantity `{warn, fail}` overrides on the `default`
163
+ bases; omitted quantities inherit their default. The non-configurable Part 5 §4.4 rules are
164
+ enforced by rejection (the `k_*` multipliers and representational-bound floor are fixed; discrete
165
+ checks admit no tolerance), with actionable errors. `xtalate convert … --tolerance-profile
166
+ ./custom.yaml` and `xtalate validate --validation-report … --tolerance-profile ./custom.yaml`
167
+ (offline re-threshold) both work; the profile name is embedded in the Validation Report.
168
+ - **New dependency: PyYAML** (`[project].dependencies`) — the config language for tolerance tables
169
+ and the golden-corpus manifests M11 governs (D48). See `docs/DECISIONS.md` D48–D49.
170
+
171
+ - **POSCAR velocity block + Maxwell–Boltzmann velocity/mass recovery (v0.2 M8).** The one deliberate
172
+ v0.1 format deferral lands, together with the two velocity-family recovery scenarios it unlocks.
173
+ - **POSCAR/CONTCAR Direct-mode velocities.** The velocity block is now read in both Cartesian
174
+ (Å/fs, stored verbatim) and Direct (fractional) conventions; Direct velocities are converted to
175
+ Cartesian Å/fs via the lattice, with a parse note, and an ambiguous mode line is read as Direct
176
+ with a `POSCAR_AMBIGUOUS_VELOCITY_MODE` warning. Absence is preserved (`velocities = None`, never
177
+ zero-filled). Export stays Cartesian (Direct-mode export is the M8 cut line).
178
+ - **`missing_velocities`** resolves with `zero_init` (an explicit rest state), `maxwell_boltzmann`
179
+ (`temperature_K`, `seed` — both recorded for reproducibility; the raw sample is emitted with no
180
+ centre-of-mass-drift removal, `docs/DECISIONS.md` D43/D45), `upload_reference` (velocities
181
+ borrowed from a second structure, shape-checked), and ✳`omit` (leave velocities absent — offered
182
+ only when the target field is optional and the mode is permissive).
183
+ - **`missing_masses`** resolves with `standard_masses` (IUPAC standard atomic weights from ASE, a
184
+ *reported default* — D44) and `manual_input`. A `maxwell_boltzmann` draw over a source without
185
+ masses **chains** a `missing_masses` recovery, resolving masses first and recording two
186
+ Assumptions; for a target that cannot store masses (POSCAR) the masses are audited in `supplied`
187
+ but not written (D47).
188
+ - **Opt-in emission.** Velocity/mass fabrication is requested by supplying the recovery choice —
189
+ it is never auto-triggered, since no v0.1 target *requires* these fields. The wiring lives in
190
+ `ConversionEngine.convert` via a new `on_demand_fabricative_scenarios` helper (D46), which
191
+ refuses incoherent requests (field already present on the source; or emission to a target that
192
+ cannot store it) as caller errors.
193
+ - CLI: `xtalate convert traj.extxyz --to poscar --recover missing_lattice=… --recover
194
+ frame_selection=last --recover missing_velocities=maxwell_boltzmann,temperature_K=300,seed=42`
195
+ produces a POSCAR with a velocity block, byte-identical on re-run. See `docs/DECISIONS.md`
196
+ D43–D47.
197
+
198
+ - **Recovery scenario catalog completion — Slice 2 (v0.2 M7).** The remaining catalog resolvers land,
199
+ completing M7 for the four v0.1 formats.
200
+ - **Parse-time recovery.** `missing_species` (a VASP-4 POSCAR with atom counts but no element
201
+ symbols) and `truncate_corrupt_tail` (a trajectory with a corrupt final frame) fire before a
202
+ Canonical Object exists; they are resolved through a new optional `ParserPlugin.parse_recover`
203
+ hook and a `parse_with_recovery` orchestrator that re-parses under the caller's preset and threads
204
+ the resulting Assumption into the Conversion Report. `missing_species` supports `species_map`
205
+ (ordered symbols, `--recover missing_species=species_map,species=H:O`) and `upload_reference`;
206
+ `truncate_corrupt_tail` supports `truncate` (keep the valid prefix) and `abort`. A recoverable
207
+ parse error without a preset now prints an actionable hint.
208
+ - **`upload_reference`** is offered for `missing_lattice` (and `missing_species`): the lattice or
209
+ symbols are borrowed from a second structure named with `--recover …=upload_reference,file=PATH`,
210
+ behind atom-count / alignment compatibility checks.
211
+ - **`split_all`** (`frame_selection=split_all`) writes **one output file per frame** into the
212
+ directory named by `-o`, via a new `ConversionResult.outputs`; each file is validated and the
213
+ per-file Validation Reports are merged into one. This closes the Slice-1 cut — `frame_selection`
214
+ now offers `split_all` wherever it triggers.
215
+ - New: the optional `ParserPlugin.parse_recover` SDK hook (additive), `conversion.parse_with_recovery`
216
+ / `ParseRecovery`, and `ConversionResult.outputs`. See `docs/DECISIONS.md` D38–D40.
217
+
218
+ - **Recovery scenario catalog completion (v0.2 M7, Slice 1).** The Recovery Engine, which v0.1
219
+ shipped resolving two scenarios preset-only, now registers and hazard-classifies the **full
220
+ MASTER_SPEC Part 4 §3.3 catalog of eight scenarios**, so classification and the honest-option-list
221
+ rule are mechanically complete for the four v0.1 formats.
222
+ - **`constraint_representation`** resolves with `project` (keep the target's representable
223
+ constraint subset, e.g. POSCAR `selective_dynamics`; the unrepresentable remainder is reported
224
+ in `removed`) or `drop_all`. Either way one Assumption is recorded and **no** field is supplied
225
+ — the kept constraints are genuine source data (selective-reductive, never fabricative).
226
+ - `missing_velocities`, `missing_masses`, `missing_energy`, `missing_species`, and
227
+ `truncate_corrupt_tail` are registered and classified but honestly **refuse** in this slice —
228
+ their resolvers land in M8 (the velocity/mass family) and v0.2 Slice 2 (the parse-time
229
+ scenarios). `missing_energy` is deliberately optionless (no scientifically defensible synthetic
230
+ energy exists).
231
+ - Option lists are **computed per source/target pair**, not static: the ✳`non_periodic` option of
232
+ `missing_lattice` is offered only for a target that can express an open cell (extXYZ, never
233
+ POSCAR), driven by a new machine-readable `allows_open_boundaries` write-capability flag
234
+ (`docs/DECISIONS.md` D35). (`split_all` was the Slice-1 cut line — it lands in Slice 2, above.)
235
+ - The Recovery Engine's dispatch is now a generalized dependency-ordered resolver table
236
+ (`frame_selection` → `constraint_representation` → `missing_lattice`), replacing the hard-coded
237
+ two-scenario branch (`docs/DECISIONS.md` D37).
238
+
239
+ ### Changed
240
+
241
+ - **A PARTIAL constraint capability now triggers recovery instead of auto-preserving.** A source
242
+ carrying a non-empty `dynamics.constraints` list converted to a target that can represent only a
243
+ *subset* of constraint kinds (POSCAR: `selective_dynamics`) no longer silently keeps-what-fits:
244
+ *which* constraints survive changes the physics of a downstream relaxation, so it is now a recorded
245
+ `constraint_representation` choice, and such a conversion **refuses without an explicit preset**.
246
+ `NONE` capability stays ordinary bulk-reductive loss; `FULL` stays preserved; an empty
247
+ `constraints=[]` preserves normally (`docs/DECISIONS.md` D36; `MASTER_SPEC` Revision 1.8).
248
+ - **Honest-loss annotations tightened.** An extXYZ `momenta` column now records the
249
+ "velocities converted" parse-note even when it is explicitly all-zero (a source stating the atoms
250
+ are at rest is information, §2 rule 3), and a CONTCAR velocity tail now annotates
251
+ `source_units["velocities"] = "angstrom/fs"` with a parse-note, rather than storing the block
252
+ with its unit left implicit.
253
+
254
+ ### Fixed
255
+
256
+ - **`frame_selection` no longer silently drops a per-frame field that lived only in a dropped
257
+ frame (v0.2 M10).** Found by the stage-2 property test. When `frame_selection` reduced a trajectory
258
+ to one structure, a per-frame path present *only* in the dropped frames (e.g. a `mixed`
259
+ `dynamics.constraints`) was eliminated with no `removed` entry — silent loss (**P1**) that the
260
+ runtime completeness invariant caught as a crash. `frame_selection` now records a `removed` entry
261
+ for every per-frame path the reduction eliminates (`recovery.engine._per_frame_paths_lost`), and
262
+ `conversion.engine` dedupes `removed` by path so a NONE-capability field flagged by both the
263
+ capability diff and the reduction is listed once. Regression fixtures in
264
+ `tests/conversion/test_frame_reduction_completeness.py`.
265
+ - **`constraint_representation=drop_all` now records the removal of an explicitly-unconstrained
266
+ `constraints=[]` (v0.2 M10).** Also found by stage 2: an empty (present, §3.6) constraint list on
267
+ the retained frame was nulled out of the write plan with a zero dropped-count and recorded in
268
+ neither `preserved` nor `removed`. It is now reported `removed`.
269
+ - **A `mixed` cell converted to a lattice-requiring target no longer crashes the exporter (v0.2
270
+ M10, D51).** A cell present in only some frames (`mixed`) whose cell-bearing frame `frame_selection`
271
+ dropped left the POSCAR/CONTCAR exporter with no lattice and raised `ValueError`, because
272
+ `missing_lattice` was detected only on a fully-`absent` required field. Pre-flight now offers
273
+ `missing_lattice` on any *not-uniformly-present* required field, and the Recovery Engine resolves it
274
+ **lazily** against the retained frame: fabricate a lattice for the cell-less frame (with a preset,
275
+ never overwriting a real cell), refuse cleanly (without one), or no-op when the retained frame kept
276
+ a real cell. The completeness invariant's P4 supplied-check is correspondingly relaxed to permit a
277
+ path that is *both* `removed` (the dropped frame's cell) and `supplied` (the fabricated
278
+ replacement) — honest, since both are reported. Regression fixtures in
279
+ `tests/conversion/test_frame_reduction_completeness.py`; the M10 stage-2 generator now exercises
280
+ `mixed` cells freely.
281
+ - **XYZ-with-comments → extXYZ no longer false-fails validation.** The extXYZ exporter writes a
282
+ carried-through comment key (`xyz:comment`) faithfully, but the parser re-namespaced *every*
283
+ comment key under `extxyz:`, so the value round-tripped under a changed path
284
+ (`extxyz:xyz:comment`) and the Validation Engine's `metadata_preservation` check reported the
285
+ planned path absent — marking every such conversion `failed`. The parser now skips the `extxyz:`
286
+ tag for a key that already carries a `<format>:` namespace, so foreign keys round-trip verbatim
287
+ while bare extXYZ keys are namespaced as before (`docs/DECISIONS.md` D41).
288
+ - **extXYZ → plain XYZ no longer false-fails on a foreign per-frame key (the D41 sibling).** Plain
289
+ XYZ holds one free-text comment line per frame (`xyz:comment`) and nothing else, but its exporter
290
+ declared the whole `custom_per_frame` container writable — so pre-flight predicted a foreign key
291
+ (an extXYZ `config_type`) Preserved, the exporter silently dropped it, and `metadata_preservation`
292
+ marked the conversion `failed`. `FormatCapabilities` gains `writable_custom_keys` (the per-key
293
+ analogue of `representable_constraint_kinds`): an unwritable per-frame key is now honestly reported
294
+ `removed`, while `xyz:comment` is still preserved (and the identity round-trip still passes) via a
295
+ per-key write plan (`docs/DECISIONS.md` D42).
296
+ The remaining eight entries are a **post-v0.1 correctness pass** — defects found by a review that
297
+ exercised the shipped v0.1 code against real inputs, each reproduced, fixed, and pinned with a
298
+ regression test, folded into this release:
299
+
300
+ - **POSCAR/CONTCAR conversions no longer false-fail validation.** The scaling factor is now
301
+ recorded as a `provenance` parse-note instead of `simulation.extra` (it is already folded into
302
+ the lattice vectors, §4). Storing it in `simulation.extra` — which no exporter can carry — made
303
+ *every* POSCAR→POSCAR/CONTCAR conversion fail `absence_conformance`, since the re-parse always
304
+ re-derives a scale (`docs/DECISIONS.md` D34).
305
+ - **POSCAR exporter reports its element-grouping permutation** (`atom_permutation`). Any
306
+ element-interleaved source (e.g. XYZ `H O H`) to POSCAR previously false-failed
307
+ `species_preservation`/`positions_rmsd` as "chemistry lost" because validation compared under
308
+ source order while the exporter had regrouped by element.
309
+ - **POSCAR coordinate-mode line now follows VASP semantics** — only `C/c/K/k` is Cartesian; every
310
+ other line (`Direct`, `Fractional`, blank, garbage) is fractional, with an ambiguous line flagged
311
+ (`POSCAR_AMBIGUOUS_COORDINATE_MODE`). The prior logic misread any non-`d` mode as Cartesian Å —
312
+ silent scientific corruption.
313
+ - **Text parsers honor the error contract on non-UTF-8 input.** XYZ, extXYZ, and POSCAR now raise a
314
+ structured `ParseError` (`*_ENCODING_ERROR`) instead of a raw `UnicodeDecodeError`.
315
+ - **extXYZ string-typed per-atom columns** (a `:S:` property such as a per-atom label) are carried
316
+ as a JSON-scalar list instead of crashing on `astype(float)`.
317
+ - **CLI `convert --json -o PATH` writes the output file.** It previously reported success while
318
+ silently writing nothing; the report JSON and the artifact are now independent outputs, and the
319
+ file-write notice goes to stderr so stdout stays pure JSON.
320
+ - **Invalid `--recover` presets** exit cleanly (usage error) instead of printing a traceback.
321
+ - **POSCAR exporter rejects unrepresentable constraints** with a clear error instead of an
322
+ `IndexError` when handed a non-`selective_dynamics` constraint.
323
+
324
+ ## [0.1.0] — 2026-07-10
325
+
326
+ First release: the complete pure-Python **library + CLI** core. It converts between four
327
+ computational-chemistry formats while reporting — and independently re-validating — every
328
+ byte of scientific information kept, dropped, or fabricated.
329
+
330
+ ### Added
331
+
332
+ - **Canonical Data Model** (`xtalate.schema`) — the single internal schema every parser
333
+ writes and every exporter reads, with the normative absence convention (`None` = "never in
334
+ the source" vs a real zero) and an on-demand `field_presence()` introspection.
335
+ - **Plugin SDK, Format Sniffer, and Capability Matrix** (`xtalate.sdk`,
336
+ `xtalate.discovery`, `xtalate.capabilities`) — stable parser/exporter contracts, a
337
+ generic confidence-scored sniffer, and a per-format, per-field read/write capability registry.
338
+ - **Formats** (`xtalate.parsers`, `xtalate.exporters`) — read and write for plain **XYZ**,
339
+ **extended XYZ** (ASE-backed, with default-laundering), **POSCAR**, and **CONTCAR**, each with a
340
+ golden round-trip and error-fixture suite.
341
+ - **Information Discovery Engine** (`xtalate.discovery`) — the ✓/✗ Discovery Report: a file's
342
+ canonical-field inventory annotated with the detected format's read capability.
343
+ - **Conversion Engine** (`xtalate.conversion`) — the pre-flight capability diff, the
344
+ `write_plan` discipline (materialized as a filtered `canonical′`), the Conversion Report, and
345
+ the completeness invariant enforced as an always-on runtime assertion.
346
+ - **Recovery Engine** (`xtalate.recovery`) — explicit, preset-only resolution of the
347
+ `frame_selection` and `missing_lattice` scenarios under the three-way hazard model; every
348
+ choice recorded as an Assumption, and a structured **refusal** when no choice is supplied.
349
+ - **Validation Engine** (`xtalate.validation`) — the unconditional post-conversion re-parse
350
+ and nine-check diff (Part 5 §2) under named tolerance profiles (`default`/`strict`/`loose`),
351
+ plus stored-report re-thresholding.
352
+ - **CLI** (`xtalate`) — `inspect`, `convert`, `validate`, and `capabilities`, with the
353
+ CI-native exit-code contract (`0`/`2`/`3`/`4`/`5`/`1`) and `--json` structured output.
354
+
355
+ ### Known limitations (v0.1 scope)
356
+
357
+ - No web service, REST API, or UI (v0.5 / v0.6).
358
+ - CIF, XDATCAR, and ASE `.traj` — the remaining Phase-1 formats — are not yet implemented (v0.2+).
359
+ - Recovery is preset-only; tolerance profiles are the three named ones (custom tables are later
360
+ seams).
361
+
362
+ [Unreleased]: https://github.com/jsong1218/Xtalate/compare/v0.2.0...HEAD
363
+ [0.2.0]: https://github.com/jsong1218/Xtalate/compare/v0.1.0...v0.2.0
364
+ [0.1.0]: https://github.com/jsong1218/Xtalate/releases/tag/v0.1.0
@@ -10,8 +10,8 @@ type: software
10
10
  authors:
11
11
  - family-names: Song
12
12
  given-names: Jiayu
13
- version: 0.1.0
14
- date-released: 2026-07-10
13
+ version: 0.2.0
14
+ date-released: 2026-07-15
15
15
  license: Apache-2.0
16
16
  repository-code: "https://github.com/jsong1218/Xtalate"
17
17
  keywords: