feat: add reader vote widget and vote-service

The skills-review desk is static, so "which draft would you ship?" needs a
stateful counterpart. vote-service is a small Go API on its own pod backed by
a JSON file on a ReadWriteOnce PVC, with one active vote per skill per source
IP as the anti-abuse rule and CORS (ALLOWED_ORIGIN) as the caller boundary.

Deployment notes that differ from the obvious path, all confirmed against the
live cluster: the image is side-loaded with `ctr image import` plus
`imagePullPolicy: Never` because kubelet has no credentials for the Nexus ref;
the pod is pinned to `kubernets` because the hostpath PV takes a nodeAffinity
for whichever node first binds it; and public exposure is Caddy on the VPS,
not the cloudflared tunnel.

The ingress controller runs with `use-forwarded-headers` off, so nginx
overwrites X-Forwarded-For with its own peer — every visitor would collapse
into one voter and each skill would cap at one vote overall. Caddy stamps the
true remote address into X-Client-IP, which nginx forwards untouched, and
clientIP() reads that first. Scoped to this app rather than flipping the
global flag, which would change client-IP handling for every other ingress.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Marcos Paulo
2026-09-04 23:49:55 +00:00
parent d12d301a1a
commit e2bcfff5ab
14 changed files with 714 additions and 8 deletions
+12
View File
@@ -42,6 +42,8 @@ npm run verify
- `hands-on/rules/` — dependency-free Guardrails lab; toggles rule sources into the prompt
- `rules/` — bilingual case study of skills, CLI ratchets, Husky, and PR review
- `skills/` — reusable design and rules-case-study skills, plus an interactive package anatomy explorer
- `skills-review/` — static review desk for submitted skills; its reader vote widget calls the separate `vote-service`
- `vote-service/` — small Go API + Kubernetes manifests backing the skills-review vote widget (see `vote-service/README.md`)
- `GATES.md` — acceptance ledger for the project
## Publishing
@@ -59,6 +61,16 @@ Server and Actions deployment path.
For the complete authoring, verification, publication, rollback, worktree, and
skill workflow, see [docs/operations-guide.md](docs/operations-guide.md).
## Reader voting on the skills-review desk
`skills-review/` is static, so its "which draft would you ship?" vote widget
calls a separate stateful service — `vote-service/`, a small Go API on its
own pod, one vote per visitor enforced server-side by IP (a MAC address is
never visible to a server across the internet, so it cannot be used). See
[vote-service/README.md](vote-service/README.md) for the API, the anti-abuse
design, and the build/push/deploy steps; `skills-review/index.html` sets
`window.SKILLS_REVIEW_VOTE_API` to point at it once deployed.
## Research
See [docs/references/README.md](docs/references/README.md) for official Claude,