@holoscript/framework 6.0.3
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/ALL-test-results.json +1 -0
- package/CHANGELOG.md +8 -0
- package/LICENSE +21 -0
- package/ROADMAP.md +175 -0
- package/dist/AgentManifest-CB4xM-Ma.d.cts +704 -0
- package/dist/AgentManifest-CB4xM-Ma.d.ts +704 -0
- package/dist/BehaviorTree-BrBFECv5.d.cts +103 -0
- package/dist/BehaviorTree-BrBFECv5.d.ts +103 -0
- package/dist/InvisibleWallet-BB6tFvRA.d.cts +1732 -0
- package/dist/InvisibleWallet-rtRrBOA8.d.ts +1732 -0
- package/dist/OrchestratorAgent-BvWgf9uw.d.cts +798 -0
- package/dist/OrchestratorAgent-Q_CbVTmO.d.ts +798 -0
- package/dist/agents/index.cjs +4790 -0
- package/dist/agents/index.d.cts +1788 -0
- package/dist/agents/index.d.ts +1788 -0
- package/dist/agents/index.js +4695 -0
- package/dist/ai/index.cjs +5347 -0
- package/dist/ai/index.d.cts +1753 -0
- package/dist/ai/index.d.ts +1753 -0
- package/dist/ai/index.js +5244 -0
- package/dist/behavior.cjs +449 -0
- package/dist/behavior.d.cts +130 -0
- package/dist/behavior.d.ts +130 -0
- package/dist/behavior.js +407 -0
- package/dist/economy/index.cjs +3659 -0
- package/dist/economy/index.d.cts +747 -0
- package/dist/economy/index.d.ts +747 -0
- package/dist/economy/index.js +3617 -0
- package/dist/implementations-D9T3un9D.d.cts +236 -0
- package/dist/implementations-D9T3un9D.d.ts +236 -0
- package/dist/index.cjs +24550 -0
- package/dist/index.d.cts +1729 -0
- package/dist/index.d.ts +1729 -0
- package/dist/index.js +24277 -0
- package/dist/learning/index.cjs +219 -0
- package/dist/learning/index.d.cts +104 -0
- package/dist/learning/index.d.ts +104 -0
- package/dist/learning/index.js +189 -0
- package/dist/negotiation/index.cjs +970 -0
- package/dist/negotiation/index.d.cts +610 -0
- package/dist/negotiation/index.d.ts +610 -0
- package/dist/negotiation/index.js +931 -0
- package/dist/skills/index.cjs +1118 -0
- package/dist/skills/index.d.cts +289 -0
- package/dist/skills/index.d.ts +289 -0
- package/dist/skills/index.js +1079 -0
- package/dist/swarm/index.cjs +5268 -0
- package/dist/swarm/index.d.cts +2433 -0
- package/dist/swarm/index.d.ts +2433 -0
- package/dist/swarm/index.js +5221 -0
- package/dist/training/index.cjs +2745 -0
- package/dist/training/index.d.cts +1734 -0
- package/dist/training/index.d.ts +1734 -0
- package/dist/training/index.js +2687 -0
- package/extract-failures.js +10 -0
- package/package.json +82 -0
- package/src/__tests__/bounty-marketplace.test.ts +374 -0
- package/src/__tests__/delegation.test.ts +144 -0
- package/src/__tests__/distributed-claimer.test.ts +147 -0
- package/src/__tests__/done-log-audit.test.ts +342 -0
- package/src/__tests__/framework.test.ts +865 -0
- package/src/__tests__/goal-synthesizer.test.ts +236 -0
- package/src/__tests__/presence.test.ts +223 -0
- package/src/__tests__/protocol-agent.test.ts +254 -0
- package/src/__tests__/revenue-splitter.test.ts +114 -0
- package/src/__tests__/scenario-driven-todo.test.ts +197 -0
- package/src/__tests__/self-improve.test.ts +349 -0
- package/src/__tests__/service-lifecycle.test.ts +237 -0
- package/src/__tests__/skill-router.test.ts +121 -0
- package/src/agents/AgentManifest.ts +493 -0
- package/src/agents/AgentRegistry.ts +475 -0
- package/src/agents/AgentTypes.ts +585 -0
- package/src/agents/AgentWalletRegistry.ts +83 -0
- package/src/agents/AuthenticatedCRDT.ts +388 -0
- package/src/agents/CapabilityMatcher.ts +453 -0
- package/src/agents/CrossRealityHandoff.ts +305 -0
- package/src/agents/CulturalMemory.ts +454 -0
- package/src/agents/FederatedRegistryAdapter.ts +429 -0
- package/src/agents/NormEngine.ts +450 -0
- package/src/agents/OrchestratorAgent.ts +414 -0
- package/src/agents/SkillWorkflowEngine.ts +472 -0
- package/src/agents/TaskDelegationService.ts +551 -0
- package/src/agents/__tests__/AgentManifest.prod.test.ts +134 -0
- package/src/agents/__tests__/AgentManifest.test.ts +182 -0
- package/src/agents/__tests__/AgentModule.test.ts +864 -0
- package/src/agents/__tests__/AgentRegistry.prod.test.ts +125 -0
- package/src/agents/__tests__/AgentRegistry.test.ts +148 -0
- package/src/agents/__tests__/AgentTypes.test.ts +534 -0
- package/src/agents/__tests__/AgentWalletRegistry.test.ts +152 -0
- package/src/agents/__tests__/AuthenticatedCRDT.test.ts +558 -0
- package/src/agents/__tests__/CapabilityMatcher.prod.test.ts +117 -0
- package/src/agents/__tests__/CapabilityMatcher.test.ts +178 -0
- package/src/agents/__tests__/CrossRealityHandoff.test.ts +402 -0
- package/src/agents/__tests__/CulturalMemory.test.ts +200 -0
- package/src/agents/__tests__/FederatedRegistryAdapter.test.ts +409 -0
- package/src/agents/__tests__/NormEngine.test.ts +276 -0
- package/src/agents/__tests__/OrchestratorAgent.test.ts +182 -0
- package/src/agents/__tests__/SkillWorkflowEngine.test.ts +357 -0
- package/src/agents/__tests__/TaskDelegationService.test.ts +446 -0
- package/src/agents/index.ts +107 -0
- package/src/agents/spatial-comms/Layer1RealTime.ts +621 -0
- package/src/agents/spatial-comms/Layer2A2A.ts +661 -0
- package/src/agents/spatial-comms/Layer3MCP.ts +651 -0
- package/src/agents/spatial-comms/ProtocolTypes.ts +543 -0
- package/src/agents/spatial-comms/SpatialCommClient.ts +483 -0
- package/src/agents/spatial-comms/__tests__/performance-benchmark.test.ts +465 -0
- package/src/agents/spatial-comms/examples/multi-agent-world-creation.ts +409 -0
- package/src/agents/spatial-comms/index.ts +66 -0
- package/src/ai/AIAdapter.ts +313 -0
- package/src/ai/AICopilot.ts +331 -0
- package/src/ai/AIOutputValidator.ts +203 -0
- package/src/ai/BTNodes.ts +239 -0
- package/src/ai/BehaviorSelector.ts +135 -0
- package/src/ai/BehaviorTree.ts +153 -0
- package/src/ai/Blackboard.ts +165 -0
- package/src/ai/GenerationAnalytics.ts +461 -0
- package/src/ai/GenerationCache.ts +265 -0
- package/src/ai/GoalPlanner.ts +165 -0
- package/src/ai/HoloScriptGenerator.ts +580 -0
- package/src/ai/InfluenceMap.ts +180 -0
- package/src/ai/NavMesh.ts +168 -0
- package/src/ai/PerceptionSystem.ts +178 -0
- package/src/ai/PromptTemplates.ts +453 -0
- package/src/ai/SemanticSearchService.ts +80 -0
- package/src/ai/StateMachine.ts +196 -0
- package/src/ai/SteeringBehavior.ts +150 -0
- package/src/ai/SteeringBehaviors.ts +244 -0
- package/src/ai/TrainingDataGenerator.ts +1082 -0
- package/src/ai/UtilityAI.ts +145 -0
- package/src/ai/__tests__/AIAdapter.prod.test.ts +259 -0
- package/src/ai/__tests__/AIAdapter.test.ts +109 -0
- package/src/ai/__tests__/AICopilot.prod.test.ts +341 -0
- package/src/ai/__tests__/AICopilot.test.ts +178 -0
- package/src/ai/__tests__/AIOutputValidator.prod.test.ts +226 -0
- package/src/ai/__tests__/AIOutputValidator.test.ts +138 -0
- package/src/ai/__tests__/BTNodes.prod.test.ts +391 -0
- package/src/ai/__tests__/BTNodes.test.ts +263 -0
- package/src/ai/__tests__/BehaviorSelector.prod.test.ts +129 -0
- package/src/ai/__tests__/BehaviorSelector.test.ts +132 -0
- package/src/ai/__tests__/BehaviorTree.prod.test.ts +266 -0
- package/src/ai/__tests__/BehaviorTree.test.ts +216 -0
- package/src/ai/__tests__/Blackboard.prod.test.ts +339 -0
- package/src/ai/__tests__/Blackboard.test.ts +183 -0
- package/src/ai/__tests__/GenerationAnalytics.prod.test.ts +141 -0
- package/src/ai/__tests__/GenerationAnalytics.test.ts +165 -0
- package/src/ai/__tests__/GenerationCache.prod.test.ts +144 -0
- package/src/ai/__tests__/GenerationCache.test.ts +171 -0
- package/src/ai/__tests__/GoalPlanner.prod.test.ts +189 -0
- package/src/ai/__tests__/GoalPlanner.test.ts +137 -0
- package/src/ai/__tests__/GoalPlannerDepth.prod.test.ts +217 -0
- package/src/ai/__tests__/HoloScriptGenerator.test.ts +125 -0
- package/src/ai/__tests__/InfluenceMap.prod.test.ts +146 -0
- package/src/ai/__tests__/InfluenceMap.test.ts +149 -0
- package/src/ai/__tests__/NavMesh.prod.test.ts +141 -0
- package/src/ai/__tests__/NavMesh.test.ts +159 -0
- package/src/ai/__tests__/PerceptionSystem.prod.test.ts +135 -0
- package/src/ai/__tests__/PerceptionSystem.test.ts +250 -0
- package/src/ai/__tests__/PromptTemplates.prod.test.ts +313 -0
- package/src/ai/__tests__/PromptTemplates.test.ts +146 -0
- package/src/ai/__tests__/SemanticSearch.test.ts +37 -0
- package/src/ai/__tests__/StateMachine.prod.test.ts +162 -0
- package/src/ai/__tests__/StateMachine.test.ts +163 -0
- package/src/ai/__tests__/SteeringBehavior.prod.test.ts +251 -0
- package/src/ai/__tests__/SteeringBehavior.test.ts +135 -0
- package/src/ai/__tests__/SteeringBehaviors.prod.test.ts +133 -0
- package/src/ai/__tests__/SteeringBehaviors.test.ts +151 -0
- package/src/ai/__tests__/TrainingDataGenerator.prod.test.ts +286 -0
- package/src/ai/__tests__/TrainingDataGenerator.test.ts +286 -0
- package/src/ai/__tests__/UtilityAI.prod.test.ts +207 -0
- package/src/ai/__tests__/UtilityAI.test.ts +155 -0
- package/src/ai/__tests__/adapters.prod.test.ts +263 -0
- package/src/ai/__tests__/adapters.test.ts +320 -0
- package/src/ai/adapters.ts +1585 -0
- package/src/ai/index.ts +130 -0
- package/src/behavior/BehaviorPresets.ts +140 -0
- package/src/behavior/BehaviorTree.ts +236 -0
- package/src/behavior/StateMachine.ts +176 -0
- package/src/behavior/StateTrait.ts +67 -0
- package/src/behavior/index.ts +8 -0
- package/src/behavior.ts +8 -0
- package/src/board/audit.ts +284 -0
- package/src/board/board-ops.ts +336 -0
- package/src/board/board-types.ts +302 -0
- package/src/board/index.ts +69 -0
- package/src/define-agent.ts +46 -0
- package/src/define-team.ts +33 -0
- package/src/delegation.ts +265 -0
- package/src/distributed-claimer.ts +228 -0
- package/src/economy/AgentBudgetEnforcer.ts +464 -0
- package/src/economy/BountyManager.ts +185 -0
- package/src/economy/CreatorRevenueAggregator.ts +460 -0
- package/src/economy/InvisibleWallet.ts +82 -0
- package/src/economy/KnowledgeMarketplace.ts +193 -0
- package/src/economy/PaymentWebhookService.ts +512 -0
- package/src/economy/RevenueSplitter.ts +156 -0
- package/src/economy/SubscriptionManager.ts +546 -0
- package/src/economy/UnifiedBudgetOptimizer.ts +635 -0
- package/src/economy/UsageMeter.ts +440 -0
- package/src/economy/_core-stubs.ts +219 -0
- package/src/economy/index.ts +100 -0
- package/src/economy/x402-facilitator.ts +1978 -0
- package/src/index.ts +348 -0
- package/src/knowledge/__tests__/knowledge-consolidator.test.ts +444 -0
- package/src/knowledge/__tests__/knowledge-store-vector.test.ts +291 -0
- package/src/knowledge/brain.ts +167 -0
- package/src/knowledge/consolidation.ts +581 -0
- package/src/knowledge/knowledge-consolidator.ts +510 -0
- package/src/knowledge/knowledge-store.ts +616 -0
- package/src/learning/MemoryConsolidator.ts +102 -0
- package/src/learning/MemoryScorer.ts +69 -0
- package/src/learning/ProceduralCompiler.ts +45 -0
- package/src/learning/SemanticClusterer.ts +66 -0
- package/src/learning/index.ts +8 -0
- package/src/llm/llm-adapter.ts +159 -0
- package/src/mesh/index.ts +309 -0
- package/src/negotiation/NegotiationProtocol.ts +694 -0
- package/src/negotiation/NegotiationTypes.ts +473 -0
- package/src/negotiation/VotingMechanisms.ts +691 -0
- package/src/negotiation/index.ts +49 -0
- package/src/protocol/goal-synthesizer.ts +317 -0
- package/src/protocol/implementations.ts +474 -0
- package/src/protocol/micro-phase-decomposer.ts +299 -0
- package/src/protocol/micro-step-decomposer.test.ts +306 -0
- package/src/protocol-agent.test.ts +353 -0
- package/src/protocol-agent.ts +670 -0
- package/src/self-improve/absorb-scanner.ts +252 -0
- package/src/self-improve/evolution-engine.ts +149 -0
- package/src/self-improve/framework-absorber.ts +214 -0
- package/src/self-improve/index.ts +50 -0
- package/src/self-improve/prompt-optimizer.ts +212 -0
- package/src/self-improve/test-generator.ts +175 -0
- package/src/skill-router.ts +186 -0
- package/src/skills/index.ts +5 -0
- package/src/skills/skill-md-bridge.ts +1699 -0
- package/src/swarm/ACOEngine.ts +261 -0
- package/src/swarm/CollectiveIntelligence.ts +383 -0
- package/src/swarm/ContributionSynthesizer.ts +481 -0
- package/src/swarm/LeaderElection.ts +393 -0
- package/src/swarm/PSOEngine.ts +206 -0
- package/src/swarm/QuorumPolicy.ts +173 -0
- package/src/swarm/SwarmCoordinator.ts +335 -0
- package/src/swarm/SwarmManager.ts +442 -0
- package/src/swarm/SwarmMembership.ts +456 -0
- package/src/swarm/VotingRound.ts +255 -0
- package/src/swarm/__tests__/ACOEngine.prod.test.ts +164 -0
- package/src/swarm/__tests__/ACOEngine.test.ts +117 -0
- package/src/swarm/__tests__/CollectiveIntelligence.prod.test.ts +296 -0
- package/src/swarm/__tests__/CollectiveIntelligence.test.ts +457 -0
- package/src/swarm/__tests__/ContributionSynthesizer.prod.test.ts +269 -0
- package/src/swarm/__tests__/ContributionSynthesizer.test.ts +254 -0
- package/src/swarm/__tests__/LeaderElection.prod.test.ts +196 -0
- package/src/swarm/__tests__/LeaderElection.test.ts +151 -0
- package/src/swarm/__tests__/PSOEngine.prod.test.ts +162 -0
- package/src/swarm/__tests__/PSOEngine.test.ts +106 -0
- package/src/swarm/__tests__/QuorumPolicy.prod.test.ts +216 -0
- package/src/swarm/__tests__/QuorumPolicy.test.ts +177 -0
- package/src/swarm/__tests__/SwarmCoordinator.prod.test.ts +186 -0
- package/src/swarm/__tests__/SwarmCoordinator.test.ts +167 -0
- package/src/swarm/__tests__/SwarmManager.prod.test.ts +308 -0
- package/src/swarm/__tests__/SwarmManager.test.ts +373 -0
- package/src/swarm/__tests__/SwarmMembership.prod.test.ts +273 -0
- package/src/swarm/__tests__/SwarmMembership.test.ts +264 -0
- package/src/swarm/__tests__/VotingRound.prod.test.ts +233 -0
- package/src/swarm/__tests__/VotingRound.test.ts +174 -0
- package/src/swarm/analytics/SwarmInspector.ts +476 -0
- package/src/swarm/analytics/SwarmMetrics.ts +449 -0
- package/src/swarm/analytics/__tests__/SwarmInspector.prod.test.ts +366 -0
- package/src/swarm/analytics/__tests__/SwarmInspector.test.ts +454 -0
- package/src/swarm/analytics/__tests__/SwarmMetrics.prod.test.ts +254 -0
- package/src/swarm/analytics/__tests__/SwarmMetrics.test.ts +370 -0
- package/src/swarm/analytics/index.ts +7 -0
- package/src/swarm/index.ts +69 -0
- package/src/swarm/messaging/BroadcastChannel.ts +509 -0
- package/src/swarm/messaging/GossipProtocol.ts +565 -0
- package/src/swarm/messaging/SwarmEventBus.ts +443 -0
- package/src/swarm/messaging/__tests__/BroadcastChannel.prod.test.ts +331 -0
- package/src/swarm/messaging/__tests__/BroadcastChannel.test.ts +333 -0
- package/src/swarm/messaging/__tests__/GossipProtocol.prod.test.ts +356 -0
- package/src/swarm/messaging/__tests__/GossipProtocol.test.ts +437 -0
- package/src/swarm/messaging/__tests__/SwarmEventBus.prod.test.ts +191 -0
- package/src/swarm/messaging/__tests__/SwarmEventBus.test.ts +247 -0
- package/src/swarm/messaging/index.ts +8 -0
- package/src/swarm/spatial/FlockingBehavior.ts +462 -0
- package/src/swarm/spatial/FormationController.ts +500 -0
- package/src/swarm/spatial/Vector3.ts +170 -0
- package/src/swarm/spatial/ZoneClaiming.ts +509 -0
- package/src/swarm/spatial/__tests__/FlockingBehavior.prod.test.ts +239 -0
- package/src/swarm/spatial/__tests__/FlockingBehavior.test.ts +298 -0
- package/src/swarm/spatial/__tests__/FormationController.prod.test.ts +240 -0
- package/src/swarm/spatial/__tests__/FormationController.test.ts +297 -0
- package/src/swarm/spatial/__tests__/Vector3.prod.test.ts +283 -0
- package/src/swarm/spatial/__tests__/Vector3.test.ts +224 -0
- package/src/swarm/spatial/__tests__/ZoneClaiming.prod.test.ts +246 -0
- package/src/swarm/spatial/__tests__/ZoneClaiming.test.ts +374 -0
- package/src/swarm/spatial/index.ts +28 -0
- package/src/team.ts +1245 -0
- package/src/training/LRScheduler.ts +377 -0
- package/src/training/QualityScoringPipeline.ts +139 -0
- package/src/training/SoftDedup.ts +461 -0
- package/src/training/SparsityMonitor.ts +685 -0
- package/src/training/SparsityMonitorTypes.ts +209 -0
- package/src/training/SpatialTrainingDataGenerator.ts +1526 -0
- package/src/training/SpatialTrainingDataTypes.ts +216 -0
- package/src/training/TrainingPipelineConfig.ts +215 -0
- package/src/training/constants.ts +94 -0
- package/src/training/index.ts +138 -0
- package/src/training/schema.ts +147 -0
- package/src/training/scripts/generate-novel-use-cases-dataset.ts +272 -0
- package/src/training/scripts/generate-spatial-dataset.ts +521 -0
- package/src/training/training/data/novel-use-cases.jsonl +153 -0
- package/src/training/training/data/spatial-reasoning-10k.jsonl +9354 -0
- package/src/training/trainingmonkey/TrainingMonkeyIntegration.ts +477 -0
- package/src/training/trainingmonkey/TrainingMonkeyTypes.ts +230 -0
- package/src/training/trainingmonkey/index.ts +26 -0
- package/src/training/trait-mappings.ts +157 -0
- package/src/types/core-stubs.d.ts +113 -0
- package/src/types.ts +304 -0
- package/test-output.txt +0 -0
- package/test-result.json +1 -0
- package/tsc-errors.txt +4 -0
- package/tsc_output.txt +0 -0
- package/tsconfig.json +14 -0
- package/tsup-learning-esm.config.ts +12 -0
- package/tsup.config.ts +21 -0
- package/typescript-errors-2.txt +0 -0
- package/typescript-errors.txt +22 -0
- package/vitest-log-utf8.txt +268 -0
- package/vitest-log.txt +0 -0
- package/vitest.config.ts +8 -0
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Knowledge Marketplace — Buy, sell, and price knowledge entries via x402 micropayments.
|
|
3
|
+
*
|
|
4
|
+
* Enables agents to monetize their discovered W/P/G entries.
|
|
5
|
+
* Integrates with KnowledgeStore for entry lookup and X402Facilitator for settlement.
|
|
6
|
+
*
|
|
7
|
+
* FW-0.6
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { StoredEntry } from '../knowledge/knowledge-store';
|
|
11
|
+
import type { KnowledgeInsight } from '../types';
|
|
12
|
+
|
|
13
|
+
// ── Types ──
|
|
14
|
+
|
|
15
|
+
export type ListingStatus = 'active' | 'sold' | 'delisted';
|
|
16
|
+
|
|
17
|
+
export interface KnowledgeListing {
|
|
18
|
+
id: string;
|
|
19
|
+
entryId: string;
|
|
20
|
+
seller: string;
|
|
21
|
+
price: number;
|
|
22
|
+
/** Currency: 'USDC' for x402 settlement, 'credits' for in-memory ledger. */
|
|
23
|
+
currency: 'USDC' | 'credits';
|
|
24
|
+
status: ListingStatus;
|
|
25
|
+
createdAt: string;
|
|
26
|
+
/** Entry snapshot for buyer preview (type + domain + truncated content). */
|
|
27
|
+
preview: {
|
|
28
|
+
type: KnowledgeInsight['type'];
|
|
29
|
+
domain: string;
|
|
30
|
+
snippet: string;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface PurchaseResult {
|
|
35
|
+
success: boolean;
|
|
36
|
+
listingId: string;
|
|
37
|
+
entryId?: string;
|
|
38
|
+
buyer: string;
|
|
39
|
+
price: number;
|
|
40
|
+
error?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface ListingResult {
|
|
44
|
+
success: boolean;
|
|
45
|
+
listingId: string;
|
|
46
|
+
error?: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface PricingFactors {
|
|
50
|
+
/** Base price per type: wisdom > pattern > gotcha. */
|
|
51
|
+
typeWeights?: Record<KnowledgeInsight['type'], number>;
|
|
52
|
+
/** Multiplier for high-confidence entries (confidence >= 0.8). */
|
|
53
|
+
confidenceMultiplier?: number;
|
|
54
|
+
/** Multiplier for high-reuse entries (reuseCount >= 5). */
|
|
55
|
+
reuseMultiplier?: number;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const DEFAULT_TYPE_WEIGHTS: Record<KnowledgeInsight['type'], number> = {
|
|
59
|
+
wisdom: 0.05,
|
|
60
|
+
pattern: 0.03,
|
|
61
|
+
gotcha: 0.02,
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// ── Marketplace ──
|
|
65
|
+
|
|
66
|
+
export class KnowledgeMarketplace {
|
|
67
|
+
private listings: Map<string, KnowledgeListing> = new Map();
|
|
68
|
+
private purchases: Map<string, PurchaseResult[]> = new Map(); // buyer -> purchases
|
|
69
|
+
private nextId = 1;
|
|
70
|
+
private pricingFactors: PricingFactors;
|
|
71
|
+
|
|
72
|
+
constructor(pricingFactors?: PricingFactors) {
|
|
73
|
+
this.pricingFactors = pricingFactors ?? {};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Estimate the value of a knowledge entry (in USDC). */
|
|
77
|
+
priceKnowledge(entry: StoredEntry): number {
|
|
78
|
+
const weights = this.pricingFactors.typeWeights ?? DEFAULT_TYPE_WEIGHTS;
|
|
79
|
+
let price = weights[entry.type] ?? 0.02;
|
|
80
|
+
|
|
81
|
+
// Confidence boost
|
|
82
|
+
if (entry.confidence >= 0.8) {
|
|
83
|
+
price *= (this.pricingFactors.confidenceMultiplier ?? 1.5);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Reuse boost — popular entries are worth more
|
|
87
|
+
if (entry.reuseCount >= 5) {
|
|
88
|
+
price *= (this.pricingFactors.reuseMultiplier ?? 2.0);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Query frequency boost
|
|
92
|
+
if (entry.queryCount >= 10) {
|
|
93
|
+
price *= 1.25;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Round to 4 decimal places
|
|
97
|
+
return Math.round(price * 10_000) / 10_000;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** List a knowledge entry for sale. */
|
|
101
|
+
sellKnowledge(entry: StoredEntry, price: number, seller: string, currency: 'USDC' | 'credits' = 'USDC'): ListingResult {
|
|
102
|
+
if (price <= 0) return { success: false, listingId: '', error: 'Price must be positive' };
|
|
103
|
+
|
|
104
|
+
// Prevent duplicate listings for same entry
|
|
105
|
+
for (const listing of this.listings.values()) {
|
|
106
|
+
if (listing.entryId === entry.id && listing.status === 'active') {
|
|
107
|
+
return { success: false, listingId: listing.id, error: 'Entry already listed' };
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const id = `listing_${String(this.nextId++).padStart(4, '0')}`;
|
|
112
|
+
|
|
113
|
+
const listing: KnowledgeListing = {
|
|
114
|
+
id,
|
|
115
|
+
entryId: entry.id,
|
|
116
|
+
seller,
|
|
117
|
+
price,
|
|
118
|
+
currency,
|
|
119
|
+
status: 'active',
|
|
120
|
+
createdAt: new Date().toISOString(),
|
|
121
|
+
preview: {
|
|
122
|
+
type: entry.type,
|
|
123
|
+
domain: entry.domain,
|
|
124
|
+
snippet: entry.content.slice(0, 100),
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
this.listings.set(id, listing);
|
|
129
|
+
return { success: true, listingId: id };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** Buy a listed knowledge entry. */
|
|
133
|
+
buyKnowledge(listingId: string, buyer: string): PurchaseResult {
|
|
134
|
+
const listing = this.listings.get(listingId);
|
|
135
|
+
if (!listing) return { success: false, listingId, buyer, price: 0, error: 'Listing not found' };
|
|
136
|
+
if (listing.status !== 'active') return { success: false, listingId, buyer, price: listing.price, error: `Listing is ${listing.status}` };
|
|
137
|
+
if (listing.seller === buyer) return { success: false, listingId, buyer, price: listing.price, error: 'Cannot buy your own listing' };
|
|
138
|
+
|
|
139
|
+
listing.status = 'sold';
|
|
140
|
+
|
|
141
|
+
const result: PurchaseResult = {
|
|
142
|
+
success: true,
|
|
143
|
+
listingId,
|
|
144
|
+
entryId: listing.entryId,
|
|
145
|
+
buyer,
|
|
146
|
+
price: listing.price,
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
// Track purchases per buyer
|
|
150
|
+
const buyerPurchases = this.purchases.get(buyer) ?? [];
|
|
151
|
+
buyerPurchases.push(result);
|
|
152
|
+
this.purchases.set(buyer, buyerPurchases);
|
|
153
|
+
|
|
154
|
+
return result;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** Get a listing by ID. */
|
|
158
|
+
getListing(listingId: string): KnowledgeListing | undefined {
|
|
159
|
+
return this.listings.get(listingId);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** List all active listings. */
|
|
163
|
+
activeListings(): KnowledgeListing[] {
|
|
164
|
+
return Array.from(this.listings.values()).filter(l => l.status === 'active');
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** Get purchase history for a buyer. */
|
|
168
|
+
purchaseHistory(buyer: string): PurchaseResult[] {
|
|
169
|
+
return this.purchases.get(buyer) ?? [];
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** Delist an entry (only the seller can delist). */
|
|
173
|
+
delist(listingId: string, seller: string): boolean {
|
|
174
|
+
const listing = this.listings.get(listingId);
|
|
175
|
+
if (!listing || listing.seller !== seller || listing.status !== 'active') return false;
|
|
176
|
+
listing.status = 'delisted';
|
|
177
|
+
return true;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** Total revenue for a seller across all sold listings. */
|
|
181
|
+
sellerRevenue(seller: string): number {
|
|
182
|
+
return Array.from(this.listings.values())
|
|
183
|
+
.filter(l => l.seller === seller && l.status === 'sold')
|
|
184
|
+
.reduce((sum, l) => sum + l.price, 0);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** Total marketplace volume (all completed sales). */
|
|
188
|
+
totalVolume(): number {
|
|
189
|
+
return Array.from(this.listings.values())
|
|
190
|
+
.filter(l => l.status === 'sold')
|
|
191
|
+
.reduce((sum, l) => sum + l.price, 0);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
@@ -0,0 +1,512 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PaymentWebhookService — Webhook processing for payment confirmations
|
|
3
|
+
*
|
|
4
|
+
* Receives payment provider webhooks, verifies HMAC-SHA256 signatures,
|
|
5
|
+
* updates LedgerEntry settlement status, and emits telemetry events.
|
|
6
|
+
*
|
|
7
|
+
* Part of HoloScript v5.8 "Live Economy".
|
|
8
|
+
*
|
|
9
|
+
* @version 1.0.0
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { TelemetryCollector } from './_core-stubs';
|
|
13
|
+
import type { LedgerEntry } from './x402-facilitator';
|
|
14
|
+
|
|
15
|
+
// =============================================================================
|
|
16
|
+
// TYPES
|
|
17
|
+
// =============================================================================
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Supported webhook providers.
|
|
21
|
+
*/
|
|
22
|
+
export type WebhookProvider = 'x402' | 'stripe' | 'coinbase' | 'custom';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Webhook event types.
|
|
26
|
+
*/
|
|
27
|
+
export type WebhookEventType =
|
|
28
|
+
| 'payment.confirmed'
|
|
29
|
+
| 'payment.failed'
|
|
30
|
+
| 'payment.refunded'
|
|
31
|
+
| 'settlement.completed'
|
|
32
|
+
| 'settlement.failed'
|
|
33
|
+
| 'subscription.renewed'
|
|
34
|
+
| 'subscription.cancelled';
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Incoming webhook payload.
|
|
38
|
+
*/
|
|
39
|
+
export interface WebhookPayload {
|
|
40
|
+
/** Provider-specific event ID */
|
|
41
|
+
eventId: string;
|
|
42
|
+
/** Event type */
|
|
43
|
+
type: WebhookEventType;
|
|
44
|
+
/** Provider name */
|
|
45
|
+
provider: WebhookProvider;
|
|
46
|
+
/** Timestamp of the event (ISO 8601) */
|
|
47
|
+
timestamp: string;
|
|
48
|
+
/** Provider-specific data */
|
|
49
|
+
data: Record<string, unknown>;
|
|
50
|
+
/** Ledger entry ID (if applicable) */
|
|
51
|
+
ledgerEntryId?: string;
|
|
52
|
+
/** Transaction hash (if on-chain) */
|
|
53
|
+
transactionHash?: string;
|
|
54
|
+
/** Amount in USDC base units */
|
|
55
|
+
amount?: number;
|
|
56
|
+
/** Payer identifier */
|
|
57
|
+
payer?: string;
|
|
58
|
+
/** Recipient identifier */
|
|
59
|
+
recipient?: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Webhook verification result.
|
|
64
|
+
*/
|
|
65
|
+
export interface WebhookVerificationResult {
|
|
66
|
+
/** Whether the webhook signature is valid */
|
|
67
|
+
verified: boolean;
|
|
68
|
+
/** Provider name */
|
|
69
|
+
provider: WebhookProvider;
|
|
70
|
+
/** Error message if verification failed */
|
|
71
|
+
error?: string;
|
|
72
|
+
/** Parsed payload (if verified) */
|
|
73
|
+
payload?: WebhookPayload;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Webhook processing result.
|
|
78
|
+
*/
|
|
79
|
+
export interface WebhookProcessingResult {
|
|
80
|
+
/** Whether processing succeeded */
|
|
81
|
+
success: boolean;
|
|
82
|
+
/** Event ID that was processed */
|
|
83
|
+
eventId: string;
|
|
84
|
+
/** Event type */
|
|
85
|
+
type: WebhookEventType;
|
|
86
|
+
/** Updated ledger entry (if applicable) */
|
|
87
|
+
updatedEntry?: LedgerEntry;
|
|
88
|
+
/** Error message if processing failed */
|
|
89
|
+
error?: string;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Webhook handler function type.
|
|
94
|
+
*/
|
|
95
|
+
export type WebhookHandler = (
|
|
96
|
+
payload: WebhookPayload
|
|
97
|
+
) => Promise<WebhookProcessingResult> | WebhookProcessingResult;
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Configuration for the webhook service.
|
|
101
|
+
*/
|
|
102
|
+
export interface WebhookServiceConfig {
|
|
103
|
+
/** HMAC secrets per provider */
|
|
104
|
+
secrets: Partial<Record<WebhookProvider, string>>;
|
|
105
|
+
/** Maximum age for webhook events (ms, default: 5 minutes) */
|
|
106
|
+
maxAgeMs?: number;
|
|
107
|
+
/** Maximum retry attempts for failed deliveries */
|
|
108
|
+
maxRetries?: number;
|
|
109
|
+
/** Retry backoff base (ms) */
|
|
110
|
+
retryBackoffMs?: number;
|
|
111
|
+
/** Telemetry collector */
|
|
112
|
+
telemetry?: TelemetryCollector;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Retry queue entry.
|
|
117
|
+
*/
|
|
118
|
+
interface RetryEntry {
|
|
119
|
+
payload: WebhookPayload;
|
|
120
|
+
attempts: number;
|
|
121
|
+
nextRetryAt: number;
|
|
122
|
+
lastError?: string;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// =============================================================================
|
|
126
|
+
// HMAC UTILITIES
|
|
127
|
+
// =============================================================================
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Compute HMAC-SHA256 hex digest (Node.js crypto).
|
|
131
|
+
*/
|
|
132
|
+
function computeHmac(payload: string, secret: string): string {
|
|
133
|
+
// Use dynamic import pattern to work in both Node and test environments
|
|
134
|
+
try {
|
|
135
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
136
|
+
const crypto = require('crypto');
|
|
137
|
+
return crypto.createHmac('sha256', secret).update(payload).digest('hex');
|
|
138
|
+
} catch {
|
|
139
|
+
// Fallback for environments without crypto
|
|
140
|
+
// Simple hash for testing — never use in production
|
|
141
|
+
let hash = 0;
|
|
142
|
+
const combined = payload + secret;
|
|
143
|
+
for (let i = 0; i < combined.length; i++) {
|
|
144
|
+
const char = combined.charCodeAt(i);
|
|
145
|
+
hash = (hash << 5) - hash + char;
|
|
146
|
+
hash = hash & hash; // Convert to 32-bit integer
|
|
147
|
+
}
|
|
148
|
+
return Math.abs(hash).toString(16).padStart(8, '0');
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Timing-safe string comparison.
|
|
154
|
+
*/
|
|
155
|
+
function timingSafeEqual(a: string, b: string): boolean {
|
|
156
|
+
if (a.length !== b.length) return false;
|
|
157
|
+
let result = 0;
|
|
158
|
+
for (let i = 0; i < a.length; i++) {
|
|
159
|
+
result |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
160
|
+
}
|
|
161
|
+
return result === 0;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// =============================================================================
|
|
165
|
+
// PAYMENT WEBHOOK SERVICE
|
|
166
|
+
// =============================================================================
|
|
167
|
+
|
|
168
|
+
export class PaymentWebhookService {
|
|
169
|
+
private config: Required<Omit<WebhookServiceConfig, 'telemetry'>> & {
|
|
170
|
+
telemetry?: TelemetryCollector;
|
|
171
|
+
};
|
|
172
|
+
private handlers: Map<WebhookEventType, WebhookHandler[]> = new Map();
|
|
173
|
+
private processedEvents: Set<string> = new Set();
|
|
174
|
+
private retryQueue: RetryEntry[] = [];
|
|
175
|
+
private ledgerUpdateCallback?: (entryId: string, updates: Partial<LedgerEntry>) => void;
|
|
176
|
+
|
|
177
|
+
// Stats
|
|
178
|
+
private stats = {
|
|
179
|
+
received: 0,
|
|
180
|
+
verified: 0,
|
|
181
|
+
processed: 0,
|
|
182
|
+
failed: 0,
|
|
183
|
+
retried: 0,
|
|
184
|
+
rejected: 0,
|
|
185
|
+
duplicates: 0,
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
constructor(config: WebhookServiceConfig) {
|
|
189
|
+
this.config = {
|
|
190
|
+
secrets: config.secrets,
|
|
191
|
+
maxAgeMs: config.maxAgeMs ?? 5 * 60 * 1000, // 5 minutes
|
|
192
|
+
maxRetries: config.maxRetries ?? 3,
|
|
193
|
+
retryBackoffMs: config.retryBackoffMs ?? 1000,
|
|
194
|
+
telemetry: config.telemetry,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// ===========================================================================
|
|
199
|
+
// VERIFICATION
|
|
200
|
+
// ===========================================================================
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Verify a webhook's HMAC-SHA256 signature.
|
|
204
|
+
*/
|
|
205
|
+
verifySignature(
|
|
206
|
+
rawBody: string,
|
|
207
|
+
signature: string,
|
|
208
|
+
provider: WebhookProvider
|
|
209
|
+
): WebhookVerificationResult {
|
|
210
|
+
this.stats.received++;
|
|
211
|
+
|
|
212
|
+
const secret = this.config.secrets[provider];
|
|
213
|
+
if (!secret) {
|
|
214
|
+
this.stats.rejected++;
|
|
215
|
+
return {
|
|
216
|
+
verified: false,
|
|
217
|
+
provider,
|
|
218
|
+
error: `No secret configured for provider: ${provider}`,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const expected = computeHmac(rawBody, secret);
|
|
223
|
+
const isValid = timingSafeEqual(expected, signature);
|
|
224
|
+
|
|
225
|
+
if (!isValid) {
|
|
226
|
+
this.stats.rejected++;
|
|
227
|
+
this.emitTelemetry('webhook_signature_invalid', { provider });
|
|
228
|
+
return {
|
|
229
|
+
verified: false,
|
|
230
|
+
provider,
|
|
231
|
+
error: 'Invalid HMAC-SHA256 signature',
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Parse payload
|
|
236
|
+
let payload: WebhookPayload;
|
|
237
|
+
try {
|
|
238
|
+
payload = JSON.parse(rawBody) as WebhookPayload;
|
|
239
|
+
} catch {
|
|
240
|
+
this.stats.rejected++;
|
|
241
|
+
return {
|
|
242
|
+
verified: false,
|
|
243
|
+
provider,
|
|
244
|
+
error: 'Invalid JSON payload',
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Check age
|
|
249
|
+
const eventAge = Date.now() - new Date(payload.timestamp).getTime();
|
|
250
|
+
if (eventAge > this.config.maxAgeMs) {
|
|
251
|
+
this.stats.rejected++;
|
|
252
|
+
return {
|
|
253
|
+
verified: false,
|
|
254
|
+
provider,
|
|
255
|
+
error: `Webhook too old: ${Math.round(eventAge / 1000)}s (max: ${Math.round(this.config.maxAgeMs / 1000)}s)`,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
this.stats.verified++;
|
|
260
|
+
this.emitTelemetry('webhook_verified', { provider, eventId: payload.eventId });
|
|
261
|
+
|
|
262
|
+
return {
|
|
263
|
+
verified: true,
|
|
264
|
+
provider,
|
|
265
|
+
payload,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// ===========================================================================
|
|
270
|
+
// PROCESSING
|
|
271
|
+
// ===========================================================================
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Process a verified webhook payload.
|
|
275
|
+
*/
|
|
276
|
+
async processWebhook(payload: WebhookPayload): Promise<WebhookProcessingResult> {
|
|
277
|
+
// Idempotency check
|
|
278
|
+
if (this.processedEvents.has(payload.eventId)) {
|
|
279
|
+
this.stats.duplicates++;
|
|
280
|
+
return {
|
|
281
|
+
success: true,
|
|
282
|
+
eventId: payload.eventId,
|
|
283
|
+
type: payload.type,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Update ledger entry if applicable
|
|
288
|
+
let updatedEntry: LedgerEntry | undefined;
|
|
289
|
+
if (payload.ledgerEntryId && this.ledgerUpdateCallback) {
|
|
290
|
+
try {
|
|
291
|
+
if (payload.type === 'payment.confirmed' || payload.type === 'settlement.completed') {
|
|
292
|
+
this.ledgerUpdateCallback(payload.ledgerEntryId, {
|
|
293
|
+
settled: true,
|
|
294
|
+
settlementTx: payload.transactionHash || null,
|
|
295
|
+
});
|
|
296
|
+
} else if (payload.type === 'payment.failed' || payload.type === 'settlement.failed') {
|
|
297
|
+
this.ledgerUpdateCallback(payload.ledgerEntryId, {
|
|
298
|
+
settled: false,
|
|
299
|
+
settlementTx: null,
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
} catch (err) {
|
|
303
|
+
// Log but don't fail — handlers may still want this event
|
|
304
|
+
this.emitTelemetry('webhook_ledger_update_error', {
|
|
305
|
+
eventId: payload.eventId,
|
|
306
|
+
error: err instanceof Error ? err.message : String(err),
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Invoke registered handlers
|
|
312
|
+
const handlers = this.handlers.get(payload.type) || [];
|
|
313
|
+
const errors: string[] = [];
|
|
314
|
+
|
|
315
|
+
for (const handler of handlers) {
|
|
316
|
+
try {
|
|
317
|
+
const result = await handler(payload);
|
|
318
|
+
if (!result.success && result.error) {
|
|
319
|
+
errors.push(result.error);
|
|
320
|
+
}
|
|
321
|
+
if (result.updatedEntry) {
|
|
322
|
+
updatedEntry = result.updatedEntry;
|
|
323
|
+
}
|
|
324
|
+
} catch (err) {
|
|
325
|
+
errors.push(err instanceof Error ? err.message : String(err));
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (errors.length > 0) {
|
|
330
|
+
this.stats.failed++;
|
|
331
|
+
this.emitTelemetry('webhook_processing_failed', {
|
|
332
|
+
eventId: payload.eventId,
|
|
333
|
+
type: payload.type,
|
|
334
|
+
errors,
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
// Add to retry queue
|
|
338
|
+
this.addToRetryQueue(payload, errors.join('; '));
|
|
339
|
+
|
|
340
|
+
return {
|
|
341
|
+
success: false,
|
|
342
|
+
eventId: payload.eventId,
|
|
343
|
+
type: payload.type,
|
|
344
|
+
error: errors.join('; '),
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Mark as processed (idempotency)
|
|
349
|
+
this.processedEvents.add(payload.eventId);
|
|
350
|
+
|
|
351
|
+
// Trim processed events set if too large
|
|
352
|
+
if (this.processedEvents.size > 10000) {
|
|
353
|
+
const entries = [...this.processedEvents];
|
|
354
|
+
this.processedEvents = new Set(entries.slice(-5000));
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
this.stats.processed++;
|
|
358
|
+
this.emitTelemetry('webhook_processed', {
|
|
359
|
+
eventId: payload.eventId,
|
|
360
|
+
type: payload.type,
|
|
361
|
+
provider: payload.provider,
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
return {
|
|
365
|
+
success: true,
|
|
366
|
+
eventId: payload.eventId,
|
|
367
|
+
type: payload.type,
|
|
368
|
+
updatedEntry,
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// ===========================================================================
|
|
373
|
+
// HANDLER REGISTRATION
|
|
374
|
+
// ===========================================================================
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Register a handler for a specific webhook event type.
|
|
378
|
+
*/
|
|
379
|
+
on(eventType: WebhookEventType, handler: WebhookHandler): void {
|
|
380
|
+
const existing = this.handlers.get(eventType) || [];
|
|
381
|
+
existing.push(handler);
|
|
382
|
+
this.handlers.set(eventType, existing);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Remove a handler for a specific webhook event type.
|
|
387
|
+
*/
|
|
388
|
+
off(eventType: WebhookEventType, handler: WebhookHandler): void {
|
|
389
|
+
const existing = this.handlers.get(eventType) || [];
|
|
390
|
+
this.handlers.set(
|
|
391
|
+
eventType,
|
|
392
|
+
existing.filter((h) => h !== handler)
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Set callback for ledger entry updates.
|
|
398
|
+
*/
|
|
399
|
+
onLedgerUpdate(callback: (entryId: string, updates: Partial<LedgerEntry>) => void): void {
|
|
400
|
+
this.ledgerUpdateCallback = callback;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// ===========================================================================
|
|
404
|
+
// RETRY QUEUE
|
|
405
|
+
// ===========================================================================
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Add a failed webhook to the retry queue.
|
|
409
|
+
*/
|
|
410
|
+
private addToRetryQueue(payload: WebhookPayload, error: string): void {
|
|
411
|
+
const existing = this.retryQueue.find((r) => r.payload.eventId === payload.eventId);
|
|
412
|
+
if (existing) {
|
|
413
|
+
existing.attempts++;
|
|
414
|
+
existing.lastError = error;
|
|
415
|
+
existing.nextRetryAt =
|
|
416
|
+
Date.now() + this.config.retryBackoffMs * Math.pow(2, existing.attempts - 1);
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
if (this.retryQueue.length >= 1000) {
|
|
421
|
+
// Evict oldest
|
|
422
|
+
this.retryQueue.shift();
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
this.retryQueue.push({
|
|
426
|
+
payload,
|
|
427
|
+
attempts: 1,
|
|
428
|
+
nextRetryAt: Date.now() + this.config.retryBackoffMs,
|
|
429
|
+
lastError: error,
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* Process retry queue entries that are ready.
|
|
435
|
+
*/
|
|
436
|
+
async processRetryQueue(): Promise<number> {
|
|
437
|
+
const now = Date.now();
|
|
438
|
+
const ready = this.retryQueue.filter(
|
|
439
|
+
(r) => r.nextRetryAt <= now && r.attempts < this.config.maxRetries
|
|
440
|
+
);
|
|
441
|
+
|
|
442
|
+
let processed = 0;
|
|
443
|
+
for (const entry of ready) {
|
|
444
|
+
const result = await this.processWebhook(entry.payload);
|
|
445
|
+
if (result.success) {
|
|
446
|
+
// Remove from queue
|
|
447
|
+
const idx = this.retryQueue.indexOf(entry);
|
|
448
|
+
if (idx >= 0) this.retryQueue.splice(idx, 1);
|
|
449
|
+
this.stats.retried++;
|
|
450
|
+
processed++;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// Remove entries that exceeded max retries
|
|
455
|
+
this.retryQueue = this.retryQueue.filter((r) => r.attempts < this.config.maxRetries);
|
|
456
|
+
|
|
457
|
+
return processed;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Get retry queue length.
|
|
462
|
+
*/
|
|
463
|
+
getRetryQueueLength(): number {
|
|
464
|
+
return this.retryQueue.length;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// ===========================================================================
|
|
468
|
+
// QUERIES
|
|
469
|
+
// ===========================================================================
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Check if an event has been processed.
|
|
473
|
+
*/
|
|
474
|
+
isProcessed(eventId: string): boolean {
|
|
475
|
+
return this.processedEvents.has(eventId);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Get comprehensive stats.
|
|
480
|
+
*/
|
|
481
|
+
getStats(): typeof this.stats & { retryQueueLength: number } {
|
|
482
|
+
return {
|
|
483
|
+
...this.stats,
|
|
484
|
+
retryQueueLength: this.retryQueue.length,
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Create a webhook signature for testing purposes.
|
|
490
|
+
*/
|
|
491
|
+
createSignature(rawBody: string, provider: WebhookProvider): string {
|
|
492
|
+
const secret = this.config.secrets[provider];
|
|
493
|
+
if (!secret) throw new Error(`No secret for provider: ${provider}`);
|
|
494
|
+
return computeHmac(rawBody, secret);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// ===========================================================================
|
|
498
|
+
// TELEMETRY
|
|
499
|
+
// ===========================================================================
|
|
500
|
+
|
|
501
|
+
private emitTelemetry(type: string, data?: Record<string, unknown>): void {
|
|
502
|
+
this.config.telemetry?.record({
|
|
503
|
+
type,
|
|
504
|
+
severity:
|
|
505
|
+
type.includes('error') || type.includes('failed') || type.includes('invalid')
|
|
506
|
+
? 'error'
|
|
507
|
+
: 'info',
|
|
508
|
+
agentId: 'payment-webhook-service',
|
|
509
|
+
data,
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
}
|