@hraness/peopleblade 0.3.5 → 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/README.md +85 -10
- package/THIRD_PARTY_NOTICES.md +89 -2
- package/dist/cli.ts +1 -1
- package/dist/cloud-sync-client.js +538 -15
- package/dist/migrations/018_exposure.sql +88 -0
- package/dist/migrations/019_exposure_attestations.sql +44 -0
- package/dist/migrations/020_exposure_attestation_sends.sql +46 -0
- package/dist/migrations/021_identity_component_projection.sql +212 -0
- package/dist/peopleblade.js +14568 -5285
- package/package.json +1 -1
|
@@ -3,8 +3,496 @@
|
|
|
3
3
|
import { Effect as Effect3 } from "effect";
|
|
4
4
|
|
|
5
5
|
// src/lib/contracts.ts
|
|
6
|
-
import { createHash } from "crypto";
|
|
6
|
+
import { createHash as createHash2 } from "crypto";
|
|
7
|
+
|
|
8
|
+
// node_modules/@hraness/oh/dist/index.js
|
|
9
|
+
import { createHash, randomBytes } from "crypto";
|
|
10
|
+
|
|
11
|
+
class OhValidationError extends Error {
|
|
12
|
+
code;
|
|
13
|
+
path;
|
|
14
|
+
constructor(code, path, message) {
|
|
15
|
+
super(`${path}: ${message}`);
|
|
16
|
+
this.name = "OhValidationError";
|
|
17
|
+
this.code = code;
|
|
18
|
+
this.path = path;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function isPlainRecord(value) {
|
|
22
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
23
|
+
return false;
|
|
24
|
+
const prototype = Object.getPrototypeOf(value);
|
|
25
|
+
return prototype === Object.prototype || prototype === null;
|
|
26
|
+
}
|
|
27
|
+
function hasExactKeys(value, keys) {
|
|
28
|
+
const actual = Object.keys(value);
|
|
29
|
+
return actual.length === keys.length && keys.every((key) => Object.hasOwn(value, key));
|
|
30
|
+
}
|
|
31
|
+
function assertUnicodeScalarString(value, path) {
|
|
32
|
+
for (let index = 0;index < value.length; index += 1) {
|
|
33
|
+
const code = value.charCodeAt(index);
|
|
34
|
+
if (code >= 55296 && code <= 56319) {
|
|
35
|
+
const next = value.charCodeAt(index + 1);
|
|
36
|
+
if (!(next >= 56320 && next <= 57343)) {
|
|
37
|
+
throw new OhValidationError("invalid-unicode", path, "contains an unpaired surrogate");
|
|
38
|
+
}
|
|
39
|
+
index += 1;
|
|
40
|
+
} else if (code >= 56320 && code <= 57343) {
|
|
41
|
+
throw new OhValidationError("invalid-unicode", path, "contains an unpaired surrogate");
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function encodeCanonical(value, path, ancestors) {
|
|
46
|
+
if (value === null || typeof value === "boolean")
|
|
47
|
+
return JSON.stringify(value);
|
|
48
|
+
if (typeof value === "string") {
|
|
49
|
+
assertUnicodeScalarString(value, path);
|
|
50
|
+
return JSON.stringify(value);
|
|
51
|
+
}
|
|
52
|
+
if (typeof value === "number") {
|
|
53
|
+
if (!Number.isFinite(value)) {
|
|
54
|
+
throw new OhValidationError("non-json-number", path, "must be finite");
|
|
55
|
+
}
|
|
56
|
+
if (Object.is(value, -0)) {
|
|
57
|
+
throw new OhValidationError("noncanonical-number", path, "negative zero is not canonical");
|
|
58
|
+
}
|
|
59
|
+
return JSON.stringify(value);
|
|
60
|
+
}
|
|
61
|
+
if (typeof value !== "object" || value === null) {
|
|
62
|
+
throw new OhValidationError("non-json-value", path, `cannot encode ${typeof value}`);
|
|
63
|
+
}
|
|
64
|
+
if (ancestors.has(value)) {
|
|
65
|
+
throw new OhValidationError("cycle", path, "contains a cycle");
|
|
66
|
+
}
|
|
67
|
+
ancestors.add(value);
|
|
68
|
+
try {
|
|
69
|
+
if (Array.isArray(value)) {
|
|
70
|
+
const encoded = [];
|
|
71
|
+
for (let index = 0;index < value.length; index += 1) {
|
|
72
|
+
if (!Object.hasOwn(value, index)) {
|
|
73
|
+
throw new OhValidationError("sparse-array", `${path}[${index}]`, "must not contain holes");
|
|
74
|
+
}
|
|
75
|
+
encoded.push(encodeCanonical(value[index], `${path}[${index}]`, ancestors));
|
|
76
|
+
}
|
|
77
|
+
const extraKeys = Reflect.ownKeys(value).filter((key) => key !== "length" && (typeof key !== "string" || !/^(?:0|[1-9][0-9]*)$/u.test(key) || Number(key) >= value.length));
|
|
78
|
+
if (extraKeys.length > 0) {
|
|
79
|
+
throw new OhValidationError("non-json-property", path, "array has non-index properties");
|
|
80
|
+
}
|
|
81
|
+
return `[${encoded.join(",")}]`;
|
|
82
|
+
}
|
|
83
|
+
if (!isPlainRecord(value)) {
|
|
84
|
+
throw new OhValidationError("non-plain-object", path, "must be a plain object");
|
|
85
|
+
}
|
|
86
|
+
const ownKeys = Reflect.ownKeys(value);
|
|
87
|
+
if (ownKeys.some((key) => typeof key !== "string")) {
|
|
88
|
+
throw new OhValidationError("non-json-property", path, "object has a symbol property");
|
|
89
|
+
}
|
|
90
|
+
const keys = ownKeys;
|
|
91
|
+
for (const key of keys) {
|
|
92
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
93
|
+
if (descriptor === undefined || !descriptor.enumerable || descriptor.get !== undefined || descriptor.set !== undefined) {
|
|
94
|
+
throw new OhValidationError("non-json-property", `${path}.${key}`, "must be an enumerable data property");
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
keys.sort();
|
|
98
|
+
const entries = keys.map((key) => {
|
|
99
|
+
assertUnicodeScalarString(key, `${path}.<key>`);
|
|
100
|
+
return `${JSON.stringify(key)}:${encodeCanonical(value[key], `${path}.${key}`, ancestors)}`;
|
|
101
|
+
});
|
|
102
|
+
return `{${entries.join(",")}}`;
|
|
103
|
+
} finally {
|
|
104
|
+
ancestors.delete(value);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
function canonicalJson(value) {
|
|
108
|
+
return encodeCanonical(value, "$", new Set);
|
|
109
|
+
}
|
|
110
|
+
function sha256Hex(value) {
|
|
111
|
+
return createHash("sha256").update(value).digest("hex");
|
|
112
|
+
}
|
|
113
|
+
function canonicalSha256(value) {
|
|
114
|
+
return sha256Hex(canonicalJson(value));
|
|
115
|
+
}
|
|
116
|
+
function parseSha256Hex(value) {
|
|
117
|
+
return typeof value === "string" && /^[a-f0-9]{64}$/u.test(value) ? value : null;
|
|
118
|
+
}
|
|
119
|
+
function parseCanonicalInstantV1(value) {
|
|
120
|
+
if (typeof value !== "string" || !/^\d{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\d|3[01])T(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d\.\d{3}Z$/u.test(value)) {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
const timestamp = Date.parse(value);
|
|
124
|
+
return Number.isFinite(timestamp) && new Date(timestamp).toISOString() === value ? value : null;
|
|
125
|
+
}
|
|
126
|
+
function safeCode(value, maximumLength = 128) {
|
|
127
|
+
return typeof value === "string" && value.length <= maximumLength && /^[a-z][a-z0-9]*(?:[._:/-][a-z0-9]+)*$/u.test(value) ? value : null;
|
|
128
|
+
}
|
|
129
|
+
function orderedUnique(values, key) {
|
|
130
|
+
return values.every((value, index) => index === 0 || key(values[index - 1]) < key(value));
|
|
131
|
+
}
|
|
132
|
+
function sortUnique(values, key) {
|
|
133
|
+
const sorted = [...values].sort((left, right) => {
|
|
134
|
+
const leftKey = key(left);
|
|
135
|
+
const rightKey = key(right);
|
|
136
|
+
return leftKey < rightKey ? -1 : leftKey > rightKey ? 1 : 0;
|
|
137
|
+
});
|
|
138
|
+
if (!orderedUnique(sorted, key)) {
|
|
139
|
+
throw new OhValidationError("duplicate", "$", "contains duplicate canonical values");
|
|
140
|
+
}
|
|
141
|
+
return sorted;
|
|
142
|
+
}
|
|
143
|
+
var OH_GRAPH_FORMAT_VERSION_V1 = 1;
|
|
144
|
+
var OH_GRAPH_LIMITS_V1 = Object.freeze({
|
|
145
|
+
changesPerOperation: 8192,
|
|
146
|
+
dependenciesPerRecord: 4096,
|
|
147
|
+
recordBytes: 1024 * 1024,
|
|
148
|
+
recordsPerSnapshot: 65536
|
|
149
|
+
});
|
|
150
|
+
var OH_KNOWLEDGE_GRAPH_RECORD_KINDS_V1 = [
|
|
151
|
+
"activity",
|
|
152
|
+
"assertion",
|
|
153
|
+
"context",
|
|
154
|
+
"dependency-manifest",
|
|
155
|
+
"edition",
|
|
156
|
+
"entity",
|
|
157
|
+
"evidence",
|
|
158
|
+
"identity-operation",
|
|
159
|
+
"inquiry",
|
|
160
|
+
"inquiry-event",
|
|
161
|
+
"review-decision",
|
|
162
|
+
"rights-decision",
|
|
163
|
+
"schema",
|
|
164
|
+
"shape",
|
|
165
|
+
"statement",
|
|
166
|
+
"type-membership",
|
|
167
|
+
"view",
|
|
168
|
+
"vocabulary"
|
|
169
|
+
];
|
|
170
|
+
function recordKey(value) {
|
|
171
|
+
return typeof value === "string" && value.length <= 512 && /^[a-z][a-z0-9]*(?:[._:/-][a-z0-9]+)*$/u.test(value) ? value : null;
|
|
172
|
+
}
|
|
173
|
+
function createKnowledgeGraphRecordV1(input) {
|
|
174
|
+
if (!isPlainRecord(input) || !hasExactKeys(input, ["dependencies", "key", "kind", "v", "value"]) || input.v !== 1 || !Array.isArray(input.dependencies))
|
|
175
|
+
throw new TypeError("Invalid graph record input.");
|
|
176
|
+
const key = recordKey(input.key);
|
|
177
|
+
const kind = OH_KNOWLEDGE_GRAPH_RECORD_KINDS_V1.find((candidate) => candidate === input.kind);
|
|
178
|
+
if (key === null || kind === undefined || input.dependencies.length > OH_GRAPH_LIMITS_V1.dependenciesPerRecord)
|
|
179
|
+
throw new TypeError("Invalid graph record identity.");
|
|
180
|
+
const dependencies = input.dependencies.map(recordKey);
|
|
181
|
+
if (dependencies.some((dependency) => dependency === null) || !orderedUnique(dependencies, String) || dependencies.includes(key)) {
|
|
182
|
+
throw new TypeError("Graph dependencies must be ordered, unique, and non-reflexive.");
|
|
183
|
+
}
|
|
184
|
+
const valueJson = canonicalJson(input.value);
|
|
185
|
+
if (Buffer.byteLength(valueJson, "utf8") > OH_GRAPH_LIMITS_V1.recordBytes) {
|
|
186
|
+
throw new RangeError("Graph record value exceeds its canonical byte limit.");
|
|
187
|
+
}
|
|
188
|
+
const payload = { dependencies, key, kind, v: 1, value: input.value };
|
|
189
|
+
return { ...payload, recordSha256: canonicalSha256(payload) };
|
|
190
|
+
}
|
|
191
|
+
function parseKnowledgeGraphRecordV1(value) {
|
|
192
|
+
if (!isPlainRecord(value) || !Object.hasOwn(value, "recordSha256"))
|
|
193
|
+
return null;
|
|
194
|
+
const recordSha256 = parseSha256Hex(value.recordSha256);
|
|
195
|
+
const { recordSha256: _digest, ...input } = value;
|
|
196
|
+
try {
|
|
197
|
+
const created = createKnowledgeGraphRecordV1(input);
|
|
198
|
+
return recordSha256 !== null && created.recordSha256 === recordSha256 ? { ...created, recordSha256 } : null;
|
|
199
|
+
} catch {
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
function changeKey(change) {
|
|
204
|
+
return change.kind === "put" ? change.record.key : change.key;
|
|
205
|
+
}
|
|
206
|
+
function canonicalKnowledgeGraphChangesV1(changes) {
|
|
207
|
+
const normalized = [];
|
|
208
|
+
for (const change of changes) {
|
|
209
|
+
if (!isPlainRecord(change) || change.v !== 1)
|
|
210
|
+
throw new TypeError("Invalid graph change.");
|
|
211
|
+
if (change.kind === "put") {
|
|
212
|
+
const record = parseKnowledgeGraphRecordV1(change.record);
|
|
213
|
+
if (record === null)
|
|
214
|
+
throw new TypeError("Invalid graph record in change.");
|
|
215
|
+
normalized.push({ kind: "put", record, v: 1 });
|
|
216
|
+
} else if (change.kind === "tombstone") {
|
|
217
|
+
const key = recordKey(change.key);
|
|
218
|
+
const priorSha256 = parseSha256Hex(change.priorSha256);
|
|
219
|
+
if (key === null || priorSha256 === null)
|
|
220
|
+
throw new TypeError("Invalid graph tombstone.");
|
|
221
|
+
normalized.push({ key, kind: "tombstone", priorSha256, v: 1 });
|
|
222
|
+
} else
|
|
223
|
+
throw new TypeError("Unknown graph change kind.");
|
|
224
|
+
}
|
|
225
|
+
return sortUnique(normalized, changeKey);
|
|
226
|
+
}
|
|
227
|
+
var OH_ONTOLOGY_VERSION_V1 = "1.0.0";
|
|
228
|
+
var OH_CONTRACT_ID_V1 = "oh.ontology.v1";
|
|
229
|
+
var OH_KNOWLEDGE_LIMITS_V1 = Object.freeze({
|
|
230
|
+
dimensions: 64,
|
|
231
|
+
listValues: 256,
|
|
232
|
+
qualifiers: 128,
|
|
233
|
+
statementBytes: 256 * 1024,
|
|
234
|
+
textBytes: 64 * 1024
|
|
235
|
+
});
|
|
236
|
+
var OH_SCHEMA_FORMAT_VERSION_V1 = 1;
|
|
237
|
+
var manifestPayload = Object.freeze({
|
|
238
|
+
contractId: OH_CONTRACT_ID_V1,
|
|
239
|
+
graphFormatVersion: OH_GRAPH_FORMAT_VERSION_V1,
|
|
240
|
+
ontologyVersion: OH_ONTOLOGY_VERSION_V1,
|
|
241
|
+
recordKinds: OH_KNOWLEDGE_GRAPH_RECORD_KINDS_V1,
|
|
242
|
+
schemaFormatVersion: OH_SCHEMA_FORMAT_VERSION_V1,
|
|
243
|
+
v: 1
|
|
244
|
+
});
|
|
245
|
+
var OH_CONTRACT_MANIFEST_V1 = Object.freeze({
|
|
246
|
+
...manifestPayload,
|
|
247
|
+
contractSha256: canonicalSha256(manifestPayload)
|
|
248
|
+
});
|
|
249
|
+
class OhRecordCodecRegistry {
|
|
250
|
+
#codecs = new Map;
|
|
251
|
+
#sealed = false;
|
|
252
|
+
register(codec) {
|
|
253
|
+
if (this.#sealed)
|
|
254
|
+
throw new TypeError("The codec registry is sealed.");
|
|
255
|
+
if (this.#codecs.has(codec.kind))
|
|
256
|
+
throw new TypeError(`A codec is already registered for ${codec.kind}.`);
|
|
257
|
+
this.#codecs.set(codec.kind, Object.freeze({ kind: codec.kind, parse: codec.parse }));
|
|
258
|
+
return this;
|
|
259
|
+
}
|
|
260
|
+
parse(kind, value) {
|
|
261
|
+
const codec = this.#codecs.get(kind);
|
|
262
|
+
if (codec !== undefined)
|
|
263
|
+
return codec.parse(value);
|
|
264
|
+
try {
|
|
265
|
+
canonicalJson(value);
|
|
266
|
+
return value;
|
|
267
|
+
} catch {
|
|
268
|
+
return null;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
has(kind) {
|
|
272
|
+
return this.#codecs.has(kind);
|
|
273
|
+
}
|
|
274
|
+
parseRequired(kind, value) {
|
|
275
|
+
const codec = this.#codecs.get(kind);
|
|
276
|
+
if (codec === undefined)
|
|
277
|
+
return null;
|
|
278
|
+
try {
|
|
279
|
+
const parsed = codec.parse(value);
|
|
280
|
+
if (parsed === null)
|
|
281
|
+
return null;
|
|
282
|
+
canonicalJson(parsed);
|
|
283
|
+
return parsed;
|
|
284
|
+
} catch {
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
seal() {
|
|
289
|
+
this.#sealed = true;
|
|
290
|
+
return this;
|
|
291
|
+
}
|
|
292
|
+
get sealed() {
|
|
293
|
+
return this.#sealed;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
var OH_OPERATION_MAX_BYTES_V1 = 64 * 1024 * 1024;
|
|
297
|
+
class OhProfileError extends Error {
|
|
298
|
+
constructor(message) {
|
|
299
|
+
super(message);
|
|
300
|
+
this.name = "OhProfileError";
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
var OH_CANONICAL_STORE_PROFILE_V1 = createOhStoreProfileV1({
|
|
304
|
+
applicationProfileSha256: null,
|
|
305
|
+
capabilities: {
|
|
306
|
+
changesSince: true,
|
|
307
|
+
dependencyClosureExport: true,
|
|
308
|
+
exactSnapshots: true,
|
|
309
|
+
operationReplication: true,
|
|
310
|
+
semanticBundleCommit: true,
|
|
311
|
+
v: 1,
|
|
312
|
+
wholeSpacePurge: false
|
|
313
|
+
},
|
|
314
|
+
profileId: "oh.store.canonical.v1",
|
|
315
|
+
profileKind: "canonical",
|
|
316
|
+
v: 1
|
|
317
|
+
});
|
|
318
|
+
var OH_WORKING_STORE_PROFILE_V1 = createOhStoreProfileV1({
|
|
319
|
+
applicationProfileSha256: null,
|
|
320
|
+
capabilities: {
|
|
321
|
+
changesSince: true,
|
|
322
|
+
dependencyClosureExport: true,
|
|
323
|
+
exactSnapshots: true,
|
|
324
|
+
operationReplication: false,
|
|
325
|
+
semanticBundleCommit: true,
|
|
326
|
+
v: 1,
|
|
327
|
+
wholeSpacePurge: true
|
|
328
|
+
},
|
|
329
|
+
profileId: "oh.store.working.v1",
|
|
330
|
+
profileKind: "working",
|
|
331
|
+
v: 1
|
|
332
|
+
});
|
|
333
|
+
var OH_DEPENDENCY_CLOSURE_LIMITS_V1 = Object.freeze({
|
|
334
|
+
bytes: 64 * 1024 * 1024,
|
|
335
|
+
records: 8192,
|
|
336
|
+
roots: 1024
|
|
337
|
+
});
|
|
338
|
+
var EMPTY_RECORDS_SHA256 = canonicalSha256([]);
|
|
339
|
+
function parseCapabilities(value) {
|
|
340
|
+
if (!isPlainRecord(value) || !hasExactKeys(value, [
|
|
341
|
+
"changesSince",
|
|
342
|
+
"dependencyClosureExport",
|
|
343
|
+
"exactSnapshots",
|
|
344
|
+
"operationReplication",
|
|
345
|
+
"semanticBundleCommit",
|
|
346
|
+
"v",
|
|
347
|
+
"wholeSpacePurge"
|
|
348
|
+
]) || value.changesSince !== true || value.dependencyClosureExport !== true || value.exactSnapshots !== true || typeof value.operationReplication !== "boolean" || value.semanticBundleCommit !== true || value.v !== 1 || typeof value.wholeSpacePurge !== "boolean")
|
|
349
|
+
return null;
|
|
350
|
+
return {
|
|
351
|
+
changesSince: true,
|
|
352
|
+
dependencyClosureExport: true,
|
|
353
|
+
exactSnapshots: true,
|
|
354
|
+
operationReplication: value.operationReplication,
|
|
355
|
+
semanticBundleCommit: true,
|
|
356
|
+
v: 1,
|
|
357
|
+
wholeSpacePurge: value.wholeSpacePurge
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
function createOhStoreProfileV1(input) {
|
|
361
|
+
if (!isPlainRecord(input) || !hasExactKeys(input, [
|
|
362
|
+
"applicationProfileSha256",
|
|
363
|
+
"capabilities",
|
|
364
|
+
"profileId",
|
|
365
|
+
"profileKind",
|
|
366
|
+
"v"
|
|
367
|
+
]) || input.v !== 1)
|
|
368
|
+
throw new TypeError("Invalid Oh store profile input.");
|
|
369
|
+
const profileId = safeCode(input.profileId);
|
|
370
|
+
const applicationProfileSha256 = input.applicationProfileSha256 === null ? null : parseSha256Hex(input.applicationProfileSha256);
|
|
371
|
+
const capabilities = parseCapabilities(input.capabilities);
|
|
372
|
+
if (profileId === null || capabilities === null || input.applicationProfileSha256 !== null && applicationProfileSha256 === null || input.profileKind !== "canonical" && input.profileKind !== "working") {
|
|
373
|
+
throw new TypeError("Invalid Oh store profile input.");
|
|
374
|
+
}
|
|
375
|
+
if (input.profileKind === "working" && (capabilities.operationReplication || !capabilities.wholeSpacePurge)) {
|
|
376
|
+
throw new OhProfileError("A working profile must disable operation replication and permit whole-space purge.");
|
|
377
|
+
}
|
|
378
|
+
if (input.profileKind === "canonical" && capabilities.wholeSpacePurge) {
|
|
379
|
+
throw new OhProfileError("A canonical profile cannot permit whole-space purge.");
|
|
380
|
+
}
|
|
381
|
+
const payload = {
|
|
382
|
+
applicationProfileSha256,
|
|
383
|
+
capabilities: Object.freeze(capabilities),
|
|
384
|
+
profileId,
|
|
385
|
+
profileKind: input.profileKind,
|
|
386
|
+
v: 1
|
|
387
|
+
};
|
|
388
|
+
return Object.freeze({ ...payload, profileSha256: canonicalSha256(payload) });
|
|
389
|
+
}
|
|
390
|
+
class OhSemanticBundleIngressV1 {
|
|
391
|
+
#codecs;
|
|
392
|
+
#store;
|
|
393
|
+
constructor(store, codecs) {
|
|
394
|
+
this.#store = store;
|
|
395
|
+
this.#codecs = codecs.seal();
|
|
396
|
+
}
|
|
397
|
+
async commit(value) {
|
|
398
|
+
if (!isPlainRecord(value) || !hasExactKeys(value, [
|
|
399
|
+
"actorId",
|
|
400
|
+
"expectedHead",
|
|
401
|
+
"instant",
|
|
402
|
+
"operationId",
|
|
403
|
+
"puts",
|
|
404
|
+
"tombstones",
|
|
405
|
+
"v"
|
|
406
|
+
]) || value.v !== 1 || !Array.isArray(value.puts) || !Array.isArray(value.tombstones) || value.puts.length + value.tombstones.length < 1 || value.puts.length + value.tombstones.length > OH_GRAPH_LIMITS_V1.changesPerOperation) {
|
|
407
|
+
throw new TypeError("Invalid semantic bundle.");
|
|
408
|
+
}
|
|
409
|
+
const actorId = safeCode(value.actorId);
|
|
410
|
+
const operationId = safeCode(value.operationId);
|
|
411
|
+
const expected = value.expectedHead;
|
|
412
|
+
const instant = value.instant === null ? undefined : parseCanonicalInstantV1(value.instant);
|
|
413
|
+
if (actorId === null || operationId === null || !isPlainRecord(expected) || !hasExactKeys(expected, ["generation", "operationSha256"]) || !Number.isSafeInteger(expected.generation) || expected.generation < 0 || expected.operationSha256 !== null && parseSha256Hex(expected.operationSha256) === null || expected.generation === 0 !== (expected.operationSha256 === null) || value.instant !== null && instant === null)
|
|
414
|
+
throw new TypeError("Invalid semantic bundle identity.");
|
|
415
|
+
const changes = [];
|
|
416
|
+
for (const item of value.puts) {
|
|
417
|
+
if (!isPlainRecord(item) || !hasExactKeys(item, ["dependencies", "key", "kind", "v", "value"]) || item.v !== 1 || !Array.isArray(item.dependencies) || !OH_KNOWLEDGE_GRAPH_RECORD_KINDS_V1.some((kind) => kind === item.kind)) {
|
|
418
|
+
throw new TypeError("Invalid semantic bundle put.");
|
|
419
|
+
}
|
|
420
|
+
const parsed = this.#codecs.parseRequired(item.kind, item.value);
|
|
421
|
+
if (parsed === null)
|
|
422
|
+
throw new TypeError(`The ${String(item.kind)} codec rejected a semantic value.`);
|
|
423
|
+
const record = createKnowledgeGraphRecordV1({
|
|
424
|
+
dependencies: item.dependencies,
|
|
425
|
+
key: item.key,
|
|
426
|
+
kind: item.kind,
|
|
427
|
+
v: 1,
|
|
428
|
+
value: parsed
|
|
429
|
+
});
|
|
430
|
+
changes.push({ kind: "put", record, v: 1 });
|
|
431
|
+
}
|
|
432
|
+
for (const item of value.tombstones) {
|
|
433
|
+
if (!isPlainRecord(item) || !hasExactKeys(item, ["key", "priorSha256", "v"]) || item.v !== 1) {
|
|
434
|
+
throw new TypeError("Invalid semantic bundle tombstone.");
|
|
435
|
+
}
|
|
436
|
+
const priorSha256 = parseSha256Hex(item.priorSha256);
|
|
437
|
+
if (typeof item.key !== "string" || priorSha256 === null)
|
|
438
|
+
throw new TypeError("Invalid semantic bundle tombstone.");
|
|
439
|
+
changes.push({ key: item.key, kind: "tombstone", priorSha256, v: 1 });
|
|
440
|
+
}
|
|
441
|
+
const canonical = canonicalKnowledgeGraphChangesV1(changes);
|
|
442
|
+
return await this.#store.commit({
|
|
443
|
+
actorId,
|
|
444
|
+
changes: canonical,
|
|
445
|
+
expectedHead: {
|
|
446
|
+
generation: expected.generation,
|
|
447
|
+
operationSha256: expected.operationSha256
|
|
448
|
+
},
|
|
449
|
+
...typeof instant === "string" ? { instant } : {},
|
|
450
|
+
operationId
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// src/lib/contracts.ts
|
|
7
456
|
import { z } from "zod";
|
|
457
|
+
|
|
458
|
+
// src/lib/public-identity.ts
|
|
459
|
+
var RESERVED_PROFILE_PUBLISHERS = new Set([
|
|
460
|
+
"api",
|
|
461
|
+
"connect",
|
|
462
|
+
"docs",
|
|
463
|
+
"about",
|
|
464
|
+
"sitemap.xml",
|
|
465
|
+
"robots.txt",
|
|
466
|
+
"llms.txt",
|
|
467
|
+
"llms-full.txt",
|
|
468
|
+
"manifest.json",
|
|
469
|
+
"favicon.ico",
|
|
470
|
+
"favicon.svg",
|
|
471
|
+
"opengraph-image",
|
|
472
|
+
"twitter-image",
|
|
473
|
+
"icon",
|
|
474
|
+
"apple-icon",
|
|
475
|
+
"_next"
|
|
476
|
+
]);
|
|
477
|
+
function parseSoulscrapeProfileUrl(value) {
|
|
478
|
+
if (typeof value !== "string" || value.length > 128)
|
|
479
|
+
return null;
|
|
480
|
+
const match = /^https:\/\/soulscrape\.com\/([a-z0-9_-]+)\/([a-z0-9-]+)$/u.exec(value);
|
|
481
|
+
if (match === null || match[0] !== value)
|
|
482
|
+
return null;
|
|
483
|
+
const username = match[1];
|
|
484
|
+
const handle = match[2];
|
|
485
|
+
if (username.length < 3 || username.length > 24 || !/^[a-z0-9](?:[a-z0-9]|[-_](?=[a-z0-9]))*[a-z0-9]$/u.test(username) || RESERVED_PROFILE_PUBLISHERS.has(username) || handle.length < 2 || handle.length > 64 || !/^[a-z0-9]+(?:-[a-z0-9]+)*$/u.test(handle))
|
|
486
|
+
return null;
|
|
487
|
+
return { url: value, username, handle };
|
|
488
|
+
}
|
|
489
|
+
function parseWikidataId(value) {
|
|
490
|
+
if (typeof value !== "string" || value.length > 11)
|
|
491
|
+
return null;
|
|
492
|
+
return /^Q[1-9][0-9]{0,9}$/u.exec(value)?.[0] === value ? value : null;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// src/lib/contracts.ts
|
|
8
496
|
var enrichmentPolicyVersion = "identity-bound-claims-v15";
|
|
9
497
|
var enrichmentPriorityPolicyVersion = "enrichment-value-v9";
|
|
10
498
|
var exactProfileTitleOnlyEvidenceExcerpt = "Title-only result for the exact stored public profile URL; no page text was returned.";
|
|
@@ -77,7 +565,7 @@ var enrichmentInputSchema = legacyEnrichmentInputSchema.extend({
|
|
|
77
565
|
identityAnchors: identityAnchorsSchema
|
|
78
566
|
}).strict();
|
|
79
567
|
function enrichmentInputSha256(value) {
|
|
80
|
-
return sha256(
|
|
568
|
+
return sha256(canonicalJson2(enrichmentInputSchema.parse({
|
|
81
569
|
displayName: value.displayName,
|
|
82
570
|
emails: value.emails,
|
|
83
571
|
phones: value.phones,
|
|
@@ -90,7 +578,7 @@ function enrichmentInputSha256(value) {
|
|
|
90
578
|
})));
|
|
91
579
|
}
|
|
92
580
|
function legacyEnrichmentInputSha256(value) {
|
|
93
|
-
return sha256(
|
|
581
|
+
return sha256(canonicalJson2(legacyEnrichmentInputSchema.parse({
|
|
94
582
|
displayName: value.displayName,
|
|
95
583
|
emails: value.emails,
|
|
96
584
|
phones: value.phones,
|
|
@@ -258,6 +746,19 @@ var serverSyncPageInputSchema = z.object({
|
|
|
258
746
|
});
|
|
259
747
|
}
|
|
260
748
|
input.contacts.forEach((contact, index) => {
|
|
749
|
+
try {
|
|
750
|
+
canonicalJson2(contact);
|
|
751
|
+
} catch (error) {
|
|
752
|
+
if (error instanceof OhValidationError) {
|
|
753
|
+
context.addIssue({
|
|
754
|
+
code: "custom",
|
|
755
|
+
path: ["contacts", index],
|
|
756
|
+
message: `Sync contact is not canonical JSON (${error.code}: ${error.message})`
|
|
757
|
+
});
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
760
|
+
throw error;
|
|
761
|
+
}
|
|
261
762
|
const version = syncContactInputVersion(contact);
|
|
262
763
|
if (version === 0)
|
|
263
764
|
return;
|
|
@@ -279,7 +780,7 @@ var serverSyncPageSchema = serverSyncPageInputSchema.transform((input) => {
|
|
|
279
780
|
ordinal: input.ordinal,
|
|
280
781
|
inputVersion,
|
|
281
782
|
selectionVersion,
|
|
282
|
-
legacyPayloadSha256: inputVersion === 2 && selectionVersion === 1 ? null : sha256(
|
|
783
|
+
legacyPayloadSha256: inputVersion === 2 && selectionVersion === 1 ? null : sha256(canonicalJson2(input.contacts)),
|
|
283
784
|
contacts: input.contacts.map((contact) => {
|
|
284
785
|
if (inputVersion === 2 && selectionVersion === 1)
|
|
285
786
|
return cloudContactSchema.parse(contact);
|
|
@@ -461,8 +962,34 @@ var enrichmentEvidenceRecordSchema = enrichmentEvidenceReferenceSchema.extend({
|
|
|
461
962
|
}).strict();
|
|
462
963
|
var publicEvidenceSchema = enrichmentEvidenceReferenceSchema.omit({ kind: true }).strict();
|
|
463
964
|
var manualResearchSchema = enrichmentOutputSchema.extend({
|
|
464
|
-
|
|
465
|
-
|
|
965
|
+
wikidataId: z.string().max(11).refine((value) => parseWikidataId(value) !== null, "Invalid Wikidata QID").nullable().optional(),
|
|
966
|
+
wikidataIdEvidenceIndex: z.number().int().min(0).max(4).nullable().optional(),
|
|
967
|
+
soulscrapeProfileUrl: z.string().max(128).refine((value) => parseSoulscrapeProfileUrl(value) !== null, "Invalid canonical Soulscrape profile URL").nullable().optional(),
|
|
968
|
+
soulscrapeProfileUrlEvidenceIndex: z.number().int().min(0).max(4).nullable().optional(),
|
|
969
|
+
evidence: z.array(publicEvidenceSchema.extend({
|
|
970
|
+
url: z.string().refine((value) => publicEvidenceSchema.shape.url.safeParse(value).success, "Evidence URL must be a bounded HTTP or HTTPS URL")
|
|
971
|
+
})).max(5)
|
|
972
|
+
}).strict().superRefine((output, context) => {
|
|
973
|
+
for (const [field, indexField] of [
|
|
974
|
+
["wikidataId", "wikidataIdEvidenceIndex"],
|
|
975
|
+
["soulscrapeProfileUrl", "soulscrapeProfileUrlEvidenceIndex"]
|
|
976
|
+
]) {
|
|
977
|
+
const value = output[field];
|
|
978
|
+
const index = output[indexField];
|
|
979
|
+
if (value == null) {
|
|
980
|
+
if (index != null)
|
|
981
|
+
context.addIssue({ code: "custom", path: [indexField], message: `${indexField} requires ${field}.` });
|
|
982
|
+
continue;
|
|
983
|
+
}
|
|
984
|
+
if (output.identityMatch !== "confirmed") {
|
|
985
|
+
context.addIssue({ code: "custom", path: [field], message: `${field} requires a confirmed identity match.` });
|
|
986
|
+
}
|
|
987
|
+
const referenceUrl = field === "wikidataId" ? `https://www.wikidata.org/wiki/${value}` : value;
|
|
988
|
+
if (index == null || output.evidence[index]?.url !== referenceUrl) {
|
|
989
|
+
context.addIssue({ code: "custom", path: [indexField], message: `${field} requires its exact cited public reference URL.` });
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
}).transform((output) => ({ ...output, evidence: output.evidence.map((item) => publicEvidenceSchema.parse(item)) }));
|
|
466
993
|
function normalizedEvidenceText(value) {
|
|
467
994
|
return value.normalize("NFKC").toLocaleLowerCase("en-US").replaceAll(/[^\p{L}\p{N}]+/gu, " ").trim();
|
|
468
995
|
}
|
|
@@ -1267,15 +1794,11 @@ var IDENTITY_GRADE_CONTENT_FREE_TEXT = new Set([
|
|
|
1267
1794
|
"verified profile",
|
|
1268
1795
|
normalizedEvidenceText(exactProfileTitleOnlyEvidenceExcerpt)
|
|
1269
1796
|
]);
|
|
1270
|
-
function
|
|
1271
|
-
|
|
1272
|
-
return JSON.stringify(value);
|
|
1273
|
-
if (Array.isArray(value))
|
|
1274
|
-
return `[${value.map(canonicalJson).join(",")}]`;
|
|
1275
|
-
return `{${Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => `${JSON.stringify(key)}:${canonicalJson(item)}`).join(",")}}`;
|
|
1797
|
+
function canonicalJson2(value) {
|
|
1798
|
+
return canonicalJson(value);
|
|
1276
1799
|
}
|
|
1277
1800
|
function sha256(value) {
|
|
1278
|
-
return
|
|
1801
|
+
return createHash2("sha256").update(value).digest("hex");
|
|
1279
1802
|
}
|
|
1280
1803
|
|
|
1281
1804
|
// src/local/cloud-configuration.ts
|
|
@@ -1417,7 +1940,7 @@ function providerHandleKey(candidate, databaseInstanceId) {
|
|
|
1417
1940
|
return candidate.provider;
|
|
1418
1941
|
if (candidate.service === null || candidate.realmExternalIdSha256 === null || candidate.bindingAuthSha256 === null)
|
|
1419
1942
|
throw new Error("Beeper provider handle is missing its bound source realm.");
|
|
1420
|
-
const realmDiscriminator = createHmac("sha256", databaseInstanceId).update(
|
|
1943
|
+
const realmDiscriminator = createHmac("sha256", databaseInstanceId).update(canonicalJson2([
|
|
1421
1944
|
"peopleblade-beeper-provider-handle-realm-v1",
|
|
1422
1945
|
candidate.bindingAuthSha256,
|
|
1423
1946
|
candidate.realmExternalIdSha256
|
|
@@ -1674,7 +2197,7 @@ function projectCloudContacts(database, options = {}) {
|
|
|
1674
2197
|
};
|
|
1675
2198
|
return cloudContactSchema.parse({
|
|
1676
2199
|
...data,
|
|
1677
|
-
metadataSha256: sha256(
|
|
2200
|
+
metadataSha256: sha256(canonicalJson2(data)),
|
|
1678
2201
|
enrichmentInputSha256: enrichmentInputSha256(data)
|
|
1679
2202
|
});
|
|
1680
2203
|
});
|