Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/runx-cli/src/official_skills.rs
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ pub(crate) const OFFICIAL_SKILLS: &[OfficialSkillLockEntry] = &[
},
OfficialSkillLockEntry {
skill_id: "runx/nitrosend",
version: "sha-9a7c21a55467",
version: "sha-79a8beef082f",
digest: "181138ac55043d03e25ad8a7d86ba972dba3835be378a8ba0f14cdfb7ae1bfa9",
},
OfficialSkillLockEntry {
Expand Down
15 changes: 7 additions & 8 deletions skills/nitrosend/X.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
skill: nitrosend
version: "0.4.3"
version: "0.4.4"
catalog:
kind: skill
audience: public
Expand Down Expand Up @@ -120,27 +120,26 @@ runners:
skill: graph/provider-operation
runner: read
inputs:
operation: plan_checkout_status
context:
purchase_id: checkout.provider_evidence.data.result.purchase_id
operation: billing_status
arguments: {}
- id: present
context:
checkout_evidence: checkout.provider_evidence.data
purchase_evidence: readback.provider_evidence.data
status_evidence: readback.provider_evidence.data
run:
type: javascript
module: graph/provider-operation/nitrosend-operation.mjs
export: presentEvidence
outputs:
checkout_evidence: object
purchase_evidence: object
status_evidence: object
artifacts:
named_emits:
checkout_evidence: checkout_evidence
purchase_evidence: purchase_evidence
status_evidence: status_evidence
packets:
checkout_evidence: nitrosend.provider_evidence.v1
purchase_evidence: nitrosend.provider_evidence.v1
status_evidence: nitrosend.provider_evidence.v1
policy:
guards:
- step: approve
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ runner: plan-checkout
operator_journeys:
- mode: standalone
confusors: [purchase-approval, spend, charge]
request: Approve this exact Nitrosend plan and create its idempotent hosted checkout.
expected_outcome: Re-read billing and plans, approve once, create the checkout with the caller key, and return provider readback without claiming payment.
request: Approve this exact Nitrosend plan and apply the idempotent provider action.
expected_outcome: Re-read billing and plans, approve once, accept a no-op current-plan result without requiring a purchase ID, and return billing readback without claiming a charge.
env:
NITROSEND_API_KEY: harness-nitrosend-token
inputs:
Expand Down Expand Up @@ -66,25 +66,7 @@ caller:
status: 200
headers: { content-type: application/json }
body: >-
{"jsonrpc":"2.0","id":"nitrosend-plan_checkout","result":{"content":[{"type":"text","text":"{\"meta\":{\"tool\":\"nitro_manage_billing\"},\"result\":{\"operation\":\"checkout\",\"purchase_id\":701,\"plan_id\":202,\"state\":\"awaiting_provider_approval\",\"checkout_url\":\"https://checkout.stripe.test/session\",\"next_action\":\"Open the hosted checkout, then poll purchase 701.\"}}"}]}}
- request:
method: POST
url: https://api.nitrosend.com/mcp
body:
json:
jsonrpc: "2.0"
id: nitrosend-plan_checkout_status
method: tools/call
params:
name: nitro_manage_billing
arguments:
operation: checkout_status
params: { purchase_id: 701 }
response:
status: 200
headers: { content-type: application/json }
body: >-
{"jsonrpc":"2.0","id":"nitrosend-plan_checkout_status","result":{"content":[{"type":"text","text":"{\"meta\":{\"tool\":\"nitro_manage_billing\"},\"result\":{\"operation\":\"checkout_status\",\"purchase_id\":701,\"plan_id\":202,\"plan_name\":\"Scale\",\"status\":\"awaiting_provider_approval\",\"activated\":false,\"next_action\":\"Open the hosted checkout, then poll purchase 701.\"}}"}]}}
{"jsonrpc":"2.0","id":"nitrosend-plan_checkout","result":{"content":[{"type":"text","text":"{\"meta\":{\"tool\":\"nitro_manage_billing\"},\"result\":{\"operation\":\"checkout\",\"action\":\"current\",\"reused\":true,\"plan_id\":202,\"status\":\"active\",\"activated\":true,\"next_action\":\"The requested plan is already current.\"}}"}]}}
expect:
status: sealed
steps: [status, plans, approve, checkout, readback, present]
Expand All @@ -95,19 +77,16 @@ expect:
data:
decision: ok
operation: plan_checkout
provider_ref: nitrosend:plan_purchase:701
present:
subset:
checkout_evidence:
data:
decision: ok
operation: plan_checkout
provider_ref: nitrosend:plan_purchase:701
purchase_evidence:
status_evidence:
data:
decision: ok
operation: plan_checkout_status
provider_ref: nitrosend:plan_purchase:701
operation: billing_status
receipt: { schema: runx.receipt.v1, state: sealed, disposition: closed }
metadata:
public_skill: nitrosend
Expand Down
40 changes: 34 additions & 6 deletions skills/nitrosend/fixtures/plan-checkout-requires-approval.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,42 @@ inputs:
plan_id: 202
idempotency_key: account-1-plan-202-v1
caller:
http_responses:
"https://api.nitrosend.com/mcp":
status: 200
headers: { content-type: application/json }
body: >-
{"jsonrpc":"2.0","id":"nitrosend-billing-preflight","result":{"content":[{"type":"text","text":"{\"meta\":{\"tool\":\"nitro_manage_billing\"},\"result\":{\"operation\":\"status\",\"commercial_tier\":\"pro\",\"has_subscription\":true,\"subscription\":{\"id\":101,\"plan_name\":\"Pro\",\"status\":\"active\",\"activated\":true},\"plans\":[{\"id\":202,\"name\":\"Scale\",\"eligible\":true}],\"next_action\":\"Approve only the exact selected plan.\"}}"}]}}
http_exchanges:
- request:
method: POST
url: https://api.nitrosend.com/mcp
body:
json:
jsonrpc: "2.0"
id: nitrosend-billing_status
method: tools/call
params:
name: nitro_manage_billing
arguments: { operation: status }
response:
status: 200
headers: { content-type: application/json }
body: >-
{"jsonrpc":"2.0","id":"nitrosend-billing_status","result":{"content":[{"type":"text","text":"{\"meta\":{\"tool\":\"nitro_manage_billing\"},\"result\":{\"operation\":\"status\",\"commercial_tier\":\"pro\",\"has_subscription\":true,\"subscription\":{\"id\":101,\"plan_name\":\"Pro\",\"status\":\"active\",\"activated\":true},\"next_action\":\"Choose a plan.\"}}"}]}}
- request:
method: POST
url: https://api.nitrosend.com/mcp
body:
json:
jsonrpc: "2.0"
id: nitrosend-billing_plans
method: tools/call
params:
name: nitro_manage_billing
arguments: { operation: plans }
response:
status: 200
headers: { content-type: application/json }
body: >-
{"jsonrpc":"2.0","id":"nitrosend-billing_plans","result":{"content":[{"type":"text","text":"{\"meta\":{\"tool\":\"nitro_manage_billing\"},\"result\":{\"operation\":\"plans\",\"plans\":[{\"id\":202,\"name\":\"Scale\",\"eligible\":true}],\"next_action\":\"Approve only the exact selected plan.\"}}"}]}}
expect:
status: needs_agent
steps: [status, plans]
metadata:
public_skill: nitrosend
source_case: plan-checkout-requires-approval
Expand Down
1 change: 0 additions & 1 deletion skills/nitrosend/graph/provider-operation/X.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,6 @@ runners:
required: false
default: nitrosend-provider-operation
description: Caller-bound transport retry identity when the parent runner has one; otherwise the existing shared fallback.
schema: { minLength: 1, maxLength: 255 }
brand_sid:
type: string
required: false
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$comment": "Captured from the deployed nitro_manage_billing tools/list contract after Nitrosend API commit 4190487687dd868223ed10bed1a9efa918c74c3e; Runx schema receipt sha256:0a0311d7c4d8d0b537f7f4d1c174a70a1619c6de1c151625a75120145c7b7dba.",
"additionalProperties": false,
"properties": {
"idempotency_key": {
"description": "Required stable key for checkout and add_funds. Reuse it only for an unchanged request.",
"maxLength": 128,
"type": "string"
},
"operation": {
"description": "Billing operation. Start with status; use plans/checkout for subscriptions and add_funds/funding_purchase_status for prepaid balance.",
"enum": [
"status",
"checkout",
"checkout_status",
"plans",
"add_funds",
"funding_purchase_status"
],
"type": "string"
},
"params": {
"additionalProperties": false,
"description": "Operation parameters: checkout requires plan_id and may require confirm; checkout_status accepts its plan purchase_id; add_funds requires amount_cents and currency; funding_purchase_status requires its funding purchase_id.",
"properties": {
"amount_cents": {
"description": "Integer amount in minor currency units",
"type": "integer"
},
"confirm": {
"description": "Set true only after operator confirmation when checkout reports confirmation_required",
"type": "boolean"
},
"currency": {
"description": "Three-letter funding currency",
"type": "string"
},
"instrument": {
"description": "Optional add-funds instrument; omit to use the account default",
"enum": [
"stripe_checkout",
"shopify_one_time"
],
"type": "string"
},
"plan_id": {
"description": "Plan ID (required for checkout)",
"type": "integer"
},
"purchase_id": {
"description": "Local purchase ID returned by checkout or add_funds, interpreted by operation",
"type": "integer"
}
},
"type": "object"
}
},
"required": [
"operation"
],
"type": "object"
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import test from "node:test";

import Ajv2020 from "ajv/dist/2020.js";

import {
blockedOperation,
normalizeOperation,
presentEvidence,
prepareOperation,
} from "./nitrosend-operation.mjs";

const billingSchema = JSON.parse(readFileSync(
new URL("./nitro-manage-billing.input-schema.json", import.meta.url),
"utf8",
));
const validateBillingArguments = new Ajv2020({ strict: true }).compile(billingSchema);

function mcpPayload(result, meta = { tool: "fixture" }) {
return {
jsonrpc: "2.0",
Expand Down Expand Up @@ -553,6 +562,15 @@ test("pins plan billing MCP sub-actions and caller retry identity", () => {
operation: "checkout_status",
params: { purchase_id: 701 },
});

for (const plan of [status, plans, checkout, readback]) {
const argumentsValue = plan.requests[0].body.params.arguments;
assert.equal(
validateBillingArguments(argumentsValue),
true,
`${plan.operation} diverged from deployed nitro_manage_billing schema: ${JSON.stringify(validateBillingArguments.errors)}`,
);
}
});

test("binds billing readback to the requested operation and purchase", () => {
Expand Down
2 changes: 1 addition & 1 deletion skills/official.lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@
},
{
"skill_id": "runx/nitrosend",
"version": "sha-9a7c21a55467",
"version": "sha-79a8beef082f",
"digest": "181138ac55043d03e25ad8a7d86ba972dba3835be378a8ba0f14cdfb7ae1bfa9",
"catalog_visibility": "public",
"catalog_role": "branded"
Expand Down