datanotes 3.1.0 → 3.2.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 (41) hide show
  1. package/dist/api/index.cjs +175 -8
  2. package/dist/api/index.js +175 -8
  3. package/dist/api/mcp.cjs +175 -8
  4. package/dist/api/mcp.js +175 -8
  5. package/dist/datanotes.cjs +591 -139
  6. package/dist/docs/api/operations/add_column.md +1 -0
  7. package/dist/docs/api/operations/add_constraint.md +2 -0
  8. package/dist/docs/api/operations/alter_column.md +1 -1
  9. package/dist/docs/api/operations/alter_table.md +3 -2
  10. package/dist/docs/api/operations/check_table.md +1 -1
  11. package/dist/docs/api/operations/create_table.md +3 -1
  12. package/dist/docs/api/operations/describe_table.md +1 -1
  13. package/dist/docs/api/operations/insert_row.md +2 -2
  14. package/dist/docs/api/operations/tags/rows-dml.md +1 -1
  15. package/dist/docs/api.md +2 -2
  16. package/dist/docs/guides/data-quality/check-constraints.md +14 -13
  17. package/dist/docs/guides/data-quality/hand-edits.md +10 -10
  18. package/dist/docs/guides/database-objects/bases.md +11 -11
  19. package/dist/docs/guides/database-objects/catalogs-and-schemas.md +16 -10
  20. package/dist/docs/guides/database-objects/columns.md +38 -38
  21. package/dist/docs/guides/database-objects/relations.md +173 -36
  22. package/dist/docs/guides/database-objects/tables.md +80 -35
  23. package/dist/docs/guides/database-objects/views.md +44 -44
  24. package/dist/docs/guides/history/change-data-feed.md +34 -34
  25. package/dist/docs/guides/history/journal-and-undo.md +12 -12
  26. package/dist/docs/guides/history/restore.md +20 -20
  27. package/dist/docs/guides/history/table-history.md +31 -31
  28. package/dist/docs/guides/read-and-write/automatic-links.md +15 -15
  29. package/dist/docs/guides/read-and-write/import-notes.md +21 -21
  30. package/dist/docs/guides/read-and-write/joins-and-aggregates.md +7 -7
  31. package/dist/docs/guides/read-and-write/read-and-filter.md +10 -10
  32. package/dist/docs/guides/read-and-write/write-rows.md +10 -10
  33. package/dist/docs/guides/search/text.md +1 -1
  34. package/dist/docs/reference/cli.md +1 -1
  35. package/dist/docs/reference/errors.md +1 -1
  36. package/dist/docs/reference/operations/auxiliary.md +2 -2
  37. package/dist/docs/reference/operations/rows.md +1 -1
  38. package/dist/docs/reference/operations/schema.md +15 -11
  39. package/dist/docs/reference/operations.md +28 -28
  40. package/dist/docs/release-notes.md +24 -0
  41. package/package.json +1 -1
@@ -13,6 +13,7 @@ Column names are permanent identifiers; only `rename_column` changes them.
13
13
  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.
14
14
  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).
15
15
  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.
16
+ 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).
16
17
 
17
18
  The schema changes only through operations like this one: never edit metastore.yaml by hand (no validation, no undo).
18
19
 
@@ -18,6 +18,7 @@ A write that changes a linked row re-checks the rows that link to it and is refu
18
18
  it breaks them, naming the changed row (in a batch, re-link them first: each step is checked on the vault
19
19
  the earlier ones left); the inverse links a write adds, undo_migration and restore_table are checked too.
20
20
  rename_column / rename_value follow into CHECKs; drop_column of a column a CHECK reads is refused.
21
+ `hint`: what to do when a row breaks the rule, in your words; a refused write puts it first in its hint, and each violation of the CHECK carries it.
21
22
 
22
23
  The schema changes only through operations like this one: never edit metastore.yaml by hand (no validation, no undo).
23
24
 
@@ -36,6 +37,7 @@ The schema changes only through operations like this one: never edit metastore.y
36
37
  | `table` | `string` | yes | | |
37
38
  | `name` | `string` | yes | | |
38
39
  | `check` | `object` | yes | | |
40
+ | `hint` | `string \| null` | no | `null` | What a refused write tells the agent when a row breaks this CHECK: how to fix the row. It comes first in the refusal's hint; describe_table lists it in constraint_hints. |
39
41
  | `dry_run` | `boolean` | no | `false` | |
40
42
 
41
43
  ## Responses
@@ -3,7 +3,7 @@
3
3
  `POST /datanotes/operations/alter_column/` (operation `alter_column`)
4
4
 
5
5
  Change a column's attributes (type, min/max/step, values, required, default, label,
6
- aliases, description, note, `ref_where`, `ref_by` (see list_link_targets); `null` removes an attribute, except
6
+ 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
7
7
  `description`, which changes but never goes (null or "" is refused); `dense` true/false). Refused with
8
8
  the list of violating rows when existing values do not satisfy the new definition. A
9
9
  different scale means a different column: add a new one instead of rescaling. To rename one
@@ -5,7 +5,7 @@
5
5
  Alter a table: move it (`catalog`, `schema`), rename it (`name`), change
6
6
  `partition` / `filename` patterns, `description` (changes, never goes: "" is refused), `aliases`,
7
7
  `exclude` folders, `row_template`
8
- ("" removes it), `history` (true | false | "default"), `autolink_target`
8
+ ("" removes it), `instructions` (how to fill a row, for agents; "" removes them), `history` (true | false | "default"), `autolink_target`
9
9
  (true | false | "default": may automatic links link its rows? default: yes, unless its rows are named by
10
10
  a date or a time) or `tags` (key/value
11
11
  labels merged into the table's, e.g. `{"domain": "health"}`; a `null` value removes that tag;
@@ -13,7 +13,7 @@ labels merged into the table's, e.g. `{"domain": "health"}`; a `null` value remo
13
13
  `archive` (folder name inside the table folder) or `fields` (untyped fields, name → default; a
14
14
  `null` value removes one). Rows, archive and transaction log follow; links are updated; renamed
15
15
  rows keep their old name in `aliases`. A change of metadata only (tags, description, aliases,
16
- history, autolink_target, row_template, fields) rewrites the table entry alone: no row is moved or rewritten. Refused
16
+ history, autolink_target, row_template, instructions, fields) rewrites the table entry alone: no row is moved or rewritten. Refused
17
17
  when a row cannot fill a pattern. Renaming or moving the table is refused while a CHECK that follows
18
18
  links, or a `ref_where` / `ref_by`, of another table reads it through a link (link targets are not
19
19
  rewritten): the conflicts name them. Use `dry_run` to see `planned_moves`; undo with undo_migration.
@@ -44,6 +44,7 @@ The schema changes only through operations like this one: never edit metastore.y
44
44
  | `autolink_target` | `boolean \| "default" \| null` | no | `null` | May automatic links link the table's rows? true, false, or "default" (yes, unless its rows are named by a date or a time). |
45
45
  | `exclude` | `string[] \| null` | no | `null` | |
46
46
  | `row_template` | `string \| null` | no | `null` | Markdown body of new rows; "" removes it. |
47
+ | `instructions` | `string \| null` | no | `null` | How to fill a row, for agents (Markdown); "" removes them. |
47
48
  | `tags` | `object \| null` | no | `null` | Tags to add or change (strings); a null value removes that tag. The other tags are kept. |
48
49
  | `clear_tags` | `boolean` | no | `false` | Remove every tag first (with `tags`: the table keeps exactly those). |
49
50
  | `archive` | `string \| null` | no | `null` | Archive folder: a folder name inside the table folder (e.g. "_archive"); "" = the database default (`archive_folder`). Archived rows follow. |
@@ -3,7 +3,7 @@
3
3
  `POST /datanotes/operations/check_table/` (operation `check_table`)
4
4
 
5
5
  Validate every row against the table (required, types, ranges, enums, links, unknown
6
- columns, CHECK constraints, those that follow links included, filtered link targets (ref_where / ref_by), stale generated/identity values, missing inverse links, 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.)
6
+ 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.)
7
7
 
8
8
  ## Request body
9
9
 
@@ -16,7 +16,7 @@ digits, `_` (no dots: pass the catalog and the schema as their own arguments).
16
16
  order: filters/avg use the position), ref (link target, full `catalog.schema.table` or "self"),
17
17
  unique, label, description, aliases, `generated` (e.g. `"date(occurred_at)"`, `"{name} {surname}"`),
18
18
  `identity` ("filename" | "created"), `vocabulary` (allowed values = names of another table's rows),
19
- `hierarchy: true` (one parent link to its own table), `dense` (written on every row),
19
+ `hierarchy: true` (one parent link to its own table), `acyclic: true` (links to rows of its own table that never loop back, such as tasks blocked by tasks; with `inverse`, the rule holds on both sides), `dense` (written on every row),
20
20
  `inverse` on a link column: the column of the `ref` table that links back (`friends` ↔
21
21
  `friends`, `children` ↔ `parents`, `partner` ↔ `partner`); writes keep both sides in step.
22
22
  - `time_column`: rows named by that date/datetime; otherwise `filename` (e.g. `"{name}"`).
@@ -25,6 +25,7 @@ digits, `_` (no dots: pass the catalog and the schema as their own arguments).
25
25
  database's setting), `autolink_target` (true / false: may automatic links link its rows? default:
26
26
  yes, unless its rows are named by a date or a time, as with `time_column`).
27
27
  - `row_template`: markdown body of new rows. Shared columns come from bases: `set_table_bases`.
28
+ - `instructions`: how to fill a row, for agents (Markdown); describe_table returns them.
28
29
  - `tags`: key/value labels of the table, strings (`{"domain": "health"}`); list_tables and
29
30
  describe_table return them, alter_table changes them.
30
31
  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.
@@ -59,6 +60,7 @@ The schema changes only through operations like this one: never edit metastore.y
59
60
  | `history` | `boolean \| null` | no | `null` | Keep the table's versions (time travel, restore); default: the database's setting. |
60
61
  | `autolink_target` | `boolean \| null` | no | `null` | May automatic links link the table's rows? Default: yes, unless its rows are named by a date or a time. |
61
62
  | `row_template` | `string \| null` | no | `null` | Markdown body of new rows; {column} placeholders are filled from the row. |
63
+ | `instructions` | `string \| null` | no | `null` | How to fill a row, for agents (Markdown): which columns first, what to ask the user. describe_table returns them. |
62
64
  | `fields` | `object \| null` | no | `null` | Untyped fields of the table (name → default), kept on every row; a field cannot share a name with a column. |
63
65
  | `tags` | `object \| null` | no | `null` | Key/value labels of the table (strings), e.g. {"domain": "health"}. |
64
66
  | `dry_run` | `boolean` | no | `false` | |
@@ -6,7 +6,7 @@ Describe a table (`catalog.schema.table` or a shorter unambiguous name): `qualif
6
6
  `catalog`, `schema`, `description`, `aliases`, `layout` (`folder`, `row_folder`: the folder pattern
7
7
  of new rows, `archive`), `options` (`partition`, `filename`, `archive`, `exclude`), typed `columns`
8
8
  with label/aliases/description and the dense flag, `untyped_fields`, `row_count`, and when set:
9
- `tags`, `bases` (the bases it inherits from, in order), `constraints` (its CHECKs, name → where), `row_template`, `history`,
9
+ `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`,
10
10
  `autolink_target`, `fields` (their values).
11
11
 
12
12
  Before adding a column (e.g. a new metric), read `columns` and match your concept against
@@ -1,8 +1,8 @@
1
- # Insert a row (new note). Values are validated against the columns; the note path comes from
1
+ # Insert a row (new note). When describe_table gives the table's `instructions`, fill the row as they say. Values are validated against the columns; the note path comes from
2
2
 
3
3
  `POST /datanotes/operations/insert_row/` (operation `insert_row`)
4
4
 
5
- Insert a row (new note). Values are validated against the columns; the note path comes from
5
+ Insert a row (new note). When describe_table gives the table's `instructions`, fill the row as they say. Values are validated against the columns; the note path comes from
6
6
  the table's `path`/`filename` patterns (e.g. an event logged today about last night lands in
7
7
  last night's day folder when you set `occurred_at` to last night). `body` is the note text.
8
8
  Generated / identity columns are computed (do not pass them); CHECK constraints are enforced.
@@ -4,7 +4,7 @@ Operations that write rows: insert, update, merge, delete, move, adopt and batch
4
4
 
5
5
  ## Operations
6
6
 
7
- - [Insert a row (new note). Values are validated against the columns; the note path comes from](https://datanotes.dev/api/operations/insert_row/): `POST /datanotes/operations/insert_row/`
7
+ - [Insert a row (new note). When describe_table gives the table's `instructions`, fill the row as they say. Values are validated against the columns; the note path comes from](https://datanotes.dev/api/operations/insert_row/): `POST /datanotes/operations/insert_row/`
8
8
  - [Update every row matching `where` (required: `{}` is every row). `where`: keys AND-ed; values are equality or operators](https://datanotes.dev/api/operations/update_rows/): `POST /datanotes/operations/update_rows/`
9
9
  - [Update exactly ONE row, addressed by its `id` (the system UUID) or its `path`; refused when](https://datanotes.dev/api/operations/update_row/): `POST /datanotes/operations/update_row/`
10
10
  - [Delete rows matching `where`. `mode`: "archive" (default, move to the table's archive](https://datanotes.dev/api/operations/delete_rows/): `POST /datanotes/operations/delete_rows/`
package/dist/docs/api.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Datanotes HTTP API
2
2
 
3
- > OpenAPI 3.1.0 description of the Datanotes 3.1.0 HTTP API: https://datanotes.dev/openapi.json
3
+ > OpenAPI 3.1.0 description of the Datanotes 3.2.0 HTTP API: https://datanotes.dev/openapi.json
4
4
 
5
5
  Database of Markdown notes: tables of notes, rows, queries, history, a change feed and the vault's notes.
6
6
 
@@ -47,7 +47,7 @@ Operations that define the database: tables, columns, constraints, bases and vie
47
47
 
48
48
  Operations that write rows: insert, update, merge, delete, move, adopt and batch.
49
49
 
50
- - [Insert a row (new note). Values are validated against the columns; the note path comes from](https://datanotes.dev/api/operations/insert_row/): `POST /datanotes/operations/insert_row/`
50
+ - [Insert a row (new note). When describe_table gives the table's `instructions`, fill the row as they say. Values are validated against the columns; the note path comes from](https://datanotes.dev/api/operations/insert_row/): `POST /datanotes/operations/insert_row/`
51
51
  - [Update every row matching `where` (required: `{}` is every row). `where`: keys AND-ed; values are equality or operators](https://datanotes.dev/api/operations/update_rows/): `POST /datanotes/operations/update_rows/`
52
52
  - [Update exactly ONE row, addressed by its `id` (the system UUID) or its `path`; refused when](https://datanotes.dev/api/operations/update_row/): `POST /datanotes/operations/update_row/`
53
53
  - [Delete rows matching `where`. `mode`: "archive" (default, move to the table's archive](https://datanotes.dev/api/operations/delete_rows/): `POST /datanotes/operations/delete_rows/`
@@ -6,7 +6,7 @@ A CHECK constraint is a rule written as a `where` filter, such as “a watched f
6
6
 
7
7
  ## Enforce rules with CHECK constraints
8
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`.
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`. `hint` says what to do when a row breaks the rule. The refusal puts it first, and `describe_table` lists it in `constraint_hints`.
10
10
 
11
11
  **`add_constraint`**
12
12
 
@@ -14,7 +14,8 @@ A CHECK constraint is a rule written as a `where` filter, such as “a watched f
14
14
  {
15
15
  "table": "film",
16
16
  "name": "rated_when_watched",
17
- "check": { "or": [{ "status": { "ne": "watched" } }, { "rating": { "exists": true } }] }
17
+ "check": { "or": [{ "status": { "ne": "watched" } }, { "rating": { "exists": true } }] },
18
+ "hint": "A watched film needs a rating from 1 to 5: ask me for it."
18
19
  }
19
20
  ```
20
21
 
@@ -28,7 +29,7 @@ Answer:
28
29
  "commits": [
29
30
  { "table": "diary.classics.film", "version": 8, "operation": "ADD CONSTRAINT" }
30
31
  ],
31
- "migration_id": "20261014T191534-add_constraint-add-constraint-rated_when_watc"
32
+ "migration_id": "20261019T040655-add_constraint-add-constraint-rated_when_watc"
32
33
  }
33
34
  ```
34
35
 
@@ -57,7 +58,7 @@ Refused:
57
58
  ```text
58
59
  insert_row refused — nothing was written:
59
60
  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
+ Hint: A watched film needs a rating from 1 to 5: ask me for it. 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
62
  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
  ```
63
64
 
@@ -85,7 +86,7 @@ Answer:
85
86
  "files_created": 1,
86
87
  "planned_creates": ["datanotes/diary/classics/film/His Girl Friday.md"],
87
88
  "commits": [{ "table": "diary.classics.film", "version": 10, "operation": "WRITE" }],
88
- "migration_id": "20261014T191534-insert_row-insert-into-film-2"
89
+ "migration_id": "20261019T040655-insert_row-insert-into-film-2"
89
90
  }
90
91
  ```
91
92
 
@@ -107,7 +108,7 @@ Answer:
107
108
  "commits": [
108
109
  { "table": "diary.classics.film", "version": 11, "operation": "DROP CONSTRAINT" }
109
110
  ],
110
- "migration_id": "20261014T191534-drop_constraint-drop-constraint-at_most_three_"
111
+ "migration_id": "20261019T040655-drop_constraint-drop-constraint-at_most_three_"
111
112
  }
112
113
  ```
113
114
 
@@ -148,7 +149,7 @@ Answer:
148
149
  "commits": [
149
150
  { "table": "main.public.genre", "version": 4, "operation": "ADD CONSTRAINT" }
150
151
  ],
151
- "migration_id": "20261014T191519-add_constraint-add-constraint-parent_by_level"
152
+ "migration_id": "20261019T040641-add_constraint-add-constraint-parent_by_level"
152
153
  }
153
154
  ```
154
155
 
@@ -227,34 +228,34 @@ Answer:
227
228
  "ok": true,
228
229
  "op": "batch",
229
230
  "dry_run": false,
230
- "migration_id": "20261014T191519-batch-add-the-silent-era-and-slapsti",
231
+ "migration_id": "20261019T040641-batch-add-the-silent-era-and-slapsti",
231
232
  "steps": [
232
233
  {
233
234
  "index": 0,
234
235
  "op": "insert_row",
235
236
  "ok": true,
236
- "migration_id": "20261014T191519-insert_row-insert-into-genre-4",
237
+ "migration_id": "20261019T040641-insert_row-insert-into-genre",
237
238
  "result": {
238
239
  "ok": true,
239
240
  "op": "insert_row",
240
241
  "files_created": 1,
241
242
  "planned_creates": ["datanotes/main/public/genre/Silent cinema.md"],
242
243
  "commits": [{ "table": "main.public.genre", "version": 5, "operation": "WRITE" }],
243
- "migration_id": "20261014T191519-insert_row-insert-into-genre-4"
244
+ "migration_id": "20261019T040641-insert_row-insert-into-genre"
244
245
  }
245
246
  },
246
247
  {
247
248
  "index": 1,
248
249
  "op": "insert_row",
249
250
  "ok": true,
250
- "migration_id": "20261014T191519-insert_row-insert-into-genre-5",
251
+ "migration_id": "20261019T040641-insert_row-insert-into-genre-2",
251
252
  "result": {
252
253
  "ok": true,
253
254
  "op": "insert_row",
254
255
  "files_created": 1,
255
256
  "planned_creates": ["datanotes/main/public/genre/Slapstick comedy.md"],
256
257
  "commits": [{ "table": "main.public.genre", "version": 6, "operation": "WRITE" }],
257
- "migration_id": "20261014T191519-insert_row-insert-into-genre-5"
258
+ "migration_id": "20261019T040641-insert_row-insert-into-genre-2"
258
259
  }
259
260
  }
260
261
  ]
@@ -342,7 +343,7 @@ Answer:
342
343
  }
343
344
  ],
344
345
  "commits": [{ "table": "main.public.genre", "version": 7, "operation": "UPDATE" }],
345
- "migration_id": "20261014T191519-update_row-update-genre"
346
+ "migration_id": "20261019T040641-update_row-update-genre"
346
347
  }
347
348
  ```
348
349
 
@@ -35,7 +35,7 @@ You edit `datanotes/main/public/genre/Silent film.md` by hand, outside the engin
35
35
  ```markdown
36
36
  ---
37
37
  table: main.public.genre
38
- id: 01a13bd7-2bd8-71e9-a39c-6ec373b79149
38
+ id: 01a15257-1485-7217-8806-89c6ca196427
39
39
  name: Silent film
40
40
  kind: cinema
41
41
  parent: "[[Cinema]]"
@@ -47,7 +47,7 @@ Read the hand edit’s event. `list_changes` long-polls with `wait_ms` until the
47
47
  **`list_changes`**
48
48
 
49
49
  ```json
50
- { "since": 39, "tables": ["genre"], "wait_ms": 10000 }
50
+ { "since": 45, "tables": ["genre"], "wait_ms": 10000 }
51
51
  ```
52
52
 
53
53
  Answer:
@@ -55,7 +55,7 @@ Answer:
55
55
  ```json
56
56
  [
57
57
  {
58
- "seq": 40,
58
+ "seq": 46,
59
59
  "kind": "update",
60
60
  "path": "datanotes/main/public/genre/Silent film.md",
61
61
  "source": "external",
@@ -152,7 +152,7 @@ Answer:
152
152
  }
153
153
  ],
154
154
  "commits": [{ "table": "main.public.genre", "version": 9, "operation": "UPDATE" }],
155
- "migration_id": "20261014T191521-update_row-update-genre"
155
+ "migration_id": "20261019T040642-update_row-update-genre"
156
156
  }
157
157
  ```
158
158
 
@@ -167,7 +167,7 @@ You edit `datanotes/main/public/watch/2026-10-03T2130.md` by hand, outside the e
167
167
  ```markdown
168
168
  ---
169
169
  table: main.public.watch
170
- id: 01a13bd7-2222-7352-9dea-dd1d7658c61f
170
+ id: 01a15257-0c9b-7856-9c12-bebb7eaf82e5
171
171
  film: His Girl Friday
172
172
  occurred_at: 2026-10-03T21:30
173
173
  where: cinema
@@ -226,7 +226,7 @@ Answer:
226
226
  }
227
227
  ],
228
228
  "commits": [{ "table": "main.public.watch", "version": 3, "operation": "RECOMPUTE" }],
229
- "migration_id": "20261014T191518-recompute_columns-recompute-id-day-label-of-watc"
229
+ "migration_id": "20261019T040640-recompute_columns-recompute-id-day-label-of-watc"
230
230
  }
231
231
  ```
232
232
 
@@ -292,7 +292,7 @@ Answer:
292
292
  ```markdown
293
293
  ---
294
294
  table: main.public.watch
295
- id: 01a13bd7-8a28-7d6f-843c-977d6d33e981
295
+ id: 01a15257-70f0-7ede-9186-f790fe037b07
296
296
  occurred_at: 2026-09-25T21:00
297
297
  film: "[[Charade]]"
298
298
  where: home
@@ -311,7 +311,7 @@ You edit `datanotes/main/public/film/Metropolis.md` by hand, outside the engine.
311
311
  ```markdown
312
312
  ---
313
313
  table: main.public.film
314
- id: 01a13bd7-4bf3-7a1e-b23c-2769c0dc41d3
314
+ id: 01a15257-31bb-7113-b86a-5d6fdca79420
315
315
  year: 1927
316
316
  minutes: 153
317
317
  status: to watch
@@ -384,7 +384,7 @@ You edit `datanotes/main/public/film/Sherlock Jr..md` by hand, outside the engin
384
384
  ```markdown
385
385
  ---
386
386
  table: main.public.film
387
- id: 01a13bd7-3226-73d9-ba6b-80f93c52766a
387
+ id: 01a15257-1cbb-7591-97e2-5b880ad79d39
388
388
  title: Sherlock Jr.: the projectionist
389
389
  year: 1924
390
390
  status: watched
@@ -471,7 +471,7 @@ You edit `datanotes/main/public/film/Sherlock Jr..md` by hand, outside the engin
471
471
  ```markdown
472
472
  ---
473
473
  table: main.public.film
474
- id: 01a13bd7-3226-73d9-ba6b-80f93c52766a
474
+ id: 01a15257-1cbb-7591-97e2-5b880ad79d39
475
475
  title: "Sherlock Jr.: the projectionist"
476
476
  year: 1924
477
477
  status: watched
@@ -30,7 +30,7 @@ Answer:
30
30
  "ok": true,
31
31
  "op": "define_base",
32
32
  "planned_texts": ["datanotes/metastore.yaml"],
33
- "migration_id": "20261014T191534-define_base-create-base-entry"
33
+ "migration_id": "20261019T040655-define_base-create-base-entry"
34
34
  }
35
35
  ```
36
36
 
@@ -50,7 +50,7 @@ Answer:
50
50
  "commits": [
51
51
  { "table": "diary.classics.film", "version": 6, "operation": "SET TABLE BASES" }
52
52
  ],
53
- "migration_id": "20261014T191534-set_table_bases-diary-classics-film-extends-en"
53
+ "migration_id": "20261019T040655-set_table_bases-diary-classics-film-extends-en"
54
54
  }
55
55
  ```
56
56
 
@@ -118,14 +118,14 @@ Answer:
118
118
  "rows": [
119
119
  {
120
120
  "path": "datanotes/diary/classics/actors/Max Schreck.md",
121
- "id": "01a13bd7-65fe-747a-b7cc-7c6c8d09e39d",
121
+ "id": "01a15257-4be9-775a-9d44-7c12bf38b0ed",
122
122
  "added_on": "2026-10-05",
123
123
  "note": "The face of Nosferatu",
124
124
  "_source": "diary.classics.actors"
125
125
  },
126
126
  {
127
127
  "path": "datanotes/diary/classics/film/Nosferatu.md",
128
- "id": "01a13bd7-659b-7233-a73c-4f8191e95b0e",
128
+ "id": "01a15257-4b79-7e2f-83db-94f0aef0d4ae",
129
129
  "added_on": "2026-10-05",
130
130
  "note": null,
131
131
  "_source": "diary.classics.film"
@@ -169,7 +169,7 @@ Answer:
169
169
  "commits": [
170
170
  { "table": "diary.classics.actors", "version": 3, "operation": "DEFINE BASE" }
171
171
  ],
172
- "migration_id": "20261014T191534-define_base-alter-base-entry"
172
+ "migration_id": "20261019T040655-define_base-alter-base-entry"
173
173
  }
174
174
  ```
175
175
 
@@ -246,7 +246,7 @@ Answer:
246
246
  "commits": [
247
247
  { "table": "diary.classics.actors", "version": 4, "operation": "DEFINE BASE" }
248
248
  ],
249
- "migration_id": "20261014T191534-define_base-alter-base-entry-2"
249
+ "migration_id": "20261019T040655-define_base-alter-base-entry-2"
250
250
  }
251
251
  ```
252
252
 
@@ -280,7 +280,7 @@ Answer:
280
280
  "ok": true,
281
281
  "op": "define_base",
282
282
  "planned_texts": ["datanotes/metastore.yaml"],
283
- "migration_id": "20261014T191534-define_base-create-base-credited"
283
+ "migration_id": "20261019T040655-define_base-create-base-credited"
284
284
  }
285
285
  ```
286
286
 
@@ -300,7 +300,7 @@ Answer:
300
300
  "commits": [
301
301
  { "table": "diary.classics.actors", "version": 5, "operation": "SET TABLE BASES" }
302
302
  ],
303
- "migration_id": "20261014T191534-set_table_bases-diary-classics-actors-extends--2"
303
+ "migration_id": "20261019T040655-set_table_bases-diary-classics-actors-extends--2"
304
304
  }
305
305
  ```
306
306
 
@@ -329,7 +329,7 @@ Answer:
329
329
  "ok": true,
330
330
  "op": "define_base",
331
331
  "planned_texts": ["datanotes/metastore.yaml"],
332
- "migration_id": "20261014T191534-define_base-create-base-tree"
332
+ "migration_id": "20261019T040655-define_base-create-base-tree"
333
333
  }
334
334
  ```
335
335
 
@@ -369,7 +369,7 @@ Answer:
369
369
  "commits": [
370
370
  { "table": "diary.classics.genre", "version": 1, "operation": "SET TABLE BASES" }
371
371
  ],
372
- "migration_id": "20261014T191534-set_table_bases-diary-classics-genre-extends-t"
372
+ "migration_id": "20261019T040655-set_table_bases-diary-classics-genre-extends-t"
373
373
  }
374
374
  ```
375
375
 
@@ -475,6 +475,6 @@ Answer:
475
475
  "operation": "SET TABLE BASES"
476
476
  }
477
477
  ],
478
- "migration_id": "20261014T191534-set_table_bases-diary-classics-genre_draft-ext"
478
+ "migration_id": "20261019T040655-set_table_bases-diary-classics-genre_draft-ext"
479
479
  }
480
480
  ```
@@ -63,7 +63,7 @@ Answer:
63
63
  "commits": [
64
64
  { "table": "diary.classics.film", "version": 0, "operation": "CREATE TABLE" }
65
65
  ],
66
- "migration_id": "20261014T191534-create_table-create-table-diary-classics-fi"
66
+ "migration_id": "20261019T040655-create_table-create-table-diary-classics-fi"
67
67
  }
68
68
  ```
69
69
 
@@ -91,7 +91,7 @@ Answer:
91
91
  "files_created": 1,
92
92
  "planned_creates": ["datanotes/diary/classics/film/Nosferatu.md"],
93
93
  "commits": [{ "table": "diary.classics.film", "version": 1, "operation": "WRITE" }],
94
- "migration_id": "20261014T191534-insert_row-insert-into-film"
94
+ "migration_id": "20261019T040655-insert_row-insert-into-film"
95
95
  }
96
96
  ```
97
97
 
@@ -127,7 +127,7 @@ Answer:
127
127
  "commits": [
128
128
  { "table": "diary.classics.film", "version": 3, "operation": "ALTER TABLE" }
129
129
  ],
130
- "migration_id": "20261014T191534-alter_table-alter-table-diary-classics-fil-2"
130
+ "migration_id": "20261019T040655-alter_table-alter-table-diary-classics-fil-2"
131
131
  }
132
132
  ```
133
133
 
@@ -144,7 +144,7 @@ Answer:
144
144
  "ok": true,
145
145
  "op": "comment_on",
146
146
  "planned_texts": ["datanotes/metastore.yaml"],
147
- "migration_id": "20261014T191534-comment_on-comment-on-catalog-diary"
147
+ "migration_id": "20261019T040655-comment_on-comment-on-catalog-diary"
148
148
  }
149
149
  ```
150
150
 
@@ -165,7 +165,7 @@ Answer:
165
165
  "ok": true,
166
166
  "op": "comment_on",
167
167
  "planned_texts": ["datanotes/metastore.yaml"],
168
- "migration_id": "20261014T191534-comment_on-comment-on-schema-diary-classi"
168
+ "migration_id": "20261019T040655-comment_on-comment-on-schema-diary-classi"
169
169
  }
170
170
  ```
171
171
 
@@ -173,7 +173,7 @@ Answer:
173
173
 
174
174
  ```yaml
175
175
  # Metastore (Datanotes): catalogs, schemas, tables, columns, views.
176
- id: 01a13bd7-658e-7cd5-b7f5-0b4bc7c37f45
176
+ id: 01a15257-4b68-7b8b-bf1b-44c297b63995
177
177
  config:
178
178
  history: true
179
179
 
@@ -270,7 +270,7 @@ Answer:
270
270
  "commits": [
271
271
  { "table": "diary.classics.film", "version": 4, "operation": "ALTER TABLE" }
272
272
  ],
273
- "migration_id": "20261014T191534-alter_table-alter-table-diary-classics-fil-3"
273
+ "migration_id": "20261019T040655-alter_table-alter-table-diary-classics-fil-3"
274
274
  }
275
275
  ```
276
276
 
@@ -287,7 +287,7 @@ Answer:
287
287
  "ok": true,
288
288
  "op": "comment_on",
289
289
  "planned_texts": ["datanotes/metastore.yaml"],
290
- "migration_id": "20261014T191534-comment_on-remove-the-comment-on-schema-d"
290
+ "migration_id": "20261019T040655-comment_on-remove-the-comment-on-schema-d"
291
291
  }
292
292
  ```
293
293
 
@@ -309,7 +309,7 @@ Answer:
309
309
  "commits": [
310
310
  { "table": "diary.classics.film", "version": 5, "operation": "ALTER TABLE" }
311
311
  ],
312
- "migration_id": "20261014T191534-alter_table-alter-table-diary-classics-fil-4"
312
+ "migration_id": "20261019T040655-alter_table-alter-table-diary-classics-fil-4"
313
313
  }
314
314
  ```
315
315
 
@@ -593,7 +593,7 @@ This is the database of this page as the engine wrote it. `metastore.yaml` holds
593
593
 
594
594
  ```yaml
595
595
  # Metastore (Datanotes): catalogs, schemas, tables, columns, views.
596
- id: 01a13bd7-658e-7cd5-b7f5-0b4bc7c37f45
596
+ id: 01a15257-4b68-7b8b-bf1b-44c297b63995
597
597
  config:
598
598
  history: true
599
599
  default_catalog: diary
@@ -626,6 +626,8 @@ catalogs:
626
626
  ne: watched
627
627
  - rating:
628
628
  exists: true
629
+ constraint_hints:
630
+ rated_when_watched: "A watched film needs a rating from 1 to 5: ask me for it."
629
631
  options:
630
632
  partition: "{year}"
631
633
  filename: "{title} ({year})"
@@ -777,6 +779,8 @@ catalogs:
777
779
  ne: watched
778
780
  - rating:
779
781
  exists: true
782
+ constraint_hints:
783
+ rated_when_watched: "A watched film needs a rating from 1 to 5: ask me for it."
780
784
  options:
781
785
  partition: "{year}"
782
786
  filename: "{title} ({year})"
@@ -831,6 +835,8 @@ catalogs:
831
835
  ne: watched
832
836
  - rating:
833
837
  exists: true
838
+ constraint_hints:
839
+ rated_when_watched: "A watched film needs a rating from 1 to 5: ask me for it."
834
840
  options:
835
841
  partition: "{year}"
836
842
  filename: "{title} ({year})"