v0.2.7-pre.003
This commit is contained in:
14
.env.example
14
.env.example
@@ -1,5 +1,5 @@
|
|||||||
# file: .env.example
|
# file: .env.example
|
||||||
# version: 4
|
# version: 5
|
||||||
|
|
||||||
# KSP Logging root directory. Used by config/std.logging.json for relative log output paths.
|
# KSP Logging root directory. Used by config/std.logging.json for relative log output paths.
|
||||||
# The current Config document fallback is "logs" when neither the process environment nor .env defines this variable.
|
# The current Config document fallback is "logs" when neither the process environment nor .env defines this variable.
|
||||||
@@ -22,10 +22,22 @@ KSP_PUBLIC_SOLANA_DEVNET_HTTP_URL=https://api.devnet.solana.com
|
|||||||
# The committed Transport document falls back to https://api.mainnet-beta.solana.com when this variable is absent.
|
# The committed Transport document falls back to https://api.mainnet-beta.solana.com when this variable is absent.
|
||||||
KSP_PUBLIC_SOLANA_MAINNET_HTTP_URL=https://api.mainnet-beta.solana.com
|
KSP_PUBLIC_SOLANA_MAINNET_HTTP_URL=https://api.mainnet-beta.solana.com
|
||||||
|
|
||||||
|
# Optional public Solana Devnet WebSocket endpoint override used by config/std.transport.json.
|
||||||
|
# The committed Transport document falls back to wss://api.devnet.solana.com when this variable is absent.
|
||||||
|
KSP_PUBLIC_SOLANA_DEVNET_WS_URL=wss://api.devnet.solana.com
|
||||||
|
|
||||||
|
# Optional public Solana Mainnet WebSocket endpoint override used by config/std.transport.json and its example.
|
||||||
|
# The committed Transport document falls back to wss://api.mainnet-beta.solana.com when this variable is absent.
|
||||||
|
KSP_PUBLIC_SOLANA_MAINNET_WS_URL=wss://api.mainnet-beta.solana.com
|
||||||
|
|
||||||
# Optional complete private-provider HTTP endpoint URL used only by the Transport example when explicitly selected.
|
# Optional complete private-provider HTTP endpoint URL used only by the Transport example when explicitly selected.
|
||||||
# Keep provider credentials in a KSP_SECRET_* variable; do not copy a real credential-bearing URL into committed JSON.
|
# Keep provider credentials in a KSP_SECRET_* variable; do not copy a real credential-bearing URL into committed JSON.
|
||||||
# KSP_SECRET_SOLANA_HTTP_URL=https://provider.example/?api-key=replace-me
|
# KSP_SECRET_SOLANA_HTTP_URL=https://provider.example/?api-key=replace-me
|
||||||
|
|
||||||
|
# Optional complete private-provider WebSocket endpoint URL used only by the Transport example when explicitly selected.
|
||||||
|
# Keep provider credentials in a KSP_SECRET_* variable; do not copy a real credential-bearing URL into committed JSON.
|
||||||
|
# KSP_SECRET_SOLANA_WS_URL=wss://provider.example/?api-key=replace-me
|
||||||
|
|
||||||
# Fade-in duration in milliseconds used by the common KSP desk splash lifecycle.
|
# Fade-in duration in milliseconds used by the common KSP desk splash lifecycle.
|
||||||
KSP_DESK_SPLASH_FADE_IN_MS=300
|
KSP_DESK_SPLASH_FADE_IN_MS=300
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
# file: Cargo.toml
|
# file: Cargo.toml
|
||||||
# version: 194
|
# version: 195
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
resolver = "3"
|
resolver = "3"
|
||||||
members = ["crates/ksp-app-config-desk", "crates/ksp-app-wallet-desk", "crates/ksp-config-lib", "crates/ksp-core-lib", "crates/ksp-logging-lib", "crates/ksp-onchain-transport-lib", "crates/ksp-wallet-lib"]
|
members = ["crates/ksp-app-config-desk", "crates/ksp-app-wallet-desk", "crates/ksp-config-lib", "crates/ksp-core-lib", "crates/ksp-logging-lib", "crates/ksp-onchain-transport-lib", "crates/ksp-wallet-lib"]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.2.7-pre.2.fix.1"
|
version = "0.2.7-pre.3"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project"
|
repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project"
|
||||||
|
|||||||
@@ -1,10 +1,27 @@
|
|||||||
{
|
{
|
||||||
"format_version": 1,
|
"format_version": 2,
|
||||||
"retry": {
|
"retry": {
|
||||||
"max_retries": 3,
|
"max_retries": 3,
|
||||||
"initial_backoff_ms": 150,
|
"initial_backoff_ms": 150,
|
||||||
"max_backoff_ms": 3000
|
"max_backoff_ms": 3000
|
||||||
},
|
},
|
||||||
|
"ws_defaults": {
|
||||||
|
"command_timeout_ms": 10000,
|
||||||
|
"close_timeout_ms": 5000,
|
||||||
|
"reconnect": {
|
||||||
|
"max_retries": 5,
|
||||||
|
"initial_backoff_ms": 250,
|
||||||
|
"max_backoff_ms": 5000
|
||||||
|
},
|
||||||
|
"resubscribe": "active_subscriptions",
|
||||||
|
"command_queue_capacity": 128,
|
||||||
|
"notification_queue_capacity": 256,
|
||||||
|
"max_active_subscriptions": 1024,
|
||||||
|
"max_pending_requests": 128,
|
||||||
|
"max_message_size_bytes": 67108864,
|
||||||
|
"max_frame_size_bytes": 16777216,
|
||||||
|
"max_write_buffer_size_bytes": 1048576
|
||||||
|
},
|
||||||
"default_profile": "mainnet_mixed",
|
"default_profile": "mainnet_mixed",
|
||||||
"profiles": [
|
"profiles": [
|
||||||
{
|
{
|
||||||
@@ -23,7 +40,9 @@
|
|||||||
{
|
{
|
||||||
"role": "default",
|
"role": "default",
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"request_kinds": ["*"],
|
"request_kinds": [
|
||||||
|
"*"
|
||||||
|
],
|
||||||
"priority": 200,
|
"priority": 200,
|
||||||
"limits": {
|
"limits": {
|
||||||
"requests_per_second": 5,
|
"requests_per_second": 5,
|
||||||
@@ -47,7 +66,9 @@
|
|||||||
{
|
{
|
||||||
"role": "default",
|
"role": "default",
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"request_kinds": ["*"],
|
"request_kinds": [
|
||||||
|
"*"
|
||||||
|
],
|
||||||
"priority": 100,
|
"priority": 100,
|
||||||
"limits": {
|
"limits": {
|
||||||
"requests_per_second": 20,
|
"requests_per_second": 20,
|
||||||
@@ -58,6 +79,28 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"ws_endpoints": [
|
||||||
|
{
|
||||||
|
"name": "mainnet_public_ws",
|
||||||
|
"enabled": true,
|
||||||
|
"provider": "solana-public",
|
||||||
|
"cluster": "mainnet-beta",
|
||||||
|
"kind": "solana_standard",
|
||||||
|
"url": "${KSP_PUBLIC_SOLANA_MAINNET_WS_URL:-wss://api.mainnet-beta.solana.com}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "mainnet_private_ws",
|
||||||
|
"enabled": true,
|
||||||
|
"provider": "private-provider",
|
||||||
|
"cluster": "mainnet-beta",
|
||||||
|
"kind": "solana_standard",
|
||||||
|
"url": "${KSP_SECRET_SOLANA_WS_URL:-wss://example.invalid}",
|
||||||
|
"session": {
|
||||||
|
"notification_queue_capacity": 512,
|
||||||
|
"max_active_subscriptions": 2048
|
||||||
|
}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,20 +1,15 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
"$id": "urn:ksp:schema:std.transport:v1",
|
"$id": "urn:ksp:schema:std.transport:v2",
|
||||||
"title": "KSP standard HTTP Transport configuration",
|
"title": "KSP standard HTTP + WebSocket Transport configuration",
|
||||||
"type": "object",
|
"oneOf": [
|
||||||
"additionalProperties": false,
|
{
|
||||||
"required": ["format_version", "retry", "default_profile", "profiles"],
|
"$ref": "#/$defs/documentV1"
|
||||||
"properties": {
|
|
||||||
"format_version": {"const": 1},
|
|
||||||
"retry": {"$ref": "#/$defs/retry"},
|
|
||||||
"default_profile": {"$ref": "#/$defs/profileId"},
|
|
||||||
"profiles": {
|
|
||||||
"type": "array",
|
|
||||||
"minItems": 1,
|
|
||||||
"items": {"$ref": "#/$defs/profile"}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"$ref": "#/$defs/documentV2"
|
||||||
|
}
|
||||||
|
],
|
||||||
"$defs": {
|
"$defs": {
|
||||||
"profileId": {
|
"profileId": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@@ -35,50 +30,99 @@
|
|||||||
"minimum": 1,
|
"minimum": 1,
|
||||||
"maximum": 4294967295
|
"maximum": 4294967295
|
||||||
},
|
},
|
||||||
|
"positiveUsize": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 1,
|
||||||
|
"maximum": 4294967295
|
||||||
|
},
|
||||||
"retry": {
|
"retry": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"required": ["max_retries", "initial_backoff_ms", "max_backoff_ms"],
|
"required": [
|
||||||
|
"max_retries",
|
||||||
|
"initial_backoff_ms",
|
||||||
|
"max_backoff_ms"
|
||||||
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"max_retries": {"type": "integer", "minimum": 0, "maximum": 100},
|
"max_retries": {
|
||||||
"initial_backoff_ms": {"$ref": "#/$defs/positiveMs"},
|
"type": "integer",
|
||||||
"max_backoff_ms": {"$ref": "#/$defs/positiveMs"}
|
"minimum": 0,
|
||||||
|
"maximum": 100
|
||||||
|
},
|
||||||
|
"initial_backoff_ms": {
|
||||||
|
"$ref": "#/$defs/positiveMs"
|
||||||
|
},
|
||||||
|
"max_backoff_ms": {
|
||||||
|
"$ref": "#/$defs/positiveMs"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"limits": {
|
"limits": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"properties": {
|
"properties": {
|
||||||
"requests_per_second": {"$ref": "#/$defs/positiveU32"},
|
"requests_per_second": {
|
||||||
"burst_capacity": {"$ref": "#/$defs/positiveU32"},
|
"$ref": "#/$defs/positiveU32"
|
||||||
"max_concurrent_requests": {"$ref": "#/$defs/positiveU32"},
|
},
|
||||||
"pause_after_rate_limit_ms": {"$ref": "#/$defs/positiveMs"}
|
"burst_capacity": {
|
||||||
|
"$ref": "#/$defs/positiveU32"
|
||||||
|
},
|
||||||
|
"max_concurrent_requests": {
|
||||||
|
"$ref": "#/$defs/positiveU32"
|
||||||
|
},
|
||||||
|
"pause_after_rate_limit_ms": {
|
||||||
|
"$ref": "#/$defs/positiveMs"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"role": {
|
"role": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"required": ["role", "enabled", "request_kinds", "priority", "limits"],
|
"required": [
|
||||||
|
"role",
|
||||||
|
"enabled",
|
||||||
|
"request_kinds",
|
||||||
|
"priority",
|
||||||
|
"limits"
|
||||||
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"role": {"$ref": "#/$defs/descriptor"},
|
"role": {
|
||||||
"enabled": {"type": "boolean"},
|
"$ref": "#/$defs/descriptor"
|
||||||
|
},
|
||||||
|
"enabled": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
"request_kinds": {
|
"request_kinds": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"minItems": 1,
|
"minItems": 1,
|
||||||
"uniqueItems": true,
|
"uniqueItems": true,
|
||||||
"items": {"$ref": "#/$defs/descriptor"},
|
"items": {
|
||||||
|
"$ref": "#/$defs/descriptor"
|
||||||
|
},
|
||||||
"allOf": [
|
"allOf": [
|
||||||
{
|
{
|
||||||
"if": {"contains": {"const": "*"}},
|
"if": {
|
||||||
"then": {"maxItems": 1}
|
"contains": {
|
||||||
|
"const": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"then": {
|
||||||
|
"maxItems": 1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"priority": {"type": "integer", "minimum": 0, "maximum": 4294967295},
|
"priority": {
|
||||||
"limits": {"$ref": "#/$defs/limits"}
|
"type": "integer",
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 4294967295
|
||||||
|
},
|
||||||
|
"limits": {
|
||||||
|
"$ref": "#/$defs/limits"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"endpoint": {
|
"httpEndpoint": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"required": [
|
"required": [
|
||||||
@@ -92,31 +136,322 @@
|
|||||||
"roles"
|
"roles"
|
||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"name": {"$ref": "#/$defs/descriptor"},
|
"name": {
|
||||||
"enabled": {"type": "boolean"},
|
"$ref": "#/$defs/descriptor"
|
||||||
"provider": {"$ref": "#/$defs/descriptor"},
|
},
|
||||||
"cluster": {"$ref": "#/$defs/descriptor"},
|
"enabled": {
|
||||||
"url": {"type": "string", "minLength": 1},
|
"type": "boolean"
|
||||||
"connect_timeout_ms": {"$ref": "#/$defs/positiveMs"},
|
},
|
||||||
"request_timeout_ms": {"$ref": "#/$defs/positiveMs"},
|
"provider": {
|
||||||
"max_idle_connections_per_host": {"type": "integer", "minimum": 1},
|
"$ref": "#/$defs/descriptor"
|
||||||
|
},
|
||||||
|
"cluster": {
|
||||||
|
"$ref": "#/$defs/descriptor"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1
|
||||||
|
},
|
||||||
|
"connect_timeout_ms": {
|
||||||
|
"$ref": "#/$defs/positiveMs"
|
||||||
|
},
|
||||||
|
"request_timeout_ms": {
|
||||||
|
"$ref": "#/$defs/positiveMs"
|
||||||
|
},
|
||||||
|
"max_idle_connections_per_host": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 1
|
||||||
|
},
|
||||||
"roles": {
|
"roles": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"minItems": 1,
|
"minItems": 1,
|
||||||
"items": {"$ref": "#/$defs/role"}
|
"items": {
|
||||||
|
"$ref": "#/$defs/role"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"profile": {
|
"wsReconnect": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"required": ["profile_id", "endpoints"],
|
"required": [
|
||||||
|
"max_retries",
|
||||||
|
"initial_backoff_ms",
|
||||||
|
"max_backoff_ms"
|
||||||
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"profile_id": {"$ref": "#/$defs/profileId"},
|
"max_retries": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 100
|
||||||
|
},
|
||||||
|
"initial_backoff_ms": {
|
||||||
|
"$ref": "#/$defs/positiveMs"
|
||||||
|
},
|
||||||
|
"max_backoff_ms": {
|
||||||
|
"$ref": "#/$defs/positiveMs"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"wsReconnectOverride": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"minProperties": 1,
|
||||||
|
"properties": {
|
||||||
|
"max_retries": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 100
|
||||||
|
},
|
||||||
|
"initial_backoff_ms": {
|
||||||
|
"$ref": "#/$defs/positiveMs"
|
||||||
|
},
|
||||||
|
"max_backoff_ms": {
|
||||||
|
"$ref": "#/$defs/positiveMs"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"wsSession": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": [
|
||||||
|
"command_timeout_ms",
|
||||||
|
"close_timeout_ms",
|
||||||
|
"reconnect",
|
||||||
|
"resubscribe",
|
||||||
|
"command_queue_capacity",
|
||||||
|
"notification_queue_capacity",
|
||||||
|
"max_active_subscriptions",
|
||||||
|
"max_pending_requests",
|
||||||
|
"max_message_size_bytes",
|
||||||
|
"max_frame_size_bytes",
|
||||||
|
"max_write_buffer_size_bytes"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"command_timeout_ms": {
|
||||||
|
"$ref": "#/$defs/positiveMs"
|
||||||
|
},
|
||||||
|
"close_timeout_ms": {
|
||||||
|
"$ref": "#/$defs/positiveMs"
|
||||||
|
},
|
||||||
|
"reconnect": {
|
||||||
|
"$ref": "#/$defs/wsReconnect"
|
||||||
|
},
|
||||||
|
"resubscribe": {
|
||||||
|
"enum": [
|
||||||
|
"never",
|
||||||
|
"active_subscriptions"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"command_queue_capacity": {
|
||||||
|
"$ref": "#/$defs/positiveUsize"
|
||||||
|
},
|
||||||
|
"notification_queue_capacity": {
|
||||||
|
"$ref": "#/$defs/positiveUsize"
|
||||||
|
},
|
||||||
|
"max_active_subscriptions": {
|
||||||
|
"$ref": "#/$defs/positiveUsize"
|
||||||
|
},
|
||||||
|
"max_pending_requests": {
|
||||||
|
"$ref": "#/$defs/positiveUsize"
|
||||||
|
},
|
||||||
|
"max_message_size_bytes": {
|
||||||
|
"$ref": "#/$defs/positiveUsize"
|
||||||
|
},
|
||||||
|
"max_frame_size_bytes": {
|
||||||
|
"$ref": "#/$defs/positiveUsize"
|
||||||
|
},
|
||||||
|
"max_write_buffer_size_bytes": {
|
||||||
|
"$ref": "#/$defs/positiveUsize"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"wsSessionOverride": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"minProperties": 1,
|
||||||
|
"properties": {
|
||||||
|
"command_timeout_ms": {
|
||||||
|
"$ref": "#/$defs/positiveMs"
|
||||||
|
},
|
||||||
|
"close_timeout_ms": {
|
||||||
|
"$ref": "#/$defs/positiveMs"
|
||||||
|
},
|
||||||
|
"reconnect": {
|
||||||
|
"$ref": "#/$defs/wsReconnectOverride"
|
||||||
|
},
|
||||||
|
"resubscribe": {
|
||||||
|
"enum": [
|
||||||
|
"never",
|
||||||
|
"active_subscriptions"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"command_queue_capacity": {
|
||||||
|
"$ref": "#/$defs/positiveUsize"
|
||||||
|
},
|
||||||
|
"notification_queue_capacity": {
|
||||||
|
"$ref": "#/$defs/positiveUsize"
|
||||||
|
},
|
||||||
|
"max_active_subscriptions": {
|
||||||
|
"$ref": "#/$defs/positiveUsize"
|
||||||
|
},
|
||||||
|
"max_pending_requests": {
|
||||||
|
"$ref": "#/$defs/positiveUsize"
|
||||||
|
},
|
||||||
|
"max_message_size_bytes": {
|
||||||
|
"$ref": "#/$defs/positiveUsize"
|
||||||
|
},
|
||||||
|
"max_frame_size_bytes": {
|
||||||
|
"$ref": "#/$defs/positiveUsize"
|
||||||
|
},
|
||||||
|
"max_write_buffer_size_bytes": {
|
||||||
|
"$ref": "#/$defs/positiveUsize"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"wsEndpoint": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": [
|
||||||
|
"name",
|
||||||
|
"enabled",
|
||||||
|
"provider",
|
||||||
|
"cluster",
|
||||||
|
"kind",
|
||||||
|
"url"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"$ref": "#/$defs/descriptor"
|
||||||
|
},
|
||||||
|
"enabled": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"provider": {
|
||||||
|
"$ref": "#/$defs/descriptor"
|
||||||
|
},
|
||||||
|
"cluster": {
|
||||||
|
"$ref": "#/$defs/descriptor"
|
||||||
|
},
|
||||||
|
"kind": {
|
||||||
|
"enum": [
|
||||||
|
"solana_standard"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1
|
||||||
|
},
|
||||||
|
"session": {
|
||||||
|
"$ref": "#/$defs/wsSessionOverride"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"profileV1": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": [
|
||||||
|
"profile_id",
|
||||||
|
"endpoints"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"profile_id": {
|
||||||
|
"$ref": "#/$defs/profileId"
|
||||||
|
},
|
||||||
"endpoints": {
|
"endpoints": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"minItems": 1,
|
"minItems": 1,
|
||||||
"items": {"$ref": "#/$defs/endpoint"}
|
"items": {
|
||||||
|
"$ref": "#/$defs/httpEndpoint"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"profileV2": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": [
|
||||||
|
"profile_id",
|
||||||
|
"endpoints",
|
||||||
|
"ws_endpoints"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"profile_id": {
|
||||||
|
"$ref": "#/$defs/profileId"
|
||||||
|
},
|
||||||
|
"endpoints": {
|
||||||
|
"type": "array",
|
||||||
|
"minItems": 1,
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/$defs/httpEndpoint"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ws_endpoints": {
|
||||||
|
"type": "array",
|
||||||
|
"minItems": 1,
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/$defs/wsEndpoint"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"documentV1": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": [
|
||||||
|
"format_version",
|
||||||
|
"retry",
|
||||||
|
"default_profile",
|
||||||
|
"profiles"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"format_version": {
|
||||||
|
"const": 1
|
||||||
|
},
|
||||||
|
"retry": {
|
||||||
|
"$ref": "#/$defs/retry"
|
||||||
|
},
|
||||||
|
"default_profile": {
|
||||||
|
"$ref": "#/$defs/profileId"
|
||||||
|
},
|
||||||
|
"profiles": {
|
||||||
|
"type": "array",
|
||||||
|
"minItems": 1,
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/$defs/profileV1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"documentV2": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": [
|
||||||
|
"format_version",
|
||||||
|
"retry",
|
||||||
|
"ws_defaults",
|
||||||
|
"default_profile",
|
||||||
|
"profiles"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"format_version": {
|
||||||
|
"const": 2
|
||||||
|
},
|
||||||
|
"retry": {
|
||||||
|
"$ref": "#/$defs/retry"
|
||||||
|
},
|
||||||
|
"ws_defaults": {
|
||||||
|
"$ref": "#/$defs/wsSession"
|
||||||
|
},
|
||||||
|
"default_profile": {
|
||||||
|
"$ref": "#/$defs/profileId"
|
||||||
|
},
|
||||||
|
"profiles": {
|
||||||
|
"type": "array",
|
||||||
|
"minItems": 1,
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/$defs/profileV2"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,27 @@
|
|||||||
{
|
{
|
||||||
"format_version": 1,
|
"format_version": 2,
|
||||||
"retry": {
|
"retry": {
|
||||||
"max_retries": 2,
|
"max_retries": 2,
|
||||||
"initial_backoff_ms": 100,
|
"initial_backoff_ms": 100,
|
||||||
"max_backoff_ms": 2000
|
"max_backoff_ms": 2000
|
||||||
},
|
},
|
||||||
|
"ws_defaults": {
|
||||||
|
"command_timeout_ms": 10000,
|
||||||
|
"close_timeout_ms": 5000,
|
||||||
|
"reconnect": {
|
||||||
|
"max_retries": 5,
|
||||||
|
"initial_backoff_ms": 250,
|
||||||
|
"max_backoff_ms": 5000
|
||||||
|
},
|
||||||
|
"resubscribe": "active_subscriptions",
|
||||||
|
"command_queue_capacity": 128,
|
||||||
|
"notification_queue_capacity": 256,
|
||||||
|
"max_active_subscriptions": 1024,
|
||||||
|
"max_pending_requests": 128,
|
||||||
|
"max_message_size_bytes": 67108864,
|
||||||
|
"max_frame_size_bytes": 16777216,
|
||||||
|
"max_write_buffer_size_bytes": 1048576
|
||||||
|
},
|
||||||
"default_profile": "devnet_public",
|
"default_profile": "devnet_public",
|
||||||
"profiles": [
|
"profiles": [
|
||||||
{
|
{
|
||||||
@@ -23,7 +40,9 @@
|
|||||||
{
|
{
|
||||||
"role": "default",
|
"role": "default",
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"request_kinds": ["*"],
|
"request_kinds": [
|
||||||
|
"*"
|
||||||
|
],
|
||||||
"priority": 100,
|
"priority": 100,
|
||||||
"limits": {
|
"limits": {
|
||||||
"requests_per_second": 5,
|
"requests_per_second": 5,
|
||||||
@@ -34,6 +53,16 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"ws_endpoints": [
|
||||||
|
{
|
||||||
|
"name": "solana_devnet_public_ws",
|
||||||
|
"enabled": true,
|
||||||
|
"provider": "solana-public",
|
||||||
|
"cluster": "devnet",
|
||||||
|
"kind": "solana_standard",
|
||||||
|
"url": "${KSP_PUBLIC_SOLANA_DEVNET_WS_URL:-wss://api.devnet.solana.com}"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -52,7 +81,9 @@
|
|||||||
{
|
{
|
||||||
"role": "default",
|
"role": "default",
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"request_kinds": ["*"],
|
"request_kinds": [
|
||||||
|
"*"
|
||||||
|
],
|
||||||
"priority": 100,
|
"priority": 100,
|
||||||
"limits": {
|
"limits": {
|
||||||
"requests_per_second": 5,
|
"requests_per_second": 5,
|
||||||
@@ -63,6 +94,16 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"ws_endpoints": [
|
||||||
|
{
|
||||||
|
"name": "solana_mainnet_public_ws",
|
||||||
|
"enabled": true,
|
||||||
|
"provider": "solana-public",
|
||||||
|
"cluster": "mainnet-beta",
|
||||||
|
"kind": "solana_standard",
|
||||||
|
"url": "${KSP_PUBLIC_SOLANA_MAINNET_WS_URL:-wss://api.mainnet-beta.solana.com}"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: crates/ksp-config-lib/README.md -->
|
<!-- file: crates/ksp-config-lib/README.md -->
|
||||||
<!-- version: 6 -->
|
<!-- version: 7 -->
|
||||||
|
|
||||||
# ksp-config-lib
|
# ksp-config-lib
|
||||||
|
|
||||||
@@ -23,7 +23,7 @@ La crate centralise les documents JSON, leurs schemas, les profils et compositio
|
|||||||
- la classification `Public`, `Internal`, `Secret` ;
|
- la classification `Public`, `Internal`, `Secret` ;
|
||||||
- les représentations réelle et sûre/redacted ainsi que la provenance des valeurs résolues ;
|
- les représentations réelle et sûre/redacted ainsi que la provenance des valeurs résolues ;
|
||||||
- l'adapter du document Logging effectif vers `ksp_logging_lib::LoggingSettings` ;
|
- l'adapter du document Logging effectif vers `ksp_logging_lib::LoggingSettings` ;
|
||||||
- l'adapter du document HTTP Transport effectif vers `ksp_onchain_transport_lib::HttpTransportSettings`, y compris redaction/provenance des URLs `KSP_SECRET_*` ;
|
- l'adapter du document Transport V1/V2 vers `HttpTransportSettings` et, en V2, `WsTransportSettings`, y compris redaction/provenance des URLs `KSP_SECRET_*` ;
|
||||||
- la surface de management pour inspecter et réparer les sources Config enregistrées, modifier `std.logging.json`, consulter les rapports d'environnement, révéler explicitement une valeur réelle et modifier `.env` ;
|
- la surface de management pour inspecter et réparer les sources Config enregistrées, modifier `std.logging.json`, consulter les rapports d'environnement, révéler explicitement une valeur réelle et modifier `.env` ;
|
||||||
- les écritures atomiques JSON/`.env` et la protection des permissions `.env` ;
|
- les écritures atomiques JSON/`.env` et la protection des permissions `.env` ;
|
||||||
- les audits workspace empêchant les bypass d'ownership Config et les oublis dans `.env.example`.
|
- les audits workspace empêchant les bypass d'ownership Config et les oublis dans `.env.example`.
|
||||||
@@ -81,7 +81,7 @@ Un secret reste accessible au runtime ou au management lorsqu'un consumer autori
|
|||||||
|
|
||||||
Les méthodes `reveal_*` constituent un opt-in explicite au réel. L'authentification/autorisation de l'utilisateur humain appartient à l'application appelante et les valeurs retournées par ces méthodes ne doivent jamais être journalisées.
|
Les méthodes `reveal_*` constituent un opt-in explicite au réel. L'authentification/autorisation de l'utilisateur humain appartient à l'application appelante et les valeurs retournées par ces méthodes ne doivent jamais être journalisées.
|
||||||
|
|
||||||
Le document Logging refuse les valeurs de sensibilité `Secret` dans sa configuration effective. Le document Transport les accepte pour les URLs endpoint : la valeur réelle est transmise au runtime légitime, tandis que la projection sûre et les `Debug` restent redacted. `std.wallet` refuse également toute sensibilité `Secret` pour `wallets_directory`/`wallets_subdirectory`; les passwords Wallet restent un autre flux Config et ne sont jamais stockés dans ce JSON.
|
Le document Logging refuse les valeurs de sensibilité `Secret` dans sa configuration effective. Le document Transport les accepte pour les URLs HTTP et WebSocket : la valeur réelle est transmise au runtime légitime, tandis que la projection sûre et les `Debug` restent redacted. `std.wallet` refuse également toute sensibilité `Secret` pour `wallets_directory`/`wallets_subdirectory`; les passwords Wallet restent un autre flux Config et ne sont jamais stockés dans ce JSON.
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
@@ -89,7 +89,7 @@ Le document Logging refuse les valeurs de sensibilité `Secret` dans sa configur
|
|||||||
- [`TODO.md`](TODO.md) — points explicitement différés après `0.1.3` ;
|
- [`TODO.md`](TODO.md) — points explicitement différés après `0.1.3` ;
|
||||||
- [`../../docs/plans/005-V0_1_3_CONFIG_FOUNDATION_PLAN.md`](../../docs/plans/005-V0_1_3_CONFIG_FOUNDATION_PLAN.md) — plan historique détaillé de la fondation Config ;
|
- [`../../docs/plans/005-V0_1_3_CONFIG_FOUNDATION_PLAN.md`](../../docs/plans/005-V0_1_3_CONFIG_FOUNDATION_PLAN.md) — plan historique détaillé de la fondation Config ;
|
||||||
- [`../../config/std.logging.json`](../../config/std.logging.json) — document standard Logging ;
|
- [`../../config/std.logging.json`](../../config/std.logging.json) — document standard Logging ;
|
||||||
- [`../../config/std.transport.json`](../../config/std.transport.json) — document standard HTTP Transport ;
|
- [`../../config/std.transport.json`](../../config/std.transport.json) — document standard Transport V2 HTTP + WebSocket, avec lecture backward du V1 HTTP-only ;
|
||||||
- [`../../config/std.wallet.json`](../../config/std.wallet.json) — racine Wallet globale et sous-répertoire optionnel par profil ;
|
- [`../../config/std.wallet.json`](../../config/std.wallet.json) — racine Wallet globale et sous-répertoire optionnel par profil ;
|
||||||
- [`../../config/composite.ksp-app-wallet-desk.json`](../../config/composite.ksp-app-wallet-desk.json) — composition Logging/Transport/Wallet de Wallet Desk ;
|
- [`../../config/composite.ksp-app-wallet-desk.json`](../../config/composite.ksp-app-wallet-desk.json) — composition Logging/Transport/Wallet de Wallet Desk ;
|
||||||
- [`../../.env.example`](../../.env.example) — inventaire versionné des variables d'environnement runtime.
|
- [`../../.env.example`](../../.env.example) — inventaire versionné des variables d'environnement runtime.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: crates/ksp-config-lib/USAGE.md -->
|
<!-- file: crates/ksp-config-lib/USAGE.md -->
|
||||||
<!-- version: 6 -->
|
<!-- version: 7 -->
|
||||||
|
|
||||||
# Utilisation de ksp-config-lib
|
# Utilisation de ksp-config-lib
|
||||||
|
|
||||||
@@ -125,7 +125,7 @@ Un `logs_directory` relatif est ancré sur le current working directory du proce
|
|||||||
|
|
||||||
Les `files[].path` restent relatifs sous le root Logging, y compris après interpolation.
|
Les `files[].path` restent relatifs sous le root Logging, y compris après interpolation.
|
||||||
|
|
||||||
### 4.1 Construire le Transport HTTP depuis Config
|
### 4.1 Construire le Transport HTTP + WebSocket depuis Config
|
||||||
|
|
||||||
Config possède également l'adapter du document `std.transport` vers le contrat runtime de `ksp-onchain-transport-lib` :
|
Config possède également l'adapter du document `std.transport` vers le contrat runtime de `ksp-onchain-transport-lib` :
|
||||||
|
|
||||||
@@ -135,12 +135,18 @@ let transport = match engine.load_resolved_transport_config(std::option::Option:
|
|||||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
};
|
};
|
||||||
|
|
||||||
let transport_settings = transport.into_settings();
|
let http_settings = transport.http_settings();
|
||||||
|
let ws_settings = transport.ws_settings();
|
||||||
|
let _ = (http_settings, ws_settings);
|
||||||
```
|
```
|
||||||
|
|
||||||
Les scalaires `*_ms` restent des valeurs Config et sont convertis en `std::time::Duration` par l'adapter. Les URLs peuvent provenir de `KSP_PUBLIC_*` ou de `KSP_SECRET_*`; dans ce dernier cas la valeur réelle reste disponible au runtime Transport, mais `ResolvedTransportConfig::effective().safe_value()` et les représentations `Debug` sont redacted.
|
`std.transport` V2 conserve `retry` et `profiles[].endpoints[]` pour HTTP, ajoute `ws_defaults` et `profiles[].ws_endpoints[]`, puis exige `kind = "solana_standard"` en `0.2.7`. Un `ws_endpoints[].session` optionnel surcharge seulement les paramètres génériques de `WsSessionSettings`.
|
||||||
|
|
||||||
La dépendance reste unidirectionnelle : Config connaît le contrat Transport pour le construire ; Transport ne connaît ni Config, ni `.env`, ni les variables KSP.
|
Le même schema enregistré conserve la lecture stricte du V1 historique : dans ce cas `http_settings()` reste disponible et `ws_settings()` retourne `None`. Aucun `WsTransportSettings` vide n'est inventé pour simuler l'absence de WebSocket.
|
||||||
|
|
||||||
|
Les scalaires `*_ms` restent des valeurs Config et sont convertis en `std::time::Duration` par l'adapter. Les URLs HTTP et WebSocket peuvent provenir de `KSP_PUBLIC_*` ou de `KSP_SECRET_*`; dans ce dernier cas la valeur réelle reste disponible au runtime Transport, mais `ResolvedTransportConfig::effective().safe_value()` et les représentations `Debug` sont redacted.
|
||||||
|
|
||||||
|
La dépendance reste unidirectionnelle : Config connaît les contrats Transport pour les construire ; Transport ne connaît ni Config, ni `.env`, ni les variables KSP.
|
||||||
|
|
||||||
### 4.2 Résoudre le répertoire Wallet depuis Config
|
### 4.2 Résoudre le répertoire Wallet depuis Config
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-config-lib/src/lib.rs
|
// file: crates/ksp-config-lib/src/lib.rs
|
||||||
// version: 16
|
// version: 17
|
||||||
|
|
||||||
#![warn(missing_docs)]
|
#![warn(missing_docs)]
|
||||||
#![deny(unreachable_pub)]
|
#![deny(unreachable_pub)]
|
||||||
@@ -154,9 +154,9 @@ pub use self::registry::DEFAULT_COMPOSITE_SCHEMA_FILENAME;
|
|||||||
pub use self::registry::DEFAULT_STD_LOGGING_FILENAME;
|
pub use self::registry::DEFAULT_STD_LOGGING_FILENAME;
|
||||||
/// Default physical filename for the standard Logging JSON Schema document.
|
/// Default physical filename for the standard Logging JSON Schema document.
|
||||||
pub use self::registry::DEFAULT_STD_LOGGING_SCHEMA_FILENAME;
|
pub use self::registry::DEFAULT_STD_LOGGING_SCHEMA_FILENAME;
|
||||||
/// Default physical filename for the standard HTTP Transport configuration document.
|
/// Default physical filename for the standard HTTP + WebSocket Transport configuration document.
|
||||||
pub use self::registry::DEFAULT_STD_TRANSPORT_FILENAME;
|
pub use self::registry::DEFAULT_STD_TRANSPORT_FILENAME;
|
||||||
/// Default physical filename for the standard HTTP Transport JSON Schema document.
|
/// Default physical filename for the standard HTTP + WebSocket Transport JSON Schema document.
|
||||||
pub use self::registry::DEFAULT_STD_TRANSPORT_SCHEMA_FILENAME;
|
pub use self::registry::DEFAULT_STD_TRANSPORT_SCHEMA_FILENAME;
|
||||||
/// Default physical filename for the standard Wallet configuration document.
|
/// Default physical filename for the standard Wallet configuration document.
|
||||||
pub use self::registry::DEFAULT_STD_WALLET_FILENAME;
|
pub use self::registry::DEFAULT_STD_WALLET_FILENAME;
|
||||||
@@ -168,13 +168,13 @@ pub use self::registry::FILE_ID_COMPOSITE_KSP_APP_WALLET_DESK;
|
|||||||
pub use self::registry::FILE_ID_SCHEMA_COMPOSITE;
|
pub use self::registry::FILE_ID_SCHEMA_COMPOSITE;
|
||||||
/// Logical file identifier for the standard Logging JSON Schema document.
|
/// Logical file identifier for the standard Logging JSON Schema document.
|
||||||
pub use self::registry::FILE_ID_SCHEMA_STD_LOGGING;
|
pub use self::registry::FILE_ID_SCHEMA_STD_LOGGING;
|
||||||
/// Logical file identifier for the standard HTTP Transport JSON Schema document.
|
/// Logical file identifier for the standard HTTP + WebSocket Transport JSON Schema document.
|
||||||
pub use self::registry::FILE_ID_SCHEMA_STD_TRANSPORT;
|
pub use self::registry::FILE_ID_SCHEMA_STD_TRANSPORT;
|
||||||
/// Logical file identifier for the standard Wallet JSON Schema document.
|
/// Logical file identifier for the standard Wallet JSON Schema document.
|
||||||
pub use self::registry::FILE_ID_SCHEMA_STD_WALLET;
|
pub use self::registry::FILE_ID_SCHEMA_STD_WALLET;
|
||||||
/// Logical file identifier for the standard Logging configuration document.
|
/// Logical file identifier for the standard Logging configuration document.
|
||||||
pub use self::registry::FILE_ID_STD_LOGGING;
|
pub use self::registry::FILE_ID_STD_LOGGING;
|
||||||
/// Logical file identifier for the standard HTTP Transport configuration document.
|
/// Logical file identifier for the standard HTTP + WebSocket Transport configuration document.
|
||||||
pub use self::registry::FILE_ID_STD_TRANSPORT;
|
pub use self::registry::FILE_ID_STD_TRANSPORT;
|
||||||
/// Logical file identifier for the standard Wallet configuration document.
|
/// Logical file identifier for the standard Wallet configuration document.
|
||||||
pub use self::registry::FILE_ID_STD_WALLET;
|
pub use self::registry::FILE_ID_STD_WALLET;
|
||||||
@@ -188,7 +188,7 @@ pub use self::sensitivity::REDACTED_CONFIG_VALUE;
|
|||||||
pub use self::sensitivity::ResolvedConfigJson;
|
pub use self::sensitivity::ResolvedConfigJson;
|
||||||
/// One resolved Config string preserving real/safe representations and provenance.
|
/// One resolved Config string preserving real/safe representations and provenance.
|
||||||
pub use self::sensitivity::ResolvedConfigText;
|
pub use self::sensitivity::ResolvedConfigText;
|
||||||
/// Effective standard HTTP Transport configuration mapped to `ksp_onchain_transport_lib::HttpTransportSettings`.
|
/// Effective standard Transport configuration mapped to HTTP and optional WebSocket runtime settings.
|
||||||
pub use self::transport::ResolvedTransportConfig;
|
pub use self::transport::ResolvedTransportConfig;
|
||||||
/// Effective standard Wallet configuration resolved to validated filesystem roots.
|
/// Effective standard Wallet configuration resolved to validated filesystem roots.
|
||||||
pub use self::wallet::ResolvedWalletConfig;
|
pub use self::wallet::ResolvedWalletConfig;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-config-lib/src/registry.rs
|
// file: crates/ksp-config-lib/src/registry.rs
|
||||||
// version: 8
|
// version: 9
|
||||||
|
|
||||||
/// Bootstrap argument used to replace a known Config filename mapping.
|
/// Bootstrap argument used to replace a known Config filename mapping.
|
||||||
pub const ARG_FILE_MAP: &str = "--filemap";
|
pub const ARG_FILE_MAP: &str = "--filemap";
|
||||||
@@ -11,9 +11,9 @@ pub const DEFAULT_COMPOSITE_SCHEMA_FILENAME: &str = "composite.schema.json";
|
|||||||
pub const DEFAULT_STD_LOGGING_FILENAME: &str = "std.logging.json";
|
pub const DEFAULT_STD_LOGGING_FILENAME: &str = "std.logging.json";
|
||||||
/// Default physical filename for the standard Logging JSON Schema document.
|
/// Default physical filename for the standard Logging JSON Schema document.
|
||||||
pub const DEFAULT_STD_LOGGING_SCHEMA_FILENAME: &str = "std.logging.schema.json";
|
pub const DEFAULT_STD_LOGGING_SCHEMA_FILENAME: &str = "std.logging.schema.json";
|
||||||
/// Default physical filename for the standard HTTP Transport configuration document.
|
/// Default physical filename for the standard HTTP + WebSocket Transport configuration document.
|
||||||
pub const DEFAULT_STD_TRANSPORT_FILENAME: &str = "std.transport.json";
|
pub const DEFAULT_STD_TRANSPORT_FILENAME: &str = "std.transport.json";
|
||||||
/// Default physical filename for the standard HTTP Transport JSON Schema document.
|
/// Default physical filename for the standard HTTP + WebSocket Transport JSON Schema document.
|
||||||
pub const DEFAULT_STD_TRANSPORT_SCHEMA_FILENAME: &str = "std.transport.schema.json";
|
pub const DEFAULT_STD_TRANSPORT_SCHEMA_FILENAME: &str = "std.transport.schema.json";
|
||||||
/// Default physical filename for the standard Wallet configuration document.
|
/// Default physical filename for the standard Wallet configuration document.
|
||||||
pub const DEFAULT_STD_WALLET_FILENAME: &str = "std.wallet.json";
|
pub const DEFAULT_STD_WALLET_FILENAME: &str = "std.wallet.json";
|
||||||
@@ -25,13 +25,13 @@ pub const FILE_ID_COMPOSITE_KSP_APP_WALLET_DESK: &str = "cfg.composite.ksp-app-w
|
|||||||
pub const FILE_ID_SCHEMA_COMPOSITE: &str = "schema.composite";
|
pub const FILE_ID_SCHEMA_COMPOSITE: &str = "schema.composite";
|
||||||
/// Logical file identifier for the standard Logging JSON Schema document.
|
/// Logical file identifier for the standard Logging JSON Schema document.
|
||||||
pub const FILE_ID_SCHEMA_STD_LOGGING: &str = "schema.std.logging";
|
pub const FILE_ID_SCHEMA_STD_LOGGING: &str = "schema.std.logging";
|
||||||
/// Logical file identifier for the standard HTTP Transport JSON Schema document.
|
/// Logical file identifier for the standard HTTP + WebSocket Transport JSON Schema document.
|
||||||
pub const FILE_ID_SCHEMA_STD_TRANSPORT: &str = "schema.std.transport";
|
pub const FILE_ID_SCHEMA_STD_TRANSPORT: &str = "schema.std.transport";
|
||||||
/// Logical file identifier for the standard Wallet JSON Schema document.
|
/// Logical file identifier for the standard Wallet JSON Schema document.
|
||||||
pub const FILE_ID_SCHEMA_STD_WALLET: &str = "schema.std.wallet";
|
pub const FILE_ID_SCHEMA_STD_WALLET: &str = "schema.std.wallet";
|
||||||
/// Logical file identifier for the standard Logging configuration document.
|
/// Logical file identifier for the standard Logging configuration document.
|
||||||
pub const FILE_ID_STD_LOGGING: &str = "cfg.std.logging";
|
pub const FILE_ID_STD_LOGGING: &str = "cfg.std.logging";
|
||||||
/// Logical file identifier for the standard HTTP Transport configuration document.
|
/// Logical file identifier for the standard HTTP + WebSocket Transport configuration document.
|
||||||
pub const FILE_ID_STD_TRANSPORT: &str = "cfg.std.transport";
|
pub const FILE_ID_STD_TRANSPORT: &str = "cfg.std.transport";
|
||||||
/// Logical file identifier for the standard Wallet configuration document.
|
/// Logical file identifier for the standard Wallet configuration document.
|
||||||
pub const FILE_ID_STD_WALLET: &str = "cfg.std.wallet";
|
pub const FILE_ID_STD_WALLET: &str = "cfg.std.wallet";
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// file: crates/ksp-config-lib/src/transport.rs
|
// file: crates/ksp-config-lib/src/transport.rs
|
||||||
// version: 2
|
// version: 3
|
||||||
|
|
||||||
/// Effective standard HTTP Transport configuration resolved from Config and mapped to the Transport runtime contract.
|
/// Effective standard on-chain Transport configuration resolved from Config and mapped to HTTP and optional WebSocket runtime contracts.
|
||||||
#[derive(Clone, Eq, PartialEq)]
|
#[derive(Clone, Eq, PartialEq)]
|
||||||
pub struct ResolvedTransportConfig {
|
pub struct ResolvedTransportConfig {
|
||||||
file_id: crate::ConfigFileId,
|
file_id: crate::ConfigFileId,
|
||||||
@@ -10,6 +10,7 @@ pub struct ResolvedTransportConfig {
|
|||||||
selection_source: crate::ConfigProfileSelectionSource,
|
selection_source: crate::ConfigProfileSelectionSource,
|
||||||
effective: crate::ResolvedConfigJson,
|
effective: crate::ResolvedConfigJson,
|
||||||
settings: ksp_onchain_transport_lib::HttpTransportSettings,
|
settings: ksp_onchain_transport_lib::HttpTransportSettings,
|
||||||
|
ws_settings: std::option::Option<ksp_onchain_transport_lib::WsTransportSettings>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ResolvedTransportConfig {
|
impl ResolvedTransportConfig {
|
||||||
@@ -47,16 +48,43 @@ impl ResolvedTransportConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the validated runtime HTTP Transport settings.
|
/// Returns the validated runtime HTTP Transport settings.
|
||||||
|
///
|
||||||
|
/// This compatibility accessor keeps the HTTP contract introduced before Transport V2.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn settings(&self) -> &ksp_onchain_transport_lib::HttpTransportSettings {
|
pub const fn settings(&self) -> &ksp_onchain_transport_lib::HttpTransportSettings {
|
||||||
return &self.settings;
|
return &self.settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the validated runtime HTTP Transport settings.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn http_settings(&self) -> &ksp_onchain_transport_lib::HttpTransportSettings {
|
||||||
|
return &self.settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns validated WebSocket Transport settings when the selected document uses format V2.
|
||||||
|
///
|
||||||
|
/// Backward-compatible V1 HTTP-only documents return [`std::option::Option::None`].
|
||||||
|
#[must_use]
|
||||||
|
pub fn ws_settings(&self) -> std::option::Option<&ksp_onchain_transport_lib::WsTransportSettings> {
|
||||||
|
return self.ws_settings.as_ref();
|
||||||
|
}
|
||||||
|
|
||||||
/// Consumes this resolved Config and returns the mapped runtime HTTP Transport settings.
|
/// Consumes this resolved Config and returns the mapped runtime HTTP Transport settings.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn into_settings(self) -> ksp_onchain_transport_lib::HttpTransportSettings {
|
pub fn into_settings(self) -> ksp_onchain_transport_lib::HttpTransportSettings {
|
||||||
return self.settings;
|
return self.settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Consumes this resolved Config and returns both HTTP and optional WebSocket runtime settings.
|
||||||
|
#[must_use]
|
||||||
|
pub fn into_transport_settings(
|
||||||
|
self,
|
||||||
|
) -> (
|
||||||
|
ksp_onchain_transport_lib::HttpTransportSettings,
|
||||||
|
std::option::Option<ksp_onchain_transport_lib::WsTransportSettings>,
|
||||||
|
) {
|
||||||
|
return (self.settings, self.ws_settings);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Debug for ResolvedTransportConfig {
|
impl std::fmt::Debug for ResolvedTransportConfig {
|
||||||
@@ -68,16 +96,16 @@ impl std::fmt::Debug for ResolvedTransportConfig {
|
|||||||
.field("profile_id", &self.profile_id)
|
.field("profile_id", &self.profile_id)
|
||||||
.field("selection_source", &self.selection_source)
|
.field("selection_source", &self.selection_source)
|
||||||
.field("effective", &self.effective)
|
.field("effective", &self.effective)
|
||||||
|
.field("has_ws_settings", &self.ws_settings.is_some())
|
||||||
.finish_non_exhaustive();
|
.finish_non_exhaustive();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl crate::ConfigDocumentEngine {
|
impl crate::ConfigDocumentEngine {
|
||||||
/// Loads the standard HTTP Transport document, selects a profile, resolves environment placeholders and maps it to Transport runtime settings.
|
/// Loads the standard Transport document, selects a profile, resolves environment placeholders and maps HTTP plus optional WebSocket runtime settings.
|
||||||
///
|
///
|
||||||
/// `requested_profile = None` uses the document `default_profile`; `Some(profile_id)` requests an explicit profile. Secret endpoint URLs are allowed
|
/// `requested_profile = None` uses the document `default_profile`; `Some(profile_id)` requests an explicit profile. Secret endpoint URLs are allowed
|
||||||
/// because the Transport URL wrapper owns runtime redaction. Invalid environment-resolved values are reported as
|
/// because Transport URL wrappers own runtime redaction. V1 documents remain HTTP-only; V2 documents require WebSocket defaults and endpoints.
|
||||||
/// [`crate::ERROR_CODE_EFFECTIVE_CONFIG_INVALID`] without copying endpoint URL values into ordinary error context.
|
|
||||||
pub fn load_resolved_transport_config(
|
pub fn load_resolved_transport_config(
|
||||||
&self,
|
&self,
|
||||||
requested_profile: std::option::Option<&str>,
|
requested_profile: std::option::Option<&str>,
|
||||||
@@ -96,7 +124,7 @@ impl crate::ConfigDocumentEngine {
|
|||||||
return resolve_transport_profile(&profile, environment);
|
return resolve_transport_profile(&profile, environment);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Maps an already resolved standard Transport profile to the runtime HTTP Transport adapter while preserving its selection provenance.
|
/// Maps an already resolved standard Transport profile to HTTP plus optional WebSocket runtime adapters while preserving selection provenance.
|
||||||
///
|
///
|
||||||
/// This entry point is intended for profiles selected by a composite. The profile must reference `cfg.std.transport`.
|
/// This entry point is intended for profiles selected by a composite. The profile must reference `cfg.std.transport`.
|
||||||
pub fn resolve_transport_config_profile(
|
pub fn resolve_transport_config_profile(
|
||||||
@@ -121,7 +149,11 @@ struct EffectiveTransportSource {
|
|||||||
format_version: u32,
|
format_version: u32,
|
||||||
profile_id: String,
|
profile_id: String,
|
||||||
retry: EffectiveRetrySource,
|
retry: EffectiveRetrySource,
|
||||||
|
#[serde(default)]
|
||||||
|
ws_defaults: std::option::Option<EffectiveWsSessionSource>,
|
||||||
endpoints: std::vec::Vec<EffectiveEndpointSource>,
|
endpoints: std::vec::Vec<EffectiveEndpointSource>,
|
||||||
|
#[serde(default)]
|
||||||
|
ws_endpoints: std::option::Option<std::vec::Vec<EffectiveWsEndpointSource>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Deserialize)]
|
#[derive(serde::Deserialize)]
|
||||||
@@ -165,7 +197,69 @@ struct EffectiveLimitsSource {
|
|||||||
pause_after_rate_limit_ms: std::option::Option<u64>,
|
pause_after_rate_limit_ms: std::option::Option<u64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
|
struct EffectiveWsSessionSource {
|
||||||
|
command_timeout_ms: u64,
|
||||||
|
close_timeout_ms: u64,
|
||||||
|
reconnect: EffectiveWsReconnectSource,
|
||||||
|
resubscribe: String,
|
||||||
|
command_queue_capacity: usize,
|
||||||
|
notification_queue_capacity: usize,
|
||||||
|
max_active_subscriptions: usize,
|
||||||
|
max_pending_requests: usize,
|
||||||
|
max_message_size_bytes: usize,
|
||||||
|
max_frame_size_bytes: usize,
|
||||||
|
max_write_buffer_size_bytes: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
|
struct EffectiveWsReconnectSource {
|
||||||
|
max_retries: u32,
|
||||||
|
initial_backoff_ms: u64,
|
||||||
|
max_backoff_ms: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
|
struct EffectiveWsEndpointSource {
|
||||||
|
name: String,
|
||||||
|
enabled: bool,
|
||||||
|
provider: String,
|
||||||
|
cluster: String,
|
||||||
|
kind: String,
|
||||||
|
url: String,
|
||||||
|
#[serde(default)]
|
||||||
|
session: std::option::Option<EffectiveWsSessionOverrideSource>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
|
struct EffectiveWsSessionOverrideSource {
|
||||||
|
command_timeout_ms: std::option::Option<u64>,
|
||||||
|
close_timeout_ms: std::option::Option<u64>,
|
||||||
|
reconnect: std::option::Option<EffectiveWsReconnectOverrideSource>,
|
||||||
|
resubscribe: std::option::Option<String>,
|
||||||
|
command_queue_capacity: std::option::Option<usize>,
|
||||||
|
notification_queue_capacity: std::option::Option<usize>,
|
||||||
|
max_active_subscriptions: std::option::Option<usize>,
|
||||||
|
max_pending_requests: std::option::Option<usize>,
|
||||||
|
max_message_size_bytes: std::option::Option<usize>,
|
||||||
|
max_frame_size_bytes: std::option::Option<usize>,
|
||||||
|
max_write_buffer_size_bytes: std::option::Option<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
|
struct EffectiveWsReconnectOverrideSource {
|
||||||
|
max_retries: std::option::Option<u32>,
|
||||||
|
initial_backoff_ms: std::option::Option<u64>,
|
||||||
|
max_backoff_ms: std::option::Option<u64>,
|
||||||
|
}
|
||||||
|
|
||||||
fn resolve_transport_profile(profile: &crate::ResolvedConfigProfile, environment: &crate::ConfigEnvironment) -> ksp_core_lib::Result<ResolvedTransportConfig> {
|
fn resolve_transport_profile(profile: &crate::ResolvedConfigProfile, environment: &crate::ConfigEnvironment) -> ksp_core_lib::Result<ResolvedTransportConfig> {
|
||||||
|
ksp_logging_lib::trace!(target: crate::TRACING_TARGET, profile_id = profile.profile_id(), "mapping standard Transport Config profile");
|
||||||
let effective = profile.resolve_effective_environment_detailed(environment);
|
let effective = profile.resolve_effective_environment_detailed(environment);
|
||||||
let effective = match effective {
|
let effective = match effective {
|
||||||
std::result::Result::Ok(value) => value,
|
std::result::Result::Ok(value) => value,
|
||||||
@@ -180,12 +274,10 @@ fn resolve_transport_profile(profile: &crate::ResolvedConfigProfile, environment
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
if source.format_version != 1 {
|
|
||||||
return std::result::Result::Err(effective_error(profile, "effective Transport format_version is unsupported"));
|
|
||||||
}
|
|
||||||
if source.profile_id != profile.profile_id() {
|
if source.profile_id != profile.profile_id() {
|
||||||
return std::result::Result::Err(effective_error(profile, "effective Transport profile_id does not match the selected profile"));
|
return std::result::Result::Err(effective_error(profile, "effective Transport profile_id does not match the selected profile"));
|
||||||
}
|
}
|
||||||
|
let format_version = source.format_version;
|
||||||
let retry = ksp_onchain_transport_lib::HttpRetrySettings::new(
|
let retry = ksp_onchain_transport_lib::HttpRetrySettings::new(
|
||||||
source.retry.max_retries,
|
source.retry.max_retries,
|
||||||
std::time::Duration::from_millis(source.retry.initial_backoff_ms),
|
std::time::Duration::from_millis(source.retry.initial_backoff_ms),
|
||||||
@@ -197,14 +289,24 @@ fn resolve_transport_profile(profile: &crate::ResolvedConfigProfile, environment
|
|||||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
};
|
};
|
||||||
let settings = ksp_onchain_transport_lib::HttpTransportSettings::new(endpoints, retry);
|
let settings = ksp_onchain_transport_lib::HttpTransportSettings::new(endpoints, retry);
|
||||||
let validation = settings.validate();
|
if let std::result::Result::Err(error) = settings.validate() {
|
||||||
if let std::result::Result::Err(error) = validation {
|
return std::result::Result::Err(transport_contract_error(profile, "effective HTTP Transport settings fail the Transport runtime contract", &error));
|
||||||
return std::result::Result::Err(transport_contract_error(profile, "effective Transport settings fail the Transport runtime contract", &error));
|
|
||||||
}
|
}
|
||||||
|
let ws_settings = map_optional_ws_settings(format_version, source.ws_defaults, source.ws_endpoints, profile);
|
||||||
|
let ws_settings = match ws_settings {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
|
};
|
||||||
|
let ws_endpoint_count = match ws_settings.as_ref() {
|
||||||
|
std::option::Option::Some(value) => value.endpoints().len(),
|
||||||
|
std::option::Option::None => 0_usize,
|
||||||
|
};
|
||||||
ksp_logging_lib::debug!(
|
ksp_logging_lib::debug!(
|
||||||
target: crate::TRACING_TARGET,
|
target: crate::TRACING_TARGET,
|
||||||
profile_id = profile.profile_id(),
|
profile_id = profile.profile_id(),
|
||||||
endpoint_count = settings.endpoints().len(),
|
format_version,
|
||||||
|
http_endpoint_count = settings.endpoints().len(),
|
||||||
|
ws_endpoint_count,
|
||||||
"mapped standard Transport Config to runtime settings"
|
"mapped standard Transport Config to runtime settings"
|
||||||
);
|
);
|
||||||
return std::result::Result::Ok(ResolvedTransportConfig {
|
return std::result::Result::Ok(ResolvedTransportConfig {
|
||||||
@@ -214,9 +316,49 @@ fn resolve_transport_profile(profile: &crate::ResolvedConfigProfile, environment
|
|||||||
selection_source: profile.selection_source(),
|
selection_source: profile.selection_source(),
|
||||||
effective,
|
effective,
|
||||||
settings,
|
settings,
|
||||||
|
ws_settings,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn map_optional_ws_settings(
|
||||||
|
format_version: u32,
|
||||||
|
defaults: std::option::Option<EffectiveWsSessionSource>,
|
||||||
|
sources: std::option::Option<std::vec::Vec<EffectiveWsEndpointSource>>,
|
||||||
|
profile: &crate::ResolvedConfigProfile,
|
||||||
|
) -> ksp_core_lib::Result<std::option::Option<ksp_onchain_transport_lib::WsTransportSettings>> {
|
||||||
|
return match format_version {
|
||||||
|
1 => {
|
||||||
|
if defaults.is_some() || sources.is_some() {
|
||||||
|
std::result::Result::Err(effective_error(profile, "Transport V1 must remain HTTP-only"))
|
||||||
|
} else {
|
||||||
|
ksp_logging_lib::trace!(target: crate::TRACING_TARGET, profile_id = profile.profile_id(), "mapped backward-compatible Transport V1 without WebSocket settings");
|
||||||
|
std::result::Result::Ok(std::option::Option::None)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
2 => {
|
||||||
|
let defaults = match defaults {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return std::result::Result::Err(effective_error(profile, "Transport V2 requires ws_defaults")),
|
||||||
|
};
|
||||||
|
let sources = match sources {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return std::result::Result::Err(effective_error(profile, "Transport V2 profile requires ws_endpoints")),
|
||||||
|
};
|
||||||
|
let endpoints = map_ws_endpoints(sources, &defaults, profile);
|
||||||
|
let endpoints = match endpoints {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
|
};
|
||||||
|
let settings = ksp_onchain_transport_lib::WsTransportSettings::new(endpoints);
|
||||||
|
if let std::result::Result::Err(error) = settings.validate() {
|
||||||
|
return std::result::Result::Err(transport_contract_error(profile, "effective WebSocket Transport settings fail the Transport runtime contract", &error));
|
||||||
|
}
|
||||||
|
std::result::Result::Ok(std::option::Option::Some(settings))
|
||||||
|
},
|
||||||
|
_ => std::result::Result::Err(effective_error(profile, "effective Transport format_version is unsupported")),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
fn map_endpoints(
|
fn map_endpoints(
|
||||||
sources: std::vec::Vec<EffectiveEndpointSource>,
|
sources: std::vec::Vec<EffectiveEndpointSource>,
|
||||||
profile: &crate::ResolvedConfigProfile,
|
profile: &crate::ResolvedConfigProfile,
|
||||||
@@ -229,7 +371,7 @@ fn map_endpoints(
|
|||||||
std::result::Result::Ok(value) => value,
|
std::result::Result::Ok(value) => value,
|
||||||
std::result::Result::Err(error) => {
|
std::result::Result::Err(error) => {
|
||||||
return std::result::Result::Err(
|
return std::result::Result::Err(
|
||||||
transport_contract_error(profile, "effective endpoint URL is invalid", &error).with_context("endpoint_name", endpoint_name),
|
transport_contract_error(profile, "effective HTTP endpoint URL is invalid", &error).with_context("endpoint_name", endpoint_name),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -253,6 +395,171 @@ fn map_endpoints(
|
|||||||
return std::result::Result::Ok(endpoints);
|
return std::result::Result::Ok(endpoints);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn map_ws_endpoints(
|
||||||
|
sources: std::vec::Vec<EffectiveWsEndpointSource>,
|
||||||
|
defaults: &EffectiveWsSessionSource,
|
||||||
|
profile: &crate::ResolvedConfigProfile,
|
||||||
|
) -> ksp_core_lib::Result<std::vec::Vec<ksp_onchain_transport_lib::WsEndpointSettings>> {
|
||||||
|
let mut endpoints = std::vec::Vec::<ksp_onchain_transport_lib::WsEndpointSettings>::with_capacity(sources.len());
|
||||||
|
for source in sources {
|
||||||
|
let endpoint_name = source.name.clone();
|
||||||
|
let protocol = map_ws_protocol_kind(source.kind.as_str(), profile, endpoint_name.as_str());
|
||||||
|
let protocol = match protocol {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
|
};
|
||||||
|
let url = ksp_onchain_transport_lib::WsEndpointUrl::parse(source.url);
|
||||||
|
let url = match url {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => {
|
||||||
|
return std::result::Result::Err(
|
||||||
|
transport_contract_error(profile, "effective WebSocket endpoint URL is invalid", &error).with_context("endpoint_name", endpoint_name),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let session = map_ws_session_settings(defaults, source.session.as_ref(), profile, endpoint_name.as_str());
|
||||||
|
let session = match session {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
|
};
|
||||||
|
endpoints.push(ksp_onchain_transport_lib::WsEndpointSettings::new(
|
||||||
|
source.name,
|
||||||
|
source.enabled,
|
||||||
|
ksp_onchain_transport_lib::WsProviderName::new(source.provider),
|
||||||
|
ksp_onchain_transport_lib::WsClusterName::new(source.cluster),
|
||||||
|
protocol,
|
||||||
|
url,
|
||||||
|
session,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
return std::result::Result::Ok(endpoints);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn map_ws_protocol_kind(
|
||||||
|
value: &str,
|
||||||
|
profile: &crate::ResolvedConfigProfile,
|
||||||
|
endpoint_name: &str,
|
||||||
|
) -> ksp_core_lib::Result<ksp_onchain_transport_lib::WsProtocolKind> {
|
||||||
|
return match value {
|
||||||
|
"solana_standard" => std::result::Result::Ok(ksp_onchain_transport_lib::WsProtocolKind::SolanaStandard),
|
||||||
|
_ => std::result::Result::Err(
|
||||||
|
effective_error(profile, "effective WebSocket protocol kind is unsupported")
|
||||||
|
.with_context("endpoint_name", endpoint_name)
|
||||||
|
.with_context("ws_kind", value),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn map_ws_session_settings(
|
||||||
|
defaults: &EffectiveWsSessionSource,
|
||||||
|
overrides: std::option::Option<&EffectiveWsSessionOverrideSource>,
|
||||||
|
profile: &crate::ResolvedConfigProfile,
|
||||||
|
endpoint_name: &str,
|
||||||
|
) -> ksp_core_lib::Result<ksp_onchain_transport_lib::WsSessionSettings> {
|
||||||
|
let mut command_timeout_ms = defaults.command_timeout_ms;
|
||||||
|
let mut close_timeout_ms = defaults.close_timeout_ms;
|
||||||
|
let mut reconnect_max_retries = defaults.reconnect.max_retries;
|
||||||
|
let mut reconnect_initial_backoff_ms = defaults.reconnect.initial_backoff_ms;
|
||||||
|
let mut reconnect_max_backoff_ms = defaults.reconnect.max_backoff_ms;
|
||||||
|
let mut resubscribe_text = defaults.resubscribe.clone();
|
||||||
|
let mut command_queue_capacity = defaults.command_queue_capacity;
|
||||||
|
let mut notification_queue_capacity = defaults.notification_queue_capacity;
|
||||||
|
let mut max_active_subscriptions = defaults.max_active_subscriptions;
|
||||||
|
let mut max_pending_requests = defaults.max_pending_requests;
|
||||||
|
let mut max_message_size_bytes = defaults.max_message_size_bytes;
|
||||||
|
let mut max_frame_size_bytes = defaults.max_frame_size_bytes;
|
||||||
|
let mut max_write_buffer_size_bytes = defaults.max_write_buffer_size_bytes;
|
||||||
|
if let std::option::Option::Some(overrides) = overrides {
|
||||||
|
if let std::option::Option::Some(value) = overrides.command_timeout_ms {
|
||||||
|
command_timeout_ms = value;
|
||||||
|
}
|
||||||
|
if let std::option::Option::Some(value) = overrides.close_timeout_ms {
|
||||||
|
close_timeout_ms = value;
|
||||||
|
}
|
||||||
|
if let std::option::Option::Some(reconnect) = overrides.reconnect.as_ref() {
|
||||||
|
if let std::option::Option::Some(value) = reconnect.max_retries {
|
||||||
|
reconnect_max_retries = value;
|
||||||
|
}
|
||||||
|
if let std::option::Option::Some(value) = reconnect.initial_backoff_ms {
|
||||||
|
reconnect_initial_backoff_ms = value;
|
||||||
|
}
|
||||||
|
if let std::option::Option::Some(value) = reconnect.max_backoff_ms {
|
||||||
|
reconnect_max_backoff_ms = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let std::option::Option::Some(value) = overrides.resubscribe.as_ref() {
|
||||||
|
resubscribe_text = value.clone();
|
||||||
|
}
|
||||||
|
if let std::option::Option::Some(value) = overrides.command_queue_capacity {
|
||||||
|
command_queue_capacity = value;
|
||||||
|
}
|
||||||
|
if let std::option::Option::Some(value) = overrides.notification_queue_capacity {
|
||||||
|
notification_queue_capacity = value;
|
||||||
|
}
|
||||||
|
if let std::option::Option::Some(value) = overrides.max_active_subscriptions {
|
||||||
|
max_active_subscriptions = value;
|
||||||
|
}
|
||||||
|
if let std::option::Option::Some(value) = overrides.max_pending_requests {
|
||||||
|
max_pending_requests = value;
|
||||||
|
}
|
||||||
|
if let std::option::Option::Some(value) = overrides.max_message_size_bytes {
|
||||||
|
max_message_size_bytes = value;
|
||||||
|
}
|
||||||
|
if let std::option::Option::Some(value) = overrides.max_frame_size_bytes {
|
||||||
|
max_frame_size_bytes = value;
|
||||||
|
}
|
||||||
|
if let std::option::Option::Some(value) = overrides.max_write_buffer_size_bytes {
|
||||||
|
max_write_buffer_size_bytes = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let reconnect = ksp_onchain_transport_lib::WsReconnectSettings::new(
|
||||||
|
reconnect_max_retries,
|
||||||
|
std::time::Duration::from_millis(reconnect_initial_backoff_ms),
|
||||||
|
std::time::Duration::from_millis(reconnect_max_backoff_ms),
|
||||||
|
);
|
||||||
|
let resubscribe = map_ws_resubscribe_policy(resubscribe_text.as_str(), profile, endpoint_name);
|
||||||
|
let resubscribe = match resubscribe {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
|
};
|
||||||
|
let settings = ksp_onchain_transport_lib::WsSessionSettings::new(
|
||||||
|
std::time::Duration::from_millis(command_timeout_ms),
|
||||||
|
std::time::Duration::from_millis(close_timeout_ms),
|
||||||
|
reconnect,
|
||||||
|
resubscribe,
|
||||||
|
command_queue_capacity,
|
||||||
|
notification_queue_capacity,
|
||||||
|
max_active_subscriptions,
|
||||||
|
max_pending_requests,
|
||||||
|
max_message_size_bytes,
|
||||||
|
max_frame_size_bytes,
|
||||||
|
max_write_buffer_size_bytes,
|
||||||
|
);
|
||||||
|
if let std::result::Result::Err(error) = settings.validate() {
|
||||||
|
return std::result::Result::Err(
|
||||||
|
transport_contract_error(profile, "effective WebSocket session settings fail the Transport runtime contract", &error)
|
||||||
|
.with_context("endpoint_name", endpoint_name),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return std::result::Result::Ok(settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn map_ws_resubscribe_policy(
|
||||||
|
value: &str,
|
||||||
|
profile: &crate::ResolvedConfigProfile,
|
||||||
|
endpoint_name: &str,
|
||||||
|
) -> ksp_core_lib::Result<ksp_onchain_transport_lib::WsResubscribePolicy> {
|
||||||
|
return match value {
|
||||||
|
"never" => std::result::Result::Ok(ksp_onchain_transport_lib::WsResubscribePolicy::Never),
|
||||||
|
"active_subscriptions" => std::result::Result::Ok(ksp_onchain_transport_lib::WsResubscribePolicy::ActiveSubscriptions),
|
||||||
|
_ => std::result::Result::Err(
|
||||||
|
effective_error(profile, "effective WebSocket resubscribe policy is unsupported")
|
||||||
|
.with_context("endpoint_name", endpoint_name)
|
||||||
|
.with_context("resubscribe", value),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
fn map_roles(
|
fn map_roles(
|
||||||
sources: std::vec::Vec<EffectiveRoleSource>,
|
sources: std::vec::Vec<EffectiveRoleSource>,
|
||||||
profile: &crate::ResolvedConfigProfile,
|
profile: &crate::ResolvedConfigProfile,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-config-lib/tests/public_api.rs
|
// file: crates/ksp-config-lib/tests/public_api.rs
|
||||||
// version: 21
|
// version: 22
|
||||||
|
|
||||||
//! Integration tests for the public `ksp-config-lib` bootstrap, registry, JSON/profile/composite, environment-resolution, sensitivity,
|
//! Integration tests for the public `ksp-config-lib` bootstrap, registry, JSON/profile/composite, environment-resolution, sensitivity,
|
||||||
//! Logging/Transport adapters and management contracts.
|
//! Logging/Transport adapters and management contracts.
|
||||||
@@ -252,6 +252,9 @@ fn transport_adapter_contract_is_available_from_crate_root() {
|
|||||||
let _loader = ksp_config_lib::ConfigDocumentEngine::load_resolved_transport_config;
|
let _loader = ksp_config_lib::ConfigDocumentEngine::load_resolved_transport_config;
|
||||||
let _composite_loader = ksp_config_lib::ConfigDocumentEngine::resolve_transport_config_profile;
|
let _composite_loader = ksp_config_lib::ConfigDocumentEngine::resolve_transport_config_profile;
|
||||||
assert!(std::mem::size_of::<ksp_config_lib::ResolvedTransportConfig>() > 0);
|
assert!(std::mem::size_of::<ksp_config_lib::ResolvedTransportConfig>() > 0);
|
||||||
|
let _http_settings = ksp_config_lib::ResolvedTransportConfig::http_settings;
|
||||||
|
let _ws_settings = ksp_config_lib::ResolvedTransportConfig::ws_settings;
|
||||||
|
let _into_transport_settings = ksp_config_lib::ResolvedTransportConfig::into_transport_settings;
|
||||||
assert_eq!(ksp_config_lib::FILE_ID_STD_TRANSPORT, "cfg.std.transport");
|
assert_eq!(ksp_config_lib::FILE_ID_STD_TRANSPORT, "cfg.std.transport");
|
||||||
assert_eq!(ksp_config_lib::FILE_ID_SCHEMA_STD_TRANSPORT, "schema.std.transport");
|
assert_eq!(ksp_config_lib::FILE_ID_SCHEMA_STD_TRANSPORT, "schema.std.transport");
|
||||||
assert_eq!(ksp_config_lib::DEFAULT_STD_TRANSPORT_FILENAME, "std.transport.json");
|
assert_eq!(ksp_config_lib::DEFAULT_STD_TRANSPORT_FILENAME, "std.transport.json");
|
||||||
|
|||||||
@@ -1,10 +1,27 @@
|
|||||||
{
|
{
|
||||||
"format_version": 1,
|
"format_version": 2,
|
||||||
"retry": {
|
"retry": {
|
||||||
"max_retries": 4,
|
"max_retries": 4,
|
||||||
"initial_backoff_ms": 125,
|
"initial_backoff_ms": 125,
|
||||||
"max_backoff_ms": 2500
|
"max_backoff_ms": 2500
|
||||||
},
|
},
|
||||||
|
"ws_defaults": {
|
||||||
|
"command_timeout_ms": 8000,
|
||||||
|
"close_timeout_ms": 4000,
|
||||||
|
"reconnect": {
|
||||||
|
"max_retries": 6,
|
||||||
|
"initial_backoff_ms": 200,
|
||||||
|
"max_backoff_ms": 4000
|
||||||
|
},
|
||||||
|
"resubscribe": "active_subscriptions",
|
||||||
|
"command_queue_capacity": 64,
|
||||||
|
"notification_queue_capacity": 96,
|
||||||
|
"max_active_subscriptions": 256,
|
||||||
|
"max_pending_requests": 48,
|
||||||
|
"max_message_size_bytes": 33554432,
|
||||||
|
"max_frame_size_bytes": 8388608,
|
||||||
|
"max_write_buffer_size_bytes": 524288
|
||||||
|
},
|
||||||
"default_profile": "secret_test",
|
"default_profile": "secret_test",
|
||||||
"profiles": [
|
"profiles": [
|
||||||
{
|
{
|
||||||
@@ -23,7 +40,9 @@
|
|||||||
{
|
{
|
||||||
"role": "default",
|
"role": "default",
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"request_kinds": ["*"],
|
"request_kinds": [
|
||||||
|
"*"
|
||||||
|
],
|
||||||
"priority": 7,
|
"priority": 7,
|
||||||
"limits": {
|
"limits": {
|
||||||
"requests_per_second": 9,
|
"requests_per_second": 9,
|
||||||
@@ -34,6 +53,25 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"ws_endpoints": [
|
||||||
|
{
|
||||||
|
"name": "fixture_private_ws",
|
||||||
|
"enabled": true,
|
||||||
|
"provider": "fixture-provider",
|
||||||
|
"cluster": "fixture-cluster",
|
||||||
|
"kind": "solana_standard",
|
||||||
|
"url": "${KSP_SECRET_TRANSPORT_TEST_WS_URL:-wss://fallback.invalid}",
|
||||||
|
"session": {
|
||||||
|
"command_timeout_ms": 4500,
|
||||||
|
"reconnect": {
|
||||||
|
"max_retries": 7
|
||||||
|
},
|
||||||
|
"resubscribe": "never",
|
||||||
|
"notification_queue_capacity": 48,
|
||||||
|
"max_pending_requests": 24
|
||||||
|
}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
{
|
||||||
|
"format_version": 1,
|
||||||
|
"retry": {
|
||||||
|
"max_retries": 1,
|
||||||
|
"initial_backoff_ms": 90,
|
||||||
|
"max_backoff_ms": 900
|
||||||
|
},
|
||||||
|
"default_profile": "legacy_http",
|
||||||
|
"profiles": [
|
||||||
|
{
|
||||||
|
"profile_id": "legacy_http",
|
||||||
|
"endpoints": [
|
||||||
|
{
|
||||||
|
"name": "legacy_http",
|
||||||
|
"enabled": true,
|
||||||
|
"provider": "legacy-provider",
|
||||||
|
"cluster": "devnet",
|
||||||
|
"url": "https://legacy.invalid",
|
||||||
|
"connect_timeout_ms": 5000,
|
||||||
|
"request_timeout_ms": 2200,
|
||||||
|
"max_idle_connections_per_host": 2,
|
||||||
|
"roles": [
|
||||||
|
{
|
||||||
|
"role": "default",
|
||||||
|
"enabled": true,
|
||||||
|
"request_kinds": [
|
||||||
|
"*"
|
||||||
|
],
|
||||||
|
"priority": 10,
|
||||||
|
"limits": {
|
||||||
|
"requests_per_second": 2,
|
||||||
|
"burst_capacity": 3,
|
||||||
|
"max_concurrent_requests": 2,
|
||||||
|
"pause_after_rate_limit_ms": 250
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-config-lib/unit_tests/transport.rs
|
// file: crates/ksp-config-lib/unit_tests/transport.rs
|
||||||
// version: 2
|
// version: 3
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn fixture_transport_profile_maps_complete_runtime_contract() {
|
fn fixture_transport_profile_maps_complete_runtime_contract() {
|
||||||
@@ -41,6 +41,49 @@ fn fixture_transport_profile_maps_complete_runtime_contract() {
|
|||||||
assert_eq!(role.limits().burst_capacity().map(std::num::NonZeroU32::get), std::option::Option::Some(12));
|
assert_eq!(role.limits().burst_capacity().map(std::num::NonZeroU32::get), std::option::Option::Some(12));
|
||||||
assert_eq!(role.limits().max_concurrent_requests().map(std::num::NonZeroU32::get), std::option::Option::Some(4));
|
assert_eq!(role.limits().max_concurrent_requests().map(std::num::NonZeroU32::get), std::option::Option::Some(4));
|
||||||
assert_eq!(role.limits().pause_after_rate_limit(), std::option::Option::Some(std::time::Duration::from_millis(650)));
|
assert_eq!(role.limits().pause_after_rate_limit(), std::option::Option::Some(std::time::Duration::from_millis(650)));
|
||||||
|
let ws = resolved.ws_settings();
|
||||||
|
assert!(ws.is_some(), "V2 fixture should expose WebSocket settings");
|
||||||
|
if let std::option::Option::Some(ws) = ws {
|
||||||
|
assert_eq!(ws.endpoints().len(), 1);
|
||||||
|
let endpoint = &ws.endpoints()[0];
|
||||||
|
assert_eq!(endpoint.name(), "fixture_private_ws");
|
||||||
|
assert_eq!(endpoint.provider().as_str(), "fixture-provider");
|
||||||
|
assert_eq!(endpoint.cluster().as_str(), "fixture-cluster");
|
||||||
|
assert_eq!(endpoint.protocol(), ksp_onchain_transport_lib::WsProtocolKind::SolanaStandard);
|
||||||
|
assert_eq!(endpoint.url().as_str(), "wss://fallback.invalid");
|
||||||
|
assert_eq!(endpoint.session().command_timeout(), std::time::Duration::from_millis(4500));
|
||||||
|
assert_eq!(endpoint.session().close_timeout(), std::time::Duration::from_millis(4000));
|
||||||
|
assert_eq!(endpoint.session().reconnect().max_retries(), 7);
|
||||||
|
assert_eq!(endpoint.session().reconnect().initial_backoff(), std::time::Duration::from_millis(200));
|
||||||
|
assert_eq!(endpoint.session().reconnect().max_backoff(), std::time::Duration::from_millis(4000));
|
||||||
|
assert_eq!(endpoint.session().resubscribe(), ksp_onchain_transport_lib::WsResubscribePolicy::Never);
|
||||||
|
assert_eq!(endpoint.session().command_queue_capacity(), 64);
|
||||||
|
assert_eq!(endpoint.session().notification_queue_capacity(), 48);
|
||||||
|
assert_eq!(endpoint.session().max_active_subscriptions(), 256);
|
||||||
|
assert_eq!(endpoint.session().max_pending_requests(), 24);
|
||||||
|
assert_eq!(endpoint.session().max_message_size_bytes(), 33_554_432);
|
||||||
|
assert_eq!(endpoint.session().max_frame_size_bytes(), 8_388_608);
|
||||||
|
assert_eq!(endpoint.session().max_write_buffer_size_bytes(), 524_288);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn v1_transport_fixture_remains_backward_readable_and_http_only() {
|
||||||
|
let engine = v1_fixture_engine();
|
||||||
|
let engine = match engine {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
let environment = crate::ConfigEnvironment::from_maps(std::collections::BTreeMap::new(), std::collections::BTreeMap::new());
|
||||||
|
let resolved = engine.load_resolved_transport_config(std::option::Option::None, &environment);
|
||||||
|
assert!(resolved.is_ok(), "strict Transport V1 fixture should remain readable: {resolved:?}");
|
||||||
|
if let std::result::Result::Ok(resolved) = resolved {
|
||||||
|
assert_eq!(resolved.profile_id(), "legacy_http");
|
||||||
|
assert_eq!(resolved.settings().retry().max_retries(), 1);
|
||||||
|
assert_eq!(resolved.settings().endpoints().len(), 1);
|
||||||
|
assert_eq!(resolved.settings().endpoints()[0].url().as_str(), "https://legacy.invalid");
|
||||||
|
assert!(resolved.ws_settings().is_none(), "V1 must not invent WebSocket runtime settings");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -59,12 +102,23 @@ fn committed_transport_document_maps_default_and_explicit_profiles() {
|
|||||||
assert_eq!(default.profile_id(), "devnet_public");
|
assert_eq!(default.profile_id(), "devnet_public");
|
||||||
assert_eq!(default.settings().endpoints()[0].cluster().as_str(), "devnet");
|
assert_eq!(default.settings().endpoints()[0].cluster().as_str(), "devnet");
|
||||||
assert_eq!(default.settings().endpoints()[0].url().as_str(), "https://api.devnet.solana.com");
|
assert_eq!(default.settings().endpoints()[0].url().as_str(), "https://api.devnet.solana.com");
|
||||||
|
let ws = default.ws_settings();
|
||||||
|
assert!(ws.is_some(), "committed V2 Devnet profile should expose WebSocket settings");
|
||||||
|
if let std::option::Option::Some(ws) = ws {
|
||||||
|
assert_eq!(ws.endpoints()[0].url().as_str(), "wss://api.devnet.solana.com");
|
||||||
|
assert_eq!(ws.endpoints()[0].protocol(), ksp_onchain_transport_lib::WsProtocolKind::SolanaStandard);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if let std::result::Result::Ok(mainnet) = mainnet {
|
if let std::result::Result::Ok(mainnet) = mainnet {
|
||||||
assert_eq!(mainnet.profile_id(), "mainnet_public");
|
assert_eq!(mainnet.profile_id(), "mainnet_public");
|
||||||
assert_eq!(mainnet.selection_source(), crate::ConfigProfileSelectionSource::Explicit);
|
assert_eq!(mainnet.selection_source(), crate::ConfigProfileSelectionSource::Explicit);
|
||||||
assert_eq!(mainnet.settings().endpoints()[0].cluster().as_str(), "mainnet-beta");
|
assert_eq!(mainnet.settings().endpoints()[0].cluster().as_str(), "mainnet-beta");
|
||||||
assert_eq!(mainnet.settings().endpoints()[0].url().as_str(), "https://api.mainnet-beta.solana.com");
|
assert_eq!(mainnet.settings().endpoints()[0].url().as_str(), "https://api.mainnet-beta.solana.com");
|
||||||
|
let ws = mainnet.ws_settings();
|
||||||
|
assert!(ws.is_some(), "committed V2 Mainnet profile should expose WebSocket settings");
|
||||||
|
if let std::option::Option::Some(ws) = ws {
|
||||||
|
assert_eq!(ws.endpoints()[0].url().as_str(), "wss://api.mainnet-beta.solana.com");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,7 +138,9 @@ fn transport_profile_preserves_global_and_profile_origin() {
|
|||||||
assert!(profile.is_ok(), "committed Transport profile should resolve: {profile:?}");
|
assert!(profile.is_ok(), "committed Transport profile should resolve: {profile:?}");
|
||||||
if let std::result::Result::Ok(profile) = profile {
|
if let std::result::Result::Ok(profile) = profile {
|
||||||
assert_eq!(profile.origin("retry"), std::option::Option::Some(crate::ConfigValueOrigin::Global));
|
assert_eq!(profile.origin("retry"), std::option::Option::Some(crate::ConfigValueOrigin::Global));
|
||||||
|
assert_eq!(profile.origin("ws_defaults"), std::option::Option::Some(crate::ConfigValueOrigin::Global));
|
||||||
assert_eq!(profile.origin("endpoints"), std::option::Option::Some(crate::ConfigValueOrigin::Profile));
|
assert_eq!(profile.origin("endpoints"), std::option::Option::Some(crate::ConfigValueOrigin::Profile));
|
||||||
|
assert_eq!(profile.origin("ws_endpoints"), std::option::Option::Some(crate::ConfigValueOrigin::Profile));
|
||||||
assert_eq!(profile.origin("format_version"), std::option::Option::Some(crate::ConfigValueOrigin::Global));
|
assert_eq!(profile.origin("format_version"), std::option::Option::Some(crate::ConfigValueOrigin::Global));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -149,6 +205,36 @@ fn secret_transport_url_is_runtime_available_but_safe_projection_is_redacted() {
|
|||||||
assert!(debug.contains(crate::REDACTED_CONFIG_VALUE));
|
assert!(debug.contains(crate::REDACTED_CONFIG_VALUE));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn secret_websocket_url_is_runtime_available_but_safe_projection_is_redacted() {
|
||||||
|
let engine = fixture_engine();
|
||||||
|
let engine = match engine {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
let canary = "wss://user:pass@secret-provider.invalid/path?api-key=transport-ws-secret-canary";
|
||||||
|
let mut process = std::collections::BTreeMap::<String, String>::new();
|
||||||
|
process.insert("KSP_SECRET_TRANSPORT_TEST_WS_URL".to_owned(), canary.to_owned());
|
||||||
|
let environment = crate::ConfigEnvironment::from_maps(process, std::collections::BTreeMap::new());
|
||||||
|
let resolved = engine.load_resolved_transport_config(std::option::Option::None, &environment);
|
||||||
|
assert!(resolved.is_ok(), "secret WebSocket endpoint should map without being exposed: {resolved:?}");
|
||||||
|
let resolved = match resolved {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
let ws = resolved.ws_settings();
|
||||||
|
assert!(ws.is_some(), "V2 fixture should expose WebSocket settings");
|
||||||
|
if let std::option::Option::Some(ws) = ws {
|
||||||
|
assert_eq!(ws.endpoints()[0].url().as_str(), canary);
|
||||||
|
}
|
||||||
|
let safe_url = resolved.effective().safe_value().pointer("/ws_endpoints/0/url").and_then(serde_json::Value::as_str);
|
||||||
|
assert_eq!(safe_url, std::option::Option::Some(crate::REDACTED_CONFIG_VALUE));
|
||||||
|
let debug = format!("{resolved:?}");
|
||||||
|
assert!(!debug.contains("transport-ws-secret-canary"));
|
||||||
|
assert!(!debug.contains("user:pass"));
|
||||||
|
assert!(debug.contains(crate::REDACTED_CONFIG_VALUE));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn transport_secret_url_provenance_uses_process_and_process_beats_dotenv() {
|
fn transport_secret_url_provenance_uses_process_and_process_beats_dotenv() {
|
||||||
let engine = fixture_engine();
|
let engine = fixture_engine();
|
||||||
@@ -216,6 +302,22 @@ fn fixture_engine() -> ksp_core_lib::Result<crate::ConfigDocumentEngine> {
|
|||||||
return std::result::Result::Ok(crate::ConfigDocumentEngine::new(bootstrap, registry));
|
return std::result::Result::Ok(crate::ConfigDocumentEngine::new(bootstrap, registry));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn v1_fixture_engine() -> ksp_core_lib::Result<crate::ConfigDocumentEngine> {
|
||||||
|
let workspace = workspace_root();
|
||||||
|
let fixture_root = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("unit_tests/fixtures_v1");
|
||||||
|
let bootstrap = crate::ConfigBootstrapOptions::from_paths(fixture_root, workspace.join("config/schemas"));
|
||||||
|
let bootstrap = match bootstrap {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
|
};
|
||||||
|
let registry = crate::ConfigFileRegistry::defaults();
|
||||||
|
let registry = match registry {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
|
};
|
||||||
|
return std::result::Result::Ok(crate::ConfigDocumentEngine::new(bootstrap, registry));
|
||||||
|
}
|
||||||
|
|
||||||
fn committed_engine() -> ksp_core_lib::Result<crate::ConfigDocumentEngine> {
|
fn committed_engine() -> ksp_core_lib::Result<crate::ConfigDocumentEngine> {
|
||||||
let workspace = workspace_root();
|
let workspace = workspace_root();
|
||||||
let bootstrap = crate::ConfigBootstrapOptions::from_paths(workspace.join("config"), workspace.join("config/schemas"));
|
let bootstrap = crate::ConfigBootstrapOptions::from_paths(workspace.join("config"), workspace.join("config/schemas"));
|
||||||
|
|||||||
225
deltas/0.2.7/pre.003.md
Normal file
225
deltas/0.2.7/pre.003.md
Normal file
@@ -0,0 +1,225 @@
|
|||||||
|
<!-- file: deltas/0.2.7/pre.003.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Delta `0.2.7-pre.003` — `std.transport` V2 HTTP + WebSocket
|
||||||
|
|
||||||
|
## 1. Base requise
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.2.7-pre.002-fix.001 appliquée
|
||||||
|
workspace.package.version = 0.2.7-pre.2.fix.1
|
||||||
|
```
|
||||||
|
|
||||||
|
Le checkpoint opérateur reçu avant cette tranche est entièrement vert : `cargo fmt`, audit Python, `cargo check --workspace`, `cargo clippy --workspace --all-targets`, tests Transport ciblés et `cargo test --workspace`.
|
||||||
|
|
||||||
|
## 2. Signal technique
|
||||||
|
|
||||||
|
Cette prerelease non-fix modifie configuration exécutable, Rust et tests. Conformément à `VER-ID-009` :
|
||||||
|
|
||||||
|
```text
|
||||||
|
livraison = 0.2.7-pre.003
|
||||||
|
workspace.package.version = 0.2.7-pre.3
|
||||||
|
commit = v0.2.7-pre.003
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucun tag prerelease.
|
||||||
|
|
||||||
|
## 3. `std.transport` V2
|
||||||
|
|
||||||
|
Le document standard livré devient :
|
||||||
|
|
||||||
|
```text
|
||||||
|
format_version = 2
|
||||||
|
retry
|
||||||
|
ws_defaults
|
||||||
|
default_profile
|
||||||
|
profiles[]
|
||||||
|
profile_id
|
||||||
|
endpoints[]
|
||||||
|
ws_endpoints[]
|
||||||
|
kind
|
||||||
|
session?
|
||||||
|
```
|
||||||
|
|
||||||
|
Le HTTP existant reste inchangé dans `endpoints[]`. `ws_defaults` contient les defaults génériques de `WsSessionSettings`; `ws_endpoints[].session` peut surcharger seulement les paramètres génériques nécessaires à un endpoint.
|
||||||
|
|
||||||
|
Le discriminateur est obligatoire :
|
||||||
|
|
||||||
|
```text
|
||||||
|
kind = solana_standard
|
||||||
|
```
|
||||||
|
|
||||||
|
Toute autre famille est rejetée en `0.2.7`. Aucun paramètre Helius, LaserStream ou autre provider-specific n'est pré-implémenté.
|
||||||
|
|
||||||
|
## 4. Backward V1 strict
|
||||||
|
|
||||||
|
Le schema enregistré passe à :
|
||||||
|
|
||||||
|
```text
|
||||||
|
urn:ksp:schema:std.transport:v2
|
||||||
|
```
|
||||||
|
|
||||||
|
Il conserve deux branches strictes discriminées par `format_version` :
|
||||||
|
|
||||||
|
```text
|
||||||
|
V1 -> HTTP-only historique
|
||||||
|
V2 -> HTTP + ws_defaults + ws_endpoints
|
||||||
|
```
|
||||||
|
|
||||||
|
Le V1 n'est pas rendu compatible par un relâchement de `additionalProperties`. Une fixture V1 dédiée prouve le chemin historique.
|
||||||
|
|
||||||
|
Dans l'adapter :
|
||||||
|
|
||||||
|
```text
|
||||||
|
V1 -> HttpTransportSettings + ws_settings = None
|
||||||
|
V2 -> HttpTransportSettings + Some(WsTransportSettings)
|
||||||
|
```
|
||||||
|
|
||||||
|
`WsTransportSettings` conserve donc son invariant `pre.002` : il n'existe jamais comme faux conteneur vide.
|
||||||
|
|
||||||
|
## 5. Adapter Config -> Transport
|
||||||
|
|
||||||
|
`ResolvedTransportConfig` conserve `settings()` pour compatibilité HTTP et ajoute :
|
||||||
|
|
||||||
|
```text
|
||||||
|
http_settings()
|
||||||
|
ws_settings() -> Option<&WsTransportSettings>
|
||||||
|
into_transport_settings() -> (HttpTransportSettings, Option<WsTransportSettings>)
|
||||||
|
```
|
||||||
|
|
||||||
|
Le mapper V2 construit :
|
||||||
|
|
||||||
|
```text
|
||||||
|
WsProviderName
|
||||||
|
WsClusterName
|
||||||
|
WsProtocolKind::SolanaStandard
|
||||||
|
WsEndpointUrl
|
||||||
|
WsReconnectSettings
|
||||||
|
WsResubscribePolicy
|
||||||
|
WsSessionSettings
|
||||||
|
WsEndpointSettings
|
||||||
|
WsTransportSettings
|
||||||
|
```
|
||||||
|
|
||||||
|
La direction reste strictement `Config -> Transport`. Aucun import de Config n'est ajouté à `ksp-onchain-transport-lib`.
|
||||||
|
|
||||||
|
## 6. Overrides session
|
||||||
|
|
||||||
|
Les valeurs de `ws_defaults` correspondent aux defaults KSP matérialisés en `pre.002`. Un endpoint peut surcharger indépendamment :
|
||||||
|
|
||||||
|
```text
|
||||||
|
command_timeout_ms
|
||||||
|
close_timeout_ms
|
||||||
|
reconnect.{max_retries, initial_backoff_ms, max_backoff_ms}
|
||||||
|
resubscribe
|
||||||
|
command_queue_capacity
|
||||||
|
notification_queue_capacity
|
||||||
|
max_active_subscriptions
|
||||||
|
max_pending_requests
|
||||||
|
max_message_size_bytes
|
||||||
|
max_frame_size_bytes
|
||||||
|
max_write_buffer_size_bytes
|
||||||
|
```
|
||||||
|
|
||||||
|
La session résultante est toujours validée par `WsSessionSettings::validate()`.
|
||||||
|
|
||||||
|
## 7. Secrets et logging
|
||||||
|
|
||||||
|
Les URLs HTTP et WebSocket peuvent provenir de `KSP_SECRET_*`; les valeurs réelles restent disponibles au runtime mais la projection Config safe les redacted. Les erreurs d'adaptation ne recopient pas l'URL.
|
||||||
|
|
||||||
|
Le mapping Config utilise exclusivement `ksp-logging-lib` avec le `TRACING_TARGET` existant de `ksp-config-lib` :
|
||||||
|
|
||||||
|
```text
|
||||||
|
trace -> début mapping et chemin backward V1
|
||||||
|
debug -> version, compteurs HTTP/WS après validation
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucun `tracing` direct n'est ajouté.
|
||||||
|
|
||||||
|
## 8. Environment inventory
|
||||||
|
|
||||||
|
Ajouts `.env.example` :
|
||||||
|
|
||||||
|
```text
|
||||||
|
KSP_PUBLIC_SOLANA_DEVNET_WS_URL
|
||||||
|
KSP_PUBLIC_SOLANA_MAINNET_WS_URL
|
||||||
|
# KSP_SECRET_SOLANA_WS_URL
|
||||||
|
```
|
||||||
|
|
||||||
|
Les URLs provider privées restent des valeurs complètes gérées via `KSP_SECRET_*`.
|
||||||
|
|
||||||
|
## 9. Tests ajoutés/étendus
|
||||||
|
|
||||||
|
```text
|
||||||
|
V2 fixture HTTP + WS complète
|
||||||
|
ws_defaults + endpoint overrides
|
||||||
|
V1 strict toujours chargeable
|
||||||
|
V1 n'invente pas de WsTransportSettings vide
|
||||||
|
committed Devnet/Mainnet V2 mappe HTTP + WS
|
||||||
|
provenance ws_defaults globale et ws_endpoints profil
|
||||||
|
secret WebSocket URL disponible au runtime mais redacted en safe/Debug
|
||||||
|
public API canary pour les nouveaux accessors
|
||||||
|
```
|
||||||
|
|
||||||
|
## 10. Fichiers principaux modifiés
|
||||||
|
|
||||||
|
```text
|
||||||
|
Cargo.toml
|
||||||
|
.env.example
|
||||||
|
config/std.transport.json
|
||||||
|
config/examples/std.transport.example.json
|
||||||
|
config/schemas/std.transport.schema.json
|
||||||
|
crates/ksp-config-lib/src/transport.rs
|
||||||
|
crates/ksp-config-lib/src/lib.rs
|
||||||
|
crates/ksp-config-lib/src/registry.rs
|
||||||
|
crates/ksp-config-lib/unit_tests/transport.rs
|
||||||
|
crates/ksp-config-lib/unit_tests/fixtures/std.transport.json
|
||||||
|
crates/ksp-config-lib/tests/public_api.rs
|
||||||
|
crates/ksp-config-lib/README.md
|
||||||
|
crates/ksp-config-lib/USAGE.md
|
||||||
|
docs/plans/014-V0_2_7_ONCHAIN_WEBSOCKET_PLAN.md
|
||||||
|
docs/validation/010-V0_2_7_ONCHAIN_WEBSOCKET.md
|
||||||
|
```
|
||||||
|
|
||||||
|
Nouveau :
|
||||||
|
|
||||||
|
```text
|
||||||
|
crates/ksp-config-lib/unit_tests/fixtures_v1/std.transport.json
|
||||||
|
deltas/0.2.7/pre.003.md
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucune dépendance externe n'est ajoutée.
|
||||||
|
|
||||||
|
## 11. Validation de préparation
|
||||||
|
|
||||||
|
Le sandbox de génération ne fournit pas Cargo. Sont exécutés ici :
|
||||||
|
|
||||||
|
```text
|
||||||
|
python3 scripts/audit_rust_workspace_rules.py
|
||||||
|
validation JSON des documents/schema
|
||||||
|
inspection absence de tracing direct
|
||||||
|
inspection version workspace
|
||||||
|
inspection archive delta
|
||||||
|
```
|
||||||
|
|
||||||
|
Les gates compilés restent opérateur-only.
|
||||||
|
|
||||||
|
## 12. Gates opérateur avant commit
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo fmt --all
|
||||||
|
python3 scripts/audit_rust_workspace_rules.py
|
||||||
|
cargo check --workspace
|
||||||
|
cargo clippy --workspace --all-targets
|
||||||
|
cargo test -p ksp-config-lib
|
||||||
|
cargo test -p ksp-onchain-transport-lib
|
||||||
|
cargo test --workspace
|
||||||
|
```
|
||||||
|
|
||||||
|
Si le checkpoint est vert :
|
||||||
|
|
||||||
|
```text
|
||||||
|
commit = v0.2.7-pre.003
|
||||||
|
```
|
||||||
|
|
||||||
|
La tranche suivante est `0.2.7-pre.004` : dépendances WebSocket, actor physique, handshake/read/write, pending JSON-RPC et serveur local déterministe.
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
<!-- file: docs/plans/014-V0_2_7_ONCHAIN_WEBSOCKET_PLAN.md -->
|
<!-- file: docs/plans/014-V0_2_7_ONCHAIN_WEBSOCKET_PLAN.md -->
|
||||||
<!-- version: 4 -->
|
<!-- version: 5 -->
|
||||||
|
|
||||||
# Plan `0.2.7` — WebSocket Solana standard
|
# Plan `0.2.7` — WebSocket Solana standard
|
||||||
|
|
||||||
> **Statut : actif, `0.2.7-pre.002`.** Le gate `pre.001` est clos. `pre.002` matérialise les settings WebSocket Transport, la redaction URL, le discriminateur de protocole, les identités locales, les états lifecycle et les snapshots sûrs. Le socket physique reste différé à `pre.004`.
|
> **Statut : actif, `0.2.7-pre.003`.** Les gates `pre.001` et `pre.002` sont clos. `pre.003` matérialise `std.transport` V2 HTTP + WebSocket, la lecture backward V1 HTTP-only et l'adapter `Config -> WsTransportSettings`. Le socket physique reste différé à `pre.004`.
|
||||||
|
|
||||||
## 1. Objet et base vérifiée
|
## 1. Objet et base vérifiée
|
||||||
|
|
||||||
@@ -110,9 +110,9 @@ Aucun gate Cargo n'est déclaré réussi. Une validation opérateur est obligato
|
|||||||
|
|
||||||
La surface WebSocket doit être additive : aucune renormalisation invasive des types `Http*` n'est requise dans `0.2.7`.
|
La surface WebSocket doit être additive : aucune renormalisation invasive des types `Http*` n'est requise dans `0.2.7`.
|
||||||
|
|
||||||
### 4.2 Config actuel est explicitement HTTP V1
|
### 4.2 Config hérité et évolution V2
|
||||||
|
|
||||||
`config/std.transport.json`, son schema et `ksp-config-lib/src/transport.rs` décrivent un document V1 HTTP :
|
À l’ouverture de `0.2.7`, `config/std.transport.json`, son schema et `ksp-config-lib/src/transport.rs` décrivaient un document V1 HTTP :
|
||||||
|
|
||||||
```text
|
```text
|
||||||
format_version = 1
|
format_version = 1
|
||||||
@@ -124,7 +124,7 @@ format_version != 1 rejeté
|
|||||||
|
|
||||||
Décision : **ne pas ajouter silencieusement des clés WebSocket au V1**.
|
Décision : **ne pas ajouter silencieusement des clés WebSocket au V1**.
|
||||||
|
|
||||||
`0.2.7` fera évoluer le même composant standard `std.transport` vers un **format V2 explicitement HTTP + WebSocket**, tout en conservant la lecture du V1 HTTP-only pour compatibilité. Config reste propriétaire du parsing/résolution et adapte ensuite vers les settings publics Transport.
|
`pre.003` fait évoluer le même composant standard `std.transport` vers un **format V2 explicitement HTTP + WebSocket**, tout en conservant la lecture du V1 HTTP-only pour compatibilité. Config reste propriétaire du parsing/résolution et adapte ensuite vers les settings publics Transport.
|
||||||
|
|
||||||
Shape cible minimale :
|
Shape cible minimale :
|
||||||
|
|
||||||
@@ -146,7 +146,21 @@ Décision d'extensibilité : **le conteneur V2 ne doit pas supposer qu'il n'exis
|
|||||||
|
|
||||||
Cette préparation ne promet aucune option Helius dans `0.2.7`. L'audit provider-specific de la release suivante décidera la relation exacte entre Helius Enhanced WebSocket, LaserStream et le moteur standard avant d'ajouter des variantes/configurations concrètes.
|
Cette préparation ne promet aucune option Helius dans `0.2.7`. L'audit provider-specific de la release suivante décidera la relation exacte entre Helius Enhanced WebSocket, LaserStream et le moteur standard avant d'ajouter des variantes/configurations concrètes.
|
||||||
|
|
||||||
Le V1 chargé sous `0.2.7` produit une configuration HTTP valide et une collection WebSocket vide. Le V2 devient le format livré par les fixtures Config dès la tranche qui matérialise le WebSocket.
|
Le V1 chargé sous `0.2.7` produit une configuration HTTP valide et aucune instance `WsTransportSettings`. Le V2 devient le format livré par les fixtures Config dès la tranche qui matérialise le WebSocket.
|
||||||
|
|
||||||
|
Checkpoint `pre.003` matérialisé :
|
||||||
|
|
||||||
|
```text
|
||||||
|
std.transport schema id = urn:ksp:schema:std.transport:v2
|
||||||
|
V1 strict HTTP-only -> toujours accepté
|
||||||
|
V2 strict HTTP + WS -> format livré
|
||||||
|
ws_defaults -> session defaults génériques
|
||||||
|
ws_endpoints[].session -> overrides génériques optionnels
|
||||||
|
ws_endpoints[].kind -> solana_standard uniquement en 0.2.7
|
||||||
|
ResolvedTransportConfig -> HTTP toujours présent + WS Option pour backward V1
|
||||||
|
```
|
||||||
|
|
||||||
|
Le schema V2 utilise deux branches strictes discriminées par `format_version`; la compatibilité V1 n'est donc pas obtenue en relâchant `additionalProperties`. Les overrides endpoint ne contiennent que des paramètres génériques de `WsSessionSettings`, jamais des options Helius/provider-specific.
|
||||||
|
|
||||||
## 5. Audit historique bot3
|
## 5. Audit historique bot3
|
||||||
|
|
||||||
@@ -853,7 +867,7 @@ L'inventaire officiel n'impose que 9 familles de subscriptions, mais le lifecycl
|
|||||||
```text
|
```text
|
||||||
pre.001 audit interne/externe + matrice 18 méthodes + bot3 + dependencies + threat model + plan/sizing
|
pre.001 audit interne/externe + matrice 18 méthodes + bot3 + dependencies + threat model + plan/sizing
|
||||||
pre.002 DONE — settings WS Transport + URL redaction + IDs/states/snapshots + tests de settings
|
pre.002 DONE — settings WS Transport + URL redaction + IDs/states/snapshots + tests de settings
|
||||||
pre.003 std.transport V2 HTTP+WS + backward V1 + discriminateur WS + schema/fixtures + Config -> WsTransportSettings
|
pre.003 DONE — std.transport V2 HTTP+WS + backward V1 + discriminateur WS + schema/fixtures + Config -> WsTransportSettings
|
||||||
pre.004 deps tokio-tungstenite/futures-util + actor physique + handshake/read/write + pending JSON-RPC + serveur local
|
pre.004 deps tokio-tungstenite/futures-util + actor physique + handshake/read/write + pending JSON-RPC + serveur local
|
||||||
pre.005 limites frame/message/request + control frames + cancellation/close/shutdown + adversarial socket tests
|
pre.005 limites frame/message/request + control frames + cancellation/close/shutdown + adversarial socket tests
|
||||||
pre.006 registry subscriptions + IDs locaux + generic subscribe/unsubscribe engine + channels typed bounded
|
pre.006 registry subscriptions + IDs locaux + generic subscribe/unsubscribe engine + channels typed bounded
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<!-- file: docs/validation/010-V0_2_7_ONCHAIN_WEBSOCKET.md -->
|
<!-- file: docs/validation/010-V0_2_7_ONCHAIN_WEBSOCKET.md -->
|
||||||
<!-- version: 4 -->
|
<!-- version: 5 -->
|
||||||
|
|
||||||
# Validation `0.2.7` — WebSocket Solana standard
|
# Validation `0.2.7` — WebSocket Solana standard
|
||||||
|
|
||||||
> **Statut : matrice active, `0.2.7-pre.002`.** Le gate normatif `pre.001` est clos ; les settings, identités, états lifecycle et snapshots sûrs sont maintenant matérialisés. Les preuves runtime socket/subscription restent ouvertes.
|
> **Statut : matrice active, `0.2.7-pre.003`.** Les settings/lifecycle `pre.002` et la composition Config V2 `pre.003` sont matérialisés. Les preuves runtime socket/subscription restent ouvertes.
|
||||||
|
|
||||||
## 1. Baseline normative
|
## 1. Baseline normative
|
||||||
|
|
||||||
@@ -253,7 +253,7 @@ Aucune de ces dependencies n'est ajoutée par `pre.001`; le graphe Cargo stable
|
|||||||
|
|
||||||
## 9. Config compliance initiale
|
## 9. Config compliance initiale
|
||||||
|
|
||||||
V1 actuel : HTTP-only strict. Décision :
|
V1 hérité : HTTP-only strict. Contrat de compatibilité :
|
||||||
|
|
||||||
```text
|
```text
|
||||||
V1 -> support de lecture conservé, WS vide
|
V1 -> support de lecture conservé, WS vide
|
||||||
@@ -266,7 +266,24 @@ Transport -X-> Config
|
|||||||
|
|
||||||
Le type Transport correspondant au discriminateur est prévu `#[non_exhaustive]`. L'objectif est de pouvoir ajouter ultérieurement une famille telle que Helius Enhanced WebSocket sans créer un nouveau conteneur Config ni injecter des options provider-specific dans les settings Solana standard. Toute valeur inconnue reste explicitement rejetée en `0.2.7`; aucun paramètre Helius n'est implémenté par anticipation.
|
Le type Transport correspondant au discriminateur est prévu `#[non_exhaustive]`. L'objectif est de pouvoir ajouter ultérieurement une famille telle que Helius Enhanced WebSocket sans créer un nouveau conteneur Config ni injecter des options provider-specific dans les settings Solana standard. Toute valeur inconnue reste explicitement rejetée en `0.2.7`; aucun paramètre Helius n'est implémenté par anticipation.
|
||||||
|
|
||||||
La schema V1 n'est pas assouplie. Une schema V2 explicite remplace la fixture standard au moment où l'adapter est matérialisé.
|
`pre.003` remplace le schema enregistré par `urn:ksp:schema:std.transport:v2`, avec deux branches strictes : V1 HTTP-only et V2 HTTP + WebSocket. La compatibilité V1 ne relâche donc ni `additionalProperties`, ni la shape historique.
|
||||||
|
|
||||||
|
Preuves `pre.003` :
|
||||||
|
|
||||||
|
```text
|
||||||
|
config/std.transport.json format_version = 2
|
||||||
|
config/examples/std.transport.example.json format_version = 2
|
||||||
|
V1 fixture dédiée load + HTTP mapping OK attendu
|
||||||
|
V1 ws_settings = None
|
||||||
|
V2 ws_settings = Some(validated)
|
||||||
|
ws_defaults globals
|
||||||
|
profiles[].ws_endpoints profile-local
|
||||||
|
ws_endpoints[].kind enum schema solana_standard
|
||||||
|
ws_endpoints[].session overrides génériques optionnels
|
||||||
|
Config -> Transport seule direction de dépendance
|
||||||
|
```
|
||||||
|
|
||||||
|
`ResolvedTransportConfig::settings()` reste l'accesseur HTTP historique. `http_settings()` l'explicite et `ws_settings()` expose `Option<&WsTransportSettings>` afin que le V1 backward ne force jamais un faux `WsTransportSettings` vide. `into_transport_settings()` permet de consommer les deux contrats ensemble.
|
||||||
|
|
||||||
## 9.1 Checkpoint settings/lifecycle `pre.002`
|
## 9.1 Checkpoint settings/lifecycle `pre.002`
|
||||||
|
|
||||||
@@ -359,7 +376,7 @@ N subscriptions same session prouvé
|
|||||||
reconnect/resubscribe/backpressure/shutdown gates verts
|
reconnect/resubscribe/backpressure/shutdown gates verts
|
||||||
unstable warnings centralisés
|
unstable warnings centralisés
|
||||||
HTTP 52+14 non régressé
|
HTTP 52+14 non régressé
|
||||||
Config V1 backward + V2 WS validés
|
Config V1 backward + V2 WS validés (**Done `pre.003`**, compilation opérateur requise)
|
||||||
smoke live opt-in documenté
|
smoke live opt-in documenté
|
||||||
cargo tree inspecté
|
cargo tree inspecté
|
||||||
cargo test --workspace vert
|
cargo test --workspace vert
|
||||||
|
|||||||
Reference in New Issue
Block a user