diff --git a/package.json b/package.json index 1a1d7fc..32210bd 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "@preserves/schema": "^0.1.1" }, "scripts": { - "regenerate": "rm -rf ./src/gen && preserves-schema-ts --module Pointer=./src/actor.ts --output ./src/gen ./schemas/**/*.prs", + "regenerate": "rm -rf ./src/gen && preserves-schema-ts --module Actor=./src/actor.ts --module Protocol=./src/protocol.ts --output ./src/gen './schemas/**/*.prs'", "regenerate:watch": "yarn regenerate --watch", "compile": "tsc", "compile:watch": "tsc -w", diff --git a/schemas/box-protocol.prs b/schemas/box-protocol.prs index 64b2c2c..5560c2e 100644 --- a/schemas/box-protocol.prs +++ b/schemas/box-protocol.prs @@ -1,8 +1,9 @@ version 1 . -pointer Pointer.Ref . +pointer Actor.Ref . BoxState = . SetBox = . -BoxCap = BoxState / . -ClientCap = SetBox / . +; BoxCap = BoxState / . +; ClientCap = SetBox / . +. diff --git a/schemas/dataspace.prs b/schemas/dataspace.prs index 04c39d9..bd5caa7 100644 --- a/schemas/dataspace.prs +++ b/schemas/dataspace.prs @@ -1,7 +1,8 @@ version 1 . +pointer Actor.Ref . ;As implemented -Observe = . +Observe = . ; ;As will be implemented soon ; Observe = . diff --git a/schemas/protocol.prs b/schemas/protocol.prs new file mode 100644 index 0000000..1abd6d7 --- /dev/null +++ b/schemas/protocol.prs @@ -0,0 +1,13 @@ +version 1 . +pointer Protocol.WireRef . + +Assertion = any . +Handle = int . +Event = Assert / Retract / Message / Sync . +Oid = int . +Turn = [[Oid Event] ...]. + +Assert = . +Retract = . +Message = . +Sync = . diff --git a/schemas/rewrite.prs b/schemas/rewrite.prs deleted file mode 100644 index 26b3fcb..0000000 --- a/schemas/rewrite.prs +++ /dev/null @@ -1,20 +0,0 @@ -version 1 . - -ConstructorSpec = / / . - -Pattern = - - - - - -]>. - -Template = - - -]>. - -Rewrite = . diff --git a/schemas/sturdy.prs b/schemas/sturdy.prs new file mode 100644 index 0000000..6e2316b --- /dev/null +++ b/schemas/sturdy.prs @@ -0,0 +1,38 @@ +version 1 . +pointer Actor.Ref . + +; Each Attenuation is a stage. The sequence of Attenuations is run RIGHT-TO-LEFT. +; That is, the newest Attenuations are at the right. +SturdyRef = . + +; An individual Attenuation is run RIGHT-TO-LEFT. +; That is, the newest Caveats are at the right. +Attenuation = [Caveat ...]. + +; embodies 1st-party caveats over assertion structure, but nothing else +; can add 3rd-party caveats and richer predicates later +Caveat = Rewrite / Alts . +Rewrite = . +Alts = . + +Resolve = . + +;--------------------------------------------------------------------------- + +ConstructorSpec = CRec / CArr / CDict . +CRec = . +CArr = . +CDict = . + +Lit = . + +Pattern = PDiscard / PBind / PAnd / PNot / Lit / PCompound . +PDiscard = <_>. +PBind = . +PAnd = . +PNot = . +PCompound = . + +Template = TRef / Lit / TCompound . +TRef = . +TCompound = . diff --git a/schemas/wire.prs b/schemas/wire.prs deleted file mode 100644 index 0b2e090..0000000 --- a/schemas/wire.prs +++ /dev/null @@ -1,17 +0,0 @@ -version 1 . - -SturdyRef = . -Caveat = rewrite.Rewrite / . - -Handle = int . -Event = Assert / Retract / Message / Sync . -Oid = int . -Turn = [[Oid Event] ...]. - -Assert = . -Retract = . -Message = . -Sync = . - -; WireRef = [0 Oid] / [1 Oid AttenuationStage ...]. -. diff --git a/src/box.ts b/src/box.ts index 2aa56f6..42f18e2 100644 --- a/src/box.ts +++ b/src/box.ts @@ -1,6 +1,6 @@ import { BoxState, SetBox } from "./gen/box-protocol.js"; +import { Observe } from "./gen/dataspace.js"; import { Assertion, Handle, Ref, Turn } from "./actor.js"; -import { Observe } from "./dataspace.js"; let startTime = Date.now(); let prevValue = 0; diff --git a/src/client.ts b/src/client.ts index baf0c41..012f1d7 100644 --- a/src/client.ts +++ b/src/client.ts @@ -1,5 +1,5 @@ import { BoxState, SetBox } from "./gen/box-protocol.js"; -import { Observe } from "./dataspace.js"; +import { Observe } from "./gen/dataspace.js"; import { Assertion, Ref, Turn } from "./actor.js"; export default function (t: Turn, ds: Ref) { diff --git a/src/dataspace.ts b/src/dataspace.ts index ee5e2d4..962b43e 100644 --- a/src/dataspace.ts +++ b/src/dataspace.ts @@ -2,6 +2,9 @@ import { Assertion, Entity, Handle, Ref, Turn } from 'actor'; import { Dictionary, IdentityMap, is, Record, Tuple } from '@preserves/core'; import { Bag, ChangeDescription } from './bag'; +import { Observe } from './gen/dataspace'; +export * from './gen/dataspace'; + // Q. Why keep "Observe"? Why not do the clever trick of asserting the // observer, and having the dataspace read the implicit pattern it's // interested in off its attenuator? @@ -14,7 +17,12 @@ import { Bag, ChangeDescription } from './bag'; // Because we want to have onlookers have some hope of seeing whether // a pattern of interest to them is being observed, and if we used // attenuators to match, we'd have to expose visibility into -// attenuators into the pattern language. See next question. +// attenuators into the pattern language. See next question. (3) +// Because reflection on attenuators is a big, heavy hammer, and it's +// better to be explicit about patterns! Also, some attenuations +// happen behind a veil of secrecy - they're not all open for the +// world to read about. Actors may proxy communications in arbitrary, +// secret ways. // // Q. What kinds of constraints on the pattern language are there? // @@ -26,9 +34,6 @@ import { Bag, ChangeDescription } from './bag'; // that enforced some kind of normal forms for its patterns, so // observer-observers and attenuator patterns don't have to deal with // spurious variation. -// -export const Observe = Record.makeConstructor<{label: Assertion, observer: Ref}, Ref>()( - Symbol.for('Observe'), ['label', 'observer']); export class Dataspace implements Partial { readonly handleMap: IdentityMap> = new IdentityMap(); diff --git a/src/gen/box-protocol.ts b/src/gen/box-protocol.ts index 56d5497..8c96947 100644 --- a/src/gen/box-protocol.ts +++ b/src/gen/box-protocol.ts @@ -1,8 +1,7 @@ import * as _ from "@preserves/core"; -import * as _i_Pointer from "../actor"; +import * as _i_Actor from "../actor"; export const $BoxState = Symbol.for("BoxState"); -export const $Observe = Symbol.for("Observe"); export const $SetBox = Symbol.for("SetBox"); export const BoxState = _.Record.makeConstructor<{"_field0": number}, _ptr>()($BoxState, ["_field0"]); @@ -13,11 +12,7 @@ export const SetBox = _.Record.makeConstructor<{"_field0": number}, _ptr>()($Set export type SetBox = _.Record<(typeof $SetBox), [number], _ptr>; -export type BoxCap = (BoxState | _.Record<(typeof $Observe), [(typeof $SetBox), _ptr], _ptr>); - -export type ClientCap = (SetBox | _.Record<(typeof $Observe), [(typeof $BoxState), _ptr], _ptr>); - -export type _ptr = _i_Pointer.Ref; +export type _ptr = _i_Actor.Ref; export type _val = _.Value<_ptr>; @@ -46,33 +41,3 @@ export function asSetBox(v: any): SetBox { if (!isSetBox(v)) {throw new TypeError(`Invalid SetBox: ${_.stringify(v)}`);} else {return v;}; } -export function isBoxCap(v: any): v is BoxCap { - return ( - isBoxState(v) || - ( - _.Record.isRecord<_val, _.Tuple<_val>, _ptr>(v) && - _.is(v.label, $Observe) && - ((v.length === 2) && _.is(v[0], $SetBox) && _.isPointer(v[1])) - ) - ); -} - -export function asBoxCap(v: any): BoxCap { - if (!isBoxCap(v)) {throw new TypeError(`Invalid BoxCap: ${_.stringify(v)}`);} else {return v;}; -} - -export function isClientCap(v: any): v is ClientCap { - return ( - isSetBox(v) || - ( - _.Record.isRecord<_val, _.Tuple<_val>, _ptr>(v) && - _.is(v.label, $Observe) && - ((v.length === 2) && _.is(v[0], $BoxState) && _.isPointer(v[1])) - ) - ); -} - -export function asClientCap(v: any): ClientCap { - if (!isClientCap(v)) {throw new TypeError(`Invalid ClientCap: ${_.stringify(v)}`);} else {return v;}; -} - diff --git a/src/gen/dataspace.ts b/src/gen/dataspace.ts index ef970a9..1b50993 100644 --- a/src/gen/dataspace.ts +++ b/src/gen/dataspace.ts @@ -1,12 +1,13 @@ import * as _ from "@preserves/core"; +import * as _i_Actor from "../actor"; export const $Observe = Symbol.for("Observe"); -export const Observe = _.Record.makeConstructor<{"_field0": symbol, "_field1": _ptr}, _ptr>()($Observe, ["_field0","_field1"]); +export const Observe = _.Record.makeConstructor<{"label": symbol, "observer": _ptr}, _ptr>()($Observe, ["label","observer"]); export type Observe = _.Record<(typeof $Observe), [symbol, _ptr], _ptr>; -export type _ptr = never; +export type _ptr = _i_Actor.Ref; export type _val = _.Value<_ptr>; diff --git a/src/gen/wire.ts b/src/gen/protocol.ts similarity index 59% rename from src/gen/wire.ts rename to src/gen/protocol.ts index 5f0f95c..a4fa6d0 100644 --- a/src/gen/wire.ts +++ b/src/gen/protocol.ts @@ -1,18 +1,12 @@ import * as _ from "@preserves/core"; -import * as _i_rewrite from "./rewrite"; +import * as _i_Protocol from "../protocol"; export const $assert = Symbol.for("assert"); export const $message = Symbol.for("message"); -export const $or = Symbol.for("or"); -export const $ref = Symbol.for("ref"); export const $retract = Symbol.for("retract"); export const $sync = Symbol.for("sync"); -export const SturdyRef = _.Record.makeConstructor<{"_field0": _val, "_field1": Array, "_field2": _.Bytes}, _ptr>()($ref, ["_field0","_field1","_field2"]); - -export type SturdyRef = _.Record<(typeof $ref), [_val, Array, _.Bytes], _ptr>; - -export type Caveat = (_i_rewrite.Rewrite | _.Record<(typeof $or), [Array<_i_rewrite.Rewrite>], _ptr>); +export type Assertion = _val; export type Handle = number; @@ -22,70 +16,31 @@ export type Oid = number; export type Turn = Array<[Oid, Event]>; -export const Assert = _.Record.makeConstructor<{"assertion": _val, "handle": Handle}, _ptr>()($assert, ["assertion","handle"]); +export const Assert = _.Record.makeConstructor<{"assertion": Assertion, "handle": Handle}, _ptr>()($assert, ["assertion","handle"]); -export type Assert = _.Record<(typeof $assert), [_val, Handle], _ptr>; +export type Assert = _.Record<(typeof $assert), [Assertion, Handle], _ptr>; export const Retract = _.Record.makeConstructor<{"handle": Handle}, _ptr>()($retract, ["handle"]); export type Retract = _.Record<(typeof $retract), [Handle], _ptr>; -export const Message = _.Record.makeConstructor<{"body": _val}, _ptr>()($message, ["body"]); +export const Message = _.Record.makeConstructor<{"body": Assertion}, _ptr>()($message, ["body"]); -export type Message = _.Record<(typeof $message), [_val], _ptr>; +export type Message = _.Record<(typeof $message), [Assertion], _ptr>; export const Sync = _.Record.makeConstructor<{"peer": _ptr}, _ptr>()($sync, ["peer"]); export type Sync = _.Record<(typeof $sync), [_ptr], _ptr>; -export type _ptr = never; +export type _ptr = _i_Protocol.WireRef; export type _val = _.Value<_ptr>; -export function isSturdyRef(v: any): v is SturdyRef { - return ( - _.Record.isRecord<_val, _.Tuple<_val>, _ptr>(v) && - _.is(v.label, $ref) && - ( - (v.length === 3) && - true && - ( - _.Array.isArray(v[1]) && - !_.Record.isRecord<_val, _.Tuple<_val>, _ptr>(v[1]) && - (v[1].length >= 0) && - v[1].slice(0).every(v => (isCaveat(v))) - ) && - _.Bytes.isBytes(v[2]) - ) - ); -} +export function isAssertion(v: any): v is Assertion {return true;} -export function asSturdyRef(v: any): SturdyRef { - if (!isSturdyRef(v)) {throw new TypeError(`Invalid SturdyRef: ${_.stringify(v)}`);} else {return v;}; -} - -export function isCaveat(v: any): v is Caveat { - return ( - _i_rewrite.isRewrite(v) || - ( - _.Record.isRecord<_val, _.Tuple<_val>, _ptr>(v) && - _.is(v.label, $or) && - ( - (v.length === 1) && - ( - _.Array.isArray(v[0]) && - !_.Record.isRecord<_val, _.Tuple<_val>, _ptr>(v[0]) && - (v[0].length >= 0) && - v[0].slice(0).every(v => (_i_rewrite.isRewrite(v))) - ) - ) - ) - ); -} - -export function asCaveat(v: any): Caveat { - if (!isCaveat(v)) {throw new TypeError(`Invalid Caveat: ${_.stringify(v)}`);} else {return v;}; +export function asAssertion(v: any): Assertion { + if (!isAssertion(v)) {throw new TypeError(`Invalid Assertion: ${_.stringify(v)}`);} else {return v;}; } export function isHandle(v: any): v is Handle {return typeof v === 'number';} @@ -131,7 +86,7 @@ export function isAssert(v: any): v is Assert { return ( _.Record.isRecord<_val, _.Tuple<_val>, _ptr>(v) && _.is(v.label, $assert) && - ((v.length === 2) && true && isHandle(v[1])) + ((v.length === 2) && isAssertion(v[0]) && isHandle(v[1])) ); } @@ -155,7 +110,7 @@ export function isMessage(v: any): v is Message { return ( _.Record.isRecord<_val, _.Tuple<_val>, _ptr>(v) && _.is(v.label, $message) && - ((v.length === 1) && true) + ((v.length === 1) && isAssertion(v[0])) ); } diff --git a/src/gen/rewrite.ts b/src/gen/rewrite.ts deleted file mode 100644 index 3899783..0000000 --- a/src/gen/rewrite.ts +++ /dev/null @@ -1,227 +0,0 @@ -import * as _ from "@preserves/core"; - -export const $__ = Symbol.for("_"); -export const $and = Symbol.for("and"); -export const $arr = Symbol.for("arr"); -export const $bind = Symbol.for("bind"); -export const $compound = Symbol.for("compound"); -export const $dict = Symbol.for("dict"); -export const $lit = Symbol.for("lit"); -export const $not = Symbol.for("not"); -export const $rec = Symbol.for("rec"); -export const $ref = Symbol.for("ref"); -export const $rewrite = Symbol.for("rewrite"); -export const __lit3 = Symbol.for("/"); - -export type ConstructorSpec = ( - _.Record<(typeof $rec), [_val, number], _ptr> | - _.Record<(typeof $arr), [number], _ptr> | - _.Record<(typeof $dict), [], _ptr> -); - -export const Pattern = _.Record.makeConstructor<{ - "_field0": [ - _.Record<(typeof $__), [], _ptr>, - _.Record<(typeof $bind), [symbol, Pattern], _ptr>, - _.Record<(typeof $and), [Array], _ptr>, - _.Record<(typeof $not), [Pattern], _ptr>, - _.Record<(typeof $lit), [_val], _ptr>, - _.Record<(typeof $compound), [ConstructorSpec, _.KeyedDictionary<_val, Pattern>], _ptr> - ] -}, _ptr>()(__lit3, ["_field0"]); - -export type Pattern = _.Record< - (typeof __lit3), - [ - [ - _.Record<(typeof $__), [], _ptr>, - _.Record<(typeof $bind), [symbol, Pattern], _ptr>, - _.Record<(typeof $and), [Array], _ptr>, - _.Record<(typeof $not), [Pattern], _ptr>, - _.Record<(typeof $lit), [_val], _ptr>, - _.Record<(typeof $compound), [ConstructorSpec, _.KeyedDictionary<_val, Pattern>], _ptr> - ] - ], - _ptr ->; - -export const Template = _.Record.makeConstructor<{ - "_field0": [ - _.Record<(typeof $ref), [symbol], _ptr>, - _.Record<(typeof $lit), [_val], _ptr>, - _.Record<(typeof $compound), [ConstructorSpec, _.KeyedDictionary<_val, Template>], _ptr> - ] -}, _ptr>()(__lit3, ["_field0"]); - -export type Template = _.Record< - (typeof __lit3), - [ - [ - _.Record<(typeof $ref), [symbol], _ptr>, - _.Record<(typeof $lit), [_val], _ptr>, - _.Record<(typeof $compound), [ConstructorSpec, _.KeyedDictionary<_val, Template>], _ptr> - ] - ], - _ptr ->; - -export const Rewrite = _.Record.makeConstructor<{"_field0": Pattern, "_field1": Template}, _ptr>()($rewrite, ["_field0","_field1"]); - -export type Rewrite = _.Record<(typeof $rewrite), [Pattern, Template], _ptr>; - -export type _ptr = never; - -export type _val = _.Value<_ptr>; - - -export function isConstructorSpec(v: any): v is ConstructorSpec { - return ( - ( - _.Record.isRecord<_val, _.Tuple<_val>, _ptr>(v) && - _.is(v.label, $rec) && - ((v.length === 2) && true && typeof v[1] === 'number') - ) || - ( - _.Record.isRecord<_val, _.Tuple<_val>, _ptr>(v) && - _.is(v.label, $arr) && - ((v.length === 1) && typeof v[0] === 'number') - ) || - ( - _.Record.isRecord<_val, _.Tuple<_val>, _ptr>(v) && - _.is(v.label, $dict) && - ((v.length === 0)) - ) - ); -} - -export function asConstructorSpec(v: any): ConstructorSpec { - if (!isConstructorSpec(v)) {throw new TypeError(`Invalid ConstructorSpec: ${_.stringify(v)}`);} else {return v;}; -} - -export function isPattern(v: any): v is Pattern { - return ( - _.Record.isRecord<_val, _.Tuple<_val>, _ptr>(v) && - _.is(v.label, __lit3) && - ( - (v.length === 1) && - ( - _.Array.isArray(v[0]) && - !_.Record.isRecord<_val, _.Tuple<_val>, _ptr>(v[0]) && - (v[0].length === 6) && - ( - _.Record.isRecord<_val, _.Tuple<_val>, _ptr>(v[0][0]) && - _.is(v[0][0].label, $__) && - ((v[0][0].length === 0)) - ) && - ( - _.Record.isRecord<_val, _.Tuple<_val>, _ptr>(v[0][1]) && - _.is(v[0][1].label, $bind) && - ( - (v[0][1].length === 2) && - typeof v[0][1][0] === 'symbol' && - isPattern(v[0][1][1]) - ) - ) && - ( - _.Record.isRecord<_val, _.Tuple<_val>, _ptr>(v[0][2]) && - _.is(v[0][2].label, $and) && - ( - (v[0][2].length === 1) && - ( - _.Array.isArray(v[0][2][0]) && - !_.Record.isRecord<_val, _.Tuple<_val>, _ptr>(v[0][2][0]) && - (v[0][2][0].length >= 0) && - v[0][2][0].slice(0).every(v => (isPattern(v))) - ) - ) - ) && - ( - _.Record.isRecord<_val, _.Tuple<_val>, _ptr>(v[0][3]) && - _.is(v[0][3].label, $not) && - ((v[0][3].length === 1) && isPattern(v[0][3][0])) - ) && - ( - _.Record.isRecord<_val, _.Tuple<_val>, _ptr>(v[0][4]) && - _.is(v[0][4].label, $lit) && - ((v[0][4].length === 1) && true) - ) && - ( - _.Record.isRecord<_val, _.Tuple<_val>, _ptr>(v[0][5]) && - _.is(v[0][5].label, $compound) && - ( - (v[0][5].length === 2) && - isConstructorSpec(v[0][5][0]) && - ( - _.Dictionary.isDictionary<_val, _ptr>(v[0][5][1]) && - ((() => { - for (const e of v[0][5][1]) {if (!(true)) return false; if (!(isPattern(e[1]))) return false;}; - return true; - })()) - ) - ) - ) - ) - ) - ); -} - -export function asPattern(v: any): Pattern { - if (!isPattern(v)) {throw new TypeError(`Invalid Pattern: ${_.stringify(v)}`);} else {return v;}; -} - -export function isTemplate(v: any): v is Template { - return ( - _.Record.isRecord<_val, _.Tuple<_val>, _ptr>(v) && - _.is(v.label, __lit3) && - ( - (v.length === 1) && - ( - _.Array.isArray(v[0]) && - !_.Record.isRecord<_val, _.Tuple<_val>, _ptr>(v[0]) && - (v[0].length === 3) && - ( - _.Record.isRecord<_val, _.Tuple<_val>, _ptr>(v[0][0]) && - _.is(v[0][0].label, $ref) && - ((v[0][0].length === 1) && typeof v[0][0][0] === 'symbol') - ) && - ( - _.Record.isRecord<_val, _.Tuple<_val>, _ptr>(v[0][1]) && - _.is(v[0][1].label, $lit) && - ((v[0][1].length === 1) && true) - ) && - ( - _.Record.isRecord<_val, _.Tuple<_val>, _ptr>(v[0][2]) && - _.is(v[0][2].label, $compound) && - ( - (v[0][2].length === 2) && - isConstructorSpec(v[0][2][0]) && - ( - _.Dictionary.isDictionary<_val, _ptr>(v[0][2][1]) && - ((() => { - for (const e of v[0][2][1]) {if (!(true)) return false; if (!(isTemplate(e[1]))) return false;}; - return true; - })()) - ) - ) - ) - ) - ) - ); -} - -export function asTemplate(v: any): Template { - if (!isTemplate(v)) {throw new TypeError(`Invalid Template: ${_.stringify(v)}`);} else {return v;}; -} - -export function isRewrite(v: any): v is Rewrite { - return ( - _.Record.isRecord<_val, _.Tuple<_val>, _ptr>(v) && - _.is(v.label, $rewrite) && - ((v.length === 2) && isPattern(v[0]) && isTemplate(v[1])) - ); -} - -export function asRewrite(v: any): Rewrite { - if (!isRewrite(v)) {throw new TypeError(`Invalid Rewrite: ${_.stringify(v)}`);} else {return v;}; -} - diff --git a/src/gen/sturdy.ts b/src/gen/sturdy.ts new file mode 100644 index 0000000..8feb3f9 --- /dev/null +++ b/src/gen/sturdy.ts @@ -0,0 +1,372 @@ +import * as _ from "@preserves/core"; +import * as _i_Actor from "../actor"; + +export const $__ = Symbol.for("_"); +export const $and = Symbol.for("and"); +export const $arr = Symbol.for("arr"); +export const $bind = Symbol.for("bind"); +export const $compound = Symbol.for("compound"); +export const $dict = Symbol.for("dict"); +export const $lit = Symbol.for("lit"); +export const $not = Symbol.for("not"); +export const $or = Symbol.for("or"); +export const $rec = Symbol.for("rec"); +export const $ref = Symbol.for("ref"); +export const $resolve = Symbol.for("resolve"); +export const $rewrite = Symbol.for("rewrite"); + +export const SturdyRef = _.Record.makeConstructor<{"oid": _val, "caveatChain": Array, "sig": _.Bytes}, _ptr>()($ref, ["oid","caveatChain","sig"]); + +export type SturdyRef = _.Record<(typeof $ref), [_val, Array, _.Bytes], _ptr>; + +export type Attenuation = Array; + +export type Caveat = (Rewrite | Alts); + +export const Rewrite = _.Record.makeConstructor<{"pattern": Pattern, "template": Template}, _ptr>()($rewrite, ["pattern","template"]); + +export type Rewrite = _.Record<(typeof $rewrite), [Pattern, Template], _ptr>; + +export const Alts = _.Record.makeConstructor<{"alternatives": Array}, _ptr>()($or, ["alternatives"]); + +export type Alts = _.Record<(typeof $or), [Array], _ptr>; + +export const Resolve = _.Record.makeConstructor<{"sturdyref": SturdyRef, "observer": _ptr}, _ptr>()($resolve, ["sturdyref","observer"]); + +export type Resolve = _.Record<(typeof $resolve), [SturdyRef, _ptr], _ptr>; + +export type ConstructorSpec = (CRec | CArr | CDict); + +export const CRec = _.Record.makeConstructor<{"label": _val, "arity": number}, _ptr>()($rec, ["label","arity"]); + +export type CRec = _.Record<(typeof $rec), [_val, number], _ptr>; + +export const CArr = _.Record.makeConstructor<{"arity": number}, _ptr>()($arr, ["arity"]); + +export type CArr = _.Record<(typeof $arr), [number], _ptr>; + +export const CDict = _.Record.makeConstructor<{}, _ptr>()($dict, []); + +export type CDict = _.Record<(typeof $dict), [], _ptr>; + +export const Lit = _.Record.makeConstructor<{"value": _val}, _ptr>()($lit, ["value"]); + +export type Lit = _.Record<(typeof $lit), [_val], _ptr>; + +export type Pattern = (PDiscard | PBind | PAnd | PNot | Lit | PCompound); + +export const PDiscard = _.Record.makeConstructor<{}, _ptr>()($__, []); + +export type PDiscard = _.Record<(typeof $__), [], _ptr>; + +export const PBind = _.Record.makeConstructor<{"name": symbol, "pattern": Pattern}, _ptr>()($bind, ["name","pattern"]); + +export type PBind = _.Record<(typeof $bind), [symbol, Pattern], _ptr>; + +export const PAnd = _.Record.makeConstructor<{"patterns": Array}, _ptr>()($and, ["patterns"]); + +export type PAnd = _.Record<(typeof $and), [Array], _ptr>; + +export const PNot = _.Record.makeConstructor<{"pattern": Pattern}, _ptr>()($not, ["pattern"]); + +export type PNot = _.Record<(typeof $not), [Pattern], _ptr>; + +export const PCompound = _.Record.makeConstructor<{"ctor": ConstructorSpec, "members": _.KeyedDictionary<_val, Pattern, _ptr>}, _ptr>()($compound, ["ctor","members"]); + +export type PCompound = _.Record< + (typeof $compound), + [ConstructorSpec, _.KeyedDictionary<_val, Pattern, _ptr>], + _ptr +>; + +export type Template = (TRef | Lit | TCompound); + +export const TRef = _.Record.makeConstructor<{"name": symbol}, _ptr>()($ref, ["name"]); + +export type TRef = _.Record<(typeof $ref), [symbol], _ptr>; + +export const TCompound = _.Record.makeConstructor<{"ctor": ConstructorSpec, "members": _.KeyedDictionary<_val, Template, _ptr>}, _ptr>()($compound, ["ctor","members"]); + +export type TCompound = _.Record< + (typeof $compound), + [ConstructorSpec, _.KeyedDictionary<_val, Template, _ptr>], + _ptr +>; + +export type _ptr = _i_Actor.Ref; + +export type _val = _.Value<_ptr>; + + +export function isSturdyRef(v: any): v is SturdyRef { + return ( + _.Record.isRecord<_val, _.Tuple<_val>, _ptr>(v) && + _.is(v.label, $ref) && + ( + (v.length === 3) && + true && + ( + _.Array.isArray(v[1]) && + !_.Record.isRecord<_val, _.Tuple<_val>, _ptr>(v[1]) && + (v[1].length >= 0) && + v[1].slice(0).every(v => (isAttenuation(v))) + ) && + _.Bytes.isBytes(v[2]) + ) + ); +} + +export function asSturdyRef(v: any): SturdyRef { + if (!isSturdyRef(v)) {throw new TypeError(`Invalid SturdyRef: ${_.stringify(v)}`);} else {return v;}; +} + +export function isAttenuation(v: any): v is Attenuation { + return ( + _.Array.isArray(v) && + !_.Record.isRecord<_val, _.Tuple<_val>, _ptr>(v) && + (v.length >= 0) && + v.slice(0).every(v => (isCaveat(v))) + ); +} + +export function asAttenuation(v: any): Attenuation { + if (!isAttenuation(v)) {throw new TypeError(`Invalid Attenuation: ${_.stringify(v)}`);} else {return v;}; +} + +export function isCaveat(v: any): v is Caveat {return (isRewrite(v) || isAlts(v));} + +export function asCaveat(v: any): Caveat { + if (!isCaveat(v)) {throw new TypeError(`Invalid Caveat: ${_.stringify(v)}`);} else {return v;}; +} + +export function isRewrite(v: any): v is Rewrite { + return ( + _.Record.isRecord<_val, _.Tuple<_val>, _ptr>(v) && + _.is(v.label, $rewrite) && + ((v.length === 2) && isPattern(v[0]) && isTemplate(v[1])) + ); +} + +export function asRewrite(v: any): Rewrite { + if (!isRewrite(v)) {throw new TypeError(`Invalid Rewrite: ${_.stringify(v)}`);} else {return v;}; +} + +export function isAlts(v: any): v is Alts { + return ( + _.Record.isRecord<_val, _.Tuple<_val>, _ptr>(v) && + _.is(v.label, $or) && + ( + (v.length === 1) && + ( + _.Array.isArray(v[0]) && + !_.Record.isRecord<_val, _.Tuple<_val>, _ptr>(v[0]) && + (v[0].length >= 0) && + v[0].slice(0).every(v => (isRewrite(v))) + ) + ) + ); +} + +export function asAlts(v: any): Alts { + if (!isAlts(v)) {throw new TypeError(`Invalid Alts: ${_.stringify(v)}`);} else {return v;}; +} + +export function isResolve(v: any): v is Resolve { + return ( + _.Record.isRecord<_val, _.Tuple<_val>, _ptr>(v) && + _.is(v.label, $resolve) && + ((v.length === 2) && isSturdyRef(v[0]) && _.isPointer(v[1])) + ); +} + +export function asResolve(v: any): Resolve { + if (!isResolve(v)) {throw new TypeError(`Invalid Resolve: ${_.stringify(v)}`);} else {return v;}; +} + +export function isConstructorSpec(v: any): v is ConstructorSpec {return (isCRec(v) || isCArr(v) || isCDict(v));} + +export function asConstructorSpec(v: any): ConstructorSpec { + if (!isConstructorSpec(v)) {throw new TypeError(`Invalid ConstructorSpec: ${_.stringify(v)}`);} else {return v;}; +} + +export function isCRec(v: any): v is CRec { + return ( + _.Record.isRecord<_val, _.Tuple<_val>, _ptr>(v) && + _.is(v.label, $rec) && + ((v.length === 2) && true && typeof v[1] === 'number') + ); +} + +export function asCRec(v: any): CRec { + if (!isCRec(v)) {throw new TypeError(`Invalid CRec: ${_.stringify(v)}`);} else {return v;}; +} + +export function isCArr(v: any): v is CArr { + return ( + _.Record.isRecord<_val, _.Tuple<_val>, _ptr>(v) && + _.is(v.label, $arr) && + ((v.length === 1) && typeof v[0] === 'number') + ); +} + +export function asCArr(v: any): CArr { + if (!isCArr(v)) {throw new TypeError(`Invalid CArr: ${_.stringify(v)}`);} else {return v;}; +} + +export function isCDict(v: any): v is CDict { + return ( + _.Record.isRecord<_val, _.Tuple<_val>, _ptr>(v) && + _.is(v.label, $dict) && + ((v.length === 0)) + ); +} + +export function asCDict(v: any): CDict { + if (!isCDict(v)) {throw new TypeError(`Invalid CDict: ${_.stringify(v)}`);} else {return v;}; +} + +export function isLit(v: any): v is Lit { + return ( + _.Record.isRecord<_val, _.Tuple<_val>, _ptr>(v) && + _.is(v.label, $lit) && + ((v.length === 1) && true) + ); +} + +export function asLit(v: any): Lit { + if (!isLit(v)) {throw new TypeError(`Invalid Lit: ${_.stringify(v)}`);} else {return v;}; +} + +export function isPattern(v: any): v is Pattern { + return ( + isPDiscard(v) || + isPBind(v) || + isPAnd(v) || + isPNot(v) || + isLit(v) || + isPCompound(v) + ); +} + +export function asPattern(v: any): Pattern { + if (!isPattern(v)) {throw new TypeError(`Invalid Pattern: ${_.stringify(v)}`);} else {return v;}; +} + +export function isPDiscard(v: any): v is PDiscard { + return ( + _.Record.isRecord<_val, _.Tuple<_val>, _ptr>(v) && + _.is(v.label, $__) && + ((v.length === 0)) + ); +} + +export function asPDiscard(v: any): PDiscard { + if (!isPDiscard(v)) {throw new TypeError(`Invalid PDiscard: ${_.stringify(v)}`);} else {return v;}; +} + +export function isPBind(v: any): v is PBind { + return ( + _.Record.isRecord<_val, _.Tuple<_val>, _ptr>(v) && + _.is(v.label, $bind) && + ((v.length === 2) && typeof v[0] === 'symbol' && isPattern(v[1])) + ); +} + +export function asPBind(v: any): PBind { + if (!isPBind(v)) {throw new TypeError(`Invalid PBind: ${_.stringify(v)}`);} else {return v;}; +} + +export function isPAnd(v: any): v is PAnd { + return ( + _.Record.isRecord<_val, _.Tuple<_val>, _ptr>(v) && + _.is(v.label, $and) && + ( + (v.length === 1) && + ( + _.Array.isArray(v[0]) && + !_.Record.isRecord<_val, _.Tuple<_val>, _ptr>(v[0]) && + (v[0].length >= 0) && + v[0].slice(0).every(v => (isPattern(v))) + ) + ) + ); +} + +export function asPAnd(v: any): PAnd { + if (!isPAnd(v)) {throw new TypeError(`Invalid PAnd: ${_.stringify(v)}`);} else {return v;}; +} + +export function isPNot(v: any): v is PNot { + return ( + _.Record.isRecord<_val, _.Tuple<_val>, _ptr>(v) && + _.is(v.label, $not) && + ((v.length === 1) && isPattern(v[0])) + ); +} + +export function asPNot(v: any): PNot { + if (!isPNot(v)) {throw new TypeError(`Invalid PNot: ${_.stringify(v)}`);} else {return v;}; +} + +export function isPCompound(v: any): v is PCompound { + return ( + _.Record.isRecord<_val, _.Tuple<_val>, _ptr>(v) && + _.is(v.label, $compound) && + ( + (v.length === 2) && + isConstructorSpec(v[0]) && + ( + _.Dictionary.isDictionary<_val, _ptr>(v[1]) && + ((() => { + for (const e of v[1]) {if (!(true)) return false; if (!(isPattern(e[1]))) return false;}; + return true; + })()) + ) + ) + ); +} + +export function asPCompound(v: any): PCompound { + if (!isPCompound(v)) {throw new TypeError(`Invalid PCompound: ${_.stringify(v)}`);} else {return v;}; +} + +export function isTemplate(v: any): v is Template {return (isTRef(v) || isLit(v) || isTCompound(v));} + +export function asTemplate(v: any): Template { + if (!isTemplate(v)) {throw new TypeError(`Invalid Template: ${_.stringify(v)}`);} else {return v;}; +} + +export function isTRef(v: any): v is TRef { + return ( + _.Record.isRecord<_val, _.Tuple<_val>, _ptr>(v) && + _.is(v.label, $ref) && + ((v.length === 1) && typeof v[0] === 'symbol') + ); +} + +export function asTRef(v: any): TRef { + if (!isTRef(v)) {throw new TypeError(`Invalid TRef: ${_.stringify(v)}`);} else {return v;}; +} + +export function isTCompound(v: any): v is TCompound { + return ( + _.Record.isRecord<_val, _.Tuple<_val>, _ptr>(v) && + _.is(v.label, $compound) && + ( + (v.length === 2) && + isConstructorSpec(v[0]) && + ( + _.Dictionary.isDictionary<_val, _ptr>(v[1]) && + ((() => { + for (const e of v[1]) {if (!(true)) return false; if (!(isTemplate(e[1]))) return false;}; + return true; + })()) + ) + ) + ); +} + +export function asTCompound(v: any): TCompound { + if (!isTCompound(v)) {throw new TypeError(`Invalid TCompound: ${_.stringify(v)}`);} else {return v;}; +} + diff --git a/src/main.ts b/src/main.ts index efb8157..6a2f70c 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,12 +1,13 @@ -import { Actor, Assertion, Turn } from './actor.js'; +import { Actor, Assertion, Ref, Turn } from './actor.js'; import { Dictionary, Record } from '@preserves/core'; import { Dataspace, Observe } from './dataspace.js'; import { Worker } from 'worker_threads'; import { Relay, spawnRelay } from './relay.js'; -import { BoxState, SetBox } from './gen/box-protocol.js'; import { attenuate, CRec, Lit, Pattern, PCompound, rfilter } from './rewrite.js'; import path from 'path'; +import { BoxState, SetBox } from './gen/box-protocol.js'; + const Instance = Record.makeConstructor<{moduleName: string, arg: Assertion}>()( Symbol.for('Instance'), ['moduleName', 'arg']); @@ -60,7 +61,7 @@ Turn.for(new Actor(), async (t: Turn) => { new Dictionary()), PCompound(CRec(Observe.constructorInfo.label, Observe.constructorInfo.arity), - new Dictionary([ + new Dictionary([ [0, Lit(SetBox.constructorInfo.label)]])))); const ds_for_client = attenuate( @@ -70,7 +71,7 @@ Turn.for(new Actor(), async (t: Turn) => { new Dictionary()), PCompound(CRec(Observe.constructorInfo.label, Observe.constructorInfo.arity), - new Dictionary([ + new Dictionary([ [0, Lit(BoxState.constructorInfo.label)]])))); diff --git a/src/protocol.ts b/src/protocol.ts index c67bee9..2be3668 100644 --- a/src/protocol.ts +++ b/src/protocol.ts @@ -1,77 +1,17 @@ -import { Handle, Ref } from './actor.js'; -import { Attenuation, Pattern, Template } from './rewrite.js'; -import { Record, Value } from '@preserves/core'; +import { Attenuation } from './gen/sturdy.js'; +import * as IO from './gen/protocol.js'; +import { Ref } from './actor.js'; -export const _Assert = Symbol.for('assert'); -export const _Retract = Symbol.for('retract'); -export const _Message = Symbol.for('message'); -export const _Sync = Symbol.for('sync'); - -function mk() { - return { - Assert: Record.makeConstructor<{assertion: Value, handle: Handle}, T>()( - _Assert, ['assertion', 'handle']), - Retract: Record.makeConstructor<{handle: Handle}, T>()( - _Retract, ['handle']), - Message: Record.makeConstructor<{body: Value}, T>()( - _Message, ['body']), - Sync: Record.makeConstructor<{peer: T}, T>()( - _Sync, ['peer']), - }; -} - -export type EntityMessage = - | Record, Handle], T> - | Record - | Record], T> - | Record; - -export type TurnMessage = Array<[Oid, EntityMessage]>; - -export type Oid = number; - -export type WireSymbol = { oid: Oid, ref: Ref, count: number }; +export type WireSymbol = { oid: IO.Oid, ref: Ref, count: number }; export type WireRef = - | { loc: "mine", oid: Oid } - | { loc: "your", oid: Oid, attenuation: EncodedAttenuation }; + | { loc: "mine", oid: IO.Oid } + | { loc: "your", oid: IO.Oid, attenuation: Attenuation }; -export const IO = mk(); - -export function myRef(oid: Oid): WireRef & { loc: "mine" } { +export function myRef(oid: IO.Oid): WireRef & { loc: "mine" } { return { loc: 'mine', oid }; } -export function yourRef(oid: Oid, attenuation: EncodedAttenuation): WireRef & { loc: "your" } { +export function yourRef(oid: IO.Oid, attenuation: Attenuation): WireRef & { loc: "your" } { return { loc: 'your', oid, attenuation }; } - -export type EncodedAttenuation = Array, Value]>>; - -export function encodeAttenuation(a: Attenuation | undefined): EncodedAttenuation { - if (a === void 0) return []; - return a.map(s => s.map(({pattern, template}) => [ - pattern as Value, - template as Value, - ])); -} - -export function decodeAttenuation(v: Array>): Attenuation { - function complain(): never { - throw new Error( - `Received invalid attenuation ${v.asPreservesText()} from peer`); - } - - if (v.length === 0) return []; - return v.map(s => { - if (!Array.isArray(s)) complain(); - return s.map(e => { - if (!(Array.isArray(e) && e.length === 2)) complain(); - // TODO: check structure of pattern and template - return { - pattern: e[0] as Pattern, - template: e[1] as Template, - }; - }); - }); -} diff --git a/src/relay.ts b/src/relay.ts index 6caa679..d177fef 100644 --- a/src/relay.ts +++ b/src/relay.ts @@ -1,24 +1,11 @@ import { Actor, Assertion, Entity, Handle, Ref, Turn } from './actor.js'; import { BytesLike, canonicalString, Decoder, encode, FlexMap, IdentityMap, mapPointers, underlying, Value } from '@preserves/core'; -import { - EncodedAttenuation, - EntityMessage, - IO, - Oid, - TurnMessage, - WireRef, - WireSymbol, - _Assert, - _Message, - _Retract, - _Sync, - decodeAttenuation, - encodeAttenuation, - myRef, - yourRef, -} from './protocol.js'; +import * as IO from './gen/protocol.js'; +import { myRef, WireRef, WireSymbol, yourRef } from './protocol.js'; import { queueTask } from './task.js'; import { attenuate } from './rewrite.js'; +import { asAttenuation, Attenuation } from './gen/sturdy.js'; +import { pointerNotAllowed } from './sturdy.js'; export class SyncPeerEntity implements Entity { readonly relay: Relay; @@ -53,14 +40,14 @@ export class SyncPeerEntity implements Entity { export class RelayEntity implements Entity { readonly relay: Relay; - readonly oid: Oid; + readonly oid: IO.Oid; - constructor(relay: Relay, oid: Oid) { + constructor(relay: Relay, oid: IO.Oid) { this.relay = relay; this.oid = oid; } - send(m: EntityMessage): void { + send(m: IO.Event): void { this.relay.send(this.oid, m); } @@ -87,7 +74,7 @@ export class RelayEntity implements Entity { } export class Membrane { - readonly byOid = new IdentityMap(); + readonly byOid = new IdentityMap(); readonly byRef = new IdentityMap(); grab(table: Table, @@ -142,8 +129,8 @@ export class Relay { readonly outboundAssertions = new IdentityMap>(); readonly exported = new Membrane(); readonly imported = new Membrane(); - nextLocalOid: Oid = 0; - pendingTurn: TurnMessage = []; + nextLocalOid: IO.Oid = 0; + pendingTurn: IO.Turn = []; debug: boolean; trustPeer: boolean; @@ -157,14 +144,13 @@ export class Relay { if (!(Array.isArray(v) && v.length >= 2 && typeof v[1] === 'number')) { complain(); } - const oid = v[1] as Oid; + const oid = v[1] as IO.Oid; switch (v[0]) { case 0: if (v.length > 2) complain(); return myRef(oid); case 1: - // TODO: check EncodedAttenuation - return yourRef(oid, v.slice(2) as EncodedAttenuation); + return yourRef(oid, asAttenuation(v.slice(2))); default: complain(); } @@ -212,7 +198,7 @@ export class Relay { // This reference has been attenuated since it was sent to us. // Do we trust the peer to enforce such attenuation on our behalf? if (this.trustPeer) { - return yourRef(r.target.oid, encodeAttenuation(r.attenuation)); + return yourRef(r.target.oid, r.attenuation); } else { // fall through: treat the attenuated ref as a local ref, and re-export it. } @@ -239,13 +225,13 @@ export class Relay { if (n.attenuation.length === 0 || r === INERT_REF) { return r; } else { - type AttenuatedRef = Ref & { __attenuations?: FlexMap }; + type AttenuatedRef = Ref & { __attenuations?: FlexMap }; const ar = r as AttenuatedRef; if (ar.__attenuations === void 0) { ar.__attenuations = new FlexMap(canonicalString); } return ar.__attenuations.getOrSet(n.attenuation, () => - attenuate(r, ... decodeAttenuation(n.attenuation))); + attenuate(r, ... n.attenuation)); } } case 'mine': { @@ -257,7 +243,7 @@ export class Relay { } } - send(remoteOid: Oid, m: EntityMessage): void { + send(remoteOid: IO.Oid, m: IO.Event): void { if (this.pendingTurn.length === 0) { queueTask(() => { if (this.debug) console.log('OUT', this.pendingTurn.asPreservesText()); @@ -266,7 +252,8 @@ export class Relay { encodePointer: n => { switch (n.loc) { case 'mine': return [0, n.oid]; - case 'your': return [1, n.oid, ... n.attenuation]; + case 'your': return [1, n.oid, ... mapPointers( + n.attenuation, pointerNotAllowed) as Array]; } }, }))); @@ -276,7 +263,7 @@ export class Relay { this.pendingTurn.push([remoteOid, m]); } - lookupLocal(localOid: Oid): Ref { + lookupLocal(localOid: IO.Oid): Ref { return this.exported.byOid.get(localOid)?.ref ?? INERT_REF; } @@ -284,7 +271,7 @@ export class Relay { Turn.for(this.actor, t => { this.decoder.write(bs); while (true) { - const wireTurn = this.decoder.try_next() as (TurnMessage | undefined); + const wireTurn = this.decoder.try_next() as (IO.Turn | undefined); if (wireTurn === void 0) break; // TODO: deep check that wireTurn really is a TurnMessage if (this.debug) console.log('IN', wireTurn.asPreservesText()); @@ -296,9 +283,9 @@ export class Relay { }); } - handle(t: Turn, r: Ref, m: EntityMessage) { + handle(t: Turn, r: Ref, m: IO.Event) { switch (m.label) { - case _Assert: { + case IO.$assert: { const [a, imported] = this.rewriteIn(t, IO.Assert._.assertion(m)); this.inboundAssertions.set(IO.Assert._.handle(m), { localHandle: t.assert(r, a), @@ -306,7 +293,7 @@ export class Relay { }); break; } - case _Retract: { + case IO.$retract: { const remoteHandle = IO.Retract._.handle(m); const h = this.inboundAssertions.get(remoteHandle); if (h === void 0) throw new Error(`Peer retracted invalid handle ${remoteHandle}`); @@ -315,13 +302,13 @@ export class Relay { t.retract(h.localHandle); break; } - case _Message: { + case IO.$message: { const [a, imported] = this.rewriteIn(t, IO.Message._.body(m)); if (imported.length > 0) throw new Error("Cannot receive transient reference"); t.message(r, a); break; } - case _Sync: { + case IO.$sync: { const imported: Array = []; const k = this.rewriteRefIn(t, IO.Sync._.peer(m), imported); t.sync(r).then(t => { @@ -335,12 +322,12 @@ export class Relay { } export interface RelayActorOptions extends RelayOptions { - initialOid?: Oid; + initialOid?: IO.Oid; initialRef?: Ref; - nextLocalOid?: Oid; + nextLocalOid?: IO.Oid; } -export function spawnRelay(t: Turn, options: RelayActorOptions & {initialOid: Oid}): Promise; +export function spawnRelay(t: Turn, options: RelayActorOptions & {initialOid: IO.Oid}): Promise; export function spawnRelay(t: Turn, options: Omit): Promise; export function spawnRelay(t: Turn, options: RelayActorOptions): Promise { diff --git a/src/rewrite.ts b/src/rewrite.ts index ddb1307..c62b8ee 100644 --- a/src/rewrite.ts +++ b/src/rewrite.ts @@ -1,124 +1,59 @@ import type { Assertion, Bindings, Handle, Ref, Turn } from "./actor.js"; -import { Dictionary, IdentityMap, is, Record, Tuple, Value } from "@preserves/core"; +import { Dictionary, IdentityMap, is, Record, Tuple } from "@preserves/core"; -export type Attenuation = Array; // array of stages, each a list of alternatives -export type RewriteStage = Array; -export type Rewrite = { pattern: Pattern, template: Template }; +import * as S from './gen/sturdy.js'; +export * from './gen/sturdy.js'; -export const _CRec = Symbol.for('rec'); -export const CRec = Record.makeConstructor<{label: Value, arity: number}, never>()( - _CRec, ['label', 'arity']); - -export const _CArr = Symbol.for('arr'); -export const CArr = Record.makeConstructor<{arity: number}, never>()( - _CArr, ['arity']); - -export const _CDict = Symbol.for('dict'); -export const CDict = Record.makeConstructor<{}, never>()( - _CDict, []); - -export type ConstructorSpec = - | Record, number], never> - | Record - | Record; - -export const _PDiscard = Symbol.for('_'); -export const PDiscard = Record.makeConstructor<{}, never>()( - _PDiscard, []); - -export const _PBind = Symbol.for('bind'); -export const PBind = Record.makeConstructor<{name: symbol, pattern: Pattern}, never>()( - _PBind, ['name', 'pattern']); - -export const _PAnd = Symbol.for('and'); -export const PAnd = Record.makeConstructor<{patterns: Array}, never>()( - _PAnd, ['patterns']); - -export const _PNot = Symbol.for('not'); -export const PNot = Record.makeConstructor<{pattern: Pattern}, never>()( - _PNot, ['pattern']); - -export const _Lit = Symbol.for('lit'); -export const Lit = Record.makeConstructor<{value: Value}, never>()( - _Lit, ['value']); - -export const _PCompound = Symbol.for('compound'); -export const PCompound = - Record.makeConstructor<{ctor: ConstructorSpec, members: Dictionary}, never>()( - _PCompound, ['ctor', 'members']); - -export type Pattern = - | Record - | Record - | Record - | Record - | Record], never> - | Record], never>; - -export const _TRef = Symbol.for('ref'); -export const TRef = Record.makeConstructor<{name: symbol}, never>()( - _TRef, ['name']); - -export const _TCompound = Symbol.for('compound'); -export const TCompound = - Record.makeConstructor<{ctor: ConstructorSpec, members: Dictionary}, never>()( - _TCompound, ['ctor', 'members']); - -export type Template = - | Record - | Record], never> - | Record], never>; - -export function match(p: Pattern, v: Assertion): Bindings | null { +export function match(p: S.Pattern, v: Assertion): Bindings | null { let bindings: Bindings = {}; - function walk(p: Pattern, v: Assertion): boolean { + function walk(p: S.Pattern, v: Assertion): boolean { switch (p.label) { - case _PDiscard: + case S.$__: return true; - case _PBind: - if (walk(PBind._.pattern(p), v)) { - bindings[PBind._.name(p).asPreservesText()] = v; + case S.$bind: + if (walk(S.PBind._.pattern(p), v)) { + bindings[S.PBind._.name(p).asPreservesText()] = v; return true; } return false; - case _PAnd: - for (const pp of PAnd._.patterns(p)) { + case S.$and: + for (const pp of S.PAnd._.patterns(p)) { if (!walk(pp, v)) return false; } return true; - case _PNot: { + case S.$not: { const savedBindings = bindings; bindings = {}; - const result = !walk(PNot._.pattern(p), v) + const result = !walk(S.PNot._.pattern(p), v) bindings = savedBindings; return result; } - case _Lit: - return is(Lit._.value(p), v); - case _PCompound: { - const ctor = PCompound._.ctor(p); - const members = PCompound._.members(p); + case S.$lit: + return is(S.Lit._.value(p), v); + case S.$compound: { + const ctor = S.PCompound._.ctor(p); + const members = S.PCompound._.members(p); switch (ctor.label) { - case _CRec: + case S.$rec: if (!Record.isRecord, Ref>(v)) return false; - if (!is(CRec._.label(ctor), v.label)) return false; - if (CRec._.arity(ctor) !== v.length) return false; + if (!is(S.CRec._.label(ctor), v.label)) return false; + if (S.CRec._.arity(ctor) !== v.length) return false; for (const [key, pp] of members) { if (typeof key !== 'number') return false; if (!walk(pp, v[key])) return false; } return true; - case _CArr: + case S.$arr: if (!Array.isArray(v)) return false; if ('label' in v) return false; - if (CArr._.arity(ctor) !== v.length) return false; + if (S.CArr._.arity(ctor) !== v.length) return false; for (const [key, pp] of members) { if (typeof key !== 'number') return false; if (!walk(pp, v[key])) return false; } return true; - case _CDict: + case S.$dict: if (!Dictionary.isDictionary(v)) return false; for (const [key, pp] of members) { const vv = v.get(key as Assertion); @@ -134,41 +69,41 @@ export function match(p: Pattern, v: Assertion): Bindings | null { return walk(p, v) ? bindings : null; } -export function instantiate(t: Template, b: Bindings): Assertion { - function walk(t: Template): Assertion { +export function instantiate(t: S.Template, b: Bindings): Assertion { + function walk(t: S.Template): Assertion { switch (t.label) { - case _TRef: { - const n = TRef._.name(t).asPreservesText() + case S.$ref: { + const n = S.TRef._.name(t).asPreservesText() const v = b[n]; if (v === void 0) throw new Error(`Unbound reference: ${n}`); return v; } - case _Lit: - return Lit._.value(t) as Assertion; - case _TCompound: { - const ctor = TCompound._.ctor(t); - const members = TCompound._.members(t); + case S.$lit: + return S.Lit._.value(t) as Assertion; + case S.$compound: { + const ctor = S.TCompound._.ctor(t); + const members = S.TCompound._.members(t); switch (ctor.label) { - case _CRec: { + case S.$rec: { const v = Record( - CRec._.label(ctor) as Assertion, + S.CRec._.label(ctor) as Assertion, [] as Assertion[], ); - v.length = CRec._.arity(ctor); + v.length = S.CRec._.arity(ctor); for (const [key, tt] of members) { v[key as number] = walk(tt); } return v; } - case _CArr: { + case S.$arr: { const v = []; - v.length = CArr._.arity(ctor); + v.length = S.CArr._.arity(ctor); for (const [key, tt] of members) { v[key as number] = walk(tt); } return v; } - case _CDict: { + case S.$dict: { const v = new Dictionary(); for (const [key, tt] of members) { v.set(key as Assertion, walk(tt)); @@ -183,21 +118,25 @@ export function instantiate(t: Template, b: Bindings): Assertion { return walk(t); } -export function rewrite(r: Rewrite, v: Assertion): Assertion | null { - const bindings = match(r.pattern, v); +export function rewrite(r: S.Rewrite, v: Assertion): Assertion | null { + const bindings = match(S.Rewrite._.pattern(r), v); if (bindings === null) return null; - return instantiate(r.template, bindings); + return instantiate(S.Rewrite._.template(r), bindings); } -export function examineAlternatives(alternatives: RewriteStage, v: Assertion): Assertion | null { - for (const r of alternatives) { - const w = rewrite(r, v); - if (w !== null) return w; +export function examineAlternatives(cav: S.Caveat, v: Assertion): Assertion | null { + if (cav.label === S.$or) { + for (const r of S.Alts._.alternatives(cav)) { + const w = rewrite(r, v); + if (w !== null) return w; + } + return null; + } else { + return rewrite(cav, v); } - return null; } -export function runRewrites(a: Attenuation | undefined, v: Assertion): Assertion | null { +export function runRewrites(a: S.Attenuation | undefined, v: Assertion): Assertion | null { if (a !== void 0) { for (const stage of a) { const w = examineAlternatives(stage, v); @@ -210,11 +149,12 @@ export function runRewrites(a: Attenuation | undefined, v: Assertion): Assertion const _a = Symbol.for('a'); -export function rfilter(... patterns: Pattern[]): RewriteStage { - return patterns.map(p => ({ pattern: PBind(_a, p), template: TRef(_a) })); +export function rfilter(... patterns: S.Pattern[]): S.Caveat { + const ps = patterns.map(p => S.Rewrite(S.PBind(_a, p), S.TRef(_a))); + return ps.length === 1 ? ps[0] : S.Alts(ps); } -export function attenuate(ref: Ref, ... a: Attenuation): Ref { +export function attenuate(ref: Ref, ... a: S.Attenuation): Ref { return { ... ref, attenuation: [... a, ... (ref.attenuation ?? [])] }; } diff --git a/src/sandbox.ts b/src/sandbox.ts index a3df988..a3dd7b5 100644 --- a/src/sandbox.ts +++ b/src/sandbox.ts @@ -1,7 +1,7 @@ -import { Actor, Assertion, Ref, Turn } from "./actor.js"; -import { Relay, spawnRelay } from "./relay.js"; -import { sturdyDecode } from "./sturdy.js"; -import { Observe } from "./dataspace.js"; +import { Actor, Ref, Turn } from "./actor"; +import { Relay, spawnRelay } from "./relay"; +import { sturdyDecode } from "./sturdy"; +import { Resolve, asSturdyRef } from "./gen/sturdy"; import * as net from 'net'; import { Bytes } from "@preserves/core"; @@ -29,7 +29,7 @@ const socket = net.createConnection({ port: 5999, host: 'localhost' }, () => { // debug: true, }).then(gatekeeper => import(moduleName).then(m => t.freshen(t => { t.assert(shutdownRef, true); - t.assert(gatekeeper, Observe(cap as Assertion, t.ref({ + t.assert(gatekeeper, Resolve(asSturdyRef(cap), t.ref({ assert(t, ds) { m.default(t, ds); } diff --git a/src/server.ts b/src/server.ts index 3527118..fa5e982 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,12 +1,13 @@ import { Actor, Handle, Turn } from './actor.js'; -import { Dataspace, Observe } from './dataspace.js'; +import { Dataspace } from './dataspace.js'; import { Relay, spawnRelay } from './relay.js'; import * as net from 'net'; -import { Caveat, mint, Or, Rewrite, sturdyEncode, SturdyRef, validate, _Or, _Rewrite } from './sturdy.js'; +import { mint, sturdyEncode, SturdyRef, validate, Resolve } from './sturdy.js'; import { KEY_LENGTH } from './cryptography.js'; -import { attenuate, Attenuation } from './rewrite.js'; +import { attenuate } from './rewrite.js'; import { Bytes, IdentityMap } from '@preserves/core'; +import { Attenuation, isResolve } from './gen/sturdy.js'; const secretKey = new Bytes(KEY_LENGTH); mint('syndicate', secretKey).then(v => { @@ -14,15 +15,6 @@ mint('syndicate', secretKey).then(v => { console.log(sturdyEncode(v).toHex()); }); -function internalize(caveatChain: Caveat[][]): Attenuation { - const a: Attenuation = []; - caveatChain.slice().reverse().forEach(cs => a.push(... cs.map(c => { - const alts = c.label === _Rewrite ? [c] : Or()._.alternatives(c); - return alts.map(r => ({ pattern: Rewrite._.pattern(r), template: Rewrite._.template(r) })); - }))); - return a; -} - Turn.for(new Actor(), t => { const ds = t.ref(new Dataspace()); @@ -41,19 +33,18 @@ Turn.for(new Actor(), t => { initialRef: t.ref({ handleMap: new IdentityMap(), async assert(t, a, h) { - if (!Observe.isClassOf(a)) return; - const r = Observe._.label(a); - if (!SturdyRef.isClassOf(r)) return; + if (!isResolve(a)) return; + const r = Resolve._.sturdyref(a); if (!await validate(r, secretKey)) { console.warn(`Invalid SturdyRef: ${r.asPreservesText()}`); return; } - const attenuated_ds = attenuate( - ds, - ... internalize(SturdyRef._.caveatChain(r))); + const cavs: Attenuation = []; + SturdyRef._.caveatChain(r).forEach(cs => cavs.push(... cs)); + const attenuated_ds = attenuate(ds, ... cavs); t.freshen(t => this.handleMap.set( h, - t.assert(Observe._.observer(a), attenuated_ds))); + t.assert(Resolve._.observer(a), attenuated_ds))); }, retract(t, h) { t.retract(this.handleMap.get(h)); diff --git a/src/sturdy-demo.ts b/src/sturdy-demo.ts index 444f158..00b3f61 100644 --- a/src/sturdy-demo.ts +++ b/src/sturdy-demo.ts @@ -2,15 +2,17 @@ import { newKey } from './cryptography.js'; import { attenuate, KEY_LENGTH, mint, Rewrite, sturdyEncode, validate } from './sturdy.js'; import * as RW from './rewrite.js'; import { Bytes, Dictionary } from '@preserves/core'; +import { Ref } from 'actor.js'; async function main() { const m1 = await mint('hello world', new Bytes(KEY_LENGTH)); console.log(m1.asPreservesText()); - const m2 = await attenuate(m1, Rewrite(RW.PBind(Symbol.for('a'), - RW.PCompound(RW.CRec(Symbol.for('says'), 2), - new Dictionary([ - [0, RW.Lit('Tony')]]))), - RW.TRef(Symbol.for('a')))); + const m2 = await attenuate(m1, Rewrite( + RW.PBind(Symbol.for('a'), + RW.PCompound(RW.CRec(Symbol.for('says'), 2), + new Dictionary([ + [0, RW.Lit('Tony')]]))), + RW.TRef(Symbol.for('a')))); console.log(m2.asPreservesText()); console.log('should be true:', await validate(m1, new Bytes(KEY_LENGTH))); console.log('should be true:', await validate(m2, new Bytes(KEY_LENGTH))); diff --git a/src/sturdy.ts b/src/sturdy.ts index 9abd0f5..e42132e 100644 --- a/src/sturdy.ts +++ b/src/sturdy.ts @@ -7,69 +7,48 @@ // California: Internet Society, 2014. import { mac } from './cryptography.js'; -import { Bytes, decode, encode, is, Record, Value } from '@preserves/core'; -import type { Pattern, Template } from './rewrite.js'; +import { Bytes, decode, encode, is } from '@preserves/core'; +import * as S from './gen/sturdy.js'; +export * from './gen/sturdy.js'; -export type EmbeddedRef = never; -export type SturdyValue = Value; - -export const _SturdyRef = Symbol.for('sturdyref'); -export const SturdyRef = Record.makeConstructor<{ - oid: SturdyValue, // (arbitrary) name of the ultimate target of the ref - caveatChain: Array[], - // ^ caveats/rewrites. Evaluated RIGHT-TO-LEFT; each Array is evaluated LEFT-TO-RIGHT - sig: Bytes, // *keyed* signature of canonicalEncode of rightmost item in [oid, ... caveatChain] -}, EmbeddedRef>()(_SturdyRef, ['oid', 'caveatChain', 'sig']); -export type SturdyRef = [SturdyValue, Array[], Bytes] & { label: typeof _SturdyRef }; - -export type Caveat = Or; -// ^ embodies 1st-party caveats over assertion structure, but nothing else -// can add 3rd-party caveats and richer predicates later - -export const _Or = Symbol.for('or'); -export const Or = () => - Record.makeConstructor<{ alternatives: T[] }, EmbeddedRef>()(_Or, ['alternatives']); -export type Or = T | Record; - -export const _Rewrite = Symbol.for('rewrite'); -export const Rewrite = Record.makeConstructor<{ - pattern: Pattern, - template: Template, -}, EmbeddedRef>()(_Rewrite, ['pattern', 'template']); -export type Rewrite = ReturnType; +export type SturdyValue = S._val; export const KEY_LENGTH = 16; // 128 bits +export function pointerNotAllowed(): never { + throw new Error("Embedded Ref not permitted in SturdyRef"); +} + export function sturdyEncode(v: SturdyValue): Bytes { - return encode(v, { + return encode(v, { canonical: true, includeAnnotations: false, - encodePointer() { throw new Error("EmbeddedRef not permitted in SturdyRef"); }, + encodePointer: pointerNotAllowed, }); } export function sturdyDecode(bs: Bytes): SturdyValue { - return decode(bs, { + return decode(bs, { includeAnnotations: false, - decodePointer() { throw new Error("EmbeddedRef not permitted in SturdyRef"); }, + decodePointer: pointerNotAllowed, }); } -export async function mint(oid: SturdyValue, secretKey: Bytes): Promise { - return SturdyRef(oid, [], await mac(secretKey, sturdyEncode(oid))); +export async function mint(oid: SturdyValue, secretKey: Bytes): Promise { + return S.SturdyRef(oid, [], await mac(secretKey, sturdyEncode(oid))); } -export async function attenuate(r: SturdyRef, ... a: Array): Promise { - return SturdyRef( - SturdyRef._.oid(r), - [... SturdyRef._.caveatChain(r), a], - await mac(SturdyRef._.sig(r), sturdyEncode(a)) +export async function attenuate(r: S.SturdyRef, ... a: S.Attenuation): Promise { + return S.SturdyRef( + S.SturdyRef._.oid(r), + [... S.SturdyRef._.caveatChain(r), a], + await mac(S.SturdyRef._.sig(r), sturdyEncode(a)) ); } -export async function validate(r: SturdyRef, secretKey: Bytes): Promise { - const sig = await SturdyRef._.caveatChain(r).reduce( +export async function validate(r: S.SturdyRef, secretKey: Bytes): Promise { + const sig = await S.SturdyRef._.caveatChain(r).reduce( async (sig, a) => mac(await sig, sturdyEncode(a)), - mac(secretKey, sturdyEncode(SturdyRef._.oid(r)))); - return is(sig, SturdyRef._.sig(r)); + mac(secretKey, sturdyEncode(S.SturdyRef._.oid(r)))); + return is(sig, S.SturdyRef._.sig(r)); }