Access

Who can open what, and what happens when the system cannot tell

Five controls that exist and run in the system this mailbox will live inside, plus one honest row about the mailbox itself. Each is named with the module that implements it, so none of this has to be taken on trust.

Two chips, and what they mean

Platform — a control that is built and running in the wider system this mailbox will live inside. Describing it is describing the house, not the room.

Mailbox — a statement about the address itself. Mailbox provisioning is a founder-confirmed step and is currently off, so every mailbox-scoped claim on this site is about what is built, never about a running inbox.

Every mechanism named on this site carries one of the two. That is not decoration: a platform control quoted as if it were a mailbox feature is a true sentence with its scope removed, and that is the most common way a page like this stops being honest.

Access control is mostly a set of small refusals. Almost nothing in it is interesting on its own, and the interesting part is only visible at the edges: what the system does when a grant is incomplete, when an actor cannot be identified, or when a link between two things has not been established. Those are the paragraphs worth reading below.

The controls

6 mechanisms, each with the module that implements it

A closed list of permissions

Seven named permissions, and no eighth that a string could invent

Platform Built and running

The permissions an administrator can hold are a closed list of seven, declared once: managing users, managing roles, viewing the audit trail, managing billing, managing settings, viewing support, and managing support. A guard checks membership of that list rather than comparing strings, so a typo is not silently a new permission that nothing grants and nothing denies.

Closed lists are unglamorous and they are most of what access control actually is. The failure they prevent is the one where a feature ships checking canManageUser while every grant in the system says manage_users, and the check therefore passes for nobody or, worse, is skipped.

packages/shared/src/admin-rbac.ts:58 (ADMIN_PERMISSIONS) · :70 (isAdminPermission)

Scope, and failing closed

A grant is bound to a tenancy level, and an unbound grant matches nothing

Platform Built and running

Holding a permission is not enough; a grant is also bound to a level -- platform, district, or school -- and to a concrete identifier at that level. A check has to match both.

The interesting behaviour is at the edges, and it is fail-closed at every one of them. A district or school grant with no identifier bound to it is treated as unbound and matches nothing at all, rather than matching everything. A narrower actor can never contain a broader target. And a district-level actor is only allowed to reach a school when the caller has already established that the school belongs to that district -- the engine will not invent the link to make a check succeed.

Every one of those is a decision to answer "no" when the honest answer is "I cannot tell". That is the property worth paying for in this kind of code and it is invisible from a feature list.

packages/shared/src/admin-rbac.ts:84 (ADMIN_SCOPE_LEVELS) · :137 (scopeContains)

Reading records leaves a trace

An access to personal data is recorded, and an unattributed read is still recorded

Platform Built and running

Access to personal data writes an audit record. The detail that says something about the design is the sentinel for an unattributed actor: when a read cannot be tied to a person, the module records it against a reserved identifier rather than skipping the record.

The alternative -- no actor, no row -- makes the exact accesses you would most want to find afterwards the ones that leave no trace. A gap in an audit trail is worse than an ugly entry in it.

packages/db/src/pii-access-audit.ts:50 (PiiAccessAuditArgs) · :44 (UNATTRIBUTED_PII_ACTOR)

A trail that shows if it was edited

The safety audit ledger is hash-chained, so a quiet edit stops verifying

Platform Built and running

For the highest-sensitivity resources, entries are chained: each one is canonicalised and hashed together with the hash of the entry before it, starting from a fixed genesis value. A verifier can walk the chain and report exactly two kinds of break -- a link whose recorded previous hash does not match, and an entry whose hash does not recompute.

What this buys is narrow and worth stating precisely: it makes tampering DETECTABLE, not impossible. Someone with enough access can still rewrite history; they cannot do it without the chain failing verification afterwards. Any product that describes a feature like this as making records unalterable is describing something that does not exist.

apps/api/src/domain/safety-audit-ledger-hashchain.ts:144 (computeSafetyEntryHash) · :194 (verifySafetyChain)

One way in for writing the trail

Recording is a service with an interface, not a line copied into each caller

Platform Built and running

Writing an audit entry goes through one declared interface with one repository-backed implementation behind it. That sounds like housekeeping and it is the reason the trail is uniform: a caller cannot invent its own slightly different record shape, and a test can substitute the interface without a database.

The failure it avoids is an audit trail that is complete in the three places someone remembered and empty in the fourth.

apps/api/src/services/audit-service.ts:29 (AuditService) · :55 (RepoAuditService)

What the mailbox itself will do

An access-controlled inbox on a system we run -- and it is not provisioned yet

Mailbox Built, latched off

Here is the honest statement about the address rather than the platform. A Castra mailbox is intended to be a real inbox with controlled access, run on our own private system, for the staff who sit closest to sensitive correspondence.

Provisioning is a founder-confirmed step and it is currently off. No mailbox exists to apply any of the controls above to, and no message is being sent or received on this domain today. Every row on this page carrying the platform chip is describing the system the mailbox will live inside, and this row is the one that tells you the room is not built yet.

That distinction is the whole reason this site puts a scope chip on every claim. Without it, the four rows above read as features of your inbox, and they are not.

mailbox/brands.ts:502-533 (the brand's own promise) · mailbox/pricebook.ts (honest-off)

Of the 12 mechanisms described across this site, 10 are platform controls running in the wider system and 2 are statements about the mailbox itself, which is not provisioned. Every claim on the site carries the chip that says which it is.Whose control is it?PlatformThe house this mailbox will live in- A closed list of permissions- Scope, and failing closed- Reading records leaves a trace- A trail that shows if it was edite- One way in for writing the trail- Every data class has a published c- Deletion eligibility is computed, - An unverified row falls to a hold - Deleted means gone from every read- The human-readable policy is generMailboxThe room -- and it is not built yet- What the mailbox itself will do- What none of the above says about
Figure 1. Generated from the same two tables that write the panels on this page and on the retention page, so the split shown here cannot disagree with the chip on any individual claim.

The precise version of a claim people overstate

Detectable is not the same as impossible, and the difference is the honest part

A hash-chained audit trail is a genuinely good mechanism and it is routinely described as something it is not. Here is the exact claim.

Each entry is canonicalised and hashed together with the hash of the entry before it. A verifier walking the chain can report exactly two kinds of failure: an entry whose recorded previous hash does not match the one actually before it, and an entry whose own hash does not recompute from its contents. Both mean the same thing to a reader: something changed after it was written.

What that does NOT mean is that records cannot be altered. Anyone with sufficient access to the store can alter them. What they cannot do is alter them and leave the chain verifying, so the alteration is discoverable afterwards by anyone who runs the check.

The reason to be this pedantic is that the overstated version -- "tamper-proof", "immutable" -- is the version a buyer repeats to their own leadership, and it is the version that becomes a problem for them rather than for us.

apps/api/src/domain/safety-audit-ledger-hashchain.ts:126 (canonicalizeSafetyEntry) · :144 · :163 (the two break reasons) · :194

Built and not built

The table with two rows against us

Product categories, not named vendors. The last two rows are the ones a buyer most needs, and both go the wrong way for us.

Every cell states its verdict in words, so the table reads correctly with the stylesheet removed and with no colour perception at all.
Question a buyer should askA general business suiteA school communications appThis product
An admin grant with no tenant bound to itOften treated as globalDepends on configurationMatches nothing -- fail closed
An access nobody can be attributed forNo row writtenWritten sometimesWritten against a reserved identifier
A quiet edit to the audit trailUndetectableDetectable if logs are shipped elsewhereChain stops verifying
Certification badges displayedSeveralSeveralNone -- and none claimed
A mailbox you can actually use todayYesYesNo -- provisioning is off

The fourth row is worth sitting with. Having no badges is presented here as a virtue, and for some buyers it is simply a disqualification -- a procurement process may require a certification we do not have. That is a real reason to choose something else, and it is better read here than discovered later.