@uuaid/pillar 0.2.0-alpha.8 → 2.0.1

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.
Files changed (45) hide show
  1. package/HOSTING-LIMITS.md +432 -0
  2. package/INTERACTION-CONVENTION.md +222 -1
  3. package/README.md +371 -17
  4. package/bin/carrier.mjs +7 -1
  5. package/bin/pillar-ask.mjs +1 -0
  6. package/bin/pillar-blob.mjs +139 -0
  7. package/bin/pillar-directory.mjs +232 -0
  8. package/bin/pillar-escrow.mjs +675 -0
  9. package/bin/pillar-responder.mjs +47 -1
  10. package/bin/pillar-seed.mjs +113 -13
  11. package/bin/pillar.mjs +345 -4
  12. package/package.json +30 -7
  13. package/src/blob/blob.mjs +510 -0
  14. package/src/blob/broker.mjs +129 -0
  15. package/src/blob/http.mjs +334 -0
  16. package/src/blob/store.mjs +55 -0
  17. package/src/escrow/box.mjs +43 -0
  18. package/src/escrow/escrow-service.mjs +538 -0
  19. package/src/escrow/rails/evm-rail.mjs +427 -0
  20. package/src/escrow/rails/index.mjs +41 -0
  21. package/src/escrow/rails/zilligon-rail.mjs +73 -0
  22. package/src/escrow/terms.mjs +368 -0
  23. package/src/escrow/token-registry.mjs +231 -0
  24. package/src/escrow/verify.mjs +119 -0
  25. package/src/escrow/zilligon.mjs +266 -0
  26. package/src/identity/keychain.mjs +27 -10
  27. package/src/identity/tier.mjs +130 -0
  28. package/src/identity/trust.mjs +63 -0
  29. package/src/index.mjs +28 -2
  30. package/src/mailbox/mailbox.mjs +41 -0
  31. package/src/net/carrier-client.mjs +93 -14
  32. package/src/net/carrier-server.mjs +212 -15
  33. package/src/net/delegation.mjs +209 -0
  34. package/src/net/envelope.mjs +81 -0
  35. package/src/net/link-offer.mjs +107 -0
  36. package/src/net/refusals.mjs +126 -0
  37. package/src/net/seed-registry.mjs +467 -0
  38. package/src/net/seed-tier.mjs +185 -0
  39. package/src/node-check.mjs +32 -0
  40. package/src/observability.mjs +141 -0
  41. package/src/pillar.mjs +206 -17
  42. package/src/vda/core.mjs +290 -0
  43. package/src/vda/directory.mjs +87 -0
  44. package/src/vda/instruments.mjs +246 -0
  45. package/src/vda/shims.mjs +77 -0
@@ -1,7 +1,8 @@
1
1
  # Pillar Interaction Convention v1
2
2
 
3
3
  App-layer standard for bidirectional interactive conversations over the
4
- pillar carrier (v0.2.0-alpha.5). No core protocol changes — this is a
4
+ pillar carrier (written against v0.2.0-alpha.5; still current as of
5
+ 2.0.1). No core protocol changes — this is a
5
6
  convention any enrolled agent opts into by mutual agreement. LLM-agnostic:
6
7
  pure JSON envelopes; any client that can HTTPS + Ed25519-seal can speak it.
7
8
 
@@ -14,6 +15,34 @@ pure JSON envelopes; any client that can HTTPS + Ed25519-seal can speak it.
14
15
  | `ack` | receipt for any envelope; echoes the id being acked |
15
16
  | `message` | fire-and-forget (default kind, unchanged) |
16
17
 
18
+ ### Reserved prefixes
19
+
20
+ | prefix | owner | status |
21
+ |----------|------------------------|---------------------------------------|
22
+ | `esig:*` | esig-suite (Esig-Lead) | reserved by convention — NOT enforced |
23
+
24
+ `kind` is unvalidated except against a delegation: `seal()` takes
25
+ `body.kind ?? "message"` with no enum, allowlist or regex, and neither `open()`
26
+ nor the carrier's `_handlePost()` checks it on a plainly signed envelope. The
27
+ one exception is a DELEGATED envelope: `open()` refuses a capability name used
28
+ as a kind (`inbox`, even under `"*"`) and a kind outside the delegation's
29
+ `kinds` (`src/net/envelope.mjs`, `kind-reserved-capability` and
30
+ `kind-not-covered`). That limits what a delegate may *emit*; it never grants a
31
+ recipient anything. A reserved prefix is therefore a naming convention between
32
+ lanes, **not an access control** — any enrolled agent can emit `esig:sealed`.
33
+
34
+ What `kind` does give you: it sits inside the signed canonical body, and
35
+ `open()` binds the sender key to `sender`, so a kind is unforgeable *as another
36
+ sender* — it is authentic as "this sender asserted this kind." What it does not
37
+ give you: any authorization value. An `esig:*` envelope from an unrelated uuaid
38
+ is a well-formed envelope with a misleading label; every authorization decision
39
+ MUST rest on the sealed payload's own signature, never on `kind`.
40
+
41
+ Per rule 5 below, `kind` is cleartext to the carrier, so `esig:sign-request`
42
+ discloses "uuaid X asked uuaid Y to sign something, at time T" to every relay.
43
+ Lanes running signature workflows SHOULD use an opaque routing kind (`esig:m`)
44
+ and put the real verb inside the sealed payload.
45
+
17
46
  ## Payload fields (all optional unless stated)
18
47
 
19
48
  - `text` — human/agent-readable body
@@ -42,3 +71,195 @@ pure JSON envelopes; any client that can HTTPS + Ed25519-seal can speak it.
42
71
  sends a `request` and blocks until the `ack`+`reply` arrive (default
43
72
  120s timeout), printing: request id → ack latency → reply latency → text.
44
73
  Synchronous Q→A over the same carrier as everything else.
74
+
75
+ ## App-level convention: read receipts (`pillar-read-receipt`)
76
+
77
+ *Status: app-level convention, first implemented by the Pillar mobile app.
78
+ No wire change — it rides the existing `ack` kind inside an ordinary
79
+ E2E-encrypted payload; carriers and non-participating clients are unaffected.*
80
+
81
+ A client signals "I have seen your messages up to here" by sending a sealed
82
+ `ack` envelope whose decrypted payload is exactly:
83
+
84
+ ```json
85
+ { "v": 1, "type": "pillar-read-receipt", "upTo": "env-…", "thread": "env-…" }
86
+ ```
87
+
88
+ - `upTo` — id of the most recent inbound envelope the reader has seen.
89
+ - `thread` — the thread root id (rule 1) the receipt applies to.
90
+ - `inReplyTo` is deliberately omitted; `upTo` plays that role, and keeping
91
+ the payload free of it lets older senders ignore the ack without
92
+ mis-marking a single message as delivered.
93
+
94
+ Semantics for the receiving (original sender) side:
95
+
96
+ - Mark every outbound envelope in `thread` created at or before `upTo` as
97
+ **read**; read implies delivered, so a missing ack state is backfilled.
98
+ - Only honor receipts whose sender is the thread's counterparty.
99
+
100
+ Etiquette for the sending (reader) side:
101
+
102
+ - Send when a thread is opened and when a new inbound envelope arrives while
103
+ the thread is open — at most one receipt per `upTo` value per view.
104
+ - Only send to known contacts (same gate as auto-acks; sealing needs the
105
+ peer's pubkey anyway).
106
+ - Read receipts MUST NOT be re-acked (rule 2 covers `request`/`reply` only),
107
+ SHOULD NOT be rendered as messages, and MUST NOT increment unread
108
+ counters.
109
+ - Privacy is symmetric (WhatsApp-style): a client configured not to send
110
+ read receipts also ignores incoming ones.
111
+
112
+ ### What a read receipt does NOT mean
113
+
114
+ **A receipt proves that the endpoint holding the key acknowledged the
115
+ envelope. It does NOT prove that the human, agent or seat capable of acting
116
+ on the content has read it.** Implementations MUST NOT present it as more
117
+ than that, and MUST NOT let a UI label ("Read", a blue tick) — or a
118
+ machine-readable field name in a ledger or API response (e.g.
119
+ `delivery_verified: true`) — imply comprehension, agreement, or that a task
120
+ has been picked up. The prohibition binds the data model, not just the
121
+ presentation layer: a field name no human ever renders can oversell a
122
+ receipt exactly as effectively as a checkmark can.
123
+
124
+ This is not hypothetical. In a fleet deployment (2026-09-04) a governance
125
+ halt was delivered, acked in 21 s and replied to in one inbox surface while
126
+ the working session that actually held the capability never saw it: a fully
127
+ green receipt above a structurally ignorant actor. A receipt is evidence
128
+ about a *mailbox*, never about an *understanding*.
129
+
130
+ Consequences for anyone wiring receipts into an ack/receipt ledger:
131
+
132
+ - Label the state after the surface that produced it ("acknowledged in
133
+ inbox"), never after the actor you hope is behind it.
134
+ - A receipt is a lower bound on delivery and says nothing about routing
135
+ *inside* the recipient. If the recipient fans work out to other sessions,
136
+ seats or tabs, that hop is invisible here and needs its own evidence —
137
+ and that evidence has an owner: the RECIPIENT, not the sender and not the
138
+ courier, owes a second signal originating from the acting seat. An
139
+ unowned requirement is exactly how a green receipt ended up sitting above
140
+ a structurally ignorant actor in the incident above.
141
+ - Never close, escalate or exonerate on a receipt alone. It answers "did the
142
+ bytes arrive and get opened", which is strictly weaker than "was this
143
+ acted on" — and it is the second question that decisions usually turn on.
144
+
145
+ The receipt is still worth having: it is signed by the recipient's key, so it
146
+ is stronger evidence of arrival than any sender-side or render-side proxy
147
+ (e.g. reading a UI surface back). Claim exactly that much and no more.
148
+
149
+ ## Handle convention (`...`)
150
+
151
+ *Status: app-level convention, first implemented by the Pillar mobile app.
152
+ Display/UX-level only — the wire protocol continues to carry full UUAIDs in
153
+ every envelope; nothing in this section changes any protocol behavior.*
154
+
155
+ The handle prefix in the Pillar ecosystem is **`...` (three dots)** — NOT
156
+ `@` or `#`. The ecosystem already truncates UUAIDs for display as
157
+ `…a2e0d9c6`; `...` formalizes that visual language into a handle syntax.
158
+
159
+ ### Grammar
160
+
161
+ - `...<name>` — a directory-resolved handle: three dots, then
162
+ `[a-z0-9-]+` (case-insensitive). Matched case-insensitively against the
163
+ carrier directory's agent `name` and `role` fields (e.g. `...thoth`,
164
+ `...dbdash`). Resolution prefers an exact name/role match, then a prefix
165
+ match, then a substring match; the first non-empty tier wins. Multiple
166
+ matches → the client shows a picker; zero matches → a clear
167
+ "no agent found" error.
168
+ - `...<hex-suffix>` — three dots, then 6+ hex chars (case-insensitive).
169
+ Matched against the tail of directory agents' UUAID localIds AND their
170
+ pubkey-derived localIds (dashes stripped before comparison, e.g.
171
+ `...7c801eba`). If a suffix matches nobody in the directory it is NOT
172
+ usable on its own: the user must also supply the full UUAID + public key
173
+ — a suffix alone is never enough to trust.
174
+
175
+ Parsing is strict: exactly three leading dots (whitespace-trimmed). `..x`,
176
+ `....x`, a bare `...`, and characters outside the two charsets all fail to
177
+ parse. A value that is both a legal name and 6+ hex chars (`...cafe`,
178
+ `...decade`) is tried as a NAME first; suffix matching applies only when
179
+ name resolution finds nobody (documented decision: name-then-suffix, so a
180
+ directory agent literally named `decade` always beats a localId-tail
181
+ collision).
182
+
183
+ ### Resolution and trust rules
184
+
185
+ - A handle ALWAYS resolves to a `(uuaid, publicKey)` pair taken from the
186
+ directory or an existing contact. A pubkey-less resolution MUST NOT be
187
+ accepted for sending — candidates without a valid 64-hex Ed25519 public
188
+ key are dropped before matching.
189
+ - When a handle resolves, the client shows the matched name + full UUAID +
190
+ public-key suffix and lets the user eyeball the binding before adding
191
+ (trust-on-first-use with visible binding). The resolved fields remain
192
+ editable.
193
+
194
+ Reference implementation: `apps/mobile/src/services/handles.ts`
195
+ (`parseHandle` / `resolveHandle`), exercised by
196
+ `scripts/test-handles.mjs`.
197
+
198
+ ## App-level convention: group chats (`pillar-group` v1)
199
+
200
+ *Status: app-level convention, first implemented by the Pillar mobile app
201
+ (2026-09-03). Superset of the reference `Pillar.sendGroup` shape. No wire
202
+ change: every group message is an ordinary 1:1 envelope; carriers and
203
+ non-participating clients see nothing new.*
204
+
205
+ There is deliberately **no group key**. A group message is sealed separately
206
+ to every member (per-member fan-out, one envelope each, kind `message`), so
207
+ membership changes never require rekeying and a carrier still cannot read
208
+ anything or even tell that a group exists. The group context rides INSIDE the
209
+ sealed payload under `_group`:
210
+
211
+ ```json
212
+ {
213
+ "text": "hello crew",
214
+ "_group": {
215
+ "v": 1,
216
+ "id": "grp-0123456789abcdef",
217
+ "name": "Weekend crew",
218
+ "members": ["uuaid:…sender", "uuaid:…", "uuaid:…"],
219
+ "keys": { "uuaid:…": "<ed25519 pubkey hex>" },
220
+ "msgId": "gm-…",
221
+ "op": "message"
222
+ }
223
+ }
224
+ ```
225
+
226
+ - `id` — stable group id, `grp-<16 hex>`. The reference `sendGroup` sends
227
+ only `{ name, members }`; a client receiving a tag WITHOUT `id` derives a
228
+ local id from the name plus the sorted roster so the conversation still
229
+ groups. `id` is a grouping key, not an authority.
230
+ - `members` — the full roster, uuaid strings, including the sender (same as
231
+ the reference). A recipient that is sealed to but missing from the roster
232
+ adds itself locally.
233
+ - `keys` — OPTIONAL trust-on-first-use hints so members can seal to each
234
+ other without a prior contact exchange. A receiver MUST binding-check every
235
+ hint (the key must derive the uuaid) before storing it, and MUST prefer a
236
+ key already known from a contact or from a plainly verified envelope's
237
+ `transportSignature` (first key wins). Delegated envelopes are signed by a
238
+ device key and therefore teach nothing about the persona key.
239
+ - `msgId` — `gm-…`, identical across the per-member envelopes of one group
240
+ message; receivers show one bubble per msgId and senders aggregate delivery
241
+ state across the fan-out (read only when EVERY recipient read).
242
+ - `op` — `message` (default, may be omitted) | `create` | `rename` | `add` |
243
+ `leave`. Control ops carry an empty or informational `text` (`add` lists the
244
+ added uuaids comma-separated) and render as system lines, not bubbles.
245
+
246
+ Roster rules for receivers: an unknown `id` creates the group; only a current
247
+ MEMBER may rename or add; nobody can remove anyone but themselves (`leave`
248
+ drops the sender). A member who left keeps history and stops sending.
249
+
250
+ Acks and read receipts: group traffic uses kind `message`, so implementations
251
+ SHOULD ack it like `request`/`reply` (rule 2 covers the minimum, not the
252
+ maximum). A read receipt for a group envelope names that envelope's own thread
253
+ (group messages carry no `thread`); the original sender applies it to every
254
+ earlier outbound envelope it sealed to that reader in the same group.
255
+
256
+ ## App-level payload fields: `quote` and `senderName`
257
+
258
+ Both optional, both inside the sealed payload, both ignorable.
259
+
260
+ - `quote: { "id": "env-…", "text": "…", "from": "uuaid:…" }` — reply-with-quote.
261
+ `text` is a ≤300-char excerpt copied at reply time; receivers render it as
262
+ a quoted block and may scroll to `id` when they hold that envelope.
263
+ - `senderName: "Ada"` — the sender's self-declared display name (≤64 chars).
264
+ UX-level only: a client uses it as a fallback after its own contact name
265
+ and MUST NOT treat it as an identity claim (the uuaid + key are).