release(v4.9.0): relay-side encrypted blob primitive + SDK Profile namespace
Ships the Prism FR (encrypted-profile-storage-v4.9.md) as a generic relay-side encrypted blob primitive: deterministically-located, AEAD-sealed blobs keyed by a 32-byte slotId derived client-side via HKDF from the user's master key. Unlocks credential-only bootstrap of new devices into existing E2EE state — no QR, no physical access. Server: BlobStore interface + Memory/Sqlite/Postgres impls, createBlobRoutes for GET/PUT/DELETE /v1/blob/:slotId with TOFU pubkey auth and If-Match CAS (409/412 semantics). Mounted on the same Hono app as the inbox; SHADE_BLOB_PG_URL / SHADE_BLOB_DB_PATH / SHADE_DISABLE_BLOB env-var plumbing in standalone. SDK: createProfileNamespace high-level wrapper (HKDF derivation, random-nonce AEAD seal, slotId-bound AAD) + low-level BlobClient. Cross-platform test vectors in test-vectors/blob-storage.json. New errors: ConflictError (409), PreconditionFailedError (412). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@shade/core",
|
||||
"version": "4.8.5",
|
||||
"version": "4.9.0",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
|
||||
@@ -158,6 +158,30 @@ export class UnauthorizedError extends ShadeError {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 409 Conflict — caller wrote to a resource that already exists without
|
||||
* supplying an If-Match precondition. V4.9: the encrypted blob primitive
|
||||
* uses this to force read-then-write on already-occupied slots.
|
||||
*/
|
||||
export class ConflictError extends ShadeError {
|
||||
constructor(message = 'Conflict') {
|
||||
super('SHADE_CONFLICT', message);
|
||||
this.name = 'ConflictError';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 412 Precondition Failed — caller supplied an If-Match etag that does
|
||||
* not match the current state. V4.9: the encrypted blob primitive uses
|
||||
* this to surface stale-CAS so clients can re-read, merge, and retry.
|
||||
*/
|
||||
export class PreconditionFailedError extends ShadeError {
|
||||
constructor(message = 'Precondition failed') {
|
||||
super('SHADE_PRECONDITION_FAILED', message);
|
||||
this.name = 'PreconditionFailedError';
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Error → HTTP Status Mapping ────────────────────────────
|
||||
|
||||
/**
|
||||
@@ -180,7 +204,10 @@ export function errorToHttpStatus(error: unknown): number {
|
||||
return 400;
|
||||
case 'SHADE_REPLAY':
|
||||
case 'SHADE_DUPLICATE_MESSAGE':
|
||||
case 'SHADE_CONFLICT':
|
||||
return 409;
|
||||
case 'SHADE_PRECONDITION_FAILED':
|
||||
return 412;
|
||||
case 'SHADE_RATE_LIMIT':
|
||||
return 429;
|
||||
case 'SHADE_TIMEOUT':
|
||||
|
||||
Reference in New Issue
Block a user