This is the quick-reference HTML runbook for the recurring processes we use to keep the Time On Tasks contributor app current in development and staging. It is intentionally separate from the chronological setup log so routine work is easy to find.
timeontasks
Console Draft: ops_console
Shared Backend: workforce_app/backend
Canonical DBFs: pm_database
Keep development, AWS staging, and the public runtime aligned on Python version, SQLite version, schema level, refreshed database content, backend code, frontend code, and service configuration. If those drift, report or workflow behavior can look broken even when the application logic itself is correct.
AWS staging is a production-like environment. Real contributors (Will, Alex) actively use it, and its database holds live work sessions and user records that cannot be recovered without a backup restore.
Treat every push, rsync, or database operation targeting staging with the same care you would give a production system. Before any staging operation, ask: does this preserve existing users and sessions? If the answer is not an obvious yes, stop and verify first.
/var/www/laneaward-staging/var/lib/laneaward-staging/workforce.dbssh -i ~/.ssh/lane_webserver.pem ubuntu@3.130.69.109ubuntu@laneaward-webserver:~$ — the internal AWS hostname ip-172-31-7-224 is the same machine; hostname was renamed from laneaward-staging to laneaward-webserver on 2026-05-01 to reflect that this server hosts both staging and production.aws-server-setup-summary.html
The repository at /Users/donaldscott/Project-Code/laneaward/repo/ is
under local Git version control. Deploy scripts read from the current working tree,
so the branch that is checked out at deploy time determines what gets pushed to
staging. Before running any deploy, confirm the intended branch with
git branch --show-current. Full background is in the
Version Control And Source Management section of the Project Reference document.
restore_staging_db.sh. Auto-selects the most recent backup or accepts a specific file. Requires two typed confirmations, takes an automatic safety backup, stops the service, restores the database, and verifies health. The service is never left stopped — recovery is attempted on failure.
| Contributor App | / = Time On Tasks |
|---|---|
| Console | https://staging.console.laneaward.com/ = combined Admin + Reports frontend |
| API | /api/ = shared LaneAward backend |
| Legacy Route | /workforce/ redirects to / |
Use this after changing the contributor frontend in timeontasks.
The staging host uses the contributor app at the root route.
Any time app.js changes, you must also bump the version token in index.html
before deploying. The service worker caches app.js by its token URL — if the token does
not change, tablets will keep serving the old cached version indefinitely.
index.html: e.g. app.js?v=20260410-signin1 → app.js?v=20260410-signin2.index.html and app.js in the same rsync run.app.js was picked up, add --checksum to force a content comparison: rsync -avh --checksum …index.html (network-first), sees the new token URL, misses the cache, and fetches the new app.js automatically.
Run from the repo root on the Mac. The script injects the staging label into
index.html and manifest.webmanifest at deploy time,
then pushes index.html, manifest.webmanifest,
app.js, sw.js, version.json,
sounds/, user-guide.html, and user-guide-images/
to the staging web root. No VM login required.
bash scripts/deploy_timeontasks.sh
Use this after changing the Operations Console frontend in ops_console.
The staging console is available at https://staging.console.laneaward.com/.
Any time app.js changes, you must also bump the version token in index.html
before deploying. The service worker caches app.js by its token URL — if the token does
not change, browsers will keep serving the old cached version.
index.html: e.g. app.js?v=20260410-console1 → app.js?v=20260410-console2.index.html and app.js in the same rsync run.app.js was picked up, add --checksum to force a content comparison.index.html (network-first), sees the new token URL, and fetches the new app.js automatically.
Run from the repo root on the Mac. The script injects the staging label into
index.html and manifest.webmanifest at deploy time,
then pushes index.html, manifest.webmanifest,
app.js, and version.json directly
to the staging console web root. No VM login required.
bash scripts/deploy_console.sh
Use this after updating any of the shared console documents: the staging runbook, user guide,
project reference, or environment topology. This script deploys documents only — it does not
touch app.js, index.html, or any application code.
Documents deployed by this script:
_documents/staging/runbook.html → runbook.html_documents/user-guide.html → user-guide.html_documents/project-reference.html → project-reference.html_documents/laneaward_environment_topology.html → topology.htmlbash scripts/deploy_console_docs.sh
Both LaneAward environments are protected by the 🚀 PWA BDR service — a shared macOS menu bar LaunchAgent on the development Mac. Nothing needs to be installed on the VM. Look for the 🚀 icon in the top menu bar to open the separate Lensboard and LaneAward dropdowns, trigger manual runs, adjust schedules, or mark jobs as Include or Skip.
| Job | What it protects | Default schedule |
|---|---|---|
VM·STAGING — Database |
/var/lib/laneaward-staging/workforce.db on AWS |
Every 12 h |
LOCAL — Source Code |
Full project on Mac (archives excluded), hardlink snapshots | Weekly |
~/projectbackups/laneaward/staging-database/~/projectbackups/laneaward/source/~/projectbackups/backup_logs/backup.log
Each VM backup is a safe online copy — the service SSHs into the VM,
runs sqlite3 .backup (no downtime, no locking), then
scps the result to the Mac and cleans up the temp file.
Every copy is independently restorable.
Use the dedicated restore script — see Process 2B. The script handles confirmation, safety backup, service stop/start, and health verification in a single guided run. Do not restore manually.
sudo cp /var/lib/laneaward-staging/workforce.db /var/lib/laneaward-staging/workforce-$(date +%F-%H%M%S).db
Use this process when staging data must be recovered from a Mac-local backup. The script is fully guided — it shows you the backup and current database file sizes, requires two explicit typed confirmations, takes an automatic safety backup before making any changes, and verifies health after the restore completes.
Staging holds live work sessions and user records from real contributors. Before running this script, confirm:
RESTORE then YES — two separate gateslaneaward-workforce-api-staging.service~/projectbackups/laneaward/staging-database/workforce_vm_staging_YYYYMMDDTHHMMSS.dbls -lht ~/projectbackups/laneaward/staging-database/
The database runs in write-ahead logging mode, so recent changes can be sitting in a companion log file rather than in the database file itself. That log is validated by its own internal markers and not against the contents of any particular database, so a log left behind from the old file is treated as valid beside the restored one and applied on top of it — producing a database that appears restored and quietly is not.
Stopping the service cleanly normally folds the log into the database and removes it, so this only becomes reachable when the service was killed rather than stopped, or another process still held the database open. The script removes the log as part of the swap, which is safe because a restore already discards the current state deliberately.
Login sessions are stored in the database, so they are replaced along with it. Any session created after the backup was taken ceases to exist and that person is signed out when the service returns. Signing back in with the usual number is all that is required. Worth remembering when testing against staging with someone else mid-session.
The restore script connects through the laneaward-vm entry in the Mac's SSH
configuration, which points at the server's public address. No tunnel or client software needs
to be running. Confirm the connection before going any further:
[Mac-local]
ssh laneaward-vm "echo SSH OK"
If this times out, the cause is almost always the security group rather than the server — confirm this Mac's address is still authorized.
Run with no argument to auto-select the most recent backup, or pass a specific backup file path:
[Mac-local — most recent backup]
bash /Users/donaldscott/Project-Code/laneaward/repo/scripts/restore_staging_db.sh
[Mac-local — specific backup file]
bash /Users/donaldscott/Project-Code/laneaward/repo/scripts/restore_staging_db.sh ~/projectbackups/laneaward/staging-database/workforce_vm_staging_TIMESTAMP.db
A clean restore finishes with:
Restore complete. Staging is live and healthy.
Restored from : ~/projectbackups/laneaward/staging-database/workforce_vm_staging_TIMESTAMP.db
Safety backup : /var/lib/laneaward-staging/workforce-pre-restore-TIMESTAMP.db
A health check only proves the service started. Confirm the data is what you expect before deleting the safety backup:
[Mac-local]
curl -sS https://staging.timeontasks.laneaward.com/api/health
Then compare record counts against the backup you restored from:
[VM] Record counts in the restored database
sudo sqlite3 /var/lib/laneaward-staging/workforce.db "select 'customers', count(*) from customer_account union all select 'orders', count(*) from sales_order union all select 'sessions', count(*) from work_session union all select 'users', count(*) from app_user;"
A correct restore will not produce a database matching the backup's checksum. The database engine does not lay bytes out identically when a file is copied and reopened, and signing in to check the result writes a new login session, genuinely changing the file. Compare record counts and dates instead, and expect one extra login session row per person who has signed in since the restore.
Once confirmed, delete the server-side safety backup:
[VM]
sudo rm /var/lib/laneaward-staging/workforce-pre-restore-TIMESTAMP.db
ssh laneaward-vm 'sudo systemctl status laneaward-workforce-api-staging.service --no-pager'ssh laneaward-vm 'sudo journalctl -u laneaward-workforce-api-staging.service -n 50 --no-pager'/var/lib/laneaward-staging/workforce-pre-restore-TIMESTAMP.db — it can be used to roll back by running the restore script again with that file as the argument.Use the VM status helper when you want one quick read on service health, HTTP routes, and database row counts.
vm_workforce_status.sh
SERVICE_NAME=laneaward-workforce-api-staging.service DB_DIR=/var/lib/laneaward-staging DIRECT_HEALTH_URL=http://127.0.0.1:9193/api/health NGINX_HEALTH_URL=https://staging.timeontasks.laneaward.com/api/health ROOT_URL=https://staging.timeontasks.laneaward.com/ CONSOLE_URL=https://staging.console.laneaward.com/ LEGACY_WORKFORCE_URL=https://staging.timeontasks.laneaward.com/workforce/ /opt/laneaward-staging/workforce_app/deploy/vm_workforce_status.sh
This is the verified safe reset for staging activity only. It preserves users, roles, teams, customer references, sales orders, and ProfitMaker import metadata.
vm_workforce_reset_activity.sh
sudo env SERVICE_NAME=laneaward-workforce-api-staging.service LIVE_DB=/var/lib/laneaward-staging/workforce.db DIRECT_HEALTH_URL=http://127.0.0.1:9193/api/health NGINX_HEALTH_URL=https://staging.timeontasks.laneaward.com/api/health STATUS_SCRIPT=/opt/laneaward-staging/workforce_app/deploy/vm_workforce_status.sh /opt/laneaward-staging/workforce_app/deploy/vm_workforce_reset_activity.sh --yes
order_task, work_session, and material_usage, and also clears work_session_correction_audit through the work_session delete cascade.app_user and current PINs.--no-backup is used.This does not remove seeded users. User cleanup still needs either the admin tool or a dedicated user-maintenance process.
Use this when backend code or schema changed and you need to push the update to staging without replacing
the SQLite database. The script uploads server.py and schema.sql, promotes them
to /opt/laneaward-staging/workforce_app/backend/, restarts the service, and verifies health.
Schema migrations (new columns, indexes) are applied automatically by ensure_schema_upgrades()
on startup — no manual SQL required.
bash /Users/donaldscott/Project-Code/laneaward/repo/scripts/deploy_backend_to_staging.sh
A clean run finishes with the service showing active and a live health response.
If you only need to restart the service without uploading new files:
vm_workforce_cutover.sh
sudo env SERVICE_NAME=laneaward-workforce-api-staging.service DB_DIR=/var/lib/laneaward-staging DIRECT_HEALTH_URL=http://127.0.0.1:9193/api/health NGINX_HEALTH_URL=https://staging.timeontasks.laneaward.com/api/health ROOT_URL=https://staging.timeontasks.laneaward.com/ CONSOLE_URL=https://staging.console.laneaward.com/ LEGACY_WORKFORCE_URL=https://staging.timeontasks.laneaward.com/workforce/ STATUS_SCRIPT=/opt/laneaward-staging/workforce_app/deploy/vm_workforce_status.sh /opt/laneaward-staging/workforce_app/deploy/vm_workforce_cutover.sh --restart-only
The shared reference data — customer numbers, customer names, order numbers, and order descriptions — is refreshed automatically every night by the ProfitMaker reference pipeline. In normal operation no one runs anything by hand. This section explains how the nightly refresh works, how to check it, the one interim step during the ProfitMaker file migration, and the emergency manual fallback.
lane-fs zips the newest ProfitMaker snapshot at 22:30 Arizona (scheduled task, service account svc-pmsync) and uploads it with a SHA-256 checksum to a restricted, upload-only SFTP drop on the VM.pm-import.timer) verifies the checksum, extracts the snapshot, computes the rolling reference window from the newest snapshot date, and runs the importer against the staging database first, then production, health-checking after each and archiving the snapshot.
Never copy a local workforce.db over staging to update reference data. That permanently
destroys any users, sessions, or activity that exist only on staging. The pipeline (and the manual
fallback) update only the reference tables (customer_account,
sales_order, profitmaker_import_manifest) and never touch
app_user, work_session, order_task, or any other operational table.
The importer also recovers descriptions from AINONOTE.DBF for orders not yet promoted to
formal LNITM line items. Previously these "shell" orders displayed as
"Order NNNNNN" placeholder text in Time On Tasks and the Operations Console.
"plaque polshd blck acrylc 9" x 12"".AINONOTE.ONLINENO position."Order NNNNNN" — correct behavior when ProfitMaker has nothing more.
No operational action required. Schema is unchanged; this is purely an enrichment of the
sales_order.description field. Implementation lives in
workforce_app/backend/import_profitmaker_reference.py
(build_item_description_lookup and supplement_summaries_from_ainonote).
Until ProfitMaker's live files finish migrating onto lane-fs, the only manual step is to drop
the newest ASIDTA snapshot zip into the lane-fs drop folder — the nightly pipeline does the rest:
ASIDTA zip into D:\Automation\bucket on lane-fs.systemctl list-timers pm-import.timer
sudo tail -40 /var/log/pm-import.log
journalctl -u pm-import.service -n 60 --no-pager
Check the file log first. The systemd journal is written to the same disk as everything else, so when the disk is full journald stops recording and journalctl shows a clean, gap-free history for a period in which jobs were actually failing. That happened on 22 August 2026: an offsite backup run failed at 08:00 and the journal showed no trace of it, while the file log recorded the error correctly. The file log is written by the job itself and survives.
sudo systemctl start pm-import.service
Confirm the API is healthy and that current order search reflects the latest imported data. The health
endpoint is public, but since the session-token release (Ver 070301, 2026-07-07) the order-search route
requires an Authorization: Bearer token — a bare curl returns
{"error": "Authentication required. Please sign in."}. Sign in with the staging tester PIN
first to obtain a token.
curl -sS https://staging.timeontasks.laneaward.com/api/health
TOKEN=$(curl -s -X POST -H "Content-Type: application/json" -d '{"pin":"444444"}' https://staging.timeontasks.laneaward.com/api/auth/pin-login | python3 -c "import sys,json; print(json.load(sys.stdin)['token'])")
curl -sS -H "Authorization: Bearer $TOKEN" "https://staging.timeontasks.laneaward.com/api/orders/search?q=107923&limit=3"
The old hand-run push scripts (refresh_workforce_reference_snapshot.sh,
push_reference_to_staging.sh, push_reference_to_production.sh) were
retired on 2026-07-14 when the automated pipeline became the sole reference writer. They
now abort with guidance so they cannot be run out of habit. Only if the pipeline is genuinely down and
reference data must be refreshed immediately, an emergency override exists:
FORCE_MANUAL_PUSH=1 bash scripts/push_reference_to_staging.sh
Both paths use the same importer and are idempotent — a manual run followed by the next nightly run is
harmless. Full design detail: _documents/pm-reference-automation-architecture.html in the
repository (also on lane-fs under D:\Automation\docs\).
Phase 1 durability hardening is now part of the backend and should be preserved whenever the application
programming interface, or API, is updated. The Time On Tasks API now opens SQLite in
Write-Ahead Logging (WAL) mode, waits up to 10 seconds for short lock contention, uses
synchronous = FULL for safer commits, and wraps each mutating route in a short
BEGIN IMMEDIATE write transaction.
PRAGMA journal_mode = WAL, which enables Write-Ahead LoggingPRAGMA busy_timeout = 10000, which gives SQLite up to 10,000 milliseconds to wait on a short lockPRAGMA synchronous = FULL, which favors safer disk writes over speedHTTP 503 Service Unavailableretryable: trueIteration test checklist:
The current backend reliability work is best understood as safe but incomplete, not partially conflicting. The first five database-reliability features are fully implemented and should remain in place together:
Write-Ahead Logging (WAL)busy_timeout = 10000synchronous = FULLBEGIN IMMEDIATE write transactionsHTTP 503 responses for SQLite busy/locked contentionIn practical terms, these five changes make the shared SQLite backend safer under short write collisions, safer during commit, and clearer when contention happens. They do not depend on the unfinished Phase 2 client work in order to remain valid.
Phase 2 (client-side retry/backoff, temporary local storage, idempotent write keys) was evaluated and deferred. A concurrent stress test at 2× the expected user load passed cleanly with significant headroom — Phase 1 alone is sufficient at current scale. Phase 2 should be reconsidered only if load grows significantly beyond current projections.
Time On Tasks includes a service worker at
timeontasks/sw.js
that improves load speed on shared tablets by caching static assets locally after the first visit.
Understanding the cache strategy is important before deploying frontend changes.
| Request type | Strategy | Why |
|---|---|---|
HTML documents (index.html, user-guide.html) | Network-first | Always fetches fresh HTML so deployed updates are visible on the next page load without any SW changes. |
Versioned static assets (app.js?v=…, icons, manifest) | Cache-first | Version token in the URL acts as the cache key. New token = new URL = automatic cache miss = fresh fetch. |
/api/* and all non-GET requests | Network-only | Task writes, session state, and PIN login must never be served from cache. |
No changes to sw.js are required. The version token does the work.
app.js or other assets.index.html (e.g. app.js?v=20260406-foreman1 → app.js?v=20260411-myfix1).index.html and the updated asset file.index.html (network-first), browser sees the new token URL, cache misses, fetches new asset, caches it. Done.CACHE_VERSION in sw.js. The new SW will delete all prior caches on activate.When updating sw.js, the browser detects the change automatically (byte-for-byte comparison on every page load). The new SW installs in the background, then activates and claims all open tabs immediately via skipWaiting and clients.claim.
index.html. The old token URL stays in cache and will be served.app.js as transferred, or add --checksum to force a content comparison.index.html to a cache-first rule. HTML must always be network-first or the stale-app-shell problem returns.
Run this test against staging to confirm that the Phase 1 reliability improvements (WAL,
busy_timeout, synchronous = FULL, BEGIN IMMEDIATE) hold up
under the expected concurrent load of up to 20 simultaneous tablet operators on the shop floor.
This test was completed before go-live and passed cleanly. The procedure is preserved here as a
reference for future validation runs (e.g. after significant backend changes or scale increases).
The test script is at workforce_app/backend/stress_test_concurrent.py and runs from
your local Mac. It seeds its own test fixtures into the staging database via SSH, runs the load,
then cleans up after itself.
ubuntu@3.130.69.109 with no passphrase prompt (BatchMode).https://staging.timeontasks.laneaward.com.cd ~/Project-Code/laneaward/repoThe test seeds and cleans up via SSH. Confirm it works first:
ssh -i ~/.ssh/lane_webserver.pem ubuntu@3.130.69.109 echo "SSH OK"
You should see SSH OK with no password prompt. If you see Permission denied (publickey), the key path is wrong or the key is not present — check that ~/.ssh/lane_webserver.pem exists on your Mac.
This is the test to run before go-live. It simulates 20 human-paced operators for 2 task cycles each. Expected wall time is roughly 20–40 seconds.
python3 workforce_app/backend/stress_test_concurrent.py
The script will print its progress as it seeds, runs, and cleans up:
[seed] Seeding 20 test user(s) on ubuntu@3.130.69.109 ...
[check] Verifying https://staging.timeontasks.laneaward.com/api/health ...
[run] Releasing 20 threads simultaneously ...
[cleanup] Removing stress-test fixtures from remote DB ...
At the end of the run, the script prints a report. A clean pass looks like this:
====================================================================
LANEAWARD TIME-ON-TASKS — CONCURRENT STRESS TEST
====================================================================
Mode: REALISTIC (human-paced 1.5–4.0 s)
Target: https://staging.timeontasks.laneaward.com
Concurrent users: 20
Iterations/user: 2
Total ops: 280
Wall time: 31.4s
Outcomes:
Successes: 280 (100%)
Hard failures: 0 (non-retryable errors or timeouts)
Retryable busy: 0 (SQLite busy-wait — server queued OK)
End-to-end latency (successful ops only):
p50: 210 ms
p75: 310 ms
p95: 480 ms
max: 740 ms
VERDICT
------------------------------------------------------------------
PASS All operations completed cleanly under concurrent load.
WAL + BEGIN IMMEDIATE handled 20-user contention with no busy errors.
Latency looks good for tablet UX (p95 = 480 ms).
The burst test fires all writes near-simultaneously with no human delay. This scenario cannot occur with real operators, but it validates the SQLite busy-timeout safety net as an absolute ceiling. Run it after the realistic test passes.
python3 workforce_app/backend/stress_test_concurrent.py --burst
Expect more retryable busy responses in this mode — that is normal and expected. The important thing is still zero hard failures.
--users N — number of concurrent users, 1–20 (default: 20)--iterations N — task cycles per user (default: 2)--burst — near-simultaneous writes, ceiling test only--no-seed — skip seeding (test users already in DB from prior run)--no-cleanup — leave test fixtures in DB for inspection--local — spawn a local server with a temp DB (no SSH, for dev use)--users to match headcount before each go-live milestone--iterations simulates a longer shift with more task cycles per personbusy_timeout plus network — very unlikely at human pace, indicates a server resource issuesudo journalctl -u laneaward-workforce-api-staging -n 100login only may indicate the pin-throttle table is accumulating rows — run Process 4 (Activity Reset) to clear it on stagingping staging.timeontasks.laneaward.com from the same machine
All stress test data is prefixed with stress_test_ so it is unambiguous in the
database. If a run is interrupted before cleanup, remove the leftovers manually:
bash scripts/cleanup_stress_test_staging.sh
The server is protected by two layers: an AWS Security Group that restricts SSH (port 22) to authorized IP addresses, and a Twingate connector that allows SSH from any location through the Twingate client. Use this process to connect to the server and to update security group rules when IP addresses change. This process applies to both the staging and production environments since both run on the same AWS server.
| Group ID | sg-0cc9719fa0e029c40 (launch-wizard-1) |
|---|---|
| COX Fiber — office | 98.175.1.150/32 · SSH allowed |
| COX Cable failover — office | 72.215.199.214/32 · SSH allowed |
| Development Mac — required | 72.208.129.218/32 · SSH allowed · carries all deploys, backups and restores |
| HTTP / HTTPS | Open to all — 0.0.0.0/0 |
| Remote Network | Lane Award PWA Server |
|---|---|
| Connector | eggplant-okapi |
| Resource address | 172.31.7.224 (server private IP) |
| SSH key | ~/.ssh/lane_webserver.pem |
The Mac's ~/.ssh/config must list the server's public IP as a name on the
laneaward-vm block, with IdentitiesOnly yes:
Host laneaward-vm 3.130.69.109
HostName 3.130.69.109
User ubuntu
IdentityFile /Users/donaldscott/.ssh/lane_webserver.pem
IdentitiesOnly yes
The deploy scripts connect as ubuntu@3.130.69.109 rather than by the alias.
Without the IP on that line those connections match no host block, so
IdentitiesOnly is not applied and the SSH agent offers every key it holds. The
server allows six authentication attempts, the agent currently holds seven keys, and
lane_webserver.pem is last in the agent's order — so the right key is never
reached and the deploy fails with "Too many authentication failures".
It fails every time rather than intermittently, and it reads as a credential or firewall
problem, which is the wrong place to start. Diagnosed and fixed 28 August 2026. If the
config is ever lost mid-task, prefixing a command with SSH_AUTH_SOCK=
bypasses the agent as a one-off.
Use this method when connecting from home or any location not on an authorized static IP. The Twingate client must be running and connected before opening SSH.
The Twingate icon lives in the Mac menu bar. Click it and verify the connection status is active.
[Mac-local]
ssh -i ~/.ssh/lane_webserver.pem ubuntu@172.31.7.224
Use this method when connecting from the office on either the fiber or cable connection. Twingate does not need to be running.
[Mac-local]
ssh -i ~/.ssh/lane_webserver.pem ubuntu@3.130.69.109
When the Twingate client is active, it intercepts connections to the server's public IP and
routes them through the connector. Pause Twingate first before using Option B, or use the
private IP (172.31.7.224) with Twingate active instead.
Run this on the server to confirm the connector service is running. A healthy connector shows State: Online in the log output.
[VM]
sudo systemctl status twingate-connector --no-pager
Run these steps when an authorized IP address changes. Requires the AWS CLI configured on the development Mac with IAM user donald.
[Mac-local]
aws ec2 describe-security-groups --group-ids sg-0cc9719fa0e029c40 --query "SecurityGroups[0].IpPermissions" --output json --no-cli-pager
[Mac-local]
aws ec2 revoke-security-group-ingress --group-id sg-0cc9719fa0e029c40 --protocol tcp --port 22 --cidr OLD.IP.ADDRESS/32
[Mac-local]
aws ec2 authorize-security-group-ingress --group-id sg-0cc9719fa0e029c40 --protocol tcp --port 22 --cidr NEW.IP.ADDRESS/32
[Mac-local]
aws ec2 describe-security-groups --group-ids sg-0cc9719fa0e029c40 --query "SecurityGroups[0].IpPermissions" --output json --no-cli-pager
Do not remove an IP that is your current connection without first confirming Twingate SSH works, or without another authorized IP still in place. If all SSH access is lost, recovery requires the AWS Console. Never remove all three SSH rules at once.
This section previously carried a pending task to revoke 72.208.129.218/32,
described as a leftover home-lab address. That was wrong and the task is
withdrawn. The address is the development Mac's own outbound address, confirmed by
asking the server which source address it sees. Every deploy script, both restore scripts
and the twice-daily database backups reach the server from it.
Revoking it breaks deploys, backups and restores simultaneously, with no common symptom to point at the cause. It is a residential address, so it can change — when it does, replace the rule using the procedure above rather than deleting it. Confirm the current value first:
[Mac-local] Ask the server what address it sees
ssh laneaward-vm 'echo ${SSH_CLIENT%% *}'
Signing in with a PIN issues a login session that lasts 12 hours as a sliding window — each use resets the clock, so an actively used device stays signed in and only idle time expires it. After roughly 12 hours of inactivity the app returns to the sign-in screen; the contributor or manager simply re-enters their PIN. No data is lost.
An automatic logout does not stop a running task timer. Work sessions live in the database and keep running until someone uses Pause, Stop, or Complete, or a manager applies a correction in the console. If a tablet logs itself out overnight while a task was left active, that task is still running — treat it as a potential runaway timer and correct it; do not assume the logout closed it.
Salaried contributors log time through the Time On Tasks app exactly like hourly workers. The Order Profitability report converts their annual salary to an effective hourly rate using the U.S. Bureau of Labor Statistics standard:
Effective Hourly Rate = Annual Salary ÷ 2,080
Session Labor Cost = (Annual Salary ÷ 2,080) × (Session Minutes ÷ 60)
2,080 = 52 weeks × 40 hours — the standard used by ADP, Paychex, QuickBooks, and the BLS.
LANEAWARD_SETUP_LOG.mdworkforce_app/deploy/README.mdaws-server-setup-summary.htmlLANEAWARD_ENVIRONMENT_TOPOLOGY.htmltimeontasks/index.htmltimeontasks/app.jstimeontasks/sw.js — service workertimeontasks/manifest.webmanifesttimeontasks/user-guide.htmlops_console/index.htmlops_console/runbook.html — deploy copy of this document; canonical source is _documents/staging/runbook.htmlworkforce_app/backend/stress_test_concurrent.py — concurrent pre-go-live validation script