Fresh Claude Code agent prompt. M12 must be complete and committed. Estimated effort: large. Documentation is a real deliverable; budget time for accuracy.
Generate openapi.yaml and serve it at /api/v1/openapi.yaml plus a viewer at /api/docs. Write the README with quickstart and operational guides. Write every doc/*.md file as specified in SPEC.md §16. Ship sample reporter scripts and firewall consumer configs in examples/. By the end, a fresh clone goes from git clone to a working blocklist via documented steps in under 10 minutes.
Verify M12:
git log --oneline -12
cd api && composer test && cd ..
cd ui && composer test && cd ..
Read SPEC.md §16 in full. The required outline of each doc/*.md is the contract for this milestone. Outline-skipping is a hard fail.
Skim SPEC.md §3 (architecture, for the Mermaid diagram you'll embed in doc/architecture.md), §6 (API surface for OpenAPI), §8 (auth flows for doc/auth-flows.md).
Place at api/public/openapi.yaml. Generate, don't hand-write — but also don't pull in a heavy framework. Two acceptable approaches:
zircote/swagger-php to generate from PHP attributes on the controllers. Add the dep, sprinkle attributes, run vendor/bin/openapi src -o public/openapi.yaml as a composer script. CI runs this and fails if the result doesn't match what's committed.api/openapi.yaml.template.php file that produces the YAML from PHP code (a structured array you serialize). Simpler; less boilerplate.Pick one and document the choice in PROGRESS.md.
Coverage:
/api/v1/report, /api/v1/blocklist)./api/v1/admin/*).x-internal: true extension and a clear "UI BFF only" description./internal/jobs/* — those are private. Mention in the spec description that they exist but are out of scope for the public contract.Schemas: define all request/response shapes (Report, Blocklist, Token, Policy, User, AuditEntry, error envelopes). Reuse via $ref.
Add a tiny route in api/src/Application/Public/DocsController.php:
GET /api/v1/openapi.yaml — serves the YAML file with Content-Type: application/yaml. Public; no auth.GET /api/docs — serves an HTML page that loads Stoplight Elements or RapiDoc from a CDN-vendored npm static asset (no external CDN). Both are single-script viewers.Pick one (RapiDoc is smaller; Stoplight Elements is prettier). Document the choice.
Replace the M01 stub. New contents (in order):
Quickstart — the 5-minute path:
git clone ...
cp .env.example .env
# edit .env: generate secrets, optionally configure OIDC
docker compose -f docker-compose.yml -f compose.scheduler.yml up -d
# browse to http://localhost:8080, log in
Generating secrets — the exact openssl rand and php password_hash commands.
First-time setup — create a reporter, get a token, send a report; create a consumer, get a token, fetch the blocklist.
Reverse proxy setup — point to examples/reverse-proxy/Caddyfile.
MySQL setup — uncomment the section in compose; set DB_DRIVER=mysql.
OIDC setup — point to doc/oidc.md.
Scheduling — host cron, systemd timer, sidecar overlay; point to examples/scheduler/.
Backups — what to back up: the irdb-data volume (or MySQL); how to restore.
Architecture — point to doc/architecture.md.
API contract — point to /api/docs viewer and doc/api-overview.md.
Replacing the UI — for future Vue/native/mobile, point to doc/frontend-development.md.
License — TBD (leave a placeholder).
Write each file according to its required outline in SPEC.md §16. Quality bar applies: every snippet must run as-is against docker compose up; no TODOs; ≤500 lines per file.
doc/architecture.md — system overview, container topology (Mermaid), where state lives, stable-vs-replaceable surfaces table, why-this-split rationale.doc/api-overview.md — base URL/versioning, auth summary, endpoint groups, common conventions (envelopes, pagination, ETag, rate limits, IP normalization), worked curl examples for: posting a report, pulling a blocklist (text + JSON), admin search via service-token impersonation, admin search via admin-kind token. Pointer to OpenAPI.doc/auth-flows.md — overview table, sequence diagrams (Mermaid) for: machine reporter, admin token, UI BFF (OIDC + local), Entra setup walkthrough (extract from M08's doc/oidc.md and merge), local admin guidance, future user-token flow sketch marked NOT IMPLEMENTED, CSRF/sessions/CORS notes.doc/frontend-development.md — the headline doc. Read-this-first; three integration patterns (BFF replacement, SPA + thin BFF, direct API + future user tokens) with worked pseudocode for the BFF replacement; minimum API surface checklist; CORS configuration; local dev (run only api, point a separate frontend dev server at it); migration path for swapping UIs at runtime; what NOT to do list (no business logic in frontend, no service token in browser, etc.).doc/api-reference.md — short. Pointer to OpenAPI as canonical; documents what OpenAPI doesn't cleanly express: rate-limit headers, ETag semantics, the X-Acting-User-Id impersonation header convention, response envelope conventions for current and future batched endpoints.If doc/oidc.md was created in M08, delete it — its content goes into doc/auth-flows.md per SPEC.md §16.
In examples/:
reporters/curl.sh — a copy-paste shell script: takes an IP and category as args, posts a report. Reads IRDB_URL and IRDB_TOKEN from env.reporters/python.py — same in Python. Single file, no deps beyond urllib. Example of a fail2ban-action wrapper inline as a comment.reporters/bash-fail2ban.sh — drop-in fail2ban action.consumers/iptables-restore.sh — pulls the blocklist, builds an ipset, atomic-replace via ipset restore.consumers/nginx-deny-include.sh — pulls and writes an include file with deny directives, reloads nginx.consumers/haproxy-acl.sh — pulls and updates an HAProxy ACL file.scheduler/host.crontab, scheduler/irdb-tick.service, scheduler/irdb-tick.timer — already stubbed in M01, fill in real content.reverse-proxy/Caddyfile — production-ready Caddy config fronting api and ui.Each script has a header comment explaining usage. Each is shell-checked (run shellcheck) and tested at least manually.
Add tests/e2e/demo.sh (and a CI job that runs it) that automates the README quickstart:
docker compose -f ... up -ddocker compose down -vAdd a CI job that:
doc/*.md for endpoint paths; compares against the OpenAPI document. Any path in docs not in the spec → fail. (The other direction is OK; not every endpoint needs prose.)reporter, consumer, admin, service); ensures spelling matches code.mermaid fenced code blocks. Test by rendering the file on a GitHub PR.version: 1.0.0 in the spec; bump for breaking changes. Document the additive-only policy.doc/ are the docs.zircote/swagger-php (if you go that route). Record in PROGRESS.md.cd api && composer cs && composer stan && composer test && cd ..
cd ui && composer cs && composer stan && composer test && cd ..
# OpenAPI is valid
docker run --rm -v "$(pwd)/api/public:/spec" \
redocly/cli:latest lint /spec/openapi.yaml
# Doc files exist and are non-empty
for f in architecture api-overview auth-flows frontend-development api-reference; do
test -s "doc/$f.md" || { echo "missing or empty: doc/$f.md"; exit 1; }
done
# Each doc file is ≤500 lines
for f in doc/*.md; do
L=$(wc -l < "$f"); [ "$L" -le 500 ] || { echo "$f too long: $L lines"; exit 1; }
done
# Doc accuracy: no stale endpoints
# (run the CI check script you wrote)
./scripts/check-doc-endpoints.sh
# E2E demo script
docker compose down -v
./tests/e2e/demo.sh
docker compose down -v
# /api/docs serves a viewer
docker compose up -d
sleep 15
curl -sf http://localhost:8081/api/v1/openapi.yaml | grep -q "openapi:"
curl -sf http://localhost:8081/api/docs | grep -qE "(rapi-doc|stoplight|elements)"
docker compose down -v
# Examples are shellcheck-clean
shellcheck examples/reporters/*.sh examples/consumers/*.sh examples/scheduler/host.crontab 2>/dev/null || true
Commit:
feat(M13): polish — OpenAPI, README, doc/, examples, e2e demo
- openapi.yaml served at /api/v1/openapi.yaml; /api/docs viewer
- README with quickstart, OIDC pointer, scheduler options, backups
- doc/{architecture,api-overview,auth-flows,frontend-development,api-reference}.md
- examples/{reporters,consumers,scheduler,reverse-proxy} with shell-checked scripts
- tests/e2e/demo.sh: clone-to-blocklist in ~10 minutes
- CI: openapi validation, doc-endpoint accuracy check
Append to PROGRESS.md:
## M13 — Polish, OpenAPI, docs (done)
**Built:** OpenAPI + viewer; README; all five doc files per SPEC §16; examples; e2e test.
**Notes for next milestone:**
- OpenAPI generation is via [zircote/swagger-php OR hand-curated array]; the source is `<path>`.
- Doc CI guard: ./scripts/check-doc-endpoints.sh
- examples/ scripts use IRDB_URL and IRDB_TOKEN env vars; document this convention.
- The "future user-token flow" in doc/auth-flows.md is the recommended extension point for SPA/native/mobile UIs.
**Deviations from SPEC:** none.
**Added dependencies:** [zircote/swagger-php if applicable]
Stop. Do not start M14.