@mnmal-ai/substrate-nl 1.0.0-alpha.11 → 1.0.0-alpha.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,7 +4,8 @@ Natural-language ask surface. A deterministic parser reads a question against a
4
4
 
5
5
  ## Contract
6
6
 
7
- - `POST /substrate/v1/nl` with `{ namespace, question, project?, type? }` returns one of `rows`, `recall`, `clarify` or `refuse`, plus a `trail` of how it got there. Send `accept: text/event-stream` for the same as SSE events (`parsed`, `judging`, `judged`, then the outcome, then `done`), otherwise it is one JSON body.
7
+ - `POST /substrate/v1/nl` with `{ namespace, question, project?, type?, asOf? }` returns one of `rows`, `recall`, `clarify` or `refuse`, plus a `trail` of how it got there. Send `accept: text/event-stream` for the same as SSE events (`parsed`, `judging`, `judged`, then the outcome, then `done`), otherwise it is one JSON body.
8
+ - `asOf` is "now" for relative dates ("yesterday", "last week") and for the year of a month-day ("on Oct 23"): an ISO-8601 date-time with an offset, such as `2026-10-02T12:00:00-05:00`. Day boundaries are in its offset. Anything else is `400 invalid_params`. Without it the server clock is used. The `parsed` event's `asOf` says which clock the question was read against.
8
9
  - `GET /substrate/v1/nl/pack/<ns>` returns the namespace pack (types, fields, values, rulings) with an `etag`; send `if-none-match` for a `304`.
9
10
  - NL is read-only: a mutation-shaped question is `refuse`, never executed.
10
11
 
@@ -1 +1 @@
1
- {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/ask/run.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,UAAU,EAGf,KAAK,gBAAgB,EAGtB,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAAE,cAAc,EAAc,MAAM,cAAc,CAAC;AAC/D,OAAO,EAAE,KAAK,KAAK,EAAqC,MAAM,mBAAmB,CAAC;AAElF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAErD,OAAO,KAAK,EAAE,UAAU,EAAE,OAAO,EAAa,MAAM,aAAa,CAAC;AAElE,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,UAAU,CAAC;IACjB,MAAM,EAAE,cAAc,CAAC;IACvB,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;CAClB;AAmCD;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,UAAU,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,GAAG,SAAS,GAAG,UAAU,EAAE,CA2B9F;AAuID,wBAAuB,MAAM,CAC3B,GAAG,EAAE,UAAU,EACf,IAAI,EAAE,gBAAgB,EACtB,IAAI,EAAE,OAAO,GACZ,cAAc,CAAC,OAAO,CAAC,CA+IzB"}
1
+ {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/ask/run.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,UAAU,EAGf,KAAK,gBAAgB,EAGtB,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAAE,cAAc,EAAc,MAAM,cAAc,CAAC;AAC/D,OAAO,EAAE,KAAK,KAAK,EAAqC,MAAM,mBAAmB,CAAC;AAElF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAErD,OAAO,KAAK,EAAE,UAAU,EAAE,OAAO,EAAa,MAAM,aAAa,CAAC;AAElE,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,UAAU,CAAC;IACjB,MAAM,EAAE,cAAc,CAAC;IACvB,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;CAClB;AAmCD;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,UAAU,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,GAAG,SAAS,GAAG,UAAU,EAAE,CA2B9F;AAuID,wBAAuB,MAAM,CAC3B,GAAG,EAAE,UAAU,EACf,IAAI,EAAE,gBAAgB,EACtB,IAAI,EAAE,OAAO,GACZ,cAAc,CAAC,OAAO,CAAC,CAgJzB"}
@@ -201,14 +201,28 @@ const WEEKDAYS = [
201
201
  "friday",
202
202
  "saturday"
203
203
  ];
204
+ /** An ISO-8601 date-time with an offset: the only accepted `asOf`. */
205
+ const AS_OF = /^(\d{4})-(\d{2})-(\d{2})T[\d:.]+(Z|[+-]\d{2}:\d{2})$/;
206
+ /** True when `asOf` is an ISO-8601 date-time with an offset naming a real instant. */
207
+ function validAsOf(asOf) {
208
+ if (typeof asOf !== "string") return false;
209
+ const m = AS_OF.exec(asOf);
210
+ if (m === null || !Number.isFinite(Date.parse(asOf))) return false;
211
+ const [y, mo, d] = [
212
+ Number(m[1]),
213
+ Number(m[2]),
214
+ Number(m[3])
215
+ ];
216
+ return new Date(Date.UTC(y, mo - 1, d)).getUTCDate() === d;
217
+ }
204
218
  /**
205
219
  * The range a time phrase names, relative to `asOf` and in its offset:
206
220
  * today, yesterday, this/last week (weeks start Monday), this/last month,
207
- * rolling "past <unit>" and "last N <units>", "since <weekday>". Null when
208
- * the phrase is not a time phrase.
221
+ * rolling "past <unit>" and "last N <units>", "since <weekday>"; and explicit
222
+ * dates (see `explicitRange`). Null when the phrase is not a time phrase.
209
223
  */
210
224
  function rangeFor(phrase, asOf) {
211
- const m = /^(\d{4})-(\d{2})-(\d{2})T[\d:.]+(Z|[+-]\d{2}:\d{2})$/.exec(asOf);
225
+ const m = AS_OF.exec(asOf);
212
226
  if (m === null) throw new Error(`asOf must be ISO-8601 with an offset: ${asOf}`);
213
227
  const [, y, mo, d, offset] = m;
214
228
  const today = Date.UTC(Number(y), Number(mo) - 1, Number(d));
@@ -268,6 +282,99 @@ function rangeFor(phrase, asOf) {
268
282
  lt: at(today + day)
269
283
  };
270
284
  }
285
+ return explicitRange(phrase, today, at, offset);
286
+ }
287
+ const MONTHS = [
288
+ "january",
289
+ "february",
290
+ "march",
291
+ "april",
292
+ "may",
293
+ "june",
294
+ "july",
295
+ "august",
296
+ "september",
297
+ "october",
298
+ "november",
299
+ "december"
300
+ ];
301
+ const DAY_MS = 864e5;
302
+ /**
303
+ * Explicit dates, always after a lead word: "on D" (that day), "from D to D" and
304
+ * "between D and D" (inclusive of the last day), "since D" / "after D" (from
305
+ * D / the day after), "before D" / "until D" (before D / through D). A date
306
+ * is ISO `YYYY-MM-DD`, an ISO date-time (an exact instant), or a month name
307
+ * (full, three-letter, or "sept") with a day, optional ordinal and optional year, in
308
+ * either order. A month-day without a year is the most recent one not after
309
+ * `today`. An invalid date is not a date.
310
+ */
311
+ function explicitRange(phrase, today, at, offset) {
312
+ const operand = (words) => {
313
+ if (words.length === 1) {
314
+ const w = words[0];
315
+ const iso = /^(\d{4})-(\d{2})-(\d{2})$/.exec(w);
316
+ if (iso !== null) return dayOf(Number(iso[1]), Number(iso[2]), Number(iso[3]));
317
+ const dt = /^(\d{4})-(\d{2})-(\d{2})t((?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?)(z|[+-](?:[01]\d|2[0-3]):[0-5]\d)?$/.exec(w);
318
+ if (dt === null || dayOf(Number(dt[1]), Number(dt[2]), Number(dt[3])) === null) return null;
319
+ const instant = `${dt[1]}-${dt[2]}-${dt[3]}T${dt[4]}${dt[5] === void 0 ? offset : dt[5].toUpperCase()}`;
320
+ return {
321
+ start: instant,
322
+ end: instant
323
+ };
324
+ }
325
+ if (words.length !== 2 && words.length !== 3) return null;
326
+ const [a = "", b = "", year] = words;
327
+ const month = (w) => MONTHS.findIndex((m) => w === m || w.length === 3 && m.startsWith(w)) + 1 || (w === "sept" ? 9 : 0);
328
+ const dom = (w) => /^(\d{1,2})(?:st|nd|rd|th)?$/.exec(w)?.[1];
329
+ const [mo, d] = month(a) > 0 ? [month(a), dom(b)] : [month(b), dom(a)];
330
+ if (mo === 0 || d === void 0) return null;
331
+ if (year !== void 0) return /^\d{4}$/.test(year) ? dayOf(Number(year), mo, Number(d)) : null;
332
+ for (let y = new Date(today).getUTCFullYear(), n = 0; n < 8; y--, n++) {
333
+ const t = Date.UTC(y, mo - 1, Number(d));
334
+ if (t <= today && valid(y, mo, Number(d))) return {
335
+ start: at(t),
336
+ end: at(t + DAY_MS)
337
+ };
338
+ }
339
+ return null;
340
+ };
341
+ const valid = (y, mo, d) => {
342
+ const t = new Date(Date.UTC(y, mo - 1, d));
343
+ return t.getUTCFullYear() === y && t.getUTCMonth() === mo - 1 && t.getUTCDate() === d;
344
+ };
345
+ const dayOf = (y, mo, d) => {
346
+ if (!valid(y, mo, d)) return null;
347
+ const t = Date.UTC(y, mo - 1, d);
348
+ return {
349
+ start: at(t),
350
+ end: at(t + DAY_MS)
351
+ };
352
+ };
353
+ const [head = "", ...rest] = phrase.split(" ");
354
+ if (head === "from" || head === "between") {
355
+ const sep = head === "from" ? "to" : "and";
356
+ const i = rest.indexOf(sep);
357
+ if (i < 0) return null;
358
+ const x = operand(rest.slice(0, i));
359
+ const y = operand(rest.slice(i + 1));
360
+ if (x === null || y === null || Date.parse(y.end) <= Date.parse(x.start)) return null;
361
+ return {
362
+ gte: x.start,
363
+ lt: y.end
364
+ };
365
+ }
366
+ const o = operand(rest);
367
+ if (o === null) return null;
368
+ switch (head) {
369
+ case "on": return o.start === o.end ? null : {
370
+ gte: o.start,
371
+ lt: o.end
372
+ };
373
+ case "since": return { gte: o.start };
374
+ case "after": return { gte: o.end };
375
+ case "before": return { lt: o.start };
376
+ case "until": return { lt: o.end };
377
+ }
271
378
  return null;
272
379
  }
273
380
 
@@ -685,7 +792,13 @@ function compose(input) {
685
792
  w.pick = pick;
686
793
  }
687
794
  let seedLatest;
795
+ let seedOrder;
688
796
  for (const k of claims.picks) if (k.phrase.type === seed && k.kind === "latest") seedLatest = k.span;
797
+ else if (k.phrase.type === seed && (k.span === "highest" || k.span === "lowest") && k.phrase.entry.value === void 0) seedOrder = {
798
+ span: `${k.span} ${k.phrase.span}`,
799
+ dir: k.kind === "max" ? "desc" : "asc",
800
+ phrase: k.phrase
801
+ };
689
802
  else if (k.phrase.type === seed) dropped.push({
690
803
  span: `${k.span} ${k.phrase.span}`,
691
804
  reason: `"${k.span}" picks one neighbour, and ${seed} is the type being asked about`,
@@ -821,10 +934,7 @@ function compose(input) {
821
934
  else {
822
935
  const field = timeField(type);
823
936
  const ruled = own(rulings.timeField, type) !== void 0;
824
- w.window.push(filter({ [field]: {
825
- gte: input.time.range.gte,
826
- lt: input.time.range.lt
827
- } }, [field], `window:${input.time.span}${ruled ? "" : ` on ${field} (default)`}`));
937
+ w.window.push(filter({ [field]: { ...input.time.range } }, [field], `window:${input.time.span}${ruled ? "" : ` on ${field} (default)`}`));
828
938
  }
829
939
  }
830
940
  }
@@ -918,6 +1028,7 @@ function compose(input) {
918
1028
  dropped,
919
1029
  timeOnSeed,
920
1030
  ...seedLatest !== void 0 ? { seedLatest } : {},
1031
+ ...seedOrder !== void 0 ? { seedOrder } : {},
921
1032
  walked: walks.size > 0 || seedParts.some((p) => p.plan.some((c) => c.kind === "ref")) || plan.length > 0,
922
1033
  strict: pickDropped || ordered.some((w) => w.pick !== void 0) || composed.some((p) => p.plan.some((c) => c.kind === "ref"))
923
1034
  };
@@ -1092,7 +1203,7 @@ function parseQuestion(question, pack, ctx) {
1092
1203
  const tokens = (question.replace(CLAUSE, (span, _q, field, raw) => {
1093
1204
  clauses.push(parseClause(span, field, raw));
1094
1205
  return " ";
1095
- }).toLowerCase().replace(/[‘’]/g, "'").match(/\$\d+(?:\.\d+)?|\d+(?:\.\d+)?[%×]|#\d+|[a-z0-9@[][a-z0-9@._'\]-]*/g) ?? []).map((t) => t.replace(/[.'-]+$/, "").replace(/'s$/, "")).filter((t) => t.length > 0).map((text) => ({
1206
+ }).toLowerCase().replace(/[‘’]/g, "'").match(/\d{4}-\d{2}-\d{2}t\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:z|[+-]\d{2}:\d{2})?|\$\d+(?:\.\d+)?|\d+(?:\.\d+)?[%×]|#\d+|[a-z0-9@[][a-z0-9@._'\]-]*/g) ?? []).map((t) => t.replace(/[.'-]+$/, "").replace(/'s$/, "")).filter((t) => t.length > 0).map((text) => ({
1096
1207
  text,
1097
1208
  used: false
1098
1209
  }));
@@ -1141,8 +1252,9 @@ function parseQuestion(question, pack, ctx) {
1141
1252
  if (time !== null) evidence.push({
1142
1253
  span: time.span,
1143
1254
  role: "time",
1144
- detail: `${time.range.gte} .. ${time.range.lt}`
1255
+ detail: `${time.range.gte ?? ""} .. ${time.range.lt ?? ""}`.trim()
1145
1256
  });
1257
+ const fieldOrder = claimFieldOrder(tokens, walkClaims, pack, rulings);
1146
1258
  const ids = [];
1147
1259
  let bareNumber = false;
1148
1260
  tokens.forEach((t, i) => {
@@ -1387,6 +1499,8 @@ function parseQuestion(question, pack, ctx) {
1387
1499
  clauses,
1388
1500
  time: composed === void 0 || composed.timeOnSeed ? time : null,
1389
1501
  order,
1502
+ fieldOrder: fieldOrder ?? composed?.seedOrder ?? null,
1503
+ plural: mentioned.get(type)?.plural ?? false,
1390
1504
  count,
1391
1505
  allProjects,
1392
1506
  historyWord,
@@ -1447,15 +1561,46 @@ function assemble(type, a) {
1447
1561
  detail: "desc, limit 1"
1448
1562
  }] : a.evidence;
1449
1563
  const recency = a.order ?? (seedLatest !== void 0 ? "desc" : null);
1450
- const conflict = a.composed?.params.orderBy !== void 0 && recency !== null;
1564
+ const derived = a.composed?.params.orderBy !== void 0;
1565
+ const fieldOrder = [];
1566
+ let sortBy = null;
1567
+ if (a.fieldOrder !== null && a.count) fieldOrder.push({
1568
+ span: a.fieldOrder.span,
1569
+ reason: "ordering does not apply to a count",
1570
+ kind: "filter"
1571
+ });
1572
+ if (a.fieldOrder !== null && !a.count) {
1573
+ const r = orderField(a.fieldOrder, type, pack, rulings);
1574
+ const reason = "reason" in r ? r.reason : derived ? "it conflicts with the derived ordering, which is kept" : null;
1575
+ if (reason !== null) fieldOrder.push({
1576
+ span: a.fieldOrder.span,
1577
+ reason,
1578
+ kind: "order"
1579
+ });
1580
+ else if ("field" in r) sortBy = {
1581
+ field: r.field,
1582
+ dir: a.fieldOrder.dir,
1583
+ limit: a.fieldOrder.limit ?? (a.plural ? 10 : 1)
1584
+ };
1585
+ }
1586
+ const conflict = (derived || sortBy !== null) && recency !== null;
1451
1587
  const order = conflict ? null : recency;
1452
1588
  const orderSpan = evidence.find((e) => e.role === "order")?.span ?? "";
1453
1589
  const ev = conflict ? evidence.filter((e) => e.role !== "order") : [...evidence];
1454
- const dropped = [...a.composed?.dropped ?? [], ...conflict ? [{
1455
- span: orderSpan,
1456
- reason: "it conflicts with the derived ordering, which is kept",
1457
- kind: "order"
1458
- }] : []];
1590
+ if (sortBy !== null) ev.push({
1591
+ span: a.fieldOrder?.span ?? "",
1592
+ role: "order",
1593
+ detail: `${sortBy.field} ${sortBy.dir}, limit ${sortBy.limit}`
1594
+ });
1595
+ const dropped = [
1596
+ ...a.composed?.dropped ?? [],
1597
+ ...fieldOrder,
1598
+ ...conflict ? [{
1599
+ span: orderSpan,
1600
+ reason: `it conflicts with the ${derived ? "derived" : "field"} ordering, which is kept`,
1601
+ kind: "order"
1602
+ }] : []
1603
+ ];
1459
1604
  const where = [];
1460
1605
  const wherePlan = /* @__PURE__ */ new Map();
1461
1606
  const yields = [];
@@ -1510,6 +1655,8 @@ function assemble(type, a) {
1510
1655
  }
1511
1656
  for (const field of usedFields) creditFieldWords(tokens, fields[field]);
1512
1657
  if (a.author !== null) where.push({ createdById: { startsWith: a.author } });
1658
+ const orderExclude = a.fieldOrder?.phrase?.entry.exclude;
1659
+ if (sortBy !== null && orderExclude !== void 0) where.push({ NOT: orderExclude });
1513
1660
  for (const flag of a.flags) {
1514
1661
  usedFields.add(flag.detail);
1515
1662
  where.push({ [flag.detail]: { eq: true } });
@@ -1535,10 +1682,7 @@ function assemble(type, a) {
1535
1682
  if (a.time !== null) {
1536
1683
  const named = Object.entries(fields).find(([, f]) => f.valueType === "date" && f.kind !== "metadata" && creditFieldWords(tokens, f));
1537
1684
  const dateField = named?.[0] ?? (a.closeVerb || a.updateVerb ? "updatedAt" : own(rulings.timeField, type) ?? "createdAt");
1538
- const clause = { [dateField]: {
1539
- gte: a.time.range.gte,
1540
- lt: a.time.range.lt
1541
- } };
1685
+ const clause = { [dateField]: { ...a.time.range } };
1542
1686
  where.push(clause);
1543
1687
  if (a.composed !== void 0) {
1544
1688
  const ruled = named !== void 0 || a.closeVerb || a.updateVerb || own(rulings.timeField, type) !== void 0;
@@ -1610,9 +1754,13 @@ function assemble(type, a) {
1610
1754
  params.orderBy = [{ [own(rulings.timeField, type) ?? "createdAt"]: order }];
1611
1755
  params.limit = 1;
1612
1756
  }
1757
+ if (sortBy !== null) {
1758
+ params.orderBy = [{ [sortBy.field]: sortBy.dir }];
1759
+ params.limit = sortBy.limit;
1760
+ }
1613
1761
  Object.assign(params, a.composed?.params ?? {});
1614
1762
  if (a.count && a.composed?.walked === true) params.count = true;
1615
- const answer = a.askField !== null ? a.askField.group ? `group:${a.askField.field}` : `field:${a.askField.field}` : a.count ? "count" : order !== null || a.ids.length === 1 ? "single" : "rows";
1763
+ const answer = a.askField !== null ? a.askField.group ? `group:${a.askField.field}` : `field:${a.askField.field}` : a.count ? "count" : order !== null || a.ids.length === 1 || sortBy?.limit === 1 ? "single" : "rows";
1616
1764
  return {
1617
1765
  type,
1618
1766
  frame: { [`${pack.namespace}/${type}`]: { params } },
@@ -1621,8 +1769,109 @@ function assemble(type, a) {
1621
1769
  residual: contentWords(tokens),
1622
1770
  plan,
1623
1771
  dropped,
1624
- strict: a.count || order !== null || a.ids.length === 1 || a.composed?.strict === true
1772
+ strict: a.count || order !== null || sortBy !== null || a.ids.length === 1 || a.composed?.strict === true
1773
+ };
1774
+ }
1775
+ /** A field named by words on one type: one of its field phrases, else its name or an alias. */
1776
+ function fieldByWords(words, fields, rulings) {
1777
+ return Object.entries(fields).find(([, f]) => f.phrases.includes(words))?.[0] ?? (words.includes(" ") ? null : resolveField(words, fields, rulings));
1778
+ }
1779
+ /**
1780
+ * Claims one field ordering. Field words are a field phrase claim, else the
1781
+ * longest run of up to three free words naming a field on some type; after
1782
+ * "by", "highest" or "lowest" an unknown single word is claimed too, so the
1783
+ * reading can drop it with a reason.
1784
+ */
1785
+ function claimFieldOrder(tokens, walkClaims, pack, rulings) {
1786
+ const somewhere = (w, numeric) => Object.values(pack.types).some((t) => {
1787
+ const name = fieldByWords(w, t.fields, rulings);
1788
+ return name !== null && (!numeric || t.fields[name]?.valueType === "number");
1789
+ });
1790
+ const fieldAt = (i, numeric) => {
1791
+ const p = walkClaims.phrases.find((x) => x.at[0] === i && x.field !== void 0 && x.entry.value === void 0);
1792
+ if (p !== void 0) return {
1793
+ phrase: p,
1794
+ span: p.span,
1795
+ end: p.at[1]
1796
+ };
1797
+ for (let n = 3; n >= 1; n--) {
1798
+ const slice = tokens.slice(i, i + n);
1799
+ if (slice.length < n || slice.some((t) => t.used)) continue;
1800
+ const w = slice.map((t) => t.text).join(" ");
1801
+ if (somewhere(w, numeric)) return {
1802
+ words: w,
1803
+ span: w,
1804
+ end: i + n - 1
1805
+ };
1806
+ }
1807
+ const t = tokens[i];
1808
+ return !numeric && t !== void 0 && !t.used && !GLUE.has(t.text) && !/^\d/.test(t.text) ? {
1809
+ words: t.text,
1810
+ span: t.text,
1811
+ end: i
1812
+ } : null;
1813
+ };
1814
+ const take = (from, f) => {
1815
+ for (let k = from; k <= f.end; k++) tokens[k].used = true;
1816
+ if (f.phrase !== void 0) walkClaims.phrases.splice(walkClaims.phrases.indexOf(f.phrase), 1);
1625
1817
  };
1818
+ const field = (f) => f.phrase !== void 0 ? { phrase: f.phrase } : { words: f.words };
1819
+ for (let i = 0; i < tokens.length; i++) {
1820
+ const t = tokens[i];
1821
+ if (t.used) continue;
1822
+ if (t.text === "top" || t.text === "bottom") {
1823
+ const n = tokens[i + 1];
1824
+ const limit = n !== void 0 && !n.used && /^[1-9]\d*$/.test(n.text) ? n : void 0;
1825
+ const by = tokens.findIndex((x, j) => j > i && x.text === "by" && !x.used && !AUTHOR_VERBS.has(tokens[j - 1]?.text ?? ""));
1826
+ const f = by < 0 ? null : fieldAt(by + 1, false);
1827
+ if (f === null) continue;
1828
+ t.used = true;
1829
+ if (limit !== void 0) limit.used = true;
1830
+ take(by, f);
1831
+ return {
1832
+ span: [
1833
+ t.text,
1834
+ limit?.text,
1835
+ "by",
1836
+ f.span
1837
+ ].filter((x) => x !== void 0).join(" "),
1838
+ dir: t.text === "top" ? "desc" : "asc",
1839
+ ...limit !== void 0 ? { limit: Number(limit.text) } : {},
1840
+ ...field(f)
1841
+ };
1842
+ }
1843
+ if (t.text === "highest" || t.text === "lowest" || t.text === "most") {
1844
+ const numeric = t.text === "most";
1845
+ const f = fieldAt(i + 1, numeric);
1846
+ if (f === null) continue;
1847
+ take(i, f);
1848
+ return {
1849
+ span: `${t.text} ${f.span}`,
1850
+ dir: t.text === "lowest" ? "asc" : "desc",
1851
+ ...numeric ? { numeric } : {},
1852
+ ...field(f)
1853
+ };
1854
+ }
1855
+ }
1856
+ return null;
1857
+ }
1858
+ /** The seed field an ordering sorts by on `type`, or why it cannot. */
1859
+ function orderField(o, type, pack, rulings) {
1860
+ const fields = own(pack.types, type)?.fields ?? {};
1861
+ const walked = { reason: "ordering across a walk is not supported" };
1862
+ let name;
1863
+ if (o.phrase !== void 0) {
1864
+ if (o.phrase.type !== type) return walked;
1865
+ name = o.phrase.field ?? null;
1866
+ } else {
1867
+ const words = o.words ?? "";
1868
+ name = fieldByWords(words, fields, rulings);
1869
+ if (name === null) return Object.values(pack.types).some((t) => fieldByWords(words, t.fields, rulings) !== null) ? walked : { reason: `${type} has no field "${words}"` };
1870
+ }
1871
+ const f = name === null ? void 0 : fields[name];
1872
+ if (name === null || f === void 0) return { reason: `${type} has no field "${o.span}"` };
1873
+ if (!((f.kind === "scalar" || f.kind === "metadata") && (f.valueType === "number" || f.valueType === "date" && o.numeric !== true))) return { reason: o.numeric === true ? `"most" needs a number field; ${name} is a ${f.valueType ?? f.kind} field` : `${name} is a ${f.valueType ?? f.kind} field; only number and date fields order` };
1874
+ return { field: name };
1626
1875
  }
1627
1876
  /** A clause's field name: the field itself, case-insensitively, then the rulings' and built-in aliases. */
1628
1877
  function resolveField(raw, fields, rulings) {
@@ -1750,7 +1999,7 @@ function pickField(candidates, fields, words) {
1750
1999
  }
1751
2000
  function claimTime(tokens, asOf) {
1752
2001
  let found = null;
1753
- claimNgrams(tokens, 3, (key, at) => {
2002
+ claimNgrams(tokens, 8, (key, at) => {
1754
2003
  if (found !== null) return false;
1755
2004
  const range = rangeFor(key, asOf);
1756
2005
  if (range === null) return false;
@@ -2216,7 +2465,7 @@ function clarify(question, reason, candidates, pack) {
2216
2465
  const isAborted = (http) => http.signal?.aborted === true;
2217
2466
  async function* runAsk(req, http, deps) {
2218
2467
  const ctx = {
2219
- asOf: (deps.now ?? (() => /* @__PURE__ */ new Date()))().toISOString(),
2468
+ asOf: req.asOf ?? (deps.now ?? (() => /* @__PURE__ */ new Date()))().toISOString(),
2220
2469
  rulings: deps.pack.rulings,
2221
2470
  ...req.project !== void 0 ? { project: req.project } : {},
2222
2471
  ...req.type !== void 0 ? { forceType: req.type } : {}
@@ -2229,7 +2478,8 @@ async function* runAsk(req, http, deps) {
2229
2478
  ...best !== void 0 ? { frame: executable(best, deps.pack) } : {},
2230
2479
  evidence: best?.evidence ?? first.evidence ?? [],
2231
2480
  residual: first.residual,
2232
- ...first.reason !== void 0 ? { reason: first.reason } : {}
2481
+ ...first.reason !== void 0 ? { reason: first.reason } : {},
2482
+ asOf: ctx.asOf
2233
2483
  };
2234
2484
  if (isAborted(http)) return yield { event: "done" };
2235
2485
  /**
@@ -2796,16 +3046,21 @@ function askHandler(deps) {
2796
3046
  const parsed = parseJsonBody(req);
2797
3047
  if (!parsed.ok) return parsed.response;
2798
3048
  const body = parsed.value;
2799
- if (body === null || typeof body !== "object" || Array.isArray(body)) return error400("body must be a JSON object: { namespace, question, project?, type? }");
3049
+ if (body === null || typeof body !== "object" || Array.isArray(body)) return error400("body must be a JSON object: { namespace, question, project?, type?, asOf? }");
2800
3050
  if (!str(body.namespace)) return error400("body.namespace must be a non-empty string");
2801
3051
  if (!str(body.question)) return error400("body.question must be a non-empty string");
2802
3052
  if (body.question.length > MAX_QUESTION_CHARS) return error400(`body.question is longer than ${MAX_QUESTION_CHARS} characters`);
2803
3053
  for (const k of ["project", "type"]) if (body[k] !== void 0 && !str(body[k])) return error400(`body.${k} must be a non-empty string when given`);
3054
+ if (body.asOf !== void 0 && !validAsOf(body.asOf)) return jsonResponse(400, {
3055
+ error: "body.asOf must be an ISO-8601 date-time with an offset, e.g. 2026-10-02T12:00:00-05:00",
3056
+ code: "invalid_params"
3057
+ });
2804
3058
  const ask = {
2805
3059
  namespace: body.namespace,
2806
3060
  question: body.question,
2807
3061
  ...str(body.project) ? { project: body.project } : {},
2808
- ...str(body.type) ? { type: body.type } : {}
3062
+ ...str(body.type) ? { type: body.type } : {},
3063
+ ...validAsOf(body.asOf) ? { asOf: body.asOf } : {}
2809
3064
  };
2810
3065
  let served;
2811
3066
  try {
package/dist/bootstrap.js CHANGED
@@ -1,3 +1,3 @@
1
- import { n as bootstrap, t as NlBootstrapConfigSchema } from "./bootstrap-CLYwjrrl.js";
1
+ import { n as bootstrap, t as NlBootstrapConfigSchema } from "./bootstrap-DbMpquDD.js";
2
2
 
3
3
  export { NlBootstrapConfigSchema, bootstrap, bootstrap as default };
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { a as askHandler, c as profileNamespace, d as runAsk, f as parseWithJudge, g as readRulings, h as mergeRulings, i as createNlPlugin, l as buildNamespacePack, m as NamespaceRulingsSchema, n as bootstrap, o as packHandler, p as parseQuestion, r as CONTRACT_VERSION, s as PackService, t as NlBootstrapConfigSchema, u as OllamaJudge } from "./bootstrap-CLYwjrrl.js";
1
+ import { a as askHandler, c as profileNamespace, d as runAsk, f as parseWithJudge, g as readRulings, h as mergeRulings, i as createNlPlugin, l as buildNamespacePack, m as NamespaceRulingsSchema, n as bootstrap, o as packHandler, p as parseQuestion, r as CONTRACT_VERSION, s as PackService, t as NlBootstrapConfigSchema, u as OllamaJudge } from "./bootstrap-DbMpquDD.js";
2
2
  import { complete } from "./complete.js";
3
3
 
4
4
  export { CONTRACT_VERSION, NamespaceRulingsSchema, NlBootstrapConfigSchema, OllamaJudge, PackService, askHandler, bootstrap, buildNamespacePack, complete, createNlPlugin, mergeRulings, packHandler, parseQuestion, parseWithJudge, profileNamespace, readRulings, runAsk };
@@ -1 +1 @@
1
- {"version":3,"file":"parse.d.ts","sourceRoot":"","sources":["../../src/parse/parse.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAE9F,OAAO,KAAK,EAAE,gBAAgB,EAAc,MAAM,oBAAoB,CAAC;AACvE,OAAO,KAAK,EAAE,aAAa,EAAa,MAAM,kBAAkB,CAAC;AAcjE,MAAM,WAAW,YAAY;IAC3B,qEAAqE;IACrE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,sDAAsD;IACtD,IAAI,EAAE,MAAM,CAAC;IACb,kCAAkC;IAClC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,OAAO,CAAC,EAAE,gBAAgB,CAAC;IAC3B,wFAAwF;IACxF,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,QAAQ,GAAG,UAAU,CAAC;AAElC,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAC,CAAC;IAC3D,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,QAAQ,EAAE,CAAC;IACrB,qDAAqD;IACrD,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,qGAAqG;IACrG,IAAI,EAAE,UAAU,EAAE,CAAC;IACnB,yFAAyF;IACzF,OAAO,EAAE,OAAO,EAAE,CAAC;IACnB;;;OAGG;IACH,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,MAAM,WAAW,GAAG,aAAa,CAAC;AAExC,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,WAAW,CAAC;IACpB,UAAU,EAAE,SAAS,EAAE,CAAC;IACxB,sEAAsE;IACtE,QAAQ,CAAC,EAAE,QAAQ,EAAE,CAAC;IACtB,yDAAyD;IACzD,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAiHD,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,aAAa,EACnB,GAAG,EAAE,YAAY,GAChB,WAAW,CA6Vb"}
1
+ {"version":3,"file":"parse.d.ts","sourceRoot":"","sources":["../../src/parse/parse.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAE9F,OAAO,KAAK,EAAE,gBAAgB,EAAc,MAAM,oBAAoB,CAAC;AACvE,OAAO,KAAK,EAAE,aAAa,EAAa,MAAM,kBAAkB,CAAC;AAgBjE,MAAM,WAAW,YAAY;IAC3B,qEAAqE;IACrE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,sDAAsD;IACtD,IAAI,EAAE,MAAM,CAAC;IACb,kCAAkC;IAClC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,OAAO,CAAC,EAAE,gBAAgB,CAAC;IAC3B,wFAAwF;IACxF,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,QAAQ,GAAG,UAAU,CAAC;AAElC,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAC,CAAC;IAC3D,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,QAAQ,EAAE,CAAC;IACrB,qDAAqD;IACrD,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,qGAAqG;IACrG,IAAI,EAAE,UAAU,EAAE,CAAC;IACnB,yFAAyF;IACzF,OAAO,EAAE,OAAO,EAAE,CAAC;IACnB;;;OAGG;IACH,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,MAAM,WAAW,GAAG,aAAa,CAAC;AAExC,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,WAAW,CAAC;IACpB,UAAU,EAAE,SAAS,EAAE,CAAC;IACxB,sEAAsE;IACtE,QAAQ,CAAC,EAAE,QAAQ,EAAE,CAAC;IACtB,yDAAyD;IACzD,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAiHD,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,aAAa,EACnB,GAAG,EAAE,YAAY,GAChB,WAAW,CAoWb"}
@@ -1,13 +1,21 @@
1
- /** A half-open range of instants, ISO-8601 with the asking offset. */
2
- export interface DateRange {
1
+ /** A half-open range of instants, ISO-8601 with the asking offset. One bound may be open. */
2
+ export type DateRange = {
3
3
  gte: string;
4
4
  lt: string;
5
- }
5
+ } | {
6
+ gte: string;
7
+ lt?: never;
8
+ } | {
9
+ gte?: never;
10
+ lt: string;
11
+ };
12
+ /** True when `asOf` is an ISO-8601 date-time with an offset naming a real instant. */
13
+ export declare function validAsOf(asOf: unknown): asOf is string;
6
14
  /**
7
15
  * The range a time phrase names, relative to `asOf` and in its offset:
8
16
  * today, yesterday, this/last week (weeks start Monday), this/last month,
9
- * rolling "past <unit>" and "last N <units>", "since <weekday>". Null when
10
- * the phrase is not a time phrase.
17
+ * rolling "past <unit>" and "last N <units>", "since <weekday>"; and explicit
18
+ * dates (see `explicitRange`). Null when the phrase is not a time phrase.
11
19
  */
12
20
  export declare function rangeFor(phrase: string, asOf: string): DateRange | null;
13
21
  //# sourceMappingURL=time.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"time.d.ts","sourceRoot":"","sources":["../../src/parse/time.ts"],"names":[],"mappings":"AAAA,sEAAsE;AACtE,MAAM,WAAW,SAAS;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,EAAE,EAAE,MAAM,CAAC;CACZ;AAID;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAgDvE"}
1
+ {"version":3,"file":"time.d.ts","sourceRoot":"","sources":["../../src/parse/time.ts"],"names":[],"mappings":"AAAA,6FAA6F;AAC7F,MAAM,MAAM,SAAS,GACjB;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAC3B;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,EAAE,CAAC,EAAE,KAAK,CAAA;CAAE,GAC3B;IAAE,GAAG,CAAC,EAAE,KAAK,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC;AAOhC,sFAAsF;AACtF,wBAAgB,SAAS,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,IAAI,MAAM,CAMvD;AAED;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAgDvE"}
@@ -107,6 +107,12 @@ export interface Composed {
107
107
  strict: boolean;
108
108
  /** The recency word ("latest") claimed by a seed phrase: the seed is ordered by it. */
109
109
  seedLatest?: string;
110
+ /** "highest"/"lowest" on a seed field phrase: the seed is ordered by that field. */
111
+ seedOrder?: {
112
+ span: string;
113
+ dir: 'desc' | 'asc';
114
+ phrase: PhraseClaim;
115
+ };
110
116
  }
111
117
  /** Clauses as one where: the clause itself, or an AND with plan paths re-rooted. */
112
118
  export declare function andOf(parts: Part[]): {
@@ -1 +1 @@
1
- {"version":3,"file":"walks.d.ts","sourceRoot":"","sources":["../../src/parse/walks.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"walks.d.ts","sourceRoot":"","sources":["../../src/parse/walks.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAqB,MAAM,yBAAyB,CAAC;AAE9E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAIlD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,cAAc,CAAC;IACvB,KAAK,EAAE,WAAW,CAAC;IACnB,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;IACjB,6EAA6E;IAC7E,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;CAC9B;AAkBD,wBAAgB,UAAU,CAAC,IAAI,EAAE,WAAW,GAAG,WAAW,CAqDzD;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,WAAW,GAAG,WAAW,CAwB1D"}
1
+ {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAqB,MAAM,yBAAyB,CAAC;AAE9E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAIlD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAGrD,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,cAAc,CAAC;IACvB,KAAK,EAAE,WAAW,CAAC;IACnB,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;IACjB,6EAA6E;IAC7E,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;CAC9B;AAkBD,wBAAgB,UAAU,CAAC,IAAI,EAAE,WAAW,GAAG,WAAW,CA8DzD;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,WAAW,GAAG,WAAW,CAwB1D"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mnmal-ai/substrate-nl",
3
- "version": "1.0.0-alpha.11",
3
+ "version": "1.0.0-alpha.13",
4
4
  "description": "Natural-language questions for Substrate — a deterministic parser with an optional constrained judge, served at POST /substrate/v1/nl and GET /substrate/v1/nl/pack/:ns, plus browser-safe completion.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -37,8 +37,8 @@
37
37
  },
38
38
  "devDependencies": {
39
39
  "@biomejs/biome": "2.6.4",
40
- "@mnmal-ai/substrate": "1.0.0-alpha.11",
41
- "@mnmal-ai/substrate-config": "1.0.0-alpha.11",
40
+ "@mnmal-ai/substrate": "1.0.0-alpha.13",
41
+ "@mnmal-ai/substrate-config": "1.0.0-alpha.13",
42
42
  "@types/node": "^26.6.4",
43
43
  "tsdown": "^0.23.0",
44
44
  "typescript": "^7.0.2",