First public release
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
# 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
|
||||
|
||||
```
|
||||
npm install @extant2000/slow-lane
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT. Copyright (c) 2026 Extant 2000 LLC. See [LICENSE](LICENSE).
|
||||
Reference in New Issue
Block a user