Real-time collaborative text file sync via CRDT
Project description
entangle — Real-Time Collaborative File Sync via CRDT
2026-04-23T02:38:53Z by Showboat 0.6.1
entangle makes a single local text file collaboratively editable across machines in real time. Run one command to share a file; anyone with the link syncs it to their filesystem and edits it with any program — vim, VS Code, sed. Changes merge automatically via CRDT (Conflict-free Replicated Data Type).
Architecture layers built in this session:
- differ — character-level text diff → positional insert/delete ops
- crdt — yrs (Yjs-compatible) CRDT engine wrapping a Y.Text
- protocol — lib0 varint encoding for the y-websocket sync wire format
- watcher — notify v6 filesystem watcher bridged to tokio async channels
- writer — atomic file writes with a suppress flag to break echo loops
- session — async event loop tying all layers together with debounce and reconnect
- room — random room ID generation and share-link formatting
Build
Build the release binary from the Cargo workspace.
cargo build --release 2>&1 | tail -3
Finished `release` profile [optimized] target(s) in 0.10s
CLI Interface
entangle exposes two subcommands: share and join.
./target/release/entangle --help
Real-time collaborative text file sync via CRDT
Usage: entangle [OPTIONS] <COMMAND>
Commands:
share Share a local file with others
join Join a shared file by its link
help Print this message or the help of the given subcommand(s)
Options:
-v, --verbose Enable debug logging
-h, --help Print help
-V, --version Print version
./target/release/entangle share --help
Share a local file with others
Usage: entangle share [OPTIONS] --server <SERVER> <FILE>
Arguments:
<FILE> Path to the file to share
Options:
--server <SERVER> y-websocket relay URL (e.g. wss://relay.example.com)
--room <ROOM> Override the room name (default: auto-generated)
--debounce <DEBOUNCE> Debounce interval for file watcher (ms) [default: 300]
-v, --verbose Enable debug logging
--poll-interval <POLL_INTERVAL> Fallback poll interval (ms) [default: 2000]
-h, --help Print help
./target/release/entangle join --help
Join a shared file by its link
Usage: entangle join [OPTIONS] <URL>
Arguments:
<URL> Share link (e.g. wss://relay.example.com/r/<room-id>)
Options:
-o, --output <OUTPUT> Local path to write the synced file
--debounce <DEBOUNCE> Debounce interval for file watcher (ms) [default: 300]
--poll-interval <POLL_INTERVAL> Fallback poll interval (ms) [default: 2000]
-v, --verbose Enable debug logging
-h, --help Print help
Layer 1 — Differ
compute_diff(old, new) produces a sequence of positional insert/delete ops that transform old into new when applied left-to-right to a Y.Text. Positions are tracked in the current CRDT state frame: deletes do not advance the cursor (they remove chars at that position), while inserts advance it by the number of chars inserted.
cargo test --lib differ:: 2>&1 | grep -E 'test |FAILED|ok\.' | head -30
test differ::tests::append_lines ... ok
test differ::tests::emoji ... ok
test differ::tests::full_replace ... ok
test differ::tests::empty_to_nonempty ... ok
test differ::tests::identical_returns_no_ops ... ok
test differ::tests::nonempty_to_empty ... ok
test differ::tests::multiline ... ok
test differ::tests::prepend_lines ... ok
test differ::tests::replace_at_end ... ok
test differ::tests::replace_at_start ... ok
test differ::tests::replace_word ... ok
test differ::tests::single_char_delete ... ok
test differ::tests::single_char_insert ... ok
test differ::tests::unicode_multibyte ... ok
test differ::tests::large_document_prefix_insert ... ok
test differ::tests::large_document_append ... ok
test result: ok. 16 passed; 0 failed; 0 ignored; 0 measured; 23 filtered out; finished in 0.01s
A quick inline demonstration: computing the diff between two strings and printing the ops.
# Simulate the differ logic in Python to show the op sequence
# (matches exactly what compute_diff produces in Rust)
import difflib
def compute_diff(old, new):
ops = []
crdt_pos = 0
matcher = difflib.SequenceMatcher(None, list(old), list(new), autojunk=False)
for tag, i1, i2, j1, j2 in matcher.get_opcodes():
if tag == 'equal':
crdt_pos += i2 - i1
elif tag == 'delete':
ops.append(('delete', crdt_pos, i2 - i1))
elif tag == 'insert':
s = new[j1:j2]
ops.append(('insert', crdt_pos, s))
crdt_pos += len(s)
elif tag == 'replace':
ops.append(('delete', crdt_pos, i2 - i1))
s = new[j1:j2]
ops.append(('insert', crdt_pos, s))
crdt_pos += len(s)
return ops
pairs = [
('hello world', 'hello beautiful world'),
('hello world', 'hi world'),
('abcde', 'ace'),
('你好', '你好世界'),
]
for old, new in pairs:
ops = compute_diff(old, new)
print(f' {old!r:25} -> {new!r:25} ops: {ops}')
'hello world' -> 'hello beautiful world' ops: [('insert', 6, 'beautiful ')]
'hello world' -> 'hi world' ops: [('delete', 1, 4), ('insert', 1, 'i')]
'abcde' -> 'ace' ops: [('delete', 1, 1), ('delete', 2, 1)]
'你好' -> '你好世界' ops: [('insert', 2, '世界')]
Layer 2 — CRDT Engine
CrdtEngine wraps a yrs::Doc with a single Y.Text named "content". Key methods:
seed(content)— populate the doc with initial text (share command startup)apply_local_edit(new_content)— diff snapshot → new, apply ops, return encoded delta bytesapply_remote_update(bytes)— decode and apply a remote delta, return new text if changedstate_vector_bytes()— encode our state vector for SyncStep1encode_state_as_update(peer_sv)— encode what the peer is missing for SyncStep2
cargo test --lib crdt:: 2>&1 | grep -E 'test |result:'
test crdt::tests::identical_edit_produces_no_update ... ok
test crdt::tests::remote_update_round_trip ... ok
test crdt::tests::local_edit_produces_update ... ok
test crdt::tests::concurrent_edits_converge ... ok
test crdt::tests::state_vector_sync_protocol ... ok
test crdt::tests::seed_and_read ... ok
test crdt::tests::two_peer_convergence ... ok
test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 32 filtered out; finished in 0.00s
The concurrent_edits_converge test verifies the core CRDT guarantee: two peers making independent edits to the same base text will always arrive at the same final state after exchanging updates, regardless of edit order.
cargo test --lib crdt::tests::concurrent_edits_converge -- --nocapture 2>&1
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.12s
Running unittests src/lib.rs (target/debug/deps/entangle-d26086c8a098f676)
running 1 test
test crdt::tests::concurrent_edits_converge ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 38 filtered out; finished in 0.00s
Layer 3 — y-websocket Protocol
protocol.rs implements the y-sync wire format using lib0 variable-length integer encoding. Each WebSocket binary frame is: [msg_type:varint][sync_type:varint][payload_len:varint][payload bytes].
Message types:
encode_sync_step1(sv)→ sent on connect, announces our state to the relayencode_sync_step2(update)→ reply to a peer's SyncStep1, sends what they're missingencode_update(update)→ incremental update after each local editdecode_message(bytes)→ parses any incoming frame into aSyncMessageenum
cargo test --lib protocol:: 2>&1 | grep -E 'test |result:'
test protocol::tests::round_trip_sync_step1 ... ok
test protocol::tests::truncated_message_returns_none ... ok
test protocol::tests::round_trip_sync_step2 ... ok
test protocol::tests::round_trip_update ... ok
test protocol::tests::varint_multibyte ... ok
test protocol::tests::unknown_msg_type_returns_none ... ok
test result: ok. 6 passed; 0 failed; 0 ignored; 0 measured; 33 filtered out; finished in 0.00s
A concrete look at the wire format — encoding a SyncStep1 and inspecting the bytes:
def write_varint(n):
buf = []
while True:
b = n & 0x7F
n >>= 7
if n == 0:
buf.append(b)
break
buf.append(b | 0x80)
return bytes(buf)
def write_var_bytes(data):
return write_varint(len(data)) + data
def encode_sync_step1(sv: bytes) -> bytes:
return write_varint(0) + write_varint(0) + write_var_bytes(sv)
def encode_update(update: bytes) -> bytes:
return write_varint(0) + write_varint(2) + write_var_bytes(update)
sv = bytes([1, 0, 1, 5]) # example state vector bytes
frame = encode_sync_step1(sv)
print(f'SyncStep1 frame : {list(frame)}')
print(f' byte 0 = {frame[0]} (msg_type=sync)')
print(f' byte 1 = {frame[1]} (sync_type=step1)')
print(f' byte 2 = {frame[2]} (payload length={frame[2]})')
print(f' rest = {list(frame[3:])} (state vector)')
update = bytes([0xde, 0xad, 0xbe, 0xef])
frame2 = encode_update(update)
print()
print(f'Update frame : {list(frame2)}')
print(f' byte 0 = {frame2[0]} (msg_type=sync)')
print(f' byte 1 = {frame2[1]} (sync_type=update)')
print(f' byte 2 = {frame2[2]} (payload length={frame2[2]})')
SyncStep1 frame : [0, 0, 4, 1, 0, 1, 5]
byte 0 = 0 (msg_type=sync)
byte 1 = 0 (sync_type=step1)
byte 2 = 4 (payload length=4)
rest = [1, 0, 1, 5] (state vector)
Update frame : [0, 2, 4, 222, 173, 190, 239]
byte 0 = 0 (msg_type=sync)
byte 1 = 2 (sync_type=update)
byte 2 = 4 (payload length=4)
Layer 4 — File Watcher
watcher::spawn_watcher starts a notify v6 RecommendedWatcher on the parent directory of the target file (watching a single file directly is unreliable on some platforms when editors use atomic save-via-rename). Events are filtered to the target filename and bridged to a tokio channel via spawn_blocking. The suppress flag (an AtomicBool shared with the writer) prevents entangle from reacting to its own writes.
Layer 5 — Atomic File Writer
writer::write_file_atomic avoids partial reads by writing to a temp file in the same directory then renaming over the target. Before writing it sets the suppress flag so the watcher ignores the resulting fs event; a short-lived tokio task clears it 50 ms later.
cargo test --lib writer:: 2>&1 | grep -E 'test |result:'
test writer::tests::no_tmp_file_remains ... ok
test writer::tests::atomic_write_creates_file ... ok
test writer::tests::suppress_is_set_then_cleared ... ok
test writer::tests::overwrites_existing_file ... ok
test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 35 filtered out; finished in 0.13s
Layer 6 — Room ID & Share Links
room::generate_room_id produces 8 random bytes hex-encoded (16 chars), e.g. a3f8c2e9b1d4f067. The share link embeds the WebSocket URL and room ID in a form the joiner can paste directly into their terminal.
cargo test --lib room:: 2>&1 | grep -E 'test |result:'
test room::tests::parse_room_id_empty_room ... ok
test room::tests::parse_room_id_missing_prefix ... ok
test room::tests::parse_room_id_valid ... ok
test room::tests::share_link_format ... ok
test room::tests::room_id_is_16_hex_chars ... ok
test room::tests::room_ids_are_unique ... ok
test result: ok. 6 passed; 0 failed; 0 ignored; 0 measured; 33 filtered out; finished in 0.00s
Property-Based Tests (proptest)
Four proptest properties exercise the differ + CRDT stack with randomly generated inputs:
- diff_roundtrip_ascii — for any two ASCII strings, applying
compute_diffops to the old string always yields the new string - diff_roundtrip_unicode — same for arbitrary Unicode
- crdt_roundtrip — applying a local edit to a
CrdtEnginealways results in the engine holding the new content - two_peer_convergence — two peers seeded with the same base, each making an independent edit, always converge after exchanging updates
cargo test --test differ_proptest 2>&1 | grep -E 'test |result:|FAILED'
test diff_roundtrip_unicode ... ok
test crdt_roundtrip ... ok
test two_peer_convergence ... ok
test diff_roundtrip_ascii ... ok
test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.59s
Full Test Suite
All 43 tests across 39 unit tests + 4 property-based tests:
cargo test 2>&1 | grep -E '^test |^test result:'
test crdt::tests::identical_edit_produces_no_update ... ok
test crdt::tests::local_edit_produces_update ... ok
test crdt::tests::remote_update_round_trip ... ok
test crdt::tests::concurrent_edits_converge ... ok
test crdt::tests::seed_and_read ... ok
test differ::tests::append_lines ... ok
test differ::tests::emoji ... ok
test crdt::tests::state_vector_sync_protocol ... ok
test differ::tests::identical_returns_no_ops ... ok
test differ::tests::full_replace ... ok
test differ::tests::empty_to_nonempty ... ok
test crdt::tests::two_peer_convergence ... ok
test differ::tests::nonempty_to_empty ... ok
test differ::tests::multiline ... ok
test differ::tests::prepend_lines ... ok
test differ::tests::replace_at_end ... ok
test differ::tests::replace_at_start ... ok
test differ::tests::replace_word ... ok
test differ::tests::single_char_delete ... ok
test differ::tests::single_char_insert ... ok
test differ::tests::unicode_multibyte ... ok
test protocol::tests::round_trip_sync_step1 ... ok
test protocol::tests::round_trip_sync_step2 ... ok
test protocol::tests::round_trip_update ... ok
test protocol::tests::truncated_message_returns_none ... ok
test protocol::tests::unknown_msg_type_returns_none ... ok
test protocol::tests::varint_multibyte ... ok
test room::tests::parse_room_id_empty_room ... ok
test room::tests::parse_room_id_missing_prefix ... ok
test room::tests::parse_room_id_valid ... ok
test room::tests::room_id_is_16_hex_chars ... ok
test room::tests::share_link_format ... ok
test room::tests::room_ids_are_unique ... ok
test writer::tests::no_tmp_file_remains ... ok
test differ::tests::large_document_prefix_insert ... ok
test writer::tests::atomic_write_creates_file ... ok
test differ::tests::large_document_append ... ok
test writer::tests::suppress_is_set_then_cleared ... ok
test writer::tests::overwrites_existing_file ... ok
test result: ok. 39 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.13s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
test diff_roundtrip_unicode ... ok
test crdt_roundtrip ... ok
test two_peer_convergence ... ok
test diff_roundtrip_ascii ... ok
test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.64s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Showboat: Verify, Extract, Pop
These three showboat commands close the loop on document integrity.
extract — prints the sequence of showboat CLI commands that would rebuild this document from scratch. Useful for auditing or replaying the demo in a fresh environment.
uvx showboat extract /home/user/entangle/demo.md 2>&1
showboat init /home/user/entangle/demo.md 'entangle — Real-Time Collaborative File Sync via CRDT'
showboat note /home/user/entangle/demo.md 'entangle makes a single local text file collaboratively editable across machines in real time. Run one command to share a file; anyone with the link syncs it to their filesystem and edits it with any program — vim, VS Code, sed. Changes merge automatically via CRDT (Conflict-free Replicated Data Type).
Architecture layers built in this session:
- **differ** — character-level text diff → positional insert/delete ops
- **crdt** — yrs (Yjs-compatible) CRDT engine wrapping a Y.Text
- **protocol** — lib0 varint encoding for the y-websocket sync wire format
- **watcher** — notify v6 filesystem watcher bridged to tokio async channels
- **writer** — atomic file writes with a suppress flag to break echo loops
- **session** — async event loop tying all layers together with debounce and reconnect
- **room** — random room ID generation and share-link formatting
## Build
Build the release binary from the Cargo workspace.'
showboat exec /home/user/entangle/demo.md bash 'cargo build --release 2>&1 | tail -3'
showboat note /home/user/entangle/demo.md '## CLI Interface
entangle exposes two subcommands: `share` and `join`.'
showboat exec /home/user/entangle/demo.md bash './target/release/entangle --help'
showboat exec /home/user/entangle/demo.md bash './target/release/entangle share --help'
showboat exec /home/user/entangle/demo.md bash './target/release/entangle join --help'
showboat note /home/user/entangle/demo.md '## Layer 1 — Differ
`compute_diff(old, new)` produces a sequence of positional insert/delete ops that transform `old` into `new` when applied left-to-right to a Y.Text. Positions are tracked in the *current CRDT state* frame: deletes do not advance the cursor (they remove chars at that position), while inserts advance it by the number of chars inserted.'
showboat exec /home/user/entangle/demo.md bash 'cargo test --lib differ:: 2>&1 | grep -E '\''test |FAILED|ok\.'\'' | head -30'
showboat note /home/user/entangle/demo.md 'A quick inline demonstration: computing the diff between two strings and printing the ops.'
showboat exec /home/user/entangle/demo.md python3 '
# Simulate the differ logic in Python to show the op sequence
# (matches exactly what compute_diff produces in Rust)
import difflib
def compute_diff(old, new):
ops = []
crdt_pos = 0
matcher = difflib.SequenceMatcher(None, list(old), list(new), autojunk=False)
for tag, i1, i2, j1, j2 in matcher.get_opcodes():
if tag == '\''equal'\'':
crdt_pos += i2 - i1
elif tag == '\''delete'\'':
ops.append(('\''delete'\'', crdt_pos, i2 - i1))
elif tag == '\''insert'\'':
s = new[j1:j2]
ops.append(('\''insert'\'', crdt_pos, s))
crdt_pos += len(s)
elif tag == '\''replace'\'':
ops.append(('\''delete'\'', crdt_pos, i2 - i1))
s = new[j1:j2]
ops.append(('\''insert'\'', crdt_pos, s))
crdt_pos += len(s)
return ops
pairs = [
('\''hello world'\'', '\''hello beautiful world'\''),
('\''hello world'\'', '\''hi world'\''),
('\''abcde'\'', '\''ace'\''),
('\''你好'\'', '\''你好世界'\''),
]
for old, new in pairs:
ops = compute_diff(old, new)
print(f'\'' {old!r:25} -> {new!r:25} ops: {ops}'\'')
'
showboat note /home/user/entangle/demo.md '## Layer 2 — CRDT Engine
`CrdtEngine` wraps a `yrs::Doc` with a single `Y.Text` named `"content"`. Key methods:
- `seed(content)` — populate the doc with initial text (share command startup)
- `apply_local_edit(new_content)` — diff snapshot → new, apply ops, return encoded delta bytes
- `apply_remote_update(bytes)` — decode and apply a remote delta, return new text if changed
- `state_vector_bytes()` — encode our state vector for SyncStep1
- `encode_state_as_update(peer_sv)` — encode what the peer is missing for SyncStep2'
showboat exec /home/user/entangle/demo.md bash 'cargo test --lib crdt:: 2>&1 | grep -E '\''test |result:'\'''
showboat note /home/user/entangle/demo.md 'The `concurrent_edits_converge` test verifies the core CRDT guarantee: two peers making independent edits to the same base text will always arrive at the same final state after exchanging updates, regardless of edit order.'
showboat exec /home/user/entangle/demo.md bash 'cargo test --lib crdt::tests::concurrent_edits_converge -- --nocapture 2>&1'
showboat note /home/user/entangle/demo.md '## Layer 3 — y-websocket Protocol
`protocol.rs` implements the y-sync wire format using lib0 variable-length integer encoding. Each WebSocket binary frame is: `[msg_type:varint][sync_type:varint][payload_len:varint][payload bytes]`.
Message types:
- `encode_sync_step1(sv)` → sent on connect, announces our state to the relay
- `encode_sync_step2(update)` → reply to a peer'\''s SyncStep1, sends what they'\''re missing
- `encode_update(update)` → incremental update after each local edit
- `decode_message(bytes)` → parses any incoming frame into a `SyncMessage` enum'
showboat exec /home/user/entangle/demo.md bash 'cargo test --lib protocol:: 2>&1 | grep -E '\''test |result:'\'''
showboat note /home/user/entangle/demo.md 'A concrete look at the wire format — encoding a SyncStep1 and inspecting the bytes:'
showboat exec /home/user/entangle/demo.md python3 '
def write_varint(n):
buf = []
while True:
b = n & 0x7F
n >>= 7
if n == 0:
buf.append(b)
break
buf.append(b | 0x80)
return bytes(buf)
def write_var_bytes(data):
return write_varint(len(data)) + data
def encode_sync_step1(sv: bytes) -> bytes:
return write_varint(0) + write_varint(0) + write_var_bytes(sv)
def encode_update(update: bytes) -> bytes:
return write_varint(0) + write_varint(2) + write_var_bytes(update)
sv = bytes([1, 0, 1, 5]) # example state vector bytes
frame = encode_sync_step1(sv)
print(f'\''SyncStep1 frame : {list(frame)}'\'')
print(f'\'' byte 0 = {frame[0]} (msg_type=sync)'\'')
print(f'\'' byte 1 = {frame[1]} (sync_type=step1)'\'')
print(f'\'' byte 2 = {frame[2]} (payload length={frame[2]})'\'')
print(f'\'' rest = {list(frame[3:])} (state vector)'\'')
update = bytes([0xde, 0xad, 0xbe, 0xef])
frame2 = encode_update(update)
print()
print(f'\''Update frame : {list(frame2)}'\'')
print(f'\'' byte 0 = {frame2[0]} (msg_type=sync)'\'')
print(f'\'' byte 1 = {frame2[1]} (sync_type=update)'\'')
print(f'\'' byte 2 = {frame2[2]} (payload length={frame2[2]})'\'')
'
showboat note /home/user/entangle/demo.md '## Layer 4 — File Watcher
`watcher::spawn_watcher` starts a `notify` v6 `RecommendedWatcher` on the *parent directory* of the target file (watching a single file directly is unreliable on some platforms when editors use atomic save-via-rename). Events are filtered to the target filename and bridged to a tokio channel via `spawn_blocking`. The suppress flag (an `AtomicBool` shared with the writer) prevents entangle from reacting to its own writes.
## Layer 5 — Atomic File Writer
`writer::write_file_atomic` avoids partial reads by writing to a temp file in the same directory then renaming over the target. Before writing it sets the suppress flag so the watcher ignores the resulting fs event; a short-lived tokio task clears it 50 ms later.'
showboat exec /home/user/entangle/demo.md bash 'cargo test --lib writer:: 2>&1 | grep -E '\''test |result:'\'''
showboat note /home/user/entangle/demo.md '## Layer 6 — Room ID & Share Links
`room::generate_room_id` produces 8 random bytes hex-encoded (16 chars), e.g. `a3f8c2e9b1d4f067`. The share link embeds the WebSocket URL and room ID in a form the joiner can paste directly into their terminal.'
showboat exec /home/user/entangle/demo.md bash 'cargo test --lib room:: 2>&1 | grep -E '\''test |result:'\'''
showboat note /home/user/entangle/demo.md '## Property-Based Tests (proptest)
Four proptest properties exercise the differ + CRDT stack with randomly generated inputs:
1. **diff_roundtrip_ascii** — for any two ASCII strings, applying `compute_diff` ops to the old string always yields the new string
2. **diff_roundtrip_unicode** — same for arbitrary Unicode
3. **crdt_roundtrip** — applying a local edit to a `CrdtEngine` always results in the engine holding the new content
4. **two_peer_convergence** — two peers seeded with the same base, each making an independent edit, always converge after exchanging updates'
showboat exec /home/user/entangle/demo.md bash 'cargo test --test differ_proptest 2>&1 | grep -E '\''test |result:|FAILED'\'''
showboat note /home/user/entangle/demo.md '## Full Test Suite
All 43 tests across 39 unit tests + 4 property-based tests:'
showboat exec /home/user/entangle/demo.md bash 'cargo test 2>&1 | grep -E '\''^test |^test result:'\'''
showboat note /home/user/entangle/demo.md '## Showboat: Verify, Extract, Pop
These three showboat commands close the loop on document integrity.
**extract** — prints the sequence of showboat CLI commands that would rebuild this document from scratch. Useful for auditing or replaying the demo in a fresh environment.'
showboat exec /home/user/entangle/demo.md bash 'uvx showboat extract /home/user/entangle/demo.md 2>&1'
showboat note /home/user/entangle/demo.md '**pop** — removes the most recent entry. Demonstrated here by adding a deliberate mistake and then removing it.
(The dummy entry above was removed by `showboat pop demo.md` — it no longer appears in the document.)
**verify** — re-runs every code block and diffs actual output against recorded output. Exits 0 if everything matches, 1 with diffs if anything changed.'
pop — removes the most recent entry. Demonstrated here by adding a deliberate mistake and then removing it.
(The dummy entry above was removed by showboat pop demo.md — it no longer appears in the document.)
verify — re-runs every code block and diffs actual output against recorded output. Exits 0 if everything matches, 1 with diffs if anything changed.
The diffs above are purely non-deterministic: parallel test execution order and sub-second build timings. Every test result line reads ok, all counts match, and the Python outputs are byte-for-byte identical. The document is reproducibly correct — only scheduling noise varies.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file entangle_sync-0.1.0.tar.gz.
File metadata
- Download URL: entangle_sync-0.1.0.tar.gz
- Upload date:
- Size: 39.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2f5c5fe1bb9e77e9755f52b5aac2b18670a6ac5354c2a1498315a789a60306df
|
|
| MD5 |
8c9e57ee54e4324abc9ca6594b72b7f9
|
|
| BLAKE2b-256 |
db51e2170e9b78d9467a8fb90b3d28c33f77781fd53f4d9e49200e70aa1a8faf
|
Provenance
The following attestation bundles were made for entangle_sync-0.1.0.tar.gz:
Publisher:
publish.yml on pirxthedev/entangle
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
entangle_sync-0.1.0.tar.gz -
Subject digest:
2f5c5fe1bb9e77e9755f52b5aac2b18670a6ac5354c2a1498315a789a60306df - Sigstore transparency entry: 1367218304
- Sigstore integration time:
-
Permalink:
pirxthedev/entangle@4f785a5ff5d87218946d905b0ef151fc3a688ead -
Branch / Tag:
refs/tags/v0.1.3 - Owner: https://github.com/pirxthedev
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@4f785a5ff5d87218946d905b0ef151fc3a688ead -
Trigger Event:
push
-
Statement type:
File details
Details for the file entangle_sync-0.1.0-py3-none-win_amd64.whl.
File metadata
- Download URL: entangle_sync-0.1.0-py3-none-win_amd64.whl
- Upload date:
- Size: 3.0 MB
- Tags: Python 3, Windows x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ed7eb72b7ab1d83e79ae534e932172209928c8d5e03e68992b6ee4f6355116fe
|
|
| MD5 |
af8578fed2723775a8eb8a56572ed9b0
|
|
| BLAKE2b-256 |
0c4f25752b0753ffe5ccddf6389872f9d958f410d66ff4525fe4d7608228a823
|
Provenance
The following attestation bundles were made for entangle_sync-0.1.0-py3-none-win_amd64.whl:
Publisher:
publish.yml on pirxthedev/entangle
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
entangle_sync-0.1.0-py3-none-win_amd64.whl -
Subject digest:
ed7eb72b7ab1d83e79ae534e932172209928c8d5e03e68992b6ee4f6355116fe - Sigstore transparency entry: 1367218393
- Sigstore integration time:
-
Permalink:
pirxthedev/entangle@4f785a5ff5d87218946d905b0ef151fc3a688ead -
Branch / Tag:
refs/tags/v0.1.3 - Owner: https://github.com/pirxthedev
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@4f785a5ff5d87218946d905b0ef151fc3a688ead -
Trigger Event:
push
-
Statement type:
File details
Details for the file entangle_sync-0.1.0-py3-none-manylinux_2_28_x86_64.whl.
File metadata
- Download URL: entangle_sync-0.1.0-py3-none-manylinux_2_28_x86_64.whl
- Upload date:
- Size: 2.8 MB
- Tags: Python 3, manylinux: glibc 2.28+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
df81f17e76edfaa4b97fad6691e14ff61bc80c4579d8d631d667b1341c4d773d
|
|
| MD5 |
180b8b03a420261622a91eef3fc3761c
|
|
| BLAKE2b-256 |
196b10fd6e9f53ff98eb152bfba96b2bda5a6b6a7fae69374924392f032345dd
|
Provenance
The following attestation bundles were made for entangle_sync-0.1.0-py3-none-manylinux_2_28_x86_64.whl:
Publisher:
publish.yml on pirxthedev/entangle
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
entangle_sync-0.1.0-py3-none-manylinux_2_28_x86_64.whl -
Subject digest:
df81f17e76edfaa4b97fad6691e14ff61bc80c4579d8d631d667b1341c4d773d - Sigstore transparency entry: 1367218802
- Sigstore integration time:
-
Permalink:
pirxthedev/entangle@4f785a5ff5d87218946d905b0ef151fc3a688ead -
Branch / Tag:
refs/tags/v0.1.3 - Owner: https://github.com/pirxthedev
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@4f785a5ff5d87218946d905b0ef151fc3a688ead -
Trigger Event:
push
-
Statement type:
File details
Details for the file entangle_sync-0.1.0-py3-none-manylinux_2_28_aarch64.whl.
File metadata
- Download URL: entangle_sync-0.1.0-py3-none-manylinux_2_28_aarch64.whl
- Upload date:
- Size: 2.8 MB
- Tags: Python 3, manylinux: glibc 2.28+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7472dc71c943eec6cf4eb7b45aaacb1d56dcf12e13e48f2f3d6ca7aefccea685
|
|
| MD5 |
6ff80be6ddc0d40fb91b617e161348ed
|
|
| BLAKE2b-256 |
09cd20f73ea3e55e190697dfb4eaaf50674e2e2e3e4eba33fa422509164af386
|
Provenance
The following attestation bundles were made for entangle_sync-0.1.0-py3-none-manylinux_2_28_aarch64.whl:
Publisher:
publish.yml on pirxthedev/entangle
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
entangle_sync-0.1.0-py3-none-manylinux_2_28_aarch64.whl -
Subject digest:
7472dc71c943eec6cf4eb7b45aaacb1d56dcf12e13e48f2f3d6ca7aefccea685 - Sigstore transparency entry: 1367218510
- Sigstore integration time:
-
Permalink:
pirxthedev/entangle@4f785a5ff5d87218946d905b0ef151fc3a688ead -
Branch / Tag:
refs/tags/v0.1.3 - Owner: https://github.com/pirxthedev
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@4f785a5ff5d87218946d905b0ef151fc3a688ead -
Trigger Event:
push
-
Statement type:
File details
Details for the file entangle_sync-0.1.0-py3-none-macosx_11_0_arm64.whl.
File metadata
- Download URL: entangle_sync-0.1.0-py3-none-macosx_11_0_arm64.whl
- Upload date:
- Size: 2.9 MB
- Tags: Python 3, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
71b44293abe802c71c3d8823d0dd5deaa981471089a4a6bde73f55ad8bcaf1c3
|
|
| MD5 |
45fef5662eebb7e90535e2652d4bc554
|
|
| BLAKE2b-256 |
99ffd2e583ee9891e8fefdf89d73ee64bd70936e3d60cc4fe94f0fd6c06986d4
|
Provenance
The following attestation bundles were made for entangle_sync-0.1.0-py3-none-macosx_11_0_arm64.whl:
Publisher:
publish.yml on pirxthedev/entangle
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
entangle_sync-0.1.0-py3-none-macosx_11_0_arm64.whl -
Subject digest:
71b44293abe802c71c3d8823d0dd5deaa981471089a4a6bde73f55ad8bcaf1c3 - Sigstore transparency entry: 1367218698
- Sigstore integration time:
-
Permalink:
pirxthedev/entangle@4f785a5ff5d87218946d905b0ef151fc3a688ead -
Branch / Tag:
refs/tags/v0.1.3 - Owner: https://github.com/pirxthedev
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@4f785a5ff5d87218946d905b0ef151fc3a688ead -
Trigger Event:
push
-
Statement type:
File details
Details for the file entangle_sync-0.1.0-py3-none-macosx_10_12_x86_64.whl.
File metadata
- Download URL: entangle_sync-0.1.0-py3-none-macosx_10_12_x86_64.whl
- Upload date:
- Size: 3.0 MB
- Tags: Python 3, macOS 10.12+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8a522abf5d8aea1e4efbdfc00d8cd85ebbce6f6b7f7b3c71ace0d63bb8332df9
|
|
| MD5 |
70cab7c1fc2bb85c822c4415f248e737
|
|
| BLAKE2b-256 |
d88c868a485f5681db9d3631848143cac9575b07a8c48b244964c7e2c4dd2e18
|
Provenance
The following attestation bundles were made for entangle_sync-0.1.0-py3-none-macosx_10_12_x86_64.whl:
Publisher:
publish.yml on pirxthedev/entangle
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
entangle_sync-0.1.0-py3-none-macosx_10_12_x86_64.whl -
Subject digest:
8a522abf5d8aea1e4efbdfc00d8cd85ebbce6f6b7f7b3c71ace0d63bb8332df9 - Sigstore transparency entry: 1367218603
- Sigstore integration time:
-
Permalink:
pirxthedev/entangle@4f785a5ff5d87218946d905b0ef151fc3a688ead -
Branch / Tag:
refs/tags/v0.1.3 - Owner: https://github.com/pirxthedev
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@4f785a5ff5d87218946d905b0ef151fc3a688ead -
Trigger Event:
push
-
Statement type: