Steinlog v2
A bid-coordination SaaS for mid-size Dutch general contractors and specialty subcontractors bidding on public tenders — re-platformed from scratch onto an all-Cloudflare architecture after v1 died of Neon compute costs. Commercial stage: pre-revenue, actively prospecting the Dutch market.
Steinlog v2 is a bid-coordination SaaS for mid-size Dutch general contractors and specialty subcontractors bidding on public tenders — a multi-tenant product where cross-tenant leakage would be an existential trust failure. The answer was not a WHERE clause. There is no tenant_id column anywhere in the schema; tenant isolation is Durable Object identity — one SQLite Durable Object per tenant, so the application is structurally incapable of a cross-tenant query, closing a leak vector a shared-table-plus-filter model can never fully close. The cost: any feature needing to aggregate across tenants — the intel and insight analytics planes — cannot just run a query; it has to be built as a separate, allowlisted event projection with its own pipeline.
The same discipline governs how the legal corpus ships. A corpus load or promotion is blocked outright on a green eval-gate run: an 84-case answer suite — 60 groundable cases across Dutch, EU and ARW/UAV/Gids law, plus 24 adversarial cases probing excluded-corpus, advice-seeking, out-of-scope and prompt-injection questions — has to clear floor thresholds and delta thresholds against the previous corpus version before a change goes live. Nothing about the corpus ships because the docs were updated; it ships because the suite stayed green.
The second supporting decision is a four-tier market-rail model that bans a market === 'NL' shortcut by construction, in application code, not just in review. The doctrine's own justification is blunt: a two-market ternary is indistinguishable from correct code until a third market exists, which is exactly why Dutch-only defaults survived 2,000-plus passing tests before the rail existed. The rail is built ahead of the third market it does not have yet, at the cost of every market-touching decision needing an explicit tier classification instead of a quick if-statement.
The same non-euphemistic instinct governs three separate disclosures. Every provenance seal is a non-qualified RFC-3161 timestamp, stated as such in the product's own copy rather than dressed up as eIDAS-qualified — the upgrade path to a qualified timestamping authority is a config change. Legal retrieval scope is two mandatory, orthogonal fields — jurisdiction and language, with no default — so a Dutch-only prompt cannot leak into a future market, and every answer is graded against a five-code refusal taxonomy rather than a single pass or fail. EU data residency is disclosed precisely: data at rest and the primary LLM's inference are EU-pinned; embeddings, reranking and OCR are disclosed as global, not folded into a residency claim they do not meet.
None of this happened because it was elegant on day one. The rebuild's own rationale is blunt: v1 died of Neon compute costs, and v2 had to make sure it did not die of Durable Object storage writes instead. That is why rows written — not requests, not compute-seconds — became the governed unit: crons that no-op when nothing changed, batched persistence instead of per-update writes, and a capacity model that tracks writes per tenant per month against the platform's included allowance. Re-platforming onto Cloudflare was not a technology preference; it was cost discipline learned the expensive way, and it is worth saying so plainly rather than laundering it as an architecture upgrade.
Commercially, the honest label is pre-revenue, actively prospecting the Dutch market — not a euphemism for further along, and not hidden behind a vaguer 'in beta.' The Netherlands is the beachhead market by design; Sweden is next, and as of the current build it is in active design rather than shipped — which is precisely why the market-rail doctrine above exists now: it is only meaningful because a second market is actually coming, not hypothetical.
The governance behind every decision above is also the answer to how this got built: he runs Steinlog as an architect directing agent fleets, not as a hands-on-every-line engineer, and the proof that this is engineering rather than vibe-coding is structural, not asserted. The clarification-drafting agent never submits anything to TenderNed — it only inserts proposed rows for a human to review and send. Its own feature flag is checked twice, at scheduling time and again at run time, so a schedule created while the flag was briefly on still no-ops if the flag flips back off before it fires. Decisions, architecture and the gates that enforce them are his; the implementation under those gates is largely agent-driven.
Observability gets the same precision rather than a blanket claim. PostHog LLM analytics instruments the public 'Vraag de Wet' law tool — every model call logged with model, latency and corpus version, never the question or answer text. The core tenant product's AI paths, X-Ray and requirement extraction among them, are not wired into that same analytics layer; they rely on structured JSON logging instead. That is a disclosed asymmetry, stated precisely rather than dressed up as a bigger claim than it is.
If I were starting Steinlog today, the cost model would not be a lesson learned after the fact — row-write accounting would be the first architectural commitment, not the correction that followed a dead product. I would also front-load the four-tier market-rail doctrine before Sweden was anywhere on the roadmap, instead of writing it in response to Dutch-only defaults that had already survived thousands of tests undetected. Discipline that arrives after the fire is still discipline; it is only cheaper before the fire than after it.
Steinlog v2
A multi-tenant SaaS for public-sector bid teams, where cross-tenant data leakage would be an existential trust failure, built on a cost model that had already killed one rebuild — v1 died of Neon compute costs, so v2 could not repeat that failure mode with Durable-Object row-writes instead.
- 1Tenant isolation by Durable Object identity — no tenant_id column exists anywhere, making cross-tenant leakage structurally impossible, paid for with a separate allowlisted analytics plane.
- 2Corpus promotion is blocked on a green eval gate: an 84-case eval suite (60 groundable, 24 adversarial) must clear floor and delta thresholds before a legal-corpus change ships.
- 3A four-tier market-rail model (invariant machinery / universal surface / jurisdiction instrument / market-only tool) bans market === 'NL' shortcuts by construction — a two-market ternary is indistinguishable from correct code until a third market exists, which is exactly why Dutch-only defaults survived 2,000+ passing tests before the doctrine existed.
- 4Timestamps are sealed with a non-qualified RFC-3161 timestamp today, disclosed as such rather than claimed as eIDAS-qualified, with a config-only upgrade path to a qualified TSA.
- 5PostHog LLM analytics instruments the public "Vraag de Wet" law tool; the core tenant AI paths (X-Ray, extraction) use structured logging instead — a disclosed asymmetry, not a blanket observability claim.
- 6A Sweden market adapter is in active design, alongside the live outbound prospecting motion into the Dutch market.
How this was done
Architected · Owned · Co-built
What I'd do differently
Would have designed the Durable-Object tenant-isolation model and the row-write cost discipline from day one, rather than learning it the hard way after v1 died of Neon compute costs.