@opendatalabs/personal-server-ts-server 1.13.1 → 1.15.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.
@@ -1 +1 @@
1
- {"version":3,"file":"question-store.d.ts","sourceRoot":"","sources":["../../src/storage/question-store.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAIL,KAAK,aAAa,EACnB,MAAM,mDAAmD,CAAC;AAgE3D,wBAAgB,yBAAyB,CACvC,EAAE,EAAE,QAAQ,CAAC,QAAQ,GACpB,aAAa,CAsFf"}
1
+ {"version":3,"file":"question-store.d.ts","sourceRoot":"","sources":["../../src/storage/question-store.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAML,KAAK,aAAa,EACnB,MAAM,mDAAmD,CAAC;AAkG3D,wBAAgB,yBAAyB,CACvC,EAAE,EAAE,QAAQ,CAAC,QAAQ,GACpB,aAAa,CAwGf"}
@@ -11,16 +11,44 @@ CREATE TABLE IF NOT EXISTS derivative_questions (
11
11
  source_scopes TEXT NOT NULL,
12
12
  question TEXT NOT NULL,
13
13
  model TEXT,
14
+ answer_shape TEXT,
14
15
  recompute TEXT NOT NULL DEFAULT 'on-change',
15
16
  registered_by TEXT NOT NULL,
16
17
  status TEXT NOT NULL,
17
18
  error TEXT,
19
+ error_code TEXT,
18
20
  created_at TEXT NOT NULL,
19
21
  updated_at TEXT NOT NULL,
20
22
  last_computed_at TEXT,
21
23
  derived_version INTEGER,
22
24
  derived_collected_at TEXT
23
25
  )`;
26
+ /**
27
+ * Databases created before the column existed migrate in place. ALTER is
28
+ * guarded by a pragma check because SQLite has no ADD COLUMN IF NOT EXISTS;
29
+ * existing rows read back as null (pre-classification failures).
30
+ */
31
+ function ensureErrorCodeColumn(db) {
32
+ const columns = db
33
+ .prepare("PRAGMA table_info(derivative_questions)")
34
+ .all();
35
+ if (columns.some((column) => column.name === "error_code"))
36
+ return;
37
+ db.exec("ALTER TABLE derivative_questions ADD COLUMN error_code TEXT");
38
+ }
39
+ /**
40
+ * Same in-place migration for the declared answer shape (stored as its
41
+ * JSON): rows written before it existed read back as null, which is the
42
+ * free-text answer the question was registered with.
43
+ */
44
+ function ensureAnswerShapeColumn(db) {
45
+ const columns = db
46
+ .prepare("PRAGMA table_info(derivative_questions)")
47
+ .all();
48
+ if (columns.some((column) => column.name === "answer_shape"))
49
+ return;
50
+ db.exec("ALTER TABLE derivative_questions ADD COLUMN answer_shape TEXT");
51
+ }
24
52
  const CREATE_INDEX_SQL = "CREATE INDEX IF NOT EXISTS idx_derivative_questions_derived_scope ON derivative_questions (derived_scope)";
25
53
  // Databases created before the recompute policy existed migrate in place;
26
54
  // their registrations keep the old follow-every-change behavior.
@@ -32,10 +60,14 @@ function toRegistration(row) {
32
60
  sourceScopes: JSON.parse(row.source_scopes),
33
61
  question: row.question,
34
62
  model: row.model,
63
+ answerShape: row.answer_shape
64
+ ? JSON.parse(row.answer_shape)
65
+ : null,
35
66
  recompute: row.recompute,
36
67
  registeredBy: JSON.parse(row.registered_by),
37
68
  status: row.status,
38
69
  error: row.error,
70
+ errorCode: row.error_code ?? null,
39
71
  createdAt: row.created_at,
40
72
  updatedAt: row.updated_at,
41
73
  lastComputedAt: row.last_computed_at,
@@ -45,6 +77,8 @@ function toRegistration(row) {
45
77
  }
46
78
  export function createSqliteQuestionStore(db) {
47
79
  db.exec(CREATE_TABLE_SQL);
80
+ ensureErrorCodeColumn(db);
81
+ ensureAnswerShapeColumn(db);
48
82
  db.exec(CREATE_INDEX_SQL);
49
83
  const columns = db
50
84
  .prepare("PRAGMA table_info(derivative_questions)")
@@ -53,21 +87,26 @@ export function createSqliteQuestionStore(db) {
53
87
  db.exec(ADD_RECOMPUTE_COLUMN_SQL);
54
88
  }
55
89
  const listAll = db.prepare("SELECT * FROM derivative_questions ORDER BY created_at ASC, question_id ASC");
90
+ // The status route polls list({ derivedScope }) on every reader request;
91
+ // this is the query idx_derivative_questions_derived_scope exists for.
92
+ const listByDerivedScope = db.prepare("SELECT * FROM derivative_questions WHERE derived_scope = ? ORDER BY created_at ASC, question_id ASC");
56
93
  const getOne = db.prepare("SELECT * FROM derivative_questions WHERE question_id = ?");
57
94
  const insertOne = db.prepare(`
58
95
  INSERT INTO derivative_questions (
59
- question_id, derived_scope, source_scopes, question, model, recompute,
60
- registered_by, status, error, created_at, updated_at,
96
+ question_id, derived_scope, source_scopes, question, model, answer_shape,
97
+ recompute, registered_by, status, error, error_code, created_at, updated_at,
61
98
  last_computed_at, derived_version, derived_collected_at
62
99
  ) VALUES (
63
100
  @question_id, @derived_scope, @source_scopes, @question, @model,
64
- @recompute, @registered_by, @status, @error, @created_at, @updated_at,
101
+ @answer_shape, @recompute, @registered_by, @status, @error, @error_code,
102
+ @created_at, @updated_at,
65
103
  @last_computed_at, @derived_version, @derived_collected_at
66
104
  )`);
67
105
  const updateOne = db.prepare(`
68
106
  UPDATE derivative_questions SET
69
107
  status = @status,
70
108
  error = @error,
109
+ error_code = @error_code,
71
110
  updated_at = @updated_at,
72
111
  last_computed_at = @last_computed_at,
73
112
  derived_version = @derived_version,
@@ -76,7 +115,10 @@ export function createSqliteQuestionStore(db) {
76
115
  const deleteOne = db.prepare("DELETE FROM derivative_questions WHERE question_id = ?");
77
116
  return {
78
117
  async list(filter) {
79
- return listAll.all()
118
+ const rows = filter?.derivedScope !== undefined
119
+ ? listByDerivedScope.all(filter.derivedScope)
120
+ : listAll.all();
121
+ return rows
80
122
  .map(toRegistration)
81
123
  .filter((registration) => matchesQuestionFilter(registration, filter));
82
124
  },
@@ -91,10 +133,14 @@ export function createSqliteQuestionStore(db) {
91
133
  source_scopes: JSON.stringify(registration.sourceScopes),
92
134
  question: registration.question,
93
135
  model: registration.model,
136
+ answer_shape: registration.answerShape
137
+ ? JSON.stringify(registration.answerShape)
138
+ : null,
94
139
  recompute: registration.recompute,
95
140
  registered_by: JSON.stringify(registration.registeredBy),
96
141
  status: registration.status,
97
142
  error: registration.error,
143
+ error_code: registration.errorCode,
98
144
  created_at: registration.createdAt,
99
145
  updated_at: registration.updatedAt,
100
146
  last_computed_at: registration.lastComputedAt,
@@ -111,6 +157,7 @@ export function createSqliteQuestionStore(db) {
111
157
  question_id: questionId,
112
158
  status: merged.status,
113
159
  error: merged.error,
160
+ error_code: merged.errorCode ?? null,
114
161
  updated_at: merged.updatedAt,
115
162
  last_computed_at: merged.lastComputedAt,
116
163
  derived_version: merged.derivedVersion,
@@ -1 +1 @@
1
- {"version":3,"file":"question-store.js","sourceRoot":"","sources":["../../src/storage/question-store.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"question-store.js","sourceRoot":"","sources":["../../src/storage/question-store.ts"],"names":[],"mappings":"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"}
@@ -61644,8 +61644,8 @@ var require_multipleOf = __commonJS({
61644
61644
  const { gen: gen2, data, schemaCode, it: it3 } = cxt;
61645
61645
  const prec = it3.opts.multipleOfPrecision;
61646
61646
  const res = gen2.let("res");
61647
- const invalid = prec ? (0, codegen_1._)`Math.abs(Math.round(${res}) - ${res}) > 1e-${prec}` : (0, codegen_1._)`${res} !== parseInt(${res})`;
61648
- cxt.fail$data((0, codegen_1._)`(${schemaCode} === 0 || (${res} = ${data}/${schemaCode}, ${invalid}))`);
61647
+ const invalid2 = prec ? (0, codegen_1._)`Math.abs(Math.round(${res}) - ${res}) > 1e-${prec}` : (0, codegen_1._)`${res} !== parseInt(${res})`;
61648
+ cxt.fail$data((0, codegen_1._)`(${schemaCode} === 0 || (${res} = ${data}/${schemaCode}, ${invalid2}))`);
61649
61649
  }
61650
61650
  };
61651
61651
  exports.default = def;
@@ -67542,8 +67542,8 @@ var require_multipleOf2 = __commonJS({
67542
67542
  const { gen: gen2, data, schemaCode, it: it3 } = cxt;
67543
67543
  const prec = it3.opts.multipleOfPrecision;
67544
67544
  const res = gen2.let("res");
67545
- const invalid = prec ? (0, codegen_1._)`Math.abs(Math.round(${res}) - ${res}) > 1e-${prec}` : (0, codegen_1._)`${res} !== parseInt(${res})`;
67546
- cxt.fail$data((0, codegen_1._)`(${schemaCode} === 0 || (${res} = ${data}/${schemaCode}, ${invalid}))`);
67545
+ const invalid2 = prec ? (0, codegen_1._)`Math.abs(Math.round(${res}) - ${res}) > 1e-${prec}` : (0, codegen_1._)`${res} !== parseInt(${res})`;
67546
+ cxt.fail$data((0, codegen_1._)`(${schemaCode} === 0 || (${res} = ${data}/${schemaCode}, ${invalid2}))`);
67547
67547
  }
67548
67548
  };
67549
67549
  exports.default = def;
@@ -114031,6 +114031,258 @@ async function loadOrCreatePsLiteServerIdentity(params) {
114031
114031
  return { persisted, account };
114032
114032
  }
114033
114033
 
114034
+ // ../core/dist/derivatives/answer-shape.js
114035
+ var MAX_ANSWER_SHAPE_FIELDS = 16;
114036
+ var MAX_ANSWER_FIELD_NAME_CHARS = 64;
114037
+ var MAX_ANSWER_STRING_CHARS = 4e3;
114038
+ var MAX_ANSWER_ENUM_VALUES = 32;
114039
+ var MAX_ANSWER_ENUM_VALUE_CHARS = 64;
114040
+ var FIELD_NAME = /^[A-Za-z][A-Za-z0-9_]*$/;
114041
+ var FIELD_TYPES = [
114042
+ "string",
114043
+ "number",
114044
+ "integer",
114045
+ "boolean",
114046
+ "enum"
114047
+ ];
114048
+ var KEYS_BY_TYPE = {
114049
+ string: ["name", "type", "required", "maxLength"],
114050
+ number: ["name", "type", "required", "min", "max"],
114051
+ integer: ["name", "type", "required", "min", "max"],
114052
+ boolean: ["name", "type", "required"],
114053
+ enum: ["name", "type", "required", "values"]
114054
+ };
114055
+ function isRecord4(value) {
114056
+ return value !== null && typeof value === "object" && !Array.isArray(value);
114057
+ }
114058
+ function invalid(message, details) {
114059
+ throw new DerivativeQuestionInvalidError(message, {
114060
+ field: "answerShape",
114061
+ ...details
114062
+ });
114063
+ }
114064
+ function parseFieldName(value, index) {
114065
+ if (typeof value !== "string" || value === "") {
114066
+ invalid(`answerShape.fields[${index}].name must be a non-empty string`, {
114067
+ index
114068
+ });
114069
+ }
114070
+ if (value.length > MAX_ANSWER_FIELD_NAME_CHARS) {
114071
+ invalid(`answerShape.fields[${index}].name is ${value.length} characters; the maximum is ${MAX_ANSWER_FIELD_NAME_CHARS}`, { index, max: MAX_ANSWER_FIELD_NAME_CHARS });
114072
+ }
114073
+ if (!FIELD_NAME.test(value)) {
114074
+ invalid(`answerShape.fields[${index}].name must start with a letter and hold only letters, digits and underscores`, { index });
114075
+ }
114076
+ return value;
114077
+ }
114078
+ function parseBound(value, index, key, integer2) {
114079
+ if (typeof value !== "number" || !Number.isFinite(value)) {
114080
+ invalid(`answerShape.fields[${index}].${key} must be a finite number`, {
114081
+ index,
114082
+ key
114083
+ });
114084
+ }
114085
+ if (integer2 && !Number.isSafeInteger(value)) {
114086
+ invalid(`answerShape.fields[${index}].${key} must be a safe integer for an integer field`, { index, key });
114087
+ }
114088
+ return value;
114089
+ }
114090
+ function parseEnumValues(value, index) {
114091
+ if (!Array.isArray(value) || value.length === 0) {
114092
+ invalid(`answerShape.fields[${index}].values must be a non-empty array of strings`, { index });
114093
+ }
114094
+ if (value.length > MAX_ANSWER_ENUM_VALUES) {
114095
+ invalid(`answerShape.fields[${index}].values lists ${value.length} values; the maximum is ${MAX_ANSWER_ENUM_VALUES}`, { index, max: MAX_ANSWER_ENUM_VALUES });
114096
+ }
114097
+ const values = [];
114098
+ for (const entry of value) {
114099
+ if (typeof entry !== "string" || entry === "") {
114100
+ invalid(`answerShape.fields[${index}].values must hold non-empty strings`, { index });
114101
+ }
114102
+ if (entry.length > MAX_ANSWER_ENUM_VALUE_CHARS) {
114103
+ invalid(`answerShape.fields[${index}].values holds a ${entry.length} character value; the maximum is ${MAX_ANSWER_ENUM_VALUE_CHARS}`, { index, max: MAX_ANSWER_ENUM_VALUE_CHARS });
114104
+ }
114105
+ if (/[\n\r\t]/.test(entry)) {
114106
+ invalid(`answerShape.fields[${index}].values must not hold line breaks or tabs`, { index });
114107
+ }
114108
+ if (values.includes(entry)) {
114109
+ invalid(`answerShape.fields[${index}].values lists the same value twice`, { index });
114110
+ }
114111
+ values.push(entry);
114112
+ }
114113
+ return values;
114114
+ }
114115
+ function parseField(value, index) {
114116
+ if (!isRecord4(value)) {
114117
+ invalid(`answerShape.fields[${index}] must be an object`, { index });
114118
+ }
114119
+ const name = parseFieldName(value.name, index);
114120
+ if (typeof value.type !== "string" || !FIELD_TYPES.includes(value.type)) {
114121
+ invalid(`answerShape.fields[${index}].type must be one of ${FIELD_TYPES.join(", ")}`, { index });
114122
+ }
114123
+ const type = value.type;
114124
+ const allowed = KEYS_BY_TYPE[type];
114125
+ for (const key of Object.keys(value)) {
114126
+ if (!allowed.includes(key)) {
114127
+ invalid(`answerShape.fields[${index}] does not accept "${key}" on a ${type} field`, { index, key: key.slice(0, MAX_ANSWER_FIELD_NAME_CHARS) });
114128
+ }
114129
+ }
114130
+ let required2 = true;
114131
+ if (value.required !== void 0 && value.required !== null) {
114132
+ if (typeof value.required !== "boolean") {
114133
+ invalid(`answerShape.fields[${index}].required must be a boolean`, {
114134
+ index
114135
+ });
114136
+ }
114137
+ required2 = value.required;
114138
+ }
114139
+ const field = { name, type, required: required2 };
114140
+ if (type === "string") {
114141
+ let maxLength = MAX_ANSWER_STRING_CHARS;
114142
+ if (value.maxLength !== void 0 && value.maxLength !== null) {
114143
+ if (typeof value.maxLength !== "number" || !Number.isSafeInteger(value.maxLength) || value.maxLength < 1 || value.maxLength > MAX_ANSWER_STRING_CHARS) {
114144
+ invalid(`answerShape.fields[${index}].maxLength must be an integer between 1 and ${MAX_ANSWER_STRING_CHARS}`, { index, max: MAX_ANSWER_STRING_CHARS });
114145
+ }
114146
+ maxLength = value.maxLength;
114147
+ }
114148
+ field.maxLength = maxLength;
114149
+ }
114150
+ if (type === "number" || type === "integer") {
114151
+ const integer2 = type === "integer";
114152
+ if (value.min !== void 0 && value.min !== null) {
114153
+ field.min = parseBound(value.min, index, "min", integer2);
114154
+ }
114155
+ if (value.max !== void 0 && value.max !== null) {
114156
+ field.max = parseBound(value.max, index, "max", integer2);
114157
+ }
114158
+ if (field.min !== void 0 && field.max !== void 0 && field.min > field.max) {
114159
+ invalid(`answerShape.fields[${index}].min is greater than its max`, {
114160
+ index
114161
+ });
114162
+ }
114163
+ }
114164
+ if (type === "enum") {
114165
+ field.values = parseEnumValues(value.values, index);
114166
+ }
114167
+ return field;
114168
+ }
114169
+ function parseAnswerShapeInput(value) {
114170
+ if (value === void 0 || value === null)
114171
+ return null;
114172
+ if (!isRecord4(value)) {
114173
+ invalid("answerShape must be an object with a fields array", {});
114174
+ }
114175
+ for (const key of Object.keys(value)) {
114176
+ if (key !== "fields") {
114177
+ invalid(`answerShape does not accept "${key}"`, {
114178
+ key: key.slice(0, MAX_ANSWER_FIELD_NAME_CHARS)
114179
+ });
114180
+ }
114181
+ }
114182
+ if (!Array.isArray(value.fields) || value.fields.length === 0) {
114183
+ invalid("answerShape.fields must be a non-empty array", {});
114184
+ }
114185
+ if (value.fields.length > MAX_ANSWER_SHAPE_FIELDS) {
114186
+ invalid(`answerShape.fields lists ${value.fields.length} fields; the maximum is ${MAX_ANSWER_SHAPE_FIELDS}`, { max: MAX_ANSWER_SHAPE_FIELDS });
114187
+ }
114188
+ const fields = [];
114189
+ const names = /* @__PURE__ */ new Set();
114190
+ value.fields.forEach((entry, index) => {
114191
+ const field = parseField(entry, index);
114192
+ if (names.has(field.name)) {
114193
+ invalid(`answerShape.fields names "${field.name}" twice`, { index });
114194
+ }
114195
+ names.add(field.name);
114196
+ fields.push(field);
114197
+ });
114198
+ return { fields };
114199
+ }
114200
+ function compileAnswerShape(shape) {
114201
+ const entries = {};
114202
+ for (const field of shape.fields) {
114203
+ let schema;
114204
+ switch (field.type) {
114205
+ case "string":
114206
+ schema = external_exports.string().max(field.maxLength ?? MAX_ANSWER_STRING_CHARS);
114207
+ break;
114208
+ case "number":
114209
+ case "integer": {
114210
+ let numeric = field.type === "integer" ? external_exports.int() : external_exports.number();
114211
+ if (field.min !== void 0)
114212
+ numeric = numeric.min(field.min);
114213
+ if (field.max !== void 0)
114214
+ numeric = numeric.max(field.max);
114215
+ schema = numeric;
114216
+ break;
114217
+ }
114218
+ case "boolean":
114219
+ schema = external_exports.boolean();
114220
+ break;
114221
+ case "enum":
114222
+ schema = external_exports.enum(field.values ?? []);
114223
+ break;
114224
+ }
114225
+ entries[field.name] = field.required ? schema : schema.nullish();
114226
+ }
114227
+ return external_exports.object(entries);
114228
+ }
114229
+ function validateAnswerShape(shape, compiled, candidate) {
114230
+ const parsed = compiled.safeParse(candidate);
114231
+ if (!parsed.success) {
114232
+ return {
114233
+ ok: false,
114234
+ // zod's messages state the constraint ("expected int", "<=5") and
114235
+ // never echo the received value, so they are safe to hand back to
114236
+ // the model in the corrective turn.
114237
+ issues: parsed.error.issues.map((issue2) => {
114238
+ const path = issue2.path.join(".");
114239
+ return path ? `${path}: ${issue2.message}` : issue2.message;
114240
+ })
114241
+ };
114242
+ }
114243
+ const value = {};
114244
+ for (const field of shape.fields) {
114245
+ const entry = parsed.data[field.name];
114246
+ if (entry === void 0 || entry === null)
114247
+ continue;
114248
+ value[field.name] = entry;
114249
+ }
114250
+ return { ok: true, value };
114251
+ }
114252
+ function describeField(field) {
114253
+ const requirement = field.required ? "required" : "optional";
114254
+ switch (field.type) {
114255
+ case "string":
114256
+ return `"${field.name}": string, at most ${field.maxLength ?? MAX_ANSWER_STRING_CHARS} characters (${requirement})`;
114257
+ case "number":
114258
+ case "integer": {
114259
+ const kind = field.type === "integer" ? "integer" : "number";
114260
+ const range = field.min !== void 0 && field.max !== void 0 ? ` between ${field.min} and ${field.max}` : field.min !== void 0 ? ` of at least ${field.min}` : field.max !== void 0 ? ` of at most ${field.max}` : "";
114261
+ return `"${field.name}": ${kind}${range} (${requirement})`;
114262
+ }
114263
+ case "boolean":
114264
+ return `"${field.name}": true or false (${requirement})`;
114265
+ case "enum":
114266
+ return `"${field.name}": exactly one of ${(field.values ?? []).map((entry) => JSON.stringify(entry)).join(", ")} (${requirement})`;
114267
+ }
114268
+ }
114269
+ function describeAnswerShape(shape) {
114270
+ return shape.fields.map((field) => ` ${describeField(field)}`);
114271
+ }
114272
+ function renderShapedAnswer(shape, value) {
114273
+ return shape.fields.filter((field) => value[field.name] !== void 0).map((field) => `${field.name}: ${String(value[field.name])}`).join("\n");
114274
+ }
114275
+ function cloneAnswerShape(shape) {
114276
+ if (!shape)
114277
+ return null;
114278
+ return {
114279
+ fields: shape.fields.map((field) => ({
114280
+ ...field,
114281
+ ...field.values ? { values: [...field.values] } : {}
114282
+ }))
114283
+ };
114284
+ }
114285
+
114034
114286
  // ../core/dist/derivatives/types.js
114035
114287
  function questionRegistrationView(registration) {
114036
114288
  return {
@@ -114039,10 +114291,12 @@ function questionRegistrationView(registration) {
114039
114291
  sourceScopes: [...registration.sourceScopes],
114040
114292
  question: registration.question,
114041
114293
  model: registration.model,
114294
+ answerShape: cloneAnswerShape(registration.answerShape),
114042
114295
  recompute: registration.recompute,
114043
114296
  registeredBy: registration.registeredBy,
114044
114297
  status: registration.status,
114045
114298
  error: registration.error,
114299
+ errorCode: registration.errorCode,
114046
114300
  createdAt: registration.createdAt,
114047
114301
  updatedAt: registration.updatedAt,
114048
114302
  lastComputedAt: registration.lastComputedAt,
@@ -114055,6 +114309,9 @@ function questionRegistrationView(registration) {
114055
114309
  function clone2(registration) {
114056
114310
  return {
114057
114311
  ...registration,
114312
+ // Registrations persisted before the field existed load as null.
114313
+ errorCode: registration.errorCode ?? null,
114314
+ answerShape: cloneAnswerShape(registration.answerShape ?? null),
114058
114315
  sourceScopes: [...registration.sourceScopes],
114059
114316
  registeredBy: { ...registration.registeredBy }
114060
114317
  };
@@ -114125,7 +114382,7 @@ var MAX_QUESTION_CHARS = 8e3;
114125
114382
  var MAX_MODEL_CHARS = 128;
114126
114383
  var MAX_ECHOED_SCOPE_CHARS = 128;
114127
114384
  var MODEL_ID = /^[A-Za-z0-9][A-Za-z0-9._:/-]*$/;
114128
- function isRecord4(value) {
114385
+ function isRecord5(value) {
114129
114386
  return value !== null && typeof value === "object" && !Array.isArray(value);
114130
114387
  }
114131
114388
  function parseScope(value, field) {
@@ -114141,7 +114398,7 @@ function parseScope(value, field) {
114141
114398
  return parsed.scope;
114142
114399
  }
114143
114400
  function parseQuestionInput(body) {
114144
- if (!isRecord4(body)) {
114401
+ if (!isRecord5(body)) {
114145
114402
  throw new DerivativeQuestionInvalidError("Body must be a JSON object");
114146
114403
  }
114147
114404
  const derivedScope = parseScope(body.derivedScope, "derivedScope");
@@ -114175,6 +114432,7 @@ function parseQuestionInput(body) {
114175
114432
  }
114176
114433
  model = body.model;
114177
114434
  }
114435
+ const answerShape = parseAnswerShapeInput(body.answerShape);
114178
114436
  let recompute = "on-change";
114179
114437
  if (body.recompute !== void 0 && body.recompute !== null) {
114180
114438
  if (body.recompute !== "snapshot" && body.recompute !== "on-change") {
@@ -114188,6 +114446,7 @@ function parseQuestionInput(body) {
114188
114446
  sourceScopes,
114189
114447
  question: body.question,
114190
114448
  model,
114449
+ answerShape,
114191
114450
  recompute
114192
114451
  };
114193
114452
  }
@@ -114237,10 +114496,12 @@ async function createQuestionRegistration(input) {
114237
114496
  sourceScopes: parsed.sourceScopes,
114238
114497
  question: parsed.question,
114239
114498
  model: parsed.model,
114499
+ answerShape: parsed.answerShape,
114240
114500
  recompute: parsed.recompute,
114241
114501
  registeredBy: input.registeredBy,
114242
114502
  status: "pending",
114243
114503
  error: null,
114504
+ errorCode: null,
114244
114505
  createdAt: at3,
114245
114506
  updatedAt: at3,
114246
114507
  lastComputedAt: null,
@@ -114273,11 +114534,11 @@ var TIMESTAMP_KEYS = [
114273
114534
  "published_at"
114274
114535
  ];
114275
114536
  var RESERVED_KEYS = /* @__PURE__ */ new Set(["$lineage", "$writtenBy", "$binary"]);
114276
- function isRecord5(value) {
114537
+ function isRecord6(value) {
114277
114538
  return value !== null && typeof value === "object" && !Array.isArray(value);
114278
114539
  }
114279
114540
  function timestampOf(item) {
114280
- if (!isRecord5(item))
114541
+ if (!isRecord6(item))
114281
114542
  return null;
114282
114543
  for (const key of TIMESTAMP_KEYS) {
114283
114544
  const value = item[key];
@@ -114318,7 +114579,7 @@ function trimSourceData(data, options = {}) {
114318
114579
  total = 0;
114319
114580
  if (Array.isArray(data))
114320
114581
  return trimArray(data, limit2);
114321
- if (isRecord5(data)) {
114582
+ if (isRecord6(data)) {
114322
114583
  const stamped = "$lineage" in data;
114323
114584
  const out = {};
114324
114585
  for (const [key, value] of Object.entries(data)) {
@@ -114350,13 +114611,28 @@ function trimSourceData(data, options = {}) {
114350
114611
  }
114351
114612
  return { data: result, kept, total, truncated: false };
114352
114613
  }
114353
- var SYSTEM_PROMPT = [
114614
+ var GROUNDING = [
114354
114615
  "You answer a question about a person using ONLY the user data provided in the message.",
114355
- "Do not use outside knowledge and do not guess; if the data does not support an answer, say so in the answer.",
114616
+ "Do not use outside knowledge and do not guess; if the data does not support an answer, say so in the answer."
114617
+ ];
114618
+ var SYSTEM_PROMPT = [
114619
+ ...GROUNDING,
114356
114620
  "Respond with a single JSON object and nothing else, with exactly these fields:",
114357
114621
  ' "answer": string, the answer to the question, written for the person the data belongs to;',
114358
114622
  ' "evidence": string, a short summary of which parts of the data support the answer.'
114359
114623
  ].join("\n");
114624
+ function shapedSystemPrompt(shape) {
114625
+ return [
114626
+ ...GROUNDING,
114627
+ "Respond with a single JSON object and nothing else, with exactly these fields:",
114628
+ ' "answer": object, holding exactly the fields listed below and no others;',
114629
+ ' "evidence": string, a short summary of which parts of the data support the answer.',
114630
+ 'The "answer" object holds:',
114631
+ ...describeAnswerShape(shape),
114632
+ "Use the declared type for every field: a number field is a JSON number, not a string.",
114633
+ "Do not add fields, do not rename them and do not put prose outside them."
114634
+ ].join("\n");
114635
+ }
114360
114636
  function buildQuestionMessages(input) {
114361
114637
  const sections = input.sources.map((source) => {
114362
114638
  const note = source.total > source.kept ? ` (newest ${source.kept} of ${source.total} items)` : "";
@@ -114374,14 +114650,34 @@ function buildQuestionMessages(input) {
114374
114650
  "## User data",
114375
114651
  ...sections,
114376
114652
  "",
114377
- "Answer the question as a JSON object with the fields answer and evidence."
114653
+ input.answerShape ? "Answer the question as a JSON object with the fields answer and evidence, where answer holds exactly the declared fields." : "Answer the question as a JSON object with the fields answer and evidence."
114378
114654
  ].join("\n");
114379
114655
  return [
114380
- { role: "system", content: SYSTEM_PROMPT },
114656
+ {
114657
+ role: "system",
114658
+ content: input.answerShape ? shapedSystemPrompt(input.answerShape) : SYSTEM_PROMPT
114659
+ },
114381
114660
  { role: "user", content: user }
114382
114661
  ];
114383
114662
  }
114384
- function parseAnswer(content) {
114663
+ function buildShapeRetryMessages(input) {
114664
+ return [
114665
+ ...input.messages,
114666
+ { role: "assistant", content: input.reply },
114667
+ {
114668
+ role: "user",
114669
+ content: [
114670
+ "That reply did not match the required answer shape:",
114671
+ ...input.issues.map((issue2) => `- ${issue2}`),
114672
+ "",
114673
+ 'Answer again as a single JSON object with the fields answer and evidence, where "answer" holds:',
114674
+ ...describeAnswerShape(input.answerShape),
114675
+ "Return the JSON object and nothing else."
114676
+ ].join("\n")
114677
+ }
114678
+ ];
114679
+ }
114680
+ function jsonFramings(content) {
114385
114681
  const candidates = [content.trim()];
114386
114682
  const fenced = /```(?:json)?\s*([\s\S]*?)```/i.exec(content);
114387
114683
  if (fenced?.[1])
@@ -114391,10 +114687,13 @@ function parseAnswer(content) {
114391
114687
  if (first !== -1 && last2 > first) {
114392
114688
  candidates.push(content.slice(first, last2 + 1));
114393
114689
  }
114394
- for (const candidate of candidates) {
114690
+ return [...new Set(candidates)];
114691
+ }
114692
+ function parseAnswer(content) {
114693
+ for (const candidate of jsonFramings(content)) {
114395
114694
  try {
114396
114695
  const parsed = JSON.parse(candidate);
114397
- if (isRecord5(parsed) && typeof parsed.answer === "string") {
114696
+ if (isRecord6(parsed) && typeof parsed.answer === "string") {
114398
114697
  return {
114399
114698
  answer: parsed.answer,
114400
114699
  evidence: typeof parsed.evidence === "string" ? parsed.evidence : null
@@ -114405,6 +114704,25 @@ function parseAnswer(content) {
114405
114704
  }
114406
114705
  return { answer: content.trim(), evidence: null };
114407
114706
  }
114707
+ function parseShapedAnswer(content) {
114708
+ const found = [];
114709
+ for (const candidate of jsonFramings(content)) {
114710
+ let parsed;
114711
+ try {
114712
+ parsed = JSON.parse(candidate);
114713
+ } catch {
114714
+ continue;
114715
+ }
114716
+ if (!isRecord6(parsed))
114717
+ continue;
114718
+ const evidence = typeof parsed.evidence === "string" ? parsed.evidence : null;
114719
+ if (isRecord6(parsed.answer)) {
114720
+ found.push({ answer: parsed.answer, evidence });
114721
+ }
114722
+ found.push({ answer: parsed, evidence });
114723
+ }
114724
+ return found;
114725
+ }
114408
114726
 
114409
114727
  // ../core/dist/derivatives/inference.js
114410
114728
  var DEFAULT_INFERENCE_BASE_URL = "https://inference.phala.com/v1";
@@ -114431,11 +114749,11 @@ var InferenceRequestError = class extends Error {
114431
114749
  this.retryable = options.retryable;
114432
114750
  }
114433
114751
  };
114434
- function isRecord6(value) {
114752
+ function isRecord7(value) {
114435
114753
  return value !== null && typeof value === "object" && !Array.isArray(value);
114436
114754
  }
114437
114755
  function readUsage(value) {
114438
- if (!isRecord6(value))
114756
+ if (!isRecord7(value))
114439
114757
  return void 0;
114440
114758
  const num2 = (v10) => typeof v10 === "number" ? v10 : void 0;
114441
114759
  const usage = {
@@ -114446,10 +114764,10 @@ function readUsage(value) {
114446
114764
  return usage;
114447
114765
  }
114448
114766
  function readContent(body) {
114449
- if (!isRecord6(body) || !Array.isArray(body.choices))
114767
+ if (!isRecord7(body) || !Array.isArray(body.choices))
114450
114768
  return null;
114451
114769
  const first = body.choices[0];
114452
- if (!isRecord6(first) || !isRecord6(first.message))
114770
+ if (!isRecord7(first) || !isRecord7(first.message))
114453
114771
  return null;
114454
114772
  const index = typeof first.index === "number" ? first.index : 0;
114455
114773
  const field = `choices.${index}.message.content`;
@@ -114458,7 +114776,7 @@ function readContent(body) {
114458
114776
  if (typeof content === "string")
114459
114777
  return { content, field, id: id2 };
114460
114778
  if (Array.isArray(content)) {
114461
- const text = content.map((part) => isRecord6(part) && typeof part.text === "string" ? part.text : "").join("");
114779
+ const text = content.map((part) => isRecord7(part) && typeof part.text === "string" ? part.text : "").join("");
114462
114780
  return { content: text, field, id: id2 };
114463
114781
  }
114464
114782
  return null;
@@ -114466,7 +114784,7 @@ function readContent(body) {
114466
114784
  async function readErrorType(response) {
114467
114785
  try {
114468
114786
  const body = await response.json();
114469
- if (isRecord6(body) && isRecord6(body.error)) {
114787
+ if (isRecord7(body) && isRecord7(body.error)) {
114470
114788
  return typeof body.error.type === "string" ? body.error.type : null;
114471
114789
  }
114472
114790
  } catch {
@@ -114574,7 +114892,7 @@ function createOpenAiCompatibleInferenceProvider(options = {}) {
114574
114892
  ok: true,
114575
114893
  result: {
114576
114894
  content,
114577
- usage: readUsage(isRecord6(parsed) ? parsed.usage : void 0),
114895
+ usage: readUsage(isRecord7(parsed) ? parsed.usage : void 0),
114578
114896
  ...receiptId ? { receiptId } : {},
114579
114897
  ...aciIdentity ? { aciIdentity } : {}
114580
114898
  }
@@ -114619,11 +114937,25 @@ async function withRetries(deps, attempt, retryable) {
114619
114937
  }
114620
114938
  }
114621
114939
  var ComputeFailure = class extends Error {
114622
- constructor(message) {
114940
+ code;
114941
+ constructor(message, code) {
114623
114942
  super(message);
114624
114943
  this.name = "ComputeFailure";
114944
+ this.code = code;
114625
114945
  }
114626
114946
  };
114947
+ function classifyComputeError(err2) {
114948
+ if (err2 instanceof ComputeFailure)
114949
+ return err2.code ?? "internal";
114950
+ if (err2 instanceof InferenceRequestError) {
114951
+ return isRetryableInferenceError(err2) ? "inference_unavailable" : "internal";
114952
+ }
114953
+ if (err2 instanceof ProtocolError) {
114954
+ const grantShaped = err2.errorCode === "DERIVATIVE_SOURCE_NOT_GRANTED" || err2.errorCode === "SCOPE_MISMATCH" || err2.errorCode === "UNREGISTERED_BUILDER" || err2.errorCode.startsWith("GRANT_");
114955
+ return grantShaped ? "grant_invalid" : "internal";
114956
+ }
114957
+ return "internal";
114958
+ }
114627
114959
  function shortError(err2) {
114628
114960
  if (err2 instanceof ComputeFailure)
114629
114961
  return err2.message;
@@ -114633,6 +114965,53 @@ function shortError(err2) {
114633
114965
  return `${err2.errorCode}: ${err2.message}`;
114634
114966
  return `compute failed (${err2 instanceof Error ? err2.name : "Error"})`;
114635
114967
  }
114968
+ async function produceAnswer(deps, registration, model, messages) {
114969
+ const ask = (turns2) => withRetries(deps, () => deps.provider.chat({
114970
+ model,
114971
+ messages: turns2,
114972
+ maxTokens: deps.maxTokens
114973
+ }), isRetryableInferenceError);
114974
+ const shape = registration.answerShape;
114975
+ if (!shape) {
114976
+ const reply = await ask(messages);
114977
+ const parsed = parseAnswer(reply.content);
114978
+ return {
114979
+ reply,
114980
+ answer: parsed.answer,
114981
+ evidence: parsed.evidence,
114982
+ answerData: null
114983
+ };
114984
+ }
114985
+ const compiled = compileAnswerShape(shape);
114986
+ let turns = messages;
114987
+ for (let attempt = 0; ; attempt += 1) {
114988
+ const reply = await ask(turns);
114989
+ const candidates = parseShapedAnswer(reply.content);
114990
+ let issues = ["the reply was not a JSON object"];
114991
+ for (const [index, candidate] of candidates.entries()) {
114992
+ const checked = validateAnswerShape(shape, compiled, candidate.answer);
114993
+ if (checked.ok) {
114994
+ return {
114995
+ reply,
114996
+ answer: renderShapedAnswer(shape, checked.value),
114997
+ evidence: candidate.evidence,
114998
+ answerData: checked.value
114999
+ };
115000
+ }
115001
+ if (index === 0)
115002
+ issues = checked.issues;
115003
+ }
115004
+ if (attempt >= 1)
115005
+ break;
115006
+ turns = buildShapeRetryMessages({
115007
+ messages,
115008
+ reply: reply.content,
115009
+ issues,
115010
+ answerShape: shape
115011
+ });
115012
+ }
115013
+ throw new ComputeFailure(`answer did not match the declared shape (fields: ${shape.fields.map((field) => field.name).join(", ")})`, "internal");
115014
+ }
114636
115015
  function collectedAtStamp(now, isTaken) {
114637
115016
  const base = now();
114638
115017
  base.setUTCMilliseconds(0);
@@ -114708,10 +115087,10 @@ async function loadSource(deps, scope) {
114708
115087
  const entry = deps.storage.findEntry({ scope });
114709
115088
  const deletion = await resolveReadDeletion({ scopeDeletions: deps.scopeDeletions, serverOwner: deps.serverOwner }, scope, entry);
114710
115089
  if (deletion) {
114711
- throw new ComputeFailure(`source scope ${scope} is deleted`);
115090
+ throw new ComputeFailure(`source scope ${scope} is deleted`, "source_missing");
114712
115091
  }
114713
115092
  if (!entry) {
114714
- throw new ComputeFailure(`source scope ${scope} has no local data`);
115093
+ throw new ComputeFailure(`source scope ${scope} has no local data`, "source_missing");
114715
115094
  }
114716
115095
  let envelope;
114717
115096
  try {
@@ -114793,18 +115172,20 @@ async function computeQuestion(questionId, deps) {
114793
115172
  await assertNoLineageCycle(deps, registration, serverOwner, sourceLineage);
114794
115173
  const messages = buildQuestionMessages({
114795
115174
  question: registration.question,
114796
- sources
115175
+ sources,
115176
+ answerShape: registration.answerShape
114797
115177
  });
114798
115178
  const model = registration.model ?? deps.provider.defaultModel;
114799
- const reply = await withRetries(deps, () => deps.provider.chat({ model, messages, maxTokens: deps.maxTokens }), isRetryableInferenceError);
114800
- const parsed = parseAnswer(reply.content);
115179
+ const produced = await produceAnswer(deps, registration, model, messages);
115180
+ const reply = produced.reply;
114801
115181
  const computedAt = now().toISOString();
114802
115182
  const lineageIds = registration.sourceScopes.map((scope) => computeDataPointId(serverOwner, scope));
114803
115183
  const record2 = {
114804
115184
  questionId: registration.questionId,
114805
115185
  question: registration.question,
114806
- answer: parsed.answer,
114807
- evidence: parsed.evidence,
115186
+ answer: produced.answer,
115187
+ ...produced.answerData ? { answerData: produced.answerData } : {},
115188
+ evidence: produced.evidence,
114808
115189
  model,
114809
115190
  computedAt,
114810
115191
  sources: sources.map((source) => ({
@@ -114847,6 +115228,7 @@ async function computeQuestion(questionId, deps) {
114847
115228
  const updated = await deps.store.update(questionId, {
114848
115229
  status: "ready",
114849
115230
  error: null,
115231
+ errorCode: null,
114850
115232
  updatedAt: computedAt,
114851
115233
  lastComputedAt: computedAt,
114852
115234
  derivedVersion: entry?.version ?? null,
@@ -114884,28 +115266,49 @@ async function computeQuestion(questionId, deps) {
114884
115266
  };
114885
115267
  } catch (err2) {
114886
115268
  const error51 = shortError(err2);
115269
+ const errorCode = classifyComputeError(err2);
114887
115270
  const at3 = now().toISOString();
114888
115271
  const updated = await deps.store.update(questionId, {
114889
115272
  status: "failed",
114890
115273
  error: error51,
115274
+ errorCode,
114891
115275
  updatedAt: at3
114892
115276
  });
114893
115277
  deps.logger?.warn?.({ questionId, derivedScope: registration.derivedScope, error: error51 }, "Derivative question compute failed");
114894
115278
  return {
114895
115279
  status: "failed",
114896
- registration: updated ?? { ...registration, status: "failed", error: error51 },
115280
+ registration: updated ?? {
115281
+ ...registration,
115282
+ status: "failed",
115283
+ error: error51,
115284
+ errorCode
115285
+ },
114897
115286
  error: error51
114898
115287
  };
114899
115288
  }
114900
115289
  }
114901
115290
 
114902
115291
  // ../core/dist/derivatives/scheduler.js
115292
+ var DEFAULT_RETRY_DELAYS_MS2 = [6e4, 3e5, 18e5];
115293
+ function isRuntimeSkipOutcome(outcome) {
115294
+ if (typeof outcome !== "object" || outcome === null)
115295
+ return false;
115296
+ const shaped = outcome;
115297
+ return shaped.status === "skipped" && shaped.reason === "runtime-unavailable";
115298
+ }
115299
+ function isTransientFailureOutcome(outcome) {
115300
+ if (typeof outcome !== "object" || outcome === null)
115301
+ return false;
115302
+ const shaped = outcome;
115303
+ return shaped.status === "failed" && shaped.registration?.errorCode === "inference_unavailable";
115304
+ }
114903
115305
  var defaultTimers = {
114904
115306
  setTimeout: (callback, ms3) => setTimeout(callback, ms3),
114905
115307
  clearTimeout: (handle) => clearTimeout(handle)
114906
115308
  };
114907
115309
  function createRecomputeScheduler(options) {
114908
115310
  const debounceMs = options.debounceMs ?? 5e3;
115311
+ const retryDelaysMs = options.retryDelaysMs ?? DEFAULT_RETRY_DELAYS_MS2;
114909
115312
  const timers = options.timers ?? defaultTimers;
114910
115313
  const now = options.now ?? (() => /* @__PURE__ */ new Date());
114911
115314
  const states = /* @__PURE__ */ new Map();
@@ -114924,7 +115327,15 @@ function createRecomputeScheduler(options) {
114924
115327
  function stateFor(questionId) {
114925
115328
  let state = states.get(questionId);
114926
115329
  if (!state) {
114927
- state = { timer: null, running: null, rerun: false };
115330
+ state = {
115331
+ timer: null,
115332
+ running: null,
115333
+ rerun: false,
115334
+ retryAttempts: 0,
115335
+ retryAtMs: null,
115336
+ retryScheduled: false,
115337
+ retryRunning: false
115338
+ };
114928
115339
  states.set(questionId, state);
114929
115340
  }
114930
115341
  return state;
@@ -114937,17 +115348,42 @@ function createRecomputeScheduler(options) {
114937
115348
  state.rerun = true;
114938
115349
  return;
114939
115350
  }
114940
- state.running = track(Promise.resolve().then(() => options.compute(questionId)).then(() => void 0, (err2) => warn({
114941
- questionId,
114942
- error: err2 instanceof Error ? err2.name : String(err2)
114943
- }, "Derivative compute threw")).then(async () => {
115351
+ state.running = track(Promise.resolve().then(() => options.compute(questionId)).then((outcome) => outcome, (err2) => {
115352
+ warn({
115353
+ questionId,
115354
+ error: err2 instanceof Error ? err2.name : String(err2)
115355
+ }, "Derivative compute threw");
115356
+ return void 0;
115357
+ }).then(async (outcome) => {
114944
115358
  state.running = null;
115359
+ state.retryAtMs = null;
115360
+ state.retryRunning = false;
115361
+ const retryable = isTransientFailureOutcome(outcome) || // A runtime-unavailable skip DURING a chain consumes an attempt
115362
+ // instead of ending the chain; on a fresh compute it keeps the
115363
+ // pre-retry behavior (pending/stale, reswept by start()).
115364
+ isRuntimeSkipOutcome(outcome) && state.retryAttempts > 0;
114945
115365
  if (state.rerun) {
114946
115366
  state.rerun = false;
115367
+ state.retryAttempts = 0;
114947
115368
  await markStale(questionId);
114948
115369
  schedule(questionId, 0);
114949
- } else if (!states.get(questionId)?.timer) {
114950
- states.delete(questionId);
115370
+ } else if (retryable && // A pending timer means a source change or explicit recompute
115371
+ // arrived during the run and already scheduled a fresher run;
115372
+ // replacing its 5s debounce with a 60s retry would delay the
115373
+ // recompute of data this compute never saw.
115374
+ state.timer === null && // After stop() nothing may be scheduled; without this a compute
115375
+ // that settles late would record a retryAtMs no timer backs.
115376
+ !stopped && state.retryAttempts < retryDelaysMs.length) {
115377
+ const delayMs = retryDelaysMs[state.retryAttempts];
115378
+ state.retryAttempts += 1;
115379
+ state.retryAtMs = now().getTime() + delayMs;
115380
+ schedule(questionId, delayMs);
115381
+ state.retryScheduled = true;
115382
+ } else {
115383
+ state.retryAttempts = 0;
115384
+ if (!states.get(questionId)?.timer) {
115385
+ states.delete(questionId);
115386
+ }
114951
115387
  }
114952
115388
  }));
114953
115389
  }
@@ -114959,6 +115395,9 @@ function createRecomputeScheduler(options) {
114959
115395
  timers.clearTimeout(state.timer);
114960
115396
  state.timer = timers.setTimeout(() => {
114961
115397
  state.timer = null;
115398
+ state.retryAtMs = null;
115399
+ state.retryRunning = state.retryScheduled;
115400
+ state.retryScheduled = false;
114962
115401
  run(questionId);
114963
115402
  }, delayMs);
114964
115403
  }
@@ -114969,11 +115408,29 @@ function createRecomputeScheduler(options) {
114969
115408
  if (registration.status === "ready" || registration.status === "failed") {
114970
115409
  await options.store.update(questionId, {
114971
115410
  status: "stale",
115411
+ // The reader contract says errorCode is null unless failed; a stale
115412
+ // question is a recompute in progress, not a terminal failure.
115413
+ errorCode: null,
114972
115414
  updatedAt: now().toISOString()
114973
115415
  });
114974
115416
  }
114975
115417
  }
115418
+ function resetRetry(questionId) {
115419
+ const state = states.get(questionId);
115420
+ if (!state)
115421
+ return;
115422
+ state.retryAttempts = 0;
115423
+ state.retryAtMs = null;
115424
+ state.retryScheduled = false;
115425
+ }
114976
115426
  return {
115427
+ nextRetryAt(questionId) {
115428
+ const retryAtMs = states.get(questionId)?.retryAtMs;
115429
+ return retryAtMs == null ? null : new Date(retryAtMs).toISOString();
115430
+ },
115431
+ retryInFlight(questionId) {
115432
+ return states.get(questionId)?.retryRunning ?? false;
115433
+ },
114977
115434
  markSourceChanged(scope, opts) {
114978
115435
  if (stopped)
114979
115436
  return;
@@ -114988,6 +115445,7 @@ function createRecomputeScheduler(options) {
114988
115445
  continue;
114989
115446
  }
114990
115447
  await markStale(registration.questionId);
115448
+ resetRetry(registration.questionId);
114991
115449
  schedule(registration.questionId, debounceMs);
114992
115450
  }
114993
115451
  })().catch((err2) => warn({ scope, error: err2 instanceof Error ? err2.name : String(err2) }, "Could not mark derivative questions stale")));
@@ -114998,7 +115456,10 @@ function createRecomputeScheduler(options) {
114998
115456
  void track(markStale(questionId).catch((err2) => warn({
114999
115457
  questionId,
115000
115458
  error: err2 instanceof Error ? err2.name : String(err2)
115001
- }, "Could not mark derivative question stale")).then(() => schedule(questionId, opts?.immediate ? 0 : debounceMs)));
115459
+ }, "Could not mark derivative question stale")).then(() => {
115460
+ resetRetry(questionId);
115461
+ schedule(questionId, opts?.immediate ? 0 : debounceMs);
115462
+ }));
115002
115463
  },
115003
115464
  async whenIdle() {
115004
115465
  const waitForTimers = !options.timers;
@@ -115018,6 +115479,9 @@ function createRecomputeScheduler(options) {
115018
115479
  if (state.timer !== null)
115019
115480
  timers.clearTimeout(state.timer);
115020
115481
  state.timer = null;
115482
+ state.retryAttempts = 0;
115483
+ state.retryAtMs = null;
115484
+ state.retryScheduled = false;
115021
115485
  }
115022
115486
  },
115023
115487
  start() {
@@ -115038,6 +115502,7 @@ function createRecomputeScheduler(options) {
115038
115502
 
115039
115503
  // ../core/dist/derivatives/api.js
115040
115504
  var MAX_QUESTION_BODY_BYTES = 16 * 1024;
115505
+ var RETRY_IN_FLIGHT_POLL_SECONDS = 5;
115041
115506
  function jsonResponse3(body, init) {
115042
115507
  const headers = new Headers(init?.headers);
115043
115508
  headers.set("Content-Type", "application/json");
@@ -115090,12 +115555,63 @@ async function loadForCaller(deps, request2, store, questionId) {
115090
115555
  }
115091
115556
  return { registration, writer };
115092
115557
  }
115558
+ async function handleStatusRoute(request2, url2, deps, store, scheduler, now) {
115559
+ if (request2.method !== "GET") {
115560
+ return errorResponse2(405, "METHOD_NOT_ALLOWED", "Method not allowed");
115561
+ }
115562
+ const derivedScopeParam = url2.searchParams.get("derivedScope");
115563
+ if (!derivedScopeParam) {
115564
+ throw new DerivativeDerivedScopeRequiredError();
115565
+ }
115566
+ const scopeResult = parseDataScopeContract(derivedScopeParam);
115567
+ if (!scopeResult.ok) {
115568
+ return errorResponse2(scopeResult.status, scopeResult.body.error, scopeResult.body.message);
115569
+ }
115570
+ const derivedScope = scopeResult.scope;
115571
+ const auth = await deps.auth.authorizeBuilderRead({
115572
+ request: request2,
115573
+ scope: derivedScope,
115574
+ grantId: selectedGrantId(request2, url2)
115575
+ }) ?? void 0;
115576
+ const registrations = await store.list({ derivedScope });
115577
+ if (registrations.length === 0) {
115578
+ throw new DerivativeQuestionNotFoundError({ derivedScope });
115579
+ }
115580
+ const STATUS_PRECEDENCE = {
115581
+ ready: 0,
115582
+ stale: 1,
115583
+ pending: 2,
115584
+ failed: 3
115585
+ };
115586
+ const registration = registrations.reduce((best, candidate) => {
115587
+ const byStatus = STATUS_PRECEDENCE[candidate.status] - STATUS_PRECEDENCE[best.status];
115588
+ if (byStatus !== 0)
115589
+ return byStatus < 0 ? candidate : best;
115590
+ return candidate.updatedAt >= best.updatedAt ? candidate : best;
115591
+ });
115592
+ const nextRetryAt = scheduler.nextRetryAt?.(registration.questionId) ?? null;
115593
+ const retryAfterSeconds = nextRetryAt !== null ? Math.max(1, Math.ceil((Date.parse(nextRetryAt) - now().getTime()) / 1e3)) : scheduler.retryInFlight?.(registration.questionId) ? RETRY_IN_FLIGHT_POLL_SECONDS : null;
115594
+ const registrarView = auth === void 0 || auth.grantId === "owner" || auth.grantId === "policy-bypass" || registration.registeredBy.kind === "builder" && typeof auth.builder === "string" && sameBuilder(registration.registeredBy.builder, auth.builder);
115595
+ const disclosedErrorCode = registration.errorCode === "grant_invalid" && !registrarView ? "internal" : registration.errorCode;
115596
+ return jsonResponse3({
115597
+ derivedScope: registration.derivedScope,
115598
+ status: registration.status,
115599
+ lastComputedAt: registration.lastComputedAt,
115600
+ derivedVersion: registration.derivedVersion,
115601
+ derivedCollectedAt: registration.derivedCollectedAt,
115602
+ // Null unless failed, whatever an old store row holds: a stale question
115603
+ // is a recompute in progress, not a terminal failure.
115604
+ errorCode: registration.status === "failed" ? disclosedErrorCode : null,
115605
+ retryAfterSeconds
115606
+ });
115607
+ }
115093
115608
  async function handlePersonalServerDerivativesRequest(request2, deps, options = {}) {
115094
115609
  try {
115095
115610
  const url2 = new URL(request2.url);
115096
115611
  const pathname = stripBasePath2(url2.pathname, options.basePath);
115097
115612
  const parts = pathname.split("/").filter(Boolean);
115098
- if (parts[0] !== "questions" || parts.length > 3) {
115613
+ const isStatusRoute = parts[0] === "status" && parts.length === 1;
115614
+ if (!isStatusRoute && (parts[0] !== "questions" || parts.length > 3)) {
115099
115615
  return errorResponse2(404, "NOT_FOUND", "Not found");
115100
115616
  }
115101
115617
  const compute = deps.compute;
@@ -115103,6 +115619,9 @@ async function handlePersonalServerDerivativesRequest(request2, deps, options =
115103
115619
  throw new DerivativeComputeUnavailableError();
115104
115620
  const { store, scheduler } = compute;
115105
115621
  const now = deps.now ?? (() => /* @__PURE__ */ new Date());
115622
+ if (isStatusRoute) {
115623
+ return await handleStatusRoute(request2, url2, deps, store, scheduler, now);
115624
+ }
115106
115625
  if (parts.length === 1) {
115107
115626
  if (request2.method === "GET") {
115108
115627
  const derivedScope = url2.searchParams.get("derivedScope") ?? void 0;
@@ -115427,7 +115946,7 @@ var E2eeAttestationError = class extends Error {
115427
115946
  this.name = "E2eeAttestationError";
115428
115947
  }
115429
115948
  };
115430
- function isRecord7(value) {
115949
+ function isRecord8(value) {
115431
115950
  return value !== null && typeof value === "object" && !Array.isArray(value);
115432
115951
  }
115433
115952
  async function sha256Hex2(bytes2) {
@@ -115445,7 +115964,7 @@ async function reportDataFor(keysetDigest, nonce) {
115445
115964
  return sha256Hex2(new TextEncoder().encode(statement));
115446
115965
  }
115447
115966
  function parseReport(body) {
115448
- if (!isRecord7(body) || typeof body.api_version !== "string" || typeof body.workload_keyset_digest !== "string" || !isRecord7(body.attestation) || typeof body.attestation.tee_type !== "string" || typeof body.attestation.report_data !== "string" || !isRecord7(body.attestation.workload_keyset)) {
115967
+ if (!isRecord8(body) || typeof body.api_version !== "string" || typeof body.workload_keyset_digest !== "string" || !isRecord8(body.attestation) || typeof body.attestation.tee_type !== "string" || typeof body.attestation.report_data !== "string" || !isRecord8(body.attestation.workload_keyset)) {
115449
115968
  throw new E2eeAttestationError("malformed_report", "attestation report is missing required fields");
115450
115969
  }
115451
115970
  const keyset = body.attestation.workload_keyset;
@@ -115492,7 +116011,7 @@ async function verifyAciReportBinding(report, nonce, nowSeconds) {
115492
116011
  }
115493
116012
  function selectX25519Key(keyset, expectedKeyId) {
115494
116013
  for (const entry of keyset.e2ee_public_keys) {
115495
- if (!isRecord7(entry) || entry.algo !== E2EE_ALGO_X25519)
116014
+ if (!isRecord8(entry) || entry.algo !== E2EE_ALGO_X25519)
115496
116015
  continue;
115497
116016
  if (typeof entry.public_key !== "string")
115498
116017
  continue;
@@ -133090,7 +133609,8 @@ async function createPsLiteQuestionStore(stateStore) {
133090
133609
  const saved = await stateStore.get(QUESTIONS_KEY);
133091
133610
  const initial = (saved?.version === 1 ? saved.questions : []).map((question) => ({
133092
133611
  ...question,
133093
- recompute: question.recompute ?? "on-change"
133612
+ recompute: question.recompute ?? "on-change",
133613
+ answerShape: question.answerShape ?? null
133094
133614
  }));
133095
133615
  return createInMemoryQuestionStore({
133096
133616
  initial,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opendatalabs/personal-server-ts-server",
3
- "version": "1.13.1",
3
+ "version": "1.15.0",
4
4
  "description": "Hono HTTP server for the Vana Personal Server — routes, middleware, composition root",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -44,8 +44,8 @@
44
44
  },
45
45
  "dependencies": {
46
46
  "@hono/node-server": "^1.19.13",
47
- "@opendatalabs/personal-server-ts-core": "1.13.1",
48
- "@opendatalabs/personal-server-ts-lite": "1.13.1",
47
+ "@opendatalabs/personal-server-ts-core": "1.15.0",
48
+ "@opendatalabs/personal-server-ts-lite": "1.15.0",
49
49
  "@opendatalabs/vana-sdk": "3.14.0",
50
50
  "better-sqlite3": "^12.11.1",
51
51
  "hono": "^4.12.27",