First public release
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,154 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { overall, validateReport } from '@extant2000/evidence-record'
|
||||
import {
|
||||
assessStream,
|
||||
formatStream,
|
||||
keyVerdict,
|
||||
transit,
|
||||
type LinkPolicy,
|
||||
type Message,
|
||||
} from '../src/index.js'
|
||||
|
||||
const msg = (p: Partial<Message> = {}): Message => ({
|
||||
id: 'm1',
|
||||
sender: 'probe-a',
|
||||
sequence: 1,
|
||||
originAt: '2026-08-01T10:00:00Z',
|
||||
receivedAt: '2026-08-01T10:20:00Z',
|
||||
keyId: 'k1',
|
||||
where: 'downlink.log',
|
||||
...p,
|
||||
})
|
||||
|
||||
const policy: LinkPolicy = { keys: [{ id: 'k1' }] }
|
||||
|
||||
describe('expiry has two ends and one check cannot express both', () => {
|
||||
const expiring: LinkPolicy = { keys: [{ id: 'k1', notAfter: '2026-08-01T10:10:00Z' }] }
|
||||
|
||||
it('calls a key that expired IN FLIGHT its own thing', () => {
|
||||
// Sent 10:00 (valid), arrived 10:20 (expired at 10:10).
|
||||
expect(keyVerdict(msg(), expiring)).toBe('expired-in-flight')
|
||||
})
|
||||
|
||||
it('ACCEPTS it, because rejecting would drop legitimate traffic', () => {
|
||||
const { apply, report } = assessStream([msg()], expiring)
|
||||
expect(apply).toHaveLength(1)
|
||||
const f = report.findings.find(x => x.id.endsWith('/key-flight'))!
|
||||
expect(f.determination).toBe('not-applicable')
|
||||
expect(f.detail).toContain('rejecting it would drop legitimate traffic')
|
||||
})
|
||||
|
||||
it('still records it, because the key is no longer trusted for anything new', () => {
|
||||
expect(formatStream([msg()], expiring)).toContain('expired while in flight')
|
||||
})
|
||||
|
||||
it('rejects a key that was already invalid at origin', () => {
|
||||
const late = msg({ originAt: '2026-08-01T10:15:00Z', receivedAt: '2026-08-01T10:30:00Z' })
|
||||
expect(keyVerdict(late, expiring)).toBe('invalid-at-origin')
|
||||
expect(assessStream([late], expiring).reject).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('revocation ignores origin time entirely', () => {
|
||||
// The key was usually compromised before anyone noticed.
|
||||
const revoked: LinkPolicy = { keys: [{ id: 'k1', revokedAt: '2026-08-01T23:00:00Z' }] }
|
||||
expect(keyVerdict(msg(), revoked)).toBe('revoked')
|
||||
const r = assessStream([msg()], revoked)
|
||||
expect(r.reject).toHaveLength(1)
|
||||
expect(r.report.findings[0]!.detail).toContain('Revocation is not a schedule')
|
||||
})
|
||||
|
||||
it('rejects an unrecognised or absent key as unassessed', () => {
|
||||
expect(keyVerdict(msg({ keyId: undefined }), policy)).toBe('unknown')
|
||||
expect(keyVerdict(msg({ keyId: 'nope' }), policy)).toBe('unknown')
|
||||
const r = assessStream([msg({ keyId: undefined })], policy)
|
||||
expect(r.report.findings[0]!.determination).toBe('not-assessed')
|
||||
expect(r.reject).toHaveLength(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('ordering is by sender sequence, never by arrival', () => {
|
||||
it('rejects an older message that arrived after a newer one', () => {
|
||||
// Applying in arrival order would overwrite newer state with older,
|
||||
// and would look like it worked.
|
||||
const out = [
|
||||
msg({ id: 'm2', sequence: 2, originAt: '2026-08-01T10:05:00Z', receivedAt: '2026-08-01T10:10:00Z' }),
|
||||
msg({ id: 'm1', sequence: 1 }),
|
||||
]
|
||||
const r = assessStream(out, policy)
|
||||
// Sequence 1 sorts first, so 2 is applied and there is no regression.
|
||||
expect(r.apply.map(m => m.sequence)).toEqual([1, 2])
|
||||
})
|
||||
|
||||
it('returns apply order by sequence even when the input is shuffled', () => {
|
||||
const shuffled = [
|
||||
msg({ id: 'c', sequence: 3 }),
|
||||
msg({ id: 'a', sequence: 1 }),
|
||||
msg({ id: 'b', sequence: 2 }),
|
||||
]
|
||||
expect(assessStream(shuffled, policy).apply.map(m => m.sequence)).toEqual([1, 2, 3])
|
||||
})
|
||||
|
||||
it('tracks sequences per sender, not globally', () => {
|
||||
const two = [
|
||||
msg({ id: 'a', sender: 'probe-a', sequence: 9 }),
|
||||
msg({ id: 'b', sender: 'probe-b', sequence: 1 }),
|
||||
]
|
||||
expect(assessStream(two, policy).reject).toHaveLength(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('a duplicate is the delivery guarantee working', () => {
|
||||
const dup = [msg({ id: 'a', sequence: 1 }), msg({ id: 'b', sequence: 1 })]
|
||||
|
||||
it('fails a duplicate when the receiver is not idempotent', () => {
|
||||
const r = assessStream(dup, policy)
|
||||
const f = r.report.findings.find(x => x.id.endsWith('/duplicate'))!
|
||||
expect(f.determination).toBe('fail')
|
||||
expect(f.detail).toContain('the assumption of exactly-once is')
|
||||
})
|
||||
|
||||
it('accepts it when the receiver declares idempotency', () => {
|
||||
const r = assessStream(dup, { ...policy, receiverIsIdempotent: true })
|
||||
const f = r.report.findings.find(x => x.id.endsWith('/duplicate'))!
|
||||
expect(f.determination).toBe('not-applicable')
|
||||
expect(r.reject).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('accepts it when the message itself is idempotent', () => {
|
||||
const r = assessStream([msg({ id: 'a', idempotent: true }), msg({ id: 'b', idempotent: true })], policy)
|
||||
expect(r.reject).toHaveLength(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('transit', () => {
|
||||
it('rejects a message too old to act on, and says to record it anyway', () => {
|
||||
const r = assessStream([msg()], { ...policy, maxTransitSeconds: 300 })
|
||||
const f = r.report.findings.find(x => x.id.endsWith('/stale'))!
|
||||
expect(f.determination).toBe('fail')
|
||||
expect(f.detail).toContain('Record it; do not act on it')
|
||||
})
|
||||
|
||||
it('flags impossible clocks as unassessed, not as a fast message', () => {
|
||||
const back = msg({ originAt: '2026-08-01T10:20:00Z', receivedAt: '2026-08-01T10:00:00Z' })
|
||||
expect(transit(back).impossible).toBe(true)
|
||||
const r = assessStream([back], policy)
|
||||
expect(r.report.findings[0]!.determination).toBe('not-assessed')
|
||||
expect(r.report.findings[0]!.detail).toContain('neither transit time nor any expiry decision')
|
||||
})
|
||||
})
|
||||
|
||||
describe('conformance with the evidence-record standard', () => {
|
||||
it('an empty stream is not-assessed, and says what silence means', () => {
|
||||
const r = assessStream([])
|
||||
expect(overall(r.report)).toBe('not-assessed')
|
||||
expect(formatStream([])).toContain('silence is not evidence that nothing was sent')
|
||||
})
|
||||
|
||||
it('every conclusion carries a citation', () => {
|
||||
expect(validateReport(assessStream([msg(), msg({ id: 'b', sequence: 2 })], policy).report)).toEqual([])
|
||||
})
|
||||
|
||||
it('passes a clean stream', () => {
|
||||
expect(overall(assessStream([msg()], policy).report)).toBe('pass')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user