Layering rules (enforced, not aspirational)
- Transport layers (REST routes, MCP tools) contain zero business logic — they parse (schema), call
packages/core, format. Oversized router files are the anti-pattern to avoid. packages/corenever imports express/MCP/Next types.packages/dbenforces two-layer tenant isolation: every tenant-scoped query runs insidewithTenant()(RLS) and carries an explicit caller-bounduser_idpredicate — defense in depth, perdocs/concepts/data-model.mdx.- 50-line function / 500-line file budget. Target, not a lint rule — same as AGENTS.md hard rule 5. Oversized files in core paths are the failure mode the budget is there to prevent.
- Dependency direction is package.json + review +
scripts/check-db-access.sh. CI does not run a dedicated dependency-direction linter.
Server topology note
API and MCP ship as one express app / one service in v1 (a shared service layer makes a separate process split unnecessary at this scale). Split into separate deployables only when load profiles demonstrably diverge — the code structure (thin adapters overcore) keeps that a deploy-config change, not a refactor.
Resource-limit composition contract
The public server exposes two optional, billing-neutral resource limits through the injectedLimits resolver:
maxLiveMemoriescaps rows wherestatus = 'active' AND valid_to IS NULL.maxActiveMcpClientscaps distinct client IDs with at least one non-revoked, unexpired OAuth token.
valid_to is already set do not. A revision is allowed at the cap because it closes one live row and appends its successor in the same transaction, leaving the live count unchanged.
OAuth admission is serialized with account credential lifecycle changes. A first grant for a new distinct client is refused at the cap, while reauthorization of an already-active client remains possible when usage is at or below the cap. If a limit is lowered below current usage, issuance converges without deleting credentials: only the newest allowed client IDs (ordered by their latest live token creation time, then client_id ascending) may reauthorize or rotate. Losing clients cannot leapfrog the retained set with a fresh authorization-code flow, and their current access tokens expire naturally. Dynamic registration and CIMD resolution remain unrestricted; the limit is enforced when a client first receives tokens.
Transport contracts are explicit:
- REST memory creation returns
409 {"error":"resource_limit_exceeded"}. - MCP tools return an
isErrorresult with reason coderesource_limit_exceeded. - The onboarding welcome-seed endpoint returns the same HTTP 409 reason.
- OAuth token issuance returns RFC-compatible HTTP 400
invalid_grantwith the safe descriptionActive MCP client limit reached.
@3ngram/server/app exports the literal RESOURCE_LIMITS_ENFORCED = true. Downstream composition roots can require this sentinel before booting, preventing an older public server from silently ignoring the optional fields. It is a runtime compatibility marker, not commercial policy.