diff --git a/.changeset/add-ai-agent-skill.md b/.changeset/add-ai-agent-skill.md deleted file mode 100644 index b5bfbd47..00000000 --- a/.changeset/add-ai-agent-skill.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"@hypercerts-org/lexicon": minor ---- - -Add AI agent skill for downstream developers, and document installation in `README.md`. -Install via `npx skills add hypercerts-org/hypercerts-lexicon`. diff --git a/.changeset/add-certified-link-evm.md b/.changeset/add-certified-link-evm.md deleted file mode 100644 index 8b3e25a9..00000000 --- a/.changeset/add-certified-link-evm.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -"@hypercerts-org/lexicon": minor ---- - -Add `app.certified.link.evm` lexicon for verifiable linking of EVM -wallet addresses to ATProto identities via cryptographic signatures. -Currently supports EOA wallets with EIP-712 typed data; the `proof` -field is an open union to allow future signature methods (e.g. -ERC-1271, ERC-6492). diff --git a/.changeset/add-missing-def-descriptions.md b/.changeset/add-missing-def-descriptions.md deleted file mode 100644 index 64a1141a..00000000 --- a/.changeset/add-missing-def-descriptions.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@hypercerts-org/lexicon": minor ---- - -Add missing descriptions to contributor and item defs diff --git a/.changeset/add-pub-leaflet-lexicons.md b/.changeset/add-pub-leaflet-lexicons.md deleted file mode 100644 index 857926c2..00000000 --- a/.changeset/add-pub-leaflet-lexicons.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@hypercerts-org/lexicon": minor ---- - -Vendor pub.leaflet and app.bsky.richtext.facet lexicon JSONs to enable runtime validation of records with description and facet fields. Previously, these external schemas were only shimmed at the TypeScript type level via @atcute packages, causing LexiconDefNotFoundError at runtime when validating records that populated optional description or facet fields. Removes the now-unnecessary create-shims.sh script and @atcute/leaflet + @atcute/bluesky dependencies. diff --git a/.changeset/attachment-content-type-known-values.md b/.changeset/attachment-content-type-known-values.md deleted file mode 100644 index bb6d0bf2..00000000 --- a/.changeset/attachment-content-type-known-values.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@hypercerts-org/lexicon": minor ---- - -Add `knownValues` to `contentType` field on attachment: report, audit, evidence, testimonial, methodology diff --git a/.changeset/badge-known-values-strongref.md b/.changeset/badge-known-values-strongref.md deleted file mode 100644 index 6b682be4..00000000 --- a/.changeset/badge-known-values-strongref.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@hypercerts-org/lexicon": minor ---- - -Add `knownValues` to `badgeType` on badge definition and change badge/badgeAward refs to `strongRef` diff --git a/.changeset/collection-short-description-facets.md b/.changeset/collection-short-description-facets.md deleted file mode 100644 index f8e17209..00000000 --- a/.changeset/collection-short-description-facets.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@hypercerts-org/lexicon": minor ---- - -Add `shortDescriptionFacets` to collection for rich text annotations on `shortDescription` diff --git a/.changeset/collection-type-known-values.md b/.changeset/collection-type-known-values.md deleted file mode 100644 index a0557467..00000000 --- a/.changeset/collection-type-known-values.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@hypercerts-org/lexicon": minor ---- - -Add `knownValues` to `type` field on collection: favorites, project, portfolio, program diff --git a/.changeset/description-union-type.md b/.changeset/description-union-type.md deleted file mode 100644 index c191c697..00000000 --- a/.changeset/description-union-type.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"@hypercerts-org/lexicon": minor ---- - -Widen description fields on activity, collection, and attachment from a bare Leaflet ref to a union of inline descriptionString and Leaflet linearDocument, with a shared def in org.hypercerts.defs. - -This is technically a breaking schema change (ref → union), but not breaking for most published records, as they already include a `$type` discriminator on the description object, so existing data validates against the union without modification. diff --git a/.changeset/funding-receipt-field-types.md b/.changeset/funding-receipt-field-types.md deleted file mode 100644 index bec9b085..00000000 --- a/.changeset/funding-receipt-field-types.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@hypercerts-org/lexicon": minor ---- - -Normalize funding receipt fields: `from`/`to` as union of DID ref + strongRef, `from` optional, `for` changed to strongRef diff --git a/.changeset/funding-receipt-text-variant.md b/.changeset/funding-receipt-text-variant.md deleted file mode 100644 index c727fa45..00000000 --- a/.changeset/funding-receipt-text-variant.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@hypercerts-org/lexicon": minor ---- - -Add `#text` variant to funding receipt `from`/`to` unions for free-text identifiers (names, wallet addresses, etc.) diff --git a/.changeset/make-funding-receipt-from-optional.md b/.changeset/make-funding-receipt-from-optional.md deleted file mode 100644 index ba472e02..00000000 --- a/.changeset/make-funding-receipt-from-optional.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@hypercerts-org/lexicon": minor ---- - -Make `from` field optional on `org.hypercerts.funding.receipt` to support anonymous funders diff --git a/.changeset/numeric-string-scores.md b/.changeset/numeric-string-scores.md deleted file mode 100644 index 24b8f46f..00000000 --- a/.changeset/numeric-string-scores.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@hypercerts-org/lexicon": minor ---- - -Change evaluation score fields (`min`, `max`, `value`) from integer to numeric strings to support fractional scores diff --git a/.changeset/optional-badge-icon.md b/.changeset/optional-badge-icon.md deleted file mode 100644 index 0d693857..00000000 --- a/.changeset/optional-badge-icon.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@hypercerts-org/lexicon": minor ---- - -Make `icon` optional on badge definition to support text-only badges diff --git a/.changeset/remove-contributors-max-length.md b/.changeset/remove-contributors-max-length.md deleted file mode 100644 index 0b14dac4..00000000 --- a/.changeset/remove-contributors-max-length.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@hypercerts-org/lexicon": minor ---- - -Remove `maxLength` cap on activity contributors array diff --git a/CHANGELOG.md b/CHANGELOG.md index cb51e109..3bfc0767 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,210 @@ # @hypercerts-org/lexicon +## 0.11.0 + +The v0.11.0 release introduces EVM identity linking, stronger type safety across badge and funding schemas, vendored Leaflet lexicons for runtime validation, and a set of schema refinements based on real-world usage from the first month of v0.10.0 adoption. All changes listed below are merged to `main` and will ship together as a single coordinated release. + +```bash +npm install @hypercerts-org/lexicon@0.11.0 +``` + +--- + +### New lexicons + +#### EVM identity linking + +`app.certified.link.evm` + +A new record type for creating verifiable links between ATProto identities and EVM wallet addresses. Each record contains a cryptographic proof — currently EIP-712 typed data signatures for EOA wallets — that binds a DID to an Ethereum address onchain. + +The `proof` field is an open union, so future signature methods (ERC-1271, ERC-6492) can be added without breaking existing records. + +```jsonc +{ + "address": "0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B", + "proof": { + "$type": "app.certified.link.evm#eip712Proof", + "signature": "0x...", + "message": { + "did": "did:plc:abc123", + "evmAddress": "0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B", + "chainId": "1", + "timestamp": "1711929600", + "nonce": "0", + }, + }, + "createdAt": "2026-04-03T00:00:00.000Z", +} +``` + +#### Vendored Leaflet and richtext lexicons + +`pub.leaflet.*` · `pub.leaflet.richtext.facet` + +The package now ships the full set of Leaflet block and richtext facet lexicon JSON files (17 files). Previously, these external schemas were shimmed at the TypeScript type level via `@atcute` packages, which caused `LexiconDefNotFoundError` at runtime when validating records with `description` or facet fields. Vendoring them fixes runtime validation and removes the `@atcute/leaflet` and `@atcute/bluesky` dependencies. + +This is a **packaging change**, not a schema change. No record structures are affected. + +--- + +### Breaking changes + +This release includes three breaking schema changes. All were identified early enough in adoption that the team decided the long-term gains outweigh the migration cost. + +#### Evaluation scores are now strings + +`org.hypercerts.context.evaluation` + +The `min`, `max`, and `value` fields on evaluation scores changed from `integer` to `string`. ATProto has no native decimal type, and integer-only scores made use cases like "3.7 out of 5" impossible. + +```diff + // Before +- { "min": 0, "max": 10, "value": 7 } ++ // After ++ { "min": "0", "max": "10", "value": "7.5" } +``` + +**Who needs to update:** + +| Consumer | Action | +| -------- | ---------------------------------------------------------------------------------------------------------------------- | +| Indexers | Change column type from `INTEGER` to `TEXT`. Backfill existing records. Update any numeric sorting or filtering logic. | +| AppViews | Parse scores as strings. Use `parseFloat()` for numeric display where appropriate. | +| Frontend | Handle both numeric strings (`"3.7"`) and potentially non-numeric strings (`"A+"`) in display components. | + +#### Badge references are now strong refs + +`app.certified.badge.award` · `app.certified.badge.response` + +Badge awards and responses now reference their parent records via `com.atproto.repo.strongRef` instead of plain lexicon refs. Strong references include both a URI and a content hash (CID), pinning the reference to a specific version of the badge definition. This prevents the meaning of an award from drifting if the underlying badge definition is later modified. + +```diff + // Before +- "badge": "at://did:plc:abc/app.certified.badge.definition/123" + + // After ++ "badge": { ++ "uri": "at://did:plc:abc/app.certified.badge.definition/123", ++ "cid": "bafyrei..." ++ } +``` + +**Who needs to update:** + +| Consumer | Action | +| ------------- | ----------------------------------------------------------------------------------------------------------------- | +| Indexers | Update parsing to expect `{ uri, cid }` objects instead of plain URI strings for `badge` and `badgeAward` fields. | +| AppViews | Update resolution logic. Dereference using both `uri` and `cid` for content verification. | +| SDK consumers | Regenerate types. Any code constructing badge awards or responses must supply the full strong ref. | + +#### Funding receipt fields normalized + +`org.hypercerts.funding.receipt` + +The `from`, `to`, and `for` fields have been reworked for consistency and stronger type safety. + +| Field | Before (v0.10.0) | After | +| ------ | ----------------- | ------------------------------------------------------------------------------------------ | +| `from` | Required, DID ref | **Optional**, union of `#text` \| `app.certified.defs#did` \| `com.atproto.repo.strongRef` | +| `to` | Plain string | Union of `#text` \| `app.certified.defs#did` \| `com.atproto.repo.strongRef` | +| `for` | AT-URI string | `com.atproto.repo.strongRef` (pins to a specific record version) | + +The `from` and `to` fields were asymmetric — `from` required an AT Protocol identity while `to` accepted any string. Now both are three-way unions that accept a free-text string (`#text` — for display names, wallet addresses, or other identifiers), a DID, or a strong reference. This treats senders and recipients uniformly while preserving the ability to reference non-ATProto participants. `from` is also optional, properly supporting anonymous funding. `for` is now a strong ref, ensuring the receipt always points to the exact version of the activity it funded. + +```diff + // Before +- "from": { "$type": "app.certified.defs#did", "did": "did:plc:sender" }, +- "to": "did:plc:recipient", +- "for": "at://did:plc:abc/org.hypercerts.claim.activity/123" + + // After — with a DID ++ "from": { "$type": "app.certified.defs#did", "did": "did:plc:sender" }, ++ "to": { "$type": "app.certified.defs#did", "did": "did:plc:recipient" }, + + // After — with a free-text identifier ++ "to": { "$type": "org.hypercerts.funding.receipt#text", "value": "0xAb58...eC9B" }, + + // After — for field ++ "for": { ++ "uri": "at://did:plc:abc/org.hypercerts.claim.activity/123", ++ "cid": "bafyrei..." ++ } +``` + +**Who needs to update:** + +| Consumer | Action | +| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Indexers | Update parsing for `from`, `to` (now unions with `$type` discriminator — handle all three variants), and `for` (now `{ uri, cid }`). Allow `NULL` for `from`. | +| AppViews | Update resolution logic for all three fields. `for` requires dereferencing via both URI and CID. Handle `#text` variants for display. | +| SDK consumers | Regenerate types. Code constructing receipts must supply union-typed `to` and strong ref `for`. | +| Frontend | Update forms to construct proper union objects for sender/recipient. Handle `#text` for non-ATProto participants. Handle missing `from` for anonymous display. | + +--- + +### Schema changes + +> **A note on "optional" fields:** Even new optional fields require attention from indexers and AppViews. If an indexer doesn't store a new field, that data is silently lost for every downstream consumer. The changes below are non-breaking in the strict sense — existing records remain valid — but ignoring them means incomplete data. + +#### Known values + +Several free-text string fields now declare `knownValues` — a set of canonical values that establish interoperability conventions across the ecosystem. Custom values are still permitted. Think of these as Schelling points, not constraints. + +| Lexicon | Field | Known values | +| ----------------------------------- | ------------- | -------------------------------------------------------------------------------------------------- | +| `org.hypercerts.collection` | `type` | `favorites` · `project` · `portfolio` · `program` | +| `org.hypercerts.context.attachment` | `contentType` | `report` · `audit` · `evidence` · `testimonial` · `methodology` | +| `app.certified.badge.definition` | `badgeType` | `endorsement` · `verification` · `participation` · `certification` · `affiliation` · `recognition` | + +**Action:** Indexers should index these values for filtering and categorization. AppViews and frontends can use them for dropdowns, search facets, and display grouping. No schema migration required — the underlying field type is still a string. + +#### Badge icon is now optional + +`app.certified.badge.definition` + +The `icon` field moved from `required` to optional. Not all badges have a visual representation — endorsements, participation records, and text-based certifications can now omit the icon entirely. + +**Action:** Indexers should allow `NULL` in the icon column. Frontend developers must add a fallback or placeholder when rendering badges without an icon — apps that assume `icon` is always present will crash or render broken UI. + +#### Contributors array is uncapped + +`org.hypercerts.claim.activity` + +Removed the `maxLength: 1000` constraint on the `contributors` array. ATProto records have a natural 1 MB size limit (~2,000–4,000 contributors), making the artificial cap unnecessary. + +**Action:** Indexers and AppViews with hardcoded length limits matching the old max should remove them. Frontends should implement pagination or lazy loading for large contributor lists to avoid performance issues. + +#### Rich text on collection short descriptions + +`org.hypercerts.collection` + +Added `shortDescriptionFacets` — an optional array of rich text facets (mentions, URLs, hashtags) that annotate the `shortDescription` field. This brings collections in line with activity claims, which already supported facets. + +**Action:** Indexers must store the new field when present — without it, rich text annotations (links, mentions) are permanently lost. AppViews should include facets in API responses. Frontends can render rich text using the standard ATProto facet model. + +--- + +### Documentation improvements + +- **Contributor and item defs** now have descriptions, improving TypeScript IntelliSense and AI code generation. +- **`occurredAt` vs `createdAt`** semantics clarified on funding receipts. `occurredAt` is when the funding happened in the real world; `createdAt` is when the record was written to the PDS. +- **Stale references** in board and measurement lexicon descriptions have been corrected. +- **README** rewritten with an ASCII namespace map and structured reference tables. +- **AI agent skill** added for downstream developers. Install via `npx skills add hypercerts-org/hypercerts-lexicon`. + +--- + +### Upgrading + +```bash +npm install @hypercerts-org/lexicon@0.11.0 +``` + +The source of truth for lexicon definitions is the [NPM package](https://www.npmjs.com/package/@hypercerts-org/lexicon) and the published ATProto repository. The `main` branch on GitHub is a development branch — do not build production applications against it. + +After upgrading, regenerate your TypeScript types and run your validation suite against the updated schemas. The package includes all lexicon JSON files and pre-built type definitions. + ## 0.10.0 ### Minor Changes diff --git a/package-lock.json b/package-lock.json index 1cbf3660..0f1c3a68 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@hypercerts-org/lexicon", - "version": "0.10.0", + "version": "0.11.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@hypercerts-org/lexicon", - "version": "0.10.0", + "version": "0.11.0", "license": "MIT", "dependencies": { "@atproto/lexicon": "^0.6.0", diff --git a/package.json b/package.json index 2877813b..7ed91659 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@hypercerts-org/lexicon", - "version": "0.10.0", + "version": "0.11.0", "description": "ATProto lexicon definitions and TypeScript types for the Hypercerts protocol", "type": "module", "main": "./dist/index.cjs",