@ar-agents/mercadopago 0.17.2 → 0.18.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/CHANGELOG.md +33 -0
- package/cookbook/18-usa-llc-self-incorporates-ar.ts +208 -0
- package/cookbook/19-forensic-compliance-dashboard.ts +320 -0
- package/cookbook/20-multi-tenant-marketplace.ts +274 -0
- package/cookbook/21-cross-jurisdictional-ap2.ts +298 -0
- package/cookbook/22-mp-webhook-afip-reconciliation.ts +374 -0
- package/cookbook/23-astro-arg-reference-customer.ts +187 -0
- package/cookbook/24-sociedad-ia-disaster-recovery.ts +350 -0
- package/cookbook/25-sociedad-ia-quarterly-compliance.ts +545 -0
- package/cookbook/26-certify-by-fetch.ts +536 -0
- package/cookbook/27-live-conformance-monitoring.ts +260 -0
- package/cookbook/28-operator-onboarding-checklist.ts +315 -0
- package/cookbook/29-publish-your-keys.ts +193 -0
- package/cookbook/30-submit-to-registry.ts +257 -0
- package/dist/index.cjs +27 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +21 -5
- package/dist/index.d.ts +21 -5
- package/dist/index.js +27 -14
- package/dist/index.js.map +1 -1
- package/package.json +5 -3
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recipe 29 — Publish your sociedad-IA's Ed25519 public key (RFC-005 § 4).
|
|
3
|
+
*
|
|
4
|
+
* # Pattern
|
|
5
|
+
*
|
|
6
|
+
* Real operators rotate keys, custody privates in secrets managers, and
|
|
7
|
+
* publish public keys at /.well-known/sociedad-ia/keys. Recipe 29 is the
|
|
8
|
+
* one-time bootstrap + the recurring rotation flow:
|
|
9
|
+
*
|
|
10
|
+
* 1. Generate an Ed25519 keypair locally (Web Crypto OR Node crypto).
|
|
11
|
+
* 2. Convert public key → SPKI base64url (RFC-005 § 4 wire format).
|
|
12
|
+
* 3. Print the public key JSON the operator can copy into
|
|
13
|
+
* apps/.../public/.well-known/sociedad-ia/keys.json.
|
|
14
|
+
* 4. Print the private key as base64url PKCS8 — to paste into the
|
|
15
|
+
* operator's secrets manager (Vercel env `AUDIT_ED25519_PRIVATE_KEY`,
|
|
16
|
+
* 1Password, AWS Secrets Manager, etc.). NEVER commit to a repo.
|
|
17
|
+
*
|
|
18
|
+
* The same keyId can stay valid indefinitely. Rotation is additive:
|
|
19
|
+
* generate a new keypair, append to the published keys list with a
|
|
20
|
+
* later validFrom + null validUntil. Set validUntil on the previous
|
|
21
|
+
* key. Old entries signed with the rotated-out key remain verifiable
|
|
22
|
+
* because the public key stays in the published list.
|
|
23
|
+
*
|
|
24
|
+
* # When to use
|
|
25
|
+
*
|
|
26
|
+
* - First-time setup: right after deploying your sociedad-IA. Generate
|
|
27
|
+
* once, paste public key into your repo, paste private key into
|
|
28
|
+
* your secrets manager.
|
|
29
|
+
* - Scheduled rotation: at 6- or 12-month cadence.
|
|
30
|
+
* - Incident response: if you suspect the private key was compromised.
|
|
31
|
+
*
|
|
32
|
+
* # No Web app needed
|
|
33
|
+
*
|
|
34
|
+
* Recipe 29 is a pure CLI script. Outputs the JSON snippets the
|
|
35
|
+
* operator pastes into their own infrastructure.
|
|
36
|
+
*
|
|
37
|
+
* # Edge / Node compatibility
|
|
38
|
+
*
|
|
39
|
+
* Web Crypto Ed25519 stable in Node 22+ and Vercel Edge. The script
|
|
40
|
+
* uses Web Crypto throughout so it runs anywhere.
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
declare const process: { argv: string[]; exit: (n: number) => void; stdout: { write: (s: string) => void } } | undefined;
|
|
44
|
+
|
|
45
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
46
|
+
// Generation
|
|
47
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
48
|
+
|
|
49
|
+
function b64urlEncode(bytes: Uint8Array): string {
|
|
50
|
+
let s = "";
|
|
51
|
+
for (let i = 0; i < bytes.length; i++) s += String.fromCharCode(bytes[i]);
|
|
52
|
+
return btoa(s).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function hexEncode(bytes: Uint8Array): string {
|
|
56
|
+
return Array.from(bytes)
|
|
57
|
+
.map((b) => b.toString(16).padStart(2, "0"))
|
|
58
|
+
.join("");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface GeneratedKeypair {
|
|
62
|
+
keyId: string;
|
|
63
|
+
alg: "ed25519";
|
|
64
|
+
/** base64url SPKI (DER-encoded SubjectPublicKeyInfo). */
|
|
65
|
+
publicKey: string;
|
|
66
|
+
/** Raw 32-byte Ed25519 point as hex. */
|
|
67
|
+
publicKeyRaw: string;
|
|
68
|
+
/** base64url PKCS8 (DER-encoded PrivateKeyInfo). DO NOT publish. */
|
|
69
|
+
privateKey: string;
|
|
70
|
+
validFrom: string;
|
|
71
|
+
validUntil: null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Generate a fresh Ed25519 keypair + return both the public and private
|
|
76
|
+
* parts in the wire formats RFC-005 § 4 expects.
|
|
77
|
+
*/
|
|
78
|
+
export async function generateKeypair(keyId: string): Promise<GeneratedKeypair> {
|
|
79
|
+
const kp = await crypto.subtle.generateKey(
|
|
80
|
+
{ name: "Ed25519" } as unknown as AlgorithmIdentifier,
|
|
81
|
+
true,
|
|
82
|
+
["sign", "verify"],
|
|
83
|
+
);
|
|
84
|
+
const pubSpki = await crypto.subtle.exportKey("spki", (kp as CryptoKeyPair).publicKey);
|
|
85
|
+
const privPkcs8 = await crypto.subtle.exportKey("pkcs8", (kp as CryptoKeyPair).privateKey);
|
|
86
|
+
const pubSpkiBytes = new Uint8Array(pubSpki);
|
|
87
|
+
// SPKI for Ed25519 = 0x30 0x2a 0x30 0x05 0x06 0x03 0x2b 0x65 0x70 0x03 0x21 0x00 || 32-byte point
|
|
88
|
+
const pubRaw = pubSpkiBytes.slice(-32);
|
|
89
|
+
return {
|
|
90
|
+
keyId,
|
|
91
|
+
alg: "ed25519",
|
|
92
|
+
publicKey: b64urlEncode(pubSpkiBytes),
|
|
93
|
+
publicKeyRaw: hexEncode(pubRaw),
|
|
94
|
+
privateKey: b64urlEncode(new Uint8Array(privPkcs8)),
|
|
95
|
+
validFrom: new Date().toISOString(),
|
|
96
|
+
validUntil: null,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
101
|
+
// Output formatting
|
|
102
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
103
|
+
|
|
104
|
+
interface PublishedKeysFile {
|
|
105
|
+
$schema: string;
|
|
106
|
+
spec: string;
|
|
107
|
+
issuer: {
|
|
108
|
+
jurisdiction: string;
|
|
109
|
+
entityId: string;
|
|
110
|
+
denominacion: string;
|
|
111
|
+
};
|
|
112
|
+
keys: Array<{
|
|
113
|
+
keyId: string;
|
|
114
|
+
alg: "ed25519";
|
|
115
|
+
publicKey: string;
|
|
116
|
+
publicKeyRaw: string;
|
|
117
|
+
validFrom: string;
|
|
118
|
+
validUntil: string | null;
|
|
119
|
+
}>;
|
|
120
|
+
note: string;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function publishedKeysFromKeypair(
|
|
124
|
+
kp: GeneratedKeypair,
|
|
125
|
+
issuer: { jurisdiction: string; entityId: string; denominacion: string },
|
|
126
|
+
): PublishedKeysFile {
|
|
127
|
+
return {
|
|
128
|
+
$schema: "https://ar-agents.ar/schemas/keys.v1.json",
|
|
129
|
+
spec: "https://ar-agents.ar/rfcs/005",
|
|
130
|
+
issuer,
|
|
131
|
+
keys: [
|
|
132
|
+
{
|
|
133
|
+
keyId: kp.keyId,
|
|
134
|
+
alg: kp.alg,
|
|
135
|
+
publicKey: kp.publicKey,
|
|
136
|
+
publicKeyRaw: kp.publicKeyRaw,
|
|
137
|
+
validFrom: kp.validFrom,
|
|
138
|
+
validUntil: kp.validUntil,
|
|
139
|
+
},
|
|
140
|
+
],
|
|
141
|
+
note: "Ed25519 public key for this sociedad-IA's RFC-004/005 audit-log signatures. Private key custody lives in the operator's secrets manager.",
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
146
|
+
// CLI: tsx 29-publish-your-keys.ts <keyId> [<cuit>] [<denominacion>]
|
|
147
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
148
|
+
|
|
149
|
+
async function main() {
|
|
150
|
+
if (typeof process === "undefined") return;
|
|
151
|
+
const [, , keyIdArg, cuit, denominacionArg] = process.argv;
|
|
152
|
+
const keyId = keyIdArg ?? `${defaultKeyIdPrefix()}-${new Date().getUTCFullYear()}-${String(new Date().getUTCMonth() + 1).padStart(2, "0")}`;
|
|
153
|
+
const jurisdiction = "AR";
|
|
154
|
+
const entityId = cuit ? `ar-sociedad:${cuit}` : "ar-sociedad:replace-with-your-cuit";
|
|
155
|
+
const denominacion = denominacionArg ?? "(replace with your sociedad's denominación)";
|
|
156
|
+
|
|
157
|
+
const kp = await generateKeypair(keyId);
|
|
158
|
+
const published = publishedKeysFromKeypair(kp, { jurisdiction, entityId, denominacion });
|
|
159
|
+
|
|
160
|
+
const writeLn = (s: string) => process!.stdout.write(`${s}\n`);
|
|
161
|
+
writeLn("# ════════════════════════════════════════════════════════════════════════");
|
|
162
|
+
writeLn("# 1. Public key — drop this into:");
|
|
163
|
+
writeLn(`# apps/<your-sociedad-app>/public/.well-known/sociedad-ia/keys.json`);
|
|
164
|
+
writeLn("# ════════════════════════════════════════════════════════════════════════");
|
|
165
|
+
writeLn(JSON.stringify(published, null, 2));
|
|
166
|
+
writeLn("");
|
|
167
|
+
writeLn("# ════════════════════════════════════════════════════════════════════════");
|
|
168
|
+
writeLn("# 2. Private key — paste into your operator's secrets manager:");
|
|
169
|
+
writeLn("# Vercel env var: AUDIT_ED25519_PRIVATE_KEY (recommended)");
|
|
170
|
+
writeLn("# DO NOT commit this to a repo. DO NOT publish.");
|
|
171
|
+
writeLn("# ════════════════════════════════════════════════════════════════════════");
|
|
172
|
+
writeLn(kp.privateKey);
|
|
173
|
+
writeLn("");
|
|
174
|
+
writeLn("# ════════════════════════════════════════════════════════════════════════");
|
|
175
|
+
writeLn("# 3. Verify with curl:");
|
|
176
|
+
writeLn("# curl https://your-sociedad.example/.well-known/sociedad-ia/keys.json | jq .keys[0].publicKey");
|
|
177
|
+
writeLn("# Should match the publicKey field in section 1 above.");
|
|
178
|
+
writeLn("# ════════════════════════════════════════════════════════════════════════");
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function defaultKeyIdPrefix(): string {
|
|
182
|
+
return "sociedad-ia-key";
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const isMain = typeof require !== "undefined" && require.main === module;
|
|
186
|
+
if (isMain) {
|
|
187
|
+
main().catch((e) => {
|
|
188
|
+
console.error(e);
|
|
189
|
+
if (typeof process !== "undefined" && "exit" in process) {
|
|
190
|
+
(process as unknown as { exit: (code: number) => void }).exit(1);
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
}
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recipe 30 — Submit your sociedad-IA to the public /registro.
|
|
3
|
+
*
|
|
4
|
+
* # Pattern
|
|
5
|
+
*
|
|
6
|
+
* Once your sociedad-IA is deployed + scoring >= 60 on the public
|
|
7
|
+
* certifier (rating C or better), you can list it in the public
|
|
8
|
+
* registry at /registro. Recipe 30 is the pre-flight check + the
|
|
9
|
+
* PR-body generator.
|
|
10
|
+
*
|
|
11
|
+
* The flow:
|
|
12
|
+
*
|
|
13
|
+
* 1. Run recipe 28 (operator readiness) against your URL.
|
|
14
|
+
* 2. Run recipe 26 (RFC certifier) against your URL.
|
|
15
|
+
* 3. If both pass, recipe 30 produces a single Markdown block you
|
|
16
|
+
* paste into a GitHub PR titled
|
|
17
|
+
* "[/registro] Add <your-sociedad-name>".
|
|
18
|
+
*
|
|
19
|
+
* 4. The PR review checks (manually):
|
|
20
|
+
* - The URL resolves
|
|
21
|
+
* - /.well-known/agents.json is valid
|
|
22
|
+
* - The disclosure is honest (e.g. claims "demo" not "productive"
|
|
23
|
+
* if the sociedad doesn't actually transact)
|
|
24
|
+
* - operatorCuit matches the entityId in the manifest
|
|
25
|
+
*
|
|
26
|
+
* 5. Merge → live in /registro within ~1 hour (next build).
|
|
27
|
+
*
|
|
28
|
+
* # When to use
|
|
29
|
+
*
|
|
30
|
+
* - First-time: after your sociedad-IA is deployed + you want
|
|
31
|
+
* public visibility.
|
|
32
|
+
* - Update: same flow, just amend the existing entry by name.
|
|
33
|
+
*
|
|
34
|
+
* # No silent failures
|
|
35
|
+
*
|
|
36
|
+
* Recipe 30 refuses to produce a PR body if the readiness or certifier
|
|
37
|
+
* checks fail. Returns a remediation report instead. This prevents
|
|
38
|
+
* over-eager submission of half-built sociedades.
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
import { certifySociedad } from "./26-certify-by-fetch";
|
|
42
|
+
import { checkOperatorReadiness } from "./28-operator-onboarding-checklist";
|
|
43
|
+
|
|
44
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
45
|
+
// Types
|
|
46
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
47
|
+
|
|
48
|
+
export interface SubmissionInput {
|
|
49
|
+
/** Display name for the registry. */
|
|
50
|
+
name: string;
|
|
51
|
+
/** Type: "demo", "productive-sociedad-ia", "library-only". */
|
|
52
|
+
type: "demo" | "productive-sociedad-ia" | "library-only";
|
|
53
|
+
/** Operator's CUIT (no formatting). */
|
|
54
|
+
operatorCuit: string;
|
|
55
|
+
/** Operator's full name (legal). */
|
|
56
|
+
operatorName: string;
|
|
57
|
+
/** Public URL of the deployed sociedad. */
|
|
58
|
+
publicUrl: string;
|
|
59
|
+
/** RFC versions claimed. */
|
|
60
|
+
rfcConformance: string[];
|
|
61
|
+
/** Plain-English honest disclosure (1-3 sentences). */
|
|
62
|
+
disclosure: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface SubmissionResult {
|
|
66
|
+
ok: boolean;
|
|
67
|
+
url: string;
|
|
68
|
+
certScore?: number;
|
|
69
|
+
certRating?: string;
|
|
70
|
+
readiness?: "ready" | "almost" | "blocked" | "not-deployed";
|
|
71
|
+
failures: string[];
|
|
72
|
+
prBody?: string;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
76
|
+
// Submission pipeline
|
|
77
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
78
|
+
|
|
79
|
+
const MINIMUM_CERT_SCORE = 60;
|
|
80
|
+
|
|
81
|
+
export async function buildRegistrySubmission(
|
|
82
|
+
input: SubmissionInput,
|
|
83
|
+
options: { apiBaseUrl?: string; fetchImpl?: typeof fetch } = {},
|
|
84
|
+
): Promise<SubmissionResult> {
|
|
85
|
+
const failures: string[] = [];
|
|
86
|
+
|
|
87
|
+
// 1. Operator readiness (recipe 28).
|
|
88
|
+
const readiness = await checkOperatorReadiness(input.publicUrl, {
|
|
89
|
+
fetchImpl: options.fetchImpl,
|
|
90
|
+
});
|
|
91
|
+
if (readiness.readiness === "blocked") {
|
|
92
|
+
failures.push(
|
|
93
|
+
`Operator readiness is "blocked" (${readiness.passedCount}/${readiness.totalCount} items passing). Fix the blocking items before submitting.`,
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// 2. RFC certifier (recipe 26).
|
|
98
|
+
const cert = await certifySociedad(input.publicUrl, {
|
|
99
|
+
fetchImpl: options.fetchImpl,
|
|
100
|
+
});
|
|
101
|
+
if (cert.score < MINIMUM_CERT_SCORE) {
|
|
102
|
+
failures.push(
|
|
103
|
+
`Certifier score ${cert.score}/${cert.rating} is below the minimum ${MINIMUM_CERT_SCORE} required for /registro listing.`,
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// 3. Honesty heuristics.
|
|
108
|
+
if (
|
|
109
|
+
input.type === "productive-sociedad-ia" &&
|
|
110
|
+
!input.disclosure.toLowerCase().includes("real")
|
|
111
|
+
) {
|
|
112
|
+
failures.push(
|
|
113
|
+
`Type is "productive-sociedad-ia" but disclosure doesn't mention "real". Be specific about what real-world transactions the sociedad performs (factura emission, MP cobros, etc.).`,
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
if (
|
|
117
|
+
input.type === "demo" &&
|
|
118
|
+
!input.disclosure.toLowerCase().includes("not a productive") &&
|
|
119
|
+
!input.disclosure.toLowerCase().includes("demo")
|
|
120
|
+
) {
|
|
121
|
+
failures.push(
|
|
122
|
+
`Type is "demo" but disclosure doesn't say "demo" or "not a productive sociedad-IA". Be explicit so a reader doesn't confuse it with a real one.`,
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
if (!input.operatorCuit.match(/^\d{2}-\d{8}-\d$/)) {
|
|
126
|
+
failures.push(
|
|
127
|
+
`operatorCuit "${input.operatorCuit}" doesn't match CUIT format XX-XXXXXXXX-X.`,
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// 4. If everything passes, generate the PR body.
|
|
132
|
+
let prBody: string | undefined;
|
|
133
|
+
if (failures.length === 0) {
|
|
134
|
+
prBody = generatePrBody(input, cert.score, cert.rating, readiness.readiness);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
ok: failures.length === 0,
|
|
139
|
+
url: input.publicUrl,
|
|
140
|
+
certScore: cert.score,
|
|
141
|
+
certRating: cert.rating,
|
|
142
|
+
readiness: readiness.readiness,
|
|
143
|
+
failures,
|
|
144
|
+
prBody,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function generatePrBody(
|
|
149
|
+
input: SubmissionInput,
|
|
150
|
+
certScore: number,
|
|
151
|
+
certRating: string,
|
|
152
|
+
readiness: string,
|
|
153
|
+
): string {
|
|
154
|
+
return `## [/registro] Add \`${input.name}\`
|
|
155
|
+
|
|
156
|
+
### What this PR does
|
|
157
|
+
|
|
158
|
+
Adds the following entry to the public /registro of known sociedad-IA
|
|
159
|
+
implementations:
|
|
160
|
+
|
|
161
|
+
\`\`\`ts
|
|
162
|
+
{
|
|
163
|
+
name: "${input.name}",
|
|
164
|
+
type: "${input.type}",
|
|
165
|
+
jurisdiction: "AR",
|
|
166
|
+
operator: "${input.operatorName}",
|
|
167
|
+
operatorCuit: "${input.operatorCuit}",
|
|
168
|
+
publicUrl: "${input.publicUrl}",
|
|
169
|
+
rfcConformance: [${input.rfcConformance.map((r) => `"${r}"`).join(", ")}],
|
|
170
|
+
disclosure: "${input.disclosure.replace(/"/g, '\\"')}",
|
|
171
|
+
status: "live",
|
|
172
|
+
listedSince: "${new Date().toISOString().slice(0, 10)}",
|
|
173
|
+
}
|
|
174
|
+
\`\`\`
|
|
175
|
+
|
|
176
|
+
### Pre-submission checks
|
|
177
|
+
|
|
178
|
+
- ✅ **Operator readiness** (recipe 28): \`${readiness}\`
|
|
179
|
+
- ✅ **RFC conformance** (recipe 26): score **${certScore}/100** rating **${certRating}**
|
|
180
|
+
- ✅ **CUIT format**: \`${input.operatorCuit}\` matches XX-XXXXXXXX-X
|
|
181
|
+
- ✅ **Disclosure honesty**: type=\`${input.type}\` aligns with disclosure text
|
|
182
|
+
|
|
183
|
+
### What I'm claiming
|
|
184
|
+
|
|
185
|
+
I attest that I am the legitimate operator of this sociedad-IA. The
|
|
186
|
+
\`operatorCuit\` corresponds to a real CUIT under my control. The
|
|
187
|
+
\`/.well-known/agents.json\` at the public URL declares this entity.
|
|
188
|
+
I will keep the deployed endpoints live for at least 90 days from
|
|
189
|
+
merge; if I take the sociedad-IA down, I will open a follow-up PR
|
|
190
|
+
removing the entry.
|
|
191
|
+
|
|
192
|
+
### Verifier instructions for the maintainer
|
|
193
|
+
|
|
194
|
+
1. \`curl https://ar-agents.ar/api/certifier?url=${input.publicUrl}\`
|
|
195
|
+
— expect score >= 60.
|
|
196
|
+
2. \`curl ${input.publicUrl}/.well-known/agents.json\` — expect issuer.operatorCuit to match \`${input.operatorCuit}\`.
|
|
197
|
+
3. Verify disclosure honesty by eye.
|
|
198
|
+
4. Merge.
|
|
199
|
+
`;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
203
|
+
// CLI: tsx 30-submit-to-registry.ts <config.json>
|
|
204
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
205
|
+
|
|
206
|
+
declare const process: { argv: string[] } | undefined;
|
|
207
|
+
|
|
208
|
+
async function main() {
|
|
209
|
+
if (typeof process === "undefined") return;
|
|
210
|
+
const configPath = process.argv[2];
|
|
211
|
+
if (!configPath) {
|
|
212
|
+
console.error("usage: tsx 30-submit-to-registry.ts <config.json>");
|
|
213
|
+
console.error("");
|
|
214
|
+
console.error("config.json shape:");
|
|
215
|
+
console.error(JSON.stringify(
|
|
216
|
+
{
|
|
217
|
+
name: "My Sociedad-IA",
|
|
218
|
+
type: "demo",
|
|
219
|
+
operatorCuit: "20-12345678-9",
|
|
220
|
+
operatorName: "Jane Doe",
|
|
221
|
+
publicUrl: "https://my-sociedad.vercel.app",
|
|
222
|
+
rfcConformance: ["rfc-001-v1", "rfc-002-v1"],
|
|
223
|
+
disclosure: "Single-library demo. Not a productive sociedad-IA.",
|
|
224
|
+
},
|
|
225
|
+
null,
|
|
226
|
+
2,
|
|
227
|
+
));
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
const fs = await import("node:fs/promises");
|
|
231
|
+
const cfg = JSON.parse(await fs.readFile(configPath, "utf8")) as SubmissionInput;
|
|
232
|
+
|
|
233
|
+
const result = await buildRegistrySubmission(cfg);
|
|
234
|
+
|
|
235
|
+
if (result.ok) {
|
|
236
|
+
console.log("--- PR body (copy-paste into your registry PR) ---\n");
|
|
237
|
+
console.log(result.prBody);
|
|
238
|
+
} else {
|
|
239
|
+
console.error("--- Submission blocked: failures need remediation ---\n");
|
|
240
|
+
for (const f of result.failures) console.error(` ✗ ${f}`);
|
|
241
|
+
console.error("\n Cert score:", result.certScore, result.certRating);
|
|
242
|
+
console.error(" Operator readiness:", result.readiness);
|
|
243
|
+
if (typeof process !== "undefined" && "exit" in process) {
|
|
244
|
+
(process as unknown as { exit: (code: number) => void }).exit(1);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const isMain = typeof require !== "undefined" && require.main === module;
|
|
250
|
+
if (isMain) {
|
|
251
|
+
main().catch((e) => {
|
|
252
|
+
console.error(e);
|
|
253
|
+
if (typeof process !== "undefined" && "exit" in process) {
|
|
254
|
+
(process as unknown as { exit: (code: number) => void }).exit(1);
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
}
|
package/dist/index.cjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
var core = require('@ar-agents/core');
|
|
3
4
|
var ai = require('ai');
|
|
4
5
|
var zod = require('zod');
|
|
5
6
|
|
|
@@ -71,19 +72,25 @@ var init_crypto = __esm({
|
|
|
71
72
|
encoder = new TextEncoder();
|
|
72
73
|
}
|
|
73
74
|
});
|
|
74
|
-
|
|
75
|
-
// src/errors.ts
|
|
76
|
-
var MercadoPagoError = class extends Error {
|
|
77
|
-
constructor(message, status, endpoint, mpResponse) {
|
|
78
|
-
super(message);
|
|
79
|
-
this.status = status;
|
|
80
|
-
this.endpoint = endpoint;
|
|
81
|
-
this.mpResponse = mpResponse;
|
|
82
|
-
this.name = "MercadoPagoError";
|
|
83
|
-
}
|
|
75
|
+
var MercadoPagoError = class extends core.ArAgentsError {
|
|
84
76
|
status;
|
|
85
77
|
endpoint;
|
|
86
78
|
mpResponse;
|
|
79
|
+
constructor(message, status, endpoint, mpResponse, init = {}) {
|
|
80
|
+
super(message, {
|
|
81
|
+
code: init.code ?? "mp_api_error",
|
|
82
|
+
retryable: init.retryable ?? (status >= 500 || status === 429 || status === 0),
|
|
83
|
+
context: {
|
|
84
|
+
status,
|
|
85
|
+
endpoint,
|
|
86
|
+
...mpResponse !== void 0 ? { mpResponse } : {}
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
this.name = "MercadoPagoError";
|
|
90
|
+
this.status = status;
|
|
91
|
+
this.endpoint = endpoint;
|
|
92
|
+
if (mpResponse !== void 0) this.mpResponse = mpResponse;
|
|
93
|
+
}
|
|
87
94
|
};
|
|
88
95
|
var MercadoPagoAuthError = class extends MercadoPagoError {
|
|
89
96
|
constructor(endpoint, body) {
|
|
@@ -91,7 +98,8 @@ var MercadoPagoAuthError = class extends MercadoPagoError {
|
|
|
91
98
|
"Mercado Pago rejected the request as unauthorized. Check the access token (TEST- prefix for sandbox, APP_USR- for production).",
|
|
92
99
|
401,
|
|
93
100
|
endpoint,
|
|
94
|
-
body
|
|
101
|
+
body,
|
|
102
|
+
{ code: "mp_auth_failed", retryable: false }
|
|
95
103
|
);
|
|
96
104
|
this.name = "MercadoPagoAuthError";
|
|
97
105
|
}
|
|
@@ -161,7 +169,8 @@ var MercadoPagoRateLimitError = class extends MercadoPagoError {
|
|
|
161
169
|
`Mercado Pago rate limit hit on ${endpoint}. ${retryAfterSeconds ? `Retry after ${retryAfterSeconds}s.` : "Retry with exponential backoff."}`,
|
|
162
170
|
429,
|
|
163
171
|
endpoint,
|
|
164
|
-
body
|
|
172
|
+
body,
|
|
173
|
+
{ code: "mp_rate_limited", retryable: true }
|
|
165
174
|
);
|
|
166
175
|
this.retryAfterSeconds = retryAfterSeconds;
|
|
167
176
|
this.name = "MercadoPagoRateLimitError";
|
|
@@ -173,7 +182,9 @@ var MercadoPagoOverloadedError = class extends MercadoPagoError {
|
|
|
173
182
|
super(
|
|
174
183
|
`Mercado Pago appears overloaded \u2014 returned a non-JSON ${status} response for ${endpoint}. Wait a few seconds and retry.`,
|
|
175
184
|
status,
|
|
176
|
-
endpoint
|
|
185
|
+
endpoint,
|
|
186
|
+
void 0,
|
|
187
|
+
{ code: "mp_overloaded", retryable: true }
|
|
177
188
|
);
|
|
178
189
|
this.name = "MercadoPagoOverloadedError";
|
|
179
190
|
}
|
|
@@ -183,7 +194,9 @@ var MercadoPagoTimeoutError = class extends MercadoPagoError {
|
|
|
183
194
|
super(
|
|
184
195
|
`Mercado Pago request timed out after ${timeoutMs}ms on ${endpoint}. Increase requestTimeoutMs or check connectivity.`,
|
|
185
196
|
0,
|
|
186
|
-
endpoint
|
|
197
|
+
endpoint,
|
|
198
|
+
void 0,
|
|
199
|
+
{ code: "mp_timeout", retryable: true }
|
|
187
200
|
);
|
|
188
201
|
this.timeoutMs = timeoutMs;
|
|
189
202
|
this.name = "MercadoPagoTimeoutError";
|