Build the agent-native compute CRM platform
CI / verify (push) Successful in 3m6s

This commit is contained in:
2026-08-13 01:39:01 -07:00
parent bfd2f8d95a
commit 853bde2265
160 changed files with 61812 additions and 483 deletions
+126
View File
@@ -0,0 +1,126 @@
import {
ACCOUNT_SIDES,
AFFILIATION_KINDS,
CUSTOMER_SEGMENTS,
DEMAND_STAGES,
INTERCONNECT_TYPES,
PRODUCT_LINES,
SUPPLIER_TYPES,
SUPPLY_STAGES,
} from './ontology';
export const IMPORT_ENTITIES = ['account', 'contact', 'demand_deal', 'supply_deal'] as const;
export type ImportEntity = (typeof IMPORT_ENTITIES)[number];
export type ImportFieldKind =
| 'text'
| 'email'
| 'url'
| 'uuid'
| 'integer'
| 'decimal'
| 'boolean'
| 'date'
| 'currency'
| 'enum';
export interface ImportFieldDefinition {
key: string;
label: string;
kind: ImportFieldKind;
required?: boolean;
options?: readonly string[];
min?: number;
max?: number;
maxLength?: number;
description?: string;
/** False for non-null columns where a blank update must leave the stored value intact. */
clearable?: boolean;
}
export interface ImportEntityDefinition {
label: string;
description: string;
fields: readonly ImportFieldDefinition[];
}
export const IMPORT_ENTITY_DEFINITIONS: Readonly<Record<ImportEntity, ImportEntityDefinition>> = {
account: {
label: 'Accounts',
description: 'Customer, supplier, and dual-sided organisations.',
fields: [
{ key: 'name', label: 'Account name', kind: 'text', required: true, maxLength: 200 },
{ key: 'domain', label: 'Domain', kind: 'text', maxLength: 255 },
{ key: 'website', label: 'Website', kind: 'url' },
{ key: 'description', label: 'Description', kind: 'text', maxLength: 10_000 },
{ key: 'side', label: 'Commercial side', kind: 'enum', required: true, options: ACCOUNT_SIDES },
{ key: 'supplierType', label: 'Supplier type', kind: 'enum', options: SUPPLIER_TYPES },
{ key: 'customerSegment', label: 'Customer segment', kind: 'enum', options: CUSTOMER_SEGMENTS },
{ key: 'country', label: 'Country', kind: 'text', maxLength: 160 },
{ key: 'region', label: 'Region', kind: 'text', maxLength: 160 },
{ key: 'jurisdiction', label: 'Legal jurisdiction', kind: 'text', maxLength: 160 },
{ key: 'ultimateParentName', label: 'Ultimate parent', kind: 'text', maxLength: 200 },
{ key: 'ultimateParentCountry', label: 'Ultimate parent country', kind: 'text', maxLength: 160 },
],
},
contact: {
label: 'Contacts',
description: 'People and their evidenced relationship to an account.',
fields: [
{ key: 'accountId', label: 'Account ID', kind: 'uuid', description: 'A PIG account UUID, not an account name.' },
{ key: 'fullName', label: 'Full name', kind: 'text', required: true, maxLength: 200 },
{ key: 'firstName', label: 'First name', kind: 'text', maxLength: 100 },
{ key: 'lastName', label: 'Last name', kind: 'text', maxLength: 100 },
{ key: 'title', label: 'Title', kind: 'text', maxLength: 200 },
{ key: 'email', label: 'Email', kind: 'email', description: 'Import only sourced or provided addresses.' },
{ key: 'phone', label: 'Phone', kind: 'text', maxLength: 100 },
{ key: 'linkedinUrl', label: 'LinkedIn URL', kind: 'url' },
{ key: 'twitterHandle', label: 'X / Twitter handle', kind: 'text', maxLength: 100 },
{ key: 'githubHandle', label: 'GitHub handle', kind: 'text', maxLength: 100 },
{ key: 'websiteUrl', label: 'Website URL', kind: 'url' },
{ key: 'affiliation', label: 'Affiliation', kind: 'enum', options: AFFILIATION_KINDS, clearable: false },
{ key: 'isDecisionMaker', label: 'Decision maker', kind: 'boolean', clearable: false },
{ key: 'confidenceNote', label: 'Provenance note', kind: 'text', maxLength: 2_000 },
],
},
demand_deal: {
label: 'Demand deals',
description: 'Customer opportunities. Account relationships must already exist in PIG.',
fields: [
{ key: 'accountId', label: 'Account ID', kind: 'uuid', required: true },
{ key: 'name', label: 'Deal name', kind: 'text', required: true, maxLength: 200 },
{ key: 'description', label: 'Description', kind: 'text', maxLength: 10_000 },
{ key: 'productLine', label: 'Product line', kind: 'enum', required: true, options: PRODUCT_LINES },
{ key: 'stage', label: 'Stage', kind: 'enum', required: true, options: DEMAND_STAGES },
{ key: 'acvCents', label: 'ACV in cents', kind: 'integer', min: 0 },
{ key: 'tcvCents', label: 'TCV in cents', kind: 'integer', min: 0 },
{ key: 'currency', label: 'Currency', kind: 'currency', clearable: false },
{ key: 'termMonths', label: 'Term months', kind: 'integer', min: 1 },
{ key: 'probability', label: 'Probability (01)', kind: 'decimal', min: 0, max: 1 },
{ key: 'expectedCloseDate', label: 'Expected close date', kind: 'date' },
{ key: 'msaExecuted', label: 'MSA executed', kind: 'boolean', clearable: false },
{ key: 'dpaExecuted', label: 'DPA executed', kind: 'boolean', clearable: false },
{ key: 'closedReason', label: 'Closed reason', kind: 'text', maxLength: 2_000 },
],
},
supply_deal: {
label: 'Supply deals',
description: 'Capacity sourcing opportunities attached to supplier accounts.',
fields: [
{ key: 'accountId', label: 'Account ID', kind: 'uuid', required: true },
{ key: 'name', label: 'Deal name', kind: 'text', required: true, maxLength: 200 },
{ key: 'stage', label: 'Stage', kind: 'enum', required: true, options: SUPPLY_STAGES },
{ key: 'gpuType', label: 'GPU type', kind: 'text', maxLength: 100 },
{ key: 'gpuCount', label: 'GPU count', kind: 'integer', min: 1 },
{ key: 'interconnectType', label: 'Interconnect', kind: 'enum', options: INTERCONNECT_TYPES },
{ key: 'targetCostPerGpuHourCents', label: 'Target cost in cents', kind: 'integer', min: 0 },
{ key: 'termMonths', label: 'Term months', kind: 'integer', min: 1 },
{ key: 'availableFrom', label: 'Available from', kind: 'date' },
{ key: 'technicalVerdict', label: 'Technical verdict', kind: 'text', maxLength: 1_000 },
{ key: 'technicalNotes', label: 'Technical notes', kind: 'text', maxLength: 10_000 },
{ key: 'financialVerdict', label: 'Financial verdict', kind: 'text', maxLength: 1_000 },
{ key: 'financialNotes', label: 'Financial notes', kind: 'text', maxLength: 10_000 },
{ key: 'rejectionReason', label: 'Rejection reason', kind: 'text', maxLength: 2_000 },
],
},
};
+2
View File
@@ -1,3 +1,5 @@
export * from './ontology';
export * from './margin';
export * from './permissions';
export * from './theme';
export * from './imports';
+46 -4
View File
@@ -256,13 +256,50 @@ export type InterconnectType = (typeof INTERCONNECT_TYPES)[number];
*
* This is how compute aggregators express reliability when they cannot offer a
* conventional uptime SLA — because they resell third-party infrastructure they
* do not control. Secure-cloud capacity sits in vetted datacenters; community
* capacity is cheaper and less dependable. See `SlaKind` below for how this
* interacts with contractual commitments.
* do not control. Government capacity is sovereign-grade and isolated,
* secure-cloud capacity sits in vetted datacenters, and community capacity is
* cheaper and less dependable. See `SlaKind` below for how this interacts with
* contractual commitments.
*/
export const SECURITY_TIERS = ['secure_cloud', 'community_cloud'] as const;
export const SECURITY_TIERS = ['government', 'secure_cloud', 'community_cloud'] as const;
export type SecurityTier = (typeof SECURITY_TIERS)[number];
/** Allocation states distinguish forecast, sold usage, and returned capacity. */
export const CONSUMING_ALLOCATION_STATUSES = ['committed', 'active', 'completed'] as const;
export const RESERVING_ALLOCATION_STATUSES = [
'planned',
...CONSUMING_ALLOCATION_STATUSES,
] as const;
export const ALLOCATION_STATUSES = [
...RESERVING_ALLOCATION_STATUSES,
'released',
] as const;
export type AllocationStatus = (typeof ALLOCATION_STATUSES)[number];
/** Commercial priority is separate from lifecycle state. */
export const GUARANTEE_TYPES = [
'guaranteed',
'committed',
'on_demand',
'preemptible',
'internal',
] as const;
export type GuaranteeType = (typeof GUARANTEE_TYPES)[number];
/** Higher classifications may satisfy lower requirements, never the reverse. */
export const SECURITY_TIER_RANK: Record<SecurityTier, number> = {
community_cloud: 0,
secure_cloud: 1,
government: 2,
};
export function securityTierSatisfies(
available: SecurityTier,
required: SecurityTier,
): boolean {
return SECURITY_TIER_RANK[available] >= SECURITY_TIER_RANK[required];
}
/** Scarcity signal on a listing. Drives "sell this now" alerts. */
export const STOCK_STATUSES = [
'Available',
@@ -347,6 +384,7 @@ export type FactBand = (typeof FACT_BANDS)[number];
export const FACT_STATUSES = [
'applied', // Written to the record
'approved', // Accepted by a person, but not written without a field-aware applicator
'proposed', // Awaiting human review
'dismissed', // Rejected by a human
'superseded', // Replaced by a newer fact
@@ -389,6 +427,10 @@ export const ACTIVITY_TYPES = [
] as const;
export type ActivityType = (typeof ACTIVITY_TYPES)[number];
/** Outbound events a linked collaboration channel may subscribe to. */
export const NOTIFICATION_KINDS = ['stage_change', 'idle_capacity'] as const;
export type NotificationKind = (typeof NOTIFICATION_KINDS)[number];
// ---------------------------------------------------------------------------
// Agent task queue
// ---------------------------------------------------------------------------
+88
View File
@@ -0,0 +1,88 @@
import type { Team, TeamRole } from './ontology';
import { TEAMS } from './ontology';
export const API_KEY_SCOPES = ['read', 'write'] as const;
export type ApiKeyScope = (typeof API_KEY_SCOPES)[number];
export const CAPABILITIES = [
'deal:write',
'commitment:write',
'contract:sign',
'data:import',
'settings:admin',
] as const;
export type Capability = (typeof CAPABILITIES)[number];
export const TEAM_CAPABILITIES = [
'deal:write',
'commitment:write',
'contract:sign',
'data:import',
] as const satisfies readonly Capability[];
export type TeamCapability = (typeof TEAM_CAPABILITIES)[number];
export type GlobalCapability = Exclude<Capability, TeamCapability>;
export interface PermissionSubject {
isPlatformAdmin: boolean;
teams: readonly { team: Team; role: TeamRole }[];
}
export interface PermissionGrant {
capability: Capability;
/** Null is a platform-wide grant; ordinary role grants always name a team. */
team: Team | null;
}
interface TeamCapabilityRule {
teams: readonly Team[];
minimumRole: TeamRole;
}
/**
* The role policy is shared by API and browser code so controls cannot drift
* from server enforcement as new write paths are added.
*/
export const TEAM_CAPABILITY_RULES: Readonly<Record<TeamCapability, TeamCapabilityRule>> = {
'deal:write': { teams: ['supply', 'demand'], minimumRole: 'member' },
'commitment:write': { teams: ['supply'], minimumRole: 'lead' },
'contract:sign': { teams: ['supply', 'demand'], minimumRole: 'admin' },
'data:import': { teams: TEAMS, minimumRole: 'admin' },
};
const ROLE_RANK: Readonly<Record<TeamRole, number>> = {
member: 0,
lead: 1,
admin: 2,
};
export function resolvePermissionGrants(subject: PermissionSubject): PermissionGrant[] {
if (subject.isPlatformAdmin) {
return CAPABILITIES.map((capability) => ({ capability, team: null }));
}
const grants: PermissionGrant[] = [];
for (const capability of TEAM_CAPABILITIES) {
const rule = TEAM_CAPABILITY_RULES[capability];
for (const membership of subject.teams) {
if (
rule.teams.includes(membership.team) &&
ROLE_RANK[membership.role] >= ROLE_RANK[rule.minimumRole]
) {
grants.push({ capability, team: membership.team });
}
}
}
return grants;
}
export function permissionGranted(
grants: readonly PermissionGrant[],
capability: Capability,
team?: Team,
): boolean {
return grants.some(
(grant) =>
grant.capability === capability &&
(grant.team === null || team === undefined || grant.team === team),
);
}
+48
View File
@@ -0,0 +1,48 @@
import { strict as assert } from 'node:assert';
import { describe, it } from 'node:test';
import { permissionGranted, resolvePermissionGrants } from '../src/permissions';
describe('role permissions', () => {
it('keeps deal writes on the side where the person is a member', () => {
const grants = resolvePermissionGrants({
isPlatformAdmin: false,
teams: [{ team: 'demand', role: 'member' }],
});
assert.equal(permissionGranted(grants, 'deal:write', 'demand'), true);
assert.equal(permissionGranted(grants, 'deal:write', 'supply'), false);
assert.equal(permissionGranted(grants, 'commitment:write', 'demand'), false);
});
it('allows supply leads to commit capacity without letting them sign contracts', () => {
const grants = resolvePermissionGrants({
isPlatformAdmin: false,
teams: [{ team: 'supply', role: 'lead' }],
});
assert.equal(permissionGranted(grants, 'commitment:write', 'supply'), true);
assert.equal(permissionGranted(grants, 'contract:sign', 'supply'), false);
});
it('keeps signing and bulk import at team-admin level', () => {
const grants = resolvePermissionGrants({
isPlatformAdmin: false,
teams: [{ team: 'demand', role: 'admin' }],
});
assert.equal(permissionGranted(grants, 'contract:sign', 'demand'), true);
assert.equal(permissionGranted(grants, 'contract:sign', 'supply'), false);
assert.equal(permissionGranted(grants, 'data:import', 'demand'), true);
assert.equal(permissionGranted(grants, 'data:import', 'research'), false);
assert.equal(permissionGranted(grants, 'settings:admin'), false);
});
it('gives platform admins global grants without synthetic team memberships', () => {
const grants = resolvePermissionGrants({ isPlatformAdmin: true, teams: [] });
assert.equal(permissionGranted(grants, 'deal:write', 'demand'), true);
assert.equal(permissionGranted(grants, 'commitment:write', 'supply'), true);
assert.equal(permissionGranted(grants, 'data:import', 'research'), true);
assert.equal(permissionGranted(grants, 'settings:admin'), true);
});
});
+24
View File
@@ -0,0 +1,24 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import { SECURITY_TIERS, securityTierSatisfies } from '../src/index';
test('security tiers form a deliberate minimum-requirement ordering', () => {
const expected: Record<(typeof SECURITY_TIERS)[number], (typeof SECURITY_TIERS)[number][]> = {
community_cloud: ['community_cloud', 'secure_cloud', 'government'],
secure_cloud: ['secure_cloud', 'government'],
government: ['government'],
};
for (const required of SECURITY_TIERS) {
const eligible = SECURITY_TIERS.filter((available) =>
securityTierSatisfies(available, required),
);
assert.deepEqual(eligible.sort(), expected[required].sort());
}
});
test('a sovereign requirement is never satisfied by community capacity', () => {
assert.equal(securityTierSatisfies('community_cloud', 'government'), false);
assert.equal(securityTierSatisfies('secure_cloud', 'government'), false);
assert.equal(securityTierSatisfies('government', 'government'), true);
});
@@ -0,0 +1 @@
ALTER TYPE "public"."pig_security_tier" ADD VALUE 'government' BEFORE 'secure_cloud';
@@ -0,0 +1 @@
ALTER TYPE "public"."pig_fact_status" ADD VALUE 'approved' BEFORE 'proposed';
@@ -0,0 +1,16 @@
CREATE TABLE "platform_settings" (
"id" text PRIMARY KEY DEFAULT 'default' NOT NULL,
"piggy_model" text DEFAULT 'nvidia/nemotron-3-nano-30b-a3b' NOT NULL,
"piggy_inference_base" text DEFAULT 'https://api.pinference.ai/api/v1' NOT NULL,
"piggy_enabled" boolean DEFAULT false NOT NULL,
"prime_api_key_encrypted" text,
"prime_api_key_updated_at" timestamp with time zone,
"prime_sync_enabled" boolean DEFAULT false NOT NULL,
"prime_sync_interval_minutes" integer DEFAULT 30 NOT NULL,
"updated_by_user_id" uuid,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT "platform_settings_singleton_check" CHECK ("platform_settings"."id" = 'default'),
CONSTRAINT "platform_settings_sync_interval_check" CHECK ("platform_settings"."prime_sync_interval_minutes" BETWEEN 1 AND 1440)
);
--> statement-breakpoint
ALTER TABLE "platform_settings" ADD CONSTRAINT "platform_settings_updated_by_user_id_users_id_fk" FOREIGN KEY ("updated_by_user_id") REFERENCES "public"."users"("id") ON DELETE set null ON UPDATE no action;
@@ -0,0 +1,33 @@
CREATE TABLE "notification_outbox" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"provider" text NOT NULL,
"kind" text NOT NULL,
"link_id" uuid,
"workspace_id" text,
"destination" text NOT NULL,
"payload" jsonb NOT NULL,
"idempotency_key" text NOT NULL,
"status" text DEFAULT 'pending' NOT NULL,
"attempts" integer DEFAULT 0 NOT NULL,
"max_attempts" integer DEFAULT 5 NOT NULL,
"due_at" timestamp with time zone DEFAULT now() NOT NULL,
"leased_until" timestamp with time zone,
"leased_by" text,
"delivered_at" timestamp with time zone,
"external_id" text,
"error" text,
"requested_by_user_id" uuid,
"created_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
DROP INDEX "channel_links_platform_channel_key";--> statement-breakpoint
ALTER TABLE "channel_links" ADD COLUMN "workspace_id" text DEFAULT '' NOT NULL;--> statement-breakpoint
ALTER TABLE "channel_links" ADD COLUMN "linked_by_user_id" uuid;--> statement-breakpoint
ALTER TABLE "channel_links" ADD COLUMN "updated_at" timestamp with time zone DEFAULT now() NOT NULL;--> statement-breakpoint
ALTER TABLE "notification_outbox" ADD CONSTRAINT "notification_outbox_link_id_channel_links_id_fk" FOREIGN KEY ("link_id") REFERENCES "public"."channel_links"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "notification_outbox" ADD CONSTRAINT "notification_outbox_requested_by_user_id_users_id_fk" FOREIGN KEY ("requested_by_user_id") REFERENCES "public"."users"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
CREATE UNIQUE INDEX "notification_outbox_idempotency_key" ON "notification_outbox" USING btree ("idempotency_key");--> statement-breakpoint
CREATE INDEX "notification_outbox_claimable_idx" ON "notification_outbox" USING btree ("provider","status","due_at");--> statement-breakpoint
CREATE INDEX "notification_outbox_link_idx" ON "notification_outbox" USING btree ("link_id");--> statement-breakpoint
ALTER TABLE "channel_links" ADD CONSTRAINT "channel_links_linked_by_user_id_users_id_fk" FOREIGN KEY ("linked_by_user_id") REFERENCES "public"."users"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
CREATE UNIQUE INDEX "channel_links_platform_workspace_channel_key" ON "channel_links" USING btree ("platform","workspace_id","channel_id");
@@ -0,0 +1,14 @@
CREATE TABLE "import_identities" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"entity" text NOT NULL,
"key_column" text NOT NULL,
"key_value" text NOT NULL,
"record_id" uuid NOT NULL,
"imported_by_user_id" uuid,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
ALTER TABLE "import_identities" ADD CONSTRAINT "import_identities_imported_by_user_id_users_id_fk" FOREIGN KEY ("imported_by_user_id") REFERENCES "public"."users"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
CREATE UNIQUE INDEX "import_identities_source_key" ON "import_identities" USING btree ("entity","key_column","key_value");--> statement-breakpoint
CREATE INDEX "import_identities_record_idx" ON "import_identities" USING btree ("entity","record_id");
@@ -0,0 +1,25 @@
CREATE TABLE "notion_connections" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"user_id" uuid NOT NULL,
"workspace_id" text NOT NULL,
"workspace_name" text,
"workspace_icon" text,
"bot_id" text,
"credentials_encrypted" text NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "notion_oauth_states" (
"state_hash" text PRIMARY KEY NOT NULL,
"user_id" uuid NOT NULL,
"verifier_hash" text NOT NULL,
"expires_at" timestamp with time zone NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
ALTER TABLE "notion_connections" ADD CONSTRAINT "notion_connections_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "notion_oauth_states" ADD CONSTRAINT "notion_oauth_states_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
CREATE UNIQUE INDEX "notion_connections_user_workspace_key" ON "notion_connections" USING btree ("user_id","workspace_id");--> statement-breakpoint
CREATE INDEX "notion_connections_user_idx" ON "notion_connections" USING btree ("user_id");--> statement-breakpoint
CREATE INDEX "notion_oauth_states_expiry_idx" ON "notion_oauth_states" USING btree ("expires_at");
@@ -0,0 +1,26 @@
CREATE TABLE "google_connections" (
"user_id" uuid PRIMARY KEY NOT NULL,
"refresh_token_encrypted" text NOT NULL,
"access_token_encrypted" text,
"access_token_expires_at" timestamp with time zone,
"scopes" jsonb DEFAULT '[]'::jsonb NOT NULL,
"connected_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "google_oauth_flows" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"user_id" uuid NOT NULL,
"state_hash" text NOT NULL,
"browser_binding_hash" text NOT NULL,
"pkce_verifier_encrypted" text NOT NULL,
"expires_at" timestamp with time zone NOT NULL,
"consumed_at" timestamp with time zone,
"created_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
ALTER TABLE "google_connections" ADD CONSTRAINT "google_connections_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "google_oauth_flows" ADD CONSTRAINT "google_oauth_flows_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
CREATE UNIQUE INDEX "google_oauth_flows_state_hash_key" ON "google_oauth_flows" USING btree ("state_hash");--> statement-breakpoint
CREATE INDEX "google_oauth_flows_expiry_idx" ON "google_oauth_flows" USING btree ("expires_at");--> statement-breakpoint
CREATE INDEX "google_oauth_flows_user_idx" ON "google_oauth_flows" USING btree ("user_id");
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+50 -1
View File
@@ -22,6 +22,55 @@
"when": 1786588874252,
"tag": "0002_invite_reuse",
"breakpoints": true
},
{
"idx": 3,
"version": "7",
"when": 1786600259806,
"tag": "0003_amazing_miss_america",
"breakpoints": true
},
{
"idx": 4,
"version": "7",
"when": 1786602049010,
"tag": "0004_fact_approval",
"breakpoints": true
},
{
"idx": 5,
"version": "7",
"when": 1786611600000,
"tag": "0005_platform_settings",
"breakpoints": true
},
{
"idx": 6,
"version": "7",
"when": 1786611700000,
"tag": "0006_dazzling_forge",
"breakpoints": true
},
{
"idx": 7,
"version": "7",
"when": 1786611800000,
"tag": "0007_nasty_vanisher",
"breakpoints": true
},
{
"idx": 8,
"version": "7",
"when": 1786611900000,
"tag": "0008_easy_scalphunter",
"breakpoints": true
},
{
"idx": 9,
"version": "7",
"when": 1786612000000,
"tag": "0009_warm_metal_master",
"breakpoints": true
}
]
}
}
+11 -32
View File
@@ -33,6 +33,13 @@ import {
timestamp,
uuid,
} from 'drizzle-orm/pg-core';
import { ALLOCATION_STATUSES, GUARANTEE_TYPES } from '@pig/core';
export {
ALLOCATION_STATUSES,
CONSUMING_ALLOCATION_STATUSES,
RESERVING_ALLOCATION_STATUSES,
} from '@pig/core';
export type { AllocationStatus } from '@pig/core';
import { capacityCommitments } from './supply';
import { demandDeals } from './demand';
import { users } from './identity';
@@ -95,7 +102,7 @@ export const allocations = pgTable(
* allocations are shown separately so a seller can see what the pipeline
* would do to utilisation without letting forecasts pollute the actuals.
*/
status: text('status').notNull().default('planned'),
status: text('status', { enum: ALLOCATION_STATUSES }).notNull().default('planned'),
/**
* A capacity HOLD with an expiry.
@@ -129,7 +136,9 @@ export const allocations = pgTable(
* system answer "can I actually sell this?" rather than "is something
* technically free?"
*/
guaranteeType: text('guarantee_type').notNull().default('committed'),
guaranteeType: text('guarantee_type', { enum: GUARANTEE_TYPES })
.notNull()
.default('committed'),
priority: integer('priority').notNull().default(100),
/**
@@ -160,35 +169,5 @@ export const allocations = pgTable(
],
);
/**
* Statuses that actually consume committed capacity.
*
* Kept here beside the column it describes so the definition cannot drift away
* from the schema. Utilisation and idle-capacity figures filter on this;
* including `planned` would let optimistic forecasting hide idle hardware.
*/
export const CONSUMING_ALLOCATION_STATUSES = ['committed', 'active', 'completed'] as const;
/**
* Statuses that block the capacity from being sold to somebody else.
*
* Deliberately WIDER than the set that counts toward utilisation and margin.
* A live hold must remove inventory from availability otherwise two sellers
* promise the same GPUs while not yet counting as sold, because it has not
* been. Conflating "cannot be offered to anyone else" with "earning revenue"
* is how a pipeline of optimistic holds comes to look like a full book.
*/
export const RESERVING_ALLOCATION_STATUSES = [
'planned',
...CONSUMING_ALLOCATION_STATUSES,
] as const;
export const ALLOCATION_STATUSES = [
'planned',
...CONSUMING_ALLOCATION_STATUSES,
'released',
] as const;
export type AllocationStatus = (typeof ALLOCATION_STATUSES)[number];
export type Allocation = typeof allocations.$inferSelect;
export type NewAllocation = typeof allocations.$inferInsert;
+20
View File
@@ -218,3 +218,23 @@ export const complianceArtifacts = pgTable(
export type ExportAuthorization = typeof exportAuthorizations.$inferSelect;
export type ComplianceDecision = typeof complianceDecisions.$inferSelect;
export type ComplianceArtifact = typeof complianceArtifacts.$inferSelect;
export type ComplianceMatchDecision = Pick<
ComplianceDecision,
'decision' | 'supersededAt'
>;
/**
* Whether a recorded export-control predicate permits a prospective match.
*
* `undefined` means no buyer-specific evaluation was requested, as in a generic
* inventory search. Once evaluation is requested, absence, review, block and a
* superseded allow all fail closed. Security classification is not a substitute
* for an export-control determination.
*/
export function complianceDecisionAllowsMatch(
decision: ComplianceMatchDecision | null | undefined,
): boolean {
if (decision === undefined) return true;
return decision?.decision === 'allow' && decision.supersededAt === null;
}
+11 -1
View File
@@ -214,15 +214,25 @@ export const channelLinks = pgTable(
id: uuid('id').primaryKey().defaultRandom(),
/** 'slack' | 'buzz' — kept as text so a new platform needs no migration. */
platform: text('platform').notNull(),
/** Slack channel ids are unique only inside a workspace. Buzz uses ''. */
workspaceId: text('workspace_id').notNull().default(''),
channelId: text('channel_id').notNull(),
channelName: text('channel_name'),
accountId: uuid('account_id').references(() => accounts.id, { onDelete: 'cascade' }),
/** Notify on stage changes, idle-capacity alerts, renewals due. */
notifyOn: jsonb('notify_on').$type<string[]>().notNull().default([]),
linkedByUserId: uuid('linked_by_user_id').references(() => users.id, {
onDelete: 'set null',
}),
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
},
(t) => [
uniqueIndex('channel_links_platform_channel_key').on(t.platform, t.channelId),
uniqueIndex('channel_links_platform_workspace_channel_key').on(
t.platform,
t.workspaceId,
t.channelId,
),
index('channel_links_account_idx').on(t.accountId),
],
);
+44
View File
@@ -0,0 +1,44 @@
import { index, jsonb, pgTable, text, timestamp, uniqueIndex, uuid } from 'drizzle-orm/pg-core';
import { users } from './identity';
/** One least-privilege Google Sheets connection per PIG user. */
export const googleConnections = pgTable('google_connections', {
userId: uuid('user_id')
.primaryKey()
.references(() => users.id, { onDelete: 'cascade' }),
/** Purpose-bound AES-256-GCM envelopes. Tokens are never returned by the API. */
refreshTokenEncrypted: text('refresh_token_encrypted').notNull(),
accessTokenEncrypted: text('access_token_encrypted'),
accessTokenExpiresAt: timestamp('access_token_expires_at', { withTimezone: true }),
scopes: jsonb('scopes').$type<string[]>().notNull().default([]),
connectedAt: timestamp('connected_at', { withTimezone: true }).notNull().defaultNow(),
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
});
/**
* Short-lived OAuth proof material stays server-side. State is hashed and the
* PKCE verifier encrypted so a database read cannot complete an OAuth flow.
*/
export const googleOauthFlows = pgTable(
'google_oauth_flows',
{
id: uuid('id').primaryKey().defaultRandom(),
userId: uuid('user_id')
.notNull()
.references(() => users.id, { onDelete: 'cascade' }),
stateHash: text('state_hash').notNull(),
browserBindingHash: text('browser_binding_hash').notNull(),
pkceVerifierEncrypted: text('pkce_verifier_encrypted').notNull(),
expiresAt: timestamp('expires_at', { withTimezone: true }).notNull(),
consumedAt: timestamp('consumed_at', { withTimezone: true }),
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
},
(table) => [
uniqueIndex('google_oauth_flows_state_hash_key').on(table.stateHash),
index('google_oauth_flows_expiry_idx').on(table.expiresAt),
index('google_oauth_flows_user_idx').on(table.userId),
],
);
export type GoogleConnection = typeof googleConnections.$inferSelect;
export type GoogleOauthFlow = typeof googleOauthFlows.$inferSelect;
+33
View File
@@ -0,0 +1,33 @@
import { index, pgTable, text, timestamp, uniqueIndex, uuid } from 'drizzle-orm/pg-core';
import { users } from './identity';
/**
* Durable source identities make repeated imports updates rather than duplicate
* creates. Raw spreadsheet content stays out of the database; only the user's
* chosen stable key and the PIG record it resolved to are retained.
*/
export const importIdentities = pgTable(
'import_identities',
{
id: uuid('id').primaryKey().defaultRandom(),
entity: text('entity').notNull(),
keyColumn: text('key_column').notNull(),
keyValue: text('key_value').notNull(),
recordId: uuid('record_id').notNull(),
importedByUserId: uuid('imported_by_user_id').references(() => users.id, {
onDelete: 'set null',
}),
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
},
(table) => [
uniqueIndex('import_identities_source_key').on(
table.entity,
table.keyColumn,
table.keyValue,
),
index('import_identities_record_idx').on(table.entity, table.recordId),
],
);
export type ImportIdentity = typeof importIdentities.$inferSelect;
+5
View File
@@ -16,6 +16,7 @@
*/
export * from './enums';
export * from './identity';
export * from './settings';
export * from './crm';
export * from './supply';
export * from './demand';
@@ -24,3 +25,7 @@ export * from './contracts';
export * from './compliance';
export * from './agent';
export * from './fields';
export * from './integrations';
export * from './imports';
export * from './google';
export * from './notion';
+44
View File
@@ -0,0 +1,44 @@
import { index, integer, jsonb, pgTable, text, timestamp, uniqueIndex, uuid } from 'drizzle-orm/pg-core';
import { channelLinks } from './crm';
import { users } from './identity';
/**
* External delivery is a separate durable concern from the CRM transaction.
* A deal write only adds a row here; a provider-specific worker owns network
* failure, retry and completion without holding the request thread open.
*/
export const notificationOutbox = pgTable(
'notification_outbox',
{
id: uuid('id').primaryKey().defaultRandom(),
provider: text('provider').notNull(),
kind: text('kind').notNull(),
linkId: uuid('link_id').references(() => channelLinks.id, { onDelete: 'set null' }),
workspaceId: text('workspace_id'),
destination: text('destination').notNull(),
payload: jsonb('payload').$type<Record<string, unknown>>().notNull(),
idempotencyKey: text('idempotency_key').notNull(),
status: text('status').notNull().default('pending'),
attempts: integer('attempts').notNull().default(0),
maxAttempts: integer('max_attempts').notNull().default(5),
dueAt: timestamp('due_at', { withTimezone: true }).notNull().defaultNow(),
leasedUntil: timestamp('leased_until', { withTimezone: true }),
leasedBy: text('leased_by'),
deliveredAt: timestamp('delivered_at', { withTimezone: true }),
externalId: text('external_id'),
error: text('error'),
requestedByUserId: uuid('requested_by_user_id').references(() => users.id, {
onDelete: 'set null',
}),
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
},
(t) => [
uniqueIndex('notification_outbox_idempotency_key').on(t.idempotencyKey),
index('notification_outbox_claimable_idx').on(t.provider, t.status, t.dueAt),
index('notification_outbox_link_idx').on(t.linkId),
],
);
export type NotificationOutboxItem = typeof notificationOutbox.$inferSelect;
+45
View File
@@ -0,0 +1,45 @@
import { index, pgTable, text, timestamp, uniqueIndex, uuid } from 'drizzle-orm/pg-core';
import { users } from './identity';
/**
* A Notion grant belongs to the person who completed OAuth. Credentials are a
* single authenticated-encryption envelope so no token-shaped value is ever
* queryable, searchable, or accidentally selected as ordinary metadata.
*/
export const notionConnections = pgTable(
'notion_connections',
{
id: uuid('id').primaryKey().defaultRandom(),
userId: uuid('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }),
workspaceId: text('workspace_id').notNull(),
workspaceName: text('workspace_name'),
workspaceIcon: text('workspace_icon'),
botId: text('bot_id'),
credentialsEncrypted: text('credentials_encrypted').notNull(),
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
},
(table) => [
uniqueIndex('notion_connections_user_workspace_key').on(table.userId, table.workspaceId),
index('notion_connections_user_idx').on(table.userId),
],
);
/**
* OAuth attempts survive multiple API processes but not replay. Only hashes
* are durable; the browser verifier is an HttpOnly, short-lived cookie.
*/
export const notionOauthStates = pgTable(
'notion_oauth_states',
{
stateHash: text('state_hash').primaryKey(),
userId: uuid('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }),
verifierHash: text('verifier_hash').notNull(),
expiresAt: timestamp('expires_at', { withTimezone: true }).notNull(),
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
},
(table) => [index('notion_oauth_states_expiry_idx').on(table.expiresAt)],
);
export type NotionConnection = typeof notionConnections.$inferSelect;
export type NotionOauthState = typeof notionOauthStates.$inferSelect;
+42
View File
@@ -0,0 +1,42 @@
import {
boolean,
check,
integer,
pgTable,
text,
timestamp,
uuid,
} from 'drizzle-orm/pg-core';
import { sql } from 'drizzle-orm';
import { users } from './identity';
/** Workspace-wide controls. The check keeps this deliberately single-tenant. */
export const platformSettings = pgTable(
'platform_settings',
{
id: text('id').primaryKey().default('default'),
piggyModel: text('piggy_model').notNull().default('nvidia/nemotron-3-nano-30b-a3b'),
piggyInferenceBase: text('piggy_inference_base')
.notNull()
.default('https://api.pinference.ai/api/v1'),
piggyEnabled: boolean('piggy_enabled').notNull().default(false),
/** AES-256-GCM envelope. Its key lives outside the database. */
primeApiKeyEncrypted: text('prime_api_key_encrypted'),
primeApiKeyUpdatedAt: timestamp('prime_api_key_updated_at', { withTimezone: true }),
primeSyncEnabled: boolean('prime_sync_enabled').notNull().default(false),
primeSyncIntervalMinutes: integer('prime_sync_interval_minutes').notNull().default(30),
updatedByUserId: uuid('updated_by_user_id').references(() => users.id, {
onDelete: 'set null',
}),
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
},
(t) => [
check('platform_settings_singleton_check', sql`${t.id} = 'default'`),
check(
'platform_settings_sync_interval_check',
sql`${t.primeSyncIntervalMinutes} BETWEEN 1 AND 1440`,
),
],
);
export type PlatformSettings = typeof platformSettings.$inferSelect;
+50 -20
View File
@@ -35,6 +35,7 @@
* is more useful than one that opens on a loss, which reads as a broken
* product rather than an under-utilised book.
*/
import { ALLOCATION_STATUSES, type AllocationStatus } from '@pig/core';
import { and, eq, like, or } from 'drizzle-orm';
import { createDatabase } from '../client';
import {
@@ -47,6 +48,7 @@ import {
contracts,
contractObligations,
demandDeals,
type NewAllocation,
sites,
slaTerms,
supplyDeals,
@@ -59,6 +61,10 @@ const day = 86_400_000;
const now = Date.now();
const at = (days: number) => new Date(now + days * day);
function isAllocationStatus(value: string): value is AllocationStatus {
return (ALLOCATION_STATUSES as readonly string[]).includes(value);
}
/** GPU-hours for a block, allowing for a maintenance/ramp haircut. */
const hours = (gpus: number, days: number, efficiency = 0.94) =>
String(Math.round(gpus * 24 * days * efficiency));
@@ -153,7 +159,12 @@ const DEMAND = [
},
request: { gpuType: 'H200', gpuCount: 256, fastFabric: true, maxPriceCents: 285 },
// Draws from the CoreWeave block.
allocation: { supplier: 'coreweave.com', share: 0.76, priceCents: 271, status: 'active' },
allocation: {
supplier: 'coreweave.com',
share: 0.76,
priceCents: 271,
status: 'active',
},
},
{
account: `${PREFIX}Verity Health AI`,
@@ -178,7 +189,12 @@ const DEMAND = [
allowedRegions: ['eu-north', 'eu-west'],
certifications: ['ISO 27001', 'SOC 2 Type II'],
},
allocation: { supplier: 'nebius.com', share: 0.55, priceCents: 249, status: 'committed' },
allocation: {
supplier: 'nebius.com',
share: 0.55,
priceCents: 249,
status: 'committed',
},
},
{
account: `${PREFIX}Northwind Robotics`,
@@ -195,7 +211,12 @@ const DEMAND = [
dpaExecuted: true,
},
request: { gpuType: 'B200', gpuCount: 32, fastFabric: true, maxPriceCents: 460 },
allocation: { supplier: 'crusoe.ai', share: 0.74, priceCents: 441, status: 'active' },
allocation: {
supplier: 'crusoe.ai',
share: 0.74,
priceCents: 441,
status: 'active',
},
},
{
account: `${PREFIX}Tessellate Labs`,
@@ -215,7 +236,13 @@ const DEMAND = [
// A HOLD, not a sale. The deal has not closed, so this reserves capacity
// without counting as revenue — the distinction the capacity view exists
// to make visible.
allocation: { supplier: 'runpod.io', share: 0.55, priceCents: 168, status: 'planned', holdDays: 12 },
allocation: {
supplier: 'runpod.io',
share: 0.55,
priceCents: 168,
status: 'planned',
holdDays: 12,
},
},
{
account: `${PREFIX}Aurelian Systems`,
@@ -554,7 +581,10 @@ async function seedDemo() {
.limit(1);
if (commitment) {
await db.insert(allocations).values({
if (!isAllocationStatus(d.allocation.status)) {
throw new Error(`Invalid demo allocation status: ${d.allocation.status}`);
}
const allocationRow = {
capacityCommitmentId: commitmentId,
demandDealId: deal.id,
gpuHours: String(
@@ -568,7 +598,8 @@ async function seedDemo() {
priority: d.allocation.status === 'planned' ? 100 : 10,
holdExpiresAt: d.allocation.holdDays ? at(d.allocation.holdDays) : null,
notes: d.account,
});
} satisfies NewAllocation;
await db.insert(allocations).values(allocationRow);
}
}
}
@@ -613,20 +644,19 @@ async function seedDemo() {
.limit(1);
if (commitment && !existingResearch) {
await db
.insert(allocations)
.values({
capacityCommitmentId: coreweave,
internalTeam: 'research',
gpuHours: String(Math.round(Number(commitment.totalGpuHours) * 0.08)),
pricePerGpuHourCents: 0,
startsAt: commitment.startsAt,
endsAt: commitment.endsAt,
status: 'active',
guaranteeType: 'internal',
priority: 200,
notes: RESEARCH_NOTE,
});
const researchAllocationRow = {
capacityCommitmentId: coreweave,
internalTeam: 'research',
gpuHours: String(Math.round(Number(commitment.totalGpuHours) * 0.08)),
pricePerGpuHourCents: 0,
startsAt: commitment.startsAt,
endsAt: commitment.endsAt,
status: 'active',
guaranteeType: 'internal',
priority: 200,
notes: RESEARCH_NOTE,
} satisfies NewAllocation;
await db.insert(allocations).values(researchAllocationRow);
}
}
+15 -6
View File
@@ -5,10 +5,14 @@
* a rename rather than a transformation. The interesting parts are the two
* places where a judgement is required.
*
* **Money.** Upstream prices are floating-point dollars per hour. PIG stores
* integer cents, because these values feed margin reporting and floating-point
* currency in a system that reports margin is a defect waiting to be found by
* an accountant. Rounding happens exactly once, here, at the boundary.
* **Node totals.** Upstream reports `gpuMemory` and `prices.onDemand` for the
* whole node, while PIG compares inventory per GPU. Normalising here prevents
* larger nodes from appearing to have more expensive GPUs, and `raw` keeps the
* original totals for reconciliation.
*
* **Money.** PIG stores integer cents, because floating-point currency in a
* system that reports margin is a defect waiting to be found by an accountant.
* Rounding happens exactly once, here, after normalising to a per-GPU price.
*
* **Interconnect.** The single most commercially loaded field, since it decides
* whether capacity can train or only serve. Upstream sends free text with
@@ -58,7 +62,7 @@ export function mapListing(listing: PrimeGpuListing): MappedListing | null {
gpuType: listing.gpuType,
socket: normaliseSocket(listing.socket),
gpuCount: listing.gpuCount,
gpuMemoryGb: listing.gpuMemory ?? null,
gpuMemoryGb: perGpu(listing.gpuMemory, listing.gpuCount),
vcpu: unwrapCount(listing.vcpu),
memoryGb: unwrapCount(listing.memory),
diskGb: listing.disk?.defaultCount ?? null,
@@ -72,7 +76,7 @@ export function mapListing(listing: PrimeGpuListing): MappedListing | null {
isSpot: listing.isSpot ?? false,
provisioningMinutes: listing.provisioningTime ?? null,
prepaidHours: listing.prepaidTime != null ? String(listing.prepaidTime) : null,
onDemandPriceCents: toCents(listing.prices?.onDemand),
onDemandPriceCents: toCents(perGpu(listing.prices?.onDemand, listing.gpuCount)),
communityPriceCents: toCents(listing.prices?.communityPrice),
priceIsVariable: listing.prices?.isVariable ?? false,
currency: listing.prices?.currency ?? 'USD',
@@ -94,6 +98,11 @@ export function toCents(dollars: number | null | undefined): number | null {
return Math.round(dollars * 100);
}
function perGpu(total: number | null | undefined, gpuCount: number): number | null {
if (total == null || !Number.isFinite(total)) return null;
return total / gpuCount;
}
function unwrapCount(value: unknown): number | null {
if (typeof value === 'number') return value;
if (value && typeof value === 'object') {
+33 -1
View File
@@ -9,6 +9,10 @@
* be represented exactly in binary, and a cent compounds across millions of
* GPU-hours.
*
* **Node totals.** Prime reports on-demand price and GPU memory for the whole
* node. PIG compares per-GPU values, so a larger node must not look more
* expensive merely because it contains more GPUs.
*
* **Interconnect.** The field that decides whether capacity can train or
* only serve. Guessing wrong in either direction loses a deal or sells a
* customer a cluster that cannot do the job.
@@ -99,11 +103,39 @@ describe('mapListing — general', () => {
});
it('converts prices to integer cents', () => {
const m = mapListing(listing({ prices: { onDemand: 2.43, communityPrice: 0.94 } }))!;
const m = mapListing(
listing({ gpuCount: 1, prices: { onDemand: 2.43, communityPrice: 0.94 } }),
)!;
assert.equal(m.onDemandPriceCents, 243);
assert.equal(m.communityPriceCents, 94);
});
it('normalises verified DataCrunch A100 node totals to the same per-GPU values', () => {
const oneGpuRaw = {
provider: 'datacrunch',
gpuType: 'A100_80GB',
gpuCount: 1,
gpuMemory: 80,
prices: { onDemand: 1.79 },
};
const twoGpuRaw = {
provider: 'datacrunch',
gpuType: 'A100_80GB',
gpuCount: 2,
gpuMemory: 160,
prices: { onDemand: 3.58 },
};
const oneGpu = mapListing({ ...oneGpuRaw, raw: oneGpuRaw })!;
const twoGpu = mapListing({ ...twoGpuRaw, raw: twoGpuRaw })!;
assert.equal(oneGpu.onDemandPriceCents, 179);
assert.equal(twoGpu.onDemandPriceCents, 179);
assert.equal(oneGpu.gpuMemoryGb, 80);
assert.equal(twoGpu.gpuMemoryGb, 80);
assert.deepEqual(twoGpu.raw, twoGpuRaw);
});
it('defaults to the secure tier unless community is stated', () => {
// Mislabelling community capacity as secure would let it be sold against a
// requirement it cannot meet.