things-api 0.5.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 (184) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +77 -0
  3. package/bin/things.js +9 -0
  4. package/dist/audit/log.d.ts +10 -0
  5. package/dist/audit/log.js +31 -0
  6. package/dist/audit/log.js.map +1 -0
  7. package/dist/audit/schema.d.ts +37 -0
  8. package/dist/audit/schema.js +9 -0
  9. package/dist/audit/schema.js.map +1 -0
  10. package/dist/cli/commands/doctor.d.ts +9 -0
  11. package/dist/cli/commands/doctor.js +60 -0
  12. package/dist/cli/commands/doctor.js.map +1 -0
  13. package/dist/cli/commands/mcp.d.ts +7 -0
  14. package/dist/cli/commands/mcp.js +26 -0
  15. package/dist/cli/commands/mcp.js.map +1 -0
  16. package/dist/cli/commands/project.d.ts +5 -0
  17. package/dist/cli/commands/project.js +50 -0
  18. package/dist/cli/commands/project.js.map +1 -0
  19. package/dist/cli/commands/reads.d.ts +21 -0
  20. package/dist/cli/commands/reads.js +295 -0
  21. package/dist/cli/commands/reads.js.map +1 -0
  22. package/dist/cli/commands/snapshot.d.ts +7 -0
  23. package/dist/cli/commands/snapshot.js +22 -0
  24. package/dist/cli/commands/snapshot.js.map +1 -0
  25. package/dist/cli/commands/todo.d.ts +6 -0
  26. package/dist/cli/commands/todo.js +43 -0
  27. package/dist/cli/commands/todo.js.map +1 -0
  28. package/dist/cli/commands/writes.d.ts +9 -0
  29. package/dist/cli/commands/writes.js +994 -0
  30. package/dist/cli/commands/writes.js.map +1 -0
  31. package/dist/cli/exit-codes.d.ts +26 -0
  32. package/dist/cli/exit-codes.js +26 -0
  33. package/dist/cli/exit-codes.js.map +1 -0
  34. package/dist/cli/main.d.ts +4 -0
  35. package/dist/cli/main.js +73 -0
  36. package/dist/cli/main.js.map +1 -0
  37. package/dist/cli/output.d.ts +42 -0
  38. package/dist/cli/output.js +16 -0
  39. package/dist/cli/output.js.map +1 -0
  40. package/dist/cli/style.d.ts +6 -0
  41. package/dist/cli/style.js +25 -0
  42. package/dist/cli/style.js.map +1 -0
  43. package/dist/client.d.ts +184 -0
  44. package/dist/client.js +187 -0
  45. package/dist/client.js.map +1 -0
  46. package/dist/config.d.ts +31 -0
  47. package/dist/config.js +76 -0
  48. package/dist/config.js.map +1 -0
  49. package/dist/contracts.d.ts +79 -0
  50. package/dist/contracts.js +46 -0
  51. package/dist/contracts.js.map +1 -0
  52. package/dist/db/baselines/db-v26.d.ts +8 -0
  53. package/dist/db/baselines/db-v26.js +16 -0
  54. package/dist/db/baselines/db-v26.js.map +1 -0
  55. package/dist/db/baselines/index.d.ts +3 -0
  56. package/dist/db/baselines/index.js +4 -0
  57. package/dist/db/baselines/index.js.map +1 -0
  58. package/dist/db/connection.d.ts +22 -0
  59. package/dist/db/connection.js +40 -0
  60. package/dist/db/connection.js.map +1 -0
  61. package/dist/db/fingerprint.d.ts +42 -0
  62. package/dist/db/fingerprint.js +82 -0
  63. package/dist/db/fingerprint.js.map +1 -0
  64. package/dist/db/locate.d.ts +13 -0
  65. package/dist/db/locate.js +44 -0
  66. package/dist/db/locate.js.map +1 -0
  67. package/dist/db/schema.d.ts +37 -0
  68. package/dist/db/schema.js +81 -0
  69. package/dist/db/schema.js.map +1 -0
  70. package/dist/diagnose.d.ts +67 -0
  71. package/dist/diagnose.js +143 -0
  72. package/dist/diagnose.js.map +1 -0
  73. package/dist/index.d.ts +46 -0
  74. package/dist/index.js +23 -0
  75. package/dist/index.js.map +1 -0
  76. package/dist/mcp/server.d.ts +8 -0
  77. package/dist/mcp/server.js +1002 -0
  78. package/dist/mcp/server.js.map +1 -0
  79. package/dist/model/dates.d.ts +38 -0
  80. package/dist/model/dates.js +104 -0
  81. package/dist/model/dates.js.map +1 -0
  82. package/dist/model/entities.d.ts +117 -0
  83. package/dist/model/entities.js +21 -0
  84. package/dist/model/entities.js.map +1 -0
  85. package/dist/model/mappers.d.ts +55 -0
  86. package/dist/model/mappers.js +105 -0
  87. package/dist/model/mappers.js.map +1 -0
  88. package/dist/model/occurrences.d.ts +46 -0
  89. package/dist/model/occurrences.js +164 -0
  90. package/dist/model/occurrences.js.map +1 -0
  91. package/dist/model/recurrence.d.ts +52 -0
  92. package/dist/model/recurrence.js +164 -0
  93. package/dist/model/recurrence.js.map +1 -0
  94. package/dist/paths.d.ts +6 -0
  95. package/dist/paths.js +30 -0
  96. package/dist/paths.js.map +1 -0
  97. package/dist/read/detail.d.ts +8 -0
  98. package/dist/read/detail.js +34 -0
  99. package/dist/read/detail.js.map +1 -0
  100. package/dist/read/project-view.d.ts +34 -0
  101. package/dist/read/project-view.js +87 -0
  102. package/dist/read/project-view.js.map +1 -0
  103. package/dist/read/queries.d.ts +39 -0
  104. package/dist/read/queries.js +134 -0
  105. package/dist/read/queries.js.map +1 -0
  106. package/dist/read/snapshot.d.ts +25 -0
  107. package/dist/read/snapshot.js +39 -0
  108. package/dist/read/snapshot.js.map +1 -0
  109. package/dist/read/tags.d.ts +17 -0
  110. package/dist/read/tags.js +64 -0
  111. package/dist/read/tags.js.map +1 -0
  112. package/dist/read/views.d.ts +152 -0
  113. package/dist/read/views.js +322 -0
  114. package/dist/read/views.js.map +1 -0
  115. package/dist/surface-copy.d.ts +15 -0
  116. package/dist/surface-copy.js +16 -0
  117. package/dist/surface-copy.js.map +1 -0
  118. package/dist/write/automation-probe.d.ts +12 -0
  119. package/dist/write/automation-probe.js +59 -0
  120. package/dist/write/automation-probe.js.map +1 -0
  121. package/dist/write/batch.d.ts +51 -0
  122. package/dist/write/batch.js +97 -0
  123. package/dist/write/batch.js.map +1 -0
  124. package/dist/write/capabilities.d.ts +16 -0
  125. package/dist/write/capabilities.js +16 -0
  126. package/dist/write/capabilities.js.map +1 -0
  127. package/dist/write/commands.d.ts +32 -0
  128. package/dist/write/commands.js +1274 -0
  129. package/dist/write/commands.js.map +1 -0
  130. package/dist/write/environment.d.ts +29 -0
  131. package/dist/write/environment.js +105 -0
  132. package/dist/write/environment.js.map +1 -0
  133. package/dist/write/experimental.d.ts +7 -0
  134. package/dist/write/experimental.js +39 -0
  135. package/dist/write/experimental.js.map +1 -0
  136. package/dist/write/failure-hints.d.ts +32 -0
  137. package/dist/write/failure-hints.js +71 -0
  138. package/dist/write/failure-hints.js.map +1 -0
  139. package/dist/write/guards.d.ts +21 -0
  140. package/dist/write/guards.js +312 -0
  141. package/dist/write/guards.js.map +1 -0
  142. package/dist/write/lock.d.ts +10 -0
  143. package/dist/write/lock.js +70 -0
  144. package/dist/write/lock.js.map +1 -0
  145. package/dist/write/operations.d.ts +262 -0
  146. package/dist/write/operations.js +34 -0
  147. package/dist/write/operations.js.map +1 -0
  148. package/dist/write/pipeline.d.ts +90 -0
  149. package/dist/write/pipeline.js +375 -0
  150. package/dist/write/pipeline.js.map +1 -0
  151. package/dist/write/planner.d.ts +40 -0
  152. package/dist/write/planner.js +57 -0
  153. package/dist/write/planner.js.map +1 -0
  154. package/dist/write/pre-state.d.ts +125 -0
  155. package/dist/write/pre-state.js +299 -0
  156. package/dist/write/pre-state.js.map +1 -0
  157. package/dist/write/reopen.d.ts +29 -0
  158. package/dist/write/reopen.js +63 -0
  159. package/dist/write/reopen.js.map +1 -0
  160. package/dist/write/reorder.d.ts +19 -0
  161. package/dist/write/reorder.js +285 -0
  162. package/dist/write/reorder.js.map +1 -0
  163. package/dist/write/undo.d.ts +53 -0
  164. package/dist/write/undo.js +703 -0
  165. package/dist/write/undo.js.map +1 -0
  166. package/dist/write/vectors/applescript.d.ts +5 -0
  167. package/dist/write/vectors/applescript.js +216 -0
  168. package/dist/write/vectors/applescript.js.map +1 -0
  169. package/dist/write/vectors/registry.d.ts +2 -0
  170. package/dist/write/vectors/registry.js +11 -0
  171. package/dist/write/vectors/registry.js.map +1 -0
  172. package/dist/write/vectors/types.d.ts +48 -0
  173. package/dist/write/vectors/types.js +2 -0
  174. package/dist/write/vectors/types.js.map +1 -0
  175. package/dist/write/vectors/url-scheme.d.ts +3 -0
  176. package/dist/write/vectors/url-scheme.js +176 -0
  177. package/dist/write/vectors/url-scheme.js.map +1 -0
  178. package/dist/write/verify/delta.d.ts +126 -0
  179. package/dist/write/verify/delta.js +281 -0
  180. package/dist/write/verify/delta.js.map +1 -0
  181. package/dist/write/verify/poller.d.ts +24 -0
  182. package/dist/write/verify/poller.js +28 -0
  183. package/dist/write/verify/poller.js.map +1 -0
  184. package/package.json +52 -0
@@ -0,0 +1,126 @@
1
+ /**
2
+ * DeltaSpec: what a mutation is expected to change, asserted against
3
+ * DECODED entities (not raw rows) so one spec serves every vector.
4
+ * Silent no-ops are first-class failures — `open` exit 0 proves nothing.
5
+ */
6
+ import type { DatabaseSync } from "node:sqlite";
7
+ import type { AnyTask, TaskType } from "../../model/entities.ts";
8
+ /** Dotted path into a decoded entity; see getField for computed paths. */
9
+ export interface FieldAssertion {
10
+ field: string;
11
+ equals: unknown;
12
+ }
13
+ export interface CreateProbe {
14
+ title: string;
15
+ type: Extract<TaskType, "to-do" | "project">;
16
+ /** Only rows created at/after this epoch-seconds instant qualify. */
17
+ sinceEpoch: number;
18
+ /**
19
+ * Alternative discovery for rows whose creationDate is intentionally
20
+ * BACKDATED (todo.add-logged): ignore sinceEpoch and instead exclude these
21
+ * pre-existing same-title uuids.
22
+ */
23
+ excludeUuids?: string[];
24
+ }
25
+ export type DeltaSpec = {
26
+ mode: "update";
27
+ uuid: string;
28
+ assert: FieldAssertion[];
29
+ } | {
30
+ mode: "create";
31
+ probe: CreateProbe;
32
+ assert: FieldAssertion[];
33
+ } | {
34
+ mode: "state";
35
+ uuid: string;
36
+ assert: FieldAssertion[];
37
+ cascade?: {
38
+ uuid: string;
39
+ assert: FieldAssertion[];
40
+ }[];
41
+ } | {
42
+ mode: "gone";
43
+ entity: "area" | "tag";
44
+ uuid: string;
45
+ }
46
+ /**
47
+ * Area/tag creation: TMArea/TMTag have no creationDate, so the probe is
48
+ * "a row with this title exists whose uuid was not present pre-write".
49
+ */
50
+ | {
51
+ mode: "entity-created";
52
+ entity: "area" | "tag";
53
+ title: string;
54
+ excludeUuids: string[];
55
+ /** For tags: expected parent uuid (null = must be root). */
56
+ parentUuid?: string | null;
57
+ } | {
58
+ mode: "trash-emptied";
59
+ }
60
+ /**
61
+ * Ordering: the given uuids must read back in strictly ascending rank on
62
+ * the named key (todayIndex for Today/Evening scopes, index elsewhere).
63
+ */
64
+ | {
65
+ mode: "ordering";
66
+ key: "index" | "todayIndex";
67
+ sequence: string[];
68
+ }
69
+ /** Area/tag property updates (TMArea/TMTag rows aren't tasks). */
70
+ | {
71
+ mode: "entity-updated";
72
+ entity: "area" | "tag";
73
+ uuid: string;
74
+ assert: FieldAssertion[];
75
+ };
76
+ /** Movement tripwires captured by the pre-read, keyed by uuid. */
77
+ export type PreModDates = Record<string, number | null>;
78
+ export interface DeltaEvaluation {
79
+ satisfied: boolean;
80
+ /** Anything at all happened (userModificationDate moved, row appeared/vanished). */
81
+ movement: boolean;
82
+ /** An ASSERTED field moved away from its pre-state (partial/contrary write). */
83
+ assertedMovement: boolean;
84
+ /** Asserted-field subset observed (best effort). */
85
+ observed: Record<string, unknown> | null;
86
+ /** For create mode: uuid of the row that satisfied the probe. */
87
+ discoveredUuid?: string;
88
+ }
89
+ export interface VerifyReader {
90
+ taskByUuid(uuid: string): AnyTask | null;
91
+ areaExists(uuid: string): boolean;
92
+ tagExists(uuid: string): boolean;
93
+ areasByTitle(title: string): {
94
+ uuid: string;
95
+ }[];
96
+ tagsByTitle(title: string): {
97
+ uuid: string;
98
+ parent: string | null;
99
+ }[];
100
+ rankOf(uuid: string, key: "index" | "todayIndex"): number | null;
101
+ trashedCount(): number;
102
+ findCreated(probe: CreateProbe): AnyTask[];
103
+ modDateOf(uuid: string): number | null;
104
+ /**
105
+ * Assertable fields of a TMArea/TMTag row: title, tags (areas, sorted
106
+ * titles), parent (tags, uuid or null), shortcut (tags). Null = row gone.
107
+ */
108
+ entityFields(entity: "area" | "tag", uuid: string): Record<string, unknown> | null;
109
+ }
110
+ export declare function createDbReader(db: DatabaseSync): VerifyReader;
111
+ /**
112
+ * Resolve an assertion path against a decoded entity. Computed paths:
113
+ * `tags` → sorted direct-tag titles; `checklistTitles` → checklist titles
114
+ * in order; otherwise a dotted walk (`area.uuid`, `project.title`, …).
115
+ */
116
+ export declare function getField(entity: AnyTask, path: string): unknown;
117
+ /**
118
+ * One verification poll: evaluate the spec against fresh reads.
119
+ * `preModDates` and `preFields` come from the pipeline's pre-read and feed
120
+ * the movement classification (timeout vs mismatch vs silent-noop).
121
+ */
122
+ export declare function evaluateDelta(spec: DeltaSpec, reader: VerifyReader, pre: {
123
+ modDates: PreModDates;
124
+ fields: Record<string, Record<string, unknown>>;
125
+ trashedCount?: number;
126
+ }): DeltaEvaluation;
@@ -0,0 +1,281 @@
1
+ import { byUuid } from "../../read/detail.js";
2
+ export function createDbReader(db) {
3
+ return {
4
+ taskByUuid: (uuid) => byUuid(db, uuid),
5
+ areaExists(uuid) {
6
+ return db.prepare("SELECT 1 FROM TMArea WHERE uuid = ?").get(uuid) !== undefined;
7
+ },
8
+ tagExists(uuid) {
9
+ return db.prepare("SELECT 1 FROM TMTag WHERE uuid = ?").get(uuid) !== undefined;
10
+ },
11
+ areasByTitle(title) {
12
+ return db.prepare("SELECT uuid FROM TMArea WHERE title = ? COLLATE NOCASE").all(title);
13
+ },
14
+ tagsByTitle(title) {
15
+ return db
16
+ .prepare("SELECT uuid, parent FROM TMTag WHERE title = ? COLLATE NOCASE")
17
+ .all(title);
18
+ },
19
+ rankOf(uuid, key) {
20
+ const column = key === "todayIndex" ? "todayIndex" : `"index"`;
21
+ const row = db.prepare(`SELECT ${column} AS rank FROM TMTask WHERE uuid = ?`).get(uuid);
22
+ return row?.rank ?? null;
23
+ },
24
+ trashedCount() {
25
+ const row = db.prepare("SELECT COUNT(*) AS n FROM TMTask WHERE trashed = 1").get();
26
+ return row.n;
27
+ },
28
+ findCreated(probe) {
29
+ const excluded = new Set(probe.excludeUuids ?? []);
30
+ const rows = db
31
+ .prepare("SELECT uuid FROM TMTask WHERE title = ? AND type = ? AND creationDate >= ? " +
32
+ "ORDER BY creationDate DESC LIMIT 25")
33
+ .all(probe.title, probe.type === "project" ? 1 : 0, probe.excludeUuids === undefined ? probe.sinceEpoch : 0).filter((r) => !excluded.has(r.uuid));
34
+ const tasks = [];
35
+ for (const r of rows) {
36
+ const task = byUuid(db, r.uuid);
37
+ if (task !== null)
38
+ tasks.push(task);
39
+ }
40
+ return tasks;
41
+ },
42
+ modDateOf(uuid) {
43
+ const row = db.prepare("SELECT userModificationDate FROM TMTask WHERE uuid = ?").get(uuid);
44
+ return row?.userModificationDate ?? null;
45
+ },
46
+ entityFields(entity, uuid) {
47
+ if (entity === "area") {
48
+ const row = db.prepare("SELECT title FROM TMArea WHERE uuid = ?").get(uuid);
49
+ if (row === undefined)
50
+ return null;
51
+ const tags = db
52
+ .prepare("SELECT t.title FROM TMAreaTag at JOIN TMTag t ON at.tags = t.uuid WHERE at.areas = ?")
53
+ .all(uuid);
54
+ return { title: row.title ?? "", tags: tags.map((t) => t.title).toSorted() };
55
+ }
56
+ const row = db
57
+ .prepare("SELECT title, parent, shortcut FROM TMTag WHERE uuid = ?")
58
+ .get(uuid);
59
+ if (row === undefined)
60
+ return null;
61
+ return { title: row.title ?? "", parent: row.parent, shortcut: row.shortcut };
62
+ },
63
+ };
64
+ }
65
+ /**
66
+ * Resolve an assertion path against a decoded entity. Computed paths:
67
+ * `tags` → sorted direct-tag titles; `checklistTitles` → checklist titles
68
+ * in order; otherwise a dotted walk (`area.uuid`, `project.title`, …).
69
+ */
70
+ export function getField(entity, path) {
71
+ // Day-precision views of the stored timestamps (backdating asserts these;
72
+ // Date objects never compare === so the raw fields are not assertable).
73
+ if (path === "stoppedDate" && "stopped" in entity) {
74
+ return entity.stopped === null ? null : localIsoDate(entity.stopped);
75
+ }
76
+ if (path === "createdDate" && "created" in entity) {
77
+ return localIsoDate(entity.created);
78
+ }
79
+ if (path === "tags" && "tags" in entity) {
80
+ return entity.tags.map((t) => t.title).toSorted();
81
+ }
82
+ if (path === "checklistTitles" && entity.type === "to-do") {
83
+ return (entity.checklist ?? []).map((c) => c.title);
84
+ }
85
+ if (path === "checklistStates" && entity.type === "to-do") {
86
+ return (entity.checklist ?? []).map((c) => c.status);
87
+ }
88
+ let current = entity;
89
+ for (const part of path.split(".")) {
90
+ if (current === null || current === undefined || typeof current !== "object")
91
+ return undefined;
92
+ current = current[part];
93
+ }
94
+ return current;
95
+ }
96
+ function localIsoDate(d) {
97
+ const p = (n) => String(n).padStart(2, "0");
98
+ return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}`;
99
+ }
100
+ function valuesEqual(a, b) {
101
+ if (Array.isArray(a) && Array.isArray(b))
102
+ return JSON.stringify(a) === JSON.stringify(b);
103
+ return a === b || (a === undefined && b === null) || (a === null && b === undefined);
104
+ }
105
+ function checkAssertions(entity, assertions) {
106
+ const observed = {};
107
+ if (entity === null)
108
+ return { pass: assertions.length === 0, observed };
109
+ let pass = true;
110
+ for (const a of assertions) {
111
+ const actual = getField(entity, a.field);
112
+ observed[a.field] = actual === undefined ? null : actual;
113
+ if (!valuesEqual(actual, a.equals))
114
+ pass = false;
115
+ }
116
+ return { pass, observed };
117
+ }
118
+ /**
119
+ * One verification poll: evaluate the spec against fresh reads.
120
+ * `preModDates` and `preFields` come from the pipeline's pre-read and feed
121
+ * the movement classification (timeout vs mismatch vs silent-noop).
122
+ */
123
+ export function evaluateDelta(spec, reader, pre) {
124
+ switch (spec.mode) {
125
+ case "update":
126
+ case "state": {
127
+ const entity = reader.taskByUuid(spec.uuid);
128
+ const { pass, observed } = checkAssertions(entity, spec.assert);
129
+ let satisfied = entity !== null && pass;
130
+ let cascadeObserved = {};
131
+ if (spec.mode === "state" && spec.cascade !== undefined) {
132
+ for (const c of spec.cascade) {
133
+ const child = reader.taskByUuid(c.uuid);
134
+ const result = checkAssertions(child, c.assert);
135
+ if (child === null || !result.pass)
136
+ satisfied = false;
137
+ for (const [k, v] of Object.entries(result.observed)) {
138
+ cascadeObserved[`${c.uuid}.${k}`] = v;
139
+ }
140
+ }
141
+ }
142
+ const movement = movedSince(spec.uuid, reader, pre);
143
+ const preFields = pre.fields[spec.uuid] ?? {};
144
+ const assertedMovement = Object.entries(observed).some(([field, value]) => field in preFields && !valuesEqual(preFields[field], value));
145
+ return {
146
+ satisfied,
147
+ movement,
148
+ assertedMovement,
149
+ observed: { ...observed, ...cascadeObserved },
150
+ };
151
+ }
152
+ case "create": {
153
+ const candidates = reader.findCreated(spec.probe);
154
+ for (const candidate of candidates) {
155
+ const { pass, observed } = checkAssertions(candidate, spec.assert);
156
+ if (pass) {
157
+ return {
158
+ satisfied: true,
159
+ movement: true,
160
+ assertedMovement: true,
161
+ observed,
162
+ discoveredUuid: candidate.uuid,
163
+ };
164
+ }
165
+ }
166
+ const nearest = candidates[0];
167
+ return {
168
+ satisfied: false,
169
+ movement: candidates.length > 0,
170
+ assertedMovement: candidates.length > 0,
171
+ observed: nearest ? checkAssertions(nearest, spec.assert).observed : null,
172
+ };
173
+ }
174
+ case "gone": {
175
+ const exists = spec.entity === "area" ? reader.areaExists(spec.uuid) : reader.tagExists(spec.uuid);
176
+ return {
177
+ satisfied: !exists,
178
+ movement: !exists,
179
+ assertedMovement: !exists,
180
+ observed: { exists },
181
+ };
182
+ }
183
+ case "entity-created": {
184
+ const rows = spec.entity === "area" ? reader.areasByTitle(spec.title) : reader.tagsByTitle(spec.title);
185
+ const fresh = rows.filter((r) => !spec.excludeUuids.includes(r.uuid));
186
+ const match = fresh.find((r) => {
187
+ if (spec.parentUuid === undefined || spec.entity === "area")
188
+ return true;
189
+ return (r.parent ?? null) === spec.parentUuid;
190
+ });
191
+ if (match !== undefined) {
192
+ return {
193
+ satisfied: true,
194
+ movement: true,
195
+ assertedMovement: true,
196
+ observed: { uuid: match.uuid, title: spec.title },
197
+ discoveredUuid: match.uuid,
198
+ };
199
+ }
200
+ return {
201
+ satisfied: false,
202
+ movement: fresh.length > 0,
203
+ assertedMovement: fresh.length > 0,
204
+ observed: null,
205
+ };
206
+ }
207
+ case "ordering": {
208
+ const ranks = spec.sequence.map((uuid) => ({ uuid, rank: reader.rankOf(uuid, spec.key) }));
209
+ const observed = {};
210
+ for (const r of ranks)
211
+ observed[r.uuid] = r.rank;
212
+ const missing = ranks.some((r) => r.rank === null);
213
+ let sorted = !missing;
214
+ for (let i = 1; i < ranks.length && sorted; i++) {
215
+ const prev = ranks[i - 1]?.rank;
216
+ const curr = ranks[i]?.rank;
217
+ if (prev === null ||
218
+ prev === undefined ||
219
+ curr === null ||
220
+ curr === undefined ||
221
+ prev >= curr) {
222
+ sorted = false;
223
+ }
224
+ }
225
+ // Movement: any rank differs from the captured pre-state.
226
+ const preRanks = pre.fields["__ordering__"] ?? {};
227
+ const moved = ranks.some((r) => preRanks[r.uuid] !== undefined && preRanks[r.uuid] !== r.rank);
228
+ return {
229
+ satisfied: sorted,
230
+ movement: moved,
231
+ assertedMovement: moved,
232
+ observed,
233
+ };
234
+ }
235
+ case "entity-updated": {
236
+ const fields = reader.entityFields(spec.entity, spec.uuid);
237
+ const observed = {};
238
+ let pass = fields !== null;
239
+ for (const a of spec.assert) {
240
+ const actual = fields?.[a.field];
241
+ observed[a.field] = actual === undefined ? null : actual;
242
+ if (!valuesEqual(actual, a.equals))
243
+ pass = false;
244
+ }
245
+ // TMArea/TMTag carry no modification date: movement = any asserted
246
+ // field departed from its captured pre-value.
247
+ const preFields = pre.fields[spec.uuid] ?? {};
248
+ const moved = Object.entries(observed).some(([field, value]) => field in preFields && !valuesEqual(preFields[field], value));
249
+ return {
250
+ satisfied: pass,
251
+ movement: moved || fields === null,
252
+ assertedMovement: moved,
253
+ observed,
254
+ };
255
+ }
256
+ case "trash-emptied": {
257
+ const remaining = reader.trashedCount();
258
+ const hadTrash = (pre.trashedCount ?? 0) > 0;
259
+ return {
260
+ satisfied: remaining === 0,
261
+ movement: hadTrash ? remaining < (pre.trashedCount ?? 0) : true,
262
+ assertedMovement: remaining !== (pre.trashedCount ?? remaining),
263
+ observed: { trashedCount: remaining },
264
+ };
265
+ }
266
+ default: {
267
+ const exhaustive = spec;
268
+ throw new Error(`unknown delta mode: ${JSON.stringify(exhaustive)}`);
269
+ }
270
+ }
271
+ }
272
+ function movedSince(uuid, reader, pre) {
273
+ const now = reader.modDateOf(uuid);
274
+ const before = pre.modDates[uuid];
275
+ if (before === undefined)
276
+ return now !== null; // row appeared
277
+ if (now === null && before !== null)
278
+ return true; // row vanished
279
+ return now !== before;
280
+ }
281
+ //# sourceMappingURL=delta.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delta.js","sourceRoot":"","sources":["../../../src/write/verify/delta.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Verification polling loop. Each poll is a discrete auto-commit read (fresh
3
+ * WAL snapshot — never wrap polls in a transaction). Cadence: immediate,
4
+ * then every 100ms for 2s, then every 300ms until deadline.
5
+ *
6
+ * Deadline classification (design §4):
7
+ * satisfied → ok
8
+ * asserted field moved → mismatch (partial or contrary write)
9
+ * only tripwire moved → timeout (something happened, not what we asked)
10
+ * nothing moved → silent-noop
11
+ */
12
+ import type { DeltaEvaluation } from "./delta.ts";
13
+ export interface PollOutcome {
14
+ kind: "ok" | "timeout" | "mismatch" | "silent-noop";
15
+ attempts: number;
16
+ elapsedMs: number;
17
+ observed: Record<string, unknown> | null;
18
+ discoveredUuid?: string;
19
+ }
20
+ export interface PollerDeps {
21
+ now?: () => number;
22
+ sleep?: (ms: number) => Promise<void>;
23
+ }
24
+ export declare function pollUntilVerified(evaluate: () => DeltaEvaluation, timeoutMs: number, deps?: PollerDeps): Promise<PollOutcome>;
@@ -0,0 +1,28 @@
1
+ export async function pollUntilVerified(evaluate, timeoutMs, deps = {}) {
2
+ const now = deps.now ?? (() => Date.now());
3
+ const sleep = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
4
+ const started = now();
5
+ const deadline = started + timeoutMs;
6
+ let attempts = 0;
7
+ let last = null;
8
+ for (;;) {
9
+ attempts += 1;
10
+ last = evaluate();
11
+ if (last.satisfied) {
12
+ return {
13
+ kind: "ok",
14
+ attempts,
15
+ elapsedMs: now() - started,
16
+ observed: last.observed,
17
+ ...(last.discoveredUuid !== undefined && { discoveredUuid: last.discoveredUuid }),
18
+ };
19
+ }
20
+ const elapsed = now() - started;
21
+ if (now() >= deadline) {
22
+ const kind = last.assertedMovement ? "mismatch" : last.movement ? "timeout" : "silent-noop";
23
+ return { kind, attempts, elapsedMs: elapsed, observed: last.observed };
24
+ }
25
+ await sleep(elapsed < 2000 ? 100 : 300);
26
+ }
27
+ }
28
+ //# sourceMappingURL=poller.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"poller.js","sourceRoot":"","sources":["../../../src/write/verify/poller.ts"],"names":[],"mappings":"AA0BA,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,QAA+B,EAC/B,SAAiB,EACjB,OAAmB,EAAE;IAErB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;IAC3C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,CAAC,CAAC,EAAU,EAAE,EAAE,CAAC,IAAI,OAAO,CAAO,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;IAC1F,MAAM,OAAO,GAAG,GAAG,EAAE,CAAC;IACtB,MAAM,QAAQ,GAAG,OAAO,GAAG,SAAS,CAAC;IACrC,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,IAAI,IAAI,GAA2B,IAAI,CAAC;IAExC,SAAS,CAAC;QACR,QAAQ,IAAI,CAAC,CAAC;QACd,IAAI,GAAG,QAAQ,EAAE,CAAC;QAClB,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YACnB,OAAO;gBACL,IAAI,EAAE,IAAI;gBACV,QAAQ;gBACR,SAAS,EAAE,GAAG,EAAE,GAAG,OAAO;gBAC1B,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,GAAG,CAAC,IAAI,CAAC,cAAc,KAAK,SAAS,IAAI,EAAE,cAAc,EAAE,IAAI,CAAC,cAAc,EAAE,CAAC;aAClF,CAAC;QACJ,CAAC;QACD,MAAM,OAAO,GAAG,GAAG,EAAE,GAAG,OAAO,CAAC;QAChC,IAAI,GAAG,EAAE,IAAI,QAAQ,EAAE,CAAC;YACtB,MAAM,IAAI,GAAG,IAAI,CAAC,gBAAgB,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,aAAa,CAAC;YAC5F,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC;QACzE,CAAC;QACD,MAAM,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IAC1C,CAAC;AACH,CAAC"}
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "things-api",
3
+ "version": "0.5.0",
4
+ "description": "Typed TypeScript library + CLI for Things 3 (Cultured Code) — verified writes, audit trail, schema-drift detection",
5
+ "license": "MIT",
6
+ "bin": {
7
+ "things": "bin/things.js"
8
+ },
9
+ "files": [
10
+ "bin",
11
+ "dist"
12
+ ],
13
+ "type": "module",
14
+ "exports": {
15
+ ".": {
16
+ "types": "./dist/index.d.ts",
17
+ "default": "./dist/index.js"
18
+ },
19
+ "./package.json": "./package.json"
20
+ },
21
+ "scripts": {
22
+ "build": "tsc -p tsconfig.build.json",
23
+ "prepublishOnly": "npm run check && npm run build",
24
+ "smoke:pack": "bash scripts/pack-smoke.sh",
25
+ "typecheck": "tsc --noEmit",
26
+ "lint": "oxlint",
27
+ "fmt": "oxfmt",
28
+ "fmt:check": "oxfmt --check",
29
+ "test": "vitest run",
30
+ "test:watch": "vitest",
31
+ "lab:run": "node lab/runner/main.ts run",
32
+ "lab:compare": "node lab/runner/main.ts compare",
33
+ "lab:gc": "node lab/runner/main.ts gc",
34
+ "lab:regress": "bash lab/scripts/regress.sh",
35
+ "check": "npm run typecheck && npm run lint && npm run fmt:check && npm run test"
36
+ },
37
+ "dependencies": {
38
+ "@modelcontextprotocol/sdk": "^1.29.0",
39
+ "commander": "^14.0.3",
40
+ "zod": "^4.4.3"
41
+ },
42
+ "devDependencies": {
43
+ "@types/node": "^24.13.2",
44
+ "oxfmt": "^0.57.0",
45
+ "oxlint": "^1.72.0",
46
+ "typescript": "^6.0.3",
47
+ "vitest": "^4.1.9"
48
+ },
49
+ "engines": {
50
+ "node": ">=24"
51
+ }
52
+ }