How to use the OME TypeScript SDK for Verified Memory
@osyra/ome is the TypeScript client for OSYRA Verified Memory and OME.
Its offline cryptographic verifiers are useful today from an authorized source
install. Its RPC methods have transport implementations, but the supported
customer Edge route is not released.
Before you begin
You need:
- Node.js 20+.
- An authorized source checkout while
v1.0.0remains unpublished. - The authoritative workspace UUID from IAM.
- An Edge-issued bearer token for network work.
- A unique, caller-generated RFC 9562 UUIDv7 for each future claim.
- For offline verification, a detached preimage, signature, and independently trusted Ed25519 public key.
The current RPC transport authenticates with a bearer token only.
auth.signingKey is accepted by the constructor but request signing is
reserved and unwired; a signing-key-only client sends no authentication
credential.
Step 1 — Install
The package is not published yet. Contributors use the checked-out package. After the private release gate clears, authenticated internal consumers will install it from CodeArtifact:
npm install @osyra/ome \
--registry=https://osyra-273863930653.d.codeartifact.us-east-1.amazonaws.com/npm/osyra-npm/There is no anonymous public npm artifact today.
For verify-only consumers, the Apache-2.0 subpath avoids the transport surface:
import { verifyReceipt } from "@osyra/ome/verify";Step 2 — Validate tenancy and construct the client
The SDK only checks that workspace is non-empty. Parse the authoritative
value as a UUID in your application:
function requireEnv(name: string): string {
const value = process.env[name];
if (!value) throw new Error(`${name} is required`);
return value;
}
function requireUuid(name: string): string {
const value = requireEnv(name).toLowerCase();
if (
!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/u.test(
value,
)
) {
throw new Error(`${name} must be a canonical UUID`);
}
return value;
}import { OmeClient } from "@osyra/ome";
const client = new OmeClient({
workspace: requireUuid("OSYRA_OME_WORKSPACE"),
auth: { token: requireEnv("OSYRA_OME_TOKEN") },
});
try {
// A supported customer RPC route will be used here after the release gate.
} finally {
await client.close();
}Construction is synchronous and performs no I/O. The first RPC creates the
transport lazily. TLS is enabled by default; transport.tls: false is rejected
unless transport.allowInsecure: true is also set.
close() marks the client closed and drops its memoized service reference. It
does not currently drain the transport or wait for a grace period.
Symbol.asyncDispose calls that same immediate close() implementation.
Current constructor options
For a future write, configure only the supported KMS signer reference:
const writer = new OmeClient({
workspace: requireUuid("OSYRA_OME_WORKSPACE"),
auth: { token: requireEnv("OSYRA_OME_TOKEN") },
signer: {
issuerDid: requireEnv("OSYRA_OME_SIGNER_DID"),
keySource: { kmsKeyId: requireEnv("OSYRA_OME_SIGNER_KMS_KEY_ID") },
},
transport: {
endpoint: "api.osyra.ai:443",
tls: true,
timeoutMs: 30_000,
},
});Although the TypeScript type still exposes secretsManagerSecretArn, AP00011
marks that arm deprecated and rejects it with OSY-MEM-5149. Production claim
signing is KMS-only.
The options verifyLocally, transport.maxRetries,
transport.retryBackoffMs, and transport.shutdownGraceMs are currently
accepted/stored but not read by runtime behavior. Do not rely on them.
Step 3 — Construct the current claim shape
The public Claim has exactly the canonical nine short-key properties:
ws, id, sub, par, iss, ch, eh, kn, tsIt has no body property. The current mapper always sends an empty body to
AP00011. Accordingly, this API cannot round-trip arbitrary text memory today.
For a structurally consistent request, ch must commit to that empty body.
The service requires a 16-byte claim ID; it does not mint one when id is
empty. Require a caller-supplied UUIDv7:
import { createHash } from "node:crypto";
import type { Claim } from "@osyra/ome";
function requireUuidv7(name: string): string {
const value = requireUuid(name);
if (value[14] !== "7" || !/[89ab]/u.test(value[19] ?? "")) {
throw new Error(`${name} must be an RFC 9562 UUIDv7`);
}
return value;
}
const workspace = requireUuid("OSYRA_OME_WORKSPACE");
const issuerDid = requireEnv("OSYRA_OME_SIGNER_DID");
const emptyBodyHash = Uint8Array.from(
createHash("sha256").update(new Uint8Array(0)).digest(),
);
const claim: Claim = {
ws: workspace,
id: requireUuidv7("OSYRA_OME_CLAIM_ID"),
sub: requireEnv("OSYRA_OME_SUBJECT_DID"),
par: [],
iss: issuerDid,
ch: emptyBodyHash,
eh: new Uint8Array(32),
kn: "IngestClaim",
ts: Date.now(),
};TransferClaim appears in the type union for forward compatibility but has no
V1 wire encoding and is rejected.
Why the write is not shown as runnable
client.memory.writeClaim(claim) has an RPC implementation, but it is not a
supported product flow today:
- The public Edge bridge and mandatory SDK → Edge → AP00011 acceptance lane are pending.
- The current claim API cannot send a non-empty memory body.
- The method returns only the claim ID and discards the server's signed response, so no receipt is obtainable for offline verification.
When the route is repaired, the method's return type remains Promise<string>
and the caller-supplied UUIDv7 is the persisted ID. The service never
fabricates success on transport failure.
Step 4 — Read the body/hash projection honestly
memory.getClaimBody(claimId) is the truthful current read projection. The
following shows the SDK mapping for an internal acceptance environment; the
customer Edge route is still pending:
const bodyView = await client.memory.getClaimBody(
requireUuidv7("OSYRA_OME_CLAIM_ID"),
);
console.info("body bytes:", bodyView.encodedCbor.length);
console.info("content hash bytes:", bodyView.contentHash.length);The SDK checks SHA-256(body) == response.content_hash. That catches an
accidental mismatch between two values returned by the same server, but the
hash is unsigned. A malicious or compromised server can replace both body and
hash consistently. This is integrity consistency, not signature-backed
authenticity.
Avoid memory.getClaim(...) for evidence. Today it calls the same body RPC and
then manufactures unavailable fields:
sub: ""iss: ""par: []kn: "IngestClaim"ts: 0- empty
signatureandsignerDid encodedCborset to the raw body, not the detached claim preimage
That object satisfies a TypeScript interface; it is not a trustworthy SignedClaim.
Step 5 — Verify separate evidence offline
Because neither writeClaim nor getClaim yields the required signed
evidence, load the detached Form-B bytes, signature, and trust anchor from your
evidence channel:
import { verifyReceipt } from "@osyra/ome/verify";
function requiredBase64(name: string, expectedLength?: number): Uint8Array {
const bytes = Uint8Array.from(Buffer.from(requireEnv(name), "base64"));
if (expectedLength !== undefined && bytes.length !== expectedLength) {
throw new Error(`${name} must decode to ${expectedLength} bytes`);
}
return bytes;
}
const preimage = requiredBase64("OSYRA_OME_PREIMAGE_B64");
const signature = requiredBase64("OSYRA_OME_SIGNATURE_B64", 64);
const trustedPubKey = requiredBase64("OSYRA_OME_TRUSTED_PUB_B64", 32);
const ok = await verifyReceipt(preimage, signature, trustedPubKey);
console.info("receipt verifies:", ok);The trust anchor must be the real workspace key published through a channel you already trust (or a key resolved and authenticated out of band). Never use the receipt's in-band key or SDK testdata as the trust decision.
verifyReceipt returns false only for a genuine signature mismatch over a
well-formed preimage. Malformed/non-canonical CBOR, a wrong domain tag, or
wrong-length inputs throw typed verification errors.
Bundle-manifest verification
The SDK can verify an already-extracted COSE_Sign1 bundle manifest:
const ok = await client.verify.bundleManifest(
coseSign1Bytes,
formBPreimageBytes,
trustedIssuerKeys,
);This verifies the manifest signature leg. It does not open and validate an
entire .ome ZIP archive; the full archive walk and JMT re-derivation are not
implemented.
Completeness Witness verification
The current witness verifier checks the detached signature and, optionally, that the signed payload commits to an expected root:
const ok = await client.verify.witness(
witnessPreimage,
witnessSignature,
trustedPubKey,
expectedRoot,
);If the witness contains a non-empty gap_proofs array, the verifier throws
OmeNotImplementedError. The JMT gap-proof leg has no completed KAT and is not
silently accepted.
A JMT root is an insertion-order-independent commitment to the current key/value set. Against a trusted committed root it can expose substitution or omission from that state. It does not prove event order or detect a reordering of history that produces the same final set; temporal ordering and completeness require separate, verified witness evidence.
Current API status
| Surface | Current status |
|---|---|
| verifyReceipt(preimage, sig, trustedKey) | Wired local detached-signature verification. |
| client.verify.bundleManifest(...) | Wired for an already-extracted COSE manifest + Form-B preimage. |
| client.verify.witness(...) | Signature + optional-root check; throws on non-empty gap_proofs. |
| client.verify.proof(...) | Throws OmeNotImplementedError; real 16-ary JMT verifier/KAT pending. |
| client.getServerSpec(...) | RPC implementation; customer Edge route pending. |
| client.checkPolicy(...) | RPC implementation; customer Edge route pending; unreachable enforcer maps to fail-closed IMPLICIT_DENY. |
| memory.writeClaim(...) | RPC implementation; PRO/ENTERPRISE only; Edge route pending; caller ID required; empty body only; returns ID and discards receipt. |
| memory.getClaimBody(...) | Body + unsigned hash consistency check; not authenticity. |
| memory.getClaim(...) | Body projection wrapped in fabricated/default SignedClaim metadata; do not use as evidence. |
| memory.list(...) | RPC implementation; customer Edge route pending. |
| memory.exportBundleManifest(...) | Drains export stream and returns its real manifest; customer Edge route pending. |
| memory.tombstone(...) | RPC implementation; customer Edge route pending. |
| memory.batchWrite/exportBundle/importBundle, proof.prove, witness.build | Throw OmeNotImplementedError. |
Handling errors
The SDK exports a typed hierarchy rooted at OmeError:
import {
OmeError,
OmeNotFoundError,
OmeNotImplementedError,
OmeTransportError,
OmeWireFormatError,
} from "@osyra/ome";
try {
await client.memory.getClaimBody(requireUuidv7("OSYRA_OME_CLAIM_ID"));
} catch (error) {
if (error instanceof OmeWireFormatError) {
// Invalid UUID, malformed server result, or body/hash mismatch.
} else if (error instanceof OmeNotFoundError) {
// No claim in the authenticated workspace.
} else if (error instanceof OmeTransportError) {
// Route/TLS/network failure.
} else if (error instanceof OmeNotImplementedError) {
// A reserved surface was called.
} else if (error instanceof OmeError) {
// Other typed OME failure.
} else {
throw error;
}
}Subpath imports
// Verify-only consumer:
import { verifyReceipt } from "@osyra/ome/verify";
// Full memory client:
import { OmeClient } from "@osyra/ome/memory";
// Root re-export:
import { OmeClient, verifyReceipt } from "@osyra/ome";This guide documents the exact @osyra/ome v1.0.0 pre-release
implementation and the current AP00011 service wire contract as verified on
2026-07-23.