ikigai-script 0.1.0

Scripts (Lisp; SPARQL queries with typed parameters and authority derived from the graphs they name; or plans, validated before they are stored, with authority derived from their steps) as resources in any ikigai host: `urn:script:{name}` is an atom with content-addressed versions, a compiled form, a result you read and runs you record, run under the least of what the script declares, what its publisher held at publish time, the host's ceiling and the runner's own capability. A host library: no binary, no elevation.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
# ikigai-script

**Scripts as resources in any ikigai host.** Write a few lines of Lisp, or a SPARQL
query, publish them at a name, and every door the host already has (REST, the REPL, MCP,
a timer, an admin panel) can fetch them, run them, and see who ran what, under exactly
what authority. No second API, no second permission system: a script is a resource like
any other, and running it is a read or a write like any other.

```text
urn:script:{name}                    Source Exists Sink Delete  the script: fetch WITHOUT running, publish, retire
urn:script:{name}:version:{digest}   Source Exists              one immutable, content-addressed version
urn:script:{name}:compiled           Source                     the head prepared for its evaluator (cached)
urn:script:{name}:result             Source                     run it as a READ: its answer
urn:script:{name}:runs               Sink                       run it for EFFECTS: answers the run's IRI
urn:script:{name}:run:{id}           Source Exists              one recorded run
urn:script:eval                      Sink                       run supplied code under the caller's own authority
urn:script:catalog                   Source                     every script the caller may read
```

A published SPARQL query or plan is also its OWN entry in the host's catalog:
`urn:script:{name}:result` and `…:runs` with its declared parameters as their arguments (see
"Queries as scripts" and "Plans as scripts").

**Which language?** When assembling anything, prefer **a query, then a plan, then Lisp.** A
query and a plan both derive their authority from what they name and are checked before they
run; Lisp declares its authority and can do anything its capability allows, in any order. Use
Lisp for control flow a plan cannot express, and preferably as one step inside a plan.

## Why resources

Because then every modality is a door, not a feature. `ikigai-web` already maps HTTP
methods onto verbs, so the REST surface is this table projected: `GET urn:script:x` fetches
the source without running it, `PUT` publishes, `DELETE` retires, `GET …:result` runs it as
a read, `POST …:runs` runs it for its effects. An agent sees a published script in its
manifold under its own grant. And because a script reaches the world only through kernel
verbs, its caching, its golden threads and its trace are the kernel's, unchanged:

- **Running is a read.** `…:result` is cacheable exactly as far as the script's own
  sub-requests are (a Lisp program opts in with `(cacheable …)`), so a pure public script
  answered a thousand times runs once. Republishing cuts the script's golden thread, and
  the cached answer goes with it.
- **Running is a write.** `…:runs` records a run atom (who, as the host stamped it; which
  version; under exactly what capability; when; the outcome; the trace span when the host
  traced) and answers its IRI. A failed run is recorded too, and its error keeps its type
  and names the record.
- **Versions are content.** Every publish stores a version named by the sha256 of its
  language, declared capability and source (`sha256:` and hex, the ecosystem's tagged
  digest). The head moves; old versions stay fetchable by name.

## The authority rule

Three authorities, never merged:

1. **Publish or change**: `urn:cap:script:write:{name}`; retire with `urn:cap:script:delete:{name}`.
2. **Run**: `urn:cap:script:run:{name}`. A script marked `public=true` is also runnable by
   any holder of `urn:cap:script:run:public`, which is the grant a host gives its anonymous
   principal.
3. **Runs as**: the runner's own capability, attenuated to

   ```text
   { declared } ∩ { what the publisher held at publish } ∩ { the host's ceiling for this script }
     + every exclusion the publisher or the ceiling carries
   ```

**No elevation.** A publish that declares a scope its publisher does not hold is refused
(`Denied`, naming the scopes): a script never runs with more than its publisher held, and
its declared `requires` never says more than its runs get. A run never holds anything its
runner does not: the only way this crate makes a run's authority is
`Invocation::issue_attenuated`, and there is no form that widens. Exclusions travel: a
publisher who could not read `/root/secret` publishes a script that cannot either, whoever
runs it.

Reading a script's source is a fourth grant, `urn:cap:script:read:{name}` (or
`urn:cap:script:read:public` for a public, published script): code is a different
sensitivity from running it. A caller without the grant is refused the same way whether
the script exists or not.

**Evaluating code is itself authority.** Every Lisp script declares `urn:cap:lisp`
implicitly, so its publisher must hold it, its host ceiling must allow it, and its runner
must hold it. See "What the host must supply" for what that means for anonymous runs.

## Drafts are private

A draft (`state=draft`, or `urn:script:eval save=`) is visible only to its **author** until it
is published. The author is the principal the host stamps (see "What the host must supply"),
the same value a publish records as the script's publisher, compared with what the host stamps
for the reader: never an argument, so no caller can name itself the author. Seeing a draft
needs the read (or run) grant AND authorship, so privacy only narrows what a grant reaches.

- To any other caller holding the grant, a draft is **absent**, and told so exactly as a name
  nobody wrote is: `NotFound` with the same words, `Exists` false, no catalog row, its compiled
  form (and so its run) not found. `Denied` would tell a reader "someone's draft is here",
  which is the thing privacy hides. A caller without the grant is `Denied` either way, as
  before.
- It is a property of the VERSION: one never published stays its author's after the head
  moves past it (fetching it by digest finds nothing), retiring a draft does not publish it,
  and once a version is published it is every reader's for good. Root sees every draft: it is
  the host's own authority, and holds the backend they are stored in.
- An author's answer is **never cached**: the kernel keys its cache on the capability, and two
  people holding the same capability are two principals.
- A SPARQL query or plan that was never published has **no catalog entry of its own**. `Meta`
  is answered to anyone who can reach the door, and the script's own contract is made of its
  text (its parameters and leading comment), so a draft wears the generic contract until it is
  published.
- ⚠ **A host that stamps no principal has no authors**: `UNSTAMPED` and `ANONYMOUS` name many
  callers at once, so a draft written under either is root's alone, and a refusal to an
  unstamped caller says so. The history in a published script's JSON record still lists each
  superseded draft's digest and who wrote it; never its content.

## The graph face

The catalog and every run record also answer `as=text/turtle`: PROV-O and the shared
vocabulary, every node an IRI this crate already names (no blank nodes), so the visualizers
and SPARQL read them as one graph and two answers merge without renaming.

```text
<urn:script:catalog>  dcterms:hasPart  <urn:script:{name}>              one per catalog row
<urn:script:{name}>   dcterms:identifier "{name}" ; ik:contentHash "sha256:…"   its head version
<urn:script:{name}:version:{digest}>  prov:specializationOf  <urn:script:{name}>
<urn:script:{name}:run:{id}>  a prov:Activity ;
    prov:used <urn:script:{name}:version:{digest}> ;          the version that ran
    prov:wasAssociatedWith <principal> ;                      as the host stamped it (when an IRI)
    prov:startedAtTime "…"^^xsd:dateTime ; prov:endedAtTime "…"^^xsd:dateTime ;
    ik:outcome <urn:script:outcome:ok>                         or …:failed; absent while running
```

A run reaches its script through the version it used, so "every run of this script" and "its
last run" are queries rather than terms. Each field the graph carries is pinned against the
JSON face as an RDF term (`tests/turtle.rs`), with the `ik:` ranges read from the published
vocabulary.

⚠ **The graph carries less than the JSON**, because the vocabulary has no term for the rest and
this crate does not invent `ik:` terms: a script's state, public flag, language, declared and
granted capability and exclusions; a run's capability, failure kind and message, result, and
trace span. Use `as=application/json` for those.

## Mounting it

A host library: no binary. A host mounts `space(config)` beside the evaluator its scripts
are written for, and decides the three things only a host can.

```rust,no_run
use ikigai_core::{Fallback, Kernel, Space, BINDINGS_THREAD};
use ikigai_script::{authority::{Ceiling, CeilingPolicy}, space, DirBackend, SpaceConfig};
use ikigai_script::sparql::SparqlDoor;
use std::sync::{Arc, OnceLock, Weak};

let config_home = std::path::PathBuf::from("/path/to/config-home");
let backend = Arc::new(DirBackend::open(config_home.join("scripts")).expect("a directory"));
let ceilings = config_home.join("script-authority");
let ceiling: CeilingPolicy = Arc::new(move |name| {
    std::fs::read_to_string(ceilings.join(name))
        .ok()
        .and_then(|text| Ceiling::parse(&text).ok())
        .unwrap_or_else(Ceiling::nothing) // no file: the script may touch nothing
});
// The kernel does not exist yet when the space is built; the change hook reaches it later.
let kernel_cell: Arc<OnceLock<Weak<Kernel>>> = Arc::new(OnceLock::new());
let cell = Arc::clone(&kernel_cell);
let scripts = space(SpaceConfig::new(backend, ceiling)
    .principal(Arc::new(|_inv| /* what your door authenticated */ "urn:example:me".into()))
    // SPARQL scripts, run against the store bound below.
    .sparql(SparqlDoor::store())
    // A published query is its own catalog entry: re-describe after every publish.
    .on_change(Arc::new(move |_name| {
        if let Some(kernel) = cell.get().and_then(Weak::upgrade) {
            kernel.cut(BINDINGS_THREAD);
        }
    })));
let store = ikigai_store::DurableStore::in_memory().expect("a store"); // `open(path)` for real
let root = Fallback::new(vec![
    Arc::new(scripts) as Arc<dyn Space>,
    Arc::new(ikigai_store::space(store)) as Arc<dyn Space>,
    Arc::new(ikigai_lisp::space()) as Arc<dyn Space>,
]);
let kernel = Arc::new(Kernel::new(Arc::new(root)));
let _ = kernel_cell.set(Arc::downgrade(&kernel));
```

### What the host must supply

- **Storage**: a `Backend`. `MemoryBackend` for tests and for hosts that publish their
  scripts from configuration at every start; `DirBackend` for plain files under a
  directory (one host process per directory).
- **The ceiling** for each script, from `<config home>/script-authority/{name}`: one
  `urn:cap:` scope per line, `#` comments, `prefix*` families allowed, `-` exclusions
  carried by every run, a lone `*` for no ceiling. `Ceiling::parse` reads the format; the
  library never reads the config home itself. **When the host changes a ceiling it cuts
  `urn:script:{name}:authority`**, or a cached result computed under the old ceiling is
  served until something else cuts it.
- **The principal**, a function of the invocation (what the host's door authenticated),
  recorded on every publish and run, and the identity a draft is private to. Never an
  argument: a caller cannot name itself. The default records `urn:script:principal:unstamped`,
  under which no caller is a draft's author (see "Drafts are private"); stamp
  `urn:script:principal:anonymous` for a caller the door cannot identify.
- **A grant for anonymous runs**, if it wants them: `urn:cap:script:run:public`,
  `urn:cap:script:read:public`, and `urn:cap:lisp`. ⚠ The last is not optional: a run is
  a sub-request to `urn:lisp:eval` under the runner's narrowed capability, and narrowing
  cannot add the language grant the runner lacks. So a host that gives anonymous callers
  `urn:cap:lisp` should not also route `urn:lisp:eval` to its anonymous door, or they can
  evaluate any code there (under nothing else, but with the CPU). Rate-limit the door
  (`ikigai-throttle`), and put a `Timeout` in front of the evaluator.
- **An out-of-band edit is survivable, not visible.** `DirBackend` compares-and-sets every
  head against what the writer read (an editor who got there first wins, and the writer
  gets a `Conflict`), checks every version file against its digest (an edited version is
  refused by name; publishing the content again repairs it), and fails a broken head for
  that script alone. A read the kernel cached before a hand edit is served until the host
  cuts `urn:script:{name}`.

## Storage: why the atoms hold their own state

The ledger owns no bytes: it authors SPARQL against `ikigai-store`'s named graphs. Scripts
cannot, because **a sub-request carries the caller's capability unchanged**. Whatever this
crate writes on a caller's behalf, the caller must hold the grant to write directly, and
here the record IS the authority: every publisher would hold write over the graph (or
`urn:file:` path) holding its own granted-at-publish snapshot, and could widen it; every
runner, anonymous included, would hold write over the run log, and could forge it. So the
two atoms (a script, a run) hold their state behind a `Backend`, nothing but this crate's
endpoints write it, and no caller ever needs a storage grant. The golden threads are still
the kernel's: a Sink or Delete at `urn:script:{name}` cuts the thread every derived read
hangs from.

## Lisp

Reached as a resource: a run is a sub-request to
`urn:lisp:eval` with the program as `in` and the run's input as `data` (read with
`(input)`), so this crate links no interpreter. Built and tested against the published
`ikigai-lisp` 0.1.15. When 0.2.0 (the allowlist sandbox) is published nothing here changes
but the dev-dependency pin: the sandbox narrows what a program can do inside the
evaluator, and everything it can reach outside still goes through the verbs, under the
run's capability.

The compiled form, for Lisp, is preparation rather than compilation: `ikigai-lisp`
offers no compile step to cache, so `…:compiled` is the head version's program bound to
the authority it runs under, cached and cut with the script. A language with a real
compile step (plans, TypeScript) fills the same slot.

## Queries as scripts

A stored SPARQL query is a script whose language is `sparql`. No second system: it gets
versions, the three authorities, run records and the manifold from everything above, and
three things of its own, each read from the PARSED text and never from a caller's argument.

```text
sink urn:script:stale language=sparql content='
# Open items nobody has touched in a while.
# @param days xsd:integer default 7 -- untouched for more than this many days
PREFIX ledger: <https://ikigai-rs.dev/ns/ledger#>
SELECT ?item ?age FROM <urn:iki:ledger:default>
WHERE { ?item ledger:age ?age FILTER(?age > ?days) } ORDER BY DESC(?age)'

source urn:script:stale:result days=14 as=text/csv
```

**Its form.** SELECT, ASK, CONSTRUCT and DESCRIBE run as READS at `…:result`, cached as the
store's answer is (they hang from the store's write threads, so a write to the store
recomputes them, and a republish cuts the script's thread as for Lisp). An UPDATE runs as a
WRITE at `…:runs`, with a run record; `…:result` on an update is refused, naming `…:runs`. A
query may also run through `…:runs` when a run should be recorded.

**Its parameters**, declared in the comment block before the first token, one per line:

```text
# @param <name> <type> [required | optional | default <value>] [-- <summary>]
```

`<name>` is the query variable it binds (`?days` or `$days`); `<type>` is an XSD datatype
(`xsd:string`, `xsd:boolean`, `xsd:integer`, `xsd:decimal`, `xsd:double`, `xsd:float`,
`xsd:date`, `xsd:dateTime`, `xsd:time`, the three durations, `xsd:anyURI`), bound as a typed
literal after its lexical form is checked and in its canonical form; or a class (`<iri>`, or
`rdfs:Resource` for any IRI), bound as an IRI. Bare means required; a `default` is optional
with that value (a JSON string, `"like this"`, when it has a space). A declared parameter the
query never uses, one the query binds itself (`BIND … AS`, `VALUES`, `GROUP BY`), a datatype
parameter in a predicate or graph-name position, and a declaration outside that block are
refused at publish. The parameters become the ArgSpecs of the script's own `…:result` and
`…:runs`, so they appear in the catalog, MCP and the Emacs aliases as real arguments. A run
refuses a missing required parameter (`MissingArgument`), a value not of its type and an
argument the script does not declare (`InvalidArgument`: a binding the query does not mention
is refused, never ignored). Through `…:runs` they may also arrive as one JSON object piped as
`content`.

**★ Values are bound as RDF terms, never spliced.** The text is parsed, each parameter's
variable is replaced by its term in the ALGEBRA (patterns, paths, expressions, templates; a
projected parameter becomes `(term AS ?p)`), and the store is sent what spargebra serializes
from that algebra, which writes a literal as one escaped token. A value that tries to close a
string, open a graph pattern or smuggle a second operation arrives as one literal:
`tests/sparql.rs` sends five such values through a query and a `DROP ALL` through an update,
and reads each back intact beside an untouched store. An IRI parameter refuses what is not an
IRI rather than mangling it.

### Derived authority

The author does not declare what a query needs; the text says it. Each graph a query names
(`FROM`, `FROM NAMED`, `GRAPH <iri>`, and the host's default dataset when it reads the default
graph without `FROM`) derives `urn:cap:store:read:graph:<iri>`; an update's one graph derives
`urn:cap:store:write:graph:<iri>`, and the read grant too when it has a `WHERE` (the store's
own rule). A `requires=` at publish that says anything else is refused, naming the derived
set. The phase-1 rule holds on top: a run gets the derived set, narrowed to what its publisher
held at publish and the host's ceiling allows, intersected with the runner's own capability.
A grant any of those withholds is refused before the store is asked, naming the graph and the
grant.

`GRAPH ?g` (a graph the text does not fix) derives the family `urn:cap:store:read:graph:*` and
runs over **the caller's readable union, and never more**: the graph grants the runner holds,
that the publisher held (all of them, when the publisher was root) and the ceiling allows,
computed per run. A parameter naming a graph is admitted the same way.

### The dataset, and what is refused at publish

A run is a sub-request to the store's graph-scoped doors (`urn:iki:store:graph-select`, `-ask`,
`-construct`, `-describe`, `-update`), so the store's pre-parse bound, sized stack, tenancy and
coming time budget apply to every run; this crate parses but never evaluates. Those doors take
ONE set of graphs, whose merge is the default graph and whose members are the named graphs, so
a script's dataset is every graph it names and its `FROM` clauses are removed from the text the
store sees. Where that one dataset would change the answer, the query is refused at publish
instead of answered differently: a query reading the default graph (a bare pattern, or a
DESCRIBE) must name its whole dataset with `FROM`, and may not also read a `GRAPH ?g`. An update
writes exactly one named graph and reads no other; `LOAD`, `SERVICE`, `DROP ALL` and a write to
the default graph are refused. The text is checked against the store's bound
(`src/limits.rs`, copied from ikigai-store) before it is parsed here at all.

### Faces

`as=` on `…:result`: `application/sparql-results+json` (the default), `+xml`, `text/csv` or
`text/tab-separated-values` for SELECT and ASK; `text/turtle` (the default) or
`application/n-triples` for CONSTRUCT and DESCRIBE. Anything else is refused, never substituted.

### What a host supplies for queries

- **The door**: `SpaceConfig::sparql(SparqlDoor::store())`, beside an `ikigai_store::space`.
  `SparqlDoor::store_at(prefix)` for a store mounted elsewhere; `.default_graphs(…)` for the
  dataset a query without `FROM` reads. Without a door, `language=sparql` is refused.
- **The change hook**: `SpaceConfig::on_change(…)`, wired to
  `kernel.cut(ikigai_core::BINDINGS_THREAD)`. Each published query is its own catalog entry
  with its own parameters, and the kernel caches descriptions under that thread, which only
  the host can cut. Unwired, a new query runs at once but the catalog, MCP and the engine's
  argument routing describe the old set.
- **The store grants** its runners need: the exact `urn:cap:store:read:graph:<iri>` (and
  write) tokens. A root runner of a query over `GRAPH ?g` that a root published lists the
  store's graphs through `urn:iki:store:graphs`, under the broad `urn:cap:store:read` it holds.
- ⚠ **A script's contract is public.** `Meta` is answered from the description, unguarded, so
  anyone who can reach the door can learn a published query's parameter names and types (not
  its text, which stays behind the read grant). The catalog itself needs the kernel's inspect
  grant, and the action manifold offers a private query only to holders of its run grant.

## Plans as scripts

A plan (an `ik:Process` graph in the process vocabulary, Turtle) is a script whose language is
`plan`. It is the most analyzable language there is: finite (no conditionals or loops; a branch
is a step that calls a language), so it is validated before it is stored and its authority is
derived rather than declared. Three host doors do the work, each reached by sub-request, so
this crate links no plan runner:

```text
urn:plan:validate   the SHACL report against the vocabulary's shapes, plus the executor's checks
urn:plan:requires   the capability each step's target contract requires (read with Meta: nothing runs)
urn:plan:eval       run the plan: every step a sub-request under the caller's capability
```

```text
sink urn:script:hello language=plan content='
# Greet someone.
@prefix ik: <https://ikigai-rs.dev/ns#> .
<urn:plan:hello> a ik:Process ; ik:input <urn:plan:hello:input:who> ;
    ik:step <urn:plan:hello:step:1> ; ik:result <urn:plan:hello:step:1> .
<urn:plan:hello:input:who> ik:inputName "who" ; ik:required false ; ik:default "world" .
<urn:plan:hello:step:1> a ik:Step ; ik:verb "Source" ; ik:resolves <urn:example:greet> ;
    ik:argument <urn:plan:hello:step:1:arg:who> .
<urn:plan:hello:step:1:arg:who> a ik:Argument ; ik:inputName "who" ;
    ik:ref <urn:plan:hello:var:who> .'

source urn:script:hello:result who=brian
```

**Publishing validates.** `urn:plan:validate` runs first, and a plan that does not conform is
refused (`InvalidArgument` on `content`) naming every shape it broke, the node and the shape's
message, e.g. `urn:ikigai:shape:step at <…:step:2>: a step is fed by at most one of
ik:pipeFrom, ik:mapOver, ik:forkOf`. Nothing is stored.

**Authority is derived.** `urn:plan:requires` reads each step's target contract and answers what
it requires (`Description::required_scopes` for the step's verb). The union is stored with the
version, and a `requires=` that says anything else is refused, naming the derived set. A step
whose target resolves nowhere makes the derivation incomplete, and the publish is refused,
naming the step: storing the rest would store a floor as the requirement. The phase-1 rule
holds on top: a run gets the derived set, narrowed to what the publisher held at publish and
the host's ceiling allows, intersected with the runner's own capability, and each step then
meets its own target's floor inside the evaluator. A plan's runner needs no language grant.
The set is derived at PUBLISH: if a step's target later asks for more, that step is refused
(fail closed) until the plan is republished and derived again.

**Read or write, from the steps.** A plan whose every step is a `Source`, `Exists` or `Meta`
runs as a READ at `…:result`, cacheable exactly as far as its least cacheable step (a republish
cuts it). Any `Sink` or `Delete` step anywhere in the graph makes it a WRITE: `…:result` refuses
it, naming `…:runs`, where every run is recorded. Decided from the parsed plan, never from an
argument.

**Its parameters** are its `ik:input` nodes: arguments of its own `…:result` and `…:runs`, with
their `ik:class`, `ik:default` and `ik:summary`. A run refuses an argument the plan does not
declare and a required one with no default before anything runs; through `…:runs` they may
also arrive as one JSON object piped as `content`. `as=` is passed to the evaluator, which
transrepts the result or refuses. `in`, `as`, `name` and `content` cannot be parameter names.

**A derived family** (a step whose target declares `urn:cap:net:*`, "holds some grant under this
prefix") is stored as the publisher's own grants under it, or, for a root publisher, as the
family, which each run turns into the RUNNER's grants under it that the ceiling allows. A root
runner cannot be enumerated, so it gets the members the ceiling names; under no ceiling it keeps
the bare family, which the kernel's floor admits and the target's own rule (a host, a path)
refuses. A host that wants a root-published plan with such a step to run under root lists the
members in that script's ceiling.

### What a host supplies for plans

Bind the three doors (`ikigai-engine`'s plan space, which needs `urn:shacl:validate` beside it),
and wire `SpaceConfig::on_change` as for queries: each published plan is its own catalog entry.
Without the doors, `language=plan` is refused (`this host takes no plans`). A plan typed ad hoc
runs at `urn:plan:eval` itself, under the caller's own capability: `urn:script:eval` points
there rather than being a second door onto it.

⚠ **Tested against a test double.** The doors ship in `ikigai-engine` 0.1.44, which is not
published yet, so `tests/common/plan.rs` is a double honoring their contract (ledger #956, part
A). When it is published, the suite should run once against the real space too.

## Not in this version

- **PATCH** (edit in place) and **rollback** (re-pointing the head at an older version):
  republish the content instead.
- **Triggers** (startup, timers, tuplespace drops, thread cuts, ledger events, webhooks),
  **bindings** (a script as an endpoint, transreptor or overlay), the **stepper**, and
  **signed elevation**: later phases of the design.
- **A graph face for the script and its versions**: they are `text/plain` and
  `application/json` only (the catalog and run records have Turtle).
- **For SPARQL**: the SPARQL Protocol face over `urn:script:eval` (ledger #955; `eval` refuses
  `language=sparql` until then); list-valued parameters (an `IN (…)` or `VALUES` over several
  terms); an update whose graph is a parameter; `urn:sparql:*` as a door (its per-query space
  reads kernel resources under the caller's own authority, so there is no graph token to derive,
  and its shared-store space has one coarse update grant).
- **Piping into `…:result`**: its one input, `data`, is optional, so the engine has no
  required argument to route a pipe into. Pipe into `…:runs` (its `content`), or name
  `data=`.

## License

MIT OR Apache-2.0.