@oxyhq/contracts 0.3.0 → 0.4.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.
- package/dist/cjs/.tsbuildinfo +1 -1
- package/dist/cjs/civic.js +163 -0
- package/dist/cjs/identity.js +84 -3
- package/dist/cjs/index.js +23 -1
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/civic.js +160 -0
- package/dist/esm/identity.js +84 -3
- package/dist/esm/index.js +5 -0
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/civic.d.ts +338 -0
- package/dist/types/identity.d.ts +37 -2
- package/dist/types/index.d.ts +3 -1
- package/dist/types/sessionStatus.d.ts +2 -2
- package/dist/types/userResponse.d.ts +22 -22
- package/package.json +1 -1
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Civic / Commons API contracts (Fase 1 — DNI + crypto-owned reputation).
|
|
3
|
+
*
|
|
4
|
+
* SINGLE SOURCE OF TRUTH for the wire shape of the public "DNI" card a Commons
|
|
5
|
+
* user shows (and others scan): the user's DID, display identity, trust tier,
|
|
6
|
+
* personhood status, verified domains, and credential badges — sealed with an
|
|
7
|
+
* Oxy custodial attestation so a scanner can verify it OFFLINE against the Oxy
|
|
8
|
+
* public key (the same `ES256K-DER-SHA256` scheme as the signed data export).
|
|
9
|
+
*
|
|
10
|
+
* The QR encodes ONLY the DID (`oxydni://card?did=…`) — never trust data — so a
|
|
11
|
+
* card cannot be spoofed by crafting a QR; the scanner resolves the signed card
|
|
12
|
+
* server-side and verifies the Oxy signature. The attestation is computed over
|
|
13
|
+
* the canonical-JSON of the `card` object, so a consumer re-canonicalizes the
|
|
14
|
+
* card it received and verifies `attestation.signature` against
|
|
15
|
+
* `attestation.publicKey` (which MUST be a current verification method of the
|
|
16
|
+
* Oxy DID).
|
|
17
|
+
*
|
|
18
|
+
* Explicit-`interface` exports (PublicCard, SignedPublicCard) follow the same
|
|
19
|
+
* node-resolution rationale as `UserNameResponse` / the identity contracts: a
|
|
20
|
+
* nested `z.infer<>` can degrade to `{}` under a consumer's
|
|
21
|
+
* `moduleResolution: "node"`, so the load-bearing shapes are declared as literal
|
|
22
|
+
* interfaces and the runtime schemas are annotated `z.ZodType<Interface>`.
|
|
23
|
+
*
|
|
24
|
+
* The `attestation` reuses the export-bundle `ExportAttestation` shape from
|
|
25
|
+
* `./identity` (mirrored, not duplicated): `{ issuer, publicKey, alg, signature,
|
|
26
|
+
* signedAt }`. It is `null` ONLY when the Oxy signing key is unconfigured (dev).
|
|
27
|
+
*
|
|
28
|
+
* Platform-agnostic — zod only, no react/react-native/expo, ESM-safe.
|
|
29
|
+
*/
|
|
30
|
+
import { z } from 'zod';
|
|
31
|
+
import { exportAttestationSchema } from './identity.js';
|
|
32
|
+
export const publicCardSchema = z.object({
|
|
33
|
+
did: z.string(),
|
|
34
|
+
userId: z.string(),
|
|
35
|
+
name: z.string(),
|
|
36
|
+
username: z.string().optional(),
|
|
37
|
+
avatarUrl: z.string().optional(),
|
|
38
|
+
trustTier: z.enum(['restricted', 'new', 'trusted', 'high_trust', 'verified']),
|
|
39
|
+
personhoodStatus: z.enum(['unverified', 'pending', 'verified']),
|
|
40
|
+
verifiedDomains: z.array(z.string()),
|
|
41
|
+
credentialBadges: z.array(z.string()),
|
|
42
|
+
issuedAt: z.number(),
|
|
43
|
+
});
|
|
44
|
+
export const signedPublicCardSchema = z.object({
|
|
45
|
+
card: publicCardSchema,
|
|
46
|
+
attestation: exportAttestationSchema.nullable(),
|
|
47
|
+
});
|
|
48
|
+
export const realLifeAttestationRecordSchema = z.object({
|
|
49
|
+
about: z.string(),
|
|
50
|
+
context: z.string(),
|
|
51
|
+
nonce: z.string(),
|
|
52
|
+
exp: z.number(),
|
|
53
|
+
geohash: z.string().optional(),
|
|
54
|
+
biometricOk: z.boolean().optional(),
|
|
55
|
+
});
|
|
56
|
+
export const realLifeAttestationResultSchema = z.object({
|
|
57
|
+
accepted: z.literal(true),
|
|
58
|
+
recordId: z.string(),
|
|
59
|
+
subjectUserId: z.string(),
|
|
60
|
+
attestorUserId: z.string(),
|
|
61
|
+
points: z.number(),
|
|
62
|
+
});
|
|
63
|
+
export const validationVerdictRecordSchema = z.object({
|
|
64
|
+
requestId: z.string(),
|
|
65
|
+
payloadHash: z.string(),
|
|
66
|
+
verdict: z.enum(['valid', 'invalid', 'abstain']),
|
|
67
|
+
});
|
|
68
|
+
/** Request body for opening a validation request (`POST /civic/validations`). */
|
|
69
|
+
export const validationOpenRequestSchema = z.object({
|
|
70
|
+
subjectUserId: z.string(),
|
|
71
|
+
actionType: z.string().min(1),
|
|
72
|
+
sourceActionId: z.string().min(1),
|
|
73
|
+
payload: z.record(z.unknown()),
|
|
74
|
+
highValue: z.boolean().optional(),
|
|
75
|
+
});
|
|
76
|
+
export const validationOpenResultSchema = z.object({
|
|
77
|
+
requestId: z.string(),
|
|
78
|
+
selectedValidatorCount: z.number(),
|
|
79
|
+
expiresAt: z.string(),
|
|
80
|
+
});
|
|
81
|
+
export const validationRequestSummarySchema = z.object({
|
|
82
|
+
id: z.string(),
|
|
83
|
+
subjectUserId: z.string(),
|
|
84
|
+
actionType: z.string(),
|
|
85
|
+
payload: z.record(z.unknown()),
|
|
86
|
+
payloadHash: z.string(),
|
|
87
|
+
status: z.enum(['pending', 'quorum_met', 'validated', 'rejected', 'expired']),
|
|
88
|
+
highValue: z.boolean(),
|
|
89
|
+
expiresAt: z.string(),
|
|
90
|
+
});
|
|
91
|
+
export const validationVoteResultSchema = z.object({
|
|
92
|
+
recorded: z.literal(true),
|
|
93
|
+
requestId: z.string(),
|
|
94
|
+
verdict: z.enum(['valid', 'invalid', 'abstain']),
|
|
95
|
+
status: z.enum(['pending', 'quorum_met', 'validated', 'rejected', 'expired']),
|
|
96
|
+
});
|
|
97
|
+
export const personhoodVouchRecordSchema = z.object({
|
|
98
|
+
about: z.string(),
|
|
99
|
+
context: z.string().optional(),
|
|
100
|
+
stake: z.number().optional(),
|
|
101
|
+
});
|
|
102
|
+
export const personhoodBreakdownSchema = z.object({
|
|
103
|
+
vouchSignal: z.number(),
|
|
104
|
+
realLifeSignal: z.number(),
|
|
105
|
+
biometricSignal: z.number(),
|
|
106
|
+
evidence: z.number(),
|
|
107
|
+
sybilPenalty: z.number(),
|
|
108
|
+
seed: z.boolean(),
|
|
109
|
+
});
|
|
110
|
+
export const personhoodStatusResultSchema = z.object({
|
|
111
|
+
userId: z.string(),
|
|
112
|
+
score: z.number(),
|
|
113
|
+
isRealPerson: z.boolean(),
|
|
114
|
+
vouchCount: z.number(),
|
|
115
|
+
realLifeCount: z.number(),
|
|
116
|
+
biometricBound: z.boolean(),
|
|
117
|
+
sybilPenalty: z.number(),
|
|
118
|
+
breakdown: personhoodBreakdownSchema.nullable(),
|
|
119
|
+
updatedAt: z.string().nullable(),
|
|
120
|
+
});
|
|
121
|
+
export const vouchResultSchema = z.object({
|
|
122
|
+
accepted: z.literal(true),
|
|
123
|
+
recordId: z.string(),
|
|
124
|
+
subjectUserId: z.string(),
|
|
125
|
+
voucherUserId: z.string(),
|
|
126
|
+
stakeAmount: z.number(),
|
|
127
|
+
points: z.number(),
|
|
128
|
+
});
|
|
129
|
+
export const credentialRecordSchema = z.object({
|
|
130
|
+
about: z.string(),
|
|
131
|
+
types: z.array(z.string().min(1)).min(1),
|
|
132
|
+
claims: z.record(z.unknown()),
|
|
133
|
+
expiresAt: z.number().optional(),
|
|
134
|
+
});
|
|
135
|
+
export const verifiableCredentialResponseSchema = z.object({
|
|
136
|
+
id: z.string(),
|
|
137
|
+
recordId: z.string(),
|
|
138
|
+
holderUserId: z.string(),
|
|
139
|
+
holderDid: z.string(),
|
|
140
|
+
issuerUserId: z.string().optional(),
|
|
141
|
+
issuerDid: z.string(),
|
|
142
|
+
types: z.array(z.string()),
|
|
143
|
+
claims: z.record(z.unknown()),
|
|
144
|
+
status: z.enum(['active', 'revoked', 'expired']),
|
|
145
|
+
issuedAt: z.number(),
|
|
146
|
+
expiresAt: z.number().optional(),
|
|
147
|
+
revokedAt: z.number().optional(),
|
|
148
|
+
});
|
|
149
|
+
export const credentialIssueResultSchema = z.object({
|
|
150
|
+
accepted: z.literal(true),
|
|
151
|
+
credential: verifiableCredentialResponseSchema,
|
|
152
|
+
});
|
|
153
|
+
export const credentialListResultSchema = z.object({
|
|
154
|
+
credentials: z.array(verifiableCredentialResponseSchema),
|
|
155
|
+
});
|
|
156
|
+
export const credentialVerifyResultSchema = z.object({
|
|
157
|
+
valid: z.boolean(),
|
|
158
|
+
reason: z.string().optional(),
|
|
159
|
+
credential: verifiableCredentialResponseSchema.nullable(),
|
|
160
|
+
});
|
package/dist/esm/identity.js
CHANGED
|
@@ -59,16 +59,97 @@ export const didDocumentSchema = z.object({
|
|
|
59
59
|
alsoKnownAs: z.array(z.string()),
|
|
60
60
|
service: z.array(didServiceSchema),
|
|
61
61
|
});
|
|
62
|
-
export const signedRecordEnvelopeSchema = z
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
export const signedRecordEnvelopeSchema = z
|
|
63
|
+
.object({
|
|
64
|
+
version: z.union([z.literal(1), z.literal(2)]),
|
|
65
|
+
type: z.enum([
|
|
66
|
+
'identity',
|
|
67
|
+
'profile',
|
|
68
|
+
'reputation_attestation',
|
|
69
|
+
'real_life_attestation',
|
|
70
|
+
'validation_verdict',
|
|
71
|
+
'personhood_vouch',
|
|
72
|
+
'credential',
|
|
73
|
+
'node',
|
|
74
|
+
]),
|
|
65
75
|
subject: z.string(),
|
|
66
76
|
issuer: z.string(),
|
|
67
77
|
record: z.record(z.unknown()),
|
|
68
78
|
issuedAt: z.number(),
|
|
79
|
+
seq: z.number().int().nonnegative().optional(),
|
|
80
|
+
prev: z.string().nullable().optional(),
|
|
81
|
+
collection: z.string().min(1).optional(),
|
|
82
|
+
rkey: z.string().min(1).optional(),
|
|
69
83
|
publicKey: z.string(),
|
|
70
84
|
alg: z.literal('ES256K-DER-SHA256'),
|
|
71
85
|
signature: z.string(),
|
|
86
|
+
})
|
|
87
|
+
.superRefine((env, ctx) => {
|
|
88
|
+
if (env.version === 2) {
|
|
89
|
+
// v2 REQUIRES the hash-chain fields. `prev` may be `null` at genesis,
|
|
90
|
+
// but the key must be present (it is part of the signed bytes), so we
|
|
91
|
+
// reject only when it is entirely absent.
|
|
92
|
+
if (typeof env.seq !== 'number') {
|
|
93
|
+
ctx.addIssue({
|
|
94
|
+
code: z.ZodIssueCode.custom,
|
|
95
|
+
message: 'v2 envelope requires `seq`',
|
|
96
|
+
path: ['seq'],
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
if (env.prev === undefined) {
|
|
100
|
+
ctx.addIssue({
|
|
101
|
+
code: z.ZodIssueCode.custom,
|
|
102
|
+
message: 'v2 envelope requires `prev` (use `null` at genesis)',
|
|
103
|
+
path: ['prev'],
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
if (typeof env.collection !== 'string') {
|
|
107
|
+
ctx.addIssue({
|
|
108
|
+
code: z.ZodIssueCode.custom,
|
|
109
|
+
message: 'v2 envelope requires `collection`',
|
|
110
|
+
path: ['collection'],
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
if (typeof env.rkey !== 'string') {
|
|
114
|
+
ctx.addIssue({
|
|
115
|
+
code: z.ZodIssueCode.custom,
|
|
116
|
+
message: 'v2 envelope requires `rkey`',
|
|
117
|
+
path: ['rkey'],
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
// v1 FORBIDS the v2 chain fields entirely, so a legacy envelope keeps
|
|
123
|
+
// its exact byte shape and cannot smuggle unsigned chain metadata.
|
|
124
|
+
if (env.seq !== undefined) {
|
|
125
|
+
ctx.addIssue({
|
|
126
|
+
code: z.ZodIssueCode.custom,
|
|
127
|
+
message: 'v1 envelope must not carry `seq`',
|
|
128
|
+
path: ['seq'],
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
if (env.prev !== undefined) {
|
|
132
|
+
ctx.addIssue({
|
|
133
|
+
code: z.ZodIssueCode.custom,
|
|
134
|
+
message: 'v1 envelope must not carry `prev`',
|
|
135
|
+
path: ['prev'],
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
if (env.collection !== undefined) {
|
|
139
|
+
ctx.addIssue({
|
|
140
|
+
code: z.ZodIssueCode.custom,
|
|
141
|
+
message: 'v1 envelope must not carry `collection`',
|
|
142
|
+
path: ['collection'],
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
if (env.rkey !== undefined) {
|
|
146
|
+
ctx.addIssue({
|
|
147
|
+
code: z.ZodIssueCode.custom,
|
|
148
|
+
message: 'v1 envelope must not carry `rkey`',
|
|
149
|
+
path: ['rkey'],
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}
|
|
72
153
|
});
|
|
73
154
|
export const verifiedDomainSchema = z.object({
|
|
74
155
|
domain: z.string(),
|
package/dist/esm/index.js
CHANGED
|
@@ -26,3 +26,8 @@ recommendationExcludeTypeSchema, recommendationBoostSchema, recommendationSignal
|
|
|
26
26
|
export {
|
|
27
27
|
// Schemas
|
|
28
28
|
verificationMethodSchema, didServiceSchema, didDocumentSchema, signedRecordEnvelopeSchema, verifiedDomainSchema, domainVerificationRequestSchema, domainVerificationInstructionsSchema, authMethodEntrySchema, authMethodsResponseSchema, exportAttestationSchema, exportBundleSchema, } from './identity.js';
|
|
29
|
+
export {
|
|
30
|
+
// Schemas
|
|
31
|
+
publicCardSchema, signedPublicCardSchema, realLifeAttestationRecordSchema, realLifeAttestationResultSchema, validationVerdictRecordSchema, validationOpenRequestSchema, validationOpenResultSchema, validationRequestSummarySchema, validationVoteResultSchema, personhoodVouchRecordSchema, personhoodBreakdownSchema, personhoodStatusResultSchema, vouchResultSchema,
|
|
32
|
+
// Verifiable Credentials (Fase 4 — NEW)
|
|
33
|
+
credentialRecordSchema, verifiableCredentialResponseSchema, credentialIssueResultSchema, credentialListResultSchema, credentialVerifyResultSchema, } from './civic.js';
|