floe-python 0.6.6__tar.gz → 0.6.7__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 (242) hide show
  1. {floe_python-0.6.6 → floe_python-0.6.7}/Cargo.lock +3 -3
  2. {floe_python-0.6.6 → floe_python-0.6.7}/PKG-INFO +1 -1
  3. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/Cargo.toml +1 -1
  4. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/lib.rs +12 -1
  5. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/manifest/builder.rs +16 -4
  6. floe_python-0.6.7/crates/floe-core/src/manifest/mod.rs +8 -0
  7. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/manifest/model.rs +8 -0
  8. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/manifest/reconstruct.rs +22 -0
  9. floe_python-0.6.7/crates/floe-core/src/manifest/runtime.rs +139 -0
  10. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/run/mod.rs +35 -1
  11. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/unit/manifest/mod.rs +51 -8
  12. floe_python-0.6.7/crates/floe-core/tests/unit/manifest/runtime.rs +77 -0
  13. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/unit/run/mod.rs +1 -0
  14. floe_python-0.6.7/crates/floe-core/tests/unit/run/replay_base.rs +41 -0
  15. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-python/Cargo.toml +2 -2
  16. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-python/pyproject.duckdb.toml +2 -2
  17. {floe_python-0.6.6 → floe_python-0.6.7}/pyproject.toml +1 -1
  18. floe_python-0.6.6/crates/floe-core/src/manifest/mod.rs +0 -6
  19. {floe_python-0.6.6 → floe_python-0.6.7}/Cargo.toml +0 -0
  20. {floe_python-0.6.6 → floe_python-0.6.7}/README.md +0 -0
  21. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/README.md +0 -0
  22. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/add_entity.rs +0 -0
  23. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/checks/cast.rs +0 -0
  24. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/checks/mismatch.rs +0 -0
  25. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/checks/mod.rs +0 -0
  26. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/checks/normalize.rs +0 -0
  27. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/checks/not_null.rs +0 -0
  28. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/checks/unique.rs +0 -0
  29. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/config/catalog.rs +0 -0
  30. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/config/location.rs +0 -0
  31. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/config/mod.rs +0 -0
  32. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/config/parse.rs +0 -0
  33. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/config/storage.rs +0 -0
  34. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/config/template.rs +0 -0
  35. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/config/types.rs +0 -0
  36. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/config/validate.rs +0 -0
  37. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/config/yaml_decode.rs +0 -0
  38. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/errors.rs +0 -0
  39. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/io/format.rs +0 -0
  40. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/io/mod.rs +0 -0
  41. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/io/read/avro.rs +0 -0
  42. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/io/read/csv.rs +0 -0
  43. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/io/read/fixed_width.rs +0 -0
  44. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/io/read/json.rs +0 -0
  45. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/io/read/json_selector.rs +0 -0
  46. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/io/read/mod.rs +0 -0
  47. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/io/read/orc.rs +0 -0
  48. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/io/read/parquet.rs +0 -0
  49. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/io/read/xlsx.rs +0 -0
  50. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/io/read/xml.rs +0 -0
  51. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/io/read/xml_selector.rs +0 -0
  52. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/io/storage/core/extensions.rs +0 -0
  53. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/io/storage/core/mod.rs +0 -0
  54. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/io/storage/core/paths.rs +0 -0
  55. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/io/storage/core/placement.rs +0 -0
  56. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/io/storage/core/planner.rs +0 -0
  57. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/io/storage/core/uri.rs +0 -0
  58. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/io/storage/core/validation.rs +0 -0
  59. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/io/storage/mod.rs +0 -0
  60. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/io/storage/object_store.rs +0 -0
  61. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/io/storage/ops/archive.rs +0 -0
  62. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/io/storage/ops/inputs.rs +0 -0
  63. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/io/storage/ops/mod.rs +0 -0
  64. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/io/storage/ops/output.rs +0 -0
  65. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/io/storage/providers/adls.rs +0 -0
  66. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/io/storage/providers/gcs.rs +0 -0
  67. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/io/storage/providers/local.rs +0 -0
  68. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/io/storage/providers/mod.rs +0 -0
  69. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/io/storage/providers/s3.rs +0 -0
  70. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/io/storage/target.rs +0 -0
  71. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/io/unique_seed/mod.rs +0 -0
  72. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/io/write/accepted.rs +0 -0
  73. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/io/write/arrow_convert.rs +0 -0
  74. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/io/write/csv.rs +0 -0
  75. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/io/write/delta/commit_metrics.rs +0 -0
  76. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/io/write/delta/options.rs +0 -0
  77. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/io/write/delta/record_batch.rs +0 -0
  78. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/io/write/delta/unity.rs +0 -0
  79. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/io/write/delta/unity_tests.rs +0 -0
  80. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/io/write/delta.rs +0 -0
  81. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/io/write/duckdb/conn.rs +0 -0
  82. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/io/write/duckdb/merge.rs +0 -0
  83. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/io/write/duckdb.rs +0 -0
  84. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/io/write/iceberg/context.rs +0 -0
  85. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/io/write/iceberg/data_files.rs +0 -0
  86. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/io/write/iceberg/glue.rs +0 -0
  87. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/io/write/iceberg/metadata.rs +0 -0
  88. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/io/write/iceberg/rest.rs +0 -0
  89. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/io/write/iceberg/schema.rs +0 -0
  90. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/io/write/iceberg.rs +0 -0
  91. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/io/write/metrics.rs +0 -0
  92. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/io/write/mod.rs +0 -0
  93. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/io/write/parquet.rs +0 -0
  94. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/io/write/parts.rs +0 -0
  95. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/io/write/sink_format.rs +0 -0
  96. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/io/write/strategy/append.rs +0 -0
  97. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/io/write/strategy/merge/keys.rs +0 -0
  98. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/io/write/strategy/merge/mod.rs +0 -0
  99. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/io/write/strategy/merge/scd1.rs +0 -0
  100. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/io/write/strategy/merge/scd2.rs +0 -0
  101. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/io/write/strategy/merge/shared.rs +0 -0
  102. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/io/write/strategy/mod.rs +0 -0
  103. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/io/write/strategy/overwrite.rs +0 -0
  104. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/lineage/mod.rs +0 -0
  105. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/log.rs +0 -0
  106. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/profile/mod.rs +0 -0
  107. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/profile/parse.rs +0 -0
  108. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/profile/types.rs +0 -0
  109. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/profile/validate.rs +0 -0
  110. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/report/build.rs +0 -0
  111. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/report/entity.rs +0 -0
  112. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/report/mod.rs +0 -0
  113. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/report/output.rs +0 -0
  114. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/run/context.rs +0 -0
  115. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/run/entity/accepted_buffer.rs +0 -0
  116. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/run/entity/accepted_write.rs +0 -0
  117. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/run/entity/incremental.rs +0 -0
  118. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/run/entity/mod.rs +0 -0
  119. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/run/entity/pii.rs +0 -0
  120. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/run/entity/precheck.rs +0 -0
  121. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/run/entity/process.rs +0 -0
  122. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/run/entity/resolve.rs +0 -0
  123. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/run/entity/validate_split.rs +0 -0
  124. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/run/events.rs +0 -0
  125. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/run/file.rs +0 -0
  126. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/run/output.rs +0 -0
  127. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/run/perf.rs +0 -0
  128. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/runner/mod.rs +0 -0
  129. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/runner/outcome.rs +0 -0
  130. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/runtime.rs +0 -0
  131. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/secret.rs +0 -0
  132. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/state/mod.rs +0 -0
  133. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/vars/mod.rs +0 -0
  134. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/vars/resolve.rs +0 -0
  135. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/src/warnings.rs +0 -0
  136. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/integration/archive_run.rs +0 -0
  137. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/integration/composite_unique.rs +0 -0
  138. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/integration/delta_run.rs +0 -0
  139. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/integration/dry_run.rs +0 -0
  140. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/integration/duckdb_run.rs +0 -0
  141. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/integration/fixed_width.rs +0 -0
  142. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/integration/iceberg_gcs_run.rs +0 -0
  143. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/integration/iceberg_glue_run.rs +0 -0
  144. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/integration/iceberg_run.rs +0 -0
  145. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/integration/iceberg_s3_run.rs +0 -0
  146. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/integration/json_selectors.rs +0 -0
  147. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/integration/local_run.rs +0 -0
  148. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/integration/mod.rs +0 -0
  149. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/integration/path_normalization.rs +0 -0
  150. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/integration/run_entities_filter.rs +0 -0
  151. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/integration.rs +0 -0
  152. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/unit/common.rs +0 -0
  153. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/unit/config/add_entity.rs +0 -0
  154. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/unit/config/adls_storage.rs +0 -0
  155. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/unit/config/adls_validation.rs +0 -0
  156. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/unit/config/catalogs.rs +0 -0
  157. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/unit/config/config_validation.rs +0 -0
  158. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/unit/config/duckdb_validation.rs +0 -0
  159. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/unit/config/gcs_storage.rs +0 -0
  160. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/unit/config/gcs_validation.rs +0 -0
  161. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/unit/config/lineage_validation.rs +0 -0
  162. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/unit/config/local_storage.rs +0 -0
  163. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/unit/config/location.rs +0 -0
  164. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/unit/config/mod.rs +0 -0
  165. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/unit/config/parse.rs +0 -0
  166. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/unit/config/pii_validation.rs +0 -0
  167. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/unit/config/remote_base.rs +0 -0
  168. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/unit/config/storage_resolver_uri.rs +0 -0
  169. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/unit/config/templating.rs +0 -0
  170. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/unit/io/format.rs +0 -0
  171. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/unit/io/mod.rs +0 -0
  172. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/unit/io/read/avro_input.rs +0 -0
  173. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/unit/io/read/csv_nulls.rs +0 -0
  174. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/unit/io/read/json_array.rs +0 -0
  175. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/unit/io/read/json_ndjson.rs +0 -0
  176. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/unit/io/read/json_selector.rs +0 -0
  177. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/unit/io/read/mod.rs +0 -0
  178. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/unit/io/read/orc_input.rs +0 -0
  179. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/unit/io/read/parquet_input.rs +0 -0
  180. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/unit/io/read/tsv.rs +0 -0
  181. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/unit/io/read/xlsx_input.rs +0 -0
  182. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/unit/io/read/xml.rs +0 -0
  183. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/unit/io/read/xml_selector.rs +0 -0
  184. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/unit/io/storage/adls.rs +0 -0
  185. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/unit/io/storage/adls_integration.rs +0 -0
  186. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/unit/io/storage/gcs.rs +0 -0
  187. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/unit/io/storage/inputs.rs +0 -0
  188. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/unit/io/storage/local.rs +0 -0
  189. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/unit/io/storage/mod.rs +0 -0
  190. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/unit/io/storage/paths.rs +0 -0
  191. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/unit/io/storage/planner.rs +0 -0
  192. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/unit/io/storage/s3.rs +0 -0
  193. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/unit/io/storage/target.rs +0 -0
  194. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/unit/io/write/delta_merge.rs +0 -0
  195. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/unit/io/write/delta_write.rs +0 -0
  196. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/unit/io/write/iceberg_write.rs +0 -0
  197. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/unit/io/write/metrics.rs +0 -0
  198. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/unit/io/write/mod.rs +0 -0
  199. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/unit/io/write/object_store.rs +0 -0
  200. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/unit/io/write/parquet_write.rs +0 -0
  201. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/unit/io/write/parts.rs +0 -0
  202. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/unit/io/write/rejected_csv.rs +0 -0
  203. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/unit/mod.rs +0 -0
  204. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/unit/profile/mod.rs +0 -0
  205. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/unit/profile/parse.rs +0 -0
  206. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/unit/profile/validate.rs +0 -0
  207. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/unit/report/accepted_output.rs +0 -0
  208. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/unit/report/mod.rs +0 -0
  209. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/unit/report/storage.rs +0 -0
  210. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/unit/run/check_order.rs +0 -0
  211. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/unit/run/checks.rs +0 -0
  212. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/unit/run/entity/accepted_output.rs +0 -0
  213. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/unit/run/entity/incremental.rs +0 -0
  214. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/unit/run/entity/mod.rs +0 -0
  215. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/unit/run/lineage.rs +0 -0
  216. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/unit/run/normalize.rs +0 -0
  217. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/unit/run/pii.rs +0 -0
  218. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/unit/run/report.rs +0 -0
  219. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/unit/run/schema_mismatch.rs +0 -0
  220. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/unit/runner/adapter.rs +0 -0
  221. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/unit/runner/mod.rs +0 -0
  222. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/unit/state/mod.rs +0 -0
  223. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/unit/vars/mod.rs +0 -0
  224. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/unit/vars/resolve.rs +0 -0
  225. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-core/tests/unit.rs +0 -0
  226. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-python/.gitignore +0 -0
  227. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-python/README.md +0 -0
  228. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-python/src/functions.rs +0 -0
  229. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-python/src/lib.rs +0 -0
  230. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-python/src/observer.rs +0 -0
  231. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-python/src/types/config.rs +0 -0
  232. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-python/src/types/errors.rs +0 -0
  233. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-python/src/types/mod.rs +0 -0
  234. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-python/src/types/outcome.rs +0 -0
  235. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-python/tests/fixtures/config.yml +0 -0
  236. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-python/tests/fixtures/in/customer/customers_valid.csv +0 -0
  237. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-python/tests/fixtures/invalid_config.yml +0 -0
  238. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-python/tests/fixtures/profile.yml +0 -0
  239. {floe_python-0.6.6 → floe_python-0.6.7}/crates/floe-python/tests/test_floe.py +0 -0
  240. {floe_python-0.6.6 → floe_python-0.6.7}/python/floe/__init__.py +0 -0
  241. {floe_python-0.6.6 → floe_python-0.6.7}/python/floe/_floe.pyi +0 -0
  242. {floe_python-0.6.6 → floe_python-0.6.7}/python/floe/py.typed +0 -0
@@ -3340,7 +3340,7 @@ dependencies = [
3340
3340
 
3341
3341
  [[package]]
3342
3342
  name = "floe-cli"
3343
- version = "0.6.6"
3343
+ version = "0.6.7"
3344
3344
  dependencies = [
3345
3345
  "assert_cmd",
3346
3346
  "clap",
@@ -3353,7 +3353,7 @@ dependencies = [
3353
3353
 
3354
3354
  [[package]]
3355
3355
  name = "floe-core"
3356
- version = "0.6.6"
3356
+ version = "0.6.7"
3357
3357
  dependencies = [
3358
3358
  "apache-avro",
3359
3359
  "arrow",
@@ -3399,7 +3399,7 @@ dependencies = [
3399
3399
 
3400
3400
  [[package]]
3401
3401
  name = "floe-python"
3402
- version = "0.6.6"
3402
+ version = "0.6.7"
3403
3403
  dependencies = [
3404
3404
  "floe-core",
3405
3405
  "pyo3",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: floe-python
3
- Version: 0.6.6
3
+ Version: 0.6.7
4
4
  Classifier: Development Status :: 4 - Beta
5
5
  Classifier: Intended Audience :: Developers
6
6
  Classifier: Intended Audience :: Science/Research
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "floe-core"
3
- version = "0.6.6"
3
+ version = "0.6.7"
4
4
  edition = "2021"
5
5
  description = "Core library for Floe, a YAML-driven technical ingestion tool."
6
6
  license = "MIT"
@@ -38,7 +38,8 @@ pub use config::{
38
38
  };
39
39
  pub use errors::{FloeError, FloeErrorKind};
40
40
  pub use manifest::{
41
- build_common_manifest_json, config_from_manifest_json, ManifestOptions, PathMode,
41
+ build_common_manifest_json, config_from_manifest_json, local_uri_for_env, manifest_runtime,
42
+ ManifestOptions, PathMode, RuntimeEnv, DEFAULT_WORK_ROOT,
42
43
  };
43
44
  pub use profile::{
44
45
  detect_malformed_placeholder, detect_unresolved_placeholders, parse_profile,
@@ -172,3 +173,13 @@ pub fn extract_config_env_vars(
172
173
  pub fn validate_config_for_tests(config: &config::RootConfig) -> FloeResult<()> {
173
174
  config::validate_config(config)
174
175
  }
176
+
177
+ /// Test-only accessor for the private manifest-replay path-resolution base (issue #443):
178
+ /// a manifest loaded from a remote URI must resolve local paths under its recorded
179
+ /// `work_root`, never against the manifest's own remote location.
180
+ pub fn manifest_replay_config_base_for_tests(
181
+ manifest_base: &config::ConfigBase,
182
+ json: &str,
183
+ ) -> config::ConfigBase {
184
+ run::manifest_replay_config_base(manifest_base, json)
185
+ }
@@ -51,6 +51,10 @@ pub struct ManifestOptions {
51
51
  pub default_domain: Option<String>,
52
52
  /// Controls how the `path` field is set in source and sink entries.
53
53
  pub path_mode: PathMode,
54
+ /// Runtime the manifest targets. `Image` records local `config_uri` / `profile_uri`
55
+ /// absolute under the container work-root (`/work`), restoring the portable pre-0.6.6
56
+ /// form; `Cli` keeps them relative / as-typed for cross-checkout reproducibility.
57
+ pub runtime_env: crate::manifest::RuntimeEnv,
54
58
  }
55
59
 
56
60
  #[derive(Debug)]
@@ -378,10 +382,16 @@ fn build_common_manifest(
378
382
  });
379
383
  }
380
384
 
381
- // Use `uri` (the path as typed / resolved remote URI), not the host-absolute
382
- // `display`, so config_uri and the manifest_id derived from it do not depend on
383
- // where the config file physically lives.
384
- let config_uri = config_location.uri.clone();
385
+ // A manifest is replayed by a different runner than the one that generated it, so
386
+ // `config_uri` must be absolute. `local_uri_for_env` absolutizes the local path against
387
+ // the target runtime's work-root: `Image` rebases the relative path under the container
388
+ // work-root (`local:///work/...`, portable + reproducible across containers), `Cli`
389
+ // records the host-canonical path. Remote config URIs pass through unchanged.
390
+ let config_uri = crate::manifest::local_uri_for_env(
391
+ &config_location.uri,
392
+ &config_location.path,
393
+ options.runtime_env,
394
+ );
385
395
  let config_checksum = std::fs::read(&config_location.path)
386
396
  .ok()
387
397
  .map(|b| sha256_hex(&b));
@@ -423,6 +433,8 @@ fn build_common_manifest(
423
433
  manifest_name: options.manifest_name.clone(),
424
434
  manifest_id: build_manifest_id(&config_uri, config_checksum.as_deref()),
425
435
  manifest_revision: None,
436
+ runtime_env: options.runtime_env.as_str(),
437
+ work_root: options.runtime_env.manifest_work_root(),
426
438
  config_uri,
427
439
  config_checksum,
428
440
  profile_uri: options.profile_uri.clone(),
@@ -0,0 +1,8 @@
1
+ mod builder;
2
+ mod model;
3
+ pub mod reconstruct;
4
+ mod runtime;
5
+
6
+ pub use builder::{build_common_manifest_json, ManifestOptions, PathMode};
7
+ pub use reconstruct::{config_from_manifest_json, manifest_runtime};
8
+ pub use runtime::{local_uri_for_env, RuntimeEnv, DEFAULT_WORK_ROOT};
@@ -12,6 +12,14 @@ pub struct CommonManifest {
12
12
  pub manifest_id: String,
13
13
  #[serde(skip_serializing_if = "Option::is_none")]
14
14
  pub manifest_revision: Option<String>,
15
+ /// Runtime the manifest targets: `"image"` (local URIs absolute under the container
16
+ /// work-root) or `"cli"` (local URIs relative / as-typed). Drives how a replaying
17
+ /// runner resolves local paths. See `RuntimeEnv`.
18
+ pub runtime_env: &'static str,
19
+ /// Work-root local paths are resolved under at replay time (e.g. `/work`). `Some` only
20
+ /// for `runtime_env == "image"`; `None` for `"cli"` (paths are relative, no fixed root).
21
+ #[serde(skip_serializing_if = "Option::is_none")]
22
+ pub work_root: Option<String>,
15
23
  pub config_uri: String,
16
24
  #[serde(skip_serializing_if = "Option::is_none")]
17
25
  pub config_checksum: Option<String>,
@@ -110,6 +110,28 @@ fn deserialize_manifest_section<T: serde::de::DeserializeOwned>(
110
110
  }
111
111
  }
112
112
 
113
+ /// The replay runtime hints recorded by the generator. Deserialized on their own (serde
114
+ /// ignores the rest of the manifest) so a runner can read them without depending on the full
115
+ /// `ManifestForRun` shape parsing cleanly.
116
+ #[derive(Debug, Default, Deserialize)]
117
+ struct ManifestRuntimeHints {
118
+ #[serde(default)]
119
+ runtime_env: Option<String>,
120
+ #[serde(default)]
121
+ work_root: Option<String>,
122
+ }
123
+
124
+ /// Parse the replay runtime hints (`runtime_env` / `work_root`) from a manifest JSON string.
125
+ /// Missing or malformed hints map to `(RuntimeEnv::Cli, None)`, so pre-0.6.7 manifests keep
126
+ /// their previous resolution behavior.
127
+ pub fn manifest_runtime(json: &str) -> (crate::manifest::RuntimeEnv, Option<String>) {
128
+ let hints: ManifestRuntimeHints = serde_json::from_str(json).unwrap_or_default();
129
+ (
130
+ crate::manifest::RuntimeEnv::from_manifest_str(hints.runtime_env.as_deref()),
131
+ hints.work_root,
132
+ )
133
+ }
134
+
113
135
  /// Parse a manifest JSON string and reconstruct a minimal RootConfig.
114
136
  /// Returns (config, report_base_uri).
115
137
  pub fn config_from_manifest_json(json: &str) -> FloeResult<(crate::config::RootConfig, String)> {
@@ -0,0 +1,139 @@
1
+ use std::path::{Path, PathBuf};
2
+
3
+ /// Default container work-root. Matches `WORKDIR /work` in every Floe Dockerfile
4
+ /// (`Dockerfile`, `Dockerfile.duckdb`, `Dockerfile.release`).
5
+ pub const DEFAULT_WORK_ROOT: &str = "/work";
6
+
7
+ /// The runtime environment a generated manifest targets. Controls whether local
8
+ /// `config_uri` / `profile_uri` (and the `manifest_id` derived from them) are recorded
9
+ /// as absolute paths under a fixed container work-root, or kept relative / as-typed.
10
+ #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
11
+ pub enum RuntimeEnv {
12
+ /// Container image: local paths are recorded absolute under the work-root (`/work`),
13
+ /// which is both portable for remote replay and reproducible across container runs
14
+ /// (every container uses the same `/work` mount).
15
+ Image,
16
+ /// Local CLI / dev checkout: local paths are recorded host-absolute (the canonicalized
17
+ /// path on the generating machine), so the manifest is resolvable on that host. It is
18
+ /// intentionally not reproducible across machines — the reproducible-CI case is served by
19
+ /// `Image` (fixed `/work`), not by CLI manifests.
20
+ #[default]
21
+ Cli,
22
+ }
23
+
24
+ impl RuntimeEnv {
25
+ /// Detect the runtime from the environment when the CLI caller did not pass `--runtime`.
26
+ ///
27
+ /// Precedence:
28
+ /// 1. `FLOE_RUNTIME=image|cli` — explicit override.
29
+ /// 2. Container heuristic: `/.dockerenv` exists, or the current dir is the `/work` mount.
30
+ /// 3. Otherwise `Cli`.
31
+ pub fn detect() -> Self {
32
+ if let Ok(value) = std::env::var("FLOE_RUNTIME") {
33
+ match value.trim().to_ascii_lowercase().as_str() {
34
+ "image" => return RuntimeEnv::Image,
35
+ "cli" => return RuntimeEnv::Cli,
36
+ _ => {}
37
+ }
38
+ }
39
+ if Path::new("/.dockerenv").exists() {
40
+ return RuntimeEnv::Image;
41
+ }
42
+ if std::env::current_dir()
43
+ .ok()
44
+ .is_some_and(|dir| dir == Path::new(DEFAULT_WORK_ROOT))
45
+ {
46
+ return RuntimeEnv::Image;
47
+ }
48
+ RuntimeEnv::Cli
49
+ }
50
+
51
+ /// String recorded as `runtime_env` in the manifest.
52
+ pub fn as_str(self) -> &'static str {
53
+ match self {
54
+ RuntimeEnv::Image => "image",
55
+ RuntimeEnv::Cli => "cli",
56
+ }
57
+ }
58
+
59
+ /// Parse the manifest-recorded `runtime_env` string. Unknown or missing maps to `Cli`,
60
+ /// which is backward-compatible: manifests generated before this field existed carry
61
+ /// relative local URIs and must keep resolving as before.
62
+ pub fn from_manifest_str(value: Option<&str>) -> Self {
63
+ match value {
64
+ Some(v) if v.eq_ignore_ascii_case("image") => RuntimeEnv::Image,
65
+ _ => RuntimeEnv::Cli,
66
+ }
67
+ }
68
+
69
+ /// Work-root that local paths are recorded relative to (at generation) and resolved
70
+ /// under (at replay). `Image` → `/work`, overridable via `FLOE_WORK_ROOT`;
71
+ /// `Cli` → the current working directory.
72
+ pub fn work_root(self) -> PathBuf {
73
+ match self {
74
+ RuntimeEnv::Image => std::env::var("FLOE_WORK_ROOT")
75
+ .ok()
76
+ .filter(|value| !value.trim().is_empty())
77
+ .map(PathBuf::from)
78
+ .unwrap_or_else(|| PathBuf::from(DEFAULT_WORK_ROOT)),
79
+ RuntimeEnv::Cli => std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")),
80
+ }
81
+ }
82
+
83
+ /// The `work_root` value recorded in the manifest: `Some("/work")` for `Image`
84
+ /// (so remote replay resolves local paths against the same root), `None` for `Cli`
85
+ /// (paths are already host-absolute and self-contained, so there is no separate root
86
+ /// to record).
87
+ pub fn manifest_work_root(self) -> Option<String> {
88
+ match self {
89
+ RuntimeEnv::Image => Some(self.work_root().to_string_lossy().replace('\\', "/")),
90
+ RuntimeEnv::Cli => None,
91
+ }
92
+ }
93
+ }
94
+
95
+ /// Given the as-typed `local://…` URI recorded for a local config/profile (`uri`) and the
96
+ /// canonicalized absolute path of that file (`canonical_path`), return the absolute form
97
+ /// appropriate for `env`. Remote URIs are passed through unchanged.
98
+ ///
99
+ /// Every local path in a manifest must be absolute, because the runner that generates the
100
+ /// manifest is not the one that replays it. The absolute base depends on the target runtime:
101
+ ///
102
+ /// - `Image`: a *relative* local path is rebased under the container work-root
103
+ /// (`local://domains/x.yml` → `local:///work/domains/x.yml`) — portable for remote replay
104
+ /// and reproducible across containers (every container mounts `/work`).
105
+ /// - `Cli`: the path is recorded host-absolute from `canonical_path`
106
+ /// (`local:///Users/you/repo/domains/x.yml`) — resolvable on the generating host.
107
+ ///
108
+ /// An already-absolute local path (leading `/`, or a Windows drive/UNC prefix) and any remote
109
+ /// URI are returned unchanged, in both modes.
110
+ pub fn local_uri_for_env(uri: &str, canonical_path: &Path, env: RuntimeEnv) -> String {
111
+ let Some(rel) = uri.strip_prefix("local://") else {
112
+ // Remote URIs (s3://, gs://, abfs://) are already environment-independent.
113
+ return uri.to_string();
114
+ };
115
+ match env {
116
+ RuntimeEnv::Cli => {
117
+ // Always record the canonicalized host-absolute path, regardless of how `-c` was
118
+ // typed (relative, or absolute with `..`). Resolvable on the generating host.
119
+ let abs = canonical_path.to_string_lossy().replace('\\', "/");
120
+ format!("local://{abs}")
121
+ }
122
+ RuntimeEnv::Image => {
123
+ if rel.starts_with('/') || has_drive_prefix(rel) {
124
+ // Already absolute — portable as-is; do not re-root under the work-root.
125
+ return uri.to_string();
126
+ }
127
+ let root = env.work_root();
128
+ let root = root.to_string_lossy().replace('\\', "/");
129
+ let root = root.trim_end_matches('/');
130
+ format!("local://{root}/{rel}")
131
+ }
132
+ }
133
+ }
134
+
135
+ /// True for a Windows drive prefix such as `C:` at the start of `path`.
136
+ fn has_drive_prefix(path: &str) -> bool {
137
+ let bytes = path.as_bytes();
138
+ bytes.len() >= 2 && bytes[0].is_ascii_alphabetic() && bytes[1] == b':'
139
+ }
@@ -107,7 +107,7 @@ pub(crate) fn run_with_manifest_runtime(
107
107
  let location = config::resolve_config_location(&manifest_str)?;
108
108
  let json = std::fs::read_to_string(&location.path)?;
109
109
  let (config, report_base_uri) = crate::manifest::config_from_manifest_json(&json)?;
110
- let config_base = location.base.clone();
110
+ let config_base = manifest_replay_config_base(&location.base, &json);
111
111
  if !options.entities.is_empty() {
112
112
  validate_entities(&config, &options.entities)?;
113
113
  }
@@ -121,6 +121,40 @@ pub(crate) fn run_with_manifest_runtime(
121
121
  run_from_context(context, options, runtime)
122
122
  }
123
123
 
124
+ /// Choose the path-resolution base for manifest replay.
125
+ ///
126
+ /// A manifest is self-contained: its source/sink URIs are already resolved and it embeds a
127
+ /// `storages` block. When it is loaded from a **remote** URI, the manifest's own bucket/prefix
128
+ /// must NOT become the resolution base — otherwise a relative path in the manifest (e.g. an
129
+ /// Iceberg sink that did not resolve to an absolute URI) is re-based onto the manifest bucket
130
+ /// and trips the `storage <name> bucket mismatch` guard (issue #443). Instead, resolve local
131
+ /// paths under the target runtime's work-root (manifest `work_root` → `FLOE_WORK_ROOT` →
132
+ /// `RuntimeEnv::detect()` default) and leave `remote_base` unset so remote paths resolve purely
133
+ /// from their storage definitions.
134
+ ///
135
+ /// For a **local** manifest file, behavior is unchanged: relative paths resolve against the
136
+ /// manifest file's directory, exactly as before.
137
+ pub(crate) fn manifest_replay_config_base(
138
+ manifest_base: &config::ConfigBase,
139
+ json: &str,
140
+ ) -> config::ConfigBase {
141
+ if manifest_base.remote_base().is_none() {
142
+ return manifest_base.clone();
143
+ }
144
+ let (runtime_env, manifest_work_root) = crate::manifest::manifest_runtime(json);
145
+ let work_root = manifest_work_root
146
+ .map(std::path::PathBuf::from)
147
+ .or_else(|| {
148
+ std::env::var("FLOE_WORK_ROOT")
149
+ .ok()
150
+ .filter(|value| !value.trim().is_empty())
151
+ .map(std::path::PathBuf::from)
152
+ })
153
+ .unwrap_or_else(|| runtime_env.work_root());
154
+ // `local_from_path` uses the parent as the local base dir, so join a sentinel file name.
155
+ config::ConfigBase::local_from_path(&work_root.join("manifest.json"))
156
+ }
157
+
124
158
  pub fn run_with_runtime(
125
159
  config_path: &Path,
126
160
  config_base: config::ConfigBase,
@@ -1,6 +1,8 @@
1
+ pub mod runtime;
2
+
1
3
  use floe_core::{
2
4
  build_common_manifest_json, config_from_manifest_json, load_config, parse_profile_from_str,
3
- resolve_config_location, ManifestOptions, PathMode,
5
+ resolve_config_location, ManifestOptions, PathMode, RuntimeEnv,
4
6
  };
5
7
  // `validate_config_for_tests` is only exercised by the duckdb-gated MotherDuck
6
8
  // round-trip assertion below; importing it unconditionally would warn (and fail the
@@ -44,10 +46,10 @@ fn manifest_uses_local_uri_for_local_config() {
44
46
  }
45
47
 
46
48
  #[test]
47
- fn manifest_config_uri_is_not_canonicalized() {
48
- // config_uri reflects the path as typed, not the canonical path: a lexical `..`
49
- // bounce (which canonicalize() would collapse) surviving in the output proves
50
- // the path was preserved as-typed.
49
+ fn manifest_cli_config_uri_is_host_absolute_canonical() {
50
+ // Under `cli` runtime every local path is recorded host-absolute and canonicalized: a
51
+ // lexical `..` bounce in the typed path is collapsed, so config_uri is a clean absolute
52
+ // URI that resolves on the generating host (the definitive "all paths absolute" rule).
51
53
  let temp_dir = tempfile::TempDir::new().expect("temp dir");
52
54
  let cfg_dir = temp_dir.path().join("cfg");
53
55
  std::fs::create_dir_all(&cfg_dir).expect("cfg dir");
@@ -58,7 +60,7 @@ fn manifest_config_uri_is_not_canonicalized() {
58
60
  )
59
61
  .expect("write config");
60
62
 
61
- // <cfg_dir>/../cfg/config.yml — canonicalize() would resolve this to
63
+ // <cfg_dir>/../cfg/config.yml — cli canonicalization resolves this to
62
64
  // <cfg_dir>/config.yml, dropping the "/../".
63
65
  let bouncy = format!("{}/../cfg/config.yml", cfg_dir.display());
64
66
  let loc = resolve_config_location(&bouncy).expect("resolve config location");
@@ -69,10 +71,51 @@ fn manifest_config_uri_is_not_canonicalized() {
69
71
 
70
72
  let uri = value["config_uri"].as_str().expect("config_uri string");
71
73
  assert!(
72
- uri.contains("/../cfg/config.yml"),
73
- "config_uri was canonicalized (lost the as-typed form): {uri}"
74
+ uri.starts_with("local:///"),
75
+ "cli config_uri must be host-absolute: {uri}"
76
+ );
77
+ assert!(
78
+ !uri.contains("/../"),
79
+ "cli config_uri must be canonicalized (no `..`): {uri}"
74
80
  );
75
81
  assert!(value["manifest_id"].as_str().unwrap().starts_with("mfv1-"));
82
+ // Default (no runtime override) auto-detects; in the test env that is `cli`.
83
+ assert_eq!(value["runtime_env"], "cli");
84
+ assert!(
85
+ value.get("work_root").is_none(),
86
+ "cli must not record work_root"
87
+ );
88
+ }
89
+
90
+ #[test]
91
+ fn manifest_image_runtime_records_runtime_env_and_work_root() {
92
+ // `image` runtime records runtime_env/work_root so a remote runner knows how to resolve
93
+ // local paths. An absolute config path is already portable, so it is preserved unchanged
94
+ // (the relative -> /work re-rooting is covered by the runtime module's unit tests and the
95
+ // CLI `--runtime image` reproducibility test).
96
+ let temp_dir = tempfile::TempDir::new().expect("temp dir");
97
+ let cfg_dir = temp_dir.path().join("cfg");
98
+ std::fs::create_dir_all(&cfg_dir).expect("cfg dir");
99
+ let config_path = cfg_dir.join("config.yml");
100
+ std::fs::write(
101
+ &config_path,
102
+ "version: \"0.1\"\nentities:\n - name: orders\n source:\n format: csv\n path: ./in/orders\n sink:\n write_mode: overwrite\n accepted:\n format: parquet\n path: ./out/orders\n policy:\n severity: warn\n schema:\n columns:\n - name: id\n type: string\n",
103
+ )
104
+ .expect("write config");
105
+
106
+ let loc = resolve_config_location(config_path.to_str().expect("utf8")).expect("resolve");
107
+ let config = load_config(&loc.path).expect("load config");
108
+ let opts = ManifestOptions {
109
+ runtime_env: RuntimeEnv::Image,
110
+ ..ManifestOptions::default()
111
+ };
112
+ let payload = build_common_manifest_json(&loc, &config, &[], None, &opts).expect("manifest");
113
+ let value: Value = serde_json::from_str(&payload).expect("valid json");
114
+
115
+ assert_eq!(value["runtime_env"], "image");
116
+ assert_eq!(value["work_root"], "/work");
117
+ let uri = value["config_uri"].as_str().expect("config_uri string");
118
+ assert!(uri.starts_with("local://"), "config_uri: {uri}");
76
119
  }
77
120
 
78
121
  #[test]
@@ -0,0 +1,77 @@
1
+ use floe_core::{local_uri_for_env, RuntimeEnv};
2
+ use std::path::Path;
3
+
4
+ #[test]
5
+ fn cli_records_host_absolute_canonical_path() {
6
+ // `Cli` ignores the (relative) as-typed URI and records the canonicalized
7
+ // host-absolute path, so the manifest is resolvable on the generating host.
8
+ assert_eq!(
9
+ local_uri_for_env(
10
+ "local://domains/orders.yml",
11
+ Path::new("/home/u/repo/domains/orders.yml"),
12
+ RuntimeEnv::Cli,
13
+ ),
14
+ "local:///home/u/repo/domains/orders.yml"
15
+ );
16
+ }
17
+
18
+ #[test]
19
+ fn image_absolutizes_relative_local_uri_under_work_root() {
20
+ // Default work-root is `/work` (no FLOE_WORK_ROOT set in the test env). `Image` uses
21
+ // the as-typed relative path and ignores the host-canonical path entirely.
22
+ assert_eq!(
23
+ local_uri_for_env(
24
+ "local://domains/orders.yml",
25
+ Path::new("/home/u/repo/domains/orders.yml"),
26
+ RuntimeEnv::Image,
27
+ ),
28
+ "local:///work/domains/orders.yml"
29
+ );
30
+ }
31
+
32
+ #[test]
33
+ fn already_absolute_local_uri_untouched_in_both_modes() {
34
+ for env in [RuntimeEnv::Cli, RuntimeEnv::Image] {
35
+ assert_eq!(
36
+ local_uri_for_env("local:///abs/orders.yml", Path::new("/abs/orders.yml"), env,),
37
+ "local:///abs/orders.yml"
38
+ );
39
+ }
40
+ }
41
+
42
+ #[test]
43
+ fn remote_uris_pass_through_in_both_modes() {
44
+ for env in [RuntimeEnv::Cli, RuntimeEnv::Image] {
45
+ assert_eq!(
46
+ local_uri_for_env("s3://bucket/orders.yml", Path::new("/tmp/orders.yml"), env,),
47
+ "s3://bucket/orders.yml"
48
+ );
49
+ }
50
+ }
51
+
52
+ #[test]
53
+ fn from_manifest_str_maps_image_case_insensitively_else_cli() {
54
+ assert_eq!(
55
+ RuntimeEnv::from_manifest_str(Some("image")),
56
+ RuntimeEnv::Image
57
+ );
58
+ assert_eq!(
59
+ RuntimeEnv::from_manifest_str(Some("IMAGE")),
60
+ RuntimeEnv::Image
61
+ );
62
+ assert_eq!(RuntimeEnv::from_manifest_str(Some("cli")), RuntimeEnv::Cli);
63
+ assert_eq!(RuntimeEnv::from_manifest_str(None), RuntimeEnv::Cli);
64
+ assert_eq!(
65
+ RuntimeEnv::from_manifest_str(Some("weird")),
66
+ RuntimeEnv::Cli
67
+ );
68
+ }
69
+
70
+ #[test]
71
+ fn manifest_work_root_recorded_only_for_image() {
72
+ assert_eq!(
73
+ RuntimeEnv::Image.manifest_work_root().as_deref(),
74
+ Some("/work")
75
+ );
76
+ assert_eq!(RuntimeEnv::Cli.manifest_work_root(), None);
77
+ }
@@ -4,5 +4,6 @@ pub mod entity;
4
4
  pub mod lineage;
5
5
  pub mod normalize;
6
6
  pub mod pii;
7
+ pub mod replay_base;
7
8
  pub mod report;
8
9
  pub mod schema_mismatch;
@@ -0,0 +1,41 @@
1
+ use floe_core::config::ConfigBase;
2
+ use floe_core::manifest_replay_config_base_for_tests as manifest_replay_config_base;
3
+ use std::path::{Path, PathBuf};
4
+
5
+ #[test]
6
+ fn remote_manifest_replay_uses_recorded_work_root_not_manifest_bucket() {
7
+ // A manifest loaded from a remote URI must NOT resolve its paths against its own
8
+ // bucket (issue #443). With runtime_env=image/work_root=/work, the replay base is a
9
+ // local root at /work with no remote_base.
10
+ let remote = ConfigBase::remote_from_uri(
11
+ PathBuf::from("/tmp/manifest"),
12
+ "s3://openlakeforge-ops/floe/manifests/x.manifest.json",
13
+ )
14
+ .expect("remote base");
15
+ let json = r#"{"runtime_env":"image","work_root":"/work"}"#;
16
+ let base = manifest_replay_config_base(&remote, json);
17
+ assert!(
18
+ base.remote_base().is_none(),
19
+ "remote manifest bucket must not become the resolution base"
20
+ );
21
+ assert_eq!(base.local_dir(), Path::new("/work"));
22
+ }
23
+
24
+ #[test]
25
+ fn remote_manifest_replay_never_keeps_remote_base_even_without_hints() {
26
+ let remote = ConfigBase::remote_from_uri(
27
+ PathBuf::from("/tmp/manifest"),
28
+ "s3://bucket/x.manifest.json",
29
+ )
30
+ .expect("remote base");
31
+ let base = manifest_replay_config_base(&remote, "{}");
32
+ assert!(base.remote_base().is_none());
33
+ }
34
+
35
+ #[test]
36
+ fn local_manifest_replay_base_is_unchanged() {
37
+ let local = ConfigBase::local_from_path(Path::new("/data/manifests/x.json"));
38
+ let base = manifest_replay_config_base(&local, "{}");
39
+ assert!(base.remote_base().is_none());
40
+ assert_eq!(base.local_dir(), Path::new("/data/manifests"));
41
+ }
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "floe-python"
3
- version = "0.6.6"
3
+ version = "0.6.7"
4
4
  edition = "2021"
5
5
  description = "Python bindings for floe-core (PyO3-based)"
6
6
  license = "Apache-2.0"
@@ -14,7 +14,7 @@ name = "_floe"
14
14
  crate-type = ["cdylib"]
15
15
 
16
16
  [dependencies]
17
- floe-core = { path = "../floe-core", version = "0.6.6", default-features = false }
17
+ floe-core = { path = "../floe-core", version = "0.6.7", default-features = false }
18
18
  pyo3 = { version = "0.29", features = ["extension-module", "abi3-py310"] }
19
19
  serde_json = "1"
20
20
 
@@ -22,7 +22,7 @@ build-backend = "maturin"
22
22
 
23
23
  [project]
24
24
  name = "floe-duckdb"
25
- version = "0.6.6"
25
+ version = "0.6.7"
26
26
  description = "DuckDB companion extension for floe — adds the native DuckDB sink to the floe Python package"
27
27
  readme = "README.md"
28
28
  requires-python = ">=3.10"
@@ -46,7 +46,7 @@ classifiers = [
46
46
  # only contributes the compiled DuckDB extension module into that package. Pin to
47
47
  # the exact lean version so the companion's `_floe_duckdb` ABI always matches the
48
48
  # `floe` package it plugs into.
49
- dependencies = ["floe-python==0.6.6"]
49
+ dependencies = ["floe-python==0.6.7"]
50
50
 
51
51
  [project.urls]
52
52
  Homepage = "https://github.com/malon64/floe"
@@ -4,7 +4,7 @@ build-backend = "maturin"
4
4
 
5
5
  [project]
6
6
  name = "floe-python"
7
- version = "0.6.6"
7
+ version = "0.6.7"
8
8
  description = "Python bindings for floe-core — run floe pipelines at Rust speed from Python and notebooks"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -1,6 +0,0 @@
1
- mod builder;
2
- mod model;
3
- pub mod reconstruct;
4
-
5
- pub use builder::{build_common_manifest_json, ManifestOptions, PathMode};
6
- pub use reconstruct::config_from_manifest_json;
File without changes
File without changes