An internal staff tool is only useful if it follows the actual job and respects who can see or change each record. This guide shows a workflow owner how to choose between configuring an existing system and asking an AI coding agent to build a small app. It then supplies a complete service-request reference: owner decisions, schema, role and API rules, an Agent brief, staff acceptance checks, restricted release and ongoing care. The reference is a design to adapt, not a deployed or tested product.

The big change

  • What changed: An AI coding agent can turn a reviewed staff workflow and precise contract into a candidate app and migration.
  • Why it matters: The owner can inspect the full route from intake through resolution, denial checks and support before admitting real staff.
  • What to watch: Generated code and a build summary do not prove access control, race behavior, production identity or recovery. Observe those checks in the actual project.

The reference route below specifies a staff service-request tracker: any active staff member submits a request, a manager assigns it, a handler works and resolves it, and the creator or manager closes it. Use that route only if it matches a real owner-approved workflow. For inventory movements, payments, personnel records or another task, first rewrite the entities, transitions and access matrix with that task's owner, then review the generated plan and code against the revised contract. A renamed request form is not a completed different workflow.

This is a documentation-based recipe, not a record that BIG CHANGE built, deployed, tested or secured an app. The owner must use authorized data and accounts and record observed results in the acceptance record below. The current primary documentation is Replit Agent; the Plan mode, managed Clerk, development and production data and private deployments pages govern the selected route.

1. Choose one staff workflow and decide whether to build

Interview the people who perform, receive and supervise the work. Fill in the workflow charter below before opening Agent: the current trigger, inputs, owner, allowed decisions, exceptions, handoff, end state, existing system of record, data classification, retention owner and three observable success measures. Follow one real authorized case from start to finish and record where the handoff fails. Check whether an existing system already solves it or has a supported configuration. If the task needs regulated records, special approval, a transaction/payment, or a system that must remain authoritative, obtain that system/data owner's review before copying data or building a replacement. Before building, obtain the real owner-supplied policy and approved test identities.

Select a builder with the charter: can it express the entity relationships, server-side role checks, workflow transitions, deployment access, data export and support ownership? A spreadsheet/form automation may be enough for one simple intake queue. The concrete Replit Agent route below fits a small custom browser app whose owner has a Replit account and budget and can review generated code. It is not evidence that Replit is best for every workplace or that its published app can replace an existing system. Record the chosen route and why in the charter.

2. Map records, roles and decisions

Use the copyable schema and app contract below as the selected route's reference implementation, then edit its policy values with the real owner. It models staff, request_categories, requests, and append-only request_events. The event table is an operational change history, not a tamper-proof legal audit log. Decide the categories, acceptable request contents, retention/export and who may activate staff. Avoid actual secrets, employee records or live requests in an AI prompt or development seed. Start with synthetic records. Store a verified Clerk user ID in staff only after the owner has matched it to the intended person; do not derive app roles from a browser field or allow sign-up to create a privileged row. A manager can administer ordinary staff after the initial bootstrap; a separate owner-authorized process controls the first manager.

The access matrix is concrete: every active role can create a request, rediscover it under My requests, read its detail/history and close it after resolution. Handlers additionally see requests assigned to them; managers additionally see the team queue and assign eligible active handlers. An assigned handler can start and resolve work. If that handler is deactivated or loses the handler role mid-work, their next action is denied and a manager can reassign assigned or in_progress work to another active handler, with a reason and preserved history. The new handler starts again. No public visitor sees request data. Every read and mutation checks server-verified identity against current active staff and record scope; hiding buttons does not authorize an API. Check organization policy for cross-team access, disability/accessibility needs and any extra approval state before adapting this matrix.

3. Give Agent a reviewable build contract

In an authorized Replit project, paste the Agent build brief below plus the owner-filled charter, schema and contract into Agent's planning flow. Ask it to list file changes, dependency versions, routes, migrations, environment values and security-sensitive assumptions before implementation. Correct any plan that substitutes a demo form, client-side-only role checks, static example rows, unbounded document upload, public deployment or fake audit history. Approve the code change only after a person reviews the plan and secret/data handling. Agent can create the project and run its own checks; the reader must inspect the actual diff, install output, migration result and failures. Record the exact package and runtime versions. Do not infer that a generated build passed from the Agent's summary.

For the selected Express route, integrate Replit-managed Clerk as documented for that project. Have Agent show the actual generated proxy, sign-in/up, callback and bootstrap paths; allow only required provider transport, public landing and public assets past the app staff gate while leaving provider protections intact. Keep the proxy before body parsers; never replace Replit-managed keys or assume an external Clerk dashboard. Put secrets only in server environment configuration. Use the generated Clerk session cookie flow, middleware with authorizedParties set to the real approved dev/production origins, and getAuth(req) (or current generated equivalent) for verified identity. Read current staff.active and role server-side on each operation. Links/GETs never mutate. App mutations require exact same-origin Origin, JSON content type and a bounded body; provider callbacks are not subjected to that app mutation rule. Protect all data-bearing HTML, API and fallback responses with staff/record gates and private, no-store; return the same no-data 404 for absent and foreign record IDs. Public bootstrap and logs hold no private records. A custom CSRF token is not a universal managed-Clerk requirement; the selected session/Origin policy must be tested. First-manager creation requires owner-reviewed verified identity and a separate development then production seed; no self-enroll endpoint or default manager.

4. Build the complete task interface and transactions

The app needs more than a form. Give requesters a new-request view, their queue, details and event history; handlers an assigned queue, work/detail view and resolve action; managers an unassigned/team queue, assignment controls, staff activation/deactivation and category management. Show a clear loading/empty/error state and keyboard/phone-sized task path. Validate title/detail/category/lengths on the server, present only categories the owner approved, and avoid collecting data the workflow does not need.

Implement the server routes and exact state/role matrix in the app contract below. The schema below includes request, staff and category versions; request events are separate from staff/category admin events. Every request/staff/category/protected owner-manager mutation uses one checked-out database connection, explicit BEGIN ISOLATION LEVEL READ COMMITTED, then the same advisory lock in a separate SELECT statement, then new post-wait SELECT statements for actor/target/assignee/category in the documented order on that connection. Do not use a pre-lock row or combine the lock with the authorization read: a REPEATABLE READ snapshot could miss a revocation committed while waiting. A version-guarded state update and its event commit together; a failed event insertion rolls back the state. A handler revocation and assignment/start/resolve serialize in either order, as do category deactivation and create. Have the reader record SHOW transaction_isolation inside an actual writer transaction and require read committed; after a lock wait, re-evaluate the predicates. Use bounded whole-transaction rollback/retry on DB deadlock/serialization failure, restarting with explicit READ COMMITTED; never silently replay a stale version. Generate IDs/timestamps server-side. For request and administrative history, allocate each database identity only in the event INSERT after the shared lock, fresh authorization reads and row decision; read each history by its identity, never by its timestamp. Escape displayed text and parameterize SQL. A role change takes effect for the next server request. Lock down direct API calls and row reads, not only visible controls. If the code uses direct browser database access instead of this trusted server, redesign and independently prove its row policy; this reference uses a trusted server.

Use the development database first. Replit's environment documentation describes separation and schema handling on publish; the owner must read actual project settings and migrations. Review a numbered migration for all six request, staff, category and history tables plus indexes before applying it. Check fresh schema/constraint readback and insert only synthetic test users and requests. Do not copy production rows into development or assume development seed users exist in production. Keep an export/rollback plan for schema changes and a reviewed data migration if modifying an existing table.

If the charter requires an existing ticket, HR or inventory system, identify which system owns each record and whether the integration reads, writes or both. Obtain its owner's API/permission approval, map identifiers and fields, keep credentials server-side, and design a retry with an idempotency key plus a visible failed-sync state; test duplicate delivery and a revoked credential with permitted synthetic records. Do not silently make this app a second authoritative store. If no integration is required, leave it out and document the deliberate boundary. The reference contract is deliberately self-contained; an integration changes its data and failure contract and needs a separate review before release.

5. Run real staff and denial checks before restricted release

Use the acceptance record below with at least two authorized non-manager test people, one handler and one manager, plus signed-out and signed-in-but-unenrolled access. Have staff perform the whole flow in the actual app: request, assign, start, resolve, close, inspect exactly five base events and re-open the screen from another session. Have a handler and manager each submit, reload My requests, inspect history and exercise creator-close; unrelated unassigned rows remain denied to a handler. Deactivate or change an in-progress handler role and verify their next action fails while a manager reassigns to another eligible handler, who starts and resolves with ordered history. Test ordinary staff/category administration and guarded owner manager lifecycle, including stale versions and last-manager protection. Interleave assignment versus handler revocation, creation versus category deactivation, and mutation versus actor revocation in both orders; observe the permitted serialized result with no extra event. Test failed event insertion rollback. Probe actual provider bootstrap and signed-out/private API, foreign or missing Origin, malformed/oversized JSON, absent versus foreign request ID, private HTML/fallback and cache headers. Record real HTTP status, visible result and database readback. Any unexpected access or missing event is a release hold. Get staff feedback on labels, mobile/keyboard use, notifications and exceptions; revise charter/code and repeat affected checks. Do not invent a passing screenshot, log or quote.

6. Release to the smallest real group, then maintain it

Review the actual Replit workspace and private deployment eligibility before publishing. Set Invite only for the authorized staff group and verify its invite/account behavior; a personal workspace may default to Public and workspace admins can still access the host. Host access and app role checks are separate: an invited but unenrolled person must still be denied app data. Replit's access change may require unpublish/publish, so plan the setting before first release. Ask the owner to approve the exact production dataset, staff roster, database migration, domain/URL and release window. Inspect the actual development/production database infrastructure and publish settings: current and legacy projects differ in how a production database is provisioned, and legacy settings can copy development rows. Explicitly prevent unsafe synthetic test data being copied. On publishing, verify the production schema and seed the first manager from a production verified Clerk identity using the approved method; development identities and rows must not be assumed to exist or be authorized in production. Run the allowed and denied routes on production with permitted test records before staff use. If any prerequisite or check fails, hold the invite rather than exposing a half-tested tool.

Assign a support owner. Document how to add/revoke staff, inspect failed requests and logs without copying sensitive values, export records, handle conflicting edits, and change categories or workflow policy through reviewed migrations. Check the actual plan's database recovery window and data recovery path; record a restore only if actually rehearsed. Make a backup/export and incident contact plan consistent with the organization's policy. Monitor usage and monthly spend. Replit Agent effort, deployment compute, production data and Clerk/identity features may carry plan-specific limits or charges; check the current account billing and quotas before build/release and set an owner budget. No price or uptime is promised here. Review staff feedback after restricted use, and stop using the app if its access boundary, record accuracy or ownership cannot be maintained.

Completion evidence

A complete outcome is an owner-approved workflow charter; reviewed generated plan/code and migration; actual end-to-end staff action and direct denied-access readbacks; restricted production identity/schema/URL verification; and a named support, retention, cost and recovery plan. An unfinished template, successful syntax check, sample UI or an Agent completion message does not meet that outcome. This guide records no real app, account, deployment, test or restore result.

Copyable workflow charter

Fill before building: one owned staff workflow

Do not send confidential records or credentials to an AI agent. The workflow owner supplies these values from an authorized process; blanks are not example facts.

Decision

Owner's answer and evidence

Workflow name, owner and approving team

Real trigger, end state and one current case traced

Current system of record and why it cannot meet the task by configuration

Who submits, handles, assigns, resolves and closes

Whether every role may submit; owner approval of all-role My/detail/history/creator close

Handler revocation/role-change recovery owner, replacement handler and required reassignment reason

Ordinary staff administrator versus separately authorized first/manager lifecycle owner

Required fields; prohibited/regulated data; retention/export owner

Categories and exact exception/escalation rules

Category rename/deactivation policy and immutable historical label requirement

Existing integrations, if any; source-of-truth direction and failure response

Accessibility, phone use and staff acceptance participants

Allowed development test data and two or more distinct test identities

Builder choice against data, roles, deployment, export, support and budget

Three measurable success checks (e.g., completion, handoff accuracy, staff time)

Production approver, authorized staff group, support and incident contacts

Exact allowed development/production origins and actual generated managed-auth transport paths

Backup/export/restore requirement and actual plan availability

Use the staff-request contract below only if these answers fit its submit→assign→work→resolve→close task. If a required action or role differs, revise the schema, API state table, UI, test matrix and plan together before asking Agent to build. Preserve the original authoritative system until owners approve migration/cutover.

Copyable SQL schema

Copy this into a reviewed, numbered development migration only after the charter fits. The owner must inspect the actual schema before applying it.

SQL
-- Reference development migration for the selected staff-request route.
-- Review against the real owner-approved charter and migration history before use.
-- No production database or app was touched to prepare this file.
CREATE EXTENSION IF NOT EXISTS pgcrypto;

CREATE TABLE staff (
  clerk_user_id text PRIMARY KEY,
  display_name text NOT NULL CHECK (length(display_name) BETWEEN 1 AND 120),
  role text NOT NULL CHECK (role IN ('requester', 'handler', 'manager')),
  active boolean NOT NULL DEFAULT true,
  version integer NOT NULL DEFAULT 1 CHECK (version > 0),
  created_at timestamptz NOT NULL DEFAULT now(),
  updated_at timestamptz NOT NULL DEFAULT now()
);

CREATE TABLE request_categories (
  id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  label text NOT NULL CHECK (length(label) BETWEEN 1 AND 80 AND label = btrim(label)),
  active boolean NOT NULL DEFAULT true,
  version integer NOT NULL DEFAULT 1 CHECK (version > 0),
  created_at timestamptz NOT NULL DEFAULT now()
);

CREATE TABLE requests (
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  creator_id text NOT NULL REFERENCES staff(clerk_user_id),
  assignee_id text REFERENCES staff(clerk_user_id),
  category_id bigint NOT NULL REFERENCES request_categories(id),
  category_label_at_create text NOT NULL CHECK (length(category_label_at_create) BETWEEN 1 AND 80),
  title text NOT NULL CHECK (length(title) BETWEEN 3 AND 160),
  detail text NOT NULL CHECK (length(detail) BETWEEN 1 AND 4000),
  status text NOT NULL DEFAULT 'open' CHECK (status IN ('open','assigned','in_progress','resolved','closed')),
  version integer NOT NULL DEFAULT 1 CHECK (version > 0),
  created_at timestamptz NOT NULL DEFAULT now(),
  updated_at timestamptz NOT NULL DEFAULT now(),
  CHECK ((status = 'open' AND assignee_id IS NULL) OR
         (status <> 'open' AND assignee_id IS NOT NULL))
);

CREATE TABLE request_events (
  id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  request_id uuid NOT NULL REFERENCES requests(id),
  actor_id text NOT NULL REFERENCES staff(clerk_user_id),
  action text NOT NULL CHECK (action IN ('created','assigned','reassigned','started','resolved','closed')),
  from_status text CHECK (from_status IS NULL OR from_status IN ('open','assigned','in_progress','resolved','closed')),
  to_status text NOT NULL CHECK (to_status IN ('open','assigned','in_progress','resolved','closed')),
  from_assignee_id text REFERENCES staff(clerk_user_id),
  to_assignee_id text REFERENCES staff(clerk_user_id),
  note text CHECK (note IS NULL OR length(note) <= 1000),
  occurred_at timestamptz NOT NULL DEFAULT statement_timestamp()
);

CREATE INDEX requests_creator_updated_idx ON requests (creator_id, updated_at DESC);
CREATE UNIQUE INDEX request_categories_label_ci_idx ON request_categories (lower(label));
CREATE INDEX requests_assignee_status_idx ON requests (assignee_id, status, updated_at DESC);
CREATE INDEX requests_status_updated_idx ON requests (status, updated_at DESC);
CREATE INDEX request_events_request_order_idx ON request_events (request_id, id);

-- Administrative history is separate from request state history. The trusted
-- server/owner channel writes one row in the same transaction as each change.
-- Neither table is claimed tamper-proof against a database administrator.
CREATE TABLE staff_admin_events (
  id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  actor_id text REFERENCES staff(clerk_user_id),
  owner_action_reference text,
  target_id text NOT NULL REFERENCES staff(clerk_user_id),
  action text NOT NULL CHECK (action IN ('bootstrap','provisioned','updated','deactivated','reactivated','manager_granted','manager_removed')),
  old_display_name text,
  new_display_name text NOT NULL,
  old_role text,
  new_role text NOT NULL,
  old_active boolean,
  new_active boolean NOT NULL,
  target_version integer NOT NULL,
  occurred_at timestamptz NOT NULL DEFAULT statement_timestamp(),
  CHECK ((actor_id IS NOT NULL) <> (owner_action_reference IS NOT NULL))
);

CREATE INDEX staff_admin_events_target_order_idx ON staff_admin_events (target_id, id);

CREATE TABLE category_admin_events (
  id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  actor_id text NOT NULL REFERENCES staff(clerk_user_id),
  category_id bigint NOT NULL REFERENCES request_categories(id),
  action text NOT NULL CHECK (action IN ('created','renamed','deactivated','reactivated')),
  old_label text,
  new_label text NOT NULL,
  old_active boolean,
  new_active boolean NOT NULL,
  category_version integer NOT NULL,
  occurred_at timestamptz NOT NULL DEFAULT statement_timestamp()
);

CREATE INDEX category_admin_events_category_order_idx ON category_admin_events (category_id, id);

-- The trusted server DB role must be private to the server. Browser clients never
-- receive its credential. Application authorization and transition checks are
-- required on every route; these schema constraints do not replace them.
-- Initial manager and categories are NOT seeded here: the owner must verify
-- identities and policy separately in each environment before adding rows.

Copyable app contract

Staff-request app contract for a reviewed AI coding agent

This is the selected complete service-request route. The owner fills real categories, identities, retention and exception policy. All active roles may submit and have New / My requests / own detail and history, including handlers and managers. Handlers also have Assigned; managers also have Team, staff and category administration. An active creator of any role may close their own resolved request. Handler assignment never reveals an unrelated unassigned request; creator access is a separate explicit grant.

Authenticated transport and private responses

Use the actual Replit-generated managed Clerk Express proxy and React sign-in/sign-up transport. Have Agent list the generated exact proxy, sign-in, sign-up, callback, session/bootstrap paths and middleware order from this project's code; exempt only those required provider transport paths, / signed-out landing and public static assets from the app staff gate. Preserve provider protections and keys; do not invent universal path names, replace Replit-managed test/live keys, require an external Clerk dashboard or bypass provider transport. Mount the generated proxy before body parsers and app gates; mount clerkMiddleware({authorizedParties:[exact development origin, exact approved production origin]}) or the currently supported generated equivalent before protected routes. Record real scheme/host/port origins and revise on authorized domain changes. Use Clerk's normal session-token cookie/browser flow and getAuth(req) server-side; identity is not a browser-supplied actorId, role or header. Replit Invite-only host login is an additional admission layer, not an app role.

Only GET navigation and read-only GET APIs may be triggered by links, prefetch or redirects. No state change on GET. For app mutations, require same-origin Origin matching the configured exact app origin (reject missing, null or foreign Origin for browser mutations), Content-Type: application/json, bounded JSON body (e.g. 16 KiB) and validated fields. If selected browser transport differs, review its documented Origin/session behavior and update this policy before release; do not break provider callbacks with an app Origin rule. This is an app rule consistent with Clerk's SameSite cookie guidance, not a claim that Clerk universally requires a custom CSRF token. Disable unnecessary CORS. Return Cache-Control: private, no-store for every data-bearing HTML/API response. Public landing, sign-in and bootstrap contain no request/staff/category data or secrets. Protect server-rendered HTML, SPA data loaders, API, file/static fallback and error rendering by default; unknown private paths return an authenticated error or no-data response, never a public fallback with embedded data. Redact body, cookie, token and private response values in logs. Return identical 404 shape for absent and foreign request IDs; do not leak title, state, category, event count or existence.

401 = no verified session; 403 = signed-in but inactive/unenrolled/wrong role; 404 = absent or outside permitted record scope; 409 = stale version/invalid current transition; 422 = invalid content, inactive target/category or unacceptable target role. Provider transport is exempt from the app staff gate only; it retains generated Clerk protections.

One simple serialization protocol for every writer

Use one checked-out PostgreSQL connection per mutation. For every request, staff, category and protected owner-manager writer, execute BEGIN ISOLATION LEVEL READ COMMITTED; on that connection, then execute a separate statement SELECT pg_advisory_xact_lock(77192026001);. Do not combine lock acquisition with a query for actor/target data and do not carry any pre-lock authorization row or snapshot forward. After the lock returns, issue new SELECT statements on the same connection to read verified actor staff, target request/staff/category, proposed handler/category as applicable, in that order (actor, target, proposed target). Check current active/role, state and supplied version only from those post-wait reads. A manager/owner mutation uses the same isolation, lock and fresh-read order. In actual reader testing, SHOW transaction_isolation inside each writer transaction must return read committed; if a driver/pool cannot guarantee the explicit BEGIN and same-connection statements, hold the build. PostgreSQL's isolation guide explains that READ COMMITTED uses a new command snapshot while REPEATABLE READ can fix the snapshot before a lock wait. Parameterize SQL. Update with WHERE ... version = expectedVersion, require one returned row, insert matching event inside the same transaction, then commit. If event insertion fails, rollback means no state change. End-of-transaction releases the lock. Keep transactions short; never perform a network/Clerk lookup or human prompt while holding it. Verify a proposed Clerk identity by backend lookup before transaction, then recheck actual database target and manager authority after lock. Lookup proves existence, not app-role authority.

Set finite lock/statement timeouts. On deadlock (40P01) or serialization failure (40001), rollback the whole transaction and retry from a new explicit READ COMMITTED BEGIN, separate lock statement and fresh post-wait reads at most twice with bounded delay; then return retryable error with no event. Expected-version conflict returns 409 without automatic replay. Never use a separate pool query between BEGIN and COMMIT. Every application/admin writer, including owner-manager channel and category/staff endpoints, must follow this protocol; uncontrolled direct DB edits are an operational hold. Read-only GETs may use separate connections but must use current scoped rows and no-store responses. Insert each request, staff or category event only after acquiring the lock and completing fresh authorization and the row decision. Its database-generated identity is the canonical ascending order within that request, staff target or category; read APIs and UI use ORDER BY id ASC (with stable pagination), not occurred_at. The event timestamp uses statement_timestamp() at the insert statement and is descriptive, not the state-order key. Identity gaps after rollback are acceptable; never infer a missing event from a gap alone. This global lock is a small-tool simplicity choice, not a throughput claim. PostgreSQL transaction advisory locks are the primitive; generated code still needs review and actual concurrency tests.

Interleaving under shared lock

Required result

Handler deactivation/demotion before assign/start/resolve or reassignment targeting them

Following mutation reads fresh inactive/non-handler row and fails; no request event. Manager can reassign stranded assigned/in_progress work.

Assign/start/resolve before handler deactivation/demotion

Earlier operation/event commits, then admin change. Manager can reassign outstanding work; a resolved item may be closed by creator/manager without handler.

Category deactivation before create

Create sees inactive category; no request/event.

Create before category deactivation

Create/event commit with category_label_at_create; later deactivation blocks new use, retains history.

Actor revocation before their mutation

Fresh actor read denies; no state/event.

Actor mutation before revocation

Earlier authorized mutation/event commits; revocation takes effect on next request.

Two admins edit same staff/category version

First commits one admin event; second gets 409, no second event.

Request update succeeds but event insertion fails

Transaction rolls back request status/version and event.

Request API and five-event base path

All protected routes derive actor from Clerk and active staff; reject browser actorId, creatorId, role, timestamps and event rows. GET /api/me returns current own verified ID/display role. GET /api/categories returns active categories to staff. Paginate list endpoints. Request reads and event reads use exactly the same row scope: creator OR (current assignee who is still an active handler) OR active manager. A demoted former handler does not keep assignment-read privilege merely because their ID remains on a record awaiting recovery. mine applies to every role; handler assigned adds assigned work; manager team adds full queue.

Route and JSON body

Authorization and effect

POST /api/requests {categoryId,title,detail}

Any active staff; fresh active category. Insert open, no assignee, server ID/time, immutable category_label_at_create, version 1 and created event in one transaction. 201 {id,version:1}.

GET /api/requests?scope=mine

Any active staff; creator_id = actor.

GET /api/requests?scope=assigned

Active handler or manager; assignee_id = actor, not unrelated unassigned rows.

GET /api/requests?scope=team

Active manager only.

GET /api/requests/:id and /events

Active creator, current assignee or manager; request events ordered by ascending event id, including paginated UI. Absent and unauthorized have identical 404.

POST /api/requests/:id/assign {assigneeId,expectedVersion}

Active manager, active handler target; only open→assigned. Increment version; assigned event with from/to assignee.

POST /api/requests/:id/reassign {assigneeId,expectedVersion,reason}

Active manager; current assigned or in_progress; new distinct active handler, reason 1–1000 chars. Result assigned even from in_progress: earlier work remains in history, new handler starts again. Increment version, reassigned event with old/new assignee and state.

POST /api/requests/:id/start {expectedVersion}

Active current assigned handler; assigned→in_progress; version and started event atomic.

POST /api/requests/:id/resolve {expectedVersion,note}

Active current assigned handler; in_progress→resolved; bounded required note, version and resolved event atomic.

POST /api/requests/:id/close {expectedVersion}

Active creator of any role or active manager; resolved→closed; version and closed event atomic. No handler-active requirement.

Ordinary no-reassignment path has exactly five request_events: created, assigned, started, resolved, closed. A deliberate reassignment adds one reassigned event each time; all earlier events remain. Stale version gives 409; reload for a human decision, never silently overwrite. After uncertain create response, read state/events before retry using a client request identifier if needed for idempotency. Request and administrative event identities are allocated by their trusted server INSERTs only after lock acquisition and authorization. For each request or admin target, ascending event id is the authoritative display order; occurred_at is a descriptive occurrence label and cannot override it. The same target may have gaps from rolled-back inserts. This operational log is not tamper-proof against DB admins. Request state changes have one matching request event; administrative changes use separate logs.

Staff and category administration

Ordinary manager: may provision, edit role between requester and handler, and deactivate/reactivate non-manager staff after verifying actual Clerk identity in this environment. Cannot target self or any manager; cannot grant manager or change a manager's role/active status. GET /api/staff returns minimal active/inactive roster with versions. PUT /api/staff/:verifiedId body {displayName,role,active,expectedVersion}: new row requires expectedVersion:null, exact verified user absent in staff, role requester/handler, version 1, 201; existing non-manager requires positive matching version, validated display name 1–120, role requester/handler and active boolean, increments version, 200. Both return {verifiedId,displayName,role,active,version}. Unchanged values return current state without event. Server verifies identity; manager-supplied label is an owner-reviewed display override, not proof of identity. Deactivation/role change may leave outstanding assignments; manager reviews queue and uses reassign. Former handler's next action is denied. Actual row change and one staff_admin_events row (old/new name, role, active, version) commit atomically; never use request_events for staff edits. Staff history reads for one target use ORDER BY id ASC, including the protected owner channel.

Manager lifecycle: first-manager bootstrap and later grant, demotion, deactivation/reactivation of a manager require an owner-authorized server-side maintenance command, never ordinary /api/staff or a browser endpoint. Specify command inputs {environment,operation,targetVerifiedClerkId,expectedVersion,reason,ownerApprovalReference}; operations are bootstrap, grant, demote, deactivate, reactivate. The operator first verifies the exact target identity in that environment's managed Clerk Auth pane/backend lookup and exact owner approval. The command uses protected server DB credentials and the same advisory-lock transaction; it fresh-reads target, count of active managers and target version. Bootstrap requires zero staff and expectedVersion:null; grant requires an existing non-manager and current version; demote/deactivate target an existing manager and current version; reactivate targets an inactive manager and current version. A grant/re-activation makes the target active manager; demotion sets owner-approved requester/handler role; deactivation preserves manager role but sets inactive. All operations increment version except bootstrap version 1 and atomically insert staff_admin_events with owner_action_reference; read back exact row/count/event. Refuse any result with zero active managers, including two racing owner actions, and any stale version. No self-service manager grant/demotion. A lost last-manager credential or unknown owner authority is a provider/organization recovery hold, not a secret bypass. Development and production require independent approvals and verified identities.

Categories: GET /api/admin/categories returns manager-only active and inactive IDs, labels, versions and timestamps. POST /api/admin/categories body {label} creates unique trimmed 1–80-character label, active/version 1, returns 201 {id,label,active,version} plus one category_admin_events.created. PATCH /api/admin/categories/:id body {label,active,expectedVersion} edits a stable ID with positive matching version; return 200 {id,label,active,version}. Validate case-insensitive uniqueness, increment version on real change and record renamed, deactivated or reactivated with old/new label, active and version. Forbid a combined label and active change in one call. Return 409 on stale version, 422 on duplicate/invalid label or impossible transition. No delete. Inactive categories remain in manager list and historical request joins; ordinary chooser excludes them. Each request keeps immutable category_label_at_create, so rename does not rewrite history. Row change and category admin event are atomic under shared lock, separate from request events. Category history reads for one category use ORDER BY id ASC.

The UI includes New/My/detail/history for all roles, Assigned for handlers, Team and staff/category admin for managers, and manager recovery of stranded assignments. Show validation, loading, forbidden, conflict, empty and actual success states; check keyboard and phone-sized paths. External sync follows charter's source-of-truth, permission, idempotency and failure review. Generated demo rows are not evidence of a real staff workflow.

Copyable Agent build brief

Paste into an authorized Replit Agent planning session

Build the owner-filled workflow charter above as a restricted internal web app. The SQL schema and app contract above define a complete staff service-request route if the charter matches; do not silently narrow a different task to a request form. First return a plan with the exact framework/runtime/package versions, file tree, database migration, routes, role/record authorization matrix, UI states, test commands, deployment/access settings, required owner inputs and cost-sensitive services. Mark any missing workflow policy as an owner decision. Do not claim implementation or tests until they run and the owner reads results.

Selected route: Replit Agent, a React browser interface with an Express trusted API, Replit-managed Clerk session identity, and Replit development/production Postgres. List exact generated managed proxy/sign-in/sign-up/callback/bootstrap paths and keep that proxy before body parsers and app gates. Preserve provider transport; gate all data-bearing HTML/API/fallback with no-store, current staff role/record scope and identical absent/foreign-record responses. Configure Clerk authorizedParties for actual approved app origins and same-origin JSON mutation policy with a bounded body; GET never mutates. Do not replace managed keys, invent a universal CSRF token requirement, expose secrets or accept client-supplied actor/role. Use a reviewed numbered migration for the SQL schema above; do not seed real staff or policy categories without owner confirmation.

Implement all of the app contract above: New/My/detail/history and creator close for requester, handler and manager; handler Assigned; manager Team, staff/category admin and reassign of assigned or in_progress work after a handler revocation. Keep five base request events and separate staff/category admin logs. First/last-manager changes use only owner-authorized protected channel, not ordinary manager UI. For every request/staff/category/protected owner-manager writer, use one checked-out Postgres connection: BEGIN ISOLATION LEVEL READ COMMITTED;, then a separate SELECT pg_advisory_xact_lock(77192026001);, then new post-wait SELECT statements for actor/target/assignee/category on that same connection. Never combine lock and data read or reuse a pre-lock authorization row/snapshot. Version-guard row update and event insert atomically; allocate each event identity only in that post-lock INSERT after authorization and row decision. All request/staff/category history APIs and UI order per target by ascending event id; timestamps are descriptive. Bound full-transaction retries to deadlock/serialization, starting again from explicit READ COMMITTED BEGIN. Build complete task views, pagination and empty/error/conflict states, not a mock form with hard-coded rows.

Deliver actual changed files, migration, environment-variable names (no values), dependency lockfile, run/build/test commands, and a test plan for exactly five base events, all-role own flow, recovery, staff/category lifecycle and both orders of handler/category/actor races. Include a transaction-start-versus-lock-order reversal: begin transaction A first but let B acquire the lock and commit an event first; then let A acquire it. Verify each request/admin history API and UI orders by inserted event identity and committed state sequence rather than transaction-start time. Have the reader record SHOW transaction_isolation = read committed in an actual writer transaction and inspect separate BEGIN, lock and post-wait authorization statements. Also test direct signed-out/unregistered/cross-user/wrong-role/deactivated/stale-version denial, provider bootstrap, foreign/missing Origin, malformed/oversized JSON and private fallback/cache. Use synthetic development data. Stop before production publishing, real-staff invitations or production seeds. If charter requires external integration, first return source-of-truth, permission, field-map, idempotency and failure design for owner review; do not silently connect accounts. Explain any generated feature that cannot be exercised without owner account access. The owner will review diffs and complete the acceptance record below with permitted identities before approving a restricted release.

Copyable acceptance record

Reader's observed acceptance and release record

Fill only after actual permitted checks. Project/account: _ Dev origin and DB identity (no secrets): _ Code revision/runtime/versions: _ Charter approval/date: _ Migration/readback: _ Production origin and approver: _

Check

Actual identity, URL/status, row/version/event or screen evidence

Pass/fail/hold

Generated managed proxy/sign-in/up/callback/bootstrap paths recorded; signed-out landing and sign-in work

Signed-out private HTML/API/fallback denied; public bootstrap contains no staff/request data

Clerk authorizedParties match actual origins; foreign/missing/null Origin mutation denied, permitted same-origin succeeds

Malformed/oversized JSON or wrong content type denied; GET navigation/prefetch causes no mutation

Private HTML/API and errors send Cache-Control: private, no-store; private values absent from logs

Unenrolled/inactive signed-in identity denied data with valid Clerk session

Requester, handler and manager each create, reload My, read own detail/history and can close own resolved item

Handler Assigned lists only assigned work; unrelated unassigned request stays absent and direct foreign/absent IDs have same 404 shape

Base flow creates exactly five request events in ascending event id: created, assigned, started, resolved, closed; timestamps are labels, not the sort key

Manager reassigns assigned and in_progress work with reason to distinct active handler; in-progress result is assigned, old history stays, new handler starts/resolves

After handler deactivation or role change, former handler's next direct action denied; creator/manager can close resolved work

Ordinary manager provisions and updates requester/handler, deactivates/reactivates with expected version and one staff admin event; cannot self-edit or grant/change manager

Owner-authorized first/manager lifecycle channel uses same lock, exact environment/target/version and event; losing last active manager denied

Category stable-ID create/rename/deactivate/reactivate, inactive admin visibility and stale-version conflict; old request retains label-at-create

In actual request/staff/category/owner-manager writer transactions, SHOW transaction_isolation returns read committed; inspect same checked-out connection, explicit BEGIN before a separate advisory-lock SELECT, and new post-wait actor/target/handler/category SELECT statements with no reused pre-lock rows or combined lock/data query

Assignment then handler revoke/demote and revoke/demote then assignment: valid earlier event versus denied later mutation, no unauthorized extra event

Start/resolve then handler revoke and revoke then start/resolve: valid earlier event versus denial; recovery remains possible

Create then category deactivate and deactivate then create: saved historical label versus denied new request, no extra event

Actor mutation then revoke and revoke then actor mutation: only serialized authorized write/event

Explicit wait test: hold a revocation transaction's lock, start a writer waiting on that lock, commit revocation; its new post-wait SELECT denies with no row/event. Reverse order commits only the earlier authorized mutation. Repeat with target-handler deactivation/demotion and category deactivation

Competing staff/category admin versions: one winner/event and one 409; request stale version gives 409 without event

Begin transaction A before B, but let B acquire the shared lock and commit its event first. After A runs, confirm request/staff/category history API and UI use ascending event id per target, including pagination, and reflect actual committed state order despite reversed transaction-start times

Forced event insert failure rolls back request/admin row/version; deadlock/serialization retry bounded and read afresh

Staff complete task on phone width and keyboard; actual feedback and owner-approved change/readback

Invite-only host and invited-but-unenrolled app denial; workspace-admin host access does not grant app role

Production DB/Clerk identity and first manager independently verified; permitted synthetic allowed/denied flow observed

Actual billing/backup/export window and restore rehearsal if performed, otherwise explicitly untested

Record staff feedback without invented quotes or confidential content. For each failure, save the minimal redacted request/response, code revision and repair/readback. Hold release on an access leak, wrong state/history, race failure, migration drift, unknown production identity or unavailable required recovery. An untested restore remains untested. Owner approval covers exact environment, data, roster, billing and URL, not any later change.

Related guides

For narrower tasks, see the expense approval app, private data dashboard and team knowledge-base app. Each solves a different workflow; none validates this reference app.

Sources & further reading