macrame-db 0.7.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. macrame_db-0.7.0/.cargo/config.toml +119 -0
  2. macrame_db-0.7.0/.github/workflows/ci.yml +137 -0
  3. macrame_db-0.7.0/.github/workflows/python.yml +187 -0
  4. macrame_db-0.7.0/.github/workflows/release.yml +87 -0
  5. macrame_db-0.7.0/.github/workflows/wheels.yml +198 -0
  6. macrame_db-0.7.0/.gitignore +36 -0
  7. macrame_db-0.7.0/Cargo.lock +2626 -0
  8. macrame_db-0.7.0/Cargo.toml +171 -0
  9. macrame_db-0.7.0/LICENSE +6 -0
  10. macrame_db-0.7.0/LICENSES/Apache-2.0.txt +105 -0
  11. macrame_db-0.7.0/LICENSES/MIT.txt +21 -0
  12. macrame_db-0.7.0/PKG-INFO +497 -0
  13. macrame_db-0.7.0/README.md +478 -0
  14. macrame_db-0.7.0/benches/budgets.rs +1426 -0
  15. macrame_db-0.7.0/bindings/python/Cargo.toml +84 -0
  16. macrame_db-0.7.0/bindings/python/src/database.rs +1113 -0
  17. macrame_db-0.7.0/bindings/python/src/errors.rs +592 -0
  18. macrame_db-0.7.0/bindings/python/src/graph.rs +530 -0
  19. macrame_db-0.7.0/bindings/python/src/lib.rs +132 -0
  20. macrame_db-0.7.0/bindings/python/src/observe.rs +211 -0
  21. macrame_db-0.7.0/bindings/python/src/rows.rs +132 -0
  22. macrame_db-0.7.0/bindings/python/src/runtime.rs +143 -0
  23. macrame_db-0.7.0/bindings/python/src/temporal.rs +245 -0
  24. macrame_db-0.7.0/bindings/python/src/testing.rs +178 -0
  25. macrame_db-0.7.0/bindings/python/src/timestamps.rs +211 -0
  26. macrame_db-0.7.0/bindings/python/src/types.rs +443 -0
  27. macrame_db-0.7.0/bindings/python/src/vector.rs +237 -0
  28. macrame_db-0.7.0/docs/Macrame Hardening Plan v0.6.0.md +1222 -0
  29. macrame_db-0.7.0/docs/Macrame Implementation Plan v0.5.6.md +758 -0
  30. macrame_db-0.7.0/docs/Macrame Python Bindings Plan v0.7.0.md +1263 -0
  31. macrame_db-0.7.0/docs/architecture/README.md +70 -0
  32. macrame_db-0.7.0/docs/architecture/REJOIN.md +28 -0
  33. macrame_db-0.7.0/docs/architecture/appendices.md +277 -0
  34. macrame_db-0.7.0/docs/architecture/s0-s3-foundations.md +146 -0
  35. macrame_db-0.7.0/docs/architecture/s11-s12-milestones-and-risks.md +40 -0
  36. macrame_db-0.7.0/docs/architecture/s13-decision-register.md +1098 -0
  37. macrame_db-0.7.0/docs/architecture/s14-python-bindings.md +557 -0
  38. macrame_db-0.7.0/docs/architecture/s4-schema.md +424 -0
  39. macrame_db-0.7.0/docs/architecture/s5-modules.md +735 -0
  40. macrame_db-0.7.0/docs/architecture/s6-s10-flows-to-dependencies.md +285 -0
  41. macrame_db-0.7.0/docs/quickref.md +769 -0
  42. macrame_db-0.7.0/examples/archive_window_diag.rs +184 -0
  43. macrame_db-0.7.0/examples/budget_density_diag.rs +133 -0
  44. macrame_db-0.7.0/examples/bulk_atomic_diag.rs +133 -0
  45. macrame_db-0.7.0/examples/chunk_diag.rs +504 -0
  46. macrame_db-0.7.0/examples/fixture_matrix_diag.rs +159 -0
  47. macrame_db-0.7.0/examples/index_coverage_probe.rs +74 -0
  48. macrame_db-0.7.0/examples/pipeline_diag.rs +155 -0
  49. macrame_db-0.7.0/examples/r15_soak.rs +324 -0
  50. macrame_db-0.7.0/examples/readonly_open_probe.rs +187 -0
  51. macrame_db-0.7.0/examples/repair_diag.rs +145 -0
  52. macrame_db-0.7.0/examples/shadow_probe.rs +206 -0
  53. macrame_db-0.7.0/examples/shadow_rebuild_diag.rs +150 -0
  54. macrame_db-0.7.0/examples/traversal_diag.rs +353 -0
  55. macrame_db-0.7.0/examples/weight_check_probe.rs +275 -0
  56. macrame_db-0.7.0/pyproject.toml +73 -0
  57. macrame_db-0.7.0/python/macrame/__init__.py +240 -0
  58. macrame_db-0.7.0/python/macrame/_macrame.pyi +822 -0
  59. macrame_db-0.7.0/python/macrame/py.typed +0 -0
  60. macrame_db-0.7.0/src/connection.rs +2642 -0
  61. macrame_db-0.7.0/src/error.rs +387 -0
  62. macrame_db-0.7.0/src/graph/algorithms.rs +454 -0
  63. macrame_db-0.7.0/src/graph/builder.rs +411 -0
  64. macrame_db-0.7.0/src/graph/edge.rs +136 -0
  65. macrame_db-0.7.0/src/graph/mod.rs +13 -0
  66. macrame_db-0.7.0/src/graph/subgraph.rs +631 -0
  67. macrame_db-0.7.0/src/graph/vector_filter.rs +516 -0
  68. macrame_db-0.7.0/src/integrity/audit.rs +53 -0
  69. macrame_db-0.7.0/src/integrity/mod.rs +38 -0
  70. macrame_db-0.7.0/src/integrity/rebuild.rs +96 -0
  71. macrame_db-0.7.0/src/integrity/shadow.rs +369 -0
  72. macrame_db-0.7.0/src/lib.rs +42 -0
  73. macrame_db-0.7.0/src/metrics.rs +604 -0
  74. macrame_db-0.7.0/src/schema/ddl.rs +657 -0
  75. macrame_db-0.7.0/src/schema/migrations.rs +655 -0
  76. macrame_db-0.7.0/src/schema/mod.rs +5 -0
  77. macrame_db-0.7.0/src/schema/seed.rs +6 -0
  78. macrame_db-0.7.0/src/temporal/archive.rs +296 -0
  79. macrame_db-0.7.0/src/temporal/as_of.rs +241 -0
  80. macrame_db-0.7.0/src/temporal/interval.rs +34 -0
  81. macrame_db-0.7.0/src/temporal/mod.rs +13 -0
  82. macrame_db-0.7.0/src/temporal/replay.rs +720 -0
  83. macrame_db-0.7.0/src/temporal/snapshot.rs +478 -0
  84. macrame_db-0.7.0/src/util/clock.rs +174 -0
  85. macrame_db-0.7.0/src/util/ids.rs +170 -0
  86. macrame_db-0.7.0/src/util/limits.rs +49 -0
  87. macrame_db-0.7.0/src/util/mod.rs +9 -0
  88. macrame_db-0.7.0/src/util/timestamp.rs +308 -0
  89. macrame_db-0.7.0/src/vector/embedding.rs +38 -0
  90. macrame_db-0.7.0/src/vector/hybrid.rs +254 -0
  91. macrame_db-0.7.0/src/vector/mod.rs +11 -0
  92. macrame_db-0.7.0/src/vector/model.rs +129 -0
  93. macrame_db-0.7.0/src/vector/registry.rs +190 -0
  94. macrame_db-0.7.0/src/vector/search.rs +220 -0
  95. macrame_db-0.7.0/tests/actor_metrics_tests.rs +252 -0
  96. macrame_db-0.7.0/tests/archive_window_tests.rs +264 -0
  97. macrame_db-0.7.0/tests/bench_control_tests.rs +108 -0
  98. macrame_db-0.7.0/tests/common/fixtures.rs +502 -0
  99. macrame_db-0.7.0/tests/common/harness.rs +68 -0
  100. macrame_db-0.7.0/tests/compat_contract_tests.rs +290 -0
  101. macrame_db-0.7.0/tests/concurrency_tests.rs +508 -0
  102. macrame_db-0.7.0/tests/diagnostic_conn_tests.rs +244 -0
  103. macrame_db-0.7.0/tests/doc_link_tests.rs +276 -0
  104. macrame_db-0.7.0/tests/doc_sync_tests.rs +181 -0
  105. macrame_db-0.7.0/tests/doctrine_property_tests.proptest-regressions +8 -0
  106. macrame_db-0.7.0/tests/doctrine_property_tests.rs +879 -0
  107. macrame_db-0.7.0/tests/doctrine_static_tests.rs +81 -0
  108. macrame_db-0.7.0/tests/fixture_matrix_tests.rs +357 -0
  109. macrame_db-0.7.0/tests/graph_property_tests.proptest-regressions +12 -0
  110. macrame_db-0.7.0/tests/graph_property_tests.rs +360 -0
  111. macrame_db-0.7.0/tests/graph_tests.rs +763 -0
  112. macrame_db-0.7.0/tests/hybrid_tests.rs +474 -0
  113. macrame_db-0.7.0/tests/index_plan_tests.rs +257 -0
  114. macrame_db-0.7.0/tests/integrity_property_tests.proptest-regressions +7 -0
  115. macrame_db-0.7.0/tests/integrity_property_tests.rs +684 -0
  116. macrame_db-0.7.0/tests/integrity_tests.rs +675 -0
  117. macrame_db-0.7.0/tests/migration_tests.rs +844 -0
  118. macrame_db-0.7.0/tests/packaging_tests.rs +141 -0
  119. macrame_db-0.7.0/tests/replay_snapshot_tests.rs +1293 -0
  120. macrame_db-0.7.0/tests/shadow_rebuild_tests.rs +368 -0
  121. macrame_db-0.7.0/tests/snapshot_chain_tests.rs +267 -0
  122. macrame_db-0.7.0/tests/storage_boundary_tests.rs +417 -0
  123. macrame_db-0.7.0/tests/temporal_tests.rs +698 -0
  124. macrame_db-0.7.0/tests/vector_filter_tests.rs +397 -0
  125. macrame_db-0.7.0/tests/vector_tests.rs +700 -0
  126. macrame_db-0.7.0/tests/wave1_regression_tests.rs +1689 -0
  127. macrame_db-0.7.0/tests/write_path_tests.rs +468 -0
@@ -0,0 +1,119 @@
1
+ # Test-harness settings for R15 (see the risk register in docs/).
2
+ #
3
+ # libSQL faults with STATUS_ACCESS_VIOLATION (0xc0000005) when local databases
4
+ # are opened *concurrently* in one process. It is not a Macrame defect: it
5
+ # reproduces with no Macrame types in the loop, it is not confined to any one
6
+ # test binary -- storage_boundary, vector_filter, replay_snapshot, write_path
7
+ # and the property binaries have all died mid-run -- and upgrading the
8
+ # dependency 0.6.0 -> 0.9.30 did not change it.
9
+ #
10
+ # ---------------------------------------------------------------------------
11
+ # If you are here because the build went red, read this paragraph first.
12
+ # ---------------------------------------------------------------------------
13
+ #
14
+ # Re-run the failing binary on its own. Every binary that has died during a
15
+ # suite run is clean in isolation -- four of them at 0/15 each, 60 isolated runs
16
+ # with no fault. A binary that fails alone is a real failure; one that only
17
+ # fails inside the suite is almost certainly this.
18
+ #
19
+ # And note the reporting hazard, because it is the opposite of what you expect:
20
+ # the fault kills the process, so the tests that already printed `ok` are
21
+ # reported, the ones behind it are silent, and that binary's `test result:` line
22
+ # never appears. **A pass-count sum therefore comes back SMALLER with no
23
+ # failures rather than red.** Anything gating on this suite must key on the
24
+ # absence of a per-target result line, and `--no-fail-fast` is not optional or
25
+ # everything alphabetically behind the fault is skipped as well.
26
+ #
27
+ # ---------------------------------------------------------------------------
28
+ # What is known, and what was believed here and is not true
29
+ # ---------------------------------------------------------------------------
30
+ #
31
+ # The trigger is CONCURRENT OPEN. A standalone reproducer separates the two
32
+ # candidate variables directly (libsql 0.9.30, Windows, release, tokio
33
+ # multi-thread, one file per task):
34
+ #
35
+ # 500 opens, sequential, one process 0/10
36
+ # 4 / 8 / 16 concurrent opens 0/10 each
37
+ # 32 concurrent opens 2/12
38
+ # 128 concurrent opens 5/12
39
+ # 128 concurrent, nothing dropped until end 3/12
40
+ #
41
+ # The threshold sits between 16 and 32 concurrent opens on this machine. The
42
+ # last row is the sharp one: holding every handle alive until the end still
43
+ # faults, so this is concurrent *open*, not an open/drop race and not teardown.
44
+ #
45
+ # REFUTED, and it was written here as the diagnosis until 0.6.0: that the
46
+ # correlate is database open/drop CHURN. The suite data reads that way -- the
47
+ # property binaries open one database per generated case and fault ~3/4 alone,
48
+ # where an ordinary 20-test binary is 0/15 -- but 500 sequential opens in one
49
+ # process is 0/10. Churn is not sufficient; concurrency is. The suite table
50
+ # cannot distinguish the two, and no inference of that kind should be drawn
51
+ # from it.
52
+ #
53
+ # ---------------------------------------------------------------------------
54
+ # What this setting does, and what it does not
55
+ # ---------------------------------------------------------------------------
56
+ #
57
+ # Serialising libtest REDUCES THE RATE. It does not remove the fault, and an
58
+ # earlier version of this file said that it did:
59
+ #
60
+ # 0.5.4, full suite, libtest default (parallel) 1/20 bad runs
61
+ # 0.5.4, full suite, --test-threads=1 0/30 bad runs
62
+ # 0.5.6, full suite, --test-threads=1 5/10 bad runs
63
+ #
64
+ # Same dependency version throughout, and the last row was measured WITH this
65
+ # setting in force. Either the rate rose with the suite (171 -> 221 tests) or
66
+ # the original 0/30 was a lucky streak. Read 0/30 as a data point, not as a
67
+ # guarantee this file provides.
68
+ #
69
+ # One thing this does not explain, flagged rather than guessed at: every binary
70
+ # is 0/15 alone while the suite that runs those same binaries serially is 5/10,
71
+ # all under RUST_TEST_THREADS = "1". If concurrent opens are required, the
72
+ # suite-level runs are finding concurrency that per-binary runs do not, and this
73
+ # data does not say where. `Database::open` taking three connections (four with
74
+ # the cadence) is the obvious suspect and is not evidence.
75
+ #
76
+ # `[env]` does not override a value already in the environment, so setting
77
+ # RUST_TEST_THREADS yourself still works -- which is how you reproduce the fault
78
+ # if you are chasing it, or check whether an upstream release has fixed it.
79
+ #
80
+ # The three generated-history targets (doctrine, integrity, graph) are behind
81
+ # the `property-tests` feature and run as their own step, because a property
82
+ # case needs a database of its own and serialising does not save them:
83
+ # doctrine_property_tests faulted 3/4 and integrity_property_tests 1/4 in the
84
+ # same session, against ~3/25 recorded at 0.5.4. Quarantined, not silenced --
85
+ # proptest replays every previously found failure from .proptest-regressions
86
+ # before a single new case is generated.
87
+ #
88
+ # Re-measured at 0.7.0 on a larger sample: doctrine_property_tests alone,
89
+ # **9 crashes in 15 runs**, on a machine that had been building wheels all
90
+ # session. That sits between the two figures above rather than contradicting
91
+ # either, and it is the clearest statement of why these are quarantined: at 60%
92
+ # no retry budget makes them a gate. The Rust source was unchanged across the
93
+ # whole of that measurement -- P4-P6 touched only the binding crate, tests_py
94
+ # and docs -- so the variable is load, not code.
95
+ #
96
+ # It also produced a textbook demonstration of the reporting hazard. With
97
+ # --no-fail-fast the run came back **308 passed, 0 failed** and exit 101: eight
98
+ # tests silently absent, and nothing in the pass/fail line saying so. See
99
+ # `tests_py/run_suite.py` for the Python-side gate built around exactly this,
100
+ # and D-107 for why the two sides need different checks.
101
+ #
102
+ # ---------------------------------------------------------------------------
103
+ # Production exposure
104
+ # ---------------------------------------------------------------------------
105
+ #
106
+ # Soaked and defended in 0.6.0 (T5.2, D-092), in the sharpened form -- ONE
107
+ # PROCESS, ONE FILE, A BOUNDED SET OF CONNECTIONS OPENED ONCE AND NEVER CHURNED.
108
+ # That wording matters: the old claim was about one `Database` handle, and a
109
+ # handle is not what the fault counts.
110
+ #
111
+ # cargo run --release --example r15_soak -- --arm claim --secs 60 --runs 6
112
+ # cargo run --release --example r15_soak -- --arm control --secs 15 --runs 10
113
+ #
114
+ # claim is 0/6 at 60s and 0/10 at 15s; control, which adds 48 concurrent opens
115
+ # to the same load, is 2/10. The control is what makes the claim arm mean
116
+ # anything -- a clean claim run on its own is indistinguishable from a harness
117
+ # that provoked nothing. Run both or neither.
118
+ [env]
119
+ RUST_TEST_THREADS = "1"
@@ -0,0 +1,137 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ workflow_call: # so release.yml can gate on exactly this
8
+
9
+ env:
10
+ CARGO_TERM_COLOR: always
11
+
12
+ jobs:
13
+ lint:
14
+ name: clippy
15
+ runs-on: ubuntu-latest
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+ - uses: dtolnay/rust-toolchain@stable
19
+ with:
20
+ components: rustfmt, clippy
21
+ - uses: Swatinem/rust-cache@v2
22
+
23
+ # `--all-targets` covers benches and examples, which is where several of
24
+ # this project's diagnostics live and where clippy has caught real defects
25
+ # (D-075's oversized DbError came out of a clippy warning). Verified clean
26
+ # under `-D warnings` at 0.6.0, so this is a gate that currently passes.
27
+ #
28
+ # `-D warnings` is scoped to this step rather than set for the workflow:
29
+ # as a global RUSTFLAGS it also applies to the `cargo publish --dry-run`
30
+ # verification build, where a warning from a dependency's build would fail
31
+ # a packaging check that has nothing to do with warnings.
32
+ - name: clippy
33
+ run: cargo clippy --all-targets --features metrics
34
+ env:
35
+ RUSTFLAGS: -D warnings
36
+
37
+ # **Report-only, deliberately.** This repo has never been rustfmt-clean —
38
+ # 246 diffs at 0.6.0, essentially all in tests and benches — so a blocking
39
+ # gate would go red on its first run for a pre-existing condition, and the
40
+ # only way to green it is a repo-wide reformat nobody reviewed. Adopting
41
+ # rustfmt is a decision worth taking on purpose: run `cargo fmt --all` in
42
+ # its own commit, then delete `continue-on-error` here.
43
+ - name: rustfmt (advisory)
44
+ run: cargo fmt --all -- --check
45
+ continue-on-error: true
46
+
47
+ # The README carries an MSRV badge and `Cargo.toml` a `rust-version`, and
48
+ # both are claims. Nothing else in the build would notice them going stale:
49
+ # the crate is developed on stable, so a newly used std method raises the real
50
+ # floor silently and the declared one stays where it was.
51
+ #
52
+ # `--all-targets` deliberately: tests and benches use `OnceLock` and other
53
+ # items the library does not, and a contributor hitting a build failure the
54
+ # badge said would not happen is the same defect as a wrong badge.
55
+ msrv:
56
+ name: MSRV (rust-version in Cargo.toml)
57
+ runs-on: ubuntu-latest
58
+ steps:
59
+ - uses: actions/checkout@v4
60
+ - id: msrv
61
+ run: echo "v=$(grep -m1 '^rust-version' Cargo.toml | sed 's/.*= *//; s/\"//g')" >> "$GITHUB_OUTPUT"
62
+ - uses: dtolnay/rust-toolchain@master
63
+ with:
64
+ toolchain: ${{ steps.msrv.outputs.v }}
65
+ - uses: Swatinem/rust-cache@v2
66
+ - run: cargo check --all-features --all-targets
67
+
68
+ test:
69
+ name: test (${{ matrix.os }})
70
+ runs-on: ${{ matrix.os }}
71
+ strategy:
72
+ fail-fast: false
73
+ matrix:
74
+ os: [ubuntu-latest, windows-latest]
75
+ steps:
76
+ - uses: actions/checkout@v4
77
+ - uses: dtolnay/rust-toolchain@stable
78
+ - uses: Swatinem/rust-cache@v2
79
+
80
+ # `.cargo/config.toml` sets RUST_TEST_THREADS = "1" for R15 and applies
81
+ # here automatically. Read that file before changing anything below.
82
+ #
83
+ # R15 is an intermittent libSQL access violation (0xC0000005) triggered by
84
+ # concurrent database opens. It kills the process, so cargo reports
85
+ # "error: N targets failed" and the run goes red for a reason that has
86
+ # nothing to do with the change under test — measured at ~5/10 full-suite
87
+ # runs on Windows. A gate that fails half the time trains people to ignore
88
+ # red, which is this project's own stated position, so the run is retried
89
+ # rather than tolerated or ignored.
90
+ #
91
+ # Three attempts, not unlimited: R15 has always passed on re-run, so a
92
+ # genuine failure still goes red after three, and the attempt count is
93
+ # visible in the log rather than hidden by a `continue-on-error`.
94
+ - name: test (with R15 retry)
95
+ shell: bash
96
+ run: |
97
+ for attempt in 1 2 3; do
98
+ echo "::group::cargo test, attempt $attempt"
99
+ if cargo test --features metrics --no-fail-fast; then
100
+ echo "::endgroup::"
101
+ echo "passed on attempt $attempt"
102
+ exit 0
103
+ fi
104
+ echo "::endgroup::"
105
+ echo "::warning::attempt $attempt failed; see R15 in .cargo/config.toml"
106
+ done
107
+ echo "::error::three consecutive test runs failed — this is not R15"
108
+ exit 1
109
+
110
+ # The generated-history binaries are quarantined behind a feature (see
111
+ # .cargo/config.toml): a property case opens a database of its own, which
112
+ # is exactly R15's trigger, so they fault far more often than the rest.
113
+ # Run as their own step so their noise cannot be mistaken for the suite's.
114
+ - name: property tests (quarantined, R15-prone)
115
+ shell: bash
116
+ run: |
117
+ for attempt in 1 2 3; do
118
+ if cargo test --features property-tests --no-fail-fast; then
119
+ exit 0
120
+ fi
121
+ echo "::warning::property attempt $attempt failed"
122
+ done
123
+ echo "::error::property tests failed three times"
124
+ exit 1
125
+
126
+ # A `cargo publish` failure after a tag is pushed is expensive: the tag is
127
+ # already public and the version number is spent. This runs the same
128
+ # packaging and verification step on every PR, so the release job's only new
129
+ # variable is the upload itself.
130
+ package:
131
+ name: cargo publish --dry-run
132
+ runs-on: ubuntu-latest
133
+ steps:
134
+ - uses: actions/checkout@v4
135
+ - uses: dtolnay/rust-toolchain@stable
136
+ - uses: Swatinem/rust-cache@v2
137
+ - run: cargo publish --dry-run
@@ -0,0 +1,187 @@
1
+ name: Python
2
+
3
+ # The Python side of the project: the binding crate compiles, the extension
4
+ # builds and installs the way a user installs it, and the 337-test suite passes
5
+ # on every platform this repository claims to support.
6
+ #
7
+ # ## Why this does not call `ci.yml` first
8
+ #
9
+ # The plan (§10) sketched this file as `rust: uses ./.github/workflows/ci.yml`
10
+ # with everything else behind `needs: rust`. That shape belonged to a file that
11
+ # also built the wheels — "the crate must be green before the wheel is built".
12
+ # P5 moved the wheel matrix into `wheels.yml`, tag-triggered, and what is left
13
+ # here is a test job whose failure mode is independent of the Rust suite's.
14
+ #
15
+ # `workflow_call` does not deduplicate: gating here would re-run clippy, MSRV,
16
+ # the two-OS Rust matrix (each with its own three R15 retries) and the publish
17
+ # dry-run on every pull request that already ran them, and would hold the Python
18
+ # answer behind ~15 minutes of work it does not depend on. Two independent
19
+ # signals, reported separately, is strictly more information than one signal
20
+ # reported late.
21
+ #
22
+ # Where the two *must* be green together is before an upload, and that is what
23
+ # `workflow_call` above is for: `wheels.yml`'s publish job gates on this file,
24
+ # exactly as `release.yml` gates on `ci.yml`. See D-108.
25
+ #
26
+ # ## The gap this closes
27
+ #
28
+ # `bindings/python` is a workspace *member* but never a *default* member — the
29
+ # root package is itself a member, so Cargo scopes bare commands to it alone,
30
+ # which is the property `tests/packaging_tests.rs` pins and the reason `cargo
31
+ # publish` stayed a one-package operation (D-098). The cost of that choice, not
32
+ # noticed until now: **nothing in CI ever compiled the binding crate.** Until
33
+ # this file existed, a pull request could break `bindings/python/src/*.rs` and
34
+ # every check would go green, because the only thing that built it was
35
+ # `wheels.yml`, which runs on tags.
36
+
37
+ on:
38
+ push:
39
+ branches: [main]
40
+ pull_request:
41
+ workflow_call: # so wheels.yml can gate an upload on exactly this
42
+
43
+ env:
44
+ CARGO_TERM_COLOR: always
45
+
46
+ jobs:
47
+ # `-p macrame-py` explicitly. Every other clippy invocation in this repository
48
+ # is scoped to the root package by Cargo's own default, so this one has to
49
+ # name the crate or it lints the same code `ci.yml` already linted.
50
+ #
51
+ # Deliberately **without** `--features extension-module`: that feature is
52
+ # turned on by `pyproject.toml` for maturin's build only, and the binding
53
+ # manifest states the crate must build without it. A check that silently
54
+ # required it would make that statement untestable.
55
+ lint:
56
+ name: clippy (binding crate)
57
+ runs-on: ubuntu-latest
58
+ steps:
59
+ - uses: actions/checkout@v4
60
+ - uses: dtolnay/rust-toolchain@stable
61
+ with:
62
+ components: clippy
63
+ - uses: Swatinem/rust-cache@v2
64
+ - name: clippy
65
+ run: cargo clippy -p macrame-py --all-targets
66
+ env:
67
+ RUSTFLAGS: -D warnings
68
+
69
+ # `tests_py/test_stubs.py` compares the stub's *names* against the
70
+ # extension in both directions. It cannot see a wrong **type** — a stub
71
+ # that says `-> int` where the runtime answers a `datetime` passes every
72
+ # one of those checks. A type checker is what reads the annotations, so
73
+ # one runs here, over the stub and the package that re-exports it.
74
+ #
75
+ # No Rust toolchain needed and no extension built: `--strict` on a `.pyi`
76
+ # is a pure text analysis. Measured clean at 0.7.0, so this is a gate that
77
+ # passes rather than a backlog.
78
+ - uses: actions/setup-python@v5
79
+ with:
80
+ python-version: "3.13"
81
+ - name: mypy (the stub, and the package that re-exports it)
82
+ run: |
83
+ python -m pip install --upgrade pip mypy
84
+ python -m mypy --strict python/macrame
85
+
86
+ test:
87
+ name: suite (${{ matrix.os }}, py${{ matrix.python }})
88
+ runs-on: ${{ matrix.os }}
89
+ strategy:
90
+ # One platform failing must not hide the others: R15 is platform-sensitive
91
+ # and the whole point of a three-OS matrix is knowing which ones it hit.
92
+ fail-fast: false
93
+ matrix:
94
+ include:
95
+ - os: ubuntu-latest
96
+ python: "3.13"
97
+ # The floor `pyproject.toml` declares. `requires-python = ">=3.10"` is
98
+ # a claim pip enforces against users and nothing enforces against us.
99
+ - os: ubuntu-latest
100
+ python: "3.10"
101
+ - os: windows-latest
102
+ python: "3.13"
103
+ # macOS has never run this suite. `wheels.yml` builds a universal2
104
+ # wheel and smoke-tests it with six lines; this is the first time the
105
+ # ledger's Python surface is exercised on Apple silicon at all.
106
+ - os: macos-latest
107
+ python: "3.13"
108
+
109
+ steps:
110
+ - uses: actions/checkout@v4
111
+ - uses: actions/setup-python@v5
112
+ with:
113
+ python-version: ${{ matrix.python }}
114
+ - uses: dtolnay/rust-toolchain@stable
115
+ - uses: Swatinem/rust-cache@v2
116
+
117
+ # `pip install .` rather than `maturin develop`: it goes through the PEP
118
+ # 517 backend, which means it reads `[tool.maturin]` and therefore builds
119
+ # with `--features extension-module` and in release, i.e. it is the same
120
+ # path a user takes. `maturin develop` would need a virtualenv here and
121
+ # would test a build configuration nobody installs.
122
+ - name: install the extension the way a user installs it
123
+ run: |
124
+ python -m pip install --upgrade pip
125
+ python -m pip install .
126
+ python -m pip install pytest
127
+
128
+ # **Not bare pytest** (D-107). R15 is an intermittent libSQL access
129
+ # violation on concurrent open, reproduced through this binding at 2 in 12
130
+ # runs, and it kills the interpreter rather than raising. `run_suite.py`
131
+ # distinguishes the four ways this suite can end — CRASH, FAILED,
132
+ # INCOMPLETE, TEARDOWN — retries only the first, three times, and reports
133
+ # the rest red on the first attempt. A green summary is not sufficient
134
+ # here and neither is a zero exit; it checks them against each other.
135
+ - name: python suite (through the R15 gate)
136
+ run: python tests_py/run_suite.py
137
+
138
+ # abi3 is what turns a 4-platform × 5-version matrix into 4 builds, and the
139
+ # claim underneath it — "one wheel serves CPython 3.10 through whatever ships
140
+ # next" — is not tested by any job above, because each of those builds and
141
+ # runs on a single interpreter.
142
+ #
143
+ # So: build once on the floor, install that same artifact into an interpreter
144
+ # it has never seen, and run the whole suite through it. If pyo3's abi3
145
+ # feature were ever dropped, every other job here would still pass and the
146
+ # wheel matrix would quietly become wrong by a factor of five.
147
+ abi3:
148
+ name: one wheel, two interpreters
149
+ runs-on: ubuntu-latest
150
+ steps:
151
+ - uses: actions/checkout@v4
152
+ - uses: actions/setup-python@v5
153
+ id: floor
154
+ with:
155
+ python-version: "3.10"
156
+ - uses: actions/setup-python@v5
157
+ id: newest
158
+ with:
159
+ python-version: "3.13"
160
+ - uses: dtolnay/rust-toolchain@stable
161
+ - uses: Swatinem/rust-cache@v2
162
+
163
+ # Debug, deliberately: this job asks what the wheel is *tagged* and
164
+ # whether it imports, not how fast it runs. `wheels.yml` builds the
165
+ # release artifact.
166
+ - name: build one wheel, on 3.10
167
+ shell: bash
168
+ run: |
169
+ "${{ steps.floor.outputs.python-path }}" -m pip install --upgrade pip maturin
170
+ "${{ steps.floor.outputs.python-path }}" -m maturin build --out dist
171
+
172
+ - name: the tag names the ABI, not the interpreter that built it
173
+ shell: bash
174
+ run: |
175
+ ls -la dist
176
+ if ! ls dist/*-abi3-*.whl >/dev/null 2>&1; then
177
+ echo "::error::no abi3 wheel — pyo3's abi3-py310 feature is off, and"
178
+ echo "::error::the wheel matrix in wheels.yml is now wrong per Python version"
179
+ exit 1
180
+ fi
181
+
182
+ - name: run the whole suite on 3.13, against the wheel built by 3.10
183
+ shell: bash
184
+ run: |
185
+ "${{ steps.newest.outputs.python-path }}" -m pip install --upgrade pip pytest
186
+ "${{ steps.newest.outputs.python-path }}" -m pip install --no-index --find-links dist macrame-db
187
+ "${{ steps.newest.outputs.python-path }}" tests_py/run_suite.py
@@ -0,0 +1,87 @@
1
+ name: Release
2
+
3
+ # Two ways in, and only one of them can publish.
4
+ #
5
+ # * push a tag `v0.6.0` -> verify, then upload to crates.io
6
+ # * run it by hand -> verify and dry-run only, unless `publish` is
7
+ # explicitly set to `true`
8
+ #
9
+ # The manual dry run exists so the whole path can be exercised without spending
10
+ # a version number. A crates.io version cannot be re-uploaded once taken, even
11
+ # if it is yanked, so a failed publish costs the number permanently.
12
+ on:
13
+ push:
14
+ tags: ["v*"]
15
+ workflow_dispatch:
16
+ inputs:
17
+ publish:
18
+ description: 'Actually upload to crates.io (otherwise dry-run only)'
19
+ type: boolean
20
+ default: false
21
+
22
+ env:
23
+ CARGO_TERM_COLOR: always
24
+
25
+ permissions:
26
+ contents: read
27
+
28
+ jobs:
29
+ # The full CI matrix, reused rather than reimplemented — a release gate that
30
+ # is a second copy of the test job is a gate that drifts from it.
31
+ verify:
32
+ uses: ./.github/workflows/ci.yml
33
+
34
+ # Cheap, and it runs *before* anything is uploaded: a tag that disagrees with
35
+ # the manifest publishes a version nobody can find from the tag, and the only
36
+ # fix is another version number.
37
+ check-version:
38
+ name: tag matches Cargo.toml
39
+ runs-on: ubuntu-latest
40
+ if: startsWith(github.ref, 'refs/tags/v')
41
+ steps:
42
+ - uses: actions/checkout@v4
43
+ - uses: dtolnay/rust-toolchain@stable
44
+ - name: compare
45
+ shell: bash
46
+ run: |
47
+ manifest=$(cargo metadata --no-deps --format-version 1 | jq -r '.packages[0].version')
48
+ tag="${GITHUB_REF_NAME#v}"
49
+ echo "manifest=$manifest tag=$tag"
50
+ if [ "$manifest" != "$tag" ]; then
51
+ echo "::error::tag $GITHUB_REF_NAME does not match Cargo.toml version $manifest"
52
+ exit 1
53
+ fi
54
+
55
+ publish:
56
+ name: publish to crates.io
57
+ needs: [verify, check-version]
58
+ runs-on: ubuntu-latest
59
+ # Tag pushes publish; a manual run publishes only when asked. `always()` is
60
+ # deliberately absent — if `check-version` is skipped on a manual run, this
61
+ # still requires `verify` to have succeeded.
62
+ if: |
63
+ startsWith(github.ref, 'refs/tags/v') ||
64
+ inputs.publish == true
65
+ environment: crates-io # add a required reviewer here to gate uploads
66
+ steps:
67
+ - uses: actions/checkout@v4
68
+ - uses: dtolnay/rust-toolchain@stable
69
+ - uses: Swatinem/rust-cache@v2
70
+
71
+ - name: publish
72
+ env:
73
+ CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
74
+ run: cargo publish
75
+
76
+ # Runs on manual invocations that did not ask to publish: the same packaging
77
+ # and verification `cargo publish` performs, stopping before the upload.
78
+ dry-run:
79
+ name: publish --dry-run
80
+ needs: verify
81
+ runs-on: ubuntu-latest
82
+ if: github.event_name == 'workflow_dispatch' && inputs.publish != true
83
+ steps:
84
+ - uses: actions/checkout@v4
85
+ - uses: dtolnay/rust-toolchain@stable
86
+ - uses: Swatinem/rust-cache@v2
87
+ - run: cargo publish --dry-run