Purpose of this exercise

AI-assisted Development Exploration

💡

The most useful findings are in the Honest Assessment section — that's where this exercise answers the question it was designed to answer: where does AI genuinely help, and what should we not rush into? Jump to assessment →

🏗️
A full local copy of Pharos360, its Rails API, and PostgreSQL database now runs in Docker on a single laptop — enabling development and testing without touching production or AWS.
♻️
The Present attendance system — originally a Java/GWT application — was completely rewritten as a modern Laravel/PHP app. It runs today, connected to live Pharos360 data, with all original features plus accessibility improvements.

📄 Read the production rewrite rationale →
📅
The Timefree scheduling app — which handles appointments, availability, and Google/Microsoft calendar sync — was debugged and restored to working order, including a calendar sync fix that had been silently broken.
Runs in Docker — start with docker compose up -d timefree timefree-worker from Server/.
🔍
Legacy Code Archaeology
Claude Code traced obscure bugs through decades-old PHP and Rails code: broken path resolution, missing database sequences, undefined functions, and misconfigured auth — diagnosing issues that would have taken days to find manually.
Accessibility (WCAG)
Both Pharos360 and Present received targeted accessibility fixes: keyboard navigation, screen-reader roles, focus management, contrast improvements, and proper semantic markup.
🎨
Modern Theme Toggle
Pharos360 and Present each gained a live "Classic / Modern" theme switcher — visible in the demo — that updates typography, spacing, and layout without breaking any existing functionality or client branding.
📋
Documentation
The entire local dev environment — all four projects, Docker commands, credentials, known gotchas, console shortcuts — was captured in a living reference document during the session.
💬
A standalone discussion facilitation tool — not currently part of the product suite — that was modernized as an exploratory project. The legacy stack was containerized and a full rewrite in React was begun, with a working UI and a key session bug resolved.

The distinction that matters

  • AI lowers the cost of exploration — four apps running locally, documented, and extended in hours instead of weeks
  • It does not replace developer judgment on production decisions, security review, or architecture
  • Those still require people

Live apps running right now

Open these in your browser

Pharos360
The full legacy PHP application — student profiles, cases, launchpad, email — running locally with fake student data and a placeholder logo.

Login: pharos.maintenance / Maintenance!
Open Pharos360
Present (Attendance)
The rewritten Laravel app. Browse courses, take attendance, view history, export rosters. Single-sign-on with Pharos360 — log in there first, then open Present.

Standalone login: F0012 / test
Open Present
Timefree (Scheduling)
Appointment scheduling, availability management, and Google/Microsoft calendar sync. Runs in Docker — start with docker compose up -d timefree timefree-worker from Server/. Log in at /sessions/new.

Demo flow: Open the faculty view, create an availability slot, then open the student view in an incognito window and book it.
Faculty view Request appointment (incognito)
Headsup (bonus)
Standalone facilitation app — not part of the current product suite. Both the legacy stack (Node 6 + Sencha Touch) and the in-progress React rewrite run in Docker simultaneously.

Start with docker compose up -d from Server/. Facilitator logs in at /; participants join at /join.
Facilitator view Join session (participant)

Honest Assessment

Where AI genuinely helps — and where to be careful

The framing behind this exercise

  • Goal: understand where AI is genuinely useful on real legacy code — not what the marketing says
  • Useful in specific, bounded ways
  • Requires developer judgment to be safe
🧪
No real data was used
  • All work done against fake students, placeholder credentials, and a local database
  • Auth, session handling, and data access carry real risk with actual student records
🔍
No peer review or test suite
  • Every change went from AI output directly to running code — a red flag in a team environment
  • AI produces complete-looking code regardless of whether a case was handled securely
🏗️
Greenfield is easier than incremental
  • Rewriting Present from scratch is a better AI fit than modifying a live production system
  • Live systems have downstream effects, real-data edge cases, and behavior that only surfaces under load
⚖️
AI is confidently wrong
  • Doesn't understand your security model, compliance obligations, or infrastructure
  • Can produce code that looks correct in testing but handles a case insecurely
  • Human review is not optional — it's the control that makes this viable

For developers

  • AI needed direction throughout — it didn't produce production-ready code autonomously
  • Claude Code surfaced the security findings in the Security Findings tab; they still need developer review before any code reaches production
  • Production hardening, architecture, and security decisions still require experienced developers
  • The value is faster exploration and faster first drafts — not fewer developers

What responsible production use requires

Developer review of every change

  • Treat AI output like a PR from a junior developer — read, questioned, approved before production

A testing layer before deployment

  • No test suite was the single biggest risk in this pilot
  • Automated tests must exist before AI-assisted changes reach production

Defined scope for AI involvement

  • Best for: scaffolding, diagnosis, boilerplate, refactoring
  • Extra scrutiny for: auth, session handling, data export, anything touching student PII

Staged rollout with a rollback plan

  • CI/CD pipeline with staging, automated tests, and a rollback path
  • That pipeline does not yet exist for these apps

Technical breakdown

What was built and how

A full account of the Docker infrastructure, Pharos360 fixes, the Present rewrite, Timefree containerization, and the directory reorganization — with the key decisions and gotchas that came up along the way.

Pharos360 PHP app Present Laravel app Timefree Rails app Headsup Node app Docker Compose v2 PostgreSQL 12 MongoDB 3.4 + 4.4
Docker Local Dev Infrastructure
  • Services
    Service Description Port
    dbPostgreSQL 12 — shared by Pharos360, Present, Timefree5432
    pharos360Ubuntu 22 + PHP 8.3 + Apache + Rails 3.x API (via rbenv)80
    presentPHP 8.3 + Laravel — attendance rewrite8080
    timefreeRails 4.2 + Ruby 2.7.8 (rbenv) — scheduling web app3002
    timefree-workerDelayed Job worker — starts after timefree health check passes
    headsupNode 6 + Sencha Touch — legacy facilitation stack3000
    headsup-nextExpress 5 + Socket.io 4 — rewrite API server3001
    headsup-clientReact 19 + Vite 6 + Tailwind v4 — rewrite frontend5173
    headsup-mongoMongoDB 3.4 — legacy stack databaseinternal
    headsup-mongo-nextMongoDB 4.4 — rewrite database (also holds seed data)internal
    Start all: cd Server/ && docker compose up -d
  • Volume mounts Live bind mounts — edit code on the Mac, see changes immediately in the container. No rebuild needed for PHP/Blade edits.
  • DB persistence Named volume pgdata survives docker compose down. Only docker compose down -v wipes it.
  • Critical gotcha configuration.php is generated by the container entrypoint on every startup — it is not a static file. It writes $CLIENT = 'localdev', which all file-path resolution depends on. If this ever breaks, rebuild the image: docker compose up -d --build pharos360
  • URL requirement Must use http://localdev.pharos360.com, never http://localhost. The client() function reads the subdomain to build paths like /var/www/sos/localdev/site/…. Localhost = empty subdomain = double-slash paths = broken includes and a stuck "Loading…" header.
  • supervisord Manages Apache + Rails Thin server inside the pharos360 container. Apache proxies /api/… → Rails on port 3000, stripping the prefix.
  • docker-compose v1 Not available on this machine. Always use docker compose (v2 plugin, space not hyphen).
PHP Pharos360 — Bug Fixes & Fixes
  • cases/queries.php Multiple scalar subqueries failed with "more than one row returned." Fixed by converting = (SELECT …) to EXISTS (SELECT 1 …) in 3 places, and adding LIMIT 1 to 4 others.
  • airbrake.php The Airbrake PHP library throws a fatal exception if projectKey is an empty string. Blanked to a no-op stub — notifyAirbrake() still exists but does nothing in local dev.
  • photo.php Fake-user image check (Q-prefix student IDs) moved before the session check so avatar images serve without a valid Pharos session — needed by Present's photo proxy.
  • emails/functions.php Added missing user_to_email() function, referenced everywhere but never defined. Looks up email from students table (students) or names table (staff) by username.
  • DB sequences All PostgreSQL ID sequences were out of sync after bulk import. Reset every _id_seq to MAX(id) of its table.
  • delayed_jobs table Table was entirely missing. Created with standard schema plus the custom client VARCHAR(255) column that Pharos::Delayed::Job requires.
  • Rails client fallback PHP calls the Rails API as http://127.0.0.1/api/… (no subdomain). Added fallback in application_controller.rb: when host matches 127.0.0.1, sets client = 'localdev'.
  • Student avatars Generated 40 placeholder PNG avatars for fake students (Q1001–Q1039) using PHP GD. Script at Server/generate_avatars.php.
Laravel 11 Present — Full Rewrite (Java/GWT → PHP) 📄 Production rationale →
  • What it replaced A Java/GWT application. The rewrite is a Laravel 11 PHP app that shares the existing Pharos360 PostgreSQL database (roster_* tables) — no data migration needed.
  • Auth Reads pharos360_session_id cookie first, falls back to PHPSESSID (set by Pharos360 on every page load). Both validated via internal HTTP call to http://pharos360/partials/cookie_checker.php. Both cookies excluded from Laravel encryption in bootstrap/app.php.
  • Duplicate rows class_faculty and view_class_list_filtered_with_names contain duplicates. All queries use DISTINCT.
  • phpspreadsheet v5 ByColumnAndRow() methods removed in v5. Replaced with Coordinate::stringFromColumnIndex($col) . $row.
  • Spark lines Per-student attendance history rendered as colored vertical bars (4px wide). Built in CourseController::buildSparkLines(), reused by AttendanceController.
  • Learn Names Students shuffled on each visit. studentsJson prepared in the controller (not in Blade) to avoid PHP parser issues with fn() closures inside @json().
  • Features Course list sidebar, Take Attendance (photo cards, cycle status, bulk email), History, Summary, Export (CSV + XLSX), Learn Names modal, Event type management, per-user Settings.
WCAG Accessibility Improvements (both apps)
  • Pharos360 Skip-to-main link, role="main", role="navigation", role="banner", keyboard-accessible app menu (role="button", tabindex, aria-expanded), flash message roles (role="alert" / "status"), viewport meta tag added globally.
  • Present Student cards: role="button", tabindex="0", Enter/Space keyboard handlers, dynamic aria-label as status cycles. Learn Names modal: focus trap, Escape closes, focus returns to trigger. Spark lines: role="img" + aria-label. Contrast: #aaa#6b7280.
  • Modern theme Both apps got a floating Classic/Modern toggle (bottom-right, persisted via localStorage). All modern styles scoped to html[data-theme="modern"]. Anti-FOUC inline script applies saved theme before first paint. Client color system left intact in Pharos360.
Node / React Headsup — Facilitation App (bonus — not current product suite)
  • What it does Facilitates structured group discussions: a facilitator runs sessions through a state machine (init → started → grouping → discussing → reporting → evaluating → closed), participants join on mobile, and the facilitator drives prompts and evaluations in real time via Socket.io.
  • Legacy stack Node.js + Express 3.2.4, MongoDB (mongoskin), Socket.io 0.9.14, Sencha Touch (Ext JS mobile) compiled client, Google OAuth — port 3000. Runs in Docker with platform: linux/amd64 required for Apple Silicon.
  • Rewrite stack Node.js + Express 5, Socket.io 4, MongoDB driver v6, React 19 + Vite 6 + Tailwind v4, Google OAuth. Server on port 3001, client dev server on port 5173. Both run simultaneously in Docker alongside the legacy stack.
  • Key bug fixed Stale device cookie: middleware/device.js trusted the browser's device cookie without verifying the document existed in MongoDB. After a volume wipe, the cookie pointed to a deleted device, causing group.js to find 0 active sockets and throw "No participants have joined." Fix: middleware now verifies the device in DB before trusting the cookie; falls back to creating a new one. Socket updateOne also uses upsert: true.
  • Styling pass Full modern UI pass on the React rewrite: Inter font, indigo-600 primary, slate neutrals, card shadows, AdminPanel as a numbered step flow (1–4 with checkmarks), WaitingRoom with prominent join code and pulsing participant dot, mobile-first participant views, SVG icons replacing decorative emojis.
  • Auth Google OAuth with hardcoded credentials in lib/authentication.js — still active. Session stored in MongoDB (web.sessions collection).
  • Start command Run docker compose up -d from Server/ — Headsup is now merged into the main compose. To rebuild only the rewrite: docker compose up headsup-next headsup-client --build -d from Server/.
  • Seed defaults Roles and prompt sets (Six Thinking Hats, SWOT Analysis, Pro/Con, Generic Prompts) are not auto-seeded on first start. Log in via Google OAuth first — the seed looks up the first user in the DB and will error if none exists. Then run once from the Claude/ root:
    docker compose -f Server/docker-compose.yml exec -T headsup-mongo-next mongo < Headsup/seed.js
    Safe to re-run — clears and re-inserts the defaults each time.
Rails 4.2 Timefree — Scheduling & Calendar Sync
  • What it does Appointment scheduling, staff availability management, and bidirectional sync with Google Calendar and Microsoft (via Graph API). Separate from the Pharos360 Docker network — has its own database.
  • Stack Rails 4.2 (via RailsLTS subscription — security patches backported to 4.2), Ruby 2.7.8, PostgreSQL. Runs in Docker alongside Pharos360 and Present — shares the same db container network. Available at http://localhost:3002.
  • Starting the app cd Server/ && docker compose up -d timefree timefree-worker. Login page: http://localhost:3002/sessions/new. The docker compose up command also works to start all services at once.
  • Delayed Job worker The timefree-worker container starts automatically once the web container passes its health check. It processes Google Calendar sync and other background jobs. Check status with docker compose ps — should show running.
  • Ruby / image Ruby 2.7.8 is installed via rbenv inside the Docker image (Server/Dockerfile.timefree). No local Ruby or mise required. First build takes ~15 min (compiles Ruby from source); subsequent starts are fast via layer cache.
  • Standalone mode BYPASS_PHAROS_360=true in config/env_var.rb — email-only login, no password, no Pharos360 session check. Full integration requires both apps on .pharos360.com subdomains via /etc/hosts.
  • Google Calendar sync fix Timefree was sending Google API webhook notifications to an invalid port by default. Fixed by correcting the callback URL and disabling SSL verification on that internal route (3f2730a).
  • Availability blocking fixes Two edge-case bugs fixed: events overlapping the start of an availability window, and events overlapping the end — both were failing to block the slot correctly (d1fa282, 38e7142).
  • Known issue All-day Google Calendar events silently block availabilities — the week view doesn't show all-day events, so the user gets no indication of why their availability isn't appearing. Needs a warning or fix.
  • Other fixes Google Calendar delete support, inaccessible calendar checks, external calendar sorting, option to have no default sync calendar, removed dead-end "Update" button from user settings, excessive sync log suppression.
Git Repository & Directory Cleanup
  • Before A Projects/ subfolder contained Present and Beacon docs alongside the top-level repo clones, causing confusion about which copy was authoritative.
  • After Present/ moved to root of Claude/. Projects/ removed. Docker volume mount updated from ../Projects/Present../Present. Image rebuilt.
  • .gitignore Added GoogleCreds (irreplaceable Google API credentials, no longer re-exportable from Google Console) and .DS_Store.
  • Separate git repos Pharos360-2.x/ has its own .git (branch master). Present/ has its own .git. The root Claude/ repo tracks infrastructure files and this launchpad.
  • Timefree Separate Rails scheduling app — now Dockerized alongside Pharos360 and Present. Two containers: timefree (web, port 3002) and timefree-worker (Delayed Job). Start with docker compose up -d from Server/.
  • Hosted demo pharos.cloudaidemo.com — read-only launchpad hosted on Cloudflare Pages (Launchpad/hosted.html). Access-controlled via Cloudflare Zero Trust (Google OAuth, allowlist by email). To update: edit hosted.html, redeploy to Cloudflare Pages. To sync with local launchpad: cp Launchpad/index.html Launchpad/hosted.html then redeploy.

Static code analysis · June 2026

Security findings

Findings from a static analysis pass across all four codebases — Pharos360, Timefree, Present, and Headsup. No runtime testing. Professional penetration testing is conducted three times per year plus one annual BurpSuite pentest; these findings are a complement to that, not a replacement.

Internal use only. This tab documents known vulnerabilities. Do not share this version of the launchpad externally — use pharos.cloudaidemo.com for external audiences.

2 High 6 Medium 1 Low 1 Informational
High Critical findings requiring prompt attention
  • Headsup — OAuth secrets in git Google OAuth client secrets are hardcoded in two source files that are tracked in git: headsup-server/lib/authentication.js:265 and headsup-server-next/src/routes/auth.js:7. Anyone with repository access has live credentials. Both use the GOCSPX- prefix confirming they are active Google OAuth secrets. These should be rotated and moved to environment variables.
  • Headsup — Arbitrary MongoDB collection access headsup-server-next/src/routes/items.js passes req.params.collection directly to db.collection(collName) with no whitelist. An authenticated user can read, write, update, or soft-delete any collection in the database — including users, web.sessions, and devices — by crafting a URL with an arbitrary collection name.
  • Pharos360 — CSRF coverage sparse Only 26 of 776 PHP files include CSRF tokens. The token implementation is correct where used, but the vast majority of form-handling pages are unprotected. A likely finding in the next pentest.
  • Pharos360 — Technical reset tool missing auth guard technical/reset_database/ajax.php runs shell_exec against database reset scripts. Unlike show_file_raw.php which calls $user->ensureMaintenance() explicitly, this file includes utilities.php but no explicit auth check was found. If utilities.php does not enforce auth by default, this endpoint may be callable without authentication. Needs verification.
Medium Should be addressed in near-term development
  • Timefree — html_safe with user data app/controllers/home_controller.rb:17 interpolates other_user.full_name into an html_safe string. If full_name contains user-supplied HTML it will be rendered unescaped — a stored XSS vector. Most other html_safe usages in Timefree are for Font Awesome icon markup and are low risk.
  • Timefree — raw JSON output without json_escape home/index.html.erb:78 and home/_event_source.html.erb:17 use raw source.to_json without json_escape. If source objects contain user-controlled strings with </script> sequences this is an XSS vector. By contrast, appointments/_form.html.erb correctly uses raw json_escape(...) — the home view should match that pattern.
  • Pharos360 — MD5 passwords still accepted utilities.php:104password_verify($password, $hash) || $hash === md5($password). The app accepts both bcrypt and MD5 hashes for legacy account compatibility. Any account not re-hashed since migration is vulnerable to rainbow table attacks if the database is compromised.
  • Pharos360 — Session regeneration minimal Only 5 session_regenerate_id calls across 776 files. Session fixation is a likely finding in the next pentest.
  • Pharos360 — Path traversal partial mitigation technical/show_file_raw.php filters / from the filename parameter but passes it to exec("cat /home/jails/$CLIENT/home/$CLIENT/uploads/$filename"). The slash-only filter may be bypassable depending on how the web server handles encoded characters. Auth check ($user->ensureMaintenance()) limits exposure to authenticated maintenance users.
  • Pharos360 — Hardcoded API key utilities.php:1755 contains a 128-character hex string assigned to $api_key. Appears to be a live credential. The service it authenticates to was not identified in static analysis.
Low / Informational Note for awareness
  • Timefree — secrets.yml in app directory Timefree/config/secrets.yml contains real secret_key_base values for all three environments. The file is correctly gitignored and not tracked, but lives alongside bind-mounted source code. Accidental commit would be a serious exposure. Consider moving to environment variables consistent with env_var.rb.
  • Present — Raw SQL queries (informational) DB::select() is used in several controllers but all user input uses ? placeholders. The interpolated $statusSums is server-generated from a status list, not user input. No injection risk identified — noted for awareness given the pattern.
Not assessed Outside scope of static analysis
  • Runtime / network TLS configuration, HTTP security headers (CSP, HSTS, X-Frame-Options), cookie flags (Secure, HttpOnly, SameSite), response-level information disclosure. These require runtime testing — covered by the existing BurpSuite pentest.
  • Dependency CVEs Third-party packages not audited here. Timefree uses RailsLTS for Rails 4.2 security backports. Run bundle audit (Timefree), npm audit (Headsup), and composer audit (Present) for current CVE status.
  • Infrastructure EC2 security groups, IAM policies, S3 bucket permissions, chroot jail configuration — not assessed.