JS

02Web application

Civilocity Portal

A client portal where nothing reaches a client without passing draft, approval, and publication, and where the database refuses to return another client's rows even if the application asks for them.

Role
Product direction, workflow architecture, governance requirements, implementation review, and acceptance
Timeframe
2026, built and deployed, pre-pilot
Status
Pre-pilot · deployed, no external users yet
Outcome
A publication pipeline where approval binds a content hash and client pages are rendered only from immutable snapshots, over a database that enforces tenant isolation independently of the application code.
How I work, on every project
I decide what the system is for, what it is forbidden to do, and what the finished outcome must be. AI agents execute against written guidance, and that guidance is ready only when the outcome is explicit and agreed.

What this is

Clients want to know where their project stands without having to call someone to ask. That is the whole product. A developer logs in and sees their schedule, their documents, their agency approvals, what is blocked, and what is waiting on them.

The firm wants the mirror image of that, and it is a harder thing to build. It wants certainty that a client never sees something unreviewed, never sees a number that had not been checked, and never sees anything belonging to somebody else.

The portal exists to make the first thing easy and the second thing structural.

This system was built as a paid engagement for Civilocity and appears here with the firm's knowledge and consent. Nothing shown or described uses real client material.

The problem

Client portals fail in two directions, and both are one-mistake failures.

They fail forward when something reaches a client too early. A draft status, a superseded drawing, a fee that was still being discussed internally. Usually nothing was done wrong. Someone published a page while a field was still in flight, and the client read it as final, because published things look final.

They fail sideways when one client sees another client's records. In most applications the only thing preventing it is that every query remembered to filter by tenant. That protection holds until one query does not, and the failure is silent. Nobody gets an error. Client A simply sees client B.

Procedure does not fix either of these. A rule that says be careful when publishing is a rule that will hold for a long time and then fail on a Friday afternoon. The design goal was to make both failures structurally impossible rather than procedurally discouraged.

Where the boundary sits

Draft STAFF EDIT FREELY Approve BINDS A HASH OF THE EXACT BYTES Publish IMMUTABLE SNAPSHOT Client view READS THE SNAPSHOT EDITING PUBLISHED MATERIAL RETURNS IT TO REVIEW
Nothing reaches a client except through this line, and the working copy is never on it. The accent marks the one place a person decides.

Publication is a pipeline with no shortcuts through it.

Staff draft. A named person approves, and approving binds a cryptographic fingerprint of the exact content approved. Change one byte afterwards and the approval no longer applies to what is now there. What the client is served does not come from the draft tables at all. It comes from a projector that renders only from immutable snapshots, so the working copy is not on the serving path in any state.

Editing published material does not quietly update it. It returns the material to a controlled review state, and it goes around the loop again.

Every action is written to an append-only audit trail. Documents are delivered as expiring, revocable links rather than as files sitting inside the portal. AI exists in the system only as draft suggestions a named human reviews, and it ships disabled by default.

The database does not trust the application

Tenant isolation is enforced twice, by two different kinds of thing.

The application checks permissions, the way any application does. Underneath it, Postgres row-level security enforces the same boundary independently, so a missing filter in application code does not become a disclosure. The database declines to return the rows. The isolation posture lives in the migrations, which means it is part of the schema rather than a configuration someone remembers to apply.

The test suite renders the same portal as two different tenants and compares the outputs byte for byte. Isolation tests run against a real Postgres instance rather than a stand-in, because row-level security that is mocked out is not being tested at all.

One layer having a bug is expected. Both layers having the same bug at the same time is a different order of unlikely.

What a client is allowed to do

The client's write surface is deliberately narrow, and it is the part of the design I get the most argument about.

Clients can post to a message thread, which is append-only, rate limited, and audited. They can upload files, which land in quarantine and wait for staff review before they are accepted into the record.

What is deliberately absent is freeform ungoverned chat, and any client action that writes directly into the project record. The portal's job is to keep structured records structured. Every channel that a client can push through is one that has to be reviewed, so each one is added only when its governance is ready, and not before.

The decisions I would defend

Server-rendered, on purpose. No single-page framework. Server-rendered pages with light interactivity, which keeps the security boundary small enough for one person to inspect end to end. Fewer moving parts on the surface that matters most.

Every guarantee maps to a numbered test. The documentation set carries a test registry, and each stated guarantee cites the tests that enforce it. A guarantee with no test behind it does not get stated. That rule is what keeps the documentation from drifting into aspiration.

The rules live in one place. Architecture decisions, the authorization and tenancy spec, the state machines, the publication contract, and the AI governance policy are numbered documents, and they are the only place rules live. Not in templates, and not in prompts.

Built ahead of the business it serves. The portal is infrastructure for the firm's expansion into direct engineering work. It is the client experience that practice will need, finished before the first client of that kind signs.

Evidence

Demonstrations use Alder Ridge, a fictional development project built for the purpose. No real client material is shown. The last two items below are capture work still to be done, and they are listed as planned rather than available.

  • 269 automated tests spanningC1 policy, API and end-to-end coverage, tenant isolation against a real Postgres instance, and golden two-tenant byte comparisons
  • Continuous integration running the same gate on every change: tests, type checking, a governance linter, and secret scanning over the full repository historyC3, on a branch-protected main that deploys automatically
  • The numbered documentation set: product doctrine, authorization and tenancy spec, schema and state machines, publication contract, test registry, AI governance
  • Demo tenant seeded with Alder Ridge, a fictional development project, so an evaluator can log in and see exactly what a client sees
  • Walkthrough recording: draft, approve with the fingerprint visible, publish, client view, audit entry

Status

Working and deployed. The publication pipeline, database-enforced tenancy, document delivery with safe links, the message thread, quarantined uploads, financial visibility grants, schedule and Gantt, 3D model review through hosted viewers, invitations with disable switches, the audit trail, and the internal admin surface. Live on managed hosting, deploying automatically from a branch-protected mainC2.

Validated. Tenant isolation verified against a real database in automated tests, with two-tenant golden comparisons coming back byte identical. 269 tests green in continuous integration.

Pre-pilot. The open gate is the owner's acceptance script and the first pilot clients. No real client data has entered the system.

In development. Notifications, which the portal does not yet have. Pilot onboarding. Richer client-side actions such as acknowledgments and structured approvals.

Known gaps. There are no external users yet, so usability without the builder in the room is unproven, and that is the gap I would want closed first. The portal is pull-only today: a client has to log in to discover that anything changed. The client write surface is narrow by design and will widen only as fast as the governance behind it does. The demo material is fictional, and the evidence above describes a system that has been built and deployed but not yet used by a paying client.

Claims ledger

What this page asserts, and where each assertion is checked. Each entry names its evidence and the source-repository commit it was checked against. A number that is not in this ledger does not appear on the page.

C1

269 automated tests pass, including tenant-isolation suites over Postgres row-level security and golden two-tenant byte-comparison checks.

documented
CI run and test registry mapping each guarantee to a numbered test; report excerpt in the evidence room
checked 2026-08-30 at 02ca10221c
C2

Deployed to production hosting; nothing reaches a client without draft → approve → publish, approval binds a content hash, published bytes come only from immutable snapshots, and the audit trail is append-only.

documented
Live deployment plus numbered product-doctrine docs; demo-tenant walkthrough video planned in Phase 2
checked 2026-08-02 at 02ca10221c
C3

Every change runs tests, type checking, governance lint, and full-history secret scanning in CI on a branch-protected main.

documented
CI workflow configuration and PR history; screenshots in the evidence room
checked 2026-08-02 at 02ca10221c

Next

03Atlas / Johann OS