xytara 3.1.17 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +120 -92
- package/RELEASE_NOTES.md +7 -2
- package/START_HERE.md +56 -224
- package/bin/xytara.js +21 -8
- package/client.js +79 -1
- package/lib/{delivery_receipt.js → delivery_receipt_verify.js} +14 -100
- package/package.json +25 -35
- package/scripts/verify_client.js +418 -0
- package/scripts/verify_release_candidate.js +146 -45
- package/.env.example +0 -173
- package/ADAPTER_START_HERE.md +0 -56
- package/BSV_TERANODE_PLATFORM_DISCONTINUITY_RUNBOOK.md +0 -126
- package/CARRIED_HANDOFF_DEMO.md +0 -60
- package/EXAMPLE_PATHS.md +0 -53
- package/FINAL_CONTRACT.md +0 -498
- package/PARTNER_READY_PATH.md +0 -71
- package/PUBLIC_DEMO_SCRIPT.md +0 -78
- package/REAL_PAYMENT_SETUP.md +0 -320
- package/SERVICE_CONTRACT.md +0 -419
- package/SUPABASE_RUNTIME_STATE_SETUP.md +0 -61
- package/TERMINOLOGY.md +0 -50
- package/WHY_XYTARA_XOONYA.md +0 -63
- package/XT1_EXECUTION_TRUTH_STANDARD.md +0 -78
- package/XYTARA_ADAPTER_PROTOCOL_TRUTH_MODEL.md +0 -75
- package/XYTARA_AUTONOMOUS_ENTRY_MODEL.md +0 -124
- package/XYTARA_DEFAULT_PATH_MODEL.md +0 -84
- package/XYTARA_EXECUTION_TRUTH_MODEL.md +0 -171
- package/XYTARA_PAYMENT_RAIL_TRUTH_MODEL.md +0 -68
- package/adapters/AUTHORING_GUIDE.md +0 -244
- package/adapters/ONBOARDING_POSTURE.md +0 -61
- package/adapters/THIRD_PARTY_SUBMISSION_PROCESS.md +0 -84
- package/adapters/adapter-manifest.schema.json +0 -283
- package/adapters/examples/example-execution-adapter.js +0 -61
- package/adapters/examples/example-execution-adapter.manifest.json +0 -81
- package/adapters/examples/example-settlement-adapter.js +0 -55
- package/adapters/examples/example-settlement-adapter.manifest.json +0 -78
- package/adapters/examples/example-third-party-acp-registration.submission-bundle.json +0 -124
- package/adapters/examples/example-third-party-erc8004-registration.submission-bundle.json +0 -121
- package/adapters/examples/example-third-party-identity-registration.certification-pack.json +0 -22
- package/adapters/examples/example-third-party-identity-registration.record.json +0 -26
- package/adapters/examples/example-third-party-identity-registration.submission-bundle.json +0 -126
- package/adapters/examples/example-third-party-langchain-registration.submission-bundle.json +0 -124
- package/adapters/examples/example-third-party-mpp-registration.submission-bundle.json +0 -120
- package/adapters/examples/example-third-party-registration.certification-pack.json +0 -22
- package/adapters/examples/example-third-party-registration.record.json +0 -28
- package/adapters/examples/example-third-party-registration.submission-bundle.json +0 -126
- package/adapters/examples/example-third-party-settlement-registration.certification-pack.json +0 -22
- package/adapters/examples/example-third-party-settlement-registration.record.json +0 -26
- package/adapters/examples/example-third-party-settlement-registration.submission-bundle.json +0 -127
- package/adapters/examples/langchain-reference-execution-adapter.js +0 -81
- package/adapters/examples/langchain-reference-execution-adapter.manifest.json +0 -79
- package/adapters/examples/langgraph-reference-execution-adapter.js +0 -81
- package/adapters/examples/langgraph-reference-execution-adapter.manifest.json +0 -79
- package/adapters/examples/minimal-third-party-execution-adapter.js +0 -61
- package/adapters/examples/minimal-third-party-execution-adapter.manifest.json +0 -79
- package/adapters/examples/minimal-third-party-execution-registration.record.json +0 -26
- package/adapters/examples/minimal-third-party-execution.certification-pack.json +0 -22
- package/adapters/examples/minimal-third-party-execution.submission-bundle.json +0 -126
- package/adapters/integration-certification-pack.schema.json +0 -82
- package/adapters/integration-registration-record.schema.json +0 -114
- package/adapters/integration-submission-bundle-set.schema.json +0 -39
- package/adapters/integration-submission-bundle.schema.json +0 -34
- package/adapters/interfaces.js +0 -56
- package/bin/xytara-release.js +0 -1429
- package/examples/_demo_env.js +0 -40
- package/examples/adapter_review_walkthrough.js +0 -54
- package/examples/export_carried_handoff.js +0 -81
- package/examples/funded_runtime_walkthrough.js +0 -71
- package/examples/partner_launch_walkthrough.js +0 -44
- package/examples/quickstart.js +0 -3256
- package/fixtures/catalog.js +0 -1156
- package/index.js +0 -395
- package/integrations/dispatch.js +0 -276
- package/integrations/registry-snapshot.schema.json +0 -36
- package/integrations/registry.js +0 -2261
- package/integrations/views.js +0 -28
- package/lib/a2a_lane_contract.js +0 -104
- package/lib/a2a_rpc.js +0 -287
- package/lib/a2c_lane_contract.js +0 -111
- package/lib/account_auth.js +0 -585
- package/lib/acp_lane_contract.js +0 -54
- package/lib/activity_audit_contract.js +0 -322
- package/lib/adapter_certification_contract.js +0 -130
- package/lib/adapter_depth_contract.js +0 -218
- package/lib/adapter_pack.js +0 -118
- package/lib/adapter_partner_pack.js +0 -89
- package/lib/adapter_promotion_pack.js +0 -89
- package/lib/adapter_protocol_truth_contract.js +0 -229
- package/lib/agent_identity.js +0 -162
- package/lib/announcement_pack.js +0 -61
- package/lib/artifact_distribution_lane_contract.js +0 -54
- package/lib/asyncapi_contract.js +0 -150
- package/lib/auth_interop_contract.js +0 -80
- package/lib/authority_binding_lifecycle_contract.js +0 -182
- package/lib/autonomous_entry_contract.js +0 -409
- package/lib/bolt_adapter.js +0 -187
- package/lib/bolt_lane_contract.js +0 -55
- package/lib/bolt_payment_front.js +0 -224
- package/lib/business_cockpit.js +0 -378
- package/lib/buyer_event_log.js +0 -230
- package/lib/buyer_safety_policy.js +0 -132
- package/lib/capability_execution_truth.js +0 -276
- package/lib/capability_registry.js +0 -633
- package/lib/catalog_contract.js +0 -966
- package/lib/catalog_family_model.js +0 -193
- package/lib/catalog_views.js +0 -55
- package/lib/cdp_jwt.js +0 -57
- package/lib/checkout_event_security.js +0 -142
- package/lib/checkout_processor_adapter.js +0 -217
- package/lib/cloudevents_contract.js +0 -80
- package/lib/command_flow.js +0 -911
- package/lib/commerce_artifacts.js +0 -591
- package/lib/commerce_authority.js +0 -449
- package/lib/commerce_bridge.js +0 -274
- package/lib/commerce_cases.js +0 -399
- package/lib/commerce_checkout.js +0 -509
- package/lib/commerce_coordination.js +0 -346
- package/lib/commerce_economics.js +0 -4017
- package/lib/commerce_identity.js +0 -578
- package/lib/commerce_quotes.js +0 -266
- package/lib/commerce_reports.js +0 -14461
- package/lib/commerce_runtime.js +0 -322
- package/lib/commerce_shell.js +0 -257
- package/lib/crypto_agility_matrix.js +0 -125
- package/lib/default_path_contract.js +0 -412
- package/lib/delivery_summary_contract.js +0 -95
- package/lib/ecosystem_entry.js +0 -64
- package/lib/erc8004_lane_contract.js +0 -65
- package/lib/event_system_lane_contract.js +0 -75
- package/lib/execution_dedupe.js +0 -86
- package/lib/execution_truth_contract.js +0 -234
- package/lib/external_credit_bridge.js +0 -142
- package/lib/external_execution_policy.js +0 -143
- package/lib/external_execution_runtime.js +0 -238
- package/lib/feature_control_lane_contract.js +0 -54
- package/lib/first_run_conversion_contract.js +0 -290
- package/lib/framework_lane_contract.js +0 -89
- package/lib/framework_provider_promotion.js +0 -235
- package/lib/full_catalog_contract.js +0 -525
- package/lib/go_live_operator_pack.js +0 -391
- package/lib/http_transport.js +0 -99
- package/lib/identity_auth.js +0 -175
- package/lib/identity_interop_contract.js +0 -82
- package/lib/integration_matrix_contract.js +0 -102
- package/lib/l402_adapter.js +0 -241
- package/lib/l402_lane_contract.js +0 -55
- package/lib/l402_payment_front.js +0 -265
- package/lib/launch_narrative.js +0 -53
- package/lib/lifecycle_continuity_contract.js +0 -115
- package/lib/machine_identity_registry_contract.js +0 -199
- package/lib/major_rails_lane_contract.js +0 -150
- package/lib/market_lane_contract.js +0 -467
- package/lib/market_lane_depth_contract.js +0 -182
- package/lib/market_lane_promotion_contract.js +0 -210
- package/lib/market_lane_trace_contract.js +0 -278
- package/lib/mcp_lane_contract.js +0 -137
- package/lib/mpp_adapter.js +0 -237
- package/lib/network_transport.js +0 -110
- package/lib/offline_verifiability_recovery_contract.js +0 -155
- package/lib/openapi_contract.js +0 -300
- package/lib/operational_receipt_contract.js +0 -165
- package/lib/operator_intelligence.js +0 -407
- package/lib/outreach_copy_pack.js +0 -51
- package/lib/outreach_message_pack.js +0 -71
- package/lib/outreach_proof.js +0 -71
- package/lib/outreach_target_pack.js +0 -60
- package/lib/partner_intelligence.js +0 -105
- package/lib/payment_config.js +0 -257
- package/lib/payment_front_registry.js +0 -53
- package/lib/payment_protocol_contract.js +0 -165
- package/lib/payment_rail_truth_contract.js +0 -324
- package/lib/payment_verification.js +0 -357
- package/lib/phase_10_closeout_pack.js +0 -45
- package/lib/phase_10_completion_pack.js +0 -76
- package/lib/phase_10_decision_record_pack.js +0 -54
- package/lib/phase_10_decision_resolution_pack.js +0 -53
- package/lib/phase_10_demand_signal_adapters_runtime_pack.js +0 -74
- package/lib/phase_10_ecosystem_backlog_triage_runtime_pack.js +0 -74
- package/lib/phase_10_freeze_baseline_pack.js +0 -67
- package/lib/phase_10_freeze_review_pack.js +0 -64
- package/lib/phase_10_gate_pack.js +0 -57
- package/lib/phase_10_long_tail_continuity_pack.js +0 -80
- package/lib/phase_10_long_tail_ecosystem_pack.js +0 -66
- package/lib/phase_10_niche_extension_registry_runtime_pack.js +0 -74
- package/lib/phase_1_claude_mcp_pack.js +0 -60
- package/lib/phase_1_claude_mcp_runtime_pack.js +0 -52
- package/lib/phase_1_ecosystem_pack.js +0 -67
- package/lib/phase_1_openai_codex_pack.js +0 -60
- package/lib/phase_1_openai_codex_runtime_pack.js +0 -52
- package/lib/phase_2_base_runtime_pack.js +0 -71
- package/lib/phase_2_base_usdc_runtime_pack.js +0 -72
- package/lib/phase_2_closeout_pack.js +0 -65
- package/lib/phase_2_completion_pack.js +0 -113
- package/lib/phase_2_decision_record_pack.js +0 -66
- package/lib/phase_2_decision_resolution_pack.js +0 -69
- package/lib/phase_2_eth_runtime_pack.js +0 -70
- package/lib/phase_2_evm_base_runtime_pack.js +0 -73
- package/lib/phase_2_evm_runtime_pack.js +0 -71
- package/lib/phase_2_flip_preview_pack.js +0 -60
- package/lib/phase_2_freeze_review_pack.js +0 -99
- package/lib/phase_2_gate_pack.js +0 -77
- package/lib/phase_2_payment_rails_pack.js +0 -71
- package/lib/phase_2_usdc_runtime_pack.js +0 -71
- package/lib/phase_3_brc_runtime_pack.js +0 -81
- package/lib/phase_3_bsv_teranode_runtime_pack.js +0 -83
- package/lib/phase_3_closeout_pack.js +0 -62
- package/lib/phase_3_completion_pack.js +0 -94
- package/lib/phase_3_decision_record_pack.js +0 -64
- package/lib/phase_3_decision_resolution_pack.js +0 -69
- package/lib/phase_3_freeze_baseline_pack.js +0 -67
- package/lib/phase_3_freeze_review_pack.js +0 -80
- package/lib/phase_3_gate_pack.js +0 -76
- package/lib/phase_3_metanet_runtime_pack.js +0 -81
- package/lib/phase_3_native_bsv_pack.js +0 -91
- package/lib/phase_3_native_continuity_pack.js +0 -82
- package/lib/phase_4_attestation_runtime_pack.js +0 -75
- package/lib/phase_4_closeout_pack.js +0 -45
- package/lib/phase_4_completion_pack.js +0 -94
- package/lib/phase_4_decision_record_pack.js +0 -54
- package/lib/phase_4_decision_resolution_pack.js +0 -45
- package/lib/phase_4_erc8004_runtime_pack.js +0 -76
- package/lib/phase_4_freeze_baseline_pack.js +0 -67
- package/lib/phase_4_freeze_review_pack.js +0 -68
- package/lib/phase_4_gate_pack.js +0 -58
- package/lib/phase_4_identity_registry_runtime_pack.js +0 -75
- package/lib/phase_4_identity_trust_pack.js +0 -73
- package/lib/phase_4_trust_continuity_pack.js +0 -86
- package/lib/phase_5_antelope_runtime_pack.js +0 -76
- package/lib/phase_5_closeout_pack.js +0 -44
- package/lib/phase_5_completion_pack.js +0 -82
- package/lib/phase_5_constellation_dag_runtime_pack.js +0 -76
- package/lib/phase_5_decision_record_pack.js +0 -54
- package/lib/phase_5_decision_resolution_pack.js +0 -45
- package/lib/phase_5_digibyte_dgb_runtime_pack.js +0 -76
- package/lib/phase_5_freeze_baseline_pack.js +0 -67
- package/lib/phase_5_freeze_review_pack.js +0 -68
- package/lib/phase_5_gate_pack.js +0 -58
- package/lib/phase_5_hedera_hbar_runtime_pack.js +0 -76
- package/lib/phase_5_kaspa_kas_runtime_pack.js +0 -76
- package/lib/phase_5_major_rails_continuity_pack.js +0 -86
- package/lib/phase_5_major_rails_pack.js +0 -110
- package/lib/phase_5_metal_runtime_pack.js +0 -76
- package/lib/phase_5_polkadot_dot_runtime_pack.js +0 -76
- package/lib/phase_5_proton_xpr_runtime_pack.js +0 -76
- package/lib/phase_5_solana_runtime_pack.js +0 -76
- package/lib/phase_6_autogen_runtime_pack.js +0 -72
- package/lib/phase_6_closeout_pack.js +0 -46
- package/lib/phase_6_completion_pack.js +0 -77
- package/lib/phase_6_decision_record_pack.js +0 -54
- package/lib/phase_6_decision_resolution_pack.js +0 -53
- package/lib/phase_6_framework_continuity_pack.js +0 -81
- package/lib/phase_6_framework_runtime_pack.js +0 -68
- package/lib/phase_6_freeze_baseline_pack.js +0 -68
- package/lib/phase_6_freeze_review_pack.js +0 -65
- package/lib/phase_6_gate_pack.js +0 -59
- package/lib/phase_6_langchain_runtime_pack.js +0 -72
- package/lib/phase_6_langgraph_runtime_pack.js +0 -72
- package/lib/phase_6_semantic_kernel_runtime_pack.js +0 -72
- package/lib/phase_7_closeout_pack.js +0 -45
- package/lib/phase_7_completion_pack.js +0 -85
- package/lib/phase_7_decision_record_pack.js +0 -53
- package/lib/phase_7_decision_resolution_pack.js +0 -53
- package/lib/phase_7_event_system_continuity_pack.js +0 -89
- package/lib/phase_7_event_system_pack.js +0 -76
- package/lib/phase_7_freeze_baseline_pack.js +0 -74
- package/lib/phase_7_freeze_review_pack.js +0 -65
- package/lib/phase_7_gate_pack.js +0 -58
- package/lib/phase_7_kafka_runtime_pack.js +0 -74
- package/lib/phase_7_mqtt_runtime_pack.js +0 -74
- package/lib/phase_7_nats_runtime_pack.js +0 -74
- package/lib/phase_7_webhook_event_bus_runtime_pack.js +0 -74
- package/lib/phase_8_closeout_pack.js +0 -46
- package/lib/phase_8_completion_pack.js +0 -82
- package/lib/phase_8_decision_record_pack.js +0 -54
- package/lib/phase_8_decision_resolution_pack.js +0 -53
- package/lib/phase_8_external_receipt_import_runtime_pack.js +0 -74
- package/lib/phase_8_external_result_import_runtime_pack.js +0 -74
- package/lib/phase_8_freeze_baseline_pack.js +0 -71
- package/lib/phase_8_freeze_review_pack.js +0 -64
- package/lib/phase_8_gate_pack.js +0 -58
- package/lib/phase_8_proof_bridge_continuity_pack.js +0 -86
- package/lib/phase_8_proof_bridges_pack.js +0 -72
- package/lib/phase_8_proof_bundle_normalization_runtime_pack.js +0 -74
- package/lib/phase_9_closeout_pack.js +0 -46
- package/lib/phase_9_completion_pack.js +0 -82
- package/lib/phase_9_custody_refs_runtime_pack.js +0 -74
- package/lib/phase_9_decision_record_pack.js +0 -54
- package/lib/phase_9_decision_resolution_pack.js +0 -53
- package/lib/phase_9_freeze_baseline_pack.js +0 -71
- package/lib/phase_9_freeze_review_pack.js +0 -64
- package/lib/phase_9_gate_pack.js +0 -58
- package/lib/phase_9_operator_accounting_bridges_runtime_pack.js +0 -74
- package/lib/phase_9_treasury_connectivity_pack.js +0 -72
- package/lib/phase_9_treasury_continuity_pack.js +0 -86
- package/lib/phase_9_treasury_destinations_runtime_pack.js +0 -74
- package/lib/phase_program_pack.js +0 -120
- package/lib/pricing_optimization_contract.js +0 -775
- package/lib/protocol_lane_contract.js +0 -117
- package/lib/provenance_lane_contract.js +0 -54
- package/lib/provider_lane_contract.js +0 -240
- package/lib/provider_network_contract.js +0 -149
- package/lib/provider_payout_execution.js +0 -155
- package/lib/provider_registry.js +0 -852
- package/lib/provider_runtime_journey_contract.js +0 -183
- package/lib/public_surface_policy_contract.js +0 -111
- package/lib/publish_plan.js +0 -51
- package/lib/rate_limiter.js +0 -81
- package/lib/record_queries.js +0 -48
- package/lib/release_candidate.js +0 -50
- package/lib/release_center.js +0 -1093
- package/lib/release_history.js +0 -205
- package/lib/release_manifest.js +0 -113
- package/lib/release_pack.js +0 -453
- package/lib/request_rate_limit.js +0 -144
- package/lib/resource_views.js +0 -31
- package/lib/revenue_launch_path_contract.js +0 -326
- package/lib/route_matchers.js +0 -34
- package/lib/route_params.js +0 -19
- package/lib/runtime_bridge.js +0 -124
- package/lib/runtime_operations_worker.js +0 -282
- package/lib/runtime_state_store.js +0 -495
- package/lib/settlement_bsv_live.js +0 -600
- package/lib/settlement_bsv_teranode.js +0 -117
- package/lib/settlement_lane_contract.js +0 -111
- package/lib/shared_signals_lane_contract.js +0 -54
- package/lib/signed_audit_provenance_contract.js +0 -83
- package/lib/soft_launch_pack.js +0 -78
- package/lib/stablecoin_lane_contract.js +0 -76
- package/lib/stripe_mpp_lane_contract.js +0 -96
- package/lib/stripe_mpp_payment_front.js +0 -258
- package/lib/telemetry_lane_contract.js +0 -54
- package/lib/treasury_destinations_contract.js +0 -764
- package/lib/treasury_egress_policy_contract.js +0 -91
- package/lib/treasury_lane_contract.js +0 -84
- package/lib/trust_portability_boundary_contract.js +0 -72
- package/lib/x402_facilitator.js +0 -198
- package/lib/x402_lane_contract.js +0 -136
- package/lib/x402_settlement_recovery.js +0 -175
- package/scripts/buyer_events_daily_report.js +0 -79
- package/scripts/registry_cli.js +0 -275
- package/scripts/state_backup_offsite.js +0 -196
- package/scripts/verify_a2a_rpc.js +0 -131
- package/scripts/verify_adapters.js +0 -554
- package/scripts/verify_agent_identity.js +0 -153
- package/scripts/verify_all.js +0 -4963
- package/scripts/verify_bolt_adapter.js +0 -128
- package/scripts/verify_buyer_event_log.js +0 -128
- package/scripts/verify_buyer_safety.js +0 -141
- package/scripts/verify_cdp_facilitator.js +0 -110
- package/scripts/verify_checkout_processor.js +0 -109
- package/scripts/verify_commerce_bridge.js +0 -177
- package/scripts/verify_control_plane_contracts.js +0 -34
- package/scripts/verify_control_plane_routes.js +0 -70
- package/scripts/verify_delivery_summary.js +0 -112
- package/scripts/verify_examples.js +0 -19
- package/scripts/verify_execution_dedupe.js +0 -189
- package/scripts/verify_execution_surfacing.js +0 -76
- package/scripts/verify_external_execution_retry.js +0 -140
- package/scripts/verify_final_activation.js +0 -191
- package/scripts/verify_first_run_conversions.js +0 -81
- package/scripts/verify_framework_provider_promotion.js +0 -152
- package/scripts/verify_full_catalog.js +0 -174
- package/scripts/verify_integrations.js +0 -620
- package/scripts/verify_l402_adapter.js +0 -86
- package/scripts/verify_lifecycle_continuity.js +0 -96
- package/scripts/verify_lifecycle_spend_binding.js +0 -88
- package/scripts/verify_market_lane_depth.js +0 -94
- package/scripts/verify_market_lane_promotion.js +0 -103
- package/scripts/verify_market_lane_traces.js +0 -98
- package/scripts/verify_market_lanes.js +0 -102
- package/scripts/verify_mpp_adapter.js +0 -128
- package/scripts/verify_operator_observability_boundary.js +0 -83
- package/scripts/verify_pricing_experiment_plan.js +0 -124
- package/scripts/verify_production_readiness.js +0 -268
- package/scripts/verify_provider_network.js +0 -819
- package/scripts/verify_provider_runtime_breadth.js +0 -109
- package/scripts/verify_provider_runtime_journeys.js +0 -111
- package/scripts/verify_rate_limiter.js +0 -52
- package/scripts/verify_refund_credit.js +0 -106
- package/scripts/verify_revenue_launch_paths.js +0 -105
- package/scripts/verify_runtime_durability.js +0 -52
- package/scripts/verify_runtime_state_guard.js +0 -117
- package/scripts/verify_service.js +0 -15421
- package/scripts/verify_settlement.js +0 -116
- package/scripts/verify_tooling.js +0 -1412
- package/scripts/verify_treasury_public_boundary.js +0 -97
- package/scripts/verify_verify_execute_settle.js +0 -209
- package/scripts/verify_x402_facilitator.js +0 -307
- package/scripts/verify_x402_settlement_recovery.js +0 -153
- package/server.js +0 -12245
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// Offline verification of the xytara client package. Node built-ins only; no network.
|
|
4
|
+
//
|
|
5
|
+
// 1. The package surface: CommerceClient, the receipt verification API, the x402 header helpers,
|
|
6
|
+
// the hosted endpoints and the package version.
|
|
7
|
+
// 2. Signed delivery receipts: a receipt built in the documented canonical form and signed with an
|
|
8
|
+
// Ed25519 key generated here verifies; tampered, stripped, unsigned and wrong-key copies do not.
|
|
9
|
+
// 3. The paid command path against a local stub of the hosted runtime: discover -> 402 challenge
|
|
10
|
+
// with x402 accepts -> pay with an x402 payment header -> 200 with the result, the settlement
|
|
11
|
+
// response and a signed delivery receipt that verifies against the stub's published keys.
|
|
12
|
+
// 4. The CLIs start: `xytara start-here`, `xytara-run --help`, `xytara first-run --json --offline`,
|
|
13
|
+
// and `xytara-run --quote-only` against the stub.
|
|
14
|
+
|
|
15
|
+
const assert = require("assert");
|
|
16
|
+
const crypto = require("crypto");
|
|
17
|
+
const fs = require("fs");
|
|
18
|
+
const http = require("http");
|
|
19
|
+
const path = require("path");
|
|
20
|
+
const { spawn, spawnSync } = require("child_process");
|
|
21
|
+
|
|
22
|
+
const PACKAGE_DIR = path.resolve(__dirname, "..");
|
|
23
|
+
const packageJson = JSON.parse(fs.readFileSync(path.join(PACKAGE_DIR, "package.json"), "utf8"));
|
|
24
|
+
const client = require(path.join(PACKAGE_DIR, "client.js"));
|
|
25
|
+
const {
|
|
26
|
+
CommerceClient,
|
|
27
|
+
RECEIPT_VERSION,
|
|
28
|
+
canonicalize,
|
|
29
|
+
hashCanonical,
|
|
30
|
+
keyRef,
|
|
31
|
+
verifyDeliveryReceipt,
|
|
32
|
+
verificationRecipe,
|
|
33
|
+
encodeX402PaymentHeader,
|
|
34
|
+
decodeX402Header,
|
|
35
|
+
summarizeCommandResultPayload,
|
|
36
|
+
endpoints,
|
|
37
|
+
version
|
|
38
|
+
} = client;
|
|
39
|
+
|
|
40
|
+
const checks = [];
|
|
41
|
+
function check(name, fn) {
|
|
42
|
+
fn();
|
|
43
|
+
checks.push(name);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ---------------------------------------------------------------------------------------------
|
|
47
|
+
// Receipt issuing, as the hosted runtime does it (kept in-test: the client never issues receipts).
|
|
48
|
+
function signReceipt(body, signer) {
|
|
49
|
+
const unsigned = {
|
|
50
|
+
...body,
|
|
51
|
+
v: RECEIPT_VERSION,
|
|
52
|
+
hashing: { algorithm: "sha256", canonicalization: "JCS (RFC 8785)" },
|
|
53
|
+
signing: {
|
|
54
|
+
canonicalization: "JCS (RFC 8785) of the receipt without `sig`",
|
|
55
|
+
suite: [signer.alg],
|
|
56
|
+
kids: [signer.kid],
|
|
57
|
+
policy: "every_listed_signature_must_verify"
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
const signedBytes = Buffer.from(canonicalize(unsigned), "utf8");
|
|
61
|
+
return {
|
|
62
|
+
...unsigned,
|
|
63
|
+
sig: {
|
|
64
|
+
state: "signed",
|
|
65
|
+
signatures: [{ alg: signer.alg, kid: signer.kid, value: crypto.sign(null, signedBytes, signer.privateKey).toString("base64") }]
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function makeSigner() {
|
|
71
|
+
const { privateKey, publicKey } = crypto.generateKeyPairSync("ed25519");
|
|
72
|
+
const kid = keyRef(publicKey);
|
|
73
|
+
return {
|
|
74
|
+
alg: "EdDSA",
|
|
75
|
+
kid,
|
|
76
|
+
privateKey,
|
|
77
|
+
publicKey,
|
|
78
|
+
trustedKey: { kid, alg: "EdDSA", public_key_spki_der_base64: publicKey.export({ type: "spki", format: "der" }).toString("base64") }
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ---------------------------------------------------------------------------------------------
|
|
83
|
+
// A local stub of the hosted runtime's buyer-facing routes (x402 facilitator lane). It plays the
|
|
84
|
+
// facilitator too: any well-formed x402 v2 payment that matches the advertised accepts settles.
|
|
85
|
+
const ACCEPTS = [{
|
|
86
|
+
scheme: "exact",
|
|
87
|
+
network: "eip155:8453",
|
|
88
|
+
asset: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
89
|
+
amount: "10000",
|
|
90
|
+
payTo: "0x" + "ab".repeat(20),
|
|
91
|
+
maxTimeoutSeconds: 60,
|
|
92
|
+
extra: { name: "USD Coin", version: "2" }
|
|
93
|
+
}];
|
|
94
|
+
const PAYER = "0x" + "11".repeat(20);
|
|
95
|
+
const SETTLEMENT_TX = "0x" + "5e".repeat(32);
|
|
96
|
+
|
|
97
|
+
function readJsonBody(request) {
|
|
98
|
+
return new Promise((resolve) => {
|
|
99
|
+
const chunks = [];
|
|
100
|
+
request.on("data", (chunk) => chunks.push(chunk));
|
|
101
|
+
request.on("end", () => {
|
|
102
|
+
try {
|
|
103
|
+
resolve(JSON.parse(Buffer.concat(chunks).toString("utf8") || "{}"));
|
|
104
|
+
} catch (_) {
|
|
105
|
+
resolve({});
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function sendJson(response, status, payload, headers) {
|
|
112
|
+
const text = JSON.stringify(payload);
|
|
113
|
+
response.writeHead(status, { "content-type": "application/json", "content-length": Buffer.byteLength(text), ...(headers || {}) });
|
|
114
|
+
response.end(text);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function startStubRuntime(signer) {
|
|
118
|
+
const quotes = new Map();
|
|
119
|
+
const keys = [signer.trustedKey];
|
|
120
|
+
const toolOutput = (body) => ({ summary: `Summary of: ${body && body.arguments && body.arguments.text ? body.arguments.text : ""}`.slice(0, 120) });
|
|
121
|
+
|
|
122
|
+
const server = http.createServer(async (request, response) => {
|
|
123
|
+
const url = new URL(request.url, "http://127.0.0.1");
|
|
124
|
+
if (request.method === "GET" && url.pathname === "/health") return sendJson(response, 200, { ok: true, status: "ok" });
|
|
125
|
+
if (request.method === "GET" && url.pathname === "/v1/catalog") {
|
|
126
|
+
return sendJson(response, 200, {
|
|
127
|
+
ok: true,
|
|
128
|
+
tasks: [{ task_ref: "adapter.mcp.invoke", category: "mcp", description: "Invoke a tool on the pay-per-call MCP server", price_minor: 1, currency: "USD_CENTS" }],
|
|
129
|
+
workflows: []
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
if (request.method === "GET" && url.pathname === "/.well-known/delivery-receipt-keys") {
|
|
133
|
+
return sendJson(response, 200, {
|
|
134
|
+
ok: true,
|
|
135
|
+
issuer: "xytara-runtime",
|
|
136
|
+
receipt_version: RECEIPT_VERSION,
|
|
137
|
+
signing_state: "signing",
|
|
138
|
+
suite: ["EdDSA"],
|
|
139
|
+
keys,
|
|
140
|
+
verification: verificationRecipe("/.well-known/delivery-receipt-keys")
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
if (request.method === "POST" && url.pathname === "/v1/delivery-receipts/verify") {
|
|
144
|
+
const body = await readJsonBody(request);
|
|
145
|
+
const receipt = body && body.receipt && typeof body.receipt === "object" ? body.receipt : body;
|
|
146
|
+
return sendJson(response, 200, { ok: true, ...verifyDeliveryReceipt(receipt, keys), issuer_expected: "xytara-runtime" });
|
|
147
|
+
}
|
|
148
|
+
if (request.method === "POST" && url.pathname === "/x402/commands/execute") {
|
|
149
|
+
const body = await readJsonBody(request);
|
|
150
|
+
if (!body.account_id || body.task_ref !== "adapter.mcp.invoke") return sendJson(response, 400, { ok: false, reason: "task_not_executable" });
|
|
151
|
+
const header = request.headers["payment-signature"];
|
|
152
|
+
if (!header) {
|
|
153
|
+
const quoteId = `quote_${crypto.randomBytes(6).toString("hex")}`;
|
|
154
|
+
quotes.set(quoteId, body);
|
|
155
|
+
const paymentRequired = {
|
|
156
|
+
x402Version: 2,
|
|
157
|
+
scheme: "exact",
|
|
158
|
+
resource: { url: `http://127.0.0.1:${server.address().port}/x402/commands/execute`, mimeType: "application/json", description: "naxytra runtime paid execution" },
|
|
159
|
+
accepts: ACCEPTS,
|
|
160
|
+
price: ACCEPTS.map((accept) => ({ network: accept.network, asset: accept.asset, amount: accept.amount, usd: "0.01" })),
|
|
161
|
+
quote: { quote_id: quoteId, status: "quoted", task_refs: ["adapter.mcp.invoke"], amount_minor: 1, currency: "USD_CENTS", payment_protocol: "x402", expires_at_iso: new Date(Date.now() + 60000).toISOString() }
|
|
162
|
+
};
|
|
163
|
+
return sendJson(response, 402, { ok: false, payment_required: paymentRequired }, {
|
|
164
|
+
"PAYMENT-REQUIRED": Buffer.from(JSON.stringify(paymentRequired), "utf8").toString("base64url"),
|
|
165
|
+
"WWW-Authenticate": "x402"
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
const payment = decodeX402Header(header);
|
|
169
|
+
if (!payment || Number(payment.x402Version) !== 2 || !payment.accepted || !payment.payload) {
|
|
170
|
+
return sendJson(response, 402, { ok: false, reason: "payment_signature_invalid" });
|
|
171
|
+
}
|
|
172
|
+
const accepted = ACCEPTS.find((entry) => entry.network === payment.accepted.network && entry.asset === payment.accepted.asset && entry.payTo === payment.accepted.payTo && String(entry.amount) === String(payment.accepted.amount));
|
|
173
|
+
if (!accepted) return sendJson(response, 402, { ok: false, reason: "payment_requirements_mismatch" });
|
|
174
|
+
if (!payment.payload.signature || !payment.payload.authorization) return sendJson(response, 402, { ok: false, reason: "payment_verification_failed" });
|
|
175
|
+
if (!quotes.has(body.quote_id)) return sendJson(response, 402, { ok: false, reason: "quote_not_found" });
|
|
176
|
+
const output = toolOutput(body.body);
|
|
177
|
+
const transactionId = `txn_${crypto.randomBytes(6).toString("hex")}`;
|
|
178
|
+
const runtimeReceiptId = `rcpt_${crypto.randomBytes(6).toString("hex")}`;
|
|
179
|
+
const settlement = { success: true, transaction: SETTLEMENT_TX, network: accepted.network, payer: PAYER };
|
|
180
|
+
const deliveryReceipt = signReceipt({
|
|
181
|
+
receipt_id: `drcpt_${runtimeReceiptId.replace(/^rcpt_/, "")}`,
|
|
182
|
+
issuer: "xytara-runtime",
|
|
183
|
+
issued_at: new Date().toISOString(),
|
|
184
|
+
call: { quote_id: body.quote_id, transaction_id: transactionId, runtime_receipt_id: runtimeReceiptId, task_ref: body.task_ref, tool_name: body.body && body.body.tool_name ? body.body.tool_name : null },
|
|
185
|
+
payment: { protocol: "x402", scheme: "exact", network: accepted.network, asset: accepted.asset, amount: accepted.amount, payer: PAYER, pay_to: accepted.payTo, settlement_tx: SETTLEMENT_TX, settled: true },
|
|
186
|
+
delivery: {
|
|
187
|
+
status: "completed",
|
|
188
|
+
provider_id: null,
|
|
189
|
+
routing: "runtime_native",
|
|
190
|
+
execution_target_id: null,
|
|
191
|
+
request_sha256: hashCanonical(body.body || {}),
|
|
192
|
+
request_hash_input: "the request `body` object (e.g. { tool_name, arguments })",
|
|
193
|
+
output_sha256: hashCanonical(output),
|
|
194
|
+
output_hash_input: "command.deliverables[0].output_payload (null when the task returns no payload)",
|
|
195
|
+
completed_at: new Date().toISOString()
|
|
196
|
+
}
|
|
197
|
+
}, signer);
|
|
198
|
+
return sendJson(response, 200, {
|
|
199
|
+
ok: true,
|
|
200
|
+
transaction: { transaction_id: transactionId, quote_id: body.quote_id, status: "completed", payment: { verification_mode: "x402_facilitator" } },
|
|
201
|
+
receipt: { receipt_id: runtimeReceiptId, settlement: { rail: "x402", mode: "x402_facilitator", txid: SETTLEMENT_TX, network: accepted.network, pay_to: accepted.payTo, status: "settled" } },
|
|
202
|
+
command: { deliverables: [{ task_ref: body.task_ref, output_payload: output }] },
|
|
203
|
+
delivery_receipt: deliveryReceipt
|
|
204
|
+
}, { "PAYMENT-RESPONSE": Buffer.from(JSON.stringify(settlement), "utf8").toString("base64") });
|
|
205
|
+
}
|
|
206
|
+
sendJson(response, 404, { ok: false, reason: "not_found" });
|
|
207
|
+
});
|
|
208
|
+
return new Promise((resolve) => server.listen(0, "127.0.0.1", () => resolve({ server, baseUrl: `http://127.0.0.1:${server.address().port}` })));
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// ---------------------------------------------------------------------------------------------
|
|
212
|
+
function runCli(args, options) {
|
|
213
|
+
return spawnSync(process.execPath, args, { cwd: PACKAGE_DIR, encoding: "utf8", ...(options || {}) });
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Non-blocking variant for a CLI that must talk to the stub runtime served by THIS process
|
|
217
|
+
// (spawnSync would block the event loop and deadlock the stub).
|
|
218
|
+
function runCliAsync(args) {
|
|
219
|
+
return new Promise((resolve, reject) => {
|
|
220
|
+
const child = spawn(process.execPath, args, { cwd: PACKAGE_DIR });
|
|
221
|
+
let stdout = "";
|
|
222
|
+
let stderr = "";
|
|
223
|
+
child.stdout.on("data", (chunk) => { stdout += chunk; });
|
|
224
|
+
child.stderr.on("data", (chunk) => { stderr += chunk; });
|
|
225
|
+
child.on("error", reject);
|
|
226
|
+
child.on("close", (status) => resolve({ status, stdout, stderr }));
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
async function main() {
|
|
231
|
+
check("package_surface", () => {
|
|
232
|
+
assert.strictEqual(typeof CommerceClient, "function", "CommerceClient export missing");
|
|
233
|
+
for (const name of ["verifyDeliveryReceipt", "canonicalize", "hashCanonical", "keyRef", "verificationRecipe", "encodeX402PaymentHeader", "decodeX402Header", "summarizeCommandResultPayload", "summarizeCatalogPayload", "summarizeReceiptPayload"]) {
|
|
234
|
+
assert.strictEqual(typeof client[name], "function", `${name} export missing`);
|
|
235
|
+
}
|
|
236
|
+
assert.strictEqual(RECEIPT_VERSION, "naxytra-delivery-receipt/1", "receipt version mismatch");
|
|
237
|
+
assert.strictEqual(endpoints.runtime, "https://xytara.onrender.com", "runtime endpoint mismatch");
|
|
238
|
+
assert.strictEqual(endpoints.mcp, "https://xytara-mcp-executor.onrender.com/mcp", "mcp endpoint mismatch");
|
|
239
|
+
assert.strictEqual(endpoints.website, "https://www.naxytra.com", "website endpoint mismatch");
|
|
240
|
+
assert.strictEqual(version, packageJson.version, "version export must come from package.json");
|
|
241
|
+
assert.strictEqual(require(path.join(PACKAGE_DIR, "client.js")), client, "client entry is a stable singleton");
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
check("canonical_form", () => {
|
|
245
|
+
assert.strictEqual(canonicalize({ b: 1, a: [1, { d: 2, c: 3 }], u: undefined, n: null }), "{\"a\":[1,{\"c\":3,\"d\":2}],\"b\":1,\"n\":null}", "JCS key ordering / undefined handling");
|
|
246
|
+
assert.strictEqual(hashCanonical({ a: 1, b: "x" }), hashCanonical({ b: "x", a: 1 }), "hashCanonical must be key-order independent");
|
|
247
|
+
assert.strictEqual(hashCanonical({ a: 1 }), crypto.createHash("sha256").update("{\"a\":1}").digest("hex"), "hashCanonical is sha256 over JCS");
|
|
248
|
+
const signer = makeSigner();
|
|
249
|
+
assert.ok(/^ed25519-spki-sha256:[0-9a-f]{64}$/.test(signer.kid), "key ref format");
|
|
250
|
+
const recipe = verificationRecipe("/.well-known/delivery-receipt-keys");
|
|
251
|
+
assert.strictEqual(recipe.keys, "/.well-known/delivery-receipt-keys", "recipe keys url");
|
|
252
|
+
assert.strictEqual(recipe.steps.length, 4, "recipe has four steps");
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
const signer = makeSigner();
|
|
256
|
+
const genuine = signReceipt({
|
|
257
|
+
receipt_id: "drcpt_test",
|
|
258
|
+
issuer: "xytara-runtime",
|
|
259
|
+
issued_at: "2026-01-01T00:00:00.000Z",
|
|
260
|
+
call: { quote_id: "quote_test", transaction_id: "txn_test", runtime_receipt_id: "rcpt_test", task_ref: "adapter.mcp.invoke", tool_name: "summarize" },
|
|
261
|
+
payment: { protocol: "x402", scheme: "exact", network: "eip155:8453", asset: ACCEPTS[0].asset, amount: "10000", payer: PAYER, pay_to: ACCEPTS[0].payTo, settlement_tx: SETTLEMENT_TX, settled: true },
|
|
262
|
+
delivery: { status: "completed", routing: "runtime_native", request_sha256: hashCanonical({ tool_name: "summarize", arguments: { text: "hi" } }), output_sha256: hashCanonical({ summary: "hi" }), completed_at: "2026-01-01T00:00:01.000Z" }
|
|
263
|
+
}, signer);
|
|
264
|
+
|
|
265
|
+
check("receipt_offline_verification", () => {
|
|
266
|
+
const result = verifyDeliveryReceipt(genuine, [signer.trustedKey]);
|
|
267
|
+
assert.strictEqual(result.valid, true, `genuine receipt must verify: ${JSON.stringify(result)}`);
|
|
268
|
+
assert.strictEqual(result.signatures[0].alg, "EdDSA", "signature algorithm");
|
|
269
|
+
assert.strictEqual(result.signed_bytes_sha256.length, 64, "signed bytes hash reported");
|
|
270
|
+
const reordered = JSON.parse(JSON.stringify({ sig: genuine.sig, delivery: genuine.delivery, ...genuine }));
|
|
271
|
+
assert.strictEqual(verifyDeliveryReceipt(reordered, [signer.trustedKey]).valid, true, "key order must not matter (JCS)");
|
|
272
|
+
const tampered = JSON.parse(JSON.stringify(genuine));
|
|
273
|
+
tampered.payment.amount = "1";
|
|
274
|
+
assert.strictEqual(verifyDeliveryReceipt(tampered, [signer.trustedKey]).valid, false, "tampered receipt must fail");
|
|
275
|
+
const stripped = JSON.parse(JSON.stringify(genuine));
|
|
276
|
+
stripped.sig.signatures = [];
|
|
277
|
+
assert.strictEqual(verifyDeliveryReceipt(stripped, [signer.trustedKey]).signatures[0].reason, "signature_missing_for_signed_suite", "stripped signature must fail");
|
|
278
|
+
const other = makeSigner();
|
|
279
|
+
assert.strictEqual(verifyDeliveryReceipt(genuine, [other.trustedKey]).signatures[0].reason, "kid_not_in_trusted_key_set", "untrusted kid must fail");
|
|
280
|
+
const unsigned = { ...genuine, sig: { state: "unsigned_signing_key_not_configured", signatures: [] } };
|
|
281
|
+
assert.strictEqual(verifyDeliveryReceipt(unsigned, [signer.trustedKey]).reason, "receipt_unsigned", "unsigned receipt must fail");
|
|
282
|
+
assert.strictEqual(verifyDeliveryReceipt({ ...genuine, v: "other/1" }, [signer.trustedKey]).reason, "unsupported_receipt_version", "unknown version must fail");
|
|
283
|
+
assert.strictEqual(verifyDeliveryReceipt(null, [signer.trustedKey]).reason, "receipt_required", "missing receipt must fail");
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
check("x402_header_helpers", () => {
|
|
287
|
+
const authorization = { from: PAYER, to: ACCEPTS[0].payTo, value: ACCEPTS[0].amount, validAfter: "0", validBefore: "9999999999", nonce: "0x" + "00".repeat(32) };
|
|
288
|
+
const header = encodeX402PaymentHeader({ accepted: ACCEPTS[0], authorization, signature: "0x" + "11".repeat(65) });
|
|
289
|
+
const decoded = decodeX402Header(header);
|
|
290
|
+
assert.strictEqual(decoded.x402Version, 2, "x402 v2");
|
|
291
|
+
assert.deepStrictEqual(decoded.accepted, ACCEPTS[0], "accepted roundtrip");
|
|
292
|
+
assert.deepStrictEqual(decoded.payload.authorization, authorization, "authorization roundtrip");
|
|
293
|
+
assert.strictEqual(decodeX402Header(Buffer.from("{\"a\":1}").toString("base64url")).a, 1, "base64url decodes too");
|
|
294
|
+
assert.strictEqual(decodeX402Header("not base64 json"), null, "garbage decodes to null");
|
|
295
|
+
assert.throws(() => encodeX402PaymentHeader({ accepted: ACCEPTS[0], authorization }), /signature/, "signature required");
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
const issuer = makeSigner();
|
|
299
|
+
const { server, baseUrl } = await startStubRuntime(issuer);
|
|
300
|
+
let paidResult = null;
|
|
301
|
+
try {
|
|
302
|
+
const runtime = new CommerceClient({ baseUrl, accountId: "acct_client_check" });
|
|
303
|
+
const payload = { command: "mcp.invoke:summarize", task_ref: "adapter.mcp.invoke", body: { tool_name: "summarize", arguments: { text: "Summarize the naxytra first-run path in one sentence." } } };
|
|
304
|
+
|
|
305
|
+
await (async () => {
|
|
306
|
+
const health = await runtime.getHealth();
|
|
307
|
+
assert.strictEqual(health.ok, true, "health");
|
|
308
|
+
const task = await runtime.getTask("adapter.mcp.invoke");
|
|
309
|
+
assert.ok(task && task.task_ref === "adapter.mcp.invoke", "catalog discovery");
|
|
310
|
+
const tasks = await runtime.listTasks();
|
|
311
|
+
assert.strictEqual(tasks.length, 1, "listTasks");
|
|
312
|
+
checks.push("runtime_discover");
|
|
313
|
+
})();
|
|
314
|
+
|
|
315
|
+
const quote = await runtime.previewExecutionQuote(payload, { instant: true });
|
|
316
|
+
assert.strictEqual(quote.payment_required, true, "quote preview must report payment_required");
|
|
317
|
+
assert.strictEqual(quote.x402Version, 2, "challenge is x402 v2");
|
|
318
|
+
assert.strictEqual(quote.accepts[0].amount, "10000", "challenge advertises the price");
|
|
319
|
+
assert.ok(quote.quote && quote.quote.quote_id, "challenge carries a quote id");
|
|
320
|
+
const raw = await runtime.postJson("/x402/commands/execute", {}, { account_id: "acct_client_check", ...payload });
|
|
321
|
+
assert.strictEqual(raw.status, 402, "raw challenge status");
|
|
322
|
+
assert.strictEqual(decodeX402Header(raw.headers.get("payment-required")).accepts[0].payTo, ACCEPTS[0].payTo, "PAYMENT-REQUIRED header decodes");
|
|
323
|
+
checks.push("runtime_402_challenge");
|
|
324
|
+
|
|
325
|
+
const accepted = quote.accepts[0];
|
|
326
|
+
const authorization = { from: PAYER, to: accepted.payTo, value: accepted.amount, validAfter: "0", validBefore: String(Math.floor(Date.now() / 1000) + 600), nonce: "0x" + crypto.randomBytes(32).toString("hex") };
|
|
327
|
+
const paymentHeader = encodeX402PaymentHeader({ accepted, authorization, signature: "0x" + crypto.randomBytes(65).toString("hex") });
|
|
328
|
+
const paid = await runtime.postJson("/x402/commands/execute", { "payment-signature": paymentHeader }, { account_id: "acct_client_check", ...payload, quote_id: quote.quote.quote_id });
|
|
329
|
+
assert.strictEqual(paid.status, 200, `paid call must succeed: ${JSON.stringify(paid.data)}`);
|
|
330
|
+
paidResult = paid.data;
|
|
331
|
+
assert.strictEqual(paid.data.transaction.status, "completed", "transaction completed");
|
|
332
|
+
assert.strictEqual(paid.data.command.deliverables[0].output_payload.summary.startsWith("Summary of:"), true, "deliverable returned");
|
|
333
|
+
const settlement = decodeX402Header(paid.headers.get("payment-response"));
|
|
334
|
+
assert.strictEqual(settlement.success, true, "PAYMENT-RESPONSE decodes to the settlement");
|
|
335
|
+
assert.strictEqual(settlement.transaction, SETTLEMENT_TX, "settlement tx");
|
|
336
|
+
const summary = summarizeCommandResultPayload(paid.data);
|
|
337
|
+
assert.ok(summary && typeof summary === "object", "summarizeCommandResultPayload");
|
|
338
|
+
checks.push("runtime_paid_execute");
|
|
339
|
+
|
|
340
|
+
const mismatched = await runtime.postJson("/x402/commands/execute", { "payment-signature": encodeX402PaymentHeader({ accepted: { ...accepted, amount: "1" }, authorization, signature: "0x00" }) }, { account_id: "acct_client_check", ...payload, quote_id: quote.quote.quote_id });
|
|
341
|
+
assert.strictEqual(mismatched.status, 402, "underpayment rejected");
|
|
342
|
+
assert.strictEqual(mismatched.data.reason, "payment_requirements_mismatch", "underpayment reason");
|
|
343
|
+
checks.push("runtime_underpayment_rejected");
|
|
344
|
+
|
|
345
|
+
const receipt = paid.data.delivery_receipt;
|
|
346
|
+
assert.strictEqual(receipt.v, RECEIPT_VERSION, "receipt version");
|
|
347
|
+
assert.strictEqual(receipt.payment.settlement_tx, SETTLEMENT_TX, "receipt binds the settlement tx");
|
|
348
|
+
assert.strictEqual(receipt.call.quote_id, quote.quote.quote_id, "receipt binds the quote");
|
|
349
|
+
assert.strictEqual(receipt.delivery.request_sha256, hashCanonical(payload.body), "request hash recomputes from the request body");
|
|
350
|
+
assert.strictEqual(receipt.delivery.output_sha256, hashCanonical(paid.data.command.deliverables[0].output_payload), "output hash recomputes from the deliverable");
|
|
351
|
+
const keys = await runtime.getJson("/.well-known/delivery-receipt-keys");
|
|
352
|
+
assert.strictEqual(keys.data.signing_state, "signing", "keys endpoint");
|
|
353
|
+
assert.strictEqual(verifyDeliveryReceipt(receipt, keys.data.keys).valid, true, "receipt verifies against the published keys");
|
|
354
|
+
const tamperedReceipt = JSON.parse(JSON.stringify(receipt));
|
|
355
|
+
tamperedReceipt.delivery.output_sha256 = hashCanonical({ summary: "something else" });
|
|
356
|
+
assert.strictEqual(verifyDeliveryReceipt(tamperedReceipt, keys.data.keys).valid, false, "tampered receipt rejected offline");
|
|
357
|
+
const remote = await runtime.postJson("/v1/delivery-receipts/verify", {}, { receipt });
|
|
358
|
+
assert.strictEqual(remote.data.valid, true, "remote verify endpoint accepts the receipt");
|
|
359
|
+
assert.strictEqual((await runtime.postJson("/v1/delivery-receipts/verify", {}, { receipt: tamperedReceipt })).data.valid, false, "remote verify rejects the tampered receipt");
|
|
360
|
+
checks.push("runtime_receipt_verification");
|
|
361
|
+
|
|
362
|
+
const quoteOnly = await runCliAsync([path.join(PACKAGE_DIR, "bin", "xytara-run.js"), "--url", baseUrl, "--account", "acct_client_check", "--task", "adapter.mcp.invoke", "--command", "mcp.invoke:summarize", "--body-json", JSON.stringify(payload.body), "--quote-only"]);
|
|
363
|
+
assert.strictEqual(quoteOnly.status, 0, `xytara-run --quote-only failed: ${quoteOnly.stderr}`);
|
|
364
|
+
const quoteOutput = JSON.parse(quoteOnly.stdout);
|
|
365
|
+
assert.strictEqual(quoteOutput.payment_required.accepts[0].amount, "10000", "xytara-run --quote-only returns the challenge");
|
|
366
|
+
checks.push("cli_run_quote_only");
|
|
367
|
+
} finally {
|
|
368
|
+
server.close();
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
check("cli_start_here", () => {
|
|
372
|
+
const result = runCli([path.join(PACKAGE_DIR, "bin", "xytara.js"), "start-here"]);
|
|
373
|
+
assert.strictEqual(result.status, 0, `xytara start-here failed: ${result.stderr}`);
|
|
374
|
+
assert.ok(result.stdout.includes("quote -> pay/credits -> execute -> prove"), "start-here spine line");
|
|
375
|
+
assert.ok(result.stdout.includes("xytara"), "start-here names the package");
|
|
376
|
+
assert.ok(result.stdout.includes("https://www.naxytra.com"), "start-here links the website");
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
check("cli_run_help", () => {
|
|
380
|
+
const result = runCli([path.join(PACKAGE_DIR, "bin", "xytara-run.js"), "--help"]);
|
|
381
|
+
assert.strictEqual(result.status, 0, "xytara-run --help failed");
|
|
382
|
+
assert.ok(result.stdout.includes("Usage: xytara-run"), "xytara-run usage text");
|
|
383
|
+
const viaDispatcher = runCli([path.join(PACKAGE_DIR, "bin", "xytara.js"), "run", "--help"]);
|
|
384
|
+
assert.strictEqual(viaDispatcher.status, 0, "xytara run --help failed");
|
|
385
|
+
assert.ok(viaDispatcher.stdout.includes("Usage: xytara-run"), "xytara run --help usage text");
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
check("cli_first_run_offline", () => {
|
|
389
|
+
const result = runCli([path.join(PACKAGE_DIR, "bin", "xytara.js"), "first-run", "--json", "--offline"]);
|
|
390
|
+
assert.strictEqual(result.status, 0, `xytara first-run --json --offline failed: ${result.stderr}`);
|
|
391
|
+
const guide = JSON.parse(result.stdout);
|
|
392
|
+
assert.strictEqual(guide.ok, true, "first-run guide ok");
|
|
393
|
+
assert.strictEqual(guide.product, "xytara", "first-run guide product");
|
|
394
|
+
assert.ok(String(guide.safe_quote_command).includes("--quote-only"), "first-run safe command is quote-only");
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
if (!fs.existsSync(path.join(PACKAGE_DIR, "bin", "xytara-release.js"))) {
|
|
398
|
+
check("cli_release_not_shipped", () => {
|
|
399
|
+
const result = runCli([path.join(PACKAGE_DIR, "bin", "xytara.js"), "release", "--summary"]);
|
|
400
|
+
assert.strictEqual(result.status, 2, "xytara release must exit 2 in the client package");
|
|
401
|
+
assert.ok(result.stderr.includes("not part of the xytara client package"), "xytara release notice");
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
console.log(JSON.stringify({
|
|
406
|
+
ok: true,
|
|
407
|
+
product: "xytara",
|
|
408
|
+
category: "xytara-client-verification",
|
|
409
|
+
version,
|
|
410
|
+
checks,
|
|
411
|
+
paid_receipt_id: paidResult && paidResult.delivery_receipt ? paidResult.delivery_receipt.receipt_id : null
|
|
412
|
+
}));
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
main().catch((error) => {
|
|
416
|
+
process.stderr.write(`${error && error.stack ? error.stack : error}\n`);
|
|
417
|
+
process.exitCode = 1;
|
|
418
|
+
});
|
|
@@ -1,23 +1,84 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
+
// Release-candidate gate for the xytara CLIENT package. Node built-ins only, so it runs both in
|
|
4
|
+
// the private repository checkout (prepublishOnly) and inside an installed node_modules/xytara.
|
|
5
|
+
//
|
|
6
|
+
// In-repo mode (server.js present next to package.json): the npm tarball must contain EXACTLY the
|
|
7
|
+
// thin client allowlist below — any extra file fails the gate — and package.json must point at
|
|
8
|
+
// the client entry and the client bins.
|
|
9
|
+
// Installed mode (no server.js): the installed package must carry nothing from the runtime side.
|
|
10
|
+
// Both modes run scripts/verify_client.js (offline client tests) and check RELEASE_NOTES.md.
|
|
11
|
+
|
|
3
12
|
const assert = require("assert");
|
|
4
13
|
const fs = require("fs");
|
|
5
14
|
const path = require("path");
|
|
6
|
-
const {
|
|
7
|
-
const packageJson = require("../package.json");
|
|
15
|
+
const { spawnSync } = require("child_process");
|
|
8
16
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
17
|
+
const PACKAGE_DIR = path.resolve(__dirname, "..");
|
|
18
|
+
const packageJson = JSON.parse(fs.readFileSync(path.join(PACKAGE_DIR, "package.json"), "utf8"));
|
|
19
|
+
|
|
20
|
+
// The complete published surface (package.json is always added by npm).
|
|
21
|
+
const SHIPPED_FILES = [
|
|
22
|
+
"client.js",
|
|
23
|
+
"lib/commerce_client.js",
|
|
24
|
+
"lib/delivery_receipt_verify.js",
|
|
25
|
+
"bin/xytara.js",
|
|
26
|
+
"bin/xytara-run.js",
|
|
27
|
+
"bin/xytara-first-run.js",
|
|
28
|
+
"scripts/verify_release_candidate.js",
|
|
29
|
+
"scripts/verify_client.js",
|
|
30
|
+
"README.md",
|
|
31
|
+
"START_HERE.md",
|
|
32
|
+
"RELEASE_NOTES.md",
|
|
33
|
+
"LICENSE",
|
|
34
|
+
"NOTICE"
|
|
35
|
+
];
|
|
36
|
+
const TARBALL_FILES = ["package.json", ...SHIPPED_FILES];
|
|
37
|
+
const SHIPPED_BINS = { xytara: "bin/xytara.js", "xytara-run": "bin/xytara-run.js" };
|
|
38
|
+
|
|
39
|
+
// Nothing from the hosted service may be present in an installed copy.
|
|
40
|
+
const FORBIDDEN_INSTALLED_PATHS = [
|
|
41
|
+
"server.js",
|
|
42
|
+
"index.js",
|
|
43
|
+
".env.example",
|
|
44
|
+
"bin/xytara-release.js",
|
|
45
|
+
"fixtures",
|
|
46
|
+
"adapters",
|
|
47
|
+
"integrations",
|
|
48
|
+
"services",
|
|
49
|
+
"examples"
|
|
50
|
+
];
|
|
51
|
+
const ALLOWED_DIR_CONTENTS = {
|
|
52
|
+
lib: ["commerce_client.js", "delivery_receipt_verify.js"],
|
|
53
|
+
bin: ["xytara.js", "xytara-run.js", "xytara-first-run.js"],
|
|
54
|
+
scripts: ["verify_release_candidate.js", "verify_client.js"]
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
function sorted(list) {
|
|
58
|
+
return list.slice().sort();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function isRepoCheckout() {
|
|
62
|
+
return fs.existsSync(path.join(PACKAGE_DIR, "server.js"));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function verifyManifest() {
|
|
66
|
+
assert.strictEqual(packageJson.name, "xytara", "package name mismatch");
|
|
67
|
+
assert.strictEqual(packageJson.main, "client.js", "package.json main must be the client entry (client.js)");
|
|
68
|
+
assert.deepStrictEqual(packageJson.bin, SHIPPED_BINS, "package.json bin must be exactly xytara and xytara-run");
|
|
69
|
+
assert.deepStrictEqual(sorted(packageJson.files || []), sorted(SHIPPED_FILES), "package.json files must equal the thin client allowlist");
|
|
70
|
+
assert.strictEqual(packageJson.license, "Apache-2.0", "client package license must stay Apache-2.0");
|
|
71
|
+
assert.strictEqual(packageJson.publishConfig && packageJson.publishConfig.access, "public", "publishConfig.access must be public");
|
|
72
|
+
assert.strictEqual(packageJson.scripts && packageJson.scripts.prepublishOnly, "npm run verify:release-candidate", "prepublishOnly must run this gate");
|
|
73
|
+
assert.strictEqual(packageJson.exports && packageJson.exports["."], "./client.js", "exports['.'] must resolve to client.js");
|
|
74
|
+
assert.strictEqual(packageJson.exports && packageJson.exports["./package.json"], "./package.json", "exports must keep ./package.json reachable");
|
|
75
|
+
for (const relative of SHIPPED_FILES) {
|
|
76
|
+
assert.strictEqual(fs.existsSync(path.join(PACKAGE_DIR, relative)), true, `shipped file missing on disk: ${relative}`);
|
|
77
|
+
}
|
|
16
78
|
}
|
|
17
79
|
|
|
18
80
|
function verifyReleaseNotes() {
|
|
19
|
-
const
|
|
20
|
-
const notes = fs.readFileSync(notesPath, "utf8");
|
|
81
|
+
const notes = fs.readFileSync(path.join(PACKAGE_DIR, "RELEASE_NOTES.md"), "utf8");
|
|
21
82
|
assert.strictEqual(
|
|
22
83
|
/^#\s+xytara\s+\d+\.\d+\.\d+\s+Release Notes\s*$/m.test(notes),
|
|
23
84
|
false,
|
|
@@ -26,7 +87,7 @@ function verifyReleaseNotes() {
|
|
|
26
87
|
assert.strictEqual(
|
|
27
88
|
notes.includes(`xytara\` ${packageJson.version}`),
|
|
28
89
|
true,
|
|
29
|
-
|
|
90
|
+
`RELEASE_NOTES.md must mention the current package version (${packageJson.version})`
|
|
30
91
|
);
|
|
31
92
|
const mentionedVersions = notes.match(/\b\d+\.\d+\.\d+\b/g) || [];
|
|
32
93
|
assert.strictEqual(
|
|
@@ -36,46 +97,86 @@ function verifyReleaseNotes() {
|
|
|
36
97
|
);
|
|
37
98
|
}
|
|
38
99
|
|
|
39
|
-
function
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
"
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
cwd: path.resolve(__dirname, ".."),
|
|
46
|
-
encoding: "utf8"
|
|
100
|
+
function parsePackDryRun() {
|
|
101
|
+
const result = spawnSync("npm pack --dry-run --json", {
|
|
102
|
+
cwd: PACKAGE_DIR,
|
|
103
|
+
encoding: "utf8",
|
|
104
|
+
shell: true,
|
|
105
|
+
maxBuffer: 16 * 1024 * 1024
|
|
47
106
|
});
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
verifyReleaseNotes();
|
|
107
|
+
assert.strictEqual(result.status, 0, `npm pack --dry-run failed: ${result.stderr || result.stdout}`);
|
|
108
|
+
const parsed = JSON.parse(result.stdout);
|
|
109
|
+
return Array.isArray(parsed) ? parsed[0] : parsed;
|
|
110
|
+
}
|
|
53
111
|
|
|
112
|
+
function verifyTarball() {
|
|
54
113
|
const dryRun = parsePackDryRun();
|
|
55
114
|
assert.strictEqual(dryRun.name, packageJson.name, "npm pack dry-run name mismatch");
|
|
56
115
|
assert.strictEqual(dryRun.version, packageJson.version, "npm pack dry-run version mismatch");
|
|
57
116
|
assert.strictEqual(Array.isArray(dryRun.files), true, "npm pack dry-run file list missing");
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
assert.
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
117
|
+
const packed = sorted(dryRun.files.map((entry) => String(entry.path).replace(/\\/g, "/")));
|
|
118
|
+
const expected = sorted(TARBALL_FILES);
|
|
119
|
+
const extra = packed.filter((file) => !expected.includes(file));
|
|
120
|
+
const missing = expected.filter((file) => !packed.includes(file));
|
|
121
|
+
assert.deepStrictEqual(
|
|
122
|
+
{ extra, missing },
|
|
123
|
+
{ extra: [], missing: [] },
|
|
124
|
+
`npm tarball must contain exactly the thin client files (extra: ${JSON.stringify(extra)}, missing: ${JSON.stringify(missing)})`
|
|
125
|
+
);
|
|
126
|
+
return packed;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function verifyInstalledLayout() {
|
|
130
|
+
for (const relative of FORBIDDEN_INSTALLED_PATHS) {
|
|
131
|
+
assert.strictEqual(fs.existsSync(path.join(PACKAGE_DIR, relative)), false, `installed client package must not contain ${relative}`);
|
|
132
|
+
}
|
|
133
|
+
for (const [dir, allowed] of Object.entries(ALLOWED_DIR_CONTENTS)) {
|
|
134
|
+
const entries = fs.readdirSync(path.join(PACKAGE_DIR, dir));
|
|
135
|
+
const unexpected = entries.filter((entry) => !allowed.includes(entry));
|
|
136
|
+
assert.deepStrictEqual(unexpected, [], `installed client package carries unexpected files in ${dir}/: ${JSON.stringify(unexpected)}`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
77
139
|
|
|
78
|
-
|
|
140
|
+
function runClientVerification() {
|
|
141
|
+
const result = spawnSync(process.execPath, [path.join(__dirname, "verify_client.js")], {
|
|
142
|
+
cwd: PACKAGE_DIR,
|
|
143
|
+
stdio: ["ignore", "pipe", "inherit"],
|
|
144
|
+
encoding: "utf8"
|
|
145
|
+
});
|
|
146
|
+
assert.strictEqual(result.status, 0, "scripts/verify_client.js failed");
|
|
147
|
+
const lastLine = String(result.stdout || "").trim().split(/\r?\n/).pop();
|
|
148
|
+
let summary = null;
|
|
149
|
+
try {
|
|
150
|
+
summary = JSON.parse(lastLine);
|
|
151
|
+
} catch (_) {
|
|
152
|
+
summary = null;
|
|
153
|
+
}
|
|
154
|
+
assert.ok(summary && summary.ok === true, "verify_client.js did not report ok");
|
|
155
|
+
return summary;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function main() {
|
|
159
|
+
const mode = isRepoCheckout() ? "repo" : "installed";
|
|
160
|
+
verifyManifest();
|
|
161
|
+
verifyReleaseNotes();
|
|
162
|
+
let tarballFiles = null;
|
|
163
|
+
if (mode === "repo") {
|
|
164
|
+
tarballFiles = verifyTarball();
|
|
165
|
+
} else {
|
|
166
|
+
verifyInstalledLayout();
|
|
167
|
+
}
|
|
168
|
+
const clientSummary = runClientVerification();
|
|
169
|
+
console.log(JSON.stringify({
|
|
170
|
+
ok: true,
|
|
171
|
+
product: "xytara",
|
|
172
|
+
category: "xytara-release-candidate-verification",
|
|
173
|
+
mode,
|
|
174
|
+
version: packageJson.version,
|
|
175
|
+
main: packageJson.main,
|
|
176
|
+
bins: Object.keys(packageJson.bin),
|
|
177
|
+
tarball_file_count: tarballFiles ? tarballFiles.length : null,
|
|
178
|
+
client_checks: clientSummary.checks
|
|
179
|
+
}));
|
|
79
180
|
}
|
|
80
181
|
|
|
81
182
|
main();
|