edge-book 0.2.5 → 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 +283 -1
  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(",")}]`;
@@ -538,6 +546,209 @@ var EdgeBookStore = class {
538
546
  await this.audit("object.create", identity.agent_id, { object_id, has_attachment: Boolean(attachment) });
539
547
  return object;
540
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
+ }
541
752
  // Issue an `object.read` grant binding ONE object to ONE subject (revocable).
542
753
  async issueObjectGrant(subjectAgentId, objectId, expiresAt = "") {
543
754
  const identity = await this.identity();
@@ -1340,6 +1551,22 @@ async function handleOwnerApi(req, res, url, adapters) {
1340
1551
  sendJson(res, 200, { contacts: await store.contacts(), mutes: await store.contactMutes() });
1341
1552
  return true;
1342
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
+ }
1343
1570
  if (req.method === "GET" && url.pathname === "/api/shared-objects") {
1344
1571
  const objects = await store.sharedObjectsFor();
1345
1572
  sendJson(res, 200, { objects: objects.map((object) => ({ ...object, grant_scope: "object.read" })) });
@@ -3199,7 +3426,15 @@ Local agent:
3199
3426
  edge-book inbox pull --relay <url> [--home <dir>]
3200
3427
  edge-book serve --host <host> --port <port> [--home <dir>]
3201
3428
  edge-book relay serve --host <host> --port <port> --store <dir>
3202
- 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`;
3203
3438
  }
3204
3439
  function takeFlag(args, name) {
3205
3440
  const idx = args.indexOf(name);
@@ -3543,6 +3778,53 @@ Expires in: ${registration.frame.ttl_ms}ms`, json: registration };
3543
3778
  ${JSON.stringify(result, null, 2)}`, json: result };
3544
3779
  }
3545
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
+ }
3546
3828
  throw new EdgeBookError("unknown_command", usage());
3547
3829
  }
3548
3830
  async function runCli(args) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "edge-book",
3
- "version": "0.2.5",
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",