Files
slow-lane/README.md
T
Paul HittandClaude Opus 5.5 49f0ddae40 Publish to the GitLab package registry on version tags
The README's install line failed: the package was never published, and
nothing told npm where the @extant2000 scope lives. A tag like v1.2.3 now
publishes to this project's registry with CI_JOB_TOKEN, and the README
sets the scope's registry before installing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 16:42:39 -04:00

101 lines
4.2 KiB
Markdown

# SlowLane
Check signatures, ordering and duplicates on a link that is slow,
intermittent and out of order.
SlowLane assesses a batch of received messages and tells you which ones to
apply, in which order, and which to reject. It is pure, with no clock and no
network. `now` is never read: every decision comes from the timestamps
carried on the messages, so the same trace always produces the same verdicts.
## What it checks and why
Security checks written for a fast, ordered, exactly-once link break on a
link that is none of those things. Worse, they fail in a direction that looks
correct. Three cases:
1. Expiry checked at the wrong end of the flight. A message signed with a key
that was valid when it was sent can arrive after the key expired. Checking
the key at arrival rejects legitimate traffic, and on a link with hours of
transit that is most of it. Checking only at origin accepts a message
whose key is no longer trusted. Each check is wrong on its own, so
`keyVerdict()` checks both ends and returns `expired-in-flight` as its own
verdict. Such a message is accepted and recorded. Revocation is different
and ignores origin time entirely, because a key is usually compromised
before anyone notices.
2. Ordering by arrival. Arrival order is a property of the link, not of the
sender. Applying updates in receipt order overwrites newer state with
older and looks like it worked. The per-sender `sequence` is the only
ordering authority, and `apply` comes back sorted by it. A caller that
loops over the input array instead has brought the bug back.
3. At-least-once delivery meeting a handler that is not idempotent. On this
kind of link a duplicate is the delivery guarantee working. It is a
finding only when the receiver has not declared itself idempotent. The
duplicate is not the anomaly; the assumption of exactly-once is.
Also:
- Too old to act on. Past `maxTransitSeconds`, a message is rejected but
worth recording. The world it described has moved on.
- Received before sent. NOT ASSESSED. The clocks disagree.
- No recognised key. NOT ASSESSED and rejected, since nothing establishes who
sent it.
## Usage
```ts
import { assessStream, formatStream, keyVerdict, type Message } from '@extant2000/slow-lane'
const messages: Message[] = [
{ id: 'm3', sender: 'probe-a', sequence: 3, originAt: '2026-09-28T10:05:00Z', receivedAt: '2026-09-28T10:25:00Z', keyId: 'k1', where: 'downlink.log' },
{ id: 'm1', sender: 'probe-a', sequence: 1, originAt: '2026-09-28T09:00:00Z', receivedAt: '2026-09-28T10:30:00Z', keyId: 'k1', where: 'downlink.log' },
{ id: 'm2', sender: 'probe-a', sequence: 2, originAt: '2026-09-28T10:00:00Z', receivedAt: '2026-09-28T10:20:00Z', keyId: 'k1', where: 'downlink.log' },
{ id: 'm2b', sender: 'probe-a', sequence: 2, originAt: '2026-09-28T10:00:00Z', receivedAt: '2026-09-28T10:40:00Z', keyId: 'k1', where: 'downlink.log' },
]
const { apply, reject, report } = assessStream(messages, {
keys: [{ id: 'k1', notAfter: '2026-09-28T10:10:00Z' }],
maxTransitSeconds: 3600,
receiverIsIdempotent: false,
})
for (const m of apply) {
// sender order, not arrival order
}
```
`report` is an evidence-record `CapabilityReport`. Print it with
`formatStream()`.
## Sample output
Produced by running the example above and printing `apply`, `reject` and
each finding's determination and summary:
```
apply: m2, m3
reject: m1, m2b
not-applicable probe-a#1 was signed with a key that expired while in flight
fail probe-a#1 spent 90 minutes in transit
not-applicable probe-a#2 was signed with a key that expired while in flight
pass probe-a#2 accepted, 1200s in transit
not-applicable probe-a#2 was signed with a key that expired while in flight
fail probe-a#2 was delivered more than once
not-applicable probe-a#3 was signed with a key that expired while in flight
pass probe-a#3 accepted, 1200s in transit
```
## Install
The package is published to the GitLab package registry, so point the
`@extant2000` scope there first:
```
npm config set @extant2000:registry https://gitlab.com/api/v4/packages/npm/
npm install @extant2000/slow-lane
```
## License
MIT. Copyright (c) 2026 Extant 2000 LLC. See [LICENSE](LICENSE).