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 adult relationship design and the scoped advertising gate, 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.

The linked-order condition. When the placement has an insertion order, at least one invoice for that order must be paid or the tear-sheet route returns invoice_unpaid. A placement without a linked order does not enter this invoice branch. The output after successful checks is a tear-sheet record, not a demonstrated PDF rendering.

And the one that has nothing to do with money

Before the tear-sheet record is created, the route selects placement submissions whose status is approved or whose page reference is present. It resolves publication suppression for the selected student references; any blocked reference produces consent_blocked with the count and prevents createTearSheet. A page reference can qualify a submission independently of its status. Selection is not proof that a creative actually ran or was printed.

This check is reached before record creation and uses the publication purpose and its applicable suppression policy. It does not establish universal explicit opt-in, a check over every creative in the school, or an advertiser delivery action. The source evidence is specific to this route and its selected set.

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 selected placement submission references a publication-suppressed student; the refusal includes the blocked count.
  • 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 — the provider-enabled branch — 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.

With the live switch off, the settlement this system can 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.

Existing child-related refusal evidence

Adult relationship design, with a specifically reached advertising gate.

The account-and-contact model is designed around adult organisational context. That statement does not prove that every CRM-related surface is free of student data. The reviewed source establishes the following narrower advertising checks; it does not establish a universal family wall or a new child-data procedure.

The tear-sheet POST route resolves the placement and campaign, checks scope, and reaches requireAdviser before its selected-submission check and record write. That adviser function refuses when any membership is minor-held or minorStaffDenied resolves the role as denied for commerce. The function runs before the consent-dependent creation branch; it is not claimed to run before every lookup in the request.

For this placement, the route selects submissions whose status is approved or whose page reference is present. Those conditions do not independently prove that a creative was printed. It obtains consent context for the selected student references and computes publication suppression. A nonempty blocked set returns consent_blocked and a count before createTearSheet; a permitted set can continue to record creation. This is a conditional refusal, not an assertion that every tear sheet is blocked.

The existing helper uses the publication purpose and its applicable policy. That is not a claim of universal explicit opt-in. No separate Dibs-style family wall, roster projection, or document-rendering guarantee is inferred from this route. This is a bounded static call-chain review, not a runtime exercise with child data or proof of a deployment.

Adult staff contacts, working notes, and commercial amounts can still be sensitive. The new public guides use fictional adult material and do not create an upload, consent collection, or record-sharing action. Additional minor-data or consent work remains a separate child-safety handback.

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.