Skip to content

Commit 7f73579

Browse files
committed
feat: CPTL Phase 2 — Advanced Trust Math (v0.6.0)
- ActionReceipt schema with canonical action categories - 7-dimension trust vector (E/P/B/D/R/A/C) - Economic scaling, diminishing returns, recency decay - Trust engine: src/lib/reputation/trust-engine.ts - API returns trust_vector + legacy windows (backward compat) - SDK: submitActionReceipt() + getTrustProfile() - CLI: coinpay reputation profile <did> - Web UI: trust vector bar chart - DB migration: action_category + action_type columns - All tests passing - Documentation updated
1 parent 754157a commit 7f73579

File tree

15 files changed

+715
-20
lines changed

15 files changed

+715
-20
lines changed

bin/coinpay

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -472,9 +472,10 @@ USAGE
472472
coinpay <command> [options]
473473
474474
COMMANDS
475-
swap Coin swap operations (no KYC)
476-
wallet Wallet management
477-
help Show this help
475+
swap Coin swap operations (no KYC)
476+
wallet Wallet management
477+
reputation Trust & reputation (CPTL)
478+
help Show this help
478479
479480
SWAP COMMANDS
480481
coinpay swap quote Get a swap quote
@@ -496,6 +497,9 @@ WALLET COMMANDS
496497
coinpay wallet backup Export encrypted backup
497498
coinpay wallet delete Delete local wallet file
498499
500+
REPUTATION COMMANDS
501+
coinpay reputation profile <did> Show trust vector & reputation for a DID
502+
499503
GLOBAL OPTIONS
500504
--password <pass> Encryption password (or COINPAY_WALLET_PASSWORD env)
501505
--wallet-file <path> Wallet file path (default: ~/.coinpay-wallet.gpg)
@@ -567,6 +571,54 @@ GLOBAL OPTIONS
567571

568572
// ── Main ────────────────────────────────────────────────────────────
569573

574+
// ── Reputation commands ──────────────────────────────────────────────
575+
576+
async function handleReputation(subCommand, args) {
577+
const opts = parseArgs(args);
578+
switch (subCommand) {
579+
case 'profile': {
580+
const did = opts._positional?.[0] || opts.did;
581+
if (!did) {
582+
console.error('Usage: coinpay reputation profile <did>');
583+
process.exit(1);
584+
}
585+
const res = await fetch(`${API_BASE}/api/reputation/agent/${encodeURIComponent(did)}/reputation`);
586+
const data = await res.json();
587+
if (!data.success) {
588+
console.error('Error:', data.error);
589+
process.exit(1);
590+
}
591+
console.log('\n📊 Reputation Profile:', did);
592+
if (data.trust_vector) {
593+
console.log('\n🔷 Trust Vector (CPTL v2):');
594+
console.log(` E (Economic): ${data.trust_vector.E}`);
595+
console.log(` P (Productivity): ${data.trust_vector.P}`);
596+
console.log(` B (Behavioral): ${data.trust_vector.B}`);
597+
console.log(` D (Diversity): ${data.trust_vector.D}`);
598+
console.log(` R (Recency): ${data.trust_vector.R}`);
599+
console.log(` A (Anomaly): ${data.trust_vector.A}`);
600+
console.log(` C (Compliance): ${data.trust_vector.C}`);
601+
}
602+
if (data.reputation?.windows) {
603+
const w = data.reputation.windows.all_time;
604+
console.log('\n📈 All-Time Stats:');
605+
console.log(` Tasks: ${w.task_count} Accepted: ${w.accepted_count} Disputed: ${w.disputed_count}`);
606+
console.log(` Volume: $${w.total_volume.toFixed(2)} Unique Buyers: ${w.unique_buyers}`);
607+
}
608+
if (data.reputation?.anti_gaming?.flagged) {
609+
console.log('\n⚠️ Anti-gaming flags:', data.reputation.anti_gaming.flags.join(', '));
610+
}
611+
break;
612+
}
613+
case 'help': case '--help': case '-h': default:
614+
console.log(`
615+
Reputation Commands:
616+
coinpay reputation profile <did> Show trust vector and reputation for a DID
617+
`);
618+
break;
619+
}
620+
}
621+
570622
async function main() {
571623
const args = process.argv.slice(2);
572624

@@ -582,6 +634,9 @@ async function main() {
582634
case 'wallet':
583635
await handleWallet(subCommand, args.slice(2));
584636
break;
637+
case 'reputation':
638+
await handleReputation(subCommand, args.slice(2));
639+
break;
585640
case 'help': case '--help': case '-h':
586641
showHelp();
587642
break;

docs/DID_REPUTATION_SYSTEM.md

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -571,14 +571,16 @@ coinpay reputation badge <did> # Get badge URL for a DID
571571
- [x] Embeddable SVG badge
572572
- [x] API documentation
573573

574-
### Phase 2 — Advanced Trust Math
575-
- [ ] ActionReceipt schema (canonical categories)
576-
- [ ] Multi-dimensional trust vector (E/P/B/D/R/A/C)
577-
- [ ] Economic scaling: `log(1 + value_usd)`
578-
- [ ] Diminishing returns: `log(1 + unique_count)`
579-
- [ ] Recency decay: 90-day half-life
580-
- [ ] Diversity multiplier
581-
- [ ] `@coinpayportal/trust-sdk` standalone package
574+
### Phase 2 — Advanced Trust Math ✅
575+
- [x] ActionReceipt schema (canonical categories)
576+
- [x] Multi-dimensional trust vector (E/P/B/D/R/A/C)
577+
- [x] Economic scaling: `log(1 + value_usd)`
578+
- [x] Diminishing returns: `log(1 + unique_count)`
579+
- [x] Recency decay: 90-day half-life
580+
- [x] Diversity multiplier
581+
- [x] SDK: `submitActionReceipt()` + `getTrustProfile()`
582+
- [x] CLI: `coinpay reputation profile <did>`
583+
- [x] Web UI: Trust vector bar chart
582584

583585
### Phase 3 — Anti-Collusion Engine
584586
- [ ] Graph-based loop detection

packages/sdk/src/reputation.d.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,39 @@ export function getCredential(client: CoinPayClient, credentialId: string): Prom
7070
export function verifyCredential(client: CoinPayClient, credential: { credential_id: string }): Promise<{ valid: boolean; reason?: string }>;
7171
export function getRevocationList(client: CoinPayClient): Promise<{ success: boolean; revoked_credentials: string[]; revocations: Array<Record<string, unknown>> }>;
7272

73+
// CPTL Phase 2
74+
75+
export type ActionCategory =
76+
| 'economic.transaction' | 'economic.dispute' | 'economic.refund'
77+
| 'productivity.task' | 'productivity.application' | 'productivity.completion'
78+
| 'identity.profile_update' | 'identity.verification'
79+
| 'social.post' | 'social.comment' | 'social.endorsement'
80+
| 'compliance.incident' | 'compliance.violation';
81+
82+
export interface ActionReceiptInput extends ReceiptInput {
83+
action_category?: ActionCategory;
84+
action_type?: string;
85+
}
86+
87+
export interface TrustVector {
88+
E: number;
89+
P: number;
90+
B: number;
91+
D: number;
92+
R: number;
93+
A: number;
94+
C: number;
95+
}
96+
97+
export interface TrustProfileResult {
98+
trust_vector: TrustVector | null;
99+
reputation: ReputationResult | null;
100+
computed_at: string | null;
101+
}
102+
103+
export function submitActionReceipt(client: CoinPayClient, receipt: ActionReceiptInput): Promise<{ success: boolean; receipt?: Record<string, unknown>; error?: string }>;
104+
export function getTrustProfile(client: CoinPayClient, agentDid: string): Promise<TrustProfileResult>;
105+
73106
export interface DidInfo {
74107
did: string;
75108
public_key: string;

packages/sdk/src/reputation.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,54 @@ export async function getReceipts(client, did) {
123123
return client.request(`/reputation/receipts?did=${encodeURIComponent(did)}`);
124124
}
125125

126+
// ═══════════════════════════════════════════════════════════
127+
// CPTL Phase 2 — Action Receipts & Trust Profile
128+
// ═══════════════════════════════════════════════════════════
129+
130+
const CANONICAL_CATEGORIES = [
131+
'economic.transaction', 'economic.dispute', 'economic.refund',
132+
'productivity.task', 'productivity.application', 'productivity.completion',
133+
'identity.profile_update', 'identity.verification',
134+
'social.post', 'social.comment', 'social.endorsement',
135+
'compliance.incident', 'compliance.violation',
136+
];
137+
138+
/**
139+
* Submit an action receipt with schema validation
140+
* @param {import('./client.js').CoinPayClient} client
141+
* @param {Object} receipt - Action receipt with action_category
142+
* @returns {Promise<Object>}
143+
*/
144+
export async function submitActionReceipt(client, receipt) {
145+
// Validate action_category if provided
146+
if (receipt.action_category && !CANONICAL_CATEGORIES.includes(receipt.action_category)) {
147+
throw new Error(`Invalid action_category: ${receipt.action_category}. Must be one of: ${CANONICAL_CATEGORIES.join(', ')}`);
148+
}
149+
// Default action_category
150+
if (!receipt.action_category) {
151+
receipt = { ...receipt, action_category: 'economic.transaction' };
152+
}
153+
return client.request('/reputation/receipt', {
154+
method: 'POST',
155+
body: JSON.stringify(receipt),
156+
});
157+
}
158+
159+
/**
160+
* Get trust profile (trust vector) for an agent DID
161+
* @param {import('./client.js').CoinPayClient} client
162+
* @param {string} agentDid
163+
* @returns {Promise<Object>} Trust vector { E, P, B, D, R, A, C }
164+
*/
165+
export async function getTrustProfile(client, agentDid) {
166+
const result = await client.request(`/reputation/agent/${encodeURIComponent(agentDid)}/reputation`);
167+
return {
168+
trust_vector: result.trust_vector || null,
169+
reputation: result.reputation || null,
170+
computed_at: result.computed_at || null,
171+
};
172+
}
173+
126174
/**
127175
* Get the badge URL for a DID
128176
* @param {string} baseUrl - The CoinPayPortal base URL

src/app/api/reputation/agent/[did]/reputation/route.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { NextRequest, NextResponse } from 'next/server';
22
import { createClient } from '@supabase/supabase-js';
33
import { computeReputation } from '@/lib/reputation/attestation-engine';
4+
import { computeTrustVector } from '@/lib/reputation/trust-engine';
45
import { isValidDid } from '@/lib/reputation/crypto';
56

67
const supabase = createClient(
@@ -20,8 +21,17 @@ export async function GET(
2021
return NextResponse.json({ success: false, error: 'Invalid DID format' }, { status: 400 });
2122
}
2223

23-
const reputation = await computeReputation(supabase, agentDid);
24-
return NextResponse.json({ success: true, reputation });
24+
const [reputation, trustProfile] = await Promise.all([
25+
computeReputation(supabase, agentDid),
26+
computeTrustVector(supabase, agentDid),
27+
]);
28+
29+
return NextResponse.json({
30+
success: true,
31+
reputation,
32+
trust_vector: trustProfile.trust_vector,
33+
computed_at: trustProfile.computed_at,
34+
});
2535
} catch (error) {
2636
console.error('Reputation query error:', error);
2737
return NextResponse.json({ success: false, error: 'Internal server error' }, { status: 500 });

src/app/docs/sdk/page.tsx

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1035,6 +1035,44 @@ const result = await client.createPayment({
10351035
</CodeBlock>
10361036
</DocSection>
10371037

1038+
{/* CPTL Phase 2 — Trust SDK */}
1039+
<DocSection title="Trust Profile & Action Receipts (CPTL v2)">
1040+
<p className="text-gray-300 mb-4">
1041+
Phase 2 adds multi-dimensional trust scoring with categorized action receipts.
1042+
</p>
1043+
<CodeBlock title="Submit Action Receipt">
1044+
{`import { submitActionReceipt } from '@profullstack/coinpay/reputation';
1045+
1046+
const result = await submitActionReceipt(client, {
1047+
receipt_id: '550e8400-...',
1048+
task_id: '550e8400-...',
1049+
agent_did: 'did:key:z6Mk...',
1050+
buyer_did: 'did:key:z6Mk...',
1051+
action_category: 'productivity.completion', // canonical category
1052+
action_type: 'code_review', // custom action type
1053+
amount: 250,
1054+
currency: 'USD',
1055+
outcome: 'accepted',
1056+
signatures: { escrow_sig: '...' },
1057+
});`}
1058+
</CodeBlock>
1059+
<CodeBlock title="Get Trust Profile">
1060+
{`import { getTrustProfile } from '@profullstack/coinpay/reputation';
1061+
1062+
const profile = await getTrustProfile(client, 'did:key:z6Mk...');
1063+
// profile.trust_vector = { E: 42.5, P: 12.3, B: 9.1, D: 2.08, R: 0.87, A: 0, C: 0 }
1064+
// profile.reputation = { windows: { ... }, anti_gaming: { ... } }
1065+
// profile.computed_at = "2026-02-13T..."`}
1066+
</CodeBlock>
1067+
<p className="text-gray-400 text-sm mt-4">
1068+
Valid action categories: <code>economic.transaction</code>, <code>economic.dispute</code>,
1069+
<code>economic.refund</code>, <code>productivity.task</code>, <code>productivity.application</code>,
1070+
<code>productivity.completion</code>, <code>identity.profile_update</code>,
1071+
<code>identity.verification</code>, <code>social.post</code>, <code>social.comment</code>,
1072+
<code>social.endorsement</code>, <code>compliance.incident</code>, <code>compliance.violation</code>
1073+
</p>
1074+
</DocSection>
1075+
10381076
{/* Footer Navigation */}
10391077
<div className="mt-12 pt-8 border-t border-white/10">
10401078
<div className="flex justify-between items-center">

0 commit comments

Comments
 (0)