@provenonce/sdk 0.14.0 → 0.17.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/dist/index.js CHANGED
@@ -29,6 +29,7 @@ __export(index_exports, {
29
29
  ProvenonceError: () => ProvenonceError,
30
30
  RateLimitError: () => RateLimitError,
31
31
  ServerError: () => ServerError,
32
+ SigilRequiredError: () => SigilRequiredError,
32
33
  StateError: () => StateError,
33
34
  ValidationError: () => ValidationError,
34
35
  computeBeat: () => computeBeat,
@@ -46,6 +47,7 @@ var ErrorCode = /* @__PURE__ */ ((ErrorCode2) => {
46
47
  ErrorCode2["VALIDATION"] = "VALIDATION";
47
48
  ErrorCode2["AUTH_INVALID"] = "AUTH_INVALID";
48
49
  ErrorCode2["AUTH_MISSING"] = "AUTH_MISSING";
50
+ ErrorCode2["SIGIL_REQUIRED"] = "SIGIL_REQUIRED";
49
51
  ErrorCode2["RATE_LIMITED"] = "RATE_LIMITED";
50
52
  ErrorCode2["AGENT_FROZEN"] = "AGENT_FROZEN";
51
53
  ErrorCode2["AGENT_NOT_INITIALIZED"] = "AGENT_NOT_INITIALIZED";
@@ -78,6 +80,13 @@ var AuthError = class extends ProvenonceError {
78
80
  this.name = "AuthError";
79
81
  }
80
82
  };
83
+ var SigilRequiredError = class extends ProvenonceError {
84
+ constructor(message = "SIGIL required: purchase a SIGIL before sending heartbeats.") {
85
+ super(message, "SIGIL_REQUIRED" /* SIGIL_REQUIRED */, 403);
86
+ this.name = "SigilRequiredError";
87
+ Object.setPrototypeOf(this, new.target.prototype);
88
+ }
89
+ };
81
90
  var RateLimitError = class extends ProvenonceError {
82
91
  constructor(message, statusCode = 429, retryAfterMs) {
83
92
  super(message, "RATE_LIMITED" /* RATE_LIMITED */, statusCode);
@@ -118,7 +127,42 @@ var ServerError = class extends ProvenonceError {
118
127
  };
119
128
  function mapApiError(statusCode, body, path) {
120
129
  const msg = typeof body.error === "string" ? body.error : `API error ${statusCode}`;
130
+ const apiCode = typeof body.code === "string" ? body.code : void 0;
131
+ if (apiCode) {
132
+ switch (apiCode) {
133
+ case "SIGIL_REQUIRED":
134
+ return new SigilRequiredError(msg);
135
+ case "AGENT_FROZEN":
136
+ return new FrozenError(msg);
137
+ case "AUTH_MISSING":
138
+ return new AuthError(msg, "AUTH_MISSING" /* AUTH_MISSING */, statusCode);
139
+ case "AUTH_INVALID":
140
+ case "AUTH_FORBIDDEN":
141
+ return new AuthError(msg, "AUTH_INVALID" /* AUTH_INVALID */, statusCode);
142
+ case "RATE_LIMITED":
143
+ case "HEARTBEAT_TOO_SOON":
144
+ case "VOLUME_CAP_REACHED":
145
+ case "SPAWN_LIMIT_REACHED": {
146
+ const retryAfter = typeof body.retry_after_ms === "number" ? body.retry_after_ms : void 0;
147
+ return new RateLimitError(msg, statusCode, retryAfter);
148
+ }
149
+ case "AGENT_NOT_FOUND":
150
+ return new NotFoundError(msg, statusCode);
151
+ case "AGENT_NOT_INITIALIZED":
152
+ return new StateError(msg, "not_initialized", "AGENT_NOT_INITIALIZED" /* AGENT_NOT_INITIALIZED */);
153
+ case "AGENT_WRONG_STATE":
154
+ case "SPAWN_CONFLICT":
155
+ return new StateError(msg, "conflict", "AGENT_WRONG_STATE" /* AGENT_WRONG_STATE */);
156
+ case "SERVER_ERROR":
157
+ case "DB_ERROR":
158
+ case "DB_NOT_CONFIGURED":
159
+ return new ServerError(msg, statusCode);
160
+ }
161
+ }
121
162
  if (statusCode === 401 || statusCode === 403) {
163
+ if (statusCode === 403 && body.sigil_required === true) {
164
+ return new SigilRequiredError(msg);
165
+ }
122
166
  const code = statusCode === 401 ? "AUTH_MISSING" /* AUTH_MISSING */ : "AUTH_INVALID" /* AUTH_INVALID */;
123
167
  return new AuthError(msg, code, statusCode);
124
168
  }
@@ -349,7 +393,7 @@ async function register(name, options) {
349
393
  if (!res.ok) throw mapApiError(res.status, data, "/api/v1/register");
350
394
  return data;
351
395
  }
352
- var BeatAgent = class {
396
+ var BeatAgent = class _BeatAgent {
353
397
  constructor(config) {
354
398
  this.chain = [];
355
399
  this.difficulty = 1e3;
@@ -361,9 +405,10 @@ var BeatAgent = class {
361
405
  this.heartbeatInterval = null;
362
406
  this.globalBeat = 0;
363
407
  this.globalAnchorHash = "";
408
+ this.cachedIdentityClass = "";
364
409
  // ── PHASE 2: SIGIL + HEARTBEAT + PROOF ──
365
- /** Cached lineage proof from the most recent heartbeat or SIGIL purchase */
366
- this.cachedProof = null;
410
+ /** Cached passport from the most recent heartbeat or SIGIL purchase */
411
+ this.cachedPassport = null;
367
412
  if (!config.apiKey || typeof config.apiKey !== "string") {
368
413
  throw new ValidationError("BeatAgentConfig.apiKey is required (must be a non-empty string)");
369
414
  }
@@ -474,10 +519,12 @@ var BeatAgent = class {
474
519
  * Start the autonomous heartbeat loop.
475
520
  * Phase 2: Sends paid heartbeats at regular intervals.
476
521
  *
477
- * @param paymentTxFn - Optional function that returns a payment tx for each heartbeat.
478
- * If not provided, uses 'devnet-skip' (devnet only).
522
+ * @param paymentTxFn - Function that returns a Solana payment tx signature for each heartbeat (required).
479
523
  */
480
524
  startHeartbeat(paymentTxFn) {
525
+ if (!paymentTxFn) {
526
+ throw new ValidationError("paymentTxFn is required. Provide a function that returns a Solana transaction signature.");
527
+ }
481
528
  if (this.heartbeatInterval) {
482
529
  this.log("Heartbeat already running.");
483
530
  return;
@@ -495,7 +542,7 @@ var BeatAgent = class {
495
542
  return;
496
543
  }
497
544
  try {
498
- const paymentTx = paymentTxFn ? await paymentTxFn() : "devnet-skip";
545
+ const paymentTx = await paymentTxFn();
499
546
  await this.heartbeat(paymentTx);
500
547
  consecutiveErrors = 0;
501
548
  } catch (err) {
@@ -641,13 +688,16 @@ var BeatAgent = class {
641
688
  const genesisHash = (0, import_crypto.createHash)("sha256").update(`provenonce:work-proof-genesis:${this.config.apiKey.slice(0, 16)}:${Date.now()}`).digest("hex");
642
689
  const beats = Math.max(beatsNeeded, 1);
643
690
  let prevHash = genesisHash;
644
- const spotCheckCount = Math.min(5, Math.max(1, Math.floor(beats / 100)));
691
+ const spotCheckCount = Math.max(
692
+ Math.min(beats, 3),
693
+ Math.min(Math.ceil(beats / 1e3), 25)
694
+ );
645
695
  const spotInterval = Math.max(1, Math.floor(beats / (spotCheckCount + 1)));
646
696
  const spotChecks = [];
647
697
  for (let i = 1; i <= beats; i++) {
648
698
  const beat = computeBeat(prevHash, i, difficulty, void 0, anchorHash);
649
699
  prevHash = beat.hash;
650
- if (i % spotInterval === 0 && spotChecks.length < spotCheckCount) {
700
+ if (i === 1 || i === beats || i % spotInterval === 0 && spotChecks.length < spotCheckCount) {
651
701
  spotChecks.push({ index: beat.index, hash: beat.hash, prev: beat.prev });
652
702
  }
653
703
  }
@@ -790,16 +840,19 @@ var BeatAgent = class {
790
840
  }
791
841
  try {
792
842
  const res = await this.api("POST", "/api/v1/sigil", body);
793
- if (res.lineage_proof) {
794
- this.cachedProof = res.lineage_proof;
843
+ const resPassport = res.passport || res.lineage_proof;
844
+ if (resPassport) {
845
+ this.cachedPassport = resPassport;
795
846
  }
796
847
  const sigilStr = res.sigil?.sigil || res.sigil?.identity_class || "";
848
+ this.cachedIdentityClass = res.sigil?.identity_class || "";
797
849
  this.log(`SIGIL purchased: ${sigilStr}`);
798
850
  this.config.onStatusChange("sigil_issued", { sigil: sigilStr });
799
851
  return {
800
852
  ok: true,
801
853
  sigil: res.sigil,
802
- lineage_proof: res.lineage_proof,
854
+ passport: resPassport,
855
+ lineage_proof: resPassport,
803
856
  fee: res.fee
804
857
  };
805
858
  } catch (err) {
@@ -807,6 +860,35 @@ var BeatAgent = class {
807
860
  return { ok: false, error: err.message };
808
861
  }
809
862
  }
863
+ /**
864
+ * Get a SIGIL attribution token from a partner skill's ref_token (RFC-018).
865
+ *
866
+ * Call this after receiving a skill's ref_token in its response.
867
+ * Returns a pvr_ attribution token to include as `at` in purchaseSigil().
868
+ *
869
+ * This is the agent-side half of the two-sided witnessed proof:
870
+ * the skill's server already called /sigil/check for this agent hash.
871
+ * This call completes the proof — our server witnesses both sides.
872
+ *
873
+ * @param partnerRef - The skill's 16-char ref_token
874
+ */
875
+ async getSigilAttribution(partnerRef) {
876
+ if (!partnerRef || typeof partnerRef !== "string" || partnerRef.length !== 16) {
877
+ return { ok: false, error: "partnerRef must be a 16-character ref_token from the skill" };
878
+ }
879
+ try {
880
+ const res = await this.api("POST", "/api/v1/sigil/check", { partner_ref: partnerRef });
881
+ return {
882
+ ok: true,
883
+ attribution_token: res.attribution_token,
884
+ signup_url: res.signup_url ?? null,
885
+ already_has_sigil: res.already_has_sigil ?? false
886
+ };
887
+ } catch (err) {
888
+ this.config.onError(err, "getSigilAttribution");
889
+ return { ok: false, error: err.message };
890
+ }
891
+ }
810
892
  /**
811
893
  * Update mutable SIGIL metadata fields.
812
894
  * Requires a SIGIL. Cannot modify immutable fields.
@@ -836,17 +918,25 @@ var BeatAgent = class {
836
918
  * Requires a SIGIL. Returns a signed lineage proof.
837
919
  * This is the Phase 2 replacement for pulse() + checkin().
838
920
  *
839
- * @param paymentTx - Solana transaction signature. Omit or 'devnet-skip' on devnet.
921
+ * @param paymentTx - Solana transaction signature (required).
840
922
  * @param globalAnchor - Optional: the global anchor index to reference.
841
923
  */
842
- async heartbeat(paymentTx, globalAnchor) {
924
+ async heartbeat(paymentTx, globalAnchor, opts) {
925
+ if (!paymentTx) {
926
+ throw new ValidationError("paymentTx is required. Provide a Solana transaction signature.");
927
+ }
843
928
  try {
844
- const res = await this.api("POST", "/api/v1/agent/heartbeat", {
845
- payment_tx: paymentTx || "devnet-skip",
929
+ const body = {
930
+ payment_tx: paymentTx,
846
931
  global_anchor: globalAnchor
847
- });
848
- if (res.lineage_proof) {
849
- this.cachedProof = res.lineage_proof;
932
+ };
933
+ if (opts?.sponsoredBy) {
934
+ body.sponsored_by = opts.sponsoredBy;
935
+ }
936
+ const res = await this.api("POST", "/api/v1/agent/heartbeat", body);
937
+ const hbPassport = res.passport || res.lineage_proof;
938
+ if (hbPassport) {
939
+ this.cachedPassport = hbPassport;
850
940
  }
851
941
  if (res.ok) {
852
942
  this.status = "active";
@@ -856,69 +946,216 @@ var BeatAgent = class {
856
946
  }
857
947
  return {
858
948
  ok: res.ok,
859
- lineage_proof: res.lineage_proof,
949
+ passport: hbPassport,
950
+ lineage_proof: hbPassport,
860
951
  heartbeat_count_epoch: res.heartbeat_count_epoch,
861
952
  billing_epoch: res.billing_epoch,
862
953
  current_beat: res.current_beat,
863
- fee: res.fee
954
+ fee: res.fee,
955
+ sponsor: res.sponsor
864
956
  };
865
957
  } catch (err) {
958
+ if (err instanceof SigilRequiredError) {
959
+ return { ok: false, sigil_required: true, error: err.message };
960
+ }
866
961
  this.config.onError(err, "heartbeat");
867
962
  return { ok: false, error: err.message };
868
963
  }
869
964
  }
870
965
  /**
871
- * Reissue a lineage proof. "Reprint, not a renewal."
966
+ * Reissue a passport. "Reprint, not a renewal."
872
967
  * Does NOT create a new lineage event.
873
968
  *
874
- * @param paymentTx - Solana transaction signature. Omit or 'devnet-skip' on devnet.
969
+ * @param paymentTx - Solana transaction signature (required).
875
970
  */
876
- async reissueProof(paymentTx) {
971
+ async reissuePassport(paymentTx) {
972
+ if (!paymentTx) {
973
+ throw new ValidationError("paymentTx is required. Provide a Solana transaction signature.");
974
+ }
877
975
  try {
878
976
  const res = await this.api("POST", "/api/v1/agent/reissue-proof", {
879
- payment_tx: paymentTx || "devnet-skip"
977
+ payment_tx: paymentTx
880
978
  });
881
- if (res.lineage_proof) {
882
- this.cachedProof = res.lineage_proof;
979
+ const rePassport = res.passport || res.lineage_proof;
980
+ if (rePassport) {
981
+ this.cachedPassport = rePassport;
883
982
  }
884
- return { ok: true, lineage_proof: res.lineage_proof };
983
+ return { ok: true, passport: rePassport, lineage_proof: rePassport };
984
+ } catch (err) {
985
+ this.config.onError(err, "reissuePassport");
986
+ return { ok: false, error: err.message };
987
+ }
988
+ }
989
+ /** @deprecated Use `reissuePassport()` instead. Sunset 2026-09-01. */
990
+ async reissueProof(paymentTx) {
991
+ return this.reissuePassport(paymentTx);
992
+ }
993
+ // ============ SPONSORSHIP (RFC-021) ============
994
+ /**
995
+ * Sponsor a child agent's heartbeats.
996
+ * The authenticated agent (this instance) becomes the sponsor, paying heartbeat
997
+ * fees on behalf of the specified child from this agent's operator wallet.
998
+ *
999
+ * @param childHash - Hash of the child agent to sponsor
1000
+ * @param opts.maxHeartbeatsEpoch - Max heartbeats per billing epoch (default: 100, max: 10,000)
1001
+ * @param opts.expiresInHours - Optional TTL for the sponsorship
1002
+ */
1003
+ async sponsorChild(childHash, opts) {
1004
+ try {
1005
+ const res = await this.api("POST", "/api/v1/agent/sponsor", {
1006
+ child_hash: childHash,
1007
+ max_heartbeats_epoch: opts?.maxHeartbeatsEpoch,
1008
+ expires_in_hours: opts?.expiresInHours
1009
+ });
1010
+ return { ok: true, sponsorship: res.sponsorship };
1011
+ } catch (err) {
1012
+ this.config.onError(err, "sponsorChild");
1013
+ return { ok: false, error: err.message };
1014
+ }
1015
+ }
1016
+ /**
1017
+ * Revoke a sponsorship for a child agent.
1018
+ * The child will no longer be able to use this agent's wallet for heartbeat payments.
1019
+ */
1020
+ async revokeSponsor(childHash) {
1021
+ try {
1022
+ await this.api("DELETE", "/api/v1/agent/sponsor", {
1023
+ child_hash: childHash
1024
+ });
1025
+ return { ok: true };
1026
+ } catch (err) {
1027
+ this.config.onError(err, "revokeSponsor");
1028
+ return { ok: false, error: err.message };
1029
+ }
1030
+ }
1031
+ /**
1032
+ * List all active sponsorships for this agent (as parent).
1033
+ */
1034
+ async listSponsorships() {
1035
+ try {
1036
+ const res = await this.api("GET", "/api/v1/agent/sponsor");
1037
+ return { ok: true, sponsorships: res.sponsorships };
885
1038
  } catch (err) {
886
- this.config.onError(err, "reissueProof");
1039
+ this.config.onError(err, "listSponsorships");
887
1040
  return { ok: false, error: err.message };
888
1041
  }
889
1042
  }
890
1043
  /**
891
- * Get the latest cached lineage proof (no network call).
892
- * Returns null if no proof has been obtained yet.
1044
+ * Get the latest cached passport (no network call).
1045
+ * Returns null if no passport has been obtained yet.
893
1046
  */
1047
+ getLatestPassport() {
1048
+ return this.cachedPassport;
1049
+ }
1050
+ /** @deprecated Use `getLatestPassport()` instead. Sunset 2026-09-01. */
894
1051
  getLatestProof() {
895
- return this.cachedProof;
1052
+ return this.getLatestPassport();
896
1053
  }
897
1054
  /**
898
- * Get the agent's passport (alias for getLatestProof).
1055
+ * Get the agent's passport (alias for getLatestPassport).
899
1056
  * The passport is the agent's portable, offline-verifiable credential.
900
1057
  * Returns null if no passport has been issued yet (requires SIGIL + heartbeat).
901
1058
  */
902
1059
  getPassport() {
903
- return this.cachedProof;
1060
+ return this.cachedPassport;
1061
+ }
1062
+ /**
1063
+ * Export this agent's passport in both flat (Passport) and W3C VC envelope formats.
1064
+ * Fetches a freshly signed passport from the server. Free endpoint, rate-limited 10/hr.
1065
+ * Returns null if the request fails.
1066
+ */
1067
+ async exportPassport() {
1068
+ try {
1069
+ const res = await this.api("GET", "/api/v1/agent/passport");
1070
+ const expPassport = res.passport || res.lineage_proof;
1071
+ if (expPassport) {
1072
+ this.cachedPassport = expPassport;
1073
+ }
1074
+ return { passport: expPassport, passport_vc: res.passport_vc, lineage_proof: expPassport };
1075
+ } catch (err) {
1076
+ this.config.onError(err, "exportPassport");
1077
+ return null;
1078
+ }
1079
+ }
1080
+ /**
1081
+ * Wrap a Passport in a W3C Verifiable Credential envelope (client-side).
1082
+ * Does not make any network call. Requires the passport to have authority_key_id.
1083
+ */
1084
+ static proofToPassportVC(proof, authorityKeyId) {
1085
+ const keyId = proof.authority_key_id || authorityKeyId || "unknown";
1086
+ return {
1087
+ "@context": [
1088
+ "https://www.w3.org/2018/credentials/v1",
1089
+ "https://provenonce.io/.well-known/provenonce-passport-v1.jsonld"
1090
+ ],
1091
+ type: ["VerifiableCredential", "ProvenoncePassport"],
1092
+ issuer: {
1093
+ id: "https://provenonce.io",
1094
+ authority_key_id: keyId
1095
+ },
1096
+ issuanceDate: new Date(proof.issued_at).toISOString(),
1097
+ expirationDate: new Date(proof.valid_until).toISOString(),
1098
+ credentialSubject: {
1099
+ id: `provenonce:agent:${proof.agent_hash}`,
1100
+ agent_hash: proof.agent_hash,
1101
+ agent_public_key: proof.agent_public_key,
1102
+ identity_class: proof.identity_class,
1103
+ registered_at_beat: proof.registered_at_beat,
1104
+ sigil_issued_at_beat: proof.sigil_issued_at_beat,
1105
+ last_heartbeat_beat: proof.last_heartbeat_beat,
1106
+ lineage_chain_hash: proof.lineage_chain_hash
1107
+ },
1108
+ proof: {
1109
+ type: "Ed25519Signature2020",
1110
+ created: new Date(proof.issued_at).toISOString(),
1111
+ verificationMethod: `https://provenonce.io/.well-known/provenonce-authority.json#${keyId}`,
1112
+ proofPurpose: "assertionMethod",
1113
+ cryptosuite: "eddsa-provenonce-2026",
1114
+ proofValue: proof.provenonce_signature
1115
+ },
1116
+ format_version: proof.format_version || 1
1117
+ };
904
1118
  }
905
1119
  /**
906
- * Verify a lineage proof locally using the authority public key.
1120
+ * Verify a passport locally using the authority public key.
907
1121
  * Offline verification — no API call, no SOL cost.
908
1122
  *
909
1123
  * Returns a VerificationResult object. The object is truthy when valid,
910
- * so `if (BeatAgent.verifyProofLocally(proof, key))` still works.
1124
+ * so `if (BeatAgent.verifyPassportLocally(proof, key))` still works.
911
1125
  *
912
- * @param proof - The LineageProof to verify
1126
+ * @param proof - The Passport to verify
913
1127
  * @param authorityPubKeyHex - 32-byte hex-encoded Ed25519 public key from /.well-known/provenonce-authority.json
914
1128
  * @param currentBeat - Optional current global beat index (for beatsSinceHeartbeat calculation)
915
1129
  */
916
- static verifyProofLocally(proof, authorityPubKeyHex, currentBeat) {
1130
+ static verifyPassportLocally(proof, authorityPubKeyHex, currentBeat) {
917
1131
  const now = Date.now();
918
1132
  const expired = now > proof.valid_until;
919
1133
  let signatureValid = false;
920
1134
  try {
921
- const canonical = JSON.stringify({
1135
+ const canonical = proof.format_version ? JSON.stringify({
1136
+ format_version: proof.format_version,
1137
+ agent_hash: proof.agent_hash,
1138
+ agent_public_key: proof.agent_public_key,
1139
+ authority_key_id: proof.authority_key_id,
1140
+ identity_class: proof.identity_class,
1141
+ registered_at_beat: proof.registered_at_beat,
1142
+ sigil_issued_at_beat: proof.sigil_issued_at_beat,
1143
+ last_heartbeat_beat: proof.last_heartbeat_beat,
1144
+ lineage_chain_hash: proof.lineage_chain_hash,
1145
+ issued_at: proof.issued_at,
1146
+ valid_until: proof.valid_until
1147
+ }) : proof.authority_key_id ? JSON.stringify({
1148
+ agent_hash: proof.agent_hash,
1149
+ agent_public_key: proof.agent_public_key,
1150
+ authority_key_id: proof.authority_key_id,
1151
+ identity_class: proof.identity_class,
1152
+ registered_at_beat: proof.registered_at_beat,
1153
+ sigil_issued_at_beat: proof.sigil_issued_at_beat,
1154
+ last_heartbeat_beat: proof.last_heartbeat_beat,
1155
+ lineage_chain_hash: proof.lineage_chain_hash,
1156
+ issued_at: proof.issued_at,
1157
+ valid_until: proof.valid_until
1158
+ }) : JSON.stringify({
922
1159
  agent_hash: proof.agent_hash,
923
1160
  agent_public_key: proof.agent_public_key,
924
1161
  identity_class: proof.identity_class,
@@ -944,12 +1181,16 @@ var BeatAgent = class {
944
1181
  const beatsSinceHeartbeat = currentBeat != null ? currentBeat - proof.last_heartbeat_beat : null;
945
1182
  let warning;
946
1183
  if (expired) {
947
- warning = "Proof has expired. Reissue with reissueProof() or send a heartbeat.";
1184
+ warning = "Passport has expired. Reissue with reissuePassport() or send a heartbeat.";
948
1185
  } else if (beatsSinceHeartbeat != null && beatsSinceHeartbeat > 60) {
949
1186
  warning = `Agent is ${beatsSinceHeartbeat} beats behind. Heartbeat may be stale.`;
950
1187
  }
951
1188
  return { valid, signatureValid, expired, lastHeartbeatBeat: proof.last_heartbeat_beat, beatsSinceHeartbeat, warning };
952
1189
  }
1190
+ /** @deprecated Use `verifyPassportLocally()` instead. Sunset 2026-09-01. */
1191
+ static verifyProofLocally(proof, authorityPubKeyHex, currentBeat) {
1192
+ return _BeatAgent.verifyPassportLocally(proof, authorityPubKeyHex, currentBeat);
1193
+ }
953
1194
  // ── STATUS ──
954
1195
  /**
955
1196
  * Get this agent's full beat status from the registry.
@@ -976,6 +1217,14 @@ var BeatAgent = class {
976
1217
  chainLength: this.chain.length
977
1218
  };
978
1219
  }
1220
+ /**
1221
+ * Returns true if this agent has purchased a SIGIL in this session,
1222
+ * or if a cached passport contains an identity_class.
1223
+ * For an authoritative check, use GET /api/v1/agent/me.
1224
+ */
1225
+ hasSigil() {
1226
+ return !!(this.cachedIdentityClass || this.cachedPassport?.identity_class);
1227
+ }
979
1228
  // ── INTERNALS ──
980
1229
  async syncGlobal() {
981
1230
  try {
@@ -991,7 +1240,6 @@ var BeatAgent = class {
991
1240
  }
992
1241
  this.globalBeat = data.anchor.beat_index;
993
1242
  this.globalAnchorHash = data.anchor.hash || "";
994
- if (data.anchor.difficulty) this.difficulty = data.anchor.difficulty;
995
1243
  this.log(`Synced to global beat ${this.globalBeat} (D=${this.difficulty})`);
996
1244
  }
997
1245
  } catch {
@@ -1083,6 +1331,7 @@ function computeBeatsLite(startHash, startIndex, count, difficulty = 1e3, anchor
1083
1331
  ProvenonceError,
1084
1332
  RateLimitError,
1085
1333
  ServerError,
1334
+ SigilRequiredError,
1086
1335
  StateError,
1087
1336
  ValidationError,
1088
1337
  computeBeat,