13dec6b4b8
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>
150 lines
6.9 KiB
Bash
150 lines
6.9 KiB
Bash
# ---------------------------------------------------------------------------
|
|
# PIG — environment
|
|
# Copy to .env and fill in. Never commit .env.
|
|
# ---------------------------------------------------------------------------
|
|
|
|
# --- Database ---------------------------------------------------------------
|
|
# PIG owns this database exclusively. Do not point it at a database shared with
|
|
# another application.
|
|
DATABASE_URL=postgres://pig:CHANGEME@localhost:5432/pig
|
|
|
|
# --- Auth (Supabase) --------------------------------------------------------
|
|
# PIG uses Supabase for authentication ONLY. It stores no passwords and issues
|
|
# no sessions of its own; it verifies incoming JWTs against the project JWKS.
|
|
#
|
|
# IMPORTANT: authorization does NOT follow from having a Supabase account.
|
|
# A user must also have a row in PIG's `users` table. If this Supabase project
|
|
# is shared with another application, that application's users get nothing here
|
|
# until they are explicitly invited.
|
|
SUPABASE_URL=https://YOUR_PROJECT_REF.supabase.co
|
|
SUPABASE_ANON_KEY=
|
|
# Service key is only needed for administrative user provisioning. Omit it and
|
|
# PIG runs fine in invite-only mode. Treat it as the most powerful secret here.
|
|
SUPABASE_SERVICE_KEY=
|
|
|
|
# --- Auth: on-premises (OIDC) ---------------------------------------------
|
|
# Set PIG_OIDC_ISSUER to authenticate against your own identity provider —
|
|
# Okta, Entra, Keycloak, Auth0, Authentik, Google Workspace, anything
|
|
# standards-compliant. It TAKES PRECEDENCE over the Supabase values above, so
|
|
# an on-prem install can leave those in place.
|
|
#
|
|
# PIG never sees a password. It verifies the token your provider issued and
|
|
# reads two things: a stable subject, and an email. Everything else — teams,
|
|
# roles, capabilities — is PIG's own data keyed on that subject, so users are
|
|
# provisioned in PIG by invite, not by your directory.
|
|
PIG_OIDC_ISSUER=
|
|
# Optional. Discovered from the issuer's /.well-known/openid-configuration when
|
|
# omitted. Set it to skip discovery entirely on an air-gapped network.
|
|
PIG_OIDC_JWKS_URI=
|
|
# STRONGLY recommended. Without it, a token your provider issued for ANY other
|
|
# application in the same tenant is accepted here as a PIG session.
|
|
PIG_OIDC_AUDIENCE=
|
|
# Comma-separated, in preference order. Defaults to email,preferred_username,upn
|
|
# which covers most providers; Entra sometimes needs upn first.
|
|
PIG_OIDC_EMAIL_CLAIMS=
|
|
|
|
# --- Application ------------------------------------------------------------
|
|
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.
|
|
PIG_ADMIN_EMAILS=
|
|
|
|
# Invite code gating self-serve profile creation. Rotate freely.
|
|
PIG_INVITE_CODE=
|
|
|
|
# --- Prime Intellect compute API -------------------------------------------
|
|
# Used to sync GPU availability into `inventory_listings`.
|
|
# Mint a key at https://app.primeintellect.ai/dashboard/tokens with the
|
|
# NARROWEST scope that works: `Availability -> Read`. PIG never provisions
|
|
# infrastructure and must not hold a key that could. Set an expiry.
|
|
PRIME_API_KEY=
|
|
PRIME_API_BASE=https://api.primeintellect.ai
|
|
# Rate limits are undocumented upstream; the sync backs off empirically.
|
|
PRIME_SYNC_ENABLED=false
|
|
PRIME_SYNC_INTERVAL_MINUTES=30
|
|
|
|
# --- Piggy (the in-app agent) ----------------------------------------------
|
|
# Piggy drains a leased queue and serves chat on an authenticated internal
|
|
# listener. Generate one internal token and give the same value to API + Piggy.
|
|
# Never publish the Piggy listener or put this token in a URL.
|
|
PIGGY_INFERENCE_API_KEY=
|
|
PIGGY_ENABLED=false
|
|
PIGGY_MODEL=nvidia/nemotron-3-nano-30b-a3b
|
|
PIGGY_INFERENCE_BASE=https://api.pinference.ai/api/v1
|
|
PIGGY_LEASE_SECONDS=300
|
|
PIGGY_INTERNAL_URL=http://127.0.0.1:8931
|
|
PIGGY_INTERNAL_TOKEN=
|
|
PIGGY_CHAT_HOST=127.0.0.1
|
|
PIGGY_CHAT_PORT=8931
|
|
# Only containers on a private network need this; never combine it with a
|
|
# 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=
|
|
SLACK_APP_TOKEN=
|
|
|
|
# --- Buzz (https://github.com/block/buzz) -----------------------------------
|
|
# Buzz agents reach PIG through the MCP server, so no PIG-specific credential is
|
|
# required. These are only for PIG pushing notifications into a Buzz relay.
|
|
BUZZ_RELAY_URL=
|
|
NOTION_CLIENT_ID=
|
|
NOTION_CLIENT_SECRET=
|
|
NOTION_REDIRECT_URI=http://localhost:8920/api/imports/notion/oauth/callback
|
|
GOOGLE_CLIENT_ID=
|
|
GOOGLE_CLIENT_SECRET=
|
|
# Must use the PIG_PUBLIC_URL origin and exact /oauth/google/callback path.
|
|
GOOGLE_REDIRECT_URI=http://localhost:8920/oauth/google/callback
|
|
BUZZ_PRIVATE_KEY=
|
|
# Optional NIP-OA owner attestation JSON for an agent identity.
|
|
BUZZ_AUTH_TAG=
|