spell-runtime 1.2.1 → 1.2.5
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 +149 -25
- package/README.txt +128 -25
- package/dist/api/server.js +114 -20
- package/dist/api/server.js.map +1 -1
- package/dist/bundle/install.d.ts +1 -1
- package/dist/bundle/install.js +57 -15
- package/dist/bundle/install.js.map +1 -1
- package/dist/cli/index.js +12 -10
- package/dist/cli/index.js.map +1 -1
- package/dist/license/entitlement.d.ts +28 -0
- package/dist/license/entitlement.js +174 -0
- package/dist/license/entitlement.js.map +1 -0
- package/dist/license/store.d.ts +4 -0
- package/dist/license/store.js +157 -34
- package/dist/license/store.js.map +1 -1
- package/dist/policy/index.d.ts +26 -0
- package/dist/policy/index.js +194 -0
- package/dist/policy/index.js.map +1 -0
- package/dist/runner/cast.js +12 -2
- package/dist/runner/cast.js.map +1 -1
- package/dist/runner/dockerRunner.d.ts +1 -0
- package/dist/runner/dockerRunner.js +116 -5
- package/dist/runner/dockerRunner.js.map +1 -1
- package/dist/runner/spell-runner.js +0 -0
- package/package.json +7 -1
package/README.md
CHANGED
|
@@ -5,18 +5,19 @@ Minimal CLI runtime for SpellBundle v1.
|
|
|
5
5
|
## Setup
|
|
6
6
|
|
|
7
7
|
- Node.js >= 20
|
|
8
|
-
-
|
|
8
|
+
- pnpm (recommended)
|
|
9
|
+
- npm (supported)
|
|
9
10
|
|
|
10
11
|
```bash
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
pnpm install
|
|
13
|
+
pnpm run build
|
|
14
|
+
pnpm test
|
|
14
15
|
```
|
|
15
16
|
|
|
16
17
|
Local dev:
|
|
17
18
|
|
|
18
19
|
```bash
|
|
19
|
-
|
|
20
|
+
pnpm run dev -- --help
|
|
20
21
|
```
|
|
21
22
|
|
|
22
23
|
## Install as CLI
|
|
@@ -43,11 +44,11 @@ npm run smoke:npx
|
|
|
43
44
|
|
|
44
45
|
## Commands
|
|
45
46
|
|
|
46
|
-
- `spell install <
|
|
47
|
+
- `spell install <source>`
|
|
47
48
|
- `spell list`
|
|
48
49
|
- `spell inspect <id> [--version x.y.z]`
|
|
49
|
-
- `spell cast <id> [--version x.y.z] [-p key=value ...] [--input input.json] [--dry-run] [--yes] [--allow-billing] [--require-signature] [--verbose] [--profile <name>]`
|
|
50
|
-
- `spell license add <name> <token>`
|
|
50
|
+
- `spell cast <id> [--version x.y.z] [-p key=value ...] [--input input.json] [--dry-run] [--yes] [--allow-billing] [--allow-unsigned] [--require-signature] [--verbose] [--profile <name>]`
|
|
51
|
+
- `spell license add <name> <entitlement-token>`
|
|
51
52
|
- `spell license list`
|
|
52
53
|
- `spell license remove <name>`
|
|
53
54
|
- `spell sign keygen <publisher> [--key-id default] [--out-dir .spell-keys]`
|
|
@@ -59,15 +60,19 @@ npm run smoke:npx
|
|
|
59
60
|
|
|
60
61
|
## Install Sources
|
|
61
62
|
|
|
62
|
-
`spell install <
|
|
63
|
+
`spell install <source>` accepts:
|
|
63
64
|
|
|
64
65
|
- local bundle paths (existing behavior)
|
|
65
|
-
- git URLs:
|
|
66
|
-
- `https
|
|
67
|
-
- `ssh
|
|
68
|
-
- `git
|
|
66
|
+
- pinned git URLs with an explicit ref suffix:
|
|
67
|
+
- `https://...#<ref>`
|
|
68
|
+
- `ssh://...#<ref>`
|
|
69
|
+
- `git@...#<ref>`
|
|
69
70
|
|
|
70
|
-
|
|
71
|
+
Git sources must include `#<ref>`. If omitted, install fails with:
|
|
72
|
+
|
|
73
|
+
- `git source requires explicit ref (#<ref>)`
|
|
74
|
+
|
|
75
|
+
When a git source is provided, runtime clones the repository, checks out the requested ref, resolves the checked-out commit SHA (`git rev-parse HEAD`), and installs from that checkout.
|
|
71
76
|
|
|
72
77
|
Limitations:
|
|
73
78
|
|
|
@@ -79,8 +84,17 @@ Limitations:
|
|
|
79
84
|
|
|
80
85
|
- Spells: `~/.spell/spells/<id_key>/<version>/`
|
|
81
86
|
- ID index: `~/.spell/spells/<id_key>/spell.id.txt`
|
|
87
|
+
- Install provenance: `~/.spell/spells/<id_key>/<version>/source.json`
|
|
82
88
|
- Logs: `~/.spell/logs/<timestamp>_<id>_<version>.json`
|
|
83
|
-
- Billing
|
|
89
|
+
- Billing entitlement records: `~/.spell/licenses/*.json`
|
|
90
|
+
|
|
91
|
+
`source.json` captures install provenance:
|
|
92
|
+
|
|
93
|
+
- `type`: `local` or `git`
|
|
94
|
+
- `source`: original install input
|
|
95
|
+
- `ref`: requested git ref (git installs only)
|
|
96
|
+
- `commit`: resolved git commit SHA (git installs only)
|
|
97
|
+
- `installed_at`: install timestamp (ISO-8601)
|
|
84
98
|
|
|
85
99
|
`id_key` is fixed as `base64url(utf8(id))`.
|
|
86
100
|
|
|
@@ -99,16 +113,34 @@ Consistency rule:
|
|
|
99
113
|
- bundle resolution by id (and optional version)
|
|
100
114
|
- input assembly (`--input` + `-p` overrides)
|
|
101
115
|
- JSON Schema validation by Ajv
|
|
102
|
-
-
|
|
116
|
+
- signature verification (default on; bypass only with `--allow-unsigned`)
|
|
117
|
+
- runtime policy guard (`~/.spell/policy.json`)
|
|
103
118
|
- platform guard
|
|
104
119
|
- risk guard (`high`/`critical` requires `--yes`)
|
|
105
120
|
- billing guard (`billing.enabled` requires `--allow-billing`)
|
|
106
|
-
- billing
|
|
121
|
+
- billing entitlement guard (`billing.enabled` + `--allow-billing` requires a matching valid entitlement from `spell license add ...`)
|
|
107
122
|
- connector token guard (`CONNECTOR_<NAME>_TOKEN`)
|
|
108
123
|
- execution summary output
|
|
109
124
|
|
|
110
125
|
If `--dry-run` is set, command exits after summary and validation.
|
|
111
126
|
|
|
127
|
+
Policy file format (`~/.spell/policy.json`):
|
|
128
|
+
|
|
129
|
+
```json
|
|
130
|
+
{
|
|
131
|
+
"version": "v1",
|
|
132
|
+
"default": "allow",
|
|
133
|
+
"publishers": { "allow": ["samples"], "deny": ["blocked"] },
|
|
134
|
+
"max_risk": "high",
|
|
135
|
+
"runtime": { "allow_execution": ["host", "docker"] }
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Notes:
|
|
140
|
+
- missing policy file => allow by default
|
|
141
|
+
- invalid policy file => `invalid policy: ...`
|
|
142
|
+
- policy rejection => `policy denied: <reason>`
|
|
143
|
+
|
|
112
144
|
## Runtime Safety Limits (v2 isolation)
|
|
113
145
|
|
|
114
146
|
`cast` enforces these runtime limits (used by direct CLI casts and API-triggered casts because the API invokes `spell cast`):
|
|
@@ -129,7 +161,22 @@ Docker mode (v1) details:
|
|
|
129
161
|
- `runtime.execution=docker` requires `runtime.docker_image`.
|
|
130
162
|
- the image must provide `spell-runner` on `PATH` (this repo publishes it as a second npm bin).
|
|
131
163
|
- the bundle is mounted read-only at `/spell`; the runner copies it into a writable temp workdir before executing steps.
|
|
132
|
-
-
|
|
164
|
+
- hardened `docker run` defaults:
|
|
165
|
+
- `--network none`
|
|
166
|
+
- `--cap-drop ALL`
|
|
167
|
+
- `--security-opt no-new-privileges`
|
|
168
|
+
- `--read-only`
|
|
169
|
+
- `--user 65532:65532`
|
|
170
|
+
- `--pids-limit 256`
|
|
171
|
+
- `--tmpfs /tmp:rw,noexec,nosuid,size=64m`
|
|
172
|
+
- hardening env overrides (all optional):
|
|
173
|
+
- `SPELL_DOCKER_NETWORK` (`none|bridge|host`, default `none`)
|
|
174
|
+
- `SPELL_DOCKER_USER` (default `65532:65532`; set empty to disable `--user`)
|
|
175
|
+
- `SPELL_DOCKER_READ_ONLY` (`1` default; set `0` to disable `--read-only`)
|
|
176
|
+
- `SPELL_DOCKER_PIDS_LIMIT` (`256` default; set `0` to disable `--pids-limit`)
|
|
177
|
+
- `SPELL_DOCKER_MEMORY` (default empty; when set adds `--memory`)
|
|
178
|
+
- `SPELL_DOCKER_CPUS` (default empty; when set adds `--cpus`)
|
|
179
|
+
- environment variables passed from host -> container are restricted to connector tokens (`CONNECTOR_<NAME>_TOKEN`) plus `SPELL_RUNTIME_STEP_TIMEOUT_MS`.
|
|
133
180
|
|
|
134
181
|
## Windows Policy
|
|
135
182
|
|
|
@@ -151,20 +198,22 @@ Use these `effect.type` words where possible:
|
|
|
151
198
|
## v1 Limitations (Intentionally Not Implemented)
|
|
152
199
|
|
|
153
200
|
- name search or ambiguous resolution (id only)
|
|
154
|
-
- registry/marketplace
|
|
201
|
+
- registry/marketplace integration
|
|
155
202
|
- real billing execution (Stripe)
|
|
156
203
|
- DAG/parallel/rollback/self-healing
|
|
157
204
|
- advanced templating language (only `{{INPUT.*}}` and `{{ENV.*}}`)
|
|
158
|
-
- docker env passthrough beyond connector tokens
|
|
205
|
+
- docker env passthrough beyond connector tokens and `SPELL_RUNTIME_STEP_TIMEOUT_MS`
|
|
159
206
|
|
|
160
207
|
## Signature (Sign + Verify)
|
|
161
208
|
|
|
162
|
-
|
|
209
|
+
`spell cast` requires signature verification by default. To bypass this for unsigned bundle workflows, use:
|
|
163
210
|
|
|
164
211
|
```bash
|
|
165
|
-
spell cast <id> --
|
|
212
|
+
spell cast <id> --allow-unsigned ...
|
|
166
213
|
```
|
|
167
214
|
|
|
215
|
+
`--require-signature` remains accepted for backward compatibility.
|
|
216
|
+
|
|
168
217
|
Signing flow:
|
|
169
218
|
|
|
170
219
|
```bash
|
|
@@ -184,6 +233,40 @@ Notes:
|
|
|
184
233
|
- publisher is derived from the spell id prefix before the first `/` (example: `samples/call-webhook` -> `samples`).
|
|
185
234
|
- public key format is ed25519 `spki` DER encoded as base64url.
|
|
186
235
|
|
|
236
|
+
## Entitlement Tokens (Billing)
|
|
237
|
+
|
|
238
|
+
`spell license add <name> <token>` now validates and stores signed entitlement tokens.
|
|
239
|
+
|
|
240
|
+
Token format:
|
|
241
|
+
|
|
242
|
+
- `ent1.<payloadBase64url>.<signatureBase64url>`
|
|
243
|
+
|
|
244
|
+
Payload JSON required fields:
|
|
245
|
+
|
|
246
|
+
- `version` (`"v1"`)
|
|
247
|
+
- `issuer` (string)
|
|
248
|
+
- `key_id` (string)
|
|
249
|
+
- `mode` (`"upfront" | "on_success" | "subscription"`)
|
|
250
|
+
- `currency` (string)
|
|
251
|
+
- `max_amount` (number)
|
|
252
|
+
- `not_before` (ISO string)
|
|
253
|
+
- `expires_at` (ISO string)
|
|
254
|
+
|
|
255
|
+
Verification rules:
|
|
256
|
+
|
|
257
|
+
- signature algorithm is ed25519
|
|
258
|
+
- signed message is the raw payload segment bytes (the exact payload base64url segment string bytes)
|
|
259
|
+
- trust source is the publisher trust store (`spell trust add ...`) keyed by `issuer + key_id`
|
|
260
|
+
- token must be within `not_before <= now <= expires_at`
|
|
261
|
+
|
|
262
|
+
Billing-enabled cast requires a matching currently-valid entitlement:
|
|
263
|
+
|
|
264
|
+
- entitlement `mode === manifest.billing.mode`
|
|
265
|
+
- entitlement `currency` equals `manifest.billing.currency` (case-insensitive)
|
|
266
|
+
- entitlement `max_amount >= manifest.billing.max_amount`
|
|
267
|
+
|
|
268
|
+
`spell license list` prints entitlement summary columns (`issuer`, `mode`, `currency`, `max_amount`, `expires_at`) and never prints raw tokens.
|
|
269
|
+
|
|
187
270
|
## Example Flow
|
|
188
271
|
|
|
189
272
|
```bash
|
|
@@ -195,6 +278,38 @@ spell cast fixtures/hello-host -p name=world
|
|
|
195
278
|
spell log <execution-id>
|
|
196
279
|
```
|
|
197
280
|
|
|
281
|
+
## OSS Release (pnpm + GitHub Actions)
|
|
282
|
+
|
|
283
|
+
Release automation is defined in:
|
|
284
|
+
|
|
285
|
+
- `.github/workflows/release.yml`
|
|
286
|
+
|
|
287
|
+
Prerequisites:
|
|
288
|
+
|
|
289
|
+
- npm account with `2FA` enabled
|
|
290
|
+
- GitHub repository secret `NPM_TOKEN` (publish-capable npm token)
|
|
291
|
+
|
|
292
|
+
Local release checks:
|
|
293
|
+
|
|
294
|
+
```bash
|
|
295
|
+
pnpm install
|
|
296
|
+
pnpm run typecheck
|
|
297
|
+
pnpm run lint
|
|
298
|
+
pnpm run build
|
|
299
|
+
pnpm test
|
|
300
|
+
pnpm run pack:check
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
Tag-based release flow:
|
|
304
|
+
|
|
305
|
+
```bash
|
|
306
|
+
npm version patch
|
|
307
|
+
git push --follow-tags
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
Pushing tag `vX.Y.Z` triggers GitHub Actions release and runs `npm publish`.
|
|
311
|
+
The workflow verifies that the git tag version matches `package.json`.
|
|
312
|
+
|
|
198
313
|
## Real-Use Sample Spells
|
|
199
314
|
|
|
200
315
|
These are product-facing examples (separate from test fixtures):
|
|
@@ -220,12 +335,20 @@ spell cast samples/call-webhook --dry-run -p event=deploy -p source=manual -p pa
|
|
|
220
335
|
- Button registry schema:
|
|
221
336
|
- `/Users/koichinishizuka/spell-runtime/examples/button-registry.v1.schema.json`
|
|
222
337
|
- Registry optional policy:
|
|
223
|
-
- `require_signature
|
|
338
|
+
- `require_signature`:
|
|
339
|
+
- `true`: Execution API enforces signature (`--require-signature`)
|
|
340
|
+
- `false`/omitted: Execution API opts into unsigned path (`--allow-unsigned`)
|
|
224
341
|
|
|
225
342
|
## Runtime Decision Log
|
|
226
343
|
|
|
227
344
|
- `/Users/koichinishizuka/spell-runtime/docs/runtime-decisions-v1.md`
|
|
228
345
|
|
|
346
|
+
## Repository Policies
|
|
347
|
+
|
|
348
|
+
- `/Users/koichinishizuka/spell-runtime/CONTRIBUTING.md`
|
|
349
|
+
- `/Users/koichinishizuka/spell-runtime/CODE_OF_CONDUCT.md`
|
|
350
|
+
- `/Users/koichinishizuka/spell-runtime/SECURITY.md`
|
|
351
|
+
|
|
229
352
|
## Execution API (Async)
|
|
230
353
|
|
|
231
354
|
Start API server:
|
|
@@ -245,14 +368,14 @@ By default it listens on `:8787` and reads:
|
|
|
245
368
|
- `GET /` (minimal Receipts UI)
|
|
246
369
|
- `GET /ui/app.js` (UI client script)
|
|
247
370
|
- `GET /api/buttons`
|
|
248
|
-
- `GET /api/spell-executions` (`status`, `button_id`, `limit` query supported)
|
|
371
|
+
- `GET /api/spell-executions` (`status`, `button_id`, `tenant_id`, `limit` query supported)
|
|
249
372
|
- `POST /api/spell-executions`
|
|
250
373
|
- `GET /api/spell-executions/:execution_id`
|
|
251
374
|
|
|
252
375
|
Optional environment variables:
|
|
253
376
|
- `SPELL_API_PORT`
|
|
254
377
|
- `SPELL_BUTTON_REGISTRY_PATH`
|
|
255
|
-
- `SPELL_API_AUTH_KEYS` (comma-separated `role=token` entries; when set, `/api/*` requires auth and derives `actor_role` from token)
|
|
378
|
+
- `SPELL_API_AUTH_KEYS` (comma-separated `role=token` or `tenant:role=token` entries; when set, `/api/*` requires auth and derives `actor_role` + `tenant_id` from token)
|
|
256
379
|
- `SPELL_API_AUTH_TOKENS` (legacy: comma-separated tokens; when set, `/api/*` requires auth but does not bind role)
|
|
257
380
|
- `SPELL_API_BODY_LIMIT_BYTES`
|
|
258
381
|
- `SPELL_API_EXECUTION_TIMEOUT_MS`
|
|
@@ -266,4 +389,5 @@ Security note:
|
|
|
266
389
|
- execution logs redact secret-like keys (`token`, `authorization`, `apiKey`, etc.)
|
|
267
390
|
- environment-derived secret values are masked in persisted logs
|
|
268
391
|
- when auth is enabled, pass `Authorization: Bearer <token>` (or `x-api-key`) for `/api` routes
|
|
392
|
+
- with `SPELL_API_AUTH_KEYS`, non-admin list requests are restricted to their own tenant and cross-tenant `tenant_id` filters return `403` (`TENANT_FORBIDDEN`)
|
|
269
393
|
- do not set both `SPELL_API_AUTH_KEYS` and `SPELL_API_AUTH_TOKENS` at the same time
|
package/README.txt
CHANGED
|
@@ -4,19 +4,20 @@ Minimal CLI runtime for SpellBundle v1.
|
|
|
4
4
|
|
|
5
5
|
1. Setup
|
|
6
6
|
- Node.js >= 20
|
|
7
|
-
-
|
|
7
|
+
- pnpm (recommended)
|
|
8
|
+
- npm (supported)
|
|
8
9
|
|
|
9
10
|
Install dependencies:
|
|
10
|
-
|
|
11
|
+
pnpm install
|
|
11
12
|
|
|
12
13
|
Build:
|
|
13
|
-
|
|
14
|
+
pnpm run build
|
|
14
15
|
|
|
15
16
|
Test:
|
|
16
|
-
|
|
17
|
+
pnpm test
|
|
17
18
|
|
|
18
19
|
Local dev:
|
|
19
|
-
|
|
20
|
+
pnpm run dev -- --help
|
|
20
21
|
|
|
21
22
|
Binary smoke checks:
|
|
22
23
|
npm run smoke:link
|
|
@@ -30,11 +31,11 @@ Manual npx (local package):
|
|
|
30
31
|
npx --yes --package file:. spell --help
|
|
31
32
|
|
|
32
33
|
2. CLI commands
|
|
33
|
-
- spell install <
|
|
34
|
+
- spell install <source>
|
|
34
35
|
- spell list
|
|
35
36
|
- spell inspect <id> [--version x.y.z]
|
|
36
|
-
- spell cast <id> [--version x.y.z] [-p key=value ...] [--input input.json] [--dry-run] [--yes] [--allow-billing] [--require-signature] [--verbose] [--profile <name>]
|
|
37
|
-
- spell license add <name> <token>
|
|
37
|
+
- spell cast <id> [--version x.y.z] [-p key=value ...] [--input input.json] [--dry-run] [--yes] [--allow-billing] [--allow-unsigned] [--require-signature] [--verbose] [--profile <name>]
|
|
38
|
+
- spell license add <name> <entitlement-token>
|
|
38
39
|
- spell license list
|
|
39
40
|
- spell license remove <name>
|
|
40
41
|
- spell sign keygen <publisher> [--key-id default] [--out-dir .spell-keys]
|
|
@@ -45,14 +46,17 @@ Manual npx (local package):
|
|
|
45
46
|
- spell log <execution-id>
|
|
46
47
|
|
|
47
48
|
2.1 Install sources
|
|
48
|
-
- spell install <
|
|
49
|
+
- spell install <source> accepts:
|
|
49
50
|
- local bundle paths (existing behavior)
|
|
50
|
-
- git URLs:
|
|
51
|
-
- https
|
|
52
|
-
- ssh
|
|
53
|
-
- git
|
|
51
|
+
- pinned git URLs with explicit refs:
|
|
52
|
+
- https://...#<ref>
|
|
53
|
+
- ssh://...#<ref>
|
|
54
|
+
- git@...#<ref>
|
|
54
55
|
|
|
55
|
-
|
|
56
|
+
Git sources must include #<ref>. If omitted, install fails with:
|
|
57
|
+
git source requires explicit ref (#<ref>)
|
|
58
|
+
|
|
59
|
+
When a git source is provided, runtime clones the repository, checks out the requested ref, resolves the checked-out commit SHA (git rev-parse HEAD), and installs from that checkout.
|
|
56
60
|
|
|
57
61
|
Limitations:
|
|
58
62
|
- git must be installed and available on PATH.
|
|
@@ -62,8 +66,16 @@ Limitations:
|
|
|
62
66
|
3. Storage layout
|
|
63
67
|
- Spells: ~/.spell/spells/<id_key>/<version>/
|
|
64
68
|
- ID index: ~/.spell/spells/<id_key>/spell.id.txt
|
|
69
|
+
- Install provenance: ~/.spell/spells/<id_key>/<version>/source.json
|
|
65
70
|
- Logs: ~/.spell/logs/<timestamp>_<id>_<version>.json
|
|
66
|
-
- Billing
|
|
71
|
+
- Billing entitlement records: ~/.spell/licenses/*.json
|
|
72
|
+
|
|
73
|
+
source.json captures install provenance:
|
|
74
|
+
- type: local or git
|
|
75
|
+
- source: original install input
|
|
76
|
+
- ref: requested git ref (git installs only)
|
|
77
|
+
- commit: resolved git commit SHA (git installs only)
|
|
78
|
+
- installed_at: install timestamp (ISO-8601)
|
|
67
79
|
|
|
68
80
|
id_key is fixed as base64url(utf8(id)).
|
|
69
81
|
- id is the logical identifier (display, package identity).
|
|
@@ -78,16 +90,31 @@ Cast performs these checks before execution:
|
|
|
78
90
|
- Bundle resolution by id (and optional version)
|
|
79
91
|
- Input assembly (--input + -p overrides)
|
|
80
92
|
- JSON Schema validation by Ajv
|
|
81
|
-
-
|
|
93
|
+
- Signature verification (default on; bypass only with --allow-unsigned)
|
|
94
|
+
- Runtime policy guard (~/.spell/policy.json)
|
|
82
95
|
- Platform guard
|
|
83
96
|
- Risk guard (high/critical requires --yes)
|
|
84
97
|
- Billing guard (billing.enabled requires --allow-billing)
|
|
85
|
-
- Billing
|
|
98
|
+
- Billing entitlement guard (billing.enabled + --allow-billing requires a matching valid entitlement from spell license add ...)
|
|
86
99
|
- Connector token guard (CONNECTOR_<NAME>_TOKEN)
|
|
87
100
|
- Execution summary output
|
|
88
101
|
|
|
89
102
|
If --dry-run is set, command exits after summary and validation.
|
|
90
103
|
|
|
104
|
+
Policy file format (~/.spell/policy.json):
|
|
105
|
+
{
|
|
106
|
+
"version": "v1",
|
|
107
|
+
"default": "allow",
|
|
108
|
+
"publishers": { "allow": ["samples"], "deny": ["blocked"] },
|
|
109
|
+
"max_risk": "high",
|
|
110
|
+
"runtime": { "allow_execution": ["host", "docker"] }
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
Notes:
|
|
114
|
+
- missing policy file => allow by default
|
|
115
|
+
- invalid policy file => invalid policy: ...
|
|
116
|
+
- policy rejection => policy denied: <reason>
|
|
117
|
+
|
|
91
118
|
4.1 Runtime safety limits (v2 isolation)
|
|
92
119
|
cast enforces these runtime limits (for direct CLI casts and API-triggered casts, because the API invokes spell cast):
|
|
93
120
|
- SPELL_RUNTIME_INPUT_MAX_BYTES (default 65536): max bytes for merged cast input (--input + -p overrides)
|
|
@@ -103,7 +130,22 @@ Docker mode (v1) details:
|
|
|
103
130
|
- runtime.execution=docker requires runtime.docker_image.
|
|
104
131
|
- the image must provide spell-runner on PATH (this repo publishes it as a second npm bin).
|
|
105
132
|
- the bundle is mounted read-only at /spell; the runner copies it into a writable temp workdir before executing steps.
|
|
106
|
-
-
|
|
133
|
+
- hardened docker run defaults:
|
|
134
|
+
- --network none
|
|
135
|
+
- --cap-drop ALL
|
|
136
|
+
- --security-opt no-new-privileges
|
|
137
|
+
- --read-only
|
|
138
|
+
- --user 65532:65532
|
|
139
|
+
- --pids-limit 256
|
|
140
|
+
- --tmpfs /tmp:rw,noexec,nosuid,size=64m
|
|
141
|
+
- hardening env overrides (all optional):
|
|
142
|
+
- SPELL_DOCKER_NETWORK (none|bridge|host, default none)
|
|
143
|
+
- SPELL_DOCKER_USER (default 65532:65532; set empty to disable --user)
|
|
144
|
+
- SPELL_DOCKER_READ_ONLY (1 default; set 0 to disable --read-only)
|
|
145
|
+
- SPELL_DOCKER_PIDS_LIMIT (256 default; set 0 to disable --pids-limit)
|
|
146
|
+
- SPELL_DOCKER_MEMORY (default empty; when set adds --memory)
|
|
147
|
+
- SPELL_DOCKER_CPUS (default empty; when set adds --cpus)
|
|
148
|
+
- env vars passed from host -> container are restricted to connector tokens (CONNECTOR_<NAME>_TOKEN) plus SPELL_RUNTIME_STEP_TIMEOUT_MS.
|
|
107
149
|
|
|
108
150
|
6. Windows policy
|
|
109
151
|
- host mode does not assume bash/sh.
|
|
@@ -121,15 +163,17 @@ Use these effect.type words where possible:
|
|
|
121
163
|
|
|
122
164
|
8. v1 limitations (intentionally not implemented)
|
|
123
165
|
- name search or ambiguous resolution (id only)
|
|
124
|
-
- registry/marketplace
|
|
166
|
+
- registry/marketplace integration
|
|
125
167
|
- real billing execution (Stripe)
|
|
126
168
|
- DAG/parallel/rollback/self-healing
|
|
127
169
|
- advanced templating language (only {{INPUT.*}} and {{ENV.*}})
|
|
128
|
-
- docker env passthrough beyond connector tokens
|
|
170
|
+
- docker env passthrough beyond connector tokens and SPELL_RUNTIME_STEP_TIMEOUT_MS
|
|
129
171
|
|
|
130
172
|
8.1 Signature (sign + verify)
|
|
131
|
-
|
|
132
|
-
spell cast <id> --
|
|
173
|
+
spell cast requires signature verification by default. To bypass this for unsigned bundle workflows:
|
|
174
|
+
spell cast <id> --allow-unsigned ...
|
|
175
|
+
|
|
176
|
+
--require-signature remains accepted for backward compatibility.
|
|
133
177
|
|
|
134
178
|
Signing flow:
|
|
135
179
|
spell sign keygen samples --key-id default --out-dir .spell-keys
|
|
@@ -145,6 +189,35 @@ Notes:
|
|
|
145
189
|
- publisher is derived from the spell id prefix before the first / (example: samples/call-webhook -> samples).
|
|
146
190
|
- public key format is ed25519 spki DER encoded as base64url.
|
|
147
191
|
|
|
192
|
+
8.2 Entitlement tokens (billing)
|
|
193
|
+
spell license add <name> <token> now validates and stores signed entitlement tokens.
|
|
194
|
+
|
|
195
|
+
Token format:
|
|
196
|
+
- ent1.<payloadBase64url>.<signatureBase64url>
|
|
197
|
+
|
|
198
|
+
Payload JSON required fields:
|
|
199
|
+
- version ("v1")
|
|
200
|
+
- issuer (string)
|
|
201
|
+
- key_id (string)
|
|
202
|
+
- mode ("upfront" | "on_success" | "subscription")
|
|
203
|
+
- currency (string)
|
|
204
|
+
- max_amount (number)
|
|
205
|
+
- not_before (ISO string)
|
|
206
|
+
- expires_at (ISO string)
|
|
207
|
+
|
|
208
|
+
Verification rules:
|
|
209
|
+
- signature algorithm: ed25519
|
|
210
|
+
- signed message: raw payload segment bytes (exact payload base64url segment string bytes)
|
|
211
|
+
- trust source: publisher trust store (spell trust add ...) keyed by issuer + key_id
|
|
212
|
+
- token must be within not_before <= now <= expires_at
|
|
213
|
+
|
|
214
|
+
Billing-enabled cast requires a matching currently-valid entitlement:
|
|
215
|
+
- entitlement mode equals manifest.billing.mode
|
|
216
|
+
- entitlement currency equals manifest.billing.currency (case-insensitive)
|
|
217
|
+
- entitlement max_amount >= manifest.billing.max_amount
|
|
218
|
+
|
|
219
|
+
spell license list prints entitlement summary columns (issuer/mode/currency/max_amount/expires_at) and does not print raw tokens.
|
|
220
|
+
|
|
148
221
|
9. Example flow
|
|
149
222
|
1) Install a local fixture
|
|
150
223
|
spell install ./fixtures/spells/hello-host
|
|
@@ -164,6 +237,29 @@ Notes:
|
|
|
164
237
|
6) Show execution log
|
|
165
238
|
spell log <execution-id>
|
|
166
239
|
|
|
240
|
+
9.1 OSS release (pnpm + GitHub Actions)
|
|
241
|
+
Automation file:
|
|
242
|
+
.github/workflows/release.yml
|
|
243
|
+
|
|
244
|
+
Prerequisites:
|
|
245
|
+
- npm account with 2FA enabled
|
|
246
|
+
- GitHub repository secret NPM_TOKEN (publish-capable npm token)
|
|
247
|
+
|
|
248
|
+
Local release checks:
|
|
249
|
+
pnpm install
|
|
250
|
+
pnpm run typecheck
|
|
251
|
+
pnpm run lint
|
|
252
|
+
pnpm run build
|
|
253
|
+
pnpm test
|
|
254
|
+
pnpm run pack:check
|
|
255
|
+
|
|
256
|
+
Tag-based release flow:
|
|
257
|
+
npm version patch
|
|
258
|
+
git push --follow-tags
|
|
259
|
+
|
|
260
|
+
Tag push vX.Y.Z triggers GitHub Actions release and runs npm publish.
|
|
261
|
+
The workflow verifies that tag version matches package.json.
|
|
262
|
+
|
|
167
263
|
10. UI connection spec
|
|
168
264
|
- Decision-complete button integration spec:
|
|
169
265
|
/Users/koichinishizuka/spell-runtime/docs/ui-connection-spec-v1.md
|
|
@@ -172,7 +268,8 @@ Notes:
|
|
|
172
268
|
- Button registry schema:
|
|
173
269
|
/Users/koichinishizuka/spell-runtime/examples/button-registry.v1.schema.json
|
|
174
270
|
- Registry optional policy:
|
|
175
|
-
require_signature
|
|
271
|
+
require_signature=true: Execution API enforces signature (--require-signature)
|
|
272
|
+
require_signature=false or omitted: Execution API opts into unsigned path (--allow-unsigned)
|
|
176
273
|
|
|
177
274
|
11. Install from npm
|
|
178
275
|
Global install:
|
|
@@ -195,6 +292,11 @@ Quick try:
|
|
|
195
292
|
13. Runtime decision log
|
|
196
293
|
- /Users/koichinishizuka/spell-runtime/docs/runtime-decisions-v1.md
|
|
197
294
|
|
|
295
|
+
13.1 Repository policies
|
|
296
|
+
- /Users/koichinishizuka/spell-runtime/CONTRIBUTING.md
|
|
297
|
+
- /Users/koichinishizuka/spell-runtime/CODE_OF_CONDUCT.md
|
|
298
|
+
- /Users/koichinishizuka/spell-runtime/SECURITY.md
|
|
299
|
+
|
|
198
300
|
14. Execution API (async)
|
|
199
301
|
Start:
|
|
200
302
|
npm run api:dev
|
|
@@ -211,14 +313,14 @@ Defaults:
|
|
|
211
313
|
GET /
|
|
212
314
|
GET /ui/app.js
|
|
213
315
|
GET /api/buttons
|
|
214
|
-
GET /api/spell-executions (status/button_id/limit query supported)
|
|
316
|
+
GET /api/spell-executions (status/button_id/tenant_id/limit query supported)
|
|
215
317
|
POST /api/spell-executions
|
|
216
318
|
GET /api/spell-executions/:execution_id
|
|
217
319
|
|
|
218
320
|
Optional environment variables:
|
|
219
321
|
- SPELL_API_PORT
|
|
220
322
|
- SPELL_BUTTON_REGISTRY_PATH
|
|
221
|
-
- SPELL_API_AUTH_KEYS (comma-separated role=token entries; when set, /api/* requires auth and derives actor_role from token)
|
|
323
|
+
- SPELL_API_AUTH_KEYS (comma-separated role=token or tenant:role=token entries; when set, /api/* requires auth and derives actor_role + tenant_id from token)
|
|
222
324
|
- SPELL_API_AUTH_TOKENS (legacy: comma-separated tokens; when set, /api/* requires auth but does not bind role)
|
|
223
325
|
- SPELL_API_BODY_LIMIT_BYTES
|
|
224
326
|
- SPELL_API_EXECUTION_TIMEOUT_MS
|
|
@@ -232,4 +334,5 @@ Security note:
|
|
|
232
334
|
- execution logs redact secret-like keys (token, authorization, apiKey, etc.)
|
|
233
335
|
- environment-derived secret values are masked in persisted logs
|
|
234
336
|
- when auth is enabled, pass Authorization: Bearer <token> (or x-api-key) for /api routes
|
|
337
|
+
- with SPELL_API_AUTH_KEYS, non-admin list requests are restricted to their own tenant and cross-tenant tenant_id filters return 403 (TENANT_FORBIDDEN)
|
|
235
338
|
- do not set both SPELL_API_AUTH_KEYS and SPELL_API_AUTH_TOKENS at the same time
|