Commit Graph

7 Commits

Author SHA1 Message Date
karti 13dec6b4b8 Rebuild the shell, add Calendar and Learn, and govern reads
CI / verify (push) Successful in 3m45s
CI / publish (push) Has been skipped
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
karti a6167629cc Move from npm to pnpm across the workspace, CI and the image
CI / verify (push) Successful in 3m23s
The monorepo was on npm workspaces. pnpm gives it a content-addressed store
shared between the eight packages, a lockfile that records the whole graph
rather than a flattened view of it, and — the reason this mattered in practice —
`workspace:*`, which makes an internal dependency unambiguous instead of a
version range that npm may satisfy from the registry.

Mechanics:

  - `packageManager: pnpm@11.21.0` pins the version; corepack installs it in CI
    and in the image, so all three environments resolve identically.
  - The npm `workspaces` array is replaced by `pnpm-workspace.yaml`. pnpm
    ignores the former, and keeping both would leave two sources of truth.
  - All six internal dependencies moved to `workspace:*`.
  - Root scripts use `pnpm -r --if-present` and `pnpm -F <pkg>`.

Two findings worth recording, both from running it rather than reading it:

`tsx` was a devDependency, but the server runs TypeScript directly in
production — the container's command is `pnpm exec tsx apps/api/src/server.ts`.
Under npm this was concealed by the runtime stage re-installing tsx by hand
after pruning dev dependencies. Under `pnpm install --prod` that sleight of
hand stops working and the image simply fails to start. tsx is now declared in
`dependencies`, which is what it has always actually been.

The first image build failed with ERR_PNPM_ABORTED_REMOVE_MODULES_DIR_NO_TTY.
That is not a pnpm bug: it had decided the modules directory was stale and
wanted confirmation before deleting it, which a non-interactive build cannot
give. The trigger was the host's `node_modules` reaching the build context —
there was no `.dockerignore` at all. pnpm's tree is symlinks into a
content-addressed store, so copying it into an image produces dangling links
and a directory pnpm rightly considers corrupt. Fixed by adding
`.dockerignore` and setting `CI=true`, which is required in any non-interactive
pnpm build.

`esbuild` is denied install scripts via `allowBuilds`. Its platform binary
arrives through the optional dependency `@esbuild/linux-x64` and the postinstall
only verifies it; confirmed by running the binary directly, which reports
0.25.12.

Verified under pnpm: typecheck clean, 150 tests / 0 failures, e2e passes, web
builds. The image was built and booted against a real Postgres — health ok,
`/api/dashboard` 401 with an issuer configured, `/` and `/capacity` serve the
SPA, `/og.png` serves as image/png, and the migrator runs from the pruned
runtime stage.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-13 04:15:54 -07:00
karti 853bde2265 Build the agent-native compute CRM platform
CI / verify (push) Successful in 3m6s
2026-08-13 01:39:01 -07:00
karti cf3117e458 Record verified Prime Intellect API facts, and log a real pricing bug
CI / verify (push) Successful in 1m33s
The token was found on cloud-1 after all, in a Claude memory note. Verifying its
claims against the live API turned up a defect in code already shipped.

**prices.onDemand is the total for the whole node, not per-GPU.** Confirmed:
datacrunch lists 1x A100 at 1.79 and 2x A100 at 3.58, and gpuMemory scales the
same way (640 for 8x 80GB). packages/prime/src/map.ts stores both as if they
were per-GPU, so an 8-GPU node reads eight times too expensive. It would have
silently poisoned inventory search, the max-price filter and every margin
comparison against bought capacity — and nobody would have noticed, because the
numbers still look plausible. Logged rather than fixed, per the instruction to
hold; it needs a regression test built from the real 1x/2x pair.

**Inference is a different host.** api.primeintellect.ai is compute and pods;
inference is api.pinference.ai/api/v1, OpenAI-compatible. PIG's config knows
only the first, so A4 and A13 need both.

**Piggy's default model** is nvidia/nemotron-3-nano-30b-a3b, and the important
detail is that it is a hybrid reasoning model which thinks aloud by default and
truncates under a tight max_tokens. `reasoning_effort: "none"` gives ~1s terse
output for tool use and extraction, which is what Piggy does nearly all of the
time.

One claim did NOT reproduce: the note warns of Cloudflare 403ing non-browser
user-agents, but PIG's own UA and curl's both returned 200. Recorded as history
in case a 403 ever appears.

Also logged: the key is a broad, never-expiring credential sitting in plaintext
in a memory markdown file. PIG's sync should hold a separate narrower key
scoped to availability reads.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 21:52:33 -07:00
karti 3398345109 Plan: add RBAC, capacity tiers, imports, admin settings; expand contracts
CI / verify (push) Successful in 2m36s
Six additions, and one promoted to foundation.

RBAC becomes a Wave 0 task rather than something absorbed into each CRUD
track. Authorization today stops at "is a member", and eight parallel write
tracks plus a bulk-import feature are about to land. Import especially: one bad
column mapping can rewrite thousands of records, so it must not ship before
there is a real answer to who may run it. Suggested default is team leads and
platform admins only — easy to loosen later, unpleasant to tighten.

A government/sovereign capacity tier joins secure and community. It is a schema
change, so it is cheaper before the tables carry real data. Matching must never
satisfy a government requirement with community capacity, and the tier
interacts with the export-control predicate already modelled in compliance.ts.

Imports are split so the work is reusable: a CSV/Excel framework carrying the
upload, mapping, dry-run preview and idempotent commit, with Notion and Google
Sheets as thin OAuth front-ends onto the same mapping step. Notion databases
are tables with typed properties, so treating them as a separate importer would
duplicate the hard part.

Admin settings gains Piggy's model selection, defaulting to a Nemotron model on
Prime Intellect inference, with the endpoint configurable so an on-prem install
can point at the customer's own.

Contracts is expanded from "surface the schema" to the full field set — the
hierarchy with order-form-beats-MSA precedence, negotiated SLA terms including
fee abatement and its trigger, spare-pool scope, maintenance classes and the
reasonable-endeavours carve-out, plus take-or-pay and termination tier. Written
as a first draft to be corrected by someone who negotiates these for a living.

Also records three open questions, including that the Prime Intellect API token
could not be found on cloud-1 — searched ~/.prime, ~/.config/prime, /opt and
/etc.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 21:33:38 -07:00
karti e4698e4d0c Add a build plan, informed by reading Comp AI CRM properly
CI / verify (push) Successful in 1m32s
Cloned trycompai/crm (MIT) and inventoried it rather than assuming. Three
findings shaped the plan.

Their component library is far deeper: 68 primitives to our 9, including
data-table, command, sheet, drawer, combobox, chart, and a set of agent-chat
components that map almost exactly onto what Piggy needs.

Their SourcedValue/Provenance pattern is worth adopting outright — a dotted
underline on any agent-derived value with a tooltip carrying the claim, the
reasons, when it was observed and the source URL. PIG already stores all of
that in `facts` and surfaces none of it.

But we are ahead of them on mobile, not behind. Measured across both repos:
211 responsive utilities across their 329 tsx files (0.6 per file) against 58
across our 16 (3.6 per file); zero safe-area handling to our five; no drawer or
sheet used for navigation, no viewport-fit. Their app is effectively
desktop-only. So the plan takes depth from them, not mobile behaviour.

The plan also says not to copy their component files. Most are shadcn/ui
originals — MIT, and designed to be installed from upstream where they are
canonical and current. Borrow the compositions as ideas; the debt is already
credited in NOTICE.

Structured into waves with real dependency edges so the work can be handed to
several agents without collision. Two foundation tasks must land alone first
(the primitive set, and the API write-path convention) because eight parallel
CRUD tracks would otherwise each invent their own.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 21:09:34 -07:00
karti c747eb2aa7 Add deployment: Dockerfile, compose, proxy config, and docs
One container plus a Postgres behind any TLS-terminating proxy. Nothing is
specific to a particular host.

The app and API are served from a SINGLE origin. This is not tidiness: browser
auth sessions live in per-origin storage, so splitting them across two
hostnames makes sign-in loop in a way that presents as a server fault. The
short alias redirects rather than serving a second origin.

Two safety properties verified by running the image, not by reading the code:

- With NODE_ENV=production and no SUPABASE_URL, the process refuses to start
  and says why. Serving the whole CRM unauthenticated is a worse outcome than
  failing to deploy, so the failure is deliberate and loud.
- In production the development auth bypass does not apply: an unauthenticated
  request to /api/dashboard returns 401 rather than adopting the first user in
  the table.

The Dockerfile typechecks all six packages as a build gate, so a deploy that
does not compile fails at build time rather than in front of a user. Runtime
runs unprivileged as `node`, and Postgres is not published to the host.

Docs cover the ontology and why it is shaped this way, agent connection for
Claude Code / Codex / prime-agent / Buzz, and the provenance rules governing
seed data about real people — including how to have your record removed.

Verified: image builds, container reports healthy, serves the SPA, enforces
auth, and the production guard exits non-zero.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 19:19:53 -07:00