13dec6b4b8
Seven parallel agents and an adversarial verification pass. The three things worth knowing before reading the diff: RBAC WAS ALREADY BUILT. docs/build-plan.md marks F2 and F3 outstanding and is stale — packages/core/src/permissions.ts and lib/mutation.ts shipped long ago. So this does not rebuild them; it closes the gaps an audit found. The big one is that reads were entirely ungoverned: every GET was "any authenticated member", so a junior demand rep and a research contractor could both pull per-block supplier cost and break-even prices from /api/capacity/margin, and every contract's negotiated terms. For a company whose margin is the business, that was the hole that mattered. Adds book:read / economics:read / team:read, a readGuard middleware, and a `viewer` role below member. THE BUTTON AND THE 403 DISAGREED — the exact thing F3 said must never happen. Contracts.tsx never called can() at all, so its save button was always enabled against a server requiring contract:sign; Capacity.tsx gated commitment creation on deal:write/demand while the server wanted commitment:write/supply. POST /api/activities was the one write bypassing executeMutation: no capability check, and any member could mutate accounts.lastActivityAt as a side effect. It is now a proper mutation() behind activity:write. The shell becomes three panes — a collapsible shadcn sidebar with an account switcher on the Piggy accent, a header with real search, and Piggy docked to the right, page-aware and persistent across navigation. The phone keeps its bottom tab bar, which is the thing this product already beat trycompai/crm on, and gains the sidebar as a sheet. Calendar is a projection over thirteen dated sources rather than a new table, because a table would duplicate dates that already live on contracts, deals and commitments and would drift — and one ledger answering the question is the whole argument. It surfaces export_authorizations and compliance_artifacts, which had indexed expires_at columns, schema comments saying they must be alerted on, and no read endpoint or UI anywhere. Learn carries two tracks. Concepts are members-only; the platform track can be opened with a share code by someone with no account. The code mints a scoped learn-only token and never a Principal — every route here resolves a principal and then checks capabilities, so a principal-minting code would be one missing check away from leaking the book. "Only platform-track rows may be code-visible" is a database CHECK constraint as well as a write-path rule, and a test asserts a valid learn token still gets 401 on /api/dashboard, /api/accounts and /api/contracts — the same invariant scripts/deploy.sh refuses to ship without. CD becomes tag-to-ship. CI publishes an image to the Gitea registry on a release-* tag and cloud-2 pulls it, so no credential on the shared runner can execute anything on production — by construction rather than by policy. Both halves of deploy.sh's original rule survive: nothing on the runner reaches the host, and a human still decides when it ships. deploy.sh gains a rollback and a public-origin check, and PIG_IMAGE now reaches compose through `sudo env`, without which sudo's env_reset silently resolved every release to pig:local. Tests 141 -> 261. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
116 lines
5.1 KiB
Markdown
116 lines
5.1 KiB
Markdown
# The ontology
|
||
|
||
Why PIG is shaped the way it is. Read `packages/core/src/ontology.ts` alongside
|
||
this — the code carries the same reasoning in comments, and it is the version
|
||
that cannot go stale.
|
||
|
||
## The one table that matters
|
||
|
||
```
|
||
capacity_commitment ──┐
|
||
(what we bought, │
|
||
at a known cost) │
|
||
├──▶ allocation ──▶ margin, utilisation, idle
|
||
│ (what we sold,
|
||
demand_deal ──┘ at a known price)
|
||
(what we sold)
|
||
```
|
||
|
||
Margin, utilisation and idle capacity all fall out of that single join. No
|
||
generic CRM can compute any of them, because none has a concept of a
|
||
cost-bearing commitment sitting behind the pipeline.
|
||
|
||
**Cost is charged against the full commitment, not only the hours that sold.**
|
||
Unsold hours are already paid for. Charging only the allocated share would
|
||
report a healthy margin on a block that is losing money — precisely the failure
|
||
this system exists to prevent.
|
||
|
||
## Three teams
|
||
|
||
**Supply**, **demand**, and **research**. Research is first-class rather than an
|
||
afterthought: internal research burn is real capacity consumption competing with
|
||
revenue for the same GPUs, and margin math that cannot see it is wrong.
|
||
|
||
## Pipelines
|
||
|
||
**Demand** — `qualification → legal → scoping → proposal → procurement → POC →
|
||
deployment → expansion`. Note that **legal sits second**. Customers do not hand
|
||
workloads to an infrastructure provider before paper is executed. Most CRMs put
|
||
contracting at the end of the funnel and are simply wrong about it here.
|
||
|
||
**Supply** — `sourced → qualifying → technical diligence → financial diligence →
|
||
pricing → contracting → onboarding → live → renewal`. Qualification is split in
|
||
two because accepting capacity is a two-key decision: engineering judges whether
|
||
the cluster can do the work, finance judges whether the economics clear. Both
|
||
verdicts are recorded attributably.
|
||
|
||
## Capacity is a shape, not a rectangle
|
||
|
||
A commitment carries `shape: {intervals[], quantities[]}` — how many GPUs are
|
||
held during each interval. Real contracts ramp across tranches and step down at
|
||
checkpoints. A single start/end/total flattens that and then reports
|
||
availability that does not exist in the month someone wants it.
|
||
|
||
Availability at any instant is therefore:
|
||
|
||
```
|
||
available(t) = shapeQuantityAt(t) − Σ overlapping allocations(t)
|
||
```
|
||
|
||
## Holds reserve; they do not sell
|
||
|
||
A live hold removes capacity from everyone else's availability — otherwise two
|
||
sellers promise the same GPUs — but does not count toward utilisation or
|
||
revenue, because it has not sold. Conflating the two is how a pipeline of
|
||
optimistic holds comes to look like a full book. Holds expire on a timer so a
|
||
stalled deal releases inventory automatically.
|
||
|
||
## Security tiers are ranked, not labelled
|
||
|
||
`community_cloud` < `secure_cloud` < `government`. A requirement is satisfied
|
||
only by capacity at or above the tier it asked for, which is why
|
||
`SECURITY_TIER_RANK` exists and why the matcher compares ranks rather than
|
||
equality. A government (sovereign) requirement served from community capacity
|
||
is not a near miss; it is the wrong answer, and an equality check would have
|
||
made it invisible rather than merely wrong.
|
||
|
||
## Service levels come in three shapes
|
||
|
||
A compute aggregator generally **cannot** offer a conventional uptime guarantee
|
||
on capacity it resells and does not control, and says so publicly. So `slaKind`
|
||
distinguishes:
|
||
|
||
- `none` — self-serve, no commitment at all
|
||
- `credits_policy` — a reliability tier plus service credits. **Not** an uptime
|
||
guarantee, and must never be displayed as one
|
||
- `negotiated` — a real signed SLA with committed, measurable metrics
|
||
|
||
Remedies matter as much as targets. `remedyType` includes `fee_abatement`,
|
||
where payment obligations are *cancelled* for affected capacity until service is
|
||
restored — uncapped in duration and materially better than a capped credit. It
|
||
cannot be expressed as a credit percentage, so it gets its own representation.
|
||
|
||
## Export control is a predicate, not a flag
|
||
|
||
US controls on advanced computing apply an **ultimate parent** test that reaches
|
||
through the corporate tree: an entity can be restricted because of where its
|
||
parent is headquartered, even when the entity itself sits somewhere
|
||
unrestricted. Country of incorporation is therefore not a valid key.
|
||
|
||
Compliance is evaluated **on the allocation edge** — this buyer, this beneficial
|
||
owner, this physical jurisdiction — recorded with its reasoning and rule
|
||
version, and re-evaluated on resale or migration. See
|
||
`packages/db/src/schema/compliance.ts`. PIG records and surfaces; it does not
|
||
make the legal determination for you.
|
||
|
||
## Evidence
|
||
|
||
Agent-derived claims land in `facts` with a confidence score, a band, evidence
|
||
and a source URL. Only `verified` claims self-apply; anything weaker waits for a
|
||
human. An agent permitted to write unattributed claims will eventually write a
|
||
wrong one, and nobody will be able to tell which.
|
||
|
||
The same principle governs seed data about real people: every record carries a
|
||
grade and a citation, authorship is never promoted to employment, and no email
|
||
address is ever inferred.
|