mrplex 0.0.1

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 (319) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +284 -0
  3. package/dist/cli/config.d.ts +28 -0
  4. package/dist/cli/config.js +48 -0
  5. package/dist/cli/config.js.map +1 -0
  6. package/dist/cli/exit-codes.d.ts +13 -0
  7. package/dist/cli/exit-codes.js +39 -0
  8. package/dist/cli/exit-codes.js.map +1 -0
  9. package/dist/cli/format.d.ts +22 -0
  10. package/dist/cli/format.js +148 -0
  11. package/dist/cli/format.js.map +1 -0
  12. package/dist/cli/main.d.ts +16 -0
  13. package/dist/cli/main.js +1518 -0
  14. package/dist/cli/main.js.map +1 -0
  15. package/dist/client/kernel-client.d.ts +97 -0
  16. package/dist/client/kernel-client.js +14 -0
  17. package/dist/client/kernel-client.js.map +1 -0
  18. package/dist/client/local.d.ts +26 -0
  19. package/dist/client/local.js +103 -0
  20. package/dist/client/local.js.map +1 -0
  21. package/dist/client/remote-mcp.d.ts +35 -0
  22. package/dist/client/remote-mcp.js +174 -0
  23. package/dist/client/remote-mcp.js.map +1 -0
  24. package/dist/embed/backfill.d.ts +24 -0
  25. package/dist/embed/backfill.js +33 -0
  26. package/dist/embed/backfill.js.map +1 -0
  27. package/dist/embed/chunker.d.ts +31 -0
  28. package/dist/embed/chunker.js +121 -0
  29. package/dist/embed/chunker.js.map +1 -0
  30. package/dist/embed/cmd-hook.d.ts +21 -0
  31. package/dist/embed/cmd-hook.js +100 -0
  32. package/dist/embed/cmd-hook.js.map +1 -0
  33. package/dist/embed/config.d.ts +61 -0
  34. package/dist/embed/config.js +109 -0
  35. package/dist/embed/config.js.map +1 -0
  36. package/dist/embed/hook.d.ts +38 -0
  37. package/dist/embed/hook.js +56 -0
  38. package/dist/embed/hook.js.map +1 -0
  39. package/dist/embed/http-hook.d.ts +13 -0
  40. package/dist/embed/http-hook.js +39 -0
  41. package/dist/embed/http-hook.js.map +1 -0
  42. package/dist/embed/worker.d.ts +62 -0
  43. package/dist/embed/worker.js +228 -0
  44. package/dist/embed/worker.js.map +1 -0
  45. package/dist/fixtures/hwe/characters/albion-ever.md +12 -0
  46. package/dist/fixtures/hwe/characters/cent.md +11 -0
  47. package/dist/fixtures/hwe/concepts/cent-demiurge-resonance.md +10 -0
  48. package/dist/fixtures/hwe/concepts/phase-looper.md +10 -0
  49. package/dist/fixtures/hwe/entities/demiurge.md +11 -0
  50. package/dist/fixtures/hwe/entities/monad.md +11 -0
  51. package/dist/fixtures/hwe/moc/globocorp-network.md +19 -0
  52. package/dist/fixtures/hwe/organizations/globocorp.md +11 -0
  53. package/dist/fixtures/hwe/readme.md +24 -0
  54. package/dist/fixtures/starship/crew/aria-okonkwo.md +14 -0
  55. package/dist/fixtures/starship/crew/bexley-orr.md +14 -0
  56. package/dist/fixtures/starship/crew/dax-thorne.md +14 -0
  57. package/dist/fixtures/starship/crew/isolde-marsh.md +16 -0
  58. package/dist/fixtures/starship/crew/kestrel-vance.md +15 -0
  59. package/dist/fixtures/starship/crew/quill-vasquez.md +14 -0
  60. package/dist/fixtures/starship/crew/soren-halloway.md +16 -0
  61. package/dist/fixtures/starship/encounters/the-drift-choir.md +14 -0
  62. package/dist/fixtures/starship/encounters/the-hollow-signal.md +16 -0
  63. package/dist/fixtures/starship/encounters/the-wandering-buoy.md +14 -0
  64. package/dist/fixtures/starship/equipment/coolant-loop-b.md +13 -0
  65. package/dist/fixtures/starship/equipment/emergency-ration-bars.md +12 -0
  66. package/dist/fixtures/starship/equipment/long-range-sensors.md +13 -0
  67. package/dist/fixtures/starship/equipment/plasma-manifold-3.md +15 -0
  68. package/dist/fixtures/starship/equipment/shuttle-corvid.md +13 -0
  69. package/dist/fixtures/starship/logs/marsh-4419-1.md +13 -0
  70. package/dist/fixtures/starship/logs/okonkwo-4415-1.md +13 -0
  71. package/dist/fixtures/starship/logs/orr-4413-1.md +13 -0
  72. package/dist/fixtures/starship/logs/orr-4420-1.md +12 -0
  73. package/dist/fixtures/starship/logs/thorne-4413-2.md +13 -0
  74. package/dist/fixtures/starship/logs/vance-4419-2.md +13 -0
  75. package/dist/fixtures/starship/misc/the-galley-ficus.md +11 -0
  76. package/dist/fixtures/starship/missions/the-cinder-run.md +17 -0
  77. package/dist/fixtures/starship/missions/the-drift-choir.md +17 -0
  78. package/dist/fixtures/starship/missions/the-hollow-signal.md +17 -0
  79. package/dist/fixtures/starship/missions/the-silent-beacon.md +17 -0
  80. package/dist/fixtures/starship/moc/crew.md +19 -0
  81. package/dist/fixtures/starship/moc/encounters.md +14 -0
  82. package/dist/fixtures/starship/moc/missions.md +15 -0
  83. package/dist/fixtures/starship/readme.md +60 -0
  84. package/dist/index.d.ts +1 -0
  85. package/dist/index.js +3 -0
  86. package/dist/index.js.map +1 -0
  87. package/dist/kernel/auth/glob.d.ts +46 -0
  88. package/dist/kernel/auth/glob.js +112 -0
  89. package/dist/kernel/auth/glob.js.map +1 -0
  90. package/dist/kernel/auth/scope.d.ts +53 -0
  91. package/dist/kernel/auth/scope.js +83 -0
  92. package/dist/kernel/auth/scope.js.map +1 -0
  93. package/dist/kernel/casefold.d.ts +37 -0
  94. package/dist/kernel/casefold.js +44 -0
  95. package/dist/kernel/casefold.js.map +1 -0
  96. package/dist/kernel/constants.d.ts +21 -0
  97. package/dist/kernel/constants.js +22 -0
  98. package/dist/kernel/constants.js.map +1 -0
  99. package/dist/kernel/context.d.ts +70 -0
  100. package/dist/kernel/context.js +114 -0
  101. package/dist/kernel/context.js.map +1 -0
  102. package/dist/kernel/deletion.d.ts +50 -0
  103. package/dist/kernel/deletion.js +83 -0
  104. package/dist/kernel/deletion.js.map +1 -0
  105. package/dist/kernel/diff.d.ts +36 -0
  106. package/dist/kernel/diff.js +61 -0
  107. package/dist/kernel/diff.js.map +1 -0
  108. package/dist/kernel/errors.d.ts +37 -0
  109. package/dist/kernel/errors.js +55 -0
  110. package/dist/kernel/errors.js.map +1 -0
  111. package/dist/kernel/frontmatter-input.d.ts +32 -0
  112. package/dist/kernel/frontmatter-input.js +85 -0
  113. package/dist/kernel/frontmatter-input.js.map +1 -0
  114. package/dist/kernel/graph.d.ts +34 -0
  115. package/dist/kernel/graph.js +559 -0
  116. package/dist/kernel/graph.js.map +1 -0
  117. package/dist/kernel/history.d.ts +74 -0
  118. package/dist/kernel/history.js +143 -0
  119. package/dist/kernel/history.js.map +1 -0
  120. package/dist/kernel/kernel.d.ts +162 -0
  121. package/dist/kernel/kernel.js +675 -0
  122. package/dist/kernel/kernel.js.map +1 -0
  123. package/dist/kernel/path-config.d.ts +101 -0
  124. package/dist/kernel/path-config.js +171 -0
  125. package/dist/kernel/path-config.js.map +1 -0
  126. package/dist/kernel/query/ast.d.ts +24 -0
  127. package/dist/kernel/query/ast.js +52 -0
  128. package/dist/kernel/query/ast.js.map +1 -0
  129. package/dist/kernel/query/cel-parse.d.ts +37 -0
  130. package/dist/kernel/query/cel-parse.js +88 -0
  131. package/dist/kernel/query/cel-parse.js.map +1 -0
  132. package/dist/kernel/query/degrees.d.ts +31 -0
  133. package/dist/kernel/query/degrees.js +135 -0
  134. package/dist/kernel/query/degrees.js.map +1 -0
  135. package/dist/kernel/query/graph-ast.d.ts +51 -0
  136. package/dist/kernel/query/graph-ast.js +139 -0
  137. package/dist/kernel/query/graph-ast.js.map +1 -0
  138. package/dist/kernel/query/query.d.ts +56 -0
  139. package/dist/kernel/query/query.js +276 -0
  140. package/dist/kernel/query/query.js.map +1 -0
  141. package/dist/kernel/validation.d.ts +64 -0
  142. package/dist/kernel/validation.js +183 -0
  143. package/dist/kernel/validation.js.map +1 -0
  144. package/dist/kernel/version-id.d.ts +10 -0
  145. package/dist/kernel/version-id.js +21 -0
  146. package/dist/kernel/version-id.js.map +1 -0
  147. package/dist/kernel/wire.d.ts +148 -0
  148. package/dist/kernel/wire.js +6 -0
  149. package/dist/kernel/wire.js.map +1 -0
  150. package/dist/links/backfill.d.ts +28 -0
  151. package/dist/links/backfill.js +33 -0
  152. package/dist/links/backfill.js.map +1 -0
  153. package/dist/links/extract.d.ts +29 -0
  154. package/dist/links/extract.js +233 -0
  155. package/dist/links/extract.js.map +1 -0
  156. package/dist/links/link-config.d.ts +70 -0
  157. package/dist/links/link-config.js +107 -0
  158. package/dist/links/link-config.js.map +1 -0
  159. package/dist/links/maintain.d.ts +37 -0
  160. package/dist/links/maintain.js +91 -0
  161. package/dist/links/maintain.js.map +1 -0
  162. package/dist/links/repair.d.ts +46 -0
  163. package/dist/links/repair.js +96 -0
  164. package/dist/links/repair.js.map +1 -0
  165. package/dist/links/resolve.d.ts +41 -0
  166. package/dist/links/resolve.js +121 -0
  167. package/dist/links/resolve.js.map +1 -0
  168. package/dist/links/stale.d.ts +32 -0
  169. package/dist/links/stale.js +73 -0
  170. package/dist/links/stale.js.map +1 -0
  171. package/dist/markdown/content-hash.d.ts +34 -0
  172. package/dist/markdown/content-hash.js +46 -0
  173. package/dist/markdown/content-hash.js.map +1 -0
  174. package/dist/markdown/frontmatter.d.ts +69 -0
  175. package/dist/markdown/frontmatter.js +147 -0
  176. package/dist/markdown/frontmatter.js.map +1 -0
  177. package/dist/markdown/hash-backfill.d.ts +22 -0
  178. package/dist/markdown/hash-backfill.js +43 -0
  179. package/dist/markdown/hash-backfill.js.map +1 -0
  180. package/dist/mcp/query-syntax.d.ts +20 -0
  181. package/dist/mcp/query-syntax.js +194 -0
  182. package/dist/mcp/query-syntax.js.map +1 -0
  183. package/dist/mcp/render.d.ts +31 -0
  184. package/dist/mcp/render.js +110 -0
  185. package/dist/mcp/render.js.map +1 -0
  186. package/dist/mcp/server.d.ts +60 -0
  187. package/dist/mcp/server.js +170 -0
  188. package/dist/mcp/server.js.map +1 -0
  189. package/dist/mcp/tools.d.ts +93 -0
  190. package/dist/mcp/tools.js +1218 -0
  191. package/dist/mcp/tools.js.map +1 -0
  192. package/dist/rest/conditional.d.ts +37 -0
  193. package/dist/rest/conditional.js +58 -0
  194. package/dist/rest/conditional.js.map +1 -0
  195. package/dist/rest/negotiate.d.ts +33 -0
  196. package/dist/rest/negotiate.js +54 -0
  197. package/dist/rest/negotiate.js.map +1 -0
  198. package/dist/rest/routes.d.ts +37 -0
  199. package/dist/rest/routes.js +744 -0
  200. package/dist/rest/routes.js.map +1 -0
  201. package/dist/seed/fixture-seed.d.ts +35 -0
  202. package/dist/seed/fixture-seed.js +75 -0
  203. package/dist/seed/fixture-seed.js.map +1 -0
  204. package/dist/seed/starship-config.d.ts +3 -0
  205. package/dist/seed/starship-config.js +5 -0
  206. package/dist/seed/starship-config.js.map +1 -0
  207. package/dist/server/headers.d.ts +36 -0
  208. package/dist/server/headers.js +30 -0
  209. package/dist/server/headers.js.map +1 -0
  210. package/dist/server/http-error.d.ts +53 -0
  211. package/dist/server/http-error.js +104 -0
  212. package/dist/server/http-error.js.map +1 -0
  213. package/dist/server/serve.d.ts +56 -0
  214. package/dist/server/serve.js +135 -0
  215. package/dist/server/serve.js.map +1 -0
  216. package/dist/shell/audit.d.ts +32 -0
  217. package/dist/shell/audit.js +28 -0
  218. package/dist/shell/audit.js.map +1 -0
  219. package/dist/shell/guard.d.ts +50 -0
  220. package/dist/shell/guard.js +176 -0
  221. package/dist/shell/guard.js.map +1 -0
  222. package/dist/shell/keys.d.ts +36 -0
  223. package/dist/shell/keys.js +64 -0
  224. package/dist/shell/keys.js.map +1 -0
  225. package/dist/shell/login.d.ts +56 -0
  226. package/dist/shell/login.js +130 -0
  227. package/dist/shell/login.js.map +1 -0
  228. package/dist/shell/oidc.d.ts +62 -0
  229. package/dist/shell/oidc.js +102 -0
  230. package/dist/shell/oidc.js.map +1 -0
  231. package/dist/shell/policy.d.ts +100 -0
  232. package/dist/shell/policy.js +232 -0
  233. package/dist/shell/policy.js.map +1 -0
  234. package/dist/shell/proxy-policy.d.ts +34 -0
  235. package/dist/shell/proxy-policy.js +120 -0
  236. package/dist/shell/proxy-policy.js.map +1 -0
  237. package/dist/shell/proxy.d.ts +47 -0
  238. package/dist/shell/proxy.js +253 -0
  239. package/dist/shell/proxy.js.map +1 -0
  240. package/dist/shell/serve.d.ts +86 -0
  241. package/dist/shell/serve.js +0 -0
  242. package/dist/shell/serve.js.map +1 -0
  243. package/dist/shell/stdio.d.ts +55 -0
  244. package/dist/shell/stdio.js +63 -0
  245. package/dist/shell/stdio.js.map +1 -0
  246. package/dist/storage/registry.d.ts +20 -0
  247. package/dist/storage/registry.js +38 -0
  248. package/dist/storage/registry.js.map +1 -0
  249. package/dist/storage/search-plan.d.ts +93 -0
  250. package/dist/storage/search-plan.js +15 -0
  251. package/dist/storage/search-plan.js.map +1 -0
  252. package/dist/storage/types.d.ts +448 -0
  253. package/dist/storage/types.js +14 -0
  254. package/dist/storage/types.js.map +1 -0
  255. package/dist/storage/versions-since.d.ts +72 -0
  256. package/dist/storage/versions-since.js +92 -0
  257. package/dist/storage/versions-since.js.map +1 -0
  258. package/dist/storage-postgres/adapter.d.ts +17 -0
  259. package/dist/storage-postgres/adapter.js +782 -0
  260. package/dist/storage-postgres/adapter.js.map +1 -0
  261. package/dist/storage-postgres/compile-postgres.d.ts +22 -0
  262. package/dist/storage-postgres/compile-postgres.js +830 -0
  263. package/dist/storage-postgres/compile-postgres.js.map +1 -0
  264. package/dist/storage-postgres/errors.d.ts +34 -0
  265. package/dist/storage-postgres/errors.js +50 -0
  266. package/dist/storage-postgres/errors.js.map +1 -0
  267. package/dist/storage-postgres/migrations/0001_init.sql +113 -0
  268. package/dist/storage-postgres/migrations/0002_content_hash.sql +7 -0
  269. package/dist/storage-postgres/migrations/index.d.ts +22 -0
  270. package/dist/storage-postgres/migrations/index.js +70 -0
  271. package/dist/storage-postgres/migrations/index.js.map +1 -0
  272. package/dist/storage-sqlite/adapter.d.ts +2 -0
  273. package/dist/storage-sqlite/adapter.js +688 -0
  274. package/dist/storage-sqlite/adapter.js.map +1 -0
  275. package/dist/storage-sqlite/compile-filter.d.ts +41 -0
  276. package/dist/storage-sqlite/compile-filter.js +881 -0
  277. package/dist/storage-sqlite/compile-filter.js.map +1 -0
  278. package/dist/storage-sqlite/compile-sqlite.d.ts +19 -0
  279. package/dist/storage-sqlite/compile-sqlite.js +176 -0
  280. package/dist/storage-sqlite/compile-sqlite.js.map +1 -0
  281. package/dist/storage-sqlite/migrations/0001_init.sql +123 -0
  282. package/dist/storage-sqlite/migrations/0002_content_hash.sql +7 -0
  283. package/dist/storage-sqlite/migrations/index.d.ts +6 -0
  284. package/dist/storage-sqlite/migrations/index.js +41 -0
  285. package/dist/storage-sqlite/migrations/index.js.map +1 -0
  286. package/dist/storage-sqlite/vec.d.ts +40 -0
  287. package/dist/storage-sqlite/vec.js +62 -0
  288. package/dist/storage-sqlite/vec.js.map +1 -0
  289. package/dist/sync/cursor.d.ts +36 -0
  290. package/dist/sync/cursor.js +66 -0
  291. package/dist/sync/cursor.js.map +1 -0
  292. package/dist/sync/daemon.d.ts +40 -0
  293. package/dist/sync/daemon.js +202 -0
  294. package/dist/sync/daemon.js.map +1 -0
  295. package/dist/sync/feed.d.ts +38 -0
  296. package/dist/sync/feed.js +125 -0
  297. package/dist/sync/feed.js.map +1 -0
  298. package/dist/sync/fs-store.d.ts +8 -0
  299. package/dist/sync/fs-store.js +76 -0
  300. package/dist/sync/fs-store.js.map +1 -0
  301. package/dist/sync/intrinsics.d.ts +52 -0
  302. package/dist/sync/intrinsics.js +76 -0
  303. package/dist/sync/intrinsics.js.map +1 -0
  304. package/dist/sync/paths.d.ts +38 -0
  305. package/dist/sync/paths.js +56 -0
  306. package/dist/sync/paths.js.map +1 -0
  307. package/dist/sync/push.d.ts +48 -0
  308. package/dist/sync/push.js +328 -0
  309. package/dist/sync/push.js.map +1 -0
  310. package/dist/sync/reconcile.d.ts +54 -0
  311. package/dist/sync/reconcile.js +252 -0
  312. package/dist/sync/reconcile.js.map +1 -0
  313. package/dist/sync/run.d.ts +27 -0
  314. package/dist/sync/run.js +46 -0
  315. package/dist/sync/run.js.map +1 -0
  316. package/dist/version.d.ts +2 -0
  317. package/dist/version.js +5 -0
  318. package/dist/version.js.map +1 -0
  319. package/package.json +80 -0
@@ -0,0 +1,675 @@
1
+ /**
2
+ * Kernel — the only place the read model, error catalog, and slug↔id
3
+ * translation live. Surfaces (CLI, MCP, REST) call these methods and never
4
+ * touch the storage layer directly.
5
+ *
6
+ * No-auth model (design §8, noauth plan): the kernel trusts every caller.
7
+ * Every op takes a uniform `ctx: CallContext` first parameter — a plain
8
+ * caller-supplied value, not a resolved identity. Writes stamp `ctx.author`
9
+ * (default "mrplex"); reads narrow visibility through `ctx.scope` when present.
10
+ *
11
+ * All kernel methods are async (m5-plan WS1). SQLite adapter is
12
+ * internally synchronous so calls resolve on the next microtask; the
13
+ * async signature is honesty about the Postgres adapter and the
14
+ * embedding hook.
15
+ */
16
+ import { randomBytes } from "node:crypto";
17
+ import { backfillRepoLinks } from "../links/backfill.js";
18
+ import { HARDCODED_DEFAULTS as LINK_DEFAULTS, effectiveLinkConfig, parseRepoOverride as parseLinkOverride, validateRepoOverride as validateLinkOverride, } from "../links/link-config.js";
19
+ import { bindDanglingToPath, reindexOutboundLinks } from "../links/maintain.js";
20
+ import { planRepairs } from "../links/repair.js";
21
+ import { findStaleLinks } from "../links/stale.js";
22
+ import { contentHash } from "../markdown/content-hash.js";
23
+ import { globToRegexSource } from "./auth/glob.js";
24
+ import { claimsGrantRead, claimsGrantRepo, normalizeClaims, } from "./auth/scope.js";
25
+ import { normalizeKey } from "./casefold.js";
26
+ import { resolveAuthor } from "./context.js";
27
+ import { deletionPath, pathIsInSystemNamespace } from "./deletion.js";
28
+ import { runDiff } from "./diff.js";
29
+ import { KernelError, docNotFound, forbidden, repoNotFound, versionNotFound } from "./errors.js";
30
+ import { canonicalizeFrontmatter, } from "./frontmatter-input.js";
31
+ import { runGraph } from "./graph.js";
32
+ import { HISTORY_INDEX_DEFAULT_LIMIT, HISTORY_SAFETY_WINDOW_MS, HISTORY_SINCE_DEFAULT_LIMIT, runHistoryIndex, runHistorySince, } from "./history.js";
33
+ import { HARDCODED_DEFAULTS, effectivePathConfig, parseRepoOverride, pathWarning, validateRepoOverride, } from "./path-config.js";
34
+ import { DEFAULT_QUERY_LIMIT, runQuery } from "./query/query.js";
35
+ import { isPathGlobPattern, normalizeExactDocumentPath, validatePath, validateSlug, } from "./validation.js";
36
+ import { decodeVersionId, encodeVersionId } from "./version-id.js";
37
+ /** Hard cap on named paths per `docs.get_many` call (after dedupe). */
38
+ export const GET_MANY_MAX_PATHS = DEFAULT_QUERY_LIMIT;
39
+ export function createKernel(config) {
40
+ const cfg = "storage" in config
41
+ ? config
42
+ : { storage: config };
43
+ const storage = cfg.storage;
44
+ const serverPathConfig = cfg.serverPathConfig ?? HARDCODED_DEFAULTS;
45
+ const serverLinkConfig = cfg.serverLinkConfig ?? LINK_DEFAULTS;
46
+ const onVersionCommitted = cfg.onVersionCommitted;
47
+ const queryEmbed = cfg.queryEmbed;
48
+ function toRepoWire(row) {
49
+ return {
50
+ repo: row.slug,
51
+ path_config: row.path_config ? JSON.parse(row.path_config) : null,
52
+ };
53
+ }
54
+ function toVersionWire(row, repoSlug) {
55
+ return {
56
+ version_id: encodeVersionId(row.id),
57
+ prev_version_id: row.prev_id === null ? null : encodeVersionId(row.prev_id),
58
+ next_version_id: row.next_id === null ? null : encodeVersionId(row.next_id),
59
+ repo: repoSlug,
60
+ path: row.path,
61
+ frontmatter: row.frontmatter,
62
+ frontmatter_raw: row.frontmatter_raw,
63
+ body: row.body,
64
+ author: row.author,
65
+ created_at: row.created_at,
66
+ // Pre-backfill rows (written before migration 0002) carry a null
67
+ // column; compute on the fly during the transition (§2.6).
68
+ content_hash: row.content_hash ?? contentHash(row.frontmatter_raw, row.body),
69
+ };
70
+ }
71
+ function repoEffectiveConfig(repo) {
72
+ return effectivePathConfig(serverPathConfig, parseRepoOverride(repo.path_config));
73
+ }
74
+ function repoEffectiveLinkConfig(repo) {
75
+ return effectiveLinkConfig(serverLinkConfig, parseLinkOverride(repo.link_config));
76
+ }
77
+ /**
78
+ * Normalized read claims for this call, or null when the context supplies no
79
+ * scope (full visibility). Null short-circuits every visibility check.
80
+ */
81
+ function claimsFor(ctx) {
82
+ return ctx.scope === undefined ? null : normalizeClaims(ctx.scope);
83
+ }
84
+ async function resolveRepo(ctx, slug) {
85
+ const row = await storage.repos_by_slug(slug);
86
+ if (!row)
87
+ throw repoNotFound(slug);
88
+ // A read claim that doesn't bind this repo at all hides it entirely — same
89
+ // shape as not-found so out-of-claim leaks no existence information (§8.4).
90
+ const claims = claimsFor(ctx);
91
+ if (claims && !claimsGrantRepo(claims, row.slug))
92
+ throw repoNotFound(slug);
93
+ return row;
94
+ }
95
+ /**
96
+ * Throw `forbidden` when a targeted read (docs.get / get_version / history /
97
+ * diff) names a path outside the call's claims. Absent scope = full access.
98
+ */
99
+ function assertReadable(claims, repoSlug, path) {
100
+ if (claims === null)
101
+ return;
102
+ if (!claimsGrantRead(claims, repoSlug, path))
103
+ throw forbidden();
104
+ }
105
+ function normalizeDocPath(repo, path) {
106
+ return normalizeExactDocumentPath(path, repoEffectiveConfig(repo));
107
+ }
108
+ function dedupeGetManyPaths(paths, repo) {
109
+ if (!Array.isArray(paths) || paths.length === 0) {
110
+ throw new KernelError("filter_invalid", {
111
+ reason: "paths must be a non-empty array of strings",
112
+ });
113
+ }
114
+ const unique = [];
115
+ const seen = new Set();
116
+ for (const p of paths) {
117
+ if (typeof p !== "string") {
118
+ throw new KernelError("filter_invalid", {
119
+ reason: "paths must be a non-empty array of strings",
120
+ });
121
+ }
122
+ const canonical = normalizeDocPath(repo, p);
123
+ const key = normalizeKey(canonical);
124
+ if (!seen.has(key)) {
125
+ seen.add(key);
126
+ unique.push(canonical);
127
+ }
128
+ }
129
+ if (unique.length > GET_MANY_MAX_PATHS) {
130
+ throw new KernelError("payload_too_large", {
131
+ limit: GET_MANY_MAX_PATHS,
132
+ got: unique.length,
133
+ reason: `paths exceeds ${GET_MANY_MAX_PATHS}`,
134
+ });
135
+ }
136
+ return unique;
137
+ }
138
+ function currentPathForStaleError(claims, repoSlug, path) {
139
+ if (claims === null)
140
+ return path;
141
+ return claimsGrantRead(claims, repoSlug, path) ? path : null;
142
+ }
143
+ function canonicalizeOrCarry(input, prev) {
144
+ if (input.frontmatter === undefined && input.frontmatter_raw === undefined && prev) {
145
+ return { frontmatter: prev.frontmatter, frontmatter_raw: prev.frontmatter_raw };
146
+ }
147
+ return canonicalizeFrontmatter(input);
148
+ }
149
+ function slugUniquifier() {
150
+ return randomBytes(3).toString("hex");
151
+ }
152
+ function isSlugSystemNamespaced(slug) {
153
+ return serverPathConfig.system_sigils.some((sigil) => slug.startsWith(sigil));
154
+ }
155
+ function slugCollisionError(slug) {
156
+ return new KernelError("slug_taken", { slug });
157
+ }
158
+ const kernel = {
159
+ repos: {
160
+ async list(ctx, opts) {
161
+ const includeSystem = opts?.include_system ?? false;
162
+ const claims = claimsFor(ctx);
163
+ const rows = await storage.repos_list();
164
+ return rows
165
+ .filter((r) => includeSystem || !isSlugSystemNamespaced(r.slug))
166
+ .filter((r) => claims === null || claimsGrantRepo(claims, r.slug))
167
+ .map(toRepoWire);
168
+ },
169
+ async get(ctx, slug) {
170
+ const repo = await storage.repos_by_slug(slug);
171
+ if (!repo)
172
+ throw repoNotFound(slug);
173
+ const claims = claimsFor(ctx);
174
+ if (claims && !claimsGrantRepo(claims, repo.slug))
175
+ throw repoNotFound(slug);
176
+ return toRepoWire(repo);
177
+ },
178
+ // The five destructive repo ops below intentionally ignore `ctx` (hence
179
+ // `_ctx`): per noauth-plan decision 11, destructive repo ops are ungated —
180
+ // reachable = allowed; the shell gates them by route/tool-name. This is
181
+ // deliberately asymmetric with `list`/`get` above, which DO filter by
182
+ // read claim: a scoped caller can `delete` a repo it can't `list`. That
183
+ // asymmetry is the design, not an oversight — don't "fix" it by threading
184
+ // claims through here.
185
+ async create(_ctx, slug) {
186
+ validateSlug(slug, serverPathConfig);
187
+ if (await storage.repos_by_slug(slug))
188
+ throw slugCollisionError(slug);
189
+ const row = await storage.repos_create({ slug, created_at: new Date().toISOString() });
190
+ return toRepoWire(row);
191
+ },
192
+ async rename(_ctx, slug, new_slug) {
193
+ const repo = await storage.repos_by_slug(slug);
194
+ if (!repo)
195
+ throw repoNotFound(slug);
196
+ if (new_slug === slug)
197
+ return toRepoWire(repo);
198
+ validateSlug(new_slug, serverPathConfig);
199
+ // A folded-equal collision that resolves to THIS repo is a recasing
200
+ // (e.g. "notes" → "Notes"), not a conflict — allow it (§3.5.1).
201
+ const occupant = await storage.repos_by_slug(new_slug);
202
+ if (occupant && occupant.id !== repo.id)
203
+ throw slugCollisionError(new_slug);
204
+ return toRepoWire(await storage.repos_rename(repo.id, new_slug));
205
+ },
206
+ async delete(_ctx, slug) {
207
+ const repo = await storage.repos_by_slug(slug);
208
+ if (!repo)
209
+ throw repoNotFound(slug);
210
+ if (isSlugSystemNamespaced(repo.slug))
211
+ return toRepoWire(repo);
212
+ const sigil = serverPathConfig.system_sigils[0];
213
+ for (let attempt = 0; attempt < 5; attempt++) {
214
+ const newSlug = `${sigil}deleted-${repo.slug}-${slugUniquifier()}`;
215
+ if (await storage.repos_by_slug(newSlug))
216
+ continue;
217
+ return toRepoWire(await storage.repos_rename(repo.id, newSlug));
218
+ }
219
+ throw new Error("repos.delete: uniquifier collision (retries exhausted)");
220
+ },
221
+ async set_path_config(_ctx, slug, config) {
222
+ const repo = await storage.repos_by_slug(slug);
223
+ if (!repo)
224
+ throw repoNotFound(slug);
225
+ if (config !== null) {
226
+ validateRepoOverride(config, serverPathConfig);
227
+ }
228
+ const configJson = config === null ? null : JSON.stringify(config);
229
+ const updated = await storage.repos_set_path_config(repo.id, configJson);
230
+ const newEffective = effectivePathConfig(serverPathConfig, config);
231
+ const warnings = [];
232
+ for (const version of await storage.versions_live_by_repo(repo.id)) {
233
+ if (pathIsInSystemNamespace(version.path, newEffective.system_sigils)) {
234
+ continue;
235
+ }
236
+ try {
237
+ validatePath(version.path, newEffective);
238
+ }
239
+ catch (err) {
240
+ warnings.push(pathWarning(encodeVersionId(version.id), version.path, err));
241
+ }
242
+ }
243
+ return { repo: toRepoWire(updated), warnings };
244
+ },
245
+ async set_link_config(_ctx, slug, config) {
246
+ const repo = await storage.repos_by_slug(slug);
247
+ if (!repo)
248
+ throw repoNotFound(slug);
249
+ if (config !== null) {
250
+ // Validates the merge; throws link_config_invalid on a bad override.
251
+ validateLinkOverride(config, serverLinkConfig);
252
+ }
253
+ const configJson = config === null ? null : JSON.stringify(config);
254
+ const updated = await storage.repos_set_link_config(repo.id, configJson);
255
+ // A config change makes the link index stale for this repo — re-extract
256
+ // the whole corpus under the new effective config now, so the index is
257
+ // immediately consistent (§11.2 "Config change ⇒ re-extraction").
258
+ const reindexed = await backfillRepoLinks(storage, repo.id, effectiveLinkConfig(serverLinkConfig, config));
259
+ return { repo: toRepoWire(updated), reindexed };
260
+ },
261
+ },
262
+ docs: {
263
+ async get(ctx, repoSlug, path) {
264
+ const repo = await resolveRepo(ctx, repoSlug);
265
+ const canonical = normalizeDocPath(repo, path);
266
+ assertReadable(claimsFor(ctx), repo.slug, canonical);
267
+ const row = await storage.version_current(repo.id, canonical);
268
+ if (!row)
269
+ throw docNotFound(repoSlug, canonical);
270
+ return toVersionWire(row, repoSlug);
271
+ },
272
+ async get_many(ctx, repoSlug, paths) {
273
+ const repo = await resolveRepo(ctx, repoSlug);
274
+ const uniquePaths = dedupeGetManyPaths(paths, repo);
275
+ const rows = await storage.versions_current_by_paths(repo.id, uniquePaths);
276
+ const byNorm = new Map(rows.map((r) => [normalizeKey(r.path), r]));
277
+ const claims = claimsFor(ctx);
278
+ const items = [];
279
+ const errors = [];
280
+ for (const path of uniquePaths) {
281
+ if (claims !== null && !claimsGrantRead(claims, repo.slug, path)) {
282
+ errors.push({ path, code: "forbidden", data: {} });
283
+ continue;
284
+ }
285
+ const row = byNorm.get(normalizeKey(path));
286
+ if (!row) {
287
+ errors.push({ path, code: "doc_not_found", data: { repo: repoSlug, path } });
288
+ continue;
289
+ }
290
+ items.push(toVersionWire(row, repoSlug));
291
+ }
292
+ return { items, errors };
293
+ },
294
+ async get_version(ctx, repoSlug, versionId) {
295
+ const repo = await resolveRepo(ctx, repoSlug);
296
+ const id = decodeVersionId(versionId);
297
+ if (id === null)
298
+ throw versionNotFound(versionId);
299
+ const row = await storage.version_by_id(id);
300
+ if (!row || row.repo_id !== repo.id)
301
+ throw versionNotFound(versionId);
302
+ assertReadable(claimsFor(ctx), repo.slug, row.path);
303
+ return toVersionWire(row, repoSlug);
304
+ },
305
+ async history(ctx, repoSlug, path, opts) {
306
+ const repo = await resolveRepo(ctx, repoSlug);
307
+ const canonical = normalizeDocPath(repo, path);
308
+ assertReadable(claimsFor(ctx), repo.slug, canonical);
309
+ const current = await storage.version_current(repo.id, canonical);
310
+ if (!current)
311
+ throw docNotFound(repoSlug, canonical);
312
+ const rows = await storage.version_history(current.document_id, opts);
313
+ return rows.map((r) => toVersionWire(r, repoSlug));
314
+ },
315
+ async diff(ctx, repoSlug, path, fromVersionId, toVersionId) {
316
+ const repo = await resolveRepo(ctx, repoSlug);
317
+ const canonical = normalizeDocPath(repo, path);
318
+ const claims = claimsFor(ctx);
319
+ return runDiff({
320
+ repo: repoSlug,
321
+ path: canonical,
322
+ from_version_id: fromVersionId,
323
+ to_version_id: toVersionId,
324
+ }, {
325
+ storage,
326
+ resolveReadRepo: (slug) => resolveRepo(ctx, slug),
327
+ authorizeReadPath: (repoSlug, p) => assertReadable(claims, repoSlug, p),
328
+ });
329
+ },
330
+ async create(ctx, repoSlug, path, input) {
331
+ const repo = await resolveRepo(ctx, repoSlug);
332
+ const canonical = normalizeDocPath(repo, path);
333
+ const author = resolveAuthor(ctx);
334
+ const canon = canonicalizeFrontmatter(input);
335
+ const { version, insertedId } = await storage.tx(async () => {
336
+ const existing = await storage.version_current(repo.id, canonical);
337
+ if (existing) {
338
+ throw new KernelError("create_conflict", {
339
+ repo: repoSlug,
340
+ path: canonical,
341
+ current_version_id: encodeVersionId(existing.id),
342
+ });
343
+ }
344
+ const doc = await storage.documents_create(repo.id);
345
+ const inserted = await storage.version_insert({
346
+ document_id: doc.id,
347
+ repo_id: repo.id,
348
+ prev_id: null,
349
+ path: canonical,
350
+ frontmatter_raw: canon.frontmatter_raw,
351
+ frontmatter: canon.frontmatter,
352
+ body: input.body,
353
+ author,
354
+ created_at: new Date().toISOString(),
355
+ });
356
+ // Links (§11.2): index this doc's outbound edges, and bind any
357
+ // danglers that were waiting for a document at this path.
358
+ const linkConfig = repoEffectiveLinkConfig(repo);
359
+ await reindexOutboundLinks(storage, linkConfig, repo.id, doc.id, canonical, input.body, canon.frontmatter);
360
+ await bindDanglingToPath(storage, repo.id, canonical, doc.id);
361
+ return {
362
+ version: toVersionWire(inserted, repoSlug),
363
+ insertedId: inserted.id,
364
+ };
365
+ });
366
+ await onVersionCommitted?.(insertedId);
367
+ return version;
368
+ },
369
+ async put(ctx, repoSlug, prevVersionId, destPath, input) {
370
+ const repo = await resolveRepo(ctx, repoSlug);
371
+ const prevId = decodeVersionId(prevVersionId);
372
+ if (prevId === null)
373
+ throw versionNotFound(prevVersionId);
374
+ const prev = await storage.version_by_id(prevId);
375
+ if (!prev || prev.repo_id !== repo.id)
376
+ throw versionNotFound(prevVersionId);
377
+ const canonicalDest = normalizeDocPath(repo, destPath);
378
+ const author = resolveAuthor(ctx);
379
+ const canon = canonicalizeOrCarry(input, prev);
380
+ const body = input.body ?? prev.body;
381
+ const claims = claimsFor(ctx);
382
+ const { version, insertedId } = await storage.tx(async () => {
383
+ const current = await storage.version_current(repo.id, prev.path);
384
+ if (!current || current.id !== prev.id) {
385
+ const docCurrent = current ?? (await storage.version_history(prev.document_id, { limit: 1 }))[0] ?? null;
386
+ throw new KernelError("stale_prev", {
387
+ current_version_id: docCurrent ? encodeVersionId(docCurrent.id) : null,
388
+ current_path: docCurrent
389
+ ? currentPathForStaleError(claims, repo.slug, docCurrent.path)
390
+ : null,
391
+ submitted_prev_version_id: prevVersionId,
392
+ });
393
+ }
394
+ if (canonicalDest !== prev.path) {
395
+ const occupant = await storage.version_current(repo.id, canonicalDest);
396
+ if (occupant && occupant.document_id !== prev.document_id) {
397
+ throw new KernelError("path_taken", {
398
+ repo: repoSlug,
399
+ path: canonicalDest,
400
+ current_version_id: encodeVersionId(occupant.id),
401
+ });
402
+ }
403
+ }
404
+ const inserted = await storage.version_insert({
405
+ document_id: prev.document_id,
406
+ repo_id: repo.id,
407
+ prev_id: prev.id,
408
+ path: canonicalDest,
409
+ frontmatter_raw: canon.frontmatter_raw,
410
+ frontmatter: canon.frontmatter,
411
+ body,
412
+ author,
413
+ created_at: new Date().toISOString(),
414
+ });
415
+ // Links (§11.2): re-extract this doc's outbound edges from the new
416
+ // version (body/frontmatter or the source path may have changed).
417
+ // Inbound edges are identity-bound, so a move needs no inbound
418
+ // churn; but a move INTO a path that has danglers binds them.
419
+ const linkConfig = repoEffectiveLinkConfig(repo);
420
+ await reindexOutboundLinks(storage, linkConfig, repo.id, prev.document_id, canonicalDest, body, canon.frontmatter);
421
+ if (canonicalDest !== prev.path) {
422
+ await bindDanglingToPath(storage, repo.id, canonicalDest, prev.document_id);
423
+ }
424
+ return {
425
+ version: toVersionWire(inserted, repoSlug),
426
+ insertedId: inserted.id,
427
+ };
428
+ });
429
+ await onVersionCommitted?.(insertedId);
430
+ return version;
431
+ },
432
+ async delete(ctx, repoSlug, prevVersionId) {
433
+ const repo = await resolveRepo(ctx, repoSlug);
434
+ const prevId = decodeVersionId(prevVersionId);
435
+ if (prevId === null)
436
+ throw versionNotFound(prevVersionId);
437
+ const prev = await storage.version_by_id(prevId);
438
+ if (!prev || prev.repo_id !== repo.id)
439
+ throw versionNotFound(prevVersionId);
440
+ const cfg = repoEffectiveConfig(repo);
441
+ // Deletion is an authored act by the deleter (noauth plan §1).
442
+ const author = resolveAuthor(ctx);
443
+ const claims = claimsFor(ctx);
444
+ const currentAtPrevPath = await storage.version_current(repo.id, prev.path);
445
+ if (!currentAtPrevPath || currentAtPrevPath.id !== prev.id) {
446
+ const docCurrent = currentAtPrevPath ??
447
+ (await storage.version_history(prev.document_id, { limit: 1 }))[0] ??
448
+ null;
449
+ throw new KernelError("stale_prev", {
450
+ current_version_id: docCurrent ? encodeVersionId(docCurrent.id) : null,
451
+ current_path: docCurrent
452
+ ? currentPathForStaleError(claims, repo.slug, docCurrent.path)
453
+ : null,
454
+ submitted_prev_version_id: prevVersionId,
455
+ });
456
+ }
457
+ if (pathIsInSystemNamespace(prev.path, cfg.system_sigils)) {
458
+ return toVersionWire(prev, repoSlug);
459
+ }
460
+ const destPath = deletionPath(cfg.system_sigils[0], prev.path, encodeVersionId(prev.id));
461
+ const { version, insertedId } = await storage.tx(async () => {
462
+ const current = await storage.version_current(repo.id, prev.path);
463
+ if (!current || current.id !== prev.id) {
464
+ const docCurrent = current ?? (await storage.version_history(prev.document_id, { limit: 1 }))[0] ?? null;
465
+ throw new KernelError("stale_prev", {
466
+ current_version_id: docCurrent ? encodeVersionId(docCurrent.id) : null,
467
+ current_path: docCurrent
468
+ ? currentPathForStaleError(claims, repo.slug, docCurrent.path)
469
+ : null,
470
+ submitted_prev_version_id: prevVersionId,
471
+ });
472
+ }
473
+ const inserted = await storage.version_insert({
474
+ document_id: prev.document_id,
475
+ repo_id: repo.id,
476
+ prev_id: prev.id,
477
+ path: destPath,
478
+ frontmatter_raw: prev.frontmatter_raw,
479
+ frontmatter: prev.frontmatter,
480
+ body: prev.body,
481
+ author,
482
+ created_at: new Date().toISOString(),
483
+ });
484
+ // Links (§11.2): clear the deleted doc's OUTBOUND edges — a doc in
485
+ // the system namespace links to nothing readable. INBOUND edges
486
+ // (target = this doc) stay bound; visibility filtering excludes
487
+ // them from live-namespace queries, and they rebind naturally if
488
+ // the doc is restored.
489
+ await storage.links_clear(prev.document_id);
490
+ return {
491
+ version: toVersionWire(inserted, repoSlug),
492
+ insertedId: inserted.id,
493
+ };
494
+ });
495
+ await onVersionCommitted?.(insertedId);
496
+ return version;
497
+ },
498
+ },
499
+ links: {
500
+ async backfill(ctx, repoSlug) {
501
+ const repo = await resolveRepo(ctx, repoSlug);
502
+ return backfillRepoLinks(storage, repo.id, repoEffectiveLinkConfig(repo));
503
+ },
504
+ async stale(ctx, repoSlug) {
505
+ const repo = await resolveRepo(ctx, repoSlug);
506
+ const rows = await findStaleLinks(storage, repo.id, repoEffectiveLinkConfig(repo));
507
+ // Scope (§11.2 "Scope interaction" — visible graph = readable graph):
508
+ // surface a stale link only when the caller can read BOTH endpoints.
509
+ // The SOURCE path is what we return + would rewrite; the `current`
510
+ // (TARGET) path we'd expose is private if the caller can't read it —
511
+ // dropping the row prevents leaking the moved target's new location.
512
+ const claims = claimsFor(ctx);
513
+ const canRead = (p) => claims === null || claimsGrantRead(claims, repo.slug, p);
514
+ return rows
515
+ .filter((r) => canRead(r.source_path) && canRead(r.current))
516
+ .map((r) => ({
517
+ repo: repoSlug,
518
+ source_path: r.source_path,
519
+ ord: r.ord,
520
+ written: r.written,
521
+ current: r.current,
522
+ }));
523
+ },
524
+ async repair(ctx, repoSlug, opts) {
525
+ const dryRun = opts?.dry_run ?? false;
526
+ const repo = await resolveRepo(ctx, repoSlug);
527
+ const plans = await planRepairs(storage, repo.id, repoEffectiveLinkConfig(repo));
528
+ const repaired = [];
529
+ const skipped = [];
530
+ const claims = claimsFor(ctx);
531
+ for (const plan of plans) {
532
+ // Only repair docs the caller may read (no write scopes in the
533
+ // engine — read claims gate what's touchable, noauth plan §1 "Out").
534
+ if (claims && !claimsGrantRead(claims, repo.slug, plan.version.path)) {
535
+ skipped.push({ path: plan.version.path, reason: "forbidden" });
536
+ continue;
537
+ }
538
+ if (dryRun) {
539
+ repaired.push({ path: plan.version.path, edges: plan.edges });
540
+ continue;
541
+ }
542
+ try {
543
+ // Repair preserves authorship: the original author effectively
544
+ // wrote this content (only stale link text changed), so carry
545
+ // forward prev.author rather than stamping the repair caller
546
+ // (noauth plan decision 7).
547
+ await kernel.docs.put({ ...ctx, author: plan.version.author }, repoSlug, encodeVersionId(plan.version.id), plan.version.path, { body: plan.newBody });
548
+ repaired.push({ path: plan.version.path, edges: plan.edges });
549
+ }
550
+ catch (err) {
551
+ if (err instanceof KernelError && err.code === "stale_prev") {
552
+ skipped.push({ path: plan.version.path, reason: "conflict" });
553
+ }
554
+ else {
555
+ throw err;
556
+ }
557
+ }
558
+ }
559
+ return { dry_run: dryRun, repaired, skipped };
560
+ },
561
+ },
562
+ async query(ctx, spec) {
563
+ return runQuery(claimsFor(ctx), spec, {
564
+ storage,
565
+ serverPathConfig,
566
+ toVersionWire,
567
+ queryEmbed,
568
+ });
569
+ },
570
+ async graph(ctx, spec) {
571
+ const repo = await storage.repos_by_slug(spec.repo);
572
+ const pathConfig = repo === null
573
+ ? serverPathConfig
574
+ : effectivePathConfig(serverPathConfig, parseRepoOverride(repo.path_config));
575
+ const deps = { storage, serverPathConfig, pathConfig };
576
+ return runGraph(claimsFor(ctx), spec, deps);
577
+ },
578
+ history: {
579
+ async since(ctx, input) {
580
+ // Resolve the optional repo filter to an id (and gate its existence
581
+ // through the caller's scope, same as resolveRepo).
582
+ let repoId;
583
+ if (input.repo !== undefined) {
584
+ const repo = await resolveRepo(ctx, input.repo);
585
+ repoId = repo.id;
586
+ }
587
+ // Build id→slug once per call.
588
+ const repos = await storage.repos_list();
589
+ const slugById = new Map(repos.map((r) => [r.id, r.slug]));
590
+ const claims = claimsFor(ctx);
591
+ const deps = {
592
+ storage,
593
+ repoSlug: (id) => slugById.get(id) ?? "",
594
+ // Delete tombstones live under the server's system sigils (`:deleted/`
595
+ // etc.); these are server-wide, so the server config is authoritative.
596
+ isSystemPath: (path) => pathIsInSystemNamespace(path, serverPathConfig.system_sigils),
597
+ canRead: (id, path) => {
598
+ if (claims === null)
599
+ return true;
600
+ const slug = slugById.get(id);
601
+ return slug !== undefined && claimsGrantRead(claims, slug, path);
602
+ },
603
+ };
604
+ return runHistorySince({
605
+ after_version: input.after_version,
606
+ repo_id: repoId,
607
+ limit: input.limit ?? HISTORY_SINCE_DEFAULT_LIMIT,
608
+ now_ms: Date.now(),
609
+ window_ms: HISTORY_SAFETY_WINDOW_MS,
610
+ }, deps);
611
+ },
612
+ async index(ctx, input) {
613
+ const repo = await resolveRepo(ctx, input.repo);
614
+ const cfg = repoEffectiveConfig(repo);
615
+ // Exclude system (`:deleted/`) and hidden (`.`) namespaces, as `query`
616
+ // defaults do — the sync live set is the visible, current corpus.
617
+ const excludedSigils = [...cfg.system_sigils, ...cfg.hidden_sigils];
618
+ const claims = claimsFor(ctx);
619
+ const deps = {
620
+ storage,
621
+ isExcluded: (path) => pathIsInSystemNamespace(path, excludedSigils),
622
+ canRead: (path) => claims === null || claimsGrantRead(claims, repo.slug, path),
623
+ };
624
+ return runHistoryIndex({
625
+ repo_id: repo.id,
626
+ through_version: input.through_version,
627
+ after_version: input.after_version,
628
+ limit: input.limit ?? HISTORY_INDEX_DEFAULT_LIMIT,
629
+ now_ms: Date.now(),
630
+ window_ms: HISTORY_SAFETY_WINDOW_MS,
631
+ }, deps);
632
+ },
633
+ async list(ctx, input) {
634
+ const repo = await resolveRepo(ctx, input.repo);
635
+ const cfg = repoEffectiveConfig(repo);
636
+ const claims = claimsFor(ctx);
637
+ // Compile the path glob to an anchored regex source (the same dialect
638
+ // scope + graph use). Omitted glob = every document in the repo.
639
+ // Literal paths accept one leading `/`; glob operands keep theirs.
640
+ const pathGlob = input.path === undefined
641
+ ? undefined
642
+ : isPathGlobPattern(input.path)
643
+ ? input.path
644
+ : normalizeExactDocumentPath(input.path, cfg);
645
+ const pathRegexes = pathGlob === undefined ? [] : [`^${globToRegexSource(pathGlob)}$`];
646
+ // Reject an unparseable bound rather than coercing to 0 — for `until`
647
+ // that would silently mean `id <= 0` (empty result), inverting intent.
648
+ const decodeBound = (kind, value) => {
649
+ const id = decodeVersionId(value);
650
+ if (id === null) {
651
+ throw new KernelError("version_not_found", { version_id: value, bound: kind });
652
+ }
653
+ return id;
654
+ };
655
+ const rows = await storage.versions_list({
656
+ repo_id: repo.id,
657
+ path_regexes: pathRegexes,
658
+ ever: input.ever ?? false,
659
+ after_id: input.since !== undefined ? decodeBound("since", input.since) : undefined,
660
+ until_id: input.until !== undefined ? decodeBound("until", input.until) : undefined,
661
+ order: input.order ?? "desc",
662
+ limit: input.limit ?? HISTORY_LIST_DEFAULT_LIMIT,
663
+ });
664
+ // Scope: a version is visible only if the caller can read its path.
665
+ return rows
666
+ .filter((r) => claims === null || claimsGrantRead(claims, repo.slug, r.path))
667
+ .map((r) => toVersionWire(r, repo.slug));
668
+ },
669
+ },
670
+ };
671
+ return kernel;
672
+ }
673
+ /** Default `history.list` page size when a caller omits `limit` (§3.5). */
674
+ const HISTORY_LIST_DEFAULT_LIMIT = 100;
675
+ //# sourceMappingURL=kernel.js.map