THE WORLD IS NOT STANDING STILL.RSS
BIG CHANGE.
BIG CHANGE, one tap away
BIG CHANGE, one tap away

Make room for the big picture.

Add BIG CHANGE to your home screen or desktop. It opens in its own window, with the latest stories when you’re online.

iPhone & iPad
Open bigchange.ai in Safari. Tap Share, then Add to Home Screen. Turn on Open as Web App if shown, then tap Add.
Android
Open bigchange.ai in Chrome. In the browser menu, choose Install app or Add to Home screen.
Desktop
In Chrome or Edge, use the install icon in the address bar or the browser’s app menu. In Safari on Mac, choose File → Add to Dock.

Markdown edition

# Build an internal staff tool with an AI coding agent, then check its restricted release

> Choose a real staff workflow, build a complete service-request tool with an AI coding agent, test roles and races, then plan a restricted release and upkeep.

By BIGCHANGE.AI Editorial

Published: 2026-10-22T23:16:38.456Z
Updated: 2026-10-22T23:16:38.456Z
Canonical: https://bigchange.ai/blog/ai-agent-internal-staff-tool-lifecycle-guide

![Conceptual charcoal illustration of one facilities worker with a closed service kit facing an office copier; a blank, dark staff terminal is mounted on the wall nearby.](https://bigchange.ai/api/media/file/staff-service-request-concept-hero-v1.png)
AI-generated conceptual illustration by BIG CHANGE.

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](https://docs.replit.com/features/agent/overview); the [Plan mode](https://docs.replit.com/features/agent/plan-mode), [managed Clerk](https://docs.replit.com/features/auth-and-identity/clerk-auth), [development and production data](https://docs.replit.com/features/data-and-storage/development-and-production) and [private deployments](https://docs.replit.com/features/publishing/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](https://docs.replit.com/features/auth-and-identity/clerk-auth) 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](https://clerk.com/docs/reference/express/clerk-middleware) with `authorizedParties` set to the real approved dev/production origins, and [`getAuth(req)`](https://clerk.com/docs/reference/express/get-auth) (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](https://docs.replit.com/features/data-and-storage/development-and-production) 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](https://docs.replit.com/features/publishing/private-deployments) 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](https://docs.replit.com/features/data-and-storage/development-and-production) 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](https://docs.replit.com/features/data-and-storage/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](https://www.postgresql.org/docs/current/transaction-iso.html) 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](https://www.postgresql.org/docs/current/explicit-locking.html) 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](https://bigchange.ai/blog/ai-agent-expense-approval-reimbursement-app-guide), [private data dashboard](https://bigchange.ai/blog/replit-agent-private-dashboard-real-data-guide) and [team knowledge-base app](https://bigchange.ai/blog/ai-agent-team-knowledge-base-app-guide). Each solves a different workflow; none validates this reference app.

## Sources & further reading

- [Replit Agent overview](https://docs.replit.com/features/agent/overview) and [Plan mode](https://docs.replit.com/features/agent/plan-mode): planning and generated-project workflow, not evidence that this app was produced.
- [Replit-managed Clerk](https://docs.replit.com/features/auth-and-identity/clerk-auth), [Clerk Express middleware](https://clerk.com/docs/reference/express/clerk-middleware) and [getAuth](https://clerk.com/docs/reference/express/get-auth): provider integration and server session identity; the actual generated transport paths still need inspection.
- [Replit development and production databases](https://docs.replit.com/features/data-and-storage/development-and-production), [private publishing](https://docs.replit.com/features/publishing/private-deployments) and [data recovery](https://docs.replit.com/features/data-and-storage/data-recovery): account-dependent database, host-access and recovery decisions.
- [PostgreSQL transaction isolation](https://www.postgresql.org/docs/current/transaction-iso.html), [explicit locking](https://www.postgresql.org/docs/current/explicit-locking.html), [current time functions](https://www.postgresql.org/docs/current/functions-datetime.html#FUNCTIONS-DATETIME-CURRENT) and [constraints](https://www.postgresql.org/docs/current/ddl-constraints.html): documented primitives behind the proposed transaction and history-order contract. `now()` marks transaction start, while `statement_timestamp()` marks the event INSERT statement; neither is the canonical event order. The schema, app behavior and races remain untested.

## Sources

- [Replit Agent overview](https://docs.replit.com/features/agent/overview) — Agent project workflow; no app generated here.
- [Replit Plan mode](https://docs.replit.com/features/agent/plan-mode) — Planning before implementation; actual plan requires reader review.
- [Replit managed Clerk authentication](https://docs.replit.com/features/auth-and-identity/clerk-auth) — Managed auth/proxy route; exact generated paths and project behavior unobserved.
- [Replit development and production databases](https://docs.replit.com/features/data-and-storage/development-and-production) — Environment separation and publish-time differences; no project binding inspected.
- [Replit private deployments](https://docs.replit.com/features/publishing/private-deployments) — Invite-only host setting and account-specific availability; no release made.
- [Replit data recovery](https://docs.replit.com/features/data-and-storage/data-recovery) — Account-dependent recovery path; no restore rehearsed.
- [Clerk Express middleware](https://clerk.com/docs/reference/express/clerk-middleware) — Server middleware and authorized parties; app roles remain separate.
- [Clerk Express getAuth](https://clerk.com/docs/reference/express/get-auth) — Verified server session identity; not a staff authorization grant.
- [PostgreSQL transaction isolation](https://www.postgresql.org/docs/current/transaction-iso.html) — READ COMMITTED command snapshots and REPEATABLE READ distinction; proposed contract untested.
- [PostgreSQL explicit locking](https://www.postgresql.org/docs/current/explicit-locking.html) — Transaction advisory lock primitive; race behavior untested.
- [PostgreSQL constraints](https://www.postgresql.org/docs/current/ddl-constraints.html) — Constraint primitives; migration unexecuted.
- [PostgreSQL current date and time functions](https://www.postgresql.org/docs/current/functions-datetime.html#FUNCTIONS-DATETIME-CURRENT) — now() marks transaction start; statement_timestamp() marks current statement start. Event identities, not timestamps, specify proposed per-target history order. No transaction test ran.