2763531ce4
CI / verify (push) Successful in 2m53s
shadcn uses `bg-accent` for its SUBTLE surfaces — dropdown item hover, command row selection, ghost and outline button hover, the dialog close affordance. The brand colour in shadcn is `primary`. PIG's Tailwind config mapped `accent` to `--accent`, which is the brand. That inverted the meaning, so every shadcn hover and selection state painted a full-strength brand block. With the monochrome "pig" palette in dark mode the brand is near-white, so a selected command row rendered as a white slab against a near-black sheet. Measured before the change: selected row rgb(250,250,250) on a rgb(9,9,11) body. `accent` now aliases `--accent-subtle` and `accent-foreground` aliases `--accent-fg`, which is what those tokens were created for. The eleven places where PIG's own components wanted a solid brand fill — filled chips, selected card borders, progress bars — move to `primary`, which still resolves to `--accent`. A `brand` alias is added for clarity. After: selected row rgb(39,39,42) in dark and rgb(244,244,245) in light, both a subtle tint above the body; the pipeline's active stage chip stays a solid rgb(250,250,250) fill, unchanged. Found by opening overlays, which earlier screenshot sweeps never did — every route had been checked, but a dropdown or a command palette only misbehaves once it is open. Worth remembering: page-level sweeps do not exercise portals. Typecheck clean, 135 unit tests and e2e green, CSP hash unchanged, 0px horizontal overflow across 12 routes at 393px and 1440px. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
269 lines
11 KiB
Markdown
269 lines
11 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.
|
||
|
||
**There are two auth providers, behind one interface.** Supabase for the
|
||
hosted deployment, OIDC for on-premises — see `apps/api/src/lib/auth-provider.ts`.
|
||
Both reduce to "verify a bearer token, return a subject and an email", because
|
||
that is all PIG needs. Never reach for a provider SDK outside that file.
|
||
|
||
**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`.
|
||
|
||
**shadcn's `accent` is a SUBTLE surface, not the brand.** shadcn uses
|
||
`bg-accent` for hover, focus and selected states — dropdown items, command
|
||
rows, ghost buttons. The brand is `primary`. In `tailwind.config.js`, `accent`
|
||
is therefore aliased to `--accent-subtle` and `primary` to `--accent`. Use
|
||
`bg-primary` for a solid brand fill; never `bg-accent`. Mapping them the other
|
||
way makes every hover state paint a full-strength brand block, which in dark
|
||
mode with the monochrome palette is a glaring white slab.
|
||
|
||
**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.
|
||
|
||
**The SPA fallback must never answer an `/api/` path.** Without an explicit
|
||
guard, an unknown API route returns `200 text/html` — the app shell — and the
|
||
caller sees `response.ok === true` before failing on `JSON.parse` with
|
||
"Unexpected token '<'", a long way from the cause. Both the static-file
|
||
middleware and the SPA fallback in `apps/api/src/server.ts` carry the guard;
|
||
anything added after them needs it too.
|
||
|
||
**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.
|