Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
374a6f0
mcp(osp): add estimate tool
EfeDurmaz16 Mar 31, 2026
4f2e14e
test(mcp): cover osp_estimate tool (#33)
EfeDurmaz16 Mar 31, 2026
3962f4b
mcp(osp): support paid provisioning inputs (#34)
EfeDurmaz16 Mar 31, 2026
73daf99
mcp(osp): validate paid provision requests
EfeDurmaz16 Mar 31, 2026
bd7f1b3
mcp(osp): add approval-aware provisioning responses (#35)
EfeDurmaz16 Mar 31, 2026
7757096
mcp(osp): add approval-aware responses
EfeDurmaz16 Mar 31, 2026
3147195
schemas(error): standardize nested error responses
EfeDurmaz16 Mar 31, 2026
158a445
schema(errors): standardize machine-actionable failures (#18)
EfeDurmaz16 Mar 31, 2026
ad31080
sardis-rail(proof): freeze proof envelope format (#147)
EfeDurmaz16 Mar 31, 2026
3c3ca5e
spec(async): clarify retry and polling semantics
EfeDurmaz16 Mar 31, 2026
caa8829
schema(escrow): add manifest and response escrow metadata (#17)
EfeDurmaz16 Mar 31, 2026
85553a7
sardis-rail(proof): bind proofs to context (#148)
EfeDurmaz16 Mar 31, 2026
ee23dad
schema(payments): add payment proof envelopes (#16)
EfeDurmaz16 Mar 31, 2026
4a47ae0
docs(profile): publish paid-core conformance level
EfeDurmaz16 Mar 31, 2026
51164c2
docs(sardis): add provider verification examples (#149)
EfeDurmaz16 Mar 31, 2026
a3a6f6e
docs(mcp): add approval flow examples (#137)
EfeDurmaz16 Mar 31, 2026
cf3d944
schemas(provision): tighten payment proof envelopes
EfeDurmaz16 Mar 31, 2026
1b6a26a
schemas(response): add escrow response metadata
EfeDurmaz16 Mar 31, 2026
32c0e59
docs(errors): add cross-sdk payment error examples (#86)
EfeDurmaz16 Mar 31, 2026
3f1057d
spec(provision): freeze paid provisioning request-response contract
EfeDurmaz16 Mar 31, 2026
149a28e
spec(payments): define agent and provider obligations for paid flows
EfeDurmaz16 Mar 31, 2026
4a4ce69
docs(protocol): publish canonical free and paid provisioning sequences
EfeDurmaz16 Mar 31, 2026
e4d71e6
crypto(fixtures): add canonical json vector pack
EfeDurmaz16 Mar 31, 2026
dffb349
sdk(ts,py): add canonicalization parity suite
EfeDurmaz16 Mar 31, 2026
300c41d
sdk(go): add canonicalization parity suite
EfeDurmaz16 Mar 31, 2026
110629b
crypto(fixtures): add signed manifest test set
EfeDurmaz16 Mar 31, 2026
e73428b
sdk(all): align invalid manifest verification behavior
EfeDurmaz16 Mar 31, 2026
855db88
docs(security): document signature verification guarantees
EfeDurmaz16 Mar 31, 2026
7541b2d
sdk(ts): add estimate request and response support
EfeDurmaz16 Mar 31, 2026
3cc3449
sdk(ts): attach payment proof to provision
EfeDurmaz16 Mar 31, 2026
25b54ed
sdk(py): add estimate models and payment proof support
EfeDurmaz16 Mar 31, 2026
135eed3
sdk(go): add estimate, proof, escrow, and async reconciliation types
EfeDurmaz16 Mar 31, 2026
6ba6735
vault(versioning): add bundle schema versioning and migration
EfeDurmaz16 Mar 31, 2026
663a07e
docs(vault): publish migration and rollback notes
EfeDurmaz16 Mar 31, 2026
2c27624
sardis-rail(mandate): add mandate creation service
EfeDurmaz16 Mar 31, 2026
8f6df9a
sardis-rail(escrow): add hold, release, refund, dispute, and reconcil…
EfeDurmaz16 Mar 31, 2026
474fd5b
sardis-rail(disputes,ledger): add dispute handling and balanced ledger
EfeDurmaz16 Mar 31, 2026
93cbe3b
mcp(tracing,examples,tests): add correlation IDs, paid flow examples,…
EfeDurmaz16 Mar 31, 2026
9118ba6
sardis-integration: add persistence, verification SDK, and reconcilia…
EfeDurmaz16 Mar 31, 2026
47fd66c
registry: add payment-aware search, trust metadata, signing, moderati…
EfeDurmaz16 Mar 31, 2026
5fe4fe4
docs(better): add complete integration specs for better-npm x OSP
EfeDurmaz16 Mar 31, 2026
edc0308
docs(better-workflow): add auth, paid provisioning, preview, CI, and …
EfeDurmaz16 Mar 31, 2026
b3bb248
docs(enterprise): add governance, approval, observability, SLOs, reco…
EfeDurmaz16 Mar 31, 2026
b9c091d
docs(gtm): add narrative, onboarding, golden paths, partner program, …
EfeDurmaz16 Mar 31, 2026
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@ OSP is payment-rail agnostic. [Sardis](https://sardis.sh) is the founding mainta
- **Payment Rail** — `sardis_wallet` payment method with escrow lifecycle
- **MCP Extension** — 9 OSP tools for Claude/GPT agents
- **CLI Bridge** — `sardis projects add` → OSP provision + Sardis payment
- **Provider Verification Example** — [Sardis proof verification guide](docs/payments/sardis-provider-verification.md)

Other payment rails (Stripe SPT, x402, MPP, invoicing) are equally supported.

Expand Down
45 changes: 45 additions & 0 deletions conformance-tests/fixtures/canonical-json/parity-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/**
* Canonical JSON parity test — TypeScript
*
* Loads the shared vector pack and verifies that the TS SDK's canonicalJson()
* produces identical output for every test vector.
*/

import { readFileSync } from "node:fs";
import { join } from "node:path";
import { canonicalJson } from "../../reference-implementation/typescript/src/crypto.js";

interface Vector {
id: string;
description: string;
input: unknown;
expected: string;
}

interface VectorPack {
vectors: Vector[];
}

const pack: VectorPack = JSON.parse(
readFileSync(join(__dirname, "vectors.json"), "utf-8"),
);

let passed = 0;
let failed = 0;

for (const v of pack.vectors) {
const actual = canonicalJson(v.input);
if (actual === v.expected) {
passed++;
} else {
failed++;
console.error(`FAIL [${v.id}]: ${v.description}`);
console.error(` expected: ${v.expected}`);
console.error(` actual: ${actual}`);
}
}

console.log(`\nCanonical JSON parity (TypeScript): ${passed}/${pack.vectors.length} passed`);
if (failed > 0) {
process.exit(1);
}
57 changes: 57 additions & 0 deletions conformance-tests/fixtures/canonical-json/parity_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package canonical_test

import (
"encoding/json"
"os"
"path/filepath"
"runtime"
"testing"

osp "github.com/anthropics/osp/osp-sdk-go"
)

type vector struct {
ID string `json:"id"`
Description string `json:"description"`
Input interface{} `json:"input"`
Expected string `json:"expected"`
}

type vectorPack struct {
Vectors []vector `json:"vectors"`
}

func loadVectors(t *testing.T) []vector {
t.Helper()
_, filename, _, _ := runtime.Caller(0)
dir := filepath.Dir(filename)
data, err := os.ReadFile(filepath.Join(dir, "vectors.json"))
if err != nil {
t.Fatalf("failed to read vectors.json: %v", err)
}
var pack vectorPack
if err := json.Unmarshal(data, &pack); err != nil {
t.Fatalf("failed to parse vectors.json: %v", err)
}
return pack.Vectors
}

func TestCanonicalJSONParity(t *testing.T) {
vectors := loadVectors(t)
for _, v := range vectors {
t.Run(v.ID, func(t *testing.T) {
// Re-marshal the input to raw JSON bytes first
inputBytes, err := json.Marshal(v.Input)
if err != nil {
t.Fatalf("failed to marshal input: %v", err)
}
actual, err := osp.CanonicalJSONFromBytes(inputBytes)
if err != nil {
t.Fatalf("CanonicalJSONFromBytes error: %v", err)
}
if string(actual) != v.Expected {
t.Errorf("mismatch\n expected: %s\n actual: %s", v.Expected, string(actual))
}
})
}
}
48 changes: 48 additions & 0 deletions conformance-tests/fixtures/canonical-json/parity_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
"""
Canonical JSON parity test — Python

Loads the shared vector pack and verifies that the Python SDK's canonical_json()
produces identical output for every test vector.
"""

import json
import sys
from pathlib import Path

# Add the Python SDK to the import path
sdk_path = Path(__file__).resolve().parents[3] / "reference-implementation" / "python" / "src"
sys.path.insert(0, str(sdk_path))

from osp.manifest import canonical_json # noqa: E402


def load_vectors() -> list[dict]:
vectors_path = Path(__file__).parent / "vectors.json"
with open(vectors_path) as f:
data = json.load(f)
return data["vectors"]


def test_canonical_json_parity():
"""Verify Python canonical_json matches every shared test vector."""
vectors = load_vectors()
passed = 0
failed = 0

for v in vectors:
actual = canonical_json(v["input"])
if actual == v["expected"]:
passed += 1
else:
failed += 1
print(f"FAIL [{v['id']}]: {v['description']}")
print(f" expected: {v['expected']}")
print(f" actual: {actual}")

print(f"\nCanonical JSON parity (Python): {passed}/{len(vectors)} passed")
return failed == 0


if __name__ == "__main__":
success = test_canonical_json_parity()
sys.exit(0 if success else 1)
114 changes: 114 additions & 0 deletions conformance-tests/fixtures/canonical-json/vectors.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
{
"description": "Canonical JSON test vectors for cross-SDK parity verification",
"version": "1.0.0",
"vectors": [
{
"id": "simple-sorted-keys",
"description": "Object keys must be sorted lexicographically",
"input": {"zebra": 1, "apple": 2, "mango": 3},
"expected": "{\"apple\":2,\"mango\":3,\"zebra\":1}"
},
{
"id": "nested-sorted-keys",
"description": "Nested object keys must also be sorted",
"input": {"b": {"z": 1, "a": 2}, "a": {"y": 3, "x": 4}},
"expected": "{\"a\":{\"x\":4,\"y\":3},\"b\":{\"a\":2,\"z\":1}}"
},
{
"id": "array-ordering-preserved",
"description": "Array element order must be preserved",
"input": {"items": [3, 1, 2]},
"expected": "{\"items\":[3,1,2]}"
},
{
"id": "unicode-escaping",
"description": "Unicode characters outside ASCII must be preserved, not escaped",
"input": {"name": "José García"},
"expected": "{\"name\":\"José García\"}"
},
{
"id": "empty-object",
"description": "Empty object serializes to {}",
"input": {},
"expected": "{}"
},
{
"id": "empty-array",
"description": "Empty array serializes to []",
"input": {"list": []},
"expected": "{\"list\":[]}"
},
{
"id": "null-value",
"description": "Null values must be preserved",
"input": {"key": null},
"expected": "{\"key\":null}"
},
{
"id": "boolean-values",
"description": "Boolean true/false serialized correctly",
"input": {"active": true, "deleted": false},
"expected": "{\"active\":true,\"deleted\":false}"
},
{
"id": "numeric-precision",
"description": "Numbers must not gain or lose precision",
"input": {"price": 29.99, "count": 0, "negative": -1},
"expected": "{\"count\":0,\"negative\":-1,\"price\":29.99}"
},
{
"id": "deeply-nested",
"description": "Deep nesting must sort at every level",
"input": {"c": {"b": {"a": 1, "z": 2}}, "a": 3},
"expected": "{\"a\":3,\"c\":{\"b\":{\"a\":1,\"z\":2}}}"
},
{
"id": "manifest-like",
"description": "Realistic manifest-shaped object",
"input": {
"provider_id": "prv_test",
"manifest_version": 1,
"display_name": "Test Provider",
"offerings": [
{
"offering_id": "db-postgres",
"tiers": [
{"tier_id": "free", "price": {"amount": "0.00", "currency": "USD"}},
{"tier_id": "pro", "price": {"amount": "29.00", "currency": "USD"}}
]
}
],
"accepted_payment_methods": ["free", "sardis_wallet"]
},
"expected": "{\"accepted_payment_methods\":[\"free\",\"sardis_wallet\"],\"display_name\":\"Test Provider\",\"manifest_version\":1,\"offerings\":[{\"offering_id\":\"db-postgres\",\"tiers\":[{\"price\":{\"amount\":\"0.00\",\"currency\":\"USD\"},\"tier_id\":\"free\"},{\"price\":{\"amount\":\"29.00\",\"currency\":\"USD\"},\"tier_id\":\"pro\"}]}],\"provider_id\":\"prv_test\"}"
},
{
"id": "payment-proof-like",
"description": "Realistic payment proof envelope",
"input": {
"version": "1",
"mandate_id": "mnd_abc",
"amount": "29.00",
"currency": "USD",
"provider_id": "prv_neon",
"offering_id": "db-postgres",
"tier_id": "pro",
"signature": "ed25519:test",
"expires_at": "2025-04-01T00:00:00Z"
},
"expected": "{\"amount\":\"29.00\",\"currency\":\"USD\",\"expires_at\":\"2025-04-01T00:00:00Z\",\"mandate_id\":\"mnd_abc\",\"offering_id\":\"db-postgres\",\"provider_id\":\"prv_neon\",\"signature\":\"ed25519:test\",\"tier_id\":\"pro\",\"version\":\"1\"}"
},
{
"id": "bad-ordering-regression",
"description": "Keys that differ only in case must sort by codepoint",
"input": {"Z": 1, "a": 2, "A": 3, "z": 4},
"expected": "{\"A\":3,\"Z\":1,\"a\":2,\"z\":4}"
},
{
"id": "string-with-special-chars",
"description": "Strings with quotes, backslashes, and control chars",
"input": {"msg": "line1\nline2\ttab \"quoted\""},
"expected": "{\"msg\":\"line1\\nline2\\ttab \\\"quoted\\\"\"}"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Invalid Manifest Verification Behavior

All OSP SDK implementations MUST produce consistent behavior when verifying manifests that are invalid, tampered, or malformed.

## Required Behavior Matrix

| Scenario | Expected Result | Error Type |
|----------|----------------|------------|
| Valid signature, untampered manifest | `valid` / `true` | — |
| Tampered field (any field except `provider_signature`) | `invalid` / `false` | `signature_mismatch` |
| Missing `provider_signature` field | `invalid` / `false` | `missing_signature` |
| Empty string `provider_signature` | `invalid` / `false` | `invalid_signature_format` |
| Malformed base64url in `provider_signature` | `invalid` / `false` | `invalid_signature_format` |
| Wrong public key (key mismatch) | `invalid` / `false` | `signature_mismatch` |
| Malformed base64url in `provider_public_key` | `invalid` / `false` | `invalid_key_format` |
| Missing `provider_public_key` field | `invalid` / `false` | `missing_public_key` |
| Valid signature but expired `effective_at` | `valid` / `true` | — (freshness is caller responsibility) |

## SDK Alignment Rules

1. **No exceptions on invalid signatures**: SDKs MUST return `false` or an error result — never throw/panic on verification failure.
2. **Consistent error categories**: All SDKs MUST use the error types listed above.
3. **Signature scope**: The signature covers the canonical JSON of the manifest *excluding* the `provider_signature` field itself.
4. **Fingerprint derivation**: Provider fingerprint = SHA-256 of the raw Ed25519 public key bytes, encoded as base64url. All SDKs MUST derive identical fingerprints from the same key material.

## Test Procedure

1. Load `manifests.json` fixtures.
2. For each fixture, call the SDK's manifest verification function.
3. Assert that the result matches `fixture.expect` (`"valid"` or `"invalid"`).
4. For invalid fixtures, assert that the error category matches the expected type.
Loading