| 1 | import { generateKeyPairSync, privateDecrypt } from 'node:crypto';
|
| 2 | import { describe, expect, it } from 'vitest';
|
| 3 | import {
|
| 4 | createConnectorAgent,
|
| 5 | createConnectorClaimInput,
|
| 6 | encryptConnectorCredential,
|
| 7 | hashConnectorClaimSecret,
|
| 8 | publicConnectorClaim,
|
| 9 | } from '../packages/persistence/src/connector-claim.js';
|
| 10 |
|
| 11 | const profile = {
|
| 12 | description: 'Compares approved supplier quotes',
|
| 13 | framework: 'LangGraph',
|
| 14 | agentVersion: '2.1.0',
|
| 15 | modelProvider: 'Anthropic',
|
| 16 | modelName: 'Claude',
|
| 17 | capabilities: ['compare supplier quotes'],
|
| 18 | limitations: ['requires approval before payment'],
|
| 19 | };
|
| 20 |
|
| 21 | describe('runtime connector claims', () => {
|
| 22 | it('keeps the claim secret out of public state and encrypts credentials to the connector', () => {
|
| 23 | const { publicKey, privateKey } = generateKeyPairSync('rsa', {
|
| 24 | modulusLength: 2048,
|
| 25 | publicKeyEncoding: { type: 'spki', format: 'pem' },
|
| 26 | privateKeyEncoding: { type: 'pkcs8', format: 'pem' },
|
| 27 | });
|
| 28 | const { claim, claimSecret } = createConnectorClaimInput({
|
| 29 | runtimeEndpoint: 'https://agent.example/openclasp',
|
| 30 | credentialPublicKey: publicKey,
|
| 31 | profile,
|
| 32 | });
|
| 33 | expect(claim.secretHash).toBe(hashConnectorClaimSecret(claimSecret));
|
| 34 | expect(publicConnectorClaim(claim)).not.toHaveProperty('secretHash');
|
| 35 | expect(publicConnectorClaim(claim)).not.toHaveProperty('credentialPublicKey');
|
| 36 |
|
| 37 | const encrypted = encryptConnectorCredential(publicKey, 'oc_at_one-time-token');
|
| 38 | expect(
|
| 39 | privateDecrypt(
|
| 40 | { key: privateKey, oaepHash: 'sha256' },
|
| 41 | Buffer.from(encrypted, 'base64url'),
|
| 42 | ).toString('utf8'),
|
| 43 | ).toBe('oc_at_one-time-token');
|
| 44 | });
|
| 45 |
|
| 46 | it('creates an agent-owned profile with self-declared provenance', async () => {
|
| 47 | const rows: Array<{ kind: string; recordId: string; payload: any; metadata?: any }> = [];
|
| 48 | const store = {
|
| 49 | list: async () => rows,
|
| 50 | upsert: async (
|
| 51 | _operatorId: string,
|
| 52 | kind: string,
|
| 53 | recordId: string,
|
| 54 | payload: any,
|
| 55 | metadata?: any,
|
| 56 | ) => void rows.push({ kind, recordId, payload, metadata }),
|
| 57 | } as any;
|
| 58 | const result = await createConnectorAgent(store, 'owner-a', 'Purchasing agent', profile);
|
| 59 | expect(result.project.name).toBe('Connected agents');
|
| 60 | expect(result.agent).toMatchObject({
|
| 61 | identityMode: 'connector_claim',
|
| 62 | name: 'Purchasing agent',
|
| 63 | nameProvenance: 'operator_attested',
|
| 64 | profileProvenance: 'self_declared',
|
| 65 | modelProvider: 'Anthropic',
|
| 66 | modelName: 'Claude',
|
| 67 | autoPublish: false,
|
| 68 | });
|
| 69 | expect(rows.find((row) => row.kind === 'agent_profile')?.metadata).toMatchObject({
|
| 70 | provenance: 'operator_attested',
|
| 71 | });
|
| 72 | });
|
| 73 | });
|