The guards, and what each one will not do

A system is defined by what it refuses, and these refusals are in code rather than in policy.

The advertising lane is the one part of this product wired from first contact to proof of publication, so it is also where the hard refusals live: what the contract ladder will not let you skip, and what marking something paid actually requires. Those sit here beside the two structural walls — the student fields that are absent rather than filtered, and the tenant boundary that is read from the session rather than from the request.

They are one page because they are one argument. Every item below is a place the system says no, and each one is stated with the rule that enforces it rather than as an intention.

The lane that is finished rather than promising

Advertising and sponsorship sales, wired end to end.

Everything above this section is a model waiting for screens. This section is the exception, and it is the strongest thing we can honestly sell today: the advertising and sponsorship lane inside a school publication, from the first advertiser conversation to the proof-of-publication document, behind two navigation entries a user clicks.

The ladder is: an advertiser relationship opens. A proposal is built from the school’s own rate card and zone configuration, totalled server-side in integer cents. The proposal is sent, and it is accepted or declined. Acceptance mints an insertion order, which is the binding document; the order is sent and signed. The signed order takes an invoice; the invoice settles; the buy fans out to the placement systems that fulfil it; and the placement produces a tear sheet.

What makes that a product rather than a diagram is that each move is validated against a literal allowlist of legal transitions before anything is written. A proposal goes draft, sent, then accepted or declined, and those last two are final. An order goes draft, sent, then signed or cancelled, and both of those are final. An invoice goes draft, sent, then paid or voided. An attempt to move outside the ladder comes back as a refusal that quotes the legal ladder in its own message. There is no code path here that arrives at a state by overwriting a status field.

Two walls that exist because the failure is expensive

No creative before the signature. Attaching artwork to an insertion order is refused unless that order is signed, and the same wall covers attaching the inventory campaign that would fulfil it. The failure this prevents is a specific and costly one: a page designed, proofed and printed against a buy nobody ever signed, discovered at the point where somebody has to decide who eats it.

No proof of publication on an unsettled buy. A tear sheet is refused while the linked order’s invoice is unpaid. A proof-of-publication document on an unpaid buy is a piece of paper that argues against you in the conversation you are about to have.

And the one that has nothing to do with money

Before a tear sheet is issued, the students appearing in the creatives that actually ran — approved, or already placed on a page — are re-checked against publication consent, and if any one of them is suppressed the document is refused, with the count of blocked students in the refusal. Draft and rejected creatives are not considered, because they were never printed.

That check is the sharpest illustration of what this whole model is for. A general CRM has no reason to think about it, because in a general CRM a proof-of-delivery attachment is an attachment. Here the artefact can contain a photograph of a child, it is going to an outside business, and consent may have been withdrawn in the weeks between the ad running and the tear sheet being asked for. So the question gets asked again at the last moment, and the answer is allowed to be no.

The refusals, by their own names

Every one of these is a literal code this system returns, not a paraphrase of one. A refusal that names itself is a refusal a support conversation can start from:

  • illegal_transition — the move is not on the ladder, and the message carries the ladder that is.
  • io_not_signed — creative or a fulfilment campaign was offered to an unsigned insertion order.
  • invoice_unpaid — a tear sheet was asked for on a buy that has not settled.
  • consent_blocked — a student on the placement is publication-suppressed, with the count of blocked students attached.
  • payment_evidence_required — an invoice was flipped to paid with no provider charge and no attested offline record.
  • prospect_converted — the deal has already provisioned a customer, so it does not move again.
  • custom_price_required — a custom line arrived without the integer anchor that IS its amount.
  • account_exists — this studio already has an account for this school, and here is its identifier.

Money

Nothing is charged. And marking something paid takes evidence.

The live payments switch defaults to off, and with it off a live key is held rather than used. There is no checkout on this website, no price on this page, and no card field anywhere in this product’s marketing surface. That is the posture across this whole estate and it is not a soft-launch caveat.

The more interesting rule is what happens when a person tries to mark an invoice paid. That transition is the one every accounts-receivable feature makes trivially easy, and it is the one that produces phantom revenue: a status flipped by somebody who meant to check later. Here it is guarded. A paid transition requires either a provider identifier with the live payments switch on — meaning a real charge exists — or an explicit offline record carrying a method and a reference, attributed to the staff member the server resolved from the session rather than to a name typed into a box. A bare request with neither is refused, by name, with nothing written and no downstream fan-out or commission triggered.

Because the live switch is off, the only settlement this system can currently record is the offline one, and it is labelled as such in the audit trail: reconciled offline, not paid by a provider. That distinction rides the record and the response. An invoice here never claims a card payment that did not happen. Which is the honest description of a receivables feature with no live rail: it can hold an attested human statement about a cheque, and it refuses to hold anything else.

One more, on the quote side. A line item that references a real plan or catalog entry has any client-supplied price discarded and re-read from the catalog. A package discount is basis points and the subtotal is computed on the server. The pattern across all of it is the same: the client may say what it wants, and the server decides what a thing costs.

Children’s data

The CRM holds adults. The one place a student can appear, the answer is no.

A CRM that sells into schools sits uncomfortably close to the most sensitive data a school keeps, and the honest way to handle that is structural rather than promissory. So: no roster or student field is read or written anywhere in the CRM model. Not gated, not filtered, not redacted on the way out — absent. An account carries a school’s name and its directory row; a contact is a member of staff or the owner of a local business; a deal carries an amount and a date. The route module that owns the account states this in its own header, and it is the shape of the code rather than a rule somebody has to remember.

There is exactly one place in this whole surface where a student can appear, and it is the advertiser tear sheet, because an advertisement can contain a photograph of a child. There, the answer is a refusal: before the document is issued, the students in the creatives that ran are re-checked against publication consent, and one suppressed student blocks the document. Consent can be withdrawn after an ad has run, and the document being produced weeks later has to respect that or the withdrawal meant nothing.

Two further walls, both in code rather than in policy. A student role is denied the advertising commerce surface outright — it is commerce, and a minor does not belong in it, so the gate refuses before anything else runs. And a credit or acknowledgment that names or depicts a student runs a consent resolution and a publication-suppression check before any write, not after.

To be exact rather than flattering: this is not a claim that we hold no sensitive data. A school’s staff contacts, notes on a relationship, and what a district is worth to your studio are all real data and some of it is commercially sensitive. The narrower and true claim is that it is adult organisational data, walled at the tenant and at the row, and that it is not children’s data.

Who can see and change what

The tenant rides the session. Never the request.

Multi-tenant leaks almost never come from a missing check. They come from a check that reads the tenant from the same place the attacker controls. So in this model the studio is resolved from the session — from the authenticated rep’s own profile, or from the organisation bound to the login — and never from the request body. There is no field a caller can set to change which studio they are operating as, because the value is not read from the request at all.

A request for another studio’s account or deal comes back as not-found rather than forbidden. That is deliberate and it is not politeness: a forbidden response confirms the record exists, which is itself a leak. Not-found says nothing.

Support access is split along the axis that matters. A support session reads across tenants, because that is what makes it possible to help you. It is refused every write: the function that resolves which studio a write targets forbids a support session by name before any handler logic runs. Reading your data to answer a question and writing to your data are different powers, and they are separated in code.

And a rep is confined to its own book plus the house pipeline: the deals it is working, the deals it owns, and the ones that are genuinely unassigned. A colleague’s deal is not visible — and, per the rule above, it is not visible as a not-found rather than as a locked door.

The whole surface requires a studio actor at all. A login that is neither a rep, nor a studio administrator, nor support is refused with a message that says what the surface is for. Fail-closed, on the way in.