@synsema/wasm 0.6.46 → 0.6.47
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +99 -4
- package/index.d.ts +8 -0
- package/package.json +1 -1
- package/synsema.wasm +0 -0
package/README.md
CHANGED
|
@@ -69,6 +69,60 @@ const r = await syn.runAsync(code, {
|
|
|
69
69
|
Worker or a trapped module.
|
|
70
70
|
- In the sync API a trap recreates the instance by itself (the call throws
|
|
71
71
|
`synsema: the module trapped (…); the instance was recreated`; the next call works).
|
|
72
|
+
- With `labels: true`, a run that hits `maxMemoryMb` reports `steps: null` and
|
|
73
|
+
`private_seen: true` (whether it ran out of memory can depend on a private value): treat it as
|
|
74
|
+
sensitive, like any run with `private_seen`.
|
|
75
|
+
|
|
76
|
+
### Ceilings for labelled code (v0.6.46)
|
|
77
|
+
|
|
78
|
+
- **`declassify` is bounded by `ceiling`.** With `labels: true` and a ceiling, only the principals
|
|
79
|
+
the ceiling names can be published: `ceiling: "stdout,declassify=customers"` (globs work:
|
|
80
|
+
`declassify=cust*`; a bare `declassify` allows any; `"sandbox"` never grants it). Otherwise
|
|
81
|
+
`declassify` fails with `label_violation: declassify is not granted for what this value is private
|
|
82
|
+
to (customers) …`, which `try`/`recover` cannot catch. Without a ceiling, or without `labels`,
|
|
83
|
+
nothing changes. **Breaking** for a host that runs labelled code under a ceiling and expects it to
|
|
84
|
+
publish: add `declassify=<principal>`.
|
|
85
|
+
- **Private data to your `llm` hook:** the program declares `require llm("customers")` and the
|
|
86
|
+
ceiling says `llm=customers@embedder` (or `llm=customers@*`). Then the prompt carries the data,
|
|
87
|
+
your hook gets `info.principals` (`["customers"]`), the answer comes back private, and `audit`
|
|
88
|
+
has `llm("customers@embedder")` (source `llm-data`). Anything less (`llm=customers@local`,
|
|
89
|
+
`llm=customers`, no ceiling, a bare `require llm`) refuses it before the hook runs. `judge` is the
|
|
90
|
+
same with `require judge("…")` and `judge=…@embedder`.
|
|
91
|
+
- **`judge` checks its capability:** a `ceiling` without `judge` refuses a `judge` block
|
|
92
|
+
(`Capability not granted: judge — declared but above the host ceiling …`).
|
|
93
|
+
|
|
94
|
+
### Trusted tools: `trusted`, `trustedCeiling` (v0.6.46)
|
|
95
|
+
|
|
96
|
+
```js
|
|
97
|
+
const trusted = { tools: `export task first_name(c)
|
|
98
|
+
require declassify("customers")
|
|
99
|
+
give declassify(c.name, "the customer's own name")
|
|
100
|
+
` };
|
|
101
|
+
const code = `use "trusted:tools" as t
|
|
102
|
+
let c be private({"name": "Ana", "card": "4111"}, "customers")
|
|
103
|
+
print(t.first_name(c))`;
|
|
104
|
+
syn.run(code, { labels: true, ceiling: "stdout", trusted, trustedCeiling: "declassify=customers" }); // prints Ana
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Code you do not trust can only call the exported tasks of a module you lend; each runs with its own
|
|
108
|
+
`require`, bounded by `trustedCeiling` (give one: without it a trusted task declassifies any
|
|
109
|
+
principal), and nothing from the caller. Its arguments are data (a task or a lambda among them is
|
|
110
|
+
an error). Works in `run`, `test`, `handle` and their `*Async` forms.
|
|
111
|
+
|
|
112
|
+
### Sealed receipts: `auditor` (v0.6.46)
|
|
113
|
+
|
|
114
|
+
The engine accepts `auditor` (an X25519 public key in base64url, from `synsema receipt keygen`) and
|
|
115
|
+
`sealed_bytes` (4096–16777216, default 65536) in a `run` request and returns the sealed receipt in
|
|
116
|
+
`receipt`. In v0.6.46 `run()`/`runAsync()` do not pass `auditor` on; use the low-level API:
|
|
117
|
+
|
|
118
|
+
```js
|
|
119
|
+
import { Synsema, instantiateSync, dispatchHostSync, offersOf } from "@synsema/wasm";
|
|
120
|
+
const syn = await Synsema.loadNode();
|
|
121
|
+
const host = { kv: { get: (ns, k) => null, set: (ns, k, v) => {} } };
|
|
122
|
+
const inst = instantiateSync(syn.module, (kind, payload) => dispatchHostSync(host, kind, payload));
|
|
123
|
+
const r = inst.call({ op: "run", source: code, filename: "app.syn", labels: true, auditor: AUDITOR_PUBLIC_KEY, host: offersOf(host) });
|
|
124
|
+
r.receipt; // the receipt object: write it as one JSON line for `synsema receipt verify` / `open`
|
|
125
|
+
```
|
|
72
126
|
|
|
73
127
|
## Lending capabilities (the host)
|
|
74
128
|
|
|
@@ -80,7 +134,7 @@ makes the builtin fail with a clear error; a `ceiling` denies above what the hos
|
|
|
80
134
|
const host = {
|
|
81
135
|
http: (req) => ({ status: 200, headers: [["content-type", "application/json"]], body: "{}" }),
|
|
82
136
|
kv: { get: (ns, k) => store.get(ns + "/" + k) ?? null, set: (ns, k, v) => store.set(ns + "/" + k, v) },
|
|
83
|
-
llm: (op, prompt) => ({ content: "…", tokens: 12 }),
|
|
137
|
+
llm: (op, prompt, info) => ({ content: "…", tokens: 12 }), // info.principals (v0.6.46)
|
|
84
138
|
log: (line) => console.log(line),
|
|
85
139
|
};
|
|
86
140
|
|
|
@@ -89,7 +143,9 @@ syn.run(`require llm\nprint(reason about "the weather")`, { host, ceiling: "stdo
|
|
|
89
143
|
```
|
|
90
144
|
|
|
91
145
|
- `kv` backs persistent agent memory (`remember`/`recall`, rules, progress — namespace
|
|
92
|
-
`memory:<declared name>`) and `state_*` (namespace `state`). `delete` and `list` are optional
|
|
146
|
+
`memory:<declared name>`) and `state_*` (namespace `state`). `delete` and `list` are optional
|
|
147
|
+
(without `list`, `state_all()` is `{}`). `get` returns the stored **text** or `null`/`undefined`;
|
|
148
|
+
`list` returns the key names as an array of strings.
|
|
93
149
|
- `kv.cas(ns, key, expected, value)` (optional) is compare-and-swap: write `value` only if the
|
|
94
150
|
stored text is exactly `expected` (`null` = the key does not exist) and return `true` or
|
|
95
151
|
`false` — anything else fails the call with `kv.cas must return true or false`. Your store must
|
|
@@ -108,10 +164,48 @@ syn.run(`require llm\nprint(reason about "the weather")`, { host, ceiling: "stdo
|
|
|
108
164
|
without `cas` they fail saying so (`the host's kv does not offer cas (kv.cas) …`) instead of
|
|
109
165
|
risking a lost write. With no host `kv`, the state belongs to the wasm instance and is atomic:
|
|
110
166
|
it lasts across that instance's `handle` requests, and each `run` starts with an empty state.
|
|
111
|
-
- `llm` backs `reason`/`decide`/`analyze`/`generate`; `llm_usage()` sums the
|
|
167
|
+
- `llm(op, prompt, info)` backs `reason`/`decide`/`analyze`/`generate`; `llm_usage()` sums the
|
|
168
|
+
tokens you report. `info.principals` lists the owners of the private data in the prompt (`[]`
|
|
169
|
+
with public data).
|
|
112
170
|
- `http` backs `fetch`/`http_*` and the blockchain read-side RPC — after the `net(host)`
|
|
113
171
|
gate, with the same URL canonicalization as the native binary.
|
|
114
172
|
|
|
173
|
+
### What a hook must answer (v0.6.46 — breaking)
|
|
174
|
+
|
|
175
|
+
A hook that fails, or answers something unexpected, is an **error of the builtin that asked**, not
|
|
176
|
+
"the key is not there" (before, the next write replaced what your store held):
|
|
177
|
+
|
|
178
|
+
| Your hook | The program gets |
|
|
179
|
+
|---|---|
|
|
180
|
+
| `kv.get` throws / rejects | `state_get: host kv.get failed: <message>` |
|
|
181
|
+
| `kv.get` returns a number, boolean or object | `… kv.get must return a string or null, got a number` |
|
|
182
|
+
| the stored text is not JSON | `state_incr: the stored value of 'c' is not JSON (…)`; nothing is written |
|
|
183
|
+
| `kv.list` throws, or returns `{keys: [{name}]}` (Cloudflare KV), `[{name}]`, `undefined` | `state_all: host kv.list failed: kv.list must return a list of strings, got an object` |
|
|
184
|
+
| a declared `memory` fails to load or is unreadable | the memory is not opened: `memory "agenda" could not be loaded (…); nothing was loaded, so nothing is written over it` |
|
|
185
|
+
| reading the audit log of `reveal`/`sign`/`wallet`/`spend` fails | the operation is refused; the log stays as it was |
|
|
186
|
+
| `llm` throws | that call fails with the hook's message (`the host's llm hook threw` without one); redacted when the prompt carried private data |
|
|
187
|
+
|
|
188
|
+
To migrate: give back the exact text the engine handed to `set` (it is JSON; do not parse it), and
|
|
189
|
+
make `list` return key names. Over a store whose `list` answers `{keys: [{name}]}`:
|
|
190
|
+
|
|
191
|
+
```js
|
|
192
|
+
list: async (ns) => (await store.list({ prefix: `${ns}/` })).keys.map((x) => x.name.slice(ns.length + 1)),
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### The `http` hook: redirects and private addresses (v0.6.46)
|
|
196
|
+
|
|
197
|
+
- A request that carries a secret (URL, query, body, form or any header) arrives with
|
|
198
|
+
`req.redirect === "error"`. Honor it — with `fetch`, pass `redirect: req.redirect ?? "follow"` —
|
|
199
|
+
or a 3xx carries the key to a host the program never named.
|
|
200
|
+
- In wasm the engine checks the host **name** against `require net`, never the address. Natively a
|
|
201
|
+
wildcard `net` grant reaches only public addresses; here that is your hook's job if the programs
|
|
202
|
+
are not yours: resolve the name, check the address against loopback, the private networks (RFC
|
|
203
|
+
1918, CGNAT `100.64.0.0/10`, ULA `fc00::/7`), link-local (`169.254.0.0/16`, the cloud metadata
|
|
204
|
+
endpoint, `fe80::/10`), multicast and the documentation ranges (an IPv4 inside an IPv6 counts as
|
|
205
|
+
that IPv4), and connect to that same address. The hook does not know whether the program named
|
|
206
|
+
the host, so name the private destinations your programs need in the hook. A complete Node hook
|
|
207
|
+
(`node:https` with a checking `lookup`) is in the Synsema docs, *WebAssembly*.
|
|
208
|
+
|
|
115
209
|
## Async hosts (browser `fetch`, IndexedDB, LLM SDKs)
|
|
116
210
|
|
|
117
211
|
The interpreter is synchronous. `runAsync`/`handleAsync` run it in a Worker and block on
|
|
@@ -138,7 +232,8 @@ res.status; res.content_type; res.headers; res.body;
|
|
|
138
232
|
```
|
|
139
233
|
|
|
140
234
|
Routes, params, query, `auth with`, `errors with`, `content()` negotiation, `redirect`,
|
|
141
|
-
pagination and `state_*` (durable through your `kv`) work as in the native server.
|
|
235
|
+
pagination and `state_*` (durable through your `kv`) work as in the native server. `receipt()`
|
|
236
|
+
in a handler lists only what that request read (v0.6.46: not what the top-level read). Not in
|
|
142
237
|
handler mode: `stream` (SSE), `proxy to`, rate limits, `static` mounts, vhosts, TLS —
|
|
143
238
|
your edge platform does those before calling the handler.
|
|
144
239
|
|
package/index.d.ts
CHANGED
|
@@ -161,6 +161,10 @@ export interface RunResult {
|
|
|
161
161
|
declassify?: unknown[];
|
|
162
162
|
/** Con `labels`: si algún valor privado pasó por la corrida (entonces `steps` no se publica). */
|
|
163
163
|
private_seen?: boolean;
|
|
164
|
+
/** v0.6.47 — con `labels`, si un corte de etiquetas paró la corrida: qué falló, dónde y cómo arreglarlo. Es
|
|
165
|
+
* del operador: el programa (y el cliente) ven en `errors` un texto fijo, porque cuál sitio cortó lo eligió el
|
|
166
|
+
* dato privado. No se lo devuelvas al modelo ni al cliente. */
|
|
167
|
+
operator_detail?: string;
|
|
164
168
|
}
|
|
165
169
|
|
|
166
170
|
export interface TestResult {
|
|
@@ -197,6 +201,10 @@ export interface HandleResult {
|
|
|
197
201
|
/** Errores de preparación del programa (parse/top-level); el status es 500. */
|
|
198
202
|
errors: string[];
|
|
199
203
|
audit: AuditEntry[];
|
|
204
|
+
/** v0.6.47 — con `labels`, si un corte de etiquetas paró la corrida: qué falló, dónde y cómo arreglarlo. Es
|
|
205
|
+
* del operador: el programa (y el cliente) ven en `errors` un texto fijo, porque cuál sitio cortó lo eligió el
|
|
206
|
+
* dato privado. No se lo devuelvas al modelo ni al cliente. */
|
|
207
|
+
operator_detail?: string;
|
|
200
208
|
}
|
|
201
209
|
|
|
202
210
|
export type ModuleSource = WebAssembly.Module | Uint8Array | ArrayBuffer | URL | string | Response;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@synsema/wasm",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.47",
|
|
4
4
|
"description": "Synsema — the programming language for AI agents — embedded as WebAssembly: run .syn programs inside browsers, Node/Bun/Deno and edge runtimes, lending them http/kv/llm from your app",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
package/synsema.wasm
CHANGED
|
Binary file
|