@quo-systems/quo 0.2.10 → 0.2.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/SPEC.md +141 -22
- package/dist/being/being.js +7 -1
- package/dist/being/digest.js +15 -1
- package/dist/being/index.d.ts +2 -2
- package/dist/being/index.js +2 -2
- package/dist/being/silence.d.ts +2 -0
- package/dist/being/silence.js +12 -0
- package/dist/being/types.d.ts +2 -0
- package/dist/being/types.js +25 -0
- package/dist/conformance/assert.js +40 -6
- package/dist/conformance/beings.d.ts +48 -5
- package/dist/conformance/beings.js +39 -8
- package/dist/conformance/estate.js +109 -18
- package/dist/conformance/index.d.ts +5 -2
- package/dist/conformance/index.js +164 -6
- package/dist/harbor/core.d.ts +3 -2
- package/dist/harbor/core.js +86 -25
- package/dist/harbor/dial.js +28 -13
- package/dist/harbor/index.d.ts +1 -0
- package/dist/harbor/index.js +3 -0
- package/dist/harbor/memory.d.ts +3 -3
- package/dist/harbor/memory.js +6 -12
- package/dist/harbor/reach.js +10 -10
- package/dist/ward/allowance.js +7 -2
- package/dist/ward/arithmetic.d.ts +1 -0
- package/dist/ward/arithmetic.js +11 -3
- package/dist/ward/door.d.ts +2 -2
- package/dist/ward/door.js +14 -5
- package/dist/ward/ground.d.ts +4 -1
- package/dist/ward/ground.js +38 -1
- package/dist/ward/heirs.d.ts +1 -2
- package/dist/ward/heirs.js +13 -14
- package/dist/ward/index.d.ts +1 -0
- package/dist/ward/index.js +3 -0
- package/dist/ward/owner.d.ts +4 -4
- package/dist/ward/owner.js +36 -19
- package/dist/ward/partition.d.ts +3 -0
- package/dist/ward/partition.js +109 -4
- package/dist/ward/seal.d.ts +1 -0
- package/dist/ward/seal.js +38 -13
- package/dist/ward/stance.d.ts +3 -2
- package/dist/ward/stance.js +48 -20
- package/dist/ward/ward.d.ts +1 -1
- package/dist/ward/ward.js +40 -19
- package/package.json +1 -1
- package/src/being/being.ts +7 -1
- package/src/being/digest.ts +15 -2
- package/src/being/index.ts +2 -2
- package/src/being/silence.ts +14 -0
- package/src/being/types.ts +29 -1
- package/src/conformance/assert.ts +37 -4
- package/src/conformance/beings.ts +41 -10
- package/src/conformance/estate.ts +106 -17
- package/src/conformance/index.ts +187 -12
- package/src/harbor/core.ts +98 -25
- package/src/harbor/dial.ts +41 -14
- package/src/harbor/index.ts +3 -0
- package/src/harbor/memory.ts +8 -14
- package/src/harbor/reach.ts +10 -11
- package/src/ward/allowance.ts +7 -2
- package/src/ward/arithmetic.ts +14 -5
- package/src/ward/door.ts +16 -7
- package/src/ward/ground.ts +43 -1
- package/src/ward/heirs.ts +13 -14
- package/src/ward/index.ts +3 -0
- package/src/ward/owner.ts +45 -26
- package/src/ward/partition.ts +109 -5
- package/src/ward/seal.ts +39 -12
- package/src/ward/stance.ts +53 -23
- package/src/ward/ward.ts +46 -25
- package/vectors/framing.json +16 -10
- package/vectors/wire.json +4 -4
package/SPEC.md
CHANGED
|
@@ -93,7 +93,13 @@ ground
|
|
|
93
93
|
|
|
94
94
|
Nothing else is ever passed. A harbor that needs a seventh thing has found a
|
|
95
95
|
gap in Quo or a leak into the ward. The shape is `Ground` in
|
|
96
|
-
`src/ward/ground.ts
|
|
96
|
+
`src/ward/ground.ts`, and beside it are the three pieces every harbor in this
|
|
97
|
+
kit builds one out of: `maker`, the code half, which finds a class by own key
|
|
98
|
+
in the first registry that holds it and remembers the object by the cells;
|
|
99
|
+
`entropy`, the one line every terrain has; and `learnPk`, the empty ask, since
|
|
100
|
+
a harbor learns its ward's pk the way anyone learns anything and has no second
|
|
101
|
+
derivation to keep in step. They are convenience and never contract: a kit
|
|
102
|
+
writes its own harbor, and may write these again.
|
|
97
103
|
|
|
98
104
|
Returned:
|
|
99
105
|
|
|
@@ -104,7 +110,9 @@ Returned:
|
|
|
104
110
|
harbor learns from an arrival, never a reason: a pk that only ever brings
|
|
105
111
|
strangers' bytes is the harbor's to rate or refuse, and what a bound key
|
|
106
112
|
hears is sealed to its own lid.
|
|
107
|
-
- **The ask.** One call: method and args in,
|
|
113
|
+
- **The ask.** One call: method and args in, a value or silence out. A value,
|
|
114
|
+
not an object: what a being answers is held to the value rule and nothing
|
|
115
|
+
narrower, so a string, a number and an array are all answers.
|
|
108
116
|
In-process and unsealed. This is the one unsealed ask in Quo, and the only
|
|
109
117
|
way a ward is piloted. Judgment, catching, and the return table are not
|
|
110
118
|
skipped: a throw inside is silence, and an unreached shape is silence.
|
|
@@ -327,7 +335,8 @@ D7 signature fails under an admitted key, or under the key hel
|
|
|
327
335
|
refusals to a bound key: a word, nothing written, heard true
|
|
328
336
|
D8 she is not there `removed`: the occupant record is gone, and this is the key it was
|
|
329
337
|
bound to when it went. `absent`: the being did not come back this run.
|
|
330
|
-
D9 a knock announces nothing `unannounced`: the heir is fresh and next is null
|
|
338
|
+
D9 a knock announces nothing `unannounced`: the heir is fresh and next is null, or is the heir
|
|
339
|
+
itself, which is no key of her own. it binds nothing.
|
|
331
340
|
D10 the number is refused `repeated`: already honoured, or at or below the span.
|
|
332
341
|
choices: the number spent, the keys rotated, heard true
|
|
333
342
|
D11 she threw `threw`
|
|
@@ -413,7 +422,10 @@ None is an object, and none carries anything but its name. In this kit
|
|
|
413
422
|
silence is the symbol `quo.silence` and a word is a frozen object under the
|
|
414
423
|
symbol key `quo.word` holding its name, both from `src/being/silence.ts`;
|
|
415
424
|
`isWord` and `wordOf` read them, and `isUnreached` is the one word a being
|
|
416
|
-
asks about most.
|
|
425
|
+
asks about most. `told` names a word and leaves everything else exactly as it
|
|
426
|
+
came, so asking which word arrived is one comparison rather than two joined by
|
|
427
|
+
an and: `told(out) === 'late'`. A kit in another language spells it however
|
|
428
|
+
that language spells one value standing for either, and owes nothing here.
|
|
417
429
|
|
|
418
430
|
### Blueprint, schema, digest
|
|
419
431
|
|
|
@@ -439,6 +451,16 @@ holding what is not a value is dropped before hashing and an array slot
|
|
|
439
451
|
holding one is null, which is what crossing an edge does to them anyway. The
|
|
440
452
|
reference is `src/being/digest.ts`.
|
|
441
453
|
|
|
454
|
+
Two places in RFC 8785 are where languages part, and a kit is held to both by
|
|
455
|
+
`vectors/framing.json`. Keys sort by **UTF-16 code unit**, not by code point
|
|
456
|
+
and not by byte, so an astral character sorts before one in the surrogate
|
|
457
|
+
range: `U+1F600` comes before `U+FB33`, which is the reverse of what a
|
|
458
|
+
language sorting code points gives. Numbers are written as ECMAScript writes
|
|
459
|
+
them, `1e+21` and `1e-7` and `0.3333333333333333`, with a negative zero
|
|
460
|
+
written `0`. A kit that reproduces every ASCII vector and neither of these
|
|
461
|
+
hashes one blueprint to two digests, and every standing between the two wards
|
|
462
|
+
refreshes on every ask.
|
|
463
|
+
|
|
442
464
|
Capability and state are two axes, never mixed.
|
|
443
465
|
|
|
444
466
|
```
|
|
@@ -498,7 +520,13 @@ cells
|
|
|
498
520
|
```
|
|
499
521
|
|
|
500
522
|
- `digest` is the hash of the blueprint she last fetched by the empty ask.
|
|
501
|
-
|
|
523
|
+
It is over what came back, whatever that was.
|
|
524
|
+
- `blueprint` is that blueprint, and only if it is one. A far describe is
|
|
525
|
+
somebody else's code and may answer any value at all; the ward reads it as
|
|
526
|
+
a blueprint before writing it as one, a list of asks with a name and an
|
|
527
|
+
input each, and writes `null` when it is not. A side walks `asks` by name,
|
|
528
|
+
and a side that broke on a far ward's answer would be one kit made wrong
|
|
529
|
+
by another. The answer itself still goes to whoever asked, unread.
|
|
502
530
|
- `seen` is the digest her ward last saw arrive with an answer. A silent
|
|
503
531
|
refresh leaves it untouched.
|
|
504
532
|
- `notes` is hers. Quo never reads it. Tier, expiry, kinship between an
|
|
@@ -585,8 +613,22 @@ An invitation to her is `{ ward }` alone. One with a secret and no heir is
|
|
|
585
613
|
not an invitation. She may be taken as a standing on that invitation, and
|
|
586
614
|
such a standing has no heir and never rotates.
|
|
587
615
|
|
|
616
|
+
The signature is checked here for no property, and the check is kept anyway.
|
|
617
|
+
Whoever asks chose that key a moment ago, the box is already authenticated to
|
|
618
|
+
the ward's padlock, and the door binds nothing, so nothing is proven by it and
|
|
619
|
+
nothing would be lost by dropping it. It stays because the payload has one
|
|
620
|
+
shape and the door has one path: `by`, `next` and `seq` are required of her
|
|
621
|
+
ask as of every other, and honoured by nobody. A kit author who goes looking
|
|
622
|
+
for the property behind this signature will not find one, and is not missing
|
|
623
|
+
anything.
|
|
624
|
+
|
|
588
625
|
She is reached without a heir, so the door keeps nothing for whoever asked:
|
|
589
|
-
no key it vouched for, and no count. The
|
|
626
|
+
no key it vouched for, and no count. The bit beside her reply is `heard`
|
|
627
|
+
false, as it is for every stranger: no key this door holds spoke. She is the
|
|
628
|
+
one place a stranger is answered by design, so she is the one place the
|
|
629
|
+
harbor's rating must still see a stranger arrive. A harbor that saves before
|
|
630
|
+
its reply goes out follows the ward writing, never the bit, since a public
|
|
631
|
+
being writes in her cells like anyone. The signature is still checked, under
|
|
590
632
|
whatever key signed. Once-only delivery does not reach her: the same sealed
|
|
591
633
|
bytes presented twice are delivered twice, and the number the payload carries
|
|
592
634
|
is required and honoured by nobody. A count per voice would be memory a
|
|
@@ -638,7 +680,17 @@ key does both: an Ed25519 pair to sign replies, an X25519 padlock every ask
|
|
|
638
680
|
is sealed to. Beings never own a padlock.
|
|
639
681
|
|
|
640
682
|
- **The ward key** comes from the seed. Its pk on the wire is the signing pk
|
|
641
|
-
then the padlock, 128 hex, and it routes.
|
|
683
|
+
then the padlock, 128 hex, and it routes. Two curves, and each secret is
|
|
684
|
+
HKDF-SHA-256 of the seed under its own label, `quo-ward-sign` and
|
|
685
|
+
`quo-ward-seal`, empty salt, 32 bytes out. Fed the seed straight the two
|
|
686
|
+
scalars would still differ, because Ed25519 hashes what it is given and
|
|
687
|
+
X25519 clamps it raw, but that is an accident of the two designs and no
|
|
688
|
+
separation: one secret would be doing two jobs with nothing said about it,
|
|
689
|
+
and a second kit would have to reproduce a construction nobody named. A
|
|
690
|
+
seed handed in as bytes of the key length is the seed; anything else, text
|
|
691
|
+
or bytes of another length, is SHA-256 first, so a thirty-two character
|
|
692
|
+
name is a name and not a key for being the right size.
|
|
693
|
+
`vectors/framing.json` pins it.
|
|
642
694
|
- **The heir** is the key the inviting ward mints at invite, for one id. It
|
|
643
695
|
keeps the heir's pk beside the id and gives the secret away. The
|
|
644
696
|
invitation IS the heir: ward pk, heir pk, heir secret. Rotation one: the
|
|
@@ -810,7 +862,11 @@ share no key learn nothing from each other.
|
|
|
810
862
|
|
|
811
863
|
Four algorithms, named once and never negotiated: Ed25519 signs, X25519
|
|
812
864
|
agrees, SHA-256 hashes, AES-256-GCM encrypts with key and nonce derived
|
|
813
|
-
together by HKDF-SHA-256 under an empty salt and the label `quo-seal`.
|
|
865
|
+
together by HKDF-SHA-256 under an empty salt and the label `quo-seal`. That
|
|
866
|
+
label is the message cipher's and no other: HKDF appears twice in this kit,
|
|
867
|
+
here from an agreement and again at the ward key from a seed, and the three
|
|
868
|
+
labels, `quo-seal`, `quo-ward-sign`, `quo-ward-seal`, are three so that two
|
|
869
|
+
derivations never answer to one name. The
|
|
814
870
|
ephemeral pk is the additional authenticated data of every box. A small-order
|
|
815
871
|
public key verifies nothing; an all-zero agreement is refused. All four are
|
|
816
872
|
in WebCrypto, which is why the ward names no package -- on any terrain that
|
|
@@ -943,8 +999,10 @@ partition
|
|
|
943
999
|
occupants id -> heir pk
|
|
944
1000
|
knocks <ward>:<heir> | public:<ward> -> { current, next, spoke, sent, seq } before take
|
|
945
1001
|
spoke: bytes came back once. sent: bytes went out once, answered or not
|
|
1002
|
+
bounded, oldest out, and one that answered outlives one that never did
|
|
946
1003
|
answered <ward>:<heir> | public:<ward> -> true knocked and answered
|
|
947
|
-
minted the last eight pks her side minted
|
|
1004
|
+
minted the last eight pks her side minted. nothing in the ward reads it: it is there to
|
|
1005
|
+
be looked at, and a kit that keeps the list empty holds the same ward
|
|
948
1006
|
heirs heir pk -> { being, id, current, announced, fresh, mark, spent } the door's view of every occupant
|
|
949
1007
|
gone heir pk -> { current, announced } the keys held when an id was removed, bounded, so their
|
|
950
1008
|
holder hears `removed` and nobody else hears a thing
|
|
@@ -956,6 +1014,12 @@ alone: an invitation carries the heir to whoever it is for, and anyone
|
|
|
956
1014
|
holding one could quote that heir back inside an invitation naming a ward of
|
|
957
1015
|
their own.
|
|
958
1016
|
|
|
1017
|
+
Only take ends a knock record, so the list is bounded like `gone`: past the
|
|
1018
|
+
count the oldest go, and one that was answered outlives one that never was,
|
|
1019
|
+
since an answered knock is a relation she may still take. A being who knocks
|
|
1020
|
+
without ever taking keeps a fixed number of keys behind her, not one per
|
|
1021
|
+
invitation she ever met.
|
|
1022
|
+
|
|
959
1023
|
The version is one value and it does not move until 1.0.0. There is nobody
|
|
960
1024
|
holding a partition of another shape to tell apart: if the shape changes, a
|
|
961
1025
|
partition is thrown away, not migrated and not counted. A ward opening a
|
|
@@ -963,6 +1027,17 @@ version it cannot read throws at birth and does not boot. Birth is where a
|
|
|
963
1027
|
ward is allowed to be loud; silence is the door's word, for asks that were
|
|
964
1028
|
made, and no ask has been made yet.
|
|
965
1029
|
|
|
1030
|
+
The version says which shape, and the ward reads the shape as well, once, at
|
|
1031
|
+
birth: every field above that the ward, the door or the heirs later act on
|
|
1032
|
+
without looking again. A partition this ward wrote is that shape by
|
|
1033
|
+
construction. One adopted from elsewhere was written by a hand, another kit,
|
|
1034
|
+
or a file that was cut short, and a `spent` that is not a list or a `mark`
|
|
1035
|
+
that is not a number is a door that honours every number, or one that
|
|
1036
|
+
rejects where it promised a word. A ward that meets one throws at birth with
|
|
1037
|
+
the path that failed, which names what is wrong to whoever is holding the
|
|
1038
|
+
file. A being's own cells are read no further than the two records the ward
|
|
1039
|
+
keeps in them: what she puts in her own is hers, of any shape JSON carries.
|
|
1040
|
+
|
|
966
1041
|
What a ward can read is a list, `READS`, and today it has one member. The
|
|
967
1042
|
list and the step that carries an older partition forward exist before
|
|
968
1043
|
1.0.0 because after it there is nowhere to put them. A partition holds every
|
|
@@ -1054,7 +1129,11 @@ anything else -> { error: 'unknown ask' }
|
|
|
1054
1129
|
every relation she had, reachable by nobody at the bare pk and told by
|
|
1055
1130
|
nobody she was replaced. Marking the one already public again is
|
|
1056
1131
|
answered, and changes nothing. A public being absent this run is
|
|
1057
|
-
reachable by nobody already, so the mark may move to another.
|
|
1132
|
+
reachable by nobody already, so the mark may move to another. A null key
|
|
1133
|
+
takes the mark off: the being stays booted with every relation she holds,
|
|
1134
|
+
and the ward answers arrivals for no heir with silence as one that never
|
|
1135
|
+
had a public being does. Retiring her is not destroying her, and the mark
|
|
1136
|
+
is free for another.
|
|
1058
1137
|
- Invite on the ward's own pk mints an owner, and only the root may ask it:
|
|
1059
1138
|
from the door it is answered as an invite on nobody. The ward has
|
|
1060
1139
|
occupants, its owners, and no standings: a knock for the ward itself is a
|
|
@@ -1142,8 +1221,15 @@ Not stressed with:
|
|
|
1142
1221
|
|
|
1143
1222
|
A harbor is what a device already has, offered to Quo: processes, storage, a
|
|
1144
1223
|
network, entropy, a clock. It fits into a device carved up by its owner,
|
|
1145
|
-
users, containers, mounts, profiles, and asks for none of them. It
|
|
1146
|
-
|
|
1224
|
+
users, containers, mounts, profiles, and asks for none of them. It judges
|
|
1225
|
+
nothing, holds no id, chooses no class, and is nobody outside its device.
|
|
1226
|
+
|
|
1227
|
+
It makes every being of every ward it serves, because `instantiate` is in
|
|
1228
|
+
the ground and the ward calls it, and it keeps what it made. That is not
|
|
1229
|
+
knowing a being. It has her object and knows nothing about her: not what she
|
|
1230
|
+
answers, not who her occupants are, not which of her cells is a secret. It
|
|
1231
|
+
never reads a partition to decide anything, and it never hands one out
|
|
1232
|
+
except as the values a store keeps.
|
|
1147
1233
|
|
|
1148
1234
|
The harbor:
|
|
1149
1235
|
|
|
@@ -1163,6 +1249,16 @@ The harbor:
|
|
|
1163
1249
|
- Passes every ward the ground, once, at birth. Receives a door and an ask.
|
|
1164
1250
|
Hands the ask to exactly one holder, by the device's own rules. That
|
|
1165
1251
|
holder is the ward's owner.
|
|
1252
|
+
- Hands its own device's code the beings it made for a ward, by key, and the
|
|
1253
|
+
keys it has. This is not a path around a door. The object is already in
|
|
1254
|
+
that process, made there a moment ago by the harbor itself, and reaching
|
|
1255
|
+
it is the device's own code calling its own object, the same reach a being
|
|
1256
|
+
has on one she booted herself. Nothing of it crosses an edge, so there is
|
|
1257
|
+
no door it could have passed instead. A key with no object is a being who
|
|
1258
|
+
is not here this run. What the device does not get is the partition: it
|
|
1259
|
+
holds every seed the ward has, and a side that wants to know which beings
|
|
1260
|
+
there are, or which one is public, asks the ward through the ask pointer,
|
|
1261
|
+
which is the only thing that answers for a ward anyway.
|
|
1166
1262
|
- Keeps the map of ward pk to door for its own wards, and ward pk to reach
|
|
1167
1263
|
for foreign ones. Learns its own wards' pks by asking them. Learns foreign
|
|
1168
1264
|
pks however it likes.
|
|
@@ -1189,8 +1285,8 @@ Not stressed with:
|
|
|
1189
1285
|
silence, and it never answers on a ward's behalf.
|
|
1190
1286
|
- Fanning out, broadcasting, or forwarding to a pk other than the one it was
|
|
1191
1287
|
given. One pk is one ward is one door.
|
|
1192
|
-
-
|
|
1193
|
-
|
|
1288
|
+
- Choosing a class, deciding an id, reading the keys inside a relation,
|
|
1289
|
+
minting an invitation, or acting on anything a partition contains.
|
|
1194
1290
|
- Judging anything. A ward judges its door. The harbor delivers to it.
|
|
1195
1291
|
- Speaking Quo. It is nobody's occupant and holds no standing anywhere. The
|
|
1196
1292
|
harbor is not a ward and is nobody's.
|
|
@@ -1207,7 +1303,9 @@ the harbor core then passes the same suite over memory stores.
|
|
|
1207
1303
|
|
|
1208
1304
|
What a harbor on a device keeps is a ward's three parts under one name: the
|
|
1209
1305
|
seed, the partition, and a **ward record**, which says where the class
|
|
1210
|
-
bodies come from and which being is the user's.
|
|
1306
|
+
bodies come from and which being is the user's. `user` is a key and nothing
|
|
1307
|
+
else: a label the device's own code hands back to get the being it made,
|
|
1308
|
+
never read to route a byte or judge an ask. That is the **store**,
|
|
1211
1309
|
`src/harbor/store.ts`: list, load, put, save the partition when the ward wrote,
|
|
1212
1310
|
save the record after a boot learned the pk, take a ward out as the first
|
|
1213
1311
|
move of a migration, and keep the directory's hints, a pk to a URL, across
|
|
@@ -1311,18 +1409,28 @@ with a wait that doubles from a second to thirty when the line drops, and
|
|
|
1311
1409
|
while it is dialed it is one of the harbor's fallbacks, where a pk nobody
|
|
1312
1410
|
here knows is sent. A device that slept, a phone in a pocket, comes back
|
|
1313
1411
|
with that wait frozen and, often, its socket closed under it by the system
|
|
1314
|
-
without a word, so the dialer takes one call from the device, `wake`:
|
|
1315
|
-
line
|
|
1316
|
-
|
|
1317
|
-
|
|
1412
|
+
without a word, so the dialer takes one call from the device, `wake`:
|
|
1413
|
+
whatever line it holds, open or still opening, is put down and a new one is
|
|
1414
|
+
dialed now, the frozen wait called off. Nothing can tell a line the system
|
|
1415
|
+
dropped from a live one, and asking would cost a round trip the four words
|
|
1416
|
+
do not have; an announce written into a dead line is taken into the send
|
|
1417
|
+
buffer and errors only when TCP gives up, minutes later, and for all of it
|
|
1418
|
+
every ask to a pk bound behind that line hears `late`. Dialing costs one
|
|
1419
|
+
handshake when the line was fine, and an ask made meanwhile waits on the new
|
|
1420
|
+
socket. A line put down this way closes when the system gets round to it,
|
|
1421
|
+
gives back what it took, and dials nothing: only the line the dialer still
|
|
1422
|
+
holds dials again. Fallbacks are a list and a pk is tried down it in
|
|
1318
1423
|
order: a harbor may dial more than one listener, and a rendezvous is a
|
|
1319
1424
|
listener and nothing more, so a ward is never reachable through one place
|
|
1320
1425
|
by anything but its own choice. A line that drops takes only its own place
|
|
1321
1426
|
out of the list. A ward is born on the core by minting a seed under a name,
|
|
1322
1427
|
leaves it by being dropped, the partition written first, and arrives by
|
|
1323
|
-
being adopted, same seed, same pk.
|
|
1324
|
-
|
|
1325
|
-
|
|
1428
|
+
being adopted, same seed, same pk. Adopting is the one path a partition
|
|
1429
|
+
written elsewhere takes, so it is where the ward's shape check is met: one
|
|
1430
|
+
that will not boot is not adopted, it goes back out of the store, and the
|
|
1431
|
+
only copy is the one the caller is still holding. What a terrain adds is a
|
|
1432
|
+
store of its own, a loader for the class bodies, and the lease its device
|
|
1433
|
+
offers; what it never touches is a ward.
|
|
1326
1434
|
|
|
1327
1435
|
## The being
|
|
1328
1436
|
|
|
@@ -1391,6 +1499,17 @@ The raw shape needs no import from the kit. The kit also offers a base class,
|
|
|
1391
1499
|
`for(occupant, asker)` decides whether this asker sees the ask, and so
|
|
1392
1500
|
whether this asker may call it: what she shows is what she can be asked,
|
|
1393
1501
|
one gate for describe and for dispatch.
|
|
1502
|
+
- Both statics are read off the class the object was made from, and a
|
|
1503
|
+
subclass that declares either **replaces** its parent's rather than adding
|
|
1504
|
+
to it. That is the rule and not an oversight: a being's blueprint is
|
|
1505
|
+
exactly what the class in front of you declares, in the order she chose,
|
|
1506
|
+
and a merge would hand her asks she may mean to drop and an order she did
|
|
1507
|
+
not write. A subclass that means to extend says so, in one spelling,
|
|
1508
|
+
`static override asks = { ...Parent.asks, mine: {} }`, and the same for
|
|
1509
|
+
cells. A parent that means to be subclassed at all annotates rather than
|
|
1510
|
+
infers, `static override cells: JsonObject = { ... }`, since this language
|
|
1511
|
+
holds a subclass's static side to its parent's and would otherwise refuse a
|
|
1512
|
+
subclass declaring fewer keys than the parent happened to write.
|
|
1394
1513
|
- `answer` is written for her. The empty ask is `describe(asker)`, which she
|
|
1395
1514
|
may override by hand. A named ask calls the method of that name with
|
|
1396
1515
|
`(args, asker)`. Anything not declared, hidden from this asker, or
|
package/dist/being/being.js
CHANGED
|
@@ -12,6 +12,12 @@ const method = (self, name) => {
|
|
|
12
12
|
}
|
|
13
13
|
return false;
|
|
14
14
|
};
|
|
15
|
+
// Both statics below are read off the class the object was made from, so a
|
|
16
|
+
// subclass declaring either replaces its parent's rather than adding to it.
|
|
17
|
+
// That is the rule: her blueprint is exactly what the class in front of you
|
|
18
|
+
// declares, in the order she chose, and merging down a chain would hand her
|
|
19
|
+
// asks she may mean to drop and an order she did not write. A subclass that
|
|
20
|
+
// means to extend says so, `static override asks = { ...Parent.asks, mine: {} }`.
|
|
15
21
|
export class Being {
|
|
16
22
|
// Her cells' defaults. Merged in at birth, only where a key is missing, so
|
|
17
23
|
// a restart keeps what she wrote.
|
|
@@ -89,7 +95,7 @@ export class Being {
|
|
|
89
95
|
// would answer with her stance, `toString` with a string, and neither is
|
|
90
96
|
// an ask she wrote. Only her own keys are asks, which is what describe
|
|
91
97
|
// shows. What she shows is what she can be asked.
|
|
92
|
-
const spec =
|
|
98
|
+
const spec = Object.hasOwn(C.asks, method) ? C.asks[method] : undefined;
|
|
93
99
|
if (!spec || (spec.for && !spec.for(this.occupant(asker), asker)))
|
|
94
100
|
return { error: 'unknown ask' };
|
|
95
101
|
const fn = this[method];
|
package/dist/being/digest.js
CHANGED
|
@@ -19,5 +19,19 @@ export const canonical = (v) => {
|
|
|
19
19
|
}
|
|
20
20
|
return JSON.stringify(v);
|
|
21
21
|
};
|
|
22
|
+
// Hex and the guard are spelled here and again in `src/ward/arithmetic.ts`,
|
|
23
|
+
// which is the price of the boundary: nothing under `src/being` imports
|
|
24
|
+
// anything above it, because this is the whole world a being's own code sees
|
|
25
|
+
// and a being reaching the ward is the thing the shape is against.
|
|
22
26
|
const hex = (bytes) => Array.from(new Uint8Array(bytes), (b) => b.toString(16).padStart(2, '0')).join('');
|
|
23
|
-
|
|
27
|
+
// `crypto.subtle` is read at the call and never captured at load. A browser
|
|
28
|
+
// on a plain http:// origin has `crypto` without `subtle`, and a terrain may
|
|
29
|
+
// install one after this module is first imported; either way the failure is
|
|
30
|
+
// one sentence and not a TypeError from inside a digest nobody can read.
|
|
31
|
+
const subtle = () => {
|
|
32
|
+
const s = globalThis.crypto?.subtle;
|
|
33
|
+
if (!s)
|
|
34
|
+
throw new Error('this terrain has no crypto.subtle: a being needs a secure context to be described');
|
|
35
|
+
return s;
|
|
36
|
+
};
|
|
37
|
+
export const digest = async (blueprint) => hex(await subtle().digest('SHA-256', new TextEncoder().encode(canonical(blueprint))));
|
package/dist/being/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { Being, type AskSpec } from './being.ts';
|
|
2
|
-
export { silence, isSilence, unreached, isUnreached, word, isWord, wordOf, DOOR_WORDS, isDoorWord } from './silence.ts';
|
|
2
|
+
export { silence, isSilence, answered, unreached, isUnreached, word, isWord, wordOf, told, DOOR_WORDS, isDoorWord } from './silence.ts';
|
|
3
3
|
export { digest, canonical } from './digest.ts';
|
|
4
|
-
export { OWNER, PUBLIC, RESERVED_IDS } from './types.ts';
|
|
4
|
+
export { OWNER, PUBLIC, RESERVED_IDS, isBlueprint, invitationArgs } from './types.ts';
|
|
5
5
|
export type * from './types.ts';
|
package/dist/being/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
2
|
// @quo-systems/quo — the Being side. What a being author imports, if anything.
|
|
3
3
|
export { Being } from './being.js';
|
|
4
|
-
export { silence, isSilence, unreached, isUnreached, word, isWord, wordOf, DOOR_WORDS, isDoorWord } from './silence.js';
|
|
4
|
+
export { silence, isSilence, answered, unreached, isUnreached, word, isWord, wordOf, told, DOOR_WORDS, isDoorWord } from './silence.js';
|
|
5
5
|
export { digest, canonical } from './digest.js';
|
|
6
|
-
export { OWNER, PUBLIC, RESERVED_IDS } from './types.js';
|
|
6
|
+
export { OWNER, PUBLIC, RESERVED_IDS, isBlueprint, invitationArgs } from './types.js';
|
package/dist/being/silence.d.ts
CHANGED
|
@@ -6,5 +6,7 @@ export declare const isDoorWord: (s: unknown) => s is DoorWord;
|
|
|
6
6
|
export declare const word: <W extends WordName>(name: W) => Word<W>;
|
|
7
7
|
export declare const isWord: (x: unknown) => x is Word;
|
|
8
8
|
export declare const wordOf: (x: Word) => WordName;
|
|
9
|
+
export declare const answered: <T>(x: T) => x is Exclude<T, typeof silence | Word>;
|
|
10
|
+
export declare const told: (x: unknown) => unknown;
|
|
9
11
|
export declare const unreached: () => Unreached;
|
|
10
12
|
export declare const isUnreached: (x: unknown) => x is Unreached;
|
package/dist/being/silence.js
CHANGED
|
@@ -22,6 +22,18 @@ export const word = (name) => {
|
|
|
22
22
|
};
|
|
23
23
|
export const isWord = (x) => x !== null && typeof x === 'object' && typeof x[WORD_KEY] === 'string';
|
|
24
24
|
export const wordOf = (x) => x[WORD_KEY];
|
|
25
|
+
// What came back is an answer and not one of the two things that are not
|
|
26
|
+
// answers. A being who only wants the object writes one test instead of
|
|
27
|
+
// three, and the three shapes stay three shapes: silence is no answer, a word
|
|
28
|
+
// is her ward telling her why there is none.
|
|
29
|
+
export const answered = (x) => !isSilence(x) && !isWord(x);
|
|
30
|
+
// What she was told, with a word given its name and everything else left
|
|
31
|
+
// exactly as it came. One value to compare against, so asking which word came
|
|
32
|
+
// back is one test and not two joined by an and: `told(out) === 'late'` says
|
|
33
|
+
// what `isWord(out) && wordOf(out) === 'late'` says, and says which answer
|
|
34
|
+
// arrived instead when it is wrong, where the pair collapses to false and
|
|
35
|
+
// names nothing. Silence stays the symbol it is, and an object stays itself.
|
|
36
|
+
export const told = (x) => (isWord(x) ? wordOf(x) : x);
|
|
25
37
|
// Unreached: no far door was reached. Nothing is known to have been
|
|
26
38
|
// delivered, so asking again is safe. A being cannot produce it: a ward that
|
|
27
39
|
// sees a word come out of a being reads it as her having thrown.
|
package/dist/being/types.d.ts
CHANGED
|
@@ -18,6 +18,7 @@ export type Invitation = {
|
|
|
18
18
|
heir?: string;
|
|
19
19
|
secret?: string;
|
|
20
20
|
};
|
|
21
|
+
export declare const invitationArgs: (inv: Invitation) => JsonObject;
|
|
21
22
|
export type Schema = JsonObject;
|
|
22
23
|
export type Ask = {
|
|
23
24
|
name: string;
|
|
@@ -29,6 +30,7 @@ export type Blueprint = {
|
|
|
29
30
|
asks: Ask[];
|
|
30
31
|
notes: Json;
|
|
31
32
|
};
|
|
33
|
+
export declare const isBlueprint: (v: unknown) => v is Blueprint;
|
|
32
34
|
export type StandingRecord = {
|
|
33
35
|
id: string;
|
|
34
36
|
digest: string | null;
|
package/dist/being/types.js
CHANGED
|
@@ -13,4 +13,29 @@
|
|
|
13
13
|
export const OWNER = 'OWNER';
|
|
14
14
|
export const PUBLIC = 'PUBLIC';
|
|
15
15
|
export const RESERVED_IDS = [OWNER, PUBLIC];
|
|
16
|
+
// An invitation as one object of values, which is what it already is. A being
|
|
17
|
+
// who hands one on sends it as args, and args are values; this is the one
|
|
18
|
+
// place that is spelled, so nobody spells it with a cast. Absent stays absent:
|
|
19
|
+
// an invitation to a public being carries no heir and no secret, and a key
|
|
20
|
+
// present and undefined is not the same object once JSON has been through it.
|
|
21
|
+
export const invitationArgs = (inv) => ({
|
|
22
|
+
ward: inv.ward,
|
|
23
|
+
...(inv.heir !== undefined ? { heir: inv.heir } : {}),
|
|
24
|
+
...(inv.secret !== undefined ? { secret: inv.secret } : {}),
|
|
25
|
+
});
|
|
26
|
+
// Whether what came back from an empty ask is a blueprint. Every describe on
|
|
27
|
+
// the far side of a door is somebody else's code, so nothing may be written
|
|
28
|
+
// into a standing's record as a blueprint without being read as one first: a
|
|
29
|
+
// side walks `asks` by name, and a list of anything else, or no list at all,
|
|
30
|
+
// is a side that breaks on a far ward's answer. The asks are checked to the
|
|
31
|
+
// depth a side reads them, a name and an input each, and no further: what a
|
|
32
|
+
// far being puts beside those is hers.
|
|
33
|
+
export const isBlueprint = (v) => {
|
|
34
|
+
if (v === null || typeof v !== 'object' || Array.isArray(v))
|
|
35
|
+
return false;
|
|
36
|
+
const { asks, notes } = v;
|
|
37
|
+
if (!Array.isArray(asks) || notes === undefined)
|
|
38
|
+
return false;
|
|
39
|
+
return asks.every((a) => a !== null && typeof a === 'object' && !Array.isArray(a) && typeof a.name === 'string' && a.input !== null && typeof a.input === 'object' && !Array.isArray(a.input));
|
|
40
|
+
};
|
|
16
41
|
export const WORD_KEY = Symbol.for('quo.word');
|
|
@@ -15,6 +15,15 @@ const show = (v) => {
|
|
|
15
15
|
return 'undefined';
|
|
16
16
|
if (typeof v === 'function')
|
|
17
17
|
return `[function ${v.name}]`;
|
|
18
|
+
// A ward's word is a frozen object under one symbol key, and JSON shows it
|
|
19
|
+
// as `{}`. A red estate chapter saying `{} is not {}` names nothing, so a
|
|
20
|
+
// value whose own keys are all symbols is shown by its keys.
|
|
21
|
+
if (v !== null && typeof v === 'object') {
|
|
22
|
+
const keys = Object.keys(v);
|
|
23
|
+
const syms = Object.getOwnPropertySymbols(v).filter((s) => Object.getOwnPropertyDescriptor(v, s).enumerable);
|
|
24
|
+
if (keys.length === 0 && syms.length > 0)
|
|
25
|
+
return `{ ${syms.map((s) => `${s.toString()}: ${show(v[s])}`).join(', ')} }`;
|
|
26
|
+
}
|
|
18
27
|
try {
|
|
19
28
|
return JSON.stringify(v) ?? Object.prototype.toString.call(v);
|
|
20
29
|
}
|
|
@@ -28,22 +37,47 @@ const fail = (why, message) => {
|
|
|
28
37
|
// Structural, and strict about shape the way `node:assert/strict` is: a value
|
|
29
38
|
// is not equal to one of another kind, and an array is not equal to an object
|
|
30
39
|
// that happens to hold the same keys.
|
|
40
|
+
// The four kinds this compares, and every other object is refused rather than
|
|
41
|
+
// guessed at. A Date, a RegExp, a boxed primitive and a class instance each
|
|
42
|
+
// carry state no key walk can see, so two of them with the same keys are
|
|
43
|
+
// equal here and unequal in node, which is a wrong yes: the one answer a
|
|
44
|
+
// suite must never give. A refusal is a red test that says so.
|
|
45
|
+
const KNOWN = [Object.prototype, null, Array.prototype, Uint8Array.prototype];
|
|
46
|
+
// What node compares: own enumerable keys, strings then enumerable symbols.
|
|
47
|
+
// Not `Reflect.ownKeys`, which also lists what a definition hid.
|
|
48
|
+
const keysOf = (v) => [...Object.keys(v), ...Object.getOwnPropertySymbols(v).filter((s) => Object.getOwnPropertyDescriptor(v, s).enumerable)];
|
|
31
49
|
export function same(a, b) {
|
|
32
50
|
if (Object.is(a, b))
|
|
33
51
|
return true;
|
|
34
52
|
if (typeof a !== 'object' || typeof b !== 'object' || a === null || b === null)
|
|
35
53
|
return false;
|
|
36
|
-
|
|
54
|
+
const proto = Object.getPrototypeOf(a);
|
|
55
|
+
if (proto !== Object.getPrototypeOf(b))
|
|
37
56
|
return false;
|
|
57
|
+
if (!KNOWN.includes(proto))
|
|
58
|
+
return false; // a Date, a RegExp, a boxed number, a class of hers
|
|
38
59
|
if (Array.isArray(a) !== Array.isArray(b))
|
|
39
60
|
return false;
|
|
40
|
-
if (Array.isArray(a) && Array.isArray(b))
|
|
41
|
-
return a.length === b.length && a.every((x, i) => same(x, b[i]));
|
|
42
61
|
if (a instanceof Uint8Array && b instanceof Uint8Array)
|
|
43
62
|
return a.length === b.length && a.every((x, i) => x === b[i]);
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
63
|
+
// An array is its indices and whatever else was hung on it, and a hole is
|
|
64
|
+
// not a present undefined: `[, 1]` and `[undefined, 1]` differ in node and
|
|
65
|
+
// must differ here. `every` skips holes, so the loop is written out.
|
|
66
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
67
|
+
if (a.length !== b.length)
|
|
68
|
+
return false;
|
|
69
|
+
for (let i = 0; i < a.length; i += 1) {
|
|
70
|
+
if (i in a !== i in b)
|
|
71
|
+
return false;
|
|
72
|
+
if (i in a && !same(a[i], b[i]))
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
const extra = (v) => keysOf(v).filter((k) => typeof k !== 'string' || !/^(?:0|[1-9]\d*)$/.test(k));
|
|
76
|
+
const xa = extra(a), xb = extra(b);
|
|
77
|
+
const at = (v, k) => v[k];
|
|
78
|
+
return xa.length === xb.length && xa.every((k) => xb.includes(k) && same(at(a, k), at(b, k)));
|
|
79
|
+
}
|
|
80
|
+
const ka = keysOf(a), kb = keysOf(b);
|
|
47
81
|
if (ka.length !== kb.length)
|
|
48
82
|
return false;
|
|
49
83
|
return ka.every((k) => kb.includes(k) && same(a[k], b[k]));
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Being } from '../being/being.ts';
|
|
2
|
-
import type { Asker, Blueprint, Invitation, JsonObject, OccupantRecord, Reply, Stance } from '../being/types.ts';
|
|
2
|
+
import type { Asker, Blueprint, Invitation, Json, JsonObject, OccupantRecord, Reply, Stance } from '../being/types.ts';
|
|
3
3
|
export declare class Printer extends Being {
|
|
4
4
|
static cells: {
|
|
5
5
|
jobs: JsonObject[];
|
|
@@ -14,8 +14,8 @@ export declare class Printer extends Being {
|
|
|
14
14
|
};
|
|
15
15
|
describe(): Blueprint;
|
|
16
16
|
print({ doc }: JsonObject, asker: Asker): {
|
|
17
|
-
printed: string | number | boolean |
|
|
18
|
-
[key: string]:
|
|
17
|
+
printed: string | number | boolean | Json[] | {
|
|
18
|
+
[key: string]: Json;
|
|
19
19
|
} | null;
|
|
20
20
|
};
|
|
21
21
|
}
|
|
@@ -68,7 +68,7 @@ export declare class Shop extends Being {
|
|
|
68
68
|
error?: undefined;
|
|
69
69
|
retry?: undefined;
|
|
70
70
|
ok: boolean;
|
|
71
|
-
receipt:
|
|
71
|
+
receipt: Json;
|
|
72
72
|
}>;
|
|
73
73
|
refund(): {
|
|
74
74
|
ok: boolean;
|
|
@@ -85,7 +85,9 @@ export declare class Customer extends Being {
|
|
|
85
85
|
};
|
|
86
86
|
};
|
|
87
87
|
};
|
|
88
|
-
join(invitation: Invitation): Promise<typeof import("../being/silence.ts").silence | import("../being/types.ts").Word
|
|
88
|
+
join(invitation: Invitation): Promise<string | number | boolean | typeof import("../being/silence.ts").silence | Json[] | import("../being/types.ts").Word<import("../being/types.ts").WordName> | {
|
|
89
|
+
[key: string]: Json;
|
|
90
|
+
} | null>;
|
|
89
91
|
buy(item: string): Promise<import("../being/types.ts").Answer>;
|
|
90
92
|
learn(): Promise<import("../being/types.ts").Answer>;
|
|
91
93
|
hi(_args: JsonObject, asker: Asker): {
|
|
@@ -97,6 +99,47 @@ export declare class Echo {
|
|
|
97
99
|
constructor(stance: Stance);
|
|
98
100
|
answer(asker: Asker, method?: string, args?: JsonObject): Reply;
|
|
99
101
|
}
|
|
102
|
+
export declare class Maker extends Being {
|
|
103
|
+
static cells: {
|
|
104
|
+
made: string[];
|
|
105
|
+
};
|
|
106
|
+
static asks: {
|
|
107
|
+
open: {
|
|
108
|
+
input: {
|
|
109
|
+
type: string;
|
|
110
|
+
properties: {
|
|
111
|
+
class: {
|
|
112
|
+
type: string;
|
|
113
|
+
};
|
|
114
|
+
key: {
|
|
115
|
+
type: string;
|
|
116
|
+
};
|
|
117
|
+
id: {
|
|
118
|
+
type: string;
|
|
119
|
+
};
|
|
120
|
+
};
|
|
121
|
+
required: string[];
|
|
122
|
+
};
|
|
123
|
+
};
|
|
124
|
+
say: {
|
|
125
|
+
input: {
|
|
126
|
+
type: string;
|
|
127
|
+
properties: {
|
|
128
|
+
id: {
|
|
129
|
+
type: string;
|
|
130
|
+
};
|
|
131
|
+
};
|
|
132
|
+
required: string[];
|
|
133
|
+
};
|
|
134
|
+
};
|
|
135
|
+
};
|
|
136
|
+
open({ class: className, key, id }: JsonObject): Promise<{
|
|
137
|
+
made: string | null;
|
|
138
|
+
}>;
|
|
139
|
+
say({ id }: JsonObject): Promise<{
|
|
140
|
+
said: Json;
|
|
141
|
+
}>;
|
|
142
|
+
}
|
|
100
143
|
export declare class Member extends Being {
|
|
101
144
|
static cells: {
|
|
102
145
|
heard: JsonObject[];
|