Replit Agent can generate the first version of a customer feature feedback board. The useful test is whether the finished app keeps requests and votes after reload, lets a moderator decide what appears publicly, and refuses changes from the wrong account. This guide gives a SaaS owner or product lead a build brief and a checkable server contract for that one app.
You need authority to choose the board's audience and data policy, a Replit project with Agent, managed Clerk Auth, Database and Publishing available, and permission to approve its release and costs. Arrange two customer test identities and a separate moderator identity. Use synthetic requests in development. The target is a public board: guests can read approved requests, signed-in customers can submit and vote, and a moderator reviews visibility. A private board needs a different read and hosting policy.
As of 22 October 2026, Replit documents Agent and Plan mode, managed Clerk Auth, SQL Database and Publishing. The instructions below combine those documented platform controls with an original proposed application specification. BIG CHANGE did not build, run, scan or deploy this board. Every response and result described as expected needs a real check in your own generated app.
If you already have a working dashboard and only need to check its first release, see our separate Vercel dashboard guide. This guide begins with the feedback board itself and its distinct account, vote and moderator rules.
The big change
- What changed: Replit documents an Agent workflow with managed app identity, SQL and publishing. A product team can specify a complete customer feedback board for AI generation, although the documentation does not establish that the generated app meets this contract.
- Why it matters: Requests and votes are customer records. Verified sessions, database transactions and moderator checks give the owner concrete ways to test who may change them and when pending text becomes public.
- What to watch: The release decision turns on the actual generated routes and separate development and production identities and databases. Keep the first host release Invite only until observed allowed and denied checks at its live URL match the reviewed revision.
The account that signs into the app is a Clerk user. Replit's publishing access is a separate door in front of the app. A database transaction means several reads and writes either complete together or leave no partial change. A revision is the request's version number; it lets the server reject an approval of text that has since changed. These distinctions matter when you review Agent's code.
1. Set the brief and review Agent's plan
Decide which real customers may use the board, what they may submit, how long records may be retained, who can moderate, and what release audience and spending limit you approve. Keep customer records, secrets, proprietary roadmaps and unlicensed assets out of the prompt. In a new Replit web-app project, select Plan mode and paste the brief below after replacing its two bracketed values. Replit's prompting guidance recommends explicit goals and iterative review; Plan mode creates ordered tasks before code changes. Check the mode and account usage before any paid Agent action. Accept the plan only if it covers managed identity, persistent SQL, server permissions, the complete board workflow and tests. Save the plan and a checkpoint.
Copyable Agent brief
Replace the bracketed product name and audience with your approved values. Submit this as the first Plan-mode request, review the plan, then accept only when it covers data, identity, server authorization and release checks. Reuse the same text as the implementation contract; do not ask Agent to silently invent missing policy.
Build a responsive web app named [PRODUCT] for [AUTHORIZED AUDIENCE]: a public feature feedback board. Use Replit-managed Clerk Auth for signup/signin, Replit PostgreSQL for persistent records, and a server API. Clerk's signup and verification messages are part of authentication; do not add a separate app-generated email send, runtime LLM call, payment system, external connector or analytics unless I separately approve it.
Data: feature_requests(id UUID, author_clerk_id TEXT, title TEXT 5..120 chars, description TEXT 20..2000 chars, status ENUM proposed/planned/in_progress/shipped/declined, visibility ENUM pending/public/hidden, revision BIGINT starting 1, created_at, updated_at); votes(request_id UUID, voter_clerk_id TEXT, created_at) with one vote per user/request; moderators(clerk_user_id TEXT primary key). Store only the minimum identity needed for ownership. Do not trust a role, author ID, vote count, status or visibility supplied by the browser. Accept revision only as a conflict-check value. Define the schema in a reviewed migration. Server returns computed vote counts; do not let a client set them.
Routes and authorization: GET /api/requests and GET /api/requests/:id return only public rows to guests and ordinary users. They use an explicit response allowlist: id, title, description, status, revision, createdAt, updatedAt and computed voteCount. Never serialize author IDs, voter IDs, moderator IDs, pending/hidden text or internal fields on these routes. GET /api/me/requests and GET /api/me/requests/:id require a verified session and return only the caller's rows, including pending/hidden; a guessed other-user ID yields 404. GET /api/moderation/requests and GET /api/moderation/requests/:id are moderator-only and include pending/hidden review rows and exact revision. POST /api/requests requires a verified session and starts status=proposed, visibility=pending, author ID from session. PATCH /api/requests/:id lets only the author change title/description while status=proposed and visibility is not hidden. It requires expected revision. If the row was public, the edit atomically resets visibility to pending, deletes old votes and increments revision; public listing/detail and caches cease to expose it until a moderator reviews the new text. DELETE /api/requests/:id withdraws only the author's proposed row at the expected revision and cascades its votes. POST /api/requests/:id/vote requires a verified user and a currently public request; DELETE /api/requests/:id/vote may remove that user's own vote even after moderation hides it. Both use the server-derived Clerk ID and are idempotent. Moderator-only PATCH /api/moderation/requests/:id changes status/visibility only at the exact reviewed expected revision; stale review returns 409. No browser-supplied role, author, voter, voteCount, status or visibility may elevate access. Reject unrecognized fields and invalid enum values. See the implementation contract below for atomic SQL and exact responses.
UI: public board with status filters, vote counts and request detail; sign-in; private **My requests** list/detail including pending and hidden entries; request submission and own-request edit/withdraw; vote/unvote state; moderator review queue and status/visibility controls; loading, empty and error states; keyboard-accessible forms and controls. Do not expose pending/hidden descriptions in public HTML, API responses, search or cached data. Disable public caching for this first version or explicitly purge/invalidate listing, detail and count caches on moderation/edit/withdraw; prove the choice with fresh unauthenticated requests. Use a safe text renderer; never interpret request text as HTML.
Build in stages with checkpoints: (1) schema/auth and server ownership checks, (2) complete public and own-request CRUD plus votes, (3) moderator flow, (4) UI, (5) tests. Preserve the Replit-managed Clerk proxy before body parsers, then Clerk Express middleware before handlers; use verified session auth as described in the implementation contract below. Show the exact files, dependency versions and route implementations. Run the project's build/tests and report actual outputs. Add authorization tests for guest, author, other user and moderator; guessed IDs, forged identity, expired session and wrong Origin; duplicate/concurrent votes; moderation racing author edit/vote; pending/hidden public/cache absence; own pending reload; invalid input. If a test cannot run, report it as untested. Stop before Publish. Do not put secrets in client code or logs.The schema and permissions to inspect
The schema above is a contract, not an assertion about generated code. A reviewer must compare the generated migration and actual database to it. If Agent uses an ORM, its generated migration must enforce the unique pair (request_id, voter_clerk_id) in PostgreSQL, not merely in UI code. A request belongs to the author_clerk_id from a verified server session. moderators must be written only through the owner's authenticated database administration, never through a public route or user-editable profile field. Record who inserted each moderator and the intended account. Development and production Clerk user IDs differ; an owner must create the production account and deliberately seed its actual ID in the production database after a restricted first publish. Do not copy a development moderator ID into production.
Here is the minimum PostgreSQL migration to compare with or hand to Agent. The application server creates UUIDs and timestamps for new requests, sets author_clerk_id from its verified Clerk session, and updates updated_at in its request edit/moderation handlers. A production schema migration requires its own preview and owner review; never run this blindly against an existing database.
CREATE TABLE feature_requests (
id uuid PRIMARY KEY,
author_clerk_id text NOT NULL,
title text NOT NULL CHECK (char_length(title) BETWEEN 5 AND 120),
description text NOT NULL CHECK (char_length(description) BETWEEN 20 AND 2000),
status text NOT NULL DEFAULT 'proposed'
CHECK (status IN ('proposed', 'planned', 'in_progress', 'shipped', 'declined')),
visibility text NOT NULL DEFAULT 'pending'
CHECK (visibility IN ('pending', 'public', 'hidden')),
revision bigint NOT NULL DEFAULT 1 CHECK (revision >= 1),
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now()
);
CREATE TABLE votes (
request_id uuid NOT NULL REFERENCES feature_requests(id) ON DELETE CASCADE,
voter_clerk_id text NOT NULL,
created_at timestamptz NOT NULL DEFAULT now(),
PRIMARY KEY (request_id, voter_clerk_id)
);
CREATE TABLE moderators (
clerk_user_id text PRIMARY KEY
);
CREATE INDEX feature_requests_public_order
ON feature_requests (created_at DESC)
WHERE visibility = 'public';The SQL does not confer route authorization. The generated server must enforce the role matrix on every request, and its database connection must remain server-side. For moderator bootstrap, obtain the exact user ID from the correct Clerk environment and insert that ID into the corresponding database through the owner's authorized database tool. Read back that row and test an ordinary user is still denied. Do not publish a reusable moderator-seeding endpoint.
The UI is a client of the API. For each listed route, read the actual handler and authentication middleware. Test its HTTP behavior directly, because hiding a button does not stop a request. Count reads must filter hidden/pending requests for public callers. Define deletion/withdrawal behavior before data is collected; if the app keeps a private tombstone for audit, the UI and public API must omit it. Error and empty states are part of completion.
The first production release should use Replit Publishing's Invite only host access while production users and database are checked. The authorized test participants must be invited through the Replit host gate and have their separate Clerk app identities. Replit says changing host access later requires unpublish and republish. The owner should plan that step and verify the app is public only after approved launch. Host access does not replace Clerk session and server authorization. If Invite only is unavailable or the intended testers cannot pass that gate, record HOLD for this route; do not substitute an undefined public prelaunch release.
Keep an acceptance record
Record project URL/revision or checkpoint, generated migration hash, route file hashes, auth environment, production deployment URL/time, and account identities used in tests (redacted in a public article). For each check record pass, fail or untested with actual response or screen evidence: signed-out public listing; signed-out create denial; own pending request after reload; guessed other-user ID denial; approved-then-edited request returning to pending with votes cleared; moderator review of that exact revision; one vote per user; vote removal; concurrent duplicate voting; moderator-versus-vote/edit races; ordinary-user moderation denial; wrong-origin, forged-ID and expired-session denial; pending/hidden absence in public API/HTML/cache; invalid input; production database separation; live allowed and denied access. The completion signal is a live approved request visible to the intended audience with one correct vote, one moderator-approved status change and all forbidden effects denied, tied to the reviewed deployment. This is a test plan, not an observed result.
2. Inspect the generated server and run request checks
This is a reviewable candidate specification for the reader's generated app, not code BIG CHANGE ran or a security certification. The reader compares it to the actual generated framework and installed SDK version. Replit currently documents an Express server behind its managed Clerk integration; if Agent generates a different stack, it must provide the equivalent verified-session, same-origin and atomic database behavior before this route can be accepted. Keep the exact generated managed proxy, managed key switching and secrets; do not substitute an external Clerk tenant or hand-edit keys.
Auth and request order
For a generated Express app, inspect its actual server entrypoint and installed @clerk/express version. Replit's managed Clerk proxy must remain before body parsers. Clerk's current Express manual documents clerkMiddleware() and getAuth(); requireAuth() is deprecated in the current reference. The following is wiring pseudocode with real Clerk method names; existingReplitManagedClerkProxy means the integration Agent actually generated, not a copyable new import or an instruction to recreate it.
app.use(existingReplitManagedClerkProxy); // keep its generated path and order
app.use(clerkMiddleware({ authorizedParties: exactApprovedOrigins }));
app.use(express.json({ limit: '32kb' }));
function sessionUserId(req, res): string | null {
const auth = getAuth(req); // only after Clerk middleware
if (!auth.isAuthenticated || !auth.userId || !auth.sessionId) {
res.status(401).json({ error: 'sign_in_required' });
return null;
}
return auth.userId; // never req.body.userId, email, header or editable metadata
}
function requireMutationOrigin(req, res): boolean {
const origin = req.get('Origin');
if (!origin || !exactApprovedOrigins.includes(origin)) {
res.status(403).json({ error: 'origin_denied' });
return false;
}
return true;
}exactApprovedOrigins is an explicit list of this app's actual development preview and final published HTTPS origin, maintained separately per environment. No wildcard, suffix match or * CORS response. Set Clerk authorizedParties to the same intended origins, as its production guide recommends; this guards the token audience/origin boundary. For this browser-only recipe require an Origin header on every POST/PATCH/DELETE and allow only a literal match. Reject cross-origin preflight and credentialed CORS from other sites. GET routes do not change state. If the generated framework needs cross-origin or machine clients, that is a separate auth/CSRF design and HOLD here. Do not claim authorizedParties alone supplies all app CSRF protection. The route accepts only an authenticated user session; API keys, M2M tokens, OAuth access tokens or a client-provided user ID are not alternative identities. Verify the installed middleware's actual token acceptance rather than broadening it. Expired/revoked sessions return 401. Never log session tokens, cookies, managed keys or full request bodies.
Add GET /api/me/identity: after sessionUserId, return only { userId } to that caller, Cache-Control: no-store. It is a fallback when the Replit Auth pane does not reveal an exact Clerk user ID. It cannot assign moderator status. A moderator check is a server-side parameterized query against moderators for this derived ID; a button, profile metadata or browser role is never authority.
Public and own reads
All API and HTML routes must send Cache-Control: no-store in this first version, including errors; disable framework static/SSR caches for the board. Do not pass database rows wholesale to JSON serializers. GET /api/requests and public detail query visibility = 'public' before count aggregation and return only {id,title,description,status,revision,createdAt,updatedAt,voteCount}. A missing, pending or hidden ID returns 404 for a public detail. Guest reads use the same allowlist. GET /api/me/requests filters author_clerk_id = sessionUserId and includes the caller's pending/public/hidden requests, with visibility and edit eligibility but no other user's records. Own detail combines both ID and author filter and returns 404 on a guessed other-user ID. GET /api/moderation/requests first checks moderator membership, then includes pending/hidden rows and exact revision for review. Never put pending/hidden descriptions in public server HTML, search metadata, preloaded JSON, CDN cache, logs or an error body.
Atomic mutation rules
Use bound SQL parameters, a single database connection per transaction, BEGIN, SELECT ... FOR UPDATE on the request row, and COMMIT or ROLLBACK. The schema's primary key on (request_id,voter_clerk_id) is essential, but it does not by itself decide HTTP idempotence or stop a vote racing visibility changes. Client form fields are allowlisted. All writes require requireMutationOrigin and sessionUserId before a transaction; moderator membership is checked from SQL, never from a client claim. Validate IDs as UUIDs, title/description lengths, enums and positive integer expectedRevision before SQL. Invalid input returns 400 (schema checks are a final backstop). Database failure rolls back and returns an error without claiming a committed effect. After an uncertain response, read the request/vote rows before retrying.
Create (POST /api/requests, JSON {title,description}): server generates UUID and session-derived author ID; SQL inserts default proposed, pending, revision=1; return 201 {id,revision:1,visibility:'pending',ownUrl:'/api/me/requests/<id>'} after commit. An unknown field such as author_clerk_id, role or visibility gets 400.
Vote (POST /api/requests/:id/vote, empty JSON body): in a transaction lock the request row with SELECT id FROM feature_requests WHERE id=$1 FOR UPDATE; if absent or no longer public, rollback and return 404. Then run INSERT INTO votes(request_id,voter_clerk_id) VALUES($1,$2) ON CONFLICT (request_id,voter_clerk_id) DO NOTHING RETURNING request_id; count rows for that request; commit. Return 201 {voted:true,voteCount} if inserted, or 200 with the same shape if the vote already existed. Two concurrent requests serialize on the request row; the composite key remains the last guard. Moderator hide/edit/withdraw use the same row lock. If hide wins first, vote gets 404; if vote wins first, hide commits after it and public reads get 404. No hidden description or vote list is returned.
Unvote (DELETE /api/requests/:id/vote, no body): lock the request row even if it is now pending/hidden, so a user may remove their own vote. Missing request returns 404. DELETE FROM votes WHERE request_id=$1 AND voter_clerk_id=$2; commit and return 204 whether one or zero own vote rows existed. Never delete another user's vote. The public count, if available, is read fresh from SQL.
Author edit (PATCH /api/requests/:id, JSON {title,description,expectedRevision}): lock the row. If absent or author ID differs, 404. If status is not proposed, visibility is hidden, or revision differs, 409 {error:'stale_or_closed'} without a write. Otherwise update only title/description, set visibility=pending, increment revision, set updated_at=now(), then DELETE FROM votes WHERE request_id=$1 in the same transaction; commit and return 200 {id,revision:newRevision,visibility:'pending',voteCount:0}. This policy deliberately clears old votes when public text changes. Because public routes are uncached and require visibility='public', the revised content is absent until separately reviewed. An edit racing a moderator update serializes; the later actor's expected revision fails 409, so a moderator cannot approve a stale text revision.
Withdraw (DELETE /api/requests/:id, JSON {expectedRevision}): lock row; other owner/absent 404; non-proposed, hidden or stale revision 409. DELETE FROM feature_requests WHERE id=$1; the FK cascades votes. Commit 204. No public tombstone or detail remains. If the owner needs retention/audit instead of deletion, this route requires a new reviewed schema and policy before implementation.
Moderate (PATCH /api/moderation/requests/:id, JSON {status,visibility,expectedRevision}): check membership with SELECT 1 FROM moderators WHERE clerk_user_id=$1 using the verified session ID; nonmember 403. Require both status and visibility and allowlisted enum values. Lock request row; absent 404, stale revision 409 {error:'stale_review'}. Set only status, visibility, revision=revision+1, updated_at=now(); commit 200 {id,status,visibility,revision}. The reviewer must inspect the request's current text and revision first. A public transition is an explicit approval of that revision; the server never auto-publishes an author edit. Hiding a row blocks new votes and public reads immediately after commit; retained votes stay private and are cleared if the author later edits under the allowed policy.
Use 403 for a signed-in nonmoderator at moderation endpoints and wrong Origin; 401 for missing/expired session. Use 404 on public/nonowner detail or mutation IDs to avoid exposing existence. Use 409 for stale or closed author/moderator revisions. Use 400 for malformed UUID, invalid body or unknown fields. Return only the fields above, not SQL RETURNING *. Prefer real SQL row counts and ON CONFLICT results over a browser-maintained counter. If Agent emits a different database abstraction, locate its transaction and locking equivalents and check the resulting SQL; otherwise hold the build.
The following SQL kernels make the transaction boundaries concrete. Run each operation using one checked-out database connection, bound parameters and an application branch on the locked row. COMMIT only when the stated conditions and row counts match; any error or mismatch rolls back. The numbered values are supplied by the server after validating the body and verified session. They are not literal SQL to paste into a database console. For count/response, select explicit columns after the mutation and never serialize a whole row.
-- Vote: $1=request UUID, $2=verified Clerk user ID
BEGIN;
SELECT id, visibility FROM feature_requests WHERE id=$1 FOR UPDATE;
-- App: absent or visibility <> 'public' => ROLLBACK, HTTP 404
INSERT INTO votes(request_id, voter_clerk_id) VALUES($1,$2)
ON CONFLICT (request_id, voter_clerk_id) DO NOTHING
RETURNING request_id;
SELECT count(*) AS vote_count FROM votes WHERE request_id=$1;
COMMIT;
-- One RETURNING row => 201; zero => 200. Both return fresh count.
-- Unvote: $1=request UUID, $2=verified Clerk user ID
BEGIN;
SELECT id FROM feature_requests WHERE id=$1 FOR UPDATE;
-- App: absent => ROLLBACK, HTTP 404; hidden/pending may continue
DELETE FROM votes WHERE request_id=$1 AND voter_clerk_id=$2;
COMMIT;
-- 204 whether deletion count is 0 or 1.
-- Author edit: $1=request UUID, $2=verified Clerk author ID,
-- $3=expected revision, $4=validated title, $5=validated description
BEGIN;
SELECT author_clerk_id, status, visibility, revision
FROM feature_requests WHERE id=$1 FOR UPDATE;
-- App: absent/wrong author => ROLLBACK, 404;
-- status <> proposed, hidden, or revision <> $3 => ROLLBACK, 409
UPDATE feature_requests SET title=$4, description=$5,
visibility='pending', revision=revision+1, updated_at=now()
WHERE id=$1 AND author_clerk_id=$2 AND revision=$3
AND status='proposed' AND visibility <> 'hidden'
RETURNING id, revision, visibility;
-- App: require exactly one returned row; else ROLLBACK, 409
DELETE FROM votes WHERE request_id=$1;
COMMIT;
-- 200 with only ID, new revision, pending visibility, voteCount 0.
-- Withdraw: $1=request UUID, $2=verified Clerk author ID,
-- $3=expected revision
BEGIN;
SELECT author_clerk_id, status, visibility, revision
FROM feature_requests WHERE id=$1 FOR UPDATE;
-- Same ownership and state checks as author edit
DELETE FROM feature_requests WHERE id=$1 AND author_clerk_id=$2
AND revision=$3 AND status='proposed' AND visibility <> 'hidden'
RETURNING id;
-- App: require one row; FK cascades votes; otherwise ROLLBACK, 409
COMMIT;
-- 204.
-- Moderate: $1=verified Clerk moderator ID, $2=request UUID,
-- $3=expected revision, $4=validated status, $5=validated visibility
BEGIN;
SELECT 1 FROM moderators WHERE clerk_user_id=$1;
-- App: absent => ROLLBACK, HTTP 403
SELECT revision FROM feature_requests WHERE id=$2 FOR UPDATE;
-- App: absent => ROLLBACK, 404; revision <> $3 => ROLLBACK, 409
UPDATE feature_requests SET status=$4, visibility=$5,
revision=revision+1, updated_at=now()
WHERE id=$2 AND revision=$3
RETURNING id, status, visibility, revision;
-- App: require one row; otherwise ROLLBACK, 409
COMMIT;
-- 200 with only the returned allowlisted fields.The moderator check and row lock occur on the same transaction connection. Revoking a moderator concurrently with this operation needs a separate owner policy; if immediate revocation semantics are required, lock the moderator row or use an equivalent serialized authorization design before relying on the app. This board recipe has no self-service moderator changes. The public HTML and API query fresh database state after each commit; no-store makes an edited or hidden item disappear from public reads without a cache purge. If an actual deployment uses an external cache/CDN despite no-store, inspect and invalidate it before acceptance.
Direct request checks for the owner
Use development users A (author), B (other customer), M (the deliberately seeded moderator), and G (signed out), with each user's real browser session. Use the actual development origin and approved test content. Record status, allowlisted response fields, database rows and fresh public HTML/API state; these are expected results, not BIG CHANGE observations. Browser dev tools or an authorized local test client can issue requests with that user's session; do not export cookies into an article or receipt.
Action | Identity/body | Expected result |
|---|---|---|
Create | A | 201, own ID/revision 1, pending; G public list/detail 404 or omits it; A own list/detail finds it after reload. |
Guess ID | B | 404, no row change. |
Forge identity | B create/edit/vote with extra | 400 or ignored only if explicitly documented; never changes the authoritative server ID. This contract chooses 400. |
Wrong origin/session | A mutation from unapproved Origin; expired session | 403 origin denial; 401 expired session; no row change. |
Approve | M review list/detail then | 200 revision 2; G public list/detail now shows allowlisted fields only. B moderation attempt 403. |
Vote repeat/concurrent | A and B each | First per user 201, repeat 200; SQL has exactly one row per user/request and count 2. No duplicate 500. |
Public text edit | A | 200 revision 3, pending and count 0; G fresh API/HTML/detail no longer exposes text; A own detail still does. |
Stale approval | M tries old revision 2, then fresh review revision 3 | Old PATCH 409 with no effect. Fresh explicit approve 200 and G sees reviewed new text with zero votes. |
Hide-vote race | M hides while B votes, or B votes while M hides | One serial order: if hide first vote 404; if vote first 201 then hide 200. After both, G cannot read item; no duplicate or partial SQL result. |
Withdraw race | A withdraws proposed request while M changes status | Exactly one valid revision wins; later stale action 404 or 409 and no orphan votes. |
Unvote after hide | B | 204 whether own vote exists or not; no other vote deleted. |
Invalid data | A short title, invalid enum, unknown field, bad UUID | 400 and no row change. |
For production, repeat the meaningful identity, authorization, persistence and visibility checks with production Clerk accounts and approved launch records. During Invite-only first publish, each allowed tester needs both Replit host admission and a separate Clerk app login. A Replit-host denial occurs before app routes and is recorded separately from app 401/403. After opening the final host audience, repeat signed-out public read and denied mutation checks at the final URL.
3. Build, test and release the reviewed app
- Build and inspect in stages. Let Agent implement the reviewed plan with Replit-managed Clerk Auth and Replit Database. Replit documents both integrations. Open generated routes, auth middleware, managed proxy, migrations and UI; compare actual source/dependency versions with the build brief above and the request contract above. Preserve the native proxy before body parsers, use
clerkMiddleware()before protected Express handlers and derive user ID fromgetAuth(req)only after verified session auth. Check exact approvedauthorizedParties, mutation Origin/CSRF policy and session-token-only boundary. Confirm all SQL mutations use bound parameters, a shared request-row lock/transaction or equivalent, explicit revision conflicts and an SQL unique vote key. Confirm app secrets are not sent to browser code or committed. The actual framework and filenames depend on generated output; use what Agent created rather than a guessed file path. Unsupported native auth/atomic behavior is HOLD until repaired. - Run development checks. Use two test accounts plus an owner-approved moderator account in Clerk's Development environment and only synthetic requests. In the Project Editor open Auth → Users, set the environment toggle to Development, select the intended signed-in moderator and copy its exact user ID. If the pane does not expose it, sign in as that person and read only
GET /api/me/identity, whose value comes from verified server auth. In Tools → Database → Development Database → My Data, use the authorized SQL query interface for the insert/readback below. Replace the placeholder with the exact ID; accept only letters, digits,_or-for this manual literal route, so quote or SQL syntax cannot enter it. If the actual ID has other characters, HOLD and use an owner-reviewed parameterized query route instead. Do not put the real ID in article copy. Verify the readback returns just the intended row and no public role-seeding endpoint exists.
INSERT INTO moderators(clerk_user_id)
VALUES ('<EXACT_VERIFIED_DEVELOPMENT_CLERK_USER_ID>')
ON CONFLICT (clerk_user_id) DO NOTHING;
SELECT clerk_user_id FROM moderators
WHERE clerk_user_id = '<EXACT_VERIFIED_DEVELOPMENT_CLERK_USER_ID>';Run the app's actual build/tests and the direct request table in the request contract above: guest, own pending reload, guessed ID, approved-then-edited pending transition and cleared votes, duplicate/concurrent votes, moderator-versus-vote/edit, cache absence, wrong Origin and expired session. Record response and SQL readback as pass/fail/untested per case, then fix failures at a new checkpoint. Replit's App Testing is a helper, not a substitute for these route and database checks.
- Prepare the first release. Review dependencies, asset rights, data destinations, secrets, security findings and actual usage settings. In Tools → Publishing, review the domain and deployed revision. Under Sharing, select Invite only before first Publish; check that the owner and invited test participants can pass that Replit host gate. Each also needs a separate Clerk app account to exercise the board; Replit host admission is not the app's authorization. Personal workspaces otherwise default Public. If Invite only is unavailable or the testers cannot be admitted, record HOLD, with no undefined public prelaunch substitute. Inspect Tools → Database to identify whether this project uses Helium or legacy Neon. Replit says Helium creates a production database automatically at publication provided the development database contains data; do not assume an empty development database qualifies. This test workflow has synthetic development rows; confirm the actual production-creation preview. For legacy Neon, open Publishing's Production database settings, select Create production database, and leave optional development-data copy off unless the owner approves exact rows. Confirm no synthetic record transfer in either path by fresh production readback. Review production machine maximum cost, monitoring and secrets; obtain the owner's approval for this specific host, audience and spend, then Publish.
- Check production and open only after acceptance. In Publishing → Overview → Adjust settings, inspect only the prefixes of managed
CLERK_PUBLISHABLE_KEYandCLERK_SECRET_KEY: published app should showpk_live...andsk_live.... Replit manages test/live switching; do not reveal or edit values, replace test keys in Secrets or configure an external Clerk tenant. In Auth → Users, change the environment toggle to Production, sign up the owner in the live app and read its exact Production Clerk ID (or the protected own-identity route). Development accounts and IDs do not carry over. In Tools → Database, select the Production Database, inspect its actual table schema/rows/connection environment, and confirm the live deployment uses it rather than Development; an absent production DB or table is HOLD. Open My Data, run the same insert/readback SQL from the development step with the exact Production Clerk ID after the same character check, and verify only that row was added. Do not seed dev ID or grant moderator through profile fields. Test a permitted owner/moderator and another admitted customer at the live Invite-only URL, plus Replit-host denial for a noninvited visitor. Test separate Clerk app 401/403 and request/vote/moderation/persistence with approved launch data. Replit says changing host access requires unpublish and republish. Only after owner acceptance of the exact revision, select intended Public access in Sharing, republish, then repeat allowed/denied app checks and unauthenticated public read at the final URL. Preserve deployment IDs, URL, auth/database environment and test readbacks. - Recover from precise failures. If sign-in fails, inspect Auth's selected environment, the managed prefix-only key readback, exact allowed origin and proxy/middleware order. Production users are distinct from development users. If writes disappear after publish, inspect production database creation, schema and connection target; Helium's creation condition and Neon controls differ. If another user can edit or moderate, keep Invite-only host access, repair server authorization and retest its HTTP denial before opening. A duplicate-vote conflict or 500 calls for the real SQL key, lock,
ON CONFLICTand transaction readback. If pending content leaks, inspect public handler allowlist, rendered HTML, cache headers and fresh requests. If a publish result is uncertain, read Publishing and the live URL before any retry. A new schema change needs migration and deployment-preview checks; Replit warns publication can apply schema changes to production and briefly interrupt service. Record actual error, repair revision and readback, never blanket retry.
Cost, privacy and limits: Replit documents Free, Power and Max Agent modes and per-account usage; publishing shows machine costs, while production database compute/storage is usage-billed. Clerk Auth is described as currently free with possible later usage pricing. Check live account limits and charges before starting paid build or release. The AI-generated app has no runtime model call in this contract, so do not attribute ongoing inference cost. We have no measured build time, savings, uptime or security result. Replit-managed Clerk currently lacks organization tenants, and app-level moderator authorization is therefore explicit. Keep customer feedback and identities only as the owner's approved policy permits. Generated code, hosting controls and auth together still need observed allow/deny tests.
Sources & further reading
- Replit Agent overview establishes Agent and Plan mode. It does not verify the board Agent will generate.
- Replit effective prompting supports a staged, explicit brief and checkpoints. It does not supply the board's authorization policy.
- Replit Clerk Auth describes managed app identity, development and production separation, and current limitations. It does not grant moderator status in this proposed app.
- Replit SQL Database documents the managed persistent database. The schema and handlers above are proposed requirements for the reader's generated app.
- Replit development and production databases explains the environment boundary and production setup, including Helium and legacy Neon paths. The reader must inspect the path their project actually uses.
- Replit Publishing overview covers deployment, database and cost controls. Publishing alone does not prove the board's access rules work.
- Replit private deployments documents Public and Invite only host access and the unpublish/republish change. This host control is distinct from Clerk app sessions.
- Clerk Express middleware documents
clerkMiddleware()andgetAuth(). Compare the installed SDK and Replit's generated proxy before applying the wiring pseudocode. - Clerk production deployment guidance describes
authorizedParties. The explicit mutation Origin rule above is an additional app requirement. - PostgreSQL 16 INSERT establishes
ON CONFLICTbehavior. The one-vote key and HTTP 201/200 policy are part of this board contract. - PostgreSQL 16 explicit locking establishes row-lock behavior. The reader still has to confirm one connection and one transaction in the generated code.



