tersign 0.1.11 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -3
- package/dist/cli.js +8 -1
- package/dist/disclose-bin.d.ts +2 -0
- package/dist/disclose-bin.js +50 -0
- package/dist/evidence/disclose.d.ts +42 -0
- package/dist/evidence/disclose.js +33 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/keystore.d.ts +23 -0
- package/dist/keystore.js +69 -0
- package/dist/mcp/server.d.ts +1 -1
- package/dist/mcp/server.js +14 -2
- package/dist/mcp/tools.d.ts +11 -0
- package/dist/mcp/tools.js +18 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -33,6 +33,16 @@ ledger: counter-signed OK (seller tersign-first, seq 1 …) VALID
|
|
|
33
33
|
curl https://tersign.ai/v1/receipts/0xe5874f1ffe87f0a6dd9eb157730f67b86ee4538b125fe30fcc4e165213dd3fc4/verify
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
+
## One-Call Disclosure Evidence
|
|
37
|
+
|
|
38
|
+
Counter-signed evidence that your agent presented a disclosure — one command, no account:
|
|
39
|
+
|
|
40
|
+
```sh
|
|
41
|
+
npx tersign disclose "You are chatting with an AI assistant." --medium chat --agent-id my-agent
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The text is digested **locally** (only the digest travels — data-minimization by construction). Your key signs the record; the ledger counter-signs it into a per-signer hash chain whose head is submitted for Bitcoin anchoring on a six-hourly cron. First call self-provisions a free signer-keyed account bound set-once to your key (key resolution: `TERSIGN_SELLER_KEY` env → macOS keychain `tersign-signer` → `~/.tersign/signer.key`, created on first use). Free tier is quota- and rate-limited — [limits](https://tersign.ai/pricing). What this is: independently verifiable evidence the disclosure was attested at that time. What it is not: a compliance certification.
|
|
45
|
+
|
|
36
46
|
## Chain of Custody
|
|
37
47
|
|
|
38
48
|
Every entry takes the same path: the seller **signs** the receipt (EIP-712, x402 offer-receipt extension) → Tersign computes the **keccak256 canonical digest** → the digest joins that **seller's hash chain**, each `seq n` bound to `seq n−1` → the neutral ledger **counter-signs** (secp256k1) → **anyone verifies**, and any venue gets a serialized envelope.
|
|
@@ -86,7 +96,7 @@ npm i tersign
|
|
|
86
96
|
}
|
|
87
97
|
```
|
|
88
98
|
|
|
89
|
-
**Tools** — `issue_receipt` · `verify_receipt` · `verify_compliance_record` · `record_refund` · `open_dispute` · `submit_dispute_evidence` · `adjudicate_dispute` · `get_dispute`
|
|
99
|
+
**Tools** — `issue_receipt` · `verify_receipt` · `verify_compliance_record` · `record_disclosure` · `record_refund` · `open_dispute` · `submit_dispute_evidence` · `adjudicate_dispute` · `get_dispute`
|
|
90
100
|
|
|
91
101
|
| Env var | Required | Purpose |
|
|
92
102
|
|---|---|---|
|
|
@@ -105,8 +115,8 @@ The agent skill `tersign-evidence` ships at [tersignhq/skills](https://github.co
|
|
|
105
115
|
|
|
106
116
|
- **Ledger + dashboard** — public verify page: https://tersign.ai/verify
|
|
107
117
|
- **Census** — hash-chained observations across the live x402 seller catalog, probed autonomously; the numbers are served live, never quoted stale: https://prober.tersign.ai/v1/prober/stats
|
|
108
|
-
- **Conformance** — RFC 8785 (JCS) canonical serialization, keccak256 digests, and the public two-sided vector suite (
|
|
109
|
-
- **Standards** — the `compliance-fields` extension (
|
|
118
|
+
- **Conformance** — RFC 8785 (JCS) canonical serialization, keccak256 digests, and the public two-sided vector suite (canonical bytes, number domain, content address, chain continuity, completeness, anchored existence, phase separation, offer binding, independence — every criterion carrying both an accepting and an adversarial vector): [tersignhq/evidence-record-conformance](https://github.com/tersignhq/evidence-record-conformance). Reproduce the bytes and your implementation is conformant — in any language.
|
|
119
|
+
- **Standards** — the `compliance-fields` extension — a typed compliance-record schema plus four evaluator-side disqualifications (independence, completeness/existence, economic-phase separation, and commitment scope — an independence claim reaches exactly as far as the record's own commitments), each executable as a two-sided conformance vector — is under review upstream ([x402-foundation/x402#2853](https://github.com/x402-foundation/x402/pull/2853)) and referenced in the x402 TSC's evidence-record charter agenda ([tsc#4](https://github.com/x402-foundation/tsc/issues/4)). The merged offer-receipt spec already carries the completeness criterion at SHOULD strength (§4.5.1, [#2811](https://github.com/x402-foundation/x402/pull/2811)).
|
|
110
120
|
|
|
111
121
|
## Machine Surfaces
|
|
112
122
|
|
package/dist/cli.js
CHANGED
|
@@ -19,12 +19,19 @@ else if (sub === 'verify') {
|
|
|
19
19
|
process.argv.splice(2, 1);
|
|
20
20
|
await import('./verify-bin.js');
|
|
21
21
|
}
|
|
22
|
+
else if (sub === 'disclose') {
|
|
23
|
+
process.argv.splice(2, 1);
|
|
24
|
+
await import('./disclose-bin.js');
|
|
25
|
+
}
|
|
22
26
|
else if (sub === 'help' || sub === '--help' || sub === '-h') {
|
|
23
27
|
console.log('tersign — evidence layer for the agent economy\n\n' +
|
|
24
28
|
' tersign start the MCP server (stdio)\n' +
|
|
25
29
|
' tersign mcp same, explicit\n' +
|
|
26
30
|
' tersign verify <receipt.json | 0xdigest> [--signer 0xaddr] [--ledger url]\n' +
|
|
27
|
-
' verify a receipt: local signature recovery + public chain check\n'
|
|
31
|
+
' verify a receipt: local signature recovery + public chain check\n' +
|
|
32
|
+
' tersign disclose "<text>" [--medium chat] [--agent-id id] [--url resourceUrl]\n' +
|
|
33
|
+
' counter-signed disclosure evidence — text digested locally,\n' +
|
|
34
|
+
' only the digest travels; key created on first use\n');
|
|
28
35
|
}
|
|
29
36
|
else {
|
|
30
37
|
console.error(`unknown subcommand '${sub}' — did you mean: tersign verify ${sub}\nrun 'tersign help' for usage`);
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/** tersign disclose — one-call counter-signed disclosure evidence.
|
|
3
|
+
*
|
|
4
|
+
* tersign disclose "<text>" [--medium chat] [--agent-id my-agent] [--kind ai-interaction]
|
|
5
|
+
* [--url <resourceUrl>] [--ledger https://tersign.ai]
|
|
6
|
+
*
|
|
7
|
+
* The text is digested LOCALLY (keccak256 over its canonical JSON form) — only the digest
|
|
8
|
+
* travels. Key resolution: TERSIGN_SELLER_KEY env → macOS keychain (tersign-signer) →
|
|
9
|
+
* ~/.tersign/signer.key; a key is created on first use. */
|
|
10
|
+
import { privateKeyToAccount } from 'viem/accounts';
|
|
11
|
+
import { recordDisclosure } from './evidence/disclose.js';
|
|
12
|
+
import { resolveSignerKey } from './keystore.js';
|
|
13
|
+
function arg(flag) {
|
|
14
|
+
const i = process.argv.indexOf(flag);
|
|
15
|
+
return i > 0 ? process.argv[i + 1] : undefined;
|
|
16
|
+
}
|
|
17
|
+
const text = process.argv[2];
|
|
18
|
+
if (text === undefined || text.startsWith('--')) {
|
|
19
|
+
console.error('usage: tersign disclose "<disclosure text>" [--medium chat] [--agent-id id] ' +
|
|
20
|
+
'[--kind ai-interaction|synthetic-content] [--url resourceUrl] [--ledger url]');
|
|
21
|
+
process.exit(2);
|
|
22
|
+
}
|
|
23
|
+
const kind = arg('--kind') ?? 'ai-interaction';
|
|
24
|
+
if (kind !== 'ai-interaction' && kind !== 'synthetic-content') {
|
|
25
|
+
console.error(`--kind must be ai-interaction or synthetic-content (got '${kind}')`);
|
|
26
|
+
process.exit(2);
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
const { key, source } = resolveSignerKey({ create: true });
|
|
30
|
+
const account = privateKeyToAccount(key);
|
|
31
|
+
console.error(`signing key: ${account.address} (${source})`);
|
|
32
|
+
const result = await recordDisclosure({
|
|
33
|
+
text,
|
|
34
|
+
kind,
|
|
35
|
+
agentId: arg('--agent-id') ?? 'cli',
|
|
36
|
+
...(arg('--medium') !== undefined ? { medium: arg('--medium') } : {}),
|
|
37
|
+
...(arg('--url') !== undefined ? { resourceUrl: arg('--url') } : {}),
|
|
38
|
+
...(arg('--ledger') !== undefined ? { ledger: arg('--ledger') } : {}),
|
|
39
|
+
account,
|
|
40
|
+
});
|
|
41
|
+
console.log(`digest ${result.digest}`);
|
|
42
|
+
console.log(`seq ${result.seq}`);
|
|
43
|
+
console.log(`countersignature ${result.countersignature.slice(0, 24)}…`);
|
|
44
|
+
console.log(`tier ${result.tier}`);
|
|
45
|
+
console.log(`verify ${result.verifyUrl}`);
|
|
46
|
+
}
|
|
47
|
+
catch (e) {
|
|
48
|
+
console.error(e instanceof Error ? e.message : String(e));
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { Account } from 'viem/accounts';
|
|
2
|
+
import { type DisclosureKind, type SignedActionRecord } from './action.js';
|
|
3
|
+
/** One-call counter-signed disclosure — the wedge flow.
|
|
4
|
+
*
|
|
5
|
+
* Data-minimization by construction: the disclosure TEXT never leaves this machine. Only
|
|
6
|
+
* `digestOf(text)` travels (or a caller-supplied digest); the ledger counter-signs the
|
|
7
|
+
* digest-bearing record into the signer's per-seller hash chain and the raw content stays
|
|
8
|
+
* under the deployer's own retention. The route is public and signer-keyed: first call
|
|
9
|
+
* self-provisions a free wedge account bound set-once to the signing key. */
|
|
10
|
+
export interface RecordDisclosureOptions {
|
|
11
|
+
/** the disclosure text as shown — digested locally, never transmitted */
|
|
12
|
+
text?: string;
|
|
13
|
+
/** pre-computed digest; wins over `text` when both are given */
|
|
14
|
+
textDigest?: `0x${string}`;
|
|
15
|
+
/** channel the disclosure was presented on: 'chat' | 'api' | 'voice' | 'ui' … */
|
|
16
|
+
medium?: string;
|
|
17
|
+
kind?: DisclosureKind;
|
|
18
|
+
/** stable identifier for the agent (deployer-scoped) */
|
|
19
|
+
agentId: string;
|
|
20
|
+
resourceUrl?: string;
|
|
21
|
+
/** ledger base URL */
|
|
22
|
+
ledger?: string;
|
|
23
|
+
/** signing account; callers on Node can resolve one via the keystore helper */
|
|
24
|
+
account: Account;
|
|
25
|
+
fetchImpl?: typeof fetch;
|
|
26
|
+
clock?: () => number;
|
|
27
|
+
}
|
|
28
|
+
export interface RecordDisclosureResult {
|
|
29
|
+
id: string;
|
|
30
|
+
digest: `0x${string}`;
|
|
31
|
+
seq: number;
|
|
32
|
+
prevDigest: `0x${string}` | null;
|
|
33
|
+
countersignature: string;
|
|
34
|
+
ledgerSigner: string;
|
|
35
|
+
signer: string;
|
|
36
|
+
verifyUrl: string;
|
|
37
|
+
tier: string;
|
|
38
|
+
note: string;
|
|
39
|
+
/** the exact signed record submitted — keep it; it is your half of the evidence */
|
|
40
|
+
record: SignedActionRecord;
|
|
41
|
+
}
|
|
42
|
+
export declare function recordDisclosure(opts: RecordDisclosureOptions): Promise<RecordDisclosureResult>;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { digestOf } from '../canonical.js';
|
|
2
|
+
import { signActionRecord } from './action.js';
|
|
3
|
+
export async function recordDisclosure(opts) {
|
|
4
|
+
if (opts.text === undefined && opts.textDigest === undefined) {
|
|
5
|
+
throw new Error('recordDisclosure needs `text` (digested locally) or a pre-computed `textDigest`');
|
|
6
|
+
}
|
|
7
|
+
const now = (opts.clock ?? (() => Math.floor(Date.now() / 1000)))();
|
|
8
|
+
const record = {
|
|
9
|
+
version: 1,
|
|
10
|
+
agent: { id: opts.agentId },
|
|
11
|
+
action: { kind: 'disclosure' },
|
|
12
|
+
disclosure: {
|
|
13
|
+
kind: opts.kind ?? 'ai-interaction',
|
|
14
|
+
presentedAt: now,
|
|
15
|
+
...(opts.medium !== undefined ? { medium: opts.medium } : {}),
|
|
16
|
+
textDigest: opts.textDigest ?? digestOf(opts.text),
|
|
17
|
+
},
|
|
18
|
+
...(opts.resourceUrl !== undefined ? { resourceUrl: opts.resourceUrl } : {}),
|
|
19
|
+
occurredAt: now,
|
|
20
|
+
};
|
|
21
|
+
const signed = await signActionRecord(record, opts.account);
|
|
22
|
+
const base = (opts.ledger ?? 'https://tersign.ai').replace(/\/$/, '');
|
|
23
|
+
const f = opts.fetchImpl ?? fetch;
|
|
24
|
+
const res = await f(`${base}/v1/disclose`, {
|
|
25
|
+
method: 'POST',
|
|
26
|
+
headers: { 'content-type': 'application/json' },
|
|
27
|
+
body: JSON.stringify({ artifact: signed }),
|
|
28
|
+
});
|
|
29
|
+
const body = await res.json().catch(() => ({}));
|
|
30
|
+
if (!res.ok)
|
|
31
|
+
throw new Error(`disclose failed: ${res.status} ${JSON.stringify(body)}`);
|
|
32
|
+
return { ...body, record: signed };
|
|
33
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export { D1IdempotencyStore, D1_IDEMPOTENCY_DDL, type D1Like } from './idempoten
|
|
|
7
7
|
export type { DisputeReason, DisputeVerdict, DisputeStatus, DisputePayloadV1, CriterionV1, AcceptanceCriteriaV1, EvidenceArtifactRef, EvidencePayloadV1, DisputeAttestationPayload, EvidenceAttestationPayload, CriteriaAttestationPayload, SignedDispute, SignedEvidence, SignedCriteria, } from './dispute/types.js';
|
|
8
8
|
export { DISPUTE_DOMAIN, EVIDENCE_DOMAIN, CRITERIA_DOMAIN, DISPUTE_TYPES, EVIDENCE_TYPES, CRITERIA_TYPES, DISPUTE_WIRE_VECTOR, disputeDigest, evidenceDigest, criteriaDigest, signDispute, verifyDispute, signEvidence, verifyEvidence, signCriteria, verifyCriteria, } from './dispute/sign.js';
|
|
9
9
|
export { ACTION_DOMAIN, ACTION_TYPES, ACTION_WIRE_VECTOR, actionDigest, signActionRecord, verifyActionRecord, type ActionKind, type DisclosureKind, type GovernanceOutcome, type ActionRecordV1, type ActionAttestationPayload, type SignedActionRecord, } from './evidence/action.js';
|
|
10
|
+
export { recordDisclosure, type RecordDisclosureOptions, type RecordDisclosureResult } from './evidence/disclose.js';
|
|
10
11
|
export { LedgerClient, type LedgerConfig, type CountersignResult } from './ledgerClient.js';
|
|
11
12
|
export { Assure, attachToExtensions, type AssureConfig, type SettlementContext, type IssuedReceipt } from './assure.js';
|
|
12
13
|
export { withAssure, extractSettlement, extractPaymentPayload, type WithAssureConfig, type SettlementInfo } from './adapter/x402.js';
|
package/dist/index.js
CHANGED
|
@@ -6,6 +6,7 @@ export { MemoryIdempotencyStore, checkIdempotency, extractPaymentId, fingerprint
|
|
|
6
6
|
export { D1IdempotencyStore, D1_IDEMPOTENCY_DDL } from './idempotency/d1.js';
|
|
7
7
|
export { DISPUTE_DOMAIN, EVIDENCE_DOMAIN, CRITERIA_DOMAIN, DISPUTE_TYPES, EVIDENCE_TYPES, CRITERIA_TYPES, DISPUTE_WIRE_VECTOR, disputeDigest, evidenceDigest, criteriaDigest, signDispute, verifyDispute, signEvidence, verifyEvidence, signCriteria, verifyCriteria, } from './dispute/sign.js';
|
|
8
8
|
export { ACTION_DOMAIN, ACTION_TYPES, ACTION_WIRE_VECTOR, actionDigest, signActionRecord, verifyActionRecord, } from './evidence/action.js';
|
|
9
|
+
export { recordDisclosure } from './evidence/disclose.js';
|
|
9
10
|
export { LedgerClient } from './ledgerClient.js';
|
|
10
11
|
export { Assure, attachToExtensions } from './assure.js';
|
|
11
12
|
export { withAssure, extractSettlement, extractPaymentPayload } from './adapter/x402.js';
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** Signer-key resolution for the CLI/MCP surfaces (Node-only — never imported by the
|
|
2
|
+
* runtime-agnostic evidence modules). The key is the DEPLOYER's key: it signs records the
|
|
3
|
+
* ledger only counter-signs, so it never leaves this machine — custody stays with the
|
|
4
|
+
* deployer by construction.
|
|
5
|
+
*
|
|
6
|
+
* Priority (first hit wins):
|
|
7
|
+
* 1. TERSIGN_SELLER_KEY env — explicit override; headless/CI/agent contract.
|
|
8
|
+
* 2. macOS keychain, service `tersign-signer` — the at-rest default on darwin.
|
|
9
|
+
* 3. keyfile ~/.tersign/signer.key (0600) — portable fallback; created with a warning
|
|
10
|
+
* recommending the env/keychain paths.
|
|
11
|
+
*
|
|
12
|
+
* All keychain access is execFileSync with an argv array — never shell-interpolated. */
|
|
13
|
+
export type SignerKeySource = 'env' | 'keychain' | 'keyfile';
|
|
14
|
+
export interface ResolvedSignerKey {
|
|
15
|
+
key: `0x${string}`;
|
|
16
|
+
source: SignerKeySource;
|
|
17
|
+
}
|
|
18
|
+
/** Resolve the deployer signing key. With `create: true`, a missing key is generated and
|
|
19
|
+
* persisted (keychain on darwin, else a 0600 keyfile); without it, resolution failure throws
|
|
20
|
+
* with the wiring instructions. */
|
|
21
|
+
export declare function resolveSignerKey(opts?: {
|
|
22
|
+
create?: boolean;
|
|
23
|
+
}): ResolvedSignerKey;
|
package/dist/keystore.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { homedir, userInfo } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { generatePrivateKey } from 'viem/accounts';
|
|
6
|
+
const KEY_PATTERN = /^0x[0-9a-fA-F]{64}$/;
|
|
7
|
+
const KEYCHAIN_SERVICE = 'tersign-signer';
|
|
8
|
+
function keyfilePath() {
|
|
9
|
+
return join(homedir(), '.tersign', 'signer.key');
|
|
10
|
+
}
|
|
11
|
+
function readKeychain() {
|
|
12
|
+
if (process.platform !== 'darwin')
|
|
13
|
+
return null;
|
|
14
|
+
try {
|
|
15
|
+
const out = execFileSync('security', ['find-generic-password', '-s', KEYCHAIN_SERVICE, '-w'], {
|
|
16
|
+
encoding: 'utf8',
|
|
17
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
18
|
+
}).trim();
|
|
19
|
+
return KEY_PATTERN.test(out) ? out : null;
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function writeKeychain(key) {
|
|
26
|
+
if (process.platform !== 'darwin')
|
|
27
|
+
return false;
|
|
28
|
+
try {
|
|
29
|
+
execFileSync('security', ['add-generic-password', '-s', KEYCHAIN_SERVICE, '-a', userInfo().username, '-w', key, '-U'], { stdio: ['ignore', 'ignore', 'ignore'] });
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/** Resolve the deployer signing key. With `create: true`, a missing key is generated and
|
|
37
|
+
* persisted (keychain on darwin, else a 0600 keyfile); without it, resolution failure throws
|
|
38
|
+
* with the wiring instructions. */
|
|
39
|
+
export function resolveSignerKey(opts = {}) {
|
|
40
|
+
const env = process.env.TERSIGN_SELLER_KEY;
|
|
41
|
+
if (env !== undefined && env !== '') {
|
|
42
|
+
if (!KEY_PATTERN.test(env))
|
|
43
|
+
throw new Error('TERSIGN_SELLER_KEY must be a 0x-prefixed 32-byte hex key');
|
|
44
|
+
return { key: env, source: 'env' };
|
|
45
|
+
}
|
|
46
|
+
const fromKeychain = readKeychain();
|
|
47
|
+
if (fromKeychain)
|
|
48
|
+
return { key: fromKeychain, source: 'keychain' };
|
|
49
|
+
const file = keyfilePath();
|
|
50
|
+
if (existsSync(file)) {
|
|
51
|
+
const raw = readFileSync(file, 'utf8').trim();
|
|
52
|
+
if (!KEY_PATTERN.test(raw))
|
|
53
|
+
throw new Error(`${file} does not contain a 0x-prefixed 32-byte hex key`);
|
|
54
|
+
return { key: raw, source: 'keyfile' };
|
|
55
|
+
}
|
|
56
|
+
if (!opts.create) {
|
|
57
|
+
throw new Error('no signing key found — set TERSIGN_SELLER_KEY, store one in the macOS keychain ' +
|
|
58
|
+
`(security add-generic-password -s ${KEYCHAIN_SERVICE} -a $USER -w 0x…), or rerun with key creation enabled`);
|
|
59
|
+
}
|
|
60
|
+
const key = generatePrivateKey();
|
|
61
|
+
if (writeKeychain(key))
|
|
62
|
+
return { key, source: 'keychain' };
|
|
63
|
+
mkdirSync(join(homedir(), '.tersign'), { recursive: true, mode: 0o700 });
|
|
64
|
+
writeFileSync(file, `${key}\n`, { mode: 0o600 });
|
|
65
|
+
chmodSync(file, 0o600);
|
|
66
|
+
console.error(`tersign: generated a new signing key at ${file} (0600). This key IS your evidence identity — ` +
|
|
67
|
+
'back it up, and prefer TERSIGN_SELLER_KEY or the OS keychain on shared machines.');
|
|
68
|
+
return { key, source: 'keyfile' };
|
|
69
|
+
}
|
package/dist/mcp/server.d.ts
CHANGED
|
@@ -8,6 +8,6 @@ export declare function envDeps(env?: Record<string, string | undefined>): McpDe
|
|
|
8
8
|
* every client; mcp.test.ts pins it against package.json so a release bump can't drift it. */
|
|
9
9
|
export declare const MCP_SERVER_IDENTITY: {
|
|
10
10
|
readonly name: "tersign";
|
|
11
|
-
readonly version: "0.
|
|
11
|
+
readonly version: "0.2.0";
|
|
12
12
|
};
|
|
13
13
|
export declare function buildServer(deps: McpDeps): McpServer;
|
package/dist/mcp/server.js
CHANGED
|
@@ -3,7 +3,7 @@ import { z } from 'zod';
|
|
|
3
3
|
import { privateKeyToAccount } from 'viem/accounts';
|
|
4
4
|
import { Assure } from '../assure.js';
|
|
5
5
|
import { LedgerClient } from '../ledgerClient.js';
|
|
6
|
-
import { adjudicateDisputeTool, getDisputeTool, issueReceiptTool, openDisputeTool, recordRefundTool, submitEvidenceTool, verifyReceiptTool, verifyRecordTool, } from './tools.js';
|
|
6
|
+
import { adjudicateDisputeTool, getDisputeTool, issueReceiptTool, openDisputeTool, recordDisclosureTool, recordRefundTool, submitEvidenceTool, verifyReceiptTool, verifyRecordTool, } from './tools.js';
|
|
7
7
|
/** MCP packaging: exposes assure as tools any MCP-speaking agent can call, so an agent
|
|
8
8
|
* (or its framework) can issue, verify, and chain receipts without importing the SDK.
|
|
9
9
|
* Config via env — see envDeps(). */
|
|
@@ -45,7 +45,7 @@ function json(value) {
|
|
|
45
45
|
}
|
|
46
46
|
/** MUST match package.json name/version — the MCP handshake self-reports this identity to
|
|
47
47
|
* every client; mcp.test.ts pins it against package.json so a release bump can't drift it. */
|
|
48
|
-
export const MCP_SERVER_IDENTITY = { name: 'tersign', version: '0.
|
|
48
|
+
export const MCP_SERVER_IDENTITY = { name: 'tersign', version: '0.2.0' };
|
|
49
49
|
export function buildServer(deps) {
|
|
50
50
|
const server = new McpServer(MCP_SERVER_IDENTITY);
|
|
51
51
|
server.registerTool('issue_receipt', {
|
|
@@ -71,6 +71,18 @@ export function buildServer(deps) {
|
|
|
71
71
|
expectedSigner: z.string().optional(),
|
|
72
72
|
},
|
|
73
73
|
}, async ({ artifact, expectedSigner }) => json(await verifyReceiptTool(artifact, expectedSigner)));
|
|
74
|
+
server.registerTool('record_disclosure', {
|
|
75
|
+
title: 'Record counter-signed disclosure',
|
|
76
|
+
description: 'One-call disclosure evidence (EU AI Act Art 50 dialect): digests the disclosure text LOCALLY, signs an action record with your key, and the public ledger counter-signs it into your per-signer hash chain. No API key needed — first call self-provisions a free signer-keyed account.',
|
|
77
|
+
inputSchema: {
|
|
78
|
+
text: z.string().optional().describe('the disclosure text as presented — digested locally, never transmitted'),
|
|
79
|
+
textDigest: z.string().regex(/^0x[0-9a-fA-F]{64}$/).optional().describe('pre-computed digest (wins over text)'),
|
|
80
|
+
medium: z.string().optional().describe("channel: 'chat' | 'api' | 'voice' | 'ui' …"),
|
|
81
|
+
kind: z.enum(['ai-interaction', 'synthetic-content']).optional(),
|
|
82
|
+
agentId: z.string().describe('stable identifier for the disclosing agent'),
|
|
83
|
+
resourceUrl: z.string().url().optional(),
|
|
84
|
+
},
|
|
85
|
+
}, async (args) => json(await recordDisclosureTool(deps, args)));
|
|
74
86
|
server.registerTool('verify_compliance_record', {
|
|
75
87
|
title: 'Verify compliance record',
|
|
76
88
|
description: 'Verify an Tersign compliance record + attestation (digest binding and signature).',
|
package/dist/mcp/tools.d.ts
CHANGED
|
@@ -55,3 +55,14 @@ export declare function submitEvidenceTool(deps: McpDeps, args: SubmitEvidenceAr
|
|
|
55
55
|
* may pull the trigger once the route guard allows it). */
|
|
56
56
|
export declare function adjudicateDisputeTool(deps: McpDeps, disputeDigest: `0x${string}`): Promise<unknown>;
|
|
57
57
|
export declare function getDisputeTool(deps: McpDeps, disputeDigest: `0x${string}`): Promise<unknown>;
|
|
58
|
+
export interface RecordDisclosureArgs {
|
|
59
|
+
text?: string | undefined;
|
|
60
|
+
textDigest?: `0x${string}` | undefined;
|
|
61
|
+
medium?: string | undefined;
|
|
62
|
+
kind?: 'ai-interaction' | 'synthetic-content' | undefined;
|
|
63
|
+
agentId: string;
|
|
64
|
+
resourceUrl?: string | undefined;
|
|
65
|
+
}
|
|
66
|
+
/** One-call counter-signed disclosure (public wedge route — no API key needed; the ledger
|
|
67
|
+
* defaults to the hosted instance). The text is digested locally; only the digest travels. */
|
|
68
|
+
export declare function recordDisclosureTool(deps: McpDeps, args: RecordDisclosureArgs): Promise<import("../index.js").RecordDisclosureResult>;
|
package/dist/mcp/tools.js
CHANGED
|
@@ -83,3 +83,21 @@ export async function getDisputeTool(deps, disputeDigest) {
|
|
|
83
83
|
throw new Error('ledger URL not configured — set TERSIGN_LEDGER_URL');
|
|
84
84
|
return ledgerFetch(deps.ledgerHttp.url, `/v1/disputes/${disputeDigest}`);
|
|
85
85
|
}
|
|
86
|
+
/** One-call counter-signed disclosure (public wedge route — no API key needed; the ledger
|
|
87
|
+
* defaults to the hosted instance). The text is digested locally; only the digest travels. */
|
|
88
|
+
export async function recordDisclosureTool(deps, args) {
|
|
89
|
+
if (!deps.signer)
|
|
90
|
+
throw new Error('no signing key configured');
|
|
91
|
+
const { recordDisclosure } = await import('../evidence/disclose.js');
|
|
92
|
+
return recordDisclosure({
|
|
93
|
+
...(args.text !== undefined ? { text: args.text } : {}),
|
|
94
|
+
...(args.textDigest !== undefined ? { textDigest: args.textDigest } : {}),
|
|
95
|
+
...(args.medium !== undefined ? { medium: args.medium } : {}),
|
|
96
|
+
...(args.kind !== undefined ? { kind: args.kind } : {}),
|
|
97
|
+
agentId: args.agentId,
|
|
98
|
+
...(args.resourceUrl !== undefined ? { resourceUrl: args.resourceUrl } : {}),
|
|
99
|
+
ledger: deps.ledgerHttp?.url ?? 'https://tersign.ai',
|
|
100
|
+
account: deps.signer,
|
|
101
|
+
...(deps.clock ? { clock: deps.clock } : {}),
|
|
102
|
+
});
|
|
103
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tersign",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Tersign \u2014 the evidence layer for the agent economy. Counter-signed receipts, agent action records, idempotency enforcement, refunds, disputes, and jury-ready evidence envelopes for x402/agent-commerce sellers.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|