@wowok/agent-mcp 3.3.3 → 3.3.6

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 (202) hide show
  1. package/dist/chain-semantics/eval-order.d.ts +17 -0
  2. package/dist/chain-semantics/eval-order.js +1 -0
  3. package/dist/chain-semantics/freeze-map.js +1 -1
  4. package/dist/chain-semantics/fund-flow.js +1 -1
  5. package/dist/chain-semantics/index.d.ts +1 -0
  6. package/dist/chain-semantics/index.js +1 -1
  7. package/dist/chain-semantics/object-graph.js +1 -1
  8. package/dist/composition/audit.js +1 -1
  9. package/dist/composition/compile.d.ts +4 -1
  10. package/dist/composition/compile.js +1 -1
  11. package/dist/data/api/api.spec.d.ts +1 -0
  12. package/dist/data/api/api.spec.js +1 -0
  13. package/dist/data/api/cli.d.ts +1 -0
  14. package/dist/data/api/cli.js +1 -0
  15. package/dist/data/api/head.d.ts +16 -0
  16. package/dist/data/api/head.js +1 -0
  17. package/dist/data/api/head.spec.d.ts +1 -0
  18. package/dist/data/api/head.spec.js +1 -0
  19. package/dist/data/api/index.d.ts +2 -0
  20. package/dist/data/api/index.js +1 -0
  21. package/dist/data/api/match.d.ts +69 -0
  22. package/dist/data/api/match.js +1 -0
  23. package/dist/data/api/match.spec.d.ts +1 -0
  24. package/dist/data/api/match.spec.js +1 -0
  25. package/dist/data/api/server.d.ts +12 -0
  26. package/dist/data/api/server.js +1 -0
  27. package/dist/data/projection/cli.d.ts +1 -0
  28. package/dist/data/projection/cli.js +1 -0
  29. package/dist/data/projection/edges.d.ts +8 -0
  30. package/dist/data/projection/edges.js +1 -0
  31. package/dist/data/projection/edges.spec.d.ts +1 -0
  32. package/dist/data/projection/edges.spec.js +1 -0
  33. package/dist/data/projection/facets.d.ts +18 -0
  34. package/dist/data/projection/facets.js +1 -0
  35. package/dist/data/projection/facets.spec.d.ts +1 -0
  36. package/dist/data/projection/facets.spec.js +1 -0
  37. package/dist/data/projection/facts.d.ts +40 -0
  38. package/dist/data/projection/facts.js +1 -0
  39. package/dist/data/projection/index.d.ts +6 -0
  40. package/dist/data/projection/index.js +1 -0
  41. package/dist/data/projection/project.d.ts +10 -0
  42. package/dist/data/projection/project.js +1 -0
  43. package/dist/data/projection/projection.spec.d.ts +1 -0
  44. package/dist/data/projection/projection.spec.js +1 -0
  45. package/dist/data/projection/schema.d.ts +1 -0
  46. package/dist/data/projection/schema.js +1 -0
  47. package/dist/data/projection/store.d.ts +49 -0
  48. package/dist/data/projection/store.js +1 -0
  49. package/dist/data/projection/types.d.ts +137 -0
  50. package/dist/data/projection/types.js +1 -0
  51. package/dist/data/sync/cdc.d.ts +34 -0
  52. package/dist/data/sync/cdc.js +1 -0
  53. package/dist/data/sync/cdc.spec.d.ts +1 -0
  54. package/dist/data/sync/cdc.spec.js +1 -0
  55. package/dist/data/sync/graphql-source.d.ts +16 -0
  56. package/dist/data/sync/graphql-source.js +1 -0
  57. package/dist/data/sync/index.d.ts +5 -0
  58. package/dist/data/sync/index.js +1 -0
  59. package/dist/data/sync/reconciler.d.ts +14 -0
  60. package/dist/data/sync/reconciler.js +1 -0
  61. package/dist/data/sync/reconciler.spec.d.ts +1 -0
  62. package/dist/data/sync/reconciler.spec.js +1 -0
  63. package/dist/data/sync/schema.d.ts +1 -0
  64. package/dist/data/sync/schema.js +1 -0
  65. package/dist/data/sync/store.d.ts +19 -0
  66. package/dist/data/sync/store.js +1 -0
  67. package/dist/data/sync/sync.spec.d.ts +1 -0
  68. package/dist/data/sync/sync.spec.js +1 -0
  69. package/dist/data/sync/synchronizer.d.ts +10 -0
  70. package/dist/data/sync/synchronizer.js +1 -0
  71. package/dist/data/sync/types.d.ts +162 -0
  72. package/dist/data/sync/types.js +1 -0
  73. package/dist/evaluation/builtins/demand-match.js +1 -1
  74. package/dist/evaluation/builtins/graph-service.spec.js +1 -1
  75. package/dist/evaluation/capability.d.ts +1 -0
  76. package/dist/evaluation/engine.js +1 -1
  77. package/dist/evaluation/extract.d.ts +7 -1
  78. package/dist/evaluation/extract.js +1 -1
  79. package/dist/evaluation/match-policy.d.ts +118 -0
  80. package/dist/evaluation/match-policy.js +1 -0
  81. package/dist/evaluation/match-policy.spec.d.ts +1 -0
  82. package/dist/evaluation/match-policy.spec.js +1 -0
  83. package/dist/evaluation/node-game.d.ts +3 -0
  84. package/dist/evaluation/node-game.js +1 -1
  85. package/dist/experience/realtime-feedback.js +1 -1
  86. package/dist/graph/onchain/expand.js +1 -1
  87. package/dist/graph/onchain/exposure.js +1 -1
  88. package/dist/harness/recover.js +1 -1
  89. package/dist/knowledge/arbitration-ledger.js +1 -1
  90. package/dist/knowledge/baselines/rules/plan.json +16 -1
  91. package/dist/knowledge/builtin-templates.d.ts +1 -1
  92. package/dist/knowledge/builtin-templates.js +1 -1
  93. package/dist/knowledge/getting-started.js +1 -1
  94. package/dist/knowledge/guard-design-patterns.d.ts +1 -1
  95. package/dist/knowledge/guard-design-patterns.js +1 -1
  96. package/dist/knowledge/guard-ledger.js +1 -1
  97. package/dist/knowledge/guard-puzzle.js +1 -1
  98. package/dist/knowledge/guard-render.js +1 -1
  99. package/dist/knowledge/guard-risk.js +1 -1
  100. package/dist/knowledge/guard-templates.d.ts +1 -1
  101. package/dist/knowledge/guard-templates.js +1 -1
  102. package/dist/knowledge/guard-translation.js +1 -1
  103. package/dist/knowledge/index.js +1 -1
  104. package/dist/knowledge/industry-registry.js +1 -1
  105. package/dist/knowledge/jsonrpc-enum.d.ts +26 -10
  106. package/dist/knowledge/jsonrpc-enum.js +1 -1
  107. package/dist/knowledge/projection.d.ts +54 -0
  108. package/dist/knowledge/projection.js +1 -0
  109. package/dist/knowledge/projection.spec.d.ts +1 -0
  110. package/dist/knowledge/projection.spec.js +1 -0
  111. package/dist/knowledge/recipient-constraint.d.ts +9 -0
  112. package/dist/knowledge/recipient-constraint.js +1 -1
  113. package/dist/knowledge/scenario-modes.d.ts +1 -0
  114. package/dist/knowledge/service-ledger.d.ts +1 -1
  115. package/dist/knowledge/service-ledger.js +1 -1
  116. package/dist/knowledge/template-loader.d.ts +1 -0
  117. package/dist/knowledge/template-loader.js +1 -1
  118. package/dist/knowledge/template-registry.js +1 -1
  119. package/dist/knowledge/template-scanner.js +1 -1
  120. package/dist/monitor/MonitorLoop.js +1 -1
  121. package/dist/participation/node-action-advisor.js +1 -1
  122. package/dist/playbooks/service-build/business-puzzle.js +1 -1
  123. package/dist/playbooks/service-build/machine-panorama.js +1 -1
  124. package/dist/playbooks/service-build/object-panorama.js +1 -1
  125. package/dist/playbooks/service-build/participation-radar.js +1 -1
  126. package/dist/playbooks/service-build/relationship-profile.js +1 -1
  127. package/dist/playbooks/service-build/service-panorama.js +1 -1
  128. package/dist/playbooks/service-build/service-quote.js +1 -1
  129. package/dist/playbooks/service-build/service-quote.spec.js +1 -1
  130. package/dist/review/index.js +1 -1
  131. package/dist/role/profiles.js +1 -1
  132. package/dist/safety/confirm-gate.js +1 -1
  133. package/dist/schema/call/allocation.js +1 -1
  134. package/dist/schema/call/base.d.ts +25 -0
  135. package/dist/schema/call/base.js +1 -1
  136. package/dist/schema/call/bridge.d.ts +1 -0
  137. package/dist/schema/call/error-codes.d.ts +1 -1
  138. package/dist/schema/call/error-codes.js +1 -1
  139. package/dist/schema/call/handler.js +1 -1
  140. package/dist/schema/call/order.d.ts +12 -0
  141. package/dist/schema/call/order.js +1 -1
  142. package/dist/schema/call/progress.js +1 -1
  143. package/dist/schema/call/proof.js +1 -1
  144. package/dist/schema/call/semantic.js +1 -1
  145. package/dist/schema/call/service.js +1 -1
  146. package/dist/schema/common/index.js +1 -1
  147. package/dist/schema/evaluation/index.d.ts +189 -0
  148. package/dist/schema/evaluation/index.js +1 -1
  149. package/dist/schema/local/index.d.ts +10 -0
  150. package/dist/schema/local/index.js +1 -1
  151. package/dist/schema/operations.d.ts +4 -0
  152. package/dist/schema/query/bi.d.ts +8 -0
  153. package/dist/schema/query/bi.js +1 -1
  154. package/dist/schema/query/index.d.ts +147 -0
  155. package/dist/schema/query/index.js +1 -1
  156. package/dist/schema/schema-version.js +1 -1
  157. package/dist/schema-query-impl/index.js +1 -1
  158. package/dist/schemas/account_operation.output.json +2 -1
  159. package/dist/schemas/bridge_operation.output.json +2 -1
  160. package/dist/schemas/evaluation_operation.output.json +384 -2
  161. package/dist/schemas/evaluation_operation.schema.json +202 -4
  162. package/dist/schemas/index.json +1 -1
  163. package/dist/schemas/keeper_operation.output.json +1 -1
  164. package/dist/schemas/local_cache_operation.output.json +2 -1
  165. package/dist/schemas/local_history_operation.output.json +2 -1
  166. package/dist/schemas/local_info_operation.output.json +2 -1
  167. package/dist/schemas/local_mark_operation.output.json +2 -1
  168. package/dist/schemas/messenger_operation.output.json +1 -1
  169. package/dist/schemas/monitor_events.output.json +1 -1
  170. package/dist/schemas/monitor_subscription.output.json +1 -1
  171. package/dist/schemas/onchain_events.output.json +1 -1
  172. package/dist/schemas/onchain_operations.output.json +35 -4
  173. package/dist/schemas/onchain_operations.schema.json +687 -103
  174. package/dist/schemas/onchain_operations_allocation.schema.json +1 -1
  175. package/dist/schemas/onchain_operations_gen_proof.schema.json +1 -1
  176. package/dist/schemas/onchain_operations_guard.schema.json +660 -94
  177. package/dist/schemas/onchain_operations_order.schema.json +19 -1
  178. package/dist/schemas/onchain_operations_progress.schema.json +2 -2
  179. package/dist/schemas/onchain_operations_service.schema.json +4 -4
  180. package/dist/schemas/onchain_table_data.output.json +2 -2
  181. package/dist/schemas/query_toolkit.output.json +316 -1
  182. package/dist/schemas/query_toolkit.schema.json +1 -1
  183. package/dist/schemas/wowok_buildin_info.output.json +259 -0
  184. package/dist/schemas/wowok_buildin_info.schema.json +121 -0
  185. package/dist/tools/build-fingerprint.d.ts +28 -0
  186. package/dist/tools/build-fingerprint.js +1 -0
  187. package/dist/tools/handlers/benchmark-migration.d.ts +4 -2
  188. package/dist/tools/handlers/benchmark-migration.js +1 -1
  189. package/dist/tools/handlers/config.js +1 -1
  190. package/dist/tools/handlers/evaluation.js +1 -1
  191. package/dist/tools/handlers/evaluation.spec.js +1 -1
  192. package/dist/tools/handlers/onchain.d.ts +7 -0
  193. package/dist/tools/handlers/onchain.js +1 -1
  194. package/dist/tools/handlers/query.js +1 -1
  195. package/dist/tools/handlers/review-chain.d.ts +9 -13
  196. package/dist/tools/handlers/review-chain.js +1 -1
  197. package/dist/tools/index.js +1 -1
  198. package/dist/tools/registry/business.js +1 -1
  199. package/dist/tools/upgrade-check.d.ts +1 -0
  200. package/dist/tools/upgrade-check.js +1 -1
  201. package/dist/tools/wrap.js +1 -1
  202. package/package.json +7 -2
@@ -0,0 +1,34 @@
1
+ import { WowClient, WowGraphQLClient, type ENTRYPOINT } from "@wowok/wowok";
2
+ import { GraphQLSnapshotSource } from "./graphql-source.js";
3
+ import type { CdcChangeBatch, CdcHead, CdcRoundResult, CdcSource, RunCdcOptions } from "./types.js";
4
+ export type { CdcChangeBatch, CdcHead, CdcSource, RunCdcOptions } from "./types.js";
5
+ export declare class GraphQLCdcSource implements CdcSource {
6
+ private readonly graphql;
7
+ private readonly snapshot;
8
+ private readonly rpc;
9
+ private readonly network;
10
+ private readonly expectedChainId;
11
+ constructor(graphql: WowGraphQLClient, snapshot: GraphQLSnapshotSource, rpc: WowClient, network: ENTRYPOINT, expectedChainId: string);
12
+ static forNetwork(network: ENTRYPOINT, expectedChainId: string): GraphQLCdcSource;
13
+ head(): Promise<CdcHead>;
14
+ readChanges(after: string, before: string, kind: string): Promise<CdcChangeBatch>;
15
+ }
16
+ export interface CdcProjection {
17
+ apply(input: unknown): Promise<{
18
+ status: string;
19
+ }>;
20
+ markTombstoneIfKind(chainId: string, objectId: string, kind: string): Promise<boolean>;
21
+ }
22
+ export interface CdcCursorStore {
23
+ cdcCheckpoint(chainId: string, kind: string): Promise<string | null>;
24
+ setCdcCheckpoint(chainId: string, kind: string, checkpoint: string): Promise<void>;
25
+ withCdcLock<T>(chainId: string, kind: string, work: () => Promise<T>): Promise<T>;
26
+ }
27
+ export declare class CdcSynchronizer {
28
+ private readonly projection;
29
+ private readonly source;
30
+ private readonly cursors;
31
+ constructor(projection: CdcProjection, source: CdcSource, cursors: CdcCursorStore);
32
+ run(options: RunCdcOptions): Promise<CdcRoundResult>;
33
+ private poll;
34
+ }
@@ -0,0 +1 @@
1
+ import{WowClient,WowGraphQLClient,getFullnodeUrl,getGraphqlUrl,normalizeWowAddress,wowTypedKindFromMoveType}from'@wowok/wowok';import{canonicalChainIdentifier,GraphQLSnapshotSource}from'./graphql-source.js';const CDC_PAGE_QUERY='\x0aquery\x20CdcPage($after:\x20UInt53!,\x20$before:\x20UInt53!,\x20$cursor:\x20String)\x20{\x0a\x20\x20\x20\x20transactions(\x0a\x20\x20\x20\x20\x20\x20\x20\x20filter:\x20{\x20afterCheckpoint:\x20$after,\x20beforeCheckpoint:\x20$before\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20first:\x2050\x0a\x20\x20\x20\x20\x20\x20\x20\x20after:\x20$cursor\x0a\x20\x20\x20\x20)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20pageInfo\x20{\x20hasNextPage\x20endCursor\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20nodes\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20digest\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20effects\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20checkpoint\x20{\x20sequenceNumber\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20objectChanges\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20nodes\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20address\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20idCreated\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20idDeleted\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20outputState\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20version\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20asMoveObject\x20{\x20contents\x20{\x20type\x20{\x20repr\x20}\x20}\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20}\x0a}',MAX_TX_PAGES=0xc8;export class GraphQLCdcSource{['graphql'];['snapshot'];['rpc'];['network'];['expectedChainId'];constructor(a,b,c,d,e){this['graphql']=a,this['snapshot']=b,this['rpc']=c,this['network']=d,this['expectedChainId']=e;}static['forNetwork'](a,b){const c=getGraphqlUrl(a);if(!c)throw new Error('No\x20GraphQL\x20endpoint\x20configured\x20for\x20'+a);return new GraphQLCdcSource(new WowGraphQLClient({'url':c,'network':a}),GraphQLSnapshotSource['forNetwork'](a,b),new WowClient({'url':getFullnodeUrl(a),'network':a}),a,b);}async['head'](){const [a,b,c]=await Promise['all']([this['graphql']['core']['getChainIdentifier'](),this['rpc']['getChainIdentifier'](),this['rpc']['getLatestCheckpointSequenceNumber']()]),d=canonicalChainIdentifier(a['chainIdentifier']),e=canonicalChainIdentifier(b),f=canonicalChainIdentifier(this['expectedChainId']);if(d!==e||e!==f)throw new Error('CHAIN_IDENTITY_MISMATCH');return{'chainId':e,'checkpoint':c};}async['readChanges'](a,b,c){const d=BigInt(a),e=BigInt(b);if(e<=d)return{'facts':[],'deletedIds':[],'observedThrough':a,'pages':0x0};const f=canonicalChainIdentifier(this['expectedChainId']),g=new Set(),h=new Set();let i=null,j=0x0,k=a;do{const m=await this['graphql']['query']({'query':CDC_PAGE_QUERY,'variables':{'after':Number(d),'before':Number(e),'cursor':i}});if(m['errors']?.['length'])throw new Error('CDC\x20GraphQL\x20error:\x20'+JSON['stringify'](m['errors'])['slice'](0x0,0x1f4));const n=m['data']?.['transactions'];if(!n)throw new Error('CDC\x20GraphQL\x20returned\x20no\x20transactions\x20connection');for(const o of n['nodes']??[]){const p=o?.['effects']?.['checkpoint']?.['sequenceNumber'];if(p===undefined||p===null)continue;const q=BigInt(p);if(q<=d||q>=e)throw new Error('CDC_INTERVAL_VIOLATION:\x20tx\x20checkpoint\x20'+p+'\x20outside\x20('+d+',\x20'+e+')');if(q>BigInt(k))k=String(q);for(const r of o?.['effects']?.['objectChanges']?.['nodes']??[]){const s=normalizeWowAddress(r['address']);if(r['idDeleted']){h['add'](s);continue;}const t=r['outputState']?.['asMoveObject']?.['contents']?.['type']?.['repr'];if(!t)continue;if(wowTypedKindFromMoveType(t)!==c)continue;g['add'](s);}}j+=0x1,i=n['pageInfo']?.['hasNextPage']?n['pageInfo']['endCursor']:null;}while(i!==null&&j<MAX_TX_PAGES);if(i!==null)throw new Error('CDC_PAGE_CAP:\x20exceeded\x20'+MAX_TX_PAGES+'\x20pages\x20for\x20('+a+',\x20'+b+')');for(const u of h)g['delete'](u);const l=await this['snapshot']['hydrateFacts']([...g],f,k);return{'facts':l,'deletedIds':[...h],'observedThrough':k,'pages':j};}}export class CdcSynchronizer{['projection'];['source'];['cursors'];constructor(a,b,c){this['projection']=a,this['source']=b,this['cursors']=c;}async['run'](a){if(!a['expectedChainId'])throw new Error('expectedChainId\x20is\x20required');if(!a['kind'])throw new Error('kind\x20is\x20required');const b=a['maxSpan']??0x1f4;if(!Number['isInteger'](b)||b<0x1)throw new Error('Invalid\x20maxSpan:\x20'+b);return this['cursors']['withCdcLock'](a['expectedChainId'],a['kind'],()=>this['poll'](a,b));}async['poll'](a,b){const c=a['expectedChainId'],d=await this['source']['head']();if(d['chainId']!==c)throw new Error('CHAIN_IDENTITY_MISMATCH:\x20source\x20reported\x20'+d['chainId']);const e=await this['cursors']['cdcCheckpoint'](c,a['kind']),f={'applied':0x0,'duplicate':0x0,'stale':0x0,'quarantined':0x0,'conflict':0x0,'tombstoned':0x0,'skipped_facts':0x0};if(e===null)return await this['cursors']['setCdcCheckpoint'](c,a['kind'],d['checkpoint']),{'chainId':c,'kind':a['kind'],'anchored':!![],'from':null,'to':d['checkpoint'],'head':d['checkpoint'],'pages':0x0,...f};const g=BigInt(e),h=BigInt(d['checkpoint']);if(h<=g)return{'chainId':c,'kind':a['kind'],'anchored':![],'from':e,'to':e,'head':d['checkpoint'],'pages':0x0,...f};const i=h<g+BigInt(b)?h:g+BigInt(b),j=await this['source']['readChanges'](e,(i+0x1n)['toString'](),a['kind']);for(const l of j['facts']){const m=l;if(!m['object_id']){f['skipped_facts']+=0x1;continue;}const n=await this['projection']['apply'](l);n['status']in f&&(f[n['status']]+=0x1);}for(const o of j['deletedIds']){await this['projection']['markTombstoneIfKind'](c,o,a['kind'])&&(f['tombstoned']+=0x1);}const k=j['observedThrough'];return BigInt(k)>g&&await this['cursors']['setCdcCheckpoint'](c,a['kind'],k),{'chainId':c,'kind':a['kind'],'anchored':![],'from':e,'to':BigInt(k)>g?k:e,'head':d['checkpoint'],'pages':j['pages'],...f};}}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ import{toBase58}from'@wowok/wowok';import{DataType,newDb}from'pg-mem';import{afterEach,beforeEach,describe,expect,it,vi}from'vitest';import{PostgresProjectionStore}from'../projection/store.js';import{CdcSynchronizer}from'./cdc.js';import{PostgresSyncStore}from'./store.js';const digest=toBase58(new Uint8Array(0x20)['fill'](0x7));function objectId(a){return'0x14'+a['toString'](0x10)['padStart'](0x2,'0')['repeat'](0x1f);}function fact(a,b={}){return{'chain_id':'chain_a','object_id':objectId(a),'object_version':'1','move_type':'0x2::service::Service<0x2::wow::WOW>','owner_kind':'Shared','payload_uri':'test://raw/service-'+a,'payload_hash':'0x'+'41'['repeat'](0x20),'payload_format':'json+bcs-v1','checkpoint':'100','tx_digest':digest,'state':'active','observed_at':'2026-10-24T00:00:00.000Z',...b};}class FakeCdcSource{['headCheckpoint'];['batches'];['chainId'];['calls']=[];constructor(a,b,c='chain_a'){this['headCheckpoint']=a,this['batches']=b,this['chainId']=c;}async['head'](){return{'chainId':this['chainId'],'checkpoint':this['headCheckpoint']};}async['readChanges'](a,b){this['calls']['push']({'after':a,'before':b});const c=this['batches']['get'](a+':'+b);if(!c)return{'facts':[],'deletedIds':[],'observedThrough':a,'pages':0x0};return c;}}function options(a,b={}){return{'expectedChainId':a['chainId'],'kind':'Service',...b};}describe('CdcSynchronizer',()=>{let a,b,c;beforeEach(async()=>{const e=newDb();e['public']['registerFunction']({'name':'pg_advisory_xact_lock','args':[DataType['integer'],DataType['integer']],'returns':DataType['integer'],'implementation':()=>0x0});const {Pool:f}=e['adapters']['createPg']();c=new f(),a=new PostgresProjectionStore(c,0x10),b=new PostgresSyncStore(c),await a['migrate'](),await b['migrate']();}),afterEach(async()=>{await a?.['close'](),await b?.['close'](),vi['restoreAllMocks']();});function d(e,f){return new FakeCdcSource(e,f??new Map());}it('anchors\x20at\x20head\x20on\x20the\x20first\x20run\x20without\x20applying\x20anything',async()=>{const e=d('120'),f=await new CdcSynchronizer(a,e,b)['run'](options(e));expect(f)['toMatchObject']({'anchored':!![],'from':null,'to':'120','applied':0x0}),expect(await b['cdcCheckpoint']('chain_a','Service'))['toBe']('120'),expect(await a['status']('chain_a'))['toMatchObject']({'active_objects':0x0});}),it('applies\x20facts\x20in\x20the\x20interval\x20and\x20advances\x20to\x20observedThrough',async()=>{await b['setCdcCheckpoint']('chain_a','Service','100');const e=d('150',new Map([['100:151',{'facts':[fact(0x1),fact(0x2,{'object_version':'2','checkpoint':'140'})],'deletedIds':[],'observedThrough':'140','pages':0x2}]])),f=await new CdcSynchronizer(a,e,b)['run'](options(e));expect(f)['toMatchObject']({'anchored':![],'from':'100','to':'140','head':'150','applied':0x2,'pages':0x2}),expect(e['calls'])['toEqual']([{'after':'100','before':'151'}]),expect(await b['cdcCheckpoint']('chain_a','Service'))['toBe']('140'),expect(await a['status']('chain_a'))['toMatchObject']({'active_objects':0x2});}),it('clamps\x20the\x20interval\x20to\x20maxSpan\x20and\x20requests\x20the\x20half-open\x20bound',async()=>{await b['setCdcCheckpoint']('chain_a','Service','100');const e=d('1000');await new CdcSynchronizer(a,e,b)['run'](options(e,{'maxSpan':0x32})),expect(e['calls'])['toEqual']([{'after':'100','before':'151'}]),expect(await b['cdcCheckpoint']('chain_a','Service'))['toBe']('100');}),it('is\x20a\x20no-op\x20when\x20head\x20has\x20not\x20advanced',async()=>{await b['setCdcCheckpoint']('chain_a','Service','150');const e=d('150'),f=await new CdcSynchronizer(a,e,b)['run'](options(e));expect(f)['toMatchObject']({'from':'150','to':'150','applied':0x0}),expect(e['calls'])['toEqual']([]);}),it('replays\x20idempotently\x20after\x20a\x20crash\x20between\x20apply\x20and\x20cursor\x20advance',async()=>{await b['setCdcCheckpoint']('chain_a','Service','100');const e=new Map([['100:102',{'facts':[fact(0x1)],'deletedIds':[],'observedThrough':'101','pages':0x1}]]),f=new FakeCdcSource('101',e),g=new CdcSynchronizer(a,f,b),h=await g['run'](options(f));expect(h['applied'])['toBe'](0x1),await c['query']('UPDATE\x20cdc_cursor\x20SET\x20checkpoint=100\x20WHERE\x20chain_id=$1\x20AND\x20kind=$2',['chain_a','Service']);const i=await g['run'](options(f));expect(i)['toMatchObject']({'applied':0x0,'duplicate':0x1,'to':'101'}),expect(await a['status']('chain_a'))['toMatchObject']({'active_objects':0x1,'raw_versions':0x1});}),it('tombstones\x20kind-matched\x20deletions\x20only',async()=>{await a['apply'](fact(0x1));const e='0x12'+'02'['repeat'](0x1f);await a['apply'](fact(0x2,{'object_id':e,'move_type':'0x2::arb::Arb','payload_uri':'test://raw/arb-2'})),await b['setCdcCheckpoint']('chain_a','Service','100');const f=d('120',new Map([['100:121',{'facts':[],'deletedIds':[objectId(0x1),e,objectId(0x3)],'observedThrough':'120','pages':0x1}]])),g=await new CdcSynchronizer(a,f,b)['run'](options(f));expect(g['tombstoned'])['toBe'](0x1);const h=await a['listCurrent']({'chainId':'chain_a','kind':'Service'});expect(h['objects'])['toHaveLength'](0x0);const i=await a['listCurrent']({'chainId':'chain_a','kind':'Arb'});expect(i['objects'])['toHaveLength'](0x1);}),it('keeps\x20the\x20cursor\x20forward-only\x20when\x20the\x20source\x20reports\x20stale\x20intervals',async()=>{await b['setCdcCheckpoint']('chain_a','Service','150');const e=d('120'),f=await new CdcSynchronizer(a,e,b)['run'](options(e));expect(f)['toMatchObject']({'from':'150','to':'150','head':'120'}),expect(await b['cdcCheckpoint']('chain_a','Service'))['toBe']('150');}),it('does\x20not\x20advance\x20past\x20what\x20the\x20source\x20returned',async()=>{await b['setCdcCheckpoint']('chain_a','Service','100');const e=d('150',new Map([['100:151',{'facts':[],'deletedIds':[],'observedThrough':'130','pages':0x1}]])),f=await new CdcSynchronizer(a,e,b)['run'](options(e));expect(f['to'])['toBe']('130'),expect(await b['cdcCheckpoint']('chain_a','Service'))['toBe']('130');});});
@@ -0,0 +1,16 @@
1
+ import { ENTRYPOINT, WowClient, WowGraphQLClient } from "@wowok/wowok";
2
+ import type { SnapshotObservation, SnapshotPage, SnapshotPageRequest, SnapshotSource } from "./types.js";
3
+ export declare function canonicalChainIdentifier(value: string): string;
4
+ export declare function factFromRpc(network: ENTRYPOINT, chainId: string, checkpoint: string, rpc: Record<string, any>, observedAt: string): Record<string, unknown> | null;
5
+ export declare class GraphQLSnapshotSource implements SnapshotSource {
6
+ private readonly graphql;
7
+ private readonly rpc;
8
+ private readonly network;
9
+ private readonly expectedChainId;
10
+ constructor(graphql: WowGraphQLClient, rpc: WowClient, network: ENTRYPOINT, expectedChainId: string);
11
+ static forNetwork(network: ENTRYPOINT, expectedChainId: string): GraphQLSnapshotSource;
12
+ observe(): Promise<SnapshotObservation>;
13
+ readPage(request: SnapshotPageRequest): Promise<SnapshotPage>;
14
+ private fetchRpcShapes;
15
+ hydrateFacts(objectIds: string[], chainId: string, checkpoint: string, signal?: AbortSignal): Promise<Record<string, unknown>[]>;
16
+ }
@@ -0,0 +1 @@
1
+ import{ObjectError,WowClient,WowGraphQLClient,fromBase58,getFullnodeUrl,getGraphqlUrl,gqlObject2RpcData,needsRpcFallback,normalizeWowAddress,object_name_from_type,toHex}from'@wowok/wowok';export function canonicalChainIdentifier(a){if(/^[0-9a-fA-F]{8}$/['test'](a))return a['toLowerCase']();const b=fromBase58(a);if(b['length']<0x4)throw new Error('Invalid\x20genesis\x20chain\x20identifier');return toHex(b['slice'](0x0,0x4));}function ownerAddress(a){switch(a['$kind']){case'AddressOwner':return a['AddressOwner'];case'ObjectOwner':return a['ObjectOwner'];case'ConsensusAddressOwner':return a['ConsensusAddressOwner']['owner'];default:return undefined;}}export function factFromRpc(a,b,c,d,e){if(!d?.['digest']||!d['type']||!d['content']||!('fields'in d['content']))return null;const f=fromBase58(d['digest']);if(f['length']!==0x20)throw new Error('Invalid\x20object\x20digest:\x20'+d['objectId']);if(!d['previousTransaction'])return null;const g=normalizeWowAddress(d['objectId']);return{'chain_id':b,'object_id':g,'object_version':String(d['version']),'move_type':d['type'],'owner_kind':d['owner']?.['$kind']??'Shared','owner_address':d['owner']?ownerAddress(d['owner']):undefined,'payload_uri':'graphql://'+a+'/object/'+g+'/version/'+d['version'],'payload_hash':'0x'+toHex(f),'payload_format':'rpc-content-v1','payload_json':d['content']?.['fields']??null,'checkpoint':c,'tx_digest':d['previousTransaction'],'state':'active','observed_at':e};}export class GraphQLSnapshotSource{['graphql'];['rpc'];['network'];['expectedChainId'];constructor(a,b,c,d){this['graphql']=a,this['rpc']=b,this['network']=c,this['expectedChainId']=d;}static['forNetwork'](a,b){const c=getGraphqlUrl(a);if(!c)throw new Error('No\x20GraphQL\x20endpoint\x20configured\x20for\x20'+a);return new GraphQLSnapshotSource(new WowGraphQLClient({'url':c,'network':a}),new WowClient({'url':getFullnodeUrl(a),'network':a}),a,b);}async['observe'](){const [a,b,c]=await Promise['all']([this['graphql']['core']['getChainIdentifier'](),this['rpc']['getChainIdentifier'](),this['rpc']['getLatestCheckpointSequenceNumber']()]),d=canonicalChainIdentifier(a['chainIdentifier']),e=canonicalChainIdentifier(b),f=canonicalChainIdentifier(this['expectedChainId']);if(d!==e||e!==f)throw new Error('CHAIN_IDENTITY_MISMATCH');return{'chainId':e,'checkpoint':c};}async['readPage'](a){const [b,c]=await Promise['all']([this['observe'](),this['graphql']['listSharedObjects']({'type':a['type'],'cursor':a['cursor'],'limit':a['limit'],'include':{'previousTransaction':!![]},'signal':a['signal']})]),d=await this['fetchRpcShapes'](c['objects']['map'](g=>g['objectId']),a['signal']),e=new Date()['toISOString'](),f=c['objects']['map']((g,h)=>{if(!g['type']||!g['previousTransaction'])throw new Error('Incomplete\x20GraphQL\x20object\x20metadata:\x20'+g['objectId']);const i=fromBase58(g['digest']);if(i['length']!==0x20)throw new Error('Invalid\x20object\x20digest:\x20'+g['objectId']);const j=d[h];if(!j)throw new Error('OBJECT_VANISHED_MID_PAGE:\x20'+g['objectId']);if(String(j['version'])!==String(g['version']))throw new Error('NON_STABLE_SNAPSHOT:\x20'+g['objectId']);const k=normalizeWowAddress(g['objectId']);return{'chain_id':b['chainId'],'object_id':k,'object_version':g['version'],'move_type':j['type']??g['type'],'owner_kind':g['owner']['$kind'],'owner_address':ownerAddress(g['owner']),'payload_uri':'graphql://'+this['network']+'/object/'+k+'/version/'+g['version'],'payload_hash':'0x'+toHex(i),'payload_format':'rpc-content-v1','payload_json':j['content']?.['fields']??null,'checkpoint':b['checkpoint'],'tx_digest':g['previousTransaction'],'state':'active','observed_at':e};});return{'chainId':b['chainId'],'checkpoint':b['checkpoint'],'facts':f,'nextCursor':c['cursor'],'hasNextPage':c['hasNextPage']};}async['fetchRpcShapes'](a,b){if(a['length']===0x0)return[];const c=new Array(a['length']),d=[],e=[],f=await this['graphql']['getObjects']({'objectIds':a,'include':{'json':!![]},'signal':b});f['objects']['forEach']((h,i)=>{if(h instanceof ObjectError)return;const j=h;if(!j['json']||!j['layout']){d['push'](a[i]),e['push'](i);return;}const k=gqlObject2RpcData(j);if(needsRpcFallback(object_name_from_type(k['type']))){d['push'](a[i]),e['push'](i);return;}c[i]=k;});if(d['length']>0x0){const g=await this['rpc']['multiGetObjects']({'objectIds':d,'options':{'showContent':!![],'showPreviousTransaction':!![]}});g['forEach']((h,j)=>{h?.['data']?.['content']&&'fields'in h['data']['content']&&(c[e[j]]=h['data']);});}return c;}async['hydrateFacts'](a,b,c,d){if(a['length']===0x0)return[];const e=await this['rpc']['multiGetObjects']({'objectIds':a,'options':{'showContent':!![],'showPreviousTransaction':!![],'showOwner':!![],'showType':!![]},'signal':d}),f=new Date()['toISOString'](),g=[];for(const h of e){if(!h?.['data'])continue;const i=factFromRpc(this['network'],b,c,h['data'],f);if(i)g['push'](i);}return g;}}
@@ -0,0 +1,5 @@
1
+ export { canonicalChainIdentifier, GraphQLSnapshotSource } from "./graphql-source.js";
2
+ export { SYNC_SCHEMA_SQL } from "./schema.js";
3
+ export { PostgresSyncStore } from "./store.js";
4
+ export { SnapshotSynchronizer } from "./synchronizer.js";
5
+ export type { CompleteSyncRunOptions, ReconciliationResult, RunSnapshotSyncOptions, SaveSyncPageOptions, SnapshotObservation, SnapshotPage, SnapshotPageRequest, SnapshotScope, SnapshotSource, SnapshotSyncResult, StartSyncRunOptions, SyncRun, } from "./types.js";
@@ -0,0 +1 @@
1
+ export{canonicalChainIdentifier,GraphQLSnapshotSource}from'./graphql-source.js';export{SYNC_SCHEMA_SQL}from'./schema.js';export{PostgresSyncStore}from'./store.js';export{SnapshotSynchronizer}from'./synchronizer.js';
@@ -0,0 +1,14 @@
1
+ import type { ApplyProjectionResult, RecordReconciliationOptions, RecordReconciliationResult } from "../projection/types.js";
2
+ import type { LiveSetReconciliationResult, RunLiveSetReconciliationOptions, SnapshotSource } from "./types.js";
3
+ export interface ReconciliationProjection {
4
+ liveSetVersions(chainId: string, kind: string): Promise<Map<string, string>>;
5
+ apply(input: unknown): Promise<ApplyProjectionResult>;
6
+ markTombstone(chainId: string, objectId: string): Promise<boolean>;
7
+ recordReconciliation(options: RecordReconciliationOptions): Promise<RecordReconciliationResult>;
8
+ }
9
+ export declare class LiveSetReconciler {
10
+ private readonly projection;
11
+ private readonly source;
12
+ constructor(projection: ReconciliationProjection, source: SnapshotSource);
13
+ run(options: RunLiveSetReconciliationOptions): Promise<LiveSetReconciliationResult>;
14
+ }
@@ -0,0 +1 @@
1
+ import{randomUUID}from'node:crypto';const RESIDUAL_CAP=0x3e8;function normalizeId(a){const b=String(a)['toLowerCase']();return b['startsWith']('0x')?b:'0x'+b;}export class LiveSetReconciler{['projection'];['source'];constructor(a,b){this['projection']=a,this['source']=b;}async['run'](a){if(!a['expectedChainId'])throw new Error('expectedChainId\x20is\x20required');if(!a['type'])throw new Error('type\x20is\x20required');if(!a['kind'])throw new Error('kind\x20is\x20required');const b=a['limit']??0x32;if(!Number['isInteger'](b)||b<0x1||b>0x3e8)throw new Error('Invalid\x20reconcile\x20limit:\x20'+b);const c=randomUUID(),d=await this['source']['observe'](),e=d['chainId'];if(e!==a['expectedChainId'])throw new Error('CHAIN_IDENTITY_MISMATCH:\x20source\x20reported\x20'+e);const f=new Map(),g=new Map();let h=d['checkpoint'],i=null,j=0x0;do{const t=await this['source']['readPage']({'type':a['type'],'cursor':i,'limit':b,'signal':a['signal']});if(t['chainId']!==e)throw new Error('CHAIN_IDENTITY_MISMATCH\x20mid-enumeration');h=t['checkpoint'];for(const u of t['facts']){const v=u;if(!v['object_id']||v['object_version']===undefined)throw new Error('Malformed\x20fact:\x20missing\x20object\x20identity');const w=normalizeId(v['object_id']),x=String(v['object_version']);f['set'](w,x);const y=g['get'](w);(!y||BigInt(x)>BigInt(y['objectVersion']))&&g['set'](w,{'objectId':w,'objectVersion':x,'raw':u});}i=t['nextCursor'],j+=0x1;}while(i!==null&&j<0x186a0);const k=[],l={'missing':0x0,'drift':0x0,'tombstoned':0x0},m=await this['projection']['liveSetVersions'](e,a['kind']),n=diffSets(f,m);for(const z of n['missing']){const A=g['get'](z);if(!A){k['push']('NO_FACT_FOR_MISSING:\x20'+z);continue;}try{const B=await this['projection']['apply'](A['raw']);B['status']==='applied'||B['status']==='duplicate'?l['missing']+=0x1:k['push']('REPAIR_REJECTED('+B['status']+'):\x20'+z);}catch(C){k['push']('REPAIR_FAILED:\x20'+z+':\x20'+message(C));}}for(const D of n['versionDrift']){const E=g['get'](D);if(!E){k['push']('NO_FACT_FOR_DRIFT:\x20'+D);continue;}try{const F=await this['projection']['apply'](E['raw']);F['status']==='applied'||F['status']==='duplicate'?l['drift']+=0x1:k['push']('REPAIR_REJECTED('+F['status']+'):\x20'+D);}catch(G){k['push']('REPAIR_FAILED:\x20'+D+':\x20'+message(G));}}for(const H of n['extra']){try{await this['projection']['markTombstone'](e,H)?l['tombstoned']+=0x1:k['push']('TOMBSTONE_NOOP:\x20'+H);}catch(I){k['push']('TOMBSTONE_FAILED:\x20'+H+':\x20'+message(I));}}const o=await this['projection']['liveSetVersions'](e,a['kind']),p=diffSets(f,o),q={'missing':p['missing']['slice'](0x0,RESIDUAL_CAP),'extra':p['extra']['slice'](0x0,RESIDUAL_CAP),'versionDrift':p['versionDrift']['slice'](0x0,RESIDUAL_CAP)},r=k['length']>0x0?k['join'](';\x20')['slice'](0x0,0xfa0):undefined,s=await this['projection']['recordReconciliation']({'chainId':e,'kind':a['kind'],'runId':c,'liveSetCheckpoint':h,'missing':q['missing'],'extra':q['extra'],'versionDrift':q['versionDrift'],'preDiff':{'missing':n['missing']['length'],'extra':n['extra']['length'],'versionDrift':n['versionDrift']['length']},'repairs':l,'liveCount':f['size'],'projectionActive':o['size'],'error':r});return{'runId':c,'chainId':e,'checkpoint':h,'kind':a['kind'],'verdict':s['verdict'],'generation':s['generation'],'reconciled_through':s['reconciledThrough'],'live_count':f['size'],'projection_active':o['size'],'pre_diff':{'missing':n['missing']['length'],'extra':n['extra']['length'],'version_drift':n['versionDrift']['length']},'repairs':l,'residual':{'missing':p['missing'],'extra':p['extra'],'version_drift':p['versionDrift']},'error':r??null};}}function diffSets(a,b){const c=[],d=[],e=[];for(const [f,g]of a){const h=b['get'](f);if(h===undefined)c['push'](f);else BigInt(h)!==BigInt(g)&&e['push'](f);}for(const i of b['keys']()){if(!a['has'](i))d['push'](i);}return{'missing':c,'extra':d,'versionDrift':e};}function message(a){return(a instanceof Error?a['message']:String(a))['slice'](0x0,0xc8);}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ import{toBase58}from'@wowok/wowok';import{DataType,newDb}from'pg-mem';import{afterEach,beforeEach,describe,expect,it,vi}from'vitest';import{PostgresProjectionStore}from'../projection/store.js';import{LiveSetReconciler}from'./reconciler.js';const digest=toBase58(new Uint8Array(0x20)['fill'](0x7));function objectId(a){return'0x14'+a['toString'](0x10)['padStart'](0x2,'0')['repeat'](0x1f);}function fact(a,b={}){return{'chain_id':'chain_a','object_id':objectId(a),'object_version':'1','move_type':'0x2::service::Service<0x2::wow::WOW>','owner_kind':'Shared','payload_uri':'test://raw/service-'+a,'payload_hash':'0x'+'41'['repeat'](0x20),'payload_format':'json+bcs-v1','checkpoint':'100','tx_digest':digest,'state':'active','observed_at':'2026-10-24T00:00:00.000Z',...b};}function page(a,b,c='100'){return{'chainId':'chain_a','checkpoint':c,'facts':a,'nextCursor':b,'hasNextPage':b!==null};}class FakeSource{['pages'];['chainId'];['checkpoint'];['cursors']=[];constructor(a,b='chain_a',c='100'){this['pages']=a,this['chainId']=b,this['checkpoint']=c;}async['observe'](){return{'chainId':this['chainId'],'checkpoint':this['checkpoint']};}async['readPage'](a){this['cursors']['push'](a['cursor']);const b=a['cursor']===null?0x0:this['pages']['findIndex'](c=>c['nextCursor']===a['cursor'])+0x1;if(b<0x0||b>=this['pages']['length'])throw new Error('Invalid\x20test\x20cursor');return this['pages'][b];}}function options(a){return{'expectedChainId':a['chainId'],'type':'0x2::service','kind':'Service','limit':0x32};}describe('LiveSetReconciler',()=>{let a,b;beforeEach(async()=>{const c=newDb();c['public']['registerFunction']({'name':'pg_advisory_xact_lock','args':[DataType['integer'],DataType['integer']],'returns':DataType['integer'],'implementation':()=>0x0});const {Pool:d}=c['adapters']['createPg']();b=new d(),a=new PostgresProjectionStore(b,0x10),await a['migrate']();}),afterEach(async()=>{await a?.['close'](),vi['restoreAllMocks']();}),it('passes\x20and\x20publishes\x20generation\x201\x20on\x20an\x20empty\x20live\x20set',async()=>{const c=new FakeSource([page([],null)]),d=await new LiveSetReconciler(a,c)['run'](options(c));expect(d)['toMatchObject']({'verdict':'pass','generation':0x1,'reconciled_through':'100','pre_diff':{'missing':0x0,'extra':0x0,'version_drift':0x0},'repairs':{'missing':0x0,'drift':0x0,'tombstoned':0x0}});const e=await a['status']('chain_a');expect(e['coverage'])['toMatchObject']({'reconciled_through':'100','degraded_reasons':[]}),expect(e['reconciliations'])['toHaveLength'](0x1),expect(e['reconciliations'][0x0])['toMatchObject']({'kind':'Service','generation':0x1,'verdict':'pass'});}),it('repairs\x20a\x20missing\x20object\x20and\x20drops\x20RECONCILIATION_REQUIRED\x20from\x20coverage',async()=>{const c=new FakeSource([page([fact(0x1)],null)]),d=await new LiveSetReconciler(a,c)['run'](options(c));expect(d)['toMatchObject']({'verdict':'pass','generation':0x1,'pre_diff':{'missing':0x1,'extra':0x0,'version_drift':0x0},'repairs':{'missing':0x1,'drift':0x0,'tombstoned':0x0},'residual':{'missing':[],'extra':[],'version_drift':[]}});const e=await a['status']('chain_a');expect(e)['toMatchObject']({'active_objects':0x1}),expect(e['coverage']['degraded_reasons'])['toEqual']([]);const f=await a['listCurrent']({'chainId':'chain_a','kind':'Service'});expect(f['coverage']['reconciled_through'])['toBe']('100'),expect(f['objects'])['toHaveLength'](0x1);}),it('tombstones\x20an\x20extra\x20object\x20without\x20deleting\x20the\x20row',async()=>{await a['apply'](fact(0x2));const c=new FakeSource([page([],null)]),d=await new LiveSetReconciler(a,c)['run'](options(c));expect(d)['toMatchObject']({'verdict':'pass','pre_diff':{'missing':0x0,'extra':0x1,'version_drift':0x0},'repairs':{'missing':0x0,'drift':0x0,'tombstoned':0x1},'residual':{'missing':[],'extra':[],'version_drift':[]}});const e=await b['query']('SELECT\x20state\x20FROM\x20object_current\x20WHERE\x20chain_id=\x27chain_a\x27\x20AND\x20object_id=$1',[Buffer['from']('14'+'02'['repeat'](0x1f),'hex')]);expect(e['rows'])['toEqual']([{'state':'deleted'}]);}),it('repairs\x20version\x20drift\x20by\x20re-applying\x20the\x20chain\x20fact',async()=>{await a['apply'](fact(0x1));const c=new FakeSource([page([fact(0x1,{'object_version':'2','previous_version':'1','checkpoint':'101','payload_uri':'test://raw/service-1-v2'})],null,'101')]),d=await new LiveSetReconciler(a,c)['run'](options(c));expect(d)['toMatchObject']({'verdict':'pass','reconciled_through':'101','pre_diff':{'missing':0x0,'extra':0x0,'version_drift':0x1},'repairs':{'missing':0x0,'drift':0x1,'tombstoned':0x0},'residual':{'missing':[],'extra':[],'version_drift':[]}});const e=await a['listCurrent']({'chainId':'chain_a','kind':'Service'});expect(e['objects'][0x0])['toMatchObject']({'objectVersion':'2','checkpoint':'101'});}),it('fails\x20closed\x20without\x20bumping\x20generation\x20when\x20a\x20repair\x20is\x20rejected',async()=>{const c=fact(0x1,{'payload_hash':'0xzz-not-hex'}),d=new FakeSource([page([c],null)]),e=await new LiveSetReconciler(a,d)['run'](options(d));expect(e['verdict'])['toBe']('fail'),expect(e['generation'])['toBeNull'](),expect(e['reconciled_through'])['toBeNull'](),expect(e['repairs']['missing'])['toBe'](0x0),expect(e['residual']['missing'])['toEqual']([objectId(0x1)]),expect(e['error'])['toContain']('REPAIR_FAILED');const f=await a['status']('chain_a');expect(f['coverage']['degraded_reasons'])['toContain']('RECONCILIATION_REQUIRED'),expect(f['reconciliations'][0x0])['toMatchObject']({'verdict':'fail','generation':0x0});}),it('bumps\x20the\x20generation\x20on\x20each\x20subsequent\x20passing\x20run',async()=>{const c=new FakeSource([page([fact(0x1)],null)]),d=new LiveSetReconciler(a,c),e=await d['run'](options(c)),f=await d['run'](options(c));expect(e['generation'])['toBe'](0x1),expect(f)['toMatchObject']({'verdict':'pass','generation':0x2,'repairs':{'missing':0x0,'drift':0x0,'tombstoned':0x0},'pre_diff':{'missing':0x0,'extra':0x0,'version_drift':0x0}}),expect((await a['status']('chain_a'))['reconciliations'][0x0]['generation'])['toBe'](0x2);}),it('rejects\x20a\x20source\x20reporting\x20a\x20different\x20chain\x20identity',async()=>{const c=new FakeSource([page([],null)],'chain_b');await expect(new LiveSetReconciler(a,c)['run'](options(c)))['rejects']['toThrow']('CHAIN_IDENTITY_MISMATCH'),expect(await a['status']('chain_a')['then'](d=>d['reconciliations']))['toEqual']([]);});});
@@ -0,0 +1 @@
1
+ export declare const SYNC_SCHEMA_SQL = "\nCREATE TABLE IF NOT EXISTS sync_run (\n chain_id TEXT NOT NULL,\n stream TEXT NOT NULL,\n run_id TEXT NOT NULL,\n state TEXT NOT NULL CHECK (state IN ('running', 'complete', 'failed')),\n scope_kind TEXT,\n shard_count INTEGER,\n shard_index INTEGER,\n cursor TEXT,\n checkpoint BIGINT,\n pages INTEGER NOT NULL DEFAULT 0,\n objects BIGINT NOT NULL DEFAULT 0,\n started_at TIMESTAMPTZ NOT NULL,\n updated_at TIMESTAMPTZ NOT NULL,\n completed_at TIMESTAMPTZ,\n error TEXT,\n PRIMARY KEY (chain_id, stream)\n);\n\nCREATE TABLE IF NOT EXISTS sync_seen (\n chain_id TEXT NOT NULL,\n stream TEXT NOT NULL,\n run_id TEXT NOT NULL,\n object_id BYTEA NOT NULL,\n object_version BIGINT NOT NULL,\n PRIMARY KEY (chain_id, stream, object_id)\n);\n\nCREATE INDEX IF NOT EXISTS sync_seen_run\n ON sync_seen (chain_id, stream, run_id, object_id);\n\nCREATE TABLE IF NOT EXISTS sync_page (\n chain_id TEXT NOT NULL,\n stream TEXT NOT NULL,\n run_id TEXT NOT NULL,\n page_seq INTEGER NOT NULL,\n checkpoint BIGINT NOT NULL,\n objects INTEGER NOT NULL DEFAULT 0,\n cursor TEXT,\n created_at TIMESTAMPTZ NOT NULL,\n PRIMARY KEY (chain_id, stream, run_id, page_seq)\n);\n\n-- W3 CDC cursor. Semantics: every checkpoint up to and including the stored\n-- value has been fully applied for (chain_id, kind). Advances only to the\n-- highest checkpoint actually observed in returned transaction nodes \u2014 never\n-- to the requested interval end \u2014 so indexer lag can never skip transactions.\nCREATE TABLE IF NOT EXISTS cdc_cursor (\n chain_id TEXT NOT NULL,\n kind TEXT NOT NULL,\n checkpoint BIGINT NOT NULL,\n updated_at TIMESTAMPTZ NOT NULL,\n PRIMARY KEY (chain_id, kind)\n);\n";
@@ -0,0 +1 @@
1
+ export const SYNC_SCHEMA_SQL='\x0aCREATE\x20TABLE\x20IF\x20NOT\x20EXISTS\x20sync_run\x20(\x0a\x20\x20\x20\x20chain_id\x20TEXT\x20NOT\x20NULL,\x0a\x20\x20\x20\x20stream\x20TEXT\x20NOT\x20NULL,\x0a\x20\x20\x20\x20run_id\x20TEXT\x20NOT\x20NULL,\x0a\x20\x20\x20\x20state\x20TEXT\x20NOT\x20NULL\x20CHECK\x20(state\x20IN\x20(\x27running\x27,\x20\x27complete\x27,\x20\x27failed\x27)),\x0a\x20\x20\x20\x20scope_kind\x20TEXT,\x0a\x20\x20\x20\x20shard_count\x20INTEGER,\x0a\x20\x20\x20\x20shard_index\x20INTEGER,\x0a\x20\x20\x20\x20cursor\x20TEXT,\x0a\x20\x20\x20\x20checkpoint\x20BIGINT,\x0a\x20\x20\x20\x20pages\x20INTEGER\x20NOT\x20NULL\x20DEFAULT\x200,\x0a\x20\x20\x20\x20objects\x20BIGINT\x20NOT\x20NULL\x20DEFAULT\x200,\x0a\x20\x20\x20\x20started_at\x20TIMESTAMPTZ\x20NOT\x20NULL,\x0a\x20\x20\x20\x20updated_at\x20TIMESTAMPTZ\x20NOT\x20NULL,\x0a\x20\x20\x20\x20completed_at\x20TIMESTAMPTZ,\x0a\x20\x20\x20\x20error\x20TEXT,\x0a\x20\x20\x20\x20PRIMARY\x20KEY\x20(chain_id,\x20stream)\x0a);\x0a\x0aCREATE\x20TABLE\x20IF\x20NOT\x20EXISTS\x20sync_seen\x20(\x0a\x20\x20\x20\x20chain_id\x20TEXT\x20NOT\x20NULL,\x0a\x20\x20\x20\x20stream\x20TEXT\x20NOT\x20NULL,\x0a\x20\x20\x20\x20run_id\x20TEXT\x20NOT\x20NULL,\x0a\x20\x20\x20\x20object_id\x20BYTEA\x20NOT\x20NULL,\x0a\x20\x20\x20\x20object_version\x20BIGINT\x20NOT\x20NULL,\x0a\x20\x20\x20\x20PRIMARY\x20KEY\x20(chain_id,\x20stream,\x20object_id)\x0a);\x0a\x0aCREATE\x20INDEX\x20IF\x20NOT\x20EXISTS\x20sync_seen_run\x0a\x20\x20\x20\x20ON\x20sync_seen\x20(chain_id,\x20stream,\x20run_id,\x20object_id);\x0a\x0aCREATE\x20TABLE\x20IF\x20NOT\x20EXISTS\x20sync_page\x20(\x0a\x20\x20\x20\x20chain_id\x20TEXT\x20NOT\x20NULL,\x0a\x20\x20\x20\x20stream\x20TEXT\x20NOT\x20NULL,\x0a\x20\x20\x20\x20run_id\x20TEXT\x20NOT\x20NULL,\x0a\x20\x20\x20\x20page_seq\x20INTEGER\x20NOT\x20NULL,\x0a\x20\x20\x20\x20checkpoint\x20BIGINT\x20NOT\x20NULL,\x0a\x20\x20\x20\x20objects\x20INTEGER\x20NOT\x20NULL\x20DEFAULT\x200,\x0a\x20\x20\x20\x20cursor\x20TEXT,\x0a\x20\x20\x20\x20created_at\x20TIMESTAMPTZ\x20NOT\x20NULL,\x0a\x20\x20\x20\x20PRIMARY\x20KEY\x20(chain_id,\x20stream,\x20run_id,\x20page_seq)\x0a);\x0a\x0a--\x20W3\x20CDC\x20cursor.\x20Semantics:\x20every\x20checkpoint\x20up\x20to\x20and\x20including\x20the\x20stored\x0a--\x20value\x20has\x20been\x20fully\x20applied\x20for\x20(chain_id,\x20kind).\x20Advances\x20only\x20to\x20the\x0a--\x20highest\x20checkpoint\x20actually\x20observed\x20in\x20returned\x20transaction\x20nodes\x20—\x20never\x0a--\x20to\x20the\x20requested\x20interval\x20end\x20—\x20so\x20indexer\x20lag\x20can\x20never\x20skip\x20transactions.\x0aCREATE\x20TABLE\x20IF\x20NOT\x20EXISTS\x20cdc_cursor\x20(\x0a\x20\x20\x20\x20chain_id\x20TEXT\x20NOT\x20NULL,\x0a\x20\x20\x20\x20kind\x20TEXT\x20NOT\x20NULL,\x0a\x20\x20\x20\x20checkpoint\x20BIGINT\x20NOT\x20NULL,\x0a\x20\x20\x20\x20updated_at\x20TIMESTAMPTZ\x20NOT\x20NULL,\x0a\x20\x20\x20\x20PRIMARY\x20KEY\x20(chain_id,\x20kind)\x0a);\x0a';
@@ -0,0 +1,19 @@
1
+ import { Pool } from "pg";
2
+ import type { CompleteSyncRunOptions, ReconciliationResult, SaveSyncPageOptions, StartSyncRunOptions, SyncRun } from "./types.js";
3
+ export declare class PostgresSyncStore {
4
+ private readonly pool;
5
+ constructor(pool: Pool);
6
+ static fromDatabaseUrl(databaseUrl: string): PostgresSyncStore;
7
+ migrate(): Promise<void>;
8
+ close(): Promise<void>;
9
+ private locked;
10
+ start(options: StartSyncRunOptions): Promise<SyncRun>;
11
+ savePage(options: SaveSyncPageOptions): Promise<SyncRun>;
12
+ complete(options: CompleteSyncRunOptions): Promise<ReconciliationResult>;
13
+ private verifyPageLedger;
14
+ fail(chainId: string, stream: string, runId: string, error: unknown): Promise<void>;
15
+ status(chainId: string): Promise<SyncRun[]>;
16
+ cdcCheckpoint(chainId: string, kind: string): Promise<string | null>;
17
+ setCdcCheckpoint(chainId: string, kind: string, checkpoint: string): Promise<void>;
18
+ withCdcLock<T>(chainId: string, kind: string, work: () => Promise<T>): Promise<T>;
19
+ }
@@ -0,0 +1 @@
1
+ import{createHash,randomUUID}from'node:crypto';import{toHex,wowAddressToBytes}from'@wowok/wowok';import{Pool}from'pg';import{SYNC_SCHEMA_SQL}from'./schema.js';function addressBytes(a){return Buffer['from'](wowAddressToBytes(a));}function hexBytes(a){return'0x'+toHex(a);}function checkpointValue(a){if(!/^\d+$/['test'](a))throw new Error('Invalid\x20checkpoint:\x20'+a);const b=BigInt(a);if(b>0x7fffffffffffffffn)throw new Error('Checkpoint\x20exceeds\x20PostgreSQL\x20BIGINT');return b;}function validateScope(a){if(!a['shard'])return;const {count:b,index:c}=a['shard'];if(!Number['isInteger'](b)||b<0x1||!Number['isInteger'](c)||c<0x0||c>=b)throw new Error('Invalid\x20shard\x20selector');}function sameScope(a,b){return a['kind']===b['kind']&&a['shard']?.['count']===b['shard']?.['count']&&a['shard']?.['index']===b['shard']?.['index'];}function mapRun(a,b=![]){return{'chainId':a['chain_id'],'stream':a['stream'],'runId':a['run_id'],'state':a['state'],'scope':{'kind':a['scope_kind']??undefined,'shard':a['shard_count']===null||a['shard_count']===undefined?undefined:{'count':Number(a['shard_count']),'index':Number(a['shard_index'])}},'cursor':a['cursor'],'checkpoint':a['checkpoint']===null||a['checkpoint']===undefined?null:String(a['checkpoint']),'pages':Number(a['pages']),'objects':Number(a['objects']),'startedAt':new Date(a['started_at'])['toISOString'](),'updatedAt':new Date(a['updated_at'])['toISOString'](),'completedAt':a['completed_at']?new Date(a['completed_at'])['toISOString']():null,'error':a['error'],'resumed':b};}export class PostgresSyncStore{['pool'];constructor(a){this['pool']=a;}static['fromDatabaseUrl'](a){return new PostgresSyncStore(new Pool({'connectionString':a}));}async['migrate'](){await this['pool']['query'](SYNC_SCHEMA_SQL);}async['close'](){await this['pool']['end']();}async['locked'](a,b,c){const d=await this['pool']['connect']();try{await d['query']('BEGIN');const e=createHash('sha256')['update'](a+':'+b)['digest']();await d['query']('SELECT\x20pg_advisory_xact_lock($1,\x20$2)',[e['readInt32BE'](0x0),e['readInt32BE'](0x4)]);const f=await c(d);return await d['query']('COMMIT'),f;}catch(g){await d['query']('ROLLBACK');throw g;}finally{d['release']();}}async['start'](a){if(!a['chainId']||!a['stream'])throw new Error('chainId\x20and\x20stream\x20are\x20required');return validateScope(a['scope']),checkpointValue(a['checkpoint']),this['locked'](a['chainId'],a['stream'],async b=>{const c=await b['query']('SELECT\x20*\x20FROM\x20sync_run\x20WHERE\x20chain_id=$1\x20AND\x20stream=$2',[a['chainId'],a['stream']]),d=c['rows'][0x0];if(d?.['checkpoint']!==null&&d?.['checkpoint']!==undefined&&checkpointValue(String(d['checkpoint']))>checkpointValue(a['checkpoint']))throw new Error('CHAIN_CHECKPOINT_REGRESSION');const e=d?.['state']==='failed'&&typeof d['error']==='string'&&d['error']['startsWith']('NON_STABLE_SNAPSHOT');if(d&&d['state']!=='complete'&&!e){const h=mapRun(d)['scope'];if(!sameScope(h,a['scope']))throw new Error('Cannot\x20resume\x20a\x20sync\x20run\x20with\x20a\x20different\x20scope');const i=await b['query']('UPDATE\x20sync_run\x20SET\x20state=\x27running\x27,\x20error=NULL,\x20updated_at=$3\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20WHERE\x20chain_id=$1\x20AND\x20stream=$2\x20RETURNING\x20*',[a['chainId'],a['stream'],new Date()['toISOString']()]);return mapRun(i['rows'][0x0],!![]);}d&&(await b['query']('DELETE\x20FROM\x20sync_seen\x20WHERE\x20chain_id=$1\x20AND\x20stream=$2',[a['chainId'],a['stream']]),await b['query']('DELETE\x20FROM\x20sync_page\x20WHERE\x20chain_id=$1\x20AND\x20stream=$2',[a['chainId'],a['stream']]));const f=new Date()['toISOString'](),g=await b['query']('INSERT\x20INTO\x20sync_run\x20(\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20chain_id,\x20stream,\x20run_id,\x20state,\x20scope_kind,\x20shard_count,\x20shard_index,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20cursor,\x20checkpoint,\x20pages,\x20objects,\x20started_at,\x20updated_at,\x20completed_at,\x20error\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20)\x20VALUES\x20($1,$2,$3,\x27running\x27,$4,$5,$6,NULL,$7,0,0,$8,$8,NULL,NULL)\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20ON\x20CONFLICT\x20(chain_id,\x20stream)\x20DO\x20UPDATE\x20SET\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20run_id=EXCLUDED.run_id,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20state=EXCLUDED.state,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20scope_kind=EXCLUDED.scope_kind,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20shard_count=EXCLUDED.shard_count,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20shard_index=EXCLUDED.shard_index,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20cursor=EXCLUDED.cursor,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20checkpoint=EXCLUDED.checkpoint,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20pages=EXCLUDED.pages,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20objects=EXCLUDED.objects,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20started_at=EXCLUDED.started_at,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20updated_at=EXCLUDED.updated_at,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20completed_at=EXCLUDED.completed_at,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20error=EXCLUDED.error\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20RETURNING\x20*',[a['chainId'],a['stream'],randomUUID(),a['scope']['kind']??null,a['scope']['shard']?.['count']??null,a['scope']['shard']?.['index']??null,a['checkpoint'],f]);return mapRun(g['rows'][0x0]);});}async['savePage'](a){return checkpointValue(a['checkpoint']),this['locked'](a['chainId'],a['stream'],async b=>{const c=await b['query']('SELECT\x20*\x20FROM\x20sync_run\x20WHERE\x20chain_id=$1\x20AND\x20stream=$2',[a['chainId'],a['stream']]),d=c['rows'][0x0];if(!d||d['run_id']!==a['runId']||d['state']!=='running')throw new Error('Sync\x20run\x20is\x20not\x20active');if(d['checkpoint']!==null&&checkpointValue(String(d['checkpoint']))>checkpointValue(a['checkpoint']))throw new Error('CHAIN_CHECKPOINT_REGRESSION');for(const f of a['objects']){const g=addressBytes(f['objectId']);await b['query']('INSERT\x20INTO\x20sync_seen\x20(chain_id,\x20stream,\x20run_id,\x20object_id,\x20object_version)\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20VALUES\x20($1,$2,$3,$4,$5)\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20ON\x20CONFLICT\x20(chain_id,\x20stream,\x20object_id)\x20DO\x20NOTHING',[a['chainId'],a['stream'],a['runId'],g,f['objectVersion']['toString']()]);const h=await b['query']('SELECT\x20object_version\x20FROM\x20sync_seen\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20WHERE\x20chain_id=$1\x20AND\x20stream=$2\x20AND\x20object_id=$3',[a['chainId'],a['stream'],g]);if(BigInt(String(h['rows'][0x0]['object_version']))!==f['objectVersion'])throw new Error('NON_STABLE_SNAPSHOT:\x20'+f['objectId']);}const e=await b['query']('UPDATE\x20sync_run\x20SET\x20cursor=$4,\x20checkpoint=$5,\x20pages=pages+1,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20objects=objects+$6,\x20updated_at=$7\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20WHERE\x20chain_id=$1\x20AND\x20stream=$2\x20AND\x20run_id=$3\x20RETURNING\x20*',[a['chainId'],a['stream'],a['runId'],a['cursor'],a['checkpoint'],a['objects']['length'],new Date()['toISOString']()]);return await b['query']('INSERT\x20INTO\x20sync_page\x20(\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20chain_id,\x20stream,\x20run_id,\x20page_seq,\x20checkpoint,\x20objects,\x20cursor,\x20created_at\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20)\x20VALUES\x20($1,$2,$3,$4,$5,$6,$7,$8)\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20ON\x20CONFLICT\x20(chain_id,\x20stream,\x20run_id,\x20page_seq)\x20DO\x20UPDATE\x20SET\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20checkpoint=EXCLUDED.checkpoint,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20objects=EXCLUDED.objects,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20cursor=EXCLUDED.cursor,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20created_at=EXCLUDED.created_at',[a['chainId'],a['stream'],a['runId'],Number(d['pages'])+0x1,a['checkpoint'],a['objects']['length'],a['cursor'],new Date()['toISOString']()]),mapRun(e['rows'][0x0],!![]);});}async['complete'](a){return this['locked'](a['chainId'],a['stream'],async b=>{const c=await b['query']('SELECT\x20*\x20FROM\x20sync_run\x20WHERE\x20chain_id=$1\x20AND\x20stream=$2',[a['chainId'],a['stream']]),d=c['rows'][0x0];if(!d||d['run_id']!==a['runId']||d['state']!=='running')throw new Error('Sync\x20run\x20is\x20not\x20active');const e=mapRun(d);await this['verifyPageLedger'](b,a['chainId'],a['stream'],e);const f=[a['chainId'],a['stream'],a['runId']],g=['c.chain_id=$1','c.state=\x27active\x27','s.object_id\x20IS\x20NULL'];e['scope']['kind']&&(f['push'](e['scope']['kind']),g['push']('c.verified_kind=$'+f['length']));e['scope']['shard']&&(f['push'](e['scope']['shard']['count'],e['scope']['shard']['index']),g['push']('c.shard_count=$'+(f['length']-0x1)),g['push']('c.shard_index=$'+f['length']));const h='FROM\x20object_current\x20c\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20LEFT\x20JOIN\x20sync_seen\x20s\x20ON\x20s.chain_id=c.chain_id\x20AND\x20s.stream=$2\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20AND\x20s.run_id=$3\x20AND\x20s.object_id=c.object_id\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20WHERE\x20'+g['join']('\x20AND\x20'),i=await b['query']('SELECT\x20COUNT(*)::TEXT\x20AS\x20count\x20'+h,f),j=await b['query']('SELECT\x20c.object_id\x20'+h+'\x20ORDER\x20BY\x20c.object_id\x20LIMIT\x201000',f),k=[a['chainId']],l=['chain_id=$1','state=\x27active\x27'];e['scope']['kind']&&(k['push'](e['scope']['kind']),l['push']('verified_kind=$'+k['length']));e['scope']['shard']&&(k['push'](e['scope']['shard']['count'],e['scope']['shard']['index']),l['push']('shard_count=$'+(k['length']-0x1)),l['push']('shard_index=$'+k['length']));const m=await b['query']('SELECT\x20COUNT(*)::TEXT\x20AS\x20count\x20FROM\x20object_current\x20WHERE\x20'+l['join']('\x20AND\x20'),k),n=await b['query']('UPDATE\x20sync_run\x20SET\x20state=\x27complete\x27,\x20completed_at=$4,\x20updated_at=$4,\x20error=NULL\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20WHERE\x20chain_id=$1\x20AND\x20stream=$2\x20AND\x20run_id=$3\x20RETURNING\x20*',[a['chainId'],a['stream'],a['runId'],new Date()['toISOString']()]),o=j['rows']['slice'](0x0,0x3e8),p=new Set(a['degradedReasons']??[]);p['add']('SNAPSHOT_NOT_CHECKPOINT_PINNED'),p['add']('INCREMENTAL_CDC_REQUIRED');const q=Number(i['rows'][0x0]['count']);if(q>0x0)p['add']('LIVE_SET_MISSING');return{'run':mapRun(n['rows'][0x0],!![]),'enumeration_complete':!![],'current_active':Number(m['rows'][0x0]['count']),'missing_objects':q,'missing_object_ids':o['map'](r=>hexBytes(r['object_id'])),'missing_truncated':q>o['length'],'coverage':{'mode':'known_objects','complete':![],'truncated':![],'total_enumerated':e['objects'],'chain_id':a['chainId'],'reconciled_through':null,'degraded_reasons':[...p]}};});}async['verifyPageLedger'](a,b,c,d){const e=await a['query']('SELECT\x20page_seq,\x20checkpoint\x20FROM\x20sync_page\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20WHERE\x20chain_id=$1\x20AND\x20stream=$2\x20AND\x20run_id=$3\x20ORDER\x20BY\x20page_seq',[b,c,d['runId']]),f=e['rows'];if(f['length']!==d['pages'])throw new Error('CHECKPOINT_GAP_DETECTED:\x20page\x20ledger\x20has\x20'+f['length']+'\x20rows\x20but\x20run\x20recorded\x20'+d['pages']+'\x20pages');let g=null;for(let h=0x0;h<f['length'];h+=0x1){const i=Number(f[h]['page_seq']);if(i!==h+0x1)throw new Error('CHECKPOINT_GAP_DETECTED:\x20page\x20ledger\x20sequence\x20break\x20at\x20position\x20'+(h+0x1)+'\x20(found\x20'+i+')');const j=checkpointValue(String(f[h]['checkpoint']));if(g!==null&&j<g)throw new Error('CHECKPOINT_GAP_DETECTED:\x20page\x20'+i+'\x20checkpoint\x20'+j+'\x20regressed\x20below\x20'+g);g=j;}}async['fail'](a,b,c,d){const e=(d instanceof Error?d['message']:String(d))['slice'](0x0,0xfa0);await this['locked'](a,b,async f=>{await f['query']('UPDATE\x20sync_run\x20SET\x20state=\x27failed\x27,\x20error=$4,\x20updated_at=$5\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20WHERE\x20chain_id=$1\x20AND\x20stream=$2\x20AND\x20run_id=$3',[a,b,c,e,new Date()['toISOString']()]);});}async['status'](a){const b=await this['pool']['query']('SELECT\x20*\x20FROM\x20sync_run\x20WHERE\x20chain_id=$1\x20ORDER\x20BY\x20stream',[a]);return b['rows']['map'](c=>mapRun(c));}async['cdcCheckpoint'](a,b){const c=await this['pool']['query']('SELECT\x20checkpoint::TEXT\x20AS\x20checkpoint\x20FROM\x20cdc_cursor\x20WHERE\x20chain_id=$1\x20AND\x20kind=$2',[a,b]);return c['rows'][0x0]?.['checkpoint']??null;}async['setCdcCheckpoint'](a,b,c){await this['pool']['query']('INSERT\x20INTO\x20cdc_cursor\x20(chain_id,\x20kind,\x20checkpoint,\x20updated_at)\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20VALUES\x20($1,$2,$3,$4)\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20ON\x20CONFLICT\x20(chain_id,\x20kind)\x20DO\x20UPDATE\x20SET\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20checkpoint=GREATEST(cdc_cursor.checkpoint,\x20EXCLUDED.checkpoint),\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20updated_at=EXCLUDED.updated_at',[a,b,checkpointValue(c),new Date()['toISOString']()]);}async['withCdcLock'](a,b,c){return this['locked'](a,'cdc:'+b,async()=>c());}}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ import{ENTRYPOINT,toBase58}from'@wowok/wowok';import{DataType,newDb}from'pg-mem';import{afterEach,beforeEach,describe,expect,it,vi}from'vitest';import{PostgresProjectionStore}from'../projection/store.js';import{GraphQLSnapshotSource}from'./graphql-source.js';import{PostgresSyncStore}from'./store.js';import{SnapshotSynchronizer}from'./synchronizer.js';const digest=toBase58(new Uint8Array(0x20)['fill'](0x7)),chainDigest=toBase58(new Uint8Array([0xa9,0x15,0x5c,0xf,...new Uint8Array(0x1c)['fill'](0x7)]));function objectId(a){return'0x14'+a['toString'](0x10)['padStart'](0x2,'0')['repeat'](0x1f);}function fact(a,b={}){return{'chain_id':'chain_a','object_id':objectId(a),'object_version':'1','move_type':'0x2::service::Service<0x2::wow::WOW>','owner_kind':'Shared','payload_uri':'test://raw/service-'+a,'payload_hash':'0x'+'41'['repeat'](0x20),'payload_format':'json+bcs-v1','checkpoint':'100','tx_digest':digest,'state':'active','observed_at':'2026-10-24T00:00:00.000Z',...b};}function page(a,b,c='100'){return{'chainId':'chain_a','checkpoint':c,'facts':a,'nextCursor':b,'hasNextPage':b!==null};}class FakeSource{['pages'];['chainId'];['checkpoint'];['cursors']=[];['failAtCursor'];['failed']=![];constructor(a,b='chain_a',c='100'){this['pages']=a,this['chainId']=b,this['checkpoint']=c;}async['observe'](){return{'chainId':this['chainId'],'checkpoint':this['checkpoint']};}async['readPage'](a){this['cursors']['push'](a['cursor']);if(!this['failed']&&this['failAtCursor']!==undefined&&a['cursor']===this['failAtCursor']){this['failed']=!![];throw new Error('injected\x20page\x20failure');}const b=a['cursor']===null?0x0:this['pages']['findIndex'](c=>c['nextCursor']===a['cursor'])+0x1;if(b<0x0||b>=this['pages']['length'])throw new Error('Invalid\x20test\x20cursor');return this['pages'][b];}}function options(a){return{'expectedChainId':a['chainId'],'stream':'graphql-shared:Service','type':'0x2::service','scope':{'kind':'Service'},'limit':0x32};}describe('SnapshotSynchronizer',()=>{let a,b,c;beforeEach(async()=>{const f=newDb();f['public']['registerFunction']({'name':'pg_advisory_xact_lock','args':[DataType['integer'],DataType['integer']],'returns':DataType['integer'],'implementation':()=>0x0});const {Pool:g}=f['adapters']['createPg']();a=new PostgresProjectionStore(new g(),0x10),c=new g(),b=new PostgresSyncStore(c),await a['migrate'](),await b['migrate']();}),afterEach(async()=>{await a?.['close'](),await b?.['close'](),vi['restoreAllMocks']();}),it('persists\x20a\x20complete\x20paginated\x20run\x20without\x20claiming\x20CDC\x20completeness',async()=>{const f=new FakeSource([page([fact(0x1)],'cursor-1'),page([fact(0x2)],null,'101')]),g=await new SnapshotSynchronizer(a,b,f)['run'](options(f));expect(g)['toMatchObject']({'applied':0x2,'duplicate':0x0,'enumeration_complete':!![],'missing_objects':0x0,'current_active':0x2}),expect(g['run'])['toMatchObject']({'state':'complete','pages':0x2,'objects':0x2,'checkpoint':'101'}),expect(g['coverage'])['toMatchObject']({'complete':![],'degraded_reasons':['SNAPSHOT_NOT_CHECKPOINT_PINNED','INCREMENTAL_CDC_REQUIRED']}),expect(f['cursors'])['toEqual']([null,'cursor-1']);}),it('reconciles\x20a\x20later\x20snapshot\x20without\x20conflicting\x20on\x20observation\x20checkpoints',async()=>{const f=new FakeSource([page([fact(0x1)],null)]),g=new SnapshotSynchronizer(a,b,f);await g['run'](options(f));const h=new FakeSource([page([fact(0x1,{'checkpoint':'101'})],null,'101')],'chain_a','101'),i=new SnapshotSynchronizer(a,b,h)['run'](options(h));await expect(i)['resolves']['toMatchObject']({'applied':0x0,'duplicate':0x1,'conflict':0x0,'missing_objects':0x0}),expect(await a['status']('chain_a'))['toMatchObject']({'raw_versions':0x1,'quarantined_versions':0x0});}),it('resumes\x20from\x20the\x20last\x20committed\x20page\x20after\x20a\x20source\x20failure',async()=>{const f=new FakeSource([page([fact(0x1)],'cursor-1'),page([fact(0x2)],null,'101')]);f['failAtCursor']='cursor-1';const g=new SnapshotSynchronizer(a,b,f);await expect(g['run'](options(f)))['rejects']['toThrow']('injected\x20page\x20failure'),expect((await b['status']('chain_a'))[0x0])['toMatchObject']({'state':'failed','cursor':'cursor-1','pages':0x1});const h=await g['run'](options(f));expect(h['run'])['toMatchObject']({'state':'complete','resumed':!![],'pages':0x2,'objects':0x2}),expect(h['applied'])['toBe'](0x1),expect(f['cursors'])['toEqual']([null,'cursor-1','cursor-1']);}),it('replays\x20facts\x20idempotently\x20when\x20a\x20crash\x20precedes\x20cursor\x20persistence',async()=>{const f=new FakeSource([page([fact(0x1)],null)]);vi['spyOn'](b,'savePage')['mockRejectedValueOnce'](new Error('injected\x20cursor\x20failure'));const g=new SnapshotSynchronizer(a,b,f);await expect(g['run'](options(f)))['rejects']['toThrow']('injected\x20cursor\x20failure');const h=await g['run'](options(f));expect(h)['toMatchObject']({'applied':0x0,'duplicate':0x1,'missing_objects':0x0}),expect(await a['status']('chain_a'))['toMatchObject']({'raw_versions':0x1,'active_objects':0x1}),expect(f['cursors'])['toEqual']([null,null]);}),it('reports\x20current\x20objects\x20missing\x20from\x20the\x20enumerated\x20live\x20set',async()=>{await a['apply'](fact(0x2));const f=new FakeSource([page([fact(0x1)],null)]),g=await new SnapshotSynchronizer(a,b,f)['run'](options(f));expect(g['missing_objects'])['toBe'](0x1),expect(g['missing_object_ids'])['toEqual']([objectId(0x2)]),expect(g['coverage']['degraded_reasons'])['toContain']('LIVE_SET_MISSING'),expect(g['coverage']['complete'])['toBe'](![]);}),it('rejects\x20an\x20object\x20changing\x20versions\x20between\x20snapshot\x20pages',async()=>{const f=new FakeSource([page([fact(0x1)],'cursor-1'),page([fact(0x1,{'object_version':'2','checkpoint':'101'})],null,'101')]),g=new SnapshotSynchronizer(a,b,f);await expect(g['run'](options(f)))['rejects']['toThrow']('NON_STABLE_SNAPSHOT'),expect((await b['status']('chain_a'))[0x0])['toMatchObject']({'state':'failed','pages':0x1});}),it('starts\x20a\x20fresh\x20enumeration\x20after\x20a\x20non-stable\x20snapshot',async()=>{const f=new FakeSource([page([fact(0x1)],'cursor-1'),page([fact(0x1,{'object_version':'2','checkpoint':'101'})],null,'101')]);await expect(new SnapshotSynchronizer(a,b,f)['run'](options(f)))['rejects']['toThrow']('NON_STABLE_SNAPSHOT');const g=(await b['status']('chain_a'))[0x0],h=new FakeSource([page([fact(0x1,{'object_version':'2','checkpoint':'101'})],null,'101')],'chain_a','101'),i=await new SnapshotSynchronizer(a,b,h)['run'](options(h));expect(i['run']['runId'])['not']['toBe'](g['runId']),expect(i)['toMatchObject']({'duplicate':0x1,'missing_objects':0x0});}),it('rejects\x20checkpoint\x20regression\x20and\x20chain\x20identity\x20mismatch',async()=>{const f=new FakeSource([page([fact(0x1)],null,'99')]);await expect(new SnapshotSynchronizer(a,b,f)['run'](options(f)))['rejects']['toThrow']('CHAIN_CHECKPOINT_REGRESSION');const g=new FakeSource([],'chain_b');await expect(new SnapshotSynchronizer(a,b,g)['run']({...options(g),'expectedChainId':'chain_a'}))['rejects']['toThrow']('CHAIN_IDENTITY_MISMATCH'),expect(await b['status']('chain_b'))['toEqual']([]);});function d(f){const g=b['savePage']['bind'](b);let h=![];vi['spyOn'](b,'savePage')['mockImplementation'](async i=>{const j=await g(i);return!h&&j['cursor']===null&&(h=!![],await f(j['runId'])),j;});}function e(){return new FakeSource([page([fact(0x1)],'cursor-1'),page([fact(0x2)],null,'101')]);}it('records\x20a\x20contiguous\x20page\x20ledger\x20and\x20completes\x20without\x20false\x20positives',async()=>{const f=e(),g=await new SnapshotSynchronizer(a,b,f)['run'](options(f));expect(g['run'])['toMatchObject']({'state':'complete','pages':0x2});const h=await c['query']('SELECT\x20page_seq,\x20checkpoint,\x20objects\x20FROM\x20sync_page\x20ORDER\x20BY\x20page_seq');expect(h['rows']['map'](i=>[Number(i['page_seq']),String(i['checkpoint']),Number(i['objects'])]))['toEqual']([[0x1,'100',0x1],[0x2,'101',0x1]]);}),it('refuses\x20to\x20complete\x20when\x20a\x20ledger\x20row\x20disappears',async()=>{const f=e();d(async h=>{await c['query']('DELETE\x20FROM\x20sync_page\x20WHERE\x20run_id=$1\x20AND\x20page_seq=1',[h]);});const g=new SnapshotSynchronizer(a,b,f);await expect(g['run'](options(f)))['rejects']['toThrow']('CHECKPOINT_GAP_DETECTED'),expect((await b['status']('chain_a'))[0x0])['toMatchObject']({'state':'failed'});}),it('refuses\x20to\x20complete\x20when\x20ledger\x20page\x20numbering\x20breaks',async()=>{const f=e();d(async h=>{await c['query']('UPDATE\x20sync_page\x20SET\x20page_seq=5\x20WHERE\x20run_id=$1\x20AND\x20page_seq=2',[h]);});const g=new SnapshotSynchronizer(a,b,f);await expect(g['run'](options(f)))['rejects']['toThrow'](/CHECKPOINT_GAP_DETECTED.*sequence break/),expect((await b['status']('chain_a'))[0x0])['toMatchObject']({'state':'failed'});}),it('refuses\x20to\x20complete\x20when\x20a\x20ledger\x20checkpoint\x20regresses',async()=>{const f=e();d(async h=>{await c['query']('UPDATE\x20sync_page\x20SET\x20checkpoint=99\x20WHERE\x20run_id=$1\x20AND\x20page_seq=2',[h]);});const g=new SnapshotSynchronizer(a,b,f);await expect(g['run'](options(f)))['rejects']['toThrow'](/CHECKPOINT_GAP_DETECTED.*regressed/),expect((await b['status']('chain_a'))[0x0])['toMatchObject']({'state':'failed'});});}),describe('GraphQLSnapshotSource',()=>{const a='0x0000000000000000000000000000000000000000000000000000000000000002',b={'struct':{'type':'0x1::string::String','fields':[{'name':'bytes','layout':{'vector':'u8'}}]}},c={'struct':{'type':a+'::service::Service<'+a+'::wow::WOW>','fields':[{'name':'name','layout':b}]}};function d(e='a9155c0f'){const f={'core':{'getChainIdentifier':vi['fn'](async()=>({'chainIdentifier':chainDigest}))},'listSharedObjects':vi['fn'](async()=>({'objects':[{'objectId':objectId(0x1),'version':'3','digest':digest,'type':a+'::service::Service<'+a+'::wow::WOW>','owner':{'$kind':'Shared','Shared':{'initialSharedVersion':'1'}},'previousTransaction':digest}],'cursor':null,'hasNextPage':![]})),'getObjects':vi['fn'](async()=>({'objects':[{'objectId':objectId(0x1),'version':'3','digest':digest,'type':a+'::service::Service<'+a+'::wow::WOW>','owner':{'$kind':'Shared','Shared':{'initialSharedVersion':'1'}},'previousTransaction':digest,'json':{'name':'one'},'layout':c}]}))},g={'getChainIdentifier':vi['fn'](async()=>e),'getLatestCheckpointSequenceNumber':vi['fn'](async()=>'100'),'multiGetObjects':vi['fn'](async()=>[])};return{'graphql':f,'rpc':g};}it('maps\x20read-only\x20chain\x20objects\x20into\x20validated\x20projection\x20inputs',async()=>{const {graphql:e,rpc:f}=d(),g=new GraphQLSnapshotSource(e,f,ENTRYPOINT['Testnet'],'a9155c0f'),h=await g['readPage']({'type':'0x2::service','cursor':null,'limit':0x32});expect(h['facts'][0x0])['toMatchObject']({'chain_id':'a9155c0f','object_id':objectId(0x1),'object_version':'3','move_type':'0x2::service::Service<0x2::wow::WOW>','checkpoint':'100','tx_digest':digest,'payload_format':'rpc-content-v1','payload_json':{'name':'one'},'state':'active'});}),it('fails\x20closed\x20when\x20the\x20fetched\x20version\x20drifts\x20from\x20the\x20enumeration',async()=>{const {graphql:e,rpc:f}=d();e['getObjects']['mockResolvedValueOnce']({'objects':[{'objectId':objectId(0x1),'version':'4','digest':digest,'type':a+'::service::Service<'+a+'::wow::WOW>','owner':{'$kind':'Shared','Shared':{'initialSharedVersion':'1'}},'previousTransaction':digest,'json':{'name':'one'},'layout':c}]});const g=new GraphQLSnapshotSource(e,f,ENTRYPOINT['Testnet'],'a9155c0f');await expect(g['readPage']({'type':'0x2::service','cursor':null,'limit':0x32}))['rejects']['toThrow']('NON_STABLE_SNAPSHOT');}),it('fails\x20closed\x20when\x20GraphQL\x20and\x20JSON-RPC\x20identities\x20differ',async()=>{const {graphql:e,rpc:f}=d();f['getChainIdentifier']['mockResolvedValue']('deadbeef');const g=new GraphQLSnapshotSource(e,f,ENTRYPOINT['Testnet'],'a9155c0f');await expect(g['observe']())['rejects']['toThrow']('CHAIN_IDENTITY_MISMATCH');});});
@@ -0,0 +1,10 @@
1
+ import type { PostgresProjectionStore } from "../projection/store.js";
2
+ import type { PostgresSyncStore } from "./store.js";
3
+ import type { RunSnapshotSyncOptions, SnapshotSource, SnapshotSyncResult } from "./types.js";
4
+ export declare class SnapshotSynchronizer {
5
+ private readonly projection;
6
+ private readonly sync;
7
+ private readonly source;
8
+ constructor(projection: PostgresProjectionStore, sync: PostgresSyncStore, source: SnapshotSource);
9
+ run(options: RunSnapshotSyncOptions): Promise<SnapshotSyncResult>;
10
+ }
@@ -0,0 +1 @@
1
+ import{parseObjectVersionFact}from'../projection/facts.js';export class SnapshotSynchronizer{['projection'];['sync'];['source'];constructor(a,b,c){this['projection']=a,this['sync']=b,this['source']=c;}async['run'](a){const b=a['limit']??0x32;if(!Number['isInteger'](b)||b<0x1||b>0x3e8)throw new Error('Invalid\x20sync\x20limit:\x20'+b);const c=await this['source']['observe']();if(c['chainId']!==a['expectedChainId'])throw new Error('CHAIN_IDENTITY_MISMATCH');let d=await this['sync']['start']({'chainId':c['chainId'],'stream':a['stream'],'scope':a['scope'],'checkpoint':c['checkpoint']});const e={'applied':0x0,'duplicate':0x0,'stale':0x0,'quarantined':0x0,'conflict':0x0},f=new Set();try{while(d['pages']===0x0||d['cursor']!==null){a['signal']?.['throwIfAborted']();const h=await this['source']['readPage']({'type':a['type'],'cursor':d['cursor'],'limit':b,'signal':a['signal']});if(h['chainId']!==a['expectedChainId'])throw new Error('CHAIN_IDENTITY_MISMATCH');if(h['hasNextPage']&&!h['nextCursor'])throw new Error('Snapshot\x20page\x20has\x20no\x20continuation\x20cursor');const i=[];for(const j of h['facts']){const k=parseObjectVersionFact(j),l=await this['projection']['applyFact'](k);e[l['status']]+=0x1;if(l['status']==='stale')f['add']('STALE_SNAPSHOT_FACT');(l['status']==='quarantined'||l['status']==='conflict')&&f['add']('PROJECTION_FACT_ERRORS'),i['push']({'objectId':k['objectId'],'objectVersion':k['objectVersion']});}d=await this['sync']['savePage']({'chainId':d['chainId'],'stream':d['stream'],'runId':d['runId'],'cursor':h['hasNextPage']?h['nextCursor']:null,'checkpoint':h['checkpoint'],'objects':i});}const g=await this['sync']['complete']({'chainId':d['chainId'],'stream':d['stream'],'runId':d['runId'],'degradedReasons':[...f]});return{...g,...e};}catch(m){await this['sync']['fail'](d['chainId'],d['stream'],d['runId'],m);throw m;}}}
@@ -0,0 +1,162 @@
1
+ import type { WowTypedObjectKind } from "@wowok/wowok";
2
+ import type { ProjectionCoverage } from "../projection/types.js";
3
+ export interface SnapshotScope {
4
+ kind?: WowTypedObjectKind;
5
+ shard?: {
6
+ count: number;
7
+ index: number;
8
+ };
9
+ }
10
+ export interface SnapshotPageRequest {
11
+ type: string;
12
+ cursor: string | null;
13
+ limit: number;
14
+ signal?: AbortSignal;
15
+ }
16
+ export interface SnapshotPage {
17
+ chainId: string;
18
+ checkpoint: string;
19
+ facts: unknown[];
20
+ nextCursor: string | null;
21
+ hasNextPage: boolean;
22
+ }
23
+ export interface SnapshotObservation {
24
+ chainId: string;
25
+ checkpoint: string;
26
+ }
27
+ export interface SnapshotSource {
28
+ observe(): Promise<SnapshotObservation>;
29
+ readPage(request: SnapshotPageRequest): Promise<SnapshotPage>;
30
+ }
31
+ export interface SyncRun {
32
+ chainId: string;
33
+ stream: string;
34
+ runId: string;
35
+ state: "running" | "complete" | "failed";
36
+ scope: SnapshotScope;
37
+ cursor: string | null;
38
+ checkpoint: string | null;
39
+ pages: number;
40
+ objects: number;
41
+ startedAt: string;
42
+ updatedAt: string;
43
+ completedAt: string | null;
44
+ error: string | null;
45
+ resumed: boolean;
46
+ }
47
+ export interface StartSyncRunOptions {
48
+ chainId: string;
49
+ stream: string;
50
+ scope: SnapshotScope;
51
+ checkpoint: string;
52
+ }
53
+ export interface SaveSyncPageOptions {
54
+ chainId: string;
55
+ stream: string;
56
+ runId: string;
57
+ cursor: string | null;
58
+ checkpoint: string;
59
+ objects: Array<{
60
+ objectId: string;
61
+ objectVersion: bigint;
62
+ }>;
63
+ }
64
+ export interface CompleteSyncRunOptions {
65
+ chainId: string;
66
+ stream: string;
67
+ runId: string;
68
+ degradedReasons?: string[];
69
+ }
70
+ export interface ReconciliationResult {
71
+ run: SyncRun;
72
+ enumeration_complete: true;
73
+ current_active: number;
74
+ missing_objects: number;
75
+ missing_object_ids: string[];
76
+ missing_truncated: boolean;
77
+ coverage: ProjectionCoverage;
78
+ }
79
+ export interface RunSnapshotSyncOptions {
80
+ expectedChainId: string;
81
+ stream: string;
82
+ type: string;
83
+ scope: SnapshotScope;
84
+ limit?: number;
85
+ signal?: AbortSignal;
86
+ }
87
+ export interface SnapshotSyncResult extends ReconciliationResult {
88
+ applied: number;
89
+ duplicate: number;
90
+ stale: number;
91
+ quarantined: number;
92
+ conflict: number;
93
+ }
94
+ export interface RunLiveSetReconciliationOptions {
95
+ expectedChainId: string;
96
+ type: string;
97
+ kind: string;
98
+ limit?: number;
99
+ signal?: AbortSignal;
100
+ }
101
+ export interface LiveSetReconciliationResult {
102
+ runId: string;
103
+ chainId: string;
104
+ checkpoint: string;
105
+ kind: string;
106
+ verdict: "pass" | "fail";
107
+ generation: number | null;
108
+ reconciled_through: string | null;
109
+ live_count: number;
110
+ projection_active: number;
111
+ pre_diff: {
112
+ missing: number;
113
+ extra: number;
114
+ version_drift: number;
115
+ };
116
+ repairs: {
117
+ missing: number;
118
+ drift: number;
119
+ tombstoned: number;
120
+ };
121
+ residual: {
122
+ missing: string[];
123
+ extra: string[];
124
+ version_drift: string[];
125
+ };
126
+ error: string | null;
127
+ }
128
+ export interface CdcHead {
129
+ chainId: string;
130
+ checkpoint: string;
131
+ }
132
+ export interface CdcChangeBatch {
133
+ facts: unknown[];
134
+ deletedIds: string[];
135
+ observedThrough: string;
136
+ pages: number;
137
+ }
138
+ export interface CdcSource {
139
+ head(): Promise<CdcHead>;
140
+ readChanges(after: string, before: string, kind: string): Promise<CdcChangeBatch>;
141
+ }
142
+ export interface RunCdcOptions {
143
+ expectedChainId: string;
144
+ kind: string;
145
+ maxSpan?: number;
146
+ }
147
+ export interface CdcRoundResult {
148
+ chainId: string;
149
+ kind: string;
150
+ anchored: boolean;
151
+ from: string | null;
152
+ to: string | null;
153
+ head: string;
154
+ pages: number;
155
+ applied: number;
156
+ duplicate: number;
157
+ stale: number;
158
+ quarantined: number;
159
+ conflict: number;
160
+ tombstoned: number;
161
+ skipped_facts: number;
162
+ }
@@ -0,0 +1 @@
1
+ export{};
@@ -1 +1 @@
1
- import{createAcceptanceRule}from'../acceptance.js';import{requiredCapabilities}from'../capability.js';import{getEvaluationEngine}from'../engine.js';import{extractDemandCapability,extractServiceCapability}from'../extract.js';import{relateRegion}from'../region.js';import{getJudgmentDoc}from'../../config/judgment.js';export const DEMAND_MATCH_EVALUATION='demand_match';function matchParams(){return getJudgmentDoc()['match'];}const dsCaps={'id':'ds_caps','description':'Extract\x20demand\x20+\x20service\x20capability\x20vectors\x20(either\x20may\x20be\x20pre-computed\x20or\x20raw)','evaluation_ids':[DEMAND_MATCH_EVALUATION],'extract':a=>({'demand_cap':a['entities']['demand_cap']??extractDemandCapability(a['entities']['demand']),'service_cap':a['entities']['service_cap']??extractServiceCapability(a['entities']['service'])})},regionRule={'id':'region','description':'Geographic\x20region\x20relation\x20between\x20demand\x20and\x20service','dimension':'region',get 'weight'(){return matchParams()['weights']['region'];},'evaluation_ids':[DEMAND_MATCH_EVALUATION],'evaluate':a=>{const b=matchParams(),c=a['facts']['demand_cap'],e=a['facts']['service_cap'],f=relateRegion(c['region'],e['region']);return{'score':b['region_scores'][f],'weight':b['weights']['region'],'reason':'region\x20'+f};}},industryRule={'id':'industry','description':'Industry\x20scenario\x20match','dimension':'industry',get 'weight'(){return matchParams()['weights']['industry'];},'evaluation_ids':[DEMAND_MATCH_EVALUATION],'evaluate':a=>{const b=matchParams(),c=a['facts']['demand_cap'],e=a['facts']['service_cap'];if(c['industry']==='general'||e['industry']==='general')return{'score':b['neutral_score'],'weight':b['weights']['industry'],'reason':'industry\x20unknown\x20(neutral)'};const f=c['industry']===e['industry']?0x64:0x0;return{'score':f,'weight':b['weights']['industry'],'reason':c['industry']+'\x20vs\x20'+e['industry']};}},priceRule={'id':'price','description':'Service\x20price\x20within\x20demand\x20budget','dimension':'price',get 'weight'(){return matchParams()['weights']['price'];},'evaluation_ids':[DEMAND_MATCH_EVALUATION],'evaluate':a=>{const b=matchParams(),c=a['facts']['demand_cap'],e=a['facts']['service_cap'];if(c['price_max']===undefined||e['price_min']===undefined)return{'score':b['neutral_score'],'weight':b['weights']['price'],'reason':'price\x20unknown\x20(neutral)'};if(e['price_min']<=c['price_max'])return{'score':0x64,'weight':b['weights']['price'],'reason':'within\x20budget'};const f=c['price_max']*BigInt(Math['round'](0x64+b['price_over_budget_tolerance_pct']))/0x64n;if(e['price_min']<=f)return{'score':b['neutral_score'],'weight':b['weights']['price'],'reason':'slightly\x20over\x20budget'};return{'score':0x0,'weight':b['weights']['price'],'reason':'over\x20budget'};}},trustRule={'id':'trust','description':'Service\x20trust\x20score','dimension':'trust',get 'weight'(){return matchParams()['weights']['trust'];},'evaluation_ids':[DEMAND_MATCH_EVALUATION],'evaluate':a=>{const b=matchParams(),c=a['facts']['service_cap'],d=c['trust_score'],e=b['trust_bands']['find'](g=>d>=g['min']),f=e?e['score']:0x0;return{'score':f,'weight':b['weights']['trust'],'reason':'trust\x20'+d};}},capabilitiesRule={'id':'capabilities','description':'Demand\x20capability\x20requirements\x20covered\x20by\x20the\x20service','dimension':'capabilities',get 'weight'(){return matchParams()['weights']['capabilities'];},'evaluation_ids':[DEMAND_MATCH_EVALUATION],'evaluate':a=>{const b=matchParams(),c=a['facts']['demand_cap'],e=a['facts']['service_cap'],f=requiredCapabilities(c['capabilities']);if(f['length']===0x0)return{'score':b['neutral_score'],'weight':b['weights']['capabilities'],'reason':'no\x20capability\x20requirement\x20(neutral)'};const g=f['filter'](h=>e['capabilities'][h]);return{'score':Math['round'](g['length']/f['length']*0x64),'weight':b['weights']['capabilities'],'matched':g,'mismatched':f['filter'](h=>!e['capabilities'][h]),'reason':g['length']+'/'+f['length']+'\x20required\x20capabilities\x20covered'};}},tagsRule={'id':'tags','description':'Keyword/tag\x20overlap','dimension':'tags',get 'weight'(){return matchParams()['weights']['tags'];},'evaluation_ids':[DEMAND_MATCH_EVALUATION],'evaluate':a=>{const b=matchParams(),c=a['facts']['demand_cap'],e=a['facts']['service_cap'],f=new Set((c['keywords']??[])['map'](i=>i['toLowerCase']())),g=new Set([...e['tags']??[],...e['keywords']??[]]['map'](i=>i['toLowerCase']()));if(f['size']===0x0)return{'score':b['neutral_score'],'weight':b['weights']['tags'],'reason':'no\x20keywords\x20(neutral)'};const h=[...f]['filter'](i=>g['has'](i))['length'];return{'score':Math['round'](h/f['size']*0x64),'weight':b['weights']['tags'],'reason':h+'/'+f['size']+'\x20keywords'};}},acceptanceRule=createAcceptanceRule(DEMAND_MATCH_EVALUATION,{'weight':matchParams()['weights']['acceptance'],'weightOf':()=>matchParams()['weights']['acceptance']});export function registerDemandMatchEvaluation(a=getEvaluationEngine()){a['registerDataSource'](dsCaps);for(const b of[regionRule,industryRule,priceRule,trustRule,capabilitiesRule,tagsRule,acceptanceRule]){a['registerRule'](b);}}export async function matchDemandToServices(a,b,c,d,e){const f=d??matchParams()['top_n'],g=getEvaluationEngine(),h=[];for(const i of b){const j=await g['evaluate'](DEMAND_MATCH_EVALUATION,{'demand_cap':a,'service':i,'presenter_history':e??[]},c);h['push']({'service':i,'result':j});}return h['sort']((k,l)=>l['result']['total']-k['result']['total']),h['slice'](0x0,f);}export async function matchServiceToDemands(a,b,c,d,e){const f=d??matchParams()['top_n'],g=getEvaluationEngine(),h=[];for(const i of b){const j=await g['evaluate'](DEMAND_MATCH_EVALUATION,{'service_cap':a,'demand':i,'presenter_history':e??[]},c);h['push']({'demand':i,'result':j});}return h['sort']((k,l)=>l['result']['total']-k['result']['total']),h['slice'](0x0,f);}
1
+ import{createAcceptanceRule}from'../acceptance.js';import{requiredCapabilities}from'../capability.js';import{getEvaluationEngine}from'../engine.js';import{extractDemandCapability,extractServiceCapability}from'../extract.js';import{relateRegion}from'../region.js';import{getJudgmentDoc}from'../../config/judgment.js';export const DEMAND_MATCH_EVALUATION='demand_match';function matchParams(){return getJudgmentDoc()['match'];}const dsCaps={'id':'ds_caps','description':'Extract\x20demand\x20+\x20service\x20capability\x20vectors\x20(either\x20may\x20be\x20pre-computed\x20or\x20raw)','evaluation_ids':[DEMAND_MATCH_EVALUATION],'extract':a=>({'demand_cap':a['entities']['demand_cap']??extractDemandCapability(a['entities']['demand']),'service_cap':a['entities']['service_cap']??extractServiceCapability(a['entities']['service'])})},regionRule={'id':'region','description':'Geographic\x20region\x20relation\x20between\x20demand\x20and\x20service','dimension':'region',get 'weight'(){return matchParams()['weights']['region'];},'evaluation_ids':[DEMAND_MATCH_EVALUATION],'evaluate':a=>{const b=matchParams(),c=a['facts']['demand_cap'],e=a['facts']['service_cap'],f=relateRegion(c['region'],e['region']);return{'score':b['region_scores'][f],'weight':b['weights']['region'],'reason':'region\x20'+f};}},industryRule={'id':'industry','description':'Industry\x20scenario\x20match','dimension':'industry',get 'weight'(){return matchParams()['weights']['industry'];},'evaluation_ids':[DEMAND_MATCH_EVALUATION],'evaluate':a=>{const b=matchParams(),c=a['facts']['demand_cap'],e=a['facts']['service_cap'];if(c['industry']==='general'||e['industry']==='general')return{'score':b['neutral_score'],'weight':b['weights']['industry'],'reason':'industry\x20unknown\x20(neutral)'};const f=c['industry']===e['industry']?0x64:0x0;return{'score':f,'weight':b['weights']['industry'],'reason':c['industry']+'\x20vs\x20'+e['industry']};}},priceRule={'id':'price','description':'Service\x20price\x20within\x20demand\x20budget','dimension':'price',get 'weight'(){return matchParams()['weights']['price'];},'evaluation_ids':[DEMAND_MATCH_EVALUATION],'evaluate':a=>{const b=matchParams(),c=a['facts']['demand_cap'],e=a['facts']['service_cap'];if(c['price_max']===undefined||e['price_min']===undefined)return{'score':b['neutral_score'],'weight':b['weights']['price'],'reason':'price\x20unknown\x20(neutral)'};if(e['price_min']<=c['price_max'])return{'score':0x64,'weight':b['weights']['price'],'reason':'within\x20budget'};const f=c['price_max']*BigInt(Math['round'](0x64+b['price_over_budget_tolerance_pct']))/0x64n;if(e['price_min']<=f)return{'score':b['neutral_score'],'weight':b['weights']['price'],'reason':'slightly\x20over\x20budget'};return{'score':0x0,'weight':b['weights']['price'],'reason':'over\x20budget'};}},trustRule={'id':'trust','description':'Service\x20trust\x20score','dimension':'trust',get 'weight'(){return matchParams()['weights']['trust'];},'evaluation_ids':[DEMAND_MATCH_EVALUATION],'evaluate':a=>{const b=matchParams(),c=a['facts']['service_cap'],d=c['trust_score'],e=b['trust_bands']['find'](h=>d>=h['min']),f=e?e['score']:0x0,g=c['trust_provenance']==='cold_start_prior'?'trust\x20'+d+'\x20(cold-start\x20prior\x20—\x20no\x20verified\x20reputation\x20yet)':'trust\x20'+d;return{'score':f,'weight':b['weights']['trust'],'reason':g};}},capabilitiesRule={'id':'capabilities','description':'Demand\x20capability\x20requirements\x20covered\x20by\x20the\x20service','dimension':'capabilities',get 'weight'(){return matchParams()['weights']['capabilities'];},'evaluation_ids':[DEMAND_MATCH_EVALUATION],'evaluate':a=>{const b=matchParams(),c=a['facts']['demand_cap'],e=a['facts']['service_cap'],f=requiredCapabilities(c['capabilities']);if(f['length']===0x0)return{'score':b['neutral_score'],'weight':b['weights']['capabilities'],'reason':'no\x20capability\x20requirement\x20(neutral)'};const g=f['filter'](h=>e['capabilities'][h]);return{'score':Math['round'](g['length']/f['length']*0x64),'weight':b['weights']['capabilities'],'matched':g,'mismatched':f['filter'](h=>!e['capabilities'][h]),'reason':g['length']+'/'+f['length']+'\x20required\x20capabilities\x20covered'};}},tagsRule={'id':'tags','description':'Keyword/tag\x20overlap','dimension':'tags',get 'weight'(){return matchParams()['weights']['tags'];},'evaluation_ids':[DEMAND_MATCH_EVALUATION],'evaluate':a=>{const b=matchParams(),c=a['facts']['demand_cap'],e=a['facts']['service_cap'],f=new Set((c['keywords']??[])['map'](i=>i['toLowerCase']())),g=new Set([...e['tags']??[],...e['keywords']??[]]['map'](i=>i['toLowerCase']()));if(f['size']===0x0)return{'score':b['neutral_score'],'weight':b['weights']['tags'],'reason':'no\x20keywords\x20(neutral)'};const h=[...f]['filter'](i=>g['has'](i))['length'];return{'score':Math['round'](h/f['size']*0x64),'weight':b['weights']['tags'],'reason':h+'/'+f['size']+'\x20keywords'};}},acceptanceRule=createAcceptanceRule(DEMAND_MATCH_EVALUATION,{'weight':matchParams()['weights']['acceptance'],'weightOf':()=>matchParams()['weights']['acceptance']});export function registerDemandMatchEvaluation(a=getEvaluationEngine()){a['registerDataSource'](dsCaps);for(const b of[regionRule,industryRule,priceRule,trustRule,capabilitiesRule,tagsRule,acceptanceRule]){a['registerRule'](b);}}export async function matchDemandToServices(a,b,c,d,e){const f=d??matchParams()['top_n'],g=getEvaluationEngine(),h=await Promise['all'](b['map'](async i=>({'service':i,'result':await g['evaluate'](DEMAND_MATCH_EVALUATION,{'demand_cap':a,'service':i,'presenter_history':e??[]},c)})));return h['sort']((i,j)=>j['result']['total']-i['result']['total']||i['service']['object']['localeCompare'](j['service']['object'])),h['slice'](0x0,f);}export async function matchServiceToDemands(a,b,c,d,e){const f=d??matchParams()['top_n'],g=getEvaluationEngine(),h=await Promise['all'](b['map'](async i=>({'demand':i,'result':await g['evaluate'](DEMAND_MATCH_EVALUATION,{'service_cap':a,'demand':i,'presenter_history':e??[]},c)})));return h['sort']((i,j)=>j['result']['total']-i['result']['total']||i['demand']['object']['localeCompare'](j['demand']['object'])),h['slice'](0x0,f);}
@@ -1 +1 @@
1
- import{describe,expect,it}from'vitest';import{ENTRYPOINT}from'@wowok/wowok';import{FakeDataPlane}from'../../graph/onchain/dataplane.js';import{expandTopology}from'../../graph/onchain/expand.js';import{FULL,GAP,fullServiceState,gapServiceState,truncatedServiceState}from'../../graph/onchain/fixtures.js';import{buildViewport}from'../../graph/onchain/viewport.js';import{runAnalyzers}from'../../graph/onchain/analyze.js';import{EVALUATION_SERVICE_INTENTS,resolveExpansionStrategy,withEvaluationBundle}from'../../graph/onchain/strategy.js';import{projectEvaluationFacts as a37a}from'../../graph/onchain/facts.js';import{evaluateGraphServiceFacts,GRAPH_RULE_INPUT_FACTS,registerGraphServiceEvaluations}from'./graph-service.js';async function evalState(a,b){registerGraphServiceEvaluations();const c=withEvaluationBundle(resolveExpansionStrategy({'intents':[...EVALUATION_SERVICE_INTENTS]})),d=await expandTopology(new FakeDataPlane(a),{'network':ENTRYPOINT['Mainnet'],'seed':b,'edges':c['edges'],'budget':c['budget']}),e=buildViewport(d,b,'neutral'),f=a37a(e,b,runAnalyzers(e));return{'projected':f,'r':await evaluateGraphServiceFacts(f)};}const scoreOf=(a,b,c)=>a[b]['breakdown']['find'](d=>d['rule_id']===c);describe('graph\x20service\x20evaluation\x20—\x20fully\x20hooked\x20shop\x20(TR-14.1)',()=>{it('scores\x20all\x20nine\x20dimensions\x20from\x20material\x20facts\x20with\x20no\x20default-low\x20deductions',async()=>{const {r:a}=await evalState(fullServiceState(),FULL['svc']);expect(a['unverified'])['toEqual']([]),expect(a['completeness'])['toBe'](0x64);const c=scoreOf(a,'trust','trust_arbitration');expect(c['score'])['toBe'](0x64),expect(c['matched'])['toEqual'](expect['arrayContaining'](['arbitrator\x20roster\x20configured','arbitration\x20fee\x20funded','arbitration\x20active'])),expect(c['reason'])['toContain']('Arbitration\x2020/20'),expect(scoreOf(a,'trust','trust_reviews')['score'])['toBe'](0x28),expect(scoreOf(a,'trust','trust_fulfillment')['score'])['toBe'](0x19);const d=scoreOf(a,'trust','trust_fund_safety');expect(d['score'])['toBe'](0x0),expect(d['mismatched'])['toContain']('compensation\x20fund\x20below\x20the\x20statutory\x203x\x20largest-order\x20exposure\x20ceiling'),expect(scoreOf(a,'trust','trust_transparency')['score'])['toBe'](0x64),expect(a['trust']['total'])['toBe'](0x31);const e=scoreOf(a,'risk','risk_fund');expect(e['score'])['toBeLessThan'](0x64),expect(e['mismatched'])['toContain']('Payout\x20exposure:\x20compensation\x20fund\x20cannot\x20cover\x20a\x20maximum-multiple\x20award\x20on\x20the\x20largest\x20windowed\x20order'),expect(e['mismatched']?.['some'](h=>h['startsWith']('R2:')))['toBe'](![]),expect(e['finding_refs']?.['map'](h=>h['id']))['toContain']('R2:'+FULL['svc']['toLowerCase']());const f=scoreOf(a,'risk','risk_trust');expect(f['score'])['toBeLessThan'](0x64),expect(f['mismatched']?.['some'](h=>h['startsWith']('R1:')))['toBe'](![]),expect(f['mismatched']?.['length'])['toBeGreaterThan'](0x0),expect(f['finding_refs']?.['some'](h=>h['id']['startsWith']('R1:')))['toBe'](!![]);for(const h of[...a['trust']['breakdown'],...a['risk']['breakdown']]){for(const i of h['mismatched']??[])expect(i)['not']['toContain']('::node::');}expect(scoreOf(a,'risk','risk_workflow')['score'])['toBe'](0x64),expect(scoreOf(a,'risk','risk_behavior')['score'])['toBe'](0x64);for(const j of[...a['trust']['breakdown'],...a['risk']['breakdown']]){const k=(j['reason']??'')['toLowerCase']();expect(k)['not']['toContain']('no\x20data'),expect(k)['not']['toContain']('default\x20low');}const g=a['trust']['trace']['filter'](l=>l['kind']==='rule'&&l['detail']['startsWith']('score='));expect(g['map'](l=>l['id']))['toEqual'](expect['arrayContaining'](Object['keys'](GRAPH_RULE_INPUT_FACTS)['filter'](l=>l['startsWith']('trust_'))));}),it('keeps\x20the\x20injection\x20+\x20requirement\x20mechanisms\x20working',async()=>{const {projected:a}=await evalState(fullServiceState(),FULL['svc']),b=await evaluateGraphServiceFacts(a,{'overrides':{'rules':{'trust_arbitration':{'weight':0x28}}}});expect(b['trust']['total'])['toBe'](0x3a);const c=await evaluateGraphServiceFacts(a,{'requirements':[{'rule_id':'trust_fund_safety','condition':'require'}]});expect(c['trust']['blocked'])['toBe'](!![]),expect(c['trust']['red_flags']['length'])['toBeGreaterThan'](0x0);});}),describe('graph\x20service\x20evaluation\x20—\x20configured-empty\x20shell\x20(TR-14.1)',()=>{it('scores\x20verified\x20absence\x20as\x20a\x20configured\x20fact\x20and\x20excludes\x20only\x20the\x20gap\x20dimension',async()=>{const {r:a}=await evalState(gapServiceState(),GAP['svc']),b=scoreOf(a,'trust','trust_arbitration');expect(b['score'])['toBe'](0x0),expect(b['reason'])['toContain']('verified\x20absence'),expect(scoreOf(a,'trust','trust_reviews')['score'])['toBe'](0x32),expect(a['unverified'])['toEqual'](['risk_workflow']),expect(scoreOf(a,'risk','risk_workflow'))['toBeUndefined'](),expect(a['risk']['unverified'])['toEqual'](['risk_workflow']),expect(a['trust']['unverified'])['toEqual'](['risk_workflow']);const c=a['risk']['trace']['find'](d=>d['kind']==='rule'&&d['id']==='risk_workflow');expect(c?.['detail'])['toContain']('not\x20applicable'),expect(scoreOf(a,'trust','trust_transparency')['score'])['toBe'](0x28),expect(scoreOf(a,'trust','trust_fund_safety')['score'])['toBe'](0x0);});}),describe('graph\x20service\x20evaluation\x20—\x20truncated\x20order\x20window\x20(TR-14.1)',()=>{it('excludes\x20the\x20unverifiable\x20dimensions\x20and\x20renormalizes\x20the\x20weighted\x20total',async()=>{const {r:a}=await evalState(truncatedServiceState(),FULL['svc']);expect(a['unverified'])['toEqual'](expect['arrayContaining'](['trust_fulfillment','trust_fund_safety','risk_fund','risk_behavior'])),expect(scoreOf(a,'trust','trust_fulfillment'))['toBeUndefined'](),expect(scoreOf(a,'trust','trust_fund_safety'))['toBeUndefined'](),expect(scoreOf(a,'risk','risk_fund'))['toBeUndefined'](),expect(scoreOf(a,'risk','risk_behavior'))['toBeUndefined'](),expect(a['trust']['total'])['toBe'](0x44);for(const b of['trust_fulfillment','trust_fund_safety']){expect(a['trust']['trace']['find'](c=>c['kind']==='rule'&&c['id']===b)?.['detail'])['toContain']('not\x20applicable');}for(const c of['risk_fund','risk_behavior']){expect(a['risk']['trace']['find'](d=>d['kind']==='rule'&&d['id']===c)?.['detail'])['toContain']('not\x20applicable');}});}),describe('graph\x20service\x20evaluation\x20—\x20scoring\x20report\x20(TR-14.2)',()=>{it('renders\x20a\x20per-dimension\x20report\x20with\x20score,\x20weight,\x20state,\x20confidence\x20and\x20evidence',async()=>{const {projected:a,r:c}=await evalState(fullServiceState(),FULL['svc']),d=new Map(a['facts']['map'](g=>[g['key'],g])),e=[];for(const g of['trust','risk']){const h=c[g];e['push'](g['toUpperCase']()+'\x20'+h['total']+'/100\x20('+h['level']+')\x20—\x20'+h['summary']);for(const i of h['breakdown']){const j=GRAPH_RULE_INPUT_FACTS[i['rule_id']]??[],k=j['map'](o=>o+':state='+(d['get'](o)?.['state']??'missing')),l=j['map'](o=>d['get'](o)?.['provenance']['confidence']??'missing'),n=j['map'](o=>d['get'](o)?.['evidence']??'missing')['join']('\x20');e['push'](i['rule_id']+':\x20score='+i['score']+'\x20weight='+i['weight']+'\x20'+k['join'](',')+'\x20confidence='+[...new Set(l)]['join']('+')+'\x20evidence=\x22'+n+'\x22');}}expect(e)['toHaveLength'](0xb);const f=e['filter'](o=>o['includes']('score='));expect(f)['toHaveLength'](0x9);for(const o of f){expect(o)['toMatch'](/weight=\d+/),expect(o)['toContain']('state=present'),expect(o)['toMatch'](/confidence=(exact|bounded|windowed|derived)/);const p=o['match'](/evidence="(.*)"/);expect(p,o)['not']['toBeNull'](),expect(p[0x1]['length'],o)['toBeGreaterThan'](0x0);}expect(c['trust']['summary'])['toContain']('weakest\x20Fund\x20safety'),expect(c['trust']['summary'])['not']['toContain']('trust_fund_safety'),expect(c['trust']['summary'])['toMatch'](/grade: (strong|fair|weak)/),expect(c['trust']['summary'])['toContain']('Compensation\x20fund');});});
1
+ import{describe,expect,it}from'vitest';import{ENTRYPOINT}from'@wowok/wowok';import{FakeDataPlane}from'../../graph/onchain/dataplane.js';import{expandTopology}from'../../graph/onchain/expand.js';import{FULL,GAP,fullServiceState,gapServiceState,truncatedServiceState}from'../../graph/onchain/fixtures.js';import{buildViewport}from'../../graph/onchain/viewport.js';import{runAnalyzers}from'../../graph/onchain/analyze.js';import{EVALUATION_SERVICE_INTENTS,resolveExpansionStrategy,withEvaluationBundle}from'../../graph/onchain/strategy.js';import{projectEvaluationFacts as a69a}from'../../graph/onchain/facts.js';import{evaluateGraphServiceFacts,GRAPH_RULE_INPUT_FACTS,registerGraphServiceEvaluations}from'./graph-service.js';async function evalState(a,b){registerGraphServiceEvaluations();const c=withEvaluationBundle(resolveExpansionStrategy({'intents':[...EVALUATION_SERVICE_INTENTS]})),d=await expandTopology(new FakeDataPlane(a),{'network':ENTRYPOINT['Mainnet'],'seed':b,'edges':c['edges'],'budget':c['budget']}),e=buildViewport(d,b,'neutral'),f=a69a(e,b,runAnalyzers(e));return{'projected':f,'r':await evaluateGraphServiceFacts(f)};}const scoreOf=(a,b,c)=>a[b]['breakdown']['find'](d=>d['rule_id']===c);describe('graph\x20service\x20evaluation\x20—\x20fully\x20hooked\x20shop\x20(TR-14.1)',()=>{it('scores\x20all\x20nine\x20dimensions\x20from\x20material\x20facts\x20with\x20no\x20default-low\x20deductions',async()=>{const {r:a}=await evalState(fullServiceState(),FULL['svc']);expect(a['unverified'])['toEqual']([]),expect(a['completeness'])['toBe'](0x64);const c=scoreOf(a,'trust','trust_arbitration');expect(c['score'])['toBe'](0x64),expect(c['matched'])['toEqual'](expect['arrayContaining'](['arbitrator\x20roster\x20configured','arbitration\x20fee\x20funded','arbitration\x20active'])),expect(c['reason'])['toContain']('Arbitration\x2020/20'),expect(scoreOf(a,'trust','trust_reviews')['score'])['toBe'](0x28),expect(scoreOf(a,'trust','trust_fulfillment')['score'])['toBe'](0x19);const d=scoreOf(a,'trust','trust_fund_safety');expect(d['score'])['toBe'](0x0),expect(d['mismatched'])['toContain']('compensation\x20fund\x20below\x20the\x20statutory\x203x\x20largest-order\x20exposure\x20ceiling'),expect(scoreOf(a,'trust','trust_transparency')['score'])['toBe'](0x64),expect(a['trust']['total'])['toBe'](0x31);const e=scoreOf(a,'risk','risk_fund');expect(e['score'])['toBeLessThan'](0x64),expect(e['mismatched'])['toContain']('Payout\x20exposure:\x20compensation\x20fund\x20cannot\x20cover\x20a\x20maximum-multiple\x20award\x20on\x20the\x20largest\x20windowed\x20order'),expect(e['mismatched']?.['some'](h=>h['startsWith']('R2:')))['toBe'](![]),expect(e['finding_refs']?.['map'](h=>h['id']))['toContain']('R2:'+FULL['svc']['toLowerCase']());const f=scoreOf(a,'risk','risk_trust');expect(f['score'])['toBeLessThan'](0x64),expect(f['mismatched']?.['some'](h=>h['startsWith']('R1:')))['toBe'](![]),expect(f['mismatched']?.['length'])['toBeGreaterThan'](0x0),expect(f['finding_refs']?.['some'](h=>h['id']['startsWith']('R1:')))['toBe'](!![]);for(const h of[...a['trust']['breakdown'],...a['risk']['breakdown']]){for(const i of h['mismatched']??[])expect(i)['not']['toContain']('::node::');}expect(scoreOf(a,'risk','risk_workflow')['score'])['toBe'](0x64),expect(scoreOf(a,'risk','risk_behavior')['score'])['toBe'](0x64);for(const j of[...a['trust']['breakdown'],...a['risk']['breakdown']]){const k=(j['reason']??'')['toLowerCase']();expect(k)['not']['toContain']('no\x20data'),expect(k)['not']['toContain']('default\x20low');}const g=a['trust']['trace']['filter'](l=>l['kind']==='rule'&&l['detail']['startsWith']('score='));expect(g['map'](l=>l['id']))['toEqual'](expect['arrayContaining'](Object['keys'](GRAPH_RULE_INPUT_FACTS)['filter'](l=>l['startsWith']('trust_'))));}),it('keeps\x20the\x20injection\x20+\x20requirement\x20mechanisms\x20working',async()=>{const {projected:a}=await evalState(fullServiceState(),FULL['svc']),b=await evaluateGraphServiceFacts(a,{'overrides':{'rules':{'trust_arbitration':{'weight':0x28}}}});expect(b['trust']['total'])['toBe'](0x3a);const c=await evaluateGraphServiceFacts(a,{'requirements':[{'rule_id':'trust_fund_safety','condition':'require'}]});expect(c['trust']['blocked'])['toBe'](!![]),expect(c['trust']['red_flags']['length'])['toBeGreaterThan'](0x0);});}),describe('graph\x20service\x20evaluation\x20—\x20configured-empty\x20shell\x20(TR-14.1)',()=>{it('scores\x20verified\x20absence\x20as\x20a\x20configured\x20fact\x20and\x20excludes\x20only\x20the\x20gap\x20dimension',async()=>{const {r:a}=await evalState(gapServiceState(),GAP['svc']),b=scoreOf(a,'trust','trust_arbitration');expect(b['score'])['toBe'](0x0),expect(b['reason'])['toContain']('verified\x20absence'),expect(scoreOf(a,'trust','trust_reviews')['score'])['toBe'](0x32),expect(a['unverified'])['toEqual'](['risk_workflow']),expect(scoreOf(a,'risk','risk_workflow'))['toBeUndefined'](),expect(a['risk']['unverified'])['toEqual'](['risk_workflow']),expect(a['trust']['unverified'])['toEqual'](['risk_workflow']);const c=a['risk']['trace']['find'](d=>d['kind']==='rule'&&d['id']==='risk_workflow');expect(c?.['detail'])['toContain']('not\x20applicable'),expect(scoreOf(a,'trust','trust_transparency')['score'])['toBe'](0x28),expect(scoreOf(a,'trust','trust_fund_safety')['score'])['toBe'](0x0);});}),describe('graph\x20service\x20evaluation\x20—\x20truncated\x20order\x20window\x20(TR-14.1)',()=>{it('excludes\x20the\x20unverifiable\x20dimensions\x20and\x20renormalizes\x20the\x20weighted\x20total',async()=>{const {r:a}=await evalState(truncatedServiceState(),FULL['svc']);expect(a['unverified'])['toEqual'](expect['arrayContaining'](['trust_fulfillment','trust_fund_safety','risk_fund','risk_behavior'])),expect(scoreOf(a,'trust','trust_fulfillment'))['toBeUndefined'](),expect(scoreOf(a,'trust','trust_fund_safety'))['toBeUndefined'](),expect(scoreOf(a,'risk','risk_fund'))['toBeUndefined'](),expect(scoreOf(a,'risk','risk_behavior'))['toBeUndefined'](),expect(a['trust']['total'])['toBe'](0x44);for(const b of['trust_fulfillment','trust_fund_safety']){expect(a['trust']['trace']['find'](c=>c['kind']==='rule'&&c['id']===b)?.['detail'])['toContain']('not\x20applicable');}for(const c of['risk_fund','risk_behavior']){expect(a['risk']['trace']['find'](d=>d['kind']==='rule'&&d['id']===c)?.['detail'])['toContain']('not\x20applicable');}});}),describe('graph\x20service\x20evaluation\x20—\x20scoring\x20report\x20(TR-14.2)',()=>{it('renders\x20a\x20per-dimension\x20report\x20with\x20score,\x20weight,\x20state,\x20confidence\x20and\x20evidence',async()=>{const {projected:a,r:c}=await evalState(fullServiceState(),FULL['svc']),d=new Map(a['facts']['map'](g=>[g['key'],g])),e=[];for(const g of['trust','risk']){const h=c[g];e['push'](g['toUpperCase']()+'\x20'+h['total']+'/100\x20('+h['level']+')\x20—\x20'+h['summary']);for(const i of h['breakdown']){const j=GRAPH_RULE_INPUT_FACTS[i['rule_id']]??[],k=j['map'](o=>o+':state='+(d['get'](o)?.['state']??'missing')),l=j['map'](o=>d['get'](o)?.['provenance']['confidence']??'missing'),n=j['map'](o=>d['get'](o)?.['evidence']??'missing')['join']('\x20');e['push'](i['rule_id']+':\x20score='+i['score']+'\x20weight='+i['weight']+'\x20'+k['join'](',')+'\x20confidence='+[...new Set(l)]['join']('+')+'\x20evidence=\x22'+n+'\x22');}}expect(e)['toHaveLength'](0xb);const f=e['filter'](o=>o['includes']('score='));expect(f)['toHaveLength'](0x9);for(const o of f){expect(o)['toMatch'](/weight=\d+/),expect(o)['toContain']('state=present'),expect(o)['toMatch'](/confidence=(exact|bounded|windowed|derived)/);const p=o['match'](/evidence="(.*)"/);expect(p,o)['not']['toBeNull'](),expect(p[0x1]['length'],o)['toBeGreaterThan'](0x0);}expect(c['trust']['summary'])['toContain']('weakest\x20Fund\x20safety'),expect(c['trust']['summary'])['not']['toContain']('trust_fund_safety'),expect(c['trust']['summary'])['toMatch'](/grade: (strong|fair|weak)/),expect(c['trust']['summary'])['toContain']('Compensation\x20fund');});});
@@ -24,6 +24,7 @@ export interface CapabilityVector {
24
24
  token_type: string;
25
25
  ticket_size: "micro" | "small" | "medium" | "large";
26
26
  trust_score: number;
27
+ trust_provenance?: "cold_start_prior" | "verified";
27
28
  reputation_like_ratio: number;
28
29
  arbitration_independent: boolean;
29
30
  merchant_history_count: number;