Purpose of this exercise

AI-assisted Development Exploration

💡

The most useful findings are in the 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 copy of Pharos360, its Rails API, and PostgreSQL database runs in Docker on an EC2 instance — enabling development and demonstration without touching production.
♻️
The Java/GWT Roster app rewritten as a modern Laravel/PHP application. Runs as a separate server, connected to live Pharos360 data, with all original features plus accessibility improvements.
The same attendance functionality built directly into Pharos360 — no separate server, same PHP codebase the team already knows, per-client feature flag controlled by pharos.maintenance. Core features complete; email notifications not yet wired.

📊 Compare both approaches →
🤖
A chat-first AI interface built into the attendance module. Ask natural language questions about attendance patterns, flag at-risk students, and generate faculty referral drafts — all powered by Claude, with markdown-rendered responses.
📅
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.
☁️
Cloud Infrastructure via Terraform
The entire demo environment is provisioned on AWS using Terraform — EC2, VPC, IAM, security groups, and DNS. Caddy handles HTTPS and wildcard subdomain routing. All apps are accessible at subdomains of aidemo.pharos360.com.
🔍
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 environment — all 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 in the cloud, 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 on AWS with fake student data and a placeholder logo.

Login: pharos.maintenance / Maintenance!
Open Pharos360
AI Attendance Assistant
Chat-first UI for attendance analysis. Ask about patterns, consecutive absences, class comparisons, or at-risk students. Generate a referral draft from any student ID.

Log in as pharos.maintenance / Maintenance!, then use Test User Accounts (Administration menu) to become judy.fakey. Then open the AI Assistant.
Open AI Assistant
Option B — Attendance in Pharos360
Attendance built directly into Pharos360 — same app, no separate server.

Enable demo: Log in as pharos.maintenance / Maintenance! → Settings → Enable Attendance.

Faculty demo: Use Become User to become judy.fakey → click Attendance in the nav.
Open Attendance
Timefree (Scheduling)
Appointment scheduling, availability management, and Google/Microsoft calendar sync.

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. The in-progress React rewrite runs in Docker on the demo server.

Facilitator logs in at /; participants join at /join.
Facilitator view Join session (participant)

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 demo 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 Terraform infrastructure, Docker setup, Pharos360 fixes, the AI attendance assistant, the built-in attendance integration (Option B), the Present rewrite (Option A), Timefree containerization, and the directory reorganization — with the key decisions and gotchas that came up along the way.

Pharos360 PHP app AI Attendance Assistant Attendance in Pharos360 Present Laravel app Timefree Rails app Headsup Node app Docker Compose v2 Terraform on AWS PostgreSQL 12
Terraform / AWS Cloud Infrastructure
  • Provisioned with Terraform — all AWS resources defined as code and applied in one pass. State stored locally at Claude/terraform/.
  • EC2 instance t3.large, Ubuntu 22.04 LTS, 50 GB gp3 encrypted root volume. Instance ID: i-04cebc4b3ede3cfa7. Managed via AWS SSM (no SSH keys needed).
  • Networking Dedicated VPC with public subnet. Security group allows 80/443 from anywhere, SSM traffic only. Elastic IP not used — static IP via instance association.
  • IAM Instance role pharos-aidemo-role grants SSM access (for remote commands) and S3 read on aidemo/* and demodefaults/* prefixes only.
  • Reverse proxy Caddy handles HTTPS termination and wildcard subdomain routing. Each app maps to a subdomain of aidemo.pharos360.com — Caddy proxies to the appropriate Docker container port.
  • DNS Rackspace A records: aidemo.pharos360.com and *.aidemo.pharos360.com44.197.141.230.
  • Subdomains p360.aidemo.pharos360.com → Pharos360 + attendance + AI assistant · present.aidemo.pharos360.com → Present (Laravel) · timefree.aidemo.pharos360.com → Timefree · headsup.aidemo.pharos360.com → Headsup Next
  • Server startup Run docker compose -f docker-compose.server.yml up -d from /opt/pharos/Server/. Do NOT use the base docker-compose.yml on the server — it conflicts with Caddy on port 80.
  • ANTHROPIC_API_KEY Stored in /opt/pharos/Server/.env (not in git). Injected into the pharos360 container via docker-compose.server.yml. Key belongs to pharosresources.com Anthropic account.
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
    caddyReverse proxy — HTTPS termination + subdomain routing (server only)80/443
  • 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 = 'p360' (cloud) or $CLIENT = 'localdev' (local), which all file-path resolution depends on.
  • supervisord Manages Apache + Rails Thin server inside the pharos360 container. Apache proxies /api/… → Rails on port 3000, stripping the prefix.
Claude / PHP AI Attendance Assistant
  • What it is A chat-first AI interface embedded in the Pharos360 attendance module. Faculty ask questions in natural language; the AI queries the database using tool calls and returns markdown-rendered answers with tables, bullet points, and bolded names.
  • Entry point attendance_ai.php — welcome message on load, four suggested prompt chips (falling behind, consecutive absences, compare classes, class summary). No auto-loaded flagged list — the AI fetches data only when asked.
  • API backend attendance_ai_api.php — tool-use loop with 6 tools: get_flagged_students, get_student_detail, get_class_summary, get_attendance_timeline, get_consecutive_absences, get_class_comparison.
  • Models Chat analysis uses claude-haiku-4-5-20251001 (fast, low cost). Referral draft generation uses claude-sonnet-4-6 (higher quality for the final written output).
  • Referral workflow Enter a student ID in the "Draft a Referral" section → AI writes a referral note in a modal → "Open Referral Form" opens referral_create.php in a new tab → faculty reviews and submits → saved to DB.
  • Settings attendance_ai_settings.php — absence threshold % and consecutive absence minimum (used as the default min_streak in tool calls). attendance_user_settings.php — export format and default status (returns to referring page after save).
  • Demo data Two summer courses seeded: ART120-01 (32 MWF sessions) and ART121-01 (20 TTh sessions), each with students showing distinct patterns — chronic absences, sudden streaks, tardy patterns, and recovering attendance — so every tool call returns meaningful results.
PHP Pharos360 — Bug Fixes & Enhancements
  • 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 demo.
  • 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. Now seeded in docker-entrypoint.sh — survives DB rebuilds.
  • 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 from DB_NAME env var.
  • Student avatars Generated 40 placeholder PNG avatars for fake students (Q1001–Q1039) using PHP GD. Script at Server/generate_avatars.php.
PHP Option B — Attendance Integrated into Pharos360 📊 Compare options →
  • What it is Take Attendance, Learn Names, History, and Export built directly into the existing Pharos360 PHP codebase — same language, same database, no separate server. Uses the same roster_* tables already created by Present.
  • New pages (7) attendance_courses.php — faculty landing; attendance_take.php — photo card grid; attendance_save.php — POST handler; attendance_history.php — past sessions with status pills; attendance_learn_names.php — shuffled card modal; attendance_event_types.php — event type CRUD; attendance_export.php — CSV download
  • Helper functions (5) Added to site/classes/queries.php: userTeachesClass(), getRosterByClass(), getAttendanceStatuses(), getOrCreateDefaultEventType(), buildSparkLines()
  • Feature flag has_attendance boolean in site_configuration_settings. When false, all attendance pages redirect home and the nav link is hidden. Controlled by pharos.maintenance via Settings page.
  • Files modified partials/userland.php — faculty nav link (Attendance or Roster depending on which setting is active); settings.php — attendance enable/disable control for pharos.maintenance
  • Not yet built Email notification endpoints (attendance_email_notify.php, attendance_email_summary.php) — Present's bulk-email feature is not yet ported. Core attendance recording is complete.
  • Auth / access All attendance pages call ensureFaculty(); class-scoped pages verify ownership via userTeachesClass(). The feature flag gate is pharos.maintenance-only — administrators cannot enable/disable it.
Laravel 11 Option A — Present: Full Rewrite (Java/GWT → PHP)
  • 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 Pharos360's cookie_checker.php. Caddy's X-Forwarded-Proto header trusted via trustProxies(at: '*').
  • 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.
  • 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 on port 5173 (served via Caddy on the cloud server).
  • 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.
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).
  • Stack Rails 4.2 (via RailsLTS subscription — security patches backported to 4.2), Ruby 2.7.8, PostgreSQL. Runs in Docker alongside all other apps. Available at https://timefree.aidemo.pharos360.com.
  • Standalone mode BYPASS_PHAROS_360=true in config/env_var.rb — email-only login, no password, no Pharos360 session check.
  • 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.
  • 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.
  • 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 Organization
  • Structure Present/ at root of Claude/. Pharos360-2.x/ has its own .git (branch master). Present/ has its own .git. The root Claude/ repo tracks infrastructure files and this launchpad. terraform/ holds Terraform state locally.
  • .gitignore Added GoogleCreds (irreplaceable Google API credentials, no longer re-exportable from Google Console) and .DS_Store.
  • Hosted demo pharos.cloudaidemo.com — this launchpad, hosted on Cloudflare Pages (Launchpad/hosted/index.html). Access-controlled via Cloudflare Zero Trust (Google OAuth, allowlist by email). App links go to *.aidemo.pharos360.com subdomains.

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.

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 — partially assessed via Terraform review. Runtime network testing not performed.