spell-runtime 1.2.0 → 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.
Files changed (41) hide show
  1. package/README.md +171 -16
  2. package/README.txt +145 -16
  3. package/dist/api/server.js +128 -21
  4. package/dist/api/server.js.map +1 -1
  5. package/dist/bundle/install.d.ts +1 -1
  6. package/dist/bundle/install.js +120 -6
  7. package/dist/bundle/install.js.map +1 -1
  8. package/dist/cli/index.js +44 -6
  9. package/dist/cli/index.js.map +1 -1
  10. package/dist/license/entitlement.d.ts +28 -0
  11. package/dist/license/entitlement.js +174 -0
  12. package/dist/license/entitlement.js.map +1 -0
  13. package/dist/license/store.d.ts +16 -0
  14. package/dist/license/store.js +271 -0
  15. package/dist/license/store.js.map +1 -0
  16. package/dist/policy/index.d.ts +26 -0
  17. package/dist/policy/index.js +194 -0
  18. package/dist/policy/index.js.map +1 -0
  19. package/dist/runner/cast.js +37 -3
  20. package/dist/runner/cast.js.map +1 -1
  21. package/dist/runner/dockerRunner.d.ts +2 -1
  22. package/dist/runner/dockerRunner.js +148 -13
  23. package/dist/runner/dockerRunner.js.map +1 -1
  24. package/dist/runner/hostRunner.d.ts +1 -1
  25. package/dist/runner/hostRunner.js +30 -3
  26. package/dist/runner/hostRunner.js.map +1 -1
  27. package/dist/runner/runtimeLimits.d.ts +6 -0
  28. package/dist/runner/runtimeLimits.js +48 -0
  29. package/dist/runner/runtimeLimits.js.map +1 -0
  30. package/dist/runner/spell-runner.js +0 -0
  31. package/dist/steps/httpStep.d.ts +1 -1
  32. package/dist/steps/httpStep.js +20 -6
  33. package/dist/steps/httpStep.js.map +1 -1
  34. package/dist/steps/shellStep.d.ts +5 -1
  35. package/dist/steps/shellStep.js +33 -7
  36. package/dist/steps/shellStep.js.map +1 -1
  37. package/dist/types.d.ts +4 -0
  38. package/dist/util/paths.d.ts +1 -0
  39. package/dist/util/paths.js +5 -0
  40. package/dist/util/paths.js.map +1 -1
  41. package/package.json +9 -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
- - npm
8
+ - pnpm (recommended)
9
+ - npm (supported)
9
10
 
10
11
  ```bash
11
- npm i
12
- npm run build
13
- npm test
12
+ pnpm install
13
+ pnpm run build
14
+ pnpm test
14
15
  ```
15
16
 
16
17
  Local dev:
17
18
 
18
19
  ```bash
19
- npm run dev -- --help
20
+ pnpm run dev -- --help
20
21
  ```
21
22
 
22
23
  ## Install as CLI
@@ -43,10 +44,13 @@ npm run smoke:npx
43
44
 
44
45
  ## Commands
45
46
 
46
- - `spell install <local-path>`
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 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>`
52
+ - `spell license list`
53
+ - `spell license remove <name>`
50
54
  - `spell sign keygen <publisher> [--key-id default] [--out-dir .spell-keys]`
51
55
  - `spell sign bundle <local-path> --private-key <file> [--key-id default] [--publisher <name>]`
52
56
  - `spell trust add <publisher> <public-key> [--key-id default]`
@@ -54,11 +58,43 @@ npm run smoke:npx
54
58
  - `spell trust remove <publisher>`
55
59
  - `spell log <execution-id>`
56
60
 
61
+ ## Install Sources
62
+
63
+ `spell install <source>` accepts:
64
+
65
+ - local bundle paths (existing behavior)
66
+ - pinned git URLs with an explicit ref suffix:
67
+ - `https://...#<ref>`
68
+ - `ssh://...#<ref>`
69
+ - `git@...#<ref>`
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.
76
+
77
+ Limitations:
78
+
79
+ - `git` must be installed and available on `PATH`.
80
+ - clone/auth/network behavior is delegated to your local `git` configuration.
81
+ - `spell.yaml` must exist at the cloned repository root (subdirectory installs are not supported).
82
+
57
83
  ## Storage Layout
58
84
 
59
85
  - Spells: `~/.spell/spells/<id_key>/<version>/`
60
86
  - ID index: `~/.spell/spells/<id_key>/spell.id.txt`
87
+ - Install provenance: `~/.spell/spells/<id_key>/<version>/source.json`
61
88
  - Logs: `~/.spell/logs/<timestamp>_<id>_<version>.json`
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)
62
98
 
63
99
  `id_key` is fixed as `base64url(utf8(id))`.
64
100
 
@@ -77,15 +113,42 @@ Consistency rule:
77
113
  - bundle resolution by id (and optional version)
78
114
  - input assembly (`--input` + `-p` overrides)
79
115
  - JSON Schema validation by Ajv
80
- - optional signature verification (`--require-signature`)
116
+ - signature verification (default on; bypass only with `--allow-unsigned`)
117
+ - runtime policy guard (`~/.spell/policy.json`)
81
118
  - platform guard
82
119
  - risk guard (`high`/`critical` requires `--yes`)
83
120
  - billing guard (`billing.enabled` requires `--allow-billing`)
121
+ - billing entitlement guard (`billing.enabled` + `--allow-billing` requires a matching valid entitlement from `spell license add ...`)
84
122
  - connector token guard (`CONNECTOR_<NAME>_TOKEN`)
85
123
  - execution summary output
86
124
 
87
125
  If `--dry-run` is set, command exits after summary and validation.
88
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
+
144
+ ## Runtime Safety Limits (v2 isolation)
145
+
146
+ `cast` enforces these runtime limits (used by direct CLI casts and API-triggered casts because the API invokes `spell cast`):
147
+
148
+ - `SPELL_RUNTIME_INPUT_MAX_BYTES` (default `65536`): max bytes for merged cast input (`--input` + `-p` overrides).
149
+ - `SPELL_RUNTIME_STEP_TIMEOUT_MS` (default `60000`): max runtime per `shell` step. On timeout, the runtime kills the step process and fails with the step name + timeout ms.
150
+ - `SPELL_RUNTIME_EXECUTION_TIMEOUT_MS` (default disabled): max total cast runtime across host/docker paths when set to an integer `> 0`.
151
+
89
152
  ## Runtime Model
90
153
 
91
154
  v1 supports:
@@ -98,7 +161,22 @@ Docker mode (v1) details:
98
161
  - `runtime.execution=docker` requires `runtime.docker_image`.
99
162
  - the image must provide `spell-runner` on `PATH` (this repo publishes it as a second npm bin).
100
163
  - the bundle is mounted read-only at `/spell`; the runner copies it into a writable temp workdir before executing steps.
101
- - environment variables passed from host -> container are restricted to connector tokens only (`CONNECTOR_<NAME>_TOKEN`). If your spell needs `{{ENV.*}}` for other values, provide them inside the image (or extend the runtime later).
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`.
102
180
 
103
181
  ## Windows Policy
104
182
 
@@ -120,20 +198,22 @@ Use these `effect.type` words where possible:
120
198
  ## v1 Limitations (Intentionally Not Implemented)
121
199
 
122
200
  - name search or ambiguous resolution (id only)
123
- - registry/marketplace/license verification
201
+ - registry/marketplace integration
124
202
  - real billing execution (Stripe)
125
203
  - DAG/parallel/rollback/self-healing
126
204
  - advanced templating language (only `{{INPUT.*}}` and `{{ENV.*}}`)
127
- - docker env passthrough beyond connector tokens
205
+ - docker env passthrough beyond connector tokens and `SPELL_RUNTIME_STEP_TIMEOUT_MS`
128
206
 
129
207
  ## Signature (Sign + Verify)
130
208
 
131
- If a bundle contains `spell.sig.json`, you can require signature verification at execution time:
209
+ `spell cast` requires signature verification by default. To bypass this for unsigned bundle workflows, use:
132
210
 
133
211
  ```bash
134
- spell cast <id> --require-signature ...
212
+ spell cast <id> --allow-unsigned ...
135
213
  ```
136
214
 
215
+ `--require-signature` remains accepted for backward compatibility.
216
+
137
217
  Signing flow:
138
218
 
139
219
  ```bash
@@ -153,6 +233,40 @@ Notes:
153
233
  - publisher is derived from the spell id prefix before the first `/` (example: `samples/call-webhook` -> `samples`).
154
234
  - public key format is ed25519 `spki` DER encoded as base64url.
155
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
+
156
270
  ## Example Flow
157
271
 
158
272
  ```bash
@@ -164,6 +278,38 @@ spell cast fixtures/hello-host -p name=world
164
278
  spell log <execution-id>
165
279
  ```
166
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
+
167
313
  ## Real-Use Sample Spells
168
314
 
169
315
  These are product-facing examples (separate from test fixtures):
@@ -189,12 +335,20 @@ spell cast samples/call-webhook --dry-run -p event=deploy -p source=manual -p pa
189
335
  - Button registry schema:
190
336
  - `/Users/koichinishizuka/spell-runtime/examples/button-registry.v1.schema.json`
191
337
  - Registry optional policy:
192
- - `require_signature` (when true, Execution API adds `--require-signature`)
338
+ - `require_signature`:
339
+ - `true`: Execution API enforces signature (`--require-signature`)
340
+ - `false`/omitted: Execution API opts into unsigned path (`--allow-unsigned`)
193
341
 
194
342
  ## Runtime Decision Log
195
343
 
196
344
  - `/Users/koichinishizuka/spell-runtime/docs/runtime-decisions-v1.md`
197
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
+
198
352
  ## Execution API (Async)
199
353
 
200
354
  Start API server:
@@ -214,14 +368,14 @@ By default it listens on `:8787` and reads:
214
368
  - `GET /` (minimal Receipts UI)
215
369
  - `GET /ui/app.js` (UI client script)
216
370
  - `GET /api/buttons`
217
- - `GET /api/spell-executions` (`status`, `button_id`, `limit` query supported)
371
+ - `GET /api/spell-executions` (`status`, `button_id`, `tenant_id`, `limit` query supported)
218
372
  - `POST /api/spell-executions`
219
373
  - `GET /api/spell-executions/:execution_id`
220
374
 
221
375
  Optional environment variables:
222
376
  - `SPELL_API_PORT`
223
377
  - `SPELL_BUTTON_REGISTRY_PATH`
224
- - `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)
225
379
  - `SPELL_API_AUTH_TOKENS` (legacy: comma-separated tokens; when set, `/api/*` requires auth but does not bind role)
226
380
  - `SPELL_API_BODY_LIMIT_BYTES`
227
381
  - `SPELL_API_EXECUTION_TIMEOUT_MS`
@@ -235,4 +389,5 @@ Security note:
235
389
  - execution logs redact secret-like keys (`token`, `authorization`, `apiKey`, etc.)
236
390
  - environment-derived secret values are masked in persisted logs
237
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`)
238
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
- - npm
7
+ - pnpm (recommended)
8
+ - npm (supported)
8
9
 
9
10
  Install dependencies:
10
- npm i
11
+ pnpm install
11
12
 
12
13
  Build:
13
- npm run build
14
+ pnpm run build
14
15
 
15
16
  Test:
16
- npm test
17
+ pnpm test
17
18
 
18
19
  Local dev:
19
- npm run dev -- --help
20
+ pnpm run dev -- --help
20
21
 
21
22
  Binary smoke checks:
22
23
  npm run smoke:link
@@ -30,10 +31,13 @@ Manual npx (local package):
30
31
  npx --yes --package file:. spell --help
31
32
 
32
33
  2. CLI commands
33
- - spell install <local-path>
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 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>
39
+ - spell license list
40
+ - spell license remove <name>
37
41
  - spell sign keygen <publisher> [--key-id default] [--out-dir .spell-keys]
38
42
  - spell sign bundle <local-path> --private-key <file> [--key-id default] [--publisher <name>]
39
43
  - spell trust add <publisher> <public-key> [--key-id default]
@@ -41,10 +45,37 @@ Manual npx (local package):
41
45
  - spell trust remove <publisher>
42
46
  - spell log <execution-id>
43
47
 
48
+ 2.1 Install sources
49
+ - spell install <source> accepts:
50
+ - local bundle paths (existing behavior)
51
+ - pinned git URLs with explicit refs:
52
+ - https://...#<ref>
53
+ - ssh://...#<ref>
54
+ - git@...#<ref>
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.
60
+
61
+ Limitations:
62
+ - git must be installed and available on PATH.
63
+ - clone/auth/network behavior is delegated to your local git configuration.
64
+ - spell.yaml must exist at the cloned repository root (subdirectory installs are not supported).
65
+
44
66
  3. Storage layout
45
67
  - Spells: ~/.spell/spells/<id_key>/<version>/
46
68
  - ID index: ~/.spell/spells/<id_key>/spell.id.txt
69
+ - Install provenance: ~/.spell/spells/<id_key>/<version>/source.json
47
70
  - Logs: ~/.spell/logs/<timestamp>_<id>_<version>.json
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)
48
79
 
49
80
  id_key is fixed as base64url(utf8(id)).
50
81
  - id is the logical identifier (display, package identity).
@@ -59,15 +90,37 @@ Cast performs these checks before execution:
59
90
  - Bundle resolution by id (and optional version)
60
91
  - Input assembly (--input + -p overrides)
61
92
  - JSON Schema validation by Ajv
62
- - Optional signature verification (--require-signature)
93
+ - Signature verification (default on; bypass only with --allow-unsigned)
94
+ - Runtime policy guard (~/.spell/policy.json)
63
95
  - Platform guard
64
96
  - Risk guard (high/critical requires --yes)
65
97
  - Billing guard (billing.enabled requires --allow-billing)
98
+ - Billing entitlement guard (billing.enabled + --allow-billing requires a matching valid entitlement from spell license add ...)
66
99
  - Connector token guard (CONNECTOR_<NAME>_TOKEN)
67
100
  - Execution summary output
68
101
 
69
102
  If --dry-run is set, command exits after summary and validation.
70
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
+
118
+ 4.1 Runtime safety limits (v2 isolation)
119
+ cast enforces these runtime limits (for direct CLI casts and API-triggered casts, because the API invokes spell cast):
120
+ - SPELL_RUNTIME_INPUT_MAX_BYTES (default 65536): max bytes for merged cast input (--input + -p overrides)
121
+ - SPELL_RUNTIME_STEP_TIMEOUT_MS (default 60000): max runtime per shell step; timed out step process is killed and cast fails with step name + timeout ms
122
+ - SPELL_RUNTIME_EXECUTION_TIMEOUT_MS (default disabled): max total cast runtime when set to an integer > 0 (enforced in host and docker paths)
123
+
71
124
  5. Runtime model
72
125
  v1 supports:
73
126
  - host: steps run in order, shell/http supported.
@@ -77,7 +130,22 @@ Docker mode (v1) details:
77
130
  - runtime.execution=docker requires runtime.docker_image.
78
131
  - the image must provide spell-runner on PATH (this repo publishes it as a second npm bin).
79
132
  - the bundle is mounted read-only at /spell; the runner copies it into a writable temp workdir before executing steps.
80
- - env vars passed from host -> container are restricted to connector tokens only (CONNECTOR_<NAME>_TOKEN). If your spell needs {{ENV.*}} for other values, provide them inside the image (or extend the runtime later).
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.
81
149
 
82
150
  6. Windows policy
83
151
  - host mode does not assume bash/sh.
@@ -95,15 +163,17 @@ Use these effect.type words where possible:
95
163
 
96
164
  8. v1 limitations (intentionally not implemented)
97
165
  - name search or ambiguous resolution (id only)
98
- - registry/marketplace/license verification
166
+ - registry/marketplace integration
99
167
  - real billing execution (Stripe)
100
168
  - DAG/parallel/rollback/self-healing
101
169
  - advanced templating language (only {{INPUT.*}} and {{ENV.*}})
102
- - docker env passthrough beyond connector tokens
170
+ - docker env passthrough beyond connector tokens and SPELL_RUNTIME_STEP_TIMEOUT_MS
103
171
 
104
172
  8.1 Signature (sign + verify)
105
- If a bundle contains spell.sig.json, you can require signature verification at execution time:
106
- spell cast <id> --require-signature ...
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.
107
177
 
108
178
  Signing flow:
109
179
  spell sign keygen samples --key-id default --out-dir .spell-keys
@@ -119,6 +189,35 @@ Notes:
119
189
  - publisher is derived from the spell id prefix before the first / (example: samples/call-webhook -> samples).
120
190
  - public key format is ed25519 spki DER encoded as base64url.
121
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
+
122
221
  9. Example flow
123
222
  1) Install a local fixture
124
223
  spell install ./fixtures/spells/hello-host
@@ -138,6 +237,29 @@ Notes:
138
237
  6) Show execution log
139
238
  spell log <execution-id>
140
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
+
141
263
  10. UI connection spec
142
264
  - Decision-complete button integration spec:
143
265
  /Users/koichinishizuka/spell-runtime/docs/ui-connection-spec-v1.md
@@ -146,7 +268,8 @@ Notes:
146
268
  - Button registry schema:
147
269
  /Users/koichinishizuka/spell-runtime/examples/button-registry.v1.schema.json
148
270
  - Registry optional policy:
149
- require_signature (when true, Execution API adds --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)
150
273
 
151
274
  11. Install from npm
152
275
  Global install:
@@ -169,6 +292,11 @@ Quick try:
169
292
  13. Runtime decision log
170
293
  - /Users/koichinishizuka/spell-runtime/docs/runtime-decisions-v1.md
171
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
+
172
300
  14. Execution API (async)
173
301
  Start:
174
302
  npm run api:dev
@@ -185,14 +313,14 @@ Defaults:
185
313
  GET /
186
314
  GET /ui/app.js
187
315
  GET /api/buttons
188
- GET /api/spell-executions (status/button_id/limit query supported)
316
+ GET /api/spell-executions (status/button_id/tenant_id/limit query supported)
189
317
  POST /api/spell-executions
190
318
  GET /api/spell-executions/:execution_id
191
319
 
192
320
  Optional environment variables:
193
321
  - SPELL_API_PORT
194
322
  - SPELL_BUTTON_REGISTRY_PATH
195
- - 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)
196
324
  - SPELL_API_AUTH_TOKENS (legacy: comma-separated tokens; when set, /api/* requires auth but does not bind role)
197
325
  - SPELL_API_BODY_LIMIT_BYTES
198
326
  - SPELL_API_EXECUTION_TIMEOUT_MS
@@ -206,4 +334,5 @@ Security note:
206
334
  - execution logs redact secret-like keys (token, authorization, apiKey, etc.)
207
335
  - environment-derived secret values are masked in persisted logs
208
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)
209
338
  - do not set both SPELL_API_AUTH_KEYS and SPELL_API_AUTH_TOKENS at the same time