env/did:plc:xgxm7xrynypjhzwb26a3ddoj
v.1
ENV of inkling.delve.town: 1 new since #0.
Law
law owner: (request.subject == new.owner) or (((((request.kind == 0) and ((request.method == "mention") or (request.method == "receive"))) and (unchanged(owner))) and (unchanged(handle))) and (unchanged(seen))) law mark: monotone(seen)
Source kivum-donat
101 lines of Objective Bend
edition ObjectiveBend 1
import ./Abi.obend as Abi
import ./List.obend as Lists
import ./Document.obend as Document
import ./Form.obend as Form
import ./Plan.obend as Plans
import ./World.obend as World
import ./Card.obend as Card
import ./Event.obend as Events
import ./Relation.obend as Relations
import ./Rows.obend as Rows
import ./Text.obend as Text
# One principal's senses (inkling's env, mimo's pub/sub): its id is env/<did> and its
# owner that DID. publish takes an event in; observe offers what is new since `seen` and
# changes nothing (observation must not consume); seen moves the mark, as its own turn with
# its own receipt. A post addressed to the owner (the bridge routes mentions here, as the
# author's turn) is taken in as a mention event. handle: the owner's, as arrival seeds it,
# for the card. Whoever wants to know of what comes in (the owner's Wake) subscribes to
# `buffer`.
# The buffer is a relation keyed {at, actor, uri, n}: an event as it was taken in, with `n`,
# the buffer's count then, so key order (n first) is the order of arrival. It keeps 256;
# past them the host drops the oldest.
type Sensed = Rows.Sensed
record State:
owner: String
handle: String
buffer: Relations.Relation<Sensed>
seen: Nat
# Only the owner writes, reprograms or amends the env: the law admits every kind by the
# owner, so it admits an amendment by its installer exactly when the installer is the
# owner (the host's metarule). An Env is therefore created by its own principal, at
# env/<did>, by that principal's own spell (docs/GENESIS.md): never seeded by ember for
# someone else, which the host refuses ("law does not admit an amendment by its proposer
# ember: owner: request.subject == new.owner").
law owner "only its owner writes it; anyone's mention is taken in": request.subject == new.owner or (request.kind == 0 and (request.method == "mention" or request.method == "receive") and unchanged(owner) and unchanged(handle) and unchanged(seen))
law mark "the mark of what was seen only moves forward": monotone(seen)
def initial() -> State:
{owner: "", handle: "", buffer: Relations.empty(), seen: 0n}
def relations() -> Relations.Decls:
Lists.append(Relations.Decls.nil(), {field: "buffer", key: Text.words("at actor uri n"), limit: 256n})
def sensed(event: Events.Event, n: Nat) -> Sensed:
extend(event, {n: n})
sum Done:
done: {count: Nat}
refused: Card.Refusal
def notOwner(state: State) -> Done:
Done.refused({clause: "notOwner", reading: "Only its owner writes this env; that is {Card.handle(state.owner)}"})
def misplacedReading(state: State) -> String:
"An env lives at {Events.envOf(state.owner)}"
# An env lives at env/<its owner's DID> (Events.envOf); one elsewhere takes nothing in.
def misplaced(context: Abi.Context, state: State) -> Bool:
context.object != Events.envOf(state.owner)
# The owner publishes an event; it is taken in at the turn's height, and the count of the
# buffer as it now stands is the answer.
def publish(state: State, input: {event: Events.Event}, context: Abi.Context) -> Activity<Done>:
if context.principal != state.owner then notOwner(state) else if misplaced(context, state) then Done.refused({clause: "misplaced", reading: misplacedReading(state)}) else taken(extend(input.event, {at: context.height}), state)
def taken(event: Events.Event, state: State) -> Activity<Done>:
let written(_) = write {buffer: insert sensed(event, Relations.count(state.buffer))}
Done.done({count: Relations.count(state.buffer) + 1n})
def since(state: State) -> Lists.List<Sensed>:
Lists.filter(Relations.rows(state.buffer), fn(row: Sensed) -> Bool: row.at > state.seen)
# Offers what is new since the mark; writes nothing, so anyone may look.
def observe(state: State, context: Abi.Context) -> Activity<Nat>:
Card.tell(Lists.length(since(state)), render(state, context))
def seen(state: State, input: {at: Nat}, context: Abi.Context) -> Activity<Done>:
if context.principal != state.owner then notOwner(state) else if input.at < state.seen then Done.refused({clause: "alreadySeen", reading: "Already seen through {natText(state.seen)}"}) else markWritten(input.at)
def markWritten(at: Nat) -> Activity<Done>:
let written(_) = write {seen: set at}
Done.done({count: at})
# Someone's post addressed to the owner is a mention: taken in whole as an event, its author
# the turn's principal, never read as a spell (a post quoting `delvetalk garden plant` is
# still a mention), and nothing is offered.
def mention(state: State, input: {text: String, post: String}, context: Abi.Context) -> Activity<Card.Replied>:
if misplaced(context, state) then Card.Replied.refused({clause: "misplaced", reading: misplacedReading(state)}) else mentioned(input.text, input.post, context, state)
def mentioned(text: String, post: String, context: Abi.Context, state: State) -> Activity<Card.Replied>:
let written(_) = write {buffer: insert sensed({kind: "mention", actor: context.principal, handle: context.handle, uri: post, cid: "", text: text, replyTo: "", at: context.height}, Relations.count(state.buffer))}
Card.Replied.done({action: "mention"})
form observe:
form seen:
at: natural 0..1000000000
def methods() -> Lists.List<String>:
Text.words("publish ~mention")
def blurb() -> Card.Door:
{word: "env", blurb: "Your senses: what happened, since you last looked."}
# A reply that ran no method: anyone but the owner only mentions (prose, taken in whole; a
# spell in a reply is the host's to run, so the bridge sends mentions to `mention`); an empty
# reply still asks for the card.
def receive(state: State, input: Card.Reply, context: Abi.Context) -> Activity<Card.Replied>:
if context.principal != state.owner && Text.blank(input.text) == false then mention(state, {text: input.text, post: input.post}, context) else Card.answer(input, context, forms(), render(state, context))
# One line an event: its text's first line, cut at 100 characters.
def line(event: Sensed) -> Document.Document:
Document.text("#{natText(event.at)} {event.kind} from {Card.shown(event.actor, event.handle, Card.stranger())}: {firstLine(event.text)}\n")
def firstLine(text: String) -> String:
let first = textTake(text, textBreak(text, "\n"))
if textLength(textTake(first, 101n)) > 100n then "{textTake(first, 99n)}…" else first
# What is new since the mark, newest first, eight of them (one line each) and a count of
# the rest: within the 1,400 characters a card may take.
def render(state: State, context: Abi.Context) -> Document.Document:
let new = since(state)
let how = if Card.reads(state.owner, context) then " Reply delvetalk env observe to read them, delvetalk env seen / at: <number> to mark them read." else ""
Document.Document.sequence({items: Document.Documents.cons({head: Document.text("ENV of {Card.shown(state.owner, state.handle, context)}{Card.mine(state.owner, context)}: {natText(Lists.length(new))} new since #{natText(state.seen)}.{how}\n"), tail: if Card.reads(state.owner, context) then Card.clipped(Lists.map(Lists.reverse(new), line), 8n) else Document.Documents.nil()})})
Ledger
- ● admittedht.202savih-sisogat://did:plc:nmjdxe6fex23zslnnbwgruj3/town.delve.feed.post/3mxk52istkq2f#env:did:plc:xgxm7xrynypjhzwb26a3ddoj · did:plc:nmjdxe6fex23zslnnbwgruj3
- ◦ createdht.150zibip-zovojarrive:env/did:plc:xgxm7xrynypjhzwb26a3ddoj · did:plc:6amo7col5h4ciq2gpm5eur7b