snapshot_machines 0.1.5-arm64-darwin

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: e5e8e2da13567b7ed203a812fb83148cb0cba953bea6897ed0222747d7ef4e8c
4
+ data.tar.gz: 85300d20edaf3d2660233d5dc6e3c9feb1cf287c0729f5330e317faf46eee815
5
+ SHA512:
6
+ metadata.gz: 69d3c5cce99a493ffea4013c63108c13f32699e52172569b504f6cb1e82c216956d4810a4977bf2c6e9c1cf5d41809db05c73e7240e5873d5a0b9718044669d1
7
+ data.tar.gz: f5580d4e7e6d5d179e29809f4119eeee754a68480b5be9a133b73f7daa020f3506bd62b18a81640c18eb945c3549f249a4b84a3ecbb5863a78b45c0f15ba7384
data/CHANGELOG.md ADDED
@@ -0,0 +1,36 @@
1
+ # Changelog
2
+
3
+ ## [0.1.5](https://github.com/seuros/snapshot_machines/compare/snapshot_machines/v0.1.4...snapshot_machines/v0.1.5) (2026-11-06)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * extract catalog ownership check for commit/abort ([f501351](https://github.com/seuros/snapshot_machines/commit/f5013515b918dfb5627939ae2d06b191298b5a85))
9
+
10
+ ## [0.1.4](https://github.com/seuros/snapshot_machines/compare/snapshot_machines/v0.1.3...snapshot_machines/v0.1.4) (2026-11-06)
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * crates.io keywords and categories for the core ([f544853](https://github.com/seuros/snapshot_machines/commit/f544853222f20d5e56b58199641258a676455f61))
16
+
17
+ ## [0.1.3](https://github.com/seuros/snapshot_machines/compare/snapshot_machines/v0.1.2...snapshot_machines/v0.1.3) (2026-11-06)
18
+
19
+
20
+ ### Bug Fixes
21
+
22
+ * keep test sources out of the packaged gem and crate ([002251a](https://github.com/seuros/snapshot_machines/commit/002251a711dd02f261ed4be6f255238fae2ef3ed))
23
+
24
+ ## [0.1.2](https://github.com/seuros/snapshot_machines/compare/snapshot_machines/v0.1.1...snapshot_machines/v0.1.2) (2026-11-06)
25
+
26
+
27
+ ### Bug Fixes
28
+
29
+ * cross builds no longer compile for the host ([e771bd5](https://github.com/seuros/snapshot_machines/commit/e771bd5f973692eef8035958143e29dc442237e7))
30
+
31
+ ## [0.1.1](https://github.com/seuros/snapshot_machines/compare/snapshot_machines-v0.1.0...snapshot_machines/v0.1.1) (2026-11-06)
32
+
33
+
34
+ ### Features
35
+
36
+ * initial extraction ([691a57e](https://github.com/seuros/snapshot_machines/commit/691a57e297015cf920bd3afc3ac9309c959c3af6))
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Abdelkader Boudih
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
9
+ of the Software, and to permit persons to whom the Software is furnished to do
10
+ so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,218 @@
1
+ # SnapshotMachines
2
+
3
+ > Every incremental backup is a promise that the full one still exists. 🗃️
4
+
5
+ A manifest catalog for Ruby. You tell it when a full snapshot starts and when it lands, and when each incremental one is taken on top. It remembers the chain, refuses to let you build on anything but the latest, keeps the old chain intact until a new full one has actually committed, and answers the only question that matters at 3 AM: to restore *this*, which manifests do I need, and in what order?
6
+
7
+ ## Quick Start
8
+
9
+ ```bash
10
+ gem 'snapshot_machines'
11
+ ```
12
+
13
+ ```ruby
14
+ catalog = SnapshotMachines::Catalog.new
15
+
16
+ full = catalog.begin(nil) # nil means "start a full snapshot"
17
+ catalog.commit(full, checksum_of(sunday_dump)) # => [1, nil, checksum]
18
+
19
+ monday = catalog.begin(1) # build on manifest 1
20
+ catalog.commit(monday, checksum_of(monday_delta)) # => [2, 1, checksum]
21
+
22
+ catalog.restore_plan(2)
23
+ # => [[1, nil, sunday_checksum], [2, 1, monday_checksum]]
24
+ ```
25
+
26
+ Each manifest is a three-element array: its id, the id it builds on, and the checksum you gave it. The restore plan is the list of manifests to replay, oldest first.
27
+
28
+ ## A Message to the Resistance
29
+
30
+ So you set up backups. Full dump every Sunday, incrementals Monday through Saturday, uploaded to a bucket with a lifecycle rule. Thirty days retention. Very responsible.
31
+
32
+ Then a Wednesday restore fails. The incrementals from Monday and Tuesday are right there. Wednesday's is there. Sunday's full dump expired at midnight because it was thirty-one days old, and nobody told the lifecycle rule that three other files depend on it.
33
+
34
+ The bytes were never the problem. The problem is that nothing in your system knew that Wednesday *needs* Sunday. The dependency lived in a naming convention and a vague memory of how the cron job works.
35
+
36
+ This gem holds that dependency. It won't take the bytes, won't upload anything, won't verify a checksum. It keeps the chain of who-builds-on-whom, and it makes a full replacement wait until the replacement has actually landed, so the old chain is never orphaned by an optimistic cleanup.
37
+
38
+ ## Chapter 1: Stardate 2025.186, the AMS Chandy
39
+
40
+ > **AMS Chandy**, NCC-4218, *Chandy*-class archive tender.
41
+ > Keep the full captain's log, then know exactly which later pages depend on it.
42
+
43
+ The tender doesn't carry the logs. It carries the index: this page is a complete log, these pages are amendments, each amendment names the page it amends. When a new complete log is being written, the old one stays on the shelf until the new one is bound. When the new one is bound, the old chain and all its amendments are struck from the index together.
44
+
45
+ An ensign with a disk-space alert suggests:
46
+
47
+ ```ruby
48
+ def prune_backups
49
+ Dir["backups/*.dump"].each do |file|
50
+ File.delete(file) if File.mtime(file) < 30.days.ago
51
+ end
52
+ end
53
+ ```
54
+
55
+ "That," says the archivist, "is how Wednesday becomes unrestorable."
56
+
57
+ ## The Flight Plan
58
+
59
+ ```mermaid
60
+ stateDiagram-v2
61
+ [*] --> empty
62
+ empty --> pending_full: begin(nil)
63
+ pending_full --> chained: commit, manifest 1 is the new base
64
+ pending_full --> empty: abort, nothing recorded
65
+ chained --> pending_incremental: begin(latest id)
66
+ pending_incremental --> chained: commit, chain grows by one
67
+ pending_incremental --> chained: abort, chain unchanged
68
+ chained --> pending_replacement: begin(nil)
69
+ pending_replacement --> chained: commit, old chain discarded
70
+ pending_replacement --> chained: abort, old chain kept
71
+ ```
72
+
73
+ And the week the full dump was replaced:
74
+
75
+ ```mermaid
76
+ sequenceDiagram
77
+ participant Cron
78
+ participant Catalog
79
+ participant Bucket
80
+
81
+ Cron->>Catalog: begin(nil)
82
+ Catalog-->>Cron: pending 1
83
+ Cron->>Bucket: upload sunday.dump
84
+ Cron->>Catalog: commit(pending 1, checksum)
85
+ Catalog-->>Cron: [1, nil, checksum]
86
+
87
+ Cron->>Catalog: begin(1)
88
+ Catalog-->>Cron: pending 2
89
+ Cron->>Bucket: upload monday.delta
90
+ Cron->>Catalog: commit(pending 2, checksum)
91
+ Catalog-->>Cron: [2, 1, checksum]
92
+
93
+ Note over Cron: next Sunday
94
+ Cron->>Catalog: begin(nil)
95
+ Catalog-->>Cron: pending 3
96
+ Cron->>Bucket: upload sunday2.dump
97
+ Note over Bucket: upload fails halfway
98
+ Cron->>Catalog: abort(pending 3)
99
+ Catalog-->>Cron: true
100
+ Cron->>Catalog: restore_plan(2)
101
+ Catalog-->>Cron: [[1, nil, ..], [2, 1, ..]]
102
+ Note over Catalog: old chain untouched. Monday is still restorable.
103
+ ```
104
+
105
+ ## How the Catalog Works
106
+
107
+ ### A full snapshot
108
+
109
+ Pass `nil` to `begin` and you get a `Pending` handle. Nothing is recorded yet. Record it with `commit` and the checksum you computed.
110
+
111
+ ```ruby
112
+ catalog = SnapshotMachines::Catalog.new
113
+ catalog.latest # => nil, nothing yet
114
+
115
+ pending = catalog.begin(nil)
116
+ pending.id # => 1
117
+ catalog.commit(pending, 42) # => [1, nil, 42]
118
+ catalog.latest # => [1, nil, 42]
119
+ ```
120
+
121
+ A full manifest has `nil` as its base. It depends on nothing.
122
+
123
+ ### An incremental snapshot
124
+
125
+ Pass the id of the latest manifest and the new one builds on it.
126
+
127
+ ```ruby
128
+ monday = catalog.begin(1)
129
+ catalog.commit(monday, 43) # => [2, 1, 43]
130
+
131
+ tuesday = catalog.begin(2)
132
+ catalog.commit(tuesday, 44) # => [3, 2, 44]
133
+ ```
134
+
135
+ Only the latest manifest can be built on. Ask to build on 1 now and the answer is `nil`. The chain is a line, not a tree.
136
+
137
+ ```ruby
138
+ catalog.begin(1) # => nil, manifest 1 is no longer the tip
139
+ catalog.begin(999) # => nil, never heard of it
140
+ ```
141
+
142
+ ### Restore
143
+
144
+ `restore_plan` walks back from the manifest you name to the full one it rests on, and hands you the list oldest first.
145
+
146
+ ```ruby
147
+ catalog.restore_plan(3)
148
+ # => [[1, nil, 42], [2, 1, 43], [3, 2, 44]]
149
+
150
+ catalog.restore_plan(2)
151
+ # => [[1, nil, 42], [2, 1, 43]]
152
+ ```
153
+
154
+ Replay them in that order and you're at Tuesday.
155
+
156
+ ### Replacing the full snapshot
157
+
158
+ A new full snapshot starts the same way, with `begin(nil)`. Until it commits, the old chain is exactly where it was.
159
+
160
+ ```ruby
161
+ replacement = catalog.begin(nil)
162
+ catalog.restore_plan(3) # => still [[1, nil, 42], [2, 1, 43], [3, 2, 44]]
163
+
164
+ catalog.commit(replacement, 50) # => [4, nil, 50]
165
+ catalog.restore_plan(3) # => nil, that chain is gone
166
+ catalog.restore_plan(4) # => [[4, nil, 50]]
167
+ ```
168
+
169
+ The moment the new full lands, every manifest of the old chain stops being restorable through this catalog. That's your signal that the old files can go. Not before.
170
+
171
+ ### Aborting
172
+
173
+ Upload failed, disk filled up, someone hit Ctrl-C. Tell the catalog.
174
+
175
+ ```ruby
176
+ attempt = catalog.begin(nil)
177
+ catalog.abort(attempt) # => true
178
+ catalog.abort(attempt) # => false, already dealt with
179
+ catalog.latest # => [4, nil, 50], unchanged
180
+ ```
181
+
182
+ An aborted full attempt leaves the old chain alone. An aborted incremental leaves the chain one link shorter than it would have been. Nothing is ever half-recorded.
183
+
184
+ ### One at a time
185
+
186
+ While a snapshot is pending, no other can begin. Commit it or abort it first.
187
+
188
+ ```ruby
189
+ busy = catalog.begin(4)
190
+ catalog.begin(nil) # => nil
191
+ catalog.begin(4) # => nil
192
+ catalog.abort(busy) # => true
193
+ ```
194
+
195
+ ## The Rules of the Tender
196
+
197
+ - **Checksums and ids are Integers** in `0..2**64-1`. The catalog stores the checksum; it never computes or verifies one. Floats and strings raise `TypeError`, negatives raise `RangeError`.
198
+ - **A chain holds 32 links.** At capacity, `begin(latest)` returns `nil`. `begin(nil)` still works, so a new full snapshot is always possible.
199
+ - **Handles belong to one catalog.** Hand a `Pending` from one catalog to another and you get `ArgumentError`. Commit the same handle twice and the second returns `nil`.
200
+ - **Rejections return nil or false.** Only malformed input raises.
201
+ - **Frozen means frozen.** `freeze` the catalog and `begin`, `commit`, and `abort` all raise `FrozenError`. Reads still work.
202
+
203
+ ## What You Think You're Doing vs Reality
204
+
205
+ ### You Think: "The retention policy keeps thirty days of backups."
206
+ ### Reality: It keeps thirty days of files. Some of those files are useless alone.
207
+
208
+ An incremental backup is not a backup. It's a diff. Its value is exactly zero without the full snapshot it was taken against, and a retention rule counting days has no idea which file that is. The catalog knows. Ask it before you delete anything.
209
+
210
+ ## What's Yours
211
+
212
+ The catalog is the index, not the archive. Take the snapshot, compute the checksum, store the bytes somewhere durable, and only then `commit`. Persist the catalog's state alongside the bytes, because a catalog that lives only in memory forgets the chain with the process. When `restore_plan` says a manifest is gone, that's when the corresponding files are safe to remove.
213
+
214
+ ## License
215
+
216
+ MIT License. See [LICENSE.txt](LICENSE.txt) for details.
217
+
218
+ **Remember: Sunday's dump was deleted on schedule. Wednesday's restore was not.**
@@ -0,0 +1,22 @@
1
+ # Changelog
2
+
3
+ ## [0.2.2](https://github.com/seuros/snapshot_machines/compare/snapshot-machines-v0.2.1...snapshot-machines-v0.2.2) (2026-11-06)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * crates.io keywords and categories for the core ([f544853](https://github.com/seuros/snapshot_machines/commit/f544853222f20d5e56b58199641258a676455f61))
9
+
10
+ ## [0.2.1](https://github.com/seuros/snapshot_machines/compare/snapshot-machines-v0.2.0...snapshot-machines-v0.2.1) (2026-11-06)
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * keep test sources out of the packaged gem and crate ([002251a](https://github.com/seuros/snapshot_machines/commit/002251a711dd02f261ed4be6f255238fae2ef3ed))
16
+
17
+ ## [0.2.0](https://github.com/seuros/snapshot_machines/compare/snapshot-machines-v0.1.0...snapshot-machines-v0.2.0) (2026-11-06)
18
+
19
+
20
+ ### Features
21
+
22
+ * initial extraction ([691a57e](https://github.com/seuros/snapshot_machines/commit/691a57e297015cf920bd3afc3ac9309c959c3af6))
@@ -0,0 +1,21 @@
1
+ # snapshot-machines
2
+
3
+ > **AMS Chandy**, NCC-4218, *Chandy*-class archive tender.
4
+
5
+ Engine-room flight manual: chart checkpoint manifest ancestry, not snapshot bytes.
6
+
7
+ Allocation-free `no_std` linear checkpoint manifest planning. Begin a full or
8
+ incremental checkpoint, write/validate its data externally, then commit its
9
+ manifest. Incrementals must name the latest committed checkpoint as parent.
10
+ Aborted attempts consume IDs so stale completions cannot commit a replacement.
11
+
12
+ A committed full checkpoint replaces the old manifest chain; a pending/aborted
13
+ full checkpoint does not. Capacity exhaustion rejects incrementals and can be
14
+ resolved by successfully committing a full checkpoint. Restore plans return
15
+ the required full-to-target chain, not missing parent references.
16
+
17
+ The supplied checksum is opaque metadata: this core does not calculate or verify
18
+ it. There is no byte-diff engine, storage consistency boundary, serialization,
19
+ data garbage collection, or crash recovery adapter. Persist manifests and data
20
+ with a deployment-specific atomic publish protocol before deleting old data.
21
+ IDs and pending handles are scoped to one catalog lifetime.
@@ -0,0 +1,2 @@
1
+ # frozen_string_literal: true
2
+ require_relative 'ffi/extconf'
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+ module SnapshotMachines
3
+ VERSION = '0.1.5'
4
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+ require_relative 'snapshot_machines/version'
3
+ require 'snapshot_machines_native/snapshot_machines_native'
@@ -0,0 +1,15 @@
1
+ module SnapshotMachines
2
+ VERSION: String
3
+ class Pending
4
+ def id: () -> Integer
5
+ end
6
+ class Catalog
7
+ def self.new: () -> Catalog
8
+ def capacity: () -> Integer
9
+ def begin: (Integer?) -> Pending?
10
+ def commit: (Pending, Integer) -> [Integer, Integer?, Integer]?
11
+ def abort: (Pending) -> bool
12
+ def latest: () -> [Integer, Integer?, Integer]?
13
+ def restore_plan: (Integer) -> Array[[Integer, Integer?, Integer]]?
14
+ end
15
+ end
metadata ADDED
@@ -0,0 +1,62 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: snapshot_machines
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.5
5
+ platform: arm64-darwin
6
+ authors:
7
+ - Abdelkader Boudih
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-11-06 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: An allocation-free Rust manifest model with a thin Ruby bridge. No snapshot
14
+ bytes or durable storage.
15
+ email:
16
+ - oss@seuros.com
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - CHANGELOG.md
22
+ - LICENSE.txt
23
+ - README.md
24
+ - ext/snapshot_machines_native/core/CHANGELOG.md
25
+ - ext/snapshot_machines_native/core/README.md
26
+ - ext/snapshot_machines_native/extconf.rb
27
+ - lib/snapshot_machines.rb
28
+ - lib/snapshot_machines/version.rb
29
+ - lib/snapshot_machines_native/snapshot_machines_native.bundle
30
+ - sig/snapshot_machines.rbs
31
+ homepage: https://github.com/seuros/snapshot_machines
32
+ licenses:
33
+ - MIT
34
+ metadata:
35
+ source_code_uri: https://github.com/seuros/snapshot_machines
36
+ bug_tracker_uri: https://github.com/seuros/snapshot_machines/issues
37
+ rubygems_mfa_required: 'true'
38
+ cargo_crate_name: snapshot_machines_native
39
+ cargo_manifest_path: ext/snapshot_machines_native/ffi/Cargo.toml
40
+ post_install_message:
41
+ rdoc_options: []
42
+ require_paths:
43
+ - lib
44
+ required_ruby_version: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '4.0'
49
+ - - "<"
50
+ - !ruby/object:Gem::Version
51
+ version: 4.1.dev
52
+ required_rubygems_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: '0'
57
+ requirements: []
58
+ rubygems_version: 3.5.23
59
+ signing_key:
60
+ specification_version: 4
61
+ summary: Bounded native snapshot manifest planning for Ruby
62
+ test_files: []