@provenonce/sdk 0.9.0 → 0.10.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.
package/README.md CHANGED
@@ -169,4 +169,4 @@ Python examples use the REST API directly — no Python SDK needed. See [`exampl
169
169
  - [Live prototype](https://provenonce.io)
170
170
  - [npm package](https://www.npmjs.com/package/@provenonce/sdk)
171
171
  - [API docs](https://provenonce.dev)
172
- - [Whitepaper](https://provenonce.dev/whitepaper)
172
+ - [Whitepaper](https://provenonce.dev/concepts/architecture)
package/dist/index.d.mts CHANGED
@@ -28,6 +28,18 @@
28
28
  */
29
29
  /** SIGIL identity class — determines tier pricing and heartbeat volume caps */
30
30
  type IdentityClass = 'narrow_task' | 'autonomous' | 'orchestrator';
31
+ /** SIGIL trust governance tier — orthogonal to identity_class (fee axis) */
32
+ type SigilTier = 'sov' | 'org' | 'ind' | 'eph' | 'sbx';
33
+ /** Substrate — what the agent runs on */
34
+ type Substrate = 'frontier' | 'open' | 'local' | 'symbolic' | 'hybrid' | 'human';
35
+ /** Substrate provider */
36
+ type SubstrateProvider = 'anthropic' | 'openai' | 'google' | 'meta' | 'mistral' | 'xai' | 'cohere' | 'deepseek' | 'custom';
37
+ /** Capability — what the agent primarily does */
38
+ type Capability = 'analyst' | 'executor' | 'orchestrator' | 'guardian' | 'retriever' | 'renderer' | 'witness';
39
+ /** Protocol — how to reach the agent */
40
+ type SigilProtocol = 'http' | 'grpc' | 'websocket' | 'mcp' | 'a2a' | 'custom';
41
+ /** Compliance regime */
42
+ type ComplianceRegime = 'gdpr' | 'pdpa' | 'hipaa' | 'sox' | 'aisi' | 'none' | 'custom';
31
43
  /**
32
44
  * Ed25519-signed lineage proof — portable, offline-verifiable credential.
33
45
  * Also known as the agent's "passport" — a cryptographic proof of identity
@@ -47,10 +59,48 @@ interface LineageProof {
47
59
  }
48
60
  /** Passport = LineageProof. The agent's portable, offline-verifiable credential. */
49
61
  type Passport = LineageProof;
62
+ /** Options for purchasing a SIGIL with full namespace */
63
+ interface SigilPurchaseOptions {
64
+ identity_class: IdentityClass;
65
+ principal: string;
66
+ tier: SigilTier;
67
+ name?: string;
68
+ payment_tx: string;
69
+ substrate?: Substrate;
70
+ substrate_provider?: SubstrateProvider;
71
+ substrate_model?: string;
72
+ capability?: Capability;
73
+ capability_scope?: string;
74
+ tools?: string[];
75
+ modality_input?: string[];
76
+ modality_output?: string[];
77
+ protocol?: SigilProtocol;
78
+ endpoint?: string;
79
+ compliance_regime?: ComplianceRegime;
80
+ }
81
+ /** Mutable SIGIL metadata fields for PATCH updates */
82
+ interface SigilMutableFields {
83
+ substrate?: Substrate;
84
+ substrate_provider?: SubstrateProvider;
85
+ substrate_model?: string;
86
+ capability?: Capability;
87
+ capability_scope?: string;
88
+ generation_trigger?: string;
89
+ tools?: string[];
90
+ modality_input?: string[];
91
+ modality_output?: string[];
92
+ protocol?: SigilProtocol;
93
+ endpoint?: string;
94
+ compliance_regime?: ComplianceRegime;
95
+ }
50
96
  /** Result from purchasing a SIGIL (Structured Identity Governance and Intelligent Lookup) */
51
97
  interface SigilResult {
52
98
  ok: boolean;
53
99
  sigil?: {
100
+ sigil: string;
101
+ sigil_name: string;
102
+ principal: string;
103
+ tier: SigilTier;
54
104
  identity_class: IdentityClass;
55
105
  issued_at_beat: number;
56
106
  birth_tx: string | null;
@@ -64,6 +114,29 @@ interface SigilResult {
64
114
  };
65
115
  error?: string;
66
116
  }
117
+ /** Result from updating mutable SIGIL metadata */
118
+ interface MetadataUpdateResult {
119
+ ok: boolean;
120
+ sigil?: string;
121
+ generation?: number;
122
+ updated_fields?: string[];
123
+ error?: string;
124
+ }
125
+ /** Result from offline lineage proof verification */
126
+ interface VerificationResult {
127
+ /** Overall validity: signature is valid AND not expired */
128
+ valid: boolean;
129
+ /** Ed25519 signature verification passed */
130
+ signatureValid: boolean;
131
+ /** Proof has passed its valid_until timestamp */
132
+ expired: boolean;
133
+ /** The beat index of the agent's last heartbeat */
134
+ lastHeartbeatBeat: number;
135
+ /** Beats elapsed since last heartbeat (null if currentBeat not provided) */
136
+ beatsSinceHeartbeat: number | null;
137
+ /** Human-readable warning if proof is expired or stale */
138
+ warning?: string;
139
+ }
67
140
  /** Result from a paid heartbeat */
68
141
  interface HeartbeatResult {
69
142
  ok: boolean;
@@ -332,10 +405,20 @@ declare class BeatAgent {
332
405
  * SIGILs gate heartbeating, lineage proofs, and offline verification.
333
406
  * One-time purchase — cannot be re-purchased.
334
407
  *
335
- * @param identityClass - 'narrow_task' (0.05 SOL), 'autonomous' (0.15 SOL), or 'orchestrator' (0.35 SOL)
336
- * @param paymentTx - Solana transaction signature proving payment. Use 'devnet-skip' on devnet.
408
+ * @param options - SIGIL purchase options (identity_class, principal, tier, name, payment_tx, + optional metadata)
409
+ *
410
+ * Legacy signature (deprecated):
411
+ * @param identityClass - 'narrow_task' | 'autonomous' | 'orchestrator'
412
+ * @param paymentTx - Solana transaction signature or 'devnet-skip'
413
+ */
414
+ purchaseSigil(optionsOrClass: SigilPurchaseOptions | IdentityClass, paymentTx?: string): Promise<SigilResult>;
415
+ /**
416
+ * Update mutable SIGIL metadata fields.
417
+ * Requires a SIGIL. Cannot modify immutable fields.
418
+ *
419
+ * @param fields - Subset of mutable SIGIL fields to update
337
420
  */
338
- purchaseSigil(identityClass: IdentityClass, paymentTx: string): Promise<SigilResult>;
421
+ updateMetadata(fields: Partial<SigilMutableFields>): Promise<MetadataUpdateResult>;
339
422
  /**
340
423
  * Send a paid heartbeat to the registry.
341
424
  * Requires a SIGIL. Returns a signed lineage proof.
@@ -371,10 +454,14 @@ declare class BeatAgent {
371
454
  * Verify a lineage proof locally using the authority public key.
372
455
  * Offline verification — no API call, no SOL cost.
373
456
  *
457
+ * Returns a VerificationResult object. The object is truthy when valid,
458
+ * so `if (BeatAgent.verifyProofLocally(proof, key))` still works.
459
+ *
374
460
  * @param proof - The LineageProof to verify
375
461
  * @param authorityPubKeyHex - 32-byte hex-encoded Ed25519 public key from /.well-known/provenonce-authority.json
462
+ * @param currentBeat - Optional current global beat index (for beatsSinceHeartbeat calculation)
376
463
  */
377
- static verifyProofLocally(proof: LineageProof, authorityPubKeyHex: string): boolean;
464
+ static verifyProofLocally(proof: LineageProof, authorityPubKeyHex: string, currentBeat?: number): VerificationResult;
378
465
  /**
379
466
  * Get this agent's full beat status from the registry.
380
467
  */
@@ -486,4 +573,4 @@ declare class ServerError extends ProvenonceError {
486
573
  constructor(message: string, statusCode?: number);
487
574
  }
488
575
 
489
- export { type AgentStatus, AuthError, type Beat, BeatAgent, type BeatAgentConfig, type CheckinResult, ErrorCode, FrozenError, type HeartbeatResult, type IdentityClass, type LineageProof, NetworkError, NotFoundError, type Passport, ProvenonceError, RateLimitError, type RegistrationResult, ServerError, type SigilResult, type SpawnResult, StateError, ValidationError, type WalletInfo, computeBeat, computeBeatsLite, generateWalletKeypair, register };
576
+ export { type AgentStatus, AuthError, type Beat, BeatAgent, type BeatAgentConfig, type Capability, type CheckinResult, type ComplianceRegime, ErrorCode, FrozenError, type HeartbeatResult, type IdentityClass, type LineageProof, type MetadataUpdateResult, NetworkError, NotFoundError, type Passport, ProvenonceError, RateLimitError, type RegistrationResult, ServerError, type SigilMutableFields, type SigilProtocol, type SigilPurchaseOptions, type SigilResult, type SigilTier, type SpawnResult, StateError, type Substrate, type SubstrateProvider, ValidationError, type VerificationResult, type WalletInfo, computeBeat, computeBeatsLite, generateWalletKeypair, register };
package/dist/index.d.ts CHANGED
@@ -28,6 +28,18 @@
28
28
  */
29
29
  /** SIGIL identity class — determines tier pricing and heartbeat volume caps */
30
30
  type IdentityClass = 'narrow_task' | 'autonomous' | 'orchestrator';
31
+ /** SIGIL trust governance tier — orthogonal to identity_class (fee axis) */
32
+ type SigilTier = 'sov' | 'org' | 'ind' | 'eph' | 'sbx';
33
+ /** Substrate — what the agent runs on */
34
+ type Substrate = 'frontier' | 'open' | 'local' | 'symbolic' | 'hybrid' | 'human';
35
+ /** Substrate provider */
36
+ type SubstrateProvider = 'anthropic' | 'openai' | 'google' | 'meta' | 'mistral' | 'xai' | 'cohere' | 'deepseek' | 'custom';
37
+ /** Capability — what the agent primarily does */
38
+ type Capability = 'analyst' | 'executor' | 'orchestrator' | 'guardian' | 'retriever' | 'renderer' | 'witness';
39
+ /** Protocol — how to reach the agent */
40
+ type SigilProtocol = 'http' | 'grpc' | 'websocket' | 'mcp' | 'a2a' | 'custom';
41
+ /** Compliance regime */
42
+ type ComplianceRegime = 'gdpr' | 'pdpa' | 'hipaa' | 'sox' | 'aisi' | 'none' | 'custom';
31
43
  /**
32
44
  * Ed25519-signed lineage proof — portable, offline-verifiable credential.
33
45
  * Also known as the agent's "passport" — a cryptographic proof of identity
@@ -47,10 +59,48 @@ interface LineageProof {
47
59
  }
48
60
  /** Passport = LineageProof. The agent's portable, offline-verifiable credential. */
49
61
  type Passport = LineageProof;
62
+ /** Options for purchasing a SIGIL with full namespace */
63
+ interface SigilPurchaseOptions {
64
+ identity_class: IdentityClass;
65
+ principal: string;
66
+ tier: SigilTier;
67
+ name?: string;
68
+ payment_tx: string;
69
+ substrate?: Substrate;
70
+ substrate_provider?: SubstrateProvider;
71
+ substrate_model?: string;
72
+ capability?: Capability;
73
+ capability_scope?: string;
74
+ tools?: string[];
75
+ modality_input?: string[];
76
+ modality_output?: string[];
77
+ protocol?: SigilProtocol;
78
+ endpoint?: string;
79
+ compliance_regime?: ComplianceRegime;
80
+ }
81
+ /** Mutable SIGIL metadata fields for PATCH updates */
82
+ interface SigilMutableFields {
83
+ substrate?: Substrate;
84
+ substrate_provider?: SubstrateProvider;
85
+ substrate_model?: string;
86
+ capability?: Capability;
87
+ capability_scope?: string;
88
+ generation_trigger?: string;
89
+ tools?: string[];
90
+ modality_input?: string[];
91
+ modality_output?: string[];
92
+ protocol?: SigilProtocol;
93
+ endpoint?: string;
94
+ compliance_regime?: ComplianceRegime;
95
+ }
50
96
  /** Result from purchasing a SIGIL (Structured Identity Governance and Intelligent Lookup) */
51
97
  interface SigilResult {
52
98
  ok: boolean;
53
99
  sigil?: {
100
+ sigil: string;
101
+ sigil_name: string;
102
+ principal: string;
103
+ tier: SigilTier;
54
104
  identity_class: IdentityClass;
55
105
  issued_at_beat: number;
56
106
  birth_tx: string | null;
@@ -64,6 +114,29 @@ interface SigilResult {
64
114
  };
65
115
  error?: string;
66
116
  }
117
+ /** Result from updating mutable SIGIL metadata */
118
+ interface MetadataUpdateResult {
119
+ ok: boolean;
120
+ sigil?: string;
121
+ generation?: number;
122
+ updated_fields?: string[];
123
+ error?: string;
124
+ }
125
+ /** Result from offline lineage proof verification */
126
+ interface VerificationResult {
127
+ /** Overall validity: signature is valid AND not expired */
128
+ valid: boolean;
129
+ /** Ed25519 signature verification passed */
130
+ signatureValid: boolean;
131
+ /** Proof has passed its valid_until timestamp */
132
+ expired: boolean;
133
+ /** The beat index of the agent's last heartbeat */
134
+ lastHeartbeatBeat: number;
135
+ /** Beats elapsed since last heartbeat (null if currentBeat not provided) */
136
+ beatsSinceHeartbeat: number | null;
137
+ /** Human-readable warning if proof is expired or stale */
138
+ warning?: string;
139
+ }
67
140
  /** Result from a paid heartbeat */
68
141
  interface HeartbeatResult {
69
142
  ok: boolean;
@@ -332,10 +405,20 @@ declare class BeatAgent {
332
405
  * SIGILs gate heartbeating, lineage proofs, and offline verification.
333
406
  * One-time purchase — cannot be re-purchased.
334
407
  *
335
- * @param identityClass - 'narrow_task' (0.05 SOL), 'autonomous' (0.15 SOL), or 'orchestrator' (0.35 SOL)
336
- * @param paymentTx - Solana transaction signature proving payment. Use 'devnet-skip' on devnet.
408
+ * @param options - SIGIL purchase options (identity_class, principal, tier, name, payment_tx, + optional metadata)
409
+ *
410
+ * Legacy signature (deprecated):
411
+ * @param identityClass - 'narrow_task' | 'autonomous' | 'orchestrator'
412
+ * @param paymentTx - Solana transaction signature or 'devnet-skip'
413
+ */
414
+ purchaseSigil(optionsOrClass: SigilPurchaseOptions | IdentityClass, paymentTx?: string): Promise<SigilResult>;
415
+ /**
416
+ * Update mutable SIGIL metadata fields.
417
+ * Requires a SIGIL. Cannot modify immutable fields.
418
+ *
419
+ * @param fields - Subset of mutable SIGIL fields to update
337
420
  */
338
- purchaseSigil(identityClass: IdentityClass, paymentTx: string): Promise<SigilResult>;
421
+ updateMetadata(fields: Partial<SigilMutableFields>): Promise<MetadataUpdateResult>;
339
422
  /**
340
423
  * Send a paid heartbeat to the registry.
341
424
  * Requires a SIGIL. Returns a signed lineage proof.
@@ -371,10 +454,14 @@ declare class BeatAgent {
371
454
  * Verify a lineage proof locally using the authority public key.
372
455
  * Offline verification — no API call, no SOL cost.
373
456
  *
457
+ * Returns a VerificationResult object. The object is truthy when valid,
458
+ * so `if (BeatAgent.verifyProofLocally(proof, key))` still works.
459
+ *
374
460
  * @param proof - The LineageProof to verify
375
461
  * @param authorityPubKeyHex - 32-byte hex-encoded Ed25519 public key from /.well-known/provenonce-authority.json
462
+ * @param currentBeat - Optional current global beat index (for beatsSinceHeartbeat calculation)
376
463
  */
377
- static verifyProofLocally(proof: LineageProof, authorityPubKeyHex: string): boolean;
464
+ static verifyProofLocally(proof: LineageProof, authorityPubKeyHex: string, currentBeat?: number): VerificationResult;
378
465
  /**
379
466
  * Get this agent's full beat status from the registry.
380
467
  */
@@ -486,4 +573,4 @@ declare class ServerError extends ProvenonceError {
486
573
  constructor(message: string, statusCode?: number);
487
574
  }
488
575
 
489
- export { type AgentStatus, AuthError, type Beat, BeatAgent, type BeatAgentConfig, type CheckinResult, ErrorCode, FrozenError, type HeartbeatResult, type IdentityClass, type LineageProof, NetworkError, NotFoundError, type Passport, ProvenonceError, RateLimitError, type RegistrationResult, ServerError, type SigilResult, type SpawnResult, StateError, ValidationError, type WalletInfo, computeBeat, computeBeatsLite, generateWalletKeypair, register };
576
+ export { type AgentStatus, AuthError, type Beat, BeatAgent, type BeatAgentConfig, type Capability, type CheckinResult, type ComplianceRegime, ErrorCode, FrozenError, type HeartbeatResult, type IdentityClass, type LineageProof, type MetadataUpdateResult, NetworkError, NotFoundError, type Passport, ProvenonceError, RateLimitError, type RegistrationResult, ServerError, type SigilMutableFields, type SigilProtocol, type SigilPurchaseOptions, type SigilResult, type SigilTier, type SpawnResult, StateError, type Substrate, type SubstrateProvider, ValidationError, type VerificationResult, type WalletInfo, computeBeat, computeBeatsLite, generateWalletKeypair, register };
package/dist/index.js CHANGED
@@ -723,26 +723,51 @@ var BeatAgent = class {
723
723
  * SIGILs gate heartbeating, lineage proofs, and offline verification.
724
724
  * One-time purchase — cannot be re-purchased.
725
725
  *
726
- * @param identityClass - 'narrow_task' (0.05 SOL), 'autonomous' (0.15 SOL), or 'orchestrator' (0.35 SOL)
727
- * @param paymentTx - Solana transaction signature proving payment. Use 'devnet-skip' on devnet.
726
+ * @param options - SIGIL purchase options (identity_class, principal, tier, name, payment_tx, + optional metadata)
727
+ *
728
+ * Legacy signature (deprecated):
729
+ * @param identityClass - 'narrow_task' | 'autonomous' | 'orchestrator'
730
+ * @param paymentTx - Solana transaction signature or 'devnet-skip'
728
731
  */
729
- async purchaseSigil(identityClass, paymentTx) {
730
- if (!identityClass || !["narrow_task", "autonomous", "orchestrator"].includes(identityClass)) {
731
- throw new ValidationError("identityClass must be narrow_task, autonomous, or orchestrator");
732
- }
733
- if (!paymentTx || typeof paymentTx !== "string") {
734
- throw new ValidationError('paymentTx is required (Solana transaction signature or "devnet-skip")');
732
+ async purchaseSigil(optionsOrClass, paymentTx) {
733
+ let body;
734
+ if (typeof optionsOrClass === "string") {
735
+ if (!optionsOrClass || !["narrow_task", "autonomous", "orchestrator"].includes(optionsOrClass)) {
736
+ throw new ValidationError("identityClass must be narrow_task, autonomous, or orchestrator");
737
+ }
738
+ if (!paymentTx || typeof paymentTx !== "string") {
739
+ throw new ValidationError('paymentTx is required (Solana transaction signature or "devnet-skip")');
740
+ }
741
+ body = {
742
+ identity_class: optionsOrClass,
743
+ payment_tx: paymentTx
744
+ // Legacy calls without principal/tier — server will require these now
745
+ // Callers must migrate to the options object form
746
+ };
747
+ } else {
748
+ const opts = optionsOrClass;
749
+ if (!opts.identity_class || !["narrow_task", "autonomous", "orchestrator"].includes(opts.identity_class)) {
750
+ throw new ValidationError("identity_class must be narrow_task, autonomous, or orchestrator");
751
+ }
752
+ if (!opts.principal || typeof opts.principal !== "string") {
753
+ throw new ValidationError("principal is required");
754
+ }
755
+ if (!opts.tier || !["sov", "org", "ind", "eph", "sbx"].includes(opts.tier)) {
756
+ throw new ValidationError("tier must be one of: sov, org, ind, eph, sbx");
757
+ }
758
+ if (!opts.payment_tx || typeof opts.payment_tx !== "string") {
759
+ throw new ValidationError("payment_tx is required");
760
+ }
761
+ body = { ...opts };
735
762
  }
736
763
  try {
737
- const res = await this.api("POST", "/api/v1/sigil", {
738
- identity_class: identityClass,
739
- payment_tx: paymentTx
740
- });
764
+ const res = await this.api("POST", "/api/v1/sigil", body);
741
765
  if (res.lineage_proof) {
742
766
  this.cachedProof = res.lineage_proof;
743
767
  }
744
- this.log(`SIGIL purchased: ${identityClass}`);
745
- this.config.onStatusChange("sigil_issued", { identity_class: identityClass });
768
+ const sigilStr = res.sigil?.sigil || res.sigil?.identity_class || "";
769
+ this.log(`SIGIL purchased: ${sigilStr}`);
770
+ this.config.onStatusChange("sigil_issued", { sigil: sigilStr });
746
771
  return {
747
772
  ok: true,
748
773
  sigil: res.sigil,
@@ -754,6 +779,30 @@ var BeatAgent = class {
754
779
  return { ok: false, error: err.message };
755
780
  }
756
781
  }
782
+ /**
783
+ * Update mutable SIGIL metadata fields.
784
+ * Requires a SIGIL. Cannot modify immutable fields.
785
+ *
786
+ * @param fields - Subset of mutable SIGIL fields to update
787
+ */
788
+ async updateMetadata(fields) {
789
+ if (!fields || Object.keys(fields).length === 0) {
790
+ throw new ValidationError("At least one metadata field is required");
791
+ }
792
+ try {
793
+ const res = await this.api("PATCH", "/api/v1/agent/metadata", fields);
794
+ this.log(`Metadata updated: ${res.updated_fields?.join(", ") || "unknown"}`);
795
+ return {
796
+ ok: true,
797
+ sigil: res.sigil,
798
+ generation: res.generation,
799
+ updated_fields: res.updated_fields
800
+ };
801
+ } catch (err) {
802
+ this.config.onError(err, "updateMetadata");
803
+ return { ok: false, error: err.message };
804
+ }
805
+ }
757
806
  /**
758
807
  * Send a paid heartbeat to the registry.
759
808
  * Requires a SIGIL. Returns a signed lineage proof.
@@ -829,14 +878,18 @@ var BeatAgent = class {
829
878
  * Verify a lineage proof locally using the authority public key.
830
879
  * Offline verification — no API call, no SOL cost.
831
880
  *
881
+ * Returns a VerificationResult object. The object is truthy when valid,
882
+ * so `if (BeatAgent.verifyProofLocally(proof, key))` still works.
883
+ *
832
884
  * @param proof - The LineageProof to verify
833
885
  * @param authorityPubKeyHex - 32-byte hex-encoded Ed25519 public key from /.well-known/provenonce-authority.json
886
+ * @param currentBeat - Optional current global beat index (for beatsSinceHeartbeat calculation)
834
887
  */
835
- static verifyProofLocally(proof, authorityPubKeyHex) {
888
+ static verifyProofLocally(proof, authorityPubKeyHex, currentBeat) {
889
+ const now = Date.now();
890
+ const expired = now > proof.valid_until;
891
+ let signatureValid = false;
836
892
  try {
837
- if (Date.now() > proof.valid_until) {
838
- return false;
839
- }
840
893
  const canonical = JSON.stringify({
841
894
  agent_hash: proof.agent_hash,
842
895
  agent_public_key: proof.agent_public_key,
@@ -849,15 +902,25 @@ var BeatAgent = class {
849
902
  valid_until: proof.valid_until
850
903
  });
851
904
  const pubBytes = Buffer.from(authorityPubKeyHex, "hex");
852
- if (pubBytes.length !== 32) return false;
853
- const ED25519_SPKI_PREFIX = Buffer.from("302a300506032b6570032100", "hex");
854
- const pubKeyDer = Buffer.concat([ED25519_SPKI_PREFIX, pubBytes]);
855
- const keyObject = (0, import_crypto.createPublicKey)({ key: pubKeyDer, format: "der", type: "spki" });
856
- const sigBuffer = Buffer.from(proof.provenonce_signature, "hex");
857
- return (0, import_crypto.verify)(null, Buffer.from(canonical), keyObject, sigBuffer);
905
+ if (pubBytes.length === 32) {
906
+ const ED25519_SPKI_PREFIX = Buffer.from("302a300506032b6570032100", "hex");
907
+ const pubKeyDer = Buffer.concat([ED25519_SPKI_PREFIX, pubBytes]);
908
+ const keyObject = (0, import_crypto.createPublicKey)({ key: pubKeyDer, format: "der", type: "spki" });
909
+ const sigBuffer = Buffer.from(proof.provenonce_signature, "hex");
910
+ signatureValid = (0, import_crypto.verify)(null, Buffer.from(canonical), keyObject, sigBuffer);
911
+ }
858
912
  } catch {
859
- return false;
913
+ signatureValid = false;
914
+ }
915
+ const valid = signatureValid && !expired;
916
+ const beatsSinceHeartbeat = currentBeat != null ? currentBeat - proof.last_heartbeat_beat : null;
917
+ let warning;
918
+ if (expired) {
919
+ warning = "Proof has expired. Reissue with reissueProof() or send a heartbeat.";
920
+ } else if (beatsSinceHeartbeat != null && beatsSinceHeartbeat > 60) {
921
+ warning = `Agent is ${beatsSinceHeartbeat} beats behind. Heartbeat may be stale.`;
860
922
  }
923
+ return { valid, signatureValid, expired, lastHeartbeatBeat: proof.last_heartbeat_beat, beatsSinceHeartbeat, warning };
861
924
  }
862
925
  // ── STATUS ──
863
926
  /**