This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
import { strict as assert } from 'node:assert';
|
||||
import { randomBytes } from 'node:crypto';
|
||||
import { describe, it } from 'node:test';
|
||||
import type { Config } from '../src/lib/config';
|
||||
import {
|
||||
inviteMetadata,
|
||||
isInferenceEndpoint,
|
||||
memberAccessSchema,
|
||||
platformSettingsResponse,
|
||||
platformSettingsSchema,
|
||||
} from '../src/routes/admin-settings';
|
||||
import { decryptSecret, encryptSecret, SecretConfigurationError } from '../src/lib/secrets';
|
||||
|
||||
describe('admin settings decisions', () => {
|
||||
it('keeps inference separate from the Prime compute API host', () => {
|
||||
assert.equal(isInferenceEndpoint('https://api.pinference.ai/api/v1'), true);
|
||||
assert.equal(isInferenceEndpoint('https://api.primeintellect.ai'), false);
|
||||
assert.equal(
|
||||
platformSettingsSchema.safeParse({
|
||||
piggyInferenceBase: 'https://api.primeintellect.ai/v1',
|
||||
}).success,
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
it('encrypts credentials with authenticated random envelopes and requires an external key', () => {
|
||||
const key = randomBytes(32).toString('base64');
|
||||
const first = encryptSecret('prime-secret', key);
|
||||
const second = encryptSecret('prime-secret', key);
|
||||
assert.notEqual(first, second);
|
||||
assert.equal(decryptSecret(first, key), 'prime-secret');
|
||||
assert.throws(
|
||||
() => encryptSecret('prime-secret', undefined),
|
||||
(error: unknown) => error instanceof SecretConfigurationError,
|
||||
);
|
||||
});
|
||||
|
||||
it('never returns a stored key, ciphertext, or invite hash in metadata', () => {
|
||||
const now = new Date('2026-08-12T12:00:00.000Z');
|
||||
const settings = platformSettingsResponse(
|
||||
{
|
||||
id: 'default',
|
||||
piggyModel: 'nvidia/nemotron-3-nano-30b-a3b',
|
||||
piggyInferenceBase: 'https://api.pinference.ai/api/v1',
|
||||
piggyEnabled: true,
|
||||
primeApiKeyEncrypted: 'v1.iv.tag.ciphertext',
|
||||
primeApiKeyUpdatedAt: now,
|
||||
primeSyncEnabled: true,
|
||||
primeSyncIntervalMinutes: 30,
|
||||
updatedByUserId: null,
|
||||
updatedAt: now,
|
||||
},
|
||||
{
|
||||
PRIME_API_KEY: 'environment-secret',
|
||||
PRIME_API_BASE: 'https://api.primeintellect.ai',
|
||||
} as Config,
|
||||
);
|
||||
assert.equal(JSON.stringify(settings).includes('ciphertext'), false);
|
||||
assert.equal(JSON.stringify(settings).includes('environment-secret'), false);
|
||||
|
||||
const invite = inviteMetadata({
|
||||
id: '00000000-0000-0000-0000-000000000001',
|
||||
codeHash: 'never-return-this',
|
||||
email: null,
|
||||
team: null,
|
||||
role: 'member',
|
||||
createdByUserId: null,
|
||||
expiresAt: null,
|
||||
usesRemaining: 1,
|
||||
scopeNote: null,
|
||||
redeemedByUserId: null,
|
||||
redeemedAt: null,
|
||||
revokedAt: null,
|
||||
createdAt: now,
|
||||
});
|
||||
assert.equal('codeHash' in invite, false);
|
||||
});
|
||||
|
||||
it('rejects duplicate team assignments rather than depending on a database conflict', () => {
|
||||
assert.equal(
|
||||
memberAccessSchema.safeParse({
|
||||
isPlatformAdmin: false,
|
||||
memberships: [
|
||||
{ team: 'supply', role: 'member' },
|
||||
{ team: 'supply', role: 'admin' },
|
||||
],
|
||||
}).success,
|
||||
false,
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,116 @@
|
||||
import { strict as assert } from 'node:assert';
|
||||
import { describe, it } from 'node:test';
|
||||
import type { Principal } from '../src/lib/auth';
|
||||
import {
|
||||
assertApiKeyActive,
|
||||
AuthError,
|
||||
hashApiKey,
|
||||
requireCapability,
|
||||
} from '../src/lib/auth';
|
||||
import {
|
||||
apiKeyCreationResponse,
|
||||
apiKeyCreateSchema,
|
||||
apiKeyMetadata,
|
||||
generateApiKey,
|
||||
resolveApiKeyTarget,
|
||||
} from '../src/routes/api-keys';
|
||||
|
||||
const userId = '00000000-0000-0000-0000-000000000001';
|
||||
const otherUserId = '00000000-0000-0000-0000-000000000002';
|
||||
|
||||
function principal(overrides: Partial<Principal> = {}): Principal {
|
||||
return {
|
||||
userId,
|
||||
email: 'seller@example.com',
|
||||
name: 'Seller',
|
||||
isPlatformAdmin: false,
|
||||
teams: [{ team: 'demand', role: 'member' }],
|
||||
via: 'jwt',
|
||||
scopes: ['read', 'write'],
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function storedKey(overrides: Record<string, unknown> = {}) {
|
||||
return {
|
||||
id: '00000000-0000-0000-0000-000000000010',
|
||||
userId,
|
||||
name: 'Codex',
|
||||
keyHash: 'stored-hash',
|
||||
keyPrefix: 'pig_abc123',
|
||||
scopes: ['read'],
|
||||
lastUsedAt: null,
|
||||
expiresAt: null,
|
||||
revokedAt: null,
|
||||
createdAt: new Date('2026-08-12T12:00:00.000Z'),
|
||||
...overrides,
|
||||
} as Parameters<typeof apiKeyMetadata>[0];
|
||||
}
|
||||
|
||||
describe('API key lifecycle decisions', () => {
|
||||
it('stores only a SHA-256 hash and returns plaintext only from creation', () => {
|
||||
const generated = generateApiKey();
|
||||
assert.match(generated.key, /^pig_[A-Za-z0-9_-]{43}$/);
|
||||
assert.equal(generated.keyHash, hashApiKey(generated.key));
|
||||
assert.equal(generated.keyHash.length, 64);
|
||||
assert.equal(generated.keyPrefix, generated.key.slice(0, 10));
|
||||
|
||||
const row = storedKey({ keyHash: generated.keyHash, keyPrefix: generated.keyPrefix });
|
||||
const created = apiKeyCreationResponse(row, generated.key);
|
||||
const listed = apiKeyMetadata(row);
|
||||
assert.equal(created.key, generated.key);
|
||||
assert.equal('key' in listed, false);
|
||||
assert.equal('keyHash' in listed, false);
|
||||
});
|
||||
|
||||
it('allows read-only or read/write keys, never write-only keys', () => {
|
||||
assert.deepEqual(apiKeyCreateSchema.parse({ name: 'Reader' }).scopes, ['read']);
|
||||
assert.deepEqual(
|
||||
apiKeyCreateSchema.parse({ name: 'Writer', scopes: ['read', 'write'] }).scopes,
|
||||
['read', 'write'],
|
||||
);
|
||||
assert.equal(apiKeyCreateSchema.safeParse({ name: 'Writer', scopes: ['write'] }).success, false);
|
||||
assert.equal(
|
||||
apiKeyCreateSchema.safeParse({ name: 'Duplicate', scopes: ['read', 'read'] }).success,
|
||||
false,
|
||||
);
|
||||
|
||||
assert.throws(
|
||||
() => requireCapability(principal({ via: 'api_key', scopes: ['read'] }), 'deal:write', 'demand'),
|
||||
(error: unknown) => error instanceof AuthError && error.code === 'insufficient_scope',
|
||||
);
|
||||
assert.doesNotThrow(() =>
|
||||
requireCapability(
|
||||
principal({ via: 'api_key', scopes: ['read', 'write'] }),
|
||||
'deal:write',
|
||||
'demand',
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('rejects a revoked key immediately while leaving an active key usable', () => {
|
||||
const now = new Date('2026-08-12T12:00:00.000Z');
|
||||
assert.doesNotThrow(() => assertApiKeyActive({ revokedAt: null, expiresAt: null }, now));
|
||||
assert.throws(
|
||||
() => assertApiKeyActive({ revokedAt: new Date('2026-08-12T11:59:00.000Z'), expiresAt: null }, now),
|
||||
(error: unknown) => error instanceof AuthError && error.code === 'revoked_key',
|
||||
);
|
||||
});
|
||||
|
||||
it('keeps self-service personal, with explicit platform-admin cross-user access', () => {
|
||||
assert.equal(resolveApiKeyTarget(principal()), userId);
|
||||
assert.throws(
|
||||
() => resolveApiKeyTarget(principal(), otherUserId),
|
||||
(error: unknown) => error instanceof AuthError && error.code === 'insufficient_permission',
|
||||
);
|
||||
assert.equal(
|
||||
resolveApiKeyTarget(principal({ isPlatformAdmin: true }), otherUserId),
|
||||
otherUserId,
|
||||
);
|
||||
assert.throws(
|
||||
() => resolveApiKeyTarget(principal({ via: 'api_key' })),
|
||||
(error: unknown) =>
|
||||
error instanceof AuthError && error.code === 'credential_management_forbidden',
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,101 @@
|
||||
/**
|
||||
* Tests for the identity-provider boundary.
|
||||
*
|
||||
* These cases pin the trust decisions shared by protected requests and profile
|
||||
* creation. Membership remains deliberately outside this module.
|
||||
*/
|
||||
import { strict as assert } from 'node:assert';
|
||||
import { generateKeyPairSync, type KeyObject } from 'node:crypto';
|
||||
import { createServer, type Server } from 'node:http';
|
||||
import type { AddressInfo } from 'node:net';
|
||||
import { after, before, describe, it } from 'node:test';
|
||||
import { exportJWK, SignJWT } from 'jose';
|
||||
import {
|
||||
createSupabaseAuthProvider,
|
||||
type AuthProvider,
|
||||
} from '../src/lib/auth-provider';
|
||||
|
||||
describe('Supabase auth provider', () => {
|
||||
let server: Server;
|
||||
let provider: AuthProvider;
|
||||
let issuer: string;
|
||||
let privateKey: KeyObject;
|
||||
|
||||
before(async () => {
|
||||
const keys = generateKeyPairSync('rsa', { modulusLength: 2048 });
|
||||
privateKey = keys.privateKey;
|
||||
const publicJwk = await exportJWK(keys.publicKey);
|
||||
|
||||
server = createServer((request, response) => {
|
||||
if (request.url !== '/auth/v1/.well-known/jwks.json') {
|
||||
response.writeHead(404).end();
|
||||
return;
|
||||
}
|
||||
response.setHeader('content-type', 'application/json');
|
||||
response.end(
|
||||
JSON.stringify({
|
||||
keys: [{ ...publicJwk, alg: 'RS256', kid: 'test-key', use: 'sig' }],
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
server.once('error', reject);
|
||||
server.listen(0, '127.0.0.1', resolve);
|
||||
});
|
||||
|
||||
const address = server.address() as AddressInfo;
|
||||
const supabaseUrl = `http://127.0.0.1:${address.port}`;
|
||||
issuer = `${supabaseUrl}/auth/v1`;
|
||||
provider = createSupabaseAuthProvider(supabaseUrl);
|
||||
});
|
||||
|
||||
after(
|
||||
() =>
|
||||
new Promise<void>((resolve, reject) => {
|
||||
server.close((error) => (error ? reject(error) : resolve()));
|
||||
}),
|
||||
);
|
||||
|
||||
async function sign(claims: Record<string, unknown>, tokenIssuer = issuer): Promise<string> {
|
||||
return new SignJWT(claims)
|
||||
.setProtectedHeader({ alg: 'RS256', kid: 'test-key' })
|
||||
.setIssuer(tokenIssuer)
|
||||
.setExpirationTime('5m')
|
||||
.sign(privateKey);
|
||||
}
|
||||
|
||||
it('returns only the verified external identity claims', async () => {
|
||||
const token = await sign({ sub: 'provider-user-1', email: 'Owner@Example.com' });
|
||||
|
||||
assert.deepEqual(await provider.verifyAccessToken(token), {
|
||||
subject: 'provider-user-1',
|
||||
email: 'Owner@Example.com',
|
||||
});
|
||||
});
|
||||
|
||||
it('rejects a correctly signed token issued for a different identity provider', async () => {
|
||||
// Signature validity alone is insufficient: without the issuer check, a
|
||||
// sibling deployment using the same key could authenticate here.
|
||||
const token = await sign({ sub: 'provider-user-1' }, 'https://other.example/auth/v1');
|
||||
|
||||
await assert.rejects(provider.verifyAccessToken(token));
|
||||
});
|
||||
|
||||
it('accepts a subject without email for protected requests', async () => {
|
||||
// Existing members are joined by subject. Email is required only by the
|
||||
// invite-gated profile flow, not as an extra condition on every request.
|
||||
const token = await sign({ sub: 'provider-user-2' });
|
||||
|
||||
assert.deepEqual(await provider.verifyAccessToken(token), {
|
||||
subject: 'provider-user-2',
|
||||
email: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('rejects a token with no stable subject', async () => {
|
||||
const token = await sign({ email: 'owner@example.com' });
|
||||
|
||||
await assert.rejects(provider.verifyAccessToken(token));
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,36 @@
|
||||
import { strict as assert } from 'node:assert';
|
||||
import { describe, it } from 'node:test';
|
||||
import type { Principal } from '../src/lib/auth';
|
||||
import { AuthError, effectivePermissions, requireCapability } from '../src/lib/auth';
|
||||
|
||||
function principal(overrides: Partial<Principal> = {}): Principal {
|
||||
return {
|
||||
userId: '00000000-0000-0000-0000-000000000001',
|
||||
email: 'seller@example.com',
|
||||
name: 'Seller',
|
||||
isPlatformAdmin: false,
|
||||
teams: [{ team: 'demand', role: 'member' }],
|
||||
via: 'jwt',
|
||||
scopes: ['read', 'write'],
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
describe('capability enforcement', () => {
|
||||
it('rejects a role grant from the wrong team', () => {
|
||||
assert.throws(
|
||||
() => requireCapability(principal(), 'deal:write', 'supply'),
|
||||
(error: unknown) => error instanceof AuthError && error.code === 'insufficient_permission',
|
||||
);
|
||||
});
|
||||
|
||||
it('removes write grants from a read-only API key', () => {
|
||||
const readOnly = principal({ via: 'api_key', scopes: ['read'] });
|
||||
|
||||
assert.deepEqual(effectivePermissions(readOnly), []);
|
||||
assert.throws(
|
||||
() => requireCapability(readOnly, 'deal:write', 'demand'),
|
||||
(error: unknown) => error instanceof AuthError && error.code === 'insufficient_scope',
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,173 @@
|
||||
import { strict as assert } from 'node:assert';
|
||||
import { createHash } from 'node:crypto';
|
||||
import { describe, it } from 'node:test';
|
||||
import { schnorr } from '@noble/curves/secp256k1.js';
|
||||
import { getPublicKey, nip19, verifyEvent, type Event } from 'nostr-tools';
|
||||
import { buzzWorkspaceId } from '../src/routes/buzz';
|
||||
import {
|
||||
BuzzNotifier,
|
||||
decodeBuzzAuthorization,
|
||||
normaliseBuzzRelayUrl,
|
||||
parseAndVerifyBuzzAuthTag,
|
||||
} from '../src/services/buzz';
|
||||
import { NotificationDeliveryError } from '../src/services/notifier';
|
||||
import { loadConfig } from '../src/lib/config';
|
||||
|
||||
const AGENT_KEY = `${'0'.repeat(63)}1`;
|
||||
const CHANNEL = '10000000-0000-4000-8000-000000000001';
|
||||
const envelope = {
|
||||
idempotencyKey: 'buzz:link:stage-event',
|
||||
destination: CHANNEL,
|
||||
workspaceId: 'buzz.example.com',
|
||||
notification: {
|
||||
kind: 'stage_change' as const,
|
||||
accountId: '20000000-0000-4000-8000-000000000002',
|
||||
dealId: '30000000-0000-4000-8000-000000000003',
|
||||
dealSide: 'demand' as const,
|
||||
dealName: 'Reserved H100 cluster',
|
||||
fromStage: 'proposal',
|
||||
toStage: 'procurement',
|
||||
changedAt: '2026-08-13T12:00:00.000Z',
|
||||
},
|
||||
};
|
||||
|
||||
describe('Buzz relay identity', () => {
|
||||
it('normalises the WebSocket URL used by Buzz clients into its HTTP bridge community', () => {
|
||||
assert.equal(normaliseBuzzRelayUrl('wss://buzz.example.com/'), 'https://buzz.example.com');
|
||||
assert.equal(buzzWorkspaceId('wss://buzz.example.com/'), 'buzz.example.com');
|
||||
});
|
||||
|
||||
it('accepts official hex and nsec representations as the same identity', () => {
|
||||
const nsec = nip19.nsecEncode(Uint8Array.from(Buffer.from(AGENT_KEY, 'hex')));
|
||||
const fromHex = new BuzzNotifier({ relayUrl: 'https://buzz.example.com', privateKey: AGENT_KEY });
|
||||
const fromNsec = new BuzzNotifier({ relayUrl: 'https://buzz.example.com', privateKey: nsec });
|
||||
assert.equal(fromHex.workspaceId, fromNsec.workspaceId);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Buzz configuration', () => {
|
||||
const base = { DATABASE_URL: 'postgres://pig:pig@localhost:5432/pig', NODE_ENV: 'test' };
|
||||
|
||||
it('requires relay and private identity together', () => {
|
||||
assert.throws(
|
||||
() => loadConfig({ ...base, BUZZ_RELAY_URL: 'https://buzz.example.com' }),
|
||||
/BUZZ_PRIVATE_KEY/,
|
||||
);
|
||||
assert.throws(
|
||||
() => loadConfig({ ...base, BUZZ_PRIVATE_KEY: AGENT_KEY }),
|
||||
/BUZZ_RELAY_URL/,
|
||||
);
|
||||
});
|
||||
|
||||
it('keeps the Buzz private identity in server configuration only', () => {
|
||||
const config = loadConfig({
|
||||
...base,
|
||||
BUZZ_RELAY_URL: 'https://buzz.example.com',
|
||||
BUZZ_PRIVATE_KEY: AGENT_KEY,
|
||||
});
|
||||
assert.equal(config.BUZZ_RELAY_URL, 'https://buzz.example.com');
|
||||
assert.equal(config.BUZZ_PRIVATE_KEY, AGENT_KEY);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Buzz signed delivery', () => {
|
||||
it('reuses the signed message event id while refreshing NIP-98 replay nonces', async () => {
|
||||
const requests: { event: Event; auth: Event }[] = [];
|
||||
let nonce = 0;
|
||||
const notifier = new BuzzNotifier({
|
||||
relayUrl: 'https://buzz.example.com',
|
||||
privateKey: AGENT_KEY,
|
||||
now: () => new Date('2026-08-13T12:00:01.000Z'),
|
||||
nonce: () => `nonce-${++nonce}`,
|
||||
fetchImpl: async (_input, init) => {
|
||||
const event = JSON.parse(String(init?.body)) as Event;
|
||||
const headers = new Headers(init?.headers);
|
||||
const auth = decodeBuzzAuthorization(headers.get('authorization') ?? '');
|
||||
assert.ok(auth);
|
||||
requests.push({ event, auth });
|
||||
return Response.json({ event_id: event.id, accepted: true, message: '' });
|
||||
},
|
||||
});
|
||||
|
||||
await notifier.send(envelope);
|
||||
await notifier.send(envelope);
|
||||
await notifier.send({ ...envelope, idempotencyKey: 'buzz:link:different-event' });
|
||||
|
||||
assert.equal(requests[0]?.event.id, requests[1]?.event.id);
|
||||
assert.notEqual(requests[0]?.event.id, requests[2]?.event.id);
|
||||
assert.notEqual(requests[0]?.auth.id, requests[1]?.auth.id);
|
||||
assert.equal(requests[0]?.event.kind, 9);
|
||||
assert.ok(verifyEvent(requests[0]!.event));
|
||||
assert.deepEqual(requests[0]?.event.tags[0], ['h', CHANNEL]);
|
||||
const payloadTag = requests[0]?.auth.tags.find((tag) => tag[0] === 'payload');
|
||||
assert.equal(
|
||||
payloadTag?.[1],
|
||||
createHash('sha256').update(JSON.stringify(requests[0]!.event)).digest('hex'),
|
||||
);
|
||||
});
|
||||
|
||||
it('refuses to route a link from another relay community', async () => {
|
||||
let called = false;
|
||||
const notifier = new BuzzNotifier({
|
||||
relayUrl: 'https://buzz.example.com',
|
||||
privateKey: AGENT_KEY,
|
||||
fetchImpl: async () => {
|
||||
called = true;
|
||||
return Response.json({});
|
||||
},
|
||||
});
|
||||
await assert.rejects(
|
||||
notifier.send({ ...envelope, workspaceId: 'other.example.com' }),
|
||||
(error: unknown) =>
|
||||
error instanceof NotificationDeliveryError &&
|
||||
error.code === 'buzz_workspace_mismatch' &&
|
||||
!error.retryable,
|
||||
);
|
||||
assert.equal(called, false);
|
||||
});
|
||||
|
||||
it('marks relay throttling retryable without exposing response content', async () => {
|
||||
const notifier = new BuzzNotifier({
|
||||
relayUrl: 'https://buzz.example.com',
|
||||
privateKey: AGENT_KEY,
|
||||
fetchImpl: async () =>
|
||||
new Response('{"error":"rate-limited: private detail"}', {
|
||||
status: 429,
|
||||
headers: { 'retry-after': '9' },
|
||||
}),
|
||||
});
|
||||
await assert.rejects(
|
||||
notifier.send(envelope),
|
||||
(error: unknown) =>
|
||||
error instanceof NotificationDeliveryError &&
|
||||
error.code === 'buzz_rate_limited' &&
|
||||
error.retryable &&
|
||||
error.retryAfterMs === 9_000 &&
|
||||
!error.message.includes('private detail'),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Buzz owner attestation', () => {
|
||||
it('verifies NIP-OA against the configured agent before sending it', () => {
|
||||
const ownerKey = Uint8Array.from(Buffer.from(`${'0'.repeat(63)}2`, 'hex'));
|
||||
const agentPublicKey = getPublicKey(Uint8Array.from(Buffer.from(AGENT_KEY, 'hex')));
|
||||
const conditions = 'kind=9';
|
||||
const digest = createHash('sha256')
|
||||
.update(`nostr:agent-auth:${agentPublicKey}:${conditions}`)
|
||||
.digest();
|
||||
const tag = JSON.stringify([
|
||||
'auth',
|
||||
getPublicKey(ownerKey),
|
||||
conditions,
|
||||
Buffer.from(schnorr.sign(digest, ownerKey)).toString('hex'),
|
||||
]);
|
||||
assert.deepEqual(parseAndVerifyBuzzAuthTag(tag, agentPublicKey), JSON.parse(tag));
|
||||
const tampered = JSON.parse(tag) as string[];
|
||||
tampered[3] = `${tampered[3]![0] === '0' ? '1' : '0'}${tampered[3]!.slice(1)}`;
|
||||
assert.throws(
|
||||
() => parseAndVerifyBuzzAuthTag(JSON.stringify(tampered), agentPublicKey),
|
||||
/BUZZ_AUTH_TAG/,
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,88 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import type { SecurityTier } from '@pig/core';
|
||||
import {
|
||||
capacityMeetsRequirement,
|
||||
type AvailabilityRow,
|
||||
} from '../src/services/capacity';
|
||||
|
||||
function capacity(securityTier: SecurityTier): AvailabilityRow {
|
||||
return {
|
||||
commitmentId: '10000000-0000-4000-8000-000000000001',
|
||||
accountId: '20000000-0000-4000-8000-000000000001',
|
||||
name: `${securityTier} block`,
|
||||
gpuType: 'H100_80GB',
|
||||
gpuCount: 8,
|
||||
interconnectType: 'Infiniband',
|
||||
securityTier,
|
||||
startsAt: new Date('2026-01-01T00:00:00Z'),
|
||||
endsAt: new Date('2027-01-01T00:00:00Z'),
|
||||
totalGpuHours: 10_000,
|
||||
soldGpuHours: 0,
|
||||
heldGpuHours: 0,
|
||||
availableGpuHours: 10_000,
|
||||
costPerGpuHourCents: 100,
|
||||
utilisation: 0,
|
||||
breakEvenPriceCents: 100,
|
||||
};
|
||||
}
|
||||
|
||||
test('government demand excludes both community and ordinary secure cloud', () => {
|
||||
const requirement = { gpuCount: 1, minSecurityTier: 'government' as const };
|
||||
assert.equal(capacityMeetsRequirement(capacity('community_cloud'), requirement), false);
|
||||
assert.equal(capacityMeetsRequirement(capacity('secure_cloud'), requirement), false);
|
||||
assert.equal(capacityMeetsRequirement(capacity('government'), requirement), true);
|
||||
});
|
||||
|
||||
test('higher classifications may satisfy lower requirements', () => {
|
||||
assert.equal(
|
||||
capacityMeetsRequirement(capacity('government'), {
|
||||
gpuCount: 1,
|
||||
minSecurityTier: 'secure_cloud',
|
||||
}),
|
||||
true,
|
||||
);
|
||||
assert.equal(
|
||||
capacityMeetsRequirement(capacity('secure_cloud'), {
|
||||
gpuCount: 1,
|
||||
minSecurityTier: 'community_cloud',
|
||||
}),
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
test('security sufficiency never overrides the export-control predicate', () => {
|
||||
const block = capacity('government');
|
||||
assert.equal(
|
||||
capacityMeetsRequirement(block, {
|
||||
gpuCount: 1,
|
||||
minSecurityTier: 'government',
|
||||
complianceDecision: { decision: 'block', supersededAt: null },
|
||||
}),
|
||||
false,
|
||||
);
|
||||
assert.equal(
|
||||
capacityMeetsRequirement(block, {
|
||||
gpuCount: 1,
|
||||
minSecurityTier: 'government',
|
||||
complianceDecision: null,
|
||||
}),
|
||||
false,
|
||||
);
|
||||
assert.equal(
|
||||
capacityMeetsRequirement(block, {
|
||||
gpuCount: 1,
|
||||
minSecurityTier: 'government',
|
||||
complianceDecision: { decision: 'allow', supersededAt: new Date() },
|
||||
}),
|
||||
false,
|
||||
);
|
||||
assert.equal(
|
||||
capacityMeetsRequirement(block, {
|
||||
gpuCount: 1,
|
||||
minSecurityTier: 'government',
|
||||
complianceDecision: { decision: 'allow', supersededAt: null },
|
||||
}),
|
||||
true,
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,185 @@
|
||||
import { strict as assert } from 'node:assert';
|
||||
import { describe, it } from 'node:test';
|
||||
import type { Allocation, Database } from '@pig/db';
|
||||
import type { Principal } from '../src/lib/auth';
|
||||
import { executeMutation } from '../src/lib/mutation';
|
||||
import { createAllocationMutationDefinition } from '../src/routes/capacity-writes';
|
||||
import {
|
||||
findCapacityViolation,
|
||||
type CommitmentCapacity,
|
||||
type ReservationCapacity,
|
||||
} from '../src/services/capacity-writes';
|
||||
|
||||
const HOUR = 3_600_000;
|
||||
const START = new Date('2026-01-01T00:00:00.000Z');
|
||||
|
||||
function at(hour: number): Date {
|
||||
return new Date(START.getTime() + hour * HOUR);
|
||||
}
|
||||
|
||||
function commitment(overrides: Partial<CommitmentCapacity> = {}): CommitmentCapacity {
|
||||
return {
|
||||
id: '10000000-0000-4000-8000-000000000001',
|
||||
gpuCount: 8,
|
||||
startsAt: at(0),
|
||||
endsAt: at(10),
|
||||
totalGpuHours: 80,
|
||||
shape: null,
|
||||
oversubscriptionPct: 0,
|
||||
terminatedAt: null,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function reservation(overrides: Partial<ReservationCapacity> = {}): ReservationCapacity {
|
||||
return {
|
||||
gpuHours: 80,
|
||||
startsAt: at(0),
|
||||
endsAt: at(10),
|
||||
status: 'committed',
|
||||
holdExpiresAt: null,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
describe('allocation availability invariant', () => {
|
||||
it('permits only the oversubscription explicitly recorded on the commitment', () => {
|
||||
const existing = reservation();
|
||||
const extra = reservation({ gpuHours: 20 });
|
||||
|
||||
assert.equal(
|
||||
findCapacityViolation(
|
||||
commitment({ totalGpuHours: 100, oversubscriptionPct: 25 }),
|
||||
[existing],
|
||||
extra,
|
||||
at(-1),
|
||||
),
|
||||
null,
|
||||
);
|
||||
assert.equal(
|
||||
findCapacityViolation(commitment({ totalGpuHours: 100 }), [existing], extra, at(-1))?.code,
|
||||
'shape_capacity_exceeded',
|
||||
);
|
||||
});
|
||||
|
||||
it('ignores expired holds but live holds still reserve capacity', () => {
|
||||
const expired = reservation({
|
||||
status: 'planned',
|
||||
holdExpiresAt: at(-1),
|
||||
});
|
||||
const live = reservation({
|
||||
status: 'planned',
|
||||
holdExpiresAt: at(1),
|
||||
});
|
||||
const requested = reservation();
|
||||
|
||||
assert.equal(findCapacityViolation(commitment(), [expired], requested, at(0)), null);
|
||||
assert.equal(
|
||||
findCapacityViolation(commitment(), [live], requested, at(0))?.code,
|
||||
'total_capacity_exceeded',
|
||||
);
|
||||
});
|
||||
|
||||
it('checks each authoritative shape interval instead of averaging the term', () => {
|
||||
const shaped = commitment({
|
||||
endsAt: at(20),
|
||||
totalGpuHours: 120,
|
||||
shape: {
|
||||
intervals: [at(0).toISOString(), at(10).toISOString(), at(20).toISOString()],
|
||||
quantities: [8, 4],
|
||||
},
|
||||
});
|
||||
const firstTranche = reservation({ gpuHours: 60 });
|
||||
const overlapsRamp = reservation({ gpuHours: 30 });
|
||||
const fitsLaterTranche = reservation({
|
||||
gpuHours: 40,
|
||||
startsAt: at(10),
|
||||
endsAt: at(20),
|
||||
});
|
||||
|
||||
assert.equal(
|
||||
findCapacityViolation(shaped, [firstTranche], overlapsRamp, at(-1))?.code,
|
||||
'shape_capacity_exceeded',
|
||||
);
|
||||
assert.equal(findCapacityViolation(shaped, [firstTranche], fitsLaterTranche, at(-1)), null);
|
||||
});
|
||||
});
|
||||
|
||||
const principal: Principal = {
|
||||
userId: '00000000-0000-4000-8000-000000000001',
|
||||
email: 'seller@example.com',
|
||||
name: 'Seller',
|
||||
isPlatformAdmin: false,
|
||||
teams: [{ team: 'demand', role: 'member' }],
|
||||
via: 'jwt',
|
||||
scopes: ['read', 'write'],
|
||||
};
|
||||
|
||||
describe('allocation mutation transaction', () => {
|
||||
it('passes the mutation transaction through the capacity check and audit write', async () => {
|
||||
const events: string[] = [];
|
||||
const tx = {
|
||||
insert: () => ({
|
||||
values: async () => {
|
||||
events.push('activity');
|
||||
},
|
||||
}),
|
||||
};
|
||||
const db = {
|
||||
transaction: async (work: (transaction: unknown) => Promise<unknown>) => {
|
||||
events.push('begin');
|
||||
const result = await work(tx);
|
||||
events.push('commit');
|
||||
return result;
|
||||
},
|
||||
} as unknown as Database;
|
||||
|
||||
const definition = createAllocationMutationDefinition((transaction) => {
|
||||
assert.equal(transaction, tx);
|
||||
return {
|
||||
createAllocation: async (input) => {
|
||||
events.push('lock-check-insert');
|
||||
return {
|
||||
allocation: {
|
||||
id: '30000000-0000-4000-8000-000000000003',
|
||||
capacityCommitmentId: input.capacityCommitmentId,
|
||||
demandDealId: input.demandDealId,
|
||||
gpuHours: String(input.gpuHours),
|
||||
pricePerGpuHourCents: input.pricePerGpuHourCents,
|
||||
status: input.status,
|
||||
} as Allocation,
|
||||
commitment: {
|
||||
id: input.capacityCommitmentId,
|
||||
name: 'Eight H100s',
|
||||
},
|
||||
deal: {
|
||||
id: input.demandDealId,
|
||||
accountId: '40000000-0000-4000-8000-000000000004',
|
||||
},
|
||||
};
|
||||
},
|
||||
createCommitment: async () => assert.fail('wrong mutation'),
|
||||
updateCommitment: async () => assert.fail('wrong mutation'),
|
||||
createHold: async () => assert.fail('wrong mutation'),
|
||||
releaseAllocation: async () => assert.fail('wrong mutation'),
|
||||
};
|
||||
});
|
||||
|
||||
await executeMutation(
|
||||
db,
|
||||
principal,
|
||||
async () => ({
|
||||
capacityCommitmentId: '10000000-0000-4000-8000-000000000001',
|
||||
demandDealId: '20000000-0000-4000-8000-000000000002',
|
||||
gpuHours: 8,
|
||||
pricePerGpuHourCents: 225,
|
||||
startsAt: '2026-01-01T00:00:00.000Z',
|
||||
endsAt: '2026-01-01T01:00:00.000Z',
|
||||
status: 'committed',
|
||||
}),
|
||||
definition,
|
||||
);
|
||||
|
||||
assert.deepEqual(events, ['begin', 'lock-check-insert', 'activity', 'commit']);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,159 @@
|
||||
import { strict as assert } from 'node:assert';
|
||||
import { describe, it } from 'node:test';
|
||||
import type { Contract, SlaTerm } from '@pig/db';
|
||||
import {
|
||||
renewalAlarm,
|
||||
resolveContractPrecedence,
|
||||
validateParentRelationship,
|
||||
} from '../src/services/contracts';
|
||||
|
||||
function contract(overrides: Partial<Contract>): Contract {
|
||||
return {
|
||||
id: '10000000-0000-4000-8000-000000000001',
|
||||
accountId: '20000000-0000-4000-8000-000000000002',
|
||||
type: 'msa',
|
||||
status: 'executed',
|
||||
side: 'demand',
|
||||
title: 'Master agreement',
|
||||
externalReference: null,
|
||||
demandDealId: null,
|
||||
supplyDealId: null,
|
||||
capacityCommitmentId: null,
|
||||
parentContractId: null,
|
||||
contractingPartyName: null,
|
||||
takeOrPayFloorPct: null,
|
||||
prepaidPct: null,
|
||||
terminationTier: null,
|
||||
assignableOnDefault: false,
|
||||
assignmentDeadlineBusinessDays: null,
|
||||
effectiveAt: null,
|
||||
expiresAt: null,
|
||||
executedAt: null,
|
||||
terminatedAt: null,
|
||||
isAutoRenew: false,
|
||||
noticeDays: null,
|
||||
valueCents: null,
|
||||
currency: 'USD',
|
||||
governingLaw: null,
|
||||
documentUrl: null,
|
||||
ownerUserId: null,
|
||||
notes: null,
|
||||
createdAt: new Date('2026-01-01T00:00:00.000Z'),
|
||||
updatedAt: new Date('2026-01-01T00:00:00.000Z'),
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function sla(overrides: Partial<SlaTerm>): SlaTerm {
|
||||
return {
|
||||
id: '30000000-0000-4000-8000-000000000003',
|
||||
contractId: '10000000-0000-4000-8000-000000000001',
|
||||
kind: 'negotiated',
|
||||
uptimeTargetPct: null,
|
||||
nodeReplacementHours: null,
|
||||
mttrHours: null,
|
||||
supportResponseHours: null,
|
||||
measurementWindow: 'monthly',
|
||||
measurementUnit: 'cluster',
|
||||
remedyType: 'service_credit',
|
||||
abatementTriggerValue: null,
|
||||
abatementTriggerUnit: null,
|
||||
claimDeadlineValue: null,
|
||||
claimDeadlineUnit: 'days',
|
||||
creditExpiryMonths: null,
|
||||
isSoleRemedy: true,
|
||||
sparePoolObligation: null,
|
||||
sparePoolScope: [],
|
||||
maintenanceClasses: [],
|
||||
reasonableEndeavoursDaysPerYear: null,
|
||||
rcaDeliveryHours: null,
|
||||
creditSchedule: [],
|
||||
creditCapPct: null,
|
||||
exclusions: null,
|
||||
createdAt: new Date('2026-01-01T00:00:00.000Z'),
|
||||
updatedAt: new Date('2026-01-01T00:00:00.000Z'),
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
describe('contract precedence', () => {
|
||||
it('uses explicit order-form terms before the MSA without copying inherited values', () => {
|
||||
const master = contract({ takeOrPayFloorPct: '70.00', prepaidPct: '25.00' });
|
||||
const order = contract({
|
||||
id: '10000000-0000-4000-8000-000000000004',
|
||||
type: 'order_form',
|
||||
title: 'Order form',
|
||||
parentContractId: master.id,
|
||||
takeOrPayFloorPct: '90.00',
|
||||
});
|
||||
const resolved = resolveContractPrecedence(order.id, [master, order], [
|
||||
sla({ uptimeTargetPct: '99.900' }),
|
||||
]);
|
||||
|
||||
const floor = resolved.contract.takeOrPayFloorPct;
|
||||
const prepaid = resolved.contract.prepaidPct;
|
||||
const uptime = resolved.sla.uptimeTargetPct;
|
||||
assert.ok(floor);
|
||||
assert.ok(prepaid);
|
||||
assert.ok(uptime);
|
||||
|
||||
assert.equal(floor.value, '90.00');
|
||||
assert.equal(floor.inherited, false);
|
||||
assert.equal(prepaid.value, '25.00');
|
||||
assert.equal(prepaid.inherited, true);
|
||||
assert.equal(uptime.sourceContractId, master.id);
|
||||
});
|
||||
|
||||
it('treats false, zero and an empty list as deliberate child overrides', () => {
|
||||
const master = contract({ assignableOnDefault: true });
|
||||
const child = contract({
|
||||
id: '10000000-0000-4000-8000-000000000004',
|
||||
parentContractId: master.id,
|
||||
assignableOnDefault: false,
|
||||
});
|
||||
const resolved = resolveContractPrecedence(child.id, [master, child], [
|
||||
sla({ sparePoolScope: ['compute nodes', 'network switches'] }),
|
||||
sla({
|
||||
id: '30000000-0000-4000-8000-000000000004',
|
||||
contractId: child.id,
|
||||
reasonableEndeavoursDaysPerYear: 0,
|
||||
sparePoolScope: [],
|
||||
}),
|
||||
]);
|
||||
|
||||
const assignable = resolved.contract.assignableOnDefault;
|
||||
const reasonableEndeavours = resolved.sla.reasonableEndeavoursDaysPerYear;
|
||||
const sparePoolScope = resolved.sla.sparePoolScope;
|
||||
assert.ok(assignable);
|
||||
assert.ok(reasonableEndeavours);
|
||||
assert.ok(sparePoolScope);
|
||||
|
||||
assert.equal(assignable.value, false);
|
||||
assert.equal(reasonableEndeavours.value, 0);
|
||||
assert.deepEqual(sparePoolScope.value, []);
|
||||
});
|
||||
});
|
||||
|
||||
describe('contract alarms and hierarchy', () => {
|
||||
it('raises the renewal alarm at the negotiated notice deadline, not at expiry', () => {
|
||||
const result = renewalAlarm(
|
||||
{
|
||||
isAutoRenew: true,
|
||||
expiresAt: new Date('2026-04-01T00:00:00.000Z'),
|
||||
noticeDays: 60,
|
||||
},
|
||||
new Date('2026-02-15T00:00:00.000Z'),
|
||||
);
|
||||
assert.equal(result.renewalNoticeAt?.toISOString(), '2026-01-31T00:00:00.000Z');
|
||||
assert.equal(result.renewalState, 'due');
|
||||
});
|
||||
|
||||
it('rejects cross-account parentage even when both records otherwise look valid', () => {
|
||||
const child = contract({ id: '10000000-0000-4000-8000-000000000004' });
|
||||
const parent = contract({ accountId: '20000000-0000-4000-8000-000000000009' });
|
||||
assert.equal(
|
||||
validateParentRelationship(child, parent, []),
|
||||
'Parent and child contracts must belong to the same account.',
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,147 @@
|
||||
import { strict as assert } from 'node:assert';
|
||||
import { describe, it } from 'node:test';
|
||||
import type { Database } from '@pig/db';
|
||||
import { facts } from '@pig/db';
|
||||
import type { Principal } from '../src/lib/auth';
|
||||
import { executeMutation, MutationError } from '../src/lib/mutation';
|
||||
import { factDecisionDefinition } from '../src/routes/facts';
|
||||
|
||||
const reviewer: Principal = {
|
||||
userId: '00000000-0000-0000-0000-000000000001',
|
||||
email: 'research@example.com',
|
||||
name: 'Research reviewer',
|
||||
isPlatformAdmin: false,
|
||||
teams: [{ team: 'research', role: 'admin' }],
|
||||
via: 'jwt',
|
||||
scopes: ['read', 'write'],
|
||||
};
|
||||
|
||||
const proposedFact = {
|
||||
id: '10000000-0000-0000-0000-000000000001',
|
||||
accountId: '20000000-0000-0000-0000-000000000001',
|
||||
contactId: null,
|
||||
field: 'supplierType',
|
||||
value: 'neocloud',
|
||||
score: '0.780',
|
||||
band: 'probable',
|
||||
status: 'proposed',
|
||||
evidence: { excerpt: 'Operates dedicated GPU cloud regions.' },
|
||||
sourceUrl: 'https://example.com/infrastructure',
|
||||
method: 'web_search',
|
||||
agentRunId: null,
|
||||
decidedByUserId: null,
|
||||
decidedAt: null,
|
||||
observedAt: new Date('2026-08-12T10:00:00Z'),
|
||||
supersededAt: null,
|
||||
createdAt: new Date('2026-08-12T10:00:00Z'),
|
||||
} as const;
|
||||
|
||||
function fakeDatabase(initial: Record<string, unknown>) {
|
||||
let stored = { ...initial };
|
||||
const updates: { table: unknown; values: Record<string, unknown> }[] = [];
|
||||
const activities: Record<string, unknown>[] = [];
|
||||
|
||||
const tx = {
|
||||
select: () => ({
|
||||
from: () => ({
|
||||
where: () => ({ limit: async () => [stored] }),
|
||||
}),
|
||||
}),
|
||||
update: (table: unknown) => ({
|
||||
set: (values: Record<string, unknown>) => ({
|
||||
where: () => ({
|
||||
returning: async () => {
|
||||
updates.push({ table, values });
|
||||
stored = { ...stored, ...values };
|
||||
return [stored];
|
||||
},
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
insert: () => ({
|
||||
values: async (row: Record<string, unknown>) => {
|
||||
activities.push(row);
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
const db = {
|
||||
transaction: async (work: (transaction: unknown) => Promise<unknown>) => work(tx),
|
||||
} as unknown as Database;
|
||||
|
||||
return { db, updates, activities };
|
||||
}
|
||||
|
||||
describe('fact review decisions', () => {
|
||||
it('approves evidence without applying an arbitrary field to the CRM record', async () => {
|
||||
const state = fakeDatabase(proposedFact);
|
||||
|
||||
const result = await executeMutation(
|
||||
state.db,
|
||||
reviewer,
|
||||
async () => ({ status: 'approved' }),
|
||||
factDecisionDefinition,
|
||||
{ id: proposedFact.id },
|
||||
);
|
||||
|
||||
assert.equal(result.fact.status, 'approved');
|
||||
assert.equal(result.recordUpdated, false);
|
||||
assert.equal(state.updates.length, 1);
|
||||
assert.equal(state.updates[0]?.table, facts);
|
||||
assert.deepEqual(state.updates[0]?.values, {
|
||||
status: 'approved',
|
||||
decidedByUserId: reviewer.userId,
|
||||
decidedAt: state.updates[0]?.values.decidedAt,
|
||||
});
|
||||
assert.ok(state.updates[0]?.values.decidedAt instanceof Date);
|
||||
assert.deepEqual(state.activities[0]?.meta, {
|
||||
factId: proposedFact.id,
|
||||
decision: 'approved',
|
||||
field: proposedFact.field,
|
||||
recordUpdated: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('refuses to approve an unsupported claim', async () => {
|
||||
const state = fakeDatabase({
|
||||
...proposedFact,
|
||||
evidence: null,
|
||||
sourceUrl: null,
|
||||
});
|
||||
|
||||
await assert.rejects(
|
||||
executeMutation(
|
||||
state.db,
|
||||
reviewer,
|
||||
async () => ({ status: 'approved' }),
|
||||
factDecisionDefinition,
|
||||
{ id: proposedFact.id },
|
||||
),
|
||||
(error: unknown) =>
|
||||
error instanceof MutationError && error.code === 'missing_evidence',
|
||||
);
|
||||
assert.deepEqual(state.updates, []);
|
||||
assert.deepEqual(state.activities, []);
|
||||
});
|
||||
|
||||
it('allows an unsupported proposal to be dismissed without manufacturing evidence', async () => {
|
||||
const state = fakeDatabase({
|
||||
...proposedFact,
|
||||
evidence: null,
|
||||
sourceUrl: null,
|
||||
});
|
||||
|
||||
const result = await executeMutation(
|
||||
state.db,
|
||||
reviewer,
|
||||
async () => ({ status: 'dismissed' }),
|
||||
factDecisionDefinition,
|
||||
{ id: proposedFact.id },
|
||||
);
|
||||
|
||||
assert.equal(result.fact.status, 'dismissed');
|
||||
assert.equal(result.recordUpdated, false);
|
||||
assert.equal(state.updates.length, 1);
|
||||
assert.equal(state.updates[0]?.table, facts);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,84 @@
|
||||
import { strict as assert } from 'node:assert';
|
||||
import { describe, it } from 'node:test';
|
||||
import { GOOGLE_OAUTH_SCOPES } from '../src/services/google-sheets';
|
||||
import {
|
||||
buildGoogleAuthorizationUrl,
|
||||
googleConnectionMetadata,
|
||||
normaliseGoogleValues,
|
||||
oauthFlowMatches,
|
||||
oauthStateHash,
|
||||
parseBoundedGoogleRange,
|
||||
} from '../src/services/google-sheets';
|
||||
|
||||
describe('Google OAuth proof and redaction', () => {
|
||||
it('binds state and PKCE without putting the verifier in the authorization URL', () => {
|
||||
const state = 'state-secret';
|
||||
const verifier = 'verifier-secret';
|
||||
const url = new URL(buildGoogleAuthorizationUrl({
|
||||
clientId: 'client-id',
|
||||
redirectUri: 'https://pig.example/oauth/google/callback',
|
||||
state,
|
||||
challenge: 'challenge',
|
||||
}));
|
||||
assert.equal(url.searchParams.get('state'), state);
|
||||
assert.equal(url.searchParams.get('code_challenge'), 'challenge');
|
||||
assert.equal(url.searchParams.get('code_challenge_method'), 'S256');
|
||||
assert.equal(url.searchParams.get('scope'), GOOGLE_OAUTH_SCOPES.join(' '));
|
||||
assert.equal(url.searchParams.get('access_type'), 'offline');
|
||||
assert.equal(url.toString().includes(verifier), false);
|
||||
});
|
||||
|
||||
it('accepts only the matching, unexpired, just-consumed state', () => {
|
||||
const now = new Date('2026-08-13T12:00:00.000Z');
|
||||
const flow = {
|
||||
stateHash: oauthStateHash('expected'),
|
||||
browserBindingHash: oauthStateHash('browser'),
|
||||
expiresAt: new Date('2026-08-13T12:01:00.000Z'),
|
||||
consumedAt: now,
|
||||
};
|
||||
assert.equal(oauthFlowMatches(flow, 'expected', 'browser', now), true);
|
||||
assert.equal(oauthFlowMatches(flow, 'attacker', 'browser', now), false);
|
||||
assert.equal(oauthFlowMatches(flow, 'expected', 'other-browser', now), false);
|
||||
assert.equal(oauthFlowMatches({ ...flow, consumedAt: null }, 'expected', 'browser', now), false);
|
||||
assert.equal(oauthFlowMatches({ ...flow, expiresAt: now }, 'expected', 'browser', now), false);
|
||||
});
|
||||
|
||||
it('never serializes encrypted tokens in connection metadata', () => {
|
||||
const metadata = googleConnectionMetadata(true, {
|
||||
userId: '00000000-0000-4000-8000-000000000001',
|
||||
refreshTokenEncrypted: 'v1.refresh.secret',
|
||||
accessTokenEncrypted: 'v1.access.secret',
|
||||
accessTokenExpiresAt: new Date(),
|
||||
scopes: [...GOOGLE_OAUTH_SCOPES],
|
||||
connectedAt: new Date('2026-08-13T12:00:00.000Z'),
|
||||
updatedAt: new Date(),
|
||||
});
|
||||
const serialized = JSON.stringify(metadata);
|
||||
assert.equal(serialized.includes('v1.refresh.secret'), false);
|
||||
assert.equal(serialized.includes('v1.access.secret'), false);
|
||||
assert.deepEqual(Object.keys(metadata), ['configured', 'connected', 'connectedAt', 'scopes']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Google Sheets range and value boundaries', () => {
|
||||
it('requires an explicit rectangular range within A14 limits and the selected grid', () => {
|
||||
assert.deepEqual(parseBoundedGoogleRange('a1:CV2001', { rowCount: 3_000, columnCount: 100 }), {
|
||||
a1: 'A1:CV2001',
|
||||
rows: 2_001,
|
||||
columns: 100,
|
||||
});
|
||||
assert.throws(() => parseBoundedGoogleRange('A:Z'));
|
||||
assert.throws(() => parseBoundedGoogleRange('A1:C2002'));
|
||||
assert.throws(() => parseBoundedGoogleRange('A1:C10', { rowCount: 9, columnCount: 3 }));
|
||||
});
|
||||
|
||||
it('normalizes formatted values into A14 rows while keeping text inert', () => {
|
||||
const table = normaliseGoogleValues([
|
||||
['external_id', 'name', 'active', 'score'],
|
||||
[7, '=IMPORTDATA("https://example.test")', true, 2.5],
|
||||
], 4);
|
||||
assert.deepEqual(table.headers, ['external_id', 'name', 'active', 'score']);
|
||||
assert.deepEqual(table.rows, [['7', '=IMPORTDATA("https://example.test")', 'true', '2.5']]);
|
||||
assert.match(table.warnings.join(' '), /formula source was not imported/i);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,100 @@
|
||||
import { strict as assert } from 'node:assert';
|
||||
import { describe, it } from 'node:test';
|
||||
import { ACCOUNT_SIDES } from '@pig/core';
|
||||
import type { Database } from '@pig/db';
|
||||
import type { Principal } from '../src/lib/auth';
|
||||
import { executeMutation } from '../src/lib/mutation';
|
||||
import { createImportCommitMutationDefinition } from '../src/routes/imports';
|
||||
import { convertImportRow, findDuplicateImportKeys } from '../src/services/imports';
|
||||
import { parseCsv, parseWorksheetXml } from '../src/services/tabular-import';
|
||||
|
||||
const principal: Principal = {
|
||||
userId: '00000000-0000-4000-8000-000000000001',
|
||||
email: 'admin@example.com',
|
||||
name: 'Import admin',
|
||||
isPlatformAdmin: false,
|
||||
teams: [{ team: 'demand', role: 'admin' }],
|
||||
via: 'jwt',
|
||||
scopes: ['read', 'write'],
|
||||
};
|
||||
|
||||
describe('untrusted tabular parsing', () => {
|
||||
it('keeps multiline CSV and formula-like cells as inert text', () => {
|
||||
const parsed = parseCsv('external_id,name\n1,"Acme\nCompute"\n2,"=WEBSERVICE(""https://example.test"")"');
|
||||
assert.deepEqual(parsed.headers, ['external_id', 'name']);
|
||||
assert.equal(parsed.rows[0]?.[1], 'Acme\nCompute');
|
||||
assert.equal(parsed.rows[1]?.[1], '=WEBSERVICE("https://example.test")');
|
||||
assert.match(parsed.warnings.join(' '), /inert text/);
|
||||
});
|
||||
|
||||
it('does not execute XLSX formulas or follow formula URLs', () => {
|
||||
const parsed = parseWorksheetXml(
|
||||
'<worksheet><sheetData><row>' +
|
||||
'<c r="A1" t="inlineStr"><is><t>external_id</t></is></c>' +
|
||||
'<c r="B1" t="inlineStr"><is><t>score</t></is></c>' +
|
||||
'</row><row><c r="A2"><v>1</v></c>' +
|
||||
'<c r="B2"><f>WEBSERVICE("https://example.test")</f><v>7</v></c>' +
|
||||
'</row></sheetData></worksheet>',
|
||||
);
|
||||
assert.deepEqual(parsed.rows, [['1', '7']]);
|
||||
assert.match(parsed.warnings.join(' '), /not executed/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('import row decisions', () => {
|
||||
it('rejects duplicate user-selected source identities', () => {
|
||||
assert.deepEqual([...findDuplicateImportKeys(['vendor-1', 'vendor-2', 'vendor-1'])], ['vendor-1']);
|
||||
});
|
||||
|
||||
it('validates ontology values from core rather than accepting invented sides', () => {
|
||||
const converted = convertImportRow(
|
||||
'account',
|
||||
['external_id', 'name', 'side'],
|
||||
['vendor-1', 'Acme', 'marketplace'],
|
||||
{ name: 'name', side: 'side' },
|
||||
true,
|
||||
);
|
||||
assert.equal(converted.values.name, 'Acme');
|
||||
const message = converted.errors[0]?.message ?? '';
|
||||
assert.match(message, /must be one of/);
|
||||
for (const side of ACCOUNT_SIDES) assert.ok(message.includes(side));
|
||||
});
|
||||
});
|
||||
|
||||
describe('import commit mutation', () => {
|
||||
it('commits imported records and their audit evidence in one transaction', async () => {
|
||||
const events: string[] = [];
|
||||
const tx = {
|
||||
insert: () => ({ values: async () => events.push('activity') }),
|
||||
};
|
||||
const db = {
|
||||
transaction: async (work: (transaction: unknown) => Promise<unknown>) => {
|
||||
events.push('begin');
|
||||
const result = await work(tx);
|
||||
events.push('commit');
|
||||
return result;
|
||||
},
|
||||
} as unknown as Database;
|
||||
const definition = createImportCommitMutationDefinition((transaction) => {
|
||||
assert.equal(transaction, tx);
|
||||
return {
|
||||
commit: async () => {
|
||||
events.push('records-and-identities');
|
||||
return { created: 1, updated: 0, total: 1 };
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
await executeMutation(db, principal, async () => ({
|
||||
entity: 'account',
|
||||
sourceName: 'accounts.csv',
|
||||
headers: ['external_id', 'name', 'side'],
|
||||
rows: [['vendor-1', 'Acme', 'demand']],
|
||||
mapping: { name: 'name', side: 'side' },
|
||||
keySourceColumn: 'external_id',
|
||||
previewDigest: 'a'.repeat(64),
|
||||
}), definition);
|
||||
|
||||
assert.deepEqual(events, ['begin', 'records-and-identities', 'activity', 'commit']);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,80 @@
|
||||
import { strict as assert } from 'node:assert';
|
||||
import { describe, it } from 'node:test';
|
||||
import { Hono } from 'hono';
|
||||
import type { Principal } from '../src/lib/auth';
|
||||
import { AuthError } from '../src/lib/auth';
|
||||
import { loadConfig } from '../src/lib/config';
|
||||
import type { ApiEnv } from '../src/lib/mutation';
|
||||
import {
|
||||
createIntegrationSettingsRoutes,
|
||||
integrationReadiness,
|
||||
} from '../src/routes/integration-settings';
|
||||
|
||||
const config = loadConfig({
|
||||
DATABASE_URL: 'postgres://pig:pig@localhost:5432/pig',
|
||||
NODE_ENV: 'test',
|
||||
SLACK_BOT_TOKEN: 'xoxb-secret-value',
|
||||
SLACK_SIGNING_SECRET: 'slack-signing-secret',
|
||||
BUZZ_RELAY_URL: 'https://buzz.example.com',
|
||||
BUZZ_PRIVATE_KEY: 'buzz-private-secret',
|
||||
BUZZ_AUTH_TAG: '["auth","owner","kind=9","secret-signature"]',
|
||||
});
|
||||
|
||||
function principal(isPlatformAdmin: boolean): Principal {
|
||||
return {
|
||||
userId: '00000000-0000-4000-8000-000000000001',
|
||||
email: 'admin@example.com',
|
||||
name: 'Admin',
|
||||
isPlatformAdmin,
|
||||
teams: [],
|
||||
via: 'jwt',
|
||||
scopes: ['read', 'write'],
|
||||
};
|
||||
}
|
||||
|
||||
describe('integration readiness', () => {
|
||||
it('reports readiness without serialising any credential material', () => {
|
||||
const body = JSON.stringify(integrationReadiness(config));
|
||||
assert.deepEqual(JSON.parse(body), {
|
||||
slack: {
|
||||
source: 'environment',
|
||||
configured: true,
|
||||
deliveryReady: true,
|
||||
commandsReady: true,
|
||||
},
|
||||
buzz: {
|
||||
source: 'environment',
|
||||
configured: true,
|
||||
deliveryReady: true,
|
||||
relayUrl: 'https://buzz.example.com',
|
||||
workspaceId: 'buzz.example.com',
|
||||
},
|
||||
});
|
||||
for (const secret of [
|
||||
config.SLACK_BOT_TOKEN,
|
||||
config.SLACK_SIGNING_SECRET,
|
||||
config.BUZZ_PRIVATE_KEY,
|
||||
config.BUZZ_AUTH_TAG,
|
||||
]) {
|
||||
assert.ok(secret);
|
||||
assert.equal(body.includes(secret), false);
|
||||
}
|
||||
});
|
||||
|
||||
it('authorizes before returning readiness metadata', async () => {
|
||||
const app = new Hono<ApiEnv>();
|
||||
app.use('*', async (c, next) => {
|
||||
c.set('principal', principal(false));
|
||||
await next();
|
||||
});
|
||||
app.route('/', createIntegrationSettingsRoutes(config));
|
||||
app.onError((error, c) => {
|
||||
if (error instanceof AuthError) return c.json({ code: error.code }, error.status);
|
||||
throw error;
|
||||
});
|
||||
|
||||
const response = await app.request('/api/admin/integrations');
|
||||
assert.equal(response.status, 403);
|
||||
assert.deepEqual(await response.json(), { code: 'insufficient_permission' });
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,122 @@
|
||||
import { strict as assert } from 'node:assert';
|
||||
import { describe, it } from 'node:test';
|
||||
import { z } from 'zod';
|
||||
import type { Database } from '@pig/db';
|
||||
import type { Principal } from '../src/lib/auth';
|
||||
import { AuthError } from '../src/lib/auth';
|
||||
import { apiError, executeMutation, MutationError } from '../src/lib/mutation';
|
||||
|
||||
const principal: Principal = {
|
||||
userId: '00000000-0000-0000-0000-000000000001',
|
||||
email: 'seller@example.com',
|
||||
name: 'Seller',
|
||||
isPlatformAdmin: false,
|
||||
teams: [{ team: 'demand', role: 'member' }],
|
||||
via: 'jwt',
|
||||
scopes: ['read', 'write'],
|
||||
};
|
||||
|
||||
function fakeDatabase(events: string[], activityRows: unknown[]): Database {
|
||||
const tx = {
|
||||
insert: () => ({
|
||||
values: async (row: unknown) => {
|
||||
events.push('activity');
|
||||
activityRows.push(row);
|
||||
},
|
||||
}),
|
||||
};
|
||||
return {
|
||||
transaction: async (work: (transaction: unknown) => Promise<unknown>) => {
|
||||
events.push('transaction');
|
||||
return work(tx);
|
||||
},
|
||||
} as unknown as Database;
|
||||
}
|
||||
|
||||
describe('mutation convention', () => {
|
||||
it('checks capability before reading attacker-controlled input', async () => {
|
||||
const events: string[] = [];
|
||||
const forbidden = { ...principal, teams: [{ team: 'supply', role: 'admin' }] } as Principal;
|
||||
|
||||
await assert.rejects(
|
||||
executeMutation(fakeDatabase(events, []), forbidden, async () => {
|
||||
events.push('body');
|
||||
return {};
|
||||
}, {
|
||||
schema: z.object({ name: z.string() }),
|
||||
permission: { capability: 'deal:write', team: 'demand' },
|
||||
invalidMessage: 'Invalid deal.',
|
||||
async mutate() {
|
||||
events.push('mutate');
|
||||
return { data: {}, activity: { type: 'note', subject: 'Changed' } };
|
||||
},
|
||||
}),
|
||||
(error: unknown) => error instanceof AuthError && error.code === 'insufficient_permission',
|
||||
);
|
||||
assert.deepEqual(events, []);
|
||||
});
|
||||
|
||||
it('rejects invalid ontology input before opening a transaction', async () => {
|
||||
const events: string[] = [];
|
||||
const stages = ['qualification', 'legal'] as const;
|
||||
|
||||
await assert.rejects(
|
||||
executeMutation(fakeDatabase(events, []), principal, async () => ({ stage: 'invented' }), {
|
||||
schema: z.object({ stage: z.enum(stages) }),
|
||||
permission: { capability: 'deal:write', team: 'demand' },
|
||||
invalidMessage: 'Invalid transition.',
|
||||
async mutate() {
|
||||
events.push('mutate');
|
||||
return { data: {}, activity: { type: 'stage_change', subject: 'Changed' } };
|
||||
},
|
||||
}),
|
||||
(error: unknown) =>
|
||||
error instanceof MutationError &&
|
||||
error.code === 'invalid_request' &&
|
||||
apiError(error.code, error.message, error.issues).issues?.length === 1,
|
||||
);
|
||||
assert.deepEqual(events, []);
|
||||
});
|
||||
|
||||
it('writes mutation evidence in the same transaction with framework attribution', async () => {
|
||||
const events: string[] = [];
|
||||
const rows: unknown[] = [];
|
||||
const result = await executeMutation(
|
||||
fakeDatabase(events, rows),
|
||||
principal,
|
||||
async () => ({ stage: 'legal' }),
|
||||
{
|
||||
schema: z.object({ stage: z.literal('legal') }),
|
||||
permission: { capability: 'deal:write', team: 'demand' },
|
||||
invalidMessage: 'Invalid transition.',
|
||||
async mutate() {
|
||||
events.push('mutate');
|
||||
const data = { id: 'deal-1' };
|
||||
return {
|
||||
data,
|
||||
activity: {
|
||||
type: 'stage_change',
|
||||
subject: 'Moved to legal',
|
||||
demandDealId: data.id,
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
assert.deepEqual(result, { id: 'deal-1' });
|
||||
assert.deepEqual(events, ['transaction', 'mutate', 'activity']);
|
||||
assert.deepEqual(rows, [
|
||||
{
|
||||
type: 'stage_change',
|
||||
subject: 'Moved to legal',
|
||||
demandDealId: 'deal-1',
|
||||
actorUserId: principal.userId,
|
||||
actorAgent: null,
|
||||
source: 'manual',
|
||||
occurredAt: (rows[0] as { occurredAt: Date }).occurredAt,
|
||||
},
|
||||
]);
|
||||
assert.ok((rows[0] as { occurredAt: unknown }).occurredAt instanceof Date);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,90 @@
|
||||
import { strict as assert } from 'node:assert';
|
||||
import { describe, it } from 'node:test';
|
||||
import {
|
||||
collectNotionPages,
|
||||
createNotionOAuthAttempt,
|
||||
flattenNotionProperty,
|
||||
notionAuthorizationUrl,
|
||||
notionConnectionMetadata,
|
||||
verifyNotionOAuthAttempt,
|
||||
} from '../src/services/notion';
|
||||
|
||||
describe('Notion OAuth decisions', () => {
|
||||
it('uses one-time state and browser binding without inventing unsupported PKCE parameters', () => {
|
||||
let byte = 0;
|
||||
const attempt = createNotionOAuthAttempt(
|
||||
new Date('2026-08-13T12:00:00.000Z'),
|
||||
(size) => Buffer.alloc(size, byte += 1),
|
||||
);
|
||||
assert.notEqual(attempt.state, attempt.verifier);
|
||||
assert.notEqual(attempt.stateHash, attempt.state);
|
||||
assert.notEqual(attempt.verifierHash, attempt.verifier);
|
||||
assert.equal(verifyNotionOAuthAttempt(
|
||||
attempt.verifier,
|
||||
attempt.verifierHash,
|
||||
attempt.expiresAt,
|
||||
new Date('2026-08-13T12:09:59.000Z'),
|
||||
), true);
|
||||
assert.equal(verifyNotionOAuthAttempt('tampered', attempt.verifierHash, attempt.expiresAt), false);
|
||||
assert.equal(verifyNotionOAuthAttempt(
|
||||
attempt.verifier,
|
||||
attempt.verifierHash,
|
||||
attempt.expiresAt,
|
||||
new Date('2026-08-13T12:10:00.000Z'),
|
||||
), false);
|
||||
const url = new URL(notionAuthorizationUrl({
|
||||
clientId: 'client-id',
|
||||
redirectUri: 'https://pig.example/api/imports/notion/oauth/callback',
|
||||
state: attempt.state,
|
||||
}));
|
||||
assert.equal(url.searchParams.get('state'), attempt.state);
|
||||
assert.equal(url.searchParams.has('code_challenge'), false);
|
||||
assert.equal(url.searchParams.has('code_verifier'), false);
|
||||
});
|
||||
|
||||
it('redacts every credential-shaped field from connection metadata', () => {
|
||||
const storedConnection = {
|
||||
id: 'connection-id',
|
||||
workspaceId: 'workspace-id',
|
||||
workspaceName: 'Sales',
|
||||
workspaceIcon: null,
|
||||
createdAt: new Date('2026-08-13T12:00:00.000Z'),
|
||||
credentialsEncrypted: 'v1.secret.envelope',
|
||||
accessToken: 'never-return',
|
||||
};
|
||||
const metadata = notionConnectionMetadata(storedConnection);
|
||||
const serialized = JSON.stringify(metadata);
|
||||
assert.equal(serialized.includes('never-return'), false);
|
||||
assert.equal(serialized.includes('envelope'), false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Notion pagination and flattening decisions', () => {
|
||||
it('follows cursors in order and stops after the declared import bound', async () => {
|
||||
const cursors: Array<string | undefined> = [];
|
||||
const rows = await collectNotionPages(async (cursor) => {
|
||||
cursors.push(cursor);
|
||||
return cursor
|
||||
? { results: [{ id: '2' }, { id: '3' }], has_more: false, next_cursor: null }
|
||||
: { results: [{ id: '1' }], has_more: true, next_cursor: 'next' };
|
||||
}, 2);
|
||||
assert.deepEqual(cursors, [undefined, 'next']);
|
||||
assert.deepEqual(rows.map((row) => row.id), ['1', '2']);
|
||||
});
|
||||
|
||||
it('maps supported values explicitly and rejects unstable property types', () => {
|
||||
assert.deepEqual(flattenNotionProperty({
|
||||
type: 'title',
|
||||
title: [{ plain_text: 'Acme' }, { plain_text: ' Compute' }],
|
||||
}), { value: 'Acme Compute' });
|
||||
assert.deepEqual(flattenNotionProperty({
|
||||
type: 'date',
|
||||
date: { start: '2026-09-01', end: '2026-09-30' },
|
||||
}), { value: '2026-09-01/2026-09-30' });
|
||||
assert.deepEqual(flattenNotionProperty({
|
||||
type: 'formula',
|
||||
formula: { type: 'number', number: 12.5 },
|
||||
}), { value: '12.5' });
|
||||
assert.match(flattenNotionProperty({ type: 'button', button: {} }).error ?? '', /stable tabular/);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,99 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { Hono } from 'hono';
|
||||
import type { Principal } from '../src/lib/auth';
|
||||
import type { ApiEnv } from '../src/lib/mutation';
|
||||
import { createPiggyChatRoutes } from '../src/routes/piggy-chat';
|
||||
|
||||
const principal: Principal = {
|
||||
userId: '10000000-0000-4000-8000-000000000001',
|
||||
email: 'member@example.com',
|
||||
name: 'Member',
|
||||
isPlatformAdmin: false,
|
||||
teams: [{ team: 'demand', role: 'member' }],
|
||||
via: 'jwt',
|
||||
scopes: ['read', 'write'],
|
||||
};
|
||||
|
||||
function appFor(fetchImpl: typeof fetch, identity: Principal = principal) {
|
||||
const app = new Hono<ApiEnv>();
|
||||
app.use('*', async (context, next) => {
|
||||
context.set('principal', identity);
|
||||
await next();
|
||||
});
|
||||
app.route(
|
||||
'/',
|
||||
createPiggyChatRoutes({
|
||||
enabled: true,
|
||||
internalUrl: 'http://127.0.0.1:8931',
|
||||
internalToken: 'internal-token-with-at-least-32-characters',
|
||||
fetchImpl,
|
||||
}),
|
||||
);
|
||||
return app;
|
||||
}
|
||||
|
||||
test('the authenticated proxy forwards bounded identity and relays NDJSON unchanged', async () => {
|
||||
let forwarded: Record<string, unknown> | undefined;
|
||||
const fetchImpl: typeof fetch = async (input, init) => {
|
||||
assert.equal(String(input), 'http://127.0.0.1:8931/internal/chat');
|
||||
assert.equal(
|
||||
new Headers(init?.headers).get('authorization'),
|
||||
'Bearer internal-token-with-at-least-32-characters',
|
||||
);
|
||||
forwarded = JSON.parse(String(init?.body)) as Record<string, unknown>;
|
||||
return new Response(
|
||||
`${JSON.stringify({ type: 'content_delta', delta: 'Scoped answer' })}\n` +
|
||||
`${JSON.stringify({ type: 'done', inputTokens: 2, outputTokens: 3 })}\n`,
|
||||
{ status: 200, headers: { 'content-type': 'application/x-ndjson' } },
|
||||
);
|
||||
};
|
||||
const app = appFor(fetchImpl);
|
||||
const response = await app.request('/api/piggy/chat', {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
message: 'Summarise this contract.',
|
||||
context: {
|
||||
type: 'contract',
|
||||
id: '20000000-0000-4000-8000-000000000002',
|
||||
label: 'Order form',
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
assert.equal(response.status, 200);
|
||||
assert.match(response.headers.get('content-type') ?? '', /application\/x-ndjson/);
|
||||
assert.deepEqual(forwarded, {
|
||||
principalUserId: principal.userId,
|
||||
message: 'Summarise this contract.',
|
||||
context: {
|
||||
type: 'contract',
|
||||
id: '20000000-0000-4000-8000-000000000002',
|
||||
label: 'Order form',
|
||||
},
|
||||
});
|
||||
assert.equal(
|
||||
await response.text(),
|
||||
`${JSON.stringify({ type: 'content_delta', delta: 'Scoped answer' })}\n` +
|
||||
`${JSON.stringify({ type: 'done', inputTokens: 2, outputTokens: 3 })}\n`,
|
||||
);
|
||||
});
|
||||
|
||||
test('a credential without read scope never reaches the internal service', async () => {
|
||||
let fetched = false;
|
||||
const app = appFor(
|
||||
async () => {
|
||||
fetched = true;
|
||||
return new Response();
|
||||
},
|
||||
{ ...principal, scopes: ['write'] },
|
||||
);
|
||||
const response = await app.request('/api/piggy/chat', {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify({ message: 'Read the book.' }),
|
||||
});
|
||||
assert.equal(response.status, 403);
|
||||
assert.equal(fetched, false);
|
||||
});
|
||||
@@ -0,0 +1,124 @@
|
||||
import { strict as assert } from 'node:assert';
|
||||
import { describe, it } from 'node:test';
|
||||
import type { Database } from '@pig/db';
|
||||
import { accounts, activities, agentTasks } from '@pig/db';
|
||||
import type { Principal } from '../src/lib/auth';
|
||||
import { AuthError } from '../src/lib/auth';
|
||||
import { executeMutation, MutationError } from '../src/lib/mutation';
|
||||
import {
|
||||
accountSupportsTeam,
|
||||
createAccountMutationDefinition,
|
||||
createDemandDealMutationDefinition,
|
||||
} from '../src/routes/records';
|
||||
|
||||
const demandPrincipal: Principal = {
|
||||
userId: '00000000-0000-4000-8000-000000000001',
|
||||
email: 'seller@example.com',
|
||||
name: 'Seller',
|
||||
isPlatformAdmin: false,
|
||||
teams: [{ team: 'demand', role: 'member' }],
|
||||
via: 'jwt',
|
||||
scopes: ['read', 'write'],
|
||||
};
|
||||
|
||||
describe('record-side decisions', () => {
|
||||
it('makes dual-side accounts available to both commercial teams', () => {
|
||||
assert.equal(accountSupportsTeam('both', 'demand'), true);
|
||||
assert.equal(accountSupportsTeam('both', 'supply'), true);
|
||||
assert.equal(accountSupportsTeam('both', 'research'), false);
|
||||
assert.equal(accountSupportsTeam('supply', 'demand'), false);
|
||||
assert.equal(accountSupportsTeam('demand', 'supply'), false);
|
||||
});
|
||||
|
||||
it('does not let a demand writer create a supply-only account', async () => {
|
||||
const db = {
|
||||
transaction: async (work: (tx: unknown) => Promise<unknown>) => work({}),
|
||||
} as unknown as Database;
|
||||
|
||||
await assert.rejects(
|
||||
executeMutation(
|
||||
db,
|
||||
demandPrincipal,
|
||||
async () => ({
|
||||
name: 'Supply only',
|
||||
side: 'supply',
|
||||
}),
|
||||
createAccountMutationDefinition(),
|
||||
),
|
||||
(error: unknown) =>
|
||||
error instanceof AuthError && error.code === 'insufficient_permission',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('record mutation evidence and relationships', () => {
|
||||
it('queues enrichment and writes the audit event in the account transaction', async () => {
|
||||
const events: string[] = [];
|
||||
const created = {
|
||||
id: '10000000-0000-4000-8000-000000000001',
|
||||
name: 'Customer',
|
||||
side: 'demand',
|
||||
};
|
||||
const tx = {
|
||||
insert: (table: unknown) => ({
|
||||
values: (row: unknown) => {
|
||||
events.push(
|
||||
table === accounts ? 'account' : table === agentTasks ? 'agent-task' : table === activities ? 'activity' : 'unknown',
|
||||
);
|
||||
return { returning: async () => [created], row };
|
||||
},
|
||||
}),
|
||||
};
|
||||
const db = {
|
||||
transaction: async (work: (transaction: unknown) => Promise<unknown>) => {
|
||||
events.push('begin');
|
||||
const result = await work(tx);
|
||||
events.push('commit');
|
||||
return result;
|
||||
},
|
||||
} as unknown as Database;
|
||||
|
||||
await executeMutation(
|
||||
db,
|
||||
demandPrincipal,
|
||||
async () => ({ name: 'Customer', side: 'demand' }),
|
||||
createAccountMutationDefinition(),
|
||||
);
|
||||
|
||||
assert.deepEqual(events, ['begin', 'account', 'agent-task', 'activity', 'commit']);
|
||||
});
|
||||
|
||||
it('rejects a demand deal attached to a supply-only account', async () => {
|
||||
const tx = {
|
||||
select: () => ({
|
||||
from: () => ({
|
||||
where: () => ({
|
||||
limit: async () => [{ id: '10000000-0000-4000-8000-000000000001', side: 'supply' }],
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
};
|
||||
const db = {
|
||||
transaction: async (work: (transaction: unknown) => Promise<unknown>) => work(tx),
|
||||
} as unknown as Database;
|
||||
|
||||
await assert.rejects(
|
||||
executeMutation(
|
||||
db,
|
||||
demandPrincipal,
|
||||
async () => ({
|
||||
accountId: '10000000-0000-4000-8000-000000000001',
|
||||
name: 'Impossible relationship',
|
||||
productLine: 'compute_reserved',
|
||||
stage: 'qualification',
|
||||
currency: 'USD',
|
||||
msaExecuted: false,
|
||||
dpaExecuted: false,
|
||||
}),
|
||||
createDemandDealMutationDefinition(),
|
||||
),
|
||||
(error: unknown) =>
|
||||
error instanceof MutationError && error.code === 'relationship_mismatch',
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,147 @@
|
||||
import { strict as assert } from 'node:assert';
|
||||
import { createHmac } from 'node:crypto';
|
||||
import { describe, it } from 'node:test';
|
||||
import type { Database } from '@pig/db';
|
||||
import type { Principal } from '../src/lib/auth';
|
||||
import { AuthError } from '../src/lib/auth';
|
||||
import { executeMutation } from '../src/lib/mutation';
|
||||
import {
|
||||
createSlackLinkMutationDefinition,
|
||||
parseSlashRequirement,
|
||||
verifySlackRequest,
|
||||
} from '../src/routes/slack';
|
||||
import { NotificationDeliveryError } from '../src/services/notifier';
|
||||
import { SlackNotifier, slackClientMessageId } from '../src/services/slack';
|
||||
|
||||
const NOW = 1_786_579_200;
|
||||
const SECRET = 'test-signing-secret';
|
||||
|
||||
function signature(timestamp: number, body: string): string {
|
||||
return `v0=${createHmac('sha256', SECRET).update(`v0:${timestamp}:${body}`).digest('hex')}`;
|
||||
}
|
||||
|
||||
describe('Slack request verification', () => {
|
||||
it('accepts the exact signed bytes and rejects tampering', () => {
|
||||
const body = 'team_id=T1&channel_id=C1&text=8+H100_80GB';
|
||||
const signed = signature(NOW, body);
|
||||
assert.equal(verifySlackRequest(SECRET, String(NOW), signed, body, NOW), true);
|
||||
assert.equal(verifySlackRequest(SECRET, String(NOW), signed, `${body}+fabric`, NOW), false);
|
||||
});
|
||||
|
||||
it('rejects replayed requests outside Slack\'s five-minute window', () => {
|
||||
const body = 'team_id=T1&channel_id=C1&text=8+H100_80GB';
|
||||
const old = NOW - 301;
|
||||
assert.equal(verifySlackRequest(SECRET, String(old), signature(old, body), body, NOW), false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Slack capacity command', () => {
|
||||
it('turns transport syntax into a CapacityService requirement without matching in the handler', () => {
|
||||
assert.deepEqual(
|
||||
parseSlashRequirement('8 H100_80GB hours=640 max=2.50 fabric tier=secure_cloud'),
|
||||
{
|
||||
gpuCount: 8,
|
||||
gpuType: 'H100_80GB',
|
||||
totalGpuHours: 640,
|
||||
maxPricePerGpuHourCents: 250,
|
||||
requiresHighSpeedInterconnect: true,
|
||||
minSecurityTier: 'secure_cloud',
|
||||
},
|
||||
);
|
||||
assert.equal(parseSlashRequirement('eight H100_80GB'), null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Slack channel link authorization', () => {
|
||||
it('denies non-admins before reading link input or opening a transaction', async () => {
|
||||
const events: string[] = [];
|
||||
const principal: Principal = {
|
||||
userId: '00000000-0000-4000-8000-000000000001',
|
||||
email: 'seller@example.com',
|
||||
name: 'Seller',
|
||||
isPlatformAdmin: false,
|
||||
teams: [{ team: 'demand', role: 'admin' }],
|
||||
via: 'jwt',
|
||||
scopes: ['read', 'write'],
|
||||
};
|
||||
const db = {
|
||||
transaction: async () => {
|
||||
events.push('transaction');
|
||||
},
|
||||
} as unknown as Database;
|
||||
|
||||
await assert.rejects(
|
||||
executeMutation(
|
||||
db,
|
||||
principal,
|
||||
async () => {
|
||||
events.push('body');
|
||||
return {};
|
||||
},
|
||||
createSlackLinkMutationDefinition(),
|
||||
),
|
||||
(error: unknown) => error instanceof AuthError && error.code === 'insufficient_permission',
|
||||
);
|
||||
assert.deepEqual(events, []);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Slack delivery decisions', () => {
|
||||
it('reuses a deterministic client message id across retries', async () => {
|
||||
const bodies: Record<string, unknown>[] = [];
|
||||
const notifier = new SlackNotifier({
|
||||
botToken: 'xoxb-test',
|
||||
fetchImpl: async (_input, init) => {
|
||||
bodies.push(JSON.parse(String(init?.body)) as Record<string, unknown>);
|
||||
return Response.json({ ok: true, ts: '123.456' });
|
||||
},
|
||||
});
|
||||
const envelope = {
|
||||
idempotencyKey: 'slack:link:event',
|
||||
destination: 'C123',
|
||||
notification: {
|
||||
kind: 'stage_change' as const,
|
||||
accountId: '10000000-0000-4000-8000-000000000001',
|
||||
dealId: '20000000-0000-4000-8000-000000000002',
|
||||
dealSide: 'demand' as const,
|
||||
dealName: 'Reserved H100 cluster',
|
||||
fromStage: 'proposal',
|
||||
toStage: 'procurement',
|
||||
changedAt: '2026-08-12T12:00:00.000Z',
|
||||
},
|
||||
};
|
||||
await notifier.send(envelope);
|
||||
await notifier.send(envelope);
|
||||
assert.equal(bodies[0]?.client_msg_id, slackClientMessageId(envelope.idempotencyKey));
|
||||
assert.equal(bodies[1]?.client_msg_id, bodies[0]?.client_msg_id);
|
||||
});
|
||||
|
||||
it('marks rate limits retryable and honours Slack retry-after', async () => {
|
||||
const notifier = new SlackNotifier({
|
||||
botToken: 'xoxb-test',
|
||||
fetchImpl: async () =>
|
||||
new Response('', { status: 429, headers: { 'retry-after': '7' } }),
|
||||
});
|
||||
await assert.rejects(
|
||||
notifier.send({
|
||||
idempotencyKey: 'one',
|
||||
destination: 'C1',
|
||||
notification: {
|
||||
kind: 'idle_capacity',
|
||||
accountId: '10000000-0000-4000-8000-000000000001',
|
||||
commitmentId: '20000000-0000-4000-8000-000000000002',
|
||||
commitmentName: 'Eight H100s',
|
||||
gpuType: 'H100_80GB',
|
||||
idleGpuHours: 640,
|
||||
idleCostCents: 120_000,
|
||||
utilisation: 0,
|
||||
observedAt: '2026-08-12T12:00:00.000Z',
|
||||
},
|
||||
}),
|
||||
(error: unknown) =>
|
||||
error instanceof NotificationDeliveryError &&
|
||||
error.retryable &&
|
||||
error.retryAfterMs === 7_000,
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user