Four phases shipped together. Each is a logical deploy unit on its own;
keeping the diff atomic so the rename runbook + migrations stay aligned.
Phase 1 — restore HP's formal review workflow
- Prisma: FeedbackItem, ReviewSession, ReviewSessionItem + enums
- New ApprovalType (NONE | SIMPLE | FORMAL) on PipelineStageDefinition
and PipelineStageTemplate. Stage row UI branches per type.
- feedback-service + review-session-service ported from HP (no ColorProbe)
- annotation-service auto-creates a FeedbackItem; revision-service
carries forward unresolved action items into the new revision.
- API: /api/reviews/*, /api/stages/[id]/feedback, /api/feedback/[id]
- Hooks: use-feedback, use-review-sessions
- UI: feedback-checklist, feedback-item-card, feedback-progress-bar,
create-session-dialog, session-builder, session-presenter,
session-summary, plus a new stage-review-panel
- Pages: /reviews list + detail, deliverable annotation review page
- Pipeline editor gets the approvalType select; sidebar gets Reviews
Phase 2 — full Dow Jones → L'Oréal rebrand + slug rename
- URL slug /dow-prod-tracker → /loreal-prod-tracker (next.config,
base path, redirects)
- docker-compose name + DB → loreal_prod_tracker; server path
/opt/loreal-prod-tracker; apache template renamed
- All visible strings → L'Oréal; sidebar bg #002B5C → black
- docs/RENAME_RUNBOOK.md describes the one-shot server migration
- Internal modules dow-excel-service/dow-import + OMG webhook domain
dowjones.com deliberately preserved (orthogonal to the rebrand)
Phase 3 — external /api/v1 for projects + deliverables
- API-key auth already in middleware; finished idempotency support
via new IdempotencyRecord model + src/lib/api/idempotency.ts
- Default-pipeline fallback in createProject when no template id given
- POST/GET /api/v1/projects + POST /api/v1/projects/[id]/deliverables
- docs/EXTERNAL_API.md with curl examples
Phase 4 — Box bidirectional integration
- JWT app-auth via jose (no extra deps). Config mounted as a docker
compose secret; deploy.sh stubs an empty {} so compose can start
before the operator drops the real JSON.
- Outbound: pushDeliverableToBox auto-fires on !APPROVED → APPROVED
in deliverable-status-service; "Send to client (Box)" manual button
on the approval stage row. Folder naming
{omgJobNumber}_{slug}_v{round}. 3-attempt exp backoff. BoxPushLog
audit.
- Inbound: /api/webhooks/box receives Box's signed events, matches by
OMG # + slug, creates a new Revision, routes to assignee or notifies
project owner. BoxInboundLog audit + two new NotificationType
values (BOX_UNMATCHED_FILE, NEW_FILE_AWAITING_REVIEWER).
- Naming-convention logic isolated in external-delivery-service so an
OMG-API transport can swap in later without touching matchers.
- Admin /settings/box page surfaces config status + recent activity.
Three Prisma migrations to apply on next deploy:
20260512000000_restore_review_workflow
20260512100000_idempotency_records
20260512200000_box_integration
URL rename is a one-shot — see docs/RENAME_RUNBOOK.md.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4.4 KiB
Rename runbook — dow-prod-tracker → loreal-prod-tracker
This is the one-time server-side migration to swing the URL slug, docker
compose project, database name, and deploy path over to the new L'Oréal
naming. Run it on optical-dev.oliver.solutions after the renamed code
has been merged to main.
The bitbucket repo name (zlalani/dow-prod-tracker) stays for now — that
rename is a separate decision.
Prerequisites
- All Phase 2 code changes merged to
mainand pushed. - A shell on
optical-dev.oliver.solutionswith sudo + docker access. - A maintenance window: ~5 minutes of downtime, expect 10–15 for backup + restore on a non-trivial database.
Step-by-step
1. Backup
mkdir -p /opt/backups
docker compose -p dow-prod-tracker exec -T db \
pg_dump -U postgres -d dow_prod_tracker \
| gzip > /opt/backups/pre-rename-$(date +%Y%m%d-%H%M).sql.gz
Verify the dump is non-empty (ls -lah should show a file > 1 MB for any
real data).
2. Stop the old stack (preserve volumes)
cd /opt/dow-prod-tracker
docker compose -p dow-prod-tracker down
# DO NOT pass -v — we need the old volumes intact for rollback.
3. Rename the deploy directory
sudo mv /opt/dow-prod-tracker /opt/loreal-prod-tracker
cd /opt/loreal-prod-tracker
4. Pull the renamed code
git pull origin main
(The repo URL still ends in dow-prod-tracker.git — that's fine; the
bitbucket rename is deferred. Only the working copy on disk moves.)
5. Bring up the new DB only, with an empty volume
docker compose -p loreal-prod-tracker up -d db
Wait ~5 s for pg_isready to pass:
until docker compose -p loreal-prod-tracker exec -T db \
pg_isready -U postgres -d loreal_prod_tracker; do
sleep 1
done
6. Restore the dump into the new DB
gunzip -c /opt/backups/pre-rename-*.sql.gz \
| docker compose -p loreal-prod-tracker exec -T db \
psql -U postgres -d loreal_prod_tracker
The dump was taken from the old dow_prod_tracker DB but pg_dump emits
explicit \connect and SET search_path lines that piping into the
new DB above handles cleanly. If you see "ERROR: relation already
exists" lines, the new DB volume wasn't empty — docker volume rm the
fresh loreal-prod-tracker_pgdata and retry from step 5.
7. Start the rest of the stack
docker compose -p loreal-prod-tracker up -d
8. Smoke check
Open https://optical-dev.oliver.solutions/loreal-prod-tracker in a
browser:
- Login renders with L'Oréal branding (black sidebar, "L'Oréal Studio Tracker" wordmark).
- A known project loads — its stages, attachments, and notes are intact.
- A recent deliverable shows correct stage state.
- Image attachments serve from
/api/uploads/....
If the page doesn't load at all, check docker compose -p loreal-prod-tracker logs -f app for startup errors.
9. Add an Apache 301 from the old URL
Append to the same vhost the new tracker lives in:
RedirectMatch 301 ^/dow-prod-tracker/?(.*)$ /loreal-prod-tracker/$1
Reload Apache: sudo systemctl reload apache2.
Test: curl -I https://optical-dev.oliver.solutions/dow-prod-tracker/dashboard
should return 301 with a Location: pointing at
/loreal-prod-tracker/dashboard.
10. Update cron entries
Anywhere we have cron pointing at the old path (/opt/dow-prod-tracker)
needs to swap to /opt/loreal-prod-tracker. Common ones:
sudo crontab -l | sed 's|/opt/dow-prod-tracker|/opt/loreal-prod-tracker|g' \
| sudo crontab -
Verify with sudo crontab -l. Notable jobs:
- Nightly DB backup (
scripts/backup-db.sh). - Any cron jobs hitting the in-app deadline notifier endpoint.
11. Cleanup (after a 1-week soak)
Once nothing's complained for a week, drop the old volumes:
docker volume rm dow-prod-tracker_pgdata dow-prod-tracker_uploads_data
You can also drop /opt/backups/pre-rename-*.sql.gz if disk pressure
matters — though keeping it indefinitely is cheap insurance.
Rollback
If anything goes wrong in steps 5–8, the old volumes are still present
under the dow-prod-tracker project name. To roll back:
docker compose -p loreal-prod-tracker down
sudo mv /opt/loreal-prod-tracker /opt/dow-prod-tracker
cd /opt/dow-prod-tracker
git checkout <previous-sha> # the SHA before the Phase 2 merge
docker compose -p dow-prod-tracker up -d
The old data is unchanged because step 1 only read from the DB and
step 2 didn't pass -v.