Simplify gatekeeper client-side protocols

This commit is contained in:
Tony Garnock-Jones 2024-06-07 11:53:22 +02:00
parent f45ab47175
commit 6fb100ee60
10 changed files with 222 additions and 193 deletions

View File

@ -1,10 +1,10 @@
/// SPDX-License-Identifier: GPL-3.0-or-later /// SPDX-License-Identifier: GPL-3.0-or-later
/// SPDX-FileCopyrightText: Copyright © 2016-2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com> /// SPDX-FileCopyrightText: Copyright © 2016-2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
import { fromJS, Bytes, Dataspace, Ref, Sturdy, AnyValue, Reader, Schemas, stringify } from "@syndicate-lang/core"; import { fromJS, Bytes, Dataspace, Ref, AnyValue, Reader, Schemas, stringify } from "@syndicate-lang/core";
import { boot as bootHtml, Anchor, template as html, HtmlFragments, GlobalEvent, UIAttribute, UIChangeableProperty } from "@syndicate-lang/html"; import { boot as bootHtml, Anchor, template as html, HtmlFragments, GlobalEvent, UIAttribute, UIChangeableProperty } from "@syndicate-lang/html";
import { boot as bootWakeDetector, WakeEvent } from "./wake-detector"; import { boot as bootWakeDetector, WakeEvent } from "./wake-detector";
import { boot as bootWsRelay } from "@syndicate-lang/ws-relay"; import { boot as bootWsRelay, contactRemote } from "@syndicate-lang/ws-relay";
import { Present, Says } from './gen/simpleChatProtocol'; import { Present, Says } from './gen/simpleChatProtocol';
import G = Schemas.gatekeeper; import G = Schemas.gatekeeper;
import N = Schemas.noise; import N = Schemas.noise;
@ -38,47 +38,40 @@ function bootChat(ds: Ref) {
console.error(e); console.error(e);
} }
assert UIAttribute('#route', 'class', 'invalid') when (!route); assert UIAttribute('#route', 'class', 'invalid') when (!route);
if (route) contactRemote(route); if (route) {
} console.log('contactRemote', route);
contactRemote(route, (remoteDs, control, addr) => {
function contactRemote(route: G.Route<Ref>) { on message WakeEvent() => at control {
console.log('contactRemote', route); send message G.ForceDisconnect();
during G.ResolvePath({ }
"route": route,
"addr": $addr, outputState('connected', 'connected to ' + stringify(addr));
"control": $control: Ref, on stop outputState('disconnected', 'disconnected from ' + stringify(addr));
"resolved": G.Resolved.accepted($remoteDs: Ref),
}) => { on message GlobalEvent('#send_chat', 'click', _) => {
on message WakeEvent() => at control { const inp = document.getElementById("chat_input") as HTMLInputElement;
send message G.ForceDisconnect(); var utterance = inp.value;
} inp.value = '';
if (utterance) {
outputState('connected', 'connected to ' + stringify(addr)); at remoteDs {
on stop outputState('disconnected', 'disconnected from ' + stringify(addr)); send message Says({ who: nym.value, what: utterance });
}
on message GlobalEvent('#send_chat', 'click', _) => {
const inp = document.getElementById("chat_input") as HTMLInputElement;
var utterance = inp.value;
inp.value = '';
if (utterance) {
at remoteDs {
send message Says({ who: nym.value, what: utterance });
} }
} }
}
at remoteDs {
at remoteDs { assert Present(nym.value);
assert Present(nym.value);
const ui = new Anchor();
const ui = new Anchor(); during Present($who: string) => at ds {
during Present($who: string) => at ds { assert ui.context(who).html('#nymlist', html`<li>${who}</li>`);
assert ui.context(who).html('#nymlist', html`<li>${who}</li>`); }
on message Says({ "who": $who: string, "what": $what: string }) => {
outputUtterance(who, what);
}
} }
}, ds);
on message Says({ "who": $who: string, "what": $what: string }) => {
outputUtterance(who, what);
}
}
} }
} }
} }

View File

@ -2,30 +2,35 @@
# yarn lockfile v1 # yarn lockfile v1
"@preserves/core@0.995.200", "@preserves/core@^0.995.200": "@preserves/core@^0.995.200":
version "0.995.200" version "0.995.200"
resolved "https://registry.yarnpkg.com/@preserves/core/-/core-0.995.200.tgz#65575cf8f9320e73b5d37fa9ac9d6881a33fd3a6" resolved "https://registry.yarnpkg.com/@preserves/core/-/core-0.995.200.tgz#65575cf8f9320e73b5d37fa9ac9d6881a33fd3a6"
integrity sha512-htZ2x+hltUpKoPsviWSuelzZW96po9zVSsN0RZEih60FahNX0R2LCqFJ6v5lyATuQ9oHALVi0w8w4rtf8oiAJw== integrity sha512-htZ2x+hltUpKoPsviWSuelzZW96po9zVSsN0RZEih60FahNX0R2LCqFJ6v5lyATuQ9oHALVi0w8w4rtf8oiAJw==
"@preserves/schema-cli@0.995.201": "@preserves/core@^0.995.206":
version "0.995.201" version "0.995.206"
resolved "https://registry.yarnpkg.com/@preserves/schema-cli/-/schema-cli-0.995.201.tgz#e87cb7ba51b225ff6ea96f177c5388907adf6717" resolved "https://registry.yarnpkg.com/@preserves/core/-/core-0.995.206.tgz#f13507aa39dbc8cbd5d10ac18d107c06e92bf519"
integrity sha512-n/lpVgWBY1UTRySPXIwWG4Yu87OrQ9b7Xo8zPPQzD/DJDTEDT3y3SUujY7UTF1pQn09xRGnfPnDu1RoRCv2J3w== integrity sha512-Uc4SSSXHQG6xYER9cCc0bkbLYM8fGVbO4kXw3DI8BaQjeUFPOX3yYNYPAvRNzmaF4MuMOZY+jSJpjJ9o5g6A0w==
"@preserves/schema-cli@>=0.995.201":
version "0.995.206"
resolved "https://registry.yarnpkg.com/@preserves/schema-cli/-/schema-cli-0.995.206.tgz#709c96d938dce3bf5b132a3da1304ca3c84bbe2e"
integrity sha512-870rwjKtQHA8huwt8MpMImnKC2cBHKYzcJi4811k5RuYs/HdbT9B81NMSiSn+Hq19/v9fEBicwwEmao3oM7hJw==
dependencies: dependencies:
"@preserves/core" "^0.995.200" "@preserves/core" "^0.995.206"
"@preserves/schema" "^0.995.201" "@preserves/schema" "^0.995.206"
chalk "^4.1" chalk "^4.1"
chokidar "^3.5" chokidar "^3.5"
commander "^7.2" commander "^7.2"
glob "^7.1" glob "^7.1"
minimatch "^3.0" minimatch "^3.0"
"@preserves/schema@^0.995.201": "@preserves/schema@^0.995.206":
version "0.995.201" version "0.995.206"
resolved "https://registry.yarnpkg.com/@preserves/schema/-/schema-0.995.201.tgz#827ad539afb0fdf68ba885ee3ff43db0fd473aa5" resolved "https://registry.yarnpkg.com/@preserves/schema/-/schema-0.995.206.tgz#8e35a7db5363374f6794009d394510871f86746e"
integrity sha512-0SuhwOEAfxtrAeOTZ8V1yNEcHHHEbfUmoFawrF+vzTQFNZRz/X3LF1FM6wOiJlHtb7tBr4B+rHXX8jfNrqrdkA== integrity sha512-JmXGxSqsNSEzZNpvOYPupSZhffvMIvgnUmKE+Yt5oiPiD/8H52EbKRrqBUQuVpvI4hvwgThWAtNIg3+q42vI2w==
dependencies: dependencies:
"@preserves/core" "^0.995.200" "@preserves/core" "^0.995.206"
"@rollup/pluginutils@^3.0.9": "@rollup/pluginutils@^3.0.9":
version "3.1.0" version "3.1.0"

View File

@ -1,4 +1,4 @@
´³bundle·µ³tcp„´³schema·³version°³ definitions·³TcpLocal´³rec´³lit³ tcp-local„´³tupleµ´³named³host´³atom³String„„´³named³port´³atom³ SignedInteger„„„„„³ TcpRemote´³rec´³lit³ ´³bundle·µ³rpc„´³schema·³version°³ definitions·³Answer´³rec´³lit³a„´³tupleµ´³named³request³any„´³named³response³any„„„„³Result´³orµµ±ok´³rec´³lit³ok„´³tupleµ´³named³value³any„„„„„µ±error´³rec´³lit³error„´³tupleµ´³named³error³any„„„„„„„³Question´³rec´³lit³q„´³tupleµ´³named³request³any„„„„„³ embeddedType´³refµ³ EntityRef„³Cap„„„µ³tcp„´³schema·³version°³ definitions·³TcpLocal´³rec´³lit³ tcp-local„´³tupleµ´³named³host´³atom³String„„´³named³port´³atom³ SignedInteger„„„„„³ TcpRemote´³rec´³lit³
tcp-remote„´³tupleµ´³named³host´³atom³String„„´³named³port´³atom³ SignedInteger„„„„„³ TcpPeerInfo´³rec´³lit³tcp-peer„´³tupleµ´³named³handle´³embedded³any„„´³named³local´³refµ„³TcpLocal„„´³named³remote´³refµ„³ TcpRemote„„„„„„³ embeddedType´³refµ³ EntityRef„³Cap„„„µ³http„´³schema·³version°³ definitions·³Chunk´³orµµ±string´³atom³String„„µ±bytes´³atom³ tcp-remote„´³tupleµ´³named³host´³atom³String„„´³named³port´³atom³ SignedInteger„„„„„³ TcpPeerInfo´³rec´³lit³tcp-peer„´³tupleµ´³named³handle´³embedded³any„„´³named³local´³refµ„³TcpLocal„„´³named³remote´³refµ„³ TcpRemote„„„„„„³ embeddedType´³refµ³ EntityRef„³Cap„„„µ³http„´³schema·³version°³ definitions·³Chunk´³orµµ±string´³atom³String„„µ±bytes´³atom³
ByteString„„„„³Headers´³dictof´³atom³Symbol„´³atom³String„„³MimeType´³atom³Symbol„³ ByteString„„„„³Headers´³dictof´³atom³Symbol„´³atom³String„„³MimeType´³atom³Symbol„³
QueryValue´³orµµ±string´³atom³String„„µ±file´³rec´³lit³file„´³tupleµ´³named³filename´³atom³String„„´³named³headers´³refµ„³Headers„„´³named³body´³atom³ QueryValue´³orµµ±string´³atom³String„„µ±file´³rec´³lit³file„´³tupleµ´³named³filename´³atom³String„„´³named³headers´³refµ„³Headers„„´³named³body´³atom³
@ -40,5 +40,5 @@ ByteString
RunService´³rec´³lit³ run-service„´³tupleµ´³named³ serviceName³any„„„„³ ServiceState´³rec´³lit³ service-state„´³tupleµ´³named³ serviceName³any„´³named³state´³refµ„³State„„„„„³ ServiceObject´³rec´³lit³service-object„´³tupleµ´³named³ serviceName³any„´³named³object³any„„„„³RequireService´³rec´³lit³require-service„´³tupleµ´³named³ serviceName³any„„„„³RestartService´³rec´³lit³restart-service„´³tupleµ´³named³ serviceName³any„„„„³ServiceDependency´³rec´³lit³ RunService´³rec´³lit³ run-service„´³tupleµ´³named³ serviceName³any„„„„³ ServiceState´³rec´³lit³ service-state„´³tupleµ´³named³ serviceName³any„´³named³state´³refµ„³State„„„„„³ ServiceObject´³rec´³lit³service-object„´³tupleµ´³named³ serviceName³any„´³named³object³any„„„„³RequireService´³rec´³lit³require-service„´³tupleµ´³named³ serviceName³any„„„„³RestartService´³rec´³lit³restart-service„´³tupleµ´³named³ serviceName³any„„„„³ServiceDependency´³rec´³lit³
depends-on„´³tupleµ´³named³depender³any„´³named³dependee´³refµ„³ ServiceState„„„„„„³ embeddedType´³refµ³ EntityRef„³Cap„„„µ³protocol„´³schema·³version°³ definitions·³Nop´³lit€„³Oid´³atom³ SignedInteger„³Sync´³rec´³lit³S„´³tupleµ´³named³peer´³embedded´³lit<69>„„„„„„³Turn´³seqof´³refµ„³ TurnEvent„„³Error´³rec´³lit³error„´³tupleµ´³named³message´³atom³String„„´³named³detail³any„„„„³Event´³orµµ±Assert´³refµ„³Assert„„µ±Retract´³refµ„³Retract„„µ±Message´³refµ„³Message„„µ±Sync´³refµ„³Sync„„„„³Assert´³rec´³lit³A„´³tupleµ´³named³ assertion´³refµ„³ Assertion„„´³named³handle´³refµ„³Handle„„„„„³Handle´³atom³ SignedInteger„³Packet´³orµµ±Turn´³refµ„³Turn„„µ±Error´³refµ„³Error„„µ± Extension´³refµ„³ Extension„„µ±Nop´³refµ„³Nop„„„„³Message´³rec´³lit³M„´³tupleµ´³named³body´³refµ„³ Assertion„„„„„³Retract´³rec´³lit³R„´³tupleµ´³named³handle´³refµ„³Handle„„„„„³ Assertion³any³ Extension´³rec´³named³label³any„´³named³fields´³seqof³any„„„³ TurnEvent´³tupleµ´³named³oid´³refµ„³Oid„„´³named³event´³refµ„³Event„„„„„³ embeddedType€„„µ³ dataspace„´³schema·³version°³ definitions·³Observe´³rec´³lit³Observe„´³tupleµ´³named³pattern´³refµ³dataspacePatterns„³Pattern„„´³named³observer´³embedded³any„„„„„„³ embeddedType´³refµ³ EntityRef„³Cap„„„µ³ depends-on„´³tupleµ´³named³depender³any„´³named³dependee´³refµ„³ ServiceState„„„„„„³ embeddedType´³refµ³ EntityRef„³Cap„„„µ³protocol„´³schema·³version°³ definitions·³Nop´³lit€„³Oid´³atom³ SignedInteger„³Sync´³rec´³lit³S„´³tupleµ´³named³peer´³embedded´³lit<69>„„„„„„³Turn´³seqof´³refµ„³ TurnEvent„„³Error´³rec´³lit³error„´³tupleµ´³named³message´³atom³String„„´³named³detail³any„„„„³Event´³orµµ±Assert´³refµ„³Assert„„µ±Retract´³refµ„³Retract„„µ±Message´³refµ„³Message„„µ±Sync´³refµ„³Sync„„„„³Assert´³rec´³lit³A„´³tupleµ´³named³ assertion´³refµ„³ Assertion„„´³named³handle´³refµ„³Handle„„„„„³Handle´³atom³ SignedInteger„³Packet´³orµµ±Turn´³refµ„³Turn„„µ±Error´³refµ„³Error„„µ± Extension´³refµ„³ Extension„„µ±Nop´³refµ„³Nop„„„„³Message´³rec´³lit³M„´³tupleµ´³named³body´³refµ„³ Assertion„„„„„³Retract´³rec´³lit³R„´³tupleµ´³named³handle´³refµ„³Handle„„„„„³ Assertion³any³ Extension´³rec´³named³label³any„´³named³fields´³seqof³any„„„³ TurnEvent´³tupleµ´³named³oid´³refµ„³Oid„„´³named³event´³refµ„³Event„„„„„³ embeddedType€„„µ³ dataspace„´³schema·³version°³ definitions·³Observe´³rec´³lit³Observe„´³tupleµ´³named³pattern´³refµ³dataspacePatterns„³Pattern„„´³named³observer´³embedded³any„„„„„„³ embeddedType´³refµ³ EntityRef„³Cap„„„µ³
gatekeeper„´³schema·³version°³ definitions·³Bind´³rec´³lit³bind„´³tupleµ´³named³ description´³refµ„³ Description„„´³named³target´³embedded³any„„´³named³observer´³refµ„³ BindObserver„„„„„³Step´³rec´³named³stepType´³atom³Symbol„„´³tupleµ´³named³detail³any„„„„³Bound´³orµµ±bound´³rec´³lit³bound„´³tupleµ´³named³pathStep´³refµ„³PathStep„„„„„„µ±Rejected´³refµ„³Rejected„„„„³Route´³rec´³lit³route„´³ tuplePrefixµ´³named³ gatekeeper„´³schema·³version°³ definitions·³Bind´³rec´³lit³bind„´³tupleµ´³named³ description´³refµ„³ Description„„´³named³target´³embedded³any„„´³named³observer´³refµ„³ BindObserver„„„„„³Step´³rec´³named³stepType´³atom³Symbol„„´³tupleµ´³named³detail³any„„„„³Bound´³orµµ±bound´³rec´³lit³bound„´³tupleµ´³named³pathStep´³refµ„³PathStep„„„„„„µ±Rejected´³refµ„³Rejected„„„„³Route´³rec´³lit³route„´³ tuplePrefixµ´³named³
transports´³seqof³any„„„´³named³ pathSteps´³seqof´³refµ„³PathStep„„„„„³Resolve´³rec´³lit³resolve„´³tupleµ´³named³step´³refµ„³Step„„´³named³observer´³embedded´³refµ„³Resolved„„„„„„³PathStep´³rec´³named³stepType´³atom³Symbol„„´³tupleµ´³named³detail³any„„„„³Rejected´³rec´³lit³rejected„´³tupleµ´³named³detail³any„„„„³Resolved´³orµµ±accepted´³rec´³lit³accepted„´³tupleµ´³named³responderSession´³embedded³any„„„„„„µ±Rejected´³refµ„³Rejected„„„„³ Description´³rec´³named³stepType´³atom³Symbol„„´³tupleµ´³named³detail³any„„„„³ ResolvePath´³rec´³lit³ resolve-path„´³tupleµ´³named³route´³refµ„³Route„„´³named³addr³any„´³named³control´³embedded´³refµ„³TransportControl„„„´³named³resolved´³refµ„³Resolved„„„„„³ BindObserver´³orµµ±present´³embedded´³refµ„³Bound„„„µ±absent´³lit€„„„„³ForceDisconnect´³rec´³lit³force-disconnect„´³tupleµ„„„³ResolvedPathStep´³rec´³lit³ path-step„´³tupleµ´³named³origin´³embedded´³refµ„³Resolve„„„´³named³pathStep´³refµ„³PathStep„„´³named³resolved´³refµ„³Resolved„„„„„³TransportControl´³refµ„³ForceDisconnect„³TransportConnection´³rec´³lit³connect-transport„´³tupleµ´³named³addr³any„´³named³control´³embedded´³refµ„³TransportControl„„„´³named³resolved´³refµ„³Resolved„„„„„„³ embeddedType´³refµ³ EntityRef„³Cap„„„µ³transportAddress„´³schema·³version°³ definitions·³Tcp´³rec´³lit³tcp„´³tupleµ´³named³host´³atom³String„„´³named³port´³atom³ SignedInteger„„„„„³Unix´³rec´³lit³unix„´³tupleµ´³named³path´³atom³String„„„„„³Stdio´³rec´³lit³stdio„´³tupleµ„„„³ WebSocket´³rec´³lit³ws„´³tupleµ´³named³url´³atom³String„„„„„„³ embeddedType€„„µ³dataspacePatterns„´³schema·³version°³ definitions·³AnyAtom´³orµµ±bool´³atom³Boolean„„µ±double´³atom³Double„„µ±int´³atom³ SignedInteger„„µ±string´³atom³String„„µ±bytes´³atom³ transports´³seqof³any„„„´³named³ pathSteps´³seqof´³refµ„³PathStep„„„„„³Resolve´³rec´³lit³resolve„´³tupleµ´³named³step´³refµ„³Step„„´³named³observer´³embedded´³refµ„³Resolved„„„„„„³PathStep´³rec´³named³stepType´³atom³Symbol„„´³tupleµ´³named³detail³any„„„„³Rejected´³rec´³lit³rejected„´³tupleµ´³named³detail³any„„„„³Resolved´³orµµ±accepted´³rec´³lit³accepted„´³tupleµ´³named³responderSession´³embedded³any„„„„„„µ±Rejected´³refµ„³Rejected„„„„³ Description´³rec´³named³stepType´³atom³Symbol„„´³tupleµ´³named³detail³any„„„„³ ResolvePath´³rec´³lit³ resolve-path„´³tupleµ´³named³route´³refµ„³Route„„„„„³ BindObserver´³orµµ±present´³embedded´³refµ„³Bound„„„µ±absent´³lit€„„„„³ ResolvedPath´³rec´³lit³ resolved-path„´³tupleµ´³named³addr³any„´³named³control´³embedded´³refµ„³TransportControl„„„´³named³responderSession´³embedded³any„„„„„³ForceDisconnect´³rec´³lit³force-disconnect„´³tupleµ„„„³ResolvePathStep´³rec´³lit³resolve-path-step„´³tupleµ´³named³origin´³embedded´³refµ„³Resolve„„„´³named³pathStep´³refµ„³PathStep„„„„„³ConnectTransport´³rec´³lit³connect-transport„´³tupleµ´³named³addr³any„„„„³ResolvedPathStep´³embedded³any„³TransportControl´³refµ„³ForceDisconnect„³ConnectedTransport´³rec´³lit³connected-transport„´³tupleµ´³named³addr³any„´³named³control´³embedded´³refµ„³TransportControl„„„´³named³responderSession´³embedded³any„„„„„„³ embeddedType´³refµ³ EntityRef„³Cap„„„µ³transportAddress„´³schema·³version°³ definitions·³Tcp´³rec´³lit³tcp„´³tupleµ´³named³host´³atom³String„„´³named³port´³atom³ SignedInteger„„„„„³Unix´³rec´³lit³unix„´³tupleµ´³named³path´³atom³String„„„„„³Stdio´³rec´³lit³stdio„´³tupleµ„„„³ WebSocket´³rec´³lit³ws„´³tupleµ´³named³url´³atom³String„„„„„„³ embeddedType€„„µ³dataspacePatterns„´³schema·³version°³ definitions·³AnyAtom´³orµµ±bool´³atom³Boolean„„µ±double´³atom³Double„„µ±int´³atom³ SignedInteger„„µ±string´³atom³String„„µ±bytes´³atom³
ByteString„„µ±symbol´³atom³Symbol„„µ±embedded´³embedded³any„„„„³Pattern´³orµµ±discard´³rec´³lit³_„´³tupleµ„„„„µ±bind´³rec´³lit³bind„´³tupleµ´³named³pattern´³refµ„³Pattern„„„„„„µ±lit´³rec´³lit³lit„´³tupleµ´³named³value´³refµ„³AnyAtom„„„„„„µ±group´³rec´³lit³group„´³tupleµ´³named³type´³refµ„³ GroupType„„´³named³entries´³dictof³any´³refµ„³Pattern„„„„„„„„„³ GroupType´³orµµ±rec´³rec´³lit³rec„´³tupleµ´³named³label³any„„„„„µ±arr´³rec´³lit³arr„´³tupleµ„„„„µ±dict´³rec´³lit³dict„´³tupleµ„„„„„„„³ embeddedType´³refµ³ EntityRef„³Cap„„„„„ ByteString„„µ±symbol´³atom³Symbol„„µ±embedded´³embedded³any„„„„³Pattern´³orµµ±discard´³rec´³lit³_„´³tupleµ„„„„µ±bind´³rec´³lit³bind„´³tupleµ´³named³pattern´³refµ„³Pattern„„„„„„µ±lit´³rec´³lit³lit„´³tupleµ´³named³value´³refµ„³AnyAtom„„„„„„µ±group´³rec´³lit³group„´³tupleµ´³named³type´³refµ„³ GroupType„„´³named³entries´³dictof³any´³refµ„³Pattern„„„„„„„„„³ GroupType´³orµµ±rec´³rec´³lit³rec„´³tupleµ´³named³label³any„„„„„µ±arr´³rec´³lit³arr„´³tupleµ„„„„µ±dict´³rec´³lit³dict„´³tupleµ„„„„„„„³ embeddedType´³refµ³ EntityRef„³Cap„„„„„

View File

@ -52,14 +52,23 @@ Bound = <bound @pathStep PathStep> / Rejected .
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Protocol at client-side dataspaces, for resolution utilities # Protocol at client-side dataspaces, for resolution utilities
# Assertion. In response to observation of this with appropriate captures/wildcards in `addr` # The client-side operates using `rpc.Question`s and `rpc.Answer`s.
# and `resolved`, respondent will follow `route.pathSteps` starting from one of the
# `route.transports`, asserting `ResolvePath` with the final `Resolved` as well as the selected
# transport `addr` and a `control` for it.
ResolvePath = <resolve-path @route Route @addr any @control #:TransportControl @resolved Resolved> .
TransportConnection = <connect-transport @addr any @control #:TransportControl @resolved Resolved> . # Assert `rpc.Question` with `ResolvePath` to request resolution of `Route`. The resolution
ResolvedPathStep = <path-step @origin #:Resolve @pathStep PathStep @resolved Resolved> . # utility will continuously try to satisfy the request, following `route.pathSteps` starting
# from one of the `route.transports`, ultimately asserting `rpc.Answer` with an `rpc.Result` in
# response. If the process completes successfully, the `rpc.Result.ok` will carry a
# `ResolvedPath`.
ResolvePath = <resolve-path @route Route> .
ResolvedPath = <resolved-path @addr any @control #:TransportControl @responderSession #:any> .
# Assertions. As `ResolvePath`/`ResolvedPath`, but just for an initial transport link setup.
ConnectTransport = <connect-transport @addr any> .
ConnectedTransport = <connected-transport @addr any @control #:TransportControl @responderSession #:any> .
# Assertions. Like `ResolvePath`/`ResolvedPath`, but for incremental resolution along a route.
ResolvePathStep = <resolve-path-step @origin #:Resolve @pathStep PathStep> .
ResolvedPathStep = #:any .
PathStep = <<rec> @stepType symbol [@detail any]> . PathStep = <<rec> @stepType symbol [@detail any]> .
# A `Route` describes a network path that can be followed to reach some target entity. # A `Route` describes a network path that can be followed to reach some target entity.

View File

@ -0,0 +1,11 @@
version 1 .
embeddedType EntityRef.Cap .
# Assertion. Establishes a frame for a request.
Question = <q @request any> .
# Assertion or message. Responds to a question.
Answer = <a @request any @response any> .
# Value. Captures a common "ok-or-error" pattern as seen in e.g. Rust.
Result = <ok @value any> / <error @error any> .

View File

@ -16,6 +16,8 @@ export * as Pattern from './runtime/pattern.js';
export * as QuasiValue from './runtime/quasivalue.js'; export * as QuasiValue from './runtime/quasivalue.js';
export * from './runtime/randomid.js'; export * from './runtime/randomid.js';
export * as Rewrite from './runtime/rewrite.js'; export * as Rewrite from './runtime/rewrite.js';
export * from './runtime/rpc.js';
export * as Rpc from './runtime/rpc.js';
export * as Skeleton from './runtime/skeleton.js'; export * as Skeleton from './runtime/skeleton.js';
export * from './runtime/space.js'; export * from './runtime/space.js';
export * from './runtime/supervise.js'; export * from './runtime/supervise.js';

View File

@ -0,0 +1,51 @@
/// SPDX-License-Identifier: GPL-3.0-or-later
/// SPDX-FileCopyrightText: Copyright © 2024 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
import { Assertable, Ref } from './actor.js';
import { ctor, dict, QuasiValue } from './quasivalue.js';
import * as R from '../gen/rpc.js';
import { fromJS } from '@preserves/core';
export { Result } from '../gen/rpc.js';
export function Question<Q extends Assertable>(q: Q): R.Question<Ref> {
return R.Question(fromJS<Ref>(q));
}
export namespace Question {
export function quasiValue(q: QuasiValue): QuasiValue {
return ctor(R.Question, q);
}
}
export function Answer<Q extends Assertable, A extends Assertable>(q: Q, a: A): R.Answer<Ref> {
return R.Answer({ request: fromJS<Ref>(q), response: fromJS<Ref>(a) });
}
export namespace Answer {
export function quasiValue(q: QuasiValue, a: QuasiValue): QuasiValue {
return ctor(R.Answer, dict(
["request", q],
["response", a]));
}
}
export function resultOk<V extends Assertable>(v: V): R.Result<Ref> {
return R.Result.ok(fromJS<Ref>(v));
}
export namespace resultOk {
export function quasiValue(v: QuasiValue): QuasiValue {
return ctor(R.Result.ok, v);
}
}
export function resultError<E extends Assertable>(e: E): R.Result<Ref> {
return R.Result.error(fromJS<Ref>(e));
}
export namespace resultError {
export function quasiValue(e: QuasiValue): QuasiValue {
return ctor(R.Result.error, e);
}
}

View File

@ -6,6 +6,7 @@ export * as dataspace from './gen/dataspace.js';
export * as gatekeeper from './gen/gatekeeper.js'; export * as gatekeeper from './gen/gatekeeper.js';
export * as protocol from './gen/protocol.js'; export * as protocol from './gen/protocol.js';
export * as noise from './gen/noise.js'; export * as noise from './gen/noise.js';
export * as rpc from './gen/rpc.js';
export * as service from './gen/service.js'; export * as service from './gen/service.js';
export * as stdenv from './gen/stdenv.js'; export * as stdenv from './gen/stdenv.js';
export * as stream from './gen/stream.js'; export * as stream from './gen/stream.js';

View File

@ -1,7 +1,7 @@
/// SPDX-License-Identifier: GPL-3.0-or-later /// SPDX-License-Identifier: GPL-3.0-or-later
/// SPDX-FileCopyrightText: Copyright © 2023-2024 Tony Garnock-Jones <tonyg@leastfixedpoint.com> /// SPDX-FileCopyrightText: Copyright © 2023-2024 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
import { Dataspace, Ref, Sturdy, Reader, Schemas, Embedded, randomId, fromJS } from "@syndicate-lang/core"; import { Dataspace, Ref, Sturdy, Reader, Schemas, Embedded, randomId, fromJS, Question, resultOk, Answer } from "@syndicate-lang/core";
import { boot as bootHtml, Widget } from "@syndicate-lang/html2"; import { boot as bootHtml, Widget } from "@syndicate-lang/html2";
import wsRelay from "@syndicate-lang/ws-relay"; import wsRelay from "@syndicate-lang/ws-relay";
import { ExampleDefinition } from './gen/example'; import { ExampleDefinition } from './gen/example';
@ -34,11 +34,10 @@ function bootApp(ds: Ref) {
'<ref {oid: "syndicate" sig: #[acowDB2/oI+6aSEC3YIxGg==]}>').next())))], '<ref {oid: "syndicate" sig: #[acowDB2/oI+6aSEC3YIxGg==]}>').next())))],
}); });
during G.ResolvePath({ assert Question(G.ResolvePath(route));
"route": route, during Answer(G.ResolvePath(route), resultOk(G.ResolvedPath({
"resolved": G.Resolved.accepted($remoteDs_e: Embedded), responderSession: $remoteDs: Ref,
}) => { }))) => {
const remoteDs = remoteDs_e.embeddedValue;
at remoteDs { at remoteDs {
assert ExampleDefinition(this_instance); assert ExampleDefinition(this_instance);
during ExampleDefinition($who: string) => { during ExampleDefinition($who: string) => {

View File

@ -7,7 +7,6 @@ import {
Bytes, Bytes,
Dataspace, Dataspace,
IdentitySet, IdentitySet,
Observe,
QuasiValue as Q, QuasiValue as Q,
Record, Record,
Ref, Ref,
@ -19,23 +18,28 @@ import {
canonicalEncode, canonicalEncode,
decode, decode,
fromJS, fromJS,
isEmbedded,
parse, parse,
stringify, stringify,
underlying, underlying,
Embeddable, Embeddable,
SaltyCrypto, SaltyCrypto,
Question,
Answer,
Result,
resultOk,
resultError,
} from "@syndicate-lang/core"; } from "@syndicate-lang/core";
import G = Schemas.gatekeeper; import G = Schemas.gatekeeper;
import S = Schemas.sturdy; import S = Schemas.sturdy;
import N = Schemas.noise; import N = Schemas.noise;
import T = Schemas.transportAddress; import T = Schemas.transportAddress;
import E = Schemas.stdenv; import E = Schemas.stdenv;
import R = Schemas.rpc;
export * from './seal.js'; export * from './seal.js';
type TransportState = { type TransportState = {
addr: T.WebSocket, addr: AnyValue,
control: Ref, control: Ref,
peer: Ref, peer: Ref,
}; };
@ -43,23 +47,17 @@ type TransportState = {
export function boot(ds = Dataspace.local, debug: boolean = false, WebSocketConstructor?: typeof WebSocket) { export function boot(ds = Dataspace.local, debug: boolean = false, WebSocketConstructor?: typeof WebSocket) {
spawn named 'transportConnector' { spawn named 'transportConnector' {
at ds { at ds {
during Observe({ "pattern": :pattern G.TransportConnection({ during Question(G.ConnectTransport(T.WebSocket($url: string))) => {
"addr": \$addrPatValue,
"control": \_,
"resolved": \_,
}) }) => {
const addr = Q.drop_lit(addrPatValue, T.toWebSocket);
if (!addr) return;
let counter =0; let counter =0;
Supervisor.always(() => ['transportConnector', fromJS(addr), counter++], () => { Supervisor.always(() => ['wsTransport', fromJS(url), counter++], () => {
console.log('connecting', addr.url, counter); console.log('connecting', url, counter);
connectTo(addr); connectWs(url);
}); });
} }
} }
} }
function connectTo(addr: T.WebSocket) { function connectWs(url: string) {
const facet = Turn.activeFacet; const facet = Turn.activeFacet;
facet.preventInertCheck(); facet.preventInertCheck();
const controlEntity = { const controlEntity = {
@ -73,9 +71,9 @@ export function boot(ds = Dataspace.local, debug: boolean = false, WebSocketCons
function succeed(ws: WebSocket) { function succeed(ws: WebSocket) {
if (final) return; if (final) return;
final = true; final = true;
console.log('opened', addr.url); console.log('opened', url);
on stop { on stop {
console.log('closing', addr.url); console.log('closing', url);
ws.close(); ws.close();
} }
ws.onclose = () => facet.turn(() => { stop facet; }); ws.onclose = () => facet.turn(() => { stop facet; });
@ -87,60 +85,50 @@ export function boot(ds = Dataspace.local, debug: boolean = false, WebSocketCons
setup: r => ws.onmessage = e => r.accept(new Uint8Array(e.data)), setup: r => ws.onmessage = e => r.accept(new Uint8Array(e.data)),
initialOid: 0, initialOid: 0,
}); });
console.log('succeed', addr.url); console.log('succeed', url);
at ds { at ds {
assert G.TransportConnection<Ref>({ assert Answer(G.ConnectTransport(fromJS<Ref>(T.WebSocket(url))), resultOk(G.ConnectedTransport({
"addr": fromJS(addr), "addr": fromJS<Ref>(T.WebSocket(url)),
"control": create controlEntity, "control": create controlEntity,
"resolved": G.Resolved.accepted(relay.peer!), "responderSession": relay.peer!,
}); })));
} }
} }
function fail(detail: Assertion) { function fail(detail: Assertion) {
if (final) return; if (final) return;
final = true; final = true;
console.log('fail', addr.url, detail); console.log('fail', url, detail);
at ds { at ds {
assert G.TransportConnection<Ref>({ assert Answer(G.ConnectTransport(fromJS<Ref>(T.WebSocket(url))), resultError(detail));
"addr": fromJS(addr),
"control": create controlEntity,
"resolved": G.Resolved.Rejected(G.Rejected(detail)),
});
} }
setTimeout(() => facet.turn(() => { stop facet; }), 10000); setTimeout(() => facet.turn(() => { stop facet; }), 10000);
} }
try { try {
const ws = new (WebSocketConstructor ?? WebSocket)(addr.url); const ws = new (WebSocketConstructor ?? WebSocket)(url);
ws.binaryType = 'arraybuffer'; ws.binaryType = 'arraybuffer';
ws.onopen = () => facet.turn(() => succeed(ws)); ws.onopen = () => facet.turn(() => succeed(ws));
ws.onclose = () => facet.turn(() => fail(Symbol.for('closed'))); ws.onclose = () => facet.turn(() => fail(Symbol.for('closed')));
ws.onerror = () => facet.turn(() => fail(Symbol.for('websocket-error-event'))); ws.onerror = () => facet.turn(() => fail(Symbol.for('websocket-error-event')));
} catch (e) { } catch (e) {
console.error('Failed opening websocket', addr.url, e); console.error('Failed opening websocket', url, e);
fail(Symbol.for('websocket-exception')); fail(Symbol.for('websocket-exception'));
} }
} }
spawn named 'pathResolver' { spawn named 'pathResolver' {
at ds { at ds {
during Observe({ "pattern": :pattern G.ResolvePath({ during Question(G.ResolvePath($route0)) => {
"route": \$routePatValue, const route = G.toRoute(route0);
"addr": \_,
"control": \_,
"resolved": \_,
}) }) => {
const route = Q.drop_lit(routePatValue, G.toRoute);
if (!route) return; if (!route) return;
field candidates: IdentitySet<TransportState> = new IdentitySet(); field candidates: IdentitySet<TransportState> = new IdentitySet();
route.transports.forEach(t => { route.transports.forEach(addr => {
const addr = T.toWebSocket(t); console.log('tracking', addr);
if (!addr) return; assert Question(G.ConnectTransport(addr));
console.log('tracking', addr.url); during Answer(G.ConnectTransport(addr), resultOk(G.ConnectedTransport({
during G.TransportConnection({
"addr": addr, "addr": addr,
"control": $control: Ref, "control": $control: Ref,
"resolved": G.Resolved.accepted($peer: Ref), "responderSession": $peer: Ref,
}) => { }))) => {
const entry = { addr, control, peer }; const entry = { addr, control, peer };
candidates.value.add(entry); candidates.value.add(entry);
candidates.changed(); candidates.changed();
@ -161,39 +149,34 @@ export function boot(ds = Dataspace.local, debug: boolean = false, WebSocketCons
} }
rootPeer.value = best.value?.peer ?? null; rootPeer.value = best.value?.peer ?? null;
} }
resolve(() => rootPeer.value, route.pathSteps, (r) => { resolve(() => ({ ref: rootPeer.value, error: false }), route.pathSteps, (r) => {
console.log('leaf', best.value?.addr?.url); console.log('leaf', best.value?.addr);
assert G.ResolvePath<Ref>({ assert Answer(G.ResolvePath(route), resultOk(G.ResolvedPath({
"route": route, "addr": best.value!.addr,
"addr": fromJS(best.value!.addr),
"control": best.value!.control, "control": best.value!.control,
"resolved": r()! "responderSession": r().ref!,
}) when (r()); }))) when (r().ref);
assert Answer(G.ResolvePath(route), resultError(r().error)) when (!r().ref);
}); });
} }
} }
} }
function resolve( function resolve(
e: () => Ref | null, e: () => { ref: Ref | null, error: AnyValue }, // gross
steps: G.PathStep[], steps: G.PathStep[],
k: (r: () => G.Resolved | null) => void, k: (r: () => { ref: Ref | null, error: AnyValue }) => void,
) { ) {
if (steps.length === 0) { if (steps.length === 0) {
k(() => { k(e);
const peer = e();
return peer === null ? null : G.Resolved.accepted(peer);
});
} else { } else {
const [step, ...more] = steps; const [step, ...more] = steps;
at ds { at ds {
during G.ResolvedPathStep({ const q = () => G.ResolvePathStep({ origin: (e().ref!), pathStep: step });
"origin": (e()!), assert Question(q()) when (e().ref);
"pathStep": step, during Answer(((q)()) /* omg */, $a: Result) when (e().ref) => {
"resolved": $resolved: G.Resolved, switch (a._variant) {
}) when (e()) => { case "ok":
switch (resolved._variant) {
case "accepted":
// Include a call to our e() in the e we pass in to the recursive // Include a call to our e() in the e we pass in to the recursive
// call to resolve(). e() returning non-null is a precondition for // call to resolve(). e() returning non-null is a precondition for
// the call; if that precondition ever changes, we want to NOT // the call; if that precondition ever changes, we want to NOT
@ -208,10 +191,18 @@ export function boot(ds = Dataspace.local, debug: boolean = false, WebSocketCons
// best.value!, which is now null, boom. With the call to e(), we // best.value!, which is now null, boom. With the call to e(), we
// short circuit and so the assertion becomes null at that point. // short circuit and so the assertion becomes null at that point.
// //
resolve(() => e() && resolved.responderSession, more, k); const nextRef = G.toResolvedPathStep(a.value);
const nextResult = (nextRef === void 0)
? { ref: null, error: 'bad ResolvedPathStep' }
: { ref: nextRef, error: false };
const nextE = () => {
const result = e();
return result.ref ? nextResult : result;
};
resolve(nextE, more, k);
break; break;
case "Rejected": case "error":
k(() => resolved); k(() => ({ ref: null, error: a.error }));
break; break;
} }
} }
@ -221,22 +212,13 @@ export function boot(ds = Dataspace.local, debug: boolean = false, WebSocketCons
spawn named 'noiseStep' { spawn named 'noiseStep' {
at ds { at ds {
during Observe({ "pattern": :pattern G.ResolvedPathStep({ during Question($q(G.ResolvePathStep({
"origin": \$originPatValue, origin: $origin: Ref,
"pathStep": G.PathStep({ pathStep: G.PathStep({
"stepType": N.$noise, stepType: N.$noise,
"detail": \$detailPatValue, detail: $spec: N.NoisePathStepDetail,
}), }),
"resolved": \_, }))) => {
}) }) => {
const origin0 = Q.drop_lit(originPatValue);
if (!origin0 || !isEmbedded(origin0)) return;
const origin = origin0;
const spec0 = Q.drop_lit(detailPatValue, N.toNoisePathStepDetail);
if (!spec0) return;
const spec = spec0;
const algorithms = SaltyCrypto.Noise_25519_ChaChaPoly_BLAKE2s; const algorithms = SaltyCrypto.Noise_25519_ChaChaPoly_BLAKE2s;
const protocol = const protocol =
spec.protocol._variant === "present" ? spec.protocol.protocol : spec.protocol._variant === "present" ? spec.protocol.protocol :
@ -270,14 +252,7 @@ export function boot(ds = Dataspace.local, debug: boolean = false, WebSocketCons
case "Rejected": case "Rejected":
stop { stop {
at ds { at ds {
assert G.ResolvedPathStep<Ref>({ assert Answer(q, resultError(response.value.detail));
"origin": origin,
"pathStep": G.PathStep({
"stepType": N.$noise,
"detail": fromJS(N.NoisePathStepDetail(spec)),
}),
"resolved": response,
});
} }
} }
} }
@ -332,14 +307,7 @@ export function boot(ds = Dataspace.local, debug: boolean = false, WebSocketCons
setup: r => relay = r, setup: r => relay = r,
initialOid: 0, initialOid: 0,
}).peer!; }).peer!;
assert G.ResolvedPathStep<Ref>({ assert Answer(q, resultOk(peer));
"origin": origin,
"pathStep": G.PathStep({
"stepType": N.$noise,
"detail": fromJS(spec),
}),
"resolved": G.Resolved.accepted(peer),
});
} }
function handlePacket(body: Assertion) { function handlePacket(body: Assertion) {
@ -368,21 +336,13 @@ export function boot(ds = Dataspace.local, debug: boolean = false, WebSocketCons
spawn named 'sturdyRefStep' { spawn named 'sturdyRefStep' {
at ds { at ds {
during Observe({ "pattern": :pattern G.ResolvedPathStep({ during Question($q(G.ResolvePathStep({
"origin": \$originPatValue, origin: $origin: Ref,
"pathStep": G.PathStep({ pathStep: G.PathStep({
"stepType": S.$ref, stepType: S.$ref,
"detail": \$detailPatValue, detail: $parameters: S.SturdyPathStepDetail,
}), }),
"resolved": \_, }))) => {
}) }) => {
const origin0 = Q.drop_lit(originPatValue);
if (!origin0 || !isEmbedded(origin0)) return;
const origin = origin0;
const parameters = Q.drop_lit(detailPatValue, S.toSturdyPathStepDetail);
if (!parameters) return;
at origin { at origin {
assert G.Resolve({ assert G.Resolve({
"step": G.Step({ "step": G.Step({
@ -392,15 +352,13 @@ export function boot(ds = Dataspace.local, debug: boolean = false, WebSocketCons
"observer": create assertionFacetObserver(e => { "observer": create assertionFacetObserver(e => {
const response = G.toResolved(e); const response = G.toResolved(e);
if (!response) return; if (!response) return;
at ds { switch (response._variant) {
assert G.ResolvedPathStep<Ref>({ case "accepted":
"origin": origin, assert Answer(q, resultOk(response.responderSession));
"pathStep": G.PathStep({ break;
"stepType": S.$ref, case "Rejected":
"detail": fromJS(parameters), assert Answer(q, resultError(response.value.detail));
}), break;
"resolved": response,
});
} }
}), }),
}); });
@ -465,7 +423,7 @@ export function decodeStandardRoute(s: string): G.Route | null {
} }
export function contactRemote( export function contactRemote(
route: G.Route<Ref> | Record<AnyValue, Array<AnyValue>, Ref>, route: G.Route | Record<AnyValue, Array<AnyValue>, Ref> ,
connectedFacet: ( connectedFacet: (
remoteObject: Ref, remoteObject: Ref,
controlObject: Ref, controlObject: Ref,
@ -473,14 +431,14 @@ export function contactRemote(
) => void, ) => void,
ds = Dataspace.local, ds = Dataspace.local,
) { ) {
const routeValue = 'pathSteps' in route ? G.fromRoute(G.Route(route)) : route; const routeValue = 'pathSteps' in route ? route : G.asRoute(route);
at ds { at ds {
during G.ResolvePath({ assert Question(G.ResolvePath(routeValue));
"route": routeValue, during Answer(G.ResolvePath(routeValue), resultOk(G.ResolvedPath({
"addr": $addr, "addr": $addr,
"control": $control: Ref, "control": $control: Ref,
"resolved": G.Resolved.accepted($resolved: Ref), "responderSession": $resolved: Ref,
}) => { }))) => {
connectedFacet(resolved, control, addr); connectedFacet(resolved, control, addr);
} }
} }