---
name: w3ds-platform-self-description
description: "Use when building a platform that other platforms or agents must understand — publishing what ontologies you write, what you can open or create, how your records relate across vaults, or the field shapes a reader needs. Also use when integrating with a platform you did not write and needing to know how its data is meant to work. Answers 'how does a platform describe itself' and 'how do I learn another platform's model'."
license: Apache 2.0
---

# A platform must describe itself, because the data cannot

## The fact this all rests on

**An envelope does not record which platform wrote it.**

A MetaEnvelope is `{id, ontology, parsed, envelopes, acl}`. The eVault's `/logs` gives `{id, metaEnvelopeId, ontology, operation, timestamp}`. The developer key authenticates the writer at write time and is **never persisted**. The Awareness packet is `{id, w3id, schemaId, data}` — the requesting platform is not carried either.

So authorship by a platform is **unknowable from the record**. It can only be *inferred* by matching an envelope's `schemaId` against what each platform declares — and `schemaId`s are not unique to one platform, so that inference must be labelled as inference, never stated as fact.

Everything a reader needs to interpret your data therefore has to be **declared somewhere on purpose**. This is not documentation. It is the only channel.

## Publish in two places, with the same content

1. **On your own vault.** Your platform has a registry eName; `registry/resolve` gives its eVault; the vault holds an envelope whose body carries a `selfDescription`. Verified live: a working platform kept its under the `User` schema on its platform-profile envelope, and a foreign agent read it end to end with no prior arrangement.
2. **At `https://<your-domain>/.well-known/w3ds-platform.json`**, mirroring it.

The vault copy is the W3DS-native path and works for an agent that only knows your eName. The well-known URL is the cheap path for anything with an HTTP client. Publish both; keep them identical.

Discoverability note worth the effort: because the public ontology registry is the **narrowest** circle of types that exist (see `reading-existing-data.md`), your self-description is often the only place an unregistered type you rely on becomes legible to anyone else. A platform that declares its ontologies makes them learnable even before the registration PR merges.

## What to declare

### Your ontologies, with reading instructions

For each type you write: its `schemaId`, its label, and the things a reader cannot guess —

- **`labelField`** — which field names the record for a human. Without it a reader falls back to `name → title → kind` and produces a list of thirty records all called `Company`.
- **`textFields`** — which fields carry prose worth indexing or searching.
- **`relations`** — which fields point at other records, and at what.
- **the cross-vault reference model** — whether the type appears as a canonical record plus references in other tenants, and which field names the canonical. A reference without `canonicalOwnerEName` names a record nobody can reach.

Declare the field shape fully enough to be copied. A reader building an interoperating record will copy your declaration field for field; a half-declaration produces a record that validates and does not render.

### The mechanics a reader would otherwise infer wrongly

This is the highest-value part of a self-description, and the part most often left out.

A working example: one platform's self-description stated that its `Message` type is **single-source on the author's vault and not mirrored**, that `Chat` *is* referenced onto each member's vault while `Message` is **not**, and that its client reads a skeleton index on its own server rather than traversing participants' vaults.

Every one of those is invisible in the data, and each one, guessed wrong, produces a confident wrong conclusion about the protocol. Two separate wrong answers about protocol mechanics were traced to reading derived tables instead of that declaration — which is why the authority order is **told > declared > measured > inferred**, and why a platform's declaration about its own behaviour outranks anyone's measurement of it.

If a record of yours needs a companion write to become visible, if a field is a version marker, if an identifier is local to your vault, if your index only refreshes on a cold open — say so. Nobody can measure their way to it, and several will try.

### What you can open and create — the `handles` convention

Declare the verbs you offer in a `handles` array on the platform-profile envelope, mirrored in the well-known file:

```json
{ "ontology": "<schemaId>", "label": "Task", "can": ["open", "create"],
  "openUrl": "https://…/open/{w3id}", "createUrl": "https://…/new" }
```

- `https` only; `{w3id}` percent-encoded.
- **Declaring nothing is correct** when you have no such route. Never declare an unbuilt verb — a declaration is a promise a stranger's agent will act on.
- **`platformName` is the join key.** Match it case-insensitively, and never join on repository, domain or product name instead: a wrong expected name produces a silent miss, not an error.

Publish the fields readers actually read. A reader looking for `avatarUrl` will not find `logoUrl`, and the platform simply appears to have no icon.

## Reader side

- **A declaration is an offer, never permission.** That a platform says it can open a type does not authorise sending anything to it.
- **Never auto-open a newly discovered handler.** The person's choice belongs on *their* vault — `PreferredHandlers` (`9a1b2c3d-4e5f-4a60-8b1c-1122334455ff`, ACL `[self]`) — not in your configuration, and it travels with them to the next application.
- **Drop malformed and non-`https` claims** rather than repairing them.
- Order offers by actual use, not by declaration order.
- **A declared schema is not a validator.** Registration and declaration both constrain nothing at write time, so read live records and follow those where they disagree with the declaration — and report the disagreement, because it is a finding about one side or the other.
- Learn a type **once** and reuse it. A self-description parsed into a reusable field map serves every user of your system, not just the one whose question triggered it.

## The failure this prevents, and the failure it does not

It prevents: a reader reconstructing your semantics from what your records happen to look like in their projection, bugs included.

It does not prevent: a reader's **parser** dropping half of what you declared. That happened to us — a platform's self-description carried its full chat model, cross-vault references and merge semantics, and our parser kept only the flat field list. The declaration was right and complete; we read it badly and then reasoned from the remains.

So, on both sides: after parsing a self-description, print what you extracted and compare it against the source. A silently truncated declaration is indistinguishable from a thin one.

## Checklist

For a platform publishing:

- [ ] Self-description on the platform's own vault **and** at `/.well-known/w3ds-platform.json`, identical.
- [ ] Every ontology written carries `schemaId`, `labelField`, `textFields`, `relations`, and its cross-vault reference model.
- [ ] Mechanics a reader cannot measure are stated: what is mirrored, what is referenced, what is single-source, which field is a version, which identifiers are vault-local.
- [ ] `handles` declares only verbs that are built, `https` only, with `platformName` as the join key.
- [ ] Unregistered types your platform depends on are declared here even though they are not in the registry.

For an agent reading one:

- [ ] The declaration was read **before** reasoning from any derived table.
- [ ] What was extracted from it was printed and checked against the source.
- [ ] Authorship of any envelope is labelled inferred, with the matching rule stated, or left unknown.
- [ ] Handler offers are not acted on without the person's choice, and that choice is stored on their vault.
