@startsimpli/integrations 0.2.33 → 0.2.35

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.
@@ -127,6 +127,16 @@ export interface SourceDocument {
127
127
  scope: CorpusScope;
128
128
  /** When the connector last wrote this row. Drives the freshness claim. */
129
129
  ingestedAt?: string | null;
130
+ /**
131
+ * WHERE IT LIVES — the CONTAINER, `/Contracts`, never `/Contracts/SOW.pdf`
132
+ * (start-simpli-api#369 ships `path` with exactly that meaning).
133
+ *
134
+ * Absent on a record whose source has no notion of a folder — a meeting note,
135
+ * an email — and that absence is honest rather than a gap to be filled: those
136
+ * records live in no folder and are reachable from the top of the Library, not
137
+ * from a bucket invented for them.
138
+ */
139
+ folder?: string;
130
140
  /**
131
141
  * Attribute names the SERVER withheld from this reader
132
142
  * (bd startsim-768w.38.33). The tenant's per-field read gate strips them from
@@ -222,6 +232,34 @@ export interface SourceFreshness {
222
232
  status: 'ok' | 'stale' | 'error' | 'never';
223
233
  /** Why it is not `ok`, in the words the server used. */
224
234
  detail?: string;
235
+ /**
236
+ * WHY THE LAST SYNC FAILED, in the vendor's own words, and a DIFFERENT FACT
237
+ * FROM THE CREDENTIAL'S VERDICT (bd startsim-ei8gm.2).
238
+ *
239
+ * The Tandem Granola card read "Failing — 20 syncs in a row failed, synced
240
+ * 16d ago" for sixteen days and nothing said why. It was our own 256 KiB
241
+ * egress cap — the per-note detail response peaked at 351 KB — which is a
242
+ * minutes-long diagnosis for anyone who could see the text and sixteen days
243
+ * for anyone who could not.
244
+ *
245
+ * `check_error` COULD NOT HAVE ANSWERED IT, which is why this is its own
246
+ * field rather than a wider read of that one. `check()` and `poll()` are
247
+ * different requests: Granola's check asks for `page_size=1` and passes,
248
+ * while its poll asks for thirty notes with transcripts and aborts. The
249
+ * credential verified cleanly throughout, so a strip built on `check_error`
250
+ * alone can count twenty failures and name none of them — and it sends the
251
+ * reader to re-enter a key that was never the problem.
252
+ *
253
+ * Central sends it only while the connection's NEWEST run is the failed one
254
+ * (`SubtreeConnectionsView._last_failure_reasons`), so it is never last
255
+ * Tuesday's error quoted over a fix that has already landed. It is SCRUBBED
256
+ * server-side against that connection's own stored credential plus the
257
+ * platform app_secret — a vendor can quote our key back inside its own error
258
+ * body — and a reason that could not be scrubbed arrives as a sentence saying
259
+ * so rather than as the raw text. Nothing here re-checks it; there is exactly
260
+ * one redaction path and it is not in the browser.
261
+ */
262
+ lastError?: string;
225
263
  }
226
264
 
227
265
  /** DRF PageNumberPagination, which is what the tenant data API speaks. */
@@ -261,6 +299,72 @@ export interface DocumentQuery {
261
299
  /** ISO instants, filtering on `occurredAt`. */
262
300
  occurredAfter?: string;
263
301
  occurredBefore?: string;
302
+ /**
303
+ * THE FILES ON ONE SHELF — the documents whose container is EXACTLY this
304
+ * folder, which is what clicking a folder in a file browser means. Goes out as
305
+ * `?attr.path=<folder>`.
306
+ */
307
+ folder?: string;
308
+ /**
309
+ * THE WHOLE SUBTREE — this folder and everything beneath it. Goes out as
310
+ * `?attr.path__under=<folder>`.
311
+ *
312
+ * TWO FIELDS AND NOT ONE, AND `__startswith` IS NEITHER OF THEM. The tenant's
313
+ * `filters.subtree_bound()` spells out why: `startswith=/Contracts` also
314
+ * matches the sibling `/Contracts-old`, and `startswith=/Contracts/` misses
315
+ * the documents sitting directly IN `/Contracts`, which is most of what a
316
+ * person means by the folder. The correct bound is the UNION of the two, a
317
+ * list endpoint ANDs its filters so a caller cannot express a union, and that
318
+ * is why the server ships it as ONE parameter. Reach for this, never for a
319
+ * prefix a client assembled.
320
+ */
321
+ underFolder?: string;
322
+ page?: number;
323
+ pageSize?: number;
324
+ }
325
+
326
+ /**
327
+ * A CONTAINER IN THE CORPUS — one node of the folder tree.
328
+ *
329
+ * NOTHING MINTS ONE (bd startsim-maios). A folder is DERIVED at read time from
330
+ * the `path` attribute every file-shaped source already carries, so it has no id
331
+ * and no row: a rename re-mints nothing and orphans nothing, which is exactly
332
+ * the failure a materialised folder table would have. That is also why `path`
333
+ * below is the address — there is no other.
334
+ */
335
+ export interface CorpusFolder {
336
+ /**
337
+ * The FULL path, as the server returned it — `/Contracts/2026`, never `2026`.
338
+ *
339
+ * IT GOES STRAIGHT BACK OUT, unmodified, as the next `under=` and as
340
+ * `folder`. The client never joins segments: doing so is a small escaping bug
341
+ * in every client that tries, and the server returns the whole path precisely
342
+ * so nobody has to.
343
+ */
344
+ path: string;
345
+ /** The last segment, for display only. Derived from `path`, never sent back. */
346
+ name: string;
347
+ /**
348
+ * DESCENDANT documents, not direct children — `/Contracts` counts the ones in
349
+ * `/Contracts/2026/Q1` too. That is the number a person reading a collapsed
350
+ * folder wants, and it is why a folder can show 12 and open onto no files of
351
+ * its own.
352
+ */
353
+ count: number;
354
+ /**
355
+ * Whether anything sits BELOW this folder. The disclosure control, answered by
356
+ * the same response that listed the folder — so drawing a triangle costs no
357
+ * probe request per node.
358
+ */
359
+ hasChildren: boolean;
360
+ }
361
+
362
+ /**
363
+ * One level of the tree. `under` is a folder the server previously returned (or
364
+ * `/` for the root, which means the whole corpus).
365
+ */
366
+ export interface FolderQuery {
367
+ under: string;
264
368
  page?: number;
265
369
  pageSize?: number;
266
370
  }
@@ -275,6 +379,23 @@ export interface CorpusApi {
275
379
  listParties(query?: { search?: string; page?: number; pageSize?: number }): Promise<CorpusPage<Party>>;
276
380
  /** Per-source freshness, for the strip at the top of the Library. */
277
381
  listSources(): Promise<SourceFreshness[]>;
382
+ /**
383
+ * THE CHILD FOLDERS of one container, paginated (bd startsim-ei8gm.5).
384
+ *
385
+ * The second of the two reads a navigable tree needs: `listDocuments({folder})`
386
+ * gives the files on a shelf, this gives the shelves below it. Server-side and
387
+ * ONE LEVEL AT A TIME (CLAUDE.md rule 8) — reading every document's path and
388
+ * building the tree in the browser is the thing this exists to remove; it
389
+ * works at 247 records and expires in the low thousands.
390
+ *
391
+ * OPTIONAL, and a deployment can also HAVE it and refuse it. The tenant route
392
+ * shipped in start-simpli-api #391, so a fork built before that answers 404
393
+ * and an older one that never learned the parameters answers 400. Both mean
394
+ * the same thing to a caller — this corpus cannot enumerate folders — so the
395
+ * composer treats a failure exactly like an absent method and renders the flat
396
+ * list it rendered before. Never a half-drawn tree.
397
+ */
398
+ listFolders?(query: FolderQuery): Promise<CorpusPage<CorpusFolder>>;
278
399
  /**
279
400
  * Optional because a tenant with no payment connector has no money events, and
280
401
  * a corpus surface that requires one would fail the Dropbox-only test.