<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>cat/lofi-node</title>
<subtitle>a self-hosted sync node for lofi apps: jazz sync over iroh, ticket-gated, with no static ip or cloud needed</subtitle>
<id>https://src.rest/cat/lofi-node/</id>
<link href="https://src.rest/cat/lofi-node/"/>
<updated>2026-10-17T16:59:28-06:00</updated><entry>
<id>b2c134a28358c7432596777812179114190d6eba</id>
<title>chore(ci): run the gate on src.rest with the native addon built from source</title>
<link href="https://src.rest/cat/lofi-node/commit/b2c134a28358c7432596777812179114190d6eba/"/>
<author><name>cat</name></author>
<updated>2026-10-17T16:59:28-06:00</updated>
<content type="text">forge attest runs .forge/ci in a clean worktree of each commit and signs the
result into refs/notes/ci; forge push sends it with the branch. It runs
deno task check, builds native/iroh-js with cargo into ~/.cache/lofi-node, and
runs deno task test against that build, so the iroh and jazz integration tests
exercise this commit&#39;s Rust source rather than the committed prebuilt.</content>
</entry>
<entry>
<id>9bc3319fe30d747921f27206ba6d5a5193f29894</id>
<title>Merge pull request #21 from FelineStateMachine/release/0.5.1</title>
<link href="https://src.rest/cat/lofi-node/commit/9bc3319fe30d747921f27206ba6d5a5193f29894/"/>
<author><name>FelineStateMachine</name></author>
<updated>2026-07-20T12:35:15-06:00</updated>
<content type="text">release: 0.5.1</content>
</entry>
<entry>
<id>1f65ea2e7b48c1faf3ee76a89210266267f2c72f</id>
<title>release: 0.5.1</title>
<link href="https://src.rest/cat/lofi-node/commit/1f65ea2e7b48c1faf3ee76a89210266267f2c72f/"/>
<author><name>Dami</name></author>
<updated>2026-07-20T12:33:22-06:00</updated>
<content type="text">Bump the package and native artifact manifest versions for the browser CORS patch release.</content>
</entry>
<entry>
<id>22f961e9b77cc34d385e024723b406cadde85201</id>
<title>Merge pull request #20 from FelineStateMachine/feat/browser-cors-sync-tickets</title>
<link href="https://src.rest/cat/lofi-node/commit/22f961e9b77cc34d385e024723b406cadde85201/"/>
<author><name>FelineStateMachine</name></author>
<updated>2026-07-20T12:27:18-06:00</updated>
<content type="text">Fix browser requests through the ticket gate</content>
</entry>
<entry>
<id>cd723ddabaf7c8660f1927543f64f2825dc79703</id>
<title>fix: support browser requests through the ticket gate</title>
<link href="https://src.rest/cat/lofi-node/commit/cd723ddabaf7c8660f1927543f64f2825dc79703/"/>
<author><name>Dami</name></author>
<updated>2026-07-20T12:23:55-06:00</updated>
<content type="text"></content>
</entry>
<entry>
<id>101161c9dc2f2a79907d902b0639bee6fd517408</id>
<title>Merge pull request #18 from FelineStateMachine/release/0.5.0</title>
<link href="https://src.rest/cat/lofi-node/commit/101161c9dc2f2a79907d902b0639bee6fd517408/"/>
<author><name>FelineStateMachine</name></author>
<updated>2026-07-19T12:08:55-06:00</updated>
<content type="text">release: 0.5.0</content>
</entry>
<entry>
<id>20568dadbb191c96446f264cd80421d505c5f84f</id>
<title>release: 0.5.0</title>
<link href="https://src.rest/cat/lofi-node/commit/20568dadbb191c96446f264cd80421d505c5f84f/"/>
<author><name>Dami</name></author>
<updated>2026-07-19T12:08:43-06:00</updated>
<content type="text"></content>
</entry>
<entry>
<id>0f11ec5c96182e60be936a1b06418beca3f4874d</id>
<title>Merge pull request #17 from FelineStateMachine/feat/connection-observability</title>
<link href="https://src.rest/cat/lofi-node/commit/0f11ec5c96182e60be936a1b06418beca3f4874d/"/>
<author><name>FelineStateMachine</name></author>
<updated>2026-07-19T11:26:44-06:00</updated>
<content type="text">feat(node): connection observability — last-seen per ticket, gated-connection status</content>
</entry>
<entry>
<id>09e5c098ccef383cc99d6ee906a87f92e229c25a</id>
<title>feat(node): connection observability — last-seen per ticket, gated-connection status</title>
<link href="https://src.rest/cat/lofi-node/commit/09e5c098ccef383cc99d6ee906a87f92e229c25a/"/>
<author><name>Dami</name></author>
<updated>2026-07-19T11:18:36-06:00</updated>
<content type="text">The gate stamps last-seen on every authenticated request and socket
close, reports it in stats(), and feeds a daemon-owned sidecar
(ticket-activity.json, dirty-flushed at most once a minute — never
tickets.json, which is CLI-written and mtime-hot-reloaded). node.status()
gains gatedConnections joined with ticket labels, and ticket list shows
last-seen from the sidecar, so revocation decisions can be grounded in
whether a device is actually still connecting.</content>
</entry>
<entry>
<id>c17df3e58a06c14e0d81fdf82015ab44b6b54e62</id>
<title>Merge pull request #16 from FelineStateMachine/feat/pop-tickets</title>
<link href="https://src.rest/cat/lofi-node/commit/c17df3e58a06c14e0d81fdf82015ab44b6b54e62/"/>
<author><name>FelineStateMachine</name></author>
<updated>2026-07-19T11:14:05-06:00</updated>
<content type="text">feat(gate): proof-of-possession sync tickets</content>
</entry>
<entry>
<id>536ce96839f1f0812c9341bd2c8e83bc7f5ca4d9</id>
<title>feat(gate): proof-of-possession sync tickets</title>
<link href="https://src.rest/cat/lofi-node/commit/536ce96839f1f0812c9341bd2c8e83bc7f5ca4d9/"/>
<author><name>Dami</name></author>
<updated>2026-07-19T11:12:00-06:00</updated>
<content type="text">A derived sync ticket can bind a device public key at the scope-down
exchange. On a bound ticket the bare secret opens only liveness and the
possession exchange; everything else requires a connect token minted by
a fresh ECDSA P-256 signature from the bound key, carried as a path
segment the jazz client preserves exactly like the ticket secret. An
exfiltrated ticket string alone no longer connects.

Challenges are single-use with a short TTL and one 401 shape for every
failure; connect tokens are stored hashed with a sliding lifetime, die
on node restart, and are purged with their ticket by the revocation
sweep. Unbound tickets and old clients keep exact bearer behavior;
tickets.json stays v1. Cross-repo signature fixtures accompany the
contract.</content>
</entry>
<entry>
<id>a33d52a00b42c0f3a297289329557dc1fabb5b5d</id>
<title>Merge pull request #15 from FelineStateMachine/release/0.4.0</title>
<link href="https://src.rest/cat/lofi-node/commit/a33d52a00b42c0f3a297289329557dc1fabb5b5d/"/>
<author><name>FelineStateMachine</name></author>
<updated>2026-07-18T23:38:32-06:00</updated>
<content type="text">release: 0.4.0 — the write-verdict taxonomy</content>
</entry>
<entry>
<id>60bf2219c27157c81cd293ce607823d46c526b00</id>
<title>release: 0.4.0</title>
<link href="https://src.rest/cat/lofi-node/commit/60bf2219c27157c81cd293ce607823d46c526b00/"/>
<author><name>Dami</name></author>
<updated>2026-07-18T23:36:46-06:00</updated>
<content type="text"></content>
</entry>
<entry>
<id>622044ff8522b36c8515fefb253a76e97b2b450f</id>
<title>Merge pull request #13 from FelineStateMachine/feat/write-expiry-groundwork</title>
<link href="https://src.rest/cat/lofi-node/commit/622044ff8522b36c8515fefb253a76e97b2b450f/"/>
<author><name>FelineStateMachine</name></author>
<updated>2026-07-18T23:13:46-06:00</updated>
<content type="text">feat(verdict): write-expiry groundwork — expired taxonomy, contract docs, upstream ask</content>
</entry>
<entry>
<id>ad9317eb213b1e5473ea4381fe1400406d268f3c</id>
<title>docs: reframe the expiry design record as the reference spec for lofi#145</title>
<link href="https://src.rest/cat/lofi-node/commit/ad9317eb213b1e5473ea4381fe1400406d268f3c/"/>
<author><name>Dami</name></author>
<updated>2026-07-18T23:03:14-06:00</updated>
<content type="text">The document records the vendor surface lofi would prefer if jazz offered
it; upstream wishes are labeled reference issues, and node-layer enforcement
is designed separately against the same contract. The wire-level sketch now
states the envelope fields and digest coverage precisely: no expiry field
exists in the submission path today, the batch digest covers only the member
manifest (so covering expires_at needs a v2 manifest), captured_frontier is
compatibility-only, and the rejection code is an open string.</content>
</entry>
<entry>
<id>e8684e96eb776819d0b23ad7d52bdee5b6c880b5</id>
<title>docs(site): the four-code taxonomy and the mechanism-neutral expiry contract</title>
<link href="https://src.rest/cat/lofi-node/commit/e8684e96eb776819d0b23ad7d52bdee5b6c880b5/"/>
<author><name>Dami</name></author>
<updated>2026-07-18T23:03:14-06:00</updated>
<content type="text">The taxonomy table now lists the store&#39;s full rejection-code space with per-
code semantics, records the transactional-offline boundary on the
re-derivability property, and states the expiry contract independent of the
enforcing component. The deadline&#39;s placement is motivated by what
enforcement may read (the declared deadline and an arrival clock, never
application payloads), matching lofi&#39;s trusted-server threat model.</content>
</entry>
<entry>
<id>3d85617558ed2d35ed18dd771e495c03b762e613</id>
<title>feat(verdict): classify transaction_conflict and permissions_head_missing as permanent</title>
<link href="https://src.rest/cat/lofi-node/commit/3d85617558ed2d35ed18dd771e495c03b762e613/"/>
<author><name>Dami</name></author>
<updated>2026-07-18T23:03:05-06:00</updated>
<content type="text">The pinned alpha&#39;s store emits four rejection codes through the batch-
settlement path: permission_denied, transaction_conflict,
permissions_head_missing, and invalid_batch_submission. The two newly
registered codes are pinned by harness tests that provoke them end to end:
a losing concurrent transaction (final, deterministic, never rebased) and a
write against a store whose schema was deployed without a permissions head
(both verdict surfaces, fate unaffected by a later permissions publish).
invalid_batch_submission stays unregistered: every trigger is an envelope
invariant the client runtime computes itself, so no rejection carrying it
can be observed from the public API; the registry documents why.

Refs #14</content>
</entry>
<entry>
<id>87a2232c6e695890a7353e1880b415f84e727b19</id>
<title>docs: draft the upstream ask for server-enforced write expiry</title>
<link href="https://src.rest/cat/lofi-node/commit/87a2232c6e695890a7353e1880b415f84e727b19/"/>
<author><name>Dami</name></author>
<updated>2026-07-18T22:34:56-06:00</updated>
<content type="text">Optional expiresAt on the batch cleartext envelope, engine-side
arrival-time comparison at the sync node, and an expired rejection
through the existing batch-settlement path with the three properties
permission_denied already satisfies.</content>
</entry>
<entry>
<id>994f08824b59da3542edfe9a1aaf5021bc9e66f2</id>
<title>docs(site): the expiry contract on the write-verdicts page</title>
<link href="https://src.rest/cat/lofi-node/commit/994f08824b59da3542edfe9a1aaf5021bc9e66f2/"/>
<author><name>Dami</name></author>
<updated>2026-07-18T22:34:56-06:00</updated>
<content type="text">Cleartext client-asserted deadline, arrival-time enforcement with a
two-minute skew tolerance, the honest-client threat model, and the
soundness rule that makes local compensation safe — stated as the
contract for when enforcement exists, with today&#39;s behavior (deadlines
ignored) called out plainly.</content>
</entry>
<entry>
<id>15339c579900e81fe280667ef5dd89b99f2088a0</id>
<title>feat(verdict): register expired as a permanent code</title>
<link href="https://src.rest/cat/lofi-node/commit/15339c579900e81fe280667ef5dd89b99f2088a0/"/>
<author><name>Dami</name></author>
<updated>2026-07-18T22:34:56-06:00</updated>
<content type="text">A write refused because it arrived past its declared deadline is final:
waiting or re-sending only arrives later still. The pinned alpha never
emits the code, so the entry is inert until a store does.</content>
</entry>
<entry>
<id>3efb3159ec58cb56fca6e91edf3dfad211f523d9</id>
<title>Merge pull request #11 from FelineStateMachine/feat/write-verdict-foundations</title>
<link href="https://src.rest/cat/lofi-node/commit/3efb3159ec58cb56fca6e91edf3dfad211f523d9/"/>
<author><name>FelineStateMachine</name></author>
<updated>2026-07-18T21:46:42-06:00</updated>
<content type="text">feat: write-verdict foundations — rejection spike, error taxonomy, uniform /health</content>
</entry>
<entry>
<id>7a7975f7c4b6719d1d9e3dd721d92ffa46d30a62</id>
<title>docs(site): write verdicts and the health/connection contract</title>
<link href="https://src.rest/cat/lofi-node/commit/7a7975f7c4b6719d1d9e3dd721d92ffa46d30a62/"/>
<author><name>Dami</name></author>
<updated>2026-07-18T21:13:36-06:00</updated>
<content type="text">New write-verdicts page: where verdicts come from, local refusal vs server
adjudication, the three rejection properties, at-least-once implications for
consumers, and the permanent/transient taxonomy. The HTTP surface page gains
the /health contract per connection mode and the client-side composition into
a connection observable.</content>
</entry>
<entry>
<id>23e82a1305713535a22a12bd136e8722658b79f0</id>
<title>test(health): GET /health answers in open, ticket, and peer modes</title>
<link href="https://src.rest/cat/lofi-node/commit/23e82a1305713535a22a12bd136e8722658b79f0/"/>
<author><name>Dami</name></author>
<updated>2026-07-18T21:13:36-06:00</updated>
<content type="text">Open mode: Jazz serves it on the public URL. Ticket mode: unauthenticated at
the top level and under a ticket base; revocation 401s the ticket path while
the top level stays 200. Peer mode: carried over the iroh tunnel. A dead
store degrades to 502, never a hang.</content>
</entry>
<entry>
<id>151d856137a1fc3f50ff527a5a4c72c7f521c76a</id>
<title>test(verdict): write-rejection harness — correlation, reconnect re-derivation, determinism</title>
<link href="https://src.rest/cat/lofi-node/commit/151d856137a1fc3f50ff527a5a4c72c7f521c76a/"/>
<author><name>Dami</name></author>
<updated>2026-07-18T21:13:36-06:00</updated>
<content type="text">The server adjudicates only stale-policy writes (the client runtime refuses
current-policy violations synchronously), so the fixture tightens permissions
while devices are offline. Pins three properties a client-side write journal
needs: rejections correlate by batch id on both verdict surfaces, unacked
writes re-sent on reconnect re-derive their rejection, and the verdict is
stable across reconnects and independent runtimes. The fallback event fires
once per runtime lifetime; re-derivation goes through wait().</content>
</entry>
<entry>
<id>9e0c98d19aa5cfd8afa7fc9b7243e035ceaeb26e</id>
<title>feat(verdict): mutation-error taxonomy — permanent vs transient, keyed by code</title>
<link href="https://src.rest/cat/lofi-node/commit/9e0c98d19aa5cfd8afa7fc9b7243e035ceaeb26e/"/>
<author><name>Dami</name></author>
<updated>2026-07-18T21:13:36-06:00</updated>
<content type="text">Consumers classify rejection codes through one exported registry instead of
string-matching reason text. The pinned alpha emits exactly one code over the
sync protocol (permission_denied, permanent); unknown codes classify transient
so an unrecognized code can never trigger irreversible compensation.</content>
</entry>
<entry>
<id>4cdab00eb626f5bb430d808dec32644050c3b894</id>
<title>Merge pull request #9 from FelineStateMachine/release/0.3.0</title>
<link href="https://src.rest/cat/lofi-node/commit/4cdab00eb626f5bb430d808dec32644050c3b894/"/>
<author><name>FelineStateMachine</name></author>
<updated>2026-07-18T17:42:40-06:00</updated>
<content type="text">release: 0.3.0</content>
</entry>
<entry>
<id>9305256419ff3757dada9ed501564ee3ac9b3563</id>
<title>release: 0.3.0</title>
<link href="https://src.rest/cat/lofi-node/commit/9305256419ff3757dada9ed501564ee3ac9b3563/"/>
<author><name>Dami</name></author>
<updated>2026-07-18T17:40:32-06:00</updated>
<content type="text">Since 0.2.1: the scope-down ticket exchange with parent-linked
revocation, owner-only modes for secret files with healing on load,
the site-voice docs adopted under docs/site, and the contract-accuracy
and operator-hardening documentation pass (proxy-log redaction,
secret-in-URL rationale, shell weight).</content>
</entry>
<entry>
<id>e04a403e165de0c3b9884a2a7db66ede755e8d62</id>
<title>Merge pull request #8 from FelineStateMachine/docs/contract-and-operator-pass</title>
<link href="https://src.rest/cat/lofi-node/commit/e04a403e165de0c3b9884a2a7db66ede755e8d62/"/>
<author><name>FelineStateMachine</name></author>
<updated>2026-07-18T17:36:16-06:00</updated>
<content type="text">docs: finish the contract-accuracy pass and the operator hardening docs</content>
</entry>
<entry>
<id>02e056f4180039c7d0907ea669968b33c0c85eca</id>
<title>docs: neutral posture — no issue references or self-history in rendered docs</title>
<link href="https://src.rest/cat/lofi-node/commit/02e056f4180039c7d0907ea669968b33c0c85eca/"/>
<author><name>Dami</name></author>
<updated>2026-07-18T17:33:09-06:00</updated>
<content type="text">Documentation addresses app authors and operators in the present tense;
provenance and history live in git, issues, and decision records. Drops
the issue citations from app-ticket and hosting, and replaces the
validation section&#39;s account of its own earlier wording with the one
fact an operator needs: the boot-time warning is benign.</content>
</entry>
<entry>
<id>1b14a9961dff4eceabe2cc88bb1b6f80b9a80e81</id>
<title>docs: finish the contract-accuracy pass and the operator hardening docs</title>
<link href="https://src.rest/cat/lofi-node/commit/1b14a9961dff4eceabe2cc88bb1b6f80b9a80e81/"/>
<author><name>Dami</name></author>
<updated>2026-07-18T17:27:42-06:00</updated>
<content type="text">Contract accuracy (lofi#122 §4): the last three places stating tickets
are stored passkey-encrypted as a blanket fact — README, the mod.ts
example, and app-ticket.md&#39;s enrollment flow — now state the shipped
custody: sealed at rest under a device-bound key, provision capability
split via the scope-down exchange and passkey-gated, boot ceremony-free.

Operator docs (lofi#122 §5): beyond-the-lan gains a proxy-log redaction
section with concrete Caddy and nginx configs (default access logs
collect /t/&lt;secret&gt; paths); tickets-explained records why the secret
rides the URL path (the browser WebSocket API cannot set headers) and
names the logging cost that choice carries; hosting-lofi-apps documents
the shell weight and precompression guidance for serving the app.

Part of FelineStateMachine/lofi#123 (D) / lofi#122 §4-§5.</content>
</entry>
</feed>
