Skip to content

Commit 52dfaae

Browse files
Testclaude
andcommitted
feat(actor): add app.certified.actor.createdVia lexicon
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>
1 parent ce3152b commit 52dfaae

5 files changed

Lines changed: 149 additions & 0 deletions

File tree

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
"@hypercerts-org/lexicon": minor
3+
---
4+
5+
Add `app.certified.actor.createdVia` record lexicon for lightweight account provenance.
6+
7+
The record carries a required `clientId` property holding the OAuth `client_id` URL (the client metadata document URL) that uniquely identifies the application which created the Certified account. This lets consumers distinguish records originating from, for example, test or staging environments from real ones, without requiring cryptographic platform attestations.
8+
9+
Like other Certified records, it includes an optional `signatures` array (`app.certified.signature.defs#list`) so the provenance marker can itself be attested.

ERD.puml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,14 @@ dataclass organization {
5656
!endif
5757
}
5858

59+
' app.certified.actor.createdVia
60+
dataclass createdVia {
61+
!if (SHOW_FIELDS == "true")
62+
clientId
63+
createdAt
64+
!endif
65+
}
66+
5967
' Contributors are represented by DIDs or human-readable strings
6068
' therefore do not require modelling via a lexicon.
6169
entity "contributor (DID/profile)" as contributorEntity #FFD4A3 {

SCHEMAS.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,22 @@ A location represented as a string, e.g. coordinates or a small GeoJSON string.
431431

432432
---
433433

434+
### `app.certified.actor.createdVia`
435+
436+
**Description:** Records which application created this Certified account, identified by the OAuth client_id URL of that application. A lightweight provenance marker that lets consumers distinguish, for example, records originating from a test or staging environment from real ones, without requiring cryptographic platform attestations.
437+
438+
**Key:** `literal:self`
439+
440+
#### Properties
441+
442+
| Property | Type | Required | Description |
443+
| ------------ | -------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
444+
| `clientId` | `string` || The OAuth `client_id` URL uniquely identifying the application that created this account. This is the client metadata document URL used during the ATProto OAuth flow (e.g. 'https://app.certified.one/oauth/client-metadata.json'). |
445+
| `createdAt` | `string` || Client-declared timestamp when this record was originally created. |
446+
| `signatures` | `ref` || Optional cryptographic signatures attesting to this record's content. |
447+
448+
---
449+
434450
### `app.certified.actor.organization`
435451

436452
**Description:** Extended metadata for an organization actor. Complements the base actor profile with organization-specific fields like legal structure and reference links.
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{
2+
"lexicon": 1,
3+
"id": "app.certified.actor.createdVia",
4+
"defs": {
5+
"main": {
6+
"type": "record",
7+
"description": "Records which application created this Certified account, identified by the OAuth client_id URL of that application. A lightweight provenance marker that lets consumers distinguish, for example, records originating from a test or staging environment from real ones, without requiring cryptographic platform attestations.",
8+
"key": "literal:self",
9+
"record": {
10+
"type": "object",
11+
"required": ["clientId", "createdAt"],
12+
"properties": {
13+
"clientId": {
14+
"type": "string",
15+
"format": "uri",
16+
"description": "The OAuth `client_id` URL uniquely identifying the application that created this account. This is the client metadata document URL used during the ATProto OAuth flow (e.g. 'https://app.certified.one/oauth/client-metadata.json')."
17+
},
18+
"createdAt": {
19+
"type": "string",
20+
"format": "datetime",
21+
"description": "Client-declared timestamp when this record was originally created."
22+
},
23+
"signatures": {
24+
"type": "ref",
25+
"ref": "app.certified.signature.defs#list",
26+
"description": "Optional cryptographic signatures attesting to this record's content."
27+
}
28+
}
29+
}
30+
}
31+
}
32+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { describe, it, expect } from "vitest";
2+
import { validate, ids } from "../generated/lexicons.js";
3+
import * as CreatedVia from "../generated/types/app/certified/actor/createdVia.js";
4+
5+
describe("app.certified.actor.createdVia", () => {
6+
it("should accept a minimal valid record (required clientId and createdAt)", () => {
7+
const result = CreatedVia.validateMain({
8+
$type: ids.AppCertifiedActorCreatedVia,
9+
clientId: "https://app.certified.one/oauth/client-metadata.json",
10+
createdAt: "2024-01-01T00:00:00.000Z",
11+
});
12+
expect(result.success).toBe(true);
13+
if (result.success) {
14+
expect(result.value.clientId).toBe(
15+
"https://app.certified.one/oauth/client-metadata.json",
16+
);
17+
}
18+
});
19+
20+
it("should accept a record with inline signatures", () => {
21+
const result = CreatedVia.validateMain({
22+
$type: ids.AppCertifiedActorCreatedVia,
23+
clientId: "https://app.certified.one/oauth/client-metadata.json",
24+
createdAt: "2024-01-01T00:00:00.000Z",
25+
signatures: [
26+
{
27+
$type: "app.certified.signature.defs#inline",
28+
signature: new Uint8Array([1, 2, 3]),
29+
key: "did:plc:abc123#atproto",
30+
},
31+
],
32+
});
33+
expect(result.success).toBe(true);
34+
});
35+
36+
it("should reject a record missing required clientId", () => {
37+
const result = validate(
38+
{ createdAt: "2024-01-01T00:00:00.000Z" },
39+
ids.AppCertifiedActorCreatedVia,
40+
"main",
41+
false,
42+
);
43+
expect(result.success).toBe(false);
44+
if (!result.success) {
45+
expect(result.error).toBeDefined();
46+
}
47+
});
48+
49+
it("should reject a record missing required createdAt", () => {
50+
const result = validate(
51+
{ clientId: "https://app.certified.one/oauth/client-metadata.json" },
52+
ids.AppCertifiedActorCreatedVia,
53+
"main",
54+
false,
55+
);
56+
expect(result.success).toBe(false);
57+
});
58+
59+
it("should reject a record with a non-uri clientId", () => {
60+
const result = validate(
61+
{
62+
clientId: "not a uri",
63+
createdAt: "2024-01-01T00:00:00.000Z",
64+
},
65+
ids.AppCertifiedActorCreatedVia,
66+
"main",
67+
false,
68+
);
69+
expect(result.success).toBe(false);
70+
});
71+
72+
it("should reject a record with an invalid createdAt format", () => {
73+
const result = validate(
74+
{
75+
clientId: "https://app.certified.one/oauth/client-metadata.json",
76+
createdAt: "not-a-datetime",
77+
},
78+
ids.AppCertifiedActorCreatedVia,
79+
"main",
80+
false,
81+
);
82+
expect(result.success).toBe(false);
83+
});
84+
});

0 commit comments

Comments
 (0)