@dzhechkov/skills-presentation-storyteller 0.1.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 (28) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +53 -0
  3. package/bin/cli.js +5 -0
  4. package/package.json +47 -0
  5. package/sources.json +19 -0
  6. package/src/cli.js +107 -0
  7. package/src/commands/doctor.js +340 -0
  8. package/src/commands/init.js +168 -0
  9. package/src/commands/list.js +146 -0
  10. package/src/commands/remove.js +182 -0
  11. package/src/commands/update.js +170 -0
  12. package/src/utils.js +149 -0
  13. package/templates/.claude/commands/presentation-storyteller.md +23 -0
  14. package/templates/.claude/skills/explore/SKILL.md +218 -0
  15. package/templates/.claude/skills/explore/references/questioning-techniques.md +151 -0
  16. package/templates/.claude/skills/explore/references/task-brief-templates.md +355 -0
  17. package/templates/.claude/skills/goap-research-ed25519/SKILL.md +418 -0
  18. package/templates/.claude/skills/goap-research-ed25519/references/ed25519-verification.md +658 -0
  19. package/templates/.claude/skills/goap-research-ed25519/references/research-actions.md +544 -0
  20. package/templates/.claude/skills/goap-research-ed25519/references/source-evaluation.md +560 -0
  21. package/templates/.claude/skills/goap-research-ed25519/scripts/ed25519_verifier.py +662 -0
  22. package/templates/.claude/skills/goap-research-ed25519/scripts/goap_planner.py +720 -0
  23. package/templates/.claude/skills/presentation-storyteller/SKILL.md +374 -0
  24. package/templates/.claude/skills/presentation-storyteller/references/example-presentation.md +273 -0
  25. package/templates/.claude/skills/presentation-storyteller/references/slide-types.md +426 -0
  26. package/templates/.claude/skills/presentation-storyteller/references/sources-index-template.md +213 -0
  27. package/templates/.claude/skills/presentation-storyteller/references/speaker-script-patterns.md +324 -0
  28. package/templates/.claude/skills/presentation-storyteller/references/storytelling-frameworks.md +270 -0
@@ -0,0 +1,662 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Ed25519 Verification Module for GOAP Research
4
+
5
+ Provides cryptographic verification capabilities for research workflows:
6
+ - Keypair generation
7
+ - Content signing
8
+ - Signature verification
9
+ - Citation chain management
10
+ - Trusted issuer whitelist
11
+ - Verification ledger
12
+
13
+ Requirements:
14
+ pip install cryptography --break-system-packages
15
+ # OR
16
+ pip install pynacl --break-system-packages
17
+ """
18
+
19
+ import hashlib
20
+ import json
21
+ import base64
22
+ from datetime import datetime
23
+ from typing import Optional, Dict, List, Tuple, Any
24
+ from dataclasses import dataclass, field, asdict
25
+ import os
26
+
27
+ # Try to import cryptography library (preferred)
28
+ try:
29
+ from cryptography.hazmat.primitives.asymmetric.ed25519 import (
30
+ Ed25519PrivateKey, Ed25519PublicKey
31
+ )
32
+ from cryptography.hazmat.primitives import serialization
33
+ from cryptography.exceptions import InvalidSignature
34
+ CRYPTO_BACKEND = "cryptography"
35
+ except ImportError:
36
+ try:
37
+ import nacl.signing
38
+ import nacl.encoding
39
+ import nacl.exceptions
40
+ CRYPTO_BACKEND = "pynacl"
41
+ except ImportError:
42
+ CRYPTO_BACKEND = None
43
+
44
+
45
+ @dataclass
46
+ class VerificationResult:
47
+ """Result of a verification operation."""
48
+ verified: bool
49
+ content_hash: str
50
+ signature: str
51
+ issuer: str
52
+ issuer_pubkey: str
53
+ timestamp: str
54
+ confidence: float
55
+ error: Optional[str] = None
56
+
57
+ def to_dict(self) -> Dict:
58
+ return asdict(self)
59
+
60
+
61
+ @dataclass
62
+ class SignedFact:
63
+ """A fact with cryptographic signature."""
64
+ claim: str
65
+ source_url: str
66
+ source_hash: str
67
+ issuer: str
68
+ issuer_pubkey: str
69
+ signature: str
70
+ timestamp: str
71
+ parent_citation: Optional[str] = None
72
+ confidence: float = 0.0
73
+ metadata: Dict[str, Any] = field(default_factory=dict)
74
+
75
+ def to_dict(self) -> Dict:
76
+ return asdict(self)
77
+
78
+ def to_json(self) -> str:
79
+ return json.dumps(self.to_dict(), indent=2)
80
+
81
+ @classmethod
82
+ def from_dict(cls, data: Dict) -> 'SignedFact':
83
+ return cls(**data)
84
+
85
+ @classmethod
86
+ def from_json(cls, json_str: str) -> 'SignedFact':
87
+ return cls.from_dict(json.loads(json_str))
88
+
89
+
90
+ @dataclass
91
+ class CitationChain:
92
+ """Chain of signed citations with integrity verification."""
93
+ chain_id: str
94
+ facts: List[SignedFact] = field(default_factory=list)
95
+ chain_signature: Optional[str] = None
96
+ chain_hash: Optional[str] = None
97
+ integrity_verified: bool = False
98
+ created_at: str = field(default_factory=lambda: datetime.utcnow().isoformat() + "Z")
99
+
100
+ def add_fact(self, fact: SignedFact) -> None:
101
+ """Add fact to chain with automatic parent linking."""
102
+ if self.facts:
103
+ fact.parent_citation = f"chain:{self.chain_id}:fact:{len(self.facts)-1}"
104
+ self.facts.append(fact)
105
+ self.chain_hash = None # Invalidate cached hash
106
+
107
+ def get_chain_hash(self) -> str:
108
+ """Calculate hash of entire chain for signing."""
109
+ if self.chain_hash is None:
110
+ chain_data = json.dumps(
111
+ [f.to_dict() for f in self.facts],
112
+ sort_keys=True
113
+ )
114
+ self.chain_hash = hashlib.sha256(chain_data.encode()).hexdigest()
115
+ return self.chain_hash
116
+
117
+ def to_dict(self) -> Dict:
118
+ return {
119
+ 'chain_id': self.chain_id,
120
+ 'facts': [f.to_dict() for f in self.facts],
121
+ 'chain_signature': self.chain_signature,
122
+ 'chain_hash': self.get_chain_hash(),
123
+ 'integrity_verified': self.integrity_verified,
124
+ 'created_at': self.created_at
125
+ }
126
+
127
+
128
+ class Ed25519Verifier:
129
+ """
130
+ Ed25519 verification system for GOAP research.
131
+
132
+ Provides:
133
+ - Keypair generation and management
134
+ - Content signing and verification
135
+ - Citation chain management
136
+ - Trusted issuer whitelist
137
+ - Verification ledger
138
+ """
139
+
140
+ DEFAULT_TRUSTED_ISSUERS = {
141
+ # News agencies
142
+ "reuters.com": None,
143
+ "ap.org": None,
144
+ "bbc.com": None,
145
+ "nytimes.com": None,
146
+ "wsj.com": None,
147
+ # Academic
148
+ "arxiv.org": None,
149
+ "nature.com": None,
150
+ "science.org": None,
151
+ "sciencedirect.com": None,
152
+ "pubmed.gov": None,
153
+ "ieee.org": None,
154
+ "acm.org": None,
155
+ # Government
156
+ ".gov": None, # Suffix match
157
+ ".gov.uk": None,
158
+ "europa.eu": None,
159
+ "who.int": None,
160
+ # Financial/Regulatory
161
+ "sec.gov": None,
162
+ "federalreserve.gov": None,
163
+ "ecb.europa.eu": None,
164
+ }
165
+
166
+ def __init__(
167
+ self,
168
+ trusted_issuers: Optional[Dict[str, Optional[str]]] = None,
169
+ verification_threshold: float = 0.85,
170
+ auto_generate_keypair: bool = False
171
+ ):
172
+ """
173
+ Initialize verifier.
174
+
175
+ Args:
176
+ trusted_issuers: Dict of domain -> public_key_b64 (None = trust without signature)
177
+ verification_threshold: Minimum confidence for verified status
178
+ auto_generate_keypair: Generate keypair on init
179
+ """
180
+ if CRYPTO_BACKEND is None:
181
+ raise RuntimeError(
182
+ "No cryptographic backend available. Install cryptography or pynacl:\n"
183
+ " pip install cryptography --break-system-packages"
184
+ )
185
+
186
+ self.trusted_issuers = trusted_issuers or self.DEFAULT_TRUSTED_ISSUERS.copy()
187
+ self.verification_threshold = verification_threshold
188
+ self.verification_ledger: List[VerificationResult] = []
189
+ self._private_key: Optional[bytes] = None
190
+ self._public_key: Optional[bytes] = None
191
+
192
+ if auto_generate_keypair:
193
+ self.generate_keypair()
194
+
195
+ def generate_keypair(self) -> Tuple[bytes, bytes]:
196
+ """
197
+ Generate new Ed25519 keypair.
198
+
199
+ Returns:
200
+ Tuple of (private_key_bytes, public_key_bytes)
201
+ """
202
+ if CRYPTO_BACKEND == "cryptography":
203
+ private_key = Ed25519PrivateKey.generate()
204
+ public_key = private_key.public_key()
205
+
206
+ private_bytes = private_key.private_bytes(
207
+ encoding=serialization.Encoding.Raw,
208
+ format=serialization.PrivateFormat.Raw,
209
+ encryption_algorithm=serialization.NoEncryption()
210
+ )
211
+ public_bytes = public_key.public_bytes(
212
+ encoding=serialization.Encoding.Raw,
213
+ format=serialization.PublicFormat.Raw
214
+ )
215
+ else: # pynacl
216
+ signing_key = nacl.signing.SigningKey.generate()
217
+ private_bytes = bytes(signing_key)
218
+ public_bytes = bytes(signing_key.verify_key)
219
+
220
+ self._private_key = private_bytes
221
+ self._public_key = public_bytes
222
+
223
+ return private_bytes, public_bytes
224
+
225
+ def load_keypair(self, private_key: bytes, public_key: Optional[bytes] = None) -> None:
226
+ """
227
+ Load existing keypair.
228
+
229
+ Args:
230
+ private_key: 32-byte private key (seed)
231
+ public_key: 32-byte public key (optional, can be derived)
232
+ """
233
+ self._private_key = private_key
234
+
235
+ if public_key is not None:
236
+ self._public_key = public_key
237
+ else:
238
+ # Derive public key from private
239
+ if CRYPTO_BACKEND == "cryptography":
240
+ pk = Ed25519PrivateKey.from_private_bytes(private_key)
241
+ self._public_key = pk.public_key().public_bytes(
242
+ encoding=serialization.Encoding.Raw,
243
+ format=serialization.PublicFormat.Raw
244
+ )
245
+ else:
246
+ signing_key = nacl.signing.SigningKey(private_key)
247
+ self._public_key = bytes(signing_key.verify_key)
248
+
249
+ def load_keypair_from_files(self, private_path: str, public_path: Optional[str] = None) -> None:
250
+ """Load keypair from files."""
251
+ with open(private_path, 'rb') as f:
252
+ private_key = f.read()
253
+
254
+ public_key = None
255
+ if public_path:
256
+ with open(public_path, 'rb') as f:
257
+ public_key = f.read()
258
+
259
+ self.load_keypair(private_key, public_key)
260
+
261
+ def save_keypair_to_files(self, private_path: str, public_path: str) -> None:
262
+ """Save keypair to files."""
263
+ if self._private_key is None or self._public_key is None:
264
+ raise ValueError("No keypair to save. Generate or load one first.")
265
+
266
+ with open(private_path, 'wb') as f:
267
+ f.write(self._private_key)
268
+ os.chmod(private_path, 0o600) # Restrict private key permissions
269
+
270
+ with open(public_path, 'wb') as f:
271
+ f.write(self._public_key)
272
+
273
+ def get_public_key_b64(self) -> str:
274
+ """Get public key as base64 string."""
275
+ if self._public_key is None:
276
+ raise ValueError("No public key available.")
277
+ return base64.b64encode(self._public_key).decode('ascii')
278
+
279
+ def sign_content(self, content: str) -> Tuple[str, str]:
280
+ """
281
+ Sign content with private key.
282
+
283
+ Args:
284
+ content: String content to sign
285
+
286
+ Returns:
287
+ Tuple of (signature_base64, content_sha256_hash)
288
+ """
289
+ if self._private_key is None:
290
+ raise ValueError("No private key loaded. Call generate_keypair() first.")
291
+
292
+ content_bytes = content.encode('utf-8')
293
+ content_hash = hashlib.sha256(content_bytes).hexdigest()
294
+
295
+ if CRYPTO_BACKEND == "cryptography":
296
+ private_key = Ed25519PrivateKey.from_private_bytes(self._private_key)
297
+ signature = private_key.sign(content_bytes)
298
+ else:
299
+ signing_key = nacl.signing.SigningKey(self._private_key)
300
+ signed = signing_key.sign(content_bytes)
301
+ signature = signed.signature
302
+
303
+ signature_b64 = base64.b64encode(signature).decode('ascii')
304
+ return signature_b64, content_hash
305
+
306
+ def verify_signature(
307
+ self,
308
+ content: str,
309
+ signature_b64: str,
310
+ public_key: bytes
311
+ ) -> bool:
312
+ """
313
+ Verify Ed25519 signature.
314
+
315
+ Args:
316
+ content: Original content
317
+ signature_b64: Base64-encoded signature
318
+ public_key: 32-byte public key
319
+
320
+ Returns:
321
+ True if valid, False otherwise
322
+ """
323
+ try:
324
+ content_bytes = content.encode('utf-8')
325
+ signature = base64.b64decode(signature_b64)
326
+
327
+ if CRYPTO_BACKEND == "cryptography":
328
+ pub_key = Ed25519PublicKey.from_public_bytes(public_key)
329
+ pub_key.verify(signature, content_bytes)
330
+ return True
331
+ else:
332
+ verify_key = nacl.signing.VerifyKey(public_key)
333
+ verify_key.verify(content_bytes, signature)
334
+ return True
335
+ except (InvalidSignature, nacl.exceptions.BadSignatureError) if CRYPTO_BACKEND == "pynacl" else InvalidSignature:
336
+ return False
337
+ except Exception:
338
+ return False
339
+
340
+ def is_trusted_issuer(self, domain: str) -> bool:
341
+ """Check if domain is in trusted issuers whitelist."""
342
+ # Direct match
343
+ if domain in self.trusted_issuers:
344
+ return True
345
+
346
+ # Suffix match for patterns like ".gov"
347
+ for trusted in self.trusted_issuers:
348
+ if trusted.startswith('.') and domain.endswith(trusted):
349
+ return True
350
+
351
+ # Check if any trusted issuer is a suffix of the domain
352
+ for trusted in self.trusted_issuers:
353
+ if domain.endswith('.' + trusted) or domain == trusted:
354
+ return True
355
+
356
+ return False
357
+
358
+ def add_trusted_issuer(self, domain: str, public_key_b64: Optional[str] = None) -> None:
359
+ """Add domain to trusted issuers."""
360
+ self.trusted_issuers[domain] = public_key_b64
361
+
362
+ def remove_trusted_issuer(self, domain: str) -> None:
363
+ """Remove domain from trusted issuers."""
364
+ self.trusted_issuers.pop(domain, None)
365
+
366
+ def create_signed_fact(
367
+ self,
368
+ claim: str,
369
+ source_url: str,
370
+ source_content: str,
371
+ issuer: str,
372
+ metadata: Optional[Dict] = None
373
+ ) -> SignedFact:
374
+ """
375
+ Create a signed fact with full verification metadata.
376
+
377
+ Args:
378
+ claim: The factual claim being made
379
+ source_url: URL of the source
380
+ source_content: Content from the source
381
+ issuer: Domain/identity of the issuer
382
+ metadata: Additional metadata
383
+
384
+ Returns:
385
+ SignedFact with Ed25519 signature
386
+ """
387
+ if self._private_key is None:
388
+ raise ValueError("No private key loaded.")
389
+
390
+ source_hash = hashlib.sha256(source_content.encode()).hexdigest()
391
+ timestamp = datetime.utcnow().isoformat() + "Z"
392
+
393
+ # Sign the claim + source_hash + timestamp
394
+ signing_content = f"{claim}|{source_hash}|{timestamp}"
395
+ signature, _ = self.sign_content(signing_content)
396
+
397
+ public_key_b64 = base64.b64encode(self._public_key).decode('ascii')
398
+
399
+ # Calculate confidence
400
+ base_confidence = 0.80 # Base for signed content
401
+ if self.is_trusted_issuer(issuer):
402
+ base_confidence = 0.95
403
+
404
+ return SignedFact(
405
+ claim=claim,
406
+ source_url=source_url,
407
+ source_hash=source_hash,
408
+ issuer=issuer,
409
+ issuer_pubkey=f"ed25519:{public_key_b64}",
410
+ signature=signature,
411
+ timestamp=timestamp,
412
+ confidence=base_confidence,
413
+ metadata=metadata or {}
414
+ )
415
+
416
+ def verify_fact(self, fact: SignedFact) -> VerificationResult:
417
+ """
418
+ Verify a signed fact.
419
+
420
+ Args:
421
+ fact: SignedFact to verify
422
+
423
+ Returns:
424
+ VerificationResult with status and confidence
425
+ """
426
+ # Extract public key from fact
427
+ if not fact.issuer_pubkey.startswith("ed25519:"):
428
+ result = VerificationResult(
429
+ verified=False,
430
+ content_hash=fact.source_hash,
431
+ signature=fact.signature,
432
+ issuer=fact.issuer,
433
+ issuer_pubkey=fact.issuer_pubkey,
434
+ timestamp=fact.timestamp,
435
+ confidence=0.0,
436
+ error="Invalid public key format (expected 'ed25519:...')"
437
+ )
438
+ self.verification_ledger.append(result)
439
+ return result
440
+
441
+ try:
442
+ pubkey_b64 = fact.issuer_pubkey[8:] # Remove "ed25519:" prefix
443
+ public_key = base64.b64decode(pubkey_b64)
444
+
445
+ # Reconstruct signing content
446
+ signing_content = f"{fact.claim}|{fact.source_hash}|{fact.timestamp}"
447
+
448
+ verified = self.verify_signature(signing_content, fact.signature, public_key)
449
+
450
+ # Calculate confidence
451
+ confidence = 0.0
452
+ if verified:
453
+ confidence = 0.80
454
+ if self.is_trusted_issuer(fact.issuer):
455
+ confidence = 0.95
456
+
457
+ result = VerificationResult(
458
+ verified=verified,
459
+ content_hash=fact.source_hash,
460
+ signature=fact.signature,
461
+ issuer=fact.issuer,
462
+ issuer_pubkey=fact.issuer_pubkey,
463
+ timestamp=fact.timestamp,
464
+ confidence=confidence,
465
+ error=None if verified else "Signature verification failed"
466
+ )
467
+
468
+ self.verification_ledger.append(result)
469
+ return result
470
+
471
+ except Exception as e:
472
+ result = VerificationResult(
473
+ verified=False,
474
+ content_hash=fact.source_hash,
475
+ signature=fact.signature,
476
+ issuer=fact.issuer,
477
+ issuer_pubkey=fact.issuer_pubkey,
478
+ timestamp=fact.timestamp,
479
+ confidence=0.0,
480
+ error=str(e)
481
+ )
482
+ self.verification_ledger.append(result)
483
+ return result
484
+
485
+ def verify_citation_chain(self, chain: CitationChain) -> Tuple[bool, float, Optional[str]]:
486
+ """
487
+ Verify entire citation chain integrity.
488
+
489
+ Args:
490
+ chain: CitationChain to verify
491
+
492
+ Returns:
493
+ Tuple of (all_verified, aggregate_confidence, error_message)
494
+ """
495
+ if not chain.facts:
496
+ return False, 0.0, "Empty chain"
497
+
498
+ all_verified = True
499
+ total_confidence = 0.0
500
+ errors = []
501
+
502
+ for i, fact in enumerate(chain.facts):
503
+ result = self.verify_fact(fact)
504
+
505
+ if not result.verified:
506
+ all_verified = False
507
+ errors.append(f"Fact {i}: {result.error}")
508
+
509
+ total_confidence += result.confidence
510
+
511
+ # Verify chain linkage
512
+ if i > 0:
513
+ expected_parent = f"chain:{chain.chain_id}:fact:{i-1}"
514
+ if fact.parent_citation != expected_parent:
515
+ all_verified = False
516
+ errors.append(f"Fact {i}: Invalid parent citation")
517
+
518
+ aggregate_confidence = total_confidence / len(chain.facts)
519
+ chain.integrity_verified = all_verified
520
+
521
+ error_msg = "; ".join(errors) if errors else None
522
+ return all_verified, aggregate_confidence, error_msg
523
+
524
+ def sign_chain(self, chain: CitationChain) -> str:
525
+ """Sign the entire citation chain."""
526
+ chain_hash = chain.get_chain_hash()
527
+ signature, _ = self.sign_content(chain_hash)
528
+ chain.chain_signature = signature
529
+ return signature
530
+
531
+ def get_verification_ledger(self) -> List[Dict]:
532
+ """Get verification ledger as list of dicts."""
533
+ return [r.to_dict() for r in self.verification_ledger]
534
+
535
+ def sign_ledger(self) -> str:
536
+ """Sign the entire verification ledger."""
537
+ ledger_json = json.dumps(self.get_verification_ledger(), sort_keys=True)
538
+ signature, _ = self.sign_content(ledger_json)
539
+ return signature
540
+
541
+ def export_ledger(self, filepath: str) -> None:
542
+ """Export verification ledger to file."""
543
+ data = {
544
+ 'ledger': self.get_verification_ledger(),
545
+ 'signature': self.sign_ledger(),
546
+ 'exported_at': datetime.utcnow().isoformat() + "Z",
547
+ 'signer_pubkey': f"ed25519:{self.get_public_key_b64()}" if self._public_key else None
548
+ }
549
+ with open(filepath, 'w') as f:
550
+ json.dump(data, f, indent=2)
551
+
552
+ def clear_ledger(self) -> None:
553
+ """Clear the verification ledger."""
554
+ self.verification_ledger.clear()
555
+
556
+
557
+ # Convenience functions
558
+ def generate_keypair_b64() -> Tuple[str, str]:
559
+ """Generate keypair and return as base64 strings."""
560
+ verifier = Ed25519Verifier()
561
+ private_bytes, public_bytes = verifier.generate_keypair()
562
+ return (
563
+ base64.b64encode(private_bytes).decode('ascii'),
564
+ base64.b64encode(public_bytes).decode('ascii')
565
+ )
566
+
567
+
568
+ def quick_sign(content: str, private_key_b64: str) -> str:
569
+ """Quick sign content with base64-encoded private key."""
570
+ verifier = Ed25519Verifier()
571
+ private_bytes = base64.b64decode(private_key_b64)
572
+ verifier.load_keypair(private_bytes)
573
+ signature, _ = verifier.sign_content(content)
574
+ return signature
575
+
576
+
577
+ def quick_verify(content: str, signature_b64: str, public_key_b64: str) -> bool:
578
+ """Quick verify content signature."""
579
+ verifier = Ed25519Verifier()
580
+ public_bytes = base64.b64decode(public_key_b64)
581
+ return verifier.verify_signature(content, signature_b64, public_bytes)
582
+
583
+
584
+ # Demo and testing
585
+ if __name__ == "__main__":
586
+ print("Ed25519 Verification Module Demo")
587
+ print("=" * 60)
588
+ print(f"Crypto Backend: {CRYPTO_BACKEND}")
589
+ print()
590
+
591
+ # Initialize verifier
592
+ verifier = Ed25519Verifier(
593
+ verification_threshold=0.85,
594
+ auto_generate_keypair=True
595
+ )
596
+
597
+ print(f"Public Key: ed25519:{verifier.get_public_key_b64()[:32]}...")
598
+ print()
599
+
600
+ # Create and sign a fact
601
+ print("[1] Creating signed fact...")
602
+ fact = verifier.create_signed_fact(
603
+ claim="The study found a 25% improvement in efficiency",
604
+ source_url="https://nature.com/articles/example",
605
+ source_content="Full article content here...",
606
+ issuer="nature.com"
607
+ )
608
+
609
+ print(f"Claim: {fact.claim}")
610
+ print(f"Source: {fact.source_url}")
611
+ print(f"Signature: {fact.signature[:32]}...")
612
+ print(f"Confidence: {fact.confidence}")
613
+ print()
614
+
615
+ # Verify the fact
616
+ print("[2] Verifying signed fact...")
617
+ result = verifier.verify_fact(fact)
618
+ print(f"Verified: {result.verified}")
619
+ print(f"Confidence: {result.confidence}")
620
+ print(f"Error: {result.error}")
621
+ print()
622
+
623
+ # Create citation chain
624
+ print("[3] Building citation chain...")
625
+ chain = CitationChain(chain_id="research_001")
626
+
627
+ for i in range(3):
628
+ fact = verifier.create_signed_fact(
629
+ claim=f"Claim {i+1} from the research",
630
+ source_url=f"https://source{i+1}.com/article",
631
+ source_content=f"Source content {i+1}",
632
+ issuer=["reuters.com", "arxiv.org", "example.com"][i]
633
+ )
634
+ chain.add_fact(fact)
635
+
636
+ print(f"Chain ID: {chain.chain_id}")
637
+ print(f"Facts: {len(chain.facts)}")
638
+ print(f"Chain Hash: {chain.get_chain_hash()[:32]}...")
639
+ print()
640
+
641
+ # Verify chain
642
+ print("[4] Verifying citation chain...")
643
+ all_verified, confidence, error = verifier.verify_citation_chain(chain)
644
+ print(f"All Verified: {all_verified}")
645
+ print(f"Aggregate Confidence: {confidence:.2%}")
646
+ print(f"Errors: {error}")
647
+ print()
648
+
649
+ # Sign chain
650
+ chain_sig = verifier.sign_chain(chain)
651
+ print(f"Chain Signature: {chain_sig[:32]}...")
652
+ print()
653
+
654
+ # Verification ledger
655
+ print("[5] Verification Ledger:")
656
+ for i, entry in enumerate(verifier.get_verification_ledger()):
657
+ print(f" {i+1}. {entry['issuer']}: {'✅' if entry['verified'] else '❌'} "
658
+ f"(confidence: {entry['confidence']:.2%})")
659
+
660
+ print()
661
+ print("=" * 60)
662
+ print("Demo complete!")