Owly Post
About Owly PostHow it was made

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

  1. 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 over vendor/owlypost), consumed as-is at the pinned submodule commit. Only core-bridge/ imports from vendor/. If the core lacks something: solve it shell-side by composing around what the core exposes (per-run env injection via runWithTenantEnv, 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 core main, via a submodule bump.
  2. Every core-table query goes through the tenant-scoped StorageAdapter. Direct Supabase queries to categories, sources, items, digests, digest_items, feedback, preference_profile outside the adapter are forbidden. CI greps for this.
  3. tenant_id is non-negotiable. No nullable tenant columns, no unscoped reads, no service-role query without an explicit tenant filter.
  4. All limits come from getEntitlements(tenant). Never hardcode a quota number outside plans.limits.
  5. 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.
  6. The leakage test stays green. Any change that touches storage, jobs or auth runs it before completion.
  7. 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.

On this page