Development Environment¶
PROJECT_OPERATIONS.md is the integrated workflow/runtime entry point. Use this file for detailed local commands, URLs, env vars, logs, accounts, and browser verification specifics.
Web Server¶
- Local Legacy browser runtime:
http://127.0.0.1:8080; reuse its existing listener and do not start a duplicate server. - If port 8080 has no listener, start the local fallback with
php -d upload_max_filesize=20M -d post_max_size=24M -S 127.0.0.1:8080 router.php. - The PHP built-in server must always receive
router.php; do not start it withphp -S 127.0.0.1:8080 -t .(or any-t .command without the router). Without the router, clean menu URLs such as/appointmentlose thepageslug and fall back to the defaultexport-dashboardlanding page. - Base URL:
http://127.0.0.1:8080 - Login URL:
http://127.0.0.1:8080/login.php
New-System Web¶
- Run from the repository root. The root
.envis dotenv syntax, not a shell script; do not usesource .envfor New-System startup. - Canonical local command:
pnpm --dir new-system dev:web
- This command uses the exact Node
24.18.0runtime pinned bynew-system/package.json; the globally installed Node version is not the New-System runtime authority. - Base URL:
http://localhost:3000 - Login URL:
http://localhost:3000/login - When
CTC_SESSION_SECRETis absent, the launcher creates one strong local-only key atnew-system/.generated/local-session-secretwith mode0600and reuses it across restarts. - Reuse the documented local UI accounts below. Do not add a temporary authentication bypass or substitute account when login fails; verify environment propagation, session resolution and DB readiness as separate boundaries.
Routing¶
- Page entry:
index.php?page=<slug> - Login route:
/login.php - Super-admin shell:
/admin-console - API v2 entrypoint:
/api/...throughapi/index.php - Deleted shared AJAX endpoints:
/app/controllers/query.phpmust return 404./app/controllers/post.phpmust return 404.- Specialized legacy controllers still present:
/app/shared/table/jtable_core.php/app/controllers/sync.php/app/controllers/excel.php/app/controllers/media.php/app/controllers/gmail.php/app/controllers/chatgpt.php- Module AJAX endpoints:
/app/systems/<SYSTEM>/<module>/query.php/app/systems/<SYSTEM>/<module>/post.php
PHP Config¶
- App config:
config/config.php - Session bootstrap:
config/session.php - Session TTL:
KJ_LOGIN_SESSION_TTL_SECONDSdefaults to 20 hours. - DB env vars used by
KJDatabase:DB_HOST,DB_NAME,DB_PORT,DB_USER,DB_PASS. - Control Tower registry env vars:
CONTROL_TOWER_DB_HOST,CONTROL_TOWER_DB_NAME,CONTROL_TOWER_DB_PORT,CONTROL_TOWER_DB_USER,CONTROL_TOWER_DB_PASS. Admin console registry edits must use this connection and must not fall back to runtimeDB_*. - AI env vars:
GEMINI_KEY, fallbackGEMINI_API_KEY. - Gmail OAuth credentials:
GMAIL_CREDENTIALS_PATH, fallbackGOOGLE_APPLICATION_CREDENTIALS, fallback external default/etc/ctc/gmail/credentials.json. Do not useconfig/credentials.json. - JSONL runtime log root:
CTC_LOG_ROOT; defaults tostorage/logswhen unset. - QuickBooks Time staff-time sync env/tenant config:
QUICKBOOKS_TIME_ACCESS_TOKEN: QuickBooks Time OAuth bearer token or API Add-On access token; empty means silent skip.- Staff user mapping is stored on the
T_USERrow asQUICKBOOKS_TIME_USER_ID. QUICKBOOKS_TIME_REGULAR_JOBCODE_ID: QuickBooks Time jobcode for regular loader work;0preserves existing no-jobcode regular clock-in/out behavior.QUICKBOOKS_TIME_BREAK_10_JOBCODE_ID,QUICKBOOKS_TIME_LUNCH_JOBCODE_ID: existing QuickBooks Time break jobcodes for 10-minute and lunch breaks.QUICKBOOKS_TIME_BASE_URL: defaults tohttps://rest.tsheets.com/api/v1.QUICKBOOKS_TIME_TIMEOUT_SECONDS: defaults to15.
Production PHP-FPM Environment¶
- Production runtime env vars are managed outside the Git worktree at
/etc/ctc/prod.env. php-fpmloads that file through the systemd drop-in at/etc/systemd/system/php-fpm.service.d/override.conf.- The drop-in should use
EnvironmentFile=/etc/ctc/prod.env; do not add per-keyEnvironment=entries for new app settings. /etc/php-fpm.d/www.confshould keepclear_env = noso PHP can read the systemd environment without per-keyenv[KEY] = $KEYpass-through lines.- Add or change production secrets by editing
/etc/ctc/prod.env, then restartphp-fpm. - Keep
/etc/ctc/prod.envowned byroot:rootwith mode600. - Do not commit production
.envfiles or secrets into the repository; the production deploy timer may rungit reset --hard origin/master.
Example production env file format:
DB_HOST="db.cwto1o3arnd0.us-west-2.rds.amazonaws.com"
DB_NAME="prod"
DB_PORT="3306"
DB_USER="ctc"
DB_PASS="..."
CONTROL_TOWER_DB_HOST="qa-control-db.example.internal"
CONTROL_TOWER_DB_NAME="control_tower"
CONTROL_TOWER_DB_PORT="3306"
CONTROL_TOWER_DB_USER="ctc_control"
CONTROL_TOWER_DB_PASS="..."
CHATGPT_KEY="..."
TELEGRAM_TOKEN="..."
CTC_LOG_ROOT="/var/log/ctc"
GMAIL_CREDENTIALS_PATH="/etc/ctc/gmail/credentials.json"
Apply production env changes:
sudo vi /etc/ctc/prod.env
sudo systemctl daemon-reload
sudo php-fpm -t
sudo systemctl restart php-fpm
Production env verification:
sudo stat -c "%U:%G %a %n" /etc/ctc/prod.env
sudo stat -c "%U:%G %a %n" /etc/ctc/gmail/credentials.json
sudo systemctl cat php-fpm
sudo grep -n "^[[:space:]]*clear_env\|^[[:space:]]*env\[" /etc/php-fpm.d/www.conf
curl -I http://localhost/login.php
MySQL¶
- Local host:
localhost - Production host:
db.cwto1o3arnd0.us-west-2.rds.amazonaws.com - Port:
3306 - Database:
prod - User:
ctc - Password: set
DB_PASSin your local shell or private.envfile. Do not duplicate the literal password in tracked docs. - Local command:
mysql -h localhost -u ctc -p"$DB_PASS" prod - Legacy PHP/local MySQL shell bootstrap:
set -a
source .env
set +a
DB Safety¶
- "Local DB" always means
localhost, regardless of.env. - Never write production DB without explicit user confirmation.
- Before any local DB write, show and confirm:
SELECT @@hostname AS host, @@port AS port, DATABASE() AS db_name;
Production DB Read-only Incident Access¶
Use this sequence only when the current session has no already authorized,
working production DB access path. When an existing path has successfully
reached the production DB in this session, use that path for the narrow
identity and ticket/data reads. Do not read /etc/ctc/prod.env, invoke
sudo, or seek alternate credentials merely to re-establish or prove access
that already works. The sequence below is the canonical fallback; do not
improvise a different host or credential source after a failed local query.
- Read this section before querying.
hubis not the CTC production host. - Connect from the workstation with
ssh ctc, notssh hub. - On
ctc, run the read-only query undersudo -nafter loading/etc/ctc/prod.env. The file is intentionallyroot:root 600; a normal user being unable to read it is expected and is not an access failure. - First query and report the database identity. Only then run the narrowly
scoped ticket/data
SELECT. - Never source the local repository
.envfor a production query. It is the local database configuration and is not production credential evidence. - Do not claim that
ctc-dbis available untilcommand -v ctc-dbsucceeds. Its absence does not justify switching tohub, browser scraping, or an unauthenticated API; use the canonical SSH path below instead.
Read-only identity command (no secret value is printed):
ssh ctc 'sudo -n bash -s' <<'REMOTE'
set -euo pipefail
set -a
source /etc/ctc/prod.env
set +a
MYSQL_PWD="$DB_PASS" mysql --protocol=TCP -h "$DB_HOST" -P "$DB_PORT" -u "$DB_USER" --batch --skip-column-names "$DB_NAME" \
-e 'SELECT @@hostname AS host, @@port AS port, DATABASE() AS db_name, CURRENT_USER() AS db_user, VERSION() AS version;'
REMOTE
If this command fails, report its exact failed boundary (ssh ctc, sudo -n,
env load, or MySQL connection) and investigate only that boundary. Do not
assert that the environment or DB permission is missing without this check.
Runtime Paths¶
- AJAX logs:
${CTC_LOG_ROOT:-storage/logs}/ajax/*.jsonl - Page report summary:
${CTC_LOG_ROOT:-storage/logs}/page-report/*.json - Page report write diagnostics are recorded in the AJAX JSONL log with
source=page-report. - QuickBooks Time sync logs:
${CTC_LOG_ROOT:-storage/logs}/integrations/quickbooks-time/*.jsonl - Sync checksum cache:
temp/<TABLE_NAME> - Upload/media root:
media/
Codex Log Diagnostics¶
- When Codex needs server-side runtime evidence for API/UI verification, prefer the bounded admin API over SSH or direct raw log-file access.
- Endpoint:
GET /api/platform/admin/logs/codex - Auth: existing authenticated super-admin session or
Authorization: Bearer <CTC_CODEX_LOG_API_TOKEN>. Codex retrieves the token only from the local macOS Keychain serviceCTC_CODEX_LOG_API_TOKEN, stores it in a shell variable for one request, and never prints or places it in a URL. Do not bypass this with direct production file access. - Useful filters:
channel: log source such asajaxorintegrations/quickbooks-time; response also includesavailableSources.sinceMinutes: recent window to inspect whendateis not specified.errorOnly: defaults to recent errors unlessrequestIdorstatusis supplied; useerrorOnly=0for full request traces.requestId,path,module,username,status,method,contains: narrow the search before inspecting raw payloads.limitandscanLines: bounded result count and tail scan depth.includeRaw: set0unless the raw bounded payload is needed.- Response fields are agent-oriented:
summary,filters,availableSources,files,items,nextQueries, andusage. - Raw payloads are bounded and masked for common secret keys, but still treat the response as sensitive support data.
Examples:
/api/platform/admin/logs/codex?channel=ajax&sinceMinutes=60&errorOnly=1&limit=50
/api/platform/admin/logs/codex?requestId=<request-id>&errorOnly=0&limit=20
/api/platform/admin/logs/codex?path=/api/tms/transloads&sinceMinutes=240&errorOnly=1&includeRaw=0
Authenticated browser-session fetch:
await fetch('/api/platform/admin/logs/codex?channel=ajax&sinceMinutes=60&errorOnly=1&limit=20', {
credentials: 'include'
}).then((response) => response.json());
Browser Testing¶
- Start the local PHP server first.
- A requested UI screenshot means a real rendered page in the requested environment and session unless the user explicitly accepts a fixture or mock. Never present a fixture, static render, or inferred layout as an actual authenticated UI capture.
- For CTC page capture and login, use the
agent-browserskill. Before its first command, runagent-browser skills get core; useagent-browser screenshot <artifact-path>from that actual browser session before reporting a missing capture path. - The local CTC login source is this file's Browser Testing → Local UI test credentials section. Use the account matching the requested role; do not ask the user for a password, remote-debugging session, or an auth-state file when this local access applies. Never print the credential.
- Treat a screenshot as captured only after saving and inspecting the artifact. Report its environment,
URL/action, authenticated role, and whether it is actual runtime or fixture evidence. If real capture
cannot be completed, report
NOT_RUNwith the failed boundary; do not claim success or repeat stale progress text. - For CTC field/business process validation, use the shared process runbook with two runtime passes: Playwright automated validation first, then browser runtime validation over the same process steps. Use the project skill
ctc-field-process-testingfor this flow even when common testing skills are available. - Use browser or Playwright for other release/readiness validation.
- If Playwright cannot launch Chromium because of macOS process or Mach port permissions, do not block on another confirmation. Use the Codex in-app browser for the same page-level validation and record that fallback.
- For existing page changes, verify with an account that can reach the page from the app menu. Direct URL checks may follow only after menu access is confirmed; new pages are exempt until menu registration exists.
- Local accounts:
admin(CLIENT_ID=1)admin2(CLIENT_ID=2)- Local UI test credentials:
adminpassword: ***admin2password: ***scripts/smoke-local.shandscripts/api-smoke.jsread the local UI test password from this ignored file.- Keep local test passwords outside tracked repository files; do not reuse
DB_PASSas a UI login fallback. - Expected admin landing page:
/?page=export-dashboard
Verification Gate¶
Logistics Workspace Coexistence Server Gate¶
When a user asks to run, restart, or verify the local Logistics Workspace through the Legacy host,
do not report the server as running from a process list, a 127.0.0.1:3000 response, or a root
redirect alone. The required local route is http://localhost:8000/logisticsWorkspace and all of
the following must be checked after the final restart/configuration change:
- the Workspace Next server, local PHP server, and local Legacy-host reverse proxy each have a live process;
GET /logisticsWorkspacereturns only the expected sign-in redirect to/logisticsWorkspace/login?next=%2FlogisticsWorkspacefor an anonymous session;GET /logisticsWorkspace/loginreturns200from the Workspace login page, not a Legacy login page, 502, 503, or an unprefixed redirect;- log in with the applicable local account and open the requested Workspace route before calling a UI change verified.
If any check fails, keep the server claim FAIL and repair the proxy/base-path behavior before
returning to Product work. Restarting the Next server after a build is mandatory; re-run this gate
after that restart. A bare 3000/login result is diagnostic only and never proves the Legacy-host
coexistence route.
- API/lint checks are not enough for UI release readiness.
- Use browser or Playwright for page-level verification.
- CTC field/process verification must report both Playwright and browser runtime evidence against the same process/scenario list, or explicitly mark the missing pass as a residual gap.
- Record URL, action, expected result, actual result, and console/network errors.
- Final API v2 cutover validation runbook:
docs/runbooks/API_V2_CUTOVER_VALIDATION_RUNBOOK.md.
Troubleshooting¶
- Login loop: check session cookie,
config/session.php, andlogin.php. - Empty page: confirm the page slug resolves to a real page file.
- API v2 failure: check HTTP status,
{ error: { code, message } }, andrequestId. - Legacy module shell failure: check
TYPE,ACTION,DATA, response envelope, andReqId. - Yard mismatch: inspect
T_YARD_LOCATIONfirst, then mirrors such asT_TRANSLOAD.WAREHOUSE_LOCATION. - DB connection issue: confirm
DB_*env is visible to the PHP process.
Sensitive Values Moved From Tracked Docs¶
GMAIL_ADDON_TOKEN="..."
---¶
Source:
.agents/rules/DEV_ENV.md— ctc repository · This page is an English translation of the source document. Identifiers, file paths and code symbols are unchanged.