6bd5526675
The Overview page overflowed 80px at 393px wide. Traced to the "The book" card: the grid column was a correct 361px, the card inside it was 457px and refused to shrink. Confirmed by forcing `min-width: 0` on grid children in the live page, which took the overflow to 0. Fixed on the Card base class rather than at the call site, because this is the third time the same trap has been fixed individually — grid and flex children default to `min-width: auto` and cards routinely hold something unshrinkable, a tabular-nums figure or a nowrap badge. `min-width: 0` is inert for a block-level card outside a flex or grid parent, so applying it always costs nothing and removes the whole class of bug. Verified by running the stack locally against the demo data: 0px overflow across all 12 routes at both 393px and 1440px. AGENTS.md updated to say any NEW container primitive needs the same, with the one-line browser check to confirm it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
249 lines
10 KiB
Markdown
249 lines
10 KiB
Markdown
# Working on PIG
|
||
|
||
Onboarding for an agent or engineer joining this codebase cold. Read this
|
||
first, then [`docs/build-plan.md`](./docs/build-plan.md) for what to build.
|
||
|
||
Named `AGENTS.md` because that is the file coding agents look for by
|
||
convention. Everything here applies equally to humans.
|
||
|
||
---
|
||
|
||
## 1. What PIG is, in three sentences
|
||
|
||
A company that aggregates GPU capacity and resells it runs **two pipelines**,
|
||
and its business is the spread between them. Generic CRMs model one pipeline
|
||
against companies — they have no concept of inventory, no concept of a
|
||
commitment you already paid for, and so cannot answer the only question that
|
||
matters: *which contracted capacity is sold, to whom, at what margin, and what
|
||
is idle right now.*
|
||
|
||
PIG is built around that question, it is Apache 2.0, and it is designed to be
|
||
self-hosted by the customer.
|
||
|
||
**The load-bearing table is `allocations`**, which joins 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.
|
||
Everything else is plumbing that exists to keep that ledger honest.
|
||
|
||
---
|
||
|
||
## 2. Orientation
|
||
|
||
```
|
||
packages/core Ontology (stages, tiers, enums) + margin arithmetic + palette
|
||
packages/db Drizzle schema, migrations, seeds
|
||
packages/prime Typed client for the Prime Intellect compute API
|
||
apps/api Hono HTTP API, auth, capacity service
|
||
apps/web React + Vite + Tailwind + shadcn-idiom components
|
||
apps/mcp MCP server (stdio) — 9 tools
|
||
docs/ ontology.md, build-plan.md, agents.md, deploy.md, seed-data.md
|
||
```
|
||
|
||
~11,000 lines. 39 tests. Node 22+.
|
||
|
||
| | |
|
||
|---|---|
|
||
| Repo | `PIG/pig` on git.karti.ai (Gitea) |
|
||
| Live | https://primeintellectgrowth.com |
|
||
| CI | Gitea Actions, `.gitea/workflows/ci.yml`, ~2 min, must stay green |
|
||
| Deploy | `bash scripts/deploy.sh` on the host — deliberately manual |
|
||
|
||
---
|
||
|
||
## 3. Running it
|
||
|
||
```bash
|
||
npm install
|
||
|
||
# Postgres. PIG needs its own database — never point it at a shared one.
|
||
docker run -d --name pig-dev -p 5432:5432 \
|
||
-e POSTGRES_USER=pig -e POSTGRES_PASSWORD=pig -e POSTGRES_DB=pig \
|
||
postgres:16-alpine
|
||
|
||
export DATABASE_URL=postgres://pig:pig@localhost:5432/pig
|
||
npm run db:migrate
|
||
npm run db:seed # sourced, cited people — optional
|
||
npm run db:demo # a plausible demo book — optional, prefixed "DEMO — "
|
||
|
||
npm run dev:api # :8920
|
||
npm run dev:web # :5173, proxies /api to 8920
|
||
```
|
||
|
||
With no `SUPABASE_URL` set, **authentication is disabled in development** and
|
||
every request runs as the first user in the table. `loadConfig` refuses to start
|
||
in production without it, so this cannot leak.
|
||
|
||
Before pushing:
|
||
|
||
```bash
|
||
npm run typecheck && npm test
|
||
```
|
||
|
||
---
|
||
|
||
## 4. Rules that must not be broken
|
||
|
||
These are load-bearing. Breaking one produces a subtle failure, not an error.
|
||
|
||
**Intelligence never lives in the API.** Handlers validate, authorize, call a
|
||
service, serialise. Research, enrichment, scoring and matching heuristics live
|
||
in the service layer or in the agent. The API signals the agent by *writing a
|
||
row to `agent_tasks`*, never by calling it — so the queue survives the agent
|
||
being down and no request thread ever blocks on a model.
|
||
|
||
**Authentication is not authorization.** A verified JWT proves someone has an
|
||
account in an identity provider that PIG *shares with another application*. It
|
||
does not prove they belong here. Access requires a row in PIG's own `users`
|
||
table. A token without one gets `403 needs_profile`, which the front end turns
|
||
into the join flow — never a login screen they have already completed.
|
||
|
||
**Cost is charged against the full commitment, not the hours that sold.**
|
||
Unsold hours are already paid for. Any other treatment reports a healthy margin
|
||
on a block that is losing money, which is precisely the failure PIG exists to
|
||
prevent. There is a test pinning this; if you "fix" it, read the test first.
|
||
|
||
**Sold and held are different.** A live hold removes capacity from everyone
|
||
else's availability — otherwise two sellers promise the same GPUs — but it is
|
||
not revenue and must never count toward utilisation.
|
||
|
||
**Money is integer cents.** Never floats. Round, never truncate: 2.43 is
|
||
2.4299999 in binary and a lost cent compounds across millions of GPU-hours.
|
||
|
||
**Agent claims carry evidence.** Enrichment writes to `facts` with a score, a
|
||
band, a source URL and a status — never directly to the record. Only `verified`
|
||
self-applies; anything weaker waits for a human. An agent allowed to write
|
||
unattributed claims will eventually write a wrong one and nobody will be able
|
||
to tell which.
|
||
|
||
**Never invent data about real people.** Seed records carry a confidence grade
|
||
and a source URL, both shown in the UI. No email address is ever inferred.
|
||
Authorship is not employment — contributors, residency participants and alumni
|
||
are recorded as what the evidence shows. Demo data is prefixed `DEMO — ` and
|
||
its customers are fictional.
|
||
|
||
---
|
||
|
||
## 5. Traps that have already bitten
|
||
|
||
Each of these cost real time. None produced an error message.
|
||
|
||
**`onConflictDoNothing()` is a no-op without a matching unique constraint.**
|
||
It has silently duplicated seed data twice. If there is no unique index to
|
||
conflict on, do an existence check instead.
|
||
|
||
**`z.coerce.boolean()` turns the string `"false"` into `true`.** Every feature
|
||
flag set to false was silently on. Use the `envBoolean` helper in
|
||
`apps/api/src/lib/config.ts`.
|
||
|
||
**Grid and flex children need `min-w-0`.** They default to
|
||
`min-width: auto`, meaning they refuse to shrink below their content — and a
|
||
`tabular-nums` figure, a `whitespace-nowrap` badge or a `truncate` title is all
|
||
it takes. The page then scrolls sideways on a phone and nothing reports an
|
||
error. This was fixed three separate times at individual call sites before
|
||
`Card` was given `min-w-0` on its base class; **any new container primitive
|
||
needs the same**. Check with:
|
||
|
||
```js
|
||
document.documentElement.scrollWidth - document.documentElement.clientWidth
|
||
```
|
||
|
||
It should be 0 on every route at 393px wide.
|
||
|
||
**Drizzle-generated migrations are not always valid SQL.** A `jsonb → integer`
|
||
cast was emitted without the `USING` clause Postgres requires. Always apply a
|
||
new migration to a real empty database before pushing — CI does this, but find
|
||
out before CI does.
|
||
|
||
**The proxy allows exactly one inline script, by hash.** `index.html` carries a
|
||
pre-paint theme script that prevents a white flash for dark-mode users. Editing
|
||
it changes its hash and the browser silently blocks it. CI asserts the hash;
|
||
if it fails, update the CSP in `deploy/Caddyfile.example`, on the server, and
|
||
the expected value in the workflow.
|
||
|
||
**`prices.onDemand` from the Prime Intellect API is the TOTAL FOR THE NODE.**
|
||
Verified: 1× A100 at 1.79, 2× A100 at 3.58. `gpuMemory` is likewise a node
|
||
total. There is an open bug for this — the mapper currently stores both as if
|
||
per-GPU, so an 8-GPU node reads eight times too expensive.
|
||
|
||
**Prime Intellect has two API hosts.** `api.primeintellect.ai` is compute and
|
||
pods. Inference is `api.pinference.ai/api/v1`, OpenAI-compatible.
|
||
|
||
**Piggy's default model thinks aloud.** `nvidia/nemotron-3-nano-30b-a3b` is a
|
||
hybrid reasoning model; under a tight `max_tokens` it rambles and truncates.
|
||
Pass `reasoning_effort: "none"` for tool use, routing and extraction.
|
||
|
||
**Deployment traps** live in `deploy/README.md` — chiefly that every Caddy site
|
||
block on that host needs `bind 10.0.0.2`, and that the CI runner uses
|
||
`container.network: host` so dependencies must be published on `127.0.0.1`.
|
||
|
||
---
|
||
|
||
## 6. Conventions
|
||
|
||
**Comments explain *why*, never *what*.** The code says what it does. Comments
|
||
carry the reasoning that would otherwise be lost — why this treatment and not
|
||
the obvious one, what breaks if it changes, what was tried and rejected. Match
|
||
the density already in the file; do not add narration.
|
||
|
||
**Match the surrounding style.** British spelling in prose and comments
|
||
(`utilisation`, `normalise`). Types are explicit at module boundaries. No
|
||
default exports.
|
||
|
||
**Enums come from `@pig/core`.** Never retype a stage list or a tier into a zod
|
||
schema — import it, so removing a value stops validating rather than silently
|
||
persisting.
|
||
|
||
**Tests pin decisions, not implementations.** The valuable cases are the ones
|
||
that would pass under a plausible-but-wrong version. Look at
|
||
`packages/core/test/margin.test.ts` for the register.
|
||
|
||
**Commits explain the reasoning**, including what was tried and rejected, and
|
||
say plainly when something was found by running the code rather than reading
|
||
it. Read `git log` for the register.
|
||
|
||
**Verify by running, not by assuming.** Several of the worst defects here were
|
||
found only by fetching a URL from another machine or applying a migration to a
|
||
real database. "It should work" has been wrong repeatedly.
|
||
|
||
---
|
||
|
||
## 7. Where to start
|
||
|
||
[`docs/build-plan.md`](./docs/build-plan.md) has 24 tasks in three waves with
|
||
real dependency edges.
|
||
|
||
**Do these first, alone, before anything fans out:**
|
||
|
||
- **F1** — install the shadcn primitive set
|
||
- **F3** — the RBAC permission model
|
||
- **F2** — the shared API write-path convention (needs F3 to call into)
|
||
|
||
They are small and they are the interface every other track codes against.
|
||
Starting parallel work before they settle is how it turns into merge conflict.
|
||
|
||
**Then the two that unblock a demo:**
|
||
|
||
- **A1** — allocation and commitment write paths. Today the core table can only
|
||
be populated by seed, so a visitor can look at the demo book but cannot enter
|
||
a deal of their own.
|
||
- **A2** — API keys. Nothing mints one, so the MCP server — the headline
|
||
feature — is unreachable in production.
|
||
|
||
**A4 (Piggy) is fully independent** and can start immediately alongside the
|
||
foundation. It touches no UI and no shared API conventions.
|
||
|
||
---
|
||
|
||
## 8. What not to do
|
||
|
||
- Do not copy component files from `trycompai/crm`. Most are shadcn/ui
|
||
originals — take them from upstream where they are canonical. Borrow the
|
||
compositions as ideas; the debt is credited in `NOTICE`.
|
||
- Do not open self-registration on the identity provider. It is shared with
|
||
another application. PIG mints accounts itself, gated on an invite.
|
||
- Do not put a production SSH key on the CI runner. Deployment is manual on
|
||
purpose.
|
||
- Do not weaken the production guard that refuses to start without an identity
|
||
provider.
|
||
- Do not seed or infer email addresses for real people.
|