3ed5a731da953efe4fbb5dc8fbaf9d79c65b214a
6 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
10d79fc35d |
Shoot the README screenshots, and make them regenerable
The screenshot section had been a placeholder since the shell was rebuilt as three panes, because there was no cheap way to re-shoot and a stale image is worse than no image. So this ships the capture, not just the captures: scripts/screenshots.mjs takes all ten pages at 1440x900 and 393x852, in light and dark, and screenshots-encode.py halves and re-encodes them to WebP — 16MB of PNG becomes 1.9MB in the tree. Two things would silently ruin a run, and the script exists to encode both. Seeding localStorage['pig.themeMode'] is not enough: the appearance preference is authoritative server-side and adopted after hydration, so every dark capture snapped back to light a beat after first paint. The /api/me/profile response is rewritten instead. And pig.sidebarOpen / pig.piggyDockOpen are per-device, so whatever the last human left behind would otherwise leak in. The run also fails on a wrong theme, a horizontal scrollbar or a console error — three things a screenshot cannot show you. Piggy is not pictured mid-conversation. It is off by default and no inference credential exists, so such an image would be a staged transcript rather than a capture. The README says that rather than implying the feature is missing. Correcting what the README asserted while shooting against the running code: read authorisation IS enforced — createReadGuardRoutes is mounted ahead of the feature routes, and routes/learn.ts and routes/activities.ts are mounted too, so only the HubSpot pair is still unreachable. The remaining read gap is that a grant cannot be narrowed, there being no row-level team filter in the query layer. Counts refreshed against the tree: 275 tests, ~47k lines, 14 migrations. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
a21ecf9e53 |
Stop deploy.sh from rewriting itself while bash is reading it
`git reset --hard origin/main` replaces this script mid-execution. bash does
not slurp a script — it reads incrementally and remembers a byte OFFSET, so
after the reset it resumes at that offset into different content.
This is not theoretical. The
|
||
|
|
13dec6b4b8 |
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> |
||
|
|
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> |
||
|
|
d4d7095605 |
Migrate before starting the application
CI / verify (push) Successful in 2m32s
|
||
|
|
73231a8944 |
Add CI, a test suite, and a deploy script
CI / verify (push) Failing after 34s
`npm test` did nothing until now. CI that runs no tests is theatre, so the tests came first — 39 of them, over the two places where an error would be silent and expensive. packages/core: the margin arithmetic. Every dashboard figure, idle-capacity alert and agent answer resolves through it, and wrong numbers still look like numbers. The cases pin decisions rather than implementation: cost is charged against the full commitment (a naive version reports the opposite sign on a loss-making block), aggregation sums cents rather than averaging percentages (averaging reports +22% on a book that is losing money), break-even prices the remaining hours and returns null rather than Infinity when there are none, and internal research burn counts as cost with no revenue. packages/prime: the upstream mapping. Rounding rather than truncating cents, because 2.43 is 2.4299999 in binary and a lost cent compounds across millions of GPU-hours. And interconnect normalisation, where an unrecognised fabric maps to Unknown rather than Ethernet — guessing low loses a deal, guessing high sells a training customer a cluster that cannot train. CI runs on push and pull request: typecheck all six packages, unit tests, migrations applied twice to a real Postgres, a seed-idempotency assertion that fails the build if row counts move on a second run, a server boot, the front-end build, and a Docker build. It also asserts the inline theme script's hash still matches the CSP the proxy allows. That script prevents a white flash for dark-mode users; if it changes without the CSP being updated, the browser silently blocks it and nothing anywhere reports an error. Deployment stays a script rather than push-to-deploy. Automating it would put an SSH key with production write access on the CI runner — a real escalation for a project this size. The script takes a database dump before migrating and refuses to finish if an unauthenticated request returns anything but 401. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |