Scaffold PIG and model the compute-GTM ontology
PIG is an agent-native CRM for two-sided AI-compute companies: businesses that buy GPU capacity from providers and resell it. Their business is the spread between two pipelines, which is precisely what a generic CRM cannot represent. The load-bearing decision is the `allocations` table, joining a capacity_commitment (what we bought, at a known cost) to a demand_deal (what we sold, at a known price). Margin, utilisation and idle capacity all fall out of that one join. Cost is charged against the full commitment rather than only the hours that sold, because unsold hours are already paid for and any other treatment flatters a block that is losing money. Domain decisions worth noting, each grounded in how this market operates: - Demand stages put `legal` second, not last. Customers do not hand workloads to an infrastructure provider before paper is executed. - Supply qualification splits technical from financial diligence, recorded attributably. Accepting capacity is a two-key decision. - Capacity carries a time SHAPE (intervals + quantities), not a window. Commitments ramp and step down; a rectangle reports availability that does not exist in the month someone wants it. - SLAs model three distinct shapes: none, a reliability tier plus credits policy, and a negotiated agreement. Aggregators generally cannot promise uptime on resold capacity, but negotiate heavyweight paper upstream. Remedies include fee abatement, which is materially better than a capped credit and is not expressible as one. - Export control is a predicate on the allocation edge, evaluated against the ULTIMATE parent's jurisdiction. Country of incorporation is not a valid key, so this cannot live as a flag on an account. - Agent-derived claims land in `facts` with a confidence band and evidence. Only verified claims self-apply; weaker ones await review. - The API never calls the agent. It writes to a leased queue, guarded by a partial unique index on unfinished work. Verified: typechecks clean, migration generates and applies to Postgres 16 (31 tables, 24 enums, 117 indexes). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,348 @@
|
||||
/**
|
||||
* Contracts: MSA, DPA, SLA, order forms, capacity commitments.
|
||||
*
|
||||
* Two things about this market shape the design.
|
||||
*
|
||||
* First, contracts are not a closing formality on the demand side — legal sits
|
||||
* early in the pipeline, because a customer will not hand workloads to an
|
||||
* infrastructure provider before paper is in place.
|
||||
*
|
||||
* Second, and less obviously: **service levels are promised differently on each
|
||||
* side of the business**. A compute aggregator generally cannot offer a
|
||||
* conventional uptime guarantee on capacity it resells and does not control,
|
||||
* and says so publicly. What it offers self-serve customers instead is a
|
||||
* reliability tier plus a service-credits policy. Yet the same company
|
||||
* negotiates heavyweight SLAs upstream with providers, and bespoke ones
|
||||
* downstream with enterprise customers on dedicated clusters.
|
||||
*
|
||||
* All three shapes therefore coexist, and the schema holds them without
|
||||
* pretending any one is the others. See `slaKind` below.
|
||||
*/
|
||||
import {
|
||||
boolean,
|
||||
index,
|
||||
integer,
|
||||
jsonb,
|
||||
numeric,
|
||||
pgTable,
|
||||
text,
|
||||
timestamp,
|
||||
uuid,
|
||||
} from 'drizzle-orm/pg-core';
|
||||
import {
|
||||
contractStatusEnum,
|
||||
contractTypeEnum,
|
||||
slaKindEnum,
|
||||
slaMetricEnum,
|
||||
} from './enums';
|
||||
import { accounts } from './crm';
|
||||
import { demandDeals } from './demand';
|
||||
import { capacityCommitments, supplyDeals } from './supply';
|
||||
import { users } from './identity';
|
||||
|
||||
export const contracts = pgTable(
|
||||
'contracts',
|
||||
{
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
accountId: uuid('account_id')
|
||||
.notNull()
|
||||
.references(() => accounts.id, { onDelete: 'cascade' }),
|
||||
|
||||
type: contractTypeEnum('type').notNull(),
|
||||
status: contractStatusEnum('status').notNull().default('draft'),
|
||||
|
||||
/**
|
||||
* Which side of the market this paper governs. The same account can hold
|
||||
* both — we may buy capacity from a neocloud under one MSA while selling
|
||||
* them managed training under another.
|
||||
*/
|
||||
side: text('side').notNull().default('demand'),
|
||||
|
||||
title: text('title').notNull(),
|
||||
/** Counterparty's own reference, for reconciliation with their systems. */
|
||||
externalReference: text('external_reference'),
|
||||
|
||||
/** Optional links to whatever this contract is about. */
|
||||
demandDealId: uuid('demand_deal_id').references(() => demandDeals.id, {
|
||||
onDelete: 'set null',
|
||||
}),
|
||||
supplyDealId: uuid('supply_deal_id').references(() => supplyDeals.id, {
|
||||
onDelete: 'set null',
|
||||
}),
|
||||
capacityCommitmentId: uuid('capacity_commitment_id').references(
|
||||
() => capacityCommitments.id,
|
||||
{ onDelete: 'set null' },
|
||||
),
|
||||
|
||||
/**
|
||||
* An order form or SLA usually hangs beneath a master agreement. Modelling
|
||||
* that hierarchy means "which MSA governs this order form?" is answerable,
|
||||
* which is the first question asked in any dispute.
|
||||
*
|
||||
* Note the order of precedence in real master agreements: **the order form
|
||||
* beats the MSA**, then exhibits and addenda, then the body of the
|
||||
* agreement, then documentation. So an order-form-level override is not an
|
||||
* annotation on the master terms — it supersedes them, and must be stored
|
||||
* as data rather than left in a notes field.
|
||||
*/
|
||||
parentContractId: uuid('parent_contract_id'),
|
||||
|
||||
/**
|
||||
* The signing entity, where it differs from the account.
|
||||
*
|
||||
* Affiliates routinely execute their own order forms under a parent's
|
||||
* master agreement, binding themselves as if an original party. Assuming
|
||||
* one contract equals one legal entity misfiles the counterparty on
|
||||
* exactly the deals large enough to matter.
|
||||
*/
|
||||
contractingPartyName: text('contracting_party_name'),
|
||||
|
||||
/**
|
||||
* Take-or-pay and prepayment terms. Together with `terminationTier` below
|
||||
* these are what make a backlog figure meaningful: contracted revenue
|
||||
* under a prepaid take-or-pay commitment and contracted revenue cancellable
|
||||
* on ninety days' notice are not the same asset, and summing them
|
||||
* unweighted overstates the book.
|
||||
*/
|
||||
takeOrPayFloorPct: numeric('take_or_pay_floor_pct', { precision: 6, scale: 2 }),
|
||||
prepaidPct: numeric('prepaid_pct', { precision: 6, scale: 2 }),
|
||||
/**
|
||||
* `1_prepaid` Take-or-pay with prepayment — the highest quality
|
||||
* `2_take_or_pay` Multi-year take-or-pay, no prepayment
|
||||
* `3_cancellable` Monthly billing with a short termination right
|
||||
*/
|
||||
terminationTier: text('termination_tier'),
|
||||
|
||||
/**
|
||||
* Whether this contract may be assigned to a substitute operator on
|
||||
* default, and within how long. A negotiated step-in right can require
|
||||
* assignment of the customer agreement *and the underlying datacentre
|
||||
* agreement* within days — which only works if the link between the two
|
||||
* exists in a system somewhere. That link is `capacityCommitmentId` above.
|
||||
*/
|
||||
assignableOnDefault: boolean('assignable_on_default').notNull().default(false),
|
||||
assignmentDeadlineBusinessDays: integer('assignment_deadline_business_days'),
|
||||
|
||||
effectiveAt: timestamp('effective_at', { withTimezone: true }),
|
||||
expiresAt: timestamp('expires_at', { withTimezone: true }),
|
||||
executedAt: timestamp('executed_at', { withTimezone: true }),
|
||||
terminatedAt: timestamp('terminated_at', { withTimezone: true }),
|
||||
|
||||
isAutoRenew: boolean('is_auto_renew').notNull().default(false),
|
||||
/** Days of notice to prevent auto-renewal. Drives the renewal alarm. */
|
||||
noticeDays: integer('notice_days'),
|
||||
|
||||
valueCents: integer('value_cents'),
|
||||
currency: text('currency').notNull().default('USD'),
|
||||
|
||||
governingLaw: text('governing_law'),
|
||||
/** Link to the executed document. PIG stores the pointer, not the paper. */
|
||||
documentUrl: text('document_url'),
|
||||
|
||||
ownerUserId: uuid('owner_user_id').references(() => users.id, { onDelete: 'set null' }),
|
||||
notes: text('notes'),
|
||||
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
},
|
||||
(t) => [
|
||||
index('contracts_account_idx').on(t.accountId),
|
||||
index('contracts_type_idx').on(t.type),
|
||||
index('contracts_status_idx').on(t.status),
|
||||
index('contracts_expiry_idx').on(t.expiresAt),
|
||||
index('contracts_demand_deal_idx').on(t.demandDealId),
|
||||
],
|
||||
);
|
||||
|
||||
/**
|
||||
* The service-level terms attached to a contract.
|
||||
*
|
||||
* `slaKind` is the important column:
|
||||
*
|
||||
* `none` Self-serve. No service commitment whatsoever.
|
||||
* `credits_policy` The common case for resold capacity — a reliability tier
|
||||
* plus service credits when a provider fails. Not an uptime
|
||||
* guarantee, and must not be displayed as one.
|
||||
* `negotiated` A real signed SLA with committed, measurable metrics.
|
||||
*
|
||||
* Recording a credits policy as though it were a negotiated SLA would let a
|
||||
* seller promise a customer something the business has not agreed to underwrite.
|
||||
*/
|
||||
export const slaTerms = pgTable(
|
||||
'sla_terms',
|
||||
{
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
contractId: uuid('contract_id')
|
||||
.notNull()
|
||||
.references(() => contracts.id, { onDelete: 'cascade' }),
|
||||
|
||||
kind: slaKindEnum('kind').notNull().default('credits_policy'),
|
||||
|
||||
/** Committed uptime, where one is genuinely committed. */
|
||||
uptimeTargetPct: numeric('uptime_target_pct', { precision: 6, scale: 3 }),
|
||||
/** Hours to replace a failed node — often more negotiated than uptime. */
|
||||
nodeReplacementHours: integer('node_replacement_hours'),
|
||||
mttrHours: integer('mttr_hours'),
|
||||
supportResponseHours: integer('support_response_hours'),
|
||||
|
||||
/** 'monthly' | 'quarterly' — the window uptime is measured over. */
|
||||
measurementWindow: text('measurement_window').notNull().default('monthly'),
|
||||
|
||||
/**
|
||||
* What the target is measured against: `region`, `instance`, `node`,
|
||||
* `rack`, `cluster`, `endpoint`.
|
||||
*
|
||||
* Rack-scale systems are commonly sold with TWO simultaneous levels — for
|
||||
* example 99% per node alongside 95% per rack, where a rack counts as
|
||||
* healthy at 16 of 18 nodes. Recording only one of them misstates what was
|
||||
* promised, so additional levels go in `slaMetricTargets` and this column
|
||||
* names the basis of the headline figure.
|
||||
*/
|
||||
measurementUnit: text('measurement_unit').notNull().default('cluster'),
|
||||
|
||||
/**
|
||||
* The remedy actually available. This is the field that matters most, and
|
||||
* the one a credits-only model gets wrong.
|
||||
*
|
||||
* `service_credit` A capped percentage of fees, claimed within a window
|
||||
* `fee_abatement` Payment obligations CANCELLED for the affected
|
||||
* capacity until service is restored — uncapped in
|
||||
* duration, and far more valuable than a credit
|
||||
* `termination_right` The customer may exit
|
||||
*
|
||||
* Fee abatement is real and negotiated: a leading provider's filed master
|
||||
* agreement cancels fees for services not performed once a hardware
|
||||
* failure persists two consecutive business days, and does not resume them
|
||||
* until the failure is resolved.
|
||||
*/
|
||||
remedyType: text('remedy_type').notNull().default('service_credit'),
|
||||
/** Consecutive duration triggering abatement, and its unit. */
|
||||
abatementTriggerValue: integer('abatement_trigger_value'),
|
||||
abatementTriggerUnit: text('abatement_trigger_unit'),
|
||||
|
||||
/**
|
||||
* Deadline to file a claim, and its unit — observed across the market from
|
||||
* ten days to two billing cycles. Miss it and the credit is simply
|
||||
* forfeit, which makes this an operational alarm, not a footnote.
|
||||
*/
|
||||
claimDeadlineValue: integer('claim_deadline_value'),
|
||||
claimDeadlineUnit: text('claim_deadline_unit').notNull().default('days'),
|
||||
/** Credits commonly expire if unused. */
|
||||
creditExpiryMonths: integer('credit_expiry_months'),
|
||||
|
||||
/** Whether credits are the sole and exclusive remedy. Usually yes. */
|
||||
isSoleRemedy: boolean('is_sole_remedy').notNull().default(true),
|
||||
|
||||
/**
|
||||
* Contractual spare-pool obligation — that the provider keeps enough
|
||||
* spares on hand to replace what cannot be remediated. Observed scoped to
|
||||
* **both compute nodes and network switches, per location**; switches are
|
||||
* routinely forgotten and are just as capable of stranding a cluster.
|
||||
*/
|
||||
sparePoolObligation: text('spare_pool_obligation'),
|
||||
sparePoolScope: jsonb('spare_pool_scope').$type<string[]>().notNull().default([]),
|
||||
|
||||
/**
|
||||
* Maintenance classes and their notice periods — planned, planned-critical
|
||||
* and emergency are typically distinct, with different notice and
|
||||
* different treatment in the uptime calculation. Some allowance of
|
||||
* maintenance hours is normally excluded from the measurement entirely.
|
||||
*/
|
||||
maintenanceClasses: jsonb('maintenance_classes')
|
||||
.$type<
|
||||
{
|
||||
class: string;
|
||||
noticeValue: number;
|
||||
noticeUnit: string;
|
||||
allowancePerPeriodHours?: number;
|
||||
excludedFromUptime: boolean;
|
||||
}[]
|
||||
>()
|
||||
.notNull()
|
||||
.default([]),
|
||||
|
||||
/**
|
||||
* Days per year during which only "reasonable endeavours" apply and the
|
||||
* service levels are suspended. A real, negotiated carve-out that can span
|
||||
* months, and one that silently voids the guarantee being sold.
|
||||
*/
|
||||
reasonableEndeavoursDaysPerYear: integer('reasonable_endeavours_days_per_year'),
|
||||
|
||||
/** Hours within which a root cause analysis is owed after a major incident. */
|
||||
rcaDeliveryHours: integer('rca_delivery_hours'),
|
||||
|
||||
/**
|
||||
* Credit schedule: bands of missed target mapped to the percentage of fees
|
||||
* credited back. Stored as JSON because every counterparty structures these
|
||||
* differently and normalising them loses the shape of the deal.
|
||||
*/
|
||||
creditSchedule: jsonb('credit_schedule')
|
||||
.$type<{ belowPct: number; creditPct: number }[]>()
|
||||
.notNull()
|
||||
.default([]),
|
||||
/** Maximum credits payable in a window, as a share of fees. */
|
||||
creditCapPct: numeric('credit_cap_pct', { precision: 6, scale: 3 }),
|
||||
|
||||
/** Carve-outs: maintenance windows, force majeure, customer-caused faults. */
|
||||
exclusions: text('exclusions'),
|
||||
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
},
|
||||
(t) => [index('sla_terms_contract_idx').on(t.contractId)],
|
||||
);
|
||||
|
||||
/** An individual committed metric, where an SLA commits several. */
|
||||
export const slaMetricTargets = pgTable(
|
||||
'sla_metric_targets',
|
||||
{
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
slaTermId: uuid('sla_term_id')
|
||||
.notNull()
|
||||
.references(() => slaTerms.id, { onDelete: 'cascade' }),
|
||||
metric: slaMetricEnum('metric').notNull(),
|
||||
targetValue: numeric('target_value', { precision: 12, scale: 3 }).notNull(),
|
||||
unit: text('unit'),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
},
|
||||
(t) => [index('sla_metric_targets_term_idx').on(t.slaTermId)],
|
||||
);
|
||||
|
||||
/**
|
||||
* Dated obligations arising from a contract — renewal notice deadlines,
|
||||
* milestone deliverables, true-up dates, security reviews.
|
||||
*
|
||||
* These are what actually get missed. A contract row with an expiry date is
|
||||
* inert; an obligation with a due date can be alerted on, which is the point.
|
||||
*/
|
||||
export const contractObligations = pgTable(
|
||||
'contract_obligations',
|
||||
{
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
contractId: uuid('contract_id')
|
||||
.notNull()
|
||||
.references(() => contracts.id, { onDelete: 'cascade' }),
|
||||
|
||||
title: text('title').notNull(),
|
||||
description: text('description'),
|
||||
/** 'renewal_notice' | 'milestone' | 'payment' | 'review' | 'true_up' */
|
||||
kind: text('kind').notNull().default('milestone'),
|
||||
|
||||
dueAt: timestamp('due_at', { withTimezone: true }).notNull(),
|
||||
completedAt: timestamp('completed_at', { withTimezone: true }),
|
||||
ownerUserId: uuid('owner_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) => [
|
||||
index('contract_obligations_contract_idx').on(t.contractId),
|
||||
index('contract_obligations_due_idx').on(t.dueAt),
|
||||
index('contract_obligations_owner_idx').on(t.ownerUserId),
|
||||
],
|
||||
);
|
||||
|
||||
export type Contract = typeof contracts.$inferSelect;
|
||||
export type NewContract = typeof contracts.$inferInsert;
|
||||
export type SlaTerm = typeof slaTerms.$inferSelect;
|
||||
export type ContractObligation = typeof contractObligations.$inferSelect;
|
||||
Reference in New Issue
Block a user