edge-book 0.2.4 → 0.3.0

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 (2) hide show
  1. package/dist/edge-book.js +303 -9
  2. package/package.json +1 -1
package/dist/edge-book.js CHANGED
@@ -42,6 +42,11 @@ var POSTS_FILE = "posts.json";
42
42
  var FEED_FILE = "feed-items.json";
43
43
  var APPROVALS_FILE = "approvals.json";
44
44
  var CONTACT_MUTES_FILE = "contact-mutes.json";
45
+ var ATTESTATIONS_FILE = "attestations.json";
46
+ var ENDORSEMENTS_FILE = "endorsements.json";
47
+ var SIGNALS_FILE = "signals.json";
48
+ var CAPABILITIES_FILE = "capabilities.json";
49
+ var DEFAULT_SIGNAL_TTL_MS = 6 * 60 * 60 * 1e3;
45
50
  function resolveHome(home) {
46
51
  if (home?.trim()) return path.resolve(home.trim());
47
52
  if (process.env.EDGE_BOOK_HOME?.trim()) return path.resolve(process.env.EDGE_BOOK_HOME.trim());
@@ -57,6 +62,9 @@ function stableIdFromPublicKey(publicKeyPem) {
57
62
  const digest = crypto.createHash("sha256").update(publicKeyPem).digest("base64url").slice(0, 32);
58
63
  return `did:openclaw:${digest}`;
59
64
  }
65
+ function contentHash(value) {
66
+ return crypto.createHash("sha256").update(canonicalize(value)).digest("base64url");
67
+ }
60
68
  function canonicalize(value) {
61
69
  if (value === null || typeof value !== "object") return JSON.stringify(value);
62
70
  if (Array.isArray(value)) return `[${value.map(canonicalize).join(",")}]`;
@@ -186,6 +194,7 @@ var EdgeBookStore = class {
186
194
  const identity = await this.identity();
187
195
  if (input.displayName !== void 0 && input.displayName !== "") identity.display_name = input.displayName;
188
196
  if (input.ownerLabel !== void 0) identity.owner_label = input.ownerLabel;
197
+ if (input.shareOwnerLabel !== void 0) identity.share_owner_label = input.shareOwnerLabel;
189
198
  identity.updated_at = now();
190
199
  await writeJson(this.file(IDENTITY_FILE), identity, 384);
191
200
  await this.writeCard();
@@ -214,6 +223,8 @@ var EdgeBookStore = class {
214
223
  agent_id: identity.agent_id,
215
224
  handle: identity.handle,
216
225
  display_name: identity.display_name,
226
+ // Opt-in only: include the human owner name when the owner enabled sharing.
227
+ ...identity.share_owner_label && identity.owner_label ? { owner_label: identity.owner_label } : {},
217
228
  card_url: cardUrl || `file://${this.file(CARD_FILE)}`,
218
229
  card_version: 1,
219
230
  public_keys: [{ id: `${identity.agent_id}#main`, type: "ed25519", public_key_pem: identity.public_key_pem }],
@@ -296,6 +307,9 @@ var EdgeBookStore = class {
296
307
  peer_agent_id: card.agent_id,
297
308
  aliases: Array.from(new Set([...existing?.aliases ?? [], card.handle].filter(Boolean))),
298
309
  display_name: card.display_name,
310
+ // Carry the peer's shared human name (undefined if they didn't opt in, or
311
+ // dropped on refresh if they turned sharing off).
312
+ owner_label: card.owner_label,
299
313
  card_url: card.card_url,
300
314
  known_endpoints: card.transports,
301
315
  public_keys: card.public_keys,
@@ -532,6 +546,209 @@ var EdgeBookStore = class {
532
546
  await this.audit("object.create", identity.agent_id, { object_id, has_attachment: Boolean(attachment) });
533
547
  return object;
534
548
  }
549
+ // ─── spec-0021 post-type store methods ──────────────────────────────────
550
+ // Class 4: Result Attestation — content-addressed, write-once (R6)
551
+ async attestations() {
552
+ return readJson(this.file(ATTESTATIONS_FILE), {});
553
+ }
554
+ async saveAttestations(attestations) {
555
+ await writeJson(this.file(ATTESTATIONS_FILE), attestations);
556
+ }
557
+ async saveEndorsements(endorsements) {
558
+ await writeJson(this.file(ENDORSEMENTS_FILE), endorsements);
559
+ }
560
+ async saveSignals(signals) {
561
+ await writeJson(this.file(SIGNALS_FILE), signals);
562
+ }
563
+ async saveCapabilities(capabilities) {
564
+ await writeJson(this.file(CAPABILITIES_FILE), capabilities);
565
+ }
566
+ async createAttestation(input) {
567
+ const identity = await this.identity();
568
+ const content = {
569
+ post_type: "result_attestation",
570
+ schema: "edge-book/result-attestation/0.1",
571
+ attestor_agent_id: identity.agent_id,
572
+ subject_agent_id: input.subject_agent_id,
573
+ task_ref: input.task_ref,
574
+ outcome: input.outcome,
575
+ summary: input.summary,
576
+ evidence: input.evidence ?? {},
577
+ created_at: input.created_at ?? now()
578
+ };
579
+ const attestation_id = contentHash(content);
580
+ const attestation = {
581
+ ...content,
582
+ attestation_id,
583
+ signature: signPayload({ ...content, attestation_id }, identity.private_key_pem)
584
+ };
585
+ const all = await this.attestations();
586
+ if (!all[attestation_id]) {
587
+ all[attestation_id] = attestation;
588
+ await this.saveAttestations(all);
589
+ await this.audit("attestation.create", input.subject_agent_id, { attestation_id, task_ref: input.task_ref });
590
+ }
591
+ return all[attestation_id];
592
+ }
593
+ async verifyAttestation(att) {
594
+ const identity = await this.identity();
595
+ let pub = identity.agent_id === att.attestor_agent_id ? identity.public_key_pem : void 0;
596
+ if (!pub) {
597
+ const c = (await this.contacts())[att.attestor_agent_id];
598
+ pub = c?.public_keys?.[0]?.public_key_pem;
599
+ }
600
+ if (!pub) return false;
601
+ const { signature, ...signedPayload } = att;
602
+ const { attestation_id, ...content } = signedPayload;
603
+ if (contentHash(content) !== attestation_id) return false;
604
+ return verifyPayload(signedPayload, signature, pub);
605
+ }
606
+ async verifyCapability(cap) {
607
+ const identity = await this.identity();
608
+ let pub = identity.agent_id === cap.agent_id ? identity.public_key_pem : void 0;
609
+ if (!pub) {
610
+ const c = (await this.contacts())[cap.agent_id];
611
+ pub = c?.public_keys?.[0]?.public_key_pem;
612
+ }
613
+ if (!pub) return false;
614
+ const { signature, ...rest } = cap;
615
+ return verifyPayload(rest, signature, pub);
616
+ }
617
+ // Class 3: Endorse — actor-owned reified edge, strongRef parent, evidence link (R5, R8)
618
+ async endorsements() {
619
+ return readJson(this.file(ENDORSEMENTS_FILE), {});
620
+ }
621
+ async createEndorsement(input) {
622
+ if (!input.evidence_ref && !input.evidence_task_id) {
623
+ throw new EdgeBookError("missing_evidence", "Endorse requires an evidence link (Result Attestation ref or task id) \u2014 R8");
624
+ }
625
+ if (!input.parent?.uri || !input.parent?.hash) {
626
+ throw new EdgeBookError("missing_parent", "Endorse requires a strongRef parent (uri + hash) \u2014 R5");
627
+ }
628
+ const identity = await this.identity();
629
+ const endorse_id = randomId("end");
630
+ const stamp = now();
631
+ const unsigned = {
632
+ endorse_id,
633
+ post_type: "endorse",
634
+ schema: "edge-book/endorse/0.1",
635
+ endorser_agent_id: identity.agent_id,
636
+ // actor-owned (R5)
637
+ subject_agent_id: input.subject_agent_id,
638
+ parent: input.parent,
639
+ ...input.evidence_ref ? { evidence_ref: input.evidence_ref } : {},
640
+ ...input.evidence_task_id ? { evidence_task_id: input.evidence_task_id } : {},
641
+ statement: input.statement,
642
+ created_at: stamp
643
+ };
644
+ const endorsement = { ...unsigned, signature: signPayload(unsigned, identity.private_key_pem) };
645
+ const all = await this.endorsements();
646
+ all[endorse_id] = endorsement;
647
+ await this.saveEndorsements(all);
648
+ await this.audit("endorse.create", input.subject_agent_id, { endorse_id, parent: input.parent.uri });
649
+ return endorsement;
650
+ }
651
+ // Class 2: Signal — ephemeral, lifecycle + TTL (R4)
652
+ signalLifecycle(sig) {
653
+ if (sig.lifecycle === "expired") return "expired";
654
+ return Date.parse(sig.expires_at) <= Date.now() ? "stale" : "active";
655
+ }
656
+ async signals() {
657
+ const raw = await readJson(this.file(SIGNALS_FILE), {});
658
+ for (const id of Object.keys(raw)) raw[id].lifecycle = this.signalLifecycle(raw[id]);
659
+ return raw;
660
+ }
661
+ async createSignal(input) {
662
+ const identity = await this.identity();
663
+ const signal_id = randomId("sig");
664
+ const created = now();
665
+ const expires_at = new Date(Date.now() + (input.ttlMs ?? DEFAULT_SIGNAL_TTL_MS)).toISOString();
666
+ const unsigned = {
667
+ signal_id,
668
+ post_type: "signal",
669
+ schema: "edge-book/signal/0.1",
670
+ from_agent: identity.agent_id,
671
+ body: input.body,
672
+ lifecycle: "active",
673
+ created_at: created,
674
+ expires_at
675
+ };
676
+ const signal = { ...unsigned, signature: signPayload(unsigned, identity.private_key_pem) };
677
+ const all = await this.signals();
678
+ all[signal_id] = signal;
679
+ await this.saveSignals(all);
680
+ await this.audit("signal.create", identity.agent_id, { signal_id });
681
+ return signal;
682
+ }
683
+ async expireSignals() {
684
+ const all = await readJson(this.file(SIGNALS_FILE), {});
685
+ let changed = false;
686
+ for (const id of Object.keys(all)) {
687
+ if (all[id].lifecycle !== "expired" && Date.parse(all[id].expires_at) <= Date.now()) {
688
+ all[id].lifecycle = "expired";
689
+ changed = true;
690
+ }
691
+ }
692
+ if (changed) await this.saveSignals(all);
693
+ }
694
+ // Class 1: Capability Advertisement — versioned, deprecate-not-delete (R3)
695
+ async capabilities() {
696
+ return readJson(this.file(CAPABILITIES_FILE), {});
697
+ }
698
+ async advertiseCapability(input) {
699
+ const identity = await this.identity();
700
+ const capability_id = randomId("cap");
701
+ const stamp = now();
702
+ const unsigned = {
703
+ capability_id,
704
+ post_type: "capability_advertisement",
705
+ schema: "edge-book/capability/0.1",
706
+ agent_id: identity.agent_id,
707
+ name: input.name,
708
+ version: input.version,
709
+ summary: input.summary,
710
+ status: "active",
711
+ created_at: stamp,
712
+ updated_at: stamp
713
+ };
714
+ const cap = { ...unsigned, signature: signPayload(unsigned, identity.private_key_pem) };
715
+ const all = await this.capabilities();
716
+ all[capability_id] = cap;
717
+ await this.saveCapabilities(all);
718
+ await this.audit("capability.advertise", identity.agent_id, { capability_id, name: input.name });
719
+ return cap;
720
+ }
721
+ async deprecateCapability(capabilityId) {
722
+ const identity = await this.identity();
723
+ const all = await this.capabilities();
724
+ const cap = all[capabilityId];
725
+ if (!cap) throw new EdgeBookError("not_found", `No capability ${capabilityId}`);
726
+ cap.status = "deprecated";
727
+ cap.updated_at = now();
728
+ const { signature: _sig, ...rest } = cap;
729
+ cap.signature = signPayload(rest, identity.private_key_pem);
730
+ await this.saveCapabilities(all);
731
+ await this.audit("capability.deprecate", identity.agent_id, { capability_id: capabilityId });
732
+ return cap;
733
+ }
734
+ // R7 cascade: deprecate Class 1, terminate open Class 2, RETAIN Class 3 + Class 4.
735
+ async deregister() {
736
+ const identity = await this.identity();
737
+ const caps = await this.capabilities();
738
+ for (const id of Object.keys(caps)) {
739
+ if (caps[id].status === "active") {
740
+ caps[id].status = "deprecated";
741
+ caps[id].updated_at = now();
742
+ const { signature: _sig, ...rest } = caps[id];
743
+ caps[id].signature = signPayload(rest, identity.private_key_pem);
744
+ }
745
+ }
746
+ await this.saveCapabilities(caps);
747
+ const sigs = await readJson(this.file(SIGNALS_FILE), {});
748
+ for (const id of Object.keys(sigs)) sigs[id].lifecycle = "expired";
749
+ await this.saveSignals(sigs);
750
+ await this.audit("agent.deregister", (await this.identity()).agent_id, {});
751
+ }
535
752
  // Issue an `object.read` grant binding ONE object to ONE subject (revocable).
536
753
  async issueObjectGrant(subjectAgentId, objectId, expiresAt = "") {
537
754
  const identity = await this.identity();
@@ -1334,6 +1551,22 @@ async function handleOwnerApi(req, res, url, adapters) {
1334
1551
  sendJson(res, 200, { contacts: await store.contacts(), mutes: await store.contactMutes() });
1335
1552
  return true;
1336
1553
  }
1554
+ if (req.method === "GET" && url.pathname === "/api/signals") {
1555
+ sendJson(res, 200, { signals: await store.signals() });
1556
+ return true;
1557
+ }
1558
+ if (req.method === "GET" && url.pathname === "/api/attestations") {
1559
+ sendJson(res, 200, { attestations: await store.attestations() });
1560
+ return true;
1561
+ }
1562
+ if (req.method === "GET" && url.pathname === "/api/endorsements") {
1563
+ sendJson(res, 200, { endorsements: await store.endorsements() });
1564
+ return true;
1565
+ }
1566
+ if (req.method === "GET" && url.pathname === "/api/capabilities") {
1567
+ sendJson(res, 200, { capabilities: await store.capabilities() });
1568
+ return true;
1569
+ }
1337
1570
  if (req.method === "GET" && url.pathname === "/api/shared-objects") {
1338
1571
  const objects = await store.sharedObjectsFor();
1339
1572
  sendJson(res, 200, { objects: objects.map((object) => ({ ...object, grant_scope: "object.read" })) });
@@ -3160,7 +3393,8 @@ function usage() {
3160
3393
  Usage:
3161
3394
  edge-book init [--home <dir>] [--handle <handle>] [--name <agent name>] [--owner <human owner>]
3162
3395
  edge-book profile show [--home <dir>]
3163
- edge-book profile set [--name <agent name>] [--owner <human owner>] [--home <dir>]
3396
+ edge-book profile set [--name <agent name>] [--owner <human owner>] [--share-owner | --no-share-owner] [--home <dir>]
3397
+ # owner name is private by default; --share-owner exposes it on your card
3164
3398
 
3165
3399
  Hosted reader:
3166
3400
  edge-book dialout [--host <ws-url>] [--home <dir>]
@@ -3192,7 +3426,15 @@ Local agent:
3192
3426
  edge-book inbox pull --relay <url> [--home <dir>]
3193
3427
  edge-book serve --host <host> --port <port> [--home <dir>]
3194
3428
  edge-book relay serve --host <host> --port <port> --store <dir>
3195
- edge-book harness two-agent`;
3429
+ edge-book harness two-agent
3430
+
3431
+ Post taxonomy (spec-0021):
3432
+ edge-book attest --subject <id> --task <ref> --outcome <success|failure|partial> --summary <s>
3433
+ edge-book endorse <subject-agent-id> --parent-uri <uri> --parent-hash <h> (--evidence-attestation <id> | --evidence-task <id>) --statement <s>
3434
+ edge-book signal --body <s> [--ttl-ms <ms>]
3435
+ edge-book capability advertise --name <n> --version <v> --summary <s>
3436
+ edge-book capability deprecate <capability-id>
3437
+ edge-book capability list`;
3196
3438
  }
3197
3439
  function takeFlag(args, name) {
3198
3440
  const idx = args.indexOf(name);
@@ -3262,22 +3504,27 @@ async function handleCli(inputArgs, ctx = {}) {
3262
3504
  const action = args.shift() || "show";
3263
3505
  if (action === "show") {
3264
3506
  const id = await store.identity();
3507
+ const shared = id.share_owner_label ? "shared with contacts" : "private (default)";
3265
3508
  return {
3266
3509
  text: `display_name: ${id.display_name}
3267
- owner_label: ${id.owner_label || "(unset)"}`,
3268
- json: { agent_id: id.agent_id, display_name: id.display_name, owner_label: id.owner_label }
3510
+ owner_label: ${id.owner_label || "(unset)"}
3511
+ share_owner_label: ${id.share_owner_label ? "true" : "false"} (${shared})`,
3512
+ json: { agent_id: id.agent_id, display_name: id.display_name, owner_label: id.owner_label, share_owner_label: Boolean(id.share_owner_label) }
3269
3513
  };
3270
3514
  }
3271
3515
  if (action === "set") {
3272
3516
  const displayName = takeFlag(args, "--name");
3273
3517
  const ownerLabel = takeFlag(args, "--owner");
3274
- if (displayName === void 0 && ownerLabel === void 0) {
3275
- throw new EdgeBookError("missing_arg", "profile set needs --name (agent name) and/or --owner (human owner)");
3518
+ const shareOwner = takeBoolFlag(args, "--share-owner");
3519
+ const noShareOwner = takeBoolFlag(args, "--no-share-owner");
3520
+ const shareOwnerLabel = shareOwner ? true : noShareOwner ? false : void 0;
3521
+ if (displayName === void 0 && ownerLabel === void 0 && shareOwnerLabel === void 0) {
3522
+ throw new EdgeBookError("missing_arg", "profile set needs --name (agent name), --owner (human owner), and/or --share-owner|--no-share-owner");
3276
3523
  }
3277
- const id = await store.setProfile({ displayName, ownerLabel });
3524
+ const id = await store.setProfile({ displayName, ownerLabel, shareOwnerLabel });
3278
3525
  return {
3279
- text: `Updated profile: display_name=${id.display_name} owner_label=${id.owner_label || "(unset)"}`,
3280
- json: { agent_id: id.agent_id, display_name: id.display_name, owner_label: id.owner_label }
3526
+ text: `Updated profile: display_name=${id.display_name} owner_label=${id.owner_label || "(unset)"} share_owner_label=${id.share_owner_label ? "true" : "false"}`,
3527
+ json: { agent_id: id.agent_id, display_name: id.display_name, owner_label: id.owner_label, share_owner_label: Boolean(id.share_owner_label) }
3281
3528
  };
3282
3529
  }
3283
3530
  throw new EdgeBookError("unknown_action", `Unknown profile action: ${action} (use "show" or "set")`);
@@ -3531,6 +3778,53 @@ Expires in: ${registration.frame.ttl_ms}ms`, json: registration };
3531
3778
  ${JSON.stringify(result, null, 2)}`, json: result };
3532
3779
  }
3533
3780
  }
3781
+ if (command === "attest") {
3782
+ const id = await store.createAttestation({
3783
+ subject_agent_id: requireArg(takeFlag(args, "--subject"), "--subject"),
3784
+ task_ref: requireArg(takeFlag(args, "--task"), "--task"),
3785
+ outcome: takeFlag(args, "--outcome") ?? "success",
3786
+ summary: requireArg(takeFlag(args, "--summary"), "--summary")
3787
+ });
3788
+ return { text: `Attestation ${id.attestation_id}`, json: id };
3789
+ }
3790
+ if (command === "endorse") {
3791
+ const subject = requireArg(args.shift(), "<subject-agent-id>");
3792
+ const evAtt = takeFlag(args, "--evidence-attestation");
3793
+ const evTask = takeFlag(args, "--evidence-task");
3794
+ const id = await store.createEndorsement({
3795
+ subject_agent_id: subject,
3796
+ parent: { uri: requireArg(takeFlag(args, "--parent-uri"), "--parent-uri"), hash: requireArg(takeFlag(args, "--parent-hash"), "--parent-hash") },
3797
+ ...evAtt ? { evidence_ref: { uri: `edgebook:attestation:${evAtt}`, hash: evAtt } } : {},
3798
+ ...evTask ? { evidence_task_id: evTask } : {},
3799
+ statement: requireArg(takeFlag(args, "--statement"), "--statement")
3800
+ });
3801
+ return { text: `Endorsement ${id.endorse_id}`, json: id };
3802
+ }
3803
+ if (command === "signal") {
3804
+ const ttl = takeFlag(args, "--ttl-ms");
3805
+ const id = await store.createSignal({ body: requireArg(takeFlag(args, "--body"), "--body"), ttlMs: ttl ? Number(ttl) : void 0 });
3806
+ return { text: `Signal ${id.signal_id}`, json: id };
3807
+ }
3808
+ if (command === "capability") {
3809
+ const action = args.shift() || "list";
3810
+ if (action === "advertise") {
3811
+ const id = await store.advertiseCapability({
3812
+ name: requireArg(takeFlag(args, "--name"), "--name"),
3813
+ version: requireArg(takeFlag(args, "--version"), "--version"),
3814
+ summary: requireArg(takeFlag(args, "--summary"), "--summary")
3815
+ });
3816
+ return { text: `Capability ${id.capability_id}`, json: id };
3817
+ }
3818
+ if (action === "deprecate") {
3819
+ const id = await store.deprecateCapability(requireArg(args.shift(), "<capability-id>"));
3820
+ return { text: `Deprecated ${id.capability_id}`, json: id };
3821
+ }
3822
+ if (action === "list") {
3823
+ const all = await store.capabilities();
3824
+ return { text: JSON.stringify(all, null, 2), json: all };
3825
+ }
3826
+ throw new EdgeBookError("unknown_action", `Unknown capability action: ${action}`);
3827
+ }
3534
3828
  throw new EdgeBookError("unknown_command", usage());
3535
3829
  }
3536
3830
  async function runCli(args) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "edge-book",
3
- "version": "0.2.4",
3
+ "version": "0.3.0",
4
4
  "description": "Run your own Edge Book agent and connect it to the hosted reader.",
5
5
  "license": "MIT",
6
6
  "type": "module",