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:
@@ -1,5 +1,6 @@
|
||||
import type { StorageProvider } from '@shade/core';
|
||||
import { ConfigurationError } from '@shade/core';
|
||||
import type { ObservabilityHook } from '@shade/observability';
|
||||
|
||||
/**
|
||||
* Shade SDK configuration.
|
||||
@@ -56,6 +57,43 @@ export interface ShadeConfig {
|
||||
/** Path prefix to mount under (default: "/shade-observer") */
|
||||
basePath?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Optional OpenTelemetry observability hook. Build with
|
||||
* `withTracer(otelTracer)` from `@shade/observability`. Default: off.
|
||||
*
|
||||
* The hook propagates to the session manager (encrypt/decrypt spans),
|
||||
* transfer engine (upload/download), and `@shade/files` (per-op).
|
||||
* `withTracer()` is itself a no-op unless `SHADE_OTEL_ENABLED` is set,
|
||||
* so leaving this configured in code stays free in production until
|
||||
* the env-var flips it on.
|
||||
*/
|
||||
observability?: ObservabilityHook;
|
||||
|
||||
/**
|
||||
* Optional Key-Transparency verifier (V3.12). When set, every
|
||||
* `fetchBundle` validates the server's inclusion proof against the
|
||||
* pinned `logPublicKey` and feeds the STH into a `LightWitness` for
|
||||
* split-view detection.
|
||||
*
|
||||
* Modes:
|
||||
* - `'observe'` — verify proofs when present, do not fail when missing.
|
||||
* - `'observe-strict'` — require a proof on every successful and 404 response.
|
||||
*
|
||||
* Default: KT verification disabled. Set this to enable.
|
||||
*/
|
||||
keyTransparency?: ShadeKTConfig;
|
||||
}
|
||||
|
||||
/** Public configuration shape for `Shade.keyTransparency`. */
|
||||
export interface ShadeKTConfig {
|
||||
mode: 'observe' | 'observe-strict';
|
||||
/** Operator's pinned signing public key (32-byte Ed25519) — base64 or raw bytes. */
|
||||
logPublicKey: Uint8Array | string;
|
||||
/** Reject STHs older than this many ms. Default 24h. */
|
||||
maxStaleMs?: number;
|
||||
/** Cap on observed-STH cache. Default 1024. */
|
||||
witnessMaxStored?: number;
|
||||
}
|
||||
|
||||
export interface ResolvedConfig {
|
||||
@@ -69,6 +107,15 @@ export interface ResolvedConfig {
|
||||
port?: number | undefined;
|
||||
basePath: string;
|
||||
};
|
||||
observability?: ObservabilityHook;
|
||||
keyTransparency?: ResolvedKTConfig;
|
||||
}
|
||||
|
||||
export interface ResolvedKTConfig {
|
||||
mode: 'observe' | 'observe-strict';
|
||||
logPublicKey: Uint8Array;
|
||||
maxStaleMs: number;
|
||||
witnessMaxStored: number;
|
||||
}
|
||||
|
||||
/** Parse and validate a ShadeConfig, resolving defaults and env var overrides */
|
||||
@@ -104,6 +151,42 @@ export function resolveConfig(input: ShadeConfig): ResolvedConfig {
|
||||
};
|
||||
}
|
||||
|
||||
if (input.observability !== undefined) {
|
||||
resolved.observability = input.observability;
|
||||
}
|
||||
|
||||
if (input.keyTransparency !== undefined) {
|
||||
const kt = input.keyTransparency;
|
||||
if (kt.mode !== 'observe' && kt.mode !== 'observe-strict') {
|
||||
throw new ConfigurationError(
|
||||
`keyTransparency.mode must be 'observe' or 'observe-strict'`,
|
||||
);
|
||||
}
|
||||
let logKey: Uint8Array;
|
||||
if (typeof kt.logPublicKey === 'string') {
|
||||
try {
|
||||
logKey = new Uint8Array(Buffer.from(kt.logPublicKey, 'base64'));
|
||||
} catch {
|
||||
throw new ConfigurationError(
|
||||
'keyTransparency.logPublicKey must be base64 or Uint8Array',
|
||||
);
|
||||
}
|
||||
} else {
|
||||
logKey = kt.logPublicKey;
|
||||
}
|
||||
if (logKey.length !== 32) {
|
||||
throw new ConfigurationError(
|
||||
`keyTransparency.logPublicKey must be 32 bytes (got ${logKey.length})`,
|
||||
);
|
||||
}
|
||||
resolved.keyTransparency = {
|
||||
mode: kt.mode,
|
||||
logPublicKey: logKey,
|
||||
maxStaleMs: kt.maxStaleMs ?? 24 * 60 * 60 * 1000,
|
||||
witnessMaxStored: kt.witnessMaxStored ?? 1024,
|
||||
};
|
||||
}
|
||||
|
||||
return resolved;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user