Skip to content

Navigation Menu

Sign in
Sign up

Releases: thatSFguy/reticulum-specifications

v2026.08.31

Choose a tag to compare

@thatSFguy thatSFguy released this 30 Aug 02:25
03ed338
This commit was created on GitHub.com and signed with GitHub’s verified signature.
GPG key ID: B5690EEEBB952194
Verified
Learn about vigilant mode.

Verified against RNS 1.5.2 / LXMF 1.1.1 — all 21 tools/verify_*.py pass, the citation gate is clean at 0 errors, and the new drift gate reports every citation still pointing at the text it cites.

Read this if you followed a source citation in v2026.08.29 or v2026.08.30. Both of those tags carried 52 citations pointing at code they did not mean. No normative claim was affected — every one of them is a wrong pointer, not a wrong statement — but a reader who followed one to check a constant or a branch would have landed somewhere unrelated and reasonably concluded the spec was wrong. That is fixed here, along with the gap that let it ship green.


The 52

Two separate defects, both introduced by the tooling used for the RNS 1.5.2 bump.

36 citations were never remapped at all. The one-off script used for the bump filtered its work list on path.startswith("RNS/"), so every citation written as `Transport.py:133` rather than `RNS/Transport.py:133` was skipped and left holding a 1.5.0 line number:

citation meant (1.5.0) landed on (1.5.2) corrected to
Transport.py:133 PATH_REQUEST_GRACE = 0.4 blank line :136
Transport.py:190 max_pr_tags = 16000 blackholed_identities = {} :195
Resource.py:481 if self.waiting_for_hmu: self.data = None :490
Resource.py:868 the windowed hashmap search fast_rate_rounds / window_max :877

Each is remapped by content — the 1.5.0 line's text located in 1.5.2 — with 60 line references verified content-identical across the two installs and none mismatched. Resource.py:1101-1107 fell inside a changed hunk and was resolved by hand: the RESOURCE_RCL cancel block now cites Resource.py:1113, the line that actually carries the claim.

15 anchored ranges had stale start lines. check_anchored accepts an anchor found anywhere inside the cited range, so a range whose start has drifted but which still contains its anchor passes silently and --fix never renumbers it. RNS/Resource.py:564-674 sat nine lines stale with a green check.

Five anchors remain deliberately mid-range. All are in files byte-identical across the pin (Destination.py, LXMF/LXMessage.py), so they cannot be drift.

Why nothing caught it, and what now does

Both classes are invisible to check_citations.py by construction:

  • a line-only citation is bounds-checked, so a stale number that still exists in the new version resolves happily — pointing at whatever now occupies it;
  • an anchored range is satisfied by its anchor appearing anywhere inside it.

tools/check_citation_drift.py asks the question that catches both: does each citation still point at the same upstream text it pointed at before the bump? It resolves every citation twice — against the base branch at the old pin, and the working tree at the new one — and requires the source line to be identical. A citation correct before and after must satisfy that; one silently left behind cannot.

It is wired into resync-citations.yml ahead of the commit step, so a drifted citation blocks the auto-commit rather than being pushed, and no-ops when the pin is unchanged.

Replaying the real 1.5.0 → 1.5.2 bump with --fix run exactly as CI runs it:

--fix rewrote 46 drifted line number(s) ← reports success, exit 0
888 citation lines compared, 419 drifted ← the new gate, exit 1

Control, same documents and same pin on both sides: 849 compared, 0 drifted, 0 unpaired.

Citation coverage

Unchanged at 502 anchored (91%), 16 line-only, 6 continuation — this release moved pointers rather than adding anchors.

A note on what "verified" has meant here

v2026.08.29 said 215 references were "verified content-identical." That was true and it was not sufficient: the verification only covered the citations the buggy filter let through, and nobody checked the filter. The drift gate exists so that claim is made by a check that sees every citation, on every bump, rather than by the same script that did the work.

Assets 2
Loading

v2026.08.30

Choose a tag to compare

@thatSFguy thatSFguy released this 29 Aug 15:33
0ffe172
This commit was created on GitHub.com and signed with GitHub’s verified signature.
GPG key ID: B5690EEEBB952194
Verified
Learn about vigilant mode.

Verified against RNS 1.5.2 / LXMF 1.1.1 — all 21 tools/verify_*.py pass, and the citation gate is clean at 0 errors.

No errata. Nothing the spec claims changed in this release; what changed is where it points. Following the precedent set by the source-citation audit in v2026.08.28, citation corrections ship as an ordinary section rather than errata — no code built against v2026.08.29 needs a change.


Twelve citations were pointing at the wrong code

The pin bump in v2026.08.29 left every citation resolving and 78% of them anchored to the text they cite. Finishing the remaining 22% surfaced something the anchoring itself had been hiding: a dozen citations aimed at unrelated code. Every one had the same tell — the cited first line was blank, or a bare except / try: / return. Nothing points at whitespace on purpose.

citation pointed at actually
Transport.py:133 blank line :136 PATH_REQUEST_GRACE = 0.4
Transport.py:1877-1882 discovery_path_requests code :1967-1968 where from_local_client is computed
Transport.py:2670-2679 link-request proof error handler :2735 the reverse_table.pop
Transport.py:2764 def synthesize_tunnel :2826 [tunnel_id, interface, paths, expires]
Transport.py:3417-3422 path-request error handler :3495-3497 the local-client grace bypass
Transport.py:4083-4086 except Exception as e: :4158-4161 the IDX_TT_* constants
Transport.py::path_request the if not packet.unpack(): guard the discovery_path_requests assignment
Resource.py:401/408 blank :410/:417 the get_random_hash() prefix sites
Resource.py:729/793 blank :738/:802 the conclusion checks
Resource.py:737 blank :746 os.unlink(self.meta_storagepath)
Resource.py:868-870 sets window_max from fast_rate_rounds :877-879 the receiver's windowed hashmap search
Resource.py:1030-1071 the send loop's exception handler :1039-1051 the if wants_more_hashmap: branch

Two of these were made worse before they were made better: anchoring cements whatever a line currently points at, so Transport.py:2764 and ::path_request were briefly given anchors that were mechanically valid and semantically wrong. They surfaced because the anchor made the mismatch legible — a bare line number never reads as absurd, which is the argument for anchoring in one line.

Anchoring coverage: 78% → 91%

v2026.08.29 v2026.08.30
anchored 429 (78%) 502 (91%)
line-only 76 16
continuation 19 6

Three additions to tools/anchor_citations.py got there:

  • Symbol-scoped anchors. path::symbolsnippet resolves by searching only the symbol's body, so it carries no line number at all — an upstream move, reformat or length change all leave it valid. The tool had been rejecting these because it tested uniqueness across the whole file: if self.type == Destination.GROUP: appears in both encrypt and decrypt, but exactly once in each.
  • Substring anchors. find_snippet matches by containment, so an anchor need not be a prefix — which the tool had been assuming. The distinguishing part of a long line often arrives late; a 186-char MDU = math.floor(...) needs only the fragment that differs from its siblings, not the whole statement. This required a guard the prefix search never did: unfiltered, substring search picks English out of log strings — 'transport link', 'request. The' — unique today and meaningless after any reword.
  • Range-start normalisation. A range beginning on a blank line or bare else: aimed its anchor at whitespace. The start advances to the first meaningful line still inside the range. Single-line citations are deliberately left alone: moving one changes what is cited, which is a judgement call rather than a normalisation.

What deliberately stays line-only

16 citations, and they are correct as they stand. 14 are lines whose text is byte-identical elsewhere in the same functionnew_raw += struct.pack("!B", packet.hops ...) appears verbatim at Transport.py:2159 and :2659, both inside _inbound. No snippet can distinguish them, so the line number is the only thing that can. The other two are a bare return at Transport.py:2166, where §13's erratum is specifically about that return, and one line still over the 70-char anchor cap.

Also ruled out along the way: multi-line anchors do not work here. find_snippet's 6-line window fallback returns the earliest window containing the text, up to 5 lines before the true start, so --fix would renumber backwards. Worth knowing before anyone reaches for it.

Loading

v2026.08.29

Choose a tag to compare

@thatSFguy thatSFguy released this 29 Aug 14:34
e865b91
This commit was created on GitHub.com and signed with GitHub’s verified signature.
GPG key ID: B5690EEEBB952194
Verified
Learn about vigilant mode.

Verified against RNS 1.5.2 / LXMF 1.1.1 — all 21 tools/verify_*.py pass, and the citation gate is clean at 0 errors.

No errata in this release. The last one carried six, three of them silent interop breaks. Nothing here invalidates code built against v2026.08.28: this is two new receiver rules in upstream, a large citation-integrity repair, and the automation that stops that repair from ever being needed by hand again.


Upstream pin: RNS 1.5.0 → 1.5.2 (#45)

Two receive-side bounds are new in 1.5.2. Both are drops, so a non-conformant sender sees silence rather than an error — which is the whole reason they are worth knowing about.

  • §2.2 — Packet.unpack rejects a zero-length data field. The minimums are now strict: the shortest conformant frame is 20 bytes (HEADER_1) or 36 (HEADER_2). This is only partly redundant with interface framing. TCPInterface has long refused frames of frame_len <= HEADER_MINSIZE, but HEADER_MINSIZE is 19 — so that bound never caught a zero-data HEADER_2 frame, which is 35 bytes and clears it comfortably. The unpack-time rule closes that case on every interface, including the ones that frame without a length floor at all (KISS, RNode, serial).

  • §4.5 — announce-specific ingress bound. Transport.preprocess_inbound drops an ANNOUNCE whose raw frame exceeds Reticulum.MTU (500) and counts a protocol violation, before validate_announce runs. This is not the general per-interface frame bound a few lines earlier, which measures against interface.HW_MTU — bitrate-derived, and reaching 524288 on a 1 Gbps link. Through RNS 1.5.0, an announce of up to half a megabyte arriving on a fast interface was carried all the way into signature validation before anything discarded it.

The matching emit bound turns out to be long-standing rather than new: Packet.pack raises above the packet's MTU, which for any non-LINK destination is Reticulum.MTU. A conformant sender could never produce an oversized announce; the new receive rule exists for the senders that aren't. §4.5 now states both halves, and tools/verify_ingress_bounds.py covers all three claims.

§2.2 also gained an upstream structural change. RNS 1.5.2 extracts the IFAC transform out of Transport.transmit into handle_outgoing_ifac (a bignum-XOR path) and handle_outgoing_ifac_legacy (the prior byte loop, verbatim); the receive side splits the same way into handle_ifac / handle_ifac_legacy. Wire bytes are unchanged — this is not an interop concern — but the section's prose and quoted block pointed into a function that no longer holds that code.

Link.py, Channel.py, Buffer.py and Destination.py are byte-identical between 1.5.0 and 1.5.2: no handshake or link-framing change.

Citation integrity: 215 references re-anchored (#45, #46)

The pin bump moved Transport.py by 75 lines and Resource.py by 12. check_citations.py --fix only re-anchors citations carrying a snippet anchor, and the 343 line-only ones were bounds-checked only — so a citation pointing at real but wrong code passed CI. The sharpest instance: MAX_RANDOM_BLOBS cited at RNS/Transport.py:159, which in 1.5.2 is MAX_RECEIPTS = 1024. A reader following it would have concluded the cap was 1024 rather than 64.

215 line references are corrected, each verified content-identical between the 1.5.0 and 1.5.2 sources. That includes a class the checker could not see at all: bare prose references (line 511-613, at line 754) carrying no citation syntax.

Three corrections go beyond renumbering:

where was now
flows/receive-announce.md quoted and RNS.Identity.validate_announce(packet) at Transport.py:1653 upstream reads and announce_valid, at :2176 — wrong before this bump, not drift from it
§12.3 relay rewrite path_table[dest] path_entry
§2.2 IFAC Transport.transmit handle_outgoing_ifac / _legacy

Citations now repair themselves (#46)

tools/anchor_citations.py migrates a line-only citation into the anchored form, which carries the upstream text it points at:

before `RNS/Transport.py:162`
after `RNS/Transport.py:162` → `MAX_RANDOM_BLOBS = 64`

Anchored coverage 126 → 429 citations (23% → 78%); line-only 343 → 76.

The point is not tidiness. An anchor splits two cases that a bare line number collapses into one: upstream moved the code (repaired mechanically) versus upstream changed it (hard error, --fix refuses, a person reads it). The second is the spec asserting something upstream no longer says, and it is the only kind worth a human's attention.

Upstream bumps are now mostly automatic (#47)

.github/workflows/resync-citations.yml runs on a Dependabot or upstream-sync PR, repairs what is mechanical, and pushes it back to the bump branch. check_citations.py --fix also repairs stale Pinned against **RNS x.y.z** headers now — a version string that drifted is generated output for the same reason a line number is.

Measured on the same pin change, across this release:

auto-repaired left for a person
v2026.08.28 37 22
after #46 177 22
v2026.08.29 191 8

Ordering is deliberate: a header reading Pinned against **RNS 1.5.2** claims the document was verified against that version, so the job runs the full verifier suite before committing anything. A bump that breaks a wire-format claim is never auto-stamped as verified.

Documentation (#48)

README's "Source citations" section documents the three citation forms, the rule they follow — a line number is generated output, never input — and which failure case reaches a person.


Known limitations

  • Anchoring cements, it does not audit. Anchors were generated from the line numbers as they stood, so a citation already pointing at the wrong place now has a matching anchor for the wrong place. Three such were found in this release, and only because their anchors were absurd on their face — one cited an except clause. 8 anchors are currently a bare RNS.log(...) or a comment, which is the signal to look for. Tracked in todo.md.
  • 87 citations remain line-only. 85 of them sit inside a named symbol and want path::symbol, which carries no line number at all. Until that lands, --strict cannot be the CI gate.
Loading

v2026.08.28

Choose a tag to compare

@thatSFguy thatSFguy released this 28 Aug 16:15
b70ff5a
This commit was created on GitHub.com and signed with GitHub’s verified signature.
GPG key ID: B5690EEEBB952194
Verified
Learn about vigilant mode.

Verified against RNS 1.5.0 / LXMF 1.1.1 — all 20 tools/verify_*.py pass, plus the new citation gate.

Six errata in this release, and three of them are silent interop breaks. If you built against v2026.08.19, read the errata section first — nothing below it will matter if your stamps and propagation retrieval are already failing.


⚠️ Errata — code built against v2026.08.19 needs changes

Section The spec said Upstream actually does What breaks
§5.7.3 ticket stamp is SHA256(ticket || message_id)[:32] truncated_hash(...)16 bytes (LXMF/LXMessage.py:300) Every ticket-stamped message rejected, in both directions. No length error is logged; the message just reads as unstamped.
§5.8.3 /get returns msgpack.packb([time.time(), [bodies]]) a flat list of bodies (LXMRouter.py:1523, :1555-1556) Body #1 decoded as a timestamp, body #2 as the message list. Fails on the first non-empty mailbox. Served bodies are also stamp-stripped (:1549) — don't slice 32 bytes off again.
§5.6.1 canonical msgpack encoding is a SHOULD a MUST whenever the message carries a §5.7 stamp The signature covers the 4-element payload (:367) while the wire carries 5 (:381), so the receiver re-encodes (:758). One wide integer envelope voids every stamped message: signature_validated = False, and message_id diverges with it.
§12.6 tunnel entry [now, expires, paths, ...] [tunnel_id, interface, paths, expires] — only index 2 was right Transport nodes read the interface handle as an expiry. Tunnels never expire, or expire instantly. Leaf clients unaffected.
§12.3.2 / §16.1 / §7.2.2 MAX_RANDOM_BLOBS = 32; max_pr_tags = 32000 64; 16000 as of RNS 1.5.0 Announce blobs evicted twice as fast as upstream — mild rebroadcast amplification on busy destinations.
§9.10 / §4.1 the microReticulum random_hash deviation is live fixed upstream (Destination.cpp:272-280) Receiver-side workarounds carried for a bug that no longer exists. §9.10 is now a historical entry keeping only the clock-hygiene advice.

The first three were found by a clean-room Go implementation and are the ones that cost real days: each fails silently, none reproduces Python↔Python, and §5.6.1 additionally fails only after a Resource transfer completes and is proved — so it reads as a transport fault.


Upstream pin: RNS 1.4.2 → 1.5.0 (#34)

No wire-format change, but RNS 1.5.0 refactors Transport heavily (+379 lines) and splits Transport.inbound into inbound / preprocess_inbound / inbound_job / _inbound around a prioritized ingress queue. 142 of 360 line references were rewritten. Behaviour newly documented:

  • §13.1.1 (new) — the prioritized ingress queue: InboundQueues, the four TC_* traffic classes and per-class depths, and which stage runs on which thread.
  • §12.5.2 — relays now drop link packets arriving before the link is validated (IDX_LT_VALIDATED gate); the branch's return is no longer commented out, so link-addressed packets stop falling through to the announce / LRPROOF / reverse-table branches.
  • §2.2Packet.unpack length-checks destination_hash and transport_id instead of accepting a short slice.

Full source-citation audit — 39 corrections (#35)

Every one of SPEC.md's 366 citations rendered next to the code it points at and read, rather than re-anchored mechanically. Surfaced the §12.6 and §9.10 errata above. Verified against rns==1.5.0 / lxmf==1.1.1 / nomadnet==1.2.8, plus RNode_Firmware@d39339f, microReticulum@40fa628 and reticulum-lora-repeater@4adb57b for the non-pip sources.

Resource MTU sizing, receive-side bounds, stamp-cost range (#36) — closes #31, #32, #33

  • §10.2 step 6 / §10.4 — which quantities follow the negotiated MTU. Only the instance-level ones (link.mtu, link.mdu, resource.sdu) move with §6.6 negotiation; HASHMAP_MAX_LEN and COLLISION_GUARD_SIZE are import-time constants off Reticulum.MTU and MUST NOT be recomputed from a negotiated MTU. Each side computes its part count as ceil(size/sdu) from its own sdu, so a disagreement breaks the count, not just the per-part bound.
  • §10.4 / §16.9 — bound cumulative received bytes against t and abort on the part that overruns; if also bounding per-part size, derive it from the link's sdu, never the fixed Resource.SDU.
  • §5.7.4stamp_cost is None or 1..254; < 1 means "off", not malformed; >= 255 is refused by the setter, and the parse side does not re-validate.

NomadNet pin split out of the verifier environment (#37)

requirements-docs.txt now carries nomadnet==1.2.8 separately; CI does not install it and no verifier imports it. The §11.6.3 link-target table's bare-number Browser.py refs were re-anchored (they matched none of the audit's citation patterns and had gone unchecked).

Three clean-room findings closed (#41) — closes #38, #39, #40

Beyond the §5.7.3 and §5.8.3 errata above, §10.11 gained a receiver-side subsection (§10.11.1) — previously the sender was fully specified and the receiver was not:

  • The reassembly store is file-backed and keyed on the advertised o alone — per-node, not per-link. Two transfers presenting the same o on different links share one file.
  • Segments are concatenated in arrival order, never placed by i; i decides only which segment is final. Out-of-order segments each pass their own integrity check, so misordering is silent.
  • Retention is a 24 h idle deadline refreshed by every append, not a transfer budget. A deadline measured from the first segment is unmeetable — upstream imposes no wall-clock bound on a single segment.
  • l is unbounded on the wire, and so is the count of concurrent partial assemblies, all held for an unauthenticated peer.

New: CI citation gate (#41)

tools/check_citations.py resolves every source citation in SPEC.md and flows/ against the pinned install, and runs as the first CI step.

The rule: a line number is generated output, never input. A citation carries a verbatim upstream snippet as its anchor:

(`LXMF/LXMessage.py:277``self.stamp == RNS.Identity.truncated_hash(ticket+self.message_id)`)

--fix rewrites drifted line numbers, so a version bump becomes bump the pin, run --fix, review the diff. It refuses to touch a snippet that exists nowhere upstream — that is a behaviour change, not drift — and exits non-zero so a successful fix run cannot mask one. --selftest proves the checker still catches each failure mode, including the exact §5.7.3 text that motivated it.

Pin declarations are gated too: a document whose Pinned against **RNS x** header disagrees with tools/requirements.txt fails CI.

All 12 flow documents re-anchored (#41)

The gate's first run found 12 of 13 flow docs still declaring the pre-1.5.0 pin. All are now re-read against it; flows/ goes from 0 to 111 anchored citations and tools/citations-exempt.txt is empty. The drift was not cosmetic — most citations pointed at real code unrelated to the claim beside them. forward-announce.md §2 also presented the rebroadcast gate as one three-term boolean when upstream nests the rate check inside it.

§5.6.1 canonical msgpack (#42) — closes #43

Covered in the errata table. §5.6 additionally now records that upstream does not try both verification variants — it selects one, on stamp presence (LXMF/LXMessage.py:755). Trying both stays right for a receiver, but a sender has no raw-bytes fallback to rely on.


Verifiers: 16 → 20

New Covers
verify_resource_sizing.py §10.2 step 6, §10.4, §5.7.4 — MTU-derived vs fixed constants, absent receive-time part checks, stamp_cost range
verify_propagation_get.py §5.8.3 — flat-list response, stamp stripping, transient_id round-trip, purge round
verify_resource_segment_store.py §10.11.1 — o-keyed store, arrival-order concatenation, retention constants
verify_canonical_msgpack.py §5.6, §5.6.1 — MUST when stamped / SHOULD when not, including the unstamped control case

verify_stamps.py gained the ticket-stamp length assertions. Every new verifier is mutation-tested: reverting the upstream behaviour it checks turns it red.

Verification status

0 🔮 SPECULATION callouts remain. Three ⚠️ UNVERIFIED callouts stand, all marked and none touching this release's changes: app-layer FIELD_* byte shapes (§5.9), PN_META_* value types (§5.9), and the unpopulated test-vector categories (§11).

Loading

v2026.08.19

Choose a tag to compare

@thatSFguy thatSFguy released this 19 Aug 13:38
fe01b24
This commit was created on GitHub.com and signed with GitHub’s verified signature.
GPG key ID: B5690EEEBB952194
Verified
Learn about vigilant mode.

Verified against RNS 1.4.2 / LXMF 1.1.1 — all 16 tools/verify_*.py pass.

Pin bump and spec-wide citation refresh (#28)

  • tools/requirements.txt moves to rns==1.4.2 / lxmf==1.1.1. Neither upstream release touches a wire format; §7.2.3 and §12.6.1 note the one documented behavior change — RNS 1.4.2 skips offline interfaces in the recursive path-request fan-out.
  • Every File.py:line citation in SPEC.md, playbook.md, agent.md, todo.md, and flows/ was re-anchored on the symbol its surrounding prose cites and repointed at the new pins. Many Transport.py pointers were RNS 1.2.4-era and off by 30–210 lines; two pointed past end-of-file.

Errata: §5.8.2 propagation-node error values (#28)

The error table carried three wrong wire values from the section's introduction:

  • ERROR_THROTTLED is 0xf6, not 0xf2 — upstream leaves 0xf2 unallocated.
  • ERROR_NOT_FOUND is 0xfd, not 0xf5.
  • ERROR_INVALID_STAMP = 0xf5 was missing entirely.

The table now lists all eight constants and warns that the values are not contiguous. Recorded in the README errata under 2026年08月19日: a client built against the old table retries immediately instead of backing off on a genuine throttle, and mis-reads 0xf5.

§5.8.1 two-destination split and path-string corrections (#29)

  • The request handlers span two destinations, not one: /offer and /get are on lxmf.propagation (ALLOW_ALL), while the control paths live on a separate lxmf.propagation.control destination — name_hash 4576672b3f55049c2e46 — whose allow-list holds only the node operator's own identity hash.
  • The control-path strings are /pn/get/stats and /pn/peer/sync, not the bare /stats and /sync the table gave, and the previously undocumented /pn/peer/unpeer handler is now tabled with the others.
  • The control-path argument shape is documented: a bare 16-byte peer destination hash (not a msgpack wrapper), with ERROR_INVALID_DATA / ERROR_NOT_FOUND semantics per handler.

No errata entry for these: nothing built against the old text could ever have worked — the documented destination and paths were unreachable — so there is no deployed implementer code to warn.

New verifier: tools/verify_lxmf_peer_constants.py (#29)

Asserts the eight LXMPeer.ERROR_* response bytes, the documented 0xf2 gap, and the five request-path strings across both destinations, plus an audit that fails CI if upstream allocates an un-enumerated ERROR_* or *_PATH constant — the check that would have caught the §5.8.2 errata above.

De-flaked verify_stamps.py (#29)

The §5.7.3 wrong-ticket check failed roughly 1 CI run in 18: the randomly derived stamp cleared the 4-bit PoW target by chance (~6%), letting the fall-through PoW path rescue a stamp the ticket shortcut had correctly rejected. The stamp is now pinned to miss the target, making the rejection deterministic, and the check additionally asserts the shortcut itself didn't fire (stamp_value != COST_TICKET) — the actual §5.7.3 claim.

Loading

v2026.07.28

Choose a tag to compare

@thatSFguy thatSFguy released this 28 Jul 17:58
7bf9418
This commit was created on GitHub.com and signed with GitHub’s verified signature.
GPG key ID: B5690EEEBB952194
Verified
Learn about vigilant mode.

Verified against RNS 1.4.1 / LXMF 1.1.0 — all 15 tools/verify_*.py scripts pass, including the new §7.1 retry-constants check.

§7.1 retry-loop timing constants pinned (#26)

  • §7.1 — the LXMF outbound retry-loop timing constants are now stated in the spec with an upstream citation (LXMF/LXMRouter.py:30-34): PATH_REQUEST_WAIT = 7 s, DELIVERY_RETRY_WAIT = 10 s (constant across attempts — no exponential backoff), MAX_DELIVERY_ATTEMPTS = 5, MAX_PATHLESS_TRIES = 1. Cross-referenced to the full per-message retry state machine in flows/lxmf-outbound-retry.md, which already documented the correct values.
  • §7.1 — fixed two line citations that predated the 1.1.0 sync: the handle_outbound path-request preamble is at ~line 1777 (was ~1672) and the retry branch at LXMRouter.py:2736+ (was 2568+).
  • tools/verify_path_request.py gains verify_retry_constants(), asserting all four values against the live LXMRouter class so upstream retry-timing drift surfaces in CI.

Also in this release

  • New proposals/ directory with a draft upstream LXMF group-messaging proposal (sender keys over the existing pairwise E2E channel + a multi-recipient propagation-node extension). Non-normative — a discussion draft addressed to upstream, not spec content.
Loading

v2026.07.26

Choose a tag to compare

@thatSFguy thatSFguy released this 26 Jul 13:18

Verified against RNS 1.4.1 / LXMF 1.1.0 — all 16 checks in the 15 tools/verify_*.py scripts pass.

Upstream security note: RNS 1.3.9 (2026年07月19日) fixes a critical, undisclosed vulnerability in rnsh. It does not affect the wire formats this spec covers, but if you operate rnsh, update it immediately.

Spec sync (RNS 1.3.5 → 1.4.1, LXMF 1.0.1 → 1.1.0)

  • §2.4 — hop count is bounded: since RNS 1.3.8 receivers drop packets with hops ≥ 128 (PATHFINDER_M) as malformed. New verify_packet_header.py check locks in the 0..127 bound.
  • §2.4 — documented the opt-in local_hops_delta originator-privacy option (RNS ≥ 1.3.7): originated packets carry a random hops value in [2, 7] instead of 0, and originated HEADER_1 announces are rewritten to HEADER_2 with the node's own transport_id. Receivers MUST NOT infer originator-adjacency or exact path length from hop counts.
  • §10.3 — since RNS 1.3.9, ResourceAdvertisement.unpack hard-rejects ADVs with t > 3 ×ばつ MAX_EFFICIENT_SIZE (~3 MiB) at parse time.
  • §10.7 — HMU hardening (RNS 1.3.9): out-of-turn hashmap updates are ignored; empty ones cancel the transfer on both sides.
  • §10.9RESOURCE_RCL is now also sent on receiver-side abort (RNS 1.3.9), not just on advertisement reject.
  • LXMF 1.0.1 → 1.1.0 carries no wire-format changes (propagation-node sync behavior only); citations refreshed to 1.1.0 line numbers.
  • Source-cited line numbers re-checked in all touched sections; §10 source map refreshed to RNS 1.4.1; pins bumped in tools/requirements.txt.

Verified against PyPI wheels — signed 1.4.1 artifacts are distributed via rngit only, which this verification environment cannot fetch.

Loading

v2026.06.19

Choose a tag to compare

@thatSFguy thatSFguy released this 19 Jun 14:18

Verified against RNS 1.3.5 / LXMF 1.0.1 — all 15 tools/verify_*.py pass.

Spec sync (#16)

  • §4.3 — announce app_data is now a 3-element array [display_name, stamp_cost, [SF_COMPRESSION]]; receivers accept both 2- and 3-element forms.
  • §5.9 — new upstream fields: FIELD_REPLY_TO (0x30), FIELD_REPLY_QUOTE (0x31), FIELD_REACTION (0x40), FIELD_COMMENT (0x41), FIELD_CONTINUATION (0x42).
  • Reactions/replies follow upstream: emit 0x40 and 0x30/0x31.
  • CI — Dependabot + verify workflow added to catch upstream drift automatically.

Spec authority (#17)

  • agent.md §0 prime directive — the spec is authoritative; every claim must cite an admissible upstream source (markqvist/Reticulum, markqvist/LXMF, or markqvist/Sideband for app-layer shapes LXMF doesn't pin). No other client app — third-party or our own — is admissible. Apps conform to the spec, not the reverse.
  • Removed third-party app references (former §5.9.12 and related notes); the spec documents only upstream forms.
Loading

AltStyle によって変換されたページ (->オリジナル) /