Skip to content

@deepfates/lync 0.3.0: one package, durable sync, ready to publish - #5

Merged
deepfates merged 38 commits into
mainfrom
fleet-wave-lync-20260708
Jul 13, 2026
Merged

@deepfates/lync 0.3.0: one package, durable sync, ready to publish#5
deepfates merged 38 commits into
mainfrom
fleet-wave-lync-20260708

Conversation

@deepfates

@deepfates deepfates commented Jul 9, 2026

Copy link
Copy Markdown
Owner

What this is

lync is a file format for keeping interaction history. Conversations, stories, and the notes people make about them. A .lync file is plain text, one JSON object per line, and each line is one event that never changes. You correct or retract something by adding a new event that points back at the old one, never by editing in place. Merging two copies means keeping every event that appears in either copy. There is no merge logic to get wrong, because two copies of the same event are the same event, and two different events that claim the same id are both kept and flagged out loud.

@deepfates/lync is the reference implementation, and after this change it is one package with no dependencies. It holds the parser, the event stores, the computed views, the loom and turn API, live sync, the lync command, and the sync relay. A program that only wants to read a .lync file installs nothing extra. The relay needs one library called ws, and it asks for that only if you actually run a relay.

Merging this says: this is @deepfates/lync 0.3.0, one name, one story, ready to publish.

What is in the box

  • The format spec, in FORMAT.md. It stands on its own. You do not need any of this code to implement lync in another language.
  • Conformance vectors, in test/vectors/v0. These are the test cases that let someone check a lync implementation written in any language. We treat them as a product, not as test plumbing.
  • The library. Parse, merge, compute views, run looms, keep an index, resolve reference links.
  • The lync command. init, append, verify, merge, view, serve, and sync.
  • The relay. A small server that passes events between people and stores each room as a plain .lync file you can open with any lync tool. You can also ask a running relay for its status: for each room, how many people are connected and how many writes are still waiting to reach disk.

Durable sync, and how we proved it

The point of sync is that what one person sees saved is really saved. This change makes that true and proves it. Three fixes and one proof.

The relay used to treat sending an event and saving an event as the same thing. If a disk write failed, the relay could still tell a reconnecting client it was up to date, and the client would skip the event forever. The relay now tracks which log it is on, and a client that reconnects after a restart starts over from a safe point instead of trusting a stale marker.

The receiving side used to record how far it had gotten before it had actually stored the event. Now it stores the event first, checks that the store succeeded, and only then records progress. A failed store freezes progress and reports the failure out loud instead of losing the event.

The relay used to keep a line only in memory if its disk write failed and the disk later healed without a restart. That line would stay off disk until the next restart. Now the relay retries the write on the next activity, so its saved log catches up on its own.

The proof is a test called the loss-free trial. It runs a real relay and three real clients, then disconnects a client and reconnects it, fails the relay's disk and heals it with no restart, and restarts the whole server. After each step it checks that every event anyone successfully wrote is in every client and on the relay's disk, and that every failure was reported out loud. It runs clean five times in a row with no flake. A reviewer confirmed each fix is load-bearing by removing it and watching the trial fail.

How it was checked

111 tests, green on every commit. Every code example in the README is run by CI, so a broken example fails the build. A fresh-clone script proves the install and first use. The whole change went through an adversarial panel on the package shape, a steel-on-steel debate, and repeated hostile review of the sync code, which is the code most able to lose data quietly. Each real bug it found is now a test.

The edges we looked at, and where they landed

Both loose ends from the durability work are now settled, not left hanging. The relay had no way to show its own state, so we added the read-only status method above. And we looked at one more shutdown edge, where a graceful shutdown during a disk outage leaves a line to reach disk on the next restart, and chose not to touch it. Nothing is lost in that case because every client still holds the line, and the fix would have added risk to the shutdown path that the loss-free proof depends on. It is recorded as a deliberate decision, not an oversight.

After merge

You log in to npm, then the one package publishes. After that, splice and textile move from the vendored copy to the published package.

deepfates added 14 commits July 8, 2026 16:42
Sweep confirmed no /Users, /home, /private/tmp, /var/folders, or file://
home paths remain in test vectors, fixtures, docs, or configs (the one CI
caught in lore-views.test.ts was already fixed in 54aa178).

Add scripts/no-machine-local-paths.sh: git-greps tracked files for
machine-local absolute paths and exits non-zero if any reappear. Bare /tmp
stays allowed (mktemp/os.tmpdir use it portably). Wired into 'pnpm verify'
so it runs locally and in CI before tests.
A cold reader who clones and runs pnpm install && pnpm build has no 'lync'
on PATH, so every bare 'lync <verb>' in the headline first-mile example failed
with 'command not found'. Switch the block to 'pnpm exec lync' (the same
invocation the Development section and fresh-clone-smoke.sh already use) and
state up front that a global/published install drops the prefix. Re-ran the
whole block verbatim from a fresh worktree: init, append, verify, view
transcript, append to imported, merge, view tree all succeed; merged.lync is a
2-line deduplicated union.
Replace the escape-hatch typings in idb-log storage with concrete row
types. getAll now annotates IDBRequest<T[]> (no 'as T[]' cast), clearAndPut
is generic over the row type (no 'unknown[]'), and conflict/pending rows
carry an explicit ConflictRow/PendingRow type modeling the composite
[id, digest] key. dumpRecords() is called once per persist instead of
three times. tsc --noEmit clean, 94 tests green.
- add publishConfig.access=public to both scoped packages (scoped
  packages default to restricted; publish would fail without this)
- fix @lync/cli bin path (drop leading ./) so npm publish stops
  auto-correcting bin[lync] and warning it was removed
- add repository/homepage/bugs metadata for usable npm pages
- add per-package README so npm package pages are not bare

Proven in a clean-room: pnpm-packed tarballs installed in a fresh
npm project (no workspace links) exercise append/verify/lossless
divergent merge via @lync/core and all five CLI verbs via the lync bin.
The browser lore bundle pulled node:crypto, node:fs, and node:path. Two
independent leaks, both fixed at the source (no fs shims, no fakes):

- node:crypto: events.ts and store.ts hashed via createHash("sha256").
  Replaced with a dependency-free, synchronous pure-TS SHA-256 (lore/sha256.ts)
  that runs identically in Node and the browser. Verified byte-for-byte against
  node:crypto across NIST vectors, every padding-block boundary (len 0..200),
  random fuzz, and subarray views (lore-sha256.test.ts).

- node:fs/node:path: file-log.ts is a genuinely node-only file store, but
  looms.ts statically imported it (for createFileLoreLooms) and index.ts
  re-exported it, poisoning every browser-reachable entry and the main barrel.
  Moved createFileLoreLooms into file-log.ts (its node-only home) and dropped
  file-log from the index barrel. The node file store now lives only at the
  explicit @lync/core/lore/file-log subpath.

Result: @lync/core (barrel), /lore/events, /store, /idb-log, /views, /looms,
/memory-log, and @lync/client/browser all bundle for platform=browser with
zero node builtins. Wire kind strings (lore/*) untouched. README updated to
import createFileLoreLooms from the file-log subpath.
…7-08): @Lync npm org unavailable; prefix-as-namespace across workspace, docs, imports
Pre-handshake, re-send the idempotent join on a bounded interval; if the
handshake still hasn't completed, fail loudly and close so the reconnect
path takes over. Duplicate peer replies no longer re-announce the peer.

Honest status: fixes a real silent-hang path (join or peer-reply lost on
an OPEN socket left the adapter waiting forever), but does NOT cure the
suite flake — 3/30 full-suite failures remain, all correlated with
'[Lync] repo shutdown failed: DocHandle is not ready' bleeding from the
Automerge repo teardown (sync-server/src/index.ts:198-204 swallows it).
- front window is lync-core + lync-cli only; the Automerge transport
  (client ws-sync, sync-server, index), browser repo wiring, and the
  migration script move to branch attic/automerge-transport
- full public-API rename: parseLyncFiles, LyncUnion, serializeLyncEvent,
  createLyncLooms, lync* views; core subpaths flatten (lync-core/events,
  /store, /views, /looms, /file-log, /idb-log, /memory-log)
- wire cutover: kinds are lync/* (spec, vectors regenerated with fresh
  digests, tests); loom ids mint lync:; file store reads .lync only;
  no legacy .lore reading, no migration story
- lync-core has zero runtime dependencies; root automerge deps removed
- FORMAT.md drops the legacy-vocabulary caveat; README describes exactly
  what ships and nothing else
@deepfates deepfates changed the title Wave: cold-reader ready + publish prep — docs loop, browser purity, real types, clean-room proof, rename to lync-core/lync-cli lync 0.2.0: one vocabulary, one complete product — cutover + publish-ready Jul 9, 2026
deepfates added 6 commits July 8, 2026 18:45
The dumb event-union relay from the line-sync design: five frames
(sub/ev/live/presence/err), per-root append-only .lync storage, seq as
resume cursor, echo-safe under union, same-id conflicts surfaced to both
sides and never resolved, truncated tails sealed as damaged and surfaced,
presence relayed never stored, optional bearer-token auth. Client verb
converges any file one-shot or stays live with --follow (pushes local
appends, appends remote events, persists the cursor).

Protocol codecs are pure and dependency-free in lync-core/sync-protocol;
the ws wiring lives in the CLI. Acceptance from the design ticket holds:
two clients converge, offline resume is exact, kill-9 recovery surfaces,
unreachable relays fail loudly instead of hanging. 71 tests, 30
consecutive green full-suite runs including the live relay tests.
The CLI file-sync shipped earlier served terminal users; it did not serve
the real consumer story — an app that embeds lync, opens looms through an
index, and shows live collaborative edits in-process. textile is exactly
that (its data path is already union-based; its /lync socket was only a
heartbeat). This builds the missing piece.

lync-core:
- createSyncedStore(store, transport): wraps any EventStore. Local appends
  push to the relay; remote lines ingest through union. Looms and indexes
  recompute reactively via the store's existing subscribe — no merge logic,
  because immutable events + union-by-id make redundancy harmless.
- createWebSocketTransport: zero-dep, browser + Node (global WebSocket),
  reconnecting, queues sends while offline. Transport is an interface, so
  the decorator is testable without sockets.
- loomRootId(loomId): the store root for a loom, so apps can sync a loom's
  root before opening it.

Revived on the native protocol (Automerge dropped entirely):
- lync-index: the memory loom index (reactive subscribe), no automerge.
- lync-client: createLoomClient facade (looms + indexes + reference
  resolution), the automerge repo coupling and transport files removed.

Proof:
- core unit tests (mock transport): push, reactive remote ingest with no
  echo loop, status, reconnect re-push.
- cli integration test (real relay, two clients over global WebSocket): a
  turn on client A reaches client B's loom reactively, both directions.
- client facade test; index memory test.
- 83 tests, 30 consecutive green full-suite runs including live sockets;
  all four packages typecheck; fresh-clone smoke green.
- clean-room from tarballs: all four packages installed in a fresh project,
  a relay started from the bin, two embedded clients converge reactively.

Published surface is now four packages: lync-core, lync-cli, lync-index,
lync-client.
Extract the relay from the CLI into lync-server so apps can embed it:
- createLyncRelay({ dir, token?, authenticate?, onConnection? }): handleUpgrade
  / handleConnection / close — mount on any Node server.
- attachLyncServer(server, { storageDir, path, authenticate, keepAliveInterval,
  maxConnections }): adds an upgrade listener for one path, passes the rest
  through. This is the shape textile's server mounts.
- startLyncServe(): standalone, used by the CLI.
The CLI now imports startLyncServe from lync-server instead of its own copy.

lync-client gains createTestLoomClient (memory looms + index, deterministic
with createId/now) for app tests.

Tests: relay mounted on an app server converges two clients; attach filters
its path and passes others through; authenticate rejection blocks a client.
Published surface is now five packages. 86 tests, all typecheck.
Socket teardown and wss.close()/httpServer.close() callbacks can block
indefinitely on some runtimes (bun). Relay close now races an orderly
shutdown against a hard cap and flushes pending appends first; standalone
serve drops lingering connections, unrefs the listen handle, and caps the
close wait. Production embedding via attachLyncServer is unaffected (it
owns no server), but this keeps graceful shutdown honest everywhere.

86 tests, 20 consecutive green.
…ew fixes)

A rival-team review found a silent-black-hole path and two minor gaps:

- MAJOR: a single appendFile failure permanently wedged a room — the
  rejected writeChain skipped every future write, and the throw skipped
  the broadcast, so the room silently accepted events into memory while
  persisting and delivering nothing. Now appendSerialized clears prior
  rejections, keeps the chain resolved (recoverable), and reports ok/fail;
  the ev handler broadcasts regardless of persistence and emits a loud
  'persist-failed' err on durability failure. Nothing fails invisibly.
- recoverRoom no longer replays a damaged/sealed-truncated line as a
  phantom event: it stays on disk (never eaten) but is not served.
- close() flushes every pending append BEFORE the capped socket teardown,
  honoring the interface's durability promise.

Regression test: a read-only relay dir forces append failures; the room
still broadcasts both events and surfaces a persist-failed err for each,
proving live delivery survives and the failure is never hidden. Cosmetic:
test fixtures/descriptions cleaned of lore/automerge vocabulary.
87 tests, 15 consecutive green.
CI runs 'pnpm verify' = guard && test && typecheck with no build step, so
bare-specifier imports must resolve to src. lync-core and lync-index were
aliased but lync-server (imported by the CLI serve/sync tests) and
lync-client were not, so vitest failed to resolve lync-server's dist entry
in CI. Added the missing src aliases; verify now passes with no dist.
@deepfates deepfates changed the title lync 0.2.0: one vocabulary, one complete product — cutover + publish-ready lync 0.2.0: complete cutover — five packages, native sync, embedded reactive collaboration Jul 9, 2026
…nesty (dee-inzc)

A rival-family (codex) pre-publish review found what same-family review
missed, both with working repros:

BLOCKER — fractional resume cursors skipped the backlog silently: decodeFrame
accepted any nonnegative number for sub.since, the relay indexed
lines[0.5] = undefined (malformed ev frames), the client appended nothing,
then persisted the live seq — a PERMANENT missed backlog. Fixed at every
entry: isCursor() (nonnegative integer) enforced in decodeFrame for
sub.since / live.seq / ev.seq, and readCursor() resets an unusable cursor
file to 0 (re-receiving the backlog is a harmless union no-op).

MAJOR — same-id conflict variants could be silently dropped: the sidecar
append's failure result was ignored, so clients were told same-id-conflict
(implying retention) while the bytes vanished with only a server log. The
relay now emits conflict-persist-failed to sender and subscribers when the
sidecar write fails.

Regressions added for both (fractional cursor file → full backlog received,
cursor repaired; read-only dir → conflict-persist-failed surfaced).
90 tests, 10/10 consecutive green.
Docs (adopter-facing truth):
- README: resolve the TODO(positioning) marker that shipped on the front
  page; seven verbs (serve/sync were omitted — the flagship feature);
  lync-server added to the Packages section (it was absent entirely).
- lync-cli README: seven verbs, serve/sync described.
- ROADMAP rewritten to post-sync reality: no internal ticket ids, no
  Automerge-transition language, no publish-sequencing that contradicts
  the release.

Spec+writers (close the conformance gap both ways):
- FORMAT.md id row: generators mint UUIDv7; importers transcribing
  pre-existing events may derive deterministic UUIDv8 from source identity
  (re-import = union no-op; upstream edit = loud same-id conflict).
- Shipped writers now actually mint UUIDv7: new zero-dep lync-core/uuid,
  wired into looms' default createId and the CLI append fallback (was
  UUIDv4 randomUUID, nonconforming with our own spec).

Publish process hardening:
- engines: node >=22 on all five packages (synced-store needs global
  WebSocket; CI pins 22) and prepublishOnly build scripts so a stale or
  missing dist cannot ship.
- rm stray packages/sync-server dir (node_modules-only leftover).

91 tests green (adds uuidv7 shape/order/uniqueness test).
import.md writes down the shipped splice producer convention: deterministic
UUIDv8 from source identity (re-import = union no-op, upstream edit = same-id
conflict, and that's a feature), the five-axis author envelope with
imported_by never actor, source-namespaced kinds, complete-source payloads,
RFC 3339 at with surfaced repairs, opt-in marked for byte determinism, zero
silent drops with a reconciliation invariant, and verify-clean output.
Grounded in splice src/outputs/lync.ts + tests; example id is reproducible
from the stated recipe.

export.md codifies exports as regenerable projections that say what they
dropped: the two blessed families (readable transcripts via branch-tree/
transcript views; training data via the leaderboard view over score/selection
annotations, matching FORMAT.md's worked example), provenance columns back to
event ids, and the never-list (invent content, silently resolve conflicts,
leak suppressed payloads, claim authority). Honest v0: column schemas listed
as open points.
A stranger's first contact with lync is a package page on npm, not the
repo README. That surface was half-built:

- Per-package READMEs were stubs (152-731 bytes); core's example didn't
  even run. All five are now real pages: pitch, install, runnable
  examples, guarantees, links to the spec and pacts.
- No tarball shipped a LICENSE (root LICENSE doesn't ride files:[dist]).
  Every package now carries its own.
- Zero keywords anywhere; lync-core's description still said
  'addressable looms' (pre-cutover vocabulary). Fixed both.
- The repo README named pacts without linking them; import/export pacts
  are now discoverable.

Enforcement: scripts/readme-examples-smoke.mjs extracts the fenced
examples from every package README and executes them against the built
packages (self-reference resolution, scratch cwd). It already caught
four lies in my own drafts (upsert->addLoom, open->openReference,
missing toUrl location arg, wrong lync-index import path) before they
shipped. Wired into pnpm verify, so CI now fails if a doc example rots.
Replaces readme-examples-smoke. The old script carried a hardcoded list
of which fenced blocks to run and which to skip — configuration that
would drift the moment anyone adds an example. Now the READMEs carry
their own contract: every fenced ts/bash block executes by default; a
block that can't run alone declares itself with an
<!-- example: fragment — reason --> comment directly above the code
(invisible on npm), and long-running examples declare the output line
that proves startup. The checker is a dumb executor with no special
cases, and it also fails if a published package has no README or no
examples at all. cli merge story made self-contained (append creates
other.lync in-story) instead of the runner conjuring the file.
Refutation from the dee-07pu adversarial review (splice session-importer
port): parseLyncFiles ran an unmemoized recursive findCycle from EVERY
accepted id — per-step path copying and linear path scans made deep
parent chains (the exact shape codex session importers emit) ~10x
slower per doubling. Measured: n=1000 1.8s, n=2000 15.4s, n=4000 152.6s;
the largest real corpus file (107,711 chained events) extrapolated to
hours-to-days. Recursion also meant stack overflow was one deep loom
away.

Rewrite: one shared iterative three-color DFS over the whole graph.
O(events + parent edges); the 107k chain now parses in 1.6s (n=4000:
75ms). Each distinct cycle is reported once in canonical rotation
(smallest id first) instead of one rotation per member in file-line
order — cycle output is now order-independent, same disease family as
the leaderboard float-order note. 93/93 with the gauntlet shapes as
regression tests.
…ences carry chosen/shown

dee-aeq1: lyncLeaderboardView accumulated scoreTotal in file-line order, so
scoreMean's float addition order (and its low bits) depended on input file
order. The view now walks annotations by sorted id, making means bit-stable
across shuffled files; regression test parses the same events in two file
orders and asserts Object.is on scoreMean.

dee-oh98: LyncSelectionReference gains additive chosen/shown string-array
fields populated from the annotation payload (empty arrays when absent), so
consumers no longer re-read the annotation event to enumerate alternatives.
@deepfates

Copy link
Copy Markdown
Owner Author

...should this be one package no?

…ncies — truthfully

Design ruling dee-6prz (2026-07-12, 4-lens adversarial panel):
- Fold lync-index -> lync-core/indexes{,/entries,/memory,/types},
  lync-client -> lync-core/client{,/testing,/types},
  lync-server -> lync-core/relay, lync-cli -> the lync bin.
  Every prior core subpath kept; subpath parity is a consumer contract.
- NO ws declaration at all (an optional peer ws@^8 hard-ERESOLVEs consumers
  carrying ws@7; no declaration = that failure class cannot exist). The relay
  acquires ws lazily via createRequire inside the still-synchronous factory;
  a missing ws throws one helpful error. Bundlers must mark ws external.
- Kill the type leak: ws ships no .d.ts, so the relay's public types are
  structural (LyncRelaySocket) — tsc for a consumer without ws passes.
- cli sync rewritten onto Node's built-in WebSocket (engines >=22):
  EventTarget listeners, MessageEvent.data (arraybuffer, never Blob),
  close()-based hard-abort instead of terminate(). Sync tests unmodified
  in what they assert.
- One README telling one story: format first, conformance vectors as product,
  then library / command / relay. FORMAT.md gains the layering paragraph —
  sync protocols stay deliberately absent from the format.
- Version 0.3.0 deliberately: same-version tarball swaps defeat npm caching.
…eration that issued it (dee-u6tq)

recoverRoom mints a random generation id (never persisted: every restart is
a new generation), carried additively on ev/live frames. The CLI sync cursor
file stores {seq, generation}; a mismatch resets to 0 and resubscribes —
union makes the re-download duplicate no-ops. Stale lives from superseded
subs are counted, not trusted. Regression test is the bug's exact shape:
persisted events, a broadcast whose disk write failed consuming a seq, a
restart, a stale cursor — the client ends with every persisted event
(verified failing with the reset neutered).
…(dee-s6dc)

Frames apply strictly in arrival order (serialized chain). A store-write
failure freezes the root's cursor — live frames cannot leapfrog it — and
screams through the new additive SyncStatus.failures channel; the line is
re-fetched on the next resubscribe. Conflicts and garbage are surfaced too.
Also carries the generation reset (dee-u6tq) on the synced-store side, with
stale-live counting so a superseded sub cannot re-poison a reset cursor.
Neuter-verified: freezing disabled makes the regression tests fail.
…(dee-i1wc)

The synced store swallowed all relay errs except same-id-conflict — a
persist-failed durability failure fanned out but never reached the client's
status channel, so a store consumer could not see the relay lose a write.
Route every non-conflict relay err (persist-failed, conflict-persist-failed,
recovered-damaged-tail, line-without-id, server-error, ...) into the failures
channel. Nothing fails invisibly. Unit test asserts persist-failed surfaces
and is not miscategorised as a conflict.
A real relay plus real synced stores over the global WebSocket, run through
the full gauntlet against one shared root: (a) a client's socket drops
mid-stream and auto-reconnects; (b) the relay's .lync file goes read-only, so
a write is fanned out but refused by disk and surfaced as persist-failed; (c)
the server restarts into a new log generation. Final invariant: every event a
client successfully appended — including the one the dead disk refused — ends
up in every other client's store AND on the relay's on-disk .lync file, and
every failure (persist-failed, generation-changed) was surfaced on every
client's status channel. Neuter-verified: dropping the err surfacing fails
leg (b); neutering the generation reset fails leg (c).
A relay whose disk write failed kept the line in memory and broadcast it,
but a same-generation re-push was a byId duplicate no-op that never retried
the write. The durable log stayed silently incomplete until a restart
rebuilt byId from disk -- undercutting the format's thesis that the saved
log is the truth.

Now each room tracks its unpersisted lines (ordered by id = append order).
persistPending() drains them, in order, as one serialized unit before the
next append to the room, and a same-line re-push retries the write instead
of no-oping. A successful flush clears the line; a still-dead disk stops the
drain (later lines stay pending, never reordered) and re-surfaces
persist-failed. The on-disk log heals on its own, no restart required.

Proof: test/cli/loss-free-trial.heal-without-restart.integration.test.ts --
disk fails for X (X reaches all clients, off disk, persist-failed surfaces),
disk heals, another client appends Y to the same root with NO restart, and
both X and Y land on disk in append order and in every client's store. The
retry is load-bearing: neuter the pending flush and the leg fails.
@deepfates deepfates changed the title lync 0.2.0: complete cutover — five packages, native sync, embedded reactive collaboration @deepfates/lync 0.3.0: one package, durable sync, ready to publish Jul 13, 2026
Give operators a way to see inside a running relay. status() returns a
per-room snapshot { root, generation, seq, subscribers, pendingUnpersisted }
where pendingUnpersisted is the durability lag the dee-1pfp retry-persist
work introduced (lines in memory but not yet on disk).

Strictly additive and read-only: it reads existing room state via a side
map of resolved rooms and mutates nothing — no touch to the write path,
sync protocol, union path, or close(). Threaded through startLyncServe
(LyncSyncServer.status) and attachLyncServer (AttachedLyncServer.status);
LyncRoomStatus exported from the relay subpath.

Tests: N-room subscriber/seq accuracy; the durability-observability case
(disk write fails -> pendingUnpersisted >= 1, heal + next append -> back to
0); and that repeated status() calls leave seq/subscribers unchanged.
README relay section shows status() as a runnable daemon example.
The npm landing page opened with the dense technical summary. It now
opens with the human version: most software forgets; lync keeps every
version by only ever adding, never editing; and everything good (full
history, safe merging, honesty on conflicts, a file that outlives its
tools, training data from your own choices) falls out of that one rule.
The precise spec, the conformance vectors, and every runnable example
are unchanged below it. check-readme-examples still passes.
@deepfates
deepfates merged commit c35d09e into main Jul 13, 2026
2 checks passed
@deepfates
deepfates deleted the fleet-wave-lync-20260708 branch July 13, 2026 09:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant