@opendatalabs/personal-server-ts-core 1.7.0 → 1.9.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/api/index.d.ts +64 -2
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +189 -45
- package/dist/api/index.js.map +1 -1
- package/dist/contracts/data.d.ts +16 -1
- package/dist/contracts/data.d.ts.map +1 -1
- package/dist/contracts/data.js +71 -19
- package/dist/contracts/data.js.map +1 -1
- package/dist/derivatives/api.d.ts +33 -0
- package/dist/derivatives/api.d.ts.map +1 -0
- package/dist/derivatives/api.js +216 -0
- package/dist/derivatives/api.js.map +1 -0
- package/dist/derivatives/compute.d.ts +106 -0
- package/dist/derivatives/compute.d.ts.map +1 -0
- package/dist/derivatives/compute.js +373 -0
- package/dist/derivatives/compute.js.map +1 -0
- package/dist/derivatives/index.d.ts +9 -0
- package/dist/derivatives/index.d.ts.map +1 -0
- package/dist/derivatives/index.js +9 -0
- package/dist/derivatives/index.js.map +1 -0
- package/dist/derivatives/inference.d.ts +102 -0
- package/dist/derivatives/inference.d.ts.map +1 -0
- package/dist/derivatives/inference.js +159 -0
- package/dist/derivatives/inference.js.map +1 -0
- package/dist/derivatives/prompt.d.ts +64 -0
- package/dist/derivatives/prompt.d.ts.map +1 -0
- package/dist/derivatives/prompt.js +186 -0
- package/dist/derivatives/prompt.js.map +1 -0
- package/dist/derivatives/registration.d.ts +53 -0
- package/dist/derivatives/registration.d.ts.map +1 -0
- package/dist/derivatives/registration.js +163 -0
- package/dist/derivatives/registration.js.map +1 -0
- package/dist/derivatives/scheduler.d.ts +56 -0
- package/dist/derivatives/scheduler.d.ts.map +1 -0
- package/dist/derivatives/scheduler.js +151 -0
- package/dist/derivatives/scheduler.js.map +1 -0
- package/dist/derivatives/store.d.ts +14 -0
- package/dist/derivatives/store.d.ts.map +1 -0
- package/dist/derivatives/store.js +75 -0
- package/dist/derivatives/store.js.map +1 -0
- package/dist/derivatives/types.d.ts +72 -0
- package/dist/derivatives/types.d.ts.map +1 -0
- package/dist/derivatives/types.js +25 -0
- package/dist/derivatives/types.js.map +1 -0
- package/dist/errors/catalog.d.ts +26 -0
- package/dist/errors/catalog.d.ts.map +1 -1
- package/dist/errors/catalog.js +39 -1
- package/dist/errors/catalog.js.map +1 -1
- package/dist/logging/access-log.d.ts +7 -1
- package/dist/logging/access-log.d.ts.map +1 -1
- package/dist/mcp/read-client.d.ts.map +1 -1
- package/dist/mcp/read-client.js +25 -1
- package/dist/mcp/read-client.js.map +1 -1
- package/dist/mcp/session.d.ts.map +1 -1
- package/dist/mcp/session.js +10 -1
- package/dist/mcp/session.js.map +1 -1
- package/dist/ports/index.d.ts +125 -0
- package/dist/ports/index.d.ts.map +1 -1
- package/dist/ports/index.js.map +1 -1
- package/dist/schemas/server-config.d.ts +12 -0
- package/dist/schemas/server-config.d.ts.map +1 -1
- package/dist/schemas/server-config.js +29 -0
- package/dist/schemas/server-config.js.map +1 -1
- package/dist/storage/adapters/index.d.ts +1 -1
- package/dist/storage/adapters/index.d.ts.map +1 -1
- package/dist/storage/adapters/index.js +1 -1
- package/dist/storage/adapters/index.js.map +1 -1
- package/dist/storage/adapters/vana.d.ts +2 -0
- package/dist/storage/adapters/vana.d.ts.map +1 -1
- package/dist/storage/adapters/vana.js +5 -1
- package/dist/storage/adapters/vana.js.map +1 -1
- package/dist/storage/index/types.d.ts +1 -0
- package/dist/storage/index/types.d.ts.map +1 -1
- package/dist/sync/data-point-feed.d.ts +35 -0
- package/dist/sync/data-point-feed.d.ts.map +1 -0
- package/dist/sync/data-point-feed.js +122 -0
- package/dist/sync/data-point-feed.js.map +1 -0
- package/dist/sync/delete-data-port.d.ts +50 -0
- package/dist/sync/delete-data-port.d.ts.map +1 -0
- package/dist/sync/delete-data-port.js +244 -0
- package/dist/sync/delete-data-port.js.map +1 -0
- package/dist/sync/engine/sync-manager.d.ts +14 -4
- package/dist/sync/engine/sync-manager.d.ts.map +1 -1
- package/dist/sync/engine/sync-manager.js +53 -8
- package/dist/sync/engine/sync-manager.js.map +1 -1
- package/dist/sync/index.d.ts +7 -0
- package/dist/sync/index.d.ts.map +1 -1
- package/dist/sync/index.js +7 -0
- package/dist/sync/index.js.map +1 -1
- package/dist/sync/pending-blob-deletions.d.ts +29 -0
- package/dist/sync/pending-blob-deletions.d.ts.map +1 -0
- package/dist/sync/pending-blob-deletions.js +116 -0
- package/dist/sync/pending-blob-deletions.js.map +1 -0
- package/dist/sync/scope-deletions.d.ts +155 -0
- package/dist/sync/scope-deletions.d.ts.map +1 -0
- package/dist/sync/scope-deletions.js +225 -0
- package/dist/sync/scope-deletions.js.map +1 -0
- package/dist/sync/tombstone.d.ts +27 -0
- package/dist/sync/tombstone.d.ts.map +1 -0
- package/dist/sync/tombstone.js +28 -0
- package/dist/sync/tombstone.js.map +1 -0
- package/dist/sync/workers/delete.d.ts +152 -31
- package/dist/sync/workers/delete.d.ts.map +1 -1
- package/dist/sync/workers/delete.js +461 -27
- package/dist/sync/workers/delete.js.map +1 -1
- package/dist/sync/workers/download.d.ts +52 -1
- package/dist/sync/workers/download.d.ts.map +1 -1
- package/dist/sync/workers/download.js +129 -2
- package/dist/sync/workers/download.js.map +1 -1
- package/dist/sync/workers/upload.d.ts +31 -1
- package/dist/sync/workers/upload.d.ts.map +1 -1
- package/dist/sync/workers/upload.js +108 -4
- package/dist/sync/workers/upload.js.map +1 -1
- package/dist/test-utils/index.d.ts +1 -0
- package/dist/test-utils/index.d.ts.map +1 -1
- package/dist/test-utils/index.js +1 -0
- package/dist/test-utils/index.js.map +1 -1
- package/dist/test-utils/memory-storage.d.ts +12 -0
- package/dist/test-utils/memory-storage.d.ts.map +1 -0
- package/dist/test-utils/memory-storage.js +146 -0
- package/dist/test-utils/memory-storage.js.map +1 -0
- package/package.json +5 -1
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inference provider adapter for the derivative compute layer.
|
|
3
|
+
*
|
|
4
|
+
* One implementation: OpenAI-compatible chat completions over `fetch`, so it
|
|
5
|
+
* runs unchanged in Node and in the browser (PS-Lite). In production the
|
|
6
|
+
* base URL is the Vana inference relay, which holds the provider key; the
|
|
7
|
+
* optional API key header exists for local development against a provider
|
|
8
|
+
* directly.
|
|
9
|
+
*
|
|
10
|
+
* E2EE SEAM (not implemented): the Phala confidential-inference E2EE v2
|
|
11
|
+
* protocol encrypts each `messages[i].content` to the model's public key
|
|
12
|
+
* and adds the headers X-E2EE-Version, X-Client-Pub-Key, X-Model-Pub-Key,
|
|
13
|
+
* X-E2EE-Nonce and X-E2EE-Timestamp; the response content comes back
|
|
14
|
+
* encrypted to the client key. `InferenceRequestEncryption` below is the
|
|
15
|
+
* hook where that lands: it sees the outgoing messages + headers and the
|
|
16
|
+
* incoming content + headers, and nothing else in this module has to change.
|
|
17
|
+
*/
|
|
18
|
+
export const DEFAULT_INFERENCE_BASE_URL = "https://inference.phala.com/v1";
|
|
19
|
+
export const DEFAULT_INFERENCE_MODEL = "z-ai/glm-5.2";
|
|
20
|
+
export const DEFAULT_INFERENCE_TIMEOUT_MS = 120_000;
|
|
21
|
+
export const DEFAULT_INFERENCE_MAX_TOKENS = 2_048;
|
|
22
|
+
export const DEFAULT_INFERENCE_REQUEST_FIELDS = {
|
|
23
|
+
provider: { aci_verified: true, zdr: true },
|
|
24
|
+
};
|
|
25
|
+
/** Thrown for a non-2xx or malformed provider reply. Carries no prompt text. */
|
|
26
|
+
export class InferenceRequestError extends Error {
|
|
27
|
+
status;
|
|
28
|
+
constructor(message, status) {
|
|
29
|
+
super(message);
|
|
30
|
+
this.status = status;
|
|
31
|
+
this.name = "InferenceRequestError";
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function isRecord(value) {
|
|
35
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
36
|
+
}
|
|
37
|
+
function readUsage(value) {
|
|
38
|
+
if (!isRecord(value))
|
|
39
|
+
return undefined;
|
|
40
|
+
const num = (v) => (typeof v === "number" ? v : undefined);
|
|
41
|
+
const usage = {
|
|
42
|
+
promptTokens: num(value.prompt_tokens),
|
|
43
|
+
completionTokens: num(value.completion_tokens),
|
|
44
|
+
totalTokens: num(value.total_tokens),
|
|
45
|
+
};
|
|
46
|
+
return usage;
|
|
47
|
+
}
|
|
48
|
+
function readContent(body) {
|
|
49
|
+
if (!isRecord(body) || !Array.isArray(body.choices))
|
|
50
|
+
return null;
|
|
51
|
+
const first = body.choices[0];
|
|
52
|
+
if (!isRecord(first) || !isRecord(first.message))
|
|
53
|
+
return null;
|
|
54
|
+
const content = first.message.content;
|
|
55
|
+
if (typeof content === "string")
|
|
56
|
+
return content;
|
|
57
|
+
// Some providers return content parts.
|
|
58
|
+
if (Array.isArray(content)) {
|
|
59
|
+
const text = content
|
|
60
|
+
.map((part) => isRecord(part) && typeof part.text === "string" ? part.text : "")
|
|
61
|
+
.join("");
|
|
62
|
+
return text;
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
export function createOpenAiCompatibleInferenceProvider(options = {}) {
|
|
67
|
+
const base = (options.baseUrl ?? DEFAULT_INFERENCE_BASE_URL).replace(/\/+$/, "");
|
|
68
|
+
const defaultModel = options.model ?? DEFAULT_INFERENCE_MODEL;
|
|
69
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_INFERENCE_TIMEOUT_MS;
|
|
70
|
+
const doFetch = options.fetch ?? fetch;
|
|
71
|
+
const requestFields = options.requestFields ?? DEFAULT_INFERENCE_REQUEST_FIELDS;
|
|
72
|
+
return {
|
|
73
|
+
defaultModel,
|
|
74
|
+
async chat(input) {
|
|
75
|
+
const headers = new Headers({ "Content-Type": "application/json" });
|
|
76
|
+
if (options.apiKey) {
|
|
77
|
+
headers.set("Authorization", `Bearer ${options.apiKey}`);
|
|
78
|
+
}
|
|
79
|
+
let messages = input.messages;
|
|
80
|
+
if (options.encryption) {
|
|
81
|
+
({ messages } = await options.encryption.encryptRequest({
|
|
82
|
+
messages,
|
|
83
|
+
headers,
|
|
84
|
+
}));
|
|
85
|
+
}
|
|
86
|
+
const body = {
|
|
87
|
+
...requestFields,
|
|
88
|
+
model: input.model || defaultModel,
|
|
89
|
+
messages,
|
|
90
|
+
max_tokens: input.maxTokens ?? DEFAULT_INFERENCE_MAX_TOKENS,
|
|
91
|
+
};
|
|
92
|
+
let response;
|
|
93
|
+
try {
|
|
94
|
+
response = await doFetch(`${base}/chat/completions`, {
|
|
95
|
+
method: "POST",
|
|
96
|
+
headers,
|
|
97
|
+
body: JSON.stringify(body),
|
|
98
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
// Transport failure: no status, no body. The message is the error
|
|
103
|
+
// class name only; it never carries request contents.
|
|
104
|
+
const name = err instanceof Error ? err.name : "Error";
|
|
105
|
+
throw new InferenceRequestError(`inference request failed before a response (${name})`, null);
|
|
106
|
+
}
|
|
107
|
+
if (!response.ok) {
|
|
108
|
+
throw new InferenceRequestError(`inference request failed with status ${response.status}`, response.status);
|
|
109
|
+
}
|
|
110
|
+
let parsed;
|
|
111
|
+
try {
|
|
112
|
+
parsed = await response.json();
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
throw new InferenceRequestError("inference response was not JSON", response.status);
|
|
116
|
+
}
|
|
117
|
+
let content = readContent(parsed);
|
|
118
|
+
if (content === null || content.trim() === "") {
|
|
119
|
+
// An empty reply must never become a "ready" derivative.
|
|
120
|
+
throw new InferenceRequestError("inference response carried no assistant content", response.status);
|
|
121
|
+
}
|
|
122
|
+
if (options.encryption) {
|
|
123
|
+
content = await options.encryption.decryptResponse({
|
|
124
|
+
content,
|
|
125
|
+
headers: response.headers,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
const receiptId = response.headers.get("x-receipt-id") ?? undefined;
|
|
129
|
+
const aciIdentity = response.headers.get("x-aci-identity") ?? undefined;
|
|
130
|
+
return {
|
|
131
|
+
content,
|
|
132
|
+
usage: readUsage(isRecord(parsed) ? parsed.usage : undefined),
|
|
133
|
+
...(receiptId ? { receiptId } : {}),
|
|
134
|
+
...(aciIdentity ? { aciIdentity } : {}),
|
|
135
|
+
};
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
/** Test double: records calls, answers with a fixed JSON object by default. */
|
|
140
|
+
export function createFakeInferenceProvider(options = {}) {
|
|
141
|
+
const calls = [];
|
|
142
|
+
return {
|
|
143
|
+
defaultModel: options.model ?? "fake-model",
|
|
144
|
+
calls,
|
|
145
|
+
async chat(input) {
|
|
146
|
+
calls.push(input);
|
|
147
|
+
if (options.respond)
|
|
148
|
+
return options.respond(input, calls.length - 1);
|
|
149
|
+
return {
|
|
150
|
+
content: JSON.stringify({
|
|
151
|
+
answer: "fake answer",
|
|
152
|
+
evidence: "fake evidence",
|
|
153
|
+
}),
|
|
154
|
+
receiptId: "fake-receipt",
|
|
155
|
+
};
|
|
156
|
+
},
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
//# sourceMappingURL=inference.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inference.js","sourceRoot":"","sources":["../../src/derivatives/inference.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prompt assembly for a question: the source scopes' latest local records,
|
|
3
|
+
* trimmed newest-first to a bounded number of items each, framed by a system
|
|
4
|
+
* prompt that pins the model to the provided data and to a JSON answer.
|
|
5
|
+
*
|
|
6
|
+
* Nothing here leaves the Personal Server except through the inference
|
|
7
|
+
* provider call the compute step makes.
|
|
8
|
+
*/
|
|
9
|
+
import type { InferenceMessage } from "./inference.js";
|
|
10
|
+
export declare const DEFAULT_MAX_SOURCE_ITEMS = 50;
|
|
11
|
+
/** Upper bound on the serialized size of one source's trimmed data. */
|
|
12
|
+
export declare const DEFAULT_MAX_SOURCE_CHARS = 200000;
|
|
13
|
+
/**
|
|
14
|
+
* Newest first. Items that carry a timestamp sort by it; items that do not
|
|
15
|
+
* keep their relative order after the timestamped ones, reversed, on the
|
|
16
|
+
* assumption that a connector appends newest last. Stable.
|
|
17
|
+
*/
|
|
18
|
+
export declare function sortNewestFirst<T>(items: readonly T[]): T[];
|
|
19
|
+
export interface TrimResult {
|
|
20
|
+
data: unknown;
|
|
21
|
+
/** Items kept / items present, summed over every array that was trimmed. */
|
|
22
|
+
kept: number;
|
|
23
|
+
total: number;
|
|
24
|
+
/** True when the char cap cut the serialization. */
|
|
25
|
+
truncated: boolean;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* The trim rule. A record whose `data` is an array is trimmed to the newest
|
|
29
|
+
* `maxItems`; a record object trims every top-level array value the same
|
|
30
|
+
* way and keeps its other keys; reserved server keys are dropped. When the
|
|
31
|
+
* serialized result still exceeds `maxChars`, items are dropped from the
|
|
32
|
+
* old end until it fits (at least one is kept), and as a last resort the
|
|
33
|
+
* serialization itself is cut at `maxChars`.
|
|
34
|
+
*/
|
|
35
|
+
export declare function trimSourceData(data: unknown, options?: {
|
|
36
|
+
maxItems?: number;
|
|
37
|
+
maxChars?: number;
|
|
38
|
+
}): TrimResult;
|
|
39
|
+
export interface PromptSource {
|
|
40
|
+
scope: string;
|
|
41
|
+
collectedAt: string;
|
|
42
|
+
version: number;
|
|
43
|
+
data: unknown;
|
|
44
|
+
kept: number;
|
|
45
|
+
total: number;
|
|
46
|
+
truncated: boolean;
|
|
47
|
+
}
|
|
48
|
+
export declare const SYSTEM_PROMPT: string;
|
|
49
|
+
/** Assemble the chat messages for one compute. */
|
|
50
|
+
export declare function buildQuestionMessages(input: {
|
|
51
|
+
question: string;
|
|
52
|
+
sources: readonly PromptSource[];
|
|
53
|
+
}): InferenceMessage[];
|
|
54
|
+
export interface ParsedAnswer {
|
|
55
|
+
answer: string;
|
|
56
|
+
evidence: string | null;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Read the model's JSON answer. Tolerates a fenced code block or leading
|
|
60
|
+
* prose around the object; falls back to the whole text as the answer when
|
|
61
|
+
* no object can be parsed (the answer is still the model's, just unframed).
|
|
62
|
+
*/
|
|
63
|
+
export declare function parseAnswer(content: string): ParsedAnswer;
|
|
64
|
+
//# sourceMappingURL=prompt.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompt.d.ts","sourceRoot":"","sources":["../../src/derivatives/prompt.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAEvD,eAAO,MAAM,wBAAwB,KAAK,CAAC;AAC3C,uEAAuE;AACvE,eAAO,MAAM,wBAAwB,SAAU,CAAC;AAyChD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,KAAK,EAAE,SAAS,CAAC,EAAE,GAAG,CAAC,EAAE,CAW3D;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,OAAO,CAAC;IACd,4EAA4E;IAC5E,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,oDAAoD;IACpD,SAAS,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,OAAO,EACb,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GACrD,UAAU,CA+CZ;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,OAAO,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,eAAO,MAAM,aAAa,QAMd,CAAC;AAEb,kDAAkD;AAClD,wBAAgB,qBAAqB,CAAC,KAAK,EAAE;IAC3C,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,SAAS,YAAY,EAAE,CAAC;CAClC,GAAG,gBAAgB,EAAE,CA0BrB;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,YAAY,CAwBzD"}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prompt assembly for a question: the source scopes' latest local records,
|
|
3
|
+
* trimmed newest-first to a bounded number of items each, framed by a system
|
|
4
|
+
* prompt that pins the model to the provided data and to a JSON answer.
|
|
5
|
+
*
|
|
6
|
+
* Nothing here leaves the Personal Server except through the inference
|
|
7
|
+
* provider call the compute step makes.
|
|
8
|
+
*/
|
|
9
|
+
export const DEFAULT_MAX_SOURCE_ITEMS = 50;
|
|
10
|
+
/** Upper bound on the serialized size of one source's trimmed data. */
|
|
11
|
+
export const DEFAULT_MAX_SOURCE_CHARS = 200_000;
|
|
12
|
+
/** Keys a record commonly carries its own timestamp under, in priority order. */
|
|
13
|
+
const TIMESTAMP_KEYS = [
|
|
14
|
+
"collectedAt",
|
|
15
|
+
"updatedAt",
|
|
16
|
+
"updated_at",
|
|
17
|
+
"update_time",
|
|
18
|
+
"createdAt",
|
|
19
|
+
"created_at",
|
|
20
|
+
"create_time",
|
|
21
|
+
"timestamp",
|
|
22
|
+
"time",
|
|
23
|
+
"date",
|
|
24
|
+
"publishedAt",
|
|
25
|
+
"published_at",
|
|
26
|
+
];
|
|
27
|
+
/** Server-stamped envelope keys that are not user data. */
|
|
28
|
+
const RESERVED_KEYS = new Set(["$lineage", "$writtenBy", "$binary"]);
|
|
29
|
+
function isRecord(value) {
|
|
30
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
31
|
+
}
|
|
32
|
+
function timestampOf(item) {
|
|
33
|
+
if (!isRecord(item))
|
|
34
|
+
return null;
|
|
35
|
+
for (const key of TIMESTAMP_KEYS) {
|
|
36
|
+
const value = item[key];
|
|
37
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
38
|
+
// Seconds vs milliseconds: anything below 1e12 is taken as seconds.
|
|
39
|
+
return value < 1e12 ? value * 1000 : value;
|
|
40
|
+
}
|
|
41
|
+
if (typeof value === "string") {
|
|
42
|
+
const parsed = Date.parse(value);
|
|
43
|
+
if (!Number.isNaN(parsed))
|
|
44
|
+
return parsed;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Newest first. Items that carry a timestamp sort by it; items that do not
|
|
51
|
+
* keep their relative order after the timestamped ones, reversed, on the
|
|
52
|
+
* assumption that a connector appends newest last. Stable.
|
|
53
|
+
*/
|
|
54
|
+
export function sortNewestFirst(items) {
|
|
55
|
+
const indexed = items.map((item, index) => ({
|
|
56
|
+
item,
|
|
57
|
+
index,
|
|
58
|
+
at: timestampOf(item),
|
|
59
|
+
}));
|
|
60
|
+
const dated = indexed
|
|
61
|
+
.filter((entry) => entry.at !== null)
|
|
62
|
+
.sort((a, b) => b.at - a.at || b.index - a.index);
|
|
63
|
+
const undated = indexed.filter((entry) => entry.at === null).reverse();
|
|
64
|
+
return [...dated, ...undated].map((entry) => entry.item);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* The trim rule. A record whose `data` is an array is trimmed to the newest
|
|
68
|
+
* `maxItems`; a record object trims every top-level array value the same
|
|
69
|
+
* way and keeps its other keys; reserved server keys are dropped. When the
|
|
70
|
+
* serialized result still exceeds `maxChars`, items are dropped from the
|
|
71
|
+
* old end until it fits (at least one is kept), and as a last resort the
|
|
72
|
+
* serialization itself is cut at `maxChars`.
|
|
73
|
+
*/
|
|
74
|
+
export function trimSourceData(data, options = {}) {
|
|
75
|
+
const maxItems = Math.max(1, options.maxItems ?? DEFAULT_MAX_SOURCE_ITEMS);
|
|
76
|
+
const maxChars = Math.max(1, options.maxChars ?? DEFAULT_MAX_SOURCE_CHARS);
|
|
77
|
+
let kept = 0;
|
|
78
|
+
let total = 0;
|
|
79
|
+
const trimArray = (items, limit) => {
|
|
80
|
+
total += items.length;
|
|
81
|
+
const sorted = sortNewestFirst(items).slice(0, limit);
|
|
82
|
+
kept += sorted.length;
|
|
83
|
+
return sorted;
|
|
84
|
+
};
|
|
85
|
+
const build = (limit) => {
|
|
86
|
+
kept = 0;
|
|
87
|
+
total = 0;
|
|
88
|
+
if (Array.isArray(data))
|
|
89
|
+
return trimArray(data, limit);
|
|
90
|
+
if (isRecord(data)) {
|
|
91
|
+
const out = {};
|
|
92
|
+
for (const [key, value] of Object.entries(data)) {
|
|
93
|
+
if (RESERVED_KEYS.has(key))
|
|
94
|
+
continue;
|
|
95
|
+
out[key] = Array.isArray(value) ? trimArray(value, limit) : value;
|
|
96
|
+
}
|
|
97
|
+
return out;
|
|
98
|
+
}
|
|
99
|
+
return data;
|
|
100
|
+
};
|
|
101
|
+
let limit = maxItems;
|
|
102
|
+
let result = build(limit);
|
|
103
|
+
let text = JSON.stringify(result) ?? "null";
|
|
104
|
+
// Shrink the item budget while the serialization is over the cap. Each
|
|
105
|
+
// pass at least halves the budget, so this ends in O(log maxItems) steps.
|
|
106
|
+
while (text.length > maxChars && limit > 1) {
|
|
107
|
+
limit = Math.max(1, Math.floor(limit / 2));
|
|
108
|
+
result = build(limit);
|
|
109
|
+
text = JSON.stringify(result) ?? "null";
|
|
110
|
+
}
|
|
111
|
+
if (text.length > maxChars) {
|
|
112
|
+
return {
|
|
113
|
+
data: `${text.slice(0, maxChars)}...[truncated]`,
|
|
114
|
+
kept,
|
|
115
|
+
total,
|
|
116
|
+
truncated: true,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
return { data: result, kept, total, truncated: false };
|
|
120
|
+
}
|
|
121
|
+
export const SYSTEM_PROMPT = [
|
|
122
|
+
"You answer a question about a person using ONLY the user data provided in the message.",
|
|
123
|
+
"Do not use outside knowledge and do not guess; if the data does not support an answer, say so in the answer.",
|
|
124
|
+
"Respond with a single JSON object and nothing else, with exactly these fields:",
|
|
125
|
+
' "answer": string, the answer to the question, written for the person the data belongs to;',
|
|
126
|
+
' "evidence": string, a short summary of which parts of the data support the answer.',
|
|
127
|
+
].join("\n");
|
|
128
|
+
/** Assemble the chat messages for one compute. */
|
|
129
|
+
export function buildQuestionMessages(input) {
|
|
130
|
+
const sections = input.sources.map((source) => {
|
|
131
|
+
const note = source.total > source.kept
|
|
132
|
+
? ` (newest ${source.kept} of ${source.total} items)`
|
|
133
|
+
: "";
|
|
134
|
+
const cut = source.truncated ? " (truncated)" : "";
|
|
135
|
+
return [
|
|
136
|
+
`### Scope: ${source.scope}${note}${cut}`,
|
|
137
|
+
`Collected at: ${source.collectedAt}`,
|
|
138
|
+
JSON.stringify(source.data),
|
|
139
|
+
].join("\n");
|
|
140
|
+
});
|
|
141
|
+
const user = [
|
|
142
|
+
"## Question",
|
|
143
|
+
input.question,
|
|
144
|
+
"",
|
|
145
|
+
"## User data",
|
|
146
|
+
...sections,
|
|
147
|
+
"",
|
|
148
|
+
"Answer the question as a JSON object with the fields answer and evidence.",
|
|
149
|
+
].join("\n");
|
|
150
|
+
return [
|
|
151
|
+
{ role: "system", content: SYSTEM_PROMPT },
|
|
152
|
+
{ role: "user", content: user },
|
|
153
|
+
];
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Read the model's JSON answer. Tolerates a fenced code block or leading
|
|
157
|
+
* prose around the object; falls back to the whole text as the answer when
|
|
158
|
+
* no object can be parsed (the answer is still the model's, just unframed).
|
|
159
|
+
*/
|
|
160
|
+
export function parseAnswer(content) {
|
|
161
|
+
const candidates = [content.trim()];
|
|
162
|
+
const fenced = /```(?:json)?\s*([\s\S]*?)```/i.exec(content);
|
|
163
|
+
if (fenced?.[1])
|
|
164
|
+
candidates.unshift(fenced[1].trim());
|
|
165
|
+
const first = content.indexOf("{");
|
|
166
|
+
const last = content.lastIndexOf("}");
|
|
167
|
+
if (first !== -1 && last > first) {
|
|
168
|
+
candidates.push(content.slice(first, last + 1));
|
|
169
|
+
}
|
|
170
|
+
for (const candidate of candidates) {
|
|
171
|
+
try {
|
|
172
|
+
const parsed = JSON.parse(candidate);
|
|
173
|
+
if (isRecord(parsed) && typeof parsed.answer === "string") {
|
|
174
|
+
return {
|
|
175
|
+
answer: parsed.answer,
|
|
176
|
+
evidence: typeof parsed.evidence === "string" ? parsed.evidence : null,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
// try the next framing
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return { answer: content.trim(), evidence: null };
|
|
185
|
+
}
|
|
186
|
+
//# sourceMappingURL=prompt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompt.js","sourceRoot":"","sources":["../../src/derivatives/prompt.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Question registration: input validation, the naming rule (shared with the
|
|
3
|
+
* lineage write path) and the cycle guard that keeps recompute-on-refresh
|
|
4
|
+
* bounded.
|
|
5
|
+
*/
|
|
6
|
+
import type { QuestionRegisteredBy, QuestionRegistration, QuestionStore } from "./types.js";
|
|
7
|
+
export declare const MAX_QUESTION_SOURCE_SCOPES = 16;
|
|
8
|
+
export declare const MAX_QUESTION_CHARS = 8000;
|
|
9
|
+
export declare const MAX_MODEL_CHARS = 128;
|
|
10
|
+
/** Unvalidated scope strings echoed in 400 details are cut to this. */
|
|
11
|
+
export declare const MAX_ECHOED_SCOPE_CHARS = 128;
|
|
12
|
+
export interface ParsedQuestionInput {
|
|
13
|
+
derivedScope: string;
|
|
14
|
+
sourceScopes: string[];
|
|
15
|
+
question: string;
|
|
16
|
+
model: string | null;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Validate the POST body of a question registration. Throws 400
|
|
20
|
+
* DERIVATIVE_QUESTION_INVALID on shape errors and 400
|
|
21
|
+
* LINEAGE_SCOPE_UNDER_SOURCE_PREFIX when the derived scope shares its
|
|
22
|
+
* first segment with a source (the lineage naming rule, applied up front
|
|
23
|
+
* so a registration that could never be written is refused here).
|
|
24
|
+
*/
|
|
25
|
+
export declare function parseQuestionInput(body: unknown): ParsedQuestionInput;
|
|
26
|
+
/**
|
|
27
|
+
* Recompute-on-refresh follows edges source -> derived. A registration whose
|
|
28
|
+
* derived scope can reach one of its own sources through other
|
|
29
|
+
* registrations would recompute forever (A from B, B from A). Refuse it.
|
|
30
|
+
* Returns the offending path (derived scope back to itself) or null.
|
|
31
|
+
*/
|
|
32
|
+
export declare function findDerivationCycle(candidate: Pick<QuestionRegistration, "derivedScope" | "sourceScopes">, existing: readonly Pick<QuestionRegistration, "derivedScope" | "sourceScopes">[]): string[] | null;
|
|
33
|
+
export interface CreateQuestionRegistrationInput {
|
|
34
|
+
body: unknown;
|
|
35
|
+
registeredBy: QuestionRegisteredBy;
|
|
36
|
+
store: QuestionStore;
|
|
37
|
+
questionId: string;
|
|
38
|
+
now: () => Date;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Validate, guard against cycles and persist a registration in `pending`.
|
|
42
|
+
* The caller has already authorized the write on `derivedScope`.
|
|
43
|
+
*/
|
|
44
|
+
export declare function createQuestionRegistration(input: CreateQuestionRegistrationInput): Promise<QuestionRegistration>;
|
|
45
|
+
/**
|
|
46
|
+
* Consent check for a builder question: the sources feed a prompt whose
|
|
47
|
+
* answer the builder reads, so every source scope must be covered by a READ
|
|
48
|
+
* entry of the builder's grant (bare entries; `write:` entries confer
|
|
49
|
+
* nothing). Returns the uncovered scopes; empty = all covered. The system
|
|
50
|
+
* prompt is not a security boundary, this is.
|
|
51
|
+
*/
|
|
52
|
+
export declare function uncoveredSourceScopes(sourceScopes: readonly string[], grantScopes: readonly string[] | undefined): string[];
|
|
53
|
+
//# sourceMappingURL=registration.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registration.d.ts","sourceRoot":"","sources":["../../src/derivatives/registration.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAUH,OAAO,KAAK,EACV,oBAAoB,EACpB,oBAAoB,EACpB,aAAa,EACd,MAAM,YAAY,CAAC;AAEpB,eAAO,MAAM,0BAA0B,KAAK,CAAC;AAC7C,eAAO,MAAM,kBAAkB,OAAQ,CAAC;AACxC,eAAO,MAAM,eAAe,MAAM,CAAC;AACnC,uEAAuE;AACvE,eAAO,MAAM,sBAAsB,MAAM,CAAC;AAK1C,MAAM,WAAW,mBAAmB;IAClC,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAyBD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,OAAO,GAAG,mBAAmB,CA8DrE;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,IAAI,CAAC,oBAAoB,EAAE,cAAc,GAAG,cAAc,CAAC,EACtE,QAAQ,EAAE,SAAS,IAAI,CACrB,oBAAoB,EACpB,cAAc,GAAG,cAAc,CAChC,EAAE,GACF,MAAM,EAAE,GAAG,IAAI,CA+BjB;AAED,MAAM,WAAW,+BAA+B;IAC9C,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,EAAE,oBAAoB,CAAC;IACnC,KAAK,EAAE,aAAa,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,MAAM,IAAI,CAAC;CACjB;AAED;;;GAGG;AACH,wBAAsB,0BAA0B,CAC9C,KAAK,EAAE,+BAA+B,GACrC,OAAO,CAAC,oBAAoB,CAAC,CA2B/B;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,YAAY,EAAE,SAAS,MAAM,EAAE,EAC/B,WAAW,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,GACzC,MAAM,EAAE,CAOV"}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Question registration: input validation, the naming rule (shared with the
|
|
3
|
+
* lineage write path) and the cycle guard that keeps recompute-on-refresh
|
|
4
|
+
* bounded.
|
|
5
|
+
*/
|
|
6
|
+
import { DerivativeCycleError, DerivativeQuestionInvalidError, } from "../errors/catalog.js";
|
|
7
|
+
import { parseDataScopeContract } from "../contracts/data.js";
|
|
8
|
+
import { assertDerivedScopeNaming } from "../lineage/lineage.js";
|
|
9
|
+
import { isWriteScopeEntry } from "../policy/data-write.js";
|
|
10
|
+
import { scopeCoveredByGrant } from "@opendatalabs/vana-sdk/browser";
|
|
11
|
+
export const MAX_QUESTION_SOURCE_SCOPES = 16;
|
|
12
|
+
export const MAX_QUESTION_CHARS = 8_000;
|
|
13
|
+
export const MAX_MODEL_CHARS = 128;
|
|
14
|
+
/** Unvalidated scope strings echoed in 400 details are cut to this. */
|
|
15
|
+
export const MAX_ECHOED_SCOPE_CHARS = 128;
|
|
16
|
+
/** Model ids as providers spell them (`z-ai/glm-5.2`, `gpt-4o-mini`, ...). */
|
|
17
|
+
const MODEL_ID = /^[A-Za-z0-9][A-Za-z0-9._:/-]*$/;
|
|
18
|
+
function isRecord(value) {
|
|
19
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
20
|
+
}
|
|
21
|
+
function parseScope(value, field) {
|
|
22
|
+
if (typeof value !== "string") {
|
|
23
|
+
throw new DerivativeQuestionInvalidError(`${field} must be a scope string`, {
|
|
24
|
+
field,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
const parsed = parseDataScopeContract(value);
|
|
28
|
+
if (!parsed.ok) {
|
|
29
|
+
throw new DerivativeQuestionInvalidError(`${field} is not a valid scope: ${parsed.body.message}`, { field, scope: value.slice(0, MAX_ECHOED_SCOPE_CHARS) });
|
|
30
|
+
}
|
|
31
|
+
return parsed.scope;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Validate the POST body of a question registration. Throws 400
|
|
35
|
+
* DERIVATIVE_QUESTION_INVALID on shape errors and 400
|
|
36
|
+
* LINEAGE_SCOPE_UNDER_SOURCE_PREFIX when the derived scope shares its
|
|
37
|
+
* first segment with a source (the lineage naming rule, applied up front
|
|
38
|
+
* so a registration that could never be written is refused here).
|
|
39
|
+
*/
|
|
40
|
+
export function parseQuestionInput(body) {
|
|
41
|
+
if (!isRecord(body)) {
|
|
42
|
+
throw new DerivativeQuestionInvalidError("Body must be a JSON object");
|
|
43
|
+
}
|
|
44
|
+
const derivedScope = parseScope(body.derivedScope, "derivedScope");
|
|
45
|
+
if (!Array.isArray(body.sourceScopes) || body.sourceScopes.length === 0) {
|
|
46
|
+
throw new DerivativeQuestionInvalidError("sourceScopes must be a non-empty array of scopes", { field: "sourceScopes" });
|
|
47
|
+
}
|
|
48
|
+
if (body.sourceScopes.length > MAX_QUESTION_SOURCE_SCOPES) {
|
|
49
|
+
throw new DerivativeQuestionInvalidError(`sourceScopes lists ${body.sourceScopes.length} scopes; the maximum is ${MAX_QUESTION_SOURCE_SCOPES}`, { field: "sourceScopes", max: MAX_QUESTION_SOURCE_SCOPES });
|
|
50
|
+
}
|
|
51
|
+
const sourceScopes = [];
|
|
52
|
+
for (const entry of body.sourceScopes) {
|
|
53
|
+
const scope = parseScope(entry, "sourceScopes[]");
|
|
54
|
+
if (sourceScopes.includes(scope)) {
|
|
55
|
+
throw new DerivativeQuestionInvalidError("sourceScopes lists the same scope twice", { field: "sourceScopes", duplicate: scope });
|
|
56
|
+
}
|
|
57
|
+
if (scope === derivedScope) {
|
|
58
|
+
throw new DerivativeQuestionInvalidError("derivedScope cannot be one of its own sources", { field: "sourceScopes", scope });
|
|
59
|
+
}
|
|
60
|
+
sourceScopes.push(scope);
|
|
61
|
+
}
|
|
62
|
+
if (typeof body.question !== "string" || body.question.trim() === "") {
|
|
63
|
+
throw new DerivativeQuestionInvalidError("question must be a non-empty string", { field: "question" });
|
|
64
|
+
}
|
|
65
|
+
if (body.question.length > MAX_QUESTION_CHARS) {
|
|
66
|
+
throw new DerivativeQuestionInvalidError(`question is ${body.question.length} characters; the maximum is ${MAX_QUESTION_CHARS}`, { field: "question", max: MAX_QUESTION_CHARS });
|
|
67
|
+
}
|
|
68
|
+
let model = null;
|
|
69
|
+
if (body.model !== undefined && body.model !== null) {
|
|
70
|
+
if (typeof body.model !== "string" ||
|
|
71
|
+
body.model.length > MAX_MODEL_CHARS ||
|
|
72
|
+
!MODEL_ID.test(body.model)) {
|
|
73
|
+
throw new DerivativeQuestionInvalidError("model must be a provider model id", { field: "model" });
|
|
74
|
+
}
|
|
75
|
+
model = body.model;
|
|
76
|
+
}
|
|
77
|
+
assertDerivedScopeNaming(derivedScope, sourceScopes);
|
|
78
|
+
return { derivedScope, sourceScopes, question: body.question, model };
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Recompute-on-refresh follows edges source -> derived. A registration whose
|
|
82
|
+
* derived scope can reach one of its own sources through other
|
|
83
|
+
* registrations would recompute forever (A from B, B from A). Refuse it.
|
|
84
|
+
* Returns the offending path (derived scope back to itself) or null.
|
|
85
|
+
*/
|
|
86
|
+
export function findDerivationCycle(candidate, existing) {
|
|
87
|
+
// derived scope -> the scopes it is computed from
|
|
88
|
+
const sourcesOf = new Map();
|
|
89
|
+
const add = (derived, sources) => {
|
|
90
|
+
const set = sourcesOf.get(derived) ?? new Set();
|
|
91
|
+
for (const source of sources)
|
|
92
|
+
set.add(source);
|
|
93
|
+
sourcesOf.set(derived, set);
|
|
94
|
+
};
|
|
95
|
+
for (const registration of existing) {
|
|
96
|
+
add(registration.derivedScope, registration.sourceScopes);
|
|
97
|
+
}
|
|
98
|
+
add(candidate.derivedScope, candidate.sourceScopes);
|
|
99
|
+
// Depth-first from the candidate's derived scope through the sources it
|
|
100
|
+
// depends on; reaching the derived scope again is a cycle. Bounded by the
|
|
101
|
+
// number of distinct scopes (visited set), so no unbounded walk.
|
|
102
|
+
const target = candidate.derivedScope;
|
|
103
|
+
const visited = new Set();
|
|
104
|
+
const stack = [
|
|
105
|
+
{ scope: target, path: [target] },
|
|
106
|
+
];
|
|
107
|
+
while (stack.length > 0) {
|
|
108
|
+
const { scope, path } = stack.pop();
|
|
109
|
+
for (const source of sourcesOf.get(scope) ?? []) {
|
|
110
|
+
if (source === target)
|
|
111
|
+
return [...path, source];
|
|
112
|
+
if (visited.has(source))
|
|
113
|
+
continue;
|
|
114
|
+
visited.add(source);
|
|
115
|
+
stack.push({ scope: source, path: [...path, source] });
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Validate, guard against cycles and persist a registration in `pending`.
|
|
122
|
+
* The caller has already authorized the write on `derivedScope`.
|
|
123
|
+
*/
|
|
124
|
+
export async function createQuestionRegistration(input) {
|
|
125
|
+
const parsed = parseQuestionInput(input.body);
|
|
126
|
+
const cycle = findDerivationCycle(parsed, await input.store.list());
|
|
127
|
+
if (cycle) {
|
|
128
|
+
throw new DerivativeCycleError({
|
|
129
|
+
derivedScope: parsed.derivedScope,
|
|
130
|
+
path: cycle,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
const at = input.now().toISOString();
|
|
134
|
+
const registration = {
|
|
135
|
+
questionId: input.questionId,
|
|
136
|
+
derivedScope: parsed.derivedScope,
|
|
137
|
+
sourceScopes: parsed.sourceScopes,
|
|
138
|
+
question: parsed.question,
|
|
139
|
+
model: parsed.model,
|
|
140
|
+
registeredBy: input.registeredBy,
|
|
141
|
+
status: "pending",
|
|
142
|
+
error: null,
|
|
143
|
+
createdAt: at,
|
|
144
|
+
updatedAt: at,
|
|
145
|
+
lastComputedAt: null,
|
|
146
|
+
derivedVersion: null,
|
|
147
|
+
derivedCollectedAt: null,
|
|
148
|
+
};
|
|
149
|
+
await input.store.insert(registration);
|
|
150
|
+
return registration;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Consent check for a builder question: the sources feed a prompt whose
|
|
154
|
+
* answer the builder reads, so every source scope must be covered by a READ
|
|
155
|
+
* entry of the builder's grant (bare entries; `write:` entries confer
|
|
156
|
+
* nothing). Returns the uncovered scopes; empty = all covered. The system
|
|
157
|
+
* prompt is not a security boundary, this is.
|
|
158
|
+
*/
|
|
159
|
+
export function uncoveredSourceScopes(sourceScopes, grantScopes) {
|
|
160
|
+
const readEntries = (grantScopes ?? []).filter((entry) => !isWriteScopeEntry(entry));
|
|
161
|
+
return sourceScopes.filter((scope) => !scopeCoveredByGrant(scope, readEntries));
|
|
162
|
+
}
|
|
163
|
+
//# sourceMappingURL=registration.js.map
|