Interactive demo · synthetic dataEvery record on this page is fabricated.

DENAVUM

A private, single-user app whose security boundary is the database, explained here rather than simulated.

The production code behind this demo is my own work — architecture, directing Claude Code, row-level security on every table, deployment.

Read about the system

WHY THERE IS NO SIMULATION ON THIS PAGE

This page has no live simulation, because the thing worth demonstrating is a database policy — and faking a database policy is not on.

The obvious demo writes itself: issue the same query as two different users, watch one row set shrink, caption it “the database decided that, not the interface”. It would take an afternoon and it would be the least honest thing on this site. There is no Postgres in your browser, so the two users would be a variable, the policy would be an if statement, and the filtering would happen in exactly the place the app is built to move it out of.

A UI-level check impersonating a database-level one does not illustrate row-level security. It demonstrates the failure mode row-level security exists to remove.

Nor is there a live instance to point at instead. DENAVUM is a private app with one account, and that account’s money, journal, health and people records are in it — so no screen, figure or record from it appears on this page.

So: the security model is here, explorable, as the live app has it. One policy is here as a migration file writes it, labelled with the version it comes from. And two parts of the system that port honestly are genuinely running further down.

tables, row-level security forced on every one
60TABLES, ROW-LEVEL SECURITY FORCED ON EVERY ONE
page routes, 42 of them behind sign-in
46PAGE ROUTES, 42 OF THEM BEHIND SIGN-IN
test files
194TEST FILES
account, and no way to sign up
1ACCOUNT, AND NO WAY TO SIGN UP

The first three were counted in the rebuild’s repository on 9 Oct 2026; the fourth is its documented design. None is read from the app or its data.

WHAT IS LIVE, AND WHAT CAME BEFORE IT

DENAVUM has had two versions, and both have run in production. The second replaced the first on 30 Sep 2026 and is the one live now.

LIVE SINCE 30 SEP 2026

The September rebuild

  • A private, single-user app on Vercel, with a Supabase Postgres database.
  • One account, its owner’s. Sign-ups are switched off, and every sign-in needs a code from an authenticator app.
  • A worker on the owner’s Mac runs the AI jobs, the Instagram sync and a nightly backup.
  • It replaced the first version, and Bridge, a local work tracker that was retired the same day.

RETIRED 30 SEP 2026

The first version

  • Three spaces: Vita for life, Opus for work, Studio for content.
  • In production on Vercel from 1 Aug 2026, against Supabase. Its 15 migration files, which define 47 tables, had been applied to the live database before the cutover; the date they ran was not recorded.
  • At the cutover its tables, views and functions were moved into a separate schema, with a verify script and a rollback script, rather than dropped.
  • The two pieces running further down this page were ported from it on 5 Sep 2026, before the rebuild began.

Checked on 9 Oct 2026: the sign-in page answers, and the worker on the Mac is running. Not checked: how much the app is used. That would take a read of production data, and this portfolio does not read production data.

HOW IT WENT LIVE, 30 SEP 2026

Not as a fresh deploy. The first version had been in production for two months, so the cutover was run as the migration of a running system, in this order:

  1. 1A full backup of production.
  2. 2The migration rehearsed on a local copy built from that backup.
  3. 3The new migrations applied.
  4. 4The rebuild deployed without taking the domain, smoke-tested, then promoted.
  5. 5The first version’s tables, views and functions moved into a separate schema, with a verify script and a rollback script.
  6. 6Bridge’s comments imported (its creator data had been moved in before the go-live began), and the worker installed on the Mac.

The same day, the whole first sign-in was rehearsed on production with a throwaway authenticator, deleted afterwards. Every write to production waits for the owner’s go-ahead.

FIVE SPACES, ONE CAPTURE BOX

Today, Life, Money, Creator and Work sit behind a single box that reads a typed line and files it. Today is a daily deck drawn from the other spaces. Open the four that hold the records, and the box in front of them all.

Life

The journal, the health check-ins and the people: the records that are the reason no single check is trusted.

WHAT IT HOLDS

  • Tasks and habits
  • Check-ins
  • A journal
  • Goals
  • People
  • Documents and reading

WHAT GUARDS IT

The AI jobs run on the owner’s Mac under a database role that cannot reach the journal, people or health tables at all.

WHERE THE BOUNDARY ACTUALLY IS

Seven layers stand between a request and a row, and the AI jobs have an eighth of their own. Five are gates: they settle where a request is going, who is asking and whether the input is well formed. Three are the boundary on what comes back, and Postgres enforces all three. Being clear about which is which is most of the security model. Open each one.

Host guard

Every request, first

A request addressed to any host but the app’s own is turned away, and pages are served with a content security policy generated per request.

A gate at the edge. It decides which address the app answers on. It does not know who is asking, and it has no opinion on what they may read.

THE TWO THINGS THAT GENUINELY RUN

Not everything in DENAVUM needs a database. Two pieces of its first version are pure functions with no database underneath them, so they came across whole when this page was built, on 5 Sep 2026 — and both of them are structural claims rather than features, which is why they were worth bringing.

One schema object, two call sites

The claim is not “it validates on the server as well”. In the first version there was exactly one schema object for a task, and both the form resolver and the Server Action imported it — so they could not drift apart the way a hand-written client check and a hand-written server check always eventually do. Type something wrong. Both panels below are calling the same object, so there is nothing you can do to make them disagree.

BROWSER — FORM RESOLVER

A convenience. It puts a message next to a field without a round trip.

REJECTED — 1

  • dueDate · The due date cannot be before the start date

SERVER ACTION — BEFORE POSTGRES

Not a convenience. A request that never went near the form arrives here too.

REJECTED — 1

  • dueDate · The due date cannot be before the start date

Identical, and not by agreement — by import. taskSchema is one object with one code path; the two panels are two call sites of it.

Recurrence, computed rather than stored

A routine stores a rule. Occurrences are derived on read. Nothing is written until a person actually records a check-in — which turns “this will not fill your database with a thousand speculative future rows” from a promise into a property of the design. This is the first version’s recurrence module, ported with no logic changed, running now.

MORNING TRIAGE

Every weekday (Mon–Fri)

Monday to Friday. Nothing on a Saturday, which is the point of having the frequency at all rather than seven checkboxes.

Six weeks from 2026-02-23. Dates the rule is due are marked “due”.
MTWTFSS
2324252627281
2 2026-03-02 due3 2026-03-03 due4 2026-03-04 due5 2026-03-05 due6 2026-03-06 due78
9 2026-03-09 due10 2026-03-10 due11 2026-03-11 due12 2026-03-12 due13 2026-03-13 due1415
16 2026-03-16 due17 2026-03-17 due18 2026-03-18 due19 2026-03-19 due20 2026-03-20 due2122
23 2026-03-23 due24 2026-03-24 due25 2026-03-25 due26 2026-03-26 due27 2026-03-27 due2829
30 2026-03-30 due31 2026-03-31 due1 2026-04-01 due2 2026-04-02 due3 2026-04-03 due45
OCCURRENCES IN VIEW
25
NEXT AFTER
2026-04-06
ROWS THIS WROTE
0
What is real, and what is simulated

Runs in your browser

  • The recurrence engine — occursOn, occurrencesBetween, describeRecurrence and the date-only arithmetic underneath them — copied from the recurrence and date modules of DENAVUM’s first version with no logic changed
  • One validation schema object, called by the browser panel and the server-action panel on this page. Not two copies that agree: the same exported object

The policy SQL is quoted verbatim from the first version’s own migration file. 40 tests assert the ported recurrence and validation behaviour, and assert that nothing in this page’s dataset names an employer or customer, carries a credential, an email address or an identifier of the live app, or repeats the retired claim that the migrations never ran.

Simulated

  • Nothing about row-level security. There is no mock policy layer, no second user, and no row set that changes — deliberately. That widget is the lie this page exists to refuse
  • The policy SQL is quoted from the first version’s migration files, not executed here. Those migrations had run against its live database before it was retired on 30 Sep 2026; the date they ran was not recorded
  • The validation chain is a plain-TypeScript port of the first version’s Zod 4 field builders — same normalisation order, same refinements, same messages. This portfolio does not depend on Zod, and the page does not pretend the library call is literally the same one
  • No database, no network, no account. Nothing on this page reads or writes anything anywhere, and no figure, screen or record on it comes from the live app