Skip to content

[HYPER-502] add app.certified.actor.createdVia lexicon#228

Draft
aspiers wants to merge 2 commits into
mainfrom
feat/certified-actor-created-via
Draft

[HYPER-502] add app.certified.actor.createdVia lexicon#228
aspiers wants to merge 2 commits into
mainfrom
feat/certified-actor-created-via

Conversation

@aspiers

@aspiers aspiers commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds a new lexicon app.certified.actor.createdVia — a lightweight account-provenance record identifying the application that created a Certified account via its OAuth client_id URL.

Refs HYPER-502.

Why

Lets consumers distinguish records originating from test/staging environments from real ones, without requiring full cryptographic platform attestations (a lighter-weight alternative to HYPER-181). The signatures array still allows the provenance marker itself to be attested when a stronger guarantee is wanted.

Schema

  • key: literal:self — one createdVia record per repo, mirroring app.certified.actor.profile / .organization.
  • clientId (required, uri) — the OAuth client_id URL (client metadata document URL) uniquely identifying the creating app. Named clientId (camelCase) to satisfy the repo's property-name-camelcase style rule; the description records that it holds the OAuth client_id value.
  • createdAt (required, datetime).
  • signatures (optional) — app.certified.signature.defs#list, consistent with every other Certified record.

Changeset

minor (non-breaking additive lexicon).

Generated / regenerated

  • generated/ — via npm run gen-api (gitignored, not in diff).
  • SCHEMAS.md — via npm run gen-schemas-md.
  • ERD.puml — added a createdVia dataclass.

⚠️ ERD images not regenerated. ERD.png / ERD.svg are committed binaries with no repo render script and PlantUML isn't available in the dev environment here. They are now stale vs ERD.puml and should be re-rendered by someone with PlantUML.

Open design question (draft)

Whether clientId should be required or optional. A signature signed by the client's service DID (did:web) can itself identify the origin, making clientId partly redundant; both-present enables a domain sanity check between the client_id URL and the did:web domain. See the discussion comment below. Pending a decision on the minimum-validity constraint.

Test plan

  • npm run check — validate + typecheck + build, 188 tests pass
  • node scripts/check-lexicon-style.js — 0 errors
  • New tests/validate-actor-created-via.test.ts

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added support for a new Certified account provenance record, including creation time, optional application identifier, and optional signatures.
    • Updated schema documentation and diagrams to reflect the new record.
  • Tests

    • Added validation coverage for valid records and common failure cases, including missing timestamps and invalid identifier formats.

@changeset-bot

changeset-bot Bot commented Jun 25, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: bb4a721

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@hypercerts-org/lexicon Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

No new commits to review since the last review.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 0f2b0079-868c-4f4d-b9ad-5e8fc738e61d

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a new app.certified.actor.createdVia ATProto lexicon record that stores OAuth client_id provenance for Certified accounts. The lexicon JSON, ERD entity, schema documentation, changeset entry, and Vitest validation tests are all introduced together.

Changes

app.certified.actor.createdVia lexicon

Layer / File(s) Summary
Lexicon definition and documentation
lexicons/app/certified/actor/createdVia.json, SCHEMAS.md, ERD.puml, .changeset/add-actor-created-via.md
Defines the app.certified.actor.createdVia record with required createdAt, optional clientId URI, and optional signatures ref; documents the schema in SCHEMAS.md and adds the createdVia dataclass entity to ERD.puml.
Validation tests
tests/validate-actor-created-via.test.ts
Vitest suite covering valid minimal records, optional clientId, inline signatures, and rejection of missing createdAt, invalid URI, and invalid datetime.

Estimated code review effort

🎯 1 (Trivial) | ⏱️ ~5 minutes

Possibly related PRs

  • hypercerts-org/hypercerts-lexicon#97: Introduced the auto-generation pipeline that produces SCHEMAS.md entries from lexicon JSON files, the same pipeline consuming the new createdVia.json.

Suggested reviewers

  • Kzoeps

🐇 A new record hops into the store,
createdVia tells who knocked at the door.
A clientId URI, a datetime too,
Signatures optional — provenance rings true!
The rabbit stamps the changeset and leaps away~ 🌿


Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (1 error)

Check name Status Explanation Resolution
Lexicon Documentation Sync ❌ Error SCHEMAS.md matches the new lexicon, but ERD.puml’s createdVia entity omits non-facet property signatures, so docs are out of sync. Add signatures[]? to the createdVia dataclass in ERD.puml so it matches lexicons/app/certified/actor/createdVia.json and SCHEMAS.md.
✅ Passed checks (6 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: adding the app.certified.actor.createdVia lexicon.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Lexicons Styleguide Compliance ✅ Passed New lexicon uses lowerCamelCase, documents ambiguous fields, and avoids unnecessary required fields; no undocumented styleguide deviation found.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/certified-actor-created-via

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@aspiers

aspiers commented Jun 25, 2026

Copy link
Copy Markdown
Contributor Author

Design debate: should client_id be required or optional?

Capturing the argument so it's not lost (still open — not resolved by this PR):

The case for making client_id optional:

  • A signatures entry is signed by the private key of the client's service DID. If that's a did:web, the DID already uniquely identifies the client, so client_id and the signing DID are partly redundant — you only strictly need one of them to establish origin.
  • A signature is a much stronger guarantee of origin integrity than an unsigned client_id string, which is just an unverified self-claim. So a signed record arguably needs no client_id at all.
  • Providing both does no harm and enables a useful sanity check: the domain of the client_id URL should match the domain in the did:web.

→ This points toward both client_id and signatures being optional, rather than client_id being required.

The open sub-question if we do that: what's the minimum that makes a valid createdVia record? A record with neither a client_id nor a signature carries no provenance information at all. Options:

  1. client_id required (current state of this PR) — simplest, but rejects signature-only records.
  2. client_id optional, no cross-field constraint — permits empty/meaningless records.
  3. client_id optional, but require at least one of client_id or signatures — captures intent, but ATProto lexicons can't express "one-of-N-required" natively, so this would have to be enforced at the application layer, not in the schema.

Leaving the PR in draft until we settle this.

Add a lightweight account-provenance record carrying the OAuth client_id
URL of the application that created a Certified account. Lets consumers
distinguish records originating from test/staging environments from real
ones without requiring cryptographic platform attestations.

- Required `clientId` (uri) and `createdAt` (datetime); key literal:self
- Optional `signatures` array, consistent with other Certified records
- Validation tests, ERD entry, regenerated SCHEMAS.md, changeset (minor)

The property is named `clientId` (camelCase) to satisfy the repo style
checker; its description and the changeset note it holds the OAuth
`client_id` URL.

Refs HYPER-502.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@aspiers aspiers force-pushed the feat/certified-actor-created-via branch from 74da239 to 52dfaae Compare June 26, 2026 17:26
Comment thread lexicons/app/certified/actor/createdVia.json Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@aspiers aspiers changed the title feat(actor): add app.certified.actor.createdVia lexicon [HYPER-502] add app.certified.actor.createdVia lexicon Jun 26, 2026
@Kzoeps

Kzoeps commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Design debate: should client_id be required or optional?

Capturing the argument so it's not lost (still open — not resolved by this PR):

The case for making client_id optional:

  • A signatures entry is signed by the private key of the client's service DID. If that's a did:web, the DID already uniquely identifies the client, so client_id and the signing DID are partly redundant — you only strictly need one of them to establish origin.
  • A signature is a much stronger guarantee of origin integrity than an unsigned client_id string, which is just an unverified self-claim. So a signed record arguably needs no client_id at all.
  • Providing both does no harm and enables a useful sanity check: the domain of the client_id URL should match the domain in the did:web.

→ This points toward both client_id and signatures being optional, rather than client_id being required.

The open sub-question if we do that: what's the minimum that makes a valid createdVia record? A record with neither a client_id nor a signature carries no provenance information at all. Options:

  1. client_id required (current state of this PR) — simplest, but rejects signature-only records.
  2. client_id optional, no cross-field constraint — permits empty/meaningless records.
  3. client_id optional, but require at least one of client_id or signatures — captures intent, but ATProto lexicons can't express "one-of-N-required" natively, so this would have to be enforced at the application layer, not in the schema.

Leaving the PR in draft until we settle this.

i think we could leave client_id optional too. since this pattern is already allowed on app.bsky.actor.profile and app.certified.actor.profile (app.bsky.actor.profile does not even require the createdAAt) granted just because they do it isnt a good answer. but i think it would be better dx if both are optional since one of n is not possible

@s-adamantine

s-adamantine commented Jun 27, 2026

Copy link
Copy Markdown
Contributor

Design debate: should client_id be required or optional?

Capturing the argument so it's not lost (still open — not resolved by this PR):

The case for making client_id optional:

  • A signatures entry is signed by the private key of the client's service DID. If that's a did:web, the DID already uniquely identifies the client, so client_id and the signing DID are partly redundant — you only strictly need one of them to establish origin.
  • A signature is a much stronger guarantee of origin integrity than an unsigned client_id string, which is just an unverified self-claim. So a signed record arguably needs no client_id at all.
  • Providing both does no harm and enables a useful sanity check: the domain of the client_id URL should match the domain in the did:web.

→ This points toward both client_id and signatures being optional, rather than client_id being required.

The open sub-question if we do that: what's the minimum that makes a valid createdVia record? A record with neither a client_id nor a signature carries no provenance information at all. Options:

  1. client_id required (current state of this PR) — simplest, but rejects signature-only records.
  2. client_id optional, no cross-field constraint — permits empty/meaningless records.
  3. client_id optional, but require at least one of client_id or signatures — captures intent, but ATProto lexicons can't express "one-of-N-required" natively, so this would have to be enforced at the application layer, not in the schema.

Leaving the PR in draft until we settle this.

I think making client_id optional makes sense as well.

Also, why is the client_id including the metadata document URL? e.g why not 'https://app.certified.one' instead? As it is a self claim, it shouldn't need to be that accurate? Also once we have the signatures, then, as you mentioned, it would be a much stronger guarantee of origin.

@holkexyz

Copy link
Copy Markdown
Member

Lets make it optional! Talked with Sharfy as well. @aspiers

Per PR #228 review consensus (holkexyz, Kzoeps, s-adamantine, aspiers),
clientId becomes optional. A signature already establishes origin via the
signing service DID, so a signature-only record is valid provenance.
ATProto lexicons cannot express "at least one of clientId/signatures", so
no cross-field constraint is added at the schema layer.

createdAt remains required, consistent with all other Certified record
lexicons (the lone exception being signature/proof).

Update tests, changeset, and SCHEMAS.md to reflect optional clientId.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@aspiers

aspiers commented Jun 29, 2026

Copy link
Copy Markdown
Contributor Author

Made clientId optional in bb4a721 (schema + tests + changeset + SCHEMAS.md). createdAt stays required, consistent with every other Certified record lexicon (only signature/proof exempts it). No "at least one of clientId/signatures" constraint, since ATProto lexicons can't express it — left to the application layer if needed.

@s-adamantine on why clientId is the full client-metadata-document URL rather than a bare origin like https://app.certified.one:

  • It's not a .well-known URL, so there's no deterministic way to locate the client metadata from just the origin — you need the full URL.
  • Multiple clients can be hosted on the same domain, so the origin alone isn't unique.
  • A client_id can even be hosted on a different domain from the app it refers to.

So the full URL is the actual OAuth client_id identifier; truncating to the origin would lose information and break the domain ↔ did:web sanity check.

@aspiers aspiers marked this pull request as ready for review June 29, 2026 12:07
Copilot AI review requested due to automatic review settings June 29, 2026 12:07

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@aspiers

aspiers commented Jun 29, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ERD.puml`:
- Around line 60-64: `ERD.puml` is missing a new non-facet field from the
`createdVia` dataclass, so the diagram is out of sync with the schema. Update
the `createdVia` block in `ERD.puml` to include `signatures` alongside
`clientId` and `createdAt`, keeping the field list aligned with the new lexicon
and `SCHEMAS.md` while still excluding any facet fields.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 78cac150-11fe-46e6-a1c9-a1734e37b0c4

📥 Commits

Reviewing files that changed from the base of the PR and between ce3152b and bb4a721.

📒 Files selected for processing (5)
  • .changeset/add-actor-created-via.md
  • ERD.puml
  • SCHEMAS.md
  • lexicons/app/certified/actor/createdVia.json
  • tests/validate-actor-created-via.test.ts

Comment thread ERD.puml
@aspiers aspiers marked this pull request as draft June 29, 2026 14:43
@aspiers

aspiers commented Jun 29, 2026

Copy link
Copy Markdown
Contributor Author

Changing back to draft in case we retire this in favour of using badges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants