{
  "platform": "meshenger",
  "platformEName": "@398892d9-2de3-5bab-b9ea-010081ba7d86",
  "platformProfileEnvelopeId": "15385d97-6879-52fc-b466-d496ffbc194b",
  "description": "W3DS-native messenger. Canonical content lives on eVaults; participants get reference envelopes that point back to canonical records. People are identified by @eName; some legacy fields carry a User.metaEnvelopeId (bare UUID) instead — resolve to @eName via the User envelope. Access is governed by ACL, not by which vault an envelope sits on.",
  "marketplace": {
    "displayName": "Meshenger",
    "description": "W3DS-native messenger — chats, group chats, voice & video calls, voice/video messages, polls and a shared calendar. Your messages and media live on your own eVault and stay portable across W3DS apps; nothing is locked to one platform.",
    "category": "Communication",
    "logoUrl": "https://meshenger.postplatforms.com/icon-512.png",
    "url": "https://meshenger.postplatforms.com",
    "note": "Marketplace card fields (W3DS marketplace reads platform profiles from AaaS /api/packets on the User ontology, keyed by platformName). logoUrl MUST be an absolute https URL — it is injected verbatim into <img src>. Published onto the PlatformProfile envelope by /api/debug/publish-self-description."
  },
  "humanDoc": "docs/meshenger-self-description.md (Meshenger repo)",
  "identity": {
    "personId": "@eName",
    "note": "participantIds and senderId may carry a User.metaEnvelopeId (bare UUID); resolve to the person's @eName via their User envelope."
  },
  "ontologies": [
    {
      "schemaId": "550e8400-e29b-41d4-a716-446655440000",
      "label": "User",
      "what": "A person's account/profile.",
      "kind": "canonical",
      "labelField": "displayName",
      "textFields": [
        "displayName",
        "username",
        "bio",
        "location",
        "website",
        "givenName",
        "familyName"
      ],
      "relations": [],
      "registered": true,
      "registrationCheckedAt": "2026-08-06"
    },
    {
      "schemaId": "550e8400-e29b-41d4-a716-446655440003",
      "label": "Chat",
      "what": "A direct or group conversation (metadata only; messages are separate).",
      "kind": "canonical",
      "labelField": "name",
      "textFields": [
        "name"
      ],
      "relations": [
        {
          "field": "participantIds",
          "predicate": "has_participant",
          "target": "person"
        },
        {
          "field": "lastMessageId",
          "predicate": "latest_message",
          "target": "message"
        }
      ],
      "reference": {
        "ownerField": "canonicalOwnerEName",
        "envelopeIdField": "canonicalChatId",
        "discriminator": "isReference",
        "note": "Canonical on creator's vault (direct) or the group's vault (group). Group roster/roles live in GroupManifest on the group vault."
      },
      "merge": {
        "what": "Two canonical Chat envelopes can describe the SAME conversation — each side's app created its own before discovering the other's. Additive markers declare the merge WITHOUT destroying either envelope, so apps that don't read them keep working unchanged.",
        "survivorField": "mergedInto",
        "absorbedField": "mergedFrom",
        "shape": "mergedInto: {chatId, ownerEName} on the absorbed chat; mergedFrom: [{chatId, ownerEName}] on the survivor. Both keep their full body and their messages.",
        "note": "A reader that understands these SHOULD present one conversation and union the messages of all merged chatIds. The survivor is authoritative for name/avatar; new messages should be written to the survivor. Markers are symmetric and idempotent — write both sides. Never delete or blank the absorbed envelope: message.chatId still points at it."
      },
      "registered": true,
      "registrationCheckedAt": "2026-08-06"
    },
    {
      "schemaId": "550e8400-e29b-41d4-a716-446655440004",
      "label": "Message",
      "what": "A single message in a chat. Single-source on the author's vault; not mirrored. Optional `provenance` {source, externalChatId, externalMessageId, originalTimestamp} marks a message back-filled from another messenger (e.g. imported from Telegram): back-dated to its original send time and authored by the importer, so cross-app clients can show its origin and dedup re-imports. Source-agnostic extension; other apps ignore it. Optional `richTextElements` carries Telegram/Slack-style stand-off formatting: an array of `{type, offset, length, url?, language?}` ranges over `content` (bold|italic|underline|strikethrough|spoiler|code|pre|blockquote|text_link). Offsets/lengths are UTF-16 code units into `content`. Additive + ignorable: `content` stays clean plain text, so an app that doesn't parse richTextElements shows the text unchanged. Named generically (not Telegram's `entities`) so the same field carries imported formatting from Telegram/Slack/WhatsApp. Source-agnostic mixin. Media and threading fields, all OPTIONAL and additive — a reader that ignores them still shows a correct message: `replyTo` is the metaEnvelopeId of the message this one answers (a plain id, not a copy — the quoted text is read from that envelope under the reader's own ACL, so a reply never duplicates content it may not be allowed to show). `fileId` is a `w3ds://file?id=...` URI for an attachment (image / video / voice / document); `durationSec` is the playable length in seconds of that attachment, written by the sender from the recorded clip, and on a `type:\"call\"` message it is the call's talk time. `transcriptStatus` (\"pending\" | \"done\" | \"error\") and `transcriptUri` carry the speech-to-text of a voice message or round video: the sender writes \"pending\" at send time, and when transcription finishes the TEXT is stored as a `.txt` File on the SAME vault as the message and `transcriptUri` points at it — the transcript is never inlined into the message body and never stored on any platform's server. `titleLine` is a short human title derived from that transcript (what a voice note is about), so a list can label a clip without playing it. A reader that wants the transcript dereferences `transcriptUri` like any other File; one that does not simply shows the clip.",
      "kind": "canonical",
      "labelField": null,
      "textFields": [
        "content"
      ],
      "textFieldAliases": {
        "content": [
          "text"
        ]
      },
      "relations": [
        {
          "field": "senderId",
          "predicate": "authored_by",
          "target": "person"
        },
        {
          "field": "chatId",
          "predicate": "in_chat",
          "target": "chat"
        },
        {
          "field": "fileId",
          "predicate": "attachment",
          "target": "file"
        },
        {
          "field": "readBy",
          "predicate": "read_by",
          "target": "person"
        },
        {
          "field": "replyTo",
          "predicate": "in_reply_to",
          "target": "message"
        },
        {
          "field": "transcriptUri",
          "predicate": "transcript_of",
          "target": "file"
        }
      ],
      "optionalFields": [
        {
          "field": "replyTo",
          "type": "string",
          "what": "metaEnvelopeId of the message being answered."
        },
        {
          "field": "fileId",
          "type": "string",
          "what": "w3ds://file?id=... of the attachment."
        },
        {
          "field": "durationSec",
          "type": "number",
          "what": "Playable length of the attachment in seconds; talk time on a call message."
        },
        {
          "field": "transcriptStatus",
          "type": "string",
          "what": "pending | done | error — speech-to-text state for a voice message or round video."
        },
        {
          "field": "transcriptUri",
          "type": "string",
          "what": "w3ds://file?id=... of the .txt transcript, on the same vault as the message."
        },
        {
          "field": "titleLine",
          "type": "string",
          "what": "Short title derived from the transcript, so a clip can be labelled without playing it."
        },
        {
          "field": "richTextElements",
          "type": "array",
          "what": "Stand-off formatting ranges over `content` (see above)."
        },
        {
          "field": "provenance",
          "type": "object",
          "what": "Set only on a message back-filled from another messenger (see above)."
        }
      ],
      "registered": true,
      "registrationCheckedAt": "2026-08-06"
    },
    {
      "schemaId": "a8bfb7cf-3200-4b25-9ea9-ee41100f212e",
      "label": "GroupManifest",
      "what": "A group's name, roster and roles. Source of truth for owner/admins (not the Chat ACL).",
      "kind": "canonical",
      "labelField": "name",
      "textFields": [
        "name",
        "description",
        "charter"
      ],
      "relations": [
        {
          "field": "owner",
          "predicate": "owned_by",
          "target": "person"
        },
        {
          "field": "admins",
          "predicate": "administered_by",
          "target": "person"
        },
        {
          "field": "members",
          "predicate": "has_member",
          "target": "person"
        },
        {
          "field": "eName",
          "predicate": "group_vault",
          "target": "group"
        }
      ],
      "registered": true,
      "registrationCheckedAt": "2026-08-06"
    },
    {
      "schemaId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "label": "File",
      "what": "An attachment. Bytes live off-envelope; the envelope is metadata.",
      "kind": "canonical",
      "labelField": "name",
      "textFields": [
        "name",
        "description",
        "displayName"
      ],
      "fieldAliases": {
        "name": [
          "filename"
        ],
        "mimeType": [
          "contentType"
        ],
        "url": [
          "publicUrl"
        ]
      },
      "relations": [
        {
          "field": "ownerId",
          "predicate": "owned_by",
          "target": "person"
        }
      ],
      "reference": {
        "ownerField": "canonicalOwnerEName",
        "envelopeIdField": "canonicalFileId",
        "discriminator": "isReference",
        "extraFields": {
          "sharedBy": "person",
          "sharedAt": "timestamp"
        }
      },
      "registered": true,
      "registrationCheckedAt": "2026-08-06"
    },
    {
      "schemaId": "b65218b4-62fb-44ea-b2f6-ffb34d83a629",
      "label": "FileContext",
      "what": "An m:n join edge linking a File to a context that uses/describes it. Identifiers only, no text.",
      "kind": "canonical",
      "labelField": null,
      "textFields": [],
      "relations": [
        {
          "field": "fileRef",
          "predicate": "file",
          "target": "file"
        },
        {
          "field": "contextRef",
          "predicate": "context",
          "target": "envelope"
        },
        {
          "field": "kind",
          "predicate": "relationship_kind",
          "target": "enum"
        }
      ],
      "registered": false,
      "registrationCheckedAt": "2026-08-06",
      "registrationNote": "registration pending: MetaState-Prototype-Project/prototype PR #1103"
    },
    {
      "schemaId": "550e8400-e29b-41d4-a716-446655440001",
      "label": "SocialMediaPost",
      "what": "A post in Meshenger's channels/feed.",
      "kind": "canonical",
      "labelField": null,
      "textFields": [
        "content",
        "hashtags"
      ],
      "relations": [
        {
          "field": "authorId",
          "predicate": "authored_by",
          "target": "person"
        },
        {
          "field": "parentPostId",
          "predicate": "in_reply_to",
          "target": "post"
        },
        {
          "field": "likedBy",
          "predicate": "liked_by",
          "target": "person"
        }
      ],
      "registered": true,
      "registrationCheckedAt": "2026-08-06"
    },
    {
      "schemaId": "880e8400-e29b-41d4-a716-446655440099",
      "label": "CalendarEvent",
      "what": "A calendar event (also used for chat 'event' messages).",
      "kind": "canonical",
      "labelField": "title",
      "textFields": [
        "title",
        "description",
        "location.label"
      ],
      "relations": [],
      "registered": true,
      "registrationCheckedAt": "2026-08-06"
    },
    {
      "schemaId": "f9ff8527-f3af-4733-b8df-fa0c97ba03ff",
      "label": "Relation",
      "what": "A reified RDF triple (subject, predicate, object) — one ontology for every reaction/edge (like, react, reply, forward, transfer, assign, pin, ...). The verb carries the meaning. Lives on the author's vault. Exception: a SHARED message pin (predicate=pin, value=all) lives on the GROUP's vault with subject=group and ACL=participants, so every member sees the same pin; a personal pin (value=me) lives on the pinner's own vault.",
      "kind": "canonical",
      "labelField": null,
      "textFields": [],
      "relations": [
        {
          "field": "subject",
          "predicate": "actor_or_origin",
          "target": "person_or_envelope"
        },
        {
          "field": "predicate",
          "predicate": "verb",
          "target": "verb"
        },
        {
          "field": "object",
          "predicate": "target",
          "target": "person_or_envelope"
        },
        {
          "field": "authorId",
          "predicate": "asserted_by",
          "target": "person"
        },
        {
          "field": "value",
          "predicate": "scalar_parameter",
          "target": "scalar"
        }
      ],
      "registered": false,
      "registrationCheckedAt": "2026-08-06",
      "registrationNote": "registration pending: MetaState-Prototype-Project/prototype PR #1103"
    },
    {
      "schemaId": "6fda64db-fd14-4fa2-bd38-77d2e5e6136d",
      "label": "Account",
      "what": "A currency balance record per (holder, currency), on the holder's vault.",
      "kind": "canonical",
      "labelField": "currencyName",
      "textFields": [
        "currencyName"
      ],
      "relations": [
        {
          "field": "accountEname",
          "predicate": "held_by",
          "target": "person"
        },
        {
          "field": "currencyEname",
          "predicate": "denominated_in",
          "target": "currency"
        }
      ],
      "registered": true,
      "registrationCheckedAt": "2026-08-06"
    },
    {
      "schemaId": "550e8400-e29b-41d4-a716-446655440006",
      "label": "Ledger",
      "what": "An append-only, hash-chained ledger entry. A transfer is a debit on the sender's vault + a credit on the receiver's.",
      "kind": "canonical",
      "labelField": null,
      "textFields": [
        "description"
      ],
      "relations": [
        {
          "field": "accountId",
          "predicate": "entry_for_account",
          "target": "account"
        },
        {
          "field": "senderAccountId",
          "predicate": "from_account",
          "target": "account"
        },
        {
          "field": "receiverAccountId",
          "predicate": "to_account",
          "target": "account"
        },
        {
          "field": "currencyId",
          "predicate": "denominated_in",
          "target": "currency"
        }
      ],
      "registered": true,
      "registrationCheckedAt": "2026-08-06"
    },
    {
      "schemaId": "5b1a2c3d-6e7f-4a80-9b1c-2d3e4f5a6b7c",
      "label": "Summary",
      "what": "A generic AI summary of some subject (a chat day, a call, later a meeting transcript or email thread), with summaryLine, prose summary, action items and suggested calendar events. Points back at what it summarises via `subject`. Computed once and stored on the subject's canonical vault (canonical + reference-mixin, never a copy); each reader translates it to their own language on display. A chat-day Summary is the AUTHORITATIVE reconciler over that day: it merges the day's chat messages with the day's call proposals into ONE action-item list — reusing each call proposal's taskId (never duplicating it), tagging each item with source (call|chat|call+chat), marking status (active|cancelled) when a proposal was dropped, recording changeNote when a proposal was amended, and derivedFrom listing the call proposal ids it supersedes. Carries `provenance` {app, generatedAt, auto} recording WHICH app produced the summary and whether a scheduler (auto) or a reader made it — so another post-platform can see a digest exists and still offer its own version. Meshenger generates chat-day digests automatically once a day per chat (server scheduler), not only on demand. For a CALL summary (`subject.kind`=`call`) the envelope also carries the real call timing — `startedAt` (when the call began), `endedAt`, `durationSeconds` — which are DISTINCT from `generatedAt` (when the AI produced the summary); read those, not generatedAt, for 'when/how long was the call'. The same startedAt/endedAt/durationSeconds are mirrored at the top level of the call-history `Message` (`type`=`call`), alongside its nested `call.*`.",
      "kind": "canonical",
      "labelField": "summaryLine",
      "textFields": [
        "summaryLine",
        "summary",
        "actionItems.task",
        "events.title",
        "events.note"
      ],
      "relations": [
        {
          "field": "subject.chatId",
          "predicate": "summarises_chat",
          "target": "chat"
        },
        {
          "field": "subject.ref",
          "predicate": "summarises",
          "target": "envelope"
        },
        {
          "field": "generatedBy",
          "predicate": "generated_by",
          "target": "person"
        }
      ],
      "reference": {
        "ownerField": "canonicalOwnerEName",
        "envelopeIdField": "canonicalSummaryId",
        "discriminator": "isReference",
        "note": "Canonical on the subject's vault (the chat's vault for chat/call summaries). subject.kind ∈ {chat-day, call, ...}."
      },
      "registered": false,
      "registrationCheckedAt": "2026-08-06",
      "registrationNote": "registration pending: MetaState-Prototype-Project/prototype PR #1103"
    },
    {
      "schemaId": "0f9a3cb8-4a9f-4b5f-a1fa-3a4c2eb1f402",
      "label": "Company",
      "what": "A company/organisation a vault belongs to. Shared ontology (not app-specific); Meshenger reads it to group org chats. Name/members live in the GroupManifest on the same vault.",
      "kind": "canonical",
      "labelField": "name",
      "textFields": [
        "name",
        "description"
      ],
      "relations": [],
      "registered": true,
      "registrationCheckedAt": "2026-08-06"
    },
    {
      "schemaId": "0f9a3cb8-4a9f-4b5f-a1fa-3a4c2eb1f403",
      "label": "Project",
      "what": "A project a vault belongs to. Shared ontology (not app-specific); Meshenger reads it to group project chats. Name/members live in the GroupManifest on the same vault.",
      "kind": "canonical",
      "labelField": "name",
      "textFields": [
        "name",
        "description"
      ],
      "relations": [],
      "registered": true,
      "registrationCheckedAt": "2026-08-06"
    },
    {
      "schemaId": "0f9a3cb8-4a9f-4b5f-a1fa-3a4c2eb1f401",
      "label": "Task",
      "what": "A task / action item. Shared ontology first defined by the 80hours task-manager; Meshenger WRITES tasks extracted from call/chat digests here so any task-manager can ingest them. Fields left empty when unknown (project/assignee); ACL = the source chat's participants so anyone can complete the missing fields.",
      "kind": "consumed",
      "labelField": "title",
      "textFields": [
        "title",
        "description"
      ],
      "relations": [
        {
          "field": "assignees",
          "predicate": "assigned_to",
          "target": "person"
        },
        {
          "field": "createdBy",
          "predicate": "created_by",
          "target": "person"
        },
        {
          "field": "homeProjectId",
          "predicate": "in_project",
          "target": "project"
        },
        {
          "field": "relatedCompanyIds",
          "predicate": "for_company",
          "target": "company"
        }
      ],
      "note": "Meshenger writes drafts (status:'planned', intakeStatus:'draft', stable id, source.app='Meshenger') and follows relocation references to the enriched canonical task. See docs/interop-task-drafts-relocation.md.",
      "registered": true,
      "registrationCheckedAt": "2026-08-06"
    },
    {
      "schemaId": "9a1b2c3d-4e5f-4a60-8b1c-1122334455ff",
      "label": "PreferredHandlers",
      "what": "A user's PRIVATE default-apps table: which app opens which ontology, on their own vault (ACL=[self]). `{ handlers: { [ontologySchemaId]: { openUrl, platform } } }`. Meshenger reads it to deep-link a Task to the user's chosen task manager; other apps may reuse the same record for their own open-in-app.",
      "kind": "canonical",
      "labelField": null,
      "textFields": [],
      "relations": [],
      "registered": false,
      "registrationCheckedAt": "2026-08-06",
      "registrationNote": "registration pending: MetaState-Prototype-Project/prototype PR #1103"
    },
    {
      "schemaId": "eb69c8ad-fec0-4b74-8727-3acd17f34f1b",
      "label": "ChatPreference",
      "what": "A user's PRIVATE per-chat viewing preferences (mute, archive, pin), ONE envelope per (self × chat) on their own vault, ACL=[self]. `{ id, chatId, muted?, archived?, pinned?, pinnedAt?, updatedAt }`. Deliberately SEPARATE from the chat reference envelope — a reference must stay a pure pointer with no semantic payload — so these mutable per-user flags get their own ontology. Booleans are explicit once written (unarchive writes archived:false, never a delete); no envelope means never-toggled. eVault is the source of truth, the app's local cache is a read-through. Any post-platform showing the same chats can honour or set the user's pin/mute/archive.",
      "kind": "canonical",
      "labelField": null,
      "textFields": [],
      "relations": [
        {
          "field": "chatId",
          "predicate": "preferences_for_chat",
          "target": "chat"
        }
      ],
      "registered": false,
      "registrationCheckedAt": "2026-08-06",
      "registrationNote": "registration pending: MetaState-Prototype-Project/prototype PR #1103"
    },
    {
      "schemaId": "55606983-2744-4d77-9200-b609029a4b3d",
      "label": "DraftMessage",
      "what": "A user's UNSENT composer state for a chat — the cross-device/cross-app draft sync layer. ONE envelope per (self × chat) on the user's OWN vault, ACL=[self]. `{ id, chatId, authorEName, text, deviceId?, replyToId?, editingId?, updatedAt }`. Carries compose CONTEXT, not just text: `replyToId` = the draft is a reply to that message; `editingId` = the draft is an in-progress EDIT of that message (text holds the edited content); the two are mutually exclusive. Updated in place (debounced) as the user types; cleared to text:\"\" on send (the envelope is reused, not deleted). `text` is unsent message BODY, so it lives only on the eVault + the client and is routed transiently by the server (never persisted server-side, never broadcast in a cross-vault event). Sync across a user's devices is last-writer-wins by updatedAt, with deviceId to ignore self-echo. Another post-platform can read/write it to show the same in-progress draft (with its reply/edit context) in its own composer.",
      "kind": "canonical",
      "labelField": null,
      "textFields": [
        "text"
      ],
      "relations": [
        {
          "field": "chatId",
          "predicate": "draft_for_chat",
          "target": "chat"
        },
        {
          "field": "authorEName",
          "predicate": "authored_by",
          "target": "person"
        },
        {
          "field": "replyToId",
          "predicate": "reply_to_message",
          "target": "message"
        },
        {
          "field": "editingId",
          "predicate": "edit_of_message",
          "target": "message"
        }
      ],
      "registered": false,
      "registrationCheckedAt": "2026-08-06",
      "registrationNote": "registration pending: MetaState-Prototype-Project/prototype PR #1103"
    },
    {
      "schemaId": "0f9a3cb8-4a9f-4b5f-a1fa-3a4c2eb1f405",
      "label": "TaskReference",
      "what": "A content-free reference to a canonical Task on another vault (participant reference OR relocation redirect). Shared with 80hours. Resolve under the reader's ACL to the final canonical Task; never dev-key-bypass.",
      "kind": "consumed",
      "labelField": null,
      "textFields": [],
      "relations": [
        {
          "field": "canonicalTaskId",
          "predicate": "references_task",
          "target": "task"
        },
        {
          "field": "canonicalOwnerEName",
          "predicate": "canonical_on",
          "target": "person_or_group"
        }
      ],
      "reference": {
        "ownerField": "canonicalOwnerEName",
        "envelopeIdField": "canonicalEnvelopeId",
        "discriminator": "isReference",
        "note": "Follow to canonicalOwnerEName/canonicalEnvelopeId; verify resolved body.id === canonicalTaskId; may chain (relocation), depth ≤ 5, detect loops."
      },
      "registered": true,
      "registrationCheckedAt": "2026-08-06"
    },
    {
      "schemaId": "e815ba40-ef85-4a2b-b6cf-e05a86d4afbd",
      "label": "CallSession",
      "what": "A single audio/video call in a chat, transport-agnostic (full-mesh WebRTC or a LiveKit SFU). Generalises the ad-hoc call Message so a call and its recording are a first-class, cross-app ontology: any post-platform can read that a call happened, who was in it, and where the recording+transcript live. Lives on the chat's canonical vault (group vault for a group call), ACL = participants, canonical + reference-mixin — never a copy. Body is references only (rule #8): media/transcript are w3ds:// file URIs on the eVault, never bytes on the server. `transport` records mesh|livekit and `recording.mode` records egress|client so the artifact's provenance is auditable and fallback is clean.",
      "kind": "canonical",
      "labelField": null,
      "textFields": [],
      "relations": [
        {
          "field": "chatId",
          "predicate": "in_chat",
          "target": "chat"
        },
        {
          "field": "initiator",
          "predicate": "initiated_by",
          "target": "person"
        },
        {
          "field": "participants",
          "predicate": "has_participant",
          "target": "person"
        },
        {
          "field": "recording.recordedBy",
          "predicate": "recorded_by",
          "target": "person"
        },
        {
          "field": "recording.tracks",
          "predicate": "has_track",
          "target": "file"
        }
      ],
      "reference": {
        "ownerField": "canonicalOwnerEName",
        "envelopeIdField": "canonicalEnvelopeId",
        "discriminator": "isReference",
        "note": "Canonical on the chat's vault (group vault for a group call)."
      },
      "registered": false,
      "registrationCheckedAt": "2026-08-06",
      "registrationNote": "declared ahead of implementation; deliberately unregistered until code writes it"
    },
    {
      "schemaId": "ac710cad-4a10-4b0a-8c3d-1a2b3c4d5e6f",
      "label": "ActionCard",
      "what": "An interactive card any W3DS platform posts into a chat: a titled/body'd item with buttons that POST back to the authoring app (a voting app, a reminder service, a treasury — usually NOT a messenger). Meshenger is a dumb renderer: it styles by `category` (important|info|question|action|event), pins it to a top-of-app overlay when `isPinned`, and on a button tap POSTs to the button's `link` via a signed cookie-less proxy, then applies whatever comes back (`text` replaces the body, `buttons` replaces the buttons) recursively. Lives on the chat's vault, ACL = participants. Body is content (title/body/buttons) → eVault + client cache only; the server keeps a skeleton (id, chatId, author, isPinned, createdAt) per rule #8. Per-user responses are NOT stored back into this card — they are separate `Relation` envelopes (predicate `respond`, subject=responder, object=card uri, value=chosen button, qualifier={text,buttons}), authored by the responder, so any messenger can read who answered and an admin can tally a poll.",
      "kind": "canonical",
      "labelField": "title",
      "textFields": [
        "title",
        "body"
      ],
      "relations": [
        {
          "field": "chatId",
          "predicate": "in_chat",
          "target": "chat"
        },
        {
          "field": "authorEName",
          "predicate": "authored_by",
          "target": "person"
        }
      ],
      "responseRelation": {
        "ontology": "Relation",
        "predicate": "respond",
        "subject": "responder @eName",
        "object": "w3ds envelope uri of the ActionCard",
        "value": "the chosen button's id/value (group by this for a tally)",
        "qualifier": "{ text?, buttons? } — the app-returned view for that user, so state re-renders without calling the app"
      },
      "deprecation": "TRANSITIONAL. ActionCard bakes presentation into the data model, which is wrong for W3DS. It is being replaced by CommunityActivity (a domain object) + the `responseOptions` mixin on real ontologies (Reminder/CalendarEvent/Poll). ActionCard will become a thin adapter onto CommunityActivity and then be removed.",
      "registered": false,
      "registrationCheckedAt": "2026-08-06",
      "registrationNote": "scheduled for removal in favour of CommunityActivity; deliberately unregistered"
    },
    {
      "schemaId": "c0117a17-1b2c-4d3e-8f4a-5b6c7d8e9f01",
      "label": "CommunityActivity",
      "what": "A generic, DOMAIN-level 'a platform posted something to a community that members see and may respond to' — the successor to ActionCard, deliberately NOT a presentation container. `activityType` follows W3C ActivityStreams (announce|question|offer|invite|acknowledge) and carries the meaning; the renderer decides the look. Prefer a more specific ontology when one fits (CalendarEvent, Reminder, Poll); CommunityActivity is the general case AND the fallback a renderer uses for any envelope it doesn't specifically know but that carries `summary` + `responseOptions`. Lives on the chat's vault, ACL = participants. `summary`/`description` carry the text (domain fields, not `title`/`body`); `relatedSubject` optionally points at the domain object it's about; `responseOptions` (shared mixin) are optional choices; `display` holds render HINTS renderers may ignore. Responses are `Relation` envelopes (predicate `respond`), authored by the responder — never written back here. When `activityType`=`question` (a POLL) it may also carry: `mode` (single|multiple — a multiple-choice vote is ONE respond Relation whose `value` is a CSV of chosen option values), `deadline` (ISO; voting closes and results open after it), and `resultsPublic` (bool). Result visibility: with a deadline, the distribution + voters are hidden until it passes; without one, until the reader has voted.",
      "kind": "canonical",
      "labelField": "summary",
      "textFields": [
        "summary",
        "description"
      ],
      "relations": [
        {
          "field": "chatId",
          "predicate": "in_chat",
          "target": "chat"
        },
        {
          "field": "authorEName",
          "predicate": "authored_by",
          "target": "person"
        },
        {
          "field": "relatedSubject.id",
          "predicate": "about",
          "target": "any"
        }
      ],
      "mixins": [
        "responseOptions"
      ],
      "responseRelation": {
        "ontology": "Relation",
        "predicate": "respond",
        "subject": "responder @eName",
        "object": "w3ds envelope uri of the activity",
        "value": "the chosen responseOptions[].value/id (group by this for a tally)"
      },
      "registered": true,
      "registrationCheckedAt": "2026-08-06"
    },
    {
      "schemaId": "0f9a3cb8-4a9f-4b5f-a1fa-3a4c2eb1f412",
      "label": "Reminder",
      "what": "REUSED from the shared W3DS dictionary (authored by task managers like 80hours to remind about a Task; also usable for any subject via `relatedSubject`). Meshenger renders it in-chat and honours an OPTIONAL `responseOptions` mixin so a reminder can offer choices (e.g. Done / Snooze) recorded as a `respond` Relation — beyond its native `status` (scheduled|acknowledged|dismissed|cancelled). We do NOT own this schema; the `responseOptions` addition is proposed to its home repo as an optional field (its schema already allows additionalProperties).",
      "kind": "reference-consumer",
      "labelField": "summary",
      "textFields": [
        "summary",
        "description"
      ],
      "relations": [
        {
          "field": "recipientEName",
          "predicate": "reminds",
          "target": "person"
        },
        {
          "field": "relatedSubject.id",
          "predicate": "about",
          "target": "any"
        }
      ],
      "mixins": [
        "responseOptions"
      ],
      "registered": false,
      "registrationCheckedAt": "2026-08-06",
      "registrationNote": "id belongs to the 80hours task family; registration is theirs to make"
    },
    {
      "schemaId": "550e8400-e29b-41d4-a716-446655440005",
      "label": "Bookmark",
      "what": "A saved post. Ecosystem-registered (Blabsy-family base set).",
      "kind": "reused",
      "origin": "Registered with the W3DS Ontology service by the ecosystem, NOT minted by Meshenger. Verified present at https://ontology.w3ds.metastate.foundation/schemas/<schemaId> on 2026-07-31.",
      "meshengerUse": "declared for interop; not written by Meshenger today",
      "registered": true,
      "registrationCheckedAt": "2026-08-06"
    },
    {
      "schemaId": "550e8400-e29b-41d4-a716-446655440008",
      "label": "Currency",
      "what": "A group's internal currency. Meshenger READS it to show a group's currency; never writes.",
      "kind": "reused",
      "origin": "Registered with the W3DS Ontology service by the ecosystem, NOT minted by Meshenger. Verified present at https://ontology.w3ds.metastate.foundation/schemas/<schemaId> on 2026-07-31.",
      "meshengerUse": "read-only",
      "registered": true,
      "registrationCheckedAt": "2026-08-06"
    },
    {
      "schemaId": "660e8400-e29b-41d4-a716-446655440100",
      "label": "Poll",
      "what": "Standalone poll. Declared for cross-app reading; Meshenger's own in-chat polls use CommunityActivity.",
      "kind": "reused",
      "origin": "Registered with the W3DS Ontology service by the ecosystem, NOT minted by Meshenger. Verified present at https://ontology.w3ds.metastate.foundation/schemas/<schemaId> on 2026-07-31.",
      "meshengerUse": "declared for interop; not written by Meshenger today",
      "registered": true,
      "registrationCheckedAt": "2026-08-06"
    },
    {
      "schemaId": "660e8400-e29b-41d4-a716-446655440101",
      "label": "Vote",
      "what": "A vote on a Poll. Same status as Poll.",
      "kind": "reused",
      "origin": "Registered with the W3DS Ontology service by the ecosystem, NOT minted by Meshenger. Verified present at https://ontology.w3ds.metastate.foundation/schemas/<schemaId> on 2026-07-31.",
      "meshengerUse": "declared for interop; not written by Meshenger today",
      "registered": true,
      "registrationCheckedAt": "2026-08-06"
    },
    {
      "schemaId": "c20e9437-02a4-4917-8cee-de35dabbdb6a",
      "label": "Reference",
      "what": "A signed reference/endorsement about a target.",
      "kind": "reused",
      "origin": "Registered with the W3DS Ontology service by the ecosystem, NOT minted by Meshenger. Verified present at https://ontology.w3ds.metastate.foundation/schemas/<schemaId> on 2026-07-31.",
      "meshengerUse": "declared for interop; not written by Meshenger today",
      "registered": true,
      "registrationCheckedAt": "2026-08-06"
    },
    {
      "schemaId": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
      "label": "Signature",
      "what": "A signature over a file hash.",
      "kind": "reused",
      "origin": "Registered with the W3DS Ontology service by the ecosystem, NOT minted by Meshenger. Verified present at https://ontology.w3ds.metastate.foundation/schemas/<schemaId> on 2026-07-31.",
      "meshengerUse": "declared for interop; not written by Meshenger today",
      "registered": true,
      "registrationCheckedAt": "2026-08-06"
    },
    {
      "schemaId": "1d83fada-581d-49b0-b6f5-1fe0766da34f",
      "label": "CharterSignature",
      "what": "A member's signature on a group charter.",
      "kind": "reused",
      "origin": "Registered with the W3DS Ontology service by the ecosystem, NOT minted by Meshenger. Verified present at https://ontology.w3ds.metastate.foundation/schemas/<schemaId> on 2026-07-31.",
      "meshengerUse": "declared for interop; not written by Meshenger today",
      "registered": true,
      "registrationCheckedAt": "2026-08-06"
    },
    {
      "schemaId": "47478875-c0cf-4b62-97b3-5b0b62d7f491",
      "label": "AvailabilityStatus",
      "what": "Whether a person may be disturbed right now. APPEND-ONLY: every change mints a NEW envelope on the owner's own vault and the current status is the one with the latest `since`, so the eVault itself is the permanent, public log of \"went Do-Not-Disturb at 14:12, came back at 16:40\". `{ statusId, ownerEName, state, since, until?, note?, autoReply?, updatedAt }`. `state` is an OPEN enum (available | dnd | busy | away): a reader meeting an unknown value MUST treat it as available — a message wrongly withheld is worse than one wrongly delivered. ACL is [\"*\"] (public) both because someone you have never chatted with should still learn not to disturb you, and because a private ACL grants read AND write to everyone listed. Meshenger renders the in-chat service line and the reply to an @-mention FROM this status plus the mentioning message; it never writes a per-chat copy of either, because only a vault's owner may mutate it and a derived record goes stale the moment they come back. MOOD is deliberately a separate axis and is not part of this ontology.",
      "kind": "canonical",
      "labelField": null,
      "textFields": [
        "note",
        "autoReply"
      ],
      "relations": [],
      "registered": false,
      "registrationCheckedAt": "2026-08-06",
      "registrationNote": "registration pending: MetaState-Prototype-Project/prototype PR #1103"
    }
  ],
  "notes": {
    "acl": "An envelope's ACL (@eNames, or ['*'] for public) governs who can read it, independent of which vault it lives on. ACL is write-only over the eVault GraphQL (not returned in reads); entries must be @eName form.",
    "privateChat": "PrivateChatDevice/Access/RecoveryRequest (94d31a7f-...) are E2E key-management artifacts, not user content — not intended for cross-app interpretation."
  }
}