@agent-native/core-corpus 0.201.0-nightly-20261005153421 → 0.201.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (235) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/.agents/skills/security/SKILL.md +9 -2
  3. package/corpus/templates/analytics/.agents/skills/session-replay/SKILL.md +14 -4
  4. package/corpus/templates/analytics/.agents/skills/storing-data/SKILL.md +12 -7
  5. package/corpus/templates/analytics/CHANGELOG.md +6 -0
  6. package/corpus/templates/analytics/README.md +2 -2
  7. package/corpus/templates/analytics/changelog/2026-10-02-events-with-a-very-long-name-app-or-page-path-no-longer-caus.md +6 -0
  8. package/corpus/templates/analytics/package.json +1 -0
  9. package/corpus/templates/analytics/server/handlers/first-party-analytics.ts +9 -19
  10. package/corpus/templates/analytics/server/handlers/session-replay.ts +34 -47
  11. package/corpus/templates/analytics/server/lib/first-party-analytics-rollups.ts +9 -6
  12. package/corpus/templates/analytics/server/lib/first-party-analytics.ts +57 -36
  13. package/corpus/templates/analytics/server/lib/indexed-text.ts +53 -0
  14. package/corpus/templates/analytics/server/lib/request-errors.ts +73 -0
  15. package/corpus/templates/analytics/server/lib/session-event-index.ts +6 -16
  16. package/corpus/templates/analytics/server/lib/session-replay.ts +12 -2
  17. package/corpus/templates/analytics/server/plugins/auth.ts +2 -1
  18. package/corpus/templates/analytics/server/plugins/otel.ts +6 -0
  19. package/corpus/templates/assets/.agents/skills/security/SKILL.md +9 -2
  20. package/corpus/templates/assets/.agents/skills/storing-data/SKILL.md +12 -7
  21. package/corpus/templates/assets/CHANGELOG.md +7 -0
  22. package/corpus/templates/assets/README.md +2 -2
  23. package/corpus/templates/assets/package.json +1 -0
  24. package/corpus/templates/assets/server/plugins/auth.ts +2 -1
  25. package/corpus/templates/assets/server/plugins/otel.ts +6 -0
  26. package/corpus/templates/brain/.agents/skills/brain/RUNBOOK.md +2 -1
  27. package/corpus/templates/brain/.agents/skills/brain/SKILL.md +5 -4
  28. package/corpus/templates/brain/.agents/skills/security/SKILL.md +9 -2
  29. package/corpus/templates/brain/.agents/skills/storing-data/SKILL.md +12 -7
  30. package/corpus/templates/brain/CHANGELOG.md +6 -0
  31. package/corpus/templates/brain/README.md +12 -8
  32. package/corpus/templates/brain/changelog/2026-10-05-private-slack-channels-are-searchable-org-wide.md +6 -0
  33. package/corpus/templates/brain/package.json +1 -0
  34. package/corpus/templates/brain/server/lib/audiences.ts +1 -98
  35. package/corpus/templates/brain/server/lib/connectors.ts +2 -166
  36. package/corpus/templates/brain/server/plugins/otel.ts +6 -0
  37. package/corpus/templates/calendar/.agents/skills/security/SKILL.md +9 -2
  38. package/corpus/templates/calendar/.agents/skills/storing-data/SKILL.md +12 -7
  39. package/corpus/templates/calendar/CHANGELOG.md +7 -0
  40. package/corpus/templates/calendar/README.md +2 -2
  41. package/corpus/templates/calendar/package.json +1 -0
  42. package/corpus/templates/calendar/server/plugins/auth.ts +2 -1
  43. package/corpus/templates/calendar/server/plugins/otel.ts +6 -0
  44. package/corpus/templates/chat/.agents/skills/security/SKILL.md +9 -2
  45. package/corpus/templates/chat/.agents/skills/storing-data/SKILL.md +12 -7
  46. package/corpus/templates/chat/README.md +2 -2
  47. package/corpus/templates/clips/.agents/skills/ai-video-tools/SKILL.md +9 -0
  48. package/corpus/templates/clips/.agents/skills/security/SKILL.md +9 -2
  49. package/corpus/templates/clips/CHANGELOG.md +24 -0
  50. package/corpus/templates/clips/README.md +2 -2
  51. package/corpus/templates/clips/actions/lib/audio-chunk-list.ts +23 -0
  52. package/corpus/templates/clips/actions/lib/audio-only-transcription.ts +187 -292
  53. package/corpus/templates/clips/actions/lib/chunked-transcription.ts +151 -0
  54. package/corpus/templates/clips/actions/lib/recording-audio-source.ts +191 -0
  55. package/corpus/templates/clips/actions/request-transcript.ts +81 -135
  56. package/corpus/templates/clips/actions/search-recordings.ts +19 -5
  57. package/corpus/templates/clips/app/components/clips-command-menu.tsx +5 -0
  58. package/corpus/templates/clips/app/components/library/library-layout.tsx +3 -1
  59. package/corpus/templates/clips/app/components/library/search-bar.tsx +5 -0
  60. package/corpus/templates/clips/app/hooks/use-library.ts +1 -0
  61. package/corpus/templates/clips/app/lib/local-recording-upload.ts +13 -6
  62. package/corpus/templates/clips/app/lib/recording-section.ts +1 -1
  63. package/corpus/templates/clips/app/routes/_app.r.$recordingId.tsx +7 -5
  64. package/corpus/templates/clips/app/routes/record.tsx +16 -6
  65. package/corpus/templates/clips/changelog/2026-10-02-long-uploads-an-hour-or-more-now-transcribe-reliably-instead.md +6 -0
  66. package/corpus/templates/clips/changelog/2026-10-05-confirming-discard-recording-now-returns-you-to-your-library.md +6 -0
  67. package/corpus/templates/clips/changelog/2026-10-05-discarding-a-recording-now-removes-its-failed-upload-card-fr.md +6 -0
  68. package/corpus/templates/clips/changelog/2026-10-05-discarding-a-recovered-recording-no-longer-warns-that-it-s-o.md +6 -0
  69. package/corpus/templates/clips/changelog/2026-10-05-search-results-identify-recordings-in-trash-and-open-them-wi.md +6 -0
  70. package/corpus/templates/clips/package.json +1 -0
  71. package/corpus/templates/clips/server/lib/builder-cdn.ts +5 -0
  72. package/corpus/templates/clips/server/lib/builder-media-compression.ts +2 -2
  73. package/corpus/templates/clips/server/plugins/auth.ts +2 -1
  74. package/corpus/templates/clips/server/plugins/otel.ts +6 -0
  75. package/corpus/templates/clips/shared/transcript-failure.ts +5 -0
  76. package/corpus/templates/content/.agents/skills/document-editing/references/databases.md +8 -2
  77. package/corpus/templates/content/.agents/skills/security/SKILL.md +9 -2
  78. package/corpus/templates/content/.agents/skills/storing-data/SKILL.md +12 -7
  79. package/corpus/templates/content/CHANGELOG.md +41 -0
  80. package/corpus/templates/content/README.md +2 -2
  81. package/corpus/templates/content/actions/_database-navigation.ts +523 -344
  82. package/corpus/templates/content/actions/_database-row-mutation.ts +44 -10
  83. package/corpus/templates/content/actions/_database-setup-discovery.ts +9 -3
  84. package/corpus/templates/content/actions/_database-utils.ts +299 -271
  85. package/corpus/templates/content/actions/_property-utils.ts +293 -190
  86. package/corpus/templates/content/actions/_relation-values.ts +3 -1
  87. package/corpus/templates/content/actions/get-content-database.ts +1 -0
  88. package/corpus/templates/content/actions/query-content-database-items.ts +32 -5
  89. package/corpus/templates/content/actions/set-document-property.ts +17 -11
  90. package/corpus/templates/content/app/components/editor/DocumentBlockFields.tsx +21 -6
  91. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +127 -43
  92. package/corpus/templates/content/app/components/editor/DocumentEditorSkeleton.tsx +159 -50
  93. package/corpus/templates/content/app/components/editor/DocumentToolbar.tsx +12 -28
  94. package/corpus/templates/content/app/components/editor/PageDraftRecovery.tsx +6 -1
  95. package/corpus/templates/content/app/components/editor/TableHoverControls.tsx +139 -70
  96. package/corpus/templates/content/app/components/editor/VisualEditor.tsx +16 -1
  97. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +180 -75
  98. package/corpus/templates/content/app/components/editor/database/DatabaseViewSkeleton.tsx +76 -0
  99. package/corpus/templates/content/app/components/editor/database/navigation-state.ts +60 -2
  100. package/corpus/templates/content/app/components/editor/database/sidebar.tsx +41 -42
  101. package/corpus/templates/content/app/components/editor/document-editor-layout.ts +18 -0
  102. package/corpus/templates/content/app/components/editor/extensions/NotionExtensions.tsx +11 -3
  103. package/corpus/templates/content/app/components/editor/suggestions/draft-session.ts +15 -7
  104. package/corpus/templates/content/app/components/editor/suggestions/model.ts +13 -0
  105. package/corpus/templates/content/app/components/editor/suggestions/read-only-blocks.ts +136 -0
  106. package/corpus/templates/content/app/components/editor/table-view.ts +73 -0
  107. package/corpus/templates/content/app/components/layout/ContentStartupShell.tsx +1 -1
  108. package/corpus/templates/content/app/components/layout/Layout.tsx +5 -1
  109. package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +57 -16
  110. package/corpus/templates/content/app/components/sidebar/MovePageDialog.tsx +4 -4
  111. package/corpus/templates/content/app/components/sidebar/SidebarNavigationRow.tsx +34 -10
  112. package/corpus/templates/content/app/global.css +16 -8
  113. package/corpus/templates/content/app/hooks/content-action-refresh.ts +14 -13
  114. package/corpus/templates/content/app/hooks/use-content-database.ts +72 -9
  115. package/corpus/templates/content/app/hooks/use-content-personal-navigation.ts +7 -3
  116. package/corpus/templates/content/app/hooks/use-documents.ts +27 -0
  117. package/corpus/templates/content/app/i18n-data.ts +3 -2
  118. package/corpus/templates/content/app/lib/content-landing.ts +3 -31
  119. package/corpus/templates/content/app/lib/files-navigation.ts +182 -10
  120. package/corpus/templates/content/app/lib/files-root-hint.ts +20 -32
  121. package/corpus/templates/content/app/lib/page-startup-hints.ts +108 -0
  122. package/corpus/templates/content/app/lib/private-link-redirect.ts +25 -0
  123. package/corpus/templates/content/app/lib/sidebar-layout-hint.ts +34 -1
  124. package/corpus/templates/content/app/lib/startup-timing.ts +2 -0
  125. package/corpus/templates/content/app/root.tsx +3 -1
  126. package/corpus/templates/content/app/routes/_app.home.tsx +10 -19
  127. package/corpus/templates/content/app/routes/p.$id.tsx +23 -5
  128. package/corpus/templates/content/changelog/2026-10-02-a-page-link-you-cant-open-now-says-so-instead-of-opening-an.md +6 -0
  129. package/corpus/templates/content/changelog/2026-10-02-after-a-refresh-the-files-sidebar-shows-the-open-page-s-fold.md +6 -0
  130. package/corpus/templates/content/changelog/2026-10-02-large-collections-open-faster.md +6 -0
  131. package/corpus/templates/content/changelog/2026-10-02-pages-stay-inside-the-window-at-every-width-long-inline-code.md +6 -0
  132. package/corpus/templates/content/changelog/2026-10-02-pages-with-open-comments-and-collections-with-sorted-or-filt.md +6 -0
  133. package/corpus/templates/content/changelog/2026-10-02-suggest-edits-works-on-pages-written-by-agents-including-pag.md +6 -0
  134. package/corpus/templates/content/changelog/2026-10-02-while-you-suggest-edits-tables-and-other-blocks-that-suggest.md +6 -0
  135. package/corpus/templates/content/changelog/2026-10-05-collection-view-context-now-survives-quick-page-changes-when.md +6 -0
  136. package/corpus/templates/content/changelog/2026-10-05-collection-views-keep-their-saved-rows-accurate-during-refre.md +6 -0
  137. package/corpus/templates/content/changelog/2026-10-05-exact-saved-view-links-keep-collection-retry-available.md +6 -0
  138. package/corpus/templates/content/changelog/2026-10-05-expanding-a-files-folder-now-shares-one-navigation-read-acro.md +6 -0
  139. package/corpus/templates/content/changelog/2026-10-05-files-navigation-retries-temporary-failures-consistently.md +6 -0
  140. package/corpus/templates/content/changelog/2026-10-05-shared-page-owners-keep-edit-and-manage-controls-in-files-an.md +6 -0
  141. package/corpus/templates/content/changelog/2026-10-05-tagging-an-ai-in-a-comment-works-again-instead-of-failing-ri.md +6 -0
  142. package/corpus/templates/content/changelog/2026-10-05-the-home-loading-shell-keeps-the-layout-of-your-last-opened-.md +6 -0
  143. package/corpus/templates/content/changelog/2026-10-05-typing-and-a-space-or-inserting-a-toggle-or-callout-no-longe.md +6 -0
  144. package/corpus/templates/content/docs/product/capabilities/content.workspace.session-restore.md +4 -1
  145. package/corpus/templates/content/e2e/playwright.config.ts +1 -1
  146. package/corpus/templates/content/package.json +2 -1
  147. package/corpus/templates/content/scripts/seed-perf-database.mjs +600 -0
  148. package/corpus/templates/content/scripts/trace-startup.mjs +25 -3
  149. package/corpus/templates/content/server/db/index.ts +1 -1
  150. package/corpus/templates/content/server/lib/suggested-edits.ts +23 -26
  151. package/corpus/templates/content/server/plugins/auth.ts +2 -1
  152. package/corpus/templates/content/server/plugins/otel.ts +6 -0
  153. package/corpus/templates/content/shared/api.ts +17 -0
  154. package/corpus/templates/content/shared/suggestion-diff.ts +124 -38
  155. package/corpus/templates/content/shared/suggestion-formatting.ts +134 -3
  156. package/corpus/templates/crm/.agents/skills/security/SKILL.md +9 -2
  157. package/corpus/templates/crm/.agents/skills/storing-data/SKILL.md +12 -7
  158. package/corpus/templates/crm/package.json +1 -0
  159. package/corpus/templates/crm/server/plugins/otel.ts +6 -0
  160. package/corpus/templates/design/.agents/skills/security/SKILL.md +9 -2
  161. package/corpus/templates/design/.agents/skills/storing-data/SKILL.md +12 -7
  162. package/corpus/templates/design/CHANGELOG.md +41 -0
  163. package/corpus/templates/design/README.md +2 -2
  164. package/corpus/templates/design/actions/list-designs.ts +11 -4
  165. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +110 -32
  166. package/corpus/templates/design/app/components/design/multi-screen/cross-screen-drop.ts +28 -0
  167. package/corpus/templates/design/app/components/design/multi-screen/types.ts +1 -0
  168. package/corpus/templates/design/app/components/home/DesignHomeLibrary.tsx +79 -0
  169. package/corpus/templates/design/app/components/layout/Header.tsx +2 -4
  170. package/corpus/templates/design/app/components/ui/tabs.tsx +59 -1
  171. package/corpus/templates/design/app/global.css +1 -34
  172. package/corpus/templates/design/app/i18n/en-US.ts +4 -0
  173. package/corpus/templates/design/app/lib/design-filter.ts +4 -2
  174. package/corpus/templates/design/app/pages/Index.tsx +91 -59
  175. package/corpus/templates/design/app/pages/design-editor/commands/cross-screen-element-drop.ts +58 -17
  176. package/corpus/templates/design/changelog/2026-10-01-filter-designs-by-owner.md +6 -0
  177. package/corpus/templates/design/changelog/2026-10-03-layers-dropped-into-visible-canvas-space-beyond-a-screen-now.md +6 -0
  178. package/corpus/templates/design/changelog/2026-10-03-visible-screen-overflow-no-longer-routes-drops-to-the-canvas.md +6 -0
  179. package/corpus/templates/design/changelog/2026-10-05-the-recent-tab-shows-loading-placeholders-until-your-designs.md +6 -0
  180. package/corpus/templates/design/package.json +1 -0
  181. package/corpus/templates/design/server/plugins/auth.ts +2 -1
  182. package/corpus/templates/design/server/plugins/otel.ts +6 -0
  183. package/corpus/templates/dispatch/.agents/skills/security/SKILL.md +9 -2
  184. package/corpus/templates/dispatch/.agents/skills/storing-data/SKILL.md +12 -7
  185. package/corpus/templates/dispatch/README.md +2 -2
  186. package/corpus/templates/dispatch/package.json +1 -0
  187. package/corpus/templates/dispatch/server/plugins/otel.ts +6 -0
  188. package/corpus/templates/dispatch/server/plugins/setup-dispatch.ts +2 -1
  189. package/corpus/templates/factory/.agents/skills/security/SKILL.md +9 -2
  190. package/corpus/templates/factory/.agents/skills/storing-data/SKILL.md +12 -7
  191. package/corpus/templates/factory/package.json +1 -0
  192. package/corpus/templates/factory/server/plugins/otel.ts +6 -0
  193. package/corpus/templates/forms/.agents/skills/security/SKILL.md +9 -2
  194. package/corpus/templates/forms/.agents/skills/storing-data/SKILL.md +12 -7
  195. package/corpus/templates/forms/README.md +2 -2
  196. package/corpus/templates/forms/package.json +1 -0
  197. package/corpus/templates/forms/server/plugins/auth.ts +2 -1
  198. package/corpus/templates/forms/server/plugins/otel.ts +6 -0
  199. package/corpus/templates/mail/.agents/skills/security/SKILL.md +9 -2
  200. package/corpus/templates/mail/.agents/skills/storing-data/SKILL.md +12 -7
  201. package/corpus/templates/mail/CHANGELOG.md +6 -0
  202. package/corpus/templates/mail/README.md +2 -2
  203. package/corpus/templates/mail/package.json +1 -0
  204. package/corpus/templates/mail/server/plugins/auth.ts +2 -1
  205. package/corpus/templates/mail/server/plugins/otel.ts +6 -0
  206. package/corpus/templates/plan/.agents/skills/security/SKILL.md +9 -2
  207. package/corpus/templates/plan/.agents/skills/storing-data/SKILL.md +12 -7
  208. package/corpus/templates/plan/CHANGELOG.md +1 -0
  209. package/corpus/templates/plan/README.md +2 -2
  210. package/corpus/templates/plan/package.json +1 -0
  211. package/corpus/templates/plan/server/plugins/auth.ts +2 -1
  212. package/corpus/templates/plan/server/plugins/otel.ts +6 -0
  213. package/corpus/templates/slides/.agents/skills/security/SKILL.md +9 -2
  214. package/corpus/templates/slides/.agents/skills/storing-data/SKILL.md +12 -7
  215. package/corpus/templates/slides/CHANGELOG.md +23 -0
  216. package/corpus/templates/slides/README.md +2 -2
  217. package/corpus/templates/slides/actions/list-decks.ts +11 -4
  218. package/corpus/templates/slides/app/components/deck/DeckFilterMenu.tsx +44 -36
  219. package/corpus/templates/slides/app/components/home/SlidesHomeLibrary.tsx +79 -0
  220. package/corpus/templates/slides/app/components/layout/Header.tsx +2 -15
  221. package/corpus/templates/slides/app/components/ui/tabs.tsx +59 -0
  222. package/corpus/templates/slides/app/global.css +3 -54
  223. package/corpus/templates/slides/app/i18n/en-US.ts +4 -0
  224. package/corpus/templates/slides/app/lib/deck-filter.ts +5 -2
  225. package/corpus/templates/slides/app/pages/Index.tsx +102 -88
  226. package/corpus/templates/slides/changelog/2026-10-01-filter-decks-by-owner.md +6 -0
  227. package/corpus/templates/slides/changelog/2026-10-02-hide-recent-until-decks-available.md +6 -0
  228. package/corpus/templates/slides/changelog/2026-10-05-the-recent-tab-shows-loading-placeholders-until-your-decks-a.md +6 -0
  229. package/corpus/templates/slides/package.json +1 -0
  230. package/corpus/templates/slides/server/handlers/request-auth-context.ts +8 -0
  231. package/corpus/templates/slides/server/plugins/auth.ts +2 -1
  232. package/corpus/templates/slides/server/plugins/otel.ts +6 -0
  233. package/corpus/templates/tasks/.agents/skills/security/SKILL.md +9 -2
  234. package/corpus/templates/tasks/.agents/skills/storing-data/SKILL.md +12 -7
  235. package/package.json +1 -1
@@ -28,8 +28,8 @@ import {
28
28
  import { hasSuggestionBodyTarget } from "../../actions/_suggestion-eligibility.js";
29
29
  import { commentIdForIdempotency } from "../../actions/add-comment.js";
30
30
  import {
31
- SUPPORTED_SUGGESTION_BLOCKS,
32
31
  SUPPORTED_SUGGESTION_MARKS,
32
+ supportsSuggestionNode,
33
33
  } from "../../app/components/editor/suggestions/model.js";
34
34
  import { createContentEditorStructuralSchema } from "../../shared/content-editor-structural-schema.js";
35
35
  import { mergeDocumentBodyIntents } from "../../shared/document-intent-merge.js";
@@ -215,8 +215,6 @@ type SuggestionDocumentJson = {
215
215
  content?: SuggestionDocumentJson[];
216
216
  };
217
217
 
218
- const SUPPORTED_SUGGESTION_INLINE_NODES = new Set(["hardBreak"]);
219
-
220
218
  function unsupportedNotionSpanAttrs(
221
219
  attrs: Record<string, unknown> | undefined,
222
220
  ) {
@@ -280,11 +278,7 @@ function unsupportedSuggestionStructure(
280
278
  }
281
279
  return result;
282
280
  }
283
- if (
284
- node.type !== "doc" &&
285
- !SUPPORTED_SUGGESTION_INLINE_NODES.has(node.type ?? "") &&
286
- !SUPPORTED_SUGGESTION_BLOCKS.has(node.type ?? "")
287
- ) {
281
+ if (!supportsSuggestionNode(node.type ?? "")) {
288
282
  result.push({ path, node });
289
283
  return result;
290
284
  }
@@ -325,6 +319,7 @@ function unsupportedStructureKey(markdown: string): string {
325
319
  function validateSuggestionStructure(
326
320
  beforeMarkdown: string,
327
321
  afterMarkdown: string,
322
+ refusal: string,
328
323
  ) {
329
324
  const after = parseSuggestionMarkdown(afterMarkdown);
330
325
  const surround = unsupportedStructureKey(
@@ -332,19 +327,14 @@ function validateSuggestionStructure(
332
327
  );
333
328
  if (
334
329
  unsupportedStructureKey(beforeMarkdown) !== surround ||
335
- unsupportedStructureKey(afterMarkdown) !== surround
336
- ) {
337
- throw new Error(
338
- "Content v1 suggestions cannot add or change unsupported structures",
339
- );
340
- }
341
- if (
330
+ unsupportedStructureKey(afterMarkdown) !== surround ||
342
331
  JSON.stringify(unsupportedRawNotionSpanAttrs(beforeMarkdown)) !==
343
- JSON.stringify(unsupportedRawNotionSpanAttrs(afterMarkdown))
332
+ JSON.stringify(unsupportedRawNotionSpanAttrs(afterMarkdown))
344
333
  ) {
345
- throw new Error(
346
- "Content v1 suggestions cannot add or change unsupported structures",
347
- );
334
+ fail(refusal, {
335
+ statusCode: 422,
336
+ errorCode: "suggestion_structure_unsupported",
337
+ });
348
338
  }
349
339
  return after;
350
340
  }
@@ -579,11 +569,16 @@ export const contentDocumentSuggestionAdapter: SuggestionAdapter = {
579
569
  );
580
570
  }
581
571
  if (before.markdown.includes("<InlineDatabase")) {
582
- throw new Error(
583
- "Pages with inline databases cannot receive suggestions yet",
584
- );
572
+ fail("Pages with inline databases cannot receive suggestions yet.", {
573
+ statusCode: 409,
574
+ errorCode: "suggestion_body_unavailable",
575
+ });
585
576
  }
586
- validateSuggestionStructure(before.markdown, after.markdown);
577
+ validateSuggestionStructure(
578
+ before.markdown,
579
+ after.markdown,
580
+ "Suggestions cannot change tables, images, or other content or formatting they do not support yet. Suggest changes to the surrounding text instead.",
581
+ );
587
582
  return operations;
588
583
  },
589
584
  async coordinateDecision(context, run) {
@@ -675,11 +670,13 @@ export const contentDocumentSuggestionAdapter: SuggestionAdapter = {
675
670
  const nextDocument = validateSuggestionStructure(
676
671
  currentContent,
677
672
  nextContent,
673
+ "This suggestion changes a table, image, or other content or formatting that suggestions do not support yet, so it cannot be accepted.",
678
674
  );
679
675
  if (currentContent.includes("<InlineDatabase")) {
680
- throw new Error(
681
- "Pages containing inline databases cannot accept suggestions yet",
682
- );
676
+ fail("Pages containing inline databases cannot accept suggestions yet.", {
677
+ statusCode: 409,
678
+ errorCode: "suggestion_body_unavailable",
679
+ });
683
680
  }
684
681
  const eligiblePrimaryIds = await assertSuggestionBodyTarget(
685
682
  tx,
@@ -6,7 +6,8 @@ export default createAuthPlugin({
6
6
  workspaceAppPublicPaths: ["/"],
7
7
  marketing: {
8
8
  appName: "Content",
9
- learnMoreUrl: "https://agent-native.com/apps/content",
9
+ learnMoreUrl:
10
+ "https://agent-native.com/apps/content?utm_source=app&utm_medium=product&utm_content=onboarding-learn-more",
10
11
  tagline:
11
12
  "Open-source Obsidian for MDX: your AI agent edits local docs, creates custom blocks, and organizes everything alongside you.",
12
13
  features: [
@@ -0,0 +1,6 @@
1
+ import { defineNitroPlugin } from "@agent-native/core/server";
2
+ import { startAgentNativeOtel } from "@agent-native/otel";
3
+
4
+ export default defineNitroPlugin(() => {
5
+ startAgentNativeOtel();
6
+ });
@@ -1077,8 +1077,25 @@ export interface ContentDatabaseNavigationPageResponse {
1077
1077
  hasMore: boolean;
1078
1078
  nextCursor: string | null;
1079
1079
  };
1080
+ /**
1081
+ * First pages of the requested expanded folders, by parent document ID. A
1082
+ * folder with no children the caller can see is left out.
1083
+ */
1084
+ branches?: Record<string, ContentDatabaseNavigationPage>;
1085
+ /**
1086
+ * Present when the read asked for expanded folders: true when the cap on
1087
+ * branches per read left out folders it could have opened, which then read
1088
+ * their own pages.
1089
+ */
1090
+ branchesTruncated?: boolean;
1080
1091
  }
1081
1092
 
1093
+ /** One Files navigation page on its own, without expanded folders. */
1094
+ export type ContentDatabaseNavigationPage = Omit<
1095
+ ContentDatabaseNavigationPageResponse,
1096
+ "branches" | "branchesTruncated"
1097
+ >;
1098
+
1082
1099
  export interface BuilderActionTiming {
1083
1100
  name: string;
1084
1101
  durationMs: number;
@@ -2,7 +2,9 @@ import { canonicalizeNfm } from "./nfm.js";
2
2
  import {
3
3
  suggestionFormattingChanges,
4
4
  suggestionMarkedSourceRanges,
5
+ suggestionSourceAlignment,
5
6
  SuggestionFormattingMappingError,
7
+ type SuggestionSourceAlignment,
6
8
  } from "./suggestion-formatting.js";
7
9
  import { resolveMarkdownSuggestionRange } from "./suggestion-rebase.js";
8
10
 
@@ -838,8 +840,13 @@ export function markdownSuggestionOperationsForEditorRevision(input: {
838
840
  },
839
841
  ];
840
842
  }
841
- const editorBefore = canonicalizeNfm(input.before);
843
+ const alignment = suggestionSourceAlignment(input.before);
844
+ const editorBefore = alignment
845
+ ? alignment.canonical
846
+ : canonicalizeNfm(input.before);
842
847
  const replacements = input.replacements.map(({ from, to }) => {
848
+ const aligned = alignment && alignedRange(alignment, from, to, "stored");
849
+ if (aligned) return aligned;
843
850
  const changedText = input.before.slice(from, to);
844
851
  const range = resolveMarkdownSuggestionRange(editorBefore, {
845
852
  before: { markdown: input.before, changedText },
@@ -854,47 +861,142 @@ export function markdownSuggestionOperationsForEditorRevision(input: {
854
861
  if (!range) throw new SuggestionFormattingMappingError();
855
862
  return range;
856
863
  });
864
+ const operations = markdownSuggestionOperationsForReplacements({
865
+ before: editorBefore,
866
+ after: input.after,
867
+ replacements,
868
+ });
857
869
  return isolateSiblingAnchorContexts(
858
- markdownSuggestionOperationsForReplacements({
859
- before: editorBefore,
860
- after: input.after,
861
- replacements,
862
- }).map((operation, ordinal) => {
863
- const range = resolveMarkdownSuggestionRange(input.before, operation);
864
- if (!range) throw new SuggestionFormattingMappingError();
865
- return operationForChange(
870
+ (alignment &&
871
+ alignedStoredOperations(
866
872
  input.before,
873
+ input.after,
874
+ operations,
875
+ alignment,
876
+ )) ??
877
+ operations.map((operation, ordinal) => {
878
+ const range = resolveMarkdownSuggestionRange(input.before, operation);
879
+ if (!range) throw new SuggestionFormattingMappingError();
880
+ return operationForChange(
881
+ input.before,
882
+ range.from,
883
+ range.to,
884
+ operation.after.changedText,
885
+ ordinal,
886
+ );
887
+ }),
888
+ );
889
+ }
890
+
891
+ function alignedRange(
892
+ alignment: SuggestionSourceAlignment,
893
+ from: number,
894
+ to: number,
895
+ side: "canonical" | "stored",
896
+ ) {
897
+ const alignedFrom = alignment.map(from, side);
898
+ const alignedTo = alignment.map(to, side);
899
+ return alignedFrom === null || alignedTo === null
900
+ ? null
901
+ : { from: alignedFrom, to: alignedTo };
902
+ }
903
+
904
+ function alignedStoredOperations(
905
+ before: string,
906
+ after: string,
907
+ operations: MarkdownSuggestionOperation[],
908
+ alignment: SuggestionSourceAlignment,
909
+ ): MarkdownSuggestionOperation[] | null {
910
+ const stored: MarkdownSuggestionOperation[] = [];
911
+ for (const operation of operations) {
912
+ const range = alignedRange(
913
+ alignment,
914
+ operation.anchor.from,
915
+ operation.anchor.to,
916
+ "canonical",
917
+ );
918
+ if (!range) return null;
919
+ stored.push(
920
+ operationForChange(
921
+ before,
867
922
  range.from,
868
923
  range.to,
869
924
  operation.after.changedText,
870
- ordinal,
871
- );
872
- }),
873
- );
925
+ stored.length,
926
+ ),
927
+ );
928
+ }
929
+ let reconstructed = before;
930
+ for (const operation of [...stored].reverse())
931
+ reconstructed =
932
+ reconstructed.slice(0, operation.anchor.from) +
933
+ operation.after.changedText +
934
+ reconstructed.slice(operation.anchor.to);
935
+ // An edit spanning a gap writes the editor's syntax for it, which a stored
936
+ // pipe table cannot hold; only a reparse shows the page still matches.
937
+ return reconstructed === after ||
938
+ canonicalizeNfm(reconstructed) === canonicalizeNfm(after)
939
+ ? stored
940
+ : null;
941
+ }
942
+
943
+ // Replays the operations onto the base's canonical form; the result must be
944
+ // the draft byte for byte, so every anchor lands where the editor shows it.
945
+ function alignedDraftAnchors(
946
+ operations: readonly MarkdownSuggestionOperation[],
947
+ draft: string,
948
+ ): MarkdownSuggestionOperation["anchor"][] | null {
949
+ const alignment = suggestionSourceAlignment(operations[0]!.before.markdown);
950
+ if (!alignment) return null;
951
+ const ranges: Array<{ from: number; to: number }> = [];
952
+ let reconstructed = "";
953
+ let cursor = 0;
954
+ for (const operation of operations) {
955
+ const range = alignedRange(
956
+ alignment,
957
+ operation.anchor.from,
958
+ operation.anchor.to,
959
+ "stored",
960
+ );
961
+ if (!range || range.from < cursor) return null;
962
+ reconstructed += alignment.canonical.slice(cursor, range.from);
963
+ ranges.push({
964
+ from: reconstructed.length,
965
+ to: reconstructed.length + operation.after.changedText.length,
966
+ });
967
+ reconstructed += operation.after.changedText;
968
+ cursor = range.to;
969
+ }
970
+ if (reconstructed + alignment.canonical.slice(cursor) !== draft) return null;
971
+ return ranges.map(({ from, to }) => ({
972
+ from,
973
+ to,
974
+ prefix: draft.slice(Math.max(0, from - 32), from),
975
+ suffix: draft.slice(to, to + 32),
976
+ }));
874
977
  }
875
978
 
876
979
  export function draftSuggestionAnchors(
877
980
  operations: readonly MarkdownSuggestionOperation[],
878
981
  draft: string,
879
982
  ): MarkdownSuggestionOperation["anchor"][] {
880
- const canonical = operations.every(
881
- (operation) =>
882
- canonicalizeNfm(operation.after.markdown) === operation.after.markdown,
883
- );
884
- let proposedRaw = operations[0]?.before.markdown ?? draft;
983
+ if (operations.length === 0) return [];
984
+ const aligned = alignedDraftAnchors(operations, draft);
985
+ if (aligned) return aligned;
986
+ let proposedRaw = operations[0]!.before.markdown;
885
987
  for (const operation of [...operations].reverse()) {
886
988
  proposedRaw =
887
989
  proposedRaw.slice(0, operation.anchor.from) +
888
990
  operation.after.changedText +
889
991
  proposedRaw.slice(operation.anchor.to);
890
992
  }
891
- const parts = canonical ? null : diffParts(proposedRaw, draft);
892
- if (!canonical && !parts) throw new SuggestionFormattingMappingError();
993
+ const parts = diffParts(proposedRaw, draft);
994
+ if (!parts) throw new SuggestionFormattingMappingError();
893
995
  const boundaryMap = new Array<number>(proposedRaw.length + 1);
894
996
  let rawOffset = 0;
895
997
  let draftOffset = 0;
896
998
  boundaryMap[0] = 0;
897
- for (const part of parts ?? []) {
999
+ for (const part of parts) {
898
1000
  if (part.type === "insert") {
899
1001
  draftOffset += part.text.length;
900
1002
  boundaryMap[rawOffset] = draftOffset;
@@ -906,27 +1008,11 @@ export function draftSuggestionAnchors(
906
1008
  boundaryMap[rawOffset] = draftOffset;
907
1009
  }
908
1010
  }
909
- if (
910
- !canonical &&
911
- (rawOffset !== proposedRaw.length || draftOffset !== draft.length)
912
- )
1011
+ if (rawOffset !== proposedRaw.length || draftOffset !== draft.length)
913
1012
  throw new SuggestionFormattingMappingError();
914
1013
 
915
1014
  let delta = 0;
916
1015
  return operations.map((operation) => {
917
- if (canonical) {
918
- const from = operation.anchor.from + delta;
919
- const to = from + operation.after.changedText.length;
920
- delta +=
921
- operation.after.changedText.length -
922
- operation.before.changedText.length;
923
- return {
924
- from,
925
- to,
926
- prefix: draft.slice(Math.max(0, from - 32), from),
927
- suffix: draft.slice(to, to + 32),
928
- };
929
- }
930
1016
  const rawFrom = operation.anchor.from + delta;
931
1017
  const rawTo = rawFrom + operation.after.changedText.length;
932
1018
  const from = boundaryMap[rawFrom];
@@ -1,4 +1,5 @@
1
1
  import {
2
+ canonicalizeNfm,
2
3
  docToNfm,
3
4
  nfmToDoc,
4
5
  serializeInlineTextNodeWithOffsets,
@@ -55,7 +56,11 @@ function longestBacktickRun(text: string): number {
55
56
  return Math.max(0, ...(text.match(/`+/g) ?? []).map((run) => run.length));
56
57
  }
57
58
 
58
- function formattingRuns(source: string): { runs: TextRun[] } | null {
59
+ function formattingRuns(source: string): {
60
+ runs: TextRun[];
61
+ restored: string;
62
+ canonicalSpans: SourceSpan[];
63
+ } | null {
59
64
  const doc = nfmToDoc(source);
60
65
  let markerPrefix = "suggestiontextboundary";
61
66
  while (source.includes(markerPrefix)) markerPrefix += "z";
@@ -133,12 +138,103 @@ function formattingRuns(source: string): { runs: TextRun[] } | null {
133
138
  },
134
139
  );
135
140
  if (unmappable || runs.some((run) => run.sourceFrom < 0)) return null;
141
+ const canonicalSpans = runs.map(({ sourceFrom, sourceTo, textOffsets }) => ({
142
+ from: sourceFrom,
143
+ to: sourceTo,
144
+ textOffsets,
145
+ }));
136
146
  if (
137
147
  restored !== source &&
138
148
  !mapStoredSourceRuns(source, restored, runs, markers, withPlaceholders)
139
149
  )
140
150
  return null;
141
- return { runs };
151
+ return { runs, restored, canonicalSpans };
152
+ }
153
+
154
+ type SourceSpan = TextRange & { textOffsets: number[] };
155
+ type SourceSide = "canonical" | "stored";
156
+
157
+ export type SuggestionSourceAlignment = {
158
+ canonical: string;
159
+ map(offset: number, from: SourceSide): number | null;
160
+ };
161
+
162
+ let lastAlignment: {
163
+ source: string;
164
+ alignment: SuggestionSourceAlignment | null;
165
+ } | null = null;
166
+
167
+ // A draft session maps every keystroke against the same stored base.
168
+ export function suggestionSourceAlignment(
169
+ source: string,
170
+ ): SuggestionSourceAlignment | null {
171
+ if (lastAlignment?.source !== source)
172
+ lastAlignment = { source, alignment: sourceAlignment(source) };
173
+ return lastAlignment.alignment;
174
+ }
175
+
176
+ // Pairs each text run's canonical and stored bytes. An offset maps inside a
177
+ // run, at a run or gap edge, or inside a gap whose bytes match on both sides;
178
+ // anywhere else the two serializations disagree and the offset has no image.
179
+ function sourceAlignment(source: string): SuggestionSourceAlignment | null {
180
+ const canonical = canonicalizeNfm(source);
181
+ const inside = (offset: number, text: string) =>
182
+ Number.isInteger(offset) && offset >= 0 && offset <= text.length;
183
+ if (canonical === source)
184
+ return {
185
+ canonical,
186
+ map: (offset) => (inside(offset, source) ? offset : null),
187
+ };
188
+ const mapped = formattingRuns(source);
189
+ if (!mapped || mapped.restored !== canonical) return null;
190
+ const texts = { canonical, stored: source };
191
+ const spans: Array<Record<SourceSide, SourceSpan>> = mapped.runs.map(
192
+ (run, index) => ({
193
+ canonical: mapped.canonicalSpans[index]!,
194
+ stored: {
195
+ from: run.sourceFrom,
196
+ to: run.sourceTo,
197
+ textOffsets: run.textOffsets,
198
+ },
199
+ }),
200
+ );
201
+ return {
202
+ canonical,
203
+ map(offset, from) {
204
+ if (!inside(offset, texts[from])) return null;
205
+ const to: SourceSide = from === "canonical" ? "stored" : "canonical";
206
+ type Edge = Record<SourceSide, number>;
207
+ const gap = (start: Edge, end: Edge) => {
208
+ if (offset === start[from]) return start[to];
209
+ if (offset === end[from]) return end[to];
210
+ return texts[from].slice(start[from], end[from]) ===
211
+ texts[to].slice(start[to], end[to])
212
+ ? start[to] + offset - start[from]
213
+ : null;
214
+ };
215
+ let previous: Edge = { canonical: 0, stored: 0 };
216
+ for (const span of spans) {
217
+ const own = span[from];
218
+ const other = span[to];
219
+ if (offset < own.from)
220
+ return gap(previous, {
221
+ canonical: span.canonical.from,
222
+ stored: span.stored.from,
223
+ });
224
+ if (offset <= own.to) {
225
+ if (offset === own.from) return other.from;
226
+ if (offset === own.to) return other.to;
227
+ const index = own.textOffsets.indexOf(offset - own.from);
228
+ return index < 0 ? null : other.from + other.textOffsets[index]!;
229
+ }
230
+ previous = { canonical: span.canonical.to, stored: span.stored.to };
231
+ }
232
+ return gap(previous, {
233
+ canonical: canonical.length,
234
+ stored: source.length,
235
+ });
236
+ },
237
+ };
142
238
  }
143
239
 
144
240
  function normalizedSourceGap(source: string, from: number, to: number): string {
@@ -155,6 +251,28 @@ function normalizedSourceGap(source: string, from: number, to: number): string {
155
251
  .slice(prefix.length);
156
252
  }
157
253
 
254
+ const TABLE_TAG = /<\/?(?:table|tr|td|th)\b[^>]*>/;
255
+
256
+ // Canonical NFM writes a stored pipe table as an HTML table. Both reduce to
257
+ // "|" between cells and a newline between rows, with or without the stored
258
+ // rows' optional outer pipes; the final reparse proves the cells.
259
+ function tableBoundaries(
260
+ gap: string,
261
+ startsLine: boolean,
262
+ endsLine: boolean,
263
+ ): string {
264
+ return `${startsLine ? "\n" : ""}${gap}${endsLine ? "\n" : ""}`
265
+ .replace(/^[ \t|:-]*$/gm, (line) =>
266
+ line.includes("|") && line.includes("-") ? "" : line,
267
+ )
268
+ .replace(/[ \t]*\|?[ \t]*\n[ \t]*\|?/g, "\n")
269
+ .replace(/<tr\b[^>]*>\s*<t[dh]\b[^>]*>/g, "\n")
270
+ .replace(/<t[dh]\b[^>]*>/g, "|")
271
+ .replace(/<\/?(?:table|tr|td|th)\b[^>]*>/g, "\n")
272
+ .replace(/\s*\|\s*/g, "|")
273
+ .replace(/\s*\n\s*/g, "\n");
274
+ }
275
+
158
276
  function mapStoredSourceRuns(
159
277
  source: string,
160
278
  canonical: string,
@@ -188,7 +306,20 @@ function mapStoredSourceRuns(
188
306
  stored = stored.replace(/\n+$/, "");
189
307
  expected = expected.replace(/\n+$/, "");
190
308
  }
191
- return stored === expected;
309
+ if (stored === expected) return true;
310
+ const lineEdges = (text: string, start: number, end: number) =>
311
+ [
312
+ start === 0 || /[\r\n]/.test(text[start - 1]!),
313
+ end === text.length || /[\r\n]/.test(text[end]!),
314
+ ] as const;
315
+ return (
316
+ TABLE_TAG.test(canonical.slice(canonicalFrom, canonicalTo)) &&
317
+ tableBoundaries(stored, ...lineEdges(source, from, to)) ===
318
+ tableBoundaries(
319
+ expected,
320
+ ...lineEdges(canonical, canonicalFrom, canonicalTo),
321
+ )
322
+ );
192
323
  };
193
324
  let cursor = 0;
194
325
  let canonicalCursor = 0;
@@ -290,9 +290,16 @@ export default defineEventHandler(async (event) => {
290
290
 
291
291
  ## Data Scoping
292
292
 
293
- In production, the framework automatically restricts all agent SQL queries to the current user's data using temporary views. This is enforced at the SQL level — the agent cannot bypass it.
293
+ In production, the framework automatically restricts all agent SQL queries to the current user's data using temporary views, and checks every statement before it runs so it can only reach those views.
294
294
 
295
- The `db-query` / `db-exec` tools (and the extension SQL bridge, which shares the same path) reject schema-qualified table references like `public.<table>` — a qualified name resolves to the base table and would skip the temp view. Use bare table names; scoping is applied automatically.
295
+ The `db-query` / `db-exec` / `db-patch` tools (and the extension SQL bridge, which shares the same path) read each statement the way Postgres does and refuse anything that could reach data around the temp views:
296
+
297
+ - schema- or database-qualified names like `public.<table>`, which resolve to the base table;
298
+ - names that resolve to anything other than the current user's temp views, such as materialized views or tables the scoping does not cover;
299
+ - app-defined SQL or PL/pgSQL functions and operators, whose bodies read tables without the views;
300
+ - built-ins that run SQL text, change session settings, or reach outside the database (`query_to_xml`, `set_config`, `dblink`, most `pg_*` functions).
301
+
302
+ `db-query` runs in a read-only transaction, and an `INSERT` must list its columns when the table has access-control columns. Use bare table names and plain expressions; scoping is applied automatically. When the agent needs data the views do not expose, add an app action with its own access checks.
296
303
 
297
304
  ### Per-User Scoping (`owner_email`)
298
305
 
@@ -75,10 +75,11 @@ state their PostgreSQL types directly.
75
75
 
76
76
  Member offboarding and email changes refuse to run while any column named
77
77
  `email`, `*_email`, `*scope_id`, `created_by`, `updated_by`, `invited_by`,
78
- `owner`, `principal_id`, `session_id`, or `user_id` has no policy. `owner_email`
79
- and `createSharesTable()` tables are handled for you. Declare every other one,
80
- including columns that are not member identities, from the app's database
81
- plugin graph (Clips does it in `server/db/index.ts`):
78
+ `owner`, `principal_id`, `session_id`, or `user_id` has no policy.
79
+ `createSharesTable()` tables are handled for you, and undeclared `owner_email`
80
+ columns transfer to the successor, which is right only for owned content.
81
+ Declare every other one, including columns that are not member identities, from
82
+ the app's database plugin graph (Clips does it in `server/db/index.ts`):
82
83
 
83
84
  ```ts
84
85
  import { registerIdentityColumns } from "@agent-native/core/org";
@@ -88,13 +89,17 @@ registerIdentityColumns([
88
89
  { table: "space_members", column: "email", emailChange: "rekey", offboard: "delete", orgScope: { column: "space_id", references: { table: "spaces", column: "id", orgColumn: "org_id" } }, reason: "Space membership grants access." },
89
90
  // Someone else's address: never rewritten.
90
91
  { table: "meeting_participants", column: "email", emailChange: "retain", offboard: "retain", reason: "Attendee address from the calendar provider." },
92
+ // A credential in an owner_email table: never handed to the successor.
93
+ { table: "api_keys", column: "owner_email", emailChange: "rekey", offboard: "revoke", reason: "Bearer keys act as their owner." },
91
94
  ]);
92
95
  ```
93
96
 
94
- Choose `delete` for grants, credentials, and pending tokens; `retain` for
97
+ Choose `delete` for grants, credentials, and pending tokens, or `revoke` (sets
98
+ `revoked_at`) when a reader treats a missing row as "not revoked"; `retain` for
95
99
  attribution, history, and third-party addresses; `transfer` only for owned
96
- data. A table without `org_id` needs `orgScope` or an organization-scoped
97
- removal leaves its rows alone.
100
+ data. Declare `owner_email` tables that hold credentials or grants, or
101
+ offboarding hands them to the successor. A table without `org_id` needs
102
+ `orgScope` or an organization-scoped removal leaves its rows alone.
98
103
 
99
104
  | Template | Tables |
100
105
  | ------------ | --------------------------------------------- |
@@ -17,6 +17,7 @@
17
17
  },
18
18
  "dependencies": {
19
19
  "@agent-native/core": "workspace:*",
20
+ "@agent-native/otel": "workspace:*",
20
21
  "@agent-native/toolkit": "workspace:*",
21
22
  "@electric-sql/pglite": "^0.5.8",
22
23
  "@fontsource-variable/inter": "^5.3.0",
@@ -0,0 +1,6 @@
1
+ import { defineNitroPlugin } from "@agent-native/core/server";
2
+ import { startAgentNativeOtel } from "@agent-native/otel";
3
+
4
+ export default defineNitroPlugin(() => {
5
+ startAgentNativeOtel();
6
+ });
@@ -290,9 +290,16 @@ export default defineEventHandler(async (event) => {
290
290
 
291
291
  ## Data Scoping
292
292
 
293
- In production, the framework automatically restricts all agent SQL queries to the current user's data using temporary views. This is enforced at the SQL level — the agent cannot bypass it.
293
+ In production, the framework automatically restricts all agent SQL queries to the current user's data using temporary views, and checks every statement before it runs so it can only reach those views.
294
294
 
295
- The `db-query` / `db-exec` tools (and the extension SQL bridge, which shares the same path) reject schema-qualified table references like `public.<table>` — a qualified name resolves to the base table and would skip the temp view. Use bare table names; scoping is applied automatically.
295
+ The `db-query` / `db-exec` / `db-patch` tools (and the extension SQL bridge, which shares the same path) read each statement the way Postgres does and refuse anything that could reach data around the temp views:
296
+
297
+ - schema- or database-qualified names like `public.<table>`, which resolve to the base table;
298
+ - names that resolve to anything other than the current user's temp views, such as materialized views or tables the scoping does not cover;
299
+ - app-defined SQL or PL/pgSQL functions and operators, whose bodies read tables without the views;
300
+ - built-ins that run SQL text, change session settings, or reach outside the database (`query_to_xml`, `set_config`, `dblink`, most `pg_*` functions).
301
+
302
+ `db-query` runs in a read-only transaction, and an `INSERT` must list its columns when the table has access-control columns. Use bare table names and plain expressions; scoping is applied automatically. When the agent needs data the views do not expose, add an app action with its own access checks.
296
303
 
297
304
  ### Per-User Scoping (`owner_email`)
298
305