Commit Graph
63 Commits
Author SHA1 Message Date
kyleandClaude Opus 5.5 97a97379e2 fix(core): free drop-table keys on drain; summary rate-limit slack
- The drop table now empties on drain, keys included. Before, after 32
  distinct (addr, reason) pairs every later drop went to the overflow
  slot until reboot. The overflow slot's reason is documented as that of
  the first drop to overflow since the last drain (review S7).
- F9's receive-side check accepts a summary at least
  SUMMARY_INTERVAL_MS / 2 after the last one acted on, so an on-time
  summary measured at 1999 ms isn't ignored (decision L, review D-f).
- DropReason::IdentityChanged loses its variant-level non_exhaustive,
  which made it unmatchable outside the crate; the enum keeps its own.
- Stale comments: drain_events is used by Node (drop the dead_code
  allow), and NodeEvent drops are this node's, not the peer's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 08:44:09 -07:00
kyleandClaude Opus 5.5 d983fea024 fix(core): reassembled observations must decode canonically
The reassembler decoded with postcard::from_bytes, which accepts
trailing bytes. It now uses take_from_bytes and rejects a non-empty
remainder, as Frame::decode does (spec §10 F13).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 08:44:09 -07:00
kyleandClaude Opus 5.5 eda9746d8e fix(core): evict silent and contact-only peers (F8)
The idle check ran only in on_frame and only for the peer that just
sent, so a peer that went quiet, or never sent (contact-only), held its
slot forever: eight spoofed addresses locked out every newcomer
(review S1).

Peers now record when their contact started (added_at), and idle peers
(now - last_inbound.unwrap_or(added_at) > PEER_IDLE_MS) are swept as if
Lost before a peer is added and at the top of next_frame. A peer that
resumes after its idle window is an implicit new contact: its frame is
processed rather than silently dropped. A repeat Contact restarts the
idle clock.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 08:44:09 -07:00
kyleandClaude Opus 5.5 ac2bf37719 fix(core): scope acks, receipts and the F6 pin to the contact
- remove_peer purges only the removed peer's pending acks, not every
  peer's (review S9, F13).
- bcast_sent records for an address are dropped when its contact starts
  or ends, and a broadcast whose send fails has its record undone, so a
  broadcast Ack is accepted only for this contact's dispatched frames
  (review S10).
- A repeat Contact for a known peer starts a new contact: it resets the
  F6 pin and the per-contact bounce and receipt records (spec §10 F6).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 08:44:09 -07:00
kyleandClaude Opus 5.5 caa245dda4 fix(core): never trust store filtering for summaries and broadcast diff
Core re-checks dest when building the summary Bloom filter, rebuilding
a peer's broadcast send queue, and emitting a broadcast, so a store
whose for_each_broadcast_id yields addressed ids can't leak them into a
summary or forward them (expired) as broadcasts (review S11).

Adds UnfilteredStore, a test store whose iterators yield every held id,
released and broadcast ones included, pinning spec §4's 'core never
trusts a backend's filtering' for custody, acks and summaries (B8).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 08:44:09 -07:00
kyle 8425232ba1 release(core): tines-core 1.0.0 2026-10-22 08:44:09 -07:00
kyle 164cf97a5b test(core): fuzz harness and proptest twin 2026-10-22 08:44:09 -07:00
kyle ef539b494f refactor(core): WallClockStore trait replaces the store_mut backdoor
Task 10b reached LogStore::set_wall_clock through a general Node::store_mut.
Kyle: replace it with a narrower accessor. A WallClockStore trait (set_wall_clock
+ peer_time_estimate, implemented only by LogStore) now backs Node::set_wall_clock
and Node::peer_time_estimate, so no &mut Store escapes the facade.
2026-10-22 08:44:09 -07:00
kyle a43a4966e1 test(core): pinned vectors + MSRV gate 2026-10-22 08:44:09 -07:00
kyle 7c8d8b74e9 feat(hosts): trusted wall time from the system clock or a peer; set observed_at 2026-10-22 08:44:09 -07:00
kyle c5378b1099 fix(core): peer pinning, idle eviction, replay and queue hardening 2026-10-22 08:44:09 -07:00
kyle 392c7c6b04 docs(core): split wire vs local constants 2026-10-22 08:44:09 -07:00
kyle 07907efec5 feat(core): settle the Store trait (flush, is_empty, conformance suite) 2026-10-22 08:44:09 -07:00
kyle 158689e265 refactor(core): narrow the 1.0 public API 2026-10-22 08:44:09 -07:00
kyle 94c3a3c8ad feat(core): reserved Signature slot on Observation 2026-10-22 08:44:09 -07:00
kyle e85b3704e0 feat(core): Dest type replaces to + lifetime_ms 2026-10-22 08:44:09 -07:00
kyle 193c7bddb8 feat(core): NodeEvent with counted drop reporting 2026-10-22 08:44:09 -07:00
kyle 99036514c2 feat(core): WIRE_VERSION byte on every frame 2026-10-22 08:44:09 -07:00
kyle 7df9f41e9f fix(bt): framing, roles, NodeId binding, contact events 2026-10-19 23:18:35 -07:00
kyle d98895b9d6 fix(service): run the exchange on a poll loop 2026-10-19 19:22:43 -07:00
kyle 2738de4959 fix(core): ack only observations the store kept
Exchange::accept enqueued a pending ack whenever
!(duplicate && addressed && released) held, even when the backend
store.insert returned Err (store full / backend error). On Err the
released flag computed to false, so an ack was still enqueued: a
receiver that did not keep the message acked it. For an addressed
offer that releases the sender's custody for a message nobody holds;
for a broadcast it fires a false confidence event at the sender.

Gate the ack enqueue on res.is_ok(): enqueue only when the store
actually retained the observation. The existing released-holder
exception still applies among kept copies. No bounce is recorded on
Err (already the case).

Adds a unit test exercising a capped store (StoreError::Full) for
both a broadcast and an addressed offer, and a tines-sim property
that drives custody with CappedStore-backed nodes and asserts the
"at least one holder" invariant still holds when stores fill.
2026-10-19 12:35:17 -07:00
kyle d099f09b63 feat(core): Node::drain_events for confidence events 2026-10-18 11:56:38 -07:00
Tines Subagent 29c30a6b33 task-4: revert on_ack release to custody chain; fix drain_ack_buffer overflow
Revert on_ack release gating to release-on-any-ack (custody chain): release
custody on the first exact Ack from ANY peer the node dispatched the
observation to (relay or recipient), not just the recipient.

drain_ack_buffer send_n==0 bug: re-keep only the target addr's own pulled
entries instead of removing buffer.entries[0] (which dropped an unrelated
peer's oldest pending ack).

Rename the tines-sim addressed proptest to
addressed_always_held_by_carrier_or_recipient and reconcile its assertion to
the custody-chain holder invariant. Reword the misleading "Ids shrink
monotonically" comment. Add unit tests: ack_releases_custody_from_a_relay and
drain_ack_buffer_overflow_drops_only_target_peer.

Gate green: tines-core 106 lib tests, tines-sim suite, sh scripts/ci.sh.
2026-10-18 11:48:40 -07:00
kyle fafda104ea feat(core): ack send path with custody/broadcast buffers
Buffer pending acks (acks for addressed, bcast_acks for broadcast,
ACK_BUFFER_CAP=256, overflow drops oldest), skip a released-duplicate
offer in accept(), and emit Ack frames in next_frame after a due summary
and before addressed/broadcast traffic, draining up to ACK_IDS_CAP ids
per destination and splitting across frames to fit mtu.

Release custody on an Ack only when the Ack confirms delivery to the
intended recipient: an Ack from a peer that merely holds an
epidemic-forwarded copy no longer releases custody, so the local copy
stays available to forward toward the recipient once the only contact to
it closes.
2026-10-18 11:21:49 -07:00
kyle 6643b107ab feat(core): ack validation, custody release, confidence events 2026-10-18 09:13:35 -07:00
kyle 2a23cc390d feat(core): Ack wire frame (last Frame variant) 2026-10-18 07:39:07 -07:00
kyle 0bb4d568ea feat(core): Store::release and is_released (0.3.0) 2026-10-18 07:22:45 -07:00
kyleandClaude Opus 5.5 eefc67e275 chore: ship LICENSE in each crate
Symlink the root Apache-2.0 LICENSE into tines-core and tines-sim so
`cargo package` includes the license text.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 05:29:59 -07:00
kyle 3961a21a26 Licenses for crates.
CI / ci (push) Successful in 1m2s
2026-10-18 05:28:23 -07:00
kyleandClaude Opus 5.5 8b233c787f chore: release 0.2.1 with crates.io metadata
Share version, edition, license, authors, repository and readme through
[workspace.package]; add per-crate documentation, keywords and
categories. Fixes `cargo publish` warning about missing documentation,
homepage or repository.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 05:26:39 -07:00
kyleandClaude Opus 5.5 5c8bfe89ac chore: release 0.2.0; document the store contract and on_send_error
CI / ci (push) Canceled after 0s
Bump tines-core and tines-sim to 0.2.0 (Peer::in_flight changed type).
Add CHANGELOG.md. README gains a "Writing a store" section and a note on
calling Exchange::on_send_error when driving Exchange without Node.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 05:24:30 -07:00
kyleandClaude Opus 5.5 9a055b5b57 refactor(core): scan only addressed ids for custody; share frag plan
- Add Store::for_each_addressed_id (default: every id; MemStore filters)
  so custody selection no longer clones every broadcast observation on
  each poll.
- Factor fragment sizing into frag::plan and MAX_FRAGMENTS, used by both
  frag::fragment and the exchange, and by the reassembler's bounds.
- Document `to` in the Observation::id hash input, matching the
  addressed-delivery plan.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 05:09:07 -07:00
kyleandClaude Opus 5.5 cc39318673 fix(core): harden exchange send path from full-codebase review
- Cut every fragment of a stream from one encoding (age frozen at the
  first fragment) and bounds-check the slice: re-encoding mid-stream
  corrupted fragments and could panic when the tick went backwards.
- Fold the bloom summary to fit the MTU instead of sending a ~540-byte
  frame that small-MTU links reject; drop the sim's summary MTU
  exemption that hid this.
- Finish a peer's in-flight stream before starting another, and count
  an addressed message as delivered only after its last fragment.
- Alternate custody and broadcast per peer, rotate through addressed
  candidates, and evict the oldest bounce record when full, so neither
  path starves.
- Add Exchange::on_send_error; Node::poll uses it so Busy/Unreachable
  sends are retried rather than silently dropped.
- Clear bounce records per contact, record duplicates against their
  sender, and skip addressed messages that cannot fit the MTU.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 05:08:08 -07:00
kyleandClaude Opus 5.5 111a40236d ci: test the no-alloc build and harden the workflow
Run clippy, docs and tests for tines-core with --no-default-features,
gating the MemStore-based tests on `alloc` so they compile there. Fail on
rustdoc warnings, pass --locked everywhere, and pin the toolchain to
1.99.0. In the workflow, use read-only permissions, cancel superseded
runs, add a 20-minute timeout, and pin actions to commit SHAs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 05:07:12 -07:00
kyle d10e3cd88e test(core): close deferred coverage gaps 2026-10-18 04:28:38 -07:00
kyle 277daae89f fix(core): rebuild_send_queue excludes addressed messages 2026-10-18 03:57:08 -07:00
kyle 1d646344e6 style: cargo fmt --all 2026-10-18 03:28:51 -07:00
kyle db39dc1df0 feat(core): Node::observe_to and belief excludes addressed-to-others 2026-10-18 02:56:17 -07:00
kyle 8ad0a7415f feat(core): addressed store-and-forward in the exchange 2026-10-18 02:39:36 -07:00
kyle c59834737b feat(core): reject expired or malformed addressed messages 2026-10-18 02:03:52 -07:00
kyle 2c932e96c0 feat(core): summaries carry only broadcast observations 2026-10-18 01:55:29 -07:00
kyle 65c4aa72c0 feat(core): Store::for_each_broadcast_id 2026-10-18 01:49:01 -07:00
kyle 085b4d35da feat(core): Observation gains to/lifetime_ms; identity includes to 2026-10-18 01:41:24 -07:00
kyle 16198b4f10 chore: make crates publishable (version req, description, authors) 2026-10-18 01:16:36 -07:00
kyle 1a02ef2d93 docs: fix broken intra-doc links; make cargo doc clean 2026-10-18 01:12:19 -07:00
kyle 946a3cec98 chore: license under Apache-2.0 (copyright K. Isom) 2026-10-18 00:01:48 -07:00
kyle 1257f227ce refactor(sim): move FrameBuf on dup; vary per-node obs; test disconnect/unreachable 2026-10-17 23:58:15 -07:00
kyle 1752bd23ff refactor(core): dedupe trim/belief comparators and emit_one; add coverage tests 2026-10-17 23:58:15 -07:00
kyle 80ccd25dde fix(core): reset reassembler slot on buffer overflow; drop tautological assert 2026-10-17 23:58:15 -07:00
kyle 09a676c243 docs: fix Bloom |1 placement and doubled 'The'; PairTransport 'broadcast' wording 2026-10-17 23:58:15 -07:00