mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-10-05 11:18:24 +00:00
refactor(contextvm): one package per CEP
`quartz` puts a NIP's implementation under `nipXX`, `marmot` puts a MIP's under `mipXX`, and both keep named packages only for what no spec number covers. ContextVM had neither: CEP-4 sat in `crypto/`, CEP-6/17/23/24/35 were four unrelated specs sharing one `ServerDiscovery.kt`, CEP-8 in `payment/`. "Which file implements CEP-17" was a grep rather than a path. Now it is `cep17RelayList/ServerRelay`. Four placements are judgment calls, recorded in the README so they can be argued with: - CEP-19 stays inside `cep04Encryption/CvmGiftWrap`. Picking the wrap kind (21059, falling back to 1059) and building the wrap are one negotiation in one class; its own package would separate a method from its only caller. - CEP-16 gets no package. It is a `_meta` key name plus the server-side obligation to inject it -- a constant and fixture behaviour, not a subsystem. - `transfer/ProgressEnvelope` stays cross-cutting: CEP-22 and CEP-41 share that framing, so it is the `marmot/foundation/` analogue. - `core/`, `jsonrpc/`, `transport/` and `mcp/` keep names because the core draft spec is not a CEP and has no number to carry. Pure moves and package/import rewrites; no behaviour changed. Also drops a redundant cast the compiler flagged in `CvmMcpClient`. Suite unchanged at 172 on jvm and 143 on the Android target. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012BfD4txdnsaPRXmNXbup9n
This commit is contained in:
+32
-14
@@ -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
|
||||
|
||||
+1
-1
@@ -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
|
||||
+86
@@ -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<Tag> = 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<Tag>): 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])
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+66
@@ -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<Event>): Map<Kind, ServerAnnouncement> =
|
||||
events
|
||||
.mapNotNull { parseOrNull(it) }
|
||||
.groupBy { it.kind }
|
||||
.mapValues { (_, list) -> list.maxBy { it.createdAt } }
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -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
|
||||
+1
-1
@@ -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
|
||||
+1
-1
@@ -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
|
||||
|
||||
+1
-1
@@ -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
|
||||
+1
-1
@@ -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
|
||||
+66
@@ -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<ServerRelay> {
|
||||
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<ServerRelay>) = relays.filter { it.read && it.write }
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -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
|
||||
+1
-1
@@ -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
|
||||
+1
-1
@@ -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
|
||||
+84
@@ -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<Tag> {
|
||||
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<Tag> =
|
||||
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)
|
||||
}
|
||||
+60
@@ -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<Tag>): 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<Tag>) {
|
||||
baseline = DiscoverySurface.parse(tags)
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -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
|
||||
+1
-1
@@ -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
|
||||
|
||||
-266
@@ -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<Tag> = 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<Tag>): 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<Tag>): 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<Tag>) {
|
||||
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<Event>): Map<Kind, ServerAnnouncement> =
|
||||
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<ServerRelay> {
|
||||
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<ServerRelay>) = 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<Tag> {
|
||||
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<Tag> =
|
||||
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)
|
||||
}
|
||||
+1
-1
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
+2
-2
@@ -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
|
||||
|
||||
+1
-1
@@ -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
|
||||
+1
-1
@@ -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
|
||||
+1
-1
@@ -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
|
||||
+1
-1
@@ -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
|
||||
+5
-2
@@ -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)
|
||||
|
||||
+1
-1
@@ -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
|
||||
+1
-1
@@ -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
|
||||
@@ -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
|
||||
|
||||
+2
-2
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user