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:
@@ -48,6 +48,19 @@ PIG_PORT=8920
|
||||
PIG_PUBLIC_URL=http://localhost:8920
|
||||
NODE_ENV=development
|
||||
|
||||
# --- Deployment: which image to run -----------------------------------------
|
||||
# Leave EMPTY to build from the working tree, which is what a development or
|
||||
# self-hosted-from-source install wants. Set it to a published tag and
|
||||
# scripts/deploy.sh pulls instead of building, and compose runs exactly that
|
||||
# image for both the app and Piggy — never one version of each.
|
||||
#
|
||||
# Set automatically by scripts/autodeploy.sh; you only put it here to pin a
|
||||
# specific release by hand.
|
||||
# PIG_IMAGE=git.karti.ai/pig/pig:release-2026-08-13
|
||||
PIG_IMAGE=
|
||||
# The loopback port the app is published on. TLS belongs to the proxy in front.
|
||||
PIG_HOST_PORT=8920
|
||||
|
||||
# Comma-separated emails granted platform-admin rights.
|
||||
# Every address listed here MUST already have an account. An address listed but
|
||||
# unregistered is a standing offer of admin to whoever claims it first.
|
||||
@@ -84,6 +97,37 @@ PIGGY_CHAT_PORT=8931
|
||||
# published Piggy port.
|
||||
PIGGY_CHAT_ALLOW_NON_LOOPBACK=false
|
||||
|
||||
# --- Deployment: the release poller -----------------------------------------
|
||||
# Only relevant on a host running scripts/autodeploy.sh. These belong in
|
||||
# /etc/pig/autodeploy.env (read by the systemd unit), not here — they are
|
||||
# listed here so the whole deployment surface is in one file to read.
|
||||
#
|
||||
# The registry credential is NOT an environment variable. It is a file, mode
|
||||
# 0600, holding a pull-only token and nothing else:
|
||||
#
|
||||
# /etc/pig/registry-token
|
||||
#
|
||||
# Mint it in Gitea as a token with `read:package` scope ONLY. A token that can
|
||||
# write packages, or push to the repository, defeats the point: the reason CI
|
||||
# cannot deploy to production is that no build-side credential should be able
|
||||
# to change what production runs, and a write-capable token here reintroduces
|
||||
# exactly that from the other end.
|
||||
#
|
||||
# PIG_REGISTRY_USER=pig-deploy # the Gitea user that owns the token
|
||||
# PIG_REGISTRY=git.karti.ai
|
||||
# PIG_IMAGE_REPO=pig/pig # Gitea lowercases the owner
|
||||
# PIG_REGISTRY_TOKEN_FILE=/etc/pig/registry-token
|
||||
# PIG_REPO_DIR=/opt/pig
|
||||
# PIG_RELEASE_TAG_PREFIX=release-
|
||||
#
|
||||
# The public origin deploy.sh checks AFTER the container is healthy, to catch a
|
||||
# proxy that is answering 200 with an empty body. Defaults to PIG_PUBLIC_URL
|
||||
# above, then to the production origin.
|
||||
# PIG_DEPLOY_PUBLIC_URL=https://primeintellectgrowth.com
|
||||
# A string the real application always renders. Change it only if index.html's
|
||||
# mount point changes.
|
||||
# PIG_DEPLOY_PUBLIC_MARKER=<div id="root">
|
||||
|
||||
# --- Slack ------------------------------------------------------------------
|
||||
SLACK_BOT_TOKEN=
|
||||
SLACK_SIGNING_SECRET=
|
||||
|
||||
Reference in New Issue
Block a user