MGR is a multi-tenant brewery operations system built on Next.js (App Router, TypeScript) and Supabase (Postgres, Auth, RLS): catalog, immutable inventory ledger, allocations/ATP, staged CSV import,...
Overview
MGR is a multi-tenant brewery operations system built on Next.js (App Router, TypeScript) and Supabase (Postgres, Auth, RLS): catalog, immutable inventory ledger, allocations/ATP, staged CSV import, staff and buyer invitations (existing accounts accept a pending invitation after signing in), orders, shipments, invoicing, the customer portal, brewing, purchasing, compliance, QuickBooks (manual payment sync), Square POS, Slack notifications, and Ask MGR chat. The staff and portal guides under content/docs/ describe what exists today. - Documentation: content/docs/ — Fumadocs MDX served at /docs (index.mdx chooses between the staff and customer-portal guides and the HTTP API reference at /docs/api, whose operations are generated from the command registry and the screens by bun run docs:api; search at /api/search) - Spec: .agents/superpowers/specs/2026-08-30-mgr-slice1-core-orders-design.md - Historical plan (superseded): .agents/superpowers/plans/2026-08-30-slice1a-foundation.
README
MGR
MGR is a multi-tenant brewery operations system built on Next.js (App
Router, TypeScript) and Supabase (Postgres, Auth, RLS): catalog, immutable
inventory ledger, allocations/ATP, staged CSV import, staff and buyer
invitations (existing accounts accept a pending invitation after signing in),
orders, shipments, invoicing, the customer portal, brewing, purchasing,
compliance, QuickBooks (manual payment sync), Square POS, Slack notifications,
and Ask MGR chat. The staff and portal guides under content/docs/ describe
what exists today.
- Documentation:
content/docs/— Fumadocs MDX served at/docs(index.mdxchooses between the staff and customer-portal guides and the HTTP API reference at/docs/api, whose operations are generated from the command registry and the screens bybun run docs:api; search at/api/search) - Spec:
.agents/superpowers/specs/2026-08-30-mgr-slice1-core-orders-design.md - Historical plan (superseded):
.agents/superpowers/plans/2026-08-30-slice1a-foundation.md - Schema:
.agents/superpowers/specs/2026-08-31-mgr-schema-design.md(tables) and2026-08-31-mgr-schema-decisions.md(why)
Iron rules
See .agents/ARCHITECTURE.md for the ownership map and the five iron rules
(commands-only, append-only ledger, RLS everywhere, admin client confined,
multi-row writes are one Postgres function)
and what enforces each one. Agents start at AGENTS.md.
Local development
Requires Node.js 24.x and Bun 1.3.x. The repository pins Node in .node-version
and package.json, and Bun in .bun-version / packageManager; use Bun for
installs and scripts, and Node 24 as the Next.js runtime.
bunx tsc --noEmit uses TypeScript 7 through the @typescript/native alias.
ESLint, Next.js tooling, and screen-composition tests still need the TypeScript
6 compiler API, supplied by Microsoft’s @typescript/typescript6 compatibility
package under the typescript name. It uses the official npm tarball because
Bun 1.3.10 resolves the nested npm alias back to the compatibility package
when the compatibility package itself is installed through an npm alias.
See Microsoft’s side-by-side guidance.
Node type definitions stay on the latest 24.x release to match the runtime.
Local Supabase runs on non-default ports (54341/54342/54343) configured
in the committed supabase/config.toml. These ports are used by every
developer and in CI — they were chosen to avoid colliding with a
default-port Supabase project running alongside locally. Verify the live
values with bunx supabase status:
API URL: http://127.0.0.1:54341
DB URL: postgresql://postgres:[email protected]:54342/postgres
Studio: http://127.0.0.1:54343
CI (.github/workflows/ci.yml) derives env vars from
bunx supabase status -o env rather than hardcoding them, so the setup
automatically adapts to the values in config.toml.
Setup
bun install
bunx supabase start # starts the local Postgres/Auth/Studio stack
bunx supabase status -o env | node scripts/supabase-env.mjs > .env.local
The mapper converts the Supabase CLI’s local key labels into the application’s
modern environment contract; nothing else is needed in .env.local.
/api/command refuses excess requests with 429 rate_limited and a
Retry-After header.
VERCEL_ENV is optional; when set it must be production, preview, or
development (lib/env/server-parser.ts). Vercel sets it on deploys; locally
it is normally absent.
Apply migrations and seed a dev user/brewery:
bunx supabase db reset # applies supabase/migrations/*.sql
bun --env-file=.env.local scripts/seed-dev.ts # idempotent; creates "Demo Brewing" + [email protected]
Seeded dev login: [email protected] / dev-password-1 (dev-only credential —
never used outside local development). A customer-only account (a
customer_users row with no brewery_users row) lands on /portal instead
of / after login — that’s the wholesale customer portal, not a bug.
Working in a worktree: create it with scripts/worktree.sh [base].
It checks out .agents/worktrees/, symlinks the gitignored
.env.local and .env.test.local from the main checkout, and runs
bun install. A plain git worktree add has neither, so Supabase calls fail
with supabaseKey is required.
Browser setup is explicit in each worktree: after the frozen install, run
bunx agent-browser install, then follow the
browse skill. The native CLI is bundled in the
locked npm tarball; do not trust or manually run its postinstall download hook.
bun run dev # http://localhost:3000
Tests
bun run test # vitest run — real local Supabase, not mocks
bun run lint # eslint, incl. the admin-client import guard
bunx tsc --noEmit # typecheck
bun run build # production build
bun run test:e2e # agent-browser smoke — local only, not run in CI
bun tests-e2e/mobile-command-surface.ts # fixture-only overlay regression; no seed or writes
Each tests/*.test.ts opens with a header comment stating what it gates; ls tests/ is the index. The rls-* and schema-* suites read pg_catalog and are the schema’s merge gate; commands-import and commands-invites cover staged import rows and staff/buyer invitations. The docs path check recognizes tracked repository roots, not generated build directories, so it does not require a dev-server run after a production build.
When upgrading Zod, run bun run docs:api and the API-reference tests: numeric
examples derive bounds from core check definitions and must pass their schemas.
Slack OAuth tests intercept the Chat SDK adapter’s own Web API client, not a
separately installed version, while exercising real credential persistence and
database locking.
Tests run against a real local Supabase stack (not a mock), but their own
throwaway one: bash scripts/test-db.sh starts a second stack (project
mgr_test, config in tests/supabase/, API 54351 / DB 54352, same baseline
via a migrations symlink), resets its database, and writes .env.test.local,
which vitest pins over inherited Bun/app settings. Local tests fail before setup unless this file names localhost ports 54351/54352 and test credentials. Re-run the script after adding a
migration; it resets automatically when the migrations change. The app stack (bunx supabase start,
5434x) is never touched by tests, so the brewery you are clicking in next dev
survives a test run and stale test rows (see chat-jobs) cannot pile up there.
Without .env.test.local, local vitest refuses to run. CI keeps its separately provisioned disposable configuration.
Every database-project run takes the shared /tmp/mgr-test-db.lock, then checks
the newest local migration against SQL history. Local runs also compare the existing migration/config
fingerprint and a per-reset identity through SQL and REST, before and after the suite.
Reset readiness executes the newly installed identity RPC; an old readable table is
not proof of a fresh PostgREST cache. These harness-only functions are installed only
on the disposable local stack, never by an application migration or on hosted.
Their reserved mgr_test_identity_ signatures are removed structurally by
types:database, so a local readiness probe never enters the committed app types.
The diagnostic names expected/actual identities and the pinned DB/API endpoints.
The exhaustive service-RPC RLS matrix admits only the exact verified reset probe,
checks its signature and grants, and still exercises anonymous/authenticated denial
plus a service-role identity positive control. Unexpected RPCs remain failures.
The explicit pure project in vitest.config.mts keeps the existing screen-only
gates and pure harness regressions independent of the running database. Vitest
routes the actual selected files: any mixed selection or unlisted/DB file still
initializes the database project’s lock and identity guard. There is no user-set
flag to skip that protection.
For an uninterrupted reset plus proof, keep the same exclusive lock throughout:
perl scripts/test-db-lock.pl ex bash -c 'MGR_TEST_DB_RESET=1 bash scripts/test-db.sh --locked-reset && node_modules/.bin/vitest run'
Use the direct Vitest executable inside this outer lock. Bunx drops the inherited
descriptor on the tested local runtime; that launch is rejected, not silently
accepted with a different lock. Normal standalone bun run test still takes its
own shared lock.
The child inherits and validates the actual lock descriptor; do not override
MGR_TEST_DB_LOCK to avoid a recursive lock. An override gives up protection from
other worktrees. --locked-reset now refuses an unprotected invocation. Startup,
reset and env publication all hold the exclusive lock; each checkout replaces its
own env symlink instead of rewriting another worktree’s configuration. Direct
supabase db reset / stop/start bypass this cooperative lock and must not run while
another proof owns the stack. Parallel full proofs need separately configured
isolated stacks (for example 5436x), not alternate lock files on the same 5435x DB.
Database types
Supabase clients use lib/supabase/database.generated.ts, generated from the
committed migrations. After applying migrations to the disposable test stack,
run bun run types:database. Pass a local disposable database URL as the first
argument when using a separate stack. Do not generate from a hosted or stale
application database. CI checks the generated file against its fresh stack.
lib/supabase/database.ts records facts the generator cannot infer: nullable
RPC arguments, trigger-populated insert fields, and non-null stock views.
JSON RPC result shapes live in lib/supabase/rpc-results.ts; retain integration
coverage when changing those contracts. Unknown external data still needs
validation before use. Tests may use tests/raw-database.ts solely for arbitrary
fixtures and deliberate invalid/private requests; its results remain unknown.
Deployment
Hosted deployments exist; this does not establish release readiness. See the
2026-09-18 release evidence
for the observed revision and outstanding gates. Before creating or changing
hosted projects, obtain the approvals in the release checklist. Configure NEXT_PUBLIC_SUPABASE_URL,
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY, and SUPABASE_SECRET_KEY in Vercel, plus
POSTGRES_URL_NON_POOLING in Production. Production builds run
scripts/vercel-build.sh (the vercel.json build command), which pushes
migrations with supabase db push before next build; a manual
bunx supabase db push is only for a preview project or recovery. Then verify
login → catalog → inventory on the deployed URL. Follow the full hosted release checklist
for approvals, environment separation, hosted advisors, smoke tests, and rollback evidence.
CI
Enable the tracked pre-push check once per clone:
git config core.hooksPath .githooks
For code changes it runs lint, the typecheck, and the pure-screen vitest
files — about a minute. next build and the database-backed suites are left
to CI, which runs them in parallel jobs against a database built from scratch
and is the merge gate; locally they took ten minutes and reset a database
other worktrees share. Run them by hand with bash scripts/test-db.sh && bunx vitest run. Documentation-only pushes skip the gate.
.github/workflows/ci.yml runs on every push and pull request: installs
deps, starts a local Supabase stack with only the services the tests use
(Postgres, Auth, PostgREST, Kong, and the mailpit SMTP sink Auth sends
invites to — supabase start -x … excludes the rest),
which applies migrations on the fresh stack, runs the full vitest suite
against it, then
bun run lint, tsc --noEmit and bun run build. This is the merge gate — RLS and
command-registry correctness are enforced here, not just locally.
CI provenance and updates
External uses: entries are pinned to 40-character commits (version comments
are for humans); all checkouts use v6. Weekly GitHub Actions Dependabot PRs
update those pins. Review the upstream commit/release diff, especially token
handling and composite actions’ own downloads, before merging; CI must pass.
The workflow-contract test rejects mutable action references.
The review marketplace is checked out separately at an immutable commit and
passed as a local directory. The pinned action’s
installer
accepts local directories but rejects https://…git#commit; inventing such a
URL would fail setup. The
marketplace manifest
points code-review at a relative directory in that same checkout, not another
floating repository. Dependabot does not update checkout’s with.ref: review
this pin manually with action updates, inspect the manifest and plugin diff,
and keep its source local. Do not enable marketplace auto-update.
The workflow renames only the checked-out catalog to mgr-reviewed-plugins:
Claude Code reserves Anthropic’s original catalog name for GitHub installs.
The plugin files and immutable checkout pin remain unchanged.
agent-browser 0.39.1’s
npm distribution bundles the
native binaries for all supported platforms. Bun’s frozen install verifies
the npm tarball against the independently committed bun.lock SHA-512
(sha512-0zdIq0v2r9Gio7K5wIe/e2W9nyLoVC6tUGpGV+btuynBQT9aCgOk5IEIyK+6OtfKRzQjp96DQ/3akBqxccigTQ==).
Removing it from trustedDependencies prevents its unchecked GitHub-release
fallback from running. On updates, review the new recorded integrity and
confirm the tarball still contains the native binaries before accepting it.
tests/installer-integrity.test.ts uses an isolated local fixture and fresh
Bun caches to prove a valid tarball installs without its hook and a substituted
tarball fails against the recorded digest.
Boundary, not an end-to-end vendor guarantee: bunx agent-browser install
installs Chrome, not the agent CLI, and does not validate a recorded digest.
The upstream
Chrome installer
fetches Chrome for Testing Stable metadata over HTTPS, then downloads the ZIP
without an independently recorded checksum unless that version is cached.
That explicit local opt-in is outside CI (CI does not run browser tests).
Do not describe this as verified Chrome provenance. Pinning an action also
does not pin every vendor runtime it installs (including Claude Code).
No real review/model/provider operation is needed for local setup proof.
Credential/permission inventory (unspecified scopes are denied wherever an
explicit permissions mapping is present):
| Action / job | Credentials and granted authority | Reason / boundary |
|---|---|---|
actions/checkout in CI |
GITHUB_TOKEN: contents read |
CI has an explicit read-only workflow default; no app key or provider secret. |
actions/checkout in review / doc / dream maintenance |
Contents read; persisted credentials disabled | Read source only; marketplace checkout also disables persistence. |
actions/checkout in publication |
Repository-scoped MGR App token: contents and pull requests write (dream also issues read) | Deterministic publication after validation, separate from the model. |
actions/checkout in @claude |
Job token: contents, pull requests and issues write | User-requested code changes; intentionally not a read-only reviewer. |
actions/setup-node, oven-sh/setup-bun, Supabase CLI setup |
Runner job context; no explicit application/provider secret input | Install pinned runtime versions; same job scopes, never elevated independently. |
anthropics/claude-code-action review |
CLAUDE_CODE_OAUTH_TOKEN; contents / pull requests / issues read, id-token write |
OIDC obtains the Claude GitHub App identity for review comments; model tool allowlist remains narrow. |
anthropics/claude-code-action doc / API maintenance |
OAuth secret; explicit read-only github.token, id-token write |
Model does not receive the publication App token. |
anthropics/claude-code-action dream maintenance |
OAuth secret; explicit github.token, contents / pull requests / issues read |
No publication permissions in the model job. |
anthropics/claude-code-action @claude |
OAuth secret; contents / pull requests / issues write, actions read, id-token write | Intentional interactive write workflow; actions read supports CI inspection. |
actions/create-github-app-token |
MGR_APP_PRIVATE_KEY and App ID |
Dream identity requests metadata read only; publication tokens explicitly request contents / PR write and dream issues read, scoped to the current repository. |
actions/upload-artifact, actions/download-artifact |
GitHub runner artifact runtime token | Same-run patch/bundle transport; neither gets the App key or OAuth secret as an input. |
| Watcher (no third-party action except read-only checkout) | github.token: issues write; stale-dream job also contents read |
Creates diagnostic issues only. |
| Hosted jobs (no external actions) | Order-email / retention bearer secrets; permissions: {} |
Existing operator-authorized service calls, never exercised by provenance verification. |
Operations runbooks
Operator procedures live in docs/operations/:
- Hosted release checklist — approvals, environment separation, advisors, smoke tests, rollback evidence.
- Hosted configuration — function region, QuickBooks credentials,
MGR_DEDICATED, invitation-only signup, recovery redirects. - Slack notifications — environment, Chat SDK database login, manifest, secret rotation, cleanup job.
- Buyer order-confirmation email — delivery worker, schedule, failure log categories, retries.
- Data retention and closing a brewery — command-record prune job, brewery export and close.
- E2E runners —
bun run test:e2eand the mobile command-surface regression. - Documentation agent — post-merge audit of the staff and portal field manuals.
Recommended Tools
Try a different keyword or remove a filter.
Install
npx skillfish add energee/mgr-zero