@synsema/wasm 0.6.45 → 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 +142 -13
- package/index.d.ts +45 -1
- package/index.mjs +19 -3
- package/package.json +1 -1
- package/synsema.wasm +0 -0
package/README.md
CHANGED
|
@@ -34,6 +34,7 @@ const r = await syn.runAsync(code, {
|
|
|
34
34
|
host,
|
|
35
35
|
labels: true, // information-flow labels on
|
|
36
36
|
input: { var: "customer", value: row, sources: { card: ["customer"] } },
|
|
37
|
+
result: "out", // r.result = the final value of `out`
|
|
37
38
|
timeoutMs: 2000, // the Worker is stopped past this
|
|
38
39
|
maxSteps: 5_000_000, // interpreter steps, deterministic
|
|
39
40
|
maxMemoryMb: 64, // memory the run reserves
|
|
@@ -44,19 +45,84 @@ const r = await syn.runAsync(code, {
|
|
|
44
45
|
private value, or a branch that depended on one, never reaches `print`, your `http` hook
|
|
45
46
|
(`fetch`/`http_*`), your `kv` (`state_*`, `remember`), your `llm` or your `sleep` (its number
|
|
46
47
|
crosses to the host): the call fails with `label_violation` before the hook runs.
|
|
47
|
-
`r.private_seen` says whether one went through.
|
|
48
|
+
`r.private_seen` says whether one went through; then `r.steps` is `null` (how many steps the
|
|
49
|
+
run took depended on the data). `sources` needs `labels: true`.
|
|
50
|
+
- `result: "out"` returns the final value of the global `out` in `r.result`, with its labels: a
|
|
51
|
+
private part comes back as `{"$private": ["customer"], "value": …}`. `r.declassify` lists each
|
|
52
|
+
`declassify` the run made. Without `labels`, `result` is plain JSON.
|
|
48
53
|
- `timeoutMs` (async mode): `0` means no deadline; otherwise milliseconds, up to 2147483647,
|
|
49
|
-
counted from when the call starts running, not while it waits in the queue. Past it the call
|
|
50
|
-
|
|
51
|
-
|
|
54
|
+
counted from when the call starts running, not while it waits in the queue. Past it the call
|
|
55
|
+
fails with a clear error and its Worker is replaced. The server's own thread never blocks. A
|
|
56
|
+
host hook already running (a `fetch`) is not cut; its result is discarded. A host hook with no
|
|
57
|
+
deadline of its own that never answers keeps its Worker busy unless the call has a `timeoutMs`.
|
|
52
58
|
- `maxSteps` and `maxMemoryMb` (both modes): the run ends with `ok: false` and an error that says
|
|
53
59
|
which limit it hit — never a trap. `maxSteps` counts interpreter steps from the start of the run
|
|
54
60
|
(in `handle`, of each request); `maxMemoryMb` counts the memory the run reserves, not what the
|
|
55
|
-
instance already held.
|
|
61
|
+
instance already held. Both are positive integers: `0` is an error, not "no limit". No call
|
|
62
|
+
inherits the limits of the previous one.
|
|
56
63
|
- Concurrent async calls wait in one queue and go to the first free Worker (up to `workers: n`). A
|
|
57
64
|
Worker that does not start in `startMs` (an option of `Synsema.load`/`loadNode`, default 30 s) is
|
|
58
|
-
stopped.
|
|
59
|
-
|
|
65
|
+
stopped. A Worker that dies fails the call it was running (not retried: it may have had
|
|
66
|
+
effects) and is replaced. `close()` is final: what waits fails with "closed".
|
|
67
|
+
- A host hook that returns something that is not JSON (a `BigInt`, a circular object) or throws a
|
|
68
|
+
value without a message counts as that hook failing in that call, in both APIs — not a hung
|
|
69
|
+
Worker or a trapped module.
|
|
70
|
+
- In the sync API a trap recreates the instance by itself (the call throws
|
|
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
|
+
```
|
|
60
126
|
|
|
61
127
|
## Lending capabilities (the host)
|
|
62
128
|
|
|
@@ -68,7 +134,7 @@ makes the builtin fail with a clear error; a `ceiling` denies above what the hos
|
|
|
68
134
|
const host = {
|
|
69
135
|
http: (req) => ({ status: 200, headers: [["content-type", "application/json"]], body: "{}" }),
|
|
70
136
|
kv: { get: (ns, k) => store.get(ns + "/" + k) ?? null, set: (ns, k, v) => store.set(ns + "/" + k, v) },
|
|
71
|
-
llm: (op, prompt) => ({ content: "…", tokens: 12 }),
|
|
137
|
+
llm: (op, prompt, info) => ({ content: "…", tokens: 12 }), // info.principals (v0.6.46)
|
|
72
138
|
log: (line) => console.log(line),
|
|
73
139
|
};
|
|
74
140
|
|
|
@@ -77,11 +143,69 @@ syn.run(`require llm\nprint(reason about "the weather")`, { host, ceiling: "stdo
|
|
|
77
143
|
```
|
|
78
144
|
|
|
79
145
|
- `kv` backs persistent agent memory (`remember`/`recall`, rules, progress — namespace
|
|
80
|
-
`memory:<declared name>`) and `state_*` (namespace `state`).
|
|
81
|
-
|
|
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.
|
|
149
|
+
- `kv.cas(ns, key, expected, value)` (optional) is compare-and-swap: write `value` only if the
|
|
150
|
+
stored text is exactly `expected` (`null` = the key does not exist) and return `true` or
|
|
151
|
+
`false` — anything else fails the call with `kv.cas must return true or false`. Your store must
|
|
152
|
+
compare and write in one step (a transaction, a conditional put):
|
|
153
|
+
|
|
154
|
+
```js
|
|
155
|
+
cas: (ns, k, expected, v) => {
|
|
156
|
+
if ((store.get(ns + "/" + k) ?? null) !== expected) return false;
|
|
157
|
+
store.set(ns + "/" + k, v);
|
|
158
|
+
return true;
|
|
159
|
+
},
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
`state_merge(key, map)` and `state_cas(key, expected, new)` depend on it. With a host `kv`
|
|
163
|
+
that has `cas` they are atomic, also across instances sharing the store. With a host `kv`
|
|
164
|
+
without `cas` they fail saying so (`the host's kv does not offer cas (kv.cas) …`) instead of
|
|
165
|
+
risking a lost write. With no host `kv`, the state belongs to the wasm instance and is atomic:
|
|
166
|
+
it lasts across that instance's `handle` requests, and each `run` starts with an empty state.
|
|
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).
|
|
82
170
|
- `http` backs `fetch`/`http_*` and the blockchain read-side RPC — after the `net(host)`
|
|
83
171
|
gate, with the same URL canonicalization as the native binary.
|
|
84
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
|
+
|
|
85
209
|
## Async hosts (browser `fetch`, IndexedDB, LLM SDKs)
|
|
86
210
|
|
|
87
211
|
The interpreter is synchronous. `runAsync`/`handleAsync` run it in a Worker and block on
|
|
@@ -108,7 +232,8 @@ res.status; res.content_type; res.headers; res.body;
|
|
|
108
232
|
```
|
|
109
233
|
|
|
110
234
|
Routes, params, query, `auth with`, `errors with`, `content()` negotiation, `redirect`,
|
|
111
|
-
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
|
|
112
237
|
handler mode: `stream` (SSE), `proxy to`, rate limits, `static` mounts, vhosts, TLS —
|
|
113
238
|
your edge platform does those before calling the handler.
|
|
114
239
|
|
|
@@ -120,8 +245,12 @@ validates without running; `syn.version()` is the artifact's release tag.
|
|
|
120
245
|
## Files, time, randomness
|
|
121
246
|
|
|
122
247
|
This artifact has **no filesystem** (`read_file` says so; pass data through `env` or the
|
|
123
|
-
source). `now()` comes from `Date.now()
|
|
124
|
-
|
|
248
|
+
source). `now()` comes from `Date.now()`. Each call asks the host once for 32 bytes of
|
|
249
|
+
entropy (`crypto.getRandomValues`), which seed that run's own ChaCha generator: `random()`,
|
|
250
|
+
`random_bytes(n)`, `token()` and RSA blinding draw from it and never ask the host again, so how
|
|
251
|
+
much randomness a run used does not show. With `labels`, `now()` returns the clock read at the
|
|
252
|
+
start of the run and at each interaction with the host (a hook call), so when and how often the
|
|
253
|
+
program read the clock does not show either. For file-based confidential jobs use the wasip1 build
|
|
125
254
|
under wasmtime (see the Synsema docs, *WebAssembly*).
|
|
126
255
|
|
|
127
256
|
## Other languages
|
package/index.d.ts
CHANGED
|
@@ -11,6 +11,12 @@ export interface HttpRequestPayload {
|
|
|
11
11
|
body_base64?: string;
|
|
12
12
|
/** Segundos. */
|
|
13
13
|
timeout: number;
|
|
14
|
+
/**
|
|
15
|
+
* v0.6.46: `"error"` cuando la request lleva un secret (en la URL, la query, el body o un header:
|
|
16
|
+
* `fetch` sólo quita `Authorization` al cambiar de host). Pasalo a `fetch` (`{redirect: req.redirect}`): un redirect no puede llevar la clave
|
|
17
|
+
* a otro host. Un hook que lo ignore queda fuera de lo que el motor puede imponer.
|
|
18
|
+
*/
|
|
19
|
+
redirect?: "error";
|
|
14
20
|
}
|
|
15
21
|
|
|
16
22
|
export interface HttpResponsePayload {
|
|
@@ -48,9 +54,26 @@ export interface LlmResult {
|
|
|
48
54
|
* En la API síncrona los hooks deben ser síncronos; en `*Async` pueden devolver Promise.
|
|
49
55
|
*/
|
|
50
56
|
export interface Host {
|
|
57
|
+
/**
|
|
58
|
+
* v0.6.46: en nativo, un `require net` con comodín sólo llega a IPs públicas (el motor resuelve
|
|
59
|
+
* y chequea la IP antes de conectar). Acá la conexión es tuya y el motor no ve la IP: si el
|
|
60
|
+
* programa no es tuyo, resolvé el nombre y chequeá la IP a la que vas a conectar (un nombre como
|
|
61
|
+
* `127.0.0.1.nip.io` resuelve al loopback), y conectá a ésa. Negá loopback (`127.0.0.0/8`,
|
|
62
|
+
* `::1`), las redes privadas (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`, CGNAT
|
|
63
|
+
* `100.64.0.0/10`, ULA `fc00::/7`), link-local (`169.254.0.0/16` con la metadata de la nube
|
|
64
|
+
* `169.254.169.254`, `fe80::/10`), multicast y los rangos de documentación; una IPv4 dentro de una
|
|
65
|
+
* IPv6 (`::ffff:a.b.c.d`) cuenta como esa IPv4. El hook no sabe si el programa nombró el host
|
|
66
|
+
* (`require net("db")`): si tus programas necesitan llegar a una red privada, nombrá vos esos
|
|
67
|
+
* destinos en el hook.
|
|
68
|
+
*/
|
|
51
69
|
http?(req: HttpRequestPayload): HttpResponsePayload | Promise<HttpResponsePayload>;
|
|
52
70
|
kv?: KvStore;
|
|
53
|
-
|
|
71
|
+
/**
|
|
72
|
+
* `info.principals`: los dueños de lo privado que lleva el prompt (vacío con datos públicos).
|
|
73
|
+
* Sólo llega al hook si el techo lo permite (`llm=<principal>@embedder`). Un hook que lanza hace
|
|
74
|
+
* fallar esa llamada con su mensaje.
|
|
75
|
+
*/
|
|
76
|
+
llm?(op: string, prompt: string, info: { principals: string[] }): string | LlmResult | Promise<string | LlmResult>;
|
|
54
77
|
log?(line: string): void | Promise<void>;
|
|
55
78
|
/** Una función, o `true` para la pausa por defecto (Atomics.wait / setTimeout). */
|
|
56
79
|
sleep?: ((secs: number) => void | Promise<void>) | true;
|
|
@@ -99,6 +122,17 @@ export interface RunOptions {
|
|
|
99
122
|
* Igual que `maxSteps`. Un entero positivo.
|
|
100
123
|
*/
|
|
101
124
|
maxMemoryMb?: number;
|
|
125
|
+
/**
|
|
126
|
+
* v0.6.46 — módulos de confianza: `{ nombre: fuente }`. El programa los importa con
|
|
127
|
+
* `use "trusted:nombre" as t` y sólo puede llamar a sus tasks exportadas, que corren con SUS
|
|
128
|
+
* `require` (acotados por `trustedCeiling`) y nada de lo del programa. Es la forma de dejar que
|
|
129
|
+
* código escrito por un modelo, bajo un `ceiling` sin `declassify`, publique sólo lo que la
|
|
130
|
+
* política de una persona publica. Los argumentos de una task de confianza son datos (una task
|
|
131
|
+
* o una lambda entre ellos es error).
|
|
132
|
+
*/
|
|
133
|
+
trusted?: Record<string, string>;
|
|
134
|
+
/** El techo de los módulos de confianza, con la gramática de `ceiling` (`"stdout,declassify=clientes"`). */
|
|
135
|
+
trustedCeiling?: string;
|
|
102
136
|
}
|
|
103
137
|
|
|
104
138
|
export interface AuditEntry {
|
|
@@ -119,12 +153,18 @@ export interface RunResult {
|
|
|
119
153
|
llm_tokens: number;
|
|
120
154
|
/** v0.6.42 — el código de salida: el de `exit(code)` si el programa lo pidió, si no 0 (ok) o 1. */
|
|
121
155
|
exit: number;
|
|
156
|
+
/** Los pasos del intérprete que llevó la corrida; `null` con `labels` si tocó un valor o una rama privada. */
|
|
157
|
+
steps: number | null;
|
|
122
158
|
/** Con `result`: el valor final de esa variable (las partes privadas como `{"$private": [...]}`). */
|
|
123
159
|
result?: unknown;
|
|
124
160
|
/** Con `labels`: cada `declassify` que corrió (quién, qué principal, dónde). */
|
|
125
161
|
declassify?: unknown[];
|
|
126
162
|
/** Con `labels`: si algún valor privado pasó por la corrida (entonces `steps` no se publica). */
|
|
127
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;
|
|
128
168
|
}
|
|
129
169
|
|
|
130
170
|
export interface TestResult {
|
|
@@ -161,6 +201,10 @@ export interface HandleResult {
|
|
|
161
201
|
/** Errores de preparación del programa (parse/top-level); el status es 500. */
|
|
162
202
|
errors: string[];
|
|
163
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;
|
|
164
208
|
}
|
|
165
209
|
|
|
166
210
|
export type ModuleSource = WebAssembly.Module | Uint8Array | ArrayBuffer | URL | string | Response;
|
package/index.mjs
CHANGED
|
@@ -14,6 +14,11 @@
|
|
|
14
14
|
// En el navegador requiere cross-origin isolation (COOP/COEP) para SharedArrayBuffer.
|
|
15
15
|
//
|
|
16
16
|
// No hay estado global: cada `load()` es una instancia (memoria lineal propia).
|
|
17
|
+
//
|
|
18
|
+
// v0.6.46 (E7): la conexión del `http` del host es del embebedor, y el motor no ve a qué IP va. En
|
|
19
|
+
// nativo un `net` con comodín sólo llega a IPs públicas; acá eso lo tiene que hacer tu hook:
|
|
20
|
+
// resolver el nombre, chequear la IP a la que conecta (los rangos están en `Host.http`, en
|
|
21
|
+
// index.d.ts) y conectar a ésa. El hook no sabe si el programa nombró el host.
|
|
17
22
|
|
|
18
23
|
const te = new TextEncoder();
|
|
19
24
|
const td = new TextDecoder();
|
|
@@ -122,7 +127,7 @@ export function dispatchHostSync(host, kind, payload) {
|
|
|
122
127
|
? casReply(syncOnly("kv.cas", host.kv.cas(payload.ns, payload.key, payload.expected ?? null, payload.value)))
|
|
123
128
|
: undefined;
|
|
124
129
|
case "llm":
|
|
125
|
-
return typeof host.llm === "function" ? normalizeLlm(syncOnly("llm", host.llm(payload.op, payload.prompt))) : undefined;
|
|
130
|
+
return typeof host.llm === "function" ? normalizeLlm(syncOnly("llm", host.llm(payload.op, payload.prompt, { principals: payload.principals ?? [] }))) : undefined;
|
|
126
131
|
case "log":
|
|
127
132
|
if (typeof host.log !== "function") return undefined;
|
|
128
133
|
syncOnly("log", host.log(payload.line));
|
|
@@ -165,7 +170,7 @@ export async function dispatchHostAsync(host, kind, payload) {
|
|
|
165
170
|
? casReply(await host.kv.cas(payload.ns, payload.key, payload.expected ?? null, payload.value))
|
|
166
171
|
: undefined;
|
|
167
172
|
case "llm":
|
|
168
|
-
return typeof host.llm === "function" ? normalizeLlm(await host.llm(payload.op, payload.prompt)) : undefined;
|
|
173
|
+
return typeof host.llm === "function" ? normalizeLlm(await host.llm(payload.op, payload.prompt, { principals: payload.principals ?? [] })) : undefined;
|
|
169
174
|
case "log":
|
|
170
175
|
if (typeof host.log !== "function") return undefined;
|
|
171
176
|
await host.log(payload.line);
|
|
@@ -344,7 +349,15 @@ function limitResult(request) {
|
|
|
344
349
|
};
|
|
345
350
|
}
|
|
346
351
|
if (request.op === "test") return { ok: false, passed: 0, failed: 0, lines: [MEMORY_LIMIT], audit: [] };
|
|
347
|
-
|
|
352
|
+
const r = { ok: false, output: [], errors: [MEMORY_LIMIT], audit: [], llm_tokens: 0, exit: 1 };
|
|
353
|
+
// v0.6.46 (E6): con etiquetas, que la corrida se haya cortado por memoria puede depender de un
|
|
354
|
+
// valor privado (un bit por corrida, como cualquier error suyo): el host lo trata como sensible,
|
|
355
|
+
// igual que una corrida que terminó con `private_seen`. Sin saber si tocó algo privado, sí.
|
|
356
|
+
if (request.labels) {
|
|
357
|
+
r.steps = null;
|
|
358
|
+
r.private_seen = true;
|
|
359
|
+
}
|
|
360
|
+
return r;
|
|
348
361
|
}
|
|
349
362
|
|
|
350
363
|
/**
|
|
@@ -370,6 +383,9 @@ function requestOf(op, source, opts = {}) {
|
|
|
370
383
|
// se descartaba por falsy y la corrida quedaba sin límite.
|
|
371
384
|
if (opts.maxSteps != null) r.max_steps = opts.maxSteps;
|
|
372
385
|
if (opts.maxMemoryMb != null) r.max_memory_mb = opts.maxMemoryMb;
|
|
386
|
+
// v0.6.46 (E3b): módulos de confianza del embebedor y su techo.
|
|
387
|
+
if (opts.trusted) r.trusted = opts.trusted;
|
|
388
|
+
if (opts.trustedCeiling) r.trusted_ceiling = opts.trustedCeiling;
|
|
373
389
|
return r;
|
|
374
390
|
}
|
|
375
391
|
|
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
|