mentionable.dev

PlatformIdentityCredential — Mentionable v0.2

Status: Draft v0.2. Profile / capability URI: https://mentionable.dev/ns/identity/v0.2 JSON-LD context URL: https://mentionable.dev/ns/identity/v0.2/context.jsonld Supersedes for new issuance: the custom IdentityEvidence signed-attestation wire format (identity-evidence-v0.1.md, legacy URI https://mentionable.dev/ns/identity/v0.1). Last updated: 2026-09-03

This document defines the Mentionable profile of W3C Verifiable Credentials 2.0 used to carry recipient-bound, request-bound platform identity assertions from a Connector to a receiving agent. A PlatformIdentityCredential is a signed statement of fact (“this Slack workspace member sent this turn”, “this platform entity was observed mentioning this agent”), never an authorization or delegation by itself.

This profile replaces the custom envelope, signed-attestation proof, Connector Card key discovery, and new issuance under metadata.mentionable.identity_evidence described in identity-evidence-v0.1.md and issue #462. It preserves #462’s platform-observed invocation semantics and the PolicyPart/authorization boundary unchanged.


1. Purpose and design split

The v0.1 IdentityEvidence envelope defined its own issuer, subject, validity, audience, and proof structure, plus a bespoke RFC 8785 (JCS) + Ed25519 signature rule. All of that semantics already exists in maintained W3C standards:

v0.2 therefore keeps the Mentionable-specific parts — the claim vocabulary, canonicalization, carrier, trust policy, and TTL/replay profile — and delegates envelope and cryptography to the standards above.

The responsibility boundary from v0.1 is unchanged:

Core MUST NOT keep a closed enum of identity methods (§7).

2. Versioning and published resources

Three URIs with distinct roles. They MUST NOT be conflated:

URIRole
https://mentionable.dev/ns/identity/v0.2Profile / capability URI. Referenced by humans and advertised in Agent Cards (a2a.capabilities.extensions[]) to signal that the agent accepts the v0.2 carrier. Dereferences to this spec.
https://mentionable.dev/ns/identity/v0.2/context.jsonldJSON-LD context. A static, version-pinned document served as application/ld+json (GitHub Pages serves it by the .jsonld file extension; the canonical media type is application/ld+json). This — and only this — URL appears in credential @context.
https://mentionable.dev/ns/identity/v0.1Legacy IdentityEvidence extension URI. Deprecated for new issuance; recognized during the compatibility window (§12.3).

The HTML profile page is NEVER used as a JSON-LD @context. A credential’s @context MUST begin with exactly this ordered pair:

["https://www.w3.org/ns/credentials/v2", "https://mentionable.dev/ns/identity/v0.2/context.jsonld"]

Additional contexts MAY follow, but receivers only need to understand the two above.

3. Credential shape

A PlatformIdentityCredential is a VC 2.0 credential with:

There is no credentialStatus. This profile uses short TTLs instead of revocation infrastructure (§9.2).

3.1 Example — direct Slack user

Issued when the Slack Connector verified (via Slack request signing) that a workspace member sent the current turn:

{
  "@context": [
    "https://www.w3.org/ns/credentials/v2",
    "https://mentionable.dev/ns/identity/v0.2/context.jsonld"
  ],
  "id": "urn:uuid:0d6f5f1e-8f3a-4a2e-9d38-6a4c8d1a9b21",
  "type": ["VerifiableCredential", "PlatformIdentityCredential"],
  "issuer": "did:web:slack-connector.example.com",
  "validFrom": "2026-08-19T12:00:00Z",
  "validUntil": "2026-08-19T12:05:00Z",
  "credentialSubject": {
    "id": "slack:T0123456/U0456789",
    "method": "urn:mentionable:auth:slack-workspace-member:v0.1",
    "assurance": "platform",
    "platform": {
      "provider": "slack",
      "workspace_id": "T0123456"
    },
    "profile": {
      "display_name": "JC",
      "username": "jc",
      "avatar": { "url": "https://avatars.slack-edge.com/T0123456-U0456789-abc123" },
      "locale": "ko-KR",
      "timezone": "Asia/Seoul"
    },
    "source": {
      "connector": "slack-connector.example.com",
      "channel": "C0987654"
    }
  },
  "proof": {
    "type": "DataIntegrityProof",
    "cryptosuite": "eddsa-jcs-2022",
    "created": "2026-08-19T12:00:00Z",
    "verificationMethod": "did:web:slack-connector.example.com#key-2026-08",
    "proofPurpose": "assertionMethod",
    "domain": "@travel@agents.example.com",
    "challenge": "9f4b6c2e-1d3a-4c5b-8e7f-2a1b3c4d5e6f",
    "proofValue": "z4oey5q2M3XKaxup3tmzN4DRFTLVqpLMweBrSxMY2xHX5XTYVQeVbY8nQAVHMrXFkXJpmEcqdoDwLWxaqA3Q1geV6"
  }
}

3.2 Example — platform-observed relay

Issued when the Connector did not verify the ultimate human, but observed a platform entity (typically another bot/app user relaying on someone’s behalf) mention the target agent in a platform context. The immediate principal is the observed platform entity, not the upstream human:

{
  "@context": [
    "https://www.w3.org/ns/credentials/v2",
    "https://mentionable.dev/ns/identity/v0.2/context.jsonld"
  ],
  "id": "urn:uuid:7c2e9b40-51df-4f6a-b1a3-9e8d0c2f4a55",
  "type": ["VerifiableCredential", "PlatformIdentityCredential"],
  "issuer": "did:web:slack-connector.example.com",
  "validFrom": "2026-08-19T12:00:00Z",
  "validUntil": "2026-08-19T12:05:00Z",
  "credentialSubject": {
    "id": "slack:T0123456/U0BOTUSER",
    "method": "urn:mentionable:auth:platform-mention:v0.1",
    "assurance": "platform",
    "platform": {
      "provider": "slack",
      "workspace_id": "T0123456"
    },
    "observedInvocation": {
      "target": "acct:travel@agents.example.com",
      "channel": "C0987654",
      "thread": "1755603600.000200",
      "message": "1755603601.000300"
    },
    "source": {
      "connector": "slack-connector.example.com",
      "channel": "C0987654"
    }
  },
  "proof": {
    "type": "DataIntegrityProof",
    "cryptosuite": "eddsa-jcs-2022",
    "created": "2026-08-19T12:00:00Z",
    "verificationMethod": "did:web:slack-connector.example.com#key-2026-08",
    "proofPurpose": "assertionMethod",
    "domain": "@travel@agents.example.com",
    "challenge": "3a7d1e88-6b2c-4f90-a5d4-c1e2f3a4b5c6",
    "proofValue": "z3FXQoLXtCUainYmNyxvB9pYlmYVpJv7qApC8QdGKmwLXY6PjfMS3jTMEfEgLrX2wA5nQpVKd6xB1cRzHqUvT9y2m"
  }
}

3.3 credentialSubject

credentialSubject is always an assertion about the immediate principal — the entity the Connector actually verified or observed. It never asserts upstream/downstream authority (§8).

type PlatformIdentityCredentialSubject = {
  id: string // canonical principal URL (§5) — never a raw @local@domain string
  method: string // open method token (§7)
  assurance: string // e.g. 'platform'; interpreted with issuer + method
  platform?: {
    provider: string // e.g. 'slack'
    workspace_id?: string
    [key: string]: unknown
  }
  observedInvocation?: {
    target: string // canonical URL of the mentioned agent (e.g. acct:travel@agents.example.com)
    channel?: string
    thread?: string
    message?: string
  }
  profile?: SenderProfileLike // presentation-only facts (§8); shape follows normalized-message.md SenderProfile
  source?: {
    transport?: string
    transport_module?: string
    connector?: string
    channel?: string
    thread?: string
    [key: string]: unknown
  }
}

observedInvocation means: the issuer observed the principal identified by credentialSubject.id mention observedInvocation.target in the given platform context. It is an observation record, not a delegation.

Credentials MUST NOT contain secrets: no Slack tokens, no raw provider objects, no private URLs, and no personal data beyond the whitelisted presentation fields.

4. Identifier semantics

Three identifiers in a credential are distinct values with distinct roles. Implementations MUST NOT substitute one for another:

IdentifierMeaning
id (top-level)This credential / observation event. The reference issuer mints a private, non-dereferenceable urn:uuid:<uuid> per issuance.
credentialSubject.idThe canonical URL of the immediate principal the assertion is about (§5).
proof.challengeThe request-binding value: exactly the outer A2A message.messageId of the message carrying this credential (§9.1). Not an identity, not a nonce pool.

Additionally proof.domain is the recipient binding — the canonical receiving agent address — and is unrelated to all three above.

5. Principal canonicalization

credentialSubject.id (and observedInvocation.target) MUST be a URL. Canonicalization rules:

Principal kindCanonical formExample
Mentionable address @local@domainacct:local@domainacct:travel@agents.example.com
Slack userslack:<team>/<user>slack:T0123456/U0456789
Email identitymailto: URLmailto:alice@example.com
ActivityPub / WebFinger accountacct: URLacct:alice@example.social

A raw @local@domain string MUST NOT appear as credentialSubject.id. Internal normalized models that require the Mentionable display form MAY project acct:local@domain back to @local@domain; that projection is a presentation concern, never a wire concern.

Note the deliberate asymmetry: proof.domain uses the display form @local@domain (it is an audience string matched exactly against the receiver’s canonical address, mirroring v0.1 audience), while subject and target identifiers are URLs.

6. Mapping from IdentityEvidence v0.1

Semantic mapping from the legacy envelope. This is the normative translation table for migration tooling and mental models — not a bidirectional codec; new issuance uses the v0.2 shape natively.

Legacy IdentityEvidencePlatformIdentityCredential
idtop-level VC id
issuerVC issuer
subjectcanonicalized credentialSubject.id
issued_atvalidFrom
expires_atvalidUntil
methodcredentialSubject.method
assurancecredentialSubject.assurance
claims.platformcredentialSubject.platform
claims.mentioncredentialSubject.observedInvocation
claims.profilecredentialSubject.profile
claims.source_turn / sourcecredentialSubject.source
audienceData Integrity proof domain
outer A2A message.messageIdData Integrity proof challenge

7. Methods

The VC envelope is shared; identity method semantics are not merged. Two methods are defined by this profile:

MethodMeaning
urn:mentionable:auth:slack-workspace-member:v0.1Direct Slack user: the Connector verified via Slack request signing that this workspace member sent the turn.
urn:mentionable:auth:platform-mention:v0.1Platform-observed relay: the Connector observed the principal mention the target agent in a platform context.

The method set is open — core MUST NOT restrict it to a closed enum. New Connectors mint new method URIs without changing core. Unknown method values MAY be structurally preserved, but a method the receiver’s policy does not explicitly understand and allow MUST NOT be used as an authorization input.

8. Claim classes and the authorization boundary

A credential is a signed assertion of fact. It grants no authorization or delegation by itself. Only after signature, proof purpose, issuer trust, domain, challenge, validity, and replay checks all pass (§13) may it become an input to receiver-local policy.

Claims fall into three classes:

Presentation-only and diagnostic-only claims MUST NOT be used for payment, account linking, destructive actions, delegation, rate-limit identity, or allowlist keys.

Receivers MUST NOT auto-generate on_behalf_of (or any delegation chain) from a PlatformIdentityCredential. Downstream authority of the original platform user is never claimed without a separate, explicit consent/delegation flow (PolicyPart step-up per policy-part-v0.1.md).

9. Proof requirements and TTL profile

9.1 Data Integrity proof

The single supported proof mechanism is an embedded Data Integrity proof. VC-JOSE-COSE, SD-JWT, and selective disclosure are out of scope for v0.2.

Normative requirements:

Issuance ordering: the Connector MUST fix the A2A message.messageId before creating the proof, put that value in proof.challenge, and send the credential inside the outer message bearing that same messageId. proof.domain MUST be re-bound to the final receiving agent on every issuance — credentials are never reused across recipients.

9.2 TTL profile

This profile uses short validity windows instead of credentialStatus / revocation:

ParameterValue
Default TTL (validUntil - validFrom)300 seconds
Maximum accepted TTL600 seconds
Clock skew allowance60 seconds

Receivers MUST reject credentials whose validity window exceeds the maximum TTL, whose validFrom is in the future beyond the skew allowance, or whose validUntil is in the past beyond the skew allowance. Both validFrom and validUntil are required by this profile.

10. Replay contract

challenge == message.messageId verification alone does NOT prevent replay: an attacker who captures the entire outer A2A message can re-send it intact, and the challenge will still match. The binding narrows replay to whole-message re-submission within the validity window; it does not eliminate it.

Therefore:

The conformance fixtures (§14) include a replay scenario: the second presentation of the same (issuer, domain, challenge) tuple is accepted by a cache-less verifier (with a documented “no replay protection” posture) and rejected by a caching verifier.

11. Issuer trust and key resolution

11.1 Trust policy before any fetch

Issuer trust is receiver-local policy. A valid signature proves only control of a key — it does not make the issuer trustworthy, and there are no built-in or hard-coded trusted Connector issuers.

The order of operations is fixed (SSRF mitigation):

  1. The receiver checks that the credential’s exact issuer identifier is pre-registered in its local issuer trust policy. If not, the credential is rejected without any network fetch. Caller-provided issuer strings never cause a fetch on their own.
  2. Only for a trusted issuer does the receiver resolve verification material (DID document or HTTPS controlled identifier document).
  3. After the signature verifies, the receiver MUST confirm that proof.verificationMethod is actually listed under the issuer/controller’s assertionMethod relation. A key that merely appears in the document (or in an unrelated relation) is an issuer/controller mismatch and a hard rejection, regardless of signature validity.

11.2 Issuer identifier forms

The generic profile accepts:

The Slack reference issuer uses did:web:<connector-host> as its canonical issuer identifier. It publishes a DID document at https://<connector-host>/.well-known/did.json containing rotating assertionMethod Multikey (Ed25519) keys. Their ids remain did:web:<connector-host>#<kid>. The same DID document MAY contain additional verification methods for orthogonal protocols. In particular, the Slack reference Connector’s OAuth federation integration publishes the same public material in a distinct JsonWebKey method at #<kid>-oauth-jwk; its v0.2 proofs continue to reference the unsuffixed Multikey. A verification method MUST NOT combine publicKeyMultibase and publicKeyJwk, as prohibited by DID Core. Key rotation is expressed by updating both representations in the DID document; verifiers re-resolve subject to their cache policy (§11.3).

11.3 Key-fetch hardening

When fetching a DID document or controlled identifier document, receivers MUST:

11.4 No automatic promotion of legacy trust entries

Legacy v0.1 deployments keyed their Trusted Connector Issuer policy by Connector hostname. Those entries MUST NOT be auto-promoted to did:web issuer trust: did:web:host is a different exact identifier with a different resolution path. Operators migrating to v0.2 pin the new exact issuer identifier explicitly (§16).

12. A2A carrier and capability negotiation

12.1 Carrier

The v0.2 carrier is:

{
  "message": {
    "messageId": "9f4b6c2e-1d3a-4c5b-8e7f-2a1b3c4d5e6f",
    "metadata": {
      "mentionable": {
        "verifiable_credentials": [
          /* PlatformIdentityCredential objects with embedded Data Integrity proof */
        ]
      }
    }
  }
}

metadata.mentionable.verifiable_credentials is an array of VC JSON objects, each carrying its embedded proof. Every credential’s proof.challenge MUST equal the enclosing message.messageId.

12.2 Capability negotiation

Receiving agents advertise v0.2 support by declaring the extension URI https://mentionable.dev/ns/identity/v0.2 in a2a.capabilities.extensions[] on their Agent Card (see agent-card.md §1.2).

Issuers (Connectors) send both direct-user and platform-observed-relay credentials via the v0.2 carrier to any v0.2-capable recipient.

Extension URI matching is exact string comparison. There is no trailing slash tolerance, no case normalization, and no scheme or version coercion: https://mentionable.dev/ns/identity/v0.2/ and https://Mentionable.dev/ns/identity/v0.2 are both non-matches. Agent Cards MUST advertise the URI verbatim as written above, and issuers MUST NOT normalize before comparing — an unclear capability signal falls under §12.3 (fail closed), not under a lenient match.

12.3 v0.1 fallback rules

Receivers treat both carriers as caller-controlled input: structural validity alone never promotes an entry to trusted identity (§13).

Transport scope. §12 covers the A2A carrier only. The REST transport’s Mentionable-Identity-Evidence header stays v0.1-only in this revision: it carries signed IdentityEvidence and MUST NOT carry a PlatformIdentityCredential. A future revision may define a REST carrier for v0.2 credentials (header size and the request-binding equivalent of proof.challenge are the open questions); until then, REST callers that need v0.2 semantics use A2A.

13. Core trust boundary

@mentionable/core separates structural parsing from security verification at the type level:

unknown metadata
  → structural parse → UnverifiedPlatformIdentityCredential
  → verification: signature + proofPurpose + issuer trust
    + issuer/controller (assertionMethod) + domain + challenge
    + validity window + replay policy
  → VerifiedPlatformIdentityCredential
  → internal identity (sender.identities / caller.presented)

14. Conformance fixtures

Versioned profile fixtures live in-repo at fixtures/platform-identity-credential/v0.2/, usable by external implementations without depending on Mentionable packages or source. The fixture set covers:

15. Security considerations

16. Migration from v0.1

For issuers (Connectors):

  1. Detect recipient capability via the Agent Card extension URI (§12.2).
  2. Issue PlatformIdentityCredential via metadata.mentionable.verifiable_credentials to v0.2-capable recipients; keep the v0.1 identity_evidence carrier only behind an explicit compatibility gate (§12.3).
  3. Stop new signed-attestation issuance on the v0.2 path entirely.

For receivers (operators):

  1. Advertise https://mentionable.dev/ns/identity/v0.2 once the verifier is deployed.
  2. Pin the new issuer explicitly. A legacy hostname trust entry (e.g. slack-connector.example.com for Connector Card discovery) is NOT automatically valid for v0.2. Add the exact new issuer identifier — for the Slack reference issuer, did:web:slack-connector.example.com — to the local issuer trust policy as a deliberate operator action (§11.4).
  3. Decide the replay posture (§10) and document it.
  4. Keep accepting v0.1 evidence during the compatibility window if needed; the semantic mapping in §6 relates the two shapes.

Relationship to issue #462: the platform-observed invocation semantics and the PolicyPart/authorization boundary defined there are preserved intact. #462’s custom envelope, signed-attestation proof, Connector Card key discovery, and new identity_evidence issuance procedure are superseded by this profile.

17. References

Normative:

Informative: