@vegastack/skills 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/README.md +2 -2
  2. package/dist/index.js +8 -35
  3. package/package.json +1 -1
  4. package/skill/architect/SKILL.md +68 -0
  5. package/skill/architect/agents/openai.yaml +4 -0
  6. package/skill/architect/assets/adr-template.md +21 -0
  7. package/skill/architect/assets/arch-template.md +20 -0
  8. package/skill/architect/references/advisory.md +102 -0
  9. package/skill/architect/references/ai-agents.md +95 -0
  10. package/skill/architect/references/data.md +90 -0
  11. package/skill/architect/references/infra.md +128 -0
  12. package/skill/architect/references/mobile.md +78 -0
  13. package/skill/architect/references/pinned-facts.md +108 -0
  14. package/skill/architect/references/principles.md +91 -0
  15. package/skill/architect/references/project-profile.md +37 -0
  16. package/skill/architect/references/security.md +97 -0
  17. package/skill/architect/references/stack.md +38 -0
  18. package/skill/architect/references/web.md +152 -0
  19. package/skill/architect/refresh/REFRESH.md +29 -0
  20. package/skill/architect/refresh/sources.json +244 -0
  21. package/skill/skill-maintainer/references/release-ops.md +11 -15
  22. package/skill/skill-maintainer/refresh/REFRESH.md +3 -3
  23. package/skill-integrity.json +20 -44
  24. package/skill/arch-guardian/SKILL.md +0 -84
  25. package/skill/arch-guardian/agents/openai.yaml +0 -4
  26. package/skill/arch-guardian/assets/adr-template.md +0 -25
  27. package/skill/arch-guardian/assets/answers-example.json +0 -10
  28. package/skill/arch-guardian/assets/architecture-profile.json +0 -13
  29. package/skill/arch-guardian/assets/architecture-profile.schema.json +0 -31
  30. package/skill/arch-guardian/assets/deployment-review-template.md +0 -24
  31. package/skill/arch-guardian/assets/service-design-template.md +0 -33
  32. package/skill/arch-guardian/assets/threat-model-template.md +0 -34
  33. package/skill/arch-guardian/references/advisory-report.md +0 -65
  34. package/skill/arch-guardian/references/architecture/agent-product.md +0 -22
  35. package/skill/arch-guardian/references/architecture/ai-cost.md +0 -24
  36. package/skill/arch-guardian/references/architecture/ai-data-boundaries.md +0 -21
  37. package/skill/arch-guardian/references/architecture/ai-evals.md +0 -28
  38. package/skill/arch-guardian/references/architecture/connectors-sandbox.md +0 -39
  39. package/skill/arch-guardian/references/architecture/data-memory.md +0 -25
  40. package/skill/arch-guardian/references/architecture/delivery-operations.md +0 -34
  41. package/skill/arch-guardian/references/architecture/durable-execution.md +0 -45
  42. package/skill/arch-guardian/references/architecture/flutter.md +0 -26
  43. package/skill/arch-guardian/references/architecture/foundation.md +0 -31
  44. package/skill/arch-guardian/references/architecture/hosting-reliability.md +0 -37
  45. package/skill/arch-guardian/references/architecture/identity-tenancy.md +0 -37
  46. package/skill/arch-guardian/references/architecture/model-lifecycle.md +0 -20
  47. package/skill/arch-guardian/references/architecture/models-observability.md +0 -23
  48. package/skill/arch-guardian/references/architecture/realtime-channels.md +0 -16
  49. package/skill/arch-guardian/references/architecture/security-privacy.md +0 -27
  50. package/skill/arch-guardian/references/architecture/topology-monorepo.md +0 -47
  51. package/skill/arch-guardian/references/architecture/web.md +0 -29
  52. package/skill/arch-guardian/references/foundation-compatibility.json +0 -44
  53. package/skill/arch-guardian/references/golden-patterns.md +0 -43
  54. package/skill/arch-guardian/references/profile-governance.md +0 -40
  55. package/skill/arch-guardian/references/rule-model.json +0 -36
  56. package/skill/arch-guardian/references/workflows.md +0 -48
  57. package/skill/arch-guardian/refresh/REFRESH.md +0 -47
  58. package/skill/arch-guardian/refresh/sources.json +0 -1171
  59. package/skill/arch-guardian/scripts/lib.mjs +0 -48
  60. package/skill/arch-guardian/scripts/profile-tool.mjs +0 -217
  61. package/skill/arch-guardian/scripts/refresh-evidence.mjs +0 -366
  62. package/skill/arch-guardian/scripts/schema-validate.mjs +0 -63
  63. package/skill/arch-guardian/scripts/validate-profile.mjs +0 -65
  64. package/skill/arch-guardian/scripts/verify-corpus.mjs +0 -136
@@ -0,0 +1,78 @@
1
+ # Mobile — Flutter production doctrine
2
+
3
+ **The Flutter app is a separate repo from the web/API repo** — it consumes the Next.js
4
+ REST/OpenAPI contract as one more client, never a `mobile/` directory in the web monorepo
5
+ (ADR-recorded, corroborated three ways; an expensive structural call to get wrong).
6
+
7
+ Flutter is the production mobile framework when a product needs a mobile app (not all do —
8
+ the profile records it). Baseline: Flutter 3.44.x stable; Impeller is the default renderer
9
+ on iOS and Android API 29+ (falls back below 29 — test one API<29 device before shipping).
10
+ Package versions here were verified 2026-08; re-verify on pub.dev before pinning.
11
+
12
+ Evidence tiers in this file: rules drawn from the shipped VegaStack app are stated plainly;
13
+ rules from official-docs research that MK has not yet ratified are tagged "(inferred)" —
14
+ confirm those on first use, per SKILL.md.
15
+
16
+ ## Architecture (official-guidance derived — inferred where the shipped app is silent)
17
+
18
+ - MVVM per official flutter.dev/app-architecture: View (widget, no logic) → ViewModel
19
+ (state + commands, 1:1 with its View) → Repository (source of truth, caching/retry,
20
+ never depends on another repository) → Service (thin stateless API/platform wrapper).
21
+ Skip the optional domain/use-case layer until logic is reused across ≥2 ViewModels.
22
+ - Project structure is layer-first: `lib/{data, domain, ui, routing, config}`, feature
23
+ folders nested inside `ui/`, shared widgets in `ui/core/`. (Blog "feature-first is the
24
+ standard" claims don't survive verification — the official reference app, Compass, is
25
+ layer-first. Feature-first is a valid escalation once app/team size demands it.)
26
+ - Class names mirror roles: `HomeViewModel`, `HomeScreen`, `UserRepository`,
27
+ `ClientApiService`.
28
+
29
+ ## State management — lean by default
30
+
31
+ - Plain `ChangeNotifier`/`ValueNotifier` ViewModels with hand-wired constructor injection
32
+ in `main.dart`. Zero extra dependencies, zero codegen — this matches both official
33
+ guidance ("personal preference") and what VegaStack has actually shipped.
34
+ - Escalate to Riverpod only when async state genuinely needs sharing across ≥3 widgets,
35
+ tests need provider-override mocking, or a second app shares a state-heavy module.
36
+ No get_it — official docs steer away from service locators; `provider` is the official
37
+ DI pick if constructor wiring ever gets unwieldy.
38
+
39
+ ## Networking & auth
40
+
41
+ - One `dio` client centralized in a single `ApiClient` — never scattered HTTP calls.
42
+ The app consumes the same contract-first REST API as the web app (web.md).
43
+ - Auth is the same Better Auth instance as web, via the bearer plugin: capture the token
44
+ from the `set-auth-token` response header on sign-in; store it in
45
+ `flutter_secure_storage` (never shared_preferences); attach `Authorization: Bearer` via
46
+ a dio interceptor; clear storage and route to sign-in on 401.
47
+ - Do NOT depend on `better_auth_flutter` (0.1.0, negligible adoption as of 2026-08) —
48
+ hand-roll the interceptor; revisit at a real 1.0.
49
+ - `shared_preferences` for non-secret local metadata only. `drift` only if genuine
50
+ offline/relational needs exist — never speculatively.
51
+
52
+ ## Design system on mobile
53
+
54
+ - Material 3 (default since 3.16); semantic colors via a hand-authored `ColorScheme` plus
55
+ `ThemeExtension` for tokens outside Material's roles — mirroring the web design-system
56
+ token names 1:1 from one Dart source of truth.
57
+ - House taste mapped: `CardTheme(elevation: 0)` with `outlineVariant` borders (flat,
58
+ borders-only); TextTheme capped at `FontWeight.w600`, never bold; subtle motion with
59
+ reduced-motion respected; Lucide-style iconography.
60
+
61
+ ## Navigation, models, testing, deploy
62
+
63
+ - Navigation: the shipped app uses plain `Navigator`; `go_router` is the official Flutter
64
+ team pick for route-heavy apps (inferred) — adopt it when deep links/route state demand
65
+ it, not by reflex.
66
+ - Models: the shipped app hand-writes all models — stay hand-written by default; freezed +
67
+ json_serializable only when codegen demonstrably earns its build cost (inferred
68
+ threshold, not MK-ratified).
69
+ - Test where MVVM pays off: unit-test ViewModels and Repositories; widget-test critical
70
+ screens; integration tests only for can't-ship-broken flows (sign-in, payment). Golden
71
+ tests, if adopted: `alchemist` over the discontinued `golden_toolkit` (inferred — no
72
+ VegaStack golden-test precedent yet).
73
+ - Deploy: GitHub Actions is the house CI; Fastlane for store signing/upload and real build
74
+ flavors (`--flavor` + per-env entry points) are the researched defaults (inferred — no
75
+ shipped store-deploy precedent yet; confirm before wiring).
76
+
77
+ Undecided (ask MK rather than assume): push-notification provider preference, offline/sync
78
+ expectations per product, store-release cadence.
@@ -0,0 +1,108 @@
1
+ # Pinned platform facts
2
+
3
+ Dated, source-verified facts that change architecture decisions and that models routinely
4
+ get wrong from stale training data. This is the ONLY file in this skill that goes stale by
5
+ itself — the weekly refresh (see refresh/REFRESH.md) re-verifies it. When a recommendation
6
+ leans on a fact older than 60 days, re-verify that one fact against its source first.
7
+
8
+ All facts below verified 2026-08-12.
9
+
10
+ ## Cloudflare
11
+
12
+ - **R2 egress is $0 at any volume.** A 500GB-stored / 2TB-served workload is ~$191/mo on
13
+ S3 vs ~$7.50/mo on R2. Reaching for S3+CloudFront "to control egress" solves a problem R2
14
+ doesn't have. Lifecycle rules, bucket locks, event notifications, and Infrequent Access
15
+ are all live — note IA transitions are one-way via lifecycle (IA→Standard needs a manual
16
+ CopyObject). [developers.cloudflare.com/r2/pricing]
17
+ - **Hyperdrive supports PostgreSQL 9.0–17.x — PG 18 is NOT supported.** Target Postgres 17
18
+ for any Hyperdrive-fronted database; re-check the supported-versions page before ever
19
+ moving to 18. Hyperdrive also supports MySQL (GA 2026-08-07) and private DBs via Workers
20
+ VPC. [developers.cloudflare.com/hyperdrive/reference/supported-databases-and-features]
21
+ - **Durable Objects default to SQLite storage** (real SQL, transactions, point-in-time
22
+ recovery), and **hibernating WebSocket DOs bill ~$0 while idle** — one-DO-per-room is
23
+ cost-competitive with Redis+socket.io for mostly-idle connections.
24
+ [developers.cloudflare.com/durable-objects]
25
+ - **Workflows: 10,000 steps default / 25,000 max, 365-day sleeps — and per-step billing
26
+ since 2026-08-10.** High-step-count designs now have a real cost dimension.
27
+ [developers.cloudflare.com/workflows]
28
+ - **Browser Rendering is metered by duration AND concurrency** ($0.09/browser-hour + $2 per
29
+ extra concurrent browser) — batch scraping through a queue; it is not free headless Chrome.
30
+ [developers.cloudflare.com/browser-rendering]
31
+ - **R2/KV/Workflows event notifications route through Queues "event subscriptions"** — a
32
+ Queue consumer covers platform state changes; don't build a bespoke webhook receiver.
33
+ - **D1 read replication only helps via the Sessions API** (`withSession(bookmark)`);
34
+ without it every query still hits the primary.
35
+
36
+ ## Next.js (16.3, released 2026-08-03)
37
+
38
+ - **PPR flags are gone** — `experimental.ppr` no longer exists; partial prerendering is
39
+ part of `cacheComponents: true` (which also replaced `dynamicIO`). [nextjs.org/blog]
40
+ - **`middleware.ts` is replaced by `proxy.ts` running on Node** — full fs/crypto/native
41
+ package access in request interception. Remember: proxy is still never the auth boundary.
42
+ - **The Adapter API is stable since 16.2** — Vercel's adapter and Cloudflare's OpenNext
43
+ adapter share the same public contract, but Cloudflare's still trails on newest features;
44
+ check the deployment feature matrix per feature, don't assume parity.
45
+ [nextjs.org/docs/app/guides/deploying-to-platforms]
46
+
47
+ ## Cloudflare Workers hard limits
48
+
49
+ - **Memory 128MB per isolate (hard), CPU 30s default / 5min max (configurable), 1000
50
+ subrequests per request (paid).** Heavy transforms (image processing, big parses)
51
+ belong in a container/worker tier, not a Worker. Workers Logs free tier: 200k
52
+ events/day, 3-day retention; automatic tracing starts billing 2026-10-01; OTLP export
53
+ needs Workers Paid. [developers.cloudflare.com/workers/platform/limits]
54
+
55
+ ## Better Auth (1.6.27, 2026-08-11)
56
+
57
+ - **1.7.0 is in RC** (rc.5 shipped 2026-08-11, same day as the pinned patch). Stay on
58
+ 1.6.x until 1.7 is stable; queued breaking changes include the MCP plugin restructure
59
+ (moves to `@better-auth/mcp`) and SAML IdP-initiated default-off. [github.com/better-auth]
60
+
61
+ - **The organizations plugin models teams, invitations, and custom RBAC end-to-end**
62
+ (`teams: { enabled: true }`, `invite-member` with `teamId`, `createAccessControl`) —
63
+ never hand-roll workspace/membership/groups schema. [better-auth.com/docs]
64
+ - **Better Auth ships an apiKey plugin** (docs/plugins/api-key — verified live
65
+ 2026-08-12; an older internal note claiming otherwise was wrong). Default to the plugin
66
+ for new projects; the flagship platform's native implementation (SHA-256 hash-stored,
67
+ raw shown once) is a recorded project decision, not the house default. The `bearer`
68
+ plugin covers token session transport (the mobile/Flutter mechanism).
69
+ - **`twoFactor` supports `allowPasswordless: true`** for users without password accounts
70
+ (passkey/OAuth/magic-link signups).
71
+
72
+ ## Agents & jobs
73
+
74
+ - **EVE (`eve` on npm, github.com/vercel/eve) is Vercel's durable-agent framework —
75
+ v0.33.2, still beta/pre-GA, shipping near-daily.** Filesystem-first agents; every
76
+ session a durable, resumable workflow. Exactly two production deploy shapes: on Vercel
77
+ as Vercel Functions with Fluid Compute (a recorded per-project exception to the hosting
78
+ default), or self-hosted as a long-running Node/OCI service beside Postgres. Never a
79
+ request-scoped/edge function (a Cloudflare Worker included) in either shape.
80
+ [vercel.com/docs/eve]
81
+ - **Self-hosted EVE durability (`@workflow/world-postgres`, stable 4.3.x) explicitly
82
+ requires a long-lived worker process — "not compatible with serverless platforms".**
83
+ Run EVE as its own Node/OCI service beside Postgres. The 5.0.0-beta channel exists;
84
+ don't pin it without a documented reason. Internally it uses graphile-worker — it is
85
+ not pg-boss and doesn't replace it. [workflow-sdk.dev/worlds/postgres]
86
+ - **pg-boss is at 12.x** — Postgres-native (`SKIP LOCKED`), no Redis. The right default
87
+ for simple background jobs/cron on this stack; BullMQ only when a genuinely complex job
88
+ graph (flows, dependencies, rate-limited pipelines) demands Redis. [npm: pg-boss]
89
+ - **trigger.dev v4 is Apache-2.0 and self-hostable free with unlimited runs** — the
90
+ credible escape hatch when a job needs multi-hour runtimes off-platform. [trigger.dev]
91
+
92
+ ## Databases & mobile
93
+
94
+ - **PlanetScale Postgres is built on Neki, not Vitess** — a newer product (GA 2025-09);
95
+ don't transfer Vitess/MySQL assumptions. No free tier (the free Hobby plan died April
96
+ 2024); Postgres pricing is SKU-based from PS-5 non-HA at $5/mo, HA from ~$15-50/mo.
97
+ ($39/mo figures seen elsewhere are the Vitess/MySQL PS-10 tier — a different product.)
98
+ [planetscale.com/pricing]
99
+ - **Flutter's default renderer is Impeller on iOS, Android, and macOS** — "disable
100
+ Impeller on Android" guidance is stale. [docs.flutter.dev]
101
+
102
+ ## Self-hosting
103
+
104
+ - **Coolify needs ~2GB RAM for its own control plane and requires active patching** —
105
+ two critical-CVE waves in 2026 alone (Jan: beta.445/451; Jun-Jul: CVE-2026-34047/49/50,
106
+ fixed in beta.471) — a recurring pattern, not a closed incident. Size Hetzner VMs
107
+ accordingly (CX22/4GB is the floor for Coolify + one small app); ~30 min/month real
108
+ maintenance, not zero. [coolify.io]
@@ -0,0 +1,91 @@
1
+ # Principles — how VegaStack decides
2
+
3
+ How MK actually makes architecture decisions, distilled from seven months of his sessions
4
+ and repos. Each rule carries its why — apply the reasoning, not just the rule.
5
+
6
+ ## Build lean first
7
+
8
+ One deployable until a concrete requirement forces a split. A single, properly structured
9
+ Next.js app owns the UI, RSC, route handlers, auth, and the REST/OpenAPI control plane — no
10
+ NestJS or Hono beside it, no speculative queue, cache, or worker. A notification sender is a
11
+ cron handler inside the app, not its own deployable. **Why:** agents habitually propose
12
+ services MK then has to strip out ("why do we still need workers like extra bloat or
13
+ maintenance surface?"). On a 3-4 person team, every moving part is real maintenance, and the
14
+ lean version is usually also the faster and more reliable one.
15
+
16
+ Lean-first governs the *count of moving parts*, not the rigor inside each part.
17
+ Correctness-critical logic — money, auth, tenancy, audit, secrets — always gets full rigor.
18
+
19
+ ## Every moving part names its trigger
20
+
21
+ Propose infrastructure only together with the trigger that justifies it, stated in the
22
+ recommendation: "a separate worker WHEN jobs exceed request timeouts", "a queue WHEN volume
23
+ makes inline processing lossy", "OpenBao WHEN self-hosting customer-managed secrets".
24
+ Provision only what the current build phase actually uses — infrastructure tracks real
25
+ usage, never anticipated usage. **Why:** MK stated "OpenBao mandatory in production" and
26
+ walked it back five days later ("some projects are just simple and straightforward").
27
+ Blanket mandates rot; triggers stay true as projects differ.
28
+
29
+ ## Pre-launch means delete, not migrate
30
+
31
+ Zero real users = no backward compatibility, no legacy shims, no deprecation windows, no
32
+ feature flags hiding unfinished work. Delete outright; reset the dev database rather than
33
+ writing migration chains. This is the DEFAULT — most VegaStack projects are pre-launch at
34
+ any given time. Expand/migrate/contract discipline begins when real users exist, not before.
35
+ **Why:** carrying compatibility for users who don't exist is pure bloat ("NO FEATURE FLAGS
36
+ PLEASE... i dont want any backwards compatibility as this is an unreleased app").
37
+ One exception: a versioned API contract consumed by a shipped mobile app counts as having
38
+ real users even while the web side iterates freely — app-store install lag keeps old
39
+ clients alive (mobile.md).
40
+
41
+ ## Reuse before you build new
42
+
43
+ Extend the existing table, service, or spine (ACL, change-log, outbox, realtime channel)
44
+ before creating a parallel one. When two components do the same job, merge them into one
45
+ canonical source. Promote a util to `/lib` the moment a second feature uses it. **Why:**
46
+ duplicated systems of record are the reference-architecture mistake MK explicitly engineers
47
+ against; two sources of the same truth always drift.
48
+
49
+ ## Enforce boundaries mechanically
50
+
51
+ Architectural boundaries that matter get a CI guard script that fails the build — monorepo
52
+ import direction (apps → packages, never the reverse; workers never import UI/React),
53
+ runtime gravity (below), server/client code separation. **Why:** convention alone was tried
54
+ and failed; a guard script is cheaper than re-reviewing the same violation forever.
55
+
56
+ ## Runtime gravity: long-running work never lives in the request tier
57
+
58
+ Anything that can run long, hold a connection, or outlive a request — agent execution, job
59
+ processing, media pipelines — runs in a separate worker/runner tier, never inside a route
60
+ handler or an OpenNext Worker. **Why:** request-scoped tiers have timeouts and body limits;
61
+ the reference codebase MK studied executed workflows inside Next.js handlers and it is the
62
+ single anti-pattern he cites most.
63
+
64
+ ## Effort scales with stakes, not habit
65
+
66
+ Security, auth, tenancy, money, and architecture foundations get generous, thorough,
67
+ adversarial treatment. Routine features on a small team get medium thoroughness — do a cheap
68
+ reconnaissance pass before committing to full-cost work, and don't build governance ceremony
69
+ a 3-4 person team will never exercise. **Why:** MK's own most recent self-correction — his
70
+ previous architecture skill "got too complicated" by applying flagship-platform rigor
71
+ everywhere.
72
+
73
+ ## Decisions are verified, recorded, and reversible for a reason
74
+
75
+ - Resolve uncertainty with evidence, never with the safer-sounding guess: check the live
76
+ official docs (current year), verify the claimed constraint, then decide. MK refused to
77
+ accept an unverified "Better Auth forces text UUID columns" claim — it was false.
78
+ - Present decisions as 2-3 options with a clear recommendation and the tradeoff that
79
+ matters, then record what was chosen and why (an ADR or one dated ledger line).
80
+ - MK reverses when: a verified assumption proves false · added cleverness regresses UX ·
81
+ a heavyweight mandate meets a simple project · his own tooling over-complicates. He
82
+ reverses toward less machinery on low stakes and toward more rigor only when a concrete
83
+ bug or vulnerability is found. Anticipate this: don't defend machinery he'd delete.
84
+
85
+ ## Context gates rigor — ask, don't assume
86
+
87
+ Rigor flexes on facts, not labels: pre-launch or live · internal, client, or OSS ·
88
+ self-hosted or managed · handles money/PII or not · has real users or not. These live in
89
+ `.vegastack/arch.md`. Client work gets the same approval gates and honesty as internal work
90
+ — if anything, more documentation, never less. When two recorded decisions conflict or the
91
+ profile can't answer, ask MK with a recommendation instead of assuming.
@@ -0,0 +1,37 @@
1
+ # Project profile — `.vegastack/arch.md`
2
+
3
+ The per-project memory that stops every session from re-deriving the same facts. It is a
4
+ head start, never the source of truth: the repository wins every disagreement.
5
+
6
+ ## First run in a project
7
+
8
+ If `.vegastack/arch.md` does not exist and the task is architectural:
9
+
10
+ 1. First infer what you can from the repo — package.json/lockfile (runtime), wrangler
11
+ files (Cloudflare; a `d1_databases` binding with no Postgres driver = the D1-only
12
+ product class), Dockerfiles/compose (self-managed), drizzle config (database),
13
+ better-auth usage, `@aws-sdk/client-s3`/R2 bindings (storage), pg-boss dependency
14
+ (jobs), `eve`/`ai` packages (agents vs plain AI features), pubspec.yaml (mobile).
15
+ 2. Ask MK or the team member only what the repo can't answer, in one short message:
16
+ - Where does this deploy? (Cloudflare Workers via OpenNext · self-managed server ·
17
+ both · exception: Vercel)
18
+ - Stage and kind? (pre-launch or live · internal, client, or oss)
19
+ - Anything non-default? (mobile app, agents, separate workers, unusual services)
20
+ 3. Write `.vegastack/arch.md` from [the template](../assets/arch-template.md), show it,
21
+ and confirm before relying on it. Creating the file needs the same write authorization
22
+ as any other file.
23
+
24
+ Never create the file during a purely read-only task or an explanation — suggest it instead.
25
+
26
+ ## Every later run
27
+
28
+ - Read the file, then trust the repo over it. If the repo disagrees (the file says bun but
29
+ the lockfile is pnpm-lock.yaml; a wrangler.jsonc appeared; a mobile/ directory exists),
30
+ say so and propose the exact one-line file update. Never silently follow a stale profile,
31
+ and never silently rewrite it either.
32
+ - Decisions and their dates belong in the `notes:` lines — one line per decision ("2026-08:
33
+ DO for realtime only, dropped for chat — SSE+Postgres instead"). This is the project's
34
+ decision ledger; keep entries short and dated.
35
+ - If the file records something that contradicts this skill's defaults, the file wins for
36
+ that project — it is a recorded decision. Report it as accepted risk only if it crosses
37
+ a red line.
@@ -0,0 +1,97 @@
1
+ # Security & auth
2
+
3
+ Auth, tenancy-isolation, and secrets work is the highest-rigor tier: extra precision on
4
+ every change, and a focused security audit after any auth-adjacent change before it's done.
5
+
6
+ ## Authentication (Better Auth, always)
7
+
8
+ - Email/password + Google sign-in is the default configuration; magic link and email OTP
9
+ as flows demand; 2FA TOTP when the product warrants (note `allowPasswordless: true` for
10
+ passkey/OAuth-only users). Follow Better Auth's documented APIs — never hand-roll session
11
+ or cache invalidation beside them.
12
+ - The organizations plugin (with teams enabled) owns orgs/workspaces/teams/invitations —
13
+ organization maps to workspace; "user groups" of any kind are Better Auth teams. Use the
14
+ SSO plugin for SAML/OIDC when enterprise auth arrives — don't build protocol code.
15
+ - Mobile uses the bearer plugin (token from `set-auth-token`, stored in secure storage) —
16
+ same auth instance, same Postgres, as the web app.
17
+ - Sessions: rolling expiry, Postgres-authoritative. Do not enable cookie-cache-style
18
+ session shortcuts without checking Better Auth's current issues — a cookieCache bug
19
+ once caused silent 5-minute logouts in production. Session revocation ("sign out
20
+ everywhere", admin-initiated) goes through Better Auth's session-list/revoke APIs
21
+ server-side — never a hand-rolled cache-invalidation scheme beside them.
22
+ - Google OAuth footgun: set `baseURL` explicitly in production config — an unset or wrong
23
+ value silently targets localhost and fails Google's exact-match redirect-URI check.
24
+ - API keys: use Better Auth's apiKey plugin by default. Whatever implements them, the
25
+ invariants hold: hash-stored with a recognizable prefix, raw value shown exactly once
26
+ and never cached or logged again, constant-time compare on verify.
27
+
28
+ ## Authorization
29
+
30
+ - Middleware/proxy is never the boundary (CVE-2025-29927 class). The boundary is a
31
+ server-only data-access layer — `requireSession()`/`requireOrgRole()`-style helpers,
32
+ re-checked per resource, on every route, auth before body parse.
33
+ - Every check fails closed: a policy-engine error, throw, or unmatched rule resolves to
34
+ deny, and the deny is still audited. No `DISABLE_AUTH`/`BILLING_ENABLED=false` style
35
+ escape hatches, ever. Dev-only conveniences (e.g. skipping email verification) are OK
36
+ only when explicitly gated to non-production with zero prod behavior change.
37
+ - Role models stay small — Owner/Admin/Member covers most products. A policy engine
38
+ (Cedar-class ABAC) needs a named trigger — fine-grained multi-principal authorization
39
+ over agent/tool calls at platform scale; today only the flagship platform has earned
40
+ it. Any other project proposing one: present the trigger and ask MK.
41
+ - Tenant identity always derives from the authenticated principal — never from a
42
+ client-supplied workspace/org ID (a trusted `?workspaceId=` param caused a real
43
+ cross-tenant IDOR). Cross-tenant lookups return 404, never 403 — no existence oracle.
44
+ - If ALL UI consumers of a route sit inside the authenticated app shell, the route
45
+ requires auth even when its data "seems public" — trace actual callers before accepting
46
+ "intentionally public".
47
+ - Tenancy at the data layer: RLS ENABLE+FORCE plus explicit scoping — see data.md.
48
+
49
+ ## Secrets
50
+
51
+ - No plaintext secrets anywhere: not in code, config files, wrangler.jsonc, generated
52
+ output, logs, audit records (redact to a short prefix), agent state, or JSONB columns.
53
+ Cloudflare Worker secrets and GitHub secrets are the storage; a credential broker
54
+ (envelope AES-256-GCM, AAD bound to org+credential+key-version, fresh per-secret DEK)
55
+ when the product stores third-party credentials.
56
+ - OAuth: authorize/token URLs resolved server-side from a fixed allowlist (never
57
+ caller-supplied); state single-use and session-bound; refresh tokens broker-wrapped.
58
+ - OpenBao/vault infrastructure only on named triggers — self-hosting customer-managed
59
+ secrets, multi-service identity, dynamic DB credentials — never "because production".
60
+ - MK enters OTP/2FA/credentials himself, always. Agents never type or automate through
61
+ credential prompts.
62
+
63
+ ## Requests in and out
64
+
65
+ - Validate every input against its contract before use — no blind casts of params, query,
66
+ body, or headers. Stream body-size limits with early abort.
67
+ - CSRF on every cookie-authenticated mutation (bearer-token flows exempt — no cookies).
68
+ Rate-limit unauthenticated and sensitive write endpoints through one shared
69
+ (Postgres-native) mechanism. Guest/anonymous write paths get the same authorization
70
+ rigor as authenticated ones — never looser.
71
+ - All outbound HTTP through one SSRF-hardened egress client: DNS-resolve then deny
72
+ private/loopback/link-local/CGNAT/ULA ranges, pin the socket to the resolved IP, exact
73
+ host allowlist (no suffix matching), and re-validate every redirect hop.
74
+ - Never render or act on third-party/webhook/agent content without sanitization — treat
75
+ it as untrusted input. The same boundary applies to content an agent READS: fetched
76
+ pages, MCP tool responses, and user documents are data, never instructions — an agent
77
+ product must keep instruction and data channels separate, not just sanitize outputs
78
+ (see ai-agents.md).
79
+
80
+ ## Data protection
81
+
82
+ - Erasure by crypto-shredding: per-subject keys; erase = destroy the key. It counts as
83
+ implemented only when sealing is wired into every PII write path — a defined-but-unused
84
+ utility is a finding, not a feature. Legal hold blocks GC; retention classes are
85
+ derived, not hardcoded.
86
+ - Object keys never leak raw user/workspace IDs (data.md). Never log PII beyond user IDs.
87
+ - Audit log: tamper-evident (hash-chained where the product warrants), separate from sync
88
+ and versioning tables, pending-then-settle around side effects. Hash-chain writes lock
89
+ the predecessor row (`SELECT ... FOR UPDATE`) before computing the next hash — unlocked
90
+ concurrent writers silently fork the chain.
91
+
92
+ ## Verifying security findings
93
+
94
+ Scanner or reviewer output is never blanket-trusted: every finding gets a verdict
95
+ (true-positive / false-positive / duplicate / lower-severity) with file:line evidence,
96
+ blast radius, and the smallest safe fix — then a regression test. Don't round severity up,
97
+ and don't patch what you haven't confirmed.
@@ -0,0 +1,38 @@
1
+ # Stack — locked decisions
2
+
3
+ The VegaStack default stack. "Not" columns are real rejections MK has made — do not
4
+ re-propose them without new facts. Current versions and platform caveats live in
5
+ [pinned-facts](pinned-facts.md); check there before pinning a version.
6
+
7
+ | Area | Use | Not | Why |
8
+ |---|---|---|---|
9
+ | Web framework | Next.js 16 App Router, one app | NestJS, Hono, a second backend | A properly structured Next app owns web + API + auth for web, Flutter, MCP, and public consumers; a second framework is pure maintenance surface |
10
+ | Runtime / package manager | Bun (default for new projects); pnpm fully sanctioned where chosen | npm, yarn — never | One lockfile discipline per project; the profile records which. Commit text lockfiles (`bun.lock`, not `bun.lockb`) |
11
+ | Auth | Better Auth: email/password + Google sign-in by default; organizations plugin (teams enabled) for orgs/workspaces/teams; bearer plugin for mobile; apiKey plugin for API keys | Hand-rolled auth, custom user-groups schema, Auth0/Clerk | Owner-stated standing rule. Organization maps to workspace (`modelName: "workspace"` pattern); teams = Better Auth teams; prefer the shipped plugins over native builds unless a project records why |
12
+ | Database | PostgreSQL, always self-managed by us: PlanetScale Postgres server or self-hosted (Hetzner or similar). PG 17 behind Hyperdrive; 17/18 otherwise. D1-only is a recorded exception for the minimal Cloudflare-native product class (vegastack-pages/vegafactory precedent, locked 2026-05-20) — check `.vegastack/arch.md` before assuming Postgres | Neon (never), D1 as a secondary store beside Postgres, MySQL | "We don't use neon at all." One datastore per product; Hyperdrive caps at PG 17.x (see pinned-facts). D1's sanctioned sidecar case: a Worker-scoped idempotency table for Stripe webhooks — Postgres still owns the ledger |
13
+ | ORM | Drizzle + drizzle-kit, single `postgres-js` driver | Prisma | House standard across every repo since Feb 2026 |
14
+ | DB from Workers | Hyperdrive binding, per-request client (`prepare: false, max: 1`, request-scoped) | Global pools, TCP clients (ioredis) in Workers | workerd forbids I/O across requests; a module-level pool is a bug, not a style choice |
15
+ | Storage | Cloudflare R2 (S3-compatible API), presigned URLs, short-lived scoped access | S3 + CloudFront by reflex | R2 egress is $0 at any scale — the cost problem S3+CDN solves doesn't exist here. For licensed self-hosted deployments, keep the S3 protocol boundary so any S3 provider or MinIO works |
16
+ | Cache / Redis | None by default. Rate limiting and caching Postgres-native; Workers KV only with a named trigger (e.g. read-mostly config/session cache at request volumes where a Postgres round-trip measurably hurts — as in the recorded auth-cache migration); a Redis-class store never correctness-bearing | Upstash Redis (migrated off), Redis "because caching" | "No Redis in P1" — a cache layer is a moving part; Postgres already exists. Never cache authorization/role data aggressively |
17
+ | Jobs / cron | pg-boss on the existing Postgres, dispatcher-only — lease/heartbeat/retry state of authority lives in our own tables. Cron parsing/description: `croner` (DST/IANA-aware) + `cronstrue` (human-readable) | BullMQ+Redis, Temporal, CF Queues by default; `node-cron`, `cron-parser` | Jobs are simple; Postgres is already there. Owning durability state keeps the queue library swappable; croner/cronstrue are the battle-tested cron pair (a cron-parser swap was made and corrected once already) |
18
+ | Agent execution | EVE (Vercel's `eve`, beta): on Vercel as Functions with Fluid Compute (recorded per-project exception), or self-hosted as its own long-running Node/OCI service with `@workflow/world-postgres` | EVE inside an OpenNext Worker or any request-scoped/edge function | EVE's Postgres world explicitly requires a long-lived worker process — "not compatible with serverless platforms". EVE owns agent sessions; pg-boss owns generic jobs — complementary, never conflated |
19
+ | AI calls | AI SDK v7 (`ai@^7`) behind a thin adapter; Anthropic default provider; Cloudflare AI Gateway for routing/telemetry when on Cloudflare | Hardcoded model IDs scattered in code, per-provider SDKs everywhere | Adapter keeps providers swappable; the gateway centralizes cost/telemetry without building it |
20
+ | Design system | Consume `@vegastack/design` + `@vegastack/ui` (Base UI primitives, semantic tokens) | Creating/modifying components upstream, raw shadcn edits, Radix for new work | Consume, don't extend — upstream changes are MK's deliberate decision. Base UI locked over Radix 2026-07 |
21
+ | Frontend state | Server state via RSC + TanStack Query; URL state via nuqs; ephemeral UI state via Zustand | Persisting remote/tenant data client-side (IndexedDB/local-first) for multi-tenant SaaS | Governance: tenant data never rests on the client. Local-first only for a confirmed single-tenant/offline product — ask first |
22
+ | Realtime | SSE tailing an event log first; Durable Objects (SQLite, WebSocket hibernation) when on Cloudflare and bidirectional state is real. One DO per collaboration scope (a `WorkspaceHub` for presence/signals, a per-document `PageSync` for edit traffic) — never one monolithic DO | socket.io + Redis pub/sub, Ably, Pusher | SSE covers most "live" needs with zero new services; hibernating DOs bill ~nothing while idle. Third-party realtime vendors were migrated off |
23
+ | Collaborative editing | Tiptap editor (markdown as source of truth), Yjs CRDT sync via `y-partyserver` inside the document DO; server-side sanitization/versioning still applies to every CRDT write | CodeMirror (abandoned 2026-05-21), ElectricSQL (removed), RxDB (reversed to plain Dexie where local cache is needed) | Locked through real reversals; CRDT must never become an XSS/version/audit bypass |
24
+ | Analytics | Self-hosted Plausible (`pb.vegastack.com`), proxied via `next-plausible` where applicable | Google Analytics, Vercel Analytics, PostHog by default | Self-hosted, privacy-clean, already running — recurs across 5 projects |
25
+ | Hosting | Cloudflare Workers via OpenNext, or self-managed servers (Docker; Coolify for push-to-deploy). Both recorded in the profile | Vercel as a default (allowed only by explicit per-project exception, recorded); a parallel Vercel path once committed to Cloudflare | Cost and egress economics favor Cloudflare/self-host; split deploy paths drift. EVE-hosted workloads are a legitimate Vercel exception |
26
+ | Email | AWS SES (behind an adapter); handle bounces/complaints via the SNS-webhook pattern into a suppression list | Per-vendor SDK sprawl, sending to suppressed addresses | Cheap, boring, already proven in-house; unhandled bounces poison sender reputation |
27
+ | Payments | Stripe; webhook intake isolated (own worker or route) with idempotent event handling | Building billing logic inside request handlers | Money paths get the full rigor tier: idempotency keys, event dedupe, DLQ |
28
+ | Monorepo | Turborepo + workspaces (Bun or pnpm) when there is more than one package; apps → packages only, enforced by a boundary check | Deep relative cross-package imports, packages importing apps | Import direction is a guard script, not a convention |
29
+ | i18n | next-intl, `localePrefix: 'as-needed'`; no hardcoded user-facing strings | Ad-hoc string tables | Already the proven pattern; English-only today but scaffolded |
30
+ | APIs | Contract-first: zod schemas → OpenAPI 3.1; auth before parse; cursor pagination on every list; RFC 9457 problem-details errors | Route-local validation, raw `request.json()`, unbounded lists | One contract feeds web, Flutter, MCP, and public consumers; sanitize responses (never return secrets/credential refs) |
31
+
32
+ ## Choosing between the two hosting targets
33
+
34
+ Cloudflare (OpenNext) when: public web product, global latency matters, R2/DO/Queues fit,
35
+ cost-per-request dominates. Self-managed (Docker/Hetzner) when: self-hosting is a product
36
+ requirement (licensed/enterprise), the workload needs long-lived processes (EVE, heavy
37
+ workers), or platform independence is worth ~30 min/month of ops. Many products use both:
38
+ OpenNext app on Cloudflare + a long-running worker container beside the database.
@@ -0,0 +1,152 @@
1
+ # Web — frontend, design system, backend/API taste
2
+
3
+ ## Project layout
4
+
5
+ - Top-level `server/` (or `packages/*` server code) holds DB, services, integrations — it
6
+ is sacred: never imported from client code, enforced with `server-only` + a boundary
7
+ check. The app router lives in `src/app/`; features are self-contained modules under
8
+ `src/features/<domain>/` (components/hooks per feature); shared pieces in
9
+ `src/{components,hooks,lib,store,messages}/`. Pages stay thin — they compose features.
10
+ - The house convention scopes agent instructions per directory (a short CLAUDE.md/AGENTS.md
11
+ at each boundary-bearing directory) — follow it where a project already does this.
12
+
13
+ ## Frontend architecture
14
+
15
+ - Server state → RSC + TanStack Query; URL state → nuqs; ephemeral UI state → Zustand.
16
+ Remote/tenant data is never persisted client-side (IndexedDB/local-first was evaluated and
17
+ rejected for multi-tenant SaaS on governance grounds — a confirmed single-tenant/offline
18
+ product is the only exception, and that's a decision to ask for, not assume).
19
+ - Next config: `output: 'standalone'`, `cacheComponents: true` (PPR flags no longer exist).
20
+ Query key factories live in non-`'use client'` `.keys.ts` modules so RSC pages can prefetch.
21
+ - Server Components by default; `'use client'` only at the lowest leaf that needs it.
22
+ `/server` (or `packages/*` server code) is never imported from client code — `server-only`
23
+ plus a boundary check, not convention.
24
+ - Request interception: on OpenNext/Cloudflare, `proxy.ts`/Node middleware does NOT work
25
+ as of 2026-08 (open adapter issues — see infra.md); use what the adapter actually
26
+ supports and re-check its docs per feature. Either way interception is never the auth
27
+ boundary (see security.md).
28
+ - Internal navigation uses `next/link`; bare `<a>` only for true external links/downloads.
29
+ - Never TypeScript `any` — `unknown` and narrow.
30
+ - Dependencies: prefer the native/browser API when it covers the need
31
+ (`Intl.RelativeTimeFormat` over date-fns); self-implement simple behavior (a tween, a diff
32
+ view) by composing design-system primitives before adding a library; any non-trivial new
33
+ dependency needs a stated justification and approval. **Why:** self-contained code has no
34
+ supply-chain, licensing, or upgrade surface, and MK rejects "library because library".
35
+
36
+ ## Design system (consuming `@vegastack/design` / `@vegastack/ui`)
37
+
38
+ - First-time setup in a fresh project (npm install, Tailwind wiring, provider, registry
39
+ auth) is a separate, already-built skill — `vegastack-consume` — use it; don't
40
+ re-derive the wiring here.
41
+ - Consume through the package barrel, never deep imports; never edit shipped component files.
42
+ A missing component or variant is an upstream request MK decides — the recommended
43
+ interim pattern is a local presentational composition in the app (inferred — confirm on
44
+ first use); don't fork the system.
45
+ - Semantic tokens only: no inline `style={}`, no arbitrary values (`bg-[#123]`, `h-[13px]`),
46
+ no raw palette classes (`bg-neutral-900`), not even for opacity. Dynamic values go through
47
+ CSS custom properties.
48
+ - Visual language: borders-only cards, flat surfaces (shadows only for true overlays); accent
49
+ color rationed to the primary action and real selected/active state — never hover, roughly
50
+ ≤10 accent elements a page. Focus stays visible always (WCAG 2.2 AA floor) — the house
51
+ mechanism is a darker border or native outline, never a `ring-*` utility. Control heights
52
+ on the 28/32/40 scale = `h-7`/`h-8`/`h-10` (no off-scale `h-9`); radius: `rounded-md`
53
+ controls, `rounded-sm` menu items, `rounded-lg` containers, `rounded-full` pills — never
54
+ `rounded-xl` on cards/dialogs. Touch-target minimum is genuinely unresolved (24px WCAG AA
55
+ vs 44px mobile-HIG both appear in the record) — ask MK when it matters.
56
+ - Typography: product code caps at `font-semibold` and uses it sparingly (inside the design
57
+ system package itself the cap is `font-medium`); headings `font-lora`; numbers, versions,
58
+ costs, and card digits always `font-mono`; uppercase only in mono at ≤14px. Fonts are
59
+ self-hosted via `next/font` — never a Google Fonts CDN request.
60
+ - Icons: Lucide only. Toasts: sonner, never native/OS prompts. 4px spacing scale.
61
+ - Motion: subtle and minimal — no button hover/press animations, no `transition-all`; motion
62
+ is for state feedback (success/error) and structural changes. `prefers-reduced-motion`
63
+ disables animation entirely (instant, not softened).
64
+ - No decorative filler: no gratuitous badges, eyebrow text, or "3 of 3" counters — MK cuts
65
+ these on sight as AI-generated design tells.
66
+ - Light AND dark theme on every shipped surface (internal-only tools may opt out, stated
67
+ up front).
68
+
69
+ ## UI completeness bar (every real surface, before it's "done")
70
+
71
+ - Full state matrix: loading skeleton shaped like the real content · empty state with
72
+ guidance · error routed to the specific failure (401/403, 404, 410, 429, generic — never
73
+ swallowed into `{}`) · success feedback. Optimistic updates where a mutation changes
74
+ visible state; the UI reflects backend mutations without a manual refresh.
75
+ - Forms: Enter submits; spinner replaces the button icon (not beside it); first field
76
+ auto-focused; all fields disabled while the primary action runs; state resets on error or
77
+ navigation.
78
+ - Responsive: no horizontal scroll at any width; computed layout math, never hardcoded pixel
79
+ offsets; truncation driven by available space, not character counts; iOS inputs must not
80
+ zoom on focus; the mobile keyboard must never occlude the active input.
81
+ - Systemic fixes: a defect found in one component (scroll leaking through an overlay, missing
82
+ cursor, wrong token) is fixed across the whole family in one sweep, with evidence.
83
+ - Perceived speed is the named pattern "instant shell": the layout shell renders
84
+ immediately (RSC + `cacheComponents`), every data region streams in behind a skeleton
85
+ shaped like its content — never a blank page or a full-page spinner.
86
+ - Long lists (>~50 rows) virtualize with `@tanstack/react-virtual` (the approved package —
87
+ still state the justification when adding it).
88
+ - Cloning/rebranding a reference site: strip every trace of the source tool's provenance
89
+ (class names, external asset domains, meta) so the result is fully self-contained, and
90
+ verify parity by clicking through as a real user — screenshot diffing alone doesn't count.
91
+
92
+ ## SEO and metadata (every public-facing app)
93
+
94
+ - Every route exports `metadata`/`generateMetadata` — not just the root layout. Public
95
+ apps ship `sitemap.ts` and `robots.ts`; `llms.txt` is generated at build time, never
96
+ hand-maintained. OG images follow the house convention (generated, mono for
97
+ names/numbers). i18n uses next-intl `localePrefix: 'as-needed'` (stack.md) so default-
98
+ locale URLs stay clean. **Why:** MK has demanded "100% SEO" coverage on shipped sites
99
+ twice — treat it as a completeness bar, not an enhancement.
100
+
101
+ ## Testing (web/backend — test where it pays off)
102
+
103
+ - Unit-test services and pure logic (Vitest); component/a11y tests run in Vitest browser
104
+ mode with the Playwright provider (real Chromium — jsdom's ARIA/layout gaps produce
105
+ false a11y results and are not trusted).
106
+ - E2E is a walking skeleton: one real happy path (boot → auth → core mutation → verify)
107
+ plus targeted adversarial invariants — not a blanket suite. DB-gated tests are authored
108
+ even when execution is blocked, and gated behind an env flag (`RUN_DB_TESTS=1` pattern);
109
+ E2E doubles implement real ports, never a parallel mock pathway.
110
+ - Every confirmed bug gets a regression test; no coverage-percentage targets — "100%"
111
+ means audit completeness, never a coverage metric. VRT/demo content is deterministic
112
+ (no `Date.now()`/`Math.random()`).
113
+
114
+ ## Backend / API design
115
+
116
+ - Contract-first: zod schemas are the single source → OpenAPI 3.1 under `/api/v1`, camelCase,
117
+ cursor pagination on every list, RFC 9457 problem-details errors with a stable error-code
118
+ catalog. One contract serves web, Flutter, MCP, and public consumers.
119
+ - Every route: auth before parse, through the shared route-handler wrapper — no route-local
120
+ zod, no raw `request.json()`. Sanitize responses: secrets and credential references never
121
+ leave the server. Enforce with a CI ratchet (unwrapped routes fail the build), not review.
122
+ - Return what the view renders, not everything — no mega-responses. Select columns explicitly.
123
+ - Body limits stream: count bytes as they arrive and abort at the limit; never buffer the
124
+ whole body first.
125
+ - Webhooks over polling; the database is the source of truth. Once provider state lands in
126
+ Postgres via webhook, don't re-poll the provider — and don't build intermediary "waiting"
127
+ pages; redirect on success and let the rest arrive in the background.
128
+ - Money paths (Stripe): isolated intake (own worker or route), idempotency keys on every
129
+ mutation, event dedupe, append-only ledgers as the single cost source of truth, audit
130
+ events written in the same transaction as the business change. Pricing/quoting math is
131
+ deterministic code with server-enforced invariants — AI drafts and judges; deterministic
132
+ code calculates and enforces (a proven client-project pattern; treat as the strong
133
+ default for money paths).
134
+ - Centralize the boring: one ID helper (never raw `crypto.randomUUID` scattered), one logger
135
+ (never `console.log`; request/org context auto-injected; never log secrets or PII beyond
136
+ user IDs), one typed fail-hard zod env parser validated at startup — no
137
+ `DISABLE_AUTH`-style escape hatches, no plaintext fallbacks.
138
+ - Dead endpoints get deleted, not deprecated-and-kept. (Exception: a versioned `/api/v1`
139
+ contract consumed by a shipped mobile app outlives web deploy cycles — app-store install
140
+ lag means mobile clients keep the old contract alive; see mobile.md.)
141
+ - Outbound webhooks to customers (when a product offers them): a defined event catalog,
142
+ Standard Webhooks-style signing with rotatable secrets, delivery via the transactional
143
+ outbox → queue with retries/backoff, auto-disable an endpoint after N consecutive
144
+ failures, and a replay surface. Never fire webhooks inline from request handlers.
145
+ - Platform-native mentions over plain text: when the data allows it, resolve and mention
146
+ the real entity (Slack user @mentions, Notion Person fields) instead of rendering names
147
+ as text — the recurring "don't just fix, improve" integration principle.
148
+ - Wizard/checkout flow-state persistence (Zustand + sessionStorage vs server-side draft)
149
+ is not yet a settled house rule — ask MK when building one.
150
+ - Rate-limited responses return 429 with a `Retry-After` header.
151
+ - Timestamps stored UTC (`timestamptz`), rendered in the user's timezone; durations as
152
+ integer milliseconds; money in integer minor units.