okf-parser 0.43.0__tar.gz → 0.45.2__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 (255) hide show
  1. {okf_parser-0.43.0 → okf_parser-0.45.2}/Cargo.lock +8 -8
  2. {okf_parser-0.43.0 → okf_parser-0.45.2}/PKG-INFO +2 -2
  3. {okf_parser-0.43.0 → okf_parser-0.45.2}/README.md +1 -1
  4. {okf_parser-0.43.0 → okf_parser-0.45.2}/benchmarks/README.md +2 -2
  5. {okf_parser-0.43.0 → okf_parser-0.45.2}/benchmarks/python_parser.py +11 -0
  6. {okf_parser-0.43.0 → okf_parser-0.45.2}/benchmarks/rust_facts.py +11 -0
  7. okf_parser-0.45.2/changelog/0.43.1.md +10 -0
  8. okf_parser-0.45.2/changelog/0.44.0.md +10 -0
  9. okf_parser-0.45.2/changelog/0.45.0.md +10 -0
  10. okf_parser-0.45.2/changelog/0.45.1.md +9 -0
  11. okf_parser-0.45.2/changelog/0.45.2.md +11 -0
  12. {okf_parser-0.43.0 → okf_parser-0.45.2}/native-npm-linux-x64/package.json +1 -1
  13. {okf_parser-0.43.0 → okf_parser-0.45.2}/okf-engine/Cargo.toml +1 -1
  14. {okf_parser-0.43.0 → okf_parser-0.45.2}/pyproject.toml +1 -1
  15. {okf_parser-0.43.0 → okf_parser-0.45.2}/rust-core/Cargo.toml +2 -2
  16. {okf_parser-0.43.0 → okf_parser-0.45.2}/scripts/check_no_duckdb_link.py +7 -0
  17. {okf_parser-0.43.0 → okf_parser-0.45.2}/scripts/native_from_wheel.py +7 -0
  18. {okf_parser-0.43.0 → okf_parser-0.45.2}/scripts/registry_state.py +24 -4
  19. {okf_parser-0.43.0 → okf_parser-0.45.2}/scripts/release_contract.py +32 -0
  20. {okf_parser-0.43.0 → okf_parser-0.45.2}/src/okf_parser/__init__.py +3 -0
  21. {okf_parser-0.43.0 → okf_parser-0.45.2}/src/okf_parser/bundle_import.py +105 -0
  22. {okf_parser-0.43.0 → okf_parser-0.45.2}/src/okf_parser/cli.py +4 -0
  23. okf_parser-0.45.2/src/okf_parser/concepts.py +118 -0
  24. {okf_parser-0.43.0 → okf_parser-0.45.2}/src/okf_parser/models.py +5 -0
  25. {okf_parser-0.43.0 → okf_parser-0.45.2}/src/okf_parser/service.py +2 -0
  26. okf_parser-0.45.2/tests/test_concepts.py +177 -0
  27. okf_parser-0.45.2/tests/test_import_preview_token.py +143 -0
  28. {okf_parser-0.43.0 → okf_parser-0.45.2}/tests/test_mcp.py +35 -8
  29. okf_parser-0.45.2/tests/test_registry_state_native_npm.py +172 -0
  30. {okf_parser-0.43.0 → okf_parser-0.45.2}/tests/test_release_contract.py +65 -4
  31. {okf_parser-0.43.0 → okf_parser-0.45.2}/typescript/package-lock.json +4 -4
  32. {okf_parser-0.43.0 → okf_parser-0.45.2}/typescript/package.json +2 -2
  33. okf_parser-0.45.2/typescript/src/version.ts +1 -0
  34. {okf_parser-0.43.0 → okf_parser-0.45.2}/typescript-duckdb/package-lock.json +5 -5
  35. {okf_parser-0.43.0 → okf_parser-0.45.2}/typescript-duckdb/package.json +2 -2
  36. {okf_parser-0.43.0 → okf_parser-0.45.2}/uv.lock +3 -3
  37. okf_parser-0.43.0/typescript/src/version.ts +0 -1
  38. {okf_parser-0.43.0 → okf_parser-0.45.2}/.gitignore +0 -0
  39. {okf_parser-0.43.0 → okf_parser-0.45.2}/Cargo.toml +0 -0
  40. {okf_parser-0.43.0 → okf_parser-0.45.2}/action.yml +0 -0
  41. {okf_parser-0.43.0 → okf_parser-0.45.2}/benchmarks/__init__.py +0 -0
  42. {okf_parser-0.43.0 → okf_parser-0.45.2}/benchmarks/results/2026-08-08-rust-mvp.md +0 -0
  43. {okf_parser-0.43.0 → okf_parser-0.45.2}/benchmarks/results/2026-08-09-rust-e2e.md +0 -0
  44. {okf_parser-0.43.0 → okf_parser-0.45.2}/benchmarks/rust-facts.mjs +0 -0
  45. {okf_parser-0.43.0 → okf_parser-0.45.2}/benchmarks/typescript-parser.mjs +0 -0
  46. {okf_parser-0.43.0 → okf_parser-0.45.2}/changelog/0.1.0.md +0 -0
  47. {okf_parser-0.43.0 → okf_parser-0.45.2}/changelog/0.10.0.md +0 -0
  48. {okf_parser-0.43.0 → okf_parser-0.45.2}/changelog/0.11.0.md +0 -0
  49. {okf_parser-0.43.0 → okf_parser-0.45.2}/changelog/0.11.1.md +0 -0
  50. {okf_parser-0.43.0 → okf_parser-0.45.2}/changelog/0.12.0.md +0 -0
  51. {okf_parser-0.43.0 → okf_parser-0.45.2}/changelog/0.13.0.md +0 -0
  52. {okf_parser-0.43.0 → okf_parser-0.45.2}/changelog/0.14.0.md +0 -0
  53. {okf_parser-0.43.0 → okf_parser-0.45.2}/changelog/0.14.1.md +0 -0
  54. {okf_parser-0.43.0 → okf_parser-0.45.2}/changelog/0.14.2.md +0 -0
  55. {okf_parser-0.43.0 → okf_parser-0.45.2}/changelog/0.15.0.md +0 -0
  56. {okf_parser-0.43.0 → okf_parser-0.45.2}/changelog/0.15.1.md +0 -0
  57. {okf_parser-0.43.0 → okf_parser-0.45.2}/changelog/0.15.2.md +0 -0
  58. {okf_parser-0.43.0 → okf_parser-0.45.2}/changelog/0.15.3.md +0 -0
  59. {okf_parser-0.43.0 → okf_parser-0.45.2}/changelog/0.15.4.md +0 -0
  60. {okf_parser-0.43.0 → okf_parser-0.45.2}/changelog/0.16.0.md +0 -0
  61. {okf_parser-0.43.0 → okf_parser-0.45.2}/changelog/0.17.0.md +0 -0
  62. {okf_parser-0.43.0 → okf_parser-0.45.2}/changelog/0.18.0.md +0 -0
  63. {okf_parser-0.43.0 → okf_parser-0.45.2}/changelog/0.18.1.md +0 -0
  64. {okf_parser-0.43.0 → okf_parser-0.45.2}/changelog/0.19.0.md +0 -0
  65. {okf_parser-0.43.0 → okf_parser-0.45.2}/changelog/0.19.1.md +0 -0
  66. {okf_parser-0.43.0 → okf_parser-0.45.2}/changelog/0.2.0.md +0 -0
  67. {okf_parser-0.43.0 → okf_parser-0.45.2}/changelog/0.20.0.md +0 -0
  68. {okf_parser-0.43.0 → okf_parser-0.45.2}/changelog/0.20.1.md +0 -0
  69. {okf_parser-0.43.0 → okf_parser-0.45.2}/changelog/0.21.0.md +0 -0
  70. {okf_parser-0.43.0 → okf_parser-0.45.2}/changelog/0.22.0.md +0 -0
  71. {okf_parser-0.43.0 → okf_parser-0.45.2}/changelog/0.23.0.md +0 -0
  72. {okf_parser-0.43.0 → okf_parser-0.45.2}/changelog/0.24.0.md +0 -0
  73. {okf_parser-0.43.0 → okf_parser-0.45.2}/changelog/0.25.0.md +0 -0
  74. {okf_parser-0.43.0 → okf_parser-0.45.2}/changelog/0.26.0.md +0 -0
  75. {okf_parser-0.43.0 → okf_parser-0.45.2}/changelog/0.27.0.md +0 -0
  76. {okf_parser-0.43.0 → okf_parser-0.45.2}/changelog/0.28.0.md +0 -0
  77. {okf_parser-0.43.0 → okf_parser-0.45.2}/changelog/0.29.0.md +0 -0
  78. {okf_parser-0.43.0 → okf_parser-0.45.2}/changelog/0.3.0.md +0 -0
  79. {okf_parser-0.43.0 → okf_parser-0.45.2}/changelog/0.30.0.md +0 -0
  80. {okf_parser-0.43.0 → okf_parser-0.45.2}/changelog/0.31.0.md +0 -0
  81. {okf_parser-0.43.0 → okf_parser-0.45.2}/changelog/0.32.0.md +0 -0
  82. {okf_parser-0.43.0 → okf_parser-0.45.2}/changelog/0.32.1.md +0 -0
  83. {okf_parser-0.43.0 → okf_parser-0.45.2}/changelog/0.33.0.md +0 -0
  84. {okf_parser-0.43.0 → okf_parser-0.45.2}/changelog/0.34.0.md +0 -0
  85. {okf_parser-0.43.0 → okf_parser-0.45.2}/changelog/0.35.0.md +0 -0
  86. {okf_parser-0.43.0 → okf_parser-0.45.2}/changelog/0.36.0.md +0 -0
  87. {okf_parser-0.43.0 → okf_parser-0.45.2}/changelog/0.37.0.md +0 -0
  88. {okf_parser-0.43.0 → okf_parser-0.45.2}/changelog/0.38.0.md +0 -0
  89. {okf_parser-0.43.0 → okf_parser-0.45.2}/changelog/0.39.0.md +0 -0
  90. {okf_parser-0.43.0 → okf_parser-0.45.2}/changelog/0.39.1.md +0 -0
  91. {okf_parser-0.43.0 → okf_parser-0.45.2}/changelog/0.4.0.md +0 -0
  92. {okf_parser-0.43.0 → okf_parser-0.45.2}/changelog/0.40.0.md +0 -0
  93. {okf_parser-0.43.0 → okf_parser-0.45.2}/changelog/0.41.0.md +0 -0
  94. {okf_parser-0.43.0 → okf_parser-0.45.2}/changelog/0.41.1.md +0 -0
  95. {okf_parser-0.43.0 → okf_parser-0.45.2}/changelog/0.41.2.md +0 -0
  96. {okf_parser-0.43.0 → okf_parser-0.45.2}/changelog/0.41.3.md +0 -0
  97. {okf_parser-0.43.0 → okf_parser-0.45.2}/changelog/0.42.0.md +0 -0
  98. {okf_parser-0.43.0 → okf_parser-0.45.2}/changelog/0.42.1.md +0 -0
  99. {okf_parser-0.43.0 → okf_parser-0.45.2}/changelog/0.42.2.md +0 -0
  100. {okf_parser-0.43.0 → okf_parser-0.45.2}/changelog/0.42.3.md +0 -0
  101. {okf_parser-0.43.0 → okf_parser-0.45.2}/changelog/0.42.4.md +0 -0
  102. {okf_parser-0.43.0 → okf_parser-0.45.2}/changelog/0.42.5.md +0 -0
  103. {okf_parser-0.43.0 → okf_parser-0.45.2}/changelog/0.42.6.md +0 -0
  104. {okf_parser-0.43.0 → okf_parser-0.45.2}/changelog/0.42.7.md +0 -0
  105. {okf_parser-0.43.0 → okf_parser-0.45.2}/changelog/0.42.8.md +0 -0
  106. {okf_parser-0.43.0 → okf_parser-0.45.2}/changelog/0.43.0.md +0 -0
  107. {okf_parser-0.43.0 → okf_parser-0.45.2}/changelog/0.5.0.md +0 -0
  108. {okf_parser-0.43.0 → okf_parser-0.45.2}/changelog/0.5.1.md +0 -0
  109. {okf_parser-0.43.0 → okf_parser-0.45.2}/changelog/0.6.0.md +0 -0
  110. {okf_parser-0.43.0 → okf_parser-0.45.2}/changelog/0.7.0.md +0 -0
  111. {okf_parser-0.43.0 → okf_parser-0.45.2}/changelog/0.8.0.md +0 -0
  112. {okf_parser-0.43.0 → okf_parser-0.45.2}/changelog/0.9.0.md +0 -0
  113. {okf_parser-0.43.0 → okf_parser-0.45.2}/conformance/content-digests.json +0 -0
  114. {okf_parser-0.43.0 → okf_parser-0.45.2}/conformance/exclusion.json +0 -0
  115. {okf_parser-0.43.0 → okf_parser-0.45.2}/conformance/formatting.json +0 -0
  116. {okf_parser-0.43.0 → okf_parser-0.45.2}/conformance/frontmatter.json +0 -0
  117. {okf_parser-0.43.0 → okf_parser-0.45.2}/conformance/schema-inference.json +0 -0
  118. {okf_parser-0.43.0 → okf_parser-0.45.2}/conformance/schema-output.json +0 -0
  119. {okf_parser-0.43.0 → okf_parser-0.45.2}/conformance/type-spec-slugs.json +0 -0
  120. {okf_parser-0.43.0 → okf_parser-0.45.2}/docs/architecture.md +0 -0
  121. {okf_parser-0.43.0 → okf_parser-0.45.2}/docs/cli.md +0 -0
  122. {okf_parser-0.43.0 → okf_parser-0.45.2}/docs/graphql.md +0 -0
  123. {okf_parser-0.43.0 → okf_parser-0.45.2}/docs/native-engine.md +0 -0
  124. {okf_parser-0.43.0 → okf_parser-0.45.2}/docs/releasing.md +0 -0
  125. {okf_parser-0.43.0 → okf_parser-0.45.2}/fastmcp.json +0 -0
  126. {okf_parser-0.43.0 → okf_parser-0.45.2}/native-npm-linux-x64/README.md +0 -0
  127. {okf_parser-0.43.0 → okf_parser-0.45.2}/okf-engine/src/engine.rs +0 -0
  128. {okf_parser-0.43.0 → okf_parser-0.45.2}/okf-engine/src/lib.rs +0 -0
  129. {okf_parser-0.43.0 → okf_parser-0.45.2}/okf-engine/src/yaml.rs +0 -0
  130. {okf_parser-0.43.0 → okf_parser-0.45.2}/rfcs/0001-python-model-codegen.md +0 -0
  131. {okf_parser-0.43.0 → okf_parser-0.45.2}/rfcs/0002-typescript-sibling-package.md +0 -0
  132. {okf_parser-0.43.0 → okf_parser-0.45.2}/rfcs/0003-resumable-multi-registry-release.md +0 -0
  133. {okf_parser-0.43.0 → okf_parser-0.45.2}/rfcs/0004-gitignore-exclusion-semantics.md +0 -0
  134. {okf_parser-0.43.0 → okf_parser-0.45.2}/rfcs/0005-relational-frontmatter-writes.md +0 -0
  135. {okf_parser-0.43.0 → okf_parser-0.45.2}/rfcs/0006-declared-column-types.md +0 -0
  136. {okf_parser-0.43.0 → okf_parser-0.45.2}/rfcs/0007-declared-relational-constraints.md +0 -0
  137. {okf_parser-0.43.0 → okf_parser-0.45.2}/rfcs/0008-effect-aware-mcp-writes.md +0 -0
  138. {okf_parser-0.43.0 → okf_parser-0.45.2}/rust-core/.gitignore +0 -0
  139. {okf_parser-0.43.0 → okf_parser-0.45.2}/rust-core/src/engine.rs +0 -0
  140. {okf_parser-0.43.0 → okf_parser-0.45.2}/rust-core/src/main.rs +0 -0
  141. {okf_parser-0.43.0 → okf_parser-0.45.2}/scripts/__init__.py +0 -0
  142. {okf_parser-0.43.0 → okf_parser-0.45.2}/src/okf_parser/apply.py +0 -0
  143. {okf_parser-0.43.0 → okf_parser-0.45.2}/src/okf_parser/bundle.py +0 -0
  144. {okf_parser-0.43.0 → okf_parser-0.45.2}/src/okf_parser/classification.py +0 -0
  145. {okf_parser-0.43.0 → okf_parser-0.45.2}/src/okf_parser/declared_schema.py +0 -0
  146. {okf_parser-0.43.0 → okf_parser-0.45.2}/src/okf_parser/digests.py +0 -0
  147. {okf_parser-0.43.0 → okf_parser-0.45.2}/src/okf_parser/discovery.py +0 -0
  148. {okf_parser-0.43.0 → okf_parser-0.45.2}/src/okf_parser/duckdb.py +0 -0
  149. {okf_parser-0.43.0 → okf_parser-0.45.2}/src/okf_parser/duckdb_types.py +0 -0
  150. {okf_parser-0.43.0 → okf_parser-0.45.2}/src/okf_parser/edit.py +0 -0
  151. {okf_parser-0.43.0 → okf_parser-0.45.2}/src/okf_parser/engine.py +0 -0
  152. {okf_parser-0.43.0 → okf_parser-0.45.2}/src/okf_parser/exclusion.py +0 -0
  153. {okf_parser-0.43.0 → okf_parser-0.45.2}/src/okf_parser/formatting.py +0 -0
  154. {okf_parser-0.43.0 → okf_parser-0.45.2}/src/okf_parser/graphql_adapter.py +0 -0
  155. {okf_parser-0.43.0 → okf_parser-0.45.2}/src/okf_parser/ingestion.py +0 -0
  156. {okf_parser-0.43.0 → okf_parser-0.45.2}/src/okf_parser/markdown_style.py +0 -0
  157. {okf_parser-0.43.0 → okf_parser-0.45.2}/src/okf_parser/parser.py +0 -0
  158. {okf_parser-0.43.0 → okf_parser-0.45.2}/src/okf_parser/pydantic_projection.py +0 -0
  159. {okf_parser-0.43.0 → okf_parser-0.45.2}/src/okf_parser/relational_schema.py +0 -0
  160. {okf_parser-0.43.0 → okf_parser-0.45.2}/src/okf_parser/rust_core.py +0 -0
  161. {okf_parser-0.43.0 → okf_parser-0.45.2}/src/okf_parser/schema_contract.py +0 -0
  162. {okf_parser-0.43.0 → okf_parser-0.45.2}/src/okf_parser/schema_export.py +0 -0
  163. {okf_parser-0.43.0 → okf_parser-0.45.2}/src/okf_parser/schema_lexemes.py +0 -0
  164. {okf_parser-0.43.0 → okf_parser-0.45.2}/src/okf_parser/spec_scaffold.py +0 -0
  165. {okf_parser-0.43.0 → okf_parser-0.45.2}/src/okf_parser/type_specs.py +0 -0
  166. {okf_parser-0.43.0 → okf_parser-0.45.2}/src/okf_parser/typed_relations.py +0 -0
  167. {okf_parser-0.43.0 → okf_parser-0.45.2}/src/okf_parser/typed_tables.py +0 -0
  168. {okf_parser-0.43.0 → okf_parser-0.45.2}/src/okf_parser/write_support.py +0 -0
  169. {okf_parser-0.43.0 → okf_parser-0.45.2}/tests/__init__.py +0 -0
  170. {okf_parser-0.43.0 → okf_parser-0.45.2}/tests/fixtures/__init__.py +0 -0
  171. {okf_parser-0.43.0 → okf_parser-0.45.2}/tests/fixtures/declared_parity.expected.json +0 -0
  172. {okf_parser-0.43.0 → okf_parser-0.45.2}/tests/fixtures/declared_parity.schema.sql +0 -0
  173. {okf_parser-0.43.0 → okf_parser-0.45.2}/tests/fixtures/pydantic_adversarial_generated.py +0 -0
  174. {okf_parser-0.43.0 → okf_parser-0.45.2}/tests/fixtures/pydantic_generated.py +0 -0
  175. {okf_parser-0.43.0 → okf_parser-0.45.2}/tests/test_apply.py +0 -0
  176. {okf_parser-0.43.0 → okf_parser-0.45.2}/tests/test_apply_preview_token.py +0 -0
  177. {okf_parser-0.43.0 → okf_parser-0.45.2}/tests/test_apply_preview_token_noop.py +0 -0
  178. {okf_parser-0.43.0 → okf_parser-0.45.2}/tests/test_bundle.py +0 -0
  179. {okf_parser-0.43.0 → okf_parser-0.45.2}/tests/test_bundle_import.py +0 -0
  180. {okf_parser-0.43.0 → okf_parser-0.45.2}/tests/test_bundle_import_reserved.py +0 -0
  181. {okf_parser-0.43.0 → okf_parser-0.45.2}/tests/test_classification.py +0 -0
  182. {okf_parser-0.43.0 → okf_parser-0.45.2}/tests/test_cli.py +0 -0
  183. {okf_parser-0.43.0 → okf_parser-0.45.2}/tests/test_conformance.py +0 -0
  184. {okf_parser-0.43.0 → okf_parser-0.45.2}/tests/test_content_digests.py +0 -0
  185. {okf_parser-0.43.0 → okf_parser-0.45.2}/tests/test_declared_schema.py +0 -0
  186. {okf_parser-0.43.0 → okf_parser-0.45.2}/tests/test_declared_schema_correctness.py +0 -0
  187. {okf_parser-0.43.0 → okf_parser-0.45.2}/tests/test_duckdb.py +0 -0
  188. {okf_parser-0.43.0 → okf_parser-0.45.2}/tests/test_duckdb_types.py +0 -0
  189. {okf_parser-0.43.0 → okf_parser-0.45.2}/tests/test_edit.py +0 -0
  190. {okf_parser-0.43.0 → okf_parser-0.45.2}/tests/test_engine_resolution.py +0 -0
  191. {okf_parser-0.43.0 → okf_parser-0.45.2}/tests/test_exclusion.py +0 -0
  192. {okf_parser-0.43.0 → okf_parser-0.45.2}/tests/test_exclusion_integration.py +0 -0
  193. {okf_parser-0.43.0 → okf_parser-0.45.2}/tests/test_formatting.py +0 -0
  194. {okf_parser-0.43.0 → okf_parser-0.45.2}/tests/test_formatting_conformance.py +0 -0
  195. {okf_parser-0.43.0 → okf_parser-0.45.2}/tests/test_graphql_adapter.py +0 -0
  196. {okf_parser-0.43.0 → okf_parser-0.45.2}/tests/test_ingestion.py +0 -0
  197. {okf_parser-0.43.0 → okf_parser-0.45.2}/tests/test_markdown_style.py +0 -0
  198. {okf_parser-0.43.0 → okf_parser-0.45.2}/tests/test_native_from_wheel.py +0 -0
  199. {okf_parser-0.43.0 → okf_parser-0.45.2}/tests/test_parser.py +0 -0
  200. {okf_parser-0.43.0 → okf_parser-0.45.2}/tests/test_pydantic_source.py +0 -0
  201. {okf_parser-0.43.0 → okf_parser-0.45.2}/tests/test_registry_state.py +0 -0
  202. {okf_parser-0.43.0 → okf_parser-0.45.2}/tests/test_relational_schema.py +0 -0
  203. {okf_parser-0.43.0 → okf_parser-0.45.2}/tests/test_rust_core.py +0 -0
  204. {okf_parser-0.43.0 → okf_parser-0.45.2}/tests/test_schema_export.py +0 -0
  205. {okf_parser-0.43.0 → okf_parser-0.45.2}/tests/test_schema_inference_conformance.py +0 -0
  206. {okf_parser-0.43.0 → okf_parser-0.45.2}/tests/test_schema_output_conformance.py +0 -0
  207. {okf_parser-0.43.0 → okf_parser-0.45.2}/tests/test_spec_scaffold.py +0 -0
  208. {okf_parser-0.43.0 → okf_parser-0.45.2}/tests/test_type_specs.py +0 -0
  209. {okf_parser-0.43.0 → okf_parser-0.45.2}/tests/test_typed_relations.py +0 -0
  210. {okf_parser-0.43.0 → okf_parser-0.45.2}/typescript/.npmignore +0 -0
  211. {okf_parser-0.43.0 → okf_parser-0.45.2}/typescript/LICENSE +0 -0
  212. {okf_parser-0.43.0 → okf_parser-0.45.2}/typescript/README.md +0 -0
  213. {okf_parser-0.43.0 → okf_parser-0.45.2}/typescript/scripts/smoke-mcp-stdio.mjs +0 -0
  214. {okf_parser-0.43.0 → okf_parser-0.45.2}/typescript/src/cli.ts +0 -0
  215. {okf_parser-0.43.0 → okf_parser-0.45.2}/typescript/src/core.ts +0 -0
  216. {okf_parser-0.43.0 → okf_parser-0.45.2}/typescript/src/digests.ts +0 -0
  217. {okf_parser-0.43.0 → okf_parser-0.45.2}/typescript/src/engine.ts +0 -0
  218. {okf_parser-0.43.0 → okf_parser-0.45.2}/typescript/src/formatting.ts +0 -0
  219. {okf_parser-0.43.0 → okf_parser-0.45.2}/typescript/src/index.ts +0 -0
  220. {okf_parser-0.43.0 → okf_parser-0.45.2}/typescript/src/ingestion.ts +0 -0
  221. {okf_parser-0.43.0 → okf_parser-0.45.2}/typescript/src/lexemes.ts +0 -0
  222. {okf_parser-0.43.0 → okf_parser-0.45.2}/typescript/src/mcp-cli.ts +0 -0
  223. {okf_parser-0.43.0 → okf_parser-0.45.2}/typescript/src/mcp.ts +0 -0
  224. {okf_parser-0.43.0 → okf_parser-0.45.2}/typescript/src/rust-core.ts +0 -0
  225. {okf_parser-0.43.0 → okf_parser-0.45.2}/typescript/src/schema.ts +0 -0
  226. {okf_parser-0.43.0 → okf_parser-0.45.2}/typescript/src/type-specs.ts +0 -0
  227. {okf_parser-0.43.0 → okf_parser-0.45.2}/typescript/test/bundle.test.ts +0 -0
  228. {okf_parser-0.43.0 → okf_parser-0.45.2}/typescript/test/classification.test.ts +0 -0
  229. {okf_parser-0.43.0 → okf_parser-0.45.2}/typescript/test/content-digests.test.ts +0 -0
  230. {okf_parser-0.43.0 → okf_parser-0.45.2}/typescript/test/engine-resolution.test.ts +0 -0
  231. {okf_parser-0.43.0 → okf_parser-0.45.2}/typescript/test/exclusion.test.ts +0 -0
  232. {okf_parser-0.43.0 → okf_parser-0.45.2}/typescript/test/formatting.test.ts +0 -0
  233. {okf_parser-0.43.0 → okf_parser-0.45.2}/typescript/test/frontmatter.test.ts +0 -0
  234. {okf_parser-0.43.0 → okf_parser-0.45.2}/typescript/test/ingestion.test.ts +0 -0
  235. {okf_parser-0.43.0 → okf_parser-0.45.2}/typescript/test/mcp.test.ts +0 -0
  236. {okf_parser-0.43.0 → okf_parser-0.45.2}/typescript/test/schema-inference-conformance.test.ts +0 -0
  237. {okf_parser-0.43.0 → okf_parser-0.45.2}/typescript/test/schema-output-conformance.test.ts +0 -0
  238. {okf_parser-0.43.0 → okf_parser-0.45.2}/typescript/test/schema.test.ts +0 -0
  239. {okf_parser-0.43.0 → okf_parser-0.45.2}/typescript/test/type-specs.test.ts +0 -0
  240. {okf_parser-0.43.0 → okf_parser-0.45.2}/typescript/tsconfig.build.json +0 -0
  241. {okf_parser-0.43.0 → okf_parser-0.45.2}/typescript/tsconfig.json +0 -0
  242. {okf_parser-0.43.0 → okf_parser-0.45.2}/typescript-duckdb/LICENSE +0 -0
  243. {okf_parser-0.43.0 → okf_parser-0.45.2}/typescript-duckdb/README.md +0 -0
  244. {okf_parser-0.43.0 → okf_parser-0.45.2}/typescript-duckdb/src/cli.ts +0 -0
  245. {okf_parser-0.43.0 → okf_parser-0.45.2}/typescript-duckdb/src/declared.ts +0 -0
  246. {okf_parser-0.43.0 → okf_parser-0.45.2}/typescript-duckdb/src/index.ts +0 -0
  247. {okf_parser-0.43.0 → okf_parser-0.45.2}/typescript-duckdb/src/normalized.ts +0 -0
  248. {okf_parser-0.43.0 → okf_parser-0.45.2}/typescript-duckdb/src/session.ts +0 -0
  249. {okf_parser-0.43.0 → okf_parser-0.45.2}/typescript-duckdb/src/typed.ts +0 -0
  250. {okf_parser-0.43.0 → okf_parser-0.45.2}/typescript-duckdb/test/current-load-options.test.ts +0 -0
  251. {okf_parser-0.43.0 → okf_parser-0.45.2}/typescript-duckdb/test/digests.test.ts +0 -0
  252. {okf_parser-0.43.0 → okf_parser-0.45.2}/typescript-duckdb/test/duckdb.test.ts +0 -0
  253. {okf_parser-0.43.0 → okf_parser-0.45.2}/typescript-duckdb/test/typed.test.ts +0 -0
  254. {okf_parser-0.43.0 → okf_parser-0.45.2}/typescript-duckdb/tsconfig.build.json +0 -0
  255. {okf_parser-0.43.0 → okf_parser-0.45.2}/typescript-duckdb/tsconfig.json +0 -0
@@ -115,9 +115,9 @@ checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
115
115
 
116
116
  [[package]]
117
117
  name = "cc"
118
- version = "1.4.2"
118
+ version = "1.4.3"
119
119
  source = "registry+https://github.com/rust-lang/crates.io-index"
120
- checksum = "5d262e149917187838d5b42777c8253bcb64500067342904e7d429499a6f277e"
120
+ checksum = "509591b7bcd67f4ef775afad7662703b4935daaa6ec0e5605cfb1090b32a2b6d"
121
121
  dependencies = [
122
122
  "find-msvc-tools",
123
123
  "shlex",
@@ -250,9 +250,9 @@ dependencies = [
250
250
 
251
251
  [[package]]
252
252
  name = "either"
253
- version = "1.17.0"
253
+ version = "1.18.0"
254
254
  source = "registry+https://github.com/rust-lang/crates.io-index"
255
- checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d"
255
+ checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34"
256
256
 
257
257
  [[package]]
258
258
  name = "encoding_rs"
@@ -265,9 +265,9 @@ dependencies = [
265
265
 
266
266
  [[package]]
267
267
  name = "find-msvc-tools"
268
- version = "0.1.10"
268
+ version = "0.1.11"
269
269
  source = "registry+https://github.com/rust-lang/crates.io-index"
270
- checksum = "26b73573e6edcd2af0cdf47bd6cb58f0b3839491263c314eaad1ccf24430e1de"
270
+ checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890"
271
271
 
272
272
  [[package]]
273
273
  name = "foldhash"
@@ -447,7 +447,7 @@ dependencies = [
447
447
 
448
448
  [[package]]
449
449
  name = "okf-core"
450
- version = "0.43.0"
450
+ version = "0.45.2"
451
451
  dependencies = [
452
452
  "clap",
453
453
  "okf-engine",
@@ -457,7 +457,7 @@ dependencies = [
457
457
 
458
458
  [[package]]
459
459
  name = "okf-engine"
460
- version = "0.39.1"
460
+ version = "0.45.2"
461
461
  dependencies = [
462
462
  "chrono",
463
463
  "ignore",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: okf-parser
3
- Version: 0.43.0
3
+ Version: 0.45.2
4
4
  Requires-Dist: cyclopts>=4.22,<5
5
5
  Requires-Dist: duckdb>=1.4,<2
6
6
  Requires-Dist: fastmcp>=3.4,<4
@@ -231,7 +231,7 @@ Add the repository as a CI check:
231
231
  ```yaml
232
232
  steps:
233
233
  - uses: actions/checkout@v7
234
- - uses: franklinbaldo/okf-parser@v0.43.0
234
+ - uses: franklinbaldo/okf-parser@v0.45.2
235
235
  with:
236
236
  path: knowledge
237
237
  ```
@@ -209,7 +209,7 @@ Add the repository as a CI check:
209
209
  ```yaml
210
210
  steps:
211
211
  - uses: actions/checkout@v7
212
- - uses: franklinbaldo/okf-parser@v0.43.0
212
+ - uses: franklinbaldo/okf-parser@v0.45.2
213
213
  with:
214
214
  path: knowledge
215
215
  ```
@@ -14,7 +14,7 @@ The default matrix uses 100, 1,000 and 5,000 concepts, five measured rounds and
14
14
  one unmeasured warm-up round:
15
15
 
16
16
  ```bash
17
- uv run python benchmarks/python_parser.py
17
+ uv run benchmarks/python_parser.py
18
18
  npm --prefix typescript run build
19
19
  node benchmarks/typescript-parser.mjs
20
20
  ```
@@ -22,7 +22,7 @@ node benchmarks/typescript-parser.mjs
22
22
  Use the same parameters in both runtimes when changing the corpus:
23
23
 
24
24
  ```bash
25
- uv run python benchmarks/python_parser.py \
25
+ uv run benchmarks/python_parser.py \
26
26
  --sizes 100,1000 --body-paragraphs 8 --rounds 7 \
27
27
  --read-concurrencies 1,4,8,16,32,64
28
28
 
@@ -1,3 +1,14 @@
1
+ #!/usr/bin/env -S uv run --script
2
+ #
3
+ # /// script
4
+ # requires-python = ">=3.12"
5
+ # dependencies = [
6
+ # "okf-parser",
7
+ # ]
8
+ #
9
+ # [tool.uv.sources]
10
+ # okf-parser = { path = "..", editable = true }
11
+ # ///
1
12
  """Reproducible parsing benchmarks for the Python runtime."""
2
13
 
3
14
  from __future__ import annotations
@@ -1,3 +1,14 @@
1
+ #!/usr/bin/env -S uv run --script
2
+ #
3
+ # /// script
4
+ # requires-python = ">=3.12"
5
+ # dependencies = [
6
+ # "okf-parser",
7
+ # ]
8
+ #
9
+ # [tool.uv.sources]
10
+ # okf-parser = { path = "..", editable = true }
11
+ # ///
1
12
  """Compare native Python Markdown facts with the experimental Rust batch core."""
2
13
 
3
14
  from __future__ import annotations
@@ -0,0 +1,10 @@
1
+ ---
2
+ type: Release
3
+ title: okf-parser 0.43.1
4
+ ---
5
+
6
+ # okf-parser 0.43.1
7
+
8
+ - Fix public-registry preflight so the native npm companion is treated as an npm artifact rather than a PyPI file.
9
+ - Verify `okf-parser-native-linux-x64` version and integrity alongside the main TypeScript package and DuckDB adapter.
10
+ - Include the native npm package in resumable publication planning and in the release completeness decision.
@@ -0,0 +1,10 @@
1
+ ---
2
+ type: Release
3
+ title: okf-parser 0.44.0
4
+ ---
5
+
6
+ # okf-parser 0.44.0
7
+
8
+ - Expose parser-owned concept records for application-facing lookup by concept id or in-bundle Markdown path.
9
+ - Expose validated structured frontmatter from `ConceptRecord`.
10
+ - Add generic frontmatter resource-relation resolution with optional target-type filtering, so consumers do not reparse Markdown or implement bundle containment themselves.
@@ -0,0 +1,10 @@
1
+ ---
2
+ type: Release
3
+ title: okf-parser 0.45.0
4
+ ---
5
+
6
+ # okf-parser 0.45.0
7
+
8
+ - Add an opaque import `preview_token` that binds a reviewed tabular source, import options, candidate paths and current destination states.
9
+ - Let import writes accept `expected_preview_token` and fail before any filesystem mutation when the source or a relevant destination changed after preview.
10
+ - Carry the parser-owned token through the Python service, CLI and MCP write surface so consumers such as Astronauta can remain preview-first without reimplementing import state.
@@ -0,0 +1,9 @@
1
+ ---
2
+ type: Release
3
+ title: okf-parser 0.45.1
4
+ ---
5
+
6
+ # okf-parser 0.45.1
7
+
8
+ - Make `verify-source` enforce Rust crate identity together with the workspace version: `rust-core`, `okf-engine`, and the internal `rust-core -> okf-engine` dependency must all match.
9
+ - Add causal regression tests for drifted crate and dependency versions, closing the gap where the documented release contract allowed Rust metadata to lag behind for multiple releases.
@@ -0,0 +1,11 @@
1
+ ---
2
+ type: Release
3
+ title: okf-parser 0.45.2
4
+ ---
5
+
6
+ # okf-parser 0.45.2
7
+
8
+ - Restore the executable bits on the six repository scripts that carry a `#!/usr/bin/env -S uv run --script` shebang (`scripts/release_contract.py`, `scripts/registry_state.py`, `scripts/native_from_wheel.py`, `scripts/check_no_duckdb_link.py`, `benchmarks/python_parser.py`, `benchmarks/rust_facts.py`). The PEP 723 migration in 0.45.1's follow-up commit committed them as `100644`, so Ruff reported `EXE001` and the whole quality gate stopped before running. Consumers of the source distribution now get scripts that are runnable as declared.
9
+ - Ship the PEP 723 inline dependency headers themselves in the source distribution: the release and benchmark scripts declare `requires-python` and their dependencies inline, so they run standalone with `uv run` without the repository's own environment.
10
+
11
+ No change to the public Python API, the CLI surface, or the wire protocol; the wheel payload is identical to 0.45.1.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "okf-parser-native-linux-x64",
3
- "version": "0.43.0",
3
+ "version": "0.45.2",
4
4
  "description": "Linux x64 native okf-core engine for okf-parser",
5
5
  "license": "MIT",
6
6
  "os": ["linux"],
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "okf-engine"
3
- version = "0.39.1"
3
+ version = "0.45.2"
4
4
  edition = "2024"
5
5
  license = "MIT"
6
6
  description = "DuckDB-independent OKF semantic ingestion engine"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "okf-parser"
3
- version = "0.43.0"
3
+ version = "0.45.2"
4
4
  description = "Relational inspection and validation for Open Knowledge Format bundles"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "okf-core"
3
- version = "0.43.0"
3
+ version = "0.45.2"
4
4
  edition = "2024"
5
5
  license = "MIT"
6
6
  description = "Native OKF CLI and compatibility adapters"
@@ -11,6 +11,6 @@ path = "src/main.rs"
11
11
 
12
12
  [dependencies]
13
13
  clap = { version = "4", features = ["derive"] }
14
- okf-engine = { path = "../okf-engine", version = "0.39.1" }
14
+ okf-engine = { path = "../okf-engine", version = "0.45.2" }
15
15
  serde = { version = "1", features = ["derive"] }
16
16
  serde_json = "1"
@@ -1,3 +1,10 @@
1
+ #!/usr/bin/env -S uv run --script
2
+ #
3
+ # /// script
4
+ # requires-python = ">=3.12"
5
+ # dependencies = [
6
+ # ]
7
+ # ///
1
8
  """Prove a built wheel's executable does not link against a DuckDB library.
2
9
 
3
10
  The Python distribution depends on `duckdb` and runs its DuckDB export
@@ -1,3 +1,10 @@
1
+ #!/usr/bin/env -S uv run --script
2
+ #
3
+ # /// script
4
+ # requires-python = ">=3.12"
5
+ # dependencies = [
6
+ # ]
7
+ # ///
1
8
  """Reuse the exact Linux x86_64 executable from a Python wheel in npm-native."""
2
9
 
3
10
  from __future__ import annotations
@@ -1,3 +1,10 @@
1
+ #!/usr/bin/env -S uv run --script
2
+ #
3
+ # /// script
4
+ # requires-python = ">=3.12"
5
+ # dependencies = [
6
+ # ]
7
+ # ///
1
8
  """Inspect public package registries without performing any mutation."""
2
9
 
3
10
  from __future__ import annotations
@@ -114,9 +121,13 @@ def _string(mapping: dict[str, object], key: str, label: str) -> str:
114
121
  # Not a fixed enum here on purpose: registry_state.py stays decoupled from
115
122
  # release_contract.py's exact wheel-kind names (one per supported platform,
116
123
  # e.g. "python-wheel-linux-x86_64") so adding/removing a target platform
117
- # never requires touching this file. Any kind other than the two npm
118
- # packages is treated as a PyPI artifact for okf-parser.
119
- _NPM_KINDS: Final = {"npm-parser", "npm-duckdb"}
124
+ # never requires touching this file. Any kind other than npm package kinds is
125
+ # treated as a PyPI artifact for okf-parser.
126
+ _NPM_KINDS: Final = {"npm-parser", "npm-duckdb", "npm-native"}
127
+ # Keep compatibility with older manifests/tests that predate the native npm
128
+ # artifact; release_contract.py is the authority that requires the complete
129
+ # current release set. When npm-native is present here, it must be inspected.
130
+ _REQUIRED_NPM_KINDS: Final = {"npm-parser", "npm-duckdb"}
120
131
  _MIN_PYPI_ARTIFACTS: Final = 2 # at least one wheel + the sdist
121
132
 
122
133
 
@@ -134,7 +145,7 @@ def _artifact_index(manifest: dict[str, object]) -> tuple[str, dict[str, Artifac
134
145
  if kind in indexed:
135
146
  _fail(f"duplicate manifest artifact kind {kind!r}")
136
147
  indexed[kind] = item
137
- if not _NPM_KINDS.issubset(indexed):
148
+ if not _REQUIRED_NPM_KINDS.issubset(indexed):
138
149
  _fail("manifest artifact set is missing an npm package")
139
150
  pypi_kinds = set(indexed) - _NPM_KINDS
140
151
  if len(pypi_kinds) < _MIN_PYPI_ARTIFACTS:
@@ -351,6 +362,15 @@ def inspect_registry_state(
351
362
  _npm_state("okf-parser", artifacts["npm-parser"], fetch, timeout),
352
363
  _npm_state("okf-parser-duckdb", artifacts["npm-duckdb"], fetch, timeout),
353
364
  ]
365
+ if "npm-native" in artifacts:
366
+ entries.append(
367
+ _npm_state(
368
+ "okf-parser-native-linux-x64",
369
+ artifacts["npm-native"],
370
+ fetch,
371
+ timeout,
372
+ )
373
+ )
354
374
  states = [entry["state"] for entry in entries]
355
375
  allowed = {"absent", "present_expected"}
356
376
  return {
@@ -1,3 +1,10 @@
1
+ #!/usr/bin/env -S uv run --script
2
+ #
3
+ # /// script
4
+ # requires-python = ">=3.12"
5
+ # dependencies = [
6
+ # ]
7
+ # ///
1
8
  """Validate synchronized release metadata and artifact bytes."""
2
9
 
3
10
  from __future__ import annotations
@@ -320,11 +327,36 @@ def _verify_changelog(root: Path, version: str) -> Path:
320
327
  return path
321
328
 
322
329
 
330
+ def _verify_rust_crates(root: Path, version: str) -> None:
331
+ """Check the Rust crate versions against the workspace version.
332
+
333
+ docs/releasing.md lists "the Rust crate version" among what verify-source
334
+ requires, but nothing read any Cargo.toml and okf-engine silently drifted
335
+ to 0.39.1 while the workspace published 0.45.0 (issue #172). rust-core
336
+ additionally pins okf-engine as an internal path dependency, so all three
337
+ numbers must move together.
338
+ """
339
+ for crate_root, name in ((root / "okf-engine", "okf-engine"), (root / "rust-core", "okf-core")):
340
+ path = crate_root / "Cargo.toml"
341
+ try:
342
+ manifest = tomllib.loads(path.read_text(encoding="utf-8"))
343
+ except (OSError, UnicodeError, tomllib.TOMLDecodeError) as exc:
344
+ _fail(f"cannot read {path}: {exc}")
345
+ package = _mapping(manifest.get("package"), f"{path} [package]")
346
+ if _string(package, "version", str(path)) != version:
347
+ _fail(f"{name} crate version must be {version}")
348
+ dependencies = _mapping(manifest.get("dependencies"), "rust-core/Cargo.toml dependencies")
349
+ internal = _mapping(dependencies.get("okf-engine"), "rust-core okf-engine dependency")
350
+ if _string(internal, "version", "rust-core okf-engine dependency") != version:
351
+ _fail(f"rust-core internal okf-engine dependency must be {version}")
352
+
353
+
323
354
  def verify_source(root: Path, tag: str | None = None) -> SourceContract:
324
355
  """Verify package names, versions, protocol, peer range and changelog."""
325
356
  version = _project_version(root)
326
357
  _verify_npm_contract(root, version)
327
358
  _verify_protocol(root, version)
359
+ _verify_rust_crates(root, version)
328
360
  changelog = _verify_changelog(root, version)
329
361
  _verify_readme_action(root, version)
330
362
  if tag is not None and tag != f"v{version}":
@@ -1,6 +1,7 @@
1
1
  """Relational inspection and validation for Open Knowledge Format bundles."""
2
2
 
3
3
  from okf_parser.bundle import Bundle, validate_path
4
+ from okf_parser.concepts import concept, resolve_relations
4
5
  from okf_parser.edit import EditError, preview_concept_edit, write_concept_edit
5
6
  from okf_parser.engine import load_bundle
6
7
  from okf_parser.graphql_adapter import (
@@ -29,10 +30,12 @@ __all__ = [
29
30
  "ValidationReport",
30
31
  "Violation",
31
32
  "build_graphql_schema",
33
+ "concept",
32
34
  "export_graphql_sdl",
33
35
  "ingest_documents",
34
36
  "load_bundle",
35
37
  "preview_concept_edit",
38
+ "resolve_relations",
36
39
  "validate_path",
37
40
  "write_concept_edit",
38
41
  ]
@@ -12,6 +12,9 @@ cannot open on its own surfaces as DuckDB's own error, unchanged.
12
12
 
13
13
  from __future__ import annotations
14
14
 
15
+ import hashlib
16
+ import hmac
17
+ import json
15
18
  from io import StringIO
16
19
  from pathlib import Path
17
20
  from typing import TYPE_CHECKING, Literal
@@ -110,12 +113,88 @@ def _matches_candidate(destination: Path, candidate: str) -> bool:
110
113
  return existing.parsed_digest == intended.parsed_digest
111
114
 
112
115
 
116
+ def _destination_state(destination: Path) -> dict[str, str]:
117
+ """Return the destination state that must remain unchanged until commit."""
118
+ if destination.is_symlink():
119
+ return {"kind": "symlink", "target": str(destination.readlink())}
120
+ if destination.is_file():
121
+ return {
122
+ "kind": "file",
123
+ "sha256": hashlib.sha256(destination.read_bytes()).hexdigest(),
124
+ }
125
+ if destination.exists():
126
+ return {"kind": "other"}
127
+ return {"kind": "absent"}
128
+
129
+
130
+ def _stable_rows(columns: Sequence[str], rows: Sequence[dict[str, object]]) -> list[list[object]]:
131
+ """Preserve the effective source values in a JSON-stable representation."""
132
+ stable: list[list[object]] = []
133
+ for row in rows:
134
+ values: list[object] = []
135
+ for column in columns:
136
+ value = row.get(column)
137
+ if value is None:
138
+ values.append(None)
139
+ elif isinstance(value, str):
140
+ values.append(["str", value])
141
+ else:
142
+ values.append([type(value).__qualname__, str(value)])
143
+ stable.append(values)
144
+ return stable
145
+
146
+
147
+ def _preview_token(
148
+ *,
149
+ source: str,
150
+ concept_type: str,
151
+ id_column: str | None,
152
+ overwrite: bool,
153
+ on_conflict: ImportConflictPolicy,
154
+ columns: Sequence[str],
155
+ rows: Sequence[dict[str, object]],
156
+ destinations: dict[str, dict[str, str]],
157
+ ) -> str:
158
+ """Bind an import preview to its source plan and current destination state."""
159
+ payload = {
160
+ "version": 1,
161
+ "source": source,
162
+ "concept_type": concept_type,
163
+ "id_column": id_column,
164
+ "overwrite": overwrite,
165
+ "on_conflict": on_conflict,
166
+ "columns": list(columns),
167
+ "rows": _stable_rows(columns, rows),
168
+ "destinations": [[relative, destinations[relative]] for relative in sorted(destinations)],
169
+ }
170
+ encoded = json.dumps(
171
+ payload,
172
+ ensure_ascii=False,
173
+ separators=(",", ":"),
174
+ sort_keys=True,
175
+ ).encode("utf-8")
176
+ return hashlib.sha256(encoded).hexdigest()
177
+
178
+
113
179
  def _validate_options(*, overwrite: bool, on_conflict: ImportConflictPolicy) -> None:
114
180
  if overwrite and on_conflict == "verify-identical":
115
181
  message = "overwrite and on_conflict='verify-identical' are mutually exclusive"
116
182
  raise BundleImportError(message)
117
183
 
118
184
 
185
+ def _validate_preview_token(
186
+ *, write: bool, preview_token: str, expected_preview_token: str | None
187
+ ) -> None:
188
+ """Fail closed when a reviewed import preview no longer matches current state."""
189
+ if (
190
+ write
191
+ and expected_preview_token is not None
192
+ and not hmac.compare_digest(preview_token, expected_preview_token)
193
+ ):
194
+ message = "import preview is stale; rerun preview before committing"
195
+ raise BundleImportError(message)
196
+
197
+
119
198
  def import_bundle( # each argument is an independent public CLI flag.
120
199
  source: str,
121
200
  path: str,
@@ -125,6 +204,7 @@ def import_bundle( # each argument is an independent public CLI flag.
125
204
  write: bool = False,
126
205
  overwrite: bool = False,
127
206
  on_conflict: ImportConflictPolicy = "skip",
207
+ expected_preview_token: str | None = None,
128
208
  ) -> dict[str, object]:
129
209
  """Materialize every row of a DuckDB-readable source as one concept document.
130
210
 
@@ -134,6 +214,11 @@ def import_bundle( # each argument is an independent public CLI flag.
134
214
  concept value as an idempotent match and any divergence as an atomic
135
215
  conflict. `overwrite` remains an explicit replacement policy and cannot be
136
216
  combined with `verify-identical`.
217
+
218
+ Dry-runs return a deterministic ``preview_token``. When a write supplies
219
+ ``expected_preview_token``, source values and every relevant destination
220
+ must still match that reviewed preview or the call fails before any
221
+ filesystem mutation.
137
222
  """
138
223
  _validate_options(overwrite=overwrite, on_conflict=on_conflict)
139
224
  root = Path(path).resolve()
@@ -142,6 +227,22 @@ def import_bundle( # each argument is an independent public CLI flag.
142
227
  message = "source column 'type' is reserved; use --type to set concept identity"
143
228
  raise BundleImportError(message)
144
229
  plan, duplicate_ids = _plan(concept_type, columns, rows, id_column)
230
+ destinations = {relative: _destination_state(root / relative) for relative in plan}
231
+ preview_token = _preview_token(
232
+ source=source,
233
+ concept_type=concept_type,
234
+ id_column=id_column,
235
+ overwrite=overwrite,
236
+ on_conflict=on_conflict,
237
+ columns=columns,
238
+ rows=rows,
239
+ destinations=destinations,
240
+ )
241
+ _validate_preview_token(
242
+ write=write,
243
+ preview_token=preview_token,
244
+ expected_preview_token=expected_preview_token,
245
+ )
145
246
  if duplicate_ids:
146
247
  return {
147
248
  "created": [],
@@ -150,6 +251,7 @@ def import_bundle( # each argument is an independent public CLI flag.
150
251
  "matched_existing": [],
151
252
  "conflicting_existing": [],
152
253
  "duplicate_ids": list(duplicate_ids),
254
+ "preview_token": preview_token,
153
255
  "written": False,
154
256
  }
155
257
 
@@ -183,6 +285,7 @@ def import_bundle( # each argument is an independent public CLI flag.
183
285
  "matched_existing": sorted(matched_existing),
184
286
  "conflicting_existing": sorted(conflicting_existing),
185
287
  "duplicate_ids": [],
288
+ "preview_token": preview_token,
186
289
  "written": False,
187
290
  }
188
291
 
@@ -194,6 +297,7 @@ def import_bundle( # each argument is an independent public CLI flag.
194
297
  "matched_existing": sorted(matched_existing),
195
298
  "conflicting_existing": [],
196
299
  "duplicate_ids": [],
300
+ "preview_token": preview_token,
197
301
  "written": False,
198
302
  }
199
303
 
@@ -210,5 +314,6 @@ def import_bundle( # each argument is an independent public CLI flag.
210
314
  "matched_existing": sorted(matched_existing),
211
315
  "conflicting_existing": [],
212
316
  "duplicate_ids": [],
317
+ "preview_token": preview_token,
213
318
  "written": True,
214
319
  }
@@ -99,6 +99,7 @@ def import_command( # each argument is an independent public CLI flag.
99
99
  write: bool = False,
100
100
  overwrite: bool = False,
101
101
  on_conflict: ImportConflictPolicy = "skip",
102
+ expected_preview_token: str | None = None,
102
103
  ) -> CliResult[JsonPayload]:
103
104
  """Materialize every row of a DuckDB-readable source (CSV, Parquet, JSON) as a concept."""
104
105
  payload = import_bundle(
@@ -109,6 +110,7 @@ def import_command( # each argument is an independent public CLI flag.
109
110
  write=write,
110
111
  overwrite=overwrite,
111
112
  on_conflict=on_conflict,
113
+ expected_preview_token=expected_preview_token,
112
114
  )
113
115
  failed = bool(payload["duplicate_ids"]) or bool(payload["conflicting_existing"])
114
116
  return CliResult(payload, 1 if failed else 0)
@@ -450,6 +452,7 @@ def mcp_import_write(
450
452
  id_column: str | None = None,
451
453
  overwrite: bool = False,
452
454
  on_conflict: ImportConflictPolicy = "skip",
455
+ expected_preview_token: str | None = None,
453
456
  ) -> dict[str, object]:
454
457
  """Commit a tabular import using the existing import service."""
455
458
  return import_bundle(
@@ -460,6 +463,7 @@ def mcp_import_write(
460
463
  write=True,
461
464
  overwrite=overwrite,
462
465
  on_conflict=on_conflict,
466
+ expected_preview_token=expected_preview_token,
463
467
  )
464
468
 
465
469