diff --git a/contextvm/README.md b/contextvm/README.md index cbc7c42a5c..3026030935 100644 --- a/contextvm/README.md +++ b/contextvm/README.md @@ -22,20 +22,38 @@ named failures rather than as silent divergence. | Spec | Status | Implemented in | | ---- | ------ | -------------- | -| Core draft spec | Draft | `core/`, `jsonrpc/`, `transport/` | -| CEP-4 Encryption | Final | `crypto/CvmGiftWrap` | -| CEP-6 Public Announcements | Final | `discovery/ServerAnnouncement` | -| CEP-8 Pricing and Payment | Draft | `payment/` | -| CEP-15 Common Tool Schemas | Draft | `schema/CommonToolSchema` | -| CEP-16 Client Pubkey Injection | Final | `fixture/` (server role) | -| CEP-17 Relay List Metadata | Draft | `discovery/ServerRelay` | -| CEP-19 Ephemeral Gift Wraps | Draft | `crypto/CvmGiftWrap` | -| CEP-21 PMI Recommendations | Draft | `payment/Pmi` | -| CEP-22 Oversized Transfer | Draft | `transfer/oversized/` | -| CEP-23 Server Profile Metadata | Draft | `discovery/` (kind 0 via quartz) | -| CEP-24 Server Reviews | Draft | `discovery/ServerReview` | -| CEP-35 Stateless Discovery | Draft | `discovery/SessionDiscovery` | -| CEP-41 Open Streams | Draft | `transfer/stream/` | +| Core draft spec | Draft | `core/`, `jsonrpc/`, `transport/`, `mcp/` | +| CEP-4 Encryption | Final | `cep04Encryption/CvmGiftWrap` | +| CEP-6 Public Announcements | Final | `cep06Announcements/` | +| CEP-8 Pricing and Payment | Draft | `cep08Payments/` | +| CEP-15 Common Tool Schemas | Draft | `cep15CommonSchemas/CommonToolSchema` | +| CEP-16 Client Pubkey Injection | Final | `mcp/McpMethods` (the `_meta` key), `fixture/` (server role) | +| CEP-17 Relay List Metadata | Draft | `cep17RelayList/ServerRelay` | +| CEP-19 Ephemeral Gift Wraps | Draft | `cep04Encryption/CvmGiftWrap` | +| CEP-21 PMI Recommendations | Draft | `cep08Payments/PaymentSession` | +| CEP-22 Oversized Transfer | Draft | `cep22OversizedTransfer/` | +| CEP-23 Server Profile Metadata | Draft | `cep06Announcements/DiscoverySurface` (kind 0 via quartz) | +| CEP-24 Server Reviews | Draft | `cep24Reviews/ServerReview` | +| CEP-35 Stateless Discovery | Draft | `cep35Discovery/SessionDiscovery` | +| CEP-41 Open Streams | Draft | `cep41OpenStreams/` | + +### Why the packages are named this way + +Each CEP gets its own `cepNNName/` package, the way `quartz` uses `nipXX` and +`marmot` uses `mipXX`: a spec number in the path is what makes "is this rule +implemented, and where" answerable without grep. Four placements are judgment +calls rather than mechanics: + +- **CEP-19 lives in `cep04Encryption/`.** Choosing the wrap kind (21059 with the + 1059 fallback) and building the wrap are one negotiation inside + `CvmGiftWrap`; a separate package would split a single method from its caller. +- **CEP-16 has no package.** It is a `_meta` key name plus the server-side + obligation to inject it — a key constant and fixture behaviour, not a + subsystem. +- **`transfer/ProgressEnvelope`** stays cross-cutting because CEP-22 and CEP-41 + share that framing; it is the `marmot/foundation/` analogue. +- **`core/`, `jsonrpc/`, `transport/`, `mcp/`** keep names because the core draft + spec is not a CEP and has no number to carry. RFC 8785 (JCS), required by CEP-8 and CEP-15, lives in `quartz/…/utils/jcs/JsonCanonicalization.kt` — it is a generic primitive, not a diff --git a/contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/crypto/CvmGiftWrap.kt b/contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/cep04Encryption/CvmGiftWrap.kt similarity index 99% rename from contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/crypto/CvmGiftWrap.kt rename to contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/cep04Encryption/CvmGiftWrap.kt index 4cf2402b23..5d07e61cdd 100644 --- a/contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/crypto/CvmGiftWrap.kt +++ b/contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/cep04Encryption/CvmGiftWrap.kt @@ -18,7 +18,7 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.contextvm.crypto +package com.vitorpamplona.contextvm.cep04Encryption import com.vitorpamplona.contextvm.core.CvmKinds import com.vitorpamplona.contextvm.core.CvmTags diff --git a/contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/cep06Announcements/DiscoverySurface.kt b/contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/cep06Announcements/DiscoverySurface.kt new file mode 100644 index 0000000000..cfdbb367db --- /dev/null +++ b/contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/cep06Announcements/DiscoverySurface.kt @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.contextvm.cep06Announcements + +import com.vitorpamplona.contextvm.core.CvmTags +import com.vitorpamplona.quartz.nip01Core.core.Tag + +/** + * What a peer told us about itself. + * + * CEP-35 makes this a *session* concept rather than an announcement one: the + * same tag vocabulary arrives either on a public announcement (CEP-6) or on the + * first direct message of a session, and both are treated as equivalent. + */ +data class DiscoverySurface( + val name: String? = null, + val about: String? = null, + val picture: String? = null, + val website: String? = null, + val supportsEncryption: Boolean = false, + val supportsEphemeralEncryption: Boolean = false, + val supportsOversizedTransfer: Boolean = false, + val supportsOpenStream: Boolean = false, + /** + * Everything else, routing excluded. + * + * CEP-35 requires unknown discovery tags to be preserved rather than + * discarded, so custom protocols can build on the same exchange. Keeping + * them reachable is what makes that work. + */ + val unknownTags: List = emptyList(), +) { + /** Raw access for a caller that understands a tag this version does not. */ + fun rawTag(name: String): Tag? = unknownTags.firstOrNull { it.isNotEmpty() && it[0] == name } + + companion object { + private val KNOWN = + setOf( + CvmTags.NAME, + CvmTags.ABOUT, + CvmTags.PICTURE, + CvmTags.WEBSITE, + CvmTags.SUPPORT_ENCRYPTION, + CvmTags.SUPPORT_ENCRYPTION_EPHEMERAL, + CvmTags.SUPPORT_OVERSIZED_TRANSFER, + CvmTags.SUPPORT_OPEN_STREAM, + ) + + fun parse(tags: Array): DiscoverySurface { + fun value(name: String) = tags.firstOrNull { it.size >= 2 && it[0] == name }?.get(1) + + return DiscoverySurface( + name = value(CvmTags.NAME), + about = value(CvmTags.ABOUT), + picture = value(CvmTags.PICTURE), + website = value(CvmTags.WEBSITE), + supportsEncryption = CvmTags.hasFlag(tags, CvmTags.SUPPORT_ENCRYPTION), + supportsEphemeralEncryption = CvmTags.hasFlag(tags, CvmTags.SUPPORT_ENCRYPTION_EPHEMERAL), + supportsOversizedTransfer = CvmTags.hasFlag(tags, CvmTags.SUPPORT_OVERSIZED_TRANSFER), + supportsOpenStream = CvmTags.hasFlag(tags, CvmTags.SUPPORT_OPEN_STREAM), + unknownTags = + tags.filter { + it.isNotEmpty() && it[0] !in KNOWN && !CvmTags.isRouting(it[0]) + }, + ) + } + } +} diff --git a/contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/cep06Announcements/ServerAnnouncement.kt b/contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/cep06Announcements/ServerAnnouncement.kt new file mode 100644 index 0000000000..81e2e6292c --- /dev/null +++ b/contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/cep06Announcements/ServerAnnouncement.kt @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.contextvm.cep06Announcements + +import com.vitorpamplona.contextvm.core.CvmKinds +import com.vitorpamplona.quartz.nip01Core.core.Event +import com.vitorpamplona.quartz.nip01Core.core.HexKey +import com.vitorpamplona.quartz.nip01Core.core.Kind + +/** + * A CEP-6 announcement. + * + * `content` is the stringified result of the matching MCP call — the initialize + * result for a server announcement, a list result for the others — so it is left + * as text for the caller to decode with the same codec it uses on the wire. + */ +data class ServerAnnouncement( + val kind: Kind, + val pubKey: HexKey, + val createdAt: Long, + val content: String, + val discovery: DiscoverySurface, +) { + companion object { + fun parseOrNull(event: Event): ServerAnnouncement? { + if (event.kind !in CvmKinds.ANNOUNCEMENTS) return null + return ServerAnnouncement( + kind = event.kind, + pubKey = event.pubKey, + createdAt = event.createdAt, + content = event.content, + discovery = DiscoverySurface.parse(event.tags), + ) + } + + /** + * Keeps the newest announcement per `(kind, pubkey)`. + * + * These kinds are replaceable, so an older event arriving late from a + * lagging relay must not overwrite a newer one already held. + */ + fun latestPerKind(events: List): Map = + events + .mapNotNull { parseOrNull(it) } + .groupBy { it.kind } + .mapValues { (_, list) -> list.maxBy { it.createdAt } } + } +} diff --git a/contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/payment/CanonicalInvocation.kt b/contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/cep08Payments/CanonicalInvocation.kt similarity index 98% rename from contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/payment/CanonicalInvocation.kt rename to contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/cep08Payments/CanonicalInvocation.kt index 75e168d17a..57a077db73 100644 --- a/contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/payment/CanonicalInvocation.kt +++ b/contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/cep08Payments/CanonicalInvocation.kt @@ -18,7 +18,7 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.contextvm.payment +package com.vitorpamplona.contextvm.cep08Payments import com.vitorpamplona.contextvm.json.toPlainJson import com.vitorpamplona.contextvm.jsonrpc.JsonRpcRequest diff --git a/contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/payment/PaymentMessages.kt b/contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/cep08Payments/PaymentMessages.kt similarity index 99% rename from contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/payment/PaymentMessages.kt rename to contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/cep08Payments/PaymentMessages.kt index e81fffec3e..c877334fc2 100644 --- a/contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/payment/PaymentMessages.kt +++ b/contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/cep08Payments/PaymentMessages.kt @@ -18,7 +18,7 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.contextvm.payment +package com.vitorpamplona.contextvm.cep08Payments import com.vitorpamplona.contextvm.jsonrpc.JsonRpcError import com.vitorpamplona.contextvm.jsonrpc.JsonRpcNotification diff --git a/contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/payment/PaymentSession.kt b/contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/cep08Payments/PaymentSession.kt similarity index 99% rename from contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/payment/PaymentSession.kt rename to contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/cep08Payments/PaymentSession.kt index ad08dfacd7..a3b1117feb 100644 --- a/contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/payment/PaymentSession.kt +++ b/contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/cep08Payments/PaymentSession.kt @@ -18,7 +18,7 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.contextvm.payment +package com.vitorpamplona.contextvm.cep08Payments import com.vitorpamplona.quartz.nip01Core.core.Tag diff --git a/contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/payment/PaymentTags.kt b/contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/cep08Payments/PaymentTags.kt similarity index 99% rename from contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/payment/PaymentTags.kt rename to contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/cep08Payments/PaymentTags.kt index 85e36101dc..03f8e90778 100644 --- a/contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/payment/PaymentTags.kt +++ b/contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/cep08Payments/PaymentTags.kt @@ -18,7 +18,7 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.contextvm.payment +package com.vitorpamplona.contextvm.cep08Payments import com.vitorpamplona.contextvm.core.CvmTags import com.vitorpamplona.quartz.nip01Core.core.Tag diff --git a/contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/schema/CommonToolSchema.kt b/contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/cep15CommonSchemas/CommonToolSchema.kt similarity index 99% rename from contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/schema/CommonToolSchema.kt rename to contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/cep15CommonSchemas/CommonToolSchema.kt index 356bdef01f..f031f0cb28 100644 --- a/contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/schema/CommonToolSchema.kt +++ b/contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/cep15CommonSchemas/CommonToolSchema.kt @@ -18,7 +18,7 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.contextvm.schema +package com.vitorpamplona.contextvm.cep15CommonSchemas import com.vitorpamplona.contextvm.core.CvmTags import com.vitorpamplona.contextvm.json.toPlainJson diff --git a/contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/cep17RelayList/ServerRelay.kt b/contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/cep17RelayList/ServerRelay.kt new file mode 100644 index 0000000000..e37f03b418 --- /dev/null +++ b/contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/cep17RelayList/ServerRelay.kt @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.contextvm.cep17RelayList + +import com.vitorpamplona.contextvm.core.CvmKinds +import com.vitorpamplona.contextvm.core.CvmTags +import com.vitorpamplona.quartz.nip01Core.core.Event + +/** + * A relay a server is reachable on (CEP-17, NIP-65 kind 10002). + * + * The ContextVM profile publishes unmarked tags, meaning the relay serves both + * directions. Markers are honoured when present but are not the norm here. + */ +data class ServerRelay( + val url: String, + val read: Boolean = true, + val write: Boolean = true, +) { + companion object { + const val READ = "read" + const val WRITE = "write" + + fun parseAll(event: Event): List { + if (event.kind != CvmKinds.RELAY_LIST) return emptyList() + return event.tags + .filter { it.size >= 2 && it[0] == CvmTags.RELAY && it[1].isNotBlank() } + .map { tag -> + when (tag.getOrNull(2)) { + READ -> ServerRelay(tag[1], read = true, write = false) + WRITE -> ServerRelay(tag[1], read = false, write = true) + // Unmarked is the recommended ContextVM profile: the + // relay is usable for both publishing and subscribing. + else -> ServerRelay(tag[1]) + } + } + } + + /** + * Relays usable for a full request/response exchange. + * + * A read-only or write-only relay cannot carry both halves, and since + * kind 25910 is ephemeral there is no fetching a response later from + * somewhere else. + */ + fun operational(relays: List) = relays.filter { it.read && it.write } + } +} diff --git a/contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/transfer/oversized/OversizedFrame.kt b/contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/cep22OversizedTransfer/OversizedFrame.kt similarity index 99% rename from contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/transfer/oversized/OversizedFrame.kt rename to contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/cep22OversizedTransfer/OversizedFrame.kt index 729fa821be..7146d433ed 100644 --- a/contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/transfer/oversized/OversizedFrame.kt +++ b/contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/cep22OversizedTransfer/OversizedFrame.kt @@ -18,7 +18,7 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.contextvm.transfer.oversized +package com.vitorpamplona.contextvm.cep22OversizedTransfer import com.vitorpamplona.contextvm.transfer.ProgressEnvelope import com.vitorpamplona.contextvm.transfer.ProgressEnvelope.Companion.asLongOrNull diff --git a/contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/transfer/oversized/OversizedTransferReceiver.kt b/contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/cep22OversizedTransfer/OversizedTransferReceiver.kt similarity index 99% rename from contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/transfer/oversized/OversizedTransferReceiver.kt rename to contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/cep22OversizedTransfer/OversizedTransferReceiver.kt index 4ad969817f..592c1913f6 100644 --- a/contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/transfer/oversized/OversizedTransferReceiver.kt +++ b/contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/cep22OversizedTransfer/OversizedTransferReceiver.kt @@ -18,7 +18,7 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.contextvm.transfer.oversized +package com.vitorpamplona.contextvm.cep22OversizedTransfer import com.vitorpamplona.contextvm.jsonrpc.JsonRpcCodec import com.vitorpamplona.contextvm.jsonrpc.JsonRpcMessage diff --git a/contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/transfer/oversized/OversizedTransferSender.kt b/contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/cep22OversizedTransfer/OversizedTransferSender.kt similarity index 98% rename from contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/transfer/oversized/OversizedTransferSender.kt rename to contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/cep22OversizedTransfer/OversizedTransferSender.kt index 066834d585..25bf5aad73 100644 --- a/contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/transfer/oversized/OversizedTransferSender.kt +++ b/contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/cep22OversizedTransfer/OversizedTransferSender.kt @@ -18,7 +18,7 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.contextvm.transfer.oversized +package com.vitorpamplona.contextvm.cep22OversizedTransfer import com.vitorpamplona.contextvm.transfer.ProgressToken import com.vitorpamplona.quartz.nip01Core.core.toHexKey diff --git a/contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/cep24Reviews/ServerReview.kt b/contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/cep24Reviews/ServerReview.kt new file mode 100644 index 0000000000..ad6264e403 --- /dev/null +++ b/contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/cep24Reviews/ServerReview.kt @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.contextvm.cep24Reviews + +import com.vitorpamplona.contextvm.core.CvmKinds +import com.vitorpamplona.quartz.nip01Core.core.HexKey +import com.vitorpamplona.quartz.nip01Core.core.Tag + +/** CEP-24 server reviews: NIP-22 comments anchored to a kind-11316 announcement. */ +object ServerReview { + /** The addressable coordinate a review targets. */ + fun coordinate(serverPubKey: HexKey) = "${CvmKinds.SERVER_ANNOUNCEMENT}:$serverPubKey:" + + /** + * Tags for a top-level review. + * + * NIP-22 uses uppercase tags for the root and lowercase for the immediate + * parent; for a top-level comment both are the announcement, hence the + * apparent duplication. + */ + fun topLevelTags( + serverPubKey: HexKey, + relayHint: String? = null, + announcementEventId: HexKey? = null, + ): List { + val coordinate = coordinate(serverPubKey) + val kind = CvmKinds.SERVER_ANNOUNCEMENT.toString() + return buildList { + add(tagOf("A", coordinate, relayHint)) + add(arrayOf("K", kind)) + add(tagOf("P", serverPubKey, relayHint)) + add(tagOf("a", coordinate, relayHint)) + announcementEventId?.let { add(arrayOf("e", it, relayHint ?: "", serverPubKey)) } + add(arrayOf("k", kind)) + add(tagOf("p", serverPubKey, relayHint)) + } + } + + /** + * Tags for a reply to an existing review. + * + * The uppercase root stays on the announcement while the lowercase parent + * moves to the comment being answered — that asymmetry is the whole point of + * NIP-22's dual tagging and is easy to get wrong. + */ + fun replyTags( + serverPubKey: HexKey, + parentCommentId: HexKey, + parentAuthor: HexKey, + relayHint: String? = null, + ): List = + buildList { + add(tagOf("A", coordinate(serverPubKey), relayHint)) + add(arrayOf("K", CvmKinds.SERVER_ANNOUNCEMENT.toString())) + add(tagOf("P", serverPubKey, relayHint)) + add(arrayOf("e", parentCommentId, relayHint ?: "", parentAuthor)) + add(arrayOf("k", CvmKinds.REVIEW.toString())) + add(tagOf("p", parentAuthor, relayHint)) + } + + private fun tagOf( + name: String, + value: String, + relayHint: String?, + ): Tag = if (relayHint != null) arrayOf(name, value, relayHint) else arrayOf(name, value) +} diff --git a/contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/cep35Discovery/SessionDiscovery.kt b/contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/cep35Discovery/SessionDiscovery.kt new file mode 100644 index 0000000000..8c6a01b53f --- /dev/null +++ b/contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/cep35Discovery/SessionDiscovery.kt @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.contextvm.cep35Discovery + +import com.vitorpamplona.contextvm.cep06Announcements.DiscoverySurface +import com.vitorpamplona.quartz.nip01Core.core.Tag + +/** + * Per-session capability learning (CEP-35). + * + * Discovery is a first-message exchange in each direction, not an + * initialize-only step: a server-to-client message that is not an initialize + * response may still carry the server's baseline. After that exchange, later + * feature tags are message-local and do not mutate the baseline unless a + * feature-specific CEP says they do — CEP-8's `payment_interaction` upsert being + * the one that does. + */ +class SessionDiscovery { + private var baseline: DiscoverySurface? = null + + /** The peer's learned baseline, or null before their first message. */ + val peer get() = baseline + + val hasLearned get() = baseline != null + + /** + * Applies a peer message's tags. + * + * The first one establishes the baseline; later ones are returned for + * message-local interpretation but leave the baseline alone. + */ + fun observe(tags: Array): DiscoverySurface { + val surface = DiscoverySurface.parse(tags) + if (baseline == null) baseline = surface + return surface + } + + /** Replaces the baseline outright. For a feature CEP that defines an update. */ + fun replaceBaseline(tags: Array) { + baseline = DiscoverySurface.parse(tags) + } +} diff --git a/contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/transfer/stream/OpenStreamFrame.kt b/contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/cep41OpenStreams/OpenStreamFrame.kt similarity index 99% rename from contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/transfer/stream/OpenStreamFrame.kt rename to contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/cep41OpenStreams/OpenStreamFrame.kt index 8511a4a60c..80c946770a 100644 --- a/contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/transfer/stream/OpenStreamFrame.kt +++ b/contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/cep41OpenStreams/OpenStreamFrame.kt @@ -18,7 +18,7 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.contextvm.transfer.stream +package com.vitorpamplona.contextvm.cep41OpenStreams import com.vitorpamplona.contextvm.transfer.ProgressEnvelope import com.vitorpamplona.contextvm.transfer.ProgressEnvelope.Companion.asLongOrNull diff --git a/contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/transfer/stream/OpenStreamReceiver.kt b/contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/cep41OpenStreams/OpenStreamReceiver.kt similarity index 99% rename from contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/transfer/stream/OpenStreamReceiver.kt rename to contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/cep41OpenStreams/OpenStreamReceiver.kt index 99ce01efdd..b3e2a78b5c 100644 --- a/contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/transfer/stream/OpenStreamReceiver.kt +++ b/contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/cep41OpenStreams/OpenStreamReceiver.kt @@ -18,7 +18,7 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.contextvm.transfer.stream +package com.vitorpamplona.contextvm.cep41OpenStreams import com.vitorpamplona.contextvm.transfer.ProgressToken diff --git a/contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/discovery/ServerDiscovery.kt b/contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/discovery/ServerDiscovery.kt deleted file mode 100644 index 7393ba12ed..0000000000 --- a/contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/discovery/ServerDiscovery.kt +++ /dev/null @@ -1,266 +0,0 @@ -/* - * Copyright (c) 2025 Vitor Pamplona - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the - * Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN - * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -package com.vitorpamplona.contextvm.discovery - -import com.vitorpamplona.contextvm.core.CvmKinds -import com.vitorpamplona.contextvm.core.CvmTags -import com.vitorpamplona.quartz.nip01Core.core.Event -import com.vitorpamplona.quartz.nip01Core.core.HexKey -import com.vitorpamplona.quartz.nip01Core.core.Kind -import com.vitorpamplona.quartz.nip01Core.core.Tag - -/** - * What a peer told us about itself. - * - * CEP-35 makes this a *session* concept rather than an announcement one: the - * same tag vocabulary arrives either on a public announcement (CEP-6) or on the - * first direct message of a session, and both are treated as equivalent. - */ -data class DiscoverySurface( - val name: String? = null, - val about: String? = null, - val picture: String? = null, - val website: String? = null, - val supportsEncryption: Boolean = false, - val supportsEphemeralEncryption: Boolean = false, - val supportsOversizedTransfer: Boolean = false, - val supportsOpenStream: Boolean = false, - /** - * Everything else, routing excluded. - * - * CEP-35 requires unknown discovery tags to be preserved rather than - * discarded, so custom protocols can build on the same exchange. Keeping - * them reachable is what makes that work. - */ - val unknownTags: List = emptyList(), -) { - /** Raw access for a caller that understands a tag this version does not. */ - fun rawTag(name: String): Tag? = unknownTags.firstOrNull { it.isNotEmpty() && it[0] == name } - - companion object { - private val KNOWN = - setOf( - CvmTags.NAME, - CvmTags.ABOUT, - CvmTags.PICTURE, - CvmTags.WEBSITE, - CvmTags.SUPPORT_ENCRYPTION, - CvmTags.SUPPORT_ENCRYPTION_EPHEMERAL, - CvmTags.SUPPORT_OVERSIZED_TRANSFER, - CvmTags.SUPPORT_OPEN_STREAM, - ) - - fun parse(tags: Array): DiscoverySurface { - fun value(name: String) = tags.firstOrNull { it.size >= 2 && it[0] == name }?.get(1) - - return DiscoverySurface( - name = value(CvmTags.NAME), - about = value(CvmTags.ABOUT), - picture = value(CvmTags.PICTURE), - website = value(CvmTags.WEBSITE), - supportsEncryption = CvmTags.hasFlag(tags, CvmTags.SUPPORT_ENCRYPTION), - supportsEphemeralEncryption = CvmTags.hasFlag(tags, CvmTags.SUPPORT_ENCRYPTION_EPHEMERAL), - supportsOversizedTransfer = CvmTags.hasFlag(tags, CvmTags.SUPPORT_OVERSIZED_TRANSFER), - supportsOpenStream = CvmTags.hasFlag(tags, CvmTags.SUPPORT_OPEN_STREAM), - unknownTags = - tags.filter { - it.isNotEmpty() && it[0] !in KNOWN && !CvmTags.isRouting(it[0]) - }, - ) - } - } -} - -/** - * Per-session capability learning (CEP-35). - * - * Discovery is a first-message exchange in each direction, not an - * initialize-only step: a server-to-client message that is not an initialize - * response may still carry the server's baseline. After that exchange, later - * feature tags are message-local and do not mutate the baseline unless a - * feature-specific CEP says they do — CEP-8's `payment_interaction` upsert being - * the one that does. - */ -class SessionDiscovery { - private var baseline: DiscoverySurface? = null - - /** The peer's learned baseline, or null before their first message. */ - val peer get() = baseline - - val hasLearned get() = baseline != null - - /** - * Applies a peer message's tags. - * - * The first one establishes the baseline; later ones are returned for - * message-local interpretation but leave the baseline alone. - */ - fun observe(tags: Array): DiscoverySurface { - val surface = DiscoverySurface.parse(tags) - if (baseline == null) baseline = surface - return surface - } - - /** Replaces the baseline outright. For a feature CEP that defines an update. */ - fun replaceBaseline(tags: Array) { - baseline = DiscoverySurface.parse(tags) - } -} - -/** - * A CEP-6 announcement. - * - * `content` is the stringified result of the matching MCP call — the initialize - * result for a server announcement, a list result for the others — so it is left - * as text for the caller to decode with the same codec it uses on the wire. - */ -data class ServerAnnouncement( - val kind: Kind, - val pubKey: HexKey, - val createdAt: Long, - val content: String, - val discovery: DiscoverySurface, -) { - companion object { - fun parseOrNull(event: Event): ServerAnnouncement? { - if (event.kind !in CvmKinds.ANNOUNCEMENTS) return null - return ServerAnnouncement( - kind = event.kind, - pubKey = event.pubKey, - createdAt = event.createdAt, - content = event.content, - discovery = DiscoverySurface.parse(event.tags), - ) - } - - /** - * Keeps the newest announcement per `(kind, pubkey)`. - * - * These kinds are replaceable, so an older event arriving late from a - * lagging relay must not overwrite a newer one already held. - */ - fun latestPerKind(events: List): Map = - events - .mapNotNull { parseOrNull(it) } - .groupBy { it.kind } - .mapValues { (_, list) -> list.maxBy { it.createdAt } } - } -} - -/** - * A relay a server is reachable on (CEP-17, NIP-65 kind 10002). - * - * The ContextVM profile publishes unmarked tags, meaning the relay serves both - * directions. Markers are honoured when present but are not the norm here. - */ -data class ServerRelay( - val url: String, - val read: Boolean = true, - val write: Boolean = true, -) { - companion object { - const val READ = "read" - const val WRITE = "write" - - fun parseAll(event: Event): List { - if (event.kind != CvmKinds.RELAY_LIST) return emptyList() - return event.tags - .filter { it.size >= 2 && it[0] == CvmTags.RELAY && it[1].isNotBlank() } - .map { tag -> - when (tag.getOrNull(2)) { - READ -> ServerRelay(tag[1], read = true, write = false) - WRITE -> ServerRelay(tag[1], read = false, write = true) - // Unmarked is the recommended ContextVM profile: the - // relay is usable for both publishing and subscribing. - else -> ServerRelay(tag[1]) - } - } - } - - /** - * Relays usable for a full request/response exchange. - * - * A read-only or write-only relay cannot carry both halves, and since - * kind 25910 is ephemeral there is no fetching a response later from - * somewhere else. - */ - fun operational(relays: List) = relays.filter { it.read && it.write } - } -} - -/** CEP-24 server reviews: NIP-22 comments anchored to a kind-11316 announcement. */ -object ServerReview { - /** The addressable coordinate a review targets. */ - fun coordinate(serverPubKey: HexKey) = "${CvmKinds.SERVER_ANNOUNCEMENT}:$serverPubKey:" - - /** - * Tags for a top-level review. - * - * NIP-22 uses uppercase tags for the root and lowercase for the immediate - * parent; for a top-level comment both are the announcement, hence the - * apparent duplication. - */ - fun topLevelTags( - serverPubKey: HexKey, - relayHint: String? = null, - announcementEventId: HexKey? = null, - ): List { - val coordinate = coordinate(serverPubKey) - val kind = CvmKinds.SERVER_ANNOUNCEMENT.toString() - return buildList { - add(tagOf("A", coordinate, relayHint)) - add(arrayOf("K", kind)) - add(tagOf("P", serverPubKey, relayHint)) - add(tagOf("a", coordinate, relayHint)) - announcementEventId?.let { add(arrayOf("e", it, relayHint ?: "", serverPubKey)) } - add(arrayOf("k", kind)) - add(tagOf("p", serverPubKey, relayHint)) - } - } - - /** - * Tags for a reply to an existing review. - * - * The uppercase root stays on the announcement while the lowercase parent - * moves to the comment being answered — that asymmetry is the whole point of - * NIP-22's dual tagging and is easy to get wrong. - */ - fun replyTags( - serverPubKey: HexKey, - parentCommentId: HexKey, - parentAuthor: HexKey, - relayHint: String? = null, - ): List = - buildList { - add(tagOf("A", coordinate(serverPubKey), relayHint)) - add(arrayOf("K", CvmKinds.SERVER_ANNOUNCEMENT.toString())) - add(tagOf("P", serverPubKey, relayHint)) - add(arrayOf("e", parentCommentId, relayHint ?: "", parentAuthor)) - add(arrayOf("k", CvmKinds.REVIEW.toString())) - add(tagOf("p", parentAuthor, relayHint)) - } - - private fun tagOf( - name: String, - value: String, - relayHint: String?, - ): Tag = if (relayHint != null) arrayOf(name, value, relayHint) else arrayOf(name, value) -} diff --git a/contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/fixture/CvmFixtureServer.kt b/contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/fixture/CvmFixtureServer.kt index c1a212e293..239712c9b3 100644 --- a/contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/fixture/CvmFixtureServer.kt +++ b/contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/fixture/CvmFixtureServer.kt @@ -20,9 +20,9 @@ */ package com.vitorpamplona.contextvm.fixture +import com.vitorpamplona.contextvm.cep04Encryption.CvmGiftWrap import com.vitorpamplona.contextvm.core.CvmKinds import com.vitorpamplona.contextvm.core.CvmMessageEvent -import com.vitorpamplona.contextvm.crypto.CvmGiftWrap import com.vitorpamplona.contextvm.jsonrpc.JsonRpcMessage import com.vitorpamplona.contextvm.jsonrpc.JsonRpcNotification import com.vitorpamplona.contextvm.jsonrpc.JsonRpcRequest diff --git a/contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/mcp/CvmMcpClient.kt b/contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/mcp/CvmMcpClient.kt index 0b5cfe8dc9..5310845bd2 100644 --- a/contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/mcp/CvmMcpClient.kt +++ b/contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/mcp/CvmMcpClient.kt @@ -20,6 +20,14 @@ */ package com.vitorpamplona.contextvm.mcp +import com.vitorpamplona.contextvm.cep22OversizedTransfer.OversizedFrame +import com.vitorpamplona.contextvm.cep22OversizedTransfer.OversizedLimits +import com.vitorpamplona.contextvm.cep22OversizedTransfer.OversizedProgressResult +import com.vitorpamplona.contextvm.cep22OversizedTransfer.OversizedTransferReceiver +import com.vitorpamplona.contextvm.cep41OpenStreams.OpenStreamEvent +import com.vitorpamplona.contextvm.cep41OpenStreams.OpenStreamFrame +import com.vitorpamplona.contextvm.cep41OpenStreams.OpenStreamPolicy +import com.vitorpamplona.contextvm.cep41OpenStreams.OpenStreamReceiver import com.vitorpamplona.contextvm.jsonrpc.JsonRpcFailure import com.vitorpamplona.contextvm.jsonrpc.JsonRpcId import com.vitorpamplona.contextvm.jsonrpc.JsonRpcMessage @@ -28,14 +36,6 @@ import com.vitorpamplona.contextvm.jsonrpc.JsonRpcRequest import com.vitorpamplona.contextvm.jsonrpc.JsonRpcSuccess import com.vitorpamplona.contextvm.transfer.ProgressEnvelope import com.vitorpamplona.contextvm.transfer.ProgressToken -import com.vitorpamplona.contextvm.transfer.oversized.OversizedFrame -import com.vitorpamplona.contextvm.transfer.oversized.OversizedLimits -import com.vitorpamplona.contextvm.transfer.oversized.OversizedProgressResult -import com.vitorpamplona.contextvm.transfer.oversized.OversizedTransferReceiver -import com.vitorpamplona.contextvm.transfer.stream.OpenStreamEvent -import com.vitorpamplona.contextvm.transfer.stream.OpenStreamFrame -import com.vitorpamplona.contextvm.transfer.stream.OpenStreamPolicy -import com.vitorpamplona.contextvm.transfer.stream.OpenStreamReceiver import com.vitorpamplona.contextvm.transport.CvmTransport import com.vitorpamplona.contextvm.transport.DualSigner import com.vitorpamplona.quartz.nip01Core.core.Tag @@ -137,7 +137,7 @@ class CvmMcpClient( onStreamFragment: (String) -> Unit = {}, ): ToolCallResult { val id = nextId() - val token = ProgressToken.Text("call-${(id as JsonRpcId.Num).value}") + val token = ProgressToken.Text("call-${id.value}") var oversized: OversizedTransferReceiver? = null var stream: OpenStreamReceiver? = null diff --git a/contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/transport/CvmTransport.kt b/contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/transport/CvmTransport.kt index cad22355d7..3b66968a79 100644 --- a/contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/transport/CvmTransport.kt +++ b/contextvm/src/commonMain/kotlin/com/vitorpamplona/contextvm/transport/CvmTransport.kt @@ -20,10 +20,10 @@ */ package com.vitorpamplona.contextvm.transport +import com.vitorpamplona.contextvm.cep04Encryption.CvmGiftWrap +import com.vitorpamplona.contextvm.cep35Discovery.SessionDiscovery import com.vitorpamplona.contextvm.core.CvmKinds import com.vitorpamplona.contextvm.core.CvmMessageEvent -import com.vitorpamplona.contextvm.crypto.CvmGiftWrap -import com.vitorpamplona.contextvm.discovery.SessionDiscovery import com.vitorpamplona.contextvm.jsonrpc.JsonRpcFailure import com.vitorpamplona.contextvm.jsonrpc.JsonRpcId import com.vitorpamplona.contextvm.jsonrpc.JsonRpcMessage diff --git a/contextvm/src/commonTest/kotlin/com/vitorpamplona/contextvm/crypto/CvmGiftWrapPolicyTest.kt b/contextvm/src/commonTest/kotlin/com/vitorpamplona/contextvm/cep04Encryption/CvmGiftWrapPolicyTest.kt similarity index 98% rename from contextvm/src/commonTest/kotlin/com/vitorpamplona/contextvm/crypto/CvmGiftWrapPolicyTest.kt rename to contextvm/src/commonTest/kotlin/com/vitorpamplona/contextvm/cep04Encryption/CvmGiftWrapPolicyTest.kt index 587fbc36c8..9351383725 100644 --- a/contextvm/src/commonTest/kotlin/com/vitorpamplona/contextvm/crypto/CvmGiftWrapPolicyTest.kt +++ b/contextvm/src/commonTest/kotlin/com/vitorpamplona/contextvm/cep04Encryption/CvmGiftWrapPolicyTest.kt @@ -18,7 +18,7 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.contextvm.crypto +package com.vitorpamplona.contextvm.cep04Encryption import com.vitorpamplona.contextvm.core.CvmKinds import com.vitorpamplona.contextvm.core.CvmTags diff --git a/contextvm/src/commonTest/kotlin/com/vitorpamplona/contextvm/payment/PaymentTest.kt b/contextvm/src/commonTest/kotlin/com/vitorpamplona/contextvm/cep08Payments/PaymentTest.kt similarity index 99% rename from contextvm/src/commonTest/kotlin/com/vitorpamplona/contextvm/payment/PaymentTest.kt rename to contextvm/src/commonTest/kotlin/com/vitorpamplona/contextvm/cep08Payments/PaymentTest.kt index 0ea5a433d7..2106259e97 100644 --- a/contextvm/src/commonTest/kotlin/com/vitorpamplona/contextvm/payment/PaymentTest.kt +++ b/contextvm/src/commonTest/kotlin/com/vitorpamplona/contextvm/cep08Payments/PaymentTest.kt @@ -18,7 +18,7 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.contextvm.payment +package com.vitorpamplona.contextvm.cep08Payments import com.vitorpamplona.contextvm.jsonrpc.JsonRpcCodec import com.vitorpamplona.contextvm.jsonrpc.JsonRpcError diff --git a/contextvm/src/commonTest/kotlin/com/vitorpamplona/contextvm/schema/CommonToolSchemaTest.kt b/contextvm/src/commonTest/kotlin/com/vitorpamplona/contextvm/cep15CommonSchemas/CommonToolSchemaTest.kt similarity index 99% rename from contextvm/src/commonTest/kotlin/com/vitorpamplona/contextvm/schema/CommonToolSchemaTest.kt rename to contextvm/src/commonTest/kotlin/com/vitorpamplona/contextvm/cep15CommonSchemas/CommonToolSchemaTest.kt index 167672d907..b83c92e0a1 100644 --- a/contextvm/src/commonTest/kotlin/com/vitorpamplona/contextvm/schema/CommonToolSchemaTest.kt +++ b/contextvm/src/commonTest/kotlin/com/vitorpamplona/contextvm/cep15CommonSchemas/CommonToolSchemaTest.kt @@ -18,7 +18,7 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.contextvm.schema +package com.vitorpamplona.contextvm.cep15CommonSchemas import com.vitorpamplona.contextvm.jsonrpc.JsonRpcCodec import kotlinx.serialization.json.JsonObject diff --git a/contextvm/src/commonTest/kotlin/com/vitorpamplona/contextvm/transfer/oversized/OversizedTransferTest.kt b/contextvm/src/commonTest/kotlin/com/vitorpamplona/contextvm/cep22OversizedTransfer/OversizedTransferTest.kt similarity index 99% rename from contextvm/src/commonTest/kotlin/com/vitorpamplona/contextvm/transfer/oversized/OversizedTransferTest.kt rename to contextvm/src/commonTest/kotlin/com/vitorpamplona/contextvm/cep22OversizedTransfer/OversizedTransferTest.kt index 918a7d59e4..97512d7626 100644 --- a/contextvm/src/commonTest/kotlin/com/vitorpamplona/contextvm/transfer/oversized/OversizedTransferTest.kt +++ b/contextvm/src/commonTest/kotlin/com/vitorpamplona/contextvm/cep22OversizedTransfer/OversizedTransferTest.kt @@ -18,7 +18,7 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.contextvm.transfer.oversized +package com.vitorpamplona.contextvm.cep22OversizedTransfer import com.vitorpamplona.contextvm.jsonrpc.JsonRpcCodec import com.vitorpamplona.contextvm.jsonrpc.JsonRpcId diff --git a/contextvm/src/commonTest/kotlin/com/vitorpamplona/contextvm/discovery/DiscoveryTest.kt b/contextvm/src/commonTest/kotlin/com/vitorpamplona/contextvm/cep35Discovery/DiscoveryTest.kt similarity index 96% rename from contextvm/src/commonTest/kotlin/com/vitorpamplona/contextvm/discovery/DiscoveryTest.kt rename to contextvm/src/commonTest/kotlin/com/vitorpamplona/contextvm/cep35Discovery/DiscoveryTest.kt index 334461c8f5..250e5b2ae6 100644 --- a/contextvm/src/commonTest/kotlin/com/vitorpamplona/contextvm/discovery/DiscoveryTest.kt +++ b/contextvm/src/commonTest/kotlin/com/vitorpamplona/contextvm/cep35Discovery/DiscoveryTest.kt @@ -18,8 +18,12 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.contextvm.discovery +package com.vitorpamplona.contextvm.cep35Discovery +import com.vitorpamplona.contextvm.cep06Announcements.DiscoverySurface +import com.vitorpamplona.contextvm.cep06Announcements.ServerAnnouncement +import com.vitorpamplona.contextvm.cep17RelayList.ServerRelay +import com.vitorpamplona.contextvm.cep24Reviews.ServerReview import com.vitorpamplona.contextvm.core.CvmKinds import com.vitorpamplona.contextvm.core.CvmTags import com.vitorpamplona.quartz.nip01Core.core.Event @@ -33,7 +37,6 @@ import kotlin.test.assertNotNull import kotlin.test.assertNull import kotlin.test.assertTrue -/** `CVM-6-*`, `CVM-17-*`, `CVM-24-*` and `CVM-35-*`. */ class DiscoveryTest { private val serverPubKey = "a".repeat(64) diff --git a/contextvm/src/commonTest/kotlin/com/vitorpamplona/contextvm/transfer/stream/OpenStreamTest.kt b/contextvm/src/commonTest/kotlin/com/vitorpamplona/contextvm/cep41OpenStreams/OpenStreamTest.kt similarity index 99% rename from contextvm/src/commonTest/kotlin/com/vitorpamplona/contextvm/transfer/stream/OpenStreamTest.kt rename to contextvm/src/commonTest/kotlin/com/vitorpamplona/contextvm/cep41OpenStreams/OpenStreamTest.kt index c4daf810b6..e2b47e4fbb 100644 --- a/contextvm/src/commonTest/kotlin/com/vitorpamplona/contextvm/transfer/stream/OpenStreamTest.kt +++ b/contextvm/src/commonTest/kotlin/com/vitorpamplona/contextvm/cep41OpenStreams/OpenStreamTest.kt @@ -18,7 +18,7 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.contextvm.transfer.stream +package com.vitorpamplona.contextvm.cep41OpenStreams import com.vitorpamplona.contextvm.jsonrpc.JsonRpcCodec import com.vitorpamplona.contextvm.jsonrpc.JsonRpcNotification diff --git a/contextvm/src/jvmTest/kotlin/com/vitorpamplona/contextvm/crypto/CvmGiftWrapCryptoTest.kt b/contextvm/src/jvmTest/kotlin/com/vitorpamplona/contextvm/cep04Encryption/CvmGiftWrapCryptoTest.kt similarity index 99% rename from contextvm/src/jvmTest/kotlin/com/vitorpamplona/contextvm/crypto/CvmGiftWrapCryptoTest.kt rename to contextvm/src/jvmTest/kotlin/com/vitorpamplona/contextvm/cep04Encryption/CvmGiftWrapCryptoTest.kt index 7045cc8be5..a9032b27e6 100644 --- a/contextvm/src/jvmTest/kotlin/com/vitorpamplona/contextvm/crypto/CvmGiftWrapCryptoTest.kt +++ b/contextvm/src/jvmTest/kotlin/com/vitorpamplona/contextvm/cep04Encryption/CvmGiftWrapCryptoTest.kt @@ -18,7 +18,7 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.contextvm.crypto +package com.vitorpamplona.contextvm.cep04Encryption import com.vitorpamplona.contextvm.core.CvmKinds import com.vitorpamplona.contextvm.core.CvmMessageEvent diff --git a/contextvm/src/jvmTest/kotlin/com/vitorpamplona/contextvm/mcp/CvmMcpClientTest.kt b/contextvm/src/jvmTest/kotlin/com/vitorpamplona/contextvm/mcp/CvmMcpClientTest.kt index 981e93d9d0..36cc1e9810 100644 --- a/contextvm/src/jvmTest/kotlin/com/vitorpamplona/contextvm/mcp/CvmMcpClientTest.kt +++ b/contextvm/src/jvmTest/kotlin/com/vitorpamplona/contextvm/mcp/CvmMcpClientTest.kt @@ -20,8 +20,10 @@ */ package com.vitorpamplona.contextvm.mcp -import com.vitorpamplona.contextvm.crypto.CvmGiftWrap -import com.vitorpamplona.contextvm.crypto.EncryptionMode +import com.vitorpamplona.contextvm.cep04Encryption.CvmGiftWrap +import com.vitorpamplona.contextvm.cep04Encryption.EncryptionMode +import com.vitorpamplona.contextvm.cep22OversizedTransfer.OversizedTransferSender +import com.vitorpamplona.contextvm.cep41OpenStreams.OpenStreamFrame import com.vitorpamplona.contextvm.fixture.CvmFixtureServer import com.vitorpamplona.contextvm.fixture.InMemoryRelayPool import com.vitorpamplona.contextvm.jsonrpc.JsonRpcCodec @@ -30,8 +32,6 @@ import com.vitorpamplona.contextvm.jsonrpc.JsonRpcFailure import com.vitorpamplona.contextvm.jsonrpc.JsonRpcId import com.vitorpamplona.contextvm.jsonrpc.JsonRpcSuccess import com.vitorpamplona.contextvm.transfer.ProgressToken -import com.vitorpamplona.contextvm.transfer.oversized.OversizedTransferSender -import com.vitorpamplona.contextvm.transfer.stream.OpenStreamFrame import com.vitorpamplona.contextvm.transport.CvmTransport import com.vitorpamplona.contextvm.transport.DualSigner import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair diff --git a/contextvm/src/jvmTest/kotlin/com/vitorpamplona/contextvm/transport/CvmTransportTest.kt b/contextvm/src/jvmTest/kotlin/com/vitorpamplona/contextvm/transport/CvmTransportTest.kt index 628d8a968c..1c5a9883f0 100644 --- a/contextvm/src/jvmTest/kotlin/com/vitorpamplona/contextvm/transport/CvmTransportTest.kt +++ b/contextvm/src/jvmTest/kotlin/com/vitorpamplona/contextvm/transport/CvmTransportTest.kt @@ -20,10 +20,10 @@ */ package com.vitorpamplona.contextvm.transport +import com.vitorpamplona.contextvm.cep04Encryption.CvmGiftWrap +import com.vitorpamplona.contextvm.cep04Encryption.EncryptionMode import com.vitorpamplona.contextvm.core.CvmKinds import com.vitorpamplona.contextvm.core.CvmTags -import com.vitorpamplona.contextvm.crypto.CvmGiftWrap -import com.vitorpamplona.contextvm.crypto.EncryptionMode import com.vitorpamplona.contextvm.fixture.CvmFixtureServer import com.vitorpamplona.contextvm.fixture.FixtureFaults import com.vitorpamplona.contextvm.fixture.InMemoryRelayPool diff --git a/quartz/plans/2026-09-17-cordn-interop.md b/quartz/plans/2026-09-17-cordn-interop.md index 0d04427c60..d604b2f68a 100644 --- a/quartz/plans/2026-09-17-cordn-interop.md +++ b/quartz/plans/2026-09-17-cordn-interop.md @@ -671,21 +671,27 @@ green on jvm. What is in: | ---------- | ----- | | 1 constants, tags, JSON-RPC codec | `core/CvmKinds`, `core/CvmTags`, `jsonrpc/` | | 2 minimal MCP client | `mcp/CvmMcpClient`, `mcp/McpMethods` | -| 3 CEP-4/19 gift wrap | `crypto/CvmGiftWrap` (pins `REQUIRED`) | +| 3 CEP-4/19 gift wrap | `cep04Encryption/CvmGiftWrap` (pins `REQUIRED`) | | 4 correlation + subscription lifecycle | `transport/CvmTransport` | -| 5 CEP-35 discovery learning | `discovery/SessionDiscovery` | -| 6 CEP-6/17/23 discovery | `discovery/ServerDiscovery` | +| 5 CEP-35 discovery learning | `cep35Discovery/SessionDiscovery` | +| 6 CEP-6/17/23 discovery | `cep06Announcements/`, `cep17RelayList/ServerRelay` | | 7 **fixture server (Tier C)** | `fixture/CvmFixtureServer`, `fixture/InMemoryRelayPool` | -| 8 CEP-22 receiver | `transfer/oversized/OversizedTransferReceiver` | -| 9 CEP-41 receiver | `transfer/stream/OpenStreamReceiver` | -| 10 CEP-22 sender | `transfer/oversized/OversizedTransferSender` | +| 8 CEP-22 receiver | `cep22OversizedTransfer/OversizedTransferReceiver` | +| 9 CEP-41 receiver | `cep41OpenStreams/OpenStreamReceiver` | +| 10 CEP-22 sender | `cep22OversizedTransfer/OversizedTransferSender` | | 11 RFC 8785 JCS | `quartz/…/utils/jcs/JsonCanonicalization` | -| 12 CEP-15 schemas | `schema/CommonToolSchema` | -| 13 CEP-8 + CEP-21 | `payment/` | +| 12 CEP-15 schemas | `cep15CommonSchemas/CommonToolSchema` | +| 13 CEP-8 + CEP-21 | `cep08Payments/` | | 14 CEP-16 injection | in the fixture's server role | -| 15 CEP-24 reviews | `discovery/ServerDiscovery.ServerReview` | +| 15 CEP-24 reviews | `cep24Reviews/ServerReview` | | 16 dual-signer | `transport/DualSigner` | +Source is organized one package per CEP (`cep04Encryption/`, `cep08Payments/`, …), matching the +`nipXX` convention in `quartz` and `mipXX` in `marmot`; only what the core draft spec defines +(`core/`, `jsonrpc/`, `transport/`, `mcp/`) and the CEP-22/41 shared framing (`transfer/`) keep +names, having no CEP number to carry. `contextvm/README.md` records the four placements that are +judgment calls. + Four findings worth carrying forward, all caught by tests rather than review: 1. **CEP-22/41 ordering.** The first receiver rejected frames whose `progress` did not increase on