datanotes 2.0.1 → 3.0.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 (181) hide show
  1. package/NOTICE +9 -0
  2. package/README.md +207 -28
  3. package/SECURITY.md +92 -45
  4. package/dist/THIRD-PARTY-NOTICES.txt +682 -0
  5. package/dist/api/catalog.d.cts +69 -0
  6. package/dist/api/catalog.d.ts +50 -2
  7. package/dist/api/cli-options.d.cts +39 -0
  8. package/dist/api/cli-options.d.ts +39 -0
  9. package/dist/api/client.d.cts +89 -0
  10. package/dist/api/client.d.ts +19 -30
  11. package/dist/api/docs.d.cts +211 -0
  12. package/dist/api/docs.d.ts +211 -0
  13. package/dist/api/guidance.d.cts +81 -0
  14. package/dist/api/guidance.d.ts +81 -0
  15. package/dist/api/index.cjs +6610 -2247
  16. package/dist/api/index.d.cts +13 -0
  17. package/dist/api/index.d.ts +10 -7
  18. package/dist/api/index.js +6610 -2247
  19. package/dist/api/local.cjs +118 -6
  20. package/dist/api/local.d.cts +95 -0
  21. package/dist/api/local.d.ts +70 -6
  22. package/dist/api/local.js +104 -6
  23. package/dist/api/mcp.cjs +17384 -0
  24. package/dist/api/mcp.d.cts +108 -0
  25. package/dist/api/mcp.d.ts +108 -0
  26. package/dist/api/mcp.js +17334 -0
  27. package/dist/api/otlp.d.cts +65 -0
  28. package/dist/api/redact.d.cts +17 -0
  29. package/dist/api/redact.d.ts +17 -0
  30. package/dist/api/requests.d.cts +26 -0
  31. package/dist/api/requests.d.ts +14 -16
  32. package/dist/api/results.d.cts +16 -0
  33. package/dist/api/results.d.ts +2 -1
  34. package/dist/api/tokens.d.cts +7 -0
  35. package/dist/datanotes.cjs +62511 -28069
  36. package/dist/docs/administration/configuration.md +59 -0
  37. package/dist/docs/administration/docker.md +80 -0
  38. package/dist/docs/administration/git-sync.md +71 -0
  39. package/dist/docs/administration/limits.md +69 -0
  40. package/dist/docs/administration/run-the-service.md +219 -0
  41. package/dist/docs/administration/status.md +61 -0
  42. package/dist/docs/administration/use-with-obsidian.md +75 -0
  43. package/dist/docs/api/operations/add_column.md +49 -0
  44. package/dist/docs/api/operations/add_constraint.md +55 -0
  45. package/dist/docs/api/operations/adopt_rows.md +50 -0
  46. package/dist/docs/api/operations/align_relations.md +41 -0
  47. package/dist/docs/api/operations/alter_column.md +46 -0
  48. package/dist/docs/api/operations/alter_table.md +67 -0
  49. package/dist/docs/api/operations/batch.md +48 -0
  50. package/dist/docs/api/operations/changes_stream.md +28 -0
  51. package/dist/docs/api/operations/check_table.md +29 -0
  52. package/dist/docs/api/operations/clone_table.md +48 -0
  53. package/dist/docs/api/operations/comment_on.md +42 -0
  54. package/dist/docs/api/operations/configure_database.md +52 -0
  55. package/dist/docs/api/operations/create_table.md +78 -0
  56. package/dist/docs/api/operations/create_view.md +54 -0
  57. package/dist/docs/api/operations/create_webhook.md +37 -0
  58. package/dist/docs/api/operations/define_base.md +52 -0
  59. package/dist/docs/api/operations/delete_rows.md +43 -0
  60. package/dist/docs/api/operations/delete_webhook.md +31 -0
  61. package/dist/docs/api/operations/describe_database.md +30 -0
  62. package/dist/docs/api/operations/describe_history.md +40 -0
  63. package/dist/docs/api/operations/describe_semantic_index.md +24 -0
  64. package/dist/docs/api/operations/describe_table.md +38 -0
  65. package/dist/docs/api/operations/docs.md +13 -0
  66. package/dist/docs/api/operations/drop_column.md +41 -0
  67. package/dist/docs/api/operations/drop_constraint.md +39 -0
  68. package/dist/docs/api/operations/drop_table.md +43 -0
  69. package/dist/docs/api/operations/drop_view.md +38 -0
  70. package/dist/docs/api/operations/engine_status.md +23 -0
  71. package/dist/docs/api/operations/graph_neighbors.md +32 -0
  72. package/dist/docs/api/operations/graph_path.md +32 -0
  73. package/dist/docs/api/operations/graph_traverse.md +43 -0
  74. package/dist/docs/api/operations/identify_session.md +24 -0
  75. package/dist/docs/api/operations/infer_table.md +43 -0
  76. package/dist/docs/api/operations/insert_row.md +48 -0
  77. package/dist/docs/api/operations/list_changes.md +35 -0
  78. package/dist/docs/api/operations/list_folder.md +28 -0
  79. package/dist/docs/api/operations/list_link_targets.md +44 -0
  80. package/dist/docs/api/operations/list_migrations.md +35 -0
  81. package/dist/docs/api/operations/list_tables.md +32 -0
  82. package/dist/docs/api/operations/list_webhooks.md +23 -0
  83. package/dist/docs/api/operations/manual_index.md +14 -0
  84. package/dist/docs/api/operations/manual_page.md +22 -0
  85. package/dist/docs/api/operations/merge_rows.md +44 -0
  86. package/dist/docs/api/operations/move_rows.md +52 -0
  87. package/dist/docs/api/operations/openapi.md +13 -0
  88. package/dist/docs/api/operations/openapi_post.md +20 -0
  89. package/dist/docs/api/operations/read_note.md +29 -0
  90. package/dist/docs/api/operations/recompute_columns.md +44 -0
  91. package/dist/docs/api/operations/record_tool_call.md +36 -0
  92. package/dist/docs/api/operations/rename_column.md +44 -0
  93. package/dist/docs/api/operations/rename_value.md +45 -0
  94. package/dist/docs/api/operations/restore_database.md +51 -0
  95. package/dist/docs/api/operations/restore_table.md +46 -0
  96. package/dist/docs/api/operations/search_notes.md +28 -0
  97. package/dist/docs/api/operations/select_rows.md +61 -0
  98. package/dist/docs/api/operations/semantic_search.md +34 -0
  99. package/dist/docs/api/operations/semantic_search_docs.md +26 -0
  100. package/dist/docs/api/operations/set_table_bases.md +44 -0
  101. package/dist/docs/api/operations/status.md +13 -0
  102. package/dist/docs/api/operations/stop_engine.md +23 -0
  103. package/dist/docs/api/operations/sync_semantic_index.md +33 -0
  104. package/dist/docs/api/operations/table_changes.md +31 -0
  105. package/dist/docs/api/operations/tags/auxiliary.md +14 -0
  106. package/dist/docs/api/operations/tags/change-feed.md +8 -0
  107. package/dist/docs/api/operations/tags/history.md +17 -0
  108. package/dist/docs/api/operations/tags/queries.md +15 -0
  109. package/dist/docs/api/operations/tags/rows-dml.md +16 -0
  110. package/dist/docs/api/operations/tags/schema-ddl.md +22 -0
  111. package/dist/docs/api/operations/tags/service.md +15 -0
  112. package/dist/docs/api/operations/tags/tokens.md +13 -0
  113. package/dist/docs/api/operations/tokens_create.md +34 -0
  114. package/dist/docs/api/operations/tokens_delete.md +29 -0
  115. package/dist/docs/api/operations/tokens_list.md +21 -0
  116. package/dist/docs/api/operations/tokens_me.md +20 -0
  117. package/dist/docs/api/operations/tokens_rotate.md +29 -0
  118. package/dist/docs/api/operations/tokens_update.md +34 -0
  119. package/dist/docs/api/operations/undo_migration.md +46 -0
  120. package/dist/docs/api/operations/update_row.md +60 -0
  121. package/dist/docs/api/operations/update_rows.md +46 -0
  122. package/dist/docs/api/operations/vacuum_table.md +37 -0
  123. package/dist/docs/api.md +135 -0
  124. package/dist/docs/develop/build-an-app.md +320 -0
  125. package/dist/docs/develop/build-on-datanotes.md +138 -0
  126. package/dist/docs/develop/change-feed-and-webhooks.md +70 -0
  127. package/dist/docs/develop/client.md +116 -0
  128. package/dist/docs/develop/concurrency.md +153 -0
  129. package/dist/docs/develop/http-api.md +79 -0
  130. package/dist/docs/develop/telemetry.md +86 -0
  131. package/dist/docs/guides/agents/docs-offline.md +99 -0
  132. package/dist/docs/guides/agents/how-agents-use-datanotes.md +124 -0
  133. package/dist/docs/guides/agents/prompts.md +495 -0
  134. package/dist/docs/guides/agents/reading-errors.md +112 -0
  135. package/dist/docs/guides/data-quality/check-constraints.md +379 -0
  136. package/dist/docs/guides/data-quality/check-table.md +63 -0
  137. package/dist/docs/guides/data-quality/hand-edits.md +507 -0
  138. package/dist/docs/guides/data-quality/sync-between-devices.md +51 -0
  139. package/dist/docs/guides/data-quality/validation.md +90 -0
  140. package/dist/docs/guides/database-objects/bases.md +480 -0
  141. package/dist/docs/guides/database-objects/catalogs-and-schemas.md +897 -0
  142. package/dist/docs/guides/database-objects/columns.md +1332 -0
  143. package/dist/docs/guides/database-objects/relations.md +1454 -0
  144. package/dist/docs/guides/database-objects/tables.md +1404 -0
  145. package/dist/docs/guides/database-objects/views.md +1003 -0
  146. package/dist/docs/guides/history/back-up.md +195 -0
  147. package/dist/docs/guides/history/change-data-feed.md +247 -0
  148. package/dist/docs/guides/history/journal-and-undo.md +262 -0
  149. package/dist/docs/guides/history/restore.md +341 -0
  150. package/dist/docs/guides/history/table-history.md +657 -0
  151. package/dist/docs/guides/history/vacuum.md +35 -0
  152. package/dist/docs/guides/read-and-write/automatic-links.md +768 -0
  153. package/dist/docs/guides/read-and-write/batch.md +139 -0
  154. package/dist/docs/guides/read-and-write/import-notes.md +896 -0
  155. package/dist/docs/guides/read-and-write/joins-and-aggregates.md +251 -0
  156. package/dist/docs/guides/read-and-write/read-and-filter.md +536 -0
  157. package/dist/docs/guides/read-and-write/write-rows.md +573 -0
  158. package/dist/docs/guides/search/link-graph.md +146 -0
  159. package/dist/docs/guides/search/semantic.md +133 -0
  160. package/dist/docs/guides/search/text.md +142 -0
  161. package/dist/docs/guides/security/security-model.md +110 -0
  162. package/dist/docs/guides/security/tokens.md +60 -0
  163. package/dist/docs/guides/security/who-did-what.md +42 -0
  164. package/dist/docs/index.md +113 -0
  165. package/dist/docs/llms.txt +33 -0
  166. package/dist/docs/reference/cli.md +136 -0
  167. package/dist/docs/reference/compatibility.md +63 -0
  168. package/dist/docs/reference/errors.md +72 -0
  169. package/dist/docs/reference/glossary.md +161 -0
  170. package/dist/docs/reference/operations/auxiliary.md +116 -0
  171. package/dist/docs/reference/operations/history.md +168 -0
  172. package/dist/docs/reference/operations/queries.md +169 -0
  173. package/dist/docs/reference/operations/rows.md +185 -0
  174. package/dist/docs/reference/operations/schema.md +316 -0
  175. package/dist/docs/reference/operations.md +96 -0
  176. package/dist/docs/reference/settings.md +31 -0
  177. package/dist/docs/release-notes.md +67 -0
  178. package/dist/docs/start/connect-agents.md +204 -0
  179. package/dist/docs/start/how-it-works.md +317 -0
  180. package/dist/docs/start/quick-start.md +126 -0
  181. package/package.json +58 -14
@@ -0,0 +1,112 @@
1
+ # Read the engine’s errors
2
+
3
+ > Every error of the engine says what is wrong, how to fix it and where to read more: the shape of an error, the mistakes agents make most, and the operations that look up the right answer.
4
+
5
+ Every error the engine returns answers three questions, so an agent can correct its call without guessing:
6
+
7
+ 1. **What is wrong**: the column, value, rule or argument the engine refused
8
+ 2. **How to fix it**: the allowed values, the closest real name, the right shape, or the operation that looks up the answer
9
+ 3. **Where to read more**: a page of this site in Markdown, with an anchor on the section that shows the fix
10
+
11
+ This page shows how an error looks, how the engine finds the fix in your data, and the mistakes agents make most often.
12
+
13
+ ## Every error has a message, a hint and a docs link
14
+
15
+ Through the Model Context Protocol (MCP) server, `datanotes mcp`, a failed call comes back as a tool error. Its text ends with two lines, `Hint:` and `Docs:`. An agent that writes a film with the status `seen` reads:
16
+
17
+ ```text
18
+ insert_row refused — nothing was written:
19
+ invalid datanotes/main/public/film/Nosferatu.md: status must be one of: to watch (on the list, not seen yet); watched (seen at least once)
20
+ Hint: Use one of the allowed values listed in the message, written exactly; describe_table shows what each one means. To add a value, alter_column the enum. Then call insert_row again (dry_run: true checks without writing).
21
+ Docs: read_docs("guides/database-objects/columns#describe-what-each-enum-value-means")
22
+ ```
23
+
24
+ Over HTTP, the same error is JSON, with `hint` and `docs` next to `error`:
25
+
26
+ ```json
27
+ {
28
+ "error": "Table not found: flim",
29
+ "hint": "Did you mean \"film\"? Tables: film. list_tables lists them …",
30
+ "docs": "https://datanotes.dev/guides/database-objects/tables.md#find-the-table-that-matches-a-request"
31
+ }
32
+ ```
33
+
34
+ A refused write, with status 422, keeps its usual body: the operation’s result with `ok: false` and the reasons. The engine adds `hint` and `docs` to it. In code, `DatanotesClient` appends both lines to two texts:
35
+
36
+ * **`call`**: the `value` of a failed call
37
+ * **`post`**: the message of the `DatanotesError` it throws, which also has `hint` and `docs` as fields
38
+
39
+ The `docs` link always points to a `.md` page, the Markdown version of a page of this site. Agents read Markdown best. The reference of an operation is the page of its kind, followed by an anchor with the operation’s name, such as `https://datanotes.dev/reference/operations/rows.md#insert_row`.
40
+
41
+ The `Docs:` line of the text names the page in two ways:
42
+
43
+ * **`datanotes mcp`**: only the `read_docs` call, which reads the page from the docs of the installed version. Its agents always have the docs tools, so they need no internet access and no web address.
44
+ * **`DatanotesClient` and `datanotes/mcp`**: the address, then the same `read_docs` call in parentheses. Your own MCP server may not serve the docs tools, so its agents keep the address. A server that serves them passes each result to `offlineDocs`, which keeps only the call. See [Build on Datanotes](https://datanotes.dev/develop/build-on-datanotes/).
45
+
46
+ [Read the docs offline](https://datanotes.dev/guides/agents/docs-offline/) describes the docs tools.
47
+
48
+ ## The engine finds the fix in your data
49
+
50
+ When the answer is data, the hint reads it from your database:
51
+
52
+ * **A table or view that doesn’t exist**: the closest names, by spelling, by case and by the table’s `aliases`. `movies` leads to `film`, and a short database lists every table and view.
53
+ * **A column that doesn’t exist**: the closest column and the table’s columns. This works wherever a call names a column: `values`, `where`, `columns`, `order_by`, `join`, `group_by` and `aggregates`.
54
+ * **A value outside an enum**: the allowed values, each with its meaning from `value_descriptions`.
55
+ * **A wrong operator in a filter**: the operator you meant, from a typo (`gtee`) or another language’s word (`>=`, `like`, `not_in`, `is_null`). Then every operator.
56
+ * **A link to a row that doesn’t exist**: `list_link_targets`, which lists the rows the link column accepts.
57
+ * **A row that doesn’t exist**: the row with the closest note name, and how to find a row’s `path` or `id`.
58
+ * **A write to a view**: the view’s own table, since you can only read a view.
59
+
60
+ ## The MCP server checks a call before the engine runs it
61
+
62
+ `datanotes mcp` checks a call against the tool’s input schema before it sends anything. It catches four mistakes:
63
+
64
+ * A tool that doesn’t exist
65
+ * An argument the tool doesn’t take
66
+ * A required argument that is missing
67
+ * A value of the wrong type
68
+
69
+ Each comes back as a tool error that lists the tool’s arguments with their types and links the tool’s reference:
70
+
71
+ ```text
72
+ select_rows: unknown argument "wehre"
73
+ Hint: For "wehre" use "where". select_rows takes: table (string, required), columns (list of strings), where (object), …
74
+ Docs: read_docs("reference/operations/queries#select_rows")
75
+ ```
76
+
77
+ Your own MCP server gets the same checks from `callEngineTool` in `datanotes/mcp`. A program checks arguments with `checkArguments` from `datanotes/client`.
78
+
79
+ ## Fix the mistakes agents make most
80
+
81
+ | Mistake | What the agent reads | The fix |
82
+ | ------------------------------------------ | ---------------------------------------------------- | ------------------------------------------------------ |
83
+ | A misspelled table, `flim` | `Table not found: flim` and `Did you mean "film"?` | Use the name given, or call `list_tables` |
84
+ | A misspelled column, `titel` | `unknown column "titel"` and `Did you mean "title"?` | Use the name given; `describe_table` lists the columns |
85
+ | A value outside an enum, `seen` | The allowed values and their meanings | Pick one, or ask the user which one they meant |
86
+ | A wrong operator, `">="` | `For ">=" use "gte"` and every operator | Write `{"rating": {"gte": 4}}` |
87
+ | A value of the wrong type | `minutes expected a number` | Send numbers unquoted and dates as `YYYY-MM-DD` |
88
+ | A row the table’s CHECK refuses | `violates CHECK rated_when_watched` and the rule | Change the values so the rule holds |
89
+ | A row path that doesn’t exist | `film has no row at …` and the closest row | Use the path given, or find it with `select_rows` |
90
+ | An argument the tool doesn’t take, `wehre` | `For "wehre" use "where"` and the tool’s arguments | Rename the argument |
91
+ | A write to a view | The view’s table | Write to the table, and read the view |
92
+ | An undo blocked by later changes | Which notes changed since | Undo the later writes first, newest first |
93
+
94
+ ## Look up the answer before you write
95
+
96
+ The hints name these operations because each one answers a whole class of mistakes:
97
+
98
+ * **`list_tables`**: every table and view, with descriptions and aliases
99
+ * **`describe_table`**: a table’s columns, types, limits, enum values with their meanings, and CHECK constraints
100
+ * **`describe_database`**: catalogs, schemas, bases and the database’s settings
101
+ * **`list_link_targets`**: the rows a link column may point to, filtered by its `ref_where` and `ref_by`
102
+ * **`check_table`**: the rows that break the schema, such as notes edited by hand
103
+ * **`list_migrations`** and **`describe_history`**: the writes to undo, and a table’s versions
104
+
105
+ A refused write changes nothing, so the agent can fix the call and send it again. To check a write without making it, pass `dry_run: true`. To make several writes succeed or fail together, send them in one `batch`.
106
+
107
+ ## Learn more about operations and refusals
108
+
109
+ * [How agents work with your vault](https://datanotes.dev/guides/agents/how-agents-use-datanotes/): what an agent reads first, and how the journal records its writes
110
+ * [Operations and MCP tools](https://datanotes.dev/reference/operations/): every operation with its arguments
111
+ * [See how the engine refuses bad values](https://datanotes.dev/guides/data-quality/validation/#see-how-the-engine-refuses-bad-values): refusals on a real engine
112
+ * [Read errors over HTTP](https://datanotes.dev/develop/http-api/#read-errors): the status codes of the HTTP API
@@ -0,0 +1,379 @@
1
+ # CHECK constraints
2
+
3
+ > Write rules that every row of a table must follow, including rules that follow links into other tables.
4
+
5
+ A CHECK constraint is a rule written as a `where` filter, such as “a watched film has a rating”. Every row of the table must pass it: the engine refuses a write that breaks it, and refuses to add a constraint that rows already break. A rule can follow links into other tables.
6
+
7
+ ## Enforce rules with CHECK constraints
8
+
9
+ `add_constraint` adds a CHECK constraint, a rule every row must satisfy, written as a `where` filter. The engine refuses it while existing rows break the rule and lists them; after that, it enforces the rule on every write. A condition on a column the row leaves empty passes, as in Structured Query Language (SQL). Inside `or`, `and` and `not`, the engine tests an empty value as it is, so a watched film without a `rating` breaks `rated_when_watched`.
10
+
11
+ **`add_constraint`**
12
+
13
+ ```json
14
+ {
15
+ "table": "film",
16
+ "name": "rated_when_watched",
17
+ "check": { "or": [{ "status": { "ne": "watched" } }, { "rating": { "exists": true } }] }
18
+ }
19
+ ```
20
+
21
+ Answer:
22
+
23
+ ```json
24
+ {
25
+ "ok": true,
26
+ "op": "add_constraint",
27
+ "planned_texts": ["datanotes/metastore.yaml"],
28
+ "commits": [
29
+ { "table": "diary.classics.film", "version": 8, "operation": "ADD CONSTRAINT" }
30
+ ],
31
+ "migration_id": "20261012T072004-add_constraint-add-constraint-rated_when_watc"
32
+ }
33
+ ```
34
+
35
+ **`add_constraint`**
36
+
37
+ ```json
38
+ { "table": "film", "name": "at_most_three_hours", "check": { "minutes": { "lte": 180 } } }
39
+ ```
40
+
41
+ **`insert_row`**
42
+
43
+ ```json
44
+ {
45
+ "table": "film",
46
+ "values": {
47
+ "title": "His Girl Friday",
48
+ "year": 1940,
49
+ "minutes": 92,
50
+ "status": "watched"
51
+ }
52
+ }
53
+ ```
54
+
55
+ Refused:
56
+
57
+ ```text
58
+ insert_row refused — nothing was written:
59
+ invalid datanotes/diary/classics/film/His Girl Friday.md: rated_when_watched violates CHECK rated_when_watched: {"or":[{"status":{"ne":"watched"}},{"rating":{"exists":true}}]}
60
+ Hint: The row breaks a CHECK constraint of the table (named in the message, with its rule): change the values so the rule holds. describe_table lists the table's constraints. Then call insert_row again (dry_run: true checks without writing).
61
+ Docs: https://datanotes.dev/guides/data-quality/check-constraints.md#enforce-rules-with-check-constraints (offline: read_docs("guides/data-quality/check-constraints#enforce-rules-with-check-constraints"))
62
+ ```
63
+
64
+ **`insert_row`**
65
+
66
+ ```json
67
+ {
68
+ "table": "film",
69
+ "values": {
70
+ "title": "His Girl Friday",
71
+ "year": 1940,
72
+ "minutes": 92,
73
+ "status": "watched",
74
+ "rating": 5
75
+ }
76
+ }
77
+ ```
78
+
79
+ Answer:
80
+
81
+ ```json
82
+ {
83
+ "ok": true,
84
+ "op": "insert_row",
85
+ "files_created": 1,
86
+ "planned_creates": ["datanotes/diary/classics/film/His Girl Friday.md"],
87
+ "commits": [{ "table": "diary.classics.film", "version": 10, "operation": "WRITE" }],
88
+ "migration_id": "20261012T072004-insert_row-insert-into-film"
89
+ }
90
+ ```
91
+
92
+ `drop_constraint` removes a constraint, here the three-hour limit, which a long restoration could break:
93
+
94
+ **`drop_constraint`**
95
+
96
+ ```json
97
+ { "table": "film", "name": "at_most_three_hours" }
98
+ ```
99
+
100
+ Answer:
101
+
102
+ ```json
103
+ {
104
+ "ok": true,
105
+ "op": "drop_constraint",
106
+ "planned_texts": ["datanotes/metastore.yaml"],
107
+ "commits": [
108
+ { "table": "diary.classics.film", "version": 11, "operation": "DROP CONSTRAINT" }
109
+ ],
110
+ "migration_id": "20261012T072004-drop_constraint-drop-constraint-at_most_three_"
111
+ }
112
+ ```
113
+
114
+ ## Write a CHECK rule that follows links
115
+
116
+ A CHECK constraint is a rule every row must satisfy, and you add one with `add_constraint`. It can follow a link the way a query does: `parent.kind` is the `kind` of the row that `parent` names. Here a genre’s parent must be an era, an era’s parent must be Cinema, and Cinema has no parent.
117
+
118
+ A rule treats links as follows:
119
+
120
+ * **Empty link**: passes at the top level of a rule and fails inside `or`, `and` and `not`; on a longer path such as `parent.parent.kind`, the rule skips only an empty first link
121
+ * **Link to a row that doesn’t exist**: fails, so it passes inside `not`
122
+ * **List of links**: every linked row must match
123
+ * **Resolution**: links resolve as in the vault, by path from the row’s note
124
+
125
+ **`add_constraint`**
126
+
127
+ ```json
128
+ {
129
+ "table": "genre",
130
+ "name": "parent_by_level",
131
+ "check": {
132
+ "or": [
133
+ { "kind": "cinema", "parent": { "exists": false } },
134
+ { "kind": "era", "parent.kind": "cinema" },
135
+ { "kind": "genre", "parent.kind": "era" }
136
+ ]
137
+ }
138
+ }
139
+ ```
140
+
141
+ Answer:
142
+
143
+ ```json
144
+ {
145
+ "ok": true,
146
+ "op": "add_constraint",
147
+ "planned_texts": ["datanotes/metastore.yaml"],
148
+ "commits": [
149
+ { "table": "main.public.genre", "version": 4, "operation": "ADD CONSTRAINT" }
150
+ ],
151
+ "migration_id": "20261012T071949-add_constraint-add-constraint-parent_by_level"
152
+ }
153
+ ```
154
+
155
+ The engine refuses a genre straight under Cinema:
156
+
157
+ **`insert_row`**
158
+
159
+ ```json
160
+ {
161
+ "table": "genre",
162
+ "values": { "name": "Horror", "kind": "genre", "parent": "[[Cinema]]" }
163
+ }
164
+ ```
165
+
166
+ Refused:
167
+
168
+ ```text
169
+ insert_row refused — nothing was written:
170
+ invalid datanotes/main/public/genre/Horror.md: parent_by_level violates CHECK parent_by_level: {"or":[{"kind":"cinema","parent":{"exists":false}},{"kind":"era","parent.kind":"cinema"},{"kind":"genre","parent.kind":"era"}]}
171
+ Hint: The row breaks a CHECK constraint of the table (named in the message, with its rule): change the values so the rule holds. describe_table lists the table's constraints. Then call insert_row again (dry_run: true checks without writing).
172
+ Docs: https://datanotes.dev/guides/data-quality/check-constraints.md#enforce-rules-with-check-constraints (offline: read_docs("guides/data-quality/check-constraints#enforce-rules-with-check-constraints"))
173
+ ```
174
+
175
+ When a write changes a linked row, the engine checks the rows that link to it again. Sound film can’t become a top level like Cinema while a genre hangs from it, and the refusal lists Screwball comedy, naming Sound film.
176
+
177
+ **`update_row`**
178
+
179
+ ```json
180
+ {
181
+ "table": "genre",
182
+ "path": "datanotes/main/public/genre/Sound film.md",
183
+ "set": { "kind": "cinema" },
184
+ "unset": ["parent"]
185
+ }
186
+ ```
187
+
188
+ Refused:
189
+
190
+ ```text
191
+ update_row refused — nothing was written:
192
+ invalid datanotes/main/public/genre/Screwball comedy.md: parent_by_level violates CHECK parent_by_level: {"or":[{"kind":"cinema","parent":{"exists":false}},{"kind":"era","parent.kind":"cinema"},{"kind":"genre","parent.kind":"era"}]} (after this change to [[Sound film]], a row it links to)
193
+ Hint: The row breaks a CHECK constraint of the table (named in the message, with its rule): change the values so the rule holds. describe_table lists the table's constraints. Then call update_row again (dry_run: true checks without writing).
194
+ Docs: https://datanotes.dev/guides/data-quality/check-constraints.md#enforce-rules-with-check-constraints (offline: read_docs("guides/data-quality/check-constraints#enforce-rules-with-check-constraints"))
195
+ ```
196
+
197
+ In a `batch`, add the era first and then its genre. The engine checks each step against the vault as the earlier steps left it, so Slapstick comedy finds its parent.
198
+
199
+ **`batch`**
200
+
201
+ ```json
202
+ {
203
+ "summary": "add the silent era and slapstick comedy",
204
+ "operations": [
205
+ {
206
+ "op": "insert_row",
207
+ "table": "genre",
208
+ "values": { "name": "Silent cinema", "kind": "era", "parent": "[[Cinema]]" }
209
+ },
210
+ {
211
+ "op": "insert_row",
212
+ "table": "genre",
213
+ "values": {
214
+ "name": "Slapstick comedy",
215
+ "kind": "genre",
216
+ "parent": "[[Silent cinema]]"
217
+ }
218
+ }
219
+ ]
220
+ }
221
+ ```
222
+
223
+ Answer:
224
+
225
+ ```json
226
+ {
227
+ "ok": true,
228
+ "op": "batch",
229
+ "dry_run": false,
230
+ "migration_id": "20261012T071949-batch-add-the-silent-era-and-slapsti",
231
+ "steps": [
232
+ {
233
+ "index": 0,
234
+ "op": "insert_row",
235
+ "ok": true,
236
+ "migration_id": "20261012T071949-insert_row-insert-into-genre-4",
237
+ "result": {
238
+ "ok": true,
239
+ "op": "insert_row",
240
+ "files_created": 1,
241
+ "planned_creates": ["datanotes/main/public/genre/Silent cinema.md"],
242
+ "commits": [{ "table": "main.public.genre", "version": 5, "operation": "WRITE" }],
243
+ "migration_id": "20261012T071949-insert_row-insert-into-genre-4"
244
+ }
245
+ },
246
+ {
247
+ "index": 1,
248
+ "op": "insert_row",
249
+ "ok": true,
250
+ "migration_id": "20261012T071949-insert_row-insert-into-genre-5",
251
+ "result": {
252
+ "ok": true,
253
+ "op": "insert_row",
254
+ "files_created": 1,
255
+ "planned_creates": ["datanotes/main/public/genre/Slapstick comedy.md"],
256
+ "commits": [{ "table": "main.public.genre", "version": 6, "operation": "WRITE" }],
257
+ "migration_id": "20261012T071949-insert_row-insert-into-genre-5"
258
+ }
259
+ }
260
+ ]
261
+ }
262
+ ```
263
+
264
+ **`select_rows`**
265
+
266
+ ```json
267
+ { "table": "genre", "columns": ["name", "kind", "parent"], "order_by": "name" }
268
+ ```
269
+
270
+ Answer:
271
+
272
+ ```json
273
+ {
274
+ "table": "main.public.genre",
275
+ "total": 5,
276
+ "rows": [
277
+ {
278
+ "path": "datanotes/main/public/genre/Cinema.md",
279
+ "name": "Cinema",
280
+ "kind": "cinema",
281
+ "parent": null
282
+ },
283
+ {
284
+ "path": "datanotes/main/public/genre/Screwball comedy.md",
285
+ "name": "Screwball comedy",
286
+ "kind": "genre",
287
+ "parent": "[[Sound film]]"
288
+ },
289
+ {
290
+ "path": "datanotes/main/public/genre/Silent cinema.md",
291
+ "name": "Silent cinema",
292
+ "kind": "era",
293
+ "parent": "[[Cinema]]"
294
+ },
295
+ {
296
+ "path": "datanotes/main/public/genre/Slapstick comedy.md",
297
+ "name": "Slapstick comedy",
298
+ "kind": "genre",
299
+ "parent": "[[Silent cinema]]"
300
+ },
301
+ {
302
+ "path": "datanotes/main/public/genre/Sound film.md",
303
+ "name": "Sound film",
304
+ "kind": "era",
305
+ "parent": "[[Cinema]]"
306
+ }
307
+ ]
308
+ }
309
+ ```
310
+
311
+ Renaming a row that other rows link to keeps the rule valid, because the links follow the row. Here Slapstick comedy follows the era to its new name, Silent film.
312
+
313
+ **`update_row`**
314
+
315
+ ```json
316
+ {
317
+ "table": "genre",
318
+ "path": "datanotes/main/public/genre/Silent cinema.md",
319
+ "set": { "name": "Silent film" }
320
+ }
321
+ ```
322
+
323
+ Answer:
324
+
325
+ ```json
326
+ {
327
+ "ok": true,
328
+ "op": "update_row",
329
+ "notes_changed": 1,
330
+ "files_moved": 1,
331
+ "planned_edits": [
332
+ {
333
+ "path": "datanotes/main/public/genre/Silent cinema.md",
334
+ "set": { "name": "Silent film" },
335
+ "remove": []
336
+ }
337
+ ],
338
+ "planned_moves": [
339
+ {
340
+ "from": "datanotes/main/public/genre/Silent cinema.md",
341
+ "to": "datanotes/main/public/genre/Silent film.md"
342
+ }
343
+ ],
344
+ "commits": [{ "table": "main.public.genre", "version": 7, "operation": "UPDATE" }],
345
+ "migration_id": "20261012T071949-update_row-update-genre"
346
+ }
347
+ ```
348
+
349
+ **`select_rows`**
350
+
351
+ ```json
352
+ {
353
+ "table": "genre",
354
+ "columns": ["name", "parent"],
355
+ "where": { "parent.kind": "era" },
356
+ "order_by": "name"
357
+ }
358
+ ```
359
+
360
+ Answer:
361
+
362
+ ```json
363
+ {
364
+ "table": "main.public.genre",
365
+ "total": 2,
366
+ "rows": [
367
+ {
368
+ "path": "datanotes/main/public/genre/Screwball comedy.md",
369
+ "name": "Screwball comedy",
370
+ "parent": "[[Sound film]]"
371
+ },
372
+ {
373
+ "path": "datanotes/main/public/genre/Slapstick comedy.md",
374
+ "name": "Slapstick comedy",
375
+ "parent": "[[Silent film]]"
376
+ }
377
+ ]
378
+ }
379
+ ```
@@ -0,0 +1,63 @@
1
+ # Find rows that break the schema
2
+
3
+ > List the rows that break their table's rules now, with check_table, for one table or the whole database.
4
+
5
+ `check_table` checks the rows a table holds now against its columns and CHECK constraints, and lists the ones that break a rule. Use it after hand edits, after a sync, or before you tighten a rule. Without `table`, it checks every table, page by page. It writes nothing.
6
+
7
+ ## Find rows that break the schema
8
+
9
+ The engine can’t refuse a hand edit, so a note you edit by hand can break the schema. `check_table` validates every row against these rules:
10
+
11
+ * **Values**: types, ranges and enums
12
+ * **Presence**: required columns and unknown keys
13
+ * **Relations and rules**: links and constraints
14
+
15
+ You edit `datanotes/main/public/film/His Girl Friday.md` by hand, outside the engine. The note now reads:
16
+
17
+ ```markdown
18
+ ---
19
+ table: main.public.film
20
+ id: 01a0f000-0000-7000-8000-0000000000bb
21
+ title: His Girl Friday
22
+ year: 1940
23
+ status: finished
24
+ rating: 6
25
+ format: Blu-ray
26
+ ---
27
+ ```
28
+
29
+ **`check_table`**
30
+
31
+ ```json
32
+ { "table": "film" }
33
+ ```
34
+
35
+ Answer:
36
+
37
+ ```json
38
+ {
39
+ "table": "film",
40
+ "rows_checked": 3,
41
+ "total": 1,
42
+ "violations": [
43
+ {
44
+ "path": "datanotes/main/public/film/His Girl Friday.md",
45
+ "violations": [
46
+ {
47
+ "column": "status",
48
+ "code": "enum",
49
+ "message": "must be one of: to watch; watched",
50
+ "value": "finished"
51
+ },
52
+ { "column": "rating", "code": "max", "message": "must be <= 5", "value": 6 },
53
+ {
54
+ "column": "format",
55
+ "code": "unknown_column",
56
+ "message": "is not a column of table \"film\"",
57
+ "value": "Blu-ray"
58
+ }
59
+ ]
60
+ }
61
+ ]
62
+ }
63
+ ```