Skip to main content

ikigai_script/
lib.rs

1//! **Scripts as resources in any ikigai host.** A script is not a feature beside the
2//! resources; it IS one, named `urn:script:{name}`, and every way of reaching it (a REST
3//! door, the REPL, an MCP agent, a timer, a panel) is a door onto the same names.
4//!
5//! ```text
6//! urn:script:{name}                    Source Exists Sink Delete  the script: fetch WITHOUT running, publish, retire
7//! urn:script:{name}:version:{digest}   Source Exists              one immutable, content-addressed version
8//! urn:script:{name}:compiled           Source                     the head prepared for its evaluator (cached)
9//! urn:script:{name}:result             Source                     run it as a READ: its answer
10//! urn:script:{name}:runs               Sink                       run it for EFFECTS: answers the run's IRI
11//! urn:script:{name}:run:{id}           Source Exists              one recorded run
12//! urn:script:eval                      Sink                       run supplied code under the caller's own authority
13//! urn:script:catalog                   Source                     every script the caller may read
14//! ```
15//!
16//! # A host library
17//!
18//! This crate binds no binary and mounts nowhere by itself. A host (`ikigai-embedded`,
19//! gonk, the CMS) mounts [`space`] beside the evaluator it offers and decides the three
20//! things only a host can — see [`SpaceConfig`]:
21//!
22//! - **where scripts live**: a [`Backend`] ([`MemoryBackend`], or [`DirBackend`] for plain
23//!   files under a directory the host chooses);
24//! - **the ceiling** each script may run under ([`authority::CeilingPolicy`], from the
25//!   host's `<config home>/script-authority/{name}`);
26//! - **who a request comes from** ([`authority::PrincipalStamper`]), recorded on every
27//!   publish and run and never taken from the caller.
28//!
29//! ```
30//! use ikigai_core::{Capability, Fallback, Kernel, Space};
31//! use ikigai_script::{authority, space, MemoryBackend, SpaceConfig};
32//! use std::sync::Arc;
33//!
34//! let scripts = space(SpaceConfig::new(
35//!     Arc::new(MemoryBackend::new()),
36//!     // Fail closed: a script the host has not given a ceiling may touch nothing.
37//!     authority::same_for_all(authority::Ceiling::nothing()),
38//! ));
39//! // Beside the evaluator the scripts are written for (`ikigai_lisp::space()`):
40//! let root = Fallback::new(vec![Arc::new(scripts) as Arc<dyn Space> /*, lisp, … */]);
41//! let kernel = Kernel::new(Arc::new(root));
42//! # let _ = (kernel, Capability::root());
43//! ```
44//!
45//! # The authority rule
46//!
47//! Three authorities, never merged: **publish** (`urn:cap:script:write:{name}`, and
48//! `…:delete:{name}` to retire), **run** (`urn:cap:script:run:{name}`, or
49//! [`authority::CAP_RUN_PUBLIC`] for a public script), and **runs as**:
50//!
51//! ```text
52//! runner's capability, attenuated to
53//!   { declared } ∩ { what the publisher held at publish } ∩ { the host's ceiling }
54//!   + every exclusion the publisher or the ceiling carries
55//! ```
56//!
57//! **No elevation**: a publish declaring a scope its publisher does not hold is refused,
58//! a run never holds anything its runner does not, and the only way a run's authority is
59//! made is [`Invocation::issue_attenuated`](ikigai_core::Invocation::issue_attenuated) —
60//! there is no form that widens. See [`authority`].
61//!
62//! # Languages are resources
63//!
64//! A run is a sub-request to the language's evaluator, so this crate links no interpreter:
65//!
66//! - **Lisp** reaches `urn:lisp:eval`, and the evaluator's own capability (`urn:cap:lisp`) is
67//!   part of what every Lisp script declares: evaluating code is itself authority.
68//! - **SPARQL** reaches the host's store through its graph-scoped doors
69//!   ([`sparql::SparqlDoor`]). A query's form, its declared and typed parameters, and the
70//!   authority it needs (the store's per-graph grants) are all read from the parsed text,
71//!   and parameter values are bound into the parsed algebra, never spliced. Each published
72//!   query is its own catalog entry with its parameters as arguments. See [`sparql`].
73//! - **Plans** (`ik:Process` graphs) reach the host's `urn:plan:eval`. A plan is validated
74//!   by `urn:plan:validate` before it is stored, its authority is DERIVED from its steps'
75//!   contracts by `urn:plan:requires`, and it is a read or a write by its steps' verbs. Each
76//!   published plan is its own catalog entry with its parameters as arguments. See [`plan`].
77//!
78//! **Preference order when assembling anything: a query, then a plan, then Lisp.** The first
79//! two derive their authority and are checked before they run; use Lisp for control flow a
80//! plan cannot express, and preferably as one step inside a plan.
81
82#![deny(missing_docs)]
83
84pub mod authority;
85pub mod backend;
86pub mod endpoints;
87pub mod graph;
88pub mod limits;
89pub mod model;
90pub mod name;
91pub mod plan;
92pub mod sparql;
93
94pub use backend::{Backend, DirBackend, MemoryBackend};
95pub use endpoints::{
96    space, Catalog, CatalogEntry, ChangeHook, LastRun, Prepared, ScriptSpace, SpaceConfig,
97};
98pub use model::{Head, Language, Outcome, Run, State, Version};
99
100/// The README's example, compiled (never run: it opens a directory) so it cannot rot.
101#[cfg(doctest)]
102#[doc = include_str!("../README.md")]
103pub struct ReadmeDoctests;