Files
karti 13dec6b4b8
CI / verify (push) Successful in 3m45s
CI / publish (push) Has been skipped
Rebuild the shell, add Calendar and Learn, and govern reads
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>
2026-08-13 15:02:48 -07:00

116 lines
5.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.