@sixb/core 0.1.3 → 0.1.5

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.
Files changed (271) hide show
  1. package/dist/actions/index.d.ts +2 -2
  2. package/dist/actions/index.d.ts.map +1 -1
  3. package/dist/actions/index.js +17 -18
  4. package/dist/actions/index.js.map +1 -1
  5. package/dist/actions/read-facade.d.ts +10 -2
  6. package/dist/actions/read-facade.d.ts.map +1 -1
  7. package/dist/actions/types/context.d.ts +45 -2
  8. package/dist/actions/types/context.d.ts.map +1 -1
  9. package/dist/actions/worker.js +4 -4
  10. package/dist/actions/worker.js.map +1 -1
  11. package/dist/agents/context-compaction.d.ts +40 -0
  12. package/dist/agents/context-compaction.d.ts.map +1 -0
  13. package/dist/agents/ids.d.ts +2 -0
  14. package/dist/agents/ids.d.ts.map +1 -1
  15. package/dist/agents/index.d.ts +6 -5
  16. package/dist/agents/index.d.ts.map +1 -1
  17. package/dist/agents/index.js +42 -37
  18. package/dist/agents/index.js.map +1 -1
  19. package/dist/agents/streams.d.ts +17 -1
  20. package/dist/agents/streams.d.ts.map +1 -1
  21. package/dist/agents/streams.js +4 -2
  22. package/dist/agents/streams.js.map +1 -1
  23. package/dist/agents/types.d.ts +11 -0
  24. package/dist/agents/types.d.ts.map +1 -1
  25. package/dist/agents/validation.d.ts +1 -1
  26. package/dist/agents/validation.d.ts.map +1 -1
  27. package/dist/auth/index.js +7 -3
  28. package/dist/auth/index.js.map +1 -1
  29. package/dist/authorization/index.js +6 -2
  30. package/dist/authorization/index.js.map +1 -1
  31. package/dist/bootstrap/index.js +30 -31
  32. package/dist/bootstrap/index.js.map +1 -1
  33. package/dist/{chunk-d6y2wsfg.js → chunk-1q02e95s.js} +12 -7
  34. package/dist/{chunk-d6y2wsfg.js.map → chunk-1q02e95s.js.map} +4 -3
  35. package/dist/{chunk-c6cfqydk.js → chunk-4h947jd8.js} +1 -1
  36. package/dist/{chunk-gqdd7508.js → chunk-4rh2m6zg.js} +1 -1
  37. package/dist/{chunk-f1cyvnrk.js → chunk-6d5xdj1q.js} +259 -9
  38. package/dist/chunk-6d5xdj1q.js.map +14 -0
  39. package/dist/{chunk-fmx1sz2h.js → chunk-72q2y8hn.js} +3 -3
  40. package/dist/{chunk-fmx1sz2h.js.map → chunk-72q2y8hn.js.map} +3 -3
  41. package/dist/{chunk-ttba8x6z.js → chunk-78c6xvyr.js} +68 -3
  42. package/dist/chunk-78c6xvyr.js.map +15 -0
  43. package/dist/{chunk-wb4051cx.js → chunk-819wnjz8.js} +11 -5
  44. package/dist/chunk-819wnjz8.js.map +12 -0
  45. package/dist/{chunk-177ed2mp.js → chunk-84h6qhmp.js} +233 -97
  46. package/dist/{chunk-177ed2mp.js.map → chunk-84h6qhmp.js.map} +9 -9
  47. package/dist/{chunk-pxtrxm3p.js → chunk-a09jx26t.js} +1 -1
  48. package/dist/{chunk-bv41fmjn.js → chunk-am6eczah.js} +59 -22
  49. package/dist/{chunk-bv41fmjn.js.map → chunk-am6eczah.js.map} +8 -7
  50. package/dist/{chunk-zpw39qhj.js → chunk-dm72y8wt.js} +25 -2
  51. package/dist/chunk-dm72y8wt.js.map +10 -0
  52. package/dist/{chunk-kqxgf6kv.js → chunk-drjqm1aa.js} +16 -1
  53. package/dist/{chunk-kqxgf6kv.js.map → chunk-drjqm1aa.js.map} +3 -3
  54. package/dist/chunk-dysw7nsm.js.map +1 -1
  55. package/dist/{chunk-4p9rh7jj.js → chunk-ea73pzk7.js} +1 -1
  56. package/dist/{chunk-4aw0jwk0.js → chunk-f6erf2vv.js} +1 -1
  57. package/dist/{chunk-4z3n1dzt.js → chunk-f83ka50g.js} +131 -25
  58. package/dist/chunk-f83ka50g.js.map +23 -0
  59. package/dist/{chunk-c8bw2b6c.js → chunk-gadsm794.js} +2 -2
  60. package/dist/{chunk-1ywq4hzk.js → chunk-h6h91mfy.js} +8 -10
  61. package/dist/{chunk-1ywq4hzk.js.map → chunk-h6h91mfy.js.map} +2 -2
  62. package/dist/{chunk-n5x5y80a.js → chunk-nzw7sm3b.js} +2 -2
  63. package/dist/{chunk-63te770v.js → chunk-qewtks41.js} +8 -8
  64. package/dist/{chunk-63te770v.js.map → chunk-qewtks41.js.map} +2 -2
  65. package/dist/{chunk-261ag4t4.js → chunk-r0rry8dn.js} +3 -3
  66. package/dist/{chunk-88ppkr54.js → chunk-rte5sm3z.js} +26 -2
  67. package/dist/chunk-rte5sm3z.js.map +12 -0
  68. package/dist/chunk-t0jjkep3.js.map +1 -1
  69. package/dist/{chunk-ettb0cy4.js → chunk-tbv03sx2.js} +59 -29
  70. package/dist/{chunk-ettb0cy4.js.map → chunk-tbv03sx2.js.map} +5 -5
  71. package/dist/{chunk-d2adj15j.js → chunk-tkdscva6.js} +1 -1
  72. package/dist/{chunk-xmggaxg4.js → chunk-v0egge8b.js} +201 -38
  73. package/dist/{chunk-xmggaxg4.js.map → chunk-v0egge8b.js.map} +8 -6
  74. package/dist/{chunk-z8j83gzd.js → chunk-x2yw73hy.js} +1 -1
  75. package/dist/{chunk-qer6am8w.js → chunk-xdf9yswn.js} +1 -1
  76. package/dist/{chunk-3rsf30p4.js → chunk-z3ga9q5m.js} +18 -21
  77. package/dist/{chunk-3rsf30p4.js.map → chunk-z3ga9q5m.js.map} +4 -5
  78. package/dist/{chunk-km4rkj4t.js → chunk-zp2v2rv6.js} +3 -3
  79. package/dist/execution/agent.js +26 -27
  80. package/dist/execution/agent.js.map +1 -1
  81. package/dist/execution/primitive.js +26 -27
  82. package/dist/execution/primitive.js.map +2 -2
  83. package/dist/execution/request.js +27 -28
  84. package/dist/execution/request.js.map +2 -2
  85. package/dist/http/index.js +1 -1
  86. package/dist/index.d.ts +6 -5
  87. package/dist/index.d.ts.map +1 -1
  88. package/dist/index.js +54 -55
  89. package/dist/index.js.map +1 -1
  90. package/dist/lake-storage/types.d.ts +2 -1
  91. package/dist/lake-storage/types.d.ts.map +1 -1
  92. package/dist/materializer/index.js +15 -16
  93. package/dist/materializer/index.js.map +1 -1
  94. package/dist/models/index.d.ts +40 -0
  95. package/dist/models/index.d.ts.map +1 -0
  96. package/dist/objects/execution.d.ts +6 -1
  97. package/dist/objects/execution.d.ts.map +1 -1
  98. package/dist/objects/index.js +19 -20
  99. package/dist/objects/index.js.map +1 -1
  100. package/dist/objects/link/endpoints.d.ts.map +1 -1
  101. package/dist/objects/query/executor.d.ts +1 -0
  102. package/dist/objects/query/executor.d.ts.map +1 -1
  103. package/dist/objects/query/explain.d.ts +1 -0
  104. package/dist/objects/query/explain.d.ts.map +1 -1
  105. package/dist/objects/query/index.d.ts +3 -1
  106. package/dist/objects/query/index.d.ts.map +1 -1
  107. package/dist/objects/query/index.js +30 -5
  108. package/dist/objects/query/index.js.map +1 -1
  109. package/dist/objects/query/ir.d.ts +14 -1
  110. package/dist/objects/query/ir.d.ts.map +1 -1
  111. package/dist/objects/query/links.d.ts +25 -0
  112. package/dist/objects/query/links.d.ts.map +1 -0
  113. package/dist/objects/query/normalize.d.ts.map +1 -1
  114. package/dist/objects/query/planner.d.ts.map +1 -1
  115. package/dist/objects/query/validate.d.ts +1 -0
  116. package/dist/objects/query/validate.d.ts.map +1 -1
  117. package/dist/ontology/internal.d.ts +1 -1
  118. package/dist/ontology/internal.d.ts.map +1 -1
  119. package/dist/ontology/internal.js +14 -6
  120. package/dist/ontology/internal.js.map +3 -3
  121. package/dist/ontology/json-schema.d.ts +10 -0
  122. package/dist/ontology/json-schema.d.ts.map +1 -1
  123. package/dist/pipelines/index.js +15 -16
  124. package/dist/pipelines/index.js.map +1 -1
  125. package/dist/projections/builders.d.ts +33 -4
  126. package/dist/projections/builders.d.ts.map +1 -1
  127. package/dist/projections/index.d.ts +1 -1
  128. package/dist/projections/index.d.ts.map +1 -1
  129. package/dist/projections/internal.js +15 -16
  130. package/dist/projections/internal.js.map +1 -1
  131. package/dist/projections/run-dispatch.d.ts +1 -0
  132. package/dist/projections/run-dispatch.d.ts.map +1 -1
  133. package/dist/projections/types.d.ts +12 -9
  134. package/dist/projections/types.d.ts.map +1 -1
  135. package/dist/projections/validation.d.ts +6 -5
  136. package/dist/projections/validation.d.ts.map +1 -1
  137. package/dist/query.d.ts +2 -1
  138. package/dist/query.d.ts.map +1 -1
  139. package/dist/query.js +2 -2
  140. package/dist/runtime/create.d.ts +3 -0
  141. package/dist/runtime/create.d.ts.map +1 -1
  142. package/dist/runtime/definitions.d.ts +3 -0
  143. package/dist/runtime/definitions.d.ts.map +1 -1
  144. package/dist/runtime/host.d.ts +2 -0
  145. package/dist/runtime/host.d.ts.map +1 -1
  146. package/dist/runtime/resolve-definitions.d.ts +2 -0
  147. package/dist/runtime/resolve-definitions.d.ts.map +1 -1
  148. package/dist/storage/agents/in-memory.d.ts +4 -0
  149. package/dist/storage/agents/in-memory.d.ts.map +1 -1
  150. package/dist/storage/agents/provider.js +9 -5
  151. package/dist/storage/agents/provider.js.map +1 -1
  152. package/dist/storage/agents/types.d.ts +5 -0
  153. package/dist/storage/agents/types.d.ts.map +1 -1
  154. package/dist/storage/ai-cost/provider.js +1 -1
  155. package/dist/storage/ai-usage/in-memory.d.ts +2 -1
  156. package/dist/storage/ai-usage/in-memory.d.ts.map +1 -1
  157. package/dist/storage/ai-usage/index.d.ts +1 -1
  158. package/dist/storage/ai-usage/index.d.ts.map +1 -1
  159. package/dist/storage/ai-usage/types.d.ts +6 -0
  160. package/dist/storage/ai-usage/types.d.ts.map +1 -1
  161. package/dist/storage/index.d.ts +3 -2
  162. package/dist/storage/index.d.ts.map +1 -1
  163. package/dist/storage/index.js +19 -14
  164. package/dist/storage/index.js.map +1 -1
  165. package/dist/storage/objects/in-memory.d.ts +6 -3
  166. package/dist/storage/objects/in-memory.d.ts.map +1 -1
  167. package/dist/storage/objects/index.d.ts +3 -1
  168. package/dist/storage/objects/index.d.ts.map +1 -1
  169. package/dist/storage/objects/keys.d.ts +16 -0
  170. package/dist/storage/objects/keys.d.ts.map +1 -0
  171. package/dist/storage/objects/types.d.ts +33 -6
  172. package/dist/storage/objects/types.d.ts.map +1 -1
  173. package/dist/storage/ontology/provider.js +1 -1
  174. package/dist/storage/workflow-runs/provider.js +9 -5
  175. package/dist/storage/workflow-runs/provider.js.map +1 -1
  176. package/dist/syncs/index.js +15 -16
  177. package/dist/syncs/index.js.map +1 -1
  178. package/dist/testing/agent-storage-contract.d.ts.map +1 -1
  179. package/dist/testing/ai-usage-storage-contract.d.ts.map +1 -1
  180. package/dist/testing/effective-storage-contract.d.ts +1 -1
  181. package/dist/testing/effective-storage-contract.d.ts.map +1 -1
  182. package/dist/testing/index.js +455 -40
  183. package/dist/testing/index.js.map +7 -7
  184. package/dist/testing/lake-storage-contract.d.ts.map +1 -1
  185. package/dist/testing/object-query-contract.d.ts +1 -1
  186. package/dist/testing/object-query-contract.d.ts.map +1 -1
  187. package/dist/webhooks/internal.js +13 -14
  188. package/dist/webhooks/internal.js.map +2 -2
  189. package/dist/workflows/index.js +27 -28
  190. package/dist/workflows/index.js.map +1 -1
  191. package/package.json +1 -1
  192. package/src/actions/index.ts +10 -1
  193. package/src/actions/read-facade.ts +125 -3
  194. package/src/actions/types/context.ts +69 -2
  195. package/src/agents/builders.ts +3 -3
  196. package/src/agents/context-compaction.ts +261 -0
  197. package/src/agents/ids.ts +5 -0
  198. package/src/agents/index.ts +18 -9
  199. package/src/agents/streams.ts +56 -0
  200. package/src/agents/types.ts +12 -0
  201. package/src/agents/validation.ts +39 -5
  202. package/src/http/api-routes.ts +7 -7
  203. package/src/index.ts +19 -0
  204. package/src/lake-storage/types.ts +2 -1
  205. package/src/materializer/telemetry/append.ts +33 -16
  206. package/src/models/index.ts +71 -0
  207. package/src/objects/execution.ts +20 -0
  208. package/src/objects/link/endpoints.ts +2 -1
  209. package/src/objects/query/executor.ts +42 -11
  210. package/src/objects/query/explain.ts +11 -0
  211. package/src/objects/query/index.ts +6 -0
  212. package/src/objects/query/ir.ts +16 -0
  213. package/src/objects/query/links.ts +308 -0
  214. package/src/objects/query/normalize.ts +40 -1
  215. package/src/objects/query/planner.ts +5 -0
  216. package/src/objects/query/validate.ts +58 -2
  217. package/src/ontology/internal.ts +1 -1
  218. package/src/ontology/json-schema.ts +27 -5
  219. package/src/projections/builders.ts +220 -15
  220. package/src/projections/index.ts +1 -0
  221. package/src/projections/ownership.ts +5 -5
  222. package/src/projections/registry.ts +9 -3
  223. package/src/projections/revision.ts +31 -5
  224. package/src/projections/run-dispatch.ts +10 -1
  225. package/src/projections/types.ts +13 -9
  226. package/src/projections/validation.ts +61 -19
  227. package/src/query.ts +2 -0
  228. package/src/runtime/create.ts +4 -0
  229. package/src/runtime/definitions.ts +3 -0
  230. package/src/runtime/host.ts +2 -0
  231. package/src/runtime/resolve-definitions.ts +28 -0
  232. package/src/storage/agents/in-memory.ts +15 -0
  233. package/src/storage/agents/types.ts +5 -0
  234. package/src/storage/ai-usage/in-memory.ts +23 -0
  235. package/src/storage/ai-usage/index.ts +1 -0
  236. package/src/storage/ai-usage/types.ts +10 -0
  237. package/src/storage/index.ts +11 -0
  238. package/src/storage/objects/in-memory.ts +103 -16
  239. package/src/storage/objects/index.ts +9 -0
  240. package/src/storage/objects/keys.ts +36 -0
  241. package/src/storage/objects/types.ts +34 -6
  242. package/src/storage/ontology/provider-header-validation.ts +2 -2
  243. package/src/storage/timeseries/store.ts +6 -6
  244. package/src/testing/agent-storage-contract.ts +12 -0
  245. package/src/testing/ai-usage-storage-contract.ts +35 -0
  246. package/src/testing/effective-storage-contract.ts +194 -4
  247. package/src/testing/lake-storage-contract.ts +56 -0
  248. package/src/testing/object-query-contract.ts +208 -1
  249. package/dist/agents/prompt.d.ts +0 -21
  250. package/dist/agents/prompt.d.ts.map +0 -1
  251. package/dist/chunk-4z3n1dzt.js.map +0 -23
  252. package/dist/chunk-6wetht33.js +0 -89
  253. package/dist/chunk-6wetht33.js.map +0 -10
  254. package/dist/chunk-88ppkr54.js.map +0 -12
  255. package/dist/chunk-f1cyvnrk.js.map +0 -13
  256. package/dist/chunk-ttba8x6z.js.map +0 -15
  257. package/dist/chunk-wb4051cx.js.map +0 -12
  258. package/dist/chunk-zpw39qhj.js.map +0 -10
  259. package/src/agents/prompt.ts +0 -85
  260. /package/dist/{chunk-c6cfqydk.js.map → chunk-4h947jd8.js.map} +0 -0
  261. /package/dist/{chunk-gqdd7508.js.map → chunk-4rh2m6zg.js.map} +0 -0
  262. /package/dist/{chunk-pxtrxm3p.js.map → chunk-a09jx26t.js.map} +0 -0
  263. /package/dist/{chunk-4p9rh7jj.js.map → chunk-ea73pzk7.js.map} +0 -0
  264. /package/dist/{chunk-4aw0jwk0.js.map → chunk-f6erf2vv.js.map} +0 -0
  265. /package/dist/{chunk-c8bw2b6c.js.map → chunk-gadsm794.js.map} +0 -0
  266. /package/dist/{chunk-n5x5y80a.js.map → chunk-nzw7sm3b.js.map} +0 -0
  267. /package/dist/{chunk-261ag4t4.js.map → chunk-r0rry8dn.js.map} +0 -0
  268. /package/dist/{chunk-d2adj15j.js.map → chunk-tkdscva6.js.map} +0 -0
  269. /package/dist/{chunk-z8j83gzd.js.map → chunk-x2yw73hy.js.map} +0 -0
  270. /package/dist/{chunk-qer6am8w.js.map → chunk-xdf9yswn.js.map} +0 -0
  271. /package/dist/{chunk-km4rkj4t.js.map → chunk-zp2v2rv6.js.map} +0 -0
@@ -0,0 +1,308 @@
1
+ import { createHash } from "node:crypto"
2
+ import { isAllowed } from "../../authorization"
3
+ import { stableJsonStringify } from "../../json"
4
+ import {
5
+ type LinkDirection,
6
+ type ObjectBatchKey,
7
+ type ObjectLinkCursor,
8
+ type ObjectLinkRow,
9
+ type ObjectRow,
10
+ type ObjectStorage,
11
+ objectBatchKey,
12
+ objectLinkCursor,
13
+ } from "../../storage"
14
+ import { ObjectQueryExecutionError } from "./errors"
15
+ import { executeObjectQuery, type QueryExecutorOptions } from "./executor"
16
+ import type { ObjectQuery } from "./ir"
17
+ import { normalizeObjectQuery } from "./normalize"
18
+ import { validateObjectQuery } from "./validate"
19
+
20
+ export interface ExecuteObjectQueryLinksInput {
21
+ projectId: string
22
+ query: ObjectQuery
23
+ direction?: LinkDirection
24
+ linkId?: string
25
+ includeObjects?: boolean
26
+ pageSize?: number
27
+ pageToken?: string
28
+ }
29
+
30
+ export interface ExecuteObjectQueryLinksResult {
31
+ objects: readonly ObjectRow[]
32
+ links: readonly ObjectLinkRow[]
33
+ hasMore: boolean
34
+ nextPageToken?: string
35
+ }
36
+
37
+ const MAX_LINK_QUERY_OBJECTS = 1_000
38
+ const MAX_LINK_PAGE_SIZE = 1_000
39
+ const DEFAULT_LINK_PAGE_SIZE = 100
40
+ const LINK_PAGE_TOKEN_PREFIX = "link:v1:"
41
+
42
+ /**
43
+ * Select a bounded object set with the query IR, then return physical links
44
+ * incident to that set. This is a separate terminal because edge rows and edge
45
+ * pagination are a different response contract from object-set queries.
46
+ */
47
+ export async function executeObjectQueryLinks(
48
+ input: ExecuteObjectQueryLinksInput,
49
+ options: QueryExecutorOptions
50
+ ): Promise<ExecuteObjectQueryLinksResult> {
51
+ const direction = input.direction ?? "both"
52
+ if (direction !== "outgoing" && direction !== "incoming" && direction !== "both") {
53
+ throw new ObjectQueryExecutionError(
54
+ "invalid_link_direction",
55
+ "direction must be 'outgoing', 'incoming', or 'both'",
56
+ "$.direction"
57
+ )
58
+ }
59
+ if (input.linkId !== undefined && input.linkId.length === 0) {
60
+ throw new ObjectQueryExecutionError("invalid_link_id", "linkId must not be empty", "$.linkId")
61
+ }
62
+
63
+ const pageSize = input.pageSize ?? DEFAULT_LINK_PAGE_SIZE
64
+ if (!Number.isInteger(pageSize) || pageSize <= 0 || pageSize > MAX_LINK_PAGE_SIZE) {
65
+ throw new ObjectQueryExecutionError(
66
+ "invalid_link_page_size",
67
+ `pageSize must be an integer between 1 and ${MAX_LINK_PAGE_SIZE}`,
68
+ "$.pageSize"
69
+ )
70
+ }
71
+ const validated = validateObjectQuery(normalizeObjectQuery(input.query), {
72
+ ontology: options.ontology,
73
+ maxLimit: options.maxLimit,
74
+ maxPageSize: options.maxPageSize,
75
+ maxRefs: options.maxRefs,
76
+ normalize: false,
77
+ })
78
+ assertLinkSelectorShape(validated.query)
79
+ const selectorQuery = validated.query
80
+ const pageScope = linkPageScope(input.projectId, selectorQuery, direction, input.linkId)
81
+ const cursor = decodeLinkPageToken(input.pageToken, pageScope)
82
+
83
+ // A root page already has an explicit result bound and must execute directly: nesting it under a
84
+ // probe limit would expose the provider's internal pageSize+1 row. All other selector shapes are
85
+ // capped here before the provider receives the selected identities.
86
+ const boundedSelector =
87
+ selectorQuery.kind === "page"
88
+ ? selectorQuery
89
+ : { kind: "limit" as const, limit: MAX_LINK_QUERY_OBJECTS + 1, input: selectorQuery }
90
+ const selection = await executeObjectQuery(
91
+ {
92
+ projectId: input.projectId,
93
+ query: boundedSelector,
94
+ includeTotal: false,
95
+ },
96
+ {
97
+ ...options,
98
+ maxLimit: Math.max(options.maxLimit ?? 1_000, MAX_LINK_QUERY_OBJECTS + 1),
99
+ }
100
+ )
101
+ if (selection.objects.length > MAX_LINK_QUERY_OBJECTS) {
102
+ throw new ObjectQueryExecutionError(
103
+ "link_query_object_limit_exceeded",
104
+ `Link query selected more than ${MAX_LINK_QUERY_OBJECTS} objects; add a limit or page node`,
105
+ "$.query"
106
+ )
107
+ }
108
+
109
+ const selected = selection.objects
110
+ if (selected.length === 0) {
111
+ return { objects: [], links: [], hasMore: false }
112
+ }
113
+
114
+ const endpointObjectTypeIds = visibleEndpointTypeIds(options)
115
+ const page = await options.storage.queryLinks({
116
+ projectId: input.projectId,
117
+ objectRefs: selected.map((row) => ({
118
+ objectTypeId: row.objectTypeId,
119
+ primaryId: row.primaryId,
120
+ })),
121
+ direction,
122
+ ...(input.linkId === undefined ? {} : { linkId: input.linkId }),
123
+ ...(endpointObjectTypeIds === undefined ? {} : { endpointObjectTypeIds }),
124
+ ...(cursor === undefined ? {} : { after: cursor }),
125
+ limit: pageSize,
126
+ })
127
+ const lastLink = page.links.at(-1)
128
+ let nextPageToken: string | undefined
129
+ if (page.hasMore) {
130
+ if (!lastLink) {
131
+ throw new Error("[Sixb] Object storage returned hasMore for an empty link page.")
132
+ }
133
+ nextPageToken = encodeLinkPageToken(objectLinkCursor(lastLink), pageScope)
134
+ }
135
+ const objects = input.includeObjects
136
+ ? await hydrateLinkQueryObjects(input.projectId, selected, page.links, options.storage)
137
+ : []
138
+
139
+ return {
140
+ objects,
141
+ links: page.links,
142
+ hasMore: page.hasMore,
143
+ ...(nextPageToken ? { nextPageToken } : {}),
144
+ }
145
+ }
146
+
147
+ interface EncodedLinkPageToken {
148
+ version: 1
149
+ scope: string
150
+ cursor: ObjectLinkCursor
151
+ }
152
+
153
+ function assertLinkSelectorShape(query: ObjectQuery, path = "$.query", root = true): void {
154
+ switch (query.kind) {
155
+ case "project":
156
+ case "expand":
157
+ throw new ObjectQueryExecutionError(
158
+ "link_selector_output_shape_not_supported",
159
+ `Link selectors do not support '${query.kind}' nodes; remove output shaping from the selector`,
160
+ path
161
+ )
162
+ case "page":
163
+ if (!root) {
164
+ throw new ObjectQueryExecutionError(
165
+ "link_selector_page_must_be_root",
166
+ "A page node in a link selector must be the outermost node",
167
+ path
168
+ )
169
+ }
170
+ assertLinkSelectorShape(query.input, `${path}.input`, false)
171
+ return
172
+ case "start":
173
+ case "refs":
174
+ return
175
+ case "set": {
176
+ for (const [index, input] of query.inputs.entries()) {
177
+ assertLinkSelectorShape(input, `${path}.inputs[${index}]`, false)
178
+ }
179
+ return
180
+ }
181
+ case "filter":
182
+ case "text":
183
+ case "vector":
184
+ case "traverse":
185
+ case "sort":
186
+ case "limit":
187
+ assertLinkSelectorShape(query.input, `${path}.input`, false)
188
+ return
189
+ }
190
+ }
191
+
192
+ function visibleEndpointTypeIds(options: QueryExecutorOptions): readonly string[] | undefined {
193
+ if (!options.authorization) return undefined
194
+ return options.ontology
195
+ .listObjectTypes()
196
+ .filter((objectType) =>
197
+ isAllowed(options.authorization, { kind: "object.view", objectTypeId: objectType.id })
198
+ )
199
+ .map((objectType) => objectType.id)
200
+ }
201
+
202
+ function objectIdentity(objectTypeId: string, primaryId: string): ObjectBatchKey {
203
+ return objectBatchKey(objectTypeId, primaryId)
204
+ }
205
+
206
+ function linkPageScope(
207
+ projectId: string,
208
+ query: ObjectQuery,
209
+ direction: LinkDirection,
210
+ linkId: string | undefined
211
+ ): string {
212
+ return createHash("sha256")
213
+ .update(
214
+ stableJsonStringify({
215
+ projectId,
216
+ query,
217
+ direction,
218
+ linkId: linkId ?? null,
219
+ })
220
+ )
221
+ .digest("base64url")
222
+ }
223
+
224
+ function encodeLinkPageToken(cursor: ObjectLinkCursor, scope: string): string {
225
+ const payload: EncodedLinkPageToken = { version: 1, scope, cursor }
226
+ return `${LINK_PAGE_TOKEN_PREFIX}${Buffer.from(JSON.stringify(payload)).toString("base64url")}`
227
+ }
228
+
229
+ function decodeLinkPageToken(
230
+ token: string | undefined,
231
+ expectedScope: string
232
+ ): ObjectLinkCursor | undefined {
233
+ if (token === undefined) return undefined
234
+ try {
235
+ if (!token.startsWith(LINK_PAGE_TOKEN_PREFIX)) throw new Error("invalid prefix")
236
+ const value = JSON.parse(
237
+ Buffer.from(token.slice(LINK_PAGE_TOKEN_PREFIX.length), "base64url").toString("utf8")
238
+ ) as unknown
239
+ if (
240
+ !isPlainObject(value) ||
241
+ value.version !== 1 ||
242
+ typeof value.scope !== "string" ||
243
+ !isObjectLinkCursor(value.cursor)
244
+ ) {
245
+ throw new Error("invalid cursor")
246
+ }
247
+ if (value.scope !== expectedScope) {
248
+ throw new Error("cursor scope mismatch")
249
+ }
250
+ return value.cursor
251
+ } catch {
252
+ throw new ObjectQueryExecutionError(
253
+ "invalid_link_page_token",
254
+ "pageToken is not a valid object-links cursor",
255
+ "$.pageToken"
256
+ )
257
+ }
258
+ }
259
+
260
+ function isObjectLinkCursor(value: unknown): value is ObjectLinkCursor {
261
+ return (
262
+ Array.isArray(value) && value.length === 5 && value.every((part) => typeof part === "string")
263
+ )
264
+ }
265
+
266
+ function isPlainObject(value: unknown): value is Record<string, unknown> {
267
+ return typeof value === "object" && value !== null && !Array.isArray(value)
268
+ }
269
+
270
+ async function hydrateLinkQueryObjects(
271
+ projectId: string,
272
+ selected: readonly ObjectRow[],
273
+ links: readonly ObjectLinkRow[],
274
+ storage: ObjectStorage
275
+ ): Promise<ObjectRow[]> {
276
+ const byIdentity = new Map(
277
+ selected.map((row) => [objectIdentity(row.objectTypeId, row.primaryId), row])
278
+ )
279
+ const orderedIdentities = selected.map((row) => objectIdentity(row.objectTypeId, row.primaryId))
280
+ const seenIdentities = new Set(orderedIdentities)
281
+ const missingRefs: { objectTypeId: string; primaryId: string }[] = []
282
+
283
+ for (const link of links) {
284
+ for (const ref of [
285
+ { objectTypeId: link.sourceTypeId, primaryId: link.sourceId },
286
+ { objectTypeId: link.targetTypeId, primaryId: link.targetId },
287
+ ]) {
288
+ const identity = objectIdentity(ref.objectTypeId, ref.primaryId)
289
+ if (seenIdentities.has(identity)) continue
290
+ seenIdentities.add(identity)
291
+ orderedIdentities.push(identity)
292
+ missingRefs.push(ref)
293
+ }
294
+ }
295
+
296
+ if (missingRefs.length > 0) {
297
+ const hydrated = await storage.getByPrimaryIdBatch({ projectId, items: missingRefs })
298
+ for (const ref of missingRefs) {
299
+ const row = hydrated.get(objectBatchKey(ref.objectTypeId, ref.primaryId))
300
+ if (row) byIdentity.set(objectIdentity(ref.objectTypeId, ref.primaryId), row)
301
+ }
302
+ }
303
+
304
+ return orderedIdentities.flatMap((identity) => {
305
+ const row = byIdentity.get(identity)
306
+ return row ? [row] : []
307
+ })
308
+ }
@@ -22,6 +22,11 @@ function normalizeNode(query: ObjectQuery): ObjectQuery {
22
22
  switch (query.kind) {
23
23
  case "start":
24
24
  return { ...query }
25
+ case "refs":
26
+ return {
27
+ kind: "refs",
28
+ refs: uniqueRefs(query.refs),
29
+ }
25
30
  case "filter":
26
31
  return normalizeFilter(query.input, query.predicate)
27
32
  case "text":
@@ -63,7 +68,14 @@ function normalizeNode(query: ObjectQuery): ObjectQuery {
63
68
  * lands above another `expand` is merged into one.
64
69
  */
65
70
  function hoistExpand(query: ObjectQuery): ObjectQuery {
66
- if (query.kind === "start" || query.kind === "set" || query.kind === "expand") return query
71
+ if (
72
+ query.kind === "start" ||
73
+ query.kind === "refs" ||
74
+ query.kind === "set" ||
75
+ query.kind === "expand"
76
+ ) {
77
+ return query
78
+ }
67
79
  if (query.input.kind !== "expand") return query
68
80
 
69
81
  const inner = query.input
@@ -236,6 +248,33 @@ function uniqueStrings(values: readonly string[]): string[] {
236
248
  return [...new Set(values)]
237
249
  }
238
250
 
251
+ function uniqueRefs(
252
+ refs: readonly { objectTypeId: string; primaryId: string }[]
253
+ ): { objectTypeId: string; primaryId: string }[] {
254
+ const seen = new Set<string>()
255
+ const unique = refs.flatMap((ref) => {
256
+ const key = JSON.stringify([ref.objectTypeId, ref.primaryId])
257
+ if (seen.has(key)) return []
258
+ seen.add(key)
259
+ return [{ ...ref }]
260
+ })
261
+
262
+ // `refs` is an object-set source, not a positional batch response. Canonical
263
+ // identity order makes equivalent sets normalize identically and matches the
264
+ // default ordering used by provider query sources.
265
+ return unique.sort(
266
+ (left, right) =>
267
+ compareStrings(left.objectTypeId, right.objectTypeId) ||
268
+ compareStrings(left.primaryId, right.primaryId)
269
+ )
270
+ }
271
+
272
+ function compareStrings(left: string, right: string): number {
273
+ if (left < right) return -1
274
+ if (left > right) return 1
275
+ return 0
276
+ }
277
+
239
278
  function uniqueStringRecord(
240
279
  record: Readonly<Record<string, readonly string[]>>
241
280
  ): Record<string, string[]> {
@@ -40,6 +40,7 @@ const DEFAULT_MAX_FALLBACK_ROWS = 1_000
40
40
  // accidental full scans.
41
41
  const fallbackNodeKinds = new Set<ObjectQuery["kind"]>([
42
42
  "start",
43
+ "refs",
43
44
  "filter",
44
45
  "sort",
45
46
  "limit",
@@ -195,6 +196,8 @@ function collectNodeProviderIssues(
195
196
  )
196
197
  }
197
198
  return
199
+ case "refs":
200
+ return
198
201
  case "filter":
199
202
  collectPredicateProviderIssues(query.predicate, `${path}.predicate`, capabilities, issues)
200
203
  collectNodeProviderIssues(query.input, `${path}.input`, capabilities, issues)
@@ -474,6 +477,7 @@ function collectFallbackNodeIssues(
474
477
 
475
478
  switch (query.kind) {
476
479
  case "start":
480
+ case "refs":
477
481
  return
478
482
  case "filter":
479
483
  case "limit":
@@ -512,6 +516,7 @@ function hasExplicitResultBound(query: ObjectQuery): boolean {
512
516
  switch (query.kind) {
513
517
  case "limit":
514
518
  case "page":
519
+ case "refs":
515
520
  return true
516
521
  case "start":
517
522
  return false
@@ -1,4 +1,11 @@
1
- import type { ObjectType, OntologyRegistry, Property, Schema, ValueType } from "../../ontology"
1
+ import type {
2
+ ObjectRef,
3
+ ObjectType,
4
+ OntologyRegistry,
5
+ Property,
6
+ Schema,
7
+ ValueType,
8
+ } from "../../ontology"
2
9
  import { normalizeDecimalValue } from "../../ontology"
3
10
  import { formatUnknownObjectTypeMessage } from "../../ontology/errors"
4
11
  import { validatePropertyValue, validateSchemaValue } from "../../ontology/validation"
@@ -30,11 +37,12 @@ export interface ObjectQueryValidationOptions {
30
37
  ontology: OntologyRegistry
31
38
  maxLimit?: number
32
39
  maxPageSize?: number
40
+ maxRefs?: number
33
41
  normalize?: boolean
34
42
  }
35
43
 
36
44
  type QueryValidationContext = Required<
37
- Pick<ObjectQueryValidationOptions, "maxLimit" | "maxPageSize">
45
+ Pick<ObjectQueryValidationOptions, "maxLimit" | "maxPageSize" | "maxRefs">
38
46
  > & {
39
47
  ontology: OntologyRegistry
40
48
  valueTypesById: ReadonlyMap<string, ValueType>
@@ -53,6 +61,7 @@ interface TextFieldResolution {
53
61
 
54
62
  const DEFAULT_MAX_LIMIT = 1_000
55
63
  const DEFAULT_MAX_PAGE_SIZE = 1_000
64
+ const DEFAULT_MAX_REFS = 1_000
56
65
 
57
66
  export function validateObjectQuery(
58
67
  query: ObjectQuery,
@@ -96,6 +105,7 @@ function createValidationContext(options: ObjectQueryValidationOptions): QueryVa
96
105
  valueTypesById: options.ontology.getValueTypesById(),
97
106
  maxLimit: options.maxLimit ?? DEFAULT_MAX_LIMIT,
98
107
  maxPageSize: options.maxPageSize ?? DEFAULT_MAX_PAGE_SIZE,
108
+ maxRefs: options.maxRefs ?? DEFAULT_MAX_REFS,
99
109
  issues: [],
100
110
  touchedObjectTypeIds: new Set<string>(),
101
111
  }
@@ -125,6 +135,8 @@ function dispatchQueryNode(
125
135
  switch (query.kind) {
126
136
  case "start":
127
137
  return validateStart(query.objectTypeId, query.includeSubtypes, path, ctx)
138
+ case "refs":
139
+ return validateRefs(query.refs, path, ctx)
128
140
  case "filter": {
129
141
  const input = validateQueryNode(query.input, `${path}.input`, ctx)
130
142
  const predicate = validatePredicate(query.predicate, input.result, `${path}.predicate`, ctx)
@@ -199,6 +211,50 @@ function dispatchQueryNode(
199
211
  }
200
212
  }
201
213
 
214
+ function validateRefs(
215
+ refs: readonly ObjectRef[],
216
+ path: string,
217
+ ctx: QueryValidationContext
218
+ ): QueryValidationResult {
219
+ if (refs.length === 0) {
220
+ addIssue(ctx, `${path}.refs`, "empty_refs", "refs must include at least one object reference")
221
+ }
222
+ if (refs.length > ctx.maxRefs) {
223
+ addIssue(
224
+ ctx,
225
+ `${path}.refs`,
226
+ "too_many_refs",
227
+ `refs must include at most ${ctx.maxRefs} object references`
228
+ )
229
+ }
230
+
231
+ const objectTypeIds = new Set<string>()
232
+ refs.forEach((ref, index) => {
233
+ const refPath = `${path}.refs[${index}]`
234
+ if (!ref.objectTypeId) {
235
+ addIssue(ctx, `${refPath}.objectTypeId`, "missing_object_type", "objectTypeId is required")
236
+ } else if (!ctx.ontology.getObjectTypeById(ref.objectTypeId)) {
237
+ addIssue(
238
+ ctx,
239
+ `${refPath}.objectTypeId`,
240
+ "unknown_object_type",
241
+ formatUnknownObjectTypeMessage(ref.objectTypeId)
242
+ )
243
+ } else {
244
+ objectTypeIds.add(ref.objectTypeId)
245
+ }
246
+
247
+ if (!ref.primaryId) {
248
+ addIssue(ctx, `${refPath}.primaryId`, "missing_primary_id", "primaryId is required")
249
+ }
250
+ })
251
+
252
+ return {
253
+ result: { objectTypeIds: [...objectTypeIds] },
254
+ query: { kind: "refs", refs: refs.map((ref) => ({ ...ref })) },
255
+ }
256
+ }
257
+
202
258
  function validateStart(
203
259
  objectTypeId: string,
204
260
  includeSubtypes: boolean | undefined,
@@ -1,3 +1,3 @@
1
1
  export type { SchemaJsonSchema } from "./json-schema"
2
- export { schemaRecordToJsonSchema } from "./json-schema"
2
+ export { schemaFieldsToJsonSchema, schemaRecordToJsonSchema } from "./json-schema"
3
3
  export { createLinkTokenMap, createPropertyTokenMap } from "./tokens"
@@ -4,20 +4,42 @@ import type { Schema, ValueType } from "./types"
4
4
 
5
5
  export type SchemaJsonSchema = Readonly<Record<string, unknown>>
6
6
 
7
+ export interface SchemaJsonField {
8
+ readonly schema: SchemaOrRef
9
+ readonly required?: boolean
10
+ readonly nullable?: boolean
11
+ }
12
+
7
13
  /** Convert a record of Sixb schemas into the strict JSON object schema used by model outputs. */
8
14
  export function schemaRecordToJsonSchema(input: {
9
15
  readonly shape: Readonly<Record<string, SchemaOrRef>>
10
16
  readonly valueTypesById: ReadonlyMap<string, ValueType>
11
17
  }): SchemaJsonSchema {
18
+ return schemaFieldsToJsonSchema({
19
+ fields: Object.fromEntries(
20
+ Object.entries(input.shape).map(([field, schema]) => [field, { schema, required: true }])
21
+ ),
22
+ valueTypesById: input.valueTypesById,
23
+ })
24
+ }
25
+
26
+ /** Convert configured fields into the strict JSON object schema accepted at runtime. */
27
+ export function schemaFieldsToJsonSchema(input: {
28
+ readonly fields: Readonly<Record<string, SchemaJsonField>>
29
+ readonly valueTypesById: ReadonlyMap<string, ValueType>
30
+ }): SchemaJsonSchema {
31
+ const required = Object.entries(input.fields)
32
+ .filter(([, field]) => field.required === true)
33
+ .map(([field]) => field)
12
34
  return {
13
35
  type: "object",
14
36
  properties: Object.fromEntries(
15
- Object.entries(input.shape).map(([field, schema]) => [
16
- field,
17
- schemaOrRefJsonSchema(schema, input.valueTypesById, new Set()),
18
- ])
37
+ Object.entries(input.fields).map(([fieldId, field]) => {
38
+ const fieldSchema = schemaOrRefJsonSchema(field.schema, input.valueTypesById, new Set())
39
+ return [fieldId, field.nullable ? { anyOf: [fieldSchema, { type: "null" }] } : fieldSchema]
40
+ })
19
41
  ),
20
- required: Object.keys(input.shape),
42
+ ...(required.length > 0 ? { required } : {}),
21
43
  additionalProperties: false,
22
44
  }
23
45
  }