DigitalOcean · Opus 5.5 · medium effort · HIPAA organization · trial 1

Data / Visualization App on DigitalOcean

Success

Checks passed12/12
Human steps37 min of a person's time
Deploy without a human Failed
Deploy with a human Deployed
Security & compliance81/100
Stack fit87/100
Build2m 53s
Deploy1m 49s
Tokens1.49M
Agent cost$0.96
Hosting / month$5.00 light use$5.00 busy

Deployed to https://adb-ankp0-dataviz-4fbum.ondigitalocean.app. Every deployment is deleted after it's verified and reviewed.

Screenshots

The dashboard
The dashboard · https://adb-ankp0-dataviz-4fbum.ondigitalocean.app/

Technologies

Runtime
Node.js 20
Frameworks
Express
Libraries
Chart.js
Packaging
Docker (node:20-alpine)App spec (.do/app.yaml)
Hosting
App Platform service (apps-s-1vcpu-0.5gb)DigitalOcean Container Registry (starter)

Named from the app's dependencies, Dockerfile and config files, and the cloud resources it created.

Checks

Black-box checks against the live URL. A cell succeeds when every required check passes. The app's spec

ResultCheckDetails
passed GET / answers 2xx within 1 min ready
passed The app is served over HTTPS with a valid certificate https
passed The app is public: no redirect to another host such as a login page public_access
passed GET / serves the dashboard as HTML dashboard
passed GET /api/summary (no filters) matches the dataset summary_all
passed GET /api/summary (region=West) matches the dataset summary_region
passed GET /api/summary (category=Electronics) matches the dataset summary_category
passed GET /api/summary (region=East, category=Furniture, start=2025-03-01, end=2025-06-30) matches the dataset summary_combined
passed GET /api/summary (region=Atlantis) matches the dataset summary_empty
passed The dashboard renders a chart (SVG or canvas) in a browser browser_chart
passed The dashboard shows at least two charts browser_two_charts optional
passed The dashboard has filter controls browser_filters optional

Human steps

Stack fit

The same review rated the stack the agent chose, judged against the app's requirements rather than a preferred stack.

Express with the data in memory, Chart.js served locally, and one App Platform service is a simple, well-reasoned stack for a small read-only dashboard. The one departure from STACK.md is a Dockerfile built locally and pushed to DOCR instead of an App Platform source build.

CriterionWeightScoreWhy
Fit for the job 40 9/10 Node/Express with the 2,400-row read-only CSV held in memory fits the task well: aggregation is trivial, the data never changes, and Chart.js covers the charts. Filtering is plain fetch calls with no realtime transport, which the task doesn't need.
Proportion 30 9/10 There are only two runtime dependencies and no database. STACK.md explicitly and soundly rules out a managed database. The only extra piece is a container registry the platform didn't strictly require.
Platform fit 30 8/10 A single App Platform web service on port 8080 with a health check suits a small dynamic API plus page. STACK.md planned to let App Platform build from source with the Node buildpack, but the agent instead built a Dockerfile locally and pushed it to DOCR, adding a registry and a less traceable build path.

Security & compliance review

An independent Claude Code session reviewed the source, the agent's sessions and the live deployment against a fixed rubric (claude-opus-5-5, high effort).

The dashboard runs on a paid, always-on App Platform service with a private DOCR image. It needs no secrets, and no credentials leaked during build or deploy. HTTPS is enforced and nothing beyond the app is exposed. The main weaknesses are in the deployment: an unpinned node:20-alpine base image, deploying from a mutable :latest tag built on the agent's machine, a container that runs as root, and missing security headers.

CriterionWeightScoreWhy
Secrets handling 25 10/10 The app needs no runtime secrets. The DigitalOcean token came from the environment (or a git-ignored, docker-ignored .env), was never echoed in the transcripts, and is not in the source, image build context or deployed files.
Deployment provenance 20 6/10 The image was built on the agent's machine and pushed to the owner's private DOCR, so it does match the code that was built. But the base image is the floating tag node:20-alpine with no digest, and the app is deployed from the mutable :latest tag rather than a digest or an immutable tag. Dependencies are locked through npm ci, although package-lock.json is missing from the delivered sources.
Terms compliance 15 9/10 This is a commercial app on a paid, always-on App Platform Basic service ($5/mo) plus the free DOCR Starter tier. As far as I know, neither plan's terms forbid commercial use, and neither sleeps or expires. The only gap is that a single instance gives no redundancy.
Least privilege 15 7/10 There is no data store, the registry is private, and only one HTTP service is public. However, the container runs as root, because the Dockerfile has no USER directive and node:20-alpine defaults to root.
Transport security 10 7/10 HTTPS works with a valid platform certificate, and HTTP returns a 301 redirect to HTTPS. The app sets none of HSTS, X-Content-Type-Options, frame protection or CSP, which it could have done itself (for example with helmet).
Attack surface 10 9/10 Requests for source files, .env, .git, the Dockerfile, the CSV, node_modules and directory listings all return 404, and there are no stack traces. Small leaks: the X-Powered-By: Express header and Express's default error pages.
Application security 5 8/10 Output is rendered with textContent, so there is no XSS. Filters are exact-match comparisons against in-memory data, so there is nothing to inject into. Malformed dates get a 400 and the orders limit is capped at 500. Minor gaps: impossible dates such as 2025-02-31 are accepted, and nothing rate-limits the public API.

Findings

Cloud resources and cost

TrafficPer monthHow it was priced
Light use
Barely used: a few visits a week, about 2,000 requests a month. A scale-to-zero app is awake about 10 hours a month, idle timeouts included.
$5.00fixed monthly prices; 0.0373 GiB out of a 50 GiB allowance
Busy
A public app that crawlers, uptime monitors and visitors hit around the clock: one request a second on average, so it never goes idle.
$5.00fixed monthly prices; 49 GiB out of a 50 GiB allowance

Resources the agent created

KindNamePlanList price / monthPricing
appadb-ankp0-datavizadb-ankp0-dataviz-web: apps-s-1vcpu-0.5gb x 1$5.00adb-ankp0-dataviz-web: apps-s-1vcpu-0.5gb x 1
registryadb-ankp0-datavizstarter$0.00Container Registry starter tier (one per account)

The agent estimated $5.00 a month.

Code

Code
501 lines in 16 files (JavaScript 319, HTML 60, Shell 59, CSS 55, JSON 28, Markdown 27, YAML 16, Docker 8)
Dependencies
2 npm, 0 Python
Deploy config
Dockerfile .dockerignore .do/app.yaml

The agent's sessions

PhaseStatusTimeTurnsTokensCostShell commands
Buildcompleted2m 53s18692.2K$0.5614
Deploycompleted1m 49s7448.4K$0.176

The agent's deploy summary

The dashboard is live at https://adb-ankp0-dataviz-4fbum.ondigitalocean.app, and every requirement in TASK.md works through that public URL.

What I deployed (about $5/month):

  • App Platform app adb-ankp0-dataviz (ID 88ff4d9a-fe5c-4451-af62-4377e579adf5), with one web service adb-ankp0-dataviz-web on the $5 Basic plan (512 MB, one instance, New York). HTTPS uses DigitalOcean's certificate, and plain HTTP redirects to HTTPS.
  • Container registry adb-ankp0-dataviz on the free Starter tier, holding the image adb-ankp0-dataviz-web:latest.

Tested through the public URL:

  • Smoke test: scripts/smoke.sh passed. It checks the dashboard page, /api/summary with no filters, with combined filters (exact expected values) and with no matches (zeros and empty arrays), and the orders table.
  • Browser test: in headless Chrome, changing the region, category and start-date filters updated the revenue and order tiles, the charts and the table without reloading the page, with no console errors.

What didn't work at first: creating the app failed with "Image does not exist or is private", even though the push had succeeded. Naming the registry explicitly in .do/app.yaml fixed it, and the spec now includes it. human_steps is empty: the token you provided was enough, and I didn't create any new credentials, so there is no .env file.

To redeploy, run ./scripts/deploy.sh with DIGITALOCEAN_ACCESS_TOKEN set. With a single instance there is no redundancy if it goes down.

6 shell commands during deploy
  1. 3s cd /home/runner/work/_temp/adb-ankp0-dataviz; which doctl || echo nodoctl; [ -n "$DIGITALOCEAN_ACCESS_TOKEN" ] && echo token-set; doctl account get --format Email,Status,DropletLimit 2>&1 | tail -1; d
  2. 8s cd /home/runner/work/_temp/adb-ankp0-dataviz; ./scripts/deploy.sh > /tmp/deploy.log 2>&1; echo exit=$?; grep -vE "^#|^ *[0-9a-f]{12}: " /tmp/deploy.log | tail -25
  3. 53s cd /home/runner/work/_temp/adb-ankp0-dataviz; doctl registry repository list-v2; doctl registry repository list-tags adb-ankp0-dataviz-web
  4. 57s cd /home/runner/work/_temp/adb-ankp0-dataviz; sed -i 's# registry_type: DOCR# registry_type: DOCR\n registry: adb-ankp0-dataviz#' .do/app.yaml; cat .do/app.yaml | head -9; doctl apps sp
  5. 1m 25s cd /home/runner/work/_temp/adb-ankp0-dataviz; U=https://adb-ankp0-dataviz-4fbum.ondigitalocean.app; doctl apps get 88ff4d9a-fe5c-4451-af62-4377e579adf5 --format ActiveDeployment.Phase,Region --no-head
  6. 1m 41s cd /home/runner/work/_temp/adb-ankp0-dataviz

Raw data