@oxyhq/contracts 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -85,14 +85,6 @@ export interface DidDocument {
85
85
  service: DidService[];
86
86
  }
87
87
  export declare const didDocumentSchema: z.ZodType<DidDocument>;
88
- /**
89
- * The category of a signed record. v1 only ever carried `identity` / `profile`
90
- * (already in production); v2 widens the union with the civic record types
91
- * (reputation attestations, real-life / peer validations, personhood vouches,
92
- * verifiable credentials) and the user-node registration record. The signing
93
- * input includes `type`, so this union is part of the signed bytes.
94
- */
95
- export type SignedRecordType = 'identity' | 'profile' | 'reputation_attestation' | 'real_life_attestation' | 'validation_verdict' | 'personhood_vouch' | 'credential' | 'node';
96
88
  /**
97
89
  * A signed record envelope. `record` is the arbitrary payload; the signing
98
90
  * input is the canonical-JSON of every envelope field EXCEPT `publicKey` and
@@ -100,6 +92,18 @@ export type SignedRecordType = 'identity' | 'profile' | 'reputation_attestation'
100
92
  * and the signer's DID — equal for self-issued records, `OXY_DID` for a
101
93
  * custodial provenance attestation). `issuedAt` is epoch milliseconds.
102
94
  *
95
+ * ## `type` — open by design
96
+ *
97
+ * `type` is an OPEN, non-empty string: it is the application-defined category of
98
+ * the record (e.g. Oxy's `identity`/`profile`/civic types, or `app.mention.*`'s
99
+ * `app_record`). The base envelope is app-agnostic, so it cannot enumerate every
100
+ * app's record categories — each app re-narrows `type` to its own closed set on
101
+ * the way INTO its own store (Oxy via {@link OxySignedRecordType}; an app via
102
+ * its own constant). Widening `type` from a closed enum to a string is
103
+ * canonical-bytes-safe: {@link signedRecordSigningInput} serializes `type` as the
104
+ * same JSON string either way, so every record already signed in production
105
+ * verifies byte-for-byte.
106
+ *
103
107
  * ## Versioning
104
108
  *
105
109
  * - **v1** is the original shape (`{version, type, subject, issuer, record,
@@ -121,7 +125,8 @@ export type SignedRecordType = 'identity' | 'profile' | 'reputation_attestation'
121
125
  */
122
126
  export interface SignedRecordEnvelope {
123
127
  version: 1 | 2;
124
- type: SignedRecordType;
128
+ /** App-defined record category (open string); each store re-narrows it. */
129
+ type: string;
125
130
  subject: string;
126
131
  issuer: string;
127
132
  record: Record<string, unknown>;
@@ -18,7 +18,11 @@ export type { FedcmTokenPayload, } from './fedcmToken';
18
18
  export { recommendationExcludeTypeSchema, recommendationBoostSchema, recommendationSignalWeightsSchema, recommendationRequestSchema, recommendationCountSchema, recommendationItemSchema, recommendationResponseSchema, appEndorsementInputSchema, appInterestInputSchema, appUserSignalIngestSchema, } from './recommendations';
19
19
  export type { RecommendationExcludeType, RecommendationBoost, RecommendationSignalWeights, RecommendationRequest, RecommendationCount, RecommendationItem, RecommendationResponse, AppEndorsementInput, AppInterestInput, AppUserSignalIngest, } from './recommendations';
20
20
  export { verificationMethodSchema, didServiceSchema, didDocumentSchema, signedRecordEnvelopeSchema, verifiedDomainSchema, domainVerificationRequestSchema, domainVerificationInstructionsSchema, authMethodEntrySchema, authMethodsResponseSchema, exportAttestationSchema, exportBundleSchema, } from './identity';
21
- export type { VerificationMethod, DidService, DidDocument, SignedRecordEnvelope, SignedRecordType, VerifiedDomain, DomainVerificationRequest, DomainVerificationInstructions, AuthMethodEntry, AuthMethodsResponse, ExportAttestation, ExportBundle, } from './identity';
21
+ export type { VerificationMethod, DidService, DidDocument, SignedRecordEnvelope, VerifiedDomain, DomainVerificationRequest, DomainVerificationInstructions, AuthMethodEntry, AuthMethodsResponse, ExportAttestation, ExportBundle, } from './identity';
22
+ export { oxySignedRecordTypeSchema, } from './oxyRecordTypes';
23
+ export type { OxySignedRecordType, } from './oxyRecordTypes';
24
+ export { chainHeadResponseSchema, logPageResponseSchema, } from './protocol';
25
+ export type { LexiconRecord, ChainHeadResponse, LogPageResponse, } from './protocol';
22
26
  export { publicCardSchema, signedPublicCardSchema, realLifeAttestationRecordSchema, realLifeAttestationResultSchema, validationVerdictRecordSchema, validationOpenRequestSchema, validationOpenResultSchema, validationRequestSummarySchema, validationVoteResultSchema, personhoodVouchRecordSchema, personhoodBreakdownSchema, personhoodStatusResultSchema, vouchResultSchema, credentialRecordSchema, verifiableCredentialResponseSchema, credentialIssueResultSchema, credentialListResultSchema, credentialVerifyResultSchema, } from './civic';
23
27
  export type { CardTrustTier, PersonhoodStatus, PublicCard, SignedPublicCard, RealLifeAttestationRecord, RealLifeAttestationResult, ValidationVerdict, ValidationRequestStatus, ValidationVerdictRecord, ValidationOpenRequest, ValidationOpenResult, ValidationRequestSummary, ValidationVoteResult, PersonhoodVouchRecord, PersonhoodBreakdown, PersonhoodStatusResult, VouchResult, CredentialStatus, CredentialRecord, VerifiableCredentialResponse, CredentialIssueResult, CredentialListResult, CredentialVerifyResult, } from './civic';
24
28
  export { linkPreviewSchema, linkPreviewBatchRequestSchema, linkPreviewBatchResponseSchema, linkPreviewResponseSchema, } from './links';
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Oxy-scoped signed-record types.
3
+ *
4
+ * The base `signedRecordEnvelopeSchema` (`./identity`) now treats `type` as an
5
+ * OPEN, non-empty string so ANY Oxy app may define its own record categories
6
+ * (e.g. `app.mention.*`'s `app_record`) on the shared envelope grammar. The Oxy
7
+ * identity/civic/node STORE, however, accepts ONLY the closed set of categories
8
+ * it knows how to verify and materialize — this module is that closed set.
9
+ *
10
+ * `oxySignedRecordTypeSchema` is the runtime gate the Oxy store re-narrows with
11
+ * (the API's `verifyEnvelope` rejects any `type` outside it; the Mongoose
12
+ * `SignedRecord.type` enum is derived from `.options`); `OxySignedRecordType` is
13
+ * the matching compile-time union the SDK identity/civic mixins type against.
14
+ *
15
+ * The signing input INCLUDES `type`, so this set is part of the signed bytes —
16
+ * a record cannot have its category swapped after signing.
17
+ *
18
+ * v1 only ever carried `identity` / `profile` (already in production); v2 added
19
+ * the civic record types (reputation attestations, real-life / peer validations,
20
+ * personhood vouches, verifiable credentials) and the user-node registration
21
+ * record. Every value here is an Oxy `app.oxy.*` (or legacy v1) category — an
22
+ * app's own `type` (e.g. `app_record`) is intentionally NOT in this set and is
23
+ * rejected by the Oxy store.
24
+ *
25
+ * Platform-agnostic — zod only, no react/react-native/expo, ESM-safe.
26
+ */
27
+ import { z } from 'zod';
28
+ export declare const oxySignedRecordTypeSchema: z.ZodEnum<["identity", "profile", "reputation_attestation", "real_life_attestation", "validation_verdict", "personhood_vouch", "credential", "node"]>;
29
+ /**
30
+ * The closed set of record categories the Oxy identity/civic/node store accepts.
31
+ * The base envelope `type` is an open string; this is what the Oxy store
32
+ * re-narrows it to.
33
+ */
34
+ export type OxySignedRecordType = z.infer<typeof oxySignedRecordTypeSchema>;
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Generic "Oxy Protocol" record surface — the app-agnostic conventions every app
3
+ * follows to decentralize its own content on the shared signed-record substrate.
4
+ *
5
+ * The base `signedRecordEnvelopeSchema` (`./identity`) is the WIRE grammar: a
6
+ * signed envelope whose `type` is an open string and whose `record` is an opaque
7
+ * `Record<string, unknown>`. An app layers its own LEXICON on top of that
8
+ * grammar — a typed projection of the `record` payload, addressed by an
9
+ * AtProto-style `(collection, rkey)` key — WITHOUT forking the envelope schema.
10
+ *
11
+ * ## Recipe — defining an app lexicon record
12
+ *
13
+ * For each record kind an app wants to publish:
14
+ *
15
+ * 1. Define the `record` PAYLOAD schema as a `z.ZodType<XPayload>` (e.g.
16
+ * `app.mention.feed.post` → `mentionPostRecordSchema: z.ZodType<MentionPost>`).
17
+ * This validates ONLY the inner `record`, not the envelope.
18
+ * 2. Declare the `collection` NSID as a constant (e.g.
19
+ * `export const MENTION_POST_COLLECTION = 'app.mention.feed.post'`).
20
+ * 3. Reuse the UNCHANGED {@link signedRecordEnvelopeSchema} for the envelope. The
21
+ * base treats `record` as `z.record(z.unknown())`, so the app validates the
22
+ * envelope with the base schema first, then parses `envelope.record` with its
23
+ * own payload schema. {@link LexiconRecord} is the typed projection that pairs
24
+ * the `(collection, rkey)` key with the parsed payload.
25
+ *
26
+ * The Oxy civic contracts (`./civic`) already follow this convention implicitly:
27
+ * each civic record (`real_life_attestation`, `personhood_vouch`, `credential`,
28
+ * …) ships a `record`-payload schema and is carried by the base envelope.
29
+ *
30
+ * ## Chain-wire shapes
31
+ *
32
+ * {@link ChainHeadResponse} and {@link LogPageResponse} are the shared response
33
+ * shapes every chain store exposes (Oxy's `GET /identity/records/:userId/chain/head`,
34
+ * `GET /identity/head/:userId`, `GET /identity/log/:userId`, and any app node's
35
+ * equivalents). They are defined ONCE here so producers and consumers (the API
36
+ * handlers, the SDK identity/nodes mixins, app nodes) cannot drift.
37
+ *
38
+ * Platform-agnostic — zod only, no react/react-native/expo, ESM-safe.
39
+ */
40
+ import { z } from 'zod';
41
+ import { type SignedRecordEnvelope } from './identity';
42
+ /**
43
+ * The typed projection of a signed envelope's `record` payload, addressed by an
44
+ * AtProto-style `(collection, rkey)` key.
45
+ *
46
+ * - `collection` is the lexicon NSID (e.g. `app.mention.feed.post`) — the
47
+ * envelope's `collection` field.
48
+ * - `rkey` is the record key within the collection (e.g. a post id) — the
49
+ * envelope's `rkey` field.
50
+ * - `record` is the app-typed payload (`TPayload`) the app validated out of the
51
+ * envelope's opaque `record`.
52
+ *
53
+ * This is a COMPILE-TIME convenience only: the wire shape is the base envelope.
54
+ * An app builds a `LexiconRecord<TPayload>` from a verified envelope by parsing
55
+ * `envelope.record` with its own `z.ZodType<TPayload>` payload schema.
56
+ */
57
+ export interface LexiconRecord<TPayload> {
58
+ collection: string;
59
+ rkey: string;
60
+ record: TPayload;
61
+ }
62
+ /**
63
+ * The current head of a subject's per-subject hash chain.
64
+ *
65
+ * `headRecordId` is the content address of the latest record (or `null` when the
66
+ * subject has no chain yet); `seq` is its sequence number (`-1` when there is no
67
+ * chain — so the next record's coordinates are always `seq: head.seq + 1`,
68
+ * genesis `0`, and `prev: head.headRecordId`, genesis `null`); `recordCount` is
69
+ * the total number of records on the chain.
70
+ */
71
+ export interface ChainHeadResponse {
72
+ headRecordId: string | null;
73
+ seq: number;
74
+ recordCount: number;
75
+ }
76
+ export declare const chainHeadResponseSchema: z.ZodType<ChainHeadResponse>;
77
+ /**
78
+ * An ordered page of a subject's verified signed-record chain — the FULL
79
+ * envelopes (so a node or any verifier re-checks them independently). `count` is
80
+ * `records.length`, echoed for convenience.
81
+ */
82
+ export interface LogPageResponse {
83
+ records: SignedRecordEnvelope[];
84
+ count: number;
85
+ }
86
+ export declare const logPageResponseSchema: z.ZodType<LogPageResponse>;
@@ -37,7 +37,10 @@ import { z } from 'zod';
37
37
  * - `first` / `last` default to `''` in Mongo, so they are optional on the wire.
38
38
  * - `full` is a Mongoose virtual — absent unless the query materialised
39
39
  * virtuals or the serializer composed it.
40
- * - `displayName` is the required canonical app-facing display string.
40
+ * - `displayName` is the canonical app-facing display string when present.
41
+ * It is OPTIONAL on the wire: the API still synthesizes a default today, but
42
+ * the contract no longer guarantees it, so consumers fall back to a handle
43
+ * (e.g. `getNormalizedUserHandle`) when it is absent.
41
44
  *
42
45
  * This is declared as an explicit `interface` rather than being inferred from
43
46
  * the runtime schema via `z.infer<typeof userNameSchema>`. Inferring it produced
@@ -47,7 +50,7 @@ import { z } from 'zod';
47
50
  * Under a consumer's `moduleResolution: "node"` (node10), that chain does not
48
51
  * always resolve, so `name.displayName` silently widened to `{}` and broke the
49
52
  * "render `name.displayName` directly" contract at the type level. An explicit
50
- * interface emits `displayName: string` literally and survives BOTH `node` and
53
+ * interface emits `displayName?: string` literally and survives BOTH `node` and
51
54
  * `bundler` resolution. The index signature preserves the passthrough behaviour
52
55
  * (additive name fields are tolerated without a coordinated contract bump).
53
56
  */
@@ -55,16 +58,18 @@ export interface UserNameResponse {
55
58
  first?: string;
56
59
  last?: string;
57
60
  full?: string;
58
- /** Required canonical display string — render this directly. */
59
- displayName: string;
61
+ /** Canonical display string when present — render this directly. */
62
+ displayName?: string;
60
63
  [key: string]: unknown;
61
64
  }
62
65
  export declare const userNameSchema: z.ZodType<UserNameResponse>;
63
66
  /**
64
67
  * The canonical user object emitted by `formatUserResponse`.
65
68
  *
66
- * `id` and `name.displayName` are guaranteed on formatted user DTOs. The rest
67
- * is forwarded from the user document and may be absent depending on the query's
69
+ * `id` is present on formatted user DTOs. `name.displayName` is OPTIONAL on the
70
+ * contract the API still synthesizes a default today, but consumers must not
71
+ * assume it is present and should fall back to a handle when it is absent. The
72
+ * rest is forwarded from the user document and may be absent depending on the query's
68
73
  * `.select(...)`/`.lean()` projection. Both `id` and `_id` are accepted because
69
74
  * some raw-document responses carry `_id` instead of `id`; resolve the
70
75
  * identifier with {@link resolveUserId}.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxyhq/contracts",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "description": "OxyHQ API contracts — single source of truth for request/response Zod schemas and inferred types, shared by the backend and the client SDKs",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",