Repository navigation
CI rehearsal only (do not merge) - #15
michael-moffett wants to merge 11 commits into
Conversation
…atrix Add a Swift stdin/stdout runner over SolanaPayKit's protocol functions, plus `harness/protocol-runners/swift.json`, so the spawned-runner block runs Swift. No mpp-protocol vector reaches the Swift SDK. Milestone 1 of the harness proposal puts Swift in the matrix.
|
| "method": challenge.method, | ||
| "intent": challenge.intent, | ||
| "request": try decodeJSON(challenge.request), | ||
| ] | ||
| if let expires = challenge.expires { result["expires"] = expires } | ||
| if let digest = challenge.digest { result["digest"] = digest } | ||
| if let opaque = challenge.opaque { result["opaque"] = try decodeJSON(opaque) } | ||
| return result | ||
| } |
There was a problem hiding this comment.
When challenge.parse receives a valid challenge with a description, such as the canonical full_challenge vector, the returned object omits it. The parsed result no longer matches the protocol object, so full conformance checks fail and callers cannot recover the description.
| try rejectUnknownKeys(payload, ["type", "transaction", "signature"], at: "payload.") | ||
| guard let request = challenge["request"] else { throw RunnerError(description: "missing challenge.request") } | ||
| let echo = try PaymentChallenge( | ||
| id: required(challenge, "id", at: "challenge."), | ||
| realm: required(challenge, "realm", at: "challenge."), | ||
| method: required(challenge, "method", at: "challenge."), | ||
| intent: required(challenge, "intent", at: "challenge."), | ||
| request: encodeJSON(request), | ||
| expires: string(challenge, "expires", at: "challenge."), | ||
| digest: string(challenge, "digest", at: "challenge."), | ||
| opaque: challenge["opaque"].map(encodeJSON) | ||
| ).echo() | ||
| return PaymentCredential( | ||
| challenge: echo, | ||
| payload: try JSONDecoder().decode(CredentialPayload.self, from: JSONSerialization.data(withJSONObject: payload)), |
There was a problem hiding this comment.
Canonical credentials cannot be formatted
The canonical basic_credential format input has type: "transaction" and a signature, but no transaction field. This code decodes it with CredentialPayload, which requires transaction for that type, so credential.format returns an error instead of a header. The canonical hash-payload format case is also rejected by the new field allowlist.
| const allowlist = parseLanguageAllowlist(process.env.MPP_CONFORMANCE_LANGUAGES); | ||
| const runners = discoverProtocolRunners().filter( | ||
| (runner) => !allowlist || allowlist.has(runner.language), | ||
| ); | ||
| for (const runner of runners) { | ||
| const known = KNOWN_RUNNER_DIVERGENCES[runner.language] ?? new Set<string>(); | ||
| const known = KNOWN_RUNNER_DIVERGENCES[runner.language] ?? {}; | ||
| describe(`mpp-protocol conformance (spawned ${runner.language} runner)`, () => { | ||
| const adapter = spawnedProtocolAdapter(runner); | ||
| for (const testCase of smokeCases) { | ||
| if (!caseRunsOnAdapter(testCase, runner.language)) continue; |
There was a problem hiding this comment.
Swift smoke coverage misses functionality
The Swift conformance job runs only the selected smoke cases: six confirm that operations remain unsupported, while the one conforming case parses a minimal challenge. It never checks the newly implemented credential.format operation or optional challenge fields. Regressions in those parts of the runner can therefore pass this job unnoticed.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
…/ was never built in the check environment, and the Swift runner tests pass (6 tests)
…no field for, naming the param; framing test added
…entChallenge cannot carry (description) and ignores extension params per spec
…tructure.reference.ts and an upstream rebase, both outside this seat; MppProtocolRunnerTests green (8 tests)
| let operation = serde_json::json!({ | ||
| "method": self.method, | ||
| "params": params, | ||
| }); |
There was a problem hiding this comment.
If a custom runtime uses root _meta to select a tenant or operation context, a credential issued for one request can be reused after that metadata changes. operation_digest() hashes the method and params but not root metadata, while the gate accepts credentials from that placement and can return Paid for the changed request.
How this was verified: The gate checks a digest that excludes Request.meta, accepts credentials from root _meta, and returns Paid after successful verification.
| realm: challenge.realm, | ||
| method: challenge.method, | ||
| intent: challenge.intent, | ||
| request: Base64UrlJson::from_value(&challenge.request)?, |
There was a problem hiding this comment.
Challenge ID fails after conversion
If a valid signed challenge's request JSON uses a different key order or whitespace, converting it to the MCP challenge and back changes its encoded request bytes but keeps the original ID. Because that ID authenticates the original bytes, the credential returned through this adapter fails verification and the client cannot complete payment.
| let meta = params | ||
| .entry("_meta") | ||
| .or_insert_with(|| Value::Object(Map::new())) | ||
| .as_object_mut() | ||
| .ok_or_else(|| Error::Other("MCP request params._meta must be an object".into()))?; | ||
| meta.insert(CREDENTIAL_META_KEY.into(), value); |
There was a problem hiding this comment.
Credential replacement creates conflict
If a request already carries a root-level credential, set_credential() writes the replacement under params._meta without removing the old one. credential() then rejects the two different values as a conflict. A client using the supported root placement cannot update its credential for a retry without clearing the old value itself.
Fork CI only. Do not merge.