Builder Log 002
Turning Audience Finder AI into a protected product.
A demo is not a product. Audience Finder AI started as a public demo interface: simulated discovery, localStorage memory, a local review console, and a Cloudflare Worker backend that anyone could query. That was fine for V1. By V2 it needed to be something safer, more controlled, and more honest about what it actually does.
This log covers what changed, why each piece matters, and what it proves about building governed AI workflows in public.
The problem with a portfolio demo
The V1 demo was technically honest but architecturally misleading. It showed the right workflow -- discovery, scoring, human review, domain memory -- but the infrastructure underneath was not the real product. The review buttons wrote to localStorage. The "live backend" was a read-only status panel. The admin console was a local HTML file that required pasting a token into a password input.
The gap between the public demo and the real operating model was too wide. Before this build could be shown to anyone serious, that gap needed to close.
What it needed to become
The requirements were not complicated but they were non-negotiable:
- The admin dashboard should be protected by real authentication, not just a local file and a token paste
- Review actions should write to the live backend, not localStorage
- The API token should never appear in browser JavaScript under any circumstances
- No outreach, no scraping, no autonomous decisions -- human approval at every step
- The system should be deployable with a documented release process, not just ad hoc wrangler commands
The architecture shift
Six components make up the V2 production architecture. Each one has a specific job and a specific security boundary.
1. Main API Worker
The live backend at audience-finder-api.gs-c4a.workers.dev. Public GET routes for status and data. Protected POST routes that require a Bearer token. The token is a Cloudflare Worker secret -- never in the repo, never returned to any browser.
2. Cloudflare KV
Four KV keys store the live product state: domain memory, review log, pending candidate queue, and source registry. KV is the source of truth. No D1, no relational database yet -- KV is sufficient for the current workflow volume.
3. Cloudflare Access
The admin dashboard at pl8ypus-admin.pages.dev/audience-finder/ is protected by Cloudflare Access. Unauthenticated requests see a login page. Access injects an identity header on authenticated requests -- there is no session cookie managed in JavaScript.
4. Admin-proxy Worker
The critical piece. The browser sends review actions to the admin-proxy Worker -- a dedicated Cloudflare Worker that validates the Access identity, checks an email allowlist, checks a feature flag, and then calls the main API Worker with the Bearer token server-side. The browser never sees the token at any point.
5. Admin dashboard
Vanilla JavaScript, no framework, no inline token. The dashboard calls the admin-proxy Worker with credentials: "include" so Cloudflare Access cookies are forwarded. The session check, review write, and candidate intake all go through the proxy. Review decisions require a modal with a mandatory reviewer note. No single-click writes.
6. Release control
A read-only pre-release check script (release-check.ps1) runs before every deployment. It checks git state, JS syntax, config file presence, feature flag values, and prints deploy commands as copy-paste reference only. The script does not deploy anything.
Why the admin-proxy pattern matters
The naive approach to a protected admin dashboard is to have the browser send the API token directly. That would work but it would also mean the token appears in JavaScript, in network requests visible in any browser dev tools, and potentially in server logs at any intermediate point.
The admin-proxy pattern solves this cleanly. The browser authenticates with Cloudflare Access (a hardware-grade identity gate). The admin-proxy Worker picks up the Access identity header, validates it against an allowlist that lives in a Worker secret, and only then adds the Bearer token to the upstream request. The token never leaves the Worker runtime. There is no point in the flow where a browser, a log, or a network capture could intercept it.
The pattern also gives a natural feature flag layer. The proxy checks AUDIENCE_ADMIN_REVIEW_ENABLED, AUDIENCE_ADMIN_CANDIDATE_ENABLED, and AUDIENCE_ADMIN_DISCOVERY_ENABLED before forwarding any write. Disabling a feature is a one-line config change and a redeploy -- no code changes required.
Manual review as a product feature
The review modal is not just a UI detail. It is a design decision about what kind of system this is.
Every review action -- Accept, Reject, Watch -- opens a modal. The modal shows the candidate's score dimensions and reasoning. It requires a reviewer note before submission. It shows a confirmation step before the write goes through. There is no way to batch-approve candidates, no keyboard shortcut that skips the confirmation, and no auto-accept path anywhere in the code.
This is deliberate friction. The system is not trying to move fast. It is trying to move carefully. The value is not in the number of domains processed -- it is in the quality of the ones that make it into memory.
The read-only audit log in the dashboard shows the latest 25 review events directly from the KV review log. It is not a separate data store -- it reads the same log that the API Worker writes to. The dashboard and the local PowerShell tools see the same data.
Controlled discovery
The discovery feature is the most constrained part of the system by design. "Controlled discovery" means exactly that: a deterministic seed pool of 19 curated sites, keyword matching against the candidate profile, stable sort, deduplication against existing KV memory, and a hard cap of 5 results per call.
There is no live search API. There is no scraping. The system does not contact any external site. It does not send any form of outreach. The discovery results land in the pending queue and wait for human review before any action is possible. The entire flow is: suggest candidates, add to queue, human reviews, human decides.
The seed pool will grow as the system proves itself. But the architecture stays the same: curated inputs, deterministic matching, human review, no autonomous action.
The hard parts
A few things that were not obvious going in:
- Cloudflare Workers can fetch other Workers, but only with the global_fetch_strictly_public compatibility flag. Without it, Worker-to-Worker fetch fails with error 1042 and the error message does not point clearly at the fix.
- Plain vars in wrangler.jsonc override Cloudflare dashboard variable values on every deploy. A flag enabled in the dashboard is silently reset to the config file value the next time you run wrangler deploy. All plain vars belong in the config file, not the dashboard.
- Cloudflare Pages Direct Upload does not activate Pages Functions routes. The Pages Function session proxy built in V1.9 returns 404 in deployment because Direct Upload bypasses the Functions runtime. The admin-proxy Worker pattern sidesteps this entirely.
- CORS preflight requests strip Cloudflare Access cookies. The browser must omit Content-Type on cross-origin fetch calls so the request stays simple and Access injects the identity header correctly. Adding a Content-Type header triggers a preflight OPTIONS request that Access blocks before the identity can be validated.
What this proves
- A governed AI workflow does not require a large team or a complex infrastructure stack -- Cloudflare Workers, KV, Access, and careful architecture decisions are enough to build a production-grade controlled system
- The admin-proxy pattern is a reusable approach for any system where browser clients need to trigger authenticated server-side writes without exposing credentials
- Feature flags in the Worker config are a lightweight release control mechanism that keeps deployment risk low and rollback fast
- Manual review is not a bottleneck -- it is a quality gate. The system is faster and safer because a human sees every decision
- A pre-release check script that does nothing but report current state is genuinely useful: it forces a review of git state, syntax, and config before every deployment
What comes next
- Expand the discovery seed pool as the system proves reliable at current scale
- Add a protected source registry management interface to the admin dashboard (same admin-proxy pattern)
- Add GitHub Actions for JS syntax checks on push -- read-only CI, no deploy automation
- Configure the custom domain admin.pl8ypus.io for the admin workspace
- Review the D1 migration plan before starting any relational data layer -- KV is still the right choice for current volume
See the system
The public demo UI and live backend status are available on the Audience Finder AI page. The builds overview covers the full portfolio context on the builds page. Speaking requests and build conversations go through the speaking page or the contact form.
Product status
Backend: Worker live
Admin: Access protected
Token: server-side only
Review: human gated
Discovery: controlled
Outreach: disabled
Log entry
002 / Product
Product
Audience Finder AI
Phase
V1 demo to V2 product