@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.
- package/HOSTING-LIMITS.md +432 -0
- package/INTERACTION-CONVENTION.md +222 -1
- package/README.md +371 -17
- package/bin/carrier.mjs +7 -1
- package/bin/pillar-ask.mjs +1 -0
- package/bin/pillar-blob.mjs +139 -0
- package/bin/pillar-directory.mjs +232 -0
- package/bin/pillar-escrow.mjs +675 -0
- package/bin/pillar-responder.mjs +47 -1
- package/bin/pillar-seed.mjs +113 -13
- package/bin/pillar.mjs +345 -4
- package/package.json +30 -7
- package/src/blob/blob.mjs +510 -0
- package/src/blob/broker.mjs +129 -0
- package/src/blob/http.mjs +334 -0
- package/src/blob/store.mjs +55 -0
- package/src/escrow/box.mjs +43 -0
- package/src/escrow/escrow-service.mjs +538 -0
- package/src/escrow/rails/evm-rail.mjs +427 -0
- package/src/escrow/rails/index.mjs +41 -0
- package/src/escrow/rails/zilligon-rail.mjs +73 -0
- package/src/escrow/terms.mjs +368 -0
- package/src/escrow/token-registry.mjs +231 -0
- package/src/escrow/verify.mjs +119 -0
- package/src/escrow/zilligon.mjs +266 -0
- package/src/identity/keychain.mjs +27 -10
- package/src/identity/tier.mjs +130 -0
- package/src/identity/trust.mjs +63 -0
- package/src/index.mjs +28 -2
- package/src/mailbox/mailbox.mjs +41 -0
- package/src/net/carrier-client.mjs +93 -14
- package/src/net/carrier-server.mjs +212 -15
- package/src/net/delegation.mjs +209 -0
- package/src/net/envelope.mjs +81 -0
- package/src/net/link-offer.mjs +107 -0
- package/src/net/refusals.mjs +126 -0
- package/src/net/seed-registry.mjs +467 -0
- package/src/net/seed-tier.mjs +185 -0
- package/src/node-check.mjs +32 -0
- package/src/observability.mjs +141 -0
- package/src/pillar.mjs +206 -17
- package/src/vda/core.mjs +290 -0
- package/src/vda/directory.mjs +87 -0
- package/src/vda/instruments.mjs +246 -0
- package/src/vda/shims.mjs +77 -0
package/bin/pillar.mjs
CHANGED
|
@@ -7,11 +7,16 @@
|
|
|
7
7
|
* pillar id [--home ~/.pillar] print our UUAID + PeerId + addrs
|
|
8
8
|
* pillar send <recipient> <kind> <json> send a signed envelope
|
|
9
9
|
* pillar recv [--home ~/.pillar] [--follow] listen for envelopes and print them
|
|
10
|
+
* pillar inbox [--home ~/.pillar] [--unread] [--notify CMD] [--mark-read]
|
|
11
|
+
* one-shot: pull pending mail, print, exit
|
|
12
|
+
* pillar trust [set|allow|block|unlist|quarantine] recipient consent policy
|
|
13
|
+
* pillar tier [issue|install|verify] signed volume/size tier grants
|
|
10
14
|
* pillar peers list currently connected peers
|
|
11
15
|
*
|
|
12
16
|
* All commands respect --home to point at an alternate pillar directory,
|
|
13
17
|
* useful for running multiple pillars on one machine (Zestro dogfood etc.).
|
|
14
18
|
*/
|
|
19
|
+
import "../src/node-check.mjs"; // must stay the first import: refuses Node < 22 before any dependency loads
|
|
15
20
|
import { mkdirSync, existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
16
21
|
import { join, dirname } from "node:path";
|
|
17
22
|
import { homedir } from "node:os";
|
|
@@ -19,6 +24,10 @@ import { argv, exit } from "node:process";
|
|
|
19
24
|
import { Keychain } from "../src/identity/keychain.mjs";
|
|
20
25
|
import { Pillar } from "../src/pillar.mjs";
|
|
21
26
|
import { loadFleetAddressBook } from "../src/address-book.mjs";
|
|
27
|
+
import { loadTrustPolicy, POLICIES } from "../src/identity/trust.mjs";
|
|
28
|
+
import { parseLinkOffer, isLinkOfferLive, LINK_GRANT_KINDS } from "../src/net/link-offer.mjs";
|
|
29
|
+
import { mintDelegationDoc } from "../src/net/delegation.mjs";
|
|
30
|
+
import { refusalLine } from "../src/net/refusals.mjs";
|
|
22
31
|
|
|
23
32
|
function parseArgs(argv) {
|
|
24
33
|
const args = { _: [] };
|
|
@@ -58,6 +67,19 @@ function loadPillar(args) {
|
|
|
58
67
|
const carriers = args.carrier
|
|
59
68
|
? [args.carrier]
|
|
60
69
|
: (process.env.PILLAR_CARRIERS ? process.env.PILLAR_CARRIERS.split(",") : (cfg.carriers ?? []));
|
|
70
|
+
// Trust gate: --policy overrides trust.json's policy for this invocation.
|
|
71
|
+
const trust = loadTrustPolicy(home);
|
|
72
|
+
if (args.policy) {
|
|
73
|
+
if (!POLICIES.includes(args.policy)) {
|
|
74
|
+
console.error(`--policy must be one of ${POLICIES.join("|")}`);
|
|
75
|
+
exit(2);
|
|
76
|
+
}
|
|
77
|
+
trust.policy = args.policy;
|
|
78
|
+
}
|
|
79
|
+
// Tier grant: <home>/tier-grant.json (issued via `pillar tier issue`) is
|
|
80
|
+
// attached to carrier delivers — raises budgets at carriers trusting the issuer.
|
|
81
|
+
const tgPath = join(home, "tier-grant.json");
|
|
82
|
+
const tierGrant = existsSync(tgPath) ? JSON.parse(readFileSync(tgPath, "utf-8")) : null;
|
|
61
83
|
return new Pillar({
|
|
62
84
|
keychain: kc,
|
|
63
85
|
mailboxPath: mbPath,
|
|
@@ -67,6 +89,8 @@ function loadPillar(args) {
|
|
|
67
89
|
dhtClientMode: cfg.dhtClientMode ?? true,
|
|
68
90
|
carriers,
|
|
69
91
|
enableLibp2p: !(args["carrier-only"] || cfg.carrierOnly === true),
|
|
92
|
+
trust,
|
|
93
|
+
tierGrant,
|
|
70
94
|
});
|
|
71
95
|
}
|
|
72
96
|
|
|
@@ -245,6 +269,121 @@ async function cmdSend(args) {
|
|
|
245
269
|
}
|
|
246
270
|
}
|
|
247
271
|
|
|
272
|
+
|
|
273
|
+
// ── delegate ──────────────────────────────────────────────────────────────────
|
|
274
|
+
// The desktop-link ceiling enforced by chat-web (browser-chat-session.ts,
|
|
275
|
+
// LINK_TTL_MS). A grant with a longer window verifies fine and is then DROPPED
|
|
276
|
+
// by the browser without a word, so this side refuses it loudly instead.
|
|
277
|
+
const LINK_TTL_DAYS_MAX = 7;
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* pillar delegate --to-browser '<uuaid://link/...>'
|
|
281
|
+
*
|
|
282
|
+
* Pair a browser tab to THIS pillar identity. The tab publishes an offer
|
|
283
|
+
* carrying its own freshly minted device key; this mints a delegation naming
|
|
284
|
+
* that key as delegate and this identity as principal, and sends it as a
|
|
285
|
+
* `_link` grant to the tab's inbox.
|
|
286
|
+
*
|
|
287
|
+
* The persona seed never leaves this machine — only a signed, time-boxed
|
|
288
|
+
* document does. Identical in every respect to what a phone sends: the
|
|
289
|
+
* handshake authenticates the PRINCIPAL (the envelope sender must equal the
|
|
290
|
+
* delegation principal), and never the kind of device the principal runs on.
|
|
291
|
+
* That is what lets a headless agent hold a desktop.
|
|
292
|
+
*/
|
|
293
|
+
async function cmdDelegate(args) {
|
|
294
|
+
const offerRaw = typeof args["to-browser"] === "string" ? args["to-browser"] : args._[1];
|
|
295
|
+
if (!offerRaw || args["to-browser"] === true) {
|
|
296
|
+
console.error("usage: pillar delegate --to-browser '<uuaid://link/...>' [--ttl-days N] [--name NAME]");
|
|
297
|
+
console.error(" Paste the offer string the browser shows under its QR code.");
|
|
298
|
+
exit(2);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const offer = parseLinkOffer(offerRaw);
|
|
302
|
+
if (!offer) {
|
|
303
|
+
console.error("not a valid link offer. Expected: uuaid://link/<delegate-uuaid>?pk=<64hex>&nonce=<hex>&exp=<iso>");
|
|
304
|
+
exit(2);
|
|
305
|
+
}
|
|
306
|
+
if (!isLinkOfferLive(offer)) {
|
|
307
|
+
console.error(`offer expired at ${offer.exp} — reload the browser tab for a fresh one`);
|
|
308
|
+
exit(1);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const ttlDays = args["ttl-days"] !== undefined ? Number(args["ttl-days"]) : LINK_TTL_DAYS_MAX;
|
|
312
|
+
if (!Number.isFinite(ttlDays) || ttlDays <= 0) {
|
|
313
|
+
console.error("--ttl-days must be a positive number");
|
|
314
|
+
exit(2);
|
|
315
|
+
}
|
|
316
|
+
// Refuse rather than mint a grant the far side discards in silence.
|
|
317
|
+
if (ttlDays > LINK_TTL_DAYS_MAX) {
|
|
318
|
+
console.error(`--ttl-days ${ttlDays} exceeds the ${LINK_TTL_DAYS_MAX}-day desktop-link ceiling.`);
|
|
319
|
+
console.error("A longer grant is a VALID document that the browser drops without reporting why.");
|
|
320
|
+
exit(2);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const p = loadPillar(args);
|
|
324
|
+
await p.start();
|
|
325
|
+
try {
|
|
326
|
+
const me = p.keychain._identity;
|
|
327
|
+
const delegation = mintDelegationDoc(p.keychain, {
|
|
328
|
+
delegate: offer.delegate,
|
|
329
|
+
delegatePublicKey: offer.pk,
|
|
330
|
+
kinds: [...LINK_GRANT_KINDS], // never "*": that would let the desktop read this identity's inbox
|
|
331
|
+
ttlDays,
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
// The snapshot's persona MUST equal the delegation principal exactly — the
|
|
335
|
+
// browser rejects the whole grant on any mismatch, treating a disagreeing
|
|
336
|
+
// snapshot as evidence that neither half can be trusted.
|
|
337
|
+
// fleet.json is { fleet: { "<role>": { uuaid, publicKey, ... } } } — keyed by
|
|
338
|
+
// ROLE, which is also the human-readable name the desktop shows. A row
|
|
339
|
+
// without a publicKey cannot be sealed to, so it is not a contact.
|
|
340
|
+
const contacts = [];
|
|
341
|
+
const fp = fleetPath(args);
|
|
342
|
+
if (existsSync(fp)) {
|
|
343
|
+
let fleet = {};
|
|
344
|
+
try {
|
|
345
|
+
fleet = JSON.parse(readFileSync(fp, "utf-8")).fleet ?? {};
|
|
346
|
+
} catch {
|
|
347
|
+
console.error(`warning: ${fp} is not readable JSON — sending 0 contacts`);
|
|
348
|
+
}
|
|
349
|
+
for (const [role, entry] of Object.entries(fleet)) {
|
|
350
|
+
if (typeof entry?.uuaid !== "string" || typeof entry?.publicKey !== "string") continue;
|
|
351
|
+
contacts.push({ uuaid: entry.uuaid, publicKey: entry.publicKey, name: String(role) });
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const payload = {
|
|
356
|
+
_link: { v: 1, op: "grant", nonce: offer.nonce },
|
|
357
|
+
delegation,
|
|
358
|
+
snapshot: {
|
|
359
|
+
persona: { uuaid: me.uuaid, publicKey: me.publicKeyHex },
|
|
360
|
+
displayName: typeof args.name === "string" ? args.name : null,
|
|
361
|
+
contacts,
|
|
362
|
+
},
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
const { envelope } = await p.send({
|
|
366
|
+
recipient: offer.delegate,
|
|
367
|
+
kind: "message",
|
|
368
|
+
payload,
|
|
369
|
+
recipientPublicKey: offer.pk,
|
|
370
|
+
resolveTimeoutMs: 5000,
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
console.log(`linked ${offer.delegate}`);
|
|
374
|
+
console.log(` principal: ${me.uuaid}`);
|
|
375
|
+
console.log(` window: ${delegation.notBefore} -> ${delegation.notAfter} (${ttlDays}d)`);
|
|
376
|
+
console.log(` contacts: ${contacts.length}`);
|
|
377
|
+
console.log(` envelope: ${envelope.id}`);
|
|
378
|
+
console.log("");
|
|
379
|
+
console.log("The tab pairs when it polls. If it does not, the grant was refused in");
|
|
380
|
+
console.log("silence — check the offer is unexpired and unclaimed, and that the tab");
|
|
381
|
+
console.log("still shows the same QR (a reload rotates the nonce and voids this grant).");
|
|
382
|
+
} finally {
|
|
383
|
+
await p.stop();
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
248
387
|
async function cmdSendGroup(args) {
|
|
249
388
|
const [groupName, kind, jsonStr] = args._.slice(1);
|
|
250
389
|
if (!groupName || !kind) {
|
|
@@ -266,7 +405,7 @@ async function cmdSendGroup(args) {
|
|
|
266
405
|
let failed = 0;
|
|
267
406
|
for (const r of results) {
|
|
268
407
|
if (r.ok) console.log(` ✓ ${r.member} via ${r.via} ${r.envelopeId}`);
|
|
269
|
-
else { failed++; console.error(` ✗ ${r.member} ${r.error}`); }
|
|
408
|
+
else { failed++; console.error(` ✗ ${r.member} ${refusalLine(r) ?? r.error}`); }
|
|
270
409
|
}
|
|
271
410
|
console.log(`group "${groupName}": ${results.length - failed}/${results.length} delivered`);
|
|
272
411
|
if (failed) exit(1);
|
|
@@ -375,7 +514,7 @@ async function cmdRecv(args) {
|
|
|
375
514
|
await p.send({ recipient: env.sender, recipientPublicKey: senderKey, kind: "reply", payload: { thread, inReplyTo: env.id, status: "error", reason: e.message.slice(0, 300), text: `zestro bridge error: ${e.message.slice(0, 300)}` } });
|
|
376
515
|
}
|
|
377
516
|
}
|
|
378
|
-
} else if (bridge) {
|
|
517
|
+
} else if (bridge && env.kind !== "ack" && env.kind !== "reply") {
|
|
379
518
|
try {
|
|
380
519
|
const result = await bridge.sendCommand({ command: JSON.stringify({ kind: env.kind, payload: env.payload, sender: env.sender }) });
|
|
381
520
|
console.log(` → zestro result: ${JSON.stringify(result).slice(0, 200)}`);
|
|
@@ -388,6 +527,10 @@ async function cmdRecv(args) {
|
|
|
388
527
|
console.error(` → zestro bridge error: ${e.message}`);
|
|
389
528
|
}
|
|
390
529
|
}
|
|
530
|
+
} else if (bridge) {
|
|
531
|
+
// ack/reply are receipts for OUR earlier sends — bridging them would
|
|
532
|
+
// inject a Zestro agent turn per receipt. Print-only (line above).
|
|
533
|
+
console.log(` → zestro: not bridged (kind=${env.kind} is a receipt)`);
|
|
391
534
|
}
|
|
392
535
|
});
|
|
393
536
|
|
|
@@ -397,6 +540,198 @@ async function cmdRecv(args) {
|
|
|
397
540
|
await new Promise(() => {});
|
|
398
541
|
}
|
|
399
542
|
|
|
543
|
+
async function cmdInbox(args) {
|
|
544
|
+
// One-shot mail check for ANY identity: point --home at the pillar dir that
|
|
545
|
+
// minted the uuaid. Pulls pending envelopes from the carrier (no long-poll),
|
|
546
|
+
// prints them decrypted, reports unread backlog, exits. --notify CMD runs a
|
|
547
|
+
// shell hook when there is mail (cron/launchd-friendly); --mark-read stamps
|
|
548
|
+
// read_at on everything printed.
|
|
549
|
+
args["carrier-only"] = true; // inbox check needs the carrier only — no libp2p listener
|
|
550
|
+
const p = loadPillar(args);
|
|
551
|
+
const fresh = [];
|
|
552
|
+
p.onEnvelope((env) => { fresh.push(env); });
|
|
553
|
+
await p.start();
|
|
554
|
+
// Address book BEFORE the drain: the trust "directory" policy needs known
|
|
555
|
+
// senders loaded, or every fresh envelope counts as a stranger.
|
|
556
|
+
loadFleetAddressBook(fleetPath(args), p);
|
|
557
|
+
const pulled = await p.drainCarrierInbox();
|
|
558
|
+
// fromPublicKey: the sender's verified Ed25519 key (transportSignature —
|
|
559
|
+
// open() binds it to `from`). Lets a drain-and-reply consumer (the fleet
|
|
560
|
+
// courier's auto-ack, a bot) seal back to a sender who is NOT in fleet.json
|
|
561
|
+
// via `pillar send … --to-pubkey <fromPublicKey>`. Additive field.
|
|
562
|
+
for (const env of fresh) {
|
|
563
|
+
console.log(JSON.stringify({ new: true, id: env.id, from: env.sender, fromPublicKey: env.transportSignature?.publicKey ?? null, kind: env.kind, at: env.createdAt, payload: env.payload }));
|
|
564
|
+
}
|
|
565
|
+
// Backlog: rows stored earlier (e.g. by a recv daemon) but never surfaced.
|
|
566
|
+
const { decrypt } = await import("../src/net/envelope.mjs");
|
|
567
|
+
const freshIds = new Set(fresh.map(e => e.id));
|
|
568
|
+
const backlog = (p.mailbox.inboxUnread?.(500) ?? []).filter(r => !freshIds.has(r.envelope_id));
|
|
569
|
+
if (args.unread) {
|
|
570
|
+
for (const row of backlog) {
|
|
571
|
+
const env = JSON.parse(row.envelope_json);
|
|
572
|
+
let payload = null;
|
|
573
|
+
try { payload = decrypt(p.keychain, env); } catch (_e) { payload = { error: "decrypt failed" }; }
|
|
574
|
+
console.log(JSON.stringify({ new: false, id: env.id, from: env.sender, fromPublicKey: env.transportSignature?.publicKey ?? null, kind: env.kind, at: env.createdAt, payload }));
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
const qCount = (p.mailbox.inboxQuarantined?.(500) ?? []).length;
|
|
578
|
+
console.log(`inbox: ${fresh.length} new, ${backlog.length} older unread${args.unread ? "" : " (--unread to print)"}${qCount ? `, ${qCount} quarantined (pillar trust quarantine)` : ""}`);
|
|
579
|
+
if (args["mark-read"]) {
|
|
580
|
+
for (const env of fresh) p.mailbox.markInboxRead?.(env.id);
|
|
581
|
+
if (args.unread) for (const row of backlog) p.mailbox.markInboxRead?.(row.envelope_id);
|
|
582
|
+
}
|
|
583
|
+
if (args.notify && typeof args.notify === "string" && (pulled > 0 || backlog.length > 0)) {
|
|
584
|
+
const { spawnSync } = await import("node:child_process");
|
|
585
|
+
const r = spawnSync(args.notify, {
|
|
586
|
+
shell: true,
|
|
587
|
+
input: JSON.stringify({ uuaid: p.uuaid, new: pulled, unread: backlog.length }) + "\n",
|
|
588
|
+
env: { ...process.env, PILLAR_NEW: String(pulled), PILLAR_UNREAD: String(backlog.length), PILLAR_UUAID: p.uuaid },
|
|
589
|
+
stdio: ["pipe", "inherit", "inherit"],
|
|
590
|
+
});
|
|
591
|
+
if (r.status !== 0) console.error(`notify hook exited ${r.status}`);
|
|
592
|
+
}
|
|
593
|
+
await p.stop();
|
|
594
|
+
exit(0);
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
async function cmdTrust(args) {
|
|
598
|
+
// pillar trust show policy + lists + quarantine count
|
|
599
|
+
// pillar trust set <open|directory|allowlist>
|
|
600
|
+
// pillar trust allow|block <uuaid> add to list (removes from the other)
|
|
601
|
+
// pillar trust unlist <uuaid> remove from both lists
|
|
602
|
+
// pillar trust quarantine [--release ID] list quarantined mail / release one
|
|
603
|
+
const home = homePath(args);
|
|
604
|
+
const tPath = join(home, "trust.json");
|
|
605
|
+
const t = loadTrustPolicy(home);
|
|
606
|
+
const sub = args._[1];
|
|
607
|
+
const save = () => writeFileSync(tPath, JSON.stringify({ policy: t.policy, allow: t.allow, block: t.block }, null, 2));
|
|
608
|
+
if (!sub) {
|
|
609
|
+
console.log(`policy: ${t.policy}${t.exists ? "" : " (no trust.json — default)"}`);
|
|
610
|
+
console.log(`allow (${t.allow.length}):`); for (const u of t.allow) console.log(` ${u}`);
|
|
611
|
+
console.log(`block (${t.block.length}):`); for (const u of t.block) console.log(` ${u}`);
|
|
612
|
+
const { Mailbox } = await import("../src/mailbox/mailbox.mjs");
|
|
613
|
+
const mb = new Mailbox(join(home, "mailbox.db"));
|
|
614
|
+
const q = mb.inboxQuarantined(500);
|
|
615
|
+
console.log(`quarantined: ${q.length} (pillar trust quarantine to list)`);
|
|
616
|
+
mb.close();
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
if (sub === "set") {
|
|
620
|
+
const pol = args._[2];
|
|
621
|
+
if (!POLICIES.includes(pol)) { console.error(`usage: pillar trust set <${POLICIES.join("|")}>`); exit(2); }
|
|
622
|
+
t.policy = pol; save();
|
|
623
|
+
console.log(`policy: ${pol} (${tPath})`);
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
if (sub === "allow" || sub === "block") {
|
|
627
|
+
const u = args._[2];
|
|
628
|
+
if (!u?.startsWith("uuaid:")) { console.error(`usage: pillar trust ${sub} <uuaid:...>`); exit(2); }
|
|
629
|
+
t.allow = t.allow.filter(x => x !== u);
|
|
630
|
+
t.block = t.block.filter(x => x !== u);
|
|
631
|
+
t[sub === "allow" ? "allow" : "block"].push(u);
|
|
632
|
+
save();
|
|
633
|
+
console.log(`${sub}: ${u}`);
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
636
|
+
if (sub === "unlist") {
|
|
637
|
+
const u = args._[2];
|
|
638
|
+
t.allow = t.allow.filter(x => x !== u);
|
|
639
|
+
t.block = t.block.filter(x => x !== u);
|
|
640
|
+
save();
|
|
641
|
+
console.log(`unlisted: ${u}`);
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
if (sub === "quarantine") {
|
|
645
|
+
const { Mailbox } = await import("../src/mailbox/mailbox.mjs");
|
|
646
|
+
const mb = new Mailbox(join(home, "mailbox.db"));
|
|
647
|
+
if (args.release) {
|
|
648
|
+
mb.db.prepare("update inbox set quarantined = null where envelope_id = ?").run(args.release);
|
|
649
|
+
console.log(`released: ${args.release} (now visible to pillar inbox --unread)`);
|
|
650
|
+
mb.close();
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
for (const row of mb.inboxQuarantined(500)) {
|
|
654
|
+
console.log(JSON.stringify({ id: row.envelope_id, from: row.sender, reason: row.quarantined, at: row.received_at }));
|
|
655
|
+
}
|
|
656
|
+
mb.close();
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
console.error("usage: pillar trust [set <policy>|allow <uuaid>|block <uuaid>|unlist <uuaid>|quarantine [--release ID]]");
|
|
660
|
+
exit(2);
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
async function cmdTier(args) {
|
|
664
|
+
// pillar tier show installed grant
|
|
665
|
+
// pillar tier issue <uuaid> <tier> [--expires ISO] [--rate N] [--max-body N] [--out FILE]
|
|
666
|
+
// sign a grant with THIS home's key (issuer)
|
|
667
|
+
// pillar tier install <grant.json> install a received grant into this home
|
|
668
|
+
// pillar tier verify <grant.json> --issuer HEX verify signature/expiry locally
|
|
669
|
+
const { makeTierGrant, verifyTierGrant, grantFingerprint, TIER_DEFAULTS } =
|
|
670
|
+
await import("../src/identity/tier.mjs");
|
|
671
|
+
const home = homePath(args);
|
|
672
|
+
const sub = args._[1];
|
|
673
|
+
if (!sub) {
|
|
674
|
+
const tgPath = join(home, "tier-grant.json");
|
|
675
|
+
if (!existsSync(tgPath)) { console.log("no tier grant installed (community tier)"); return; }
|
|
676
|
+
const g = JSON.parse(readFileSync(tgPath, "utf-8"));
|
|
677
|
+
console.log(`tier: ${g.tier} rate: ${g.envelopesPerMin}/min maxBody: ${g.maxBodyBytes}B`);
|
|
678
|
+
console.log(`subject: ${g.uuaid}`);
|
|
679
|
+
console.log(`issuer: ${g.issuer}`);
|
|
680
|
+
console.log(`expires: ${g.expires} fingerprint: ${grantFingerprint(g)}`);
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
if (sub === "issue") {
|
|
684
|
+
const [, , uuaid, tier] = args._;
|
|
685
|
+
if (!uuaid || !TIER_DEFAULTS[tier]) {
|
|
686
|
+
console.error(`usage: pillar tier issue <uuaid:...> <${Object.keys(TIER_DEFAULTS).join("|")}> [--expires ISO] [--rate N] [--max-body BYTES] [--out FILE]`);
|
|
687
|
+
exit(2);
|
|
688
|
+
}
|
|
689
|
+
const kc = new Keychain(join(home, "keychain.json"));
|
|
690
|
+
kc.load({ passphrase: process.env.PILLAR_PASSPHRASE ?? "" });
|
|
691
|
+
const expires = args.expires ?? new Date(Date.now() + 365 * 86400_000).toISOString();
|
|
692
|
+
const grant = makeTierGrant(kc, {
|
|
693
|
+
uuaid, tier, expires,
|
|
694
|
+
envelopesPerMin: args.rate ? Number(args.rate) : undefined,
|
|
695
|
+
maxBodyBytes: args["max-body"] ? Number(args["max-body"]) : undefined,
|
|
696
|
+
});
|
|
697
|
+
const out = typeof args.out === "string" ? args.out : `tier-grant-${grantFingerprint(grant)}.json`;
|
|
698
|
+
writeFileSync(out, JSON.stringify(grant, null, 2));
|
|
699
|
+
console.log(`issued ${tier} grant for ${uuaid}`);
|
|
700
|
+
console.log(` rate ${grant.envelopesPerMin}/min, maxBody ${grant.maxBodyBytes}B, expires ${expires}`);
|
|
701
|
+
console.log(` issuer ${grant.issuer}`);
|
|
702
|
+
console.log(` -> ${out} (send to the grantee; they run: pillar tier install ${out})`);
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
705
|
+
if (sub === "install") {
|
|
706
|
+
const file = args._[2];
|
|
707
|
+
if (!file || !existsSync(file)) { console.error("usage: pillar tier install <grant.json>"); exit(2); }
|
|
708
|
+
const g = JSON.parse(readFileSync(file, "utf-8"));
|
|
709
|
+
const kc = new Keychain(join(home, "keychain.json"));
|
|
710
|
+
kc.load({ passphrase: process.env.PILLAR_PASSPHRASE ?? "" });
|
|
711
|
+
if (g.uuaid !== kc._identity.uuaid) {
|
|
712
|
+
console.error(`grant subject ${g.uuaid} != this home's identity ${kc._identity.uuaid}`);
|
|
713
|
+
exit(1);
|
|
714
|
+
}
|
|
715
|
+
const v = verifyTierGrant(g, [g.issuer]); // structural check (sig by named issuer, expiry, ceilings)
|
|
716
|
+
if (!v.ok) { console.error(`grant invalid: ${v.reason}`); exit(1); }
|
|
717
|
+
writeFileSync(join(home, "tier-grant.json"), JSON.stringify(g, null, 2));
|
|
718
|
+
console.log(`installed ${g.tier} grant (fingerprint ${grantFingerprint(g)}) — sent with every carrier deliver`);
|
|
719
|
+
console.log(`note: budgets rise only at carriers that trust issuer ${g.issuer.slice(0, 16)}…`);
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
if (sub === "verify") {
|
|
723
|
+
const file = args._[2];
|
|
724
|
+
const issuer = args.issuer;
|
|
725
|
+
if (!file || !existsSync(file) || !issuer) { console.error("usage: pillar tier verify <grant.json> --issuer HEX"); exit(2); }
|
|
726
|
+
const g = JSON.parse(readFileSync(file, "utf-8"));
|
|
727
|
+
const v = verifyTierGrant(g, [String(issuer)]);
|
|
728
|
+
console.log(v.ok ? `VALID: ${g.tier} for ${g.uuaid} until ${g.expires}` : `INVALID: ${v.reason}`);
|
|
729
|
+
exit(v.ok ? 0 : 1);
|
|
730
|
+
}
|
|
731
|
+
console.error("usage: pillar tier [issue|install|verify]");
|
|
732
|
+
exit(2);
|
|
733
|
+
}
|
|
734
|
+
|
|
400
735
|
async function cmdPeers(args) {
|
|
401
736
|
const p = loadPillar(args);
|
|
402
737
|
await p.start();
|
|
@@ -416,11 +751,17 @@ async function main() {
|
|
|
416
751
|
case "send-group": return cmdSendGroup(args);
|
|
417
752
|
case "groups": return cmdGroups(args);
|
|
418
753
|
case "recv": return cmdRecv(args);
|
|
754
|
+
case "inbox": return cmdInbox(args);
|
|
755
|
+
case "trust": return cmdTrust(args);
|
|
756
|
+
case "tier": return cmdTier(args);
|
|
419
757
|
case "peers": return cmdPeers(args);
|
|
758
|
+
case "delegate": return cmdDelegate(args);
|
|
420
759
|
default:
|
|
421
|
-
console.error(`usage: pillar <init|id|send|send-group|groups|recv|peers> [--home DIR]`);
|
|
760
|
+
console.error(`usage: pillar <init|id|send|send-group|groups|recv|inbox|trust|tier|peers|delegate> [--home DIR]`);
|
|
422
761
|
exit(2);
|
|
423
762
|
}
|
|
424
763
|
}
|
|
425
764
|
|
|
426
|
-
|
|
765
|
+
// A carrier or broker refusal prints one line, `<code>: <fix>` (src/net/refusals.mjs);
|
|
766
|
+
// anything else keeps the full error.
|
|
767
|
+
main().catch(e => { console.error(refusalLine(e) ?? e); exit(1); });
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uuaid/pillar",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "2.0.1",
|
|
4
|
+
"description": "Pillar (Protocol for Inter-agent Linked Ledger And Relay) \u2014 the secure delivery layer of UACP, the Universal Agent Communication Protocol. UACP = UUAID (identity) + Envelope (signed, encrypted container) + Pillar (relay, mailbox, routing, store-and-forward). Reference implementation of the UACP delivery layer; includes VDA-1 (IAASO-3901, in ballot) delivery attestation and a blind escrow for the Zilligon ZGN / EVM rails.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "src/index.mjs",
|
|
@@ -10,29 +10,52 @@
|
|
|
10
10
|
"pillar-carrier": "bin/carrier.mjs",
|
|
11
11
|
"pillar-ask": "bin/pillar-ask.mjs",
|
|
12
12
|
"pillar-responder": "bin/pillar-responder.mjs",
|
|
13
|
-
"pillar-seed": "bin/pillar-seed.mjs"
|
|
13
|
+
"pillar-seed": "bin/pillar-seed.mjs",
|
|
14
|
+
"pillar-directory": "bin/pillar-directory.mjs",
|
|
15
|
+
"pillar-escrow": "bin/pillar-escrow.mjs",
|
|
16
|
+
"pillar-blob": "bin/pillar-blob.mjs"
|
|
14
17
|
},
|
|
15
18
|
"exports": {
|
|
16
|
-
".": "./src/index.mjs"
|
|
19
|
+
".": "./src/index.mjs",
|
|
20
|
+
"./keychain": "./src/identity/keychain.mjs",
|
|
21
|
+
"./jcs": "./src/identity/jcs.mjs",
|
|
22
|
+
"./tier": "./src/identity/tier.mjs",
|
|
23
|
+
"./envelope": "./src/net/envelope.mjs",
|
|
24
|
+
"./delegation": "./src/net/delegation.mjs",
|
|
25
|
+
"./e2e": "./src/crypto/e2e.mjs",
|
|
26
|
+
"./carrier-client": "./src/net/carrier-client.mjs",
|
|
27
|
+
"./seed-tier": "./src/net/seed-tier.mjs",
|
|
28
|
+
"./seed-registry": "./src/net/seed-registry.mjs",
|
|
29
|
+
"./blob": "./src/blob/blob.mjs",
|
|
30
|
+
"./blob/http": "./src/blob/http.mjs",
|
|
31
|
+
"./blob/store": "./src/blob/store.mjs",
|
|
32
|
+
"./blob/broker": "./src/blob/broker.mjs",
|
|
33
|
+
"./refusals": "./src/net/refusals.mjs",
|
|
34
|
+
"./package.json": "./package.json"
|
|
17
35
|
},
|
|
18
36
|
"files": [
|
|
19
37
|
"src",
|
|
20
38
|
"bin",
|
|
21
39
|
"README.md",
|
|
22
40
|
"INTERACTION-CONVENTION.md",
|
|
41
|
+
"HOSTING-LIMITS.md",
|
|
23
42
|
"LICENSE"
|
|
24
43
|
],
|
|
25
44
|
"scripts": {
|
|
26
45
|
"test": "node --test --test-reporter spec test/unit/*.test.mjs",
|
|
27
46
|
"test:int": "node --test --test-reporter spec test/integration/*.test.mjs",
|
|
28
47
|
"test:chaos": "node --test --test-reporter spec test/chaos/*.test.mjs",
|
|
29
|
-
"test:all": "node --test --test-reporter spec test/unit/*.test.mjs test/integration/*.test.mjs test/chaos/*.test.mjs"
|
|
48
|
+
"test:all": "node --test --test-reporter spec test/unit/*.test.mjs test/integration/*.test.mjs test/chaos/*.test.mjs",
|
|
49
|
+
"preflight": "node scripts/preflight-publish.mjs",
|
|
50
|
+
"prepublishOnly": "node scripts/preflight-publish.mjs",
|
|
51
|
+
"postpublish": "node scripts/verify-publish.mjs"
|
|
30
52
|
},
|
|
31
53
|
"engines": {
|
|
32
|
-
"node": ">=
|
|
54
|
+
"node": ">=22"
|
|
33
55
|
},
|
|
34
56
|
"dependencies": {
|
|
35
57
|
"@chainsafe/libp2p-noise": "^17.0.0",
|
|
58
|
+
"@noble/hashes": "^2.3.0",
|
|
36
59
|
"@chainsafe/libp2p-yamux": "^8.0.1",
|
|
37
60
|
"@libp2p/autonat": "^3.0.26",
|
|
38
61
|
"@libp2p/bootstrap": "^12.0.29",
|
|
@@ -48,7 +71,7 @@
|
|
|
48
71
|
"@libp2p/websockets": "^10.1.19",
|
|
49
72
|
"@multiformats/multiaddr": "^13.0.3",
|
|
50
73
|
"@noble/curves": "^2.3.0",
|
|
51
|
-
"better-sqlite3": "^11.
|
|
74
|
+
"better-sqlite3": "^12.11.1",
|
|
52
75
|
"it-pipe": "^3.0.1",
|
|
53
76
|
"it-pushable": "^3.2.4",
|
|
54
77
|
"libp2p": "^3.3.8",
|