The agent rules
The rules the coding agent works under in this repo: seven golden rules, mandatory gates, style constraints
What this repo is
The hosted, paid, multi-tenant shell for Owly Post. It consumes the open source core
through a git submodule: vendor/owlypost is the public repo — a flat single-user app,
untouched — pinned to a commit on its main. The cloud-owned bridge package
core-bridge/ (named @owlypost/core, a pnpm workspace member, workspace:*
dependency) re-exports the vendor lib/ surface and is the ONLY code allowed to import
vendor internals. The shell adds tenancy, auth, billing, quotas and a polished UI.
SPEC.md is the source of truth. Read it before non-trivial work.
The core is an independent open source project (owlypost, AGPL, public), vendored here
only as the submodule. It is never modified from this repo; see golden rule 1.
Golden rules
- Never reimplement core logic here, and never modify the core. Feed detection,
ingestion, summarization, digest generation and the preference profile come from
@owlypost/core(the bridge overvendor/owlypost), consumed as-is at the pinned submodule commit. Onlycore-bridge/imports fromvendor/. If the core lacks something: solve it shell-side by composing around what the core exposes (per-run env injection viarunWithTenantEnv, per-run metering, bridge additions like OPML — all fine). If that is truly impossible, stop and flag it to Esmee: a core change is a separate product decision for the open source project, made by her; the cloud consumes it only after it lands on coremain, via a submodule bump. - Every core-table query goes through the tenant-scoped
StorageAdapter. Direct Supabase queries tocategories,sources,items,digests,digest_items,feedback,preference_profileoutside the adapter are forbidden. CI greps for this. tenant_idis non-negotiable. No nullable tenant columns, no unscoped reads, no service-role query without an explicit tenant filter.- All limits come from
getEntitlements(tenant). Never hardcode a quota number outsideplans.limits. - Secrets never enter the repo. Env config only. The Supabase service-role key is server-side only. Stripe stays in test mode until the Phase 4 launch checklist.
- The leakage test stays green. Any change that touches storage, jobs or auth runs it before completion.
- Spec deviations are logged, not improvised silently. When SPEC.md is silent or
ambiguous: pick the smallest solution consistent with the decisions table (SPEC §1),
record it in
DECISIONS_LOG.md, flag it in the phase report.
Stack
Next.js (App Router, TypeScript strict) · Supabase (Postgres + Auth, Frankfurt) ·
Netlify (hosting, scheduled + background functions) · Stripe (subscriptions, Checkout,
webhooks, Customer Portal, Stripe Tax) · Resend (digest email) · @owlypost/core (git submodule vendor/owlypost, workspace member).
Core upgrades
A core upgrade is one PR that moves the vendor/owlypost submodule to a newer commit
on core main (Renovate git-submodules manager, or manually: git -C vendor/owlypost fetch origin && git -C vendor/owlypost checkout <sha>, then commit the gitlink). All
gates, including pnpm test:leak, must pass on that PR. A core interface change shows
up as a failing typecheck there and is fixed in the same PR, never by patching the core.
Commands
git submodule update --init # once after clone (core source in vendor/owlypost)
pnpm dev # local dev
pnpm test # unit tests (webhook handlers, entitlements, adapters)
pnpm test:leak # the cross-tenant leakage test — mandatory gate
pnpm lint # includes the no-direct-core-table-query gate
pnpm typecheck(Keep the leakage test as a separate, named, mandatory command.)
Testing requirements
- Cross-tenant leakage test: two seeded tenants, full pipeline both, zero cross reads/writes.
- Every Stripe webhook handler unit-tested (happy path + signature failure + out-of-order events).
- Quota enforcement tested at the boundary: exactly at the cap, one over the cap.
- Scheduler idempotency: a tenant is never digested twice for the same period.
Style
TypeScript strict, no any. Server components by default; client components only where
interaction demands it. Money in integer cents. Times stored UTC, rendered in tenant
timezone. User-facing copy in English, friendly, owl personality in empty/error states.