@vegastack/skills 0.4.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.
- package/README.md +4 -2
- package/dist/index.js +38 -45
- package/package.json +1 -1
- package/skill/architect/SKILL.md +68 -0
- package/skill/architect/agents/openai.yaml +4 -0
- package/skill/architect/assets/adr-template.md +21 -0
- package/skill/architect/assets/arch-template.md +20 -0
- package/skill/architect/references/advisory.md +102 -0
- package/skill/architect/references/ai-agents.md +95 -0
- package/skill/architect/references/data.md +90 -0
- package/skill/architect/references/infra.md +128 -0
- package/skill/architect/references/mobile.md +78 -0
- package/skill/architect/references/pinned-facts.md +108 -0
- package/skill/architect/references/principles.md +91 -0
- package/skill/architect/references/project-profile.md +37 -0
- package/skill/architect/references/security.md +97 -0
- package/skill/architect/references/stack.md +38 -0
- package/skill/architect/references/web.md +152 -0
- package/skill/architect/refresh/REFRESH.md +29 -0
- package/skill/architect/refresh/sources.json +244 -0
- package/skill/skill-maintainer/references/release-ops.md +11 -15
- package/skill/skill-maintainer/refresh/REFRESH.md +3 -3
- package/skill-integrity.json +20 -44
- package/skill/arch-guardian/SKILL.md +0 -84
- package/skill/arch-guardian/agents/openai.yaml +0 -4
- package/skill/arch-guardian/assets/adr-template.md +0 -25
- package/skill/arch-guardian/assets/answers-example.json +0 -10
- package/skill/arch-guardian/assets/architecture-profile.json +0 -13
- package/skill/arch-guardian/assets/architecture-profile.schema.json +0 -31
- package/skill/arch-guardian/assets/deployment-review-template.md +0 -24
- package/skill/arch-guardian/assets/service-design-template.md +0 -33
- package/skill/arch-guardian/assets/threat-model-template.md +0 -34
- package/skill/arch-guardian/references/advisory-report.md +0 -65
- package/skill/arch-guardian/references/architecture/agent-product.md +0 -22
- package/skill/arch-guardian/references/architecture/ai-cost.md +0 -24
- package/skill/arch-guardian/references/architecture/ai-data-boundaries.md +0 -21
- package/skill/arch-guardian/references/architecture/ai-evals.md +0 -28
- package/skill/arch-guardian/references/architecture/connectors-sandbox.md +0 -39
- package/skill/arch-guardian/references/architecture/data-memory.md +0 -25
- package/skill/arch-guardian/references/architecture/delivery-operations.md +0 -34
- package/skill/arch-guardian/references/architecture/durable-execution.md +0 -45
- package/skill/arch-guardian/references/architecture/flutter.md +0 -26
- package/skill/arch-guardian/references/architecture/foundation.md +0 -31
- package/skill/arch-guardian/references/architecture/hosting-reliability.md +0 -37
- package/skill/arch-guardian/references/architecture/identity-tenancy.md +0 -37
- package/skill/arch-guardian/references/architecture/model-lifecycle.md +0 -20
- package/skill/arch-guardian/references/architecture/models-observability.md +0 -23
- package/skill/arch-guardian/references/architecture/realtime-channels.md +0 -16
- package/skill/arch-guardian/references/architecture/security-privacy.md +0 -27
- package/skill/arch-guardian/references/architecture/topology-monorepo.md +0 -47
- package/skill/arch-guardian/references/architecture/web.md +0 -29
- package/skill/arch-guardian/references/foundation-compatibility.json +0 -44
- package/skill/arch-guardian/references/golden-patterns.md +0 -43
- package/skill/arch-guardian/references/profile-governance.md +0 -40
- package/skill/arch-guardian/references/rule-model.json +0 -36
- package/skill/arch-guardian/references/workflows.md +0 -48
- package/skill/arch-guardian/refresh/REFRESH.md +0 -47
- package/skill/arch-guardian/refresh/sources.json +0 -1171
- package/skill/arch-guardian/scripts/lib.mjs +0 -48
- package/skill/arch-guardian/scripts/profile-tool.mjs +0 -217
- package/skill/arch-guardian/scripts/refresh-evidence.mjs +0 -366
- package/skill/arch-guardian/scripts/schema-validate.mjs +0 -63
- package/skill/arch-guardian/scripts/validate-profile.mjs +0 -65
- package/skill/arch-guardian/scripts/verify-corpus.mjs +0 -136
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# Infra — hosting, CI/CD, observability
|
|
2
|
+
|
|
3
|
+
## Hosting targets
|
|
4
|
+
|
|
5
|
+
Two sanctioned targets, recorded per project in `.vegastack/arch.md`:
|
|
6
|
+
|
|
7
|
+
- **Cloudflare Workers via OpenNext** — the default for public web products. Once a project
|
|
8
|
+
commits to Cloudflare, never stand up a parallel Vercel deploy path (deploy paths drift).
|
|
9
|
+
Shared packages must still *work* on Vercel for downstream consumers — that's portability,
|
|
10
|
+
not a deploy target.
|
|
11
|
+
- **Self-managed servers** (Hetzner-class, Docker; Coolify for push-to-deploy) — when
|
|
12
|
+
self-hosting is a product requirement, the workload needs long-lived processes (EVE, heavy
|
|
13
|
+
workers), or platform independence justifies ~30 min/month of real ops.
|
|
14
|
+
- Vercel only by explicit, recorded per-project exception (e.g. EVE-hosted workloads).
|
|
15
|
+
Internal/admin apps that need no edge features may run plain `next start` in Docker —
|
|
16
|
+
OpenNext is the default, not a ritual.
|
|
17
|
+
|
|
18
|
+
## Cloudflare discipline
|
|
19
|
+
|
|
20
|
+
- Provision only what the current phase uses. Queues, KV, Durable Objects, Workflows are
|
|
21
|
+
all trigger-gated: DO when live collaboration/presence actually ships (hibernating
|
|
22
|
+
WebSockets make it cheap then — see pinned-facts); Queues when webhook/event volume is
|
|
23
|
+
real; never scaffolded ahead of need. Delete stale Workers, Hyperdrive configs, and
|
|
24
|
+
buckets — with an explicit create/delete accounting before touching anything, and
|
|
25
|
+
`wrangler` used carefully.
|
|
26
|
+
- Multi-env via the Workers environments feature (`env.*`) — never separate top-level
|
|
27
|
+
Workers per environment. One Hyperdrive per environment, shared by every service hitting
|
|
28
|
+
the same database.
|
|
29
|
+
- Naming: hyphen-only across all Cloudflare resources; purge inconsistent legacy names.
|
|
30
|
+
- Domains: production on the `vegastack.com` apex/subdomains; non-prod environments under
|
|
31
|
+
`*.vegastack.dev`. Every Worker sets `workers_dev: false` + `custom_domain: true` — the
|
|
32
|
+
default `*.workers.dev` origin bypasses Cloudflare Access, so leaving it on exposes a
|
|
33
|
+
gated Worker unauthenticated (this is security, not cosmetics). Preview environments are
|
|
34
|
+
access-protected by default; only production is public.
|
|
35
|
+
- Environments are `dev` / `preview` / `production` via the Workers `env.*` feature; the
|
|
36
|
+
`staging` git branch deploys to the `preview` environment — there is no fourth Worker
|
|
37
|
+
environment unless a project records one. Secrets are set per environment
|
|
38
|
+
(`wrangler secret put X --env production`). Local env files: `.env` feeds `next dev`,
|
|
39
|
+
`.dev.vars` feeds `wrangler dev`/Miniflare — both gitignored, both with committed
|
|
40
|
+
`.example` companions.
|
|
41
|
+
- No Cloudflare Images — optimize with `sharp` at build/upload time and serve from R2 (the
|
|
42
|
+
paid add-on solves a problem `sharp` + free egress already solve).
|
|
43
|
+
- Runtime constraints are architectural facts: no TCP clients, per-request DB connections
|
|
44
|
+
(data.md), and OpenNext trails vanilla Next.js — check OpenNext docs per feature instead
|
|
45
|
+
of assuming. Two dated specifics: Turbopack is supported since adapter v1.15.0 (the old
|
|
46
|
+
breakage is fixed; re-verify only on older pins); `proxy.ts`/Node middleware does NOT
|
|
47
|
+
work on OpenNext Cloudflare as of 2026-08 (open issues opennextjs-cloudflare#962/#1277,
|
|
48
|
+
workers-sdk#13755/#13937) — don't design a Cloudflare-hosted feature around `proxy.ts`;
|
|
49
|
+
re-check the trackers before assuming it shipped.
|
|
50
|
+
- Edge/CDN caching never bypasses live authorization — revoked, expired, or
|
|
51
|
+
password-protected content is re-checked even when the artifact is cached.
|
|
52
|
+
- Cloudflare Tunnel is the default for exposing self-hosted services and remote dev
|
|
53
|
+
previews (already paid for via the Workers plan). Cloudflare Access gates paths by data
|
|
54
|
+
sensitivity with email allowlists — not one site-wide gate.
|
|
55
|
+
|
|
56
|
+
## Self-managed discipline
|
|
57
|
+
|
|
58
|
+
- Containers hardened: non-root user, `cap_drop: [ALL]`, `read_only: true` where runtime
|
|
59
|
+
paths allow; healthchecks on every service. Migrations and storage provisioning run in
|
|
60
|
+
the app container's own entrypoint under a Postgres advisory lock (safe when replicas
|
|
61
|
+
race on boot) — not a separate bootstrap deployable, unless a project records a concrete
|
|
62
|
+
reason for one (the shipped self-host pattern deliberately avoids an extra init
|
|
63
|
+
container).
|
|
64
|
+
- Ship `docker compose up` as the self-host story: bundle only Postgres + MinIO-class
|
|
65
|
+
essentials; everything else (vector store, gateway, vault) is connect-your-own.
|
|
66
|
+
- Server sizing: SSD, compute, and RAM over disk capacity, optimized for ROI within the
|
|
67
|
+
budget MK states for that server (ask if none was stated — don't guess one); Coolify
|
|
68
|
+
itself needs ~2GB RAM and real patching (pinned-facts). Cloud infra beyond Cloudflare
|
|
69
|
+
goes through Terraform, IAM scoped narrowly per purpose and region.
|
|
70
|
+
- Any infra cleanup (disks, stale resources, runners) needs explicit authorization first,
|
|
71
|
+
scoped to verified-stale items — never blanket cleanup.
|
|
72
|
+
|
|
73
|
+
## CI/CD
|
|
74
|
+
|
|
75
|
+
- Never commit, tag, push, merge, publish, deploy, or create paid/cloud resources without
|
|
76
|
+
MK's explicit go-ahead for that step — this holds even in fully autonomous runs (it is
|
|
77
|
+
the red line, stated identically in SKILL.md).
|
|
78
|
+
- Build passes before any commit. When committing: review the complete uncommitted diff
|
|
79
|
+
(not just this session's), draft a conventional-commit message covering all of it, wait
|
|
80
|
+
for approval.
|
|
81
|
+
- Package manager is locked per project — read it from the repo, pin the exact version
|
|
82
|
+
(corepack/bun), never silently substitute.
|
|
83
|
+
- Branch protection on main/staging/develop (no force-push), branch-naming enforced
|
|
84
|
+
server-side and in a pre-push hook. DB migrations apply via CI only (data.md).
|
|
85
|
+
- CI is cost-managed, not maximal: path-condition expensive jobs (VRT, contract tests) so
|
|
86
|
+
docs/skills-only changes skip them; push heavy verification into local pre-commit/pre-push
|
|
87
|
+
hooks and self-hosted runners; hosted Actions minutes are a tracked budget. Verification
|
|
88
|
+
is local-first — CI verifies that it happened, via a committed receipt (a signed
|
|
89
|
+
`.gates/receipt.json` bound to the git tree hash) rather than re-executing browser
|
|
90
|
+
suites. A receipt is attestation, not proof — skipping a gate becomes visible, not
|
|
91
|
+
impossible.
|
|
92
|
+
- Wire every architectural guard script (import boundaries, runtime gravity, route-wrapper
|
|
93
|
+
ratchet) into the one composed check command — a separate script someone forgets to run
|
|
94
|
+
doesn't exist.
|
|
95
|
+
- Green CI is necessary, never sufficient: a unit is done after a real
|
|
96
|
+
boot → auth → reach → mutate → verify pass against live infra. On CI failure:
|
|
97
|
+
diagnose and report the root cause first; fix second.
|
|
98
|
+
- Releases via Changesets + GitHub Actions. MK enters npm OTP/2FA and other credentials
|
|
99
|
+
himself — never the agent. Node 24 standard; deprecated Actions versions are routine
|
|
100
|
+
housekeeping, fixed proactively.
|
|
101
|
+
|
|
102
|
+
## Observability (small-team baseline)
|
|
103
|
+
|
|
104
|
+
- Structured JSON logs (pino) to stdout with OTel-shaped fields (trace/request/org context
|
|
105
|
+
auto-injected) — OTel-compatible by convention now, full OTel SDK/collector only when
|
|
106
|
+
cross-service trace correlation becomes a real pain, not before.
|
|
107
|
+
- The append-only Postgres event log is the authoritative telemetry source; metrics/log
|
|
108
|
+
tables are derived and disposable (30-day raw, 13-month rollups, compress after ~7 days;
|
|
109
|
+
TimescaleDB opportunistic with partition+BRIN fallback).
|
|
110
|
+
- On Cloudflare, use the built-in free tier first: Workers Logs (200k/day, 3-day
|
|
111
|
+
retention) and the analytics dashboard cost nothing; note Workers tracing starts billing
|
|
112
|
+
2026-10-01, and OTLP export needs Workers Paid.
|
|
113
|
+
- Alerting honest to team size: an uptime monitor (Uptime Kuma-class) + a cron that checks
|
|
114
|
+
error-rate thresholds in the event table and posts to Slack, plus a dead-man's-switch
|
|
115
|
+
ping for silent job failures. No paging/SLO/on-call ceremony — that practice is
|
|
116
|
+
deliberately undefined until the team needs it; propose per project, don't invent doctrine.
|
|
117
|
+
|
|
118
|
+
## Incidents
|
|
119
|
+
|
|
120
|
+
- Active production incident: diagnose first (root cause with evidence), fix second — the
|
|
121
|
+
standing diagnose-before-fix rule applies under pressure too. Rolling back a deploy IS a
|
|
122
|
+
deploy: it still needs MK's go-ahead — reach him with the evidence and the recommended
|
|
123
|
+
rollback rather than acting; mitigation that doesn't deploy (feature-level disable via
|
|
124
|
+
config, traffic block) can proceed and be reported.
|
|
125
|
+
- Every real incident gets a short postmortem in the project's `docs/postmortems/`
|
|
126
|
+
(established precedent): what happened, root cause, the guard that now prevents it.
|
|
127
|
+
Codify the recurring correction (advisory.md) — an incident that doesn't change a rule
|
|
128
|
+
or a check will repeat.
|
|
@@ -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.
|