Start with Soterias around an existing function. Add security.Executor only for operations that need native enforcement, and audit for offline verification. Python uses ainvoke; TypeScript uses invokeAsync. Existing public entry points remain compatible.
| Need | Start here |
|---|---|
| Observe existing work | Soterias → invoke / track |
| Reuse a pure calculation | Soterias → cache / cache_async (TypeScript: cacheAsync) |
| Execute an exact protected request | security.Executor → prepare / invoke |
| Review an operation separately | security.Reviewer → prepare / approve |
| Provision bounded authority | security.Issuer / Registry / Delegation |
| Verify historical evidence | audit.Verifier / ChainVerifier / replay |
Install the matching API preview
These examples use the API 2 preview, not names promised by the public RC5 release. Existing RC5 entry points remain available. The Python wheel below is verified on Windows x64; it is not a cross-platform release.
python -m pip install https://docs.soterias.io/downloads/api-preview/soterias_local-0.1.0rc5%2Bapi2-cp311-abi3-win_amd64.whlnpm install https://docs.soterias.io/downloads/api-preview/soterias-core-0.1.0-rc.5-api.2.tgz
npm install --save-dev typescript@5.8.3 @types/node@24Python imports soterias. TypeScript imports @soterias/core. Use Python 3.11+ for this wheel and Node 22.23.4+ for these examples. For a .mts file, compile with npx tsc example.mts --target ES2022 --module NodeNext --moduleResolution NodeNext --skipLibCheck, then run the emitted .mjs file.
Application walkthrough
Choose Python or TypeScript. Create example.py or example.mts and append that language's chunks in order. Each language forms one complete program; indentation and open blocks carry across chunks.
Import the packages
Import the SDK and standard-library helpers. These are the real package exports used by the executable program.
"""A complete local application: Python chunks on the feature pages use this source."""
import asyncio
import json
from pathlib import Path
import sqlite3
import tempfile
from soterias import Soteriasimport assert from 'node:assert/strict';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { DatabaseSync } from 'node:sqlite';
import { Soterias } from '@soterias/core';Create an application and an ordinary database
Soterias observes this existing code; it does not secure these direct writes.
# %% Create an application and an ordinary database
# Soterias observes this existing code; it does not secure these direct writes.
root = Path(tempfile.mkdtemp(prefix="soterias-invoices-"))
database = sqlite3.connect(root / "invoices.sqlite3")
database.execute("CREATE TABLE invoices (id INTEGER PRIMARY KEY, cents INTEGER)")
sdk = Soterias(workflow="invoices", directory=root / "metrics")
def post_invoice(cents):
row = database.execute("INSERT INTO invoices(cents) VALUES (?)", (cents,))
database.commit()
return {"id": row.lastrowid, "cents": cents}// %% Create an application and an ordinary database
// Soterias observes this existing code; it does not secure these direct writes.
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'soterias-invoices-'));
const database = new DatabaseSync(path.join(root, 'invoices.sqlite3'));
database.exec('CREATE TABLE invoices (id INTEGER PRIMARY KEY, cents INTEGER)');
const sdk = new Soterias({ workflow: 'invoices', directory: path.join(root, 'metrics') });
function postInvoice(cents: number) {
const row = database.prepare('INSERT INTO invoices(cents) VALUES (?)').run(cents);
return { id: Number(row.lastInsertRowid), cents };
}
try {Observe synchronous and asynchronous calls
Repeating the same input still runs the write twice.
# %% Observe synchronous and asynchronous calls
# Repeating the same input still runs the write twice.
first = sdk.invoke(post_invoice, 1500)
second = sdk.invoke(post_invoice, 1500)
assert first["id"] != second["id"]
tracked_post = sdk.track(post_invoice)
assert tracked_post(500)["cents"] == 500
async def review_invoices():
await asyncio.sleep(0)
return database.execute("SELECT SUM(cents) FROM invoices").fetchone()[0]
async def finish():
try:
total = await sdk.ainvoke(review_invoices)
assert total == 3500
tracked_review = sdk.track_async(review_invoices)
assert await tracked_review() == total
await sdk.flush()
summary = sdk.summary()
assert summary["counts"]["raw"] == 5 // %% Observe synchronous and asynchronous calls
// Repeating the same input still runs the write twice.
const first = sdk.invoke(postInvoice, 1500);
const second = sdk.invoke(postInvoice, 1500);
assert.notEqual(first.id, second.id);
const trackedPost = sdk.track(postInvoice);
assert.equal(trackedPost(500).cents, 500);
const total = await sdk.invokeAsync(async () => {
await Promise.resolve();
return database.prepare('SELECT SUM(cents) AS total FROM invoices').get()!.total;
});
assert.equal(total, 3500);
const trackedReview = sdk.trackAsync(async () => total);
assert.equal(await trackedReview(), total);
await sdk.flush();
const summary = sdk.summary();
assert.equal(summary.counts.raw, 5);Inspect and export local measurements
Each export is explicit; nothing is uploaded.
# %% Inspect and export local measurements
# Each export is explicit; nothing is uploaded.
for name, export in (
("runs.json", sdk.metrics.export_json()),
("runs.csv", sdk.metrics.export_csv()),
("runs.otlp.json", sdk.metrics.export_otlp()),
):
(root / name).write_bytes(export.data)
print(json.dumps({"status": "PASS", "total_cents": total,
"runs": summary["counts"]["raw"], "directory": str(root)}))
finally: // %% Inspect and export local measurements
// Each export is explicit; nothing is uploaded.
for (const [name, page] of [
['runs.json', sdk.metrics.exportJson()],
['runs.csv', sdk.metrics.exportCsv()],
['runs.otlp.json', sdk.metrics.exportOtlp()],
] as const) fs.writeFileSync(path.join(root, name), page.data);
console.log(JSON.stringify({ status: 'PASS', total_cents: total,
runs: summary.counts.raw, directory: root }));
} finally {Close the runtime and database
Wait for pending recordings before leaving the process.
# %% Close the runtime and database
# Wait for pending recordings before leaving the process.
await sdk.close_async()
database.close()
asyncio.run(finish()) // %% Close the runtime and database
// Wait for pending recordings before leaving the process.
await sdk.closeAsync();
database.close();
}Add a feature
Calculation caching adds an independent verifier and version. Protected execution introduces operator configuration and a separate reviewer. Choose a feature for all capabilities and their complete inline examples. Advanced owners remain available as security.Host and the compatibility exports; the Rust authority checks and native artifacts are unchanged by the shorter names.