datanotes 3.3.0 → 3.4.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 (72) hide show
  1. package/SECURITY.md +5 -3
  2. package/dist/api/extensions.cjs +1345 -21
  3. package/dist/api/extensions.js +1345 -21
  4. package/dist/api/index.cjs +1345 -21
  5. package/dist/api/index.js +1345 -21
  6. package/dist/api/mcp.cjs +1345 -21
  7. package/dist/api/mcp.js +1345 -21
  8. package/dist/datanotes.cjs +2927 -609
  9. package/dist/docs/administration/extensions.md +2 -2
  10. package/dist/docs/api/operations/add_column.md +1 -0
  11. package/dist/docs/api/operations/alter_table.md +4 -3
  12. package/dist/docs/api/operations/check_table.md +1 -0
  13. package/dist/docs/api/operations/create_table.md +1 -0
  14. package/dist/docs/api/operations/describe_database.md +2 -0
  15. package/dist/docs/api/operations/describe_history.md +1 -0
  16. package/dist/docs/api/operations/describe_semantic_index.md +1 -0
  17. package/dist/docs/api/operations/describe_table.md +1 -0
  18. package/dist/docs/api/operations/graph_neighbors.md +1 -0
  19. package/dist/docs/api/operations/graph_path.md +1 -0
  20. package/dist/docs/api/operations/graph_traverse.md +1 -0
  21. package/dist/docs/api/operations/infer_table.md +1 -0
  22. package/dist/docs/api/operations/list_changes.md +1 -0
  23. package/dist/docs/api/operations/list_folder.md +1 -0
  24. package/dist/docs/api/operations/list_link_targets.md +1 -0
  25. package/dist/docs/api/operations/list_migrations.md +1 -0
  26. package/dist/docs/api/operations/list_tables.md +2 -0
  27. package/dist/docs/api/operations/list_webhooks.md +1 -0
  28. package/dist/docs/api/operations/read_note.md +1 -0
  29. package/dist/docs/api/operations/search.md +39 -0
  30. package/dist/docs/api/operations/search_notes.md +1 -0
  31. package/dist/docs/api/operations/select_rows.md +1 -0
  32. package/dist/docs/api/operations/semantic_search.md +1 -0
  33. package/dist/docs/api/operations/table_changes.md +1 -0
  34. package/dist/docs/api/operations/tags/queries.md +1 -0
  35. package/dist/docs/api/operations/tags/tokens.md +1 -1
  36. package/dist/docs/api/operations/tokens_create.md +1 -0
  37. package/dist/docs/api/operations/tokens_update.md +2 -1
  38. package/dist/docs/api.md +5 -4
  39. package/dist/docs/develop/build-an-app.md +2 -1
  40. package/dist/docs/develop/change-feed-and-webhooks.md +1 -1
  41. package/dist/docs/develop/http-api.md +1 -1
  42. package/dist/docs/guides/data-quality/check-constraints.md +10 -10
  43. package/dist/docs/guides/data-quality/hand-edits.md +8 -8
  44. package/dist/docs/guides/database-objects/bases.md +11 -11
  45. package/dist/docs/guides/database-objects/catalogs-and-schemas.md +30 -10
  46. package/dist/docs/guides/database-objects/columns.md +243 -40
  47. package/dist/docs/guides/database-objects/relations.md +28 -28
  48. package/dist/docs/guides/database-objects/tables.md +167 -43
  49. package/dist/docs/guides/database-objects/views.md +44 -44
  50. package/dist/docs/guides/history/change-data-feed.md +34 -34
  51. package/dist/docs/guides/history/journal-and-undo.md +12 -12
  52. package/dist/docs/guides/history/restore.md +20 -20
  53. package/dist/docs/guides/history/table-history.md +31 -31
  54. package/dist/docs/guides/read-and-write/automatic-links.md +15 -15
  55. package/dist/docs/guides/read-and-write/import-notes.md +20 -20
  56. package/dist/docs/guides/read-and-write/joins-and-aggregates.md +7 -7
  57. package/dist/docs/guides/read-and-write/read-and-filter.md +10 -10
  58. package/dist/docs/guides/read-and-write/write-rows.md +10 -10
  59. package/dist/docs/guides/search/hybrid.md +162 -0
  60. package/dist/docs/guides/search/text.md +1 -1
  61. package/dist/docs/guides/security/security-model.md +5 -3
  62. package/dist/docs/guides/security/tokens.md +121 -8
  63. package/dist/docs/reference/cli.md +1 -1
  64. package/dist/docs/reference/glossary.md +1 -1
  65. package/dist/docs/reference/operations/auxiliary.md +3 -3
  66. package/dist/docs/reference/operations/queries.md +16 -0
  67. package/dist/docs/reference/operations/rows.md +1 -1
  68. package/dist/docs/reference/operations/schema.md +5 -5
  69. package/dist/docs/reference/operations.md +6 -5
  70. package/dist/docs/release-notes.md +59 -0
  71. package/dist/docs/start/connect-agents.md +10 -3
  72. package/package.json +1 -1
@@ -1,8 +1,8 @@
1
1
  # Give each program its own token
2
2
 
3
- > Create named tokens with their own permissions, so the engine limits what each program or agent can do and the journal says who wrote what.
3
+ > Create named tokens with their own permissions, so the engine limits what each program or agent can read and write, and the journal says who wrote what.
4
4
 
5
- A named token lets a program, a device or an agent call the engine with only the permissions it needs. The owner token can do everything; a named token reads everything and writes only what you allow. The journal records the token’s name with every write it makes, so you can tell who changed what.
5
+ A named token lets a program, a device or an agent call the engine with only the permissions it needs. The owner token can do everything. A named token reads what you allow and writes only what you allow. Without `read`, it reads every table. The journal records the token’s name with every write it makes, so you can tell who changed what.
6
6
 
7
7
  To see a token at work in a complete program, follow [Build an app on Datanotes](https://datanotes.dev/develop/build-an-app/). For the ways to build on the engine, read [Build on Datanotes](https://datanotes.dev/develop/build-on-datanotes/). [Who did what](https://datanotes.dev/guides/security/who-did-what/) shows how the journal names each token.
8
8
 
@@ -17,7 +17,7 @@ curl -X POST http://127.0.0.1:27150/datanotes/tokens/create/ \
17
17
  -d '{"name": "film-diary", "write": ["main.public.*"]}'
18
18
  ```
19
19
 
20
- This request creates a token named `film-diary` that may write rows of every table in the `main.public` schema, such as `film` and `actor`. The answer holds the token in `token` and the registry’s entry in `entry`. The name is kebab-case, and `owner` is reserved.
20
+ This request creates a token named `film-diary`. It may write rows of every table in the `main.public` schema, such as `film` and `actor`. It passes no `read`, so the token reads every table. The answer holds the token in `token` and the registry’s entry in `entry`. The name is kebab-case, and `owner` is reserved.
21
21
 
22
22
  ## Manage your tokens
23
23
 
@@ -32,13 +32,124 @@ The owner manages tokens with these routes under `/datanotes/`:
32
32
 
33
33
  ## Choose what a token can do
34
34
 
35
- A named token can always read. Every other permission comes from its entry:
35
+ Each permission of a named token comes from its entry:
36
36
 
37
- * **Row writes**: only on tables that match a pattern in `write`: `catalog.schema.table`, `catalog.schema.*`, `catalog.*` or `*`
38
- * **Schema changes**: only with `"ddl": true`
39
- * **Maintenance**: only with `"maintain": true`, meant for an agent’s own token; it covers configuration, undo, restore, vacuum and rebuilding the semantic index
37
+ * **Row reads**: only of tables that match a pattern in `read`. Without `read`, the token reads every table.
38
+ * **Row writes**: only on tables that match a pattern in `write`.
39
+ * **Schema changes**: only with `"ddl": true`.
40
+ * **Maintenance**: only with `"maintain": true`, meant for an agent’s own token. It covers configuration, undo, restore, vacuum and rebuilding the semantic index.
40
41
 
41
- Whatever its permissions, a named token can’t create or delete tokens and webhooks, list tokens, rotate them or read the engine’s status: those stay the owner’s. A token may list webhooks and read its own entry with `tokens/me/`. With `tokens/update/`, it may change its own `description` and `autolink`.
42
+ A pattern of `read` or `write` takes one of these forms:
43
+
44
+ * **`catalog.schema.table`**: one table, such as `personal.food.pantry`
45
+ * **`catalog.schema.*`**: every table of a schema
46
+ * **`catalog.*`**: every table of a catalog
47
+ * **`*`**: every table
48
+
49
+ Some routes stay the owner’s, whatever a named token’s permissions. A named token can’t create or delete tokens and webhooks, list tokens, rotate them or read the engine’s status. A token may list webhooks and read its own entry with `tokens/me/`. With `tokens/update/`, it may change its own `description` and `autolink`, never its permissions.
50
+
51
+ ## Limit what a token can read
52
+
53
+ By default, a named token reads every table. Give it `read` patterns, and it reads only the tables they match. Use them for an agent that works on one part of your notes, so it never sees the rest.
54
+
55
+ This request creates a `cook` token for a cooking agent:
56
+
57
+ ```bash
58
+ curl -X POST http://127.0.0.1:27150/datanotes/tokens/create/ \
59
+ -H "Authorization: Bearer your_owner_token" \
60
+ -H "Content-Type: application/json" \
61
+ -d '{
62
+ "name": "cook",
63
+ "read": ["personal.food.*"],
64
+ "write": ["personal.food.pantry"]
65
+ }'
66
+ ```
67
+
68
+ The `cook` token has these permissions:
69
+
70
+ * **It reads** `personal.food.pantry`, `personal.food.meals` and every other table of `personal.food`.
71
+ * **It writes** only `personal.food.pantry`.
72
+ * **It never sees** `personal.health.*`: its rows, notes in search, nodes in the graph, changes in the feed, or names in `list_tables`.
73
+
74
+ A new table in `personal.food` becomes readable at once, because the pattern matches it.
75
+
76
+ `tokens/` and `tokens/me/` show `read` next to `write`. A token without `read` shows `["*"]`. `datanotes status` prints the limits in its text, such as `cook (read personal.food.*, write personal.food.pantry)`. A token with `"read": []` shows `read nothing`. The status in JavaScript Object Notation (JSON) carries `read` only when it is limited.
77
+
78
+ ### The rules of `read`
79
+
80
+ The engine follows these rules:
81
+
82
+ * **No `read` means every table.** A token made before read patterns existed keeps reading everything. `["*"]` means the same.
83
+ * **A token reads what it writes.** Every `write` pattern must fall inside a `read` pattern. A write answers with the rows it touches and takes `where` filters, so a write-only token would still read. `tokens/create/` and `tokens/update/` refuse such a token with 400, and the hint names the missing pattern.
84
+ * **`ddl` and `maintain` need every table.** A token with either one can’t have a limited `read`. Cloning a table, adding a constraint, undo and restore read rows of any table. The engine refuses the combination with 400.
85
+ * **A view needs its tables too.** To read a view, a token must read the view’s name and every table the view reads. That includes nested views, joins, unions and the links its filters follow. `list_tables` lists a view only when the engine can tell its tables from the view’s query alone. It hides a view over a union, even when every table of the union is readable.
86
+ * **Free notes stay hidden.** Only a token that reads every table reads a note outside tables, or any other file of the vault. A token with a limited `read` never reads free notes.
87
+ * **A link stays visible as text.** A readable row may link to a row the token can’t read. The link text, such as `[[Pasta]]`, stays in the answer. `expand` doesn’t expand it, so the linked note’s properties stay hidden. `file_backlink_count` stays a plain count.
88
+
89
+ ### What the engine filters and what it refuses
90
+
91
+ The engine refuses a name you write yourself and filters a set you did not list.
92
+
93
+ A name you write is refused with 403 when the token can’t read it:
94
+
95
+ * The `table` of a query, a join or a union branch
96
+ * A link a `where` filter follows, such as `for_weight.kg`, and the target of `within`
97
+ * `this_note`, and the `path` of `read_note`
98
+ * An exact table name in `tables`, as `search`, `semantic_search` and the graph operations take it
99
+ * The `table` of `describe_table`, `check_table`, `infer_table`, `list_link_targets`, `table_changes` and `describe_history`
100
+
101
+ The refusal names the token and the table, such as `token cook may not read personal.health.weight`. It also carries a hint and a link to this section.
102
+
103
+ A set you did not list is filtered to what the token may read, with no error:
104
+
105
+ * **Listings**: `list_tables`, `describe_database`, `list_folder` and `system.information_schema`
106
+ * **Bases**: `base:<name>` keeps only the readable tables that extend it
107
+ * **Wildcards**: a pattern in `tables`, or every table when you leave `table` out
108
+ * **Search and the graph**: `search`, `search_notes`, `semantic_search`, and the neighbours and paths of the graph
109
+ * **History and changes**: `list_changes`, `list_migrations`, `describe_history` and the change feed’s stream
110
+
111
+ The engine filters before it applies `limit`, so a search still returns up to `limit` results. A token sees its own tool calls on the change feed, never another token’s. `list_webhooks` shows only the webhooks whose tables the token may all read.
112
+
113
+ A few more rules apply to a token with a limited `read`:
114
+
115
+ * **Schema changes stay hidden.** The change feed never shows it a change to `metastore.yaml`, whatever the table.
116
+ * **A short name means the readable table.** In the graph’s `tables`, `through` and `from_nodes.tables`, a short name may match several tables. The engine then picks the one the token may read.
117
+ * **A refused write keeps its short message.** A link the engine refuses lists no rows of a table the token can’t read. The tool-call event of that refusal on the change feed holds the same short message.
118
+
119
+ A write by a limited token still checks every table: links, uniqueness and CHECKs. Only its answer changes. It leaves out the rows of tables the token can’t read, even when the write touched them.
120
+
121
+ ### Names are hidden, not secret
122
+
123
+ Listings hide the tables a token can’t read. A refusal, though, repeats the name the caller gave. So a caller who guesses `personal.health.weight` learns that the table exists. It never learns the table’s rows. The contents are protected; the names are only hidden from listings. Don’t put a secret in a table’s name.
124
+
125
+ ### Change what a token reads
126
+
127
+ The owner changes `read` with `tokens/update/`. This request lets `cook` read the health tables too:
128
+
129
+ ```bash
130
+ curl -X POST http://127.0.0.1:27150/datanotes/tokens/update/ \
131
+ -H "Authorization: Bearer your_owner_token" \
132
+ -H "Content-Type: application/json" \
133
+ -d '{
134
+ "name": "cook",
135
+ "read": ["personal.food.*", "personal.health.*"]
136
+ }'
137
+ ```
138
+
139
+ To let a token read every table again, pass `"read": null`. A token can’t change its own `read`: the engine answers 403.
140
+
141
+ The token of an [extension](https://datanotes.dev/administration/extensions/), `ext-<name>`, gets no `read` from the installer, so it reads every table. You may limit it with `tokens/update/`, as long as `read` covers the tables it writes. A tool that then reads another table gets a 403. An install or update may write a table outside `read`. The engine then refuses the token’s new `write` with 400. Widen `read` first, then run the command again.
142
+
143
+ ### Known gaps
144
+
145
+ Read patterns limit tables, not columns or rows. These gaps remain:
146
+
147
+ * **No write-only tokens.** A token always reads what it writes.
148
+ * **No column or row limits.** A token reads every column and every row of a readable table.
149
+ * **Writes into other tables.** A write can change rows of other tables: `on_referenced: "set_null"`, inverse links and `align_relations`. The engine doesn’t check those changes against `write` yet. The answer still hides the rows the token can’t read.
150
+ * **Counts.** `describe_semantic_index` and `file_backlink_count` count every note, readable or not. They may tell how many notes exist.
151
+ * **Tool calls.** Any token may record a tool call with `tool-calls/record/`, in the name of any agent.
152
+ * **Guessed names.** As [Names are hidden, not secret](#names-are-hidden-not-secret) explains, a refusal confirms that a table exists.
42
153
 
43
154
  ## Turn automatic links off for a sync program
44
155
 
@@ -58,3 +169,5 @@ A token’s `autolink` is the default of its writes:
58
169
  * **Without it**, the token’s writes follow the database’s `autolink` setting.
59
170
 
60
171
  Turn it off for the tokens of sync programs and of modules that write text for you. Keep it unset for an agent’s token. To change it later, call `tokens/update/` with `autolink`. Pass `null` to follow the database setting again. `tokens/` and `tokens/me/` list it with the other fields of the entry.
172
+
173
+ With a limited `read`, automatic links point only to notes the token may read.
@@ -3,7 +3,7 @@
3
3
  > The datanotes command and its options.
4
4
 
5
5
  ```text
6
- datanotes 3.3.0: Markdown notes as database tables, for you and your agents.
6
+ datanotes 3.4.0: Markdown notes as database tables, for you and your agents.
7
7
 
8
8
 
9
9
  Usage:
@@ -142,7 +142,7 @@ A set of rows with the same columns, defined in the metastore. See [Tables](http
142
142
 
143
143
  ## Token
144
144
 
145
- A credential for the HTTP API, sent as `Authorization: Bearer`. The owner token can do everything; a named token has only the permissions you give it. See [Tokens and permissions](https://datanotes.dev/guides/security/tokens/).
145
+ A credential for the HTTP API, sent as `Authorization: Bearer`. The owner token can do everything. A named token reads and writes only the tables you allow, and has only the permissions you give it. See [Tokens and permissions](https://datanotes.dev/guides/security/tokens/).
146
146
 
147
147
  ## Transaction log
148
148
 
@@ -10,7 +10,7 @@ Every write is validated. Writes to rows and to the schema are journaled and und
10
10
 
11
11
  **List Tables** · read-only
12
12
 
13
- List every table: `name`, `catalog`, `schema`, `qualified` (`personal.metrics.mood` — use it wherever a table is expected; `metrics.mood` or `mood` also work when unambiguous), `path` and, when declared, `description`, `aliases` (other words for what it records) and `tags`. Tables are defined in `metastore.yaml` (catalog → schema → table): read it freely, but it is read-only for agents and changes only through the schema operations (create_table, add_column, …). Returns `{tables, views}`: `views` lists the stored views (same fields, no path): select them like tables. `system.information_schema` is read with `select_rows`.
13
+ List every table: `name`, `catalog`, `schema`, `qualified` (`personal.metrics.mood` — use it wherever a table is expected; `metrics.mood` or `mood` also work when unambiguous), `path` and, when declared, `description`, `aliases` (other words for what it records) and `tags`. Tables are defined in `metastore.yaml` (catalog → schema → table): read it freely, but it is read-only for agents and changes only through the schema operations (create_table, add_column, …). Returns `{tables, views}`: `views` lists the stored views (same fields, no path): select them like tables. `system.information_schema` is read with `select_rows`. A token with `read` patterns sees only the tables and views it may read.
14
14
 
15
15
  Call this first to find the table for what the user reports (match their words against name, description and aliases), and before creating tables or columns so you reuse existing ones instead of creating duplicates.
16
16
 
@@ -36,7 +36,7 @@ Examples: [Catalogs, schemas and the database](https://datanotes.dev/guides/data
36
36
 
37
37
  **Describe Database** · read-only
38
38
 
39
- Describe the database: its folder (`root`), `metastore` (`metastore.yaml`: read it freely, but it is read-only for agents and changes only through the schema operations), configuration (default catalog and schema, row partition and file-name formats, archive folder, time zone, history, automatic links), journal folder, `bases` (shared column sets and the tables using them), `catalogs` → `schemas` → `tables` (with `row_count`, `tags`, and `unreadable_count` when some rows’ front matter cannot be read) and `views`; catalogs and schemas show their `comment` (set with `comment_on`). A table or base defined before descriptions were required has `missing_descriptions`: what lacks one, for alter_table / alter_column (define_base for a base) to fill, so every table and column says what it holds.
39
+ Describe the database: its folder (`root`), `metastore` (`metastore.yaml`: read it freely, but it is read-only for agents and changes only through the schema operations), configuration (default catalog and schema, row partition and file-name formats, archive folder, time zone, history, automatic links), journal folder, `bases` (shared column sets and the tables using them), `catalogs` → `schemas` → `tables` (with `row_count`, `tags`, and `unreadable_count` when some rows’ front matter cannot be read) and `views`; catalogs and schemas show their `comment` (set with `comment_on`). A table or base defined before descriptions were required has `missing_descriptions`: what lacks one, for alter_table / alter_column (define_base for a base) to fill, so every table and column says what it holds. A token with `read` patterns sees only the tables, views and bases it may read.
40
40
 
41
41
  Examples: [Catalogs, schemas and the database](https://datanotes.dev/guides/database-objects/catalogs-and-schemas/), [Hand edits](https://datanotes.dev/guides/data-quality/hand-edits/).
42
42
 
@@ -68,7 +68,7 @@ Examples: [Catalogs, schemas and the database](https://datanotes.dev/guides/data
68
68
 
69
69
  Validate every row against the table (required, types, ranges, enums, links, unknown columns, CHECK constraints, those that follow links included, filtered link targets (ref_where / ref_by), stale generated/identity values, missing inverse links, cycles of hierarchy and acyclic columns, front matter that cannot be read: code `unreadable`, with no column, for a note whose `table` line names the table but whose YAML is broken, which every other read leaves out). A CHECK violation has code `check` and the CHECK’s name as `constraint` (every other code names its `column`), as in the `violations` of list_changes. Use it to find rows edited by hand that break the schema. Without `table`, every table is checked: each row then names its `table`, `tables_checked` counts the tables checked, and a table that cannot be checked is listed in `errors` ({table, error}) while the others are. The rows with violations are paged: `limit` (default 500, at most 5000) from `offset`; `total` counts them all. (Hand edits are also checked as they happen: see `violations` in list_changes.)
70
70
 
71
- Examples: [Relations and hierarchies](https://datanotes.dev/guides/database-objects/relations/), [Hand edits](https://datanotes.dev/guides/data-quality/hand-edits/), [Find rows that break the schema](https://datanotes.dev/guides/data-quality/check-table/).
71
+ Examples: [Tables](https://datanotes.dev/guides/database-objects/tables/), [Relations and hierarchies](https://datanotes.dev/guides/database-objects/relations/), [Hand edits](https://datanotes.dev/guides/data-quality/hand-edits/), [Find rows that break the schema](https://datanotes.dev/guides/data-quality/check-table/).
72
72
 
73
73
  | Parameter | Type | Required | Default | Description |
74
74
  | --------- | --------- | -------- | ------- | -------------------------------------------- |
@@ -110,6 +110,22 @@ Examples: [Find notes by meaning](https://datanotes.dev/guides/search/semantic/)
110
110
  | `folder` | string \\ | null | | `null` |
111
111
  | `min_score` | number \\ | null | | `null` |
112
112
 
113
+ ## `search`
114
+
115
+ **Search** · read-only
116
+
117
+ Search the notes by their words and, when semantic search is on, by their meaning: `{query, results: [{path, table?, score, heading, snippet, matched_by}], meaning}`, one result per note, best first. It always works: with semantic search off it ranks by words alone (BM25: rare words count more; a word in the note’s name or properties counts more), with it on it fuses both rankings (Reciprocal Rank Fusion), so a note found only by its words or only by its meaning is still there. `matched_by` says which (`words`, `meaning`); `meaning` is `on`, `off` or `failed` (the embedding provider failed: words only, `meaning_error` says why). Query syntax as search_docs: `"a phrase"` and `+word` must appear, `-word` must not (both rankings); words in any language, case and accents ignored, no stemming. Filters: `tables` (qualified or short names), `folder`, `min_score` (the meaning ranking’s cosine floor). `limit`: default 10, max 100. Then read_note / select_rows for the details.
118
+
119
+ Examples: [Search notes by words and meaning](https://datanotes.dev/guides/search/hybrid/).
120
+
121
+ | Parameter | Type | Required | Default | Description |
122
+ | ----------- | ------------ | -------- | ------- | ----------- |
123
+ | `query` | string | yes | | |
124
+ | `limit` | integer | | `10` | |
125
+ | `tables` | string\[] \\ | null | | `null` |
126
+ | `folder` | string \\ | null | | `null` |
127
+ | `min_score` | number \\ | null | | `null` |
128
+
113
129
  ## `graph_neighbors`
114
130
 
115
131
  **Graph Neighbors** · read-only
@@ -34,7 +34,7 @@ Update exactly ONE row, addressed by its `id` (the system UUID) or its `path`; r
34
34
  * `expect`: compare-and-set — `{"status": "open"}` writes only if the row still has those values (a person may have changed it since you read it); otherwise refused, nothing written.
35
35
  * `autolink`: the text written links the notes it mentions by name, at their first mention (off with `autolink: false` or the database’s setting). `links_added` lists the links made; `ambiguous_mentions` the mentions left as text with the notes they may name: read those, then link the right one with update_row and `autolink: false`, or leave the text.
36
36
 
37
- Examples: [Columns and types](https://datanotes.dev/guides/database-objects/columns/), [Bases](https://datanotes.dev/guides/database-objects/bases/), [Relations and hierarchies](https://datanotes.dev/guides/database-objects/relations/), [CHECK constraints](https://datanotes.dev/guides/data-quality/check-constraints/), [Hand edits](https://datanotes.dev/guides/data-quality/hand-edits/), [Write and move rows](https://datanotes.dev/guides/read-and-write/write-rows/), [Automatic links](https://datanotes.dev/guides/read-and-write/automatic-links/), [Batch writes](https://datanotes.dev/guides/read-and-write/batch/), [Journal and undo](https://datanotes.dev/guides/history/journal-and-undo/), [Table history and time travel](https://datanotes.dev/guides/history/table-history/), [Change data feed](https://datanotes.dev/guides/history/change-data-feed/), [Restore a table or the database](https://datanotes.dev/guides/history/restore/).
37
+ Examples: [Tables](https://datanotes.dev/guides/database-objects/tables/), [Columns and types](https://datanotes.dev/guides/database-objects/columns/), [Bases](https://datanotes.dev/guides/database-objects/bases/), [Relations and hierarchies](https://datanotes.dev/guides/database-objects/relations/), [CHECK constraints](https://datanotes.dev/guides/data-quality/check-constraints/), [Hand edits](https://datanotes.dev/guides/data-quality/hand-edits/), [Write and move rows](https://datanotes.dev/guides/read-and-write/write-rows/), [Automatic links](https://datanotes.dev/guides/read-and-write/automatic-links/), [Batch writes](https://datanotes.dev/guides/read-and-write/batch/), [Journal and undo](https://datanotes.dev/guides/history/journal-and-undo/), [Table history and time travel](https://datanotes.dev/guides/history/table-history/), [Change data feed](https://datanotes.dev/guides/history/change-data-feed/), [Restore a table or the database](https://datanotes.dev/guides/history/restore/).
38
38
 
39
39
  | Parameter | Type | Required | Default | Description |
40
40
  | --------------- | ------------ | -------- | ------- | ----------- |
@@ -18,7 +18,7 @@ Create a table: `name`, in `catalog` and `schema` (default: the database’s def
18
18
  * The table’s options, as alter_table names them: `aliases` (how agents find it, with the description), `partition`, `exclude`, `archive`, `history` (keep its versions; default: the database’s setting), `autolink_target` (true / false: may automatic links link its rows? default: yes, unless its rows are named by a date or a time, as with `time_column`).
19
19
  * `row_template`: markdown body of new rows. Shared columns come from bases: `set_table_bases`.
20
20
  * `instructions`: how to fill a row, for agents (Markdown); describe_table returns them.
21
- * `tags`: key/value labels of the table, strings (`{"domain": "health"}`); list_tables and describe_table return them, alter_table changes them. Enum columns: `values` and, for each, its meaning in `value_descriptions` (`{"stated": "the user said it", …}`): describe_table shows them and a refused value lists them. Generated link columns: `generated: "link({occurred_at:YYYY-MM-DD})"` with `ref` = the days table links each row to the row of `ref` named by the template (its folder read from the schema; the target may not exist yet). Filtered link targets (link columns): `ref_where` (a `where` every target must match) and `ref_by` (a column of the row picks the target level: `{"column": "kind", "levels": ["area", "project", "workstream"]}` or `{"column": "kind", "map": {"area": null, "project": ["area"]}}`; null = top level, no link; `target_column` when the target names it differently: an enum target column must have every value the map allows, a string one takes any value). A link to no row of the table, or to the row itself, is refused too. Existing rows that break them refuse the change; `list_link_targets` lists the valid targets of a row.
21
+ * `tags`: key/value labels of the table, strings (`{"domain": "health"}`); list_tables and describe_table return them, alter_table changes them. Enum columns: `values` and, for each, its meaning in `value_descriptions` (`{"stated": "the user said it", …}`): describe_table shows them and a refused value lists them. Generated link columns: `generated: "link({occurred_at:YYYY-MM-DD})"` with `ref` = the days table links each row to the row of `ref` named by the template (its folder read from the schema; the target may not exist yet). Generated aggregates of a json column holding a list of objects (integer or number columns; avg: number): `count(feeds)`, `count(feeds.min)` (the items with a min), `sum(feeds.min)`, `avg(…)`, `min(…)`, `max(…)`, with an optional filter on one field: `count(feeds, side = "left")`, `sum(feeds.min, min > 0)` (=, !=, <, <=, >, >=; “text”, a number, true or false). Recomputed on every write; no list: count and sum 0, avg/min/max empty; a field that is not a number: empty. Filtered link targets (link columns): `ref_where` (a `where` every target must match) and `ref_by` (a column of the row picks the target level: `{"column": "kind", "levels": ["area", "project", "workstream"]}` or `{"column": "kind", "map": {"area": null, "project": ["area"]}}`; null = top level, no link; `target_column` when the target names it differently: an enum target column must have every value the map allows, a string one takes any value). A link to no row of the table, or to the row itself, is refused too. Existing rows that break them refuse the change; `list_link_targets` lists the valid targets of a row.
22
22
 
23
23
  The schema changes only through operations like this one: never edit metastore.yaml by hand (no validation, no undo).
24
24
 
@@ -65,7 +65,7 @@ Examples: [Tables](https://datanotes.dev/guides/database-objects/tables/).
65
65
 
66
66
  **Alter Table** · destructive write
67
67
 
68
- Alter a table: move it (`catalog`, `schema`), rename it (`name`), change `partition` / `filename` patterns, `description` (changes, never goes: “” is refused), `aliases`, `exclude` folders, `row_template` (“” removes it), `instructions` (how to fill a row, for agents; “” removes them), `history` (true | false | “default”), `autolink_target` (true | false | “default”: may automatic links link its rows? default: yes, unless its rows are named by a date or a time) or `tags` (key/value labels merged into the table’s, e.g. `{"domain": "health"}`; a `null` value removes that tag; `clear_tags: true` removes every tag first, so with `tags` the table keeps exactly those), `archive` (folder name inside the table folder) or `fields` (untyped fields, name → default; a `null` value removes one). Rows, archive and transaction log follow; links are updated; renamed rows keep their old name in `aliases`. A change of metadata only (tags, description, aliases, history, autolink_target, row_template, instructions, fields) rewrites the table entry alone: no row is moved or rewritten. Refused when a row cannot fill a pattern. Renaming or moving the table is refused while a CHECK that follows links, or a `ref_where` / `ref_by`, of another table reads it through a link (link targets are not rewritten): the conflicts name them. Use `dry_run` to see `planned_moves`; undo with undo_migration.
68
+ Alter a table: move it (`catalog`, `schema`), rename it (`name`), change `partition` / `filename` patterns, `description` (changes, never goes: “” is refused), `aliases`, `exclude` folders, `row_template` (“” removes it), `instructions` (how to fill a row, for agents; “” removes them), `history` (true | false | “default”), `autolink_target` (true | false | “default”: may automatic links link its rows? default: yes, unless its rows are named by a date or a time) or `tags` (key/value labels merged into the table’s, e.g. `{"domain": "health"}`; a `null` value removes that tag; `clear_tags: true` removes every tag first, so with `tags` the table keeps exactly those), `archive` (folder name inside the table folder) or `fields` (untyped fields, name → default; a `null` value removes one). Rows, archive and transaction log follow; links are updated; renamed rows keep their old name in `aliases`. A change of metadata only (tags, description, aliases, history, autolink_target, row_template, instructions, fields) rewrites the table entry alone: no row is moved or rewritten. Refused when a row cannot fill a pattern. Renaming or moving the table rewrites what names it in other definitions: the `ref` and `vocabulary` of the columns of other tables and bases (lists of links included) and the tables of views (`table`, `union[].table`, `join.table`); `schema_references` lists them. CHECKs and `ref_where` / `ref_by` that read it through a link follow the new `ref`. Use `dry_run` to see `planned_moves`; undo with undo_migration.
69
69
 
70
70
  The schema changes only through operations like this one: never edit metastore.yaml by hand (no validation, no undo).
71
71
 
@@ -115,11 +115,11 @@ Examples: [Tables](https://datanotes.dev/guides/database-objects/tables/), [Base
115
115
 
116
116
  **Add Column** · write
117
117
 
118
- Add a typed column. Sparse by default (written only on rows that have a value — use for metrics, e.g. `{"name": "metric_mood", "type": "integer", "min": 1, "max": 5, "label": "Mood", "aliases": ["mood"], "description": "Mood of the day, 1 = bad, 5 = great"}`). `description` is required: what the column holds, for the agents and people who read the schema (the engine writes it for `identity` “uuid” / “created” columns). `dense: true` in the spec adds it to every row with its `default`. Call `describe_table` first: never add a column whose meaning an existing column (name/label/aliases/description) already covers. Column names are permanent identifiers; only `rename_column` changes them. Enum columns: `values` and, for each, its meaning in `value_descriptions` (`{"stated": "the user said it", …}`): describe_table shows them and a refused value lists them. Generated link columns: `generated: "link({occurred_at:YYYY-MM-DD})"` with `ref` = the days table links each row to the row of `ref` named by the template (its folder read from the schema; the target may not exist yet). Filtered link targets (link columns): `ref_where` (a `where` every target must match) and `ref_by` (a column of the row picks the target level: `{"column": "kind", "levels": ["area", "project", "workstream"]}` or `{"column": "kind", "map": {"area": null, "project": ["area"]}}`; null = top level, no link; `target_column` when the target names it differently: an enum target column must have every value the map allows, a string one takes any value). A link to no row of the table, or to the row itself, is refused too. Existing rows that break them refuse the change; `list_link_targets` lists the valid targets of a row. Acyclic links: `acyclic: true` on a link (or list of links) to the table itself refuses every write that closes a cycle, on either side of an `inverse`; refused while existing rows form one (they are listed).
118
+ Add a typed column. Sparse by default (written only on rows that have a value — use for metrics, e.g. `{"name": "metric_mood", "type": "integer", "min": 1, "max": 5, "label": "Mood", "aliases": ["mood"], "description": "Mood of the day, 1 = bad, 5 = great"}`). `description` is required: what the column holds, for the agents and people who read the schema (the engine writes it for `identity` “uuid” / “created” columns). `dense: true` in the spec adds it to every row with its `default`. Call `describe_table` first: never add a column whose meaning an existing column (name/label/aliases/description) already covers. Column names are permanent identifiers; only `rename_column` changes them. Enum columns: `values` and, for each, its meaning in `value_descriptions` (`{"stated": "the user said it", …}`): describe_table shows them and a refused value lists them. Generated link columns: `generated: "link({occurred_at:YYYY-MM-DD})"` with `ref` = the days table links each row to the row of `ref` named by the template (its folder read from the schema; the target may not exist yet). Generated aggregates of a json column holding a list of objects (integer or number columns; avg: number): `count(feeds)`, `count(feeds.min)` (the items with a min), `sum(feeds.min)`, `avg(…)`, `min(…)`, `max(…)`, with an optional filter on one field: `count(feeds, side = "left")`, `sum(feeds.min, min > 0)` (=, !=, <, <=, >, >=; “text”, a number, true or false). Recomputed on every write; no list: count and sum 0, avg/min/max empty; a field that is not a number: empty. Filtered link targets (link columns): `ref_where` (a `where` every target must match) and `ref_by` (a column of the row picks the target level: `{"column": "kind", "levels": ["area", "project", "workstream"]}` or `{"column": "kind", "map": {"area": null, "project": ["area"]}}`; null = top level, no link; `target_column` when the target names it differently: an enum target column must have every value the map allows, a string one takes any value). A link to no row of the table, or to the row itself, is refused too. Existing rows that break them refuse the change; `list_link_targets` lists the valid targets of a row. Acyclic links: `acyclic: true` on a link (or list of links) to the table itself refuses every write that closes a cycle, on either side of an `inverse`; refused while existing rows form one (they are listed).
119
119
 
120
120
  The schema changes only through operations like this one: never edit metastore.yaml by hand (no validation, no undo).
121
121
 
122
- Examples: [Columns and types](https://datanotes.dev/guides/database-objects/columns/), [Relations and hierarchies](https://datanotes.dev/guides/database-objects/relations/), [Import existing notes](https://datanotes.dev/guides/read-and-write/import-notes/).
122
+ Examples: [Tables](https://datanotes.dev/guides/database-objects/tables/), [Columns and types](https://datanotes.dev/guides/database-objects/columns/), [Relations and hierarchies](https://datanotes.dev/guides/database-objects/relations/), [Import existing notes](https://datanotes.dev/guides/read-and-write/import-notes/).
123
123
 
124
124
  | Parameter | Type | Required | Default | Description |
125
125
  | --------- | ------- | -------- | ------- | ---------------------------------------------------------------------------------------------------------- |
@@ -290,7 +290,7 @@ Create (or `replace`) a view: a named select stored in metastore.yaml. Name it w
290
290
 
291
291
  The schema changes only through operations like this one: never edit metastore.yaml by hand (no validation, no undo).
292
292
 
293
- Examples: [Views](https://datanotes.dev/guides/database-objects/views/).
293
+ Examples: [Tables](https://datanotes.dev/guides/database-objects/tables/), [Views](https://datanotes.dev/guides/database-objects/views/).
294
294
 
295
295
  | Parameter | Type | Required | Default | Description |
296
296
  | ------------- | ------------ | -------- | ------- | ----------- |
@@ -2,7 +2,7 @@
2
2
 
3
3
  > Every operation of the HTTP API, offered to agents as an MCP tool of the same name, grouped by kind.
4
4
 
5
- The engine has 54 operations of five kinds, like the statements of a SQL reference. The MCP server, `datanotes mcp`, offers each operation as an MCP tool of the same name, and `engineTools()` of `datanotes/mcp` gives the same definitions to [your own MCP server](https://datanotes.dev/develop/build-on-datanotes/#build-your-own-mcp-server). `DatanotesClient.call(name, args)` runs one from code. Over HTTP each one is `POST /datanotes/operations/<name>/`, with its arguments as the JSON body: the [HTTP API reference](https://datanotes.dev/api/) lists them.
5
+ The engine has 55 operations of five kinds, like the statements of a SQL reference. The MCP server, `datanotes mcp`, offers each operation as an MCP tool of the same name, and `engineTools()` of `datanotes/mcp` gives the same definitions to [your own MCP server](https://datanotes.dev/develop/build-on-datanotes/#build-your-own-mcp-server). `DatanotesClient.call(name, args)` runs one from code. Over HTTP each one is `POST /datanotes/operations/<name>/`, with its arguments as the JSON body: the [HTTP API reference](https://datanotes.dev/api/) lists them.
6
6
 
7
7
  Next to them, `datanotes mcp` offers three tools that read these docs offline, `list_docs`, `read_docs` and `search_docs`; they aren’t operations of the engine ([Read the docs offline](https://datanotes.dev/guides/agents/docs-offline/)). `search_docs` takes required and excluded words, and becomes a hybrid search when the engine has semantic search on ([Search by words and by meaning](https://datanotes.dev/guides/agents/docs-offline/#search-by-words-and-by-meaning)).
8
8
 
@@ -16,7 +16,7 @@ Operations that define the database: tables, columns, constraints, bases and vie
16
16
  | [`drop_table`](https://datanotes.dev/reference/operations/schema/#drop_table) | Drop a table. | [Tables](https://datanotes.dev/guides/database-objects/tables/) |
17
17
  | [`alter_table`](https://datanotes.dev/reference/operations/schema/#alter_table) | Alter a table: move it (`catalog`, `schema`), rename it (`name`), change `partition` / `filename` patterns, `description` (changes, never goes: “” is refused), `aliases`, `exclude` folders, `row_template` (“” removes it), `instructions` (how to fill a row, for agents; “” removes them), `history` (true \| false \| “default”), `autolink_target` (true \| false \| “default”: may automatic links link its rows? default: yes, unless its rows are named by a date or a time) or `tags` (key/value labels merged into the table’s, e.g. | [Catalogs, schemas and the database](https://datanotes.dev/guides/database-objects/catalogs-and-schemas/), [Tables](https://datanotes.dev/guides/database-objects/tables/), [Automatic links](https://datanotes.dev/guides/read-and-write/automatic-links/), [Import existing notes](https://datanotes.dev/guides/read-and-write/import-notes/), [Table history and time travel](https://datanotes.dev/guides/history/table-history/) |
18
18
  | [`clone_table`](https://datanotes.dev/reference/operations/schema/#clone_table) | CREATE TABLE `target` CLONE `source`: same definition plus independent copies of its rows (deep clone), optionally as of a `version` / `timestamp`. | [Tables](https://datanotes.dev/guides/database-objects/tables/), [Bases](https://datanotes.dev/guides/database-objects/bases/), [Table history and time travel](https://datanotes.dev/guides/history/table-history/) |
19
- | [`add_column`](https://datanotes.dev/reference/operations/schema/#add_column) | Add a typed column. | [Columns and types](https://datanotes.dev/guides/database-objects/columns/), [Relations and hierarchies](https://datanotes.dev/guides/database-objects/relations/), [Import existing notes](https://datanotes.dev/guides/read-and-write/import-notes/) |
19
+ | [`add_column`](https://datanotes.dev/reference/operations/schema/#add_column) | Add a typed column. | [Tables](https://datanotes.dev/guides/database-objects/tables/), [Columns and types](https://datanotes.dev/guides/database-objects/columns/), [Relations and hierarchies](https://datanotes.dev/guides/database-objects/relations/), [Import existing notes](https://datanotes.dev/guides/read-and-write/import-notes/) |
20
20
  | [`rename_column`](https://datanotes.dev/reference/operations/schema/#rename_column) | Rename a column in metastore.yaml and on every row. | [Columns and types](https://datanotes.dev/guides/database-objects/columns/) |
21
21
  | [`drop_column`](https://datanotes.dev/reference/operations/schema/#drop_column) | Remove a column from the schema and every row. | [Columns and types](https://datanotes.dev/guides/database-objects/columns/) |
22
22
  | [`alter_column`](https://datanotes.dev/reference/operations/schema/#alter_column) | Change a column’s attributes (type, min/max/step, values, required, default, label, aliases, description, note, `ref_where`, `ref_by` (see list_link_targets), `acyclic` (refused while the rows already form a cycle: they are listed); `null` removes an attribute, except `description`, which changes but never goes (null or “” is refused); `dense` true/false). | [Tables](https://datanotes.dev/guides/database-objects/tables/), [Columns and types](https://datanotes.dev/guides/database-objects/columns/), [Relations and hierarchies](https://datanotes.dev/guides/database-objects/relations/) |
@@ -26,7 +26,7 @@ Operations that define the database: tables, columns, constraints, bases and vie
26
26
  | [`set_table_bases`](https://datanotes.dev/reference/operations/schema/#set_table_bases) | Set the bases a table inherits from (`bases`, in order; `[]` removes them). | [Bases](https://datanotes.dev/guides/database-objects/bases/) |
27
27
  | [`add_constraint`](https://datanotes.dev/reference/operations/schema/#add_constraint) | ALTER TABLE ADD CONSTRAINT `name` CHECK: `check` is a where filter every row must match, e.g. | [CHECK constraints](https://datanotes.dev/guides/data-quality/check-constraints/) |
28
28
  | [`drop_constraint`](https://datanotes.dev/reference/operations/schema/#drop_constraint) | Remove the CHECK constraint `name` from `table`. | [CHECK constraints](https://datanotes.dev/guides/data-quality/check-constraints/) |
29
- | [`create_view`](https://datanotes.dev/reference/operations/schema/#create_view) | Create (or `replace`) a view: a named select stored in metastore.yaml. | [Views](https://datanotes.dev/guides/database-objects/views/) |
29
+ | [`create_view`](https://datanotes.dev/reference/operations/schema/#create_view) | Create (or `replace`) a view: a named select stored in metastore.yaml. | [Tables](https://datanotes.dev/guides/database-objects/tables/), [Views](https://datanotes.dev/guides/database-objects/views/) |
30
30
  | [`drop_view`](https://datanotes.dev/reference/operations/schema/#drop_view) | Remove a view from `metastore.yaml` (undo with `undo_migration`). | [Views](https://datanotes.dev/guides/database-objects/views/) |
31
31
 
32
32
  ## Rows (DML)
@@ -36,7 +36,7 @@ Operations that write rows: insert, update, merge, delete, move, adopt and batch
36
36
  | Operation | What it does | Examples |
37
37
  | -------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
38
38
  | [`insert_row`](https://datanotes.dev/reference/operations/rows/#insert_row) | Insert a row (new note). | [Catalogs, schemas and the database](https://datanotes.dev/guides/database-objects/catalogs-and-schemas/), [Tables](https://datanotes.dev/guides/database-objects/tables/), [Columns and types](https://datanotes.dev/guides/database-objects/columns/), [Bases](https://datanotes.dev/guides/database-objects/bases/), [Relations and hierarchies](https://datanotes.dev/guides/database-objects/relations/), [Validation](https://datanotes.dev/guides/data-quality/validation/), [CHECK constraints](https://datanotes.dev/guides/data-quality/check-constraints/), [Write and move rows](https://datanotes.dev/guides/read-and-write/write-rows/), [Automatic links](https://datanotes.dev/guides/read-and-write/automatic-links/), [Batch writes](https://datanotes.dev/guides/read-and-write/batch/), [Import existing notes](https://datanotes.dev/guides/read-and-write/import-notes/), [Journal and undo](https://datanotes.dev/guides/history/journal-and-undo/), [Table history and time travel](https://datanotes.dev/guides/history/table-history/), [Change data feed](https://datanotes.dev/guides/history/change-data-feed/), [Restore a table or the database](https://datanotes.dev/guides/history/restore/) |
39
- | [`update_row`](https://datanotes.dev/reference/operations/rows/#update_row) | Update exactly ONE row, addressed by its `id` (the system UUID) or its `path`; refused when the row does not exist (or id and path disagree). | [Columns and types](https://datanotes.dev/guides/database-objects/columns/), [Bases](https://datanotes.dev/guides/database-objects/bases/), [Relations and hierarchies](https://datanotes.dev/guides/database-objects/relations/), [CHECK constraints](https://datanotes.dev/guides/data-quality/check-constraints/), [Hand edits](https://datanotes.dev/guides/data-quality/hand-edits/), [Write and move rows](https://datanotes.dev/guides/read-and-write/write-rows/), [Automatic links](https://datanotes.dev/guides/read-and-write/automatic-links/), [Batch writes](https://datanotes.dev/guides/read-and-write/batch/), [Journal and undo](https://datanotes.dev/guides/history/journal-and-undo/), [Table history and time travel](https://datanotes.dev/guides/history/table-history/), [Change data feed](https://datanotes.dev/guides/history/change-data-feed/), [Restore a table or the database](https://datanotes.dev/guides/history/restore/) |
39
+ | [`update_row`](https://datanotes.dev/reference/operations/rows/#update_row) | Update exactly ONE row, addressed by its `id` (the system UUID) or its `path`; refused when the row does not exist (or id and path disagree). | [Tables](https://datanotes.dev/guides/database-objects/tables/), [Columns and types](https://datanotes.dev/guides/database-objects/columns/), [Bases](https://datanotes.dev/guides/database-objects/bases/), [Relations and hierarchies](https://datanotes.dev/guides/database-objects/relations/), [CHECK constraints](https://datanotes.dev/guides/data-quality/check-constraints/), [Hand edits](https://datanotes.dev/guides/data-quality/hand-edits/), [Write and move rows](https://datanotes.dev/guides/read-and-write/write-rows/), [Automatic links](https://datanotes.dev/guides/read-and-write/automatic-links/), [Batch writes](https://datanotes.dev/guides/read-and-write/batch/), [Journal and undo](https://datanotes.dev/guides/history/journal-and-undo/), [Table history and time travel](https://datanotes.dev/guides/history/table-history/), [Change data feed](https://datanotes.dev/guides/history/change-data-feed/), [Restore a table or the database](https://datanotes.dev/guides/history/restore/) |
40
40
  | [`update_rows`](https://datanotes.dev/reference/operations/rows/#update_rows) | Update every row matching `where` (required: `{}` is every row). | [Write and move rows](https://datanotes.dev/guides/read-and-write/write-rows/) |
41
41
  | [`merge_rows`](https://datanotes.dev/reference/operations/rows/#merge_rows) | MERGE (upsert) `rows` into `table` by the key columns `on` (e.g. | [Write and move rows](https://datanotes.dev/guides/read-and-write/write-rows/) |
42
42
  | [`delete_rows`](https://datanotes.dev/reference/operations/rows/#delete_rows) | Delete rows matching `where`. | [Relations and hierarchies](https://datanotes.dev/guides/database-objects/relations/), [Write and move rows](https://datanotes.dev/guides/read-and-write/write-rows/), [Table history and time travel](https://datanotes.dev/guides/history/table-history/) |
@@ -58,6 +58,7 @@ Operations that read rows and notes: select_rows, link targets, text and semanti
58
58
  | [`list_folder`](https://datanotes.dev/reference/operations/queries/#list_folder) | The direct children of a vault folder: `{folder, folders, files}`, each a vault path, sorted; dot-files and dot-folders are left out. | [Catalogs, schemas and the database](https://datanotes.dev/guides/database-objects/catalogs-and-schemas/), [Tables](https://datanotes.dev/guides/database-objects/tables/), [Columns and types](https://datanotes.dev/guides/database-objects/columns/), [Write and move rows](https://datanotes.dev/guides/read-and-write/write-rows/), [Read and search notes](https://datanotes.dev/guides/search/text/) |
59
59
  | [`search_notes`](https://datanotes.dev/reference/operations/queries/#search_notes) | Plain-text search: every word of `query` must occur, case- and accent-insensitive, in the note’s path or text; there are no operators. | [Read and search notes](https://datanotes.dev/guides/search/text/) |
60
60
  | [`semantic_search`](https://datanotes.dev/reference/operations/queries/#semantic_search) | Find notes by meaning, not exact words: `{query, results: [{path, score, heading, snippet, table?}]}`, one result per note (its best passage), best first. | [Find notes by meaning](https://datanotes.dev/guides/search/semantic/) |
61
+ | [`search`](https://datanotes.dev/reference/operations/queries/#search) | Search the notes by their words and, when semantic search is on, by their meaning: `{query, results: [{path, table?, score, heading, snippet, matched_by}], meaning}`, one result per note, best first. | [Search notes by words and meaning](https://datanotes.dev/guides/search/hybrid/) |
61
62
  | [`graph_neighbors`](https://datanotes.dev/reference/operations/queries/#graph_neighbors) | Direct neighbours of notes: what they link to (`out`) and what links to them (`in`, backlinks), with counts per edge label. | [Walk the link graph](https://datanotes.dev/guides/search/link-graph/) |
62
63
  | [`graph_traverse`](https://datanotes.dev/reference/operations/queries/#graph_traverse) | Breadth-first walk over links from `from_nodes` (`{"paths": [...]}`, `{"table", "where"}` and/or `{"tables": [...]}`). | [Walk the link graph](https://datanotes.dev/guides/search/link-graph/) |
63
64
  | [`graph_path`](https://datanotes.dev/reference/operations/queries/#graph_path) | Shortest chain of links between two sets of notes (each `{"paths": [...]}`, `{"table", "where"}` and/or `{"tables": [...]}`): `found`, `length` and `path`: the steps (`from`, `label`, `direction`, `to`). | [Walk the link graph](https://datanotes.dev/guides/search/link-graph/) |
@@ -90,7 +91,7 @@ Operations that describe and check the database, configure it and maintain its i
90
91
  | [`describe_table`](https://datanotes.dev/reference/operations/auxiliary/#describe_table) | Describe a table (`catalog.schema.table` or a shorter unambiguous name): `qualified`, `name`, `catalog`, `schema`, `description`, `aliases`, `layout` (`folder`, `row_folder`: the folder pattern of new rows, `archive`), `options` (`partition`, `filename`, `archive`, `exclude`), typed `columns` with label/aliases/description and the dense flag, `untyped_fields`, `row_count`, and when set: `tags`, `bases` (the bases it inherits from, in order), `constraints` (its CHECKs, name → where), `constraint_hints` (their own hints, name → hint), `instructions` (how to fill a row: follow them before you write one), `row_template`, `history`, `autolink_target`, `fields` (their values). | [Catalogs, schemas and the database](https://datanotes.dev/guides/database-objects/catalogs-and-schemas/), [Tables](https://datanotes.dev/guides/database-objects/tables/), [Bases](https://datanotes.dev/guides/database-objects/bases/), [Relations and hierarchies](https://datanotes.dev/guides/database-objects/relations/) |
91
92
  | [`describe_database`](https://datanotes.dev/reference/operations/auxiliary/#describe_database) | Describe the database: its folder (`root`), `metastore` (`metastore.yaml`: read it freely, but it is read-only for agents and changes only through the schema operations), configuration (default catalog and schema, row partition and file-name formats, archive folder, time zone, history, automatic links), journal folder, `bases` (shared column sets and the tables using them), `catalogs` → `schemas` → `tables` (with `row_count`, `tags`, and `unreadable_count` when some rows’ front matter cannot be read) and `views`; catalogs and schemas show their `comment` (set with `comment_on`). | [Catalogs, schemas and the database](https://datanotes.dev/guides/database-objects/catalogs-and-schemas/), [Hand edits](https://datanotes.dev/guides/data-quality/hand-edits/) |
92
93
  | [`configure_database`](https://datanotes.dev/reference/operations/auxiliary/#configure_database) | Configure the database (persisted in metastore.yaml `config:`): `root` (rename the database folder; links follow; revert by renaming back), `default_catalog`, `default_schema`, `default_partition` (“” = flat) and `default_filename` (date formats for new time-based tables), `archive_folder`, `timezone` (IANA; completes date-times written without offset), `history` (transaction log for every table: true in a database the engine creates, false when `config` lacks it, as in a metastore from before 3.0.0; change it only on the user’s request), `autolink` (the text a write puts in a body links the notes it mentions; on by default). | [Catalogs, schemas and the database](https://datanotes.dev/guides/database-objects/catalogs-and-schemas/), [Tables](https://datanotes.dev/guides/database-objects/tables/), [Automatic links](https://datanotes.dev/guides/read-and-write/automatic-links/), [Table history and time travel](https://datanotes.dev/guides/history/table-history/) |
93
- | [`check_table`](https://datanotes.dev/reference/operations/auxiliary/#check_table) | Validate every row against the table (required, types, ranges, enums, links, unknown columns, CHECK constraints, those that follow links included, filtered link targets (ref_where / ref_by), stale generated/identity values, missing inverse links, cycles of hierarchy and acyclic columns, front matter that cannot be read: code `unreadable`, with no column, for a note whose `table` line names the table but whose YAML is broken, which every other read leaves out). | [Relations and hierarchies](https://datanotes.dev/guides/database-objects/relations/), [Hand edits](https://datanotes.dev/guides/data-quality/hand-edits/), [Find rows that break the schema](https://datanotes.dev/guides/data-quality/check-table/) |
94
+ | [`check_table`](https://datanotes.dev/reference/operations/auxiliary/#check_table) | Validate every row against the table (required, types, ranges, enums, links, unknown columns, CHECK constraints, those that follow links included, filtered link targets (ref_where / ref_by), stale generated/identity values, missing inverse links, cycles of hierarchy and acyclic columns, front matter that cannot be read: code `unreadable`, with no column, for a note whose `table` line names the table but whose YAML is broken, which every other read leaves out). | [Tables](https://datanotes.dev/guides/database-objects/tables/), [Relations and hierarchies](https://datanotes.dev/guides/database-objects/relations/), [Hand edits](https://datanotes.dev/guides/data-quality/hand-edits/), [Find rows that break the schema](https://datanotes.dev/guides/data-quality/check-table/) |
94
95
  | [`infer_table`](https://datanotes.dev/reference/operations/auxiliary/#infer_table) | Read-only schema inference from notes’ front matter. | [Hand edits](https://datanotes.dev/guides/data-quality/hand-edits/), [Import existing notes](https://datanotes.dev/guides/read-and-write/import-notes/) |
95
96
  | [`describe_semantic_index`](https://datanotes.dev/reference/operations/auxiliary/#describe_semantic_index) | The semantic search index: `enabled`, `provider`, `model`, `files`, `passages`, `pending`, `running`, `last_sync`, `last_error`. | [Find notes by meaning](https://datanotes.dev/guides/search/semantic/) |
96
97
  | [`sync_semantic_index`](https://datanotes.dev/reference/operations/auxiliary/#sync_semantic_index) | Bring the semantic search index up to date: embed the notes changed since the last run (the engine also does it on its own about ten seconds after edits). | [Find notes by meaning](https://datanotes.dev/guides/search/semantic/) |
@@ -4,6 +4,65 @@
4
4
 
5
5
  This page lists each version of Datanotes, newest first. Datanotes follows [semantic versioning](https://semver.org): a major version can break what you rely on, and its notes list those changes first. The package is on [npm](https://www.npmjs.com/package/datanotes), and each version has a [GitHub release](https://github.com/davidefornelli/datanotes/releases).
6
6
 
7
+ ## 3.4.0
8
+
9
+ Datanotes 3.4.0 brings three additions:
10
+
11
+ * search the notes by words and meaning in one call
12
+ * limit what a named token reads
13
+ * count and total the items of a `json` list
14
+
15
+ It only adds to 3.3: no operation, argument or setting goes away, and a token without `read` reads every table as before.
16
+
17
+ ### Search by words and meaning
18
+
19
+ [Search notes by words and meaning](https://datanotes.dev/guides/search/hybrid/) explains how it ranks.
20
+
21
+ * **`search`**: a new operation. It ranks the notes by their words, with BM25. With semantic search on, it fuses that ranking with the meaning, with Reciprocal Rank Fusion.
22
+ * **Always on**: with semantic search off, or when the embedding provider fails, it ranks by words alone. `meaning` says which.
23
+ * **`matched_by`**: each hit says whether its words, its meaning or both found it.
24
+ * **Query syntax**: `"a phrase"`, `+word` and `-word`, as in `search_docs`. Filters: `tables`, `folder` and `min_score`.
25
+ * **Any language**: case and accents are ignored, and words aren’t stemmed.
26
+ * **Read permissions**: a token searches only the notes it may read.
27
+
28
+ ### Named tokens: `read` patterns
29
+
30
+ [Limit what a token can read](https://datanotes.dev/guides/security/tokens/#limit-what-a-token-can-read) explains the rules.
31
+
32
+ * **`read`**: a new field of `tokens/create/` and `tokens/update/`. It takes table patterns, like `write`: `catalog.schema.table`, `catalog.schema.*`, `catalog.*` or `*`. `tokens/update/` takes `null` to read every table again.
33
+ * **Refused reads**: a query, a view or a note that names a table outside `read` answers 403. The message names the table, with a hint and a docs link.
34
+ * **Filtered sets**: listings, bases, search, the graph, history and the change feed leave out the tables the token may not read.
35
+ * **Free notes**: a token with a limited `read` never reads the notes outside tables.
36
+ * **Write implies read**: every `write` pattern must fall inside `read`. A token with `ddl` or `maintain` keeps reading every table.
37
+ * **Answers of writes**: a write still checks every table. Its answer leaves out the rows the token may not read.
38
+ * **Where it shows**: `tokens/`, `tokens/me/` and `datanotes status` list `read`. A token without it lists `["*"]`.
39
+
40
+ ### Count and total the items of a json list
41
+
42
+ A `generated` column can now aggregate a `json` column that holds a list of objects. For example, a day keeps its feedings in `feeds`. The engine then keeps `feeds_count` and `left_min` up to date. [Count and total the items of a json list](https://datanotes.dev/guides/database-objects/columns/#count-and-total-the-items-of-a-json-list) shows it.
43
+
44
+ * **Five functions**: `count(feeds)`, `count(feeds.min)` for the items with a value, `sum(feeds.min)`, `avg(…)`, `min(…)` and `max(…)`.
45
+ * **A filter**: `count(feeds, side = "left")` keeps the items whose field matches. It compares one field with text, a number, `true` or `false`, using `=`, `!=`, `<`, `<=`, `>` or `>=`.
46
+ * **On every write**: the engine recomputes the value when it writes the row. After a hand edit, `check_table` reports the stale value and `recompute_columns` fixes it.
47
+ * **Checked when you define it**: the engine refuses an aggregate of a column that isn’t `json`, or a column type that doesn’t fit the result. Each refusal has its own hint and docs.
48
+ * **No wrong numbers**: with no items, `count` and `sum` are 0, and `avg`, `min` and `max` are empty. A field that isn’t a number leaves the value empty.
49
+
50
+ ## 3.3.1
51
+
52
+ Datanotes 3.3.1 fixes `alter_table` when it renames or moves a table. It only adds to 3.3: no operation, argument or setting goes away.
53
+
54
+ ### Fixes
55
+
56
+ * **References follow a moved table**: `alter_table` with `name`, `catalog` or `schema` rewrites every name of the table in the other definitions. Before, the links of other tables kept the old name, and `check_table` then reported a `ref` violation on every row. The engine now rewrites:
57
+
58
+ * the `ref` of link columns and lists of links, in tables and in bases
59
+ * the `vocabulary` of string columns
60
+ * the tables a view reads, in `table`, `union` and `join`
61
+
62
+ * **`schema_references`**: a new field of the answer lists each name rewritten, also on a dry run. `undo_migration` puts the old names back. [Rename or move a table](https://datanotes.dev/guides/database-objects/tables/#rename-or-move-a-table) shows it.
63
+
64
+ * **CHECKs and link filters**: a CHECK that follows links, or a `ref_where` / `ref_by`, that reads the table through a link no longer refuses the move. These rules name columns, not tables, so they follow the rewritten `ref`. `drop_table` still refuses while they read the table.
65
+
7
66
  ## 3.3.0
8
67
 
9
68
  Datanotes 3.3.0 installs extensions: tables, views, bases and MCP tools from a git repository, pinned to a version. It only adds to 3.2: no operation, argument or setting goes away.
@@ -156,7 +156,14 @@ The engine still checks the token on every call. A tool that writes also needs a
156
156
 
157
157
  ## Give each agent its own token
158
158
 
159
- A named token limits what an agent can do, and the journal records its name. It writes only the tables you allow in `write`, changes the schema only with `ddl`, and undoes changes only with `maintain`. Create one with the owner’s token:
159
+ A named token limits what an agent can do, and the journal records its name. It has these limits:
160
+
161
+ * **Reads**: only the tables you allow in `read`; without `read`, every table
162
+ * **Writes**: only the tables you allow in `write`
163
+ * **Schema changes**: only with `ddl`
164
+ * **Undo**: only with `maintain`
165
+
166
+ Create one with the owner’s token. This one reads and writes every table, changes the schema and may undo:
160
167
 
161
168
  ```bash
162
169
  curl -s http://127.0.0.1:27150/datanotes/tokens/create/ \
@@ -165,7 +172,7 @@ curl -s http://127.0.0.1:27150/datanotes/tokens/create/ \
165
172
  -d '{"name": "claude", "write": ["*"], "ddl": true, "maintain": true}'
166
173
  ```
167
174
 
168
- The answer contains the token, and shows it only once. [Tokens](https://datanotes.dev/guides/security/tokens/) describes every permission.
175
+ The answer contains the token, and shows it only once. [Tokens](https://datanotes.dev/guides/security/tokens/) describes every permission. To keep an agent to some tables, give it `read` patterns, as [Limit what a token can read](https://datanotes.dev/guides/security/tokens/#limit-what-a-token-can-read) shows.
169
176
 
170
177
  ## Agents and prompt injection
171
178
 
@@ -173,7 +180,7 @@ An agent reads text it didn’t write: your notes, web pages, emails and the ans
173
180
 
174
181
  * **Offer only the tools it needs.** Use `--profile read` for an agent that reads, and `--tools` for a narrow one.
175
182
  * **Keep the webhook tools for yourself.** They are off by default. Add `--owner-tools` only for an agent you ask to create a webhook.
176
- * **Give it a named token.** A [named token](#give-each-agent-its-own-token) writes only the tables you allow, and never creates webhooks.
183
+ * **Give it a named token.** A [named token](#give-each-agent-its-own-token) reads and writes only the tables you allow. It never creates webhooks.
177
184
  * **Confirm what leaves the database.** Ask your MCP client to confirm tool calls that write or send data.
178
185
  * **Check what it changed.** [See which agent made a change](#see-which-agent-made-a-change), and undo it with `undo_migration`.
179
186
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "datanotes",
3
- "version": "3.3.0",
3
+ "version": "3.4.0",
4
4
  "mcpName": "io.github.davidefornelli/datanotes",
5
5
  "description": "The engine of your second brain: Markdown notes as database tables, for you and your agents.",
6
6
  "keywords": [