Reposition around team collisions, and run the API on a function platform - #34
Merged
Merged
Conversation
The landing page had drifted to solo-first -- "You don't need a team" in the hero, a FAQ entry calling team-necessity "the most common misread" -- which contradicts the product-scope decision in BUILD_INSTRUCTIONS.md and describes a user the product is not shaped for. Invites, roles, seats, presence, claims, handoffs, and per-workspace autonomy policy are all team machinery. The audience is shared projects whose members all run coding agents. Working alone today still counts when the project is shared -- teammates' agents will land work on it again, and the proposals waiting on return are the point. A repository only one person will ever touch is explicitly out of scope; there is nothing to coordinate with and plain Git is the better tool. Rewrites the hero, subtitle, hero microcopy, comparison table, benefits heading, use-case card, and pricing intro on the landing page; replaces the "do I need a team" FAQ with "what if I'm the only one working on it today"; rewrites README's "Who it's for"; and sharpens the product-scope decision plus Phase 10's design intent so the doc and the site finally agree. Also adds createRequestHandler() to the service and a serverless adapter, so a function platform can run the same routing and auth rather than a forked copy. Incomplete on purpose: broadcasts become no-ops without a persistent process, and the in-memory rate limiter counts per instance -- the pairing-code limit is a brute-force defense over a 40-bit code space and must move to the database before any serverless deployment is real. Verified: pnpm build, pnpm test (304 passed). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…limits Adds a serverless path so the coordination service can run as Vercel functions alongside the marketing site instead of needing its own always-on host. apps/service/src/main.ts stays the persistent-process entrypoint self-hosters run; this is an addition, not a replacement, and both share one router. - createRequestHandler() exposes the route handler without a Node server wrapped around it, so the platform's (IncomingMessage, ServerResponse) pair runs the identical routing and auth rather than a forked copy. - serverless.ts builds store + JWKS once per instance and passes a no-op broadcast gateway, because a function has nowhere to hold /v1/stream open. Live push therefore degrades to the daemon's existing poll until Supabase Realtime carries the notification instead. - apps/docs-site/api/[...path].ts plus vercel.json route /api/* into it. The Vercel project's root directory is already apps/docs-site, so the site build is untouched. The part that is not optional: an in-memory counter gives every instance its own full budget, which turns the pairing-claim throttle -- the brute-force defence over a 40-bit code space -- into N x 10/min for N warm instances. Migration 012 adds a rate_limits table and takeRateLimit() spends the budget in one statement, so concurrent instances cannot interleave a read and a write and both conclude they are under the limit. Enabled via durableRateLimits, on for serverless and off for a single persistent process, where it would only buy a round-trip per request. Other routes stay in memory deliberately: being approximate about a courtesy limit costs nothing. Verified: pnpm build, pnpm test (304 passed), pnpm test:postgres (24 passed, including one that fails without the single-statement counter). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this changes
Docs and marketing site: repositions Crosscode around its actual audience — shared projects whose team members all run coding agents — replacing landing-page copy that had drifted to solo-first ("You don't need a team" in the hero, a FAQ entry calling team-necessity "the most common misread"). Rewrites the hero, subtitle, comparison table, benefits heading, use-case card, pricing intro and two FAQ entries; rewrites README's "Who it's for"; and sharpens the product-scope decision and Phase 10 design intent in
BUILD_INSTRUCTIONS.mdso the doc and the site finally agree.Coordination service: adds a serverless path so the API can run as functions alongside the marketing site instead of needing its own always-on host.
createRequestHandler()exposes the router without a Node server around it,serverless.tsbuilds store + JWKS once per instance with a no-op broadcast gateway, andapps/docs-site/api/[...path].tsplusvercel.jsonroute/api/*into it.apps/service/src/main.tsremains the persistent-process entrypoint self-hosters run — this is an addition, and both paths share one router rather than forking route logic. Migration 012 adds arate_limitstable with a single-statementtakeRateLimit().Why
The site was describing a user the product is not shaped for: invites, roles, seats, presence, claims, handoffs and per-workspace autonomy policy are all team machinery. Working alone still counts when the project is shared — teammates' agents will land work on it again — but a repository only one person will ever touch has nothing to coordinate with, and plain Git is the better tool.
On the service side, running the API from the existing Vercel project removes an always-on host, a Docker deployment, and the
api.DNS record from the critical path. The durable rate limiter is not optional for that: an in-memory counter gives every instance its own full budget, which would turn the pairing-claim throttle into N x 10/min for N warm instances.Testing
pnpm buildpassespnpm testpasses (304 passed)pnpm test:postgrespasses (24 passed)Three new Postgres tests cover the durable limiter: one budget spent across callers with buckets kept independent, the limit holding when the same bucket is spent concurrently (this one fails against a read-then-write implementation), and window rollover plus pruning.
Note for reviewers running the Postgres suites locally: pick your own container name and port. Several agents are working this repo concurrently on one Docker daemon, and reusing the default means
docker runsilently fails to bind and the suite connects to somebody else's schema.Security / trust-boundary impact
Yes — one item, deliberately, and it is the reason migration 012 exists.
POST /v1/pairing-codes/claimis unauthenticated, so its per-IP throttle is the only thing standing between an attacker and brute-forcing a 40-bit code space (Contract A). That budget is now spent against the database whendurableRateLimitsis set, in oneINSERT ... ON CONFLICT DO UPDATEso concurrent instances cannot interleave a read and a write and both conclude they are under the limit. It is on for serverless and off for a single persistent process, where it would only buy a round-trip per request. Every other route stays in memory on purpose: being approximate about a courtesy limit costs nothing.The
rate_limitstable has RLS enabled with no policies, which denies every non-service role by default — it is service-internal bookkeeping no member ever selects from. Auth, RLS on member-facing tables, checkpoint/materialization safety and excluded-path handling are untouched.One known gap, called out rather than hidden: a function platform cannot hold
/v1/streamopen, so broadcasts become no-ops and live push degrades to the daemon's existing unconditional poll until Supabase Realtime carries the notification instead. Nothing breaks — the daemon already polls on a timer — but "instant" becomes "next poll" on a serverless deployment.Related issues
Follows #32 and #33.