release(v4.0.0): Shade GA — V3.x consolidation + audit prep
Some checks failed
Test / test (push) Has been cancelled
Cross-platform vectors / TypeScript vectors (bun) (push) Has been cancelled
Cross-platform vectors / Kotlin vectors (gradle) (push) Has been cancelled
Docker build and publish / docker (push) Has been cancelled
Publish / publish (push) Has been cancelled
Some checks failed
Test / test (push) Has been cancelled
Cross-platform vectors / TypeScript vectors (bun) (push) Has been cancelled
Cross-platform vectors / Kotlin vectors (gradle) (push) Has been cancelled
Docker build and publish / docker (push) Has been cancelled
Publish / publish (push) Has been cancelled
V3.1 → V3.12 consolidated and tagged for the first GA release. Wire format unchanged from 0.4.x — 4.0 peers interoperate with 0.4.x peers byte-for-byte. The version bump is semantic: audit-cycle complete, opt-in surface fully exposed, threat model refreshed for every new surface. Highlights: - All 24 @shade/* packages bumped to 4.0.0 in lockstep. - CHANGELOG 4.0.0 section is the canonical manifest of what landed. - THREAT-MODEL extended (§10 fingerprint gates, §11 WebRTC P2P, §12 Web-Worker boundary) + residual-risks table refreshed. - OpenAPI now covers all 27 routes: prekey, transfer, KT, inbox, bridge, observer, /metrics, /healthz, /ready. - MIGRATION 0.3.x → 4.0 documented + smoke-tested against shade migrate-storage on a real SQLite DB. - docs/audit/REVIEW-BUNDLE.md + SCOPE.md ready for external reviewer. - scripts/soak.ts harness for the GA-stable 2-week soak window. - All V*.md plans archived under docs/archive/ with Status: Done. - Voice/Video carved out into V5.0; 4.0 audit focuses on the frozen non-realtime stack. Tests: TS 1000/1000 + Kotlin 11/11 cross-platform vectors green. Docker: gt.zyon.no/stian/shade-prekey:4.0.0 builds and reports version 4.0.0 on /health. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
104
packages/shade-recovery/tests/helpers.ts
Normal file
104
packages/shade-recovery/tests/helpers.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* Test helpers — boot a local prekey server, mint Shade instances, and
|
||||
* pair them with an in-process delivery transport that calls
|
||||
* `shade.receive` on the recipient. Mirrors the pattern other Shade
|
||||
* test suites use, kept private to this package so we don't pull in
|
||||
* `@shade/server` at runtime.
|
||||
*/
|
||||
|
||||
import { createPrekeyServer, MemoryPrekeyStore, PrekeyServerEvents } from '@shade/server';
|
||||
import { SubtleCryptoProvider } from '@shade/crypto-web';
|
||||
import { createShade, type Shade } from '@shade/sdk';
|
||||
import type { ShadeEnvelope } from '@shade/core';
|
||||
import type { RecoveryDeliver } from '../src/setup.js';
|
||||
|
||||
export interface TestEnv {
|
||||
prekeyUrl: string;
|
||||
stop: () => void;
|
||||
}
|
||||
|
||||
export async function startTestPrekeyServer(): Promise<TestEnv> {
|
||||
const crypto = new SubtleCryptoProvider();
|
||||
const prekey = createPrekeyServer({
|
||||
crypto,
|
||||
store: new MemoryPrekeyStore(),
|
||||
disableRateLimit: true,
|
||||
events: new PrekeyServerEvents(),
|
||||
});
|
||||
const server = Bun.serve({ port: 0, fetch: prekey.fetch });
|
||||
const port = (server as unknown as { port: number }).port;
|
||||
return {
|
||||
prekeyUrl: `http://localhost:${port}`,
|
||||
stop: () => server.stop(),
|
||||
};
|
||||
}
|
||||
|
||||
export async function spawnShade(prekeyUrl: string, address: string): Promise<Shade> {
|
||||
return createShade({ prekeyServer: prekeyUrl, address });
|
||||
}
|
||||
|
||||
/**
|
||||
* In-process transport that delivers `(to, envelope)` to the named
|
||||
* Shade by calling `shade.receive(from, envelope)` on it. Matches the
|
||||
* `RecoveryDeliver` shape so it can be plugged into setup/guardian/
|
||||
* request flows.
|
||||
*
|
||||
* Construction order matters: register every party before delivering
|
||||
* so the lookup never fails. Use `addr` to keep the from-address
|
||||
* symmetric with what `Shade.send` uses.
|
||||
*/
|
||||
export class MemoryRecoveryTransport {
|
||||
private readonly directory = new Map<string, Shade>();
|
||||
/** Per-pair pending-deliveries chain to preserve ordering. */
|
||||
private readonly chains = new Map<string, Promise<unknown>>();
|
||||
/** Counters of how many envelopes flowed in each direction (for tests). */
|
||||
public readonly delivered: Array<{ from: string; to: string }> = [];
|
||||
/**
|
||||
* Optional drop-after-N policy used to simulate guardians that go
|
||||
* unreachable. Keyed on `to` address. Set with `dropAfter(addr, n)`.
|
||||
*/
|
||||
private readonly dropPolicies = new Map<string, number>();
|
||||
|
||||
add(shade: Shade): void {
|
||||
this.directory.set(shade.myAddress, shade);
|
||||
}
|
||||
|
||||
/** Drop envelopes addressed to `to` after the first `n` have flowed. */
|
||||
dropAfter(to: string, n: number): void {
|
||||
this.dropPolicies.set(to, n);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a `RecoveryDeliver` callback bound to `from`. Call this once
|
||||
* per Shade so its outbound sends route through the transport.
|
||||
*/
|
||||
bind(from: Shade): RecoveryDeliver {
|
||||
return async (to: string, envelope: ShadeEnvelope) => {
|
||||
const recipient = this.directory.get(to);
|
||||
if (recipient === undefined) {
|
||||
throw new Error(`MemoryRecoveryTransport: unknown recipient "${to}"`);
|
||||
}
|
||||
// Apply drop policy.
|
||||
const policy = this.dropPolicies.get(to);
|
||||
if (policy !== undefined) {
|
||||
if (policy <= 0) throw new Error(`MemoryRecoveryTransport: dropping for "${to}"`);
|
||||
this.dropPolicies.set(to, policy - 1);
|
||||
}
|
||||
this.delivered.push({ from: from.myAddress, to });
|
||||
// Serialize per (from→to) pair to preserve ordering.
|
||||
const key = `${from.myAddress}→${to}`;
|
||||
const prev = this.chains.get(key) ?? Promise.resolve();
|
||||
const next = prev.then(async () => {
|
||||
await recipient.receive(from.myAddress, envelope);
|
||||
});
|
||||
this.chains.set(
|
||||
key,
|
||||
next.catch(() => {
|
||||
// chain even on failures so subsequent sends don't deadlock
|
||||
return undefined;
|
||||
}),
|
||||
);
|
||||
await next;
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user