Problem
Personal productivity tools rarely double as engineering evidence. Off-the-shelf SaaS solves the daily problem but leaves nothing to show for the work; throwaway scripts produce no durable system. There is no obvious path from “I want a useful personal app” to “I have shipped a production-grade system I can point at.”
The harder layer underneath: most AI-assisted development happens inside short-lived prototypes. The tools work, but the artifacts they produce are not reviewable as evidence of engineering judgment — there is no specification, no ADR, no test that pins a real invariant. The career story stops at “I used Copilot.”
Product idea
A single-user web application that combines the modules a working software engineer actually uses day-to-day — tasks, finance, automation, reading, learning — into one modular monolith. The same artifact is both the product (something Anton uses every day) and the portfolio (something a recruiter or mentor can review).
Two surfaces, one repository:
anton415.ru— this public site. CV, blog, case studies. Zero authenticated calls, zero references to private data.app.anton415.ru— the private hub itself. Yandex ID OAuth + email allow-list. Single tenant in the strongest sense — nouser_idin the schema, no roles, no sharing.
The two origins are separated by subdomain on purpose: independent cookie scope, independent indexing rules, independent Caddy site blocks. See ADR-0001.
System scope
One Go binary serves the REST API and the built SPA’s static assets. One PostgreSQL 16 database stores all module data. One Caddy edge handles TLS and security headers. Production runs on a single Terraform-provisioned Yandex Cloud VM. The frozen Orchestrator module’s n8n service still ships in the same docker-compose stack, backed by its own n8n-postgres.
What is deliberately not in scope:
- No microservices. Service decomposition is an explicit non-goal — see ARCHITECTURE.md §1.
- No multi-tenancy primitives. Single user means single user.
- No public auth, no public accounts, no public demo.
- No CMS for the public site. Markdown + typed TS modules, validated at build time — see ADR-0003.
Current modules
| Module | Status | Surface |
|---|---|---|
| Todo | Production-like | Daily personal use. Real auth, real DB, real deploys, daily backups. |
| Auth | Production-like | Yandex ID OAuth + email allow-list. |
| Public site | In Progress | This site. Astro + MDX, static, served from a path baked into the Caddy stack. |
| Finance | Frozen | Schema, migrations, and module slice in place. No real data ever shown publicly. |
| Orchestrator | Frozen | Module slice + n8n integration scaffolding. Frozen until the workflow stabilizes. |
| News | Planned | Reading / aggregation module. Not yet implemented. |
| FIRE · Investments | Planned | Package markers only. |
Only Todo and the public site are under active development. Frozen modules ship as honest case studies rather than live features. See the per-module case studies for Todo, Finance, and Orchestrator.
Architecture
A modular monolith with strict ports-and-adapters per module. The dependency graph is one-directional: adapters → application → domain. Domain code imports neither HTTP nor SQL. Shared infrastructure (config, db pool, logging, router composition) lives under internal/platform/. The reference module is internal/todo/ — domain, application, postgres adapter, http adapter.
The frontend mirrors the backend split: a private SPA (Vite + React + TS + Tailwind v4) is consumed by the Go binary’s static-asset handler; the public site (Astro + MDX) is built into the same Alpine image and served directly by Caddy on the apex origin.
A few non-obvious calls:
- The
/api/v1/orchestrator/n8n/*callback router is mounted before the session middleware on purpose — it is reachable without a session and is protected by a callback token instead. - The frontend module name (
tasks) does not match the backend module name (todo). Surface concept vs. domain concept; kept separate on purpose.
AI-first development workflow
Every non-trivial change starts with a specification and an ADR. ChatGPT helps analyze the problem and draft scope. Claude Code drafts the architecture, walks the codebase, and writes the implementation. Codex handles visual polish on the frontend. Anton reviews and approves every PR.
Boundaries that matter are enforced by the build, not by review:
- Layer dependencies are enforced by the package graph — a domain file that imports
pgxdoes not compile. - The public/private isolation is enforced by the workspace boundary — the public site and the private SPA live as separate npm packages, and a CI guard blocks the public-site source from referencing the private package.
- The deploy guardrail is enforced by per-PR approval on changes under
deploy/,migrations/,Dockerfile,docker-compose*.yml,Caddyfile, or.github/workflows/.
The phase contract for each change is captured in PHASE_PROMPT_TEMPLATE.md: one phase per PR, an Evidence Link in the description, no --no-verify, no force-push.
Public/private boundary
This case study lives on anton415.ru. The application itself lives on app.anton415.ru behind Yandex ID authentication and an email allow-list. The two origins are independent:
- Separate cookie scope (the private session cookie is never sent to the public site).
- Separate indexing rules —
anton415.ruis indexable;app.anton415.rushipsX-Robots-Tag: noindex, nofollowand a disallow-allrobots.txt. - Separate Caddy site blocks → independent security headers and caching policy.
The public site contains zero /api/v1/* calls and zero references to private todo content, finance figures, or calendar entries. Every public screenshot — when any are added — uses synthetic demo data per SPEC §15.3.
Technology stack
- Backend. Go 1.25, chi router, pgxpool,
golang-migrate, slog. One binary, one process. - Private frontend. Vite + React + TypeScript, Tailwind v4, React Router v7.
- Public site. Astro 5 + MDX. Content collections validated by a Zod schema at build time — see
apps/site/src/content.config.ts. - Edge. Caddy with automatic Let’s Encrypt for both origins.
- Persistence. PostgreSQL 16 via pgxpool.
.up.sql/.down.sqlmigration pairs undermigrations/, applied bygolang-migrateon every deploy. - Infrastructure. Docker Compose on a Terraform-provisioned Yandex Cloud VM. DNS managed in
infra/terraform/. - Auth. Yandex ID OAuth with an email allow-list. Sessions are server-managed cookies, scoped to
app.anton415.ru.
Deployment model
A multi-stage Dockerfile builds the SPA and the Go binary into one Alpine image. The public site’s static output is baked into the same image at a path Caddy serves directly — no second app process.
CI (.github/workflows/ci.yml) runs Go (gofmt, go vet, go test, go build), both frontend builds (private SPA and public site), and a container smoke build on every PR. Deploys (.github/workflows/deploy.yml) are release-triggered: build → push to registry → SSH to the VM → run migrations → recreate containers → smoke-check /health on both origins.
Changes under deploy/, migrations/, Dockerfile, docker-compose*.yml, deploy/caddy/Caddyfile, or .github/workflows/ require explicit per-PR approval.
Current status
The Todo module is in daily personal use. Authentication, persistence, deployment, backups, and migrations are all wired end-to-end. The public site you’re reading is the most recent surface to land; the domain split (anton415.ru public, app.anton415.ru private) shipped in Phase 3 as a dual-host swap with a revertable Caddyfile. The first real blog post, Building a Personal AI-first Hub as a Path to AI Software Engineer, is published.
Roadmap
- Add sitemap and RSS via the Astro integrations — a small follow-up PR after the polish phase lands.
- Second real blog post on specification-driven development (SPEC §11.3 #2).
- News module reaches MVP.
- Investments module reaches first usable cut.
- Document and publish the AI-first workflow as the Orchestrator case study, once the workflow is stable enough to deserve a UI.
Lessons learned
- AI tools without a specification produce fragile results. The slow part is writing the SPEC and the ADRs; the fast part is the implementation. Spec-first changes ship in one pass; spec-skipped ones loop twice.
- Boundaries enforced by the build survive; boundaries in a document don’t. The most valuable rules in AGENTS.md are the ones a
go vet, a TypeScript path map, or a CI grep refuses to compile around. - “Simple” personal modules still need real engineering. The Todo module looks small until you list everything attached to it — auth, migrations, backups, CI, deploy guardrails, OAuth callback management, structured logging. None of those are optional if the result is going to be evidence.
- One Evidence Link per phase keeps scope honest. The phase PRs that shipped fast all had a single Evidence Link (CI green + smoke + screenshot). The ones that drifted were the ones where “while I was here” was the operative phrase.
- The public/private boundary is structural, not procedural. Subdomain split + workspace separation + zero shared imports — those three together make accidental leakage hard. A path-based split would have been one missing
noindexaway from indexing the private hub.
Links
- Repository: github.com/anton415/anton415-hub
- Architecture overview: ARCHITECTURE.md
- AI-agent contract: AGENTS.md
- Public-site plan: docs/public-site/PLAN.md
- ADRs: 0001 — domain boundary · 0002 — repo placement · 0003 — static content
- First blog post: Building a Personal AI-first Hub as a Path to AI Software Engineer