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>
This commit is contained in:
@@ -30,23 +30,28 @@ 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/core Ontology (stages, tiers, enums) + permissions + margin + palette
|
||||
packages/db Drizzle schema (47 tables), migrations, seeds
|
||||
packages/prime Typed client for the Prime Intellect compute API
|
||||
apps/api Hono HTTP API, auth, capacity service
|
||||
apps/api Hono HTTP API, auth, capacity/contract/calendar services
|
||||
apps/web React + Vite + Tailwind + shadcn-idiom components
|
||||
apps/piggy The agent — lease-based queue worker + private chat server
|
||||
apps/mcp MCP server (stdio) — 9 tools
|
||||
docs/ ontology.md, build-plan.md, agents.md, deploy.md, seed-data.md
|
||||
apps/cli `pig`, the HTTP surface for scripts and agent kernels
|
||||
docs/ ontology.md, build-plan.md, agents.md, seed-data.md
|
||||
deploy/ README.md (deployment), Caddyfile example, autodeploy units
|
||||
```
|
||||
|
||||
~11,000 lines. 39 tests. Node 22+.
|
||||
~45,000 lines including tests. 261 tests across five packages
|
||||
(core 62, prime 24, api 157, piggy 13, cli 5), plus a critical-path E2E suite
|
||||
under `apps/api/e2e`. 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 |
|
||||
| Deploy | Push a `release-*` tag; CI publishes the image and the host's poller pulls it. A push to `main` deploys nothing. `bash scripts/deploy.sh` on the host is the manual path |
|
||||
|
||||
---
|
||||
|
||||
@@ -204,8 +209,10 @@ 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.
|
||||
total. `packages/prime/src/map.ts` now divides both by `gpuCount` at the
|
||||
boundary and keeps the node totals in `raw` for reconciliation — this was a
|
||||
real bug that made an 8-GPU node read eight times too expensive. Anything new
|
||||
that reads an upstream price must normalise the same way.
|
||||
|
||||
**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
|
||||
@@ -221,6 +228,15 @@ pods. Inference is `api.pinference.ai/api/v1`, OpenAI-compatible.
|
||||
hybrid reasoning model; under a tight `max_tokens` it rambles and truncates.
|
||||
Pass `reasoning_effort: "none"` for tool use, routing and extraction.
|
||||
|
||||
**A route file with green tests can still be unmounted.** Every route module is
|
||||
a factory returning a `Hono` app, and `createApp` has to call it. The tests
|
||||
mount the factory themselves, so they pass whether or not `app.ts` ever does.
|
||||
Four modules are in exactly that state right now — `read-guards.ts`,
|
||||
`learn.ts`, `hubspot.ts`, `hubspot-webhook.ts` — which is why read
|
||||
authorisation is unenforced and `/learn` answers 404 from a page that is in the
|
||||
navigation. After adding a route file, curl the path against a running server;
|
||||
the test suite cannot tell you.
|
||||
|
||||
**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`.
|
||||
@@ -258,28 +274,30 @@ 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.
|
||||
**Every task in the original three-wave plan has shipped.**
|
||||
[`docs/build-plan.md`](./docs/build-plan.md) is now an audited record of that
|
||||
rather than a queue, and it carries the remaining work at the bottom. The two
|
||||
interfaces everything else codes against — `packages/core/src/permissions.ts`
|
||||
(the RBAC model) and `apps/api/src/lib/mutation.ts` (the write path) — are
|
||||
settled; read them before adding any write.
|
||||
|
||||
**Do these first, alone, before anything fans out:**
|
||||
**The highest-value work now, in order:**
|
||||
|
||||
- **F1** — install the shadcn primitive set
|
||||
- **F3** — the RBAC permission model
|
||||
- **F2** — the shared API write-path convention (needs F3 to call into)
|
||||
1. **Mount `createReadGuardRoutes`.** The read half of the permission model is
|
||||
written, tabulated and tested, and does nothing, because `app.ts` never
|
||||
mounts it. Until it does, every authenticated member can read supplier cost
|
||||
and margin. It is one line, and it must be registered *before* the handlers
|
||||
it guards — Hono runs matched handlers in registration order.
|
||||
2. **Mount `learn.ts`.** `/learn` is in the navigation and its API answers 404.
|
||||
3. **Enqueue the other six agent task kinds.** The worker is complete; only
|
||||
`enrich_account` and `enrich_contact` are ever written to `agent_tasks`, so
|
||||
Piggy does far less than the ontology implies.
|
||||
4. **Mount the HubSpot routes, or delete them.** Seven tables, OAuth, sync jobs
|
||||
and webhook verification, all written, tested and unreachable.
|
||||
|
||||
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.
|
||||
`app.ts` is the one shared file. If your change needs a route mounted, a public
|
||||
path allowlisted or a schema widened there, say so rather than racing another
|
||||
agent for it.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user