Files
pig/.env.example
T
karti 13dec6b4b8
CI / verify (push) Successful in 3m45s
CI / publish (push) Has been skipped
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>
2026-08-13 15:02:48 -07:00

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=