Creator Trust — ADR-122, ADR-123
Creator Trust — ADR-122, ADR-123
Two rulings from a review of three “Trust Moat” proposals (2026-08-08). The review’s finding was that the broadest and most marketable of them would have been read as contradicting a feature the specification already contains — §7.2 embeds campaign content by design — and that the honest, narrower version is both defensible and checkable, which the broad one was not.
ADR-122 is the first ADR in this corpus whose primary enforcement is contractual rather than technical. That is stated here rather than blurred, because P4 asks for the enforcement point and “a term in a vendor agreement” is a real answer only if someone names who verifies it and when.
ADR-122 — The AI Covenant Is Narrow, Checkable, and Separates Three Claims
Status: Accepted · Date: 2026-08-08 ·
Depends-on 042 (the Campaign scope key that makes embeddings purgeable as a unit) ·
interacts with 060, 078, 098, 109
Context
The proposal was a strict guarantee: 100% IP retention, and user assets, maps and campaigns never used to train AI — offered as a marketing asset against competitors with ambiguous terms.
The market instinct is right and the broad wording is not survivable. §7.2 specifies a
Retrieval-Augmented Generation pipeline over PostgreSQL + pgvector, an ILLMProvider
encapsulating GenerateAsync() and EmbedAsync(), and dispatch to GeminiProvider,
OpenAIProvider or LocalOllamaProvider. It records that campaign-derived summaries and
embeddings are Campaign(RoomId, BranchId) scope. So the platform vectorises user
campaigns today, by specification.
Embedding is not training, and that distinction is technically correct. It is also
invisible to the audience this covenant is aimed at. A creator told “we never use your
content for AI”, who then learns their campaign is vectorised and shipped to OpenAI as
retrieval context, will not accept the distinction as a defence — and in an anti-AI pledge,
in a market the proposal itself describes as paranoid, a discovered nuance is the most
damaging possible form of the M-F-03 overclaim. The reputational asymmetry runs entirely
against the broad version.
Decision — three claims, kept separate
- Users retain copyright in their content. The platform takes only the hosting-and-processing licence its own pipeline requires. This must be stated as a licence and not as an absence of one: §6.2 transcodes, re-encodes and generates 2D bakes (ADR-005 requires a bake), and §7.2 embeds — all derivative works. A covenant phrased so a lawyer reads “no licence granted” would put the bake pipeline outside its own terms, which is a worse failure than the ambiguity it set out to fix.
- No model is trained on user content. True structurally: there is no training pipeline in this architecture, and this is the claim the platform can keep.
- User content IS embedded and sent to the configured provider for retrieval, and this is disclosed up front rather than discovered. Clause 6 places the sentence where a creator decides.
Decision — the parts the platform cannot guarantee alone
- Third-party providers are held by contract, with a named enforcement point. Content
routed to
GeminiProviderorOpenAIProviderleaves this system, and what a third party does with an API input is governed by its terms. A release-checklist item verifies that every configured provider carries a binding zero-retention, no-training term, and a provider without one may not be configured. This is the contractual enforcement the header names: it is a real check with an owner and a moment, not an assumption. LocalOllamaProvideris the structural opt-out — the one path where the guarantee holds by construction rather than by contract, and therefore the honest answer for a creator who will not rely on a vendor term. Studio’s BYOK path is outside the covenant entirely (Studio_Architecture.md§9 item 12: creator-keyed requests go directly to the provider and never transit platform infrastructure), and this record says so rather than letting silence imply coverage.- The disclosure is a term of the publish flow, not a link in a footer. Studio’s Marketplace publish flow presents, before the creator commits: “Your content is embedded (vectorised) to enable in-game AI features for buyers. We guarantee it will NEVER be used for model training.” Both halves are mandatory — the guarantee alone is the overclaim, and the disclosure alone wastes a real assurance.
Decision — the gap this review found
- Campaign-derived embeddings gain the erasure path they never had. §7.2 built a purge
job for licensed material keyed on
sourceId, and recorded that campaign embeddings areCampaign(RoomId, BranchId)scope — but nothing said what happens to them when a campaign is deleted. A licence takedown had a deletion path and a user’s own content did not, which is exactly backwards for a document about creator trust. Deleting a campaign purges every summary and embedding under its scope key, reusing that job with a different key rather than building a second one, and bounded byQ-054because an embedding is a derived artefact in that quantity’s own sense.- This does not weaken §7.3’s zero-deletion claim. That claim is already qualified there as scoped to the timeline — no in-game operation destroys history — and campaign deletion is an account-level act on the same side of that line as §7.6’s erasure.
Alternatives Considered and Why Rejected
- The broad covenant as proposed. Rejected on the reputational asymmetry above. It is not false — no training happens — but it would be read as covering embedding, and the correction would arrive as a discovery rather than a disclosure.
- Say nothing and let the ToS carry it. Rejected: the competitive value is real, and a term nobody reads is not a moat.
- Guarantee no third-party transmission at all. Rejected: it deletes the AI feature set, or forces local-only operation on every user rather than offering it to those who want it.
- Treat “we do not train” as self-evident and skip the provider term. Rejected — this is the most likely shortcut. The platform’s own behaviour is not the exposure; the API input’s destination is, and an unverified vendor term is an assumption wearing a guarantee’s clothes.
Consequences (including negative)
- The covenant is weaker than the one a competitor might print, because it admits embedding. That is the cost of it being true, and the corpus prefers a narrower claim that survives scrutiny to a broader one that does not.
- A provider whose terms change silently breaks the covenant between checklist runs. The release-checklist item bounds this to one release cycle; it does not eliminate it, and no mechanism here detects a mid-cycle change.
- Clause 7 makes campaign deletion more expensive — a purge over an embedding index is
not free — and it is bounded by
Q-054, which is itself Pending. Until that value exists, the deadline is named and unquantified.
Rights-holders (ADR-079)
The creator, in their content, which clause 1 states rather than assumes. No new class of retained personal data is created by this ADR: the embeddings already existed under §7.2 and this record gives them a deletion path they lacked, which moves in the subject’s favour.
Enforcement
- An architecture test asserts no code path carries user-asset or campaign bytes into a training sink, which is trivially satisfiable today (no such sink exists) and is therefore a regression guard rather than a discovery — the reason to write it now is that the first such path would otherwise arrive unremarked.
- A release-checklist item verifies the zero-retention/no-training term for every configured provider (clause 4). This is the contractual enforcement point, and it is the weakest link in the ADR — a checklist is a habit with a name, and it is stated as such.
- A test asserts campaign deletion purges embeddings under the scope key, sharing its fixture with §7.2’s licence-revocation job so the two cannot drift.
- Not enforceable, stated: nothing in this tree verifies what a third party does after receiving an API request. The covenant’s third-party leg rests on contract and audit, and no test will ever change that.
ADR-123 — Custom-Asset Access Has an Authority, and the Play Client Has No Publish Path
Status: Accepted · Date: 2026-08-08 · Amends 006 (adds the download half it did not specify) · Amends 085 (adds the third stale-authorisation path) · Depends-on 072 (the liveness states that gate issuance) · Depends-on 104 (the revocable grant issuance re-checks per request) · interacts with 109, 060
Context
Two proposals were reviewed and they turned out to be one subject: what authority governs a custom asset, and where may it cross a boundary.
The first asked to document that custom uploads live in private buckets reachable only by short-lived presigned URLs issued to authenticated room participants. The corpus did not say that anywhere. ADR-006 specified the presigned pipeline for writes; how an asset is read back was left to inference — the more consequential half for a vault that is private by default. So this is a new commitment, and that is the strongest argument for making it.
Decision — read access
- Custom uploads live in private R2 buckets, no public access, no index, reachable only
through a short-lived presigned read URL (
Q-089). - Issuance re-derives authority from the
RoomGrainper request. The asset must belong to a room in which the caller currently holds a membership grant (ADR-104), and a revoked grant stops issuance immediately. Minting per request rather than per session is the whole mechanism: a URL issued while the caller was a member must not outlive the membership. - A room that is not Active issues nothing. Dormant and Deactivated rooms (ADR-072) mint no URLs, because an asset reachable while nothing is running is an asset reachable by a link alone.
- A presigned URL is a bearer capability and is handled as one: response body only,
never a redirect
Locationwhere a referrer leaks it, never written to a log or a trace attribute, and the shortest TTL that completes a fetch. - This is a third path revocation must reach, alongside ADR-085’s document-write
capability token and export entitlement. It had the same shape as those two and was not
on the list, which is why ADR-085’s record and
security-and-disclosure.mdare both amended rather than left to be reconciled by a reader. - What is NOT claimed: custom uploads are not encrypted at rest. ADR-060’s chunked AES-GCM protects premium third-party bundles; a creator’s own map is ordinary bytes, and a privacy guarantee implying otherwise would be the overclaim §6.1 already warns against.
Decision — the boundary
- The VTT play client contains zero publishing pathways. The Personal Vault is for running games. Stated as an absence rather than as a multi-step confirmation, because a path that does not exist needs no ceremony to guard — the strongest form available under P3, and it cannot be weakened by a later UX decision without deleting this clause.
- The Marketplace publish flow and its ToS consent event live exclusively in Studio (ADR-109), which is already where the Asset Manager holds Commercial Bundle packaging and the creator’s signing material. Putting a publish path in the play client would put signing material there eventually, which is the placement argument ADR-109 already made.
Alternatives Considered and Why Rejected
- A multi-step authorisation and ToS dialog in the VTT client (as proposed). Rejected by clause 7: it guards a path that should not exist, and a procedural guard is weaker than a structural one. It would also have split the publish flow across two applications.
- Session-scoped rather than per-request issuance. Rejected by clause 2 — it is precisely the stale-authorisation shape ADR-085 exists to name.
- Public bucket with unguessable keys. Rejected: security by URL entropy has no revocation story, and a leaked key is permanent.
- Serving custom assets through the CDN for cache efficiency. Rejected as specified, because a shared cache weakens the per-request authority in clause 2. Not foreclosed — it would need its own record stating how cache keys carry authority.
Consequences (including negative)
-
Assets are unreachable while a room is Dormant, which is a real behaviour change for anything that expected to prefetch outside a session. It is the intended trade and it will be discovered by the first feature that wants background loading.
-
Per-request issuance adds a round trip per asset, mitigated by OPFS caching (§6.1) which means the URL is needed once per client per asset version rather than per view.
-
Q-089is Pending. The TTL is a security parameter with no value yet, and the registry records why a guess would be the wrong way to set it. -
Clause 7 constrains unbuilt code. The frontend skeleton does not exist, so this is a forward constraint — legitimate, and the same shape ADR-068 used for the SDK contract, but it binds a team that has not started.
Superseded by events, 2026-08-08 — the skeleton was built the same week. The play client now has a shell and a Personal Vault (
apps/vtt-frontend-client/src/ui/), so this is no longer a forward constraint and the sentence above is kept as the record of what was true when the decision was taken rather than as a description of the tree. The clause held through the build without amendment, which is the outcome a forward constraint is for. What changed is that it is now enforced by something that is not vacuous — see the third and fourth bullets under Enforcement.
Enforcement
- The issuance path checks membership and liveness; a test asserts a revoked member is refused a URL, and another asserts a Dormant room mints none.
- An architecture test asserts the VTT client bundle contains no publish route — the
structural form of clause 7, and the reason it is a boundary rather than a policy.
tools/check-bundle-absence.mjsis that test. It passes vacuously and its own header says so: there is no publish code in this tree for it to find, which is what the clause decided, so its positive control is the only evidence the detector works. It earns its place by being what goes red the day somebody adds an endpoint. - Two checks added 2026-08-08, when the play client’s UI was built, and neither is vacuous.
The gap the bundle scan leaves is one of sequence: a publish pathway does not arrive as an
endpoint, it arrives as a button, weeks earlier, wired to nothing — and by the time there
is a route to detect, the affordance is a requirement. So the clause is now also enforced
where it would first be broken.
src/ui/vault/vault-actions.tsis a closed registry andvault-actions.test.tspins it for exact equality in both directions. Every control the vault renders comes from it, andVaultActionScopehas one member, so an action that leaves the account cannot be spelled — the top ofP3’s ordering.e2e/vault.spec.tsthen asserts the set the DOM rendered equals that registry, which is what stops a hand-written control appearing beside it.tools/check-vault-boundary.mjsscanssrc/ui/**for outward-facing affordance wording (comments stripped, so this record’s own vocabulary does not trip it) and is apnpm run buildgate. Verified by mutation on the day it landed: aPublish to Marketplacebutton added toVaultCard.tsxfailed the build naming the file and line; a fifth entry in the registry failed the ledger test with this clause quoted; a control rendered past the registry failed the DOM assertion.
- A test asserts a presigned URL never appears in a log template or a redirect
Location, reusing the shape the Marketplace’s own §8.2 rule already implies. - Not enforced, stated: nothing prevents a user sharing a URL they legitimately received
within its TTL.
Q-089bounds that window; it does not close it.