Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions .agents/skills/building-with-hypercerts-lexicons/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,7 @@ CERTIFIED
actor/profile (user profile)
actor/organization (org metadata)
badge/response ──> badge/award ──> badge/definition
badge/award ────────────> custom typed data (inline or strongRef)
graph/follow ───────────> account DID (social follow)
graph/entityFollow ─────> record (by AT-URI) (non-account follow)
feed/like ──────────────> record (by strongRef) (social like)
Expand All @@ -368,6 +369,46 @@ Every arrow is a `strongRef` or union reference stored on AT Protocol.

## Common Patterns

### Badge extensions

`app.certified.badge.award.extensions` is an optional array (maximum 20)
containing inline typed objects, `com.atproto.repo.strongRef` objects, or both.
Define custom data in your own lexicon, not as new shared badge fields. Inline
payloads avoid separate record writes and reads; references pin separately
stored records by URI and CID. Strong references in this union must include
`$type: "com.atproto.repo.strongRef"` as well as `uri` and `cid`.

`app.certified.badge.definition.extensionTypes` declares the extension types
that belong to a badge: an optional array (maximum 20) of `{ type, required }`
entries. `type` is a payload type identifier (at most 512 bytes): a full
Lexicon reference such as `org.example.goodmarket.defs#approvalMetadata` for
an inline object, or a record NSID such as
`org.example.goodmarket.approvalMetadata` for a referenced record. `required`
defaults to false. `badgeType` still describes the badge's broad category, not
its extension type.

Consumers apply two rules, using the definition version referenced by the
award. First, an award that lacks a `required: true` extension is not a valid
award of the badge; a payload that fails its own schema counts as missing.
Second, an extension whose type is not listed is outside the badge's contract:
the award stays valid, and the extension may be ignored but must not be
presented as data defined by the badge. Omitting `extensionTypes` or leaving
it empty declares no extensions. Match inline payloads by exact `$type` and
references by the collection NSID in the AT-URI, not the strongRef wrapper's
type. A required entry names one form (inline or referenced), so list only the
form you issue.

The shared Lexicon validator checks the carrier structure, not this
cross-record contract or arbitrary third-party inline fields. Register and
validate your own extension schemas separately even when they are included in
the same validator registry.

Good Market Approved can use a custom payload with just `sectors: string[]`
and `focus: string[]`. The placeholder identifiers above are illustrative, not
existing Good Market lexicons. See the repository's
[badge extension guide](https://github.com/hypercerts-org/hypercerts-lexicon/blob/main/docs/design/badge-extensions.md)
for complete inline and referenced examples.

### Creating an Activity (Hypercert)

```typescript
Expand Down
5 changes: 5 additions & 0 deletions .changeset/badge-extension-contract.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@hypercerts-org/lexicon": minor
---

Add optional `extensions` to badge awards as an open union of inline typed objects and strong references, and optional `extensionTypes` to badge definitions as a list of `{ type, required }` entries declaring which extension types belong to the badge. An award lacking a required extension is invalid; extensions of undeclared types are outside the badge's contract and do not invalidate the award. Integrators can attach their own schema-defined data without modifying the shared badge lexicons. Enforcing the contract and validating third-party payloads remains an application-level responsibility.
Binary file modified ERD-with-fields.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion ERD-with-fields.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified ERD.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions ERD.puml
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ dataclass badgeDefinition {
icon?
description?
allowedIssuers[]?
extensionTypes[]?
createdAt
!endif
}
Expand All @@ -181,6 +182,8 @@ dataclass badgeAward {
badge
subject
note?
url?
extensions[]?
createdAt
!endif
}
Expand All @@ -194,6 +197,14 @@ dataclass badgeResponse {
!endif
}

note right of badgeAward
Extensions are inline typed objects or
strong references to publisher-defined records.
The referenced definition's extensionTypes
declares which types belong to the badge
and which are required.
end note

' app.certified.graph.follow
dataclass follow {
!if (SHOW_FIELDS == "true")
Expand Down
2 changes: 1 addition & 1 deletion ERD.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
38 changes: 38 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ CERTIFIED ─ shared lexicons (certified.app)
actor/profile (user profile)
actor/organization (org metadata)
badge/response ──► badge/award ──► badge/definition
badge/award ────────────► custom typed data (inline or strongRef)
graph/follow ────────────► account DID (social follow)
graph/entityFollow ──────► record (by AT-URI) (non-account follow)
feed/like ───────────────► record (by strongRef) (social like)
Expand Down Expand Up @@ -374,6 +375,43 @@ snapshot semantic, and it's what the lexicon uses across
`badge.award.badge`, `badge.response.badgeAward`, `funding.receipt.for`,
and similar fields where the historical content needs to stay stable.

### Badge extensions declare a contract on the definition

Badge awards can carry optional `extensions`: an open-union array of inline
typed objects and `com.atproto.repo.strongRef` objects. Integrators define
their own extension lexicons; the shared badge schema does not need to
enumerate each project's types. Inline data avoids a separate record write
and read, while a strong reference allows a separately stored, version-pinned
payload. Both forms can appear in the same array (at most 20 entries).

A badge definition is the type of its awards, so it also declares the
extension data that belongs to the badge. The optional `extensionTypes` array
holds up to 20 entries of the form `{ type, required }`. `type` is a fully
qualified payload type identifier (at most 512 bytes), such as
`org.example.goodmarket.defs#approvalMetadata` for an inline object or
`org.example.goodmarket.approvalMetadata` for a referenced record. `badgeType`
remains the broad category (for example, `certification`), not the extension
schema identifier.

Two rules apply, using the definition version referenced by the award:

1. An award that lacks an extension marked `required: true` is not a valid
award of the badge. A payload that fails its own schema counts as missing.
2. An extension whose type is not listed is outside the badge's contract. The
award stays valid; consumers may ignore the extension and should not present
it as data defined by the badge.

Omitting `extensionTypes` or leaving it empty declares no extensions. For a
strong reference, the payload type is the collection NSID in its AT-URI, not
the wrapper's `com.atproto.repo.strongRef` type.

**Lexicon validation alone does not enforce this cross-record contract or
validate third-party inline payloads admitted by the open union.** Consumers
must perform those checks separately.

See [Badge extensions](docs/design/badge-extensions.md) for the Good Market
Approved example, publishing requirements, and consumer behavior.

## Entity Relationship Diagram

![Hypercert ERD](ERD.svg)
Expand Down
Loading
Loading