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>
101 lines
4.2 KiB
Markdown
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).
|