This directory contains the TypeScript/React and Rust clients used by Maple and internal OpenSecret applications. Both clients establish attested, end-to-end encrypted sessions with an OpenSecret backend and expose the API surface needed by those applications.
The developer/platform API remains part of the TypeScript SDK for internal OpenSecret workflows. This repository does not maintain or deploy a separate documentation website; keep behavior documentation close to the exported code and tests.
src/—@mapleai/sdk, including the React providers, encrypted API client, attestation policy, model/conversation APIs, and internal developer platform client.rust/— themaple-sdkcrate, imported asmaple_sdkby native clients.docs/PLATFORM.md— internal developer/platform API notes.- the
sdk-ts,sdk-rust, anddeps-sdklanes in the rootscripts/ci/lanes.py— TypeScript, Rust, and supply-chain validation for this directory, run by the rootci.ymlworkflow.
Maple's own Rust consumers (proxy, apps/maple-agent, the Research native
shell) build the in-tree Rust SDK through path dependencies, so SDK changes
are usable in this repository as soon as they merge; publication serves
external users. TypeScript consumers may use a file: dependency the same
way or pin a published version. The consumer's manifest and lockfile
determine what it builds.
Research uses the Rust SDK on desktop and mobile for native authentication;
the embedded proxy remains desktop-only. See the
consumer version policy
for switching sources and preparing client releases.
The package names are @mapleai/sdk and maple-sdk, first published at
TypeScript version 3.5.2 and Rust version 3.6.2. Their versions and publication
remain independent of Maple application releases.
The rename preserves the exported API, including OpenSecretProvider,
useOpenSecret, OpenSecretDeveloper and OpenSecretClient. OpenSecret remains
the backend name. Backend URLs, configuration variables, signed-PCR verification
and encrypted transport retain their existing contracts. Existing published
@opensecret/react and opensecret packages remain available to older consumers.
Both SDKs use Transport V2 starting at 4.0.0. Version 4 requires a backend
with Transport V2 support and does not fall back to V1. Applications
upgrading from V1 must establish a new session and ask existing users to sign in
again. Native authentication and hosted OAuth callbacks must use the matching
V2 flows; updating only one side of that handoff is insufficient.
The TypeScript SDK, Rust SDK, and Maple app are published independently.
package.json and rust/Cargo.toml declare
the in-tree SDK versions; each consumer's manifest and lockfile declare the
version and source it actually builds. Editing SDK source does not change a
registry-pinned consumer; use local links when validating an SDK change with
that consumer. See the publishing guide.
For non-local endpoints, both SDKs require HTTPS, verify AWS Nitro attestation, and enforce an environment-scoped PCR0 trust policy before completing key exchange. The SDKs bundle environment-specific PCR0 trust roots and the verification key used to authenticate signed remote history entries.
Mock attestation is limited to exact loopback development endpoints (plus the documented Android emulator alias in the Rust SDK). Do not weaken attestation, PCR0 validation, or encrypted transport to accommodate a caller.
Certificate validity is checked against the device clock, as AWS specifies,
after the chain and COSE signatures are verified. notBefore gets a
five-minute leeway (ATTESTATION_NOT_BEFORE_LEEWAY_MS in both SDKs) because
the enclave leaf certificate is issued without backdating and re-issued about
every 2 h 45 m, so a device clock a few seconds slow used to fail right after
each re-issue. notAfter is strict: a fast device clock is bounded by the
leaf's remaining validity (15 minutes to 3 hours), and freshness itself comes
from the per-handshake nonce. A failure raises AttestationClockSkewError,
whose message compares the device clock with the document's signed timestamp
and tells the user to check the device's date, time and time zone; the OAuth
handlers rethrow it instead of a generic provider failure.
The SDKs use operating-system or Web Crypto randomness for keys, nonces, and session material. Never substitute deterministic or convenience randomness in production paths.
Managed SDK requests can resend the original logical operation once after a
fresh, verified attestation handshake, only on outer HTTP 400 with exactly
x-opensecret-error-contract: 1 and x-opensecret-error-code equal to
session_not_found or request_decryption_failed. The backend marks only an
actually missing/expired session or failed AEAD authentication of the incoming
request, before application dispatch. The resend uses a new session and request
ID and applies to mutations and inference as well as reads.
These outer hints are unauthenticated. As with V1's best-effort recovery, an intermediary can forge one after the original operation executed and cause a duplicate under the new session. Per-session replay protection does not guarantee cross-session at-most-once execution; use application-level idempotency when needed.
Response decryption/framing failures, network failures, timeouts, partial
streams, redirects, generic 400/503, and ordinary application errors do not
trigger automatic resend. Prepared native-handoff redemption and session-bound
OAuth callbacks require restarting their flows. There is no V1 fallback, and
credentials remain inside encrypted requests. Token refresh remains proactive
and coalesced; an authenticated expired-access response may refresh credentials
for future operations without resending the failed operation.
Install the published version selected for your application and backend. The
4.0.0 pin below illustrates the command; it is not a current-version claim:
bun add --exact @mapleai/sdk@4.0.0Wrap the application with OpenSecretProvider and supply the backend URL and
project/client ID from the environment you selected. The SDK requires both
values; it does not select a backend for you.
| Environment | Backend configuration | Authentication for validation |
|---|---|---|
| Local | Actual HTTP loopback URL and port, with the project ID registered in that backend | Local email/password or guest fixtures; ordinary local validation does not require provider OAuth |
| Hosted development | HTTPS development backend, its project ID, and pcrConfig.environment = "development" |
A valid account in that development backend |
| Production | HTTPS production backend, its project ID, and production PCR policy | Intentionally selected production account and flow |
The client's API URL, PCR policy, browser OAuth origin, and callback scheme are separate settings. Pointing the API at loopback does not change a consuming app's hosted OAuth origin. Select a compatible auth flow in that app before validation. Keep secrets out of public frontend build configuration.
import { OpenSecretProvider } from "@mapleai/sdk";
import type { ReactNode } from "react";
export function AppProviders({
children,
apiUrl,
clientId,
pcrEnvironment
}: {
children: ReactNode;
apiUrl: string;
clientId: string;
pcrEnvironment: "production" | "development";
}) {
return (
<OpenSecretProvider
apiUrl={apiUrl}
clientId={clientId}
pcrConfig={{ environment: pcrEnvironment }}
>
{children}
</OpenSecretProvider>
);
}Use useOpenSecret for authentication, encrypted application APIs,
conversations, inference, and account operations. Internal developer tooling
uses OpenSecretDeveloper and useOpenSecretDeveloper; preserve that surface
when changing the public exports.
The three browser initiation methods accept an optional final callback URL:
const os = useOpenSecret();
await os.initiateGoogleAuth(inviteCode); // Existing provider default.
await os.initiateGoogleAuth(inviteCode, `${window.location.origin}/auth/google/callback`);
await os.initiateGitHubAuth(inviteCode, `${window.location.origin}/auth/github/callback`);
await os.initiateAppleAuth(inviteCode, `${window.location.origin}/auth/apple/callback`);The SDK forwards a supplied URL unchanged as redirect_url; an omitted
argument keeps the existing request shape. The backend validates exact
membership in that project's provider settings. Its platform settings types
also expose additional_redirect_urls?: string[] | null: when the provider
object is supplied, omission or null preserves the list and [] clears it.
See the backend contract.
Deploy backend callback-selection support and register the URL with the provider before selecting a non-default callback. An older backend ignores the new field and uses its default; the SDK does not silently retry with a different callback. Native Apple sign-in and the Rust SDK are unchanged.
systemOne answers typed questions (noul, choice, score) about a JSON
state with probabilities instead of text (POST /v1/systemone). model is
glm-5-3-flash by default (Continuum, calibrated, takes images) or one of
Tinfoil's decision-small, decision-medium and decision-large (text only,
up to 26 options per choice). It uses the signed-in session, or an API key
through options.apiKey; useOpenSecret().systemOne is the same function:
import { systemOne, SystemOneError } from "@mapleai/sdk";
try {
const { answers } = await systemOne({
state: { ticket: "Charged twice, need a refund today." },
questions: {
is_urgent: { type: "noul", instructions: "Does the customer need help today?" },
intent: {
type: "choice",
instructions: "What does the customer want?",
criteria: { refund: "money back", question: "information", praise: "thanks" }
},
frustration: {
type: "score",
instructions: "How frustrated is the customer?",
criteria: ["Low", "Medium", "High"]
}
}
});
if (answers.intent.type === "choice") {
console.log(answers.intent.choice, answers.intent.confidence);
}
} catch (error) {
if (error instanceof SystemOneError) console.log(error.status, error.code); // 422, system_one_*
}Choice options are numbered in the order of the criteria keys (JavaScript
keeps insertion order for non-integer keys). Optional request fields are
images (data: URLs shown with the state) and temperature (1 returns
raw probabilities). Rejections carry the backend's status (422 for schema
problems, 413 for size limits, chat's statuses for quota and capacity) and
its x-opensecret-error-code.
Use the pinned Nix shell and Bun version. bun.lock is the supported dependency
lockfile; repository installation and updates use Bun. npm is used only to
publish the built tarball, so do not create an npm lockfile.
nix develop --no-update-lock-file
bun install --frozen-lockfile --ignore-scripts
bun run format:check
bun run buildFor tests without a configured backend, use the credential-free selection in
build-sdk-publish.sh, which CI runs as
the sdk-ts lane (just lane sdk-ts from the repository root).
An unfiltered bun test also collects integration tests and requires the
fixtures described below.
Integration tests read the variables documented in .env.example. The monorepo
os-sdk-e2e lane (opensecret-sdk-e2e.sh)
migrates disposable PostgreSQL, starts the in-tree services/opensecret/ backend from
the same checkout on loopback, and creates disposable SDK fixtures. It does
not depend on the hosted development service or stored test-account credentials.
Tests that spend model/provider capacity are opt-in with RUN_LIVE_AI=1 and
are not part of the deterministic pull-request gate. Backend contract changes
and SDK changes are validated together against that checkout; released clients
and independently deployed backend versions still need compatibility review.
Both SDKs accept local roots first, then fetch their selected environment's
signed PCR history on a miss from
MaplePrivacyLabs/Maple/master/services/opensecret/: pcrProdHistory.json for
production and pcrDevHistory.json for development. The existing verification
key, custom history URL overrides, and redirect rejection are unchanged.
Embedded roots are versioned with each shipped SDK: editing or pruning in-tree
roots does not replace the roots in published packages. Older published SDKs and
installed clients still use the legacy
OpenSecretCloud/opensecret URLs, which remain a manual compatibility mirror.
Changing source defaults does not update those clients or publish an SDK. See
the backend compatibility procedure.
Inspect the publishable npm artifact with:
bun run packOnly dist/ is included in the package.
Publishing runs in GitHub Actions. To dispatch validation from sdk/, replace
VERSION with the exact TypeScript version committed in package.json on
master:
just publish-npm VERSIONThis defaults to a dry run. See the SDK publishing guide for the protected publish action and the one-time registry setup.
Add the selected published SDK version to a Rust application. This example
illustrates an exact 4.0.0 pin rather than declaring the current version:
[dependencies]
maple-sdk = "=4.0.0"Import the primary entry point with use maple_sdk::OpenSecretClient.
See rust/README.md for native
client examples and transport details.
Run the Rust validation from the sdk/ directory:
nix develop --no-update-lock-file -c bash -lc '
set -euo pipefail
cd rust
cargo fmt --all -- --check
cargo clippy --locked --all-targets --all-features -- -D warnings
cargo test --locked --all-features --lib
cargo doc --locked --no-deps --all-features
'These library checks do not require a backend or account credentials.
Integration tests use explicit backend fixtures and are separate from this
default validation path; see Rust testing and
opensecret-sdk-e2e.sh.
To dispatch validation from sdk/, replace VERSION with the exact Rust
version committed in rust/Cargo.toml on master:
just publish-cargo VERSIONThis defaults to a dry run. Both recipes only dispatch GitHub Actions; they do not build or publish packages locally. Each SDK has its own workflow and version, independent of Maple application releases. Follow the SDK publishing guide to publish.
- Keep the TypeScript and Rust attestation policies aligned intentionally; neither SDK's passing tests prove parity with the other.
- Treat API compatibility, authentication state, encrypted retry behavior, and PCR policy changes as security-sensitive.
- Update source comments and focused tests with behavior changes instead of regenerating a standalone documentation site.
- Validate the built npm package and Rust crate boundary before publishing a release.
MIT