diff --git a/src/architecture.md b/src/architecture.md index a41467a..4294403 100644 --- a/src/architecture.md +++ b/src/architecture.md @@ -1,5 +1,7 @@ # Architecture + + The **[Syndicated Actor Model (SAM)](./glossary.md#syndicated-actor-model)** is at the heart of Synit. In turn, the SAM builds upon [E-style actors](./glossary.md#e), replacing message-exchange with eventually-consistent *state replication* as the fundamental building diff --git a/src/guide/preserves.md b/src/guide/preserves.md index 09ece06..823207f 100644 --- a/src/guide/preserves.md +++ b/src/guide/preserves.md @@ -5,7 +5,7 @@ data. - [Preserves homepage](https://preserves.gitlab.io/) - [Preserves specification](https://preserves.gitlab.io/preserves/preserves.html) - - [Preserves schema-language specification](https://preserves.gitlab.io/preserves/preserves-schema.html) + - [Preserves Schema specification](https://preserves.gitlab.io/preserves/preserves-schema.html) - [Source code](https://gitlab.com/preserves/preserves) for many (not all) of the implementations - Implementations for [Nim](https://git.sr.ht/~ehmry/preserves-nim), @@ -55,9 +55,9 @@ dictionary, and record compound types. From the specification: ### Concrete syntax -Because Preserves' semantics are independent of its syntax, we may use different syntax for it -in different settings. Values can be automatically, losslessly translated from one syntax to -another. +Preserves offers *multiple* syntaxes, each useful in different settings. Values are +automatically, losslessly translatable from one syntax to another because Preserves' semantics +are syntax-independent. The core Preserves specification defines a text-based, human-readable, JSON-like syntax, that is a syntactic superset of JSON, and a completely equivalent compact binary syntax, crucial to @@ -92,7 +92,7 @@ serialization ordering of elements in sets and keys in dictionaries. Having a canonical form means that, for example, a cryptographic hash of a value's canonical serialization can be used as a unique fingerprint for the value. -For example, the SHA-512 digest of the canonical serializartion of the value +For example, the SHA-512 digest of the canonical serialization of the value ```preserves @@ -109,8 +109,8 @@ is ### Capabilities Preserves values can include *embedded references*, written as values with a `#!` prefix. For -example, a command adding `` to the user settings database might be sent to the -root dataspace as follows: +example, a command adding `` to the user settings database might look like this +as it travels over a Unix pipe connecting a program to the root dataspace: ```preserves > #![0 123]> @@ -121,14 +121,15 @@ capability reference, `#![0 123]`, which encodes a transport-specific reference > TODO: Link to documentation for `sturdy.prs`. -The syntax of values under `#!` differs depending on the medium carrying the message: -point-to-point transports need to be able to refer to "my references" (`#![0 `*n*`]`) and "your -references" (`#![1 `*n*`]`); multicast/broadcast media (like Ethernet) need to be able to name +The syntax of values under `#!` differs depending on the medium carrying the message. +For example, point-to-point transports need to be able to refer to "my references" (`#![0 `*n*`]`) and "your +references" (`#![1 `*n*`]`), while multicast/broadcast media (like Ethernet) need to be able to name references within specific, named conversational participants (`#![ -`*n*`]`) ; in-memory representations use direct pointers (`#!140425190562944`); and so on. In -every case, the references themselves function very similarly to Unix file descriptors: an -integer or similar that unforgeably denotes, in a local context, some complex data structure on -the other side of a trust boundary. +`*n*`]`), and in-memory representations need to use direct pointers (`#!140425190562944`). + +In every case, the references themselves work like Unix file descriptors: an integer or similar +that unforgeably denotes, in a local context, some complex data structure on the other side of +a trust boundary. When capability-bearing Preserves values are read off a transport, the capabilities are automatically rewritten into references to in-memory proxy objects. The reverse process of @@ -138,7 +139,7 @@ rewriting capability references happens when an in-memory value is serialized fo Preserves comes with a schema language suitable for defining protocols among actors/programs in Synit. Because Preserves is a superset of JSON, its schemas can be used for parsing JSON just -as well as for native Preserves values. From the [schema +as well as for native Preserves values.[^you-have-to-use-a-preserves-reader] From the [schema specification](https://preserves.gitlab.io/preserves/preserves-schema.html): > A Preserves schema connects Preserves Values to host-language data @@ -334,14 +335,17 @@ Here's the Preserves value equivalent to the example above, expressed using the called [Syrup](https://github.com/ocapn/syrup#pseudo-specification), reminiscent of [`bencode`](https://en.wikipedia.org/wiki/Bencode). +[^you-have-to-use-a-preserves-reader]: You have to use a Preserves text-syntax reader on JSON + terms to do this, though: JSON values like `null`, `true`, and `false` naively read as + Preserves *symbols*. Preserves doesn't have the concept of `null`. + [^this-example-from-mdbook]: This example is a simplified form of the preprocessor type definitions for [mdBook](https://rust-lang.github.io/mdBook/for_developers/preprocessors.html), the system - used to render this manual. I use a real [Preserves schema + used to render these pages. I use a real [Preserves schema definition](https://git.syndicate-lang.org/synit/synit/src/branch/main/manual/book.prs) for parsing and producing Serde's JSON representation of mdBook `Book` structures in order to - [preprocess the manual's source - code](https://git.syndicate-lang.org/synit/synit/src/branch/main/manual/mdbook-ditaa). + [preprocess the text](https://git.syndicate-lang.org/synit/synit/src/branch/main/manual/mdbook-ditaa). [^lose-compatibility]: By doing so, we lose compatibility with the Serde structures, but the point is to show the kinds of schemas available to us once we move away from strict diff --git a/src/introduction.md b/src/introduction.md index ef259c1..e4fc8c3 100644 --- a/src/introduction.md +++ b/src/introduction.md @@ -1,4 +1,4 @@ -# Synit: a Reactive Operating System +# Synit is a Reactive Operating System ## Welcome! @@ -14,25 +14,32 @@ familiar to Linux users, but also incorporates many ideas drawn from programming languages and operating systems not closely connected with Linux's Unix heritage. -It is currently **experimental** software. - - Project homepage: - - Source code: -## Organisation of this manual + ## Quickstart diff --git a/src/operation/index.md b/src/operation/index.md index 8aab167..d74423b 100644 --- a/src/operation/index.md +++ b/src/operation/index.md @@ -15,9 +15,9 @@ bus](./system-bus.md) (NB. not at PID 1). | | | | | | +--+--+ +---+---+ +--+--+ +----+----+ +---+---+ +-----+-----+ |init | |console| |udevd| |Network | |Wifi | |Session bus| -+-----+ |getty | +-----+ |Interface| |Daemon | ... +-----+-----+ - +-------+ |Monitor | |(wlan0)| | - |Daemon | +-------+ | ++-----+ |getty | +-----+ |Interface| |Daemon | ... |(syndicate–| + +-------+ |Monitor | |(wlan0)| | server) | + |Daemon | +-------+ +-----+-----+ +---------+ | | +----------+----------------------------+ diff --git a/src/operation/system-bus.md b/src/operation/system-bus.md index 3d9c591..08c7e3a 100644 --- a/src/operation/system-bus.md +++ b/src/operation/system-bus.md @@ -23,7 +23,7 @@ It provides: through the gatekeeper. 5. An [`inotify`](https://en.wikipedia.org/wiki/Inotify)-based **[configuration - loader](./builtin/config-watcher.md)** which loads and executes configuration files written + tracker](./builtin/config-watcher.md)** which loads and executes configuration files written in the scripting language. 6. [**Process startup and supervision**](./builtin/daemon.md) diff --git a/src/protocol.md b/src/protocol.md index bc8fe67..205597c 100644 --- a/src/protocol.md +++ b/src/protocol.md @@ -1,13 +1,16 @@ # Syndicate Protocol -Actors in the same local [scope](./glossary#scope) communicate directly. To communicate further -afield, scopes may be *connected* using [relay actors](./glossary.md#relay). Relays allow +Actors that share a local [scope](./glossary#scope) can communicate directly. To communicate +further afield, scopes are *connected* using [relay actors](./glossary.md#relay). Relays allow *indirect* communication: distant entities can be addressed as if they were local. Relays exchange *Syndicate Protocol* messages across a [transport](./glossary.md#transport). A *transport* is the underlying medium connecting one relay to its counterparts on a given network. For example, a TLS-on-TCP/IP socket may connect a pair of relays to one another, or a -UDP multicast socket may connect an entire group of relays across an ethernet. +UDP multicast socket may connect an entire group of relays across an +ethernet.[^relaying-over-syndicate] + + + ## Transport requirements Transports must - be able to carry [Preserves](./glossary.md#preserves) values back and forth, - be reliable and in-order, - - have a well-defined lifecycle (created → connected → disconnected), and + - have a well-defined session lifecycle (created → connected → disconnected), and - assure confidentiality, integrity and replay-resistance. This document focuses primarily on point-to-point transports, discussing multicast and @@ -328,110 +333,6 @@ The above definitions imply some *validity constraints* on `Caveat`s. Implementations MUST enforce these constraints (either statically or dynamically). -#### Pseudocode for attenuation, pattern matching, and template instantiation - -##### Attenuation -```python -def attenuate(attenuation, value): - for caveat in reversed(attenuation): - value = applyCaveat(caveat, value) - if value is None: - return None - return value - -def applyCaveat(caveat, value): - if caveat is 'Alts' variant: - for rewrite in caveat.alternatives: - possibleResult = tryRewrite(rewrite, value); - if possibleResult is not None: - return possibleResult - return None - if caveat is 'Rewrite' variant: - return tryRewrite(caveat, value) - -def tryRewrite(rewrite, value): - bindings = applyPattern(rewrite.pattern, value) - if bindings is None: - return None - else: - return instantiateTemplate(rewrite.template, bindings) -``` - -##### Pattern matching -```python -def match(pattern, value, bindings): - if pattern is 'PDiscard' variant: - return True - if pattern is 'PAtom' variant: - return True if value is of the appropriate atomic class else False - if pattern is 'PEmbedded' variant: - return True if value is a capability else False - if pattern is 'PBind' variant: - append value to bindings - return match(pattern.pattern, value, bindings) - if pattern is 'PAnd' variant: - for p in pattern.patterns: - if not match(p, value, bindings): - return False - return True - if pattern is 'PNot' variant: - return False if match(pattern.pattern, value, bindings) else True - if pattern is 'Lit' variant: - return (pattern.value == value) - if pattern is 'PCompound' variant: - if pattern is 'rec' variant: - if value is not a record: return False - if value.label is not equal to pattern.label: return False - if value.fields.length is not equal to pattern.fields.length: return False - for i in [0 .. pattern.fields.length): - if not match(pattern.fields[i], value.fields[i], bindings): - return False - return True - if pattern is 'arr' variant: - if value is not a sequence: return False - if value.length is not equal to pattern.items.length: return False - for i in [0 .. pattern.items.length): - if not match(pattern.items[i], value[i], bindings): - return False - return True - if pattern is 'dict' variant: - if value is not a dictionary: return False - for k in keys of pattern.entries: - if k not in keys of value: return False - if not match(pattern.entries[k], value[k], bindings): - return False - return True -``` - -##### Template instantiation -```python -def instantiate(template, bindings): - if template is 'TAttenuate' variant: - c = instantiate(template.template, bindings) - if c is not a capability: raise an exception - c′ = c with the caveats in template.attenuation appended to the existing - attenuation in c - return c′ - if template is 'TRef' variant: - if 0 ≤ template.binding < bindings.length: - return bindings[template.binding] - else: - raise an exception - if template is 'Lit' variant: - return template.value - if template is 'TCompound' variant: - if template is 'rec' variant: - return Record(label=template.label, - fields=[instantiate(t, bindings) for t in template.fields]) - if template is 'arr' variant: - return [instantiate(t, bindings) for t in template.items] - if template is 'dict' variant: - result = {} - for k in keys of template.entries: - result[k] = instantiate(template.entries[k], bindings) - return result -``` - ## Membranes In order to correctly map between embedded references on the wire and entity references local @@ -569,7 +470,7 @@ WebSockets Multicast/broadcast, in-memory -## Reference: Complete schema of the protocol +## Appendix: Complete schema of the protocol The following is a consolidated form of the definitions from the text above. @@ -641,10 +542,122 @@ TCompound = / @dict . ``` +## Appendix: Pseudocode for attenuation, pattern matching, and template instantiation + +### Attenuation + +```python +def attenuate(attenuation, value): + for caveat in reversed(attenuation): + value = applyCaveat(caveat, value) + if value is None: + return None + return value + +def applyCaveat(caveat, value): + if caveat is 'Alts' variant: + for rewrite in caveat.alternatives: + possibleResult = tryRewrite(rewrite, value); + if possibleResult is not None: + return possibleResult + return None + if caveat is 'Rewrite' variant: + return tryRewrite(caveat, value) + +def tryRewrite(rewrite, value): + bindings = applyPattern(rewrite.pattern, value) + if bindings is None: + return None + else: + return instantiateTemplate(rewrite.template, bindings) +``` + +### Pattern matching + +```python +def match(pattern, value, bindings): + if pattern is 'PDiscard' variant: + return True + if pattern is 'PAtom' variant: + return True if value is of the appropriate atomic class else False + if pattern is 'PEmbedded' variant: + return True if value is a capability else False + if pattern is 'PBind' variant: + append value to bindings + return match(pattern.pattern, value, bindings) + if pattern is 'PAnd' variant: + for p in pattern.patterns: + if not match(p, value, bindings): + return False + return True + if pattern is 'PNot' variant: + return False if match(pattern.pattern, value, bindings) else True + if pattern is 'Lit' variant: + return (pattern.value == value) + if pattern is 'PCompound' variant: + if pattern is 'rec' variant: + if value is not a record: return False + if value.label is not equal to pattern.label: return False + if value.fields.length is not equal to pattern.fields.length: return False + for i in [0 .. pattern.fields.length): + if not match(pattern.fields[i], value.fields[i], bindings): + return False + return True + if pattern is 'arr' variant: + if value is not a sequence: return False + if value.length is not equal to pattern.items.length: return False + for i in [0 .. pattern.items.length): + if not match(pattern.items[i], value[i], bindings): + return False + return True + if pattern is 'dict' variant: + if value is not a dictionary: return False + for k in keys of pattern.entries: + if k not in keys of value: return False + if not match(pattern.entries[k], value[k], bindings): + return False + return True +``` + +### Template instantiation + +```python +def instantiate(template, bindings): + if template is 'TAttenuate' variant: + c = instantiate(template.template, bindings) + if c is not a capability: raise an exception + c′ = c with the caveats in template.attenuation appended to the existing + attenuation in c + return c′ + if template is 'TRef' variant: + if 0 ≤ template.binding < bindings.length: + return bindings[template.binding] + else: + raise an exception + if template is 'Lit' variant: + return template.value + if template is 'TCompound' variant: + if template is 'rec' variant: + return Record(label=template.label, + fields=[instantiate(t, bindings) for t in template.fields]) + if template is 'arr' variant: + return [instantiate(t, bindings) for t in template.items] + if template is 'dict' variant: + result = {} + for k in keys of template.entries: + result[k] = instantiate(template.entries[k], bindings) + return result +``` + --- #### Notes +[^relaying-over-syndicate]: In fact, it makes perfect sense to run the relay protocol between + actors that are *already connected* in some scope: this is like running a VPN, tunnelling + IP over IP. A variation of the Syndicate Protocol like this gives [federated + dataspaces](https://syndicate-lang.org/about/history/#postdoc). + [^no-extensions-yet]: This specification does not define any extensions, but future revisions could, for example, use extensions to perform version-negotiation. Another potential future use could be to propagate provenance information for tracing/debugging. diff --git a/style.css b/style.css index 51855d8..a27ddaf 100644 --- a/style.css +++ b/style.css @@ -1,5 +1,6 @@ div.ditaa { - overflow: scroll; + overflow: auto; + line-height: 0; } svg text {