@knkcs/contenthub-ui 0.0.0-stage → 0.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.
@@ -0,0 +1,1596 @@
1
+ import { HostIdentity } from '@knkcs/anker/host';
2
+
3
+ /**
4
+ * One of a workspace's own reasons a Content may be blocked, such as "being
5
+ * revised by the author" (CONTEXT.md, Block Reason). Configuration, not
6
+ * versioned.
7
+ */
8
+ declare interface BlockReason {
9
+ id: string;
10
+ name: string;
11
+ /** ISO 8601. */
12
+ createTime: string;
13
+ /** ISO 8601. */
14
+ updateTime: string;
15
+ }
16
+
17
+ /** A content Blueprint, as the `BlueprintSource` port names it. */
18
+ declare interface BlueprintInfo {
19
+ id: string;
20
+ /** What editors call it. */
21
+ name: string;
22
+ }
23
+
24
+ /** One page of `BlueprintSource.searchBlueprints`. */
25
+ declare interface BlueprintPage {
26
+ blueprints: BlueprintInfo[];
27
+ /** Empty on the last page. */
28
+ nextPageToken: string;
29
+ }
30
+
31
+ /** A Release of a content Blueprint: what a new Content pins. */
32
+ declare interface BlueprintReleaseInfo {
33
+ id: string;
34
+ blueprintId: string;
35
+ /** The name given when it was cut, such as "2026-10". */
36
+ name: string;
37
+ /** ISO 8601: when it was cut. */
38
+ releaseTime: string;
39
+ /**
40
+ * Why contenthub refuses to pin it, as `CheckBlueprintRelease` answers;
41
+ * empty or absent when Content can pin it. A Release is offered either
42
+ * way, a refused one disabled with its reasons, so a Blueprint author sees
43
+ * why it can't be used rather than where it went.
44
+ */
45
+ refusals?: RefusalReason[];
46
+ }
47
+
48
+ /**
49
+ * The workspace's content Blueprints and their Releases, for New Content and
50
+ * the Library's Blueprint filter. contenthub serves them itself
51
+ * (`BlueprintCatalogService`, #238): blueprinthub is its direct dependency,
52
+ * not a port a host fills (ADR 0020 as amended), so every screen builds this
53
+ * from its `ContenthubClient` unless a host passes its own, which only tests
54
+ * need.
55
+ */
56
+ declare interface BlueprintSource {
57
+ /**
58
+ * Unarchived content Blueprints whose name holds `query` (every one for
59
+ * ""), by name. `signal` aborts a search the user has moved on from.
60
+ */
61
+ searchBlueprints(input: {
62
+ query: string;
63
+ pageToken?: string;
64
+ signal?: AbortSignal;
65
+ }): Promise<BlueprintPage>;
66
+ /** The Blueprints of these ids, archived ones too; an unknown id is left out. */
67
+ getBlueprints(ids: string[]): Promise<BlueprintInfo[]>;
68
+ /** Every Release of a Blueprint, newest first; empty while none has been cut. */
69
+ listReleases(blueprintId: string): Promise<BlueprintReleaseInfo[]>;
70
+ }
71
+
72
+ /** What a Branch is for: one an editor works on, or a Carry-forward link. */
73
+ declare type BranchKind = "editorial" | "carry_forward";
74
+
75
+ /** What a Chain on Dated Content, effective on a date, would be and meet (PreviewChain). */
76
+ declare interface ChainPreview {
77
+ /** The Release it would fork from; "" in a gap. */
78
+ forkReleaseId: string;
79
+ /** The later Releases it would carry forward through. */
80
+ carryForwardReleaseIds: string[];
81
+ /** Whether a final link would merge Main's head. */
82
+ mergesMain: boolean;
83
+ /** The Releases the period cuts into. */
84
+ cutIntoReleaseIds: string[];
85
+ /** The Releases the period covers entirely: planning it as it is would be refused. */
86
+ coveredReleaseIds: string[];
87
+ }
88
+
89
+ /** A Change Set as editors see it (CONTEXT.md, Change Set). */
90
+ declare interface ChangeSet {
91
+ id: string;
92
+ name: string;
93
+ /** Absent when it records none, as a one-off correction does. */
94
+ source?: ChangeSetSource;
95
+ state: ChangeSetState;
96
+ /** Who opened it: an odon user id. */
97
+ createdBy: string;
98
+ /** ISO 8601. */
99
+ createTime: string;
100
+ /** ISO 8601; absent while it is open. */
101
+ closeTime?: string;
102
+ /** In the order they were started. Empty in a list. */
103
+ chains: ChangeSetChain[];
104
+ /** The Change Sets it depends on, whatever their state. Empty in a list. */
105
+ dependsOnChangeSetIds: string[];
106
+ /**
107
+ * The slug of its status in statushub's `contenthub/change_set`
108
+ * catalogue; "" for an unknown status, which it has until someone first
109
+ * sets one. Frozen once it is closed.
110
+ */
111
+ status: string;
112
+ /** Who is working on it now: odon user ids, in the order they were set. */
113
+ assigneeIds: string[];
114
+ /**
115
+ * Its Planned Releases, proposed ones included, in the order they were
116
+ * planned. Empty in a list.
117
+ */
118
+ plannedReleases: PlannedRelease[];
119
+ }
120
+
121
+ /** One Branch of a Chain, with the head a save on it names. */
122
+ declare interface ChangeSetBranch {
123
+ id: string;
124
+ /** Its place in its Chain, from 0. */
125
+ position: number;
126
+ kind: BranchKind;
127
+ headRevisionId: string;
128
+ /**
129
+ * On a Chain on Dated Content, the date its change takes effect
130
+ * (YYYY-MM-DD): an editorial Branch's own, or the first day of the
131
+ * Release a Carry-forward link carries. Absent or "" otherwise.
132
+ */
133
+ effectiveOn?: string;
134
+ /**
135
+ * The Release it carries: the first Branch's fork Release, or the later
136
+ * Release a Carry-forward link carries. Absent or "" where it carries
137
+ * Main's head or nothing.
138
+ */
139
+ carriedReleaseId?: string;
140
+ /** Whether a Pending Merge is stored on it, which `getPendingMerge` reads. */
141
+ hasPendingMerge?: boolean;
142
+ }
143
+
144
+ /** The ordered Branches of one Content inside a Change Set. */
145
+ declare interface ChangeSetChain {
146
+ id: string;
147
+ contentId: string;
148
+ /** Born in this Change Set: it has no Main Branch until it is published. */
149
+ born: boolean;
150
+ /** By position. */
151
+ branches: ChangeSetBranch[];
152
+ }
153
+
154
+ /** One page of `listChangeSets`. */
155
+ declare interface ChangeSetList {
156
+ changeSets: ChangeSet[];
157
+ /** Empty on the last page. */
158
+ nextPageToken: string;
159
+ }
160
+
161
+ /**
162
+ * The publication that triggered a Change Set (CONTEXT.md, Source), such as
163
+ * 2026-09-20, "BGBl. 2026 I Nr. 270".
164
+ */
165
+ declare interface ChangeSetSource {
166
+ /** The day it was published, as YYYY-MM-DD. */
167
+ date: string;
168
+ /** How it is cited: one line, at most 500 characters. */
169
+ reference: string;
170
+ }
171
+
172
+ /** Where a Change Set is in its life: open, then published or discarded for good. */
173
+ declare type ChangeSetState = "open" | "published" | "discarded";
174
+
175
+ /**
176
+ * A status of statushub's `contenthub/change_set` catalogue, as a label to
177
+ * show: what the `StatusSource` port answers.
178
+ */
179
+ declare interface ChangeSetStatusInfo {
180
+ slug: string;
181
+ /** Its display name, in the workspace's words. */
182
+ name: string;
183
+ /** Its colour as statushub holds it (a CSS colour); absent for none. */
184
+ color?: string;
185
+ }
186
+
187
+ /** What `updateChangeSet` changes of a Change Set. */
188
+ declare type ChangeSetUpdateField = "name" | "source";
189
+
190
+ /** What a Check Result says. */
191
+ declare type CheckOutcome = "passed" | "failed" | "waived";
192
+
193
+ /** One recorded outcome of a Required Check, against the heads it saw. */
194
+ declare interface CheckResult {
195
+ id: string;
196
+ requiredCheckId: string;
197
+ heads: Record<string, string>;
198
+ outcome: CheckOutcome;
199
+ /** Why, for a waiver; optional otherwise. */
200
+ note: string;
201
+ /** Who recorded it: a person, or a flow's Run-as user. */
202
+ recordedBy: string;
203
+ /** ISO 8601. */
204
+ recordTime: string;
205
+ }
206
+
207
+ /** Whether a Required Check gates each Branch, or the Change Set once (ADR 0014). */
208
+ declare type CheckScope = "branch" | "change_set";
209
+
210
+ /**
211
+ * Where one Required Check stands on one thing it applies to: a Branch or a
212
+ * Branch-less Planned Release for Branch scope, the whole Change Set for
213
+ * Change Set scope.
214
+ */
215
+ declare interface CheckStanding {
216
+ check: RequiredCheck;
217
+ /** For Branch scope, its Content; "" for Change Set scope. */
218
+ contentId: string;
219
+ /** For Branch scope, the Branch, or the Branch-less Planned Release; "" otherwise. */
220
+ branchId: string;
221
+ releaseId: string;
222
+ /** What a result recorded now names. */
223
+ heads: Record<string, string>;
224
+ /** Newest first: the first is the latest, the one that counts. */
225
+ results: CheckResult[];
226
+ /** The latest result names other heads: a save since made it count for nothing. */
227
+ stale: boolean;
228
+ /** The latest result is passed or waived, and not stale. */
229
+ cleared: boolean;
230
+ }
231
+
232
+ /**
233
+ * A node of the workspace's tree for filing Content (CONTEXT.md,
234
+ * Collection). Filing is not a save and is not versioned.
235
+ */
236
+ declare interface Collection {
237
+ id: string;
238
+ name: string;
239
+ /** The Collection it sits in; "" at the root. */
240
+ parentId: string;
241
+ /** A Restricted Collection, visible only to those granted on it (ADR 0017). */
242
+ restricted: boolean;
243
+ }
244
+
245
+ /**
246
+ * A level of access to a Restricted Collection (ADR 0017): `viewer` sees the
247
+ * Content filed in it, `editor` also saves and files it, and `owner`, given to
248
+ * whoever restricted it and never granted, also manages the grants.
249
+ */
250
+ declare type CollectionAccessLevel = "viewer" | "editor" | "owner";
251
+
252
+ /** One person's access to a Restricted Collection. */
253
+ declare interface CollectionGrant {
254
+ userId: string;
255
+ level: CollectionAccessLevel;
256
+ }
257
+
258
+ /** What a Revision is compared against. */
259
+ declare type CompareBase = {
260
+ kind: "revision";
261
+ revisionId: string;
262
+ } | {
263
+ kind: "fork";
264
+ branchId: string;
265
+ } | {
266
+ kind: "main_head";
267
+ };
268
+
269
+ /** One Content as a read found it: at a Main head, a Branch head or a Release. */
270
+ declare interface Content {
271
+ id: string;
272
+ /** The Blueprint, and the Blueprint Release its Revision pins. */
273
+ blueprintId: string;
274
+ blueprintReleaseId: string;
275
+ /** The Revision read. */
276
+ revisionId: string;
277
+ /** Its data, keyed by field accessor. */
278
+ data: Record<string, unknown>;
279
+ /**
280
+ * The Change Set and Branch it was read on; "" when read on Main or at a
281
+ * Release. A search on Main finds Branch heads too, so a Main list can
282
+ * hold rows read in a Change Set.
283
+ */
284
+ changeSetId: string;
285
+ branchId: string;
286
+ /** ISO 8601: when the Revision was written. */
287
+ revisionCreateTime: string;
288
+ /** The Release it was read at, in the In Force view; "" elsewhere. */
289
+ releaseId: string;
290
+ }
291
+
292
+ /**
293
+ * A Content's unversioned attributes (ADR 0015): the same wherever it is
294
+ * read, and never part of a Revision or a Release.
295
+ */
296
+ declare interface ContentAttributes {
297
+ /** The Collections it is filed in, by id. */
298
+ collectionIds: string[];
299
+ /** Who maintains it: odon user ids, in the order they were set. */
300
+ assigneeIds: string[];
301
+ /** Absent while it is not blocked. */
302
+ block?: ContentBlock;
303
+ /** When it was archived, ISO 8601; absent while it is not Archived. */
304
+ archiveTime?: string;
305
+ }
306
+
307
+ /**
308
+ * A Content's Block (CONTEXT.md, Block): editing is closed to all but
309
+ * `allowedUserIds` until it is lifted. A Block controls editing, not seeing.
310
+ */
311
+ declare interface ContentBlock {
312
+ blockReasonId: string;
313
+ /** Who may still edit it: odon user ids; empty is nobody. */
314
+ allowedUserIds: string[];
315
+ /** The odon user who set it. */
316
+ blockedBy: string;
317
+ /** ISO 8601. */
318
+ blockTime: string;
319
+ }
320
+
321
+ /** One Content as `getContent` reads it, with its Blueprint Release and attributes. */
322
+ declare interface ContentDetail {
323
+ content: Content;
324
+ /** Absent when this build can't read the Resolved Release. */
325
+ blueprintRelease?: ResolvedBlueprintRelease;
326
+ attributes: ContentAttributes;
327
+ }
328
+
329
+ /**
330
+ * Everything contenthub-ui's screens ask contenthub, and nothing else: the
331
+ * port a host fills (ADR 0020). `createConnectContenthubClient` is the
332
+ * production implementation; `@knkcs/contenthub-ui/testing` has a stub over
333
+ * an in-memory workspace.
334
+ *
335
+ * Errors the screens act on arrive as the typed errors below, never as a
336
+ * ConnectError the screen would have to pick details out of. Anything else
337
+ * (no permission, the service down) arrives as it came.
338
+ *
339
+ * The workspace is never an argument: contenthub reads it from the caller's
340
+ * token (contenthub ADR 0017), so a host cannot ask about another workspace by
341
+ * mistake.
342
+ */
343
+ declare interface ContenthubClient {
344
+ /** The workspace's Block Reasons, by name. */
345
+ listBlockReasons(): Promise<BlockReason[]>;
346
+ /**
347
+ * Adds a Block Reason. Rejects with `BlockReasonNameTakenError` when the
348
+ * workspace already has one of that name, and `InvalidInputError` for a
349
+ * name the service refuses (blank, too long, more than one line).
350
+ */
351
+ createBlockReason(input: {
352
+ name: string;
353
+ }): Promise<BlockReason>;
354
+ /** Renames a Block Reason; its id stays. Rejects as `createBlockReason` does. */
355
+ renameBlockReason(input: {
356
+ id: string;
357
+ name: string;
358
+ }): Promise<BlockReason>;
359
+ /** Removes a Block Reason. */
360
+ deleteBlockReason(input: {
361
+ id: string;
362
+ }): Promise<void>;
363
+ /**
364
+ * Change Sets, newest first, without their Chains, dependencies or
365
+ * Planned Releases. Each filter given must hold.
366
+ */
367
+ listChangeSets(input?: {
368
+ state?: ChangeSetState;
369
+ /** Only those this odon user is assigned to. */
370
+ assigneeId?: string;
371
+ /** Only those with a Chain on this Content. */
372
+ contentId?: string;
373
+ /** At most 200; 50 when absent. */
374
+ pageSize?: number;
375
+ pageToken?: string;
376
+ }): Promise<ChangeSetList>;
377
+ /**
378
+ * Opens a Change Set. Rejects with `InvalidInputError` for a name or
379
+ * Source the service refuses.
380
+ */
381
+ createChangeSet(input: {
382
+ name: string;
383
+ source?: ChangeSetSource;
384
+ }): Promise<ChangeSet>;
385
+ /** A Change Set with its Chains, their Branches' heads and its dependencies. */
386
+ getChangeSet(id: string): Promise<ChangeSet>;
387
+ /**
388
+ * Renames an open Change Set and corrects its Source, as `updateMask`
389
+ * says: a field it doesn't name is left as it is, and a masked `source`
390
+ * that is absent clears the Source. Rejects with `InvalidInputError` for
391
+ * a name or Source the service refuses, and `ChangeSetClosedError` once
392
+ * it is published or discarded: the Source freezes at Publish.
393
+ */
394
+ updateChangeSet(input: {
395
+ id: string;
396
+ updateMask: ChangeSetUpdateField[];
397
+ name?: string;
398
+ source?: ChangeSetSource;
399
+ /** Makes a retry safe (CONTEXT.md, Idempotency Key). */
400
+ idempotencyKey?: string;
401
+ }): Promise<ChangeSet>;
402
+ /**
403
+ * Moves the status label from `expectedStatus` (the status the caller
404
+ * saw, "" for none) to `status`. Rejects with
405
+ * `StatusTransitionRefusedError` when statushub refuses the move,
406
+ * `StaleStatusError` when the status moved meanwhile,
407
+ * `ChangeSetClosedError` when the Change Set is published or discarded,
408
+ * and `ServiceUnavailableError` while statushub can't be reached.
409
+ */
410
+ setChangeSetStatus(input: {
411
+ id: string;
412
+ expectedStatus: string;
413
+ status: string;
414
+ }): Promise<ChangeSet>;
415
+ /** Replaces who is working on it: the whole new set of odon user ids. */
416
+ setChangeSetAssignees(input: {
417
+ id: string;
418
+ assigneeIds: string[];
419
+ }): Promise<ChangeSet>;
420
+ /**
421
+ * Makes the Change Set depend on another open one. Rejects with
422
+ * `InvalidInputError` for one that would close a cycle or is closed, and
423
+ * `ChangeSetClosedError` when this Change Set itself is closed.
424
+ */
425
+ addDependency(input: {
426
+ id: string;
427
+ dependsOnId: string;
428
+ }): Promise<ChangeSet>;
429
+ /** Undoes `addDependency` while the Change Set is open. */
430
+ removeDependency(input: {
431
+ id: string;
432
+ dependsOnId: string;
433
+ }): Promise<ChangeSet>;
434
+ /**
435
+ * Publish's dry run with `reviewedHeads` (Branch id to head Revision id):
436
+ * every blocker a Publish with them would be refused with now, and the
437
+ * Releases it would cut. Writes nothing. Rejects with `WithheldWorkError`
438
+ * when Publish would, for a caller who can't see all of the Change Set.
439
+ */
440
+ validatePublish(input: {
441
+ id: string;
442
+ reviewedHeads: Record<string, string>;
443
+ }): Promise<PublishReview>;
444
+ /**
445
+ * Publishes the Change Set with exactly `reviewedHeads`: every Branch at
446
+ * the head the caller reviewed. Rejects with `PublishRefusedError`
447
+ * carrying every blocker when it is refused, `ChangeSetClosedError`
448
+ * when it is closed (published with other heads, or discarded), and
449
+ * `WithheldWorkError` when it holds work the caller can't see.
450
+ */
451
+ publish(input: {
452
+ id: string;
453
+ reviewedHeads: Record<string, string>;
454
+ }): Promise<PublishOutcome>;
455
+ /**
456
+ * Merges what Main (and each dependency) gained since into every Chain
457
+ * of the Change Set. A Branch whose merge has Conflicts, or fails its
458
+ * Blueprint Release, is left with a Pending Merge, which the Branch
459
+ * editor resolves. Rejects with `ChangeSetClosedError` when it is closed,
460
+ * and, without `chainId`, `WithheldWorkError` when it holds work the
461
+ * caller can't see.
462
+ */
463
+ updateFromMain(input: {
464
+ id: string;
465
+ /** One of its Chains to update alone; every Chain when absent. */
466
+ chainId?: string;
467
+ /** Makes a retry safe (CONTEXT.md, Idempotency Key). */
468
+ idempotencyKey?: string;
469
+ }): Promise<UpdateFromMainOutcome>;
470
+ /**
471
+ * Closes the Change Set for good without publishing it. Rejects with
472
+ * `ChangeSetClosedError` when it is closed already, and
473
+ * `WithheldWorkError` when it holds work the caller can't see.
474
+ */
475
+ discardChangeSet(id: string): Promise<ChangeSet>;
476
+ /**
477
+ * Where every Required Check that applies to the Change Set stands: the
478
+ * heads a result recorded now names, its results newest first, and
479
+ * whether the latest is stale or clears it.
480
+ */
481
+ listCheckResults(changeSetId: string): Promise<CheckStanding[]>;
482
+ /**
483
+ * Records a Check Result against `heads`, the heads the recorder saw.
484
+ * Rejects with `StaleHeadError` when one of them moved,
485
+ * `CheckResultDeniedError` when the caller may not record it (an
486
+ * independent check's passed or waived from an author of what it
487
+ * covers), `InvalidInputError` for what
488
+ * the service refuses (a waiver without a note), and
489
+ * `ChangeSetClosedError` when the Change Set is closed.
490
+ */
491
+ recordCheckResult(input: {
492
+ changeSetId: string;
493
+ requiredCheckId: string;
494
+ heads: Record<string, string>;
495
+ outcome: CheckOutcome;
496
+ note: string;
497
+ }): Promise<CheckResult>;
498
+ /** The workspace's Required Checks, by name. */
499
+ listRequiredChecks(): Promise<RequiredCheck[]>;
500
+ /**
501
+ * Declares a Required Check; it gates every open Change Set at once.
502
+ * Rejects with `NameTakenError` when another check has the name, and
503
+ * `InvalidInputError` for anything else the service refuses.
504
+ */
505
+ createRequiredCheck(input: RequiredCheckInput): Promise<RequiredCheck>;
506
+ /** Replaces every field of a Required Check; its id stays. Rejects as create does. */
507
+ updateRequiredCheck(input: RequiredCheckInput & {
508
+ id: string;
509
+ }): Promise<RequiredCheck>;
510
+ /**
511
+ * Removes a Required Check: it stops gating. Its results are kept and
512
+ * count for nothing; its recorders' grants go with it. Rejects with
513
+ * `ServiceUnavailableError` while Guardian doesn't take that, and nothing
514
+ * is removed.
515
+ */
516
+ deleteRequiredCheck(input: {
517
+ id: string;
518
+ }): Promise<void>;
519
+ /**
520
+ * Who records a Required Check: every member until an admin narrows it
521
+ * (ADR 0017's 2026-10-03 amendment). Admin only: rejects with
522
+ * `AccessDeniedError` otherwise, and with `GuardianModeRequiredError` in
523
+ * `authz.mode: workspace`, where every member records every check and
524
+ * nothing narrows it.
525
+ */
526
+ listRequiredCheckRecorders(requiredCheckId: string): Promise<RequiredCheckRecorders>;
527
+ /**
528
+ * Makes a member, or every member, a recorder of a Required Check. The
529
+ * caller may grant themselves: an admin records a check only when
530
+ * granted (break-glass, announced like any grant). Rejects as
531
+ * `listRequiredCheckRecorders` does, with `InvalidInputError` for a
532
+ * grantee who is not a member, and `ServiceUnavailableError` while
533
+ * Guardian doesn't take the grant.
534
+ */
535
+ grantRequiredCheckRecorder(input: {
536
+ requiredCheckId: string;
537
+ recorder: RecorderGrant;
538
+ }): Promise<void>;
539
+ /**
540
+ * Takes recorder from a person, or from every member, which narrows the
541
+ * check to the people granted by name. Rejects as grant does.
542
+ */
543
+ revokeRequiredCheckRecorder(input: {
544
+ requiredCheckId: string;
545
+ recorder: RecorderGrant;
546
+ }): Promise<void>;
547
+ /** Every Collection of the workspace, by name, then id: a tree by `parentId`. */
548
+ listCollections(): Promise<Collection[]>;
549
+ /**
550
+ * Creates a Collection at the root or under `parentId`. Rejects with
551
+ * `NameTakenError` for a sibling of the same name, `InvalidInputError` for
552
+ * a name the service refuses, and `RestrictionUnavailableError` for a
553
+ * restricted one in workspace mode.
554
+ */
555
+ createCollection(input: {
556
+ name: string;
557
+ parentId?: string;
558
+ restricted?: boolean;
559
+ }): Promise<Collection>;
560
+ /** Renames a Collection, keeping its place and filing. Rejects as create does. */
561
+ renameCollection(input: {
562
+ id: string;
563
+ name: string;
564
+ }): Promise<Collection>;
565
+ /**
566
+ * Moves a Collection, with everything under it, under `parentId` ("" for
567
+ * the root). Rejects with `InvalidInputError` for a move under itself, and
568
+ * `NameTakenError` for a sibling of the same name there, whose
569
+ * `takenName` is "": the request carries no name.
570
+ */
571
+ moveCollection(input: {
572
+ id: string;
573
+ parentId: string;
574
+ }): Promise<Collection>;
575
+ /**
576
+ * Removes a Collection and unfiles its Content, answering the Content it
577
+ * unfiled. Rejects with `InvalidInputError` while Collections sit under it.
578
+ */
579
+ removeCollection(input: {
580
+ id: string;
581
+ }): Promise<string[]>;
582
+ /**
583
+ * Restricts a Collection (the caller becomes its owner), or opens it
584
+ * again, which discloses its Content to every viewer and so needs
585
+ * `acknowledgeDisclosure`; opening it clears its grants. Rejects with
586
+ * `RestrictionUnavailableError` in workspace mode, and
587
+ * `InvalidInputError` for an unacknowledged disclosure.
588
+ */
589
+ setCollectionRestricted(input: {
590
+ id: string;
591
+ restricted: boolean;
592
+ acknowledgeDisclosure?: boolean;
593
+ }): Promise<Collection>;
594
+ /**
595
+ * Who is granted on a Restricted Collection, its owner included. Rejects
596
+ * with `AccessDeniedError` unless the caller is its owner or an admin, and
597
+ * `RestrictionUnavailableError` in workspace mode.
598
+ */
599
+ listCollectionGrants(collectionId: string): Promise<CollectionGrant[]>;
600
+ /**
601
+ * Grants a member viewer or editor on a Restricted Collection, replacing
602
+ * what they held. The caller may grant themselves (break-glass, announced
603
+ * like any grant). Rejects as `listCollectionGrants` does, and with
604
+ * `InvalidInputError` for a grantee who is not a member or an open
605
+ * Collection.
606
+ */
607
+ grantCollectionAccess(input: {
608
+ collectionId: string;
609
+ userId: string;
610
+ level: "viewer" | "editor";
611
+ }): Promise<void>;
612
+ /** Takes a person's grant on a Restricted Collection. Rejects as grant does. */
613
+ revokeCollectionAccess(input: {
614
+ collectionId: string;
615
+ userId: string;
616
+ }): Promise<void>;
617
+ /**
618
+ * Content in one view, ordered by id (then Branch, for a search), each
619
+ * filter given holding. Rejects with `InvalidInputError` for a search in
620
+ * the In Force view, which Editor Search never covers (ADR 0019), and for
621
+ * a search the service refuses.
622
+ */
623
+ listContents(input?: {
624
+ /** Main when absent. */
625
+ view?: ContentView;
626
+ /**
627
+ * Only Content filed directly in this Collection, not in Collections
628
+ * under it.
629
+ */
630
+ collectionId?: string;
631
+ /** Editor Search: a name, number or phrase, letter for letter but ignoring case. */
632
+ search?: string;
633
+ /**
634
+ * Only Content of one of these Blueprints, filtered by the service so
635
+ * that every page but the last is full; empty or absent for every
636
+ * Blueprint. At most 100.
637
+ */
638
+ blueprintIds?: string[];
639
+ /** Only Content this user maintains: one of its assignees. */
640
+ assigneeId?: string;
641
+ /** At most 200; 50 when absent. */
642
+ pageSize?: number;
643
+ pageToken?: string;
644
+ }): Promise<ContentList>;
645
+ /**
646
+ * A page of the workspace's content Blueprints Content can be made from:
647
+ * not archived, with at least one Release, whose accessor or name holds
648
+ * `query`. contenthub asks blueprinthub as the caller (#238). Rejects
649
+ * with `ServiceUnavailableError` while blueprinthub gives no answer.
650
+ */
651
+ searchBlueprints(input: {
652
+ query: string;
653
+ pageToken?: string;
654
+ signal?: AbortSignal;
655
+ }): Promise<BlueprintPage>;
656
+ /**
657
+ * Every Release of a Blueprint, newest first, each with contenthub's
658
+ * answer: `refusals` is empty when Content can pin it. Empty for a
659
+ * Blueprint the workspace doesn't have. Rejects with
660
+ * `ServiceUnavailableError` while blueprinthub gives no answer.
661
+ */
662
+ listBlueprintReleases(blueprintId: string): Promise<BlueprintReleaseInfo[]>;
663
+ /**
664
+ * Creates a Content in an open Change Set, pinning a Blueprint Release,
665
+ * with empty data: its one Chain has one Branch, opened in the Branch
666
+ * editor. Rejects with `ChangeSetClosedError` for a closed Change Set,
667
+ * `ContentRefusedError` for a Release contenthub can't load or empty data
668
+ * the Release refuses (a required field), and `InvalidInputError` for
669
+ * anything else the service refuses.
670
+ */
671
+ createContent(input: {
672
+ changeSetId: string;
673
+ /** May be "" with `copyFrom`, to pin what the source pins. */
674
+ blueprintReleaseId: string;
675
+ /** Duplicate: the new Content's first Revision takes this source's data. */
676
+ copyFrom?: CopyFrom;
677
+ }): Promise<Content>;
678
+ /**
679
+ * Saves a new Revision on a Branch of an open Change Set, on top of
680
+ * `expectedHeadId`, the head the editor loaded (ADR 0003). With
681
+ * `fieldMask` the save is partial: each top-level field it names takes
682
+ * its value in `data`, or is removed when `data` doesn't hold it, and
683
+ * every other field is carried over from the head. Answers the Content
684
+ * as read in the Change Set afterwards.
685
+ *
686
+ * Rejects with `StaleHeadError` when the head moved (nothing is written,
687
+ * and nothing is merged: ADR 0020), `ContentRefusedError` with one reason
688
+ * per Violation the data breaks, `ContentBlockedError` or
689
+ * `ContentArchivedError` for Content closed to the caller,
690
+ * `ChangeSetClosedError` for a closed Change Set, and `InvalidInputError`
691
+ * for anything else the service refuses.
692
+ */
693
+ saveRevision(input: {
694
+ /** The Branch's Change Set, which a closed refusal names. */
695
+ changeSetId: string;
696
+ branchId: string;
697
+ expectedHeadId: string;
698
+ data: Record<string, unknown>;
699
+ /** Top-level field accessors; absent saves `data` as the whole Revision. */
700
+ fieldMask?: string[];
701
+ /**
702
+ * Makes a retry safe (CONTEXT.md, Idempotency Key): sent again with
703
+ * the same request, it answers the first save and writes nothing.
704
+ */
705
+ idempotencyKey?: string;
706
+ }): Promise<Content>;
707
+ /**
708
+ * One Content with its Blueprint Release and attributes: on Main, or
709
+ * inside `changeSetId`. Content born in an open Change Set exists only
710
+ * there, so read anywhere else it is a NotFound ConnectError.
711
+ */
712
+ getContent(input: {
713
+ id: string;
714
+ changeSetId?: string;
715
+ }): Promise<ContentDetail>;
716
+ /** Its Timeline, with the Planned Releases of open Change Sets beside it. */
717
+ getTimeline(contentId: string): Promise<ContentTimeline>;
718
+ /**
719
+ * Revisions, newest first: every one of a published Content, or those of
720
+ * one Branch (the only way to list those of Content born in an open
721
+ * Change Set).
722
+ */
723
+ listRevisions(input: {
724
+ target: {
725
+ contentId: string;
726
+ } | {
727
+ branchId: string;
728
+ };
729
+ pageSize?: number;
730
+ pageToken?: string;
731
+ }): Promise<RevisionList>;
732
+ /** How `revisionId` differs from a base, field by field. */
733
+ compare(input: {
734
+ revisionId: string;
735
+ base: CompareBase;
736
+ }): Promise<RevisionComparison>;
737
+ /** The outgoing edges of one Revision, its own only. */
738
+ listReferences(input: {
739
+ contentId: string;
740
+ revisionId: string;
741
+ }): Promise<Edge[]>;
742
+ /**
743
+ * One page of the edges pointing at a Content from Releases In Force
744
+ * today and, asked, open Branch heads: Releases first, then Change Sets.
745
+ */
746
+ listReferrers(input: {
747
+ contentId: string;
748
+ includeOpenChangeSets?: boolean;
749
+ /** 100 when absent; above 1000 reads as 1000. */
750
+ pageSize?: number;
751
+ pageToken?: string;
752
+ }): Promise<ReferrerList>;
753
+ /** The pinned edges of a Branch head or a Revision that are outdated today. */
754
+ listOutdatedPins(input: {
755
+ branchId: string;
756
+ } | {
757
+ revisionId: string;
758
+ }): Promise<OutdatedPin[]>;
759
+ /** Files a Content in exactly these Collections: no save, no Revision (ADR 0015). Answers its Collections. */
760
+ setContentCollections(input: {
761
+ contentId: string;
762
+ collectionIds: string[];
763
+ }): Promise<string[]>;
764
+ /** Replaces who maintains a Content: no save, no Revision. */
765
+ setContentAssignees(input: {
766
+ contentId: string;
767
+ assigneeIds: string[];
768
+ }): Promise<ContentAttributes>;
769
+ /** Blocks a Content, replacing any Block it has. */
770
+ blockContent(input: {
771
+ contentId: string;
772
+ blockReasonId: string;
773
+ allowedUserIds: string[];
774
+ }): Promise<ContentAttributes>;
775
+ unblockContent(contentId: string): Promise<ContentAttributes>;
776
+ /**
777
+ * Archives a published Content with no Release In Force today or later.
778
+ * Rejects with `InvalidInputError` for any other, with the service's reason.
779
+ */
780
+ archiveContent(contentId: string): Promise<ContentAttributes>;
781
+ unarchiveContent(contentId: string): Promise<ContentAttributes>;
782
+ /**
783
+ * Starts a Chain on a published Content, in `changeSetId` or in a new
784
+ * Change Set named `changeSetName` (after the Content when empty).
785
+ * Answers the Change Set and the Content as read on the Branch to edit.
786
+ * Rejects with `ContentBlockedError`, `ContentArchivedError`,
787
+ * `ChangeSetClosedError`, and `InvalidInputError` for anything else the
788
+ * service refuses (a missing or needless effective date).
789
+ */
790
+ startEditing(input: {
791
+ contentId: string;
792
+ changeSetId?: string;
793
+ /** YYYY-MM-DD: required for Dated Content, refused for undated. */
794
+ effectiveOn?: string;
795
+ changeSetName?: string;
796
+ }): Promise<{
797
+ changeSet: ChangeSet;
798
+ content: Content;
799
+ }>;
800
+ /** What a Chain on Dated Content, effective on a date, would fork from and meet. */
801
+ previewChain(input: {
802
+ changeSetId: string;
803
+ contentId: string;
804
+ effectiveOn: string;
805
+ /** The period to meet the Content's Releases with; from `effectiveOn`, open, when absent. */
806
+ validityPeriod?: ValidityPeriod;
807
+ }): Promise<ChainPreview>;
808
+ /**
809
+ * Plans the Release Publish will cut of an editorial Branch's head, with
810
+ * its Validity Period (required for Dated Content, refused for undated)
811
+ * and Release Spec values. Rejects with `ContentRefusedError` for Release
812
+ * Spec values the Blueprint Release refuses, `CoversReleaseError` when
813
+ * the period would cover a Release entirely, and as any edit does.
814
+ */
815
+ planRelease(input: {
816
+ changeSetId: string;
817
+ branchId: string;
818
+ validityPeriod?: ValidityPeriod;
819
+ releaseSpec?: Record<string, unknown>;
820
+ supersedesReleaseIds?: string[];
821
+ idempotencyKey?: string;
822
+ }): Promise<PlannedRelease>;
823
+ /**
824
+ * Changes a Planned Release: each part given replaces what it had, each
825
+ * absent keeps it. The Change Set's proposals are derived again. Rejects
826
+ * as `planRelease` does, and with `InvalidInputError` for a proposal.
827
+ */
828
+ updatePlannedRelease(input: {
829
+ changeSetId: string;
830
+ plannedReleaseId: string;
831
+ validityPeriod?: ValidityPeriod;
832
+ releaseSpec?: Record<string, unknown>;
833
+ supersedesReleaseIds?: string[];
834
+ idempotencyKey?: string;
835
+ }): Promise<PlannedRelease>;
836
+ /**
837
+ * Adds an editorial Branch to a Chain on Dated Content, for a later
838
+ * date. Answers the Change Set and the new Branch's id. Rejects with
839
+ * `InvalidInputError` for a date not after the Chain's last editorial
840
+ * Branch's, or a Chain without dates.
841
+ */
842
+ appendBranch(input: {
843
+ changeSetId: string;
844
+ chainId: string;
845
+ effectiveOn: string;
846
+ idempotencyKey?: string;
847
+ }): Promise<{
848
+ changeSet: ChangeSet;
849
+ branchId: string;
850
+ }>;
851
+ /**
852
+ * Removes a Chain from an open Change Set with its Planned Releases; its
853
+ * Revisions stay in history. Rejects with `InvalidInputError` for the
854
+ * Chain of Content born in the Change Set.
855
+ */
856
+ dropChain(input: {
857
+ changeSetId: string;
858
+ chainId: string;
859
+ idempotencyKey?: string;
860
+ }): Promise<ChangeSet>;
861
+ /** The Pending Merge on a Branch, with what its resolution decides; null for none. */
862
+ getPendingMerge(branchId: string): Promise<PendingMerge | null>;
863
+ /**
864
+ * Resolves the Pending Merge on a Branch field by field, as a merge
865
+ * Revision that becomes its head. Rejects with `StaleHeadError` when the
866
+ * head moved, `ContentRefusedError` when the result fails the Blueprint
867
+ * Release, and `InvalidInputError` for a field undecided or a Branch
868
+ * without a Pending Merge.
869
+ */
870
+ resolveMerge(input: {
871
+ changeSetId: string;
872
+ branchId: string;
873
+ expectedHeadId: string;
874
+ resolutions: FieldResolution[];
875
+ idempotencyKey?: string;
876
+ }): Promise<Content>;
877
+ /** Drops the Pending Merge on a Branch, which stays behind its base. */
878
+ abortMerge(input: {
879
+ changeSetId: string;
880
+ branchId: string;
881
+ idempotencyKey?: string;
882
+ }): Promise<void>;
883
+ /**
884
+ * Preview: the Content as In Force on `validOn` (today when absent) if
885
+ * the open Change Set were published now (ReleaseService's
886
+ * preview_change_set); null when nothing is In Force then.
887
+ */
888
+ previewInForce(input: {
889
+ contentId: string;
890
+ changeSetId: string;
891
+ validOn?: string;
892
+ }): Promise<PreviewedContent | null>;
893
+ /** Plans a Repeal of Dated Content ending on `lastDay`. */
894
+ planRepeal(input: {
895
+ changeSetId: string;
896
+ contentId: string;
897
+ lastDay: string;
898
+ }): Promise<PlannedEnd>;
899
+ /** Plans a Withdrawal of undated Content. */
900
+ planWithdrawal(input: {
901
+ changeSetId: string;
902
+ contentId: string;
903
+ }): Promise<PlannedEnd>;
904
+ }
905
+
906
+ /** One page of `listContents`. */
907
+ declare interface ContentList {
908
+ contents: Content[];
909
+ /** Empty on the last page. */
910
+ nextPageToken: string;
911
+ }
912
+
913
+ /** A Content's Timeline, with what open Change Sets plan to add to it. */
914
+ declare interface ContentTimeline {
915
+ entries: TimelineEntry[];
916
+ /** By Change Set, in the order they were planned. */
917
+ plannedReleases: {
918
+ changeSetId: string;
919
+ plannedRelease: PlannedRelease;
920
+ }[];
921
+ }
922
+
923
+ /**
924
+ * Where the Content Library reads each Content: on Main (what has been
925
+ * published, at its Main head), at its Release In Force today, or inside one
926
+ * Change Set (at the head of its Chain's last Branch).
927
+ */
928
+ declare type ContentView = {
929
+ kind: "main";
930
+ } | {
931
+ kind: "in_force";
932
+ } | {
933
+ kind: "change_set";
934
+ changeSetId: string;
935
+ };
936
+
937
+ /** What a new Content copies (`createContent`'s copy_from). */
938
+ declare type CopyFrom = {
939
+ revisionId: string;
940
+ } | {
941
+ releaseId: string;
942
+ };
943
+
944
+ export declare function createStubBlueprintSource(blueprints?: BlueprintInfo[], releases?: BlueprintReleaseInfo[]): BlueprintSource;
945
+
946
+ export declare function createStubContenthubClient(options?: StubContenthubClientOptions): ContenthubClient;
947
+
948
+ export declare function createStubStatusSource(statuses?: ChangeSetStatusInfo[]): StatusSource;
949
+
950
+ /** One edge of one Revision. */
951
+ declare interface Edge {
952
+ sourceContentId: string;
953
+ sourceRevisionId: string;
954
+ /** fieldkit's Path of the value it comes from; "" for the Blueprint edge. */
955
+ path: string;
956
+ kind: EdgeKind;
957
+ /** Exactly one of the three targets is set. */
958
+ targetContentId: string;
959
+ targetAssetId: string;
960
+ targetBlueprintReleaseId: string;
961
+ /** The Release the edge is pinned to; "" when it follows the Release In Force. */
962
+ pinReleaseId: string;
963
+ }
964
+
965
+ /** What kind of edge of the Content Graph (ADR 0009). */
966
+ declare type EdgeKind = "reference" | "toc" | "include" | "exclude" | "replace" | "annotate" | "link" | "footnote" | "anchor" | "media" | "blueprint" | "unspecified";
967
+
968
+ /** One field that differs between two Revisions. */
969
+ declare interface FieldChange {
970
+ /** "/" and the field's accessor. */
971
+ path: string;
972
+ kind: "added" | "removed" | "changed";
973
+ /** Absent on the side that doesn't hold the field. */
974
+ before?: unknown;
975
+ after?: unknown;
976
+ }
977
+
978
+ /** One decision of a merge resolution: a side's value, or the editor's own. */
979
+ declare type FieldResolution = {
980
+ path: string;
981
+ side: "ours" | "theirs";
982
+ } | {
983
+ path: string;
984
+ value: unknown;
985
+ };
986
+
987
+ /** An unpinned edge a Repeal or Withdrawal will leave pointing at a gap. */
988
+ declare interface GapReferrer {
989
+ edge: Edge;
990
+ /** The Release holding the edge. */
991
+ releaseId: string;
992
+ }
993
+
994
+ /** One field a merge resolution must decide, with each side's value. */
995
+ declare interface MergeField {
996
+ /** "/" and the field's accessor, as a Violation spells it. */
997
+ path: string;
998
+ /** Where within the field it stopped. */
999
+ conflictPaths: string[];
1000
+ /** The field's value in base, ours (the Branch's head) and theirs; absent on a side without it. */
1001
+ base?: unknown;
1002
+ ours?: unknown;
1003
+ theirs?: unknown;
1004
+ }
1005
+
1006
+ /** A pinned edge whose Release is superseded, or is not the one In Force today. */
1007
+ declare interface OutdatedPin {
1008
+ edge: Edge;
1009
+ /** "" if none. */
1010
+ supersededByReleaseId: string;
1011
+ /** The target's Release In Force today; "" in a gap. */
1012
+ inForceReleaseId: string;
1013
+ }
1014
+
1015
+ /**
1016
+ * A merge on a Branch that stopped (CONTEXT.md, Conflict), with what its
1017
+ * resolution decides: saves on the Branch and Publish wait until it is
1018
+ * resolved or aborted.
1019
+ */
1020
+ declare interface PendingMerge {
1021
+ branchId: string;
1022
+ contentId: string;
1023
+ reason: PendingMergeReason;
1024
+ /** The Branch's head, which a resolution names as the head it built on. */
1025
+ oursRevisionId: string;
1026
+ /** What the Branch was merging. */
1027
+ theirsRevisionId: string;
1028
+ /** Whether theirs is what a Carry-forward link carries rather than Main's head. */
1029
+ carried: boolean;
1030
+ /** The fields a resolution must decide, by path. */
1031
+ fields: MergeField[];
1032
+ /** Every other field, merged. */
1033
+ merged: Record<string, unknown>;
1034
+ }
1035
+
1036
+ /** Why Update from Main stopped a merge: Conflicts, or merged data the Blueprint Release refuses. */
1037
+ declare type PendingMergeReason = "conflict" | "invalid";
1038
+
1039
+ /** Which Branch holds a Pending Merge: the Branch editor shows the merge itself. */
1040
+ declare interface PendingMergeRef {
1041
+ branchId: string;
1042
+ contentId: string;
1043
+ }
1044
+
1045
+ /** A Repeal or Withdrawal planned, and the referrers it will leave with a gap. */
1046
+ declare interface PlannedEnd {
1047
+ plannedRelease: PlannedRelease;
1048
+ /** A warning, never a refusal (ADR 0009). */
1049
+ gapReferrers: GapReferrer[];
1050
+ }
1051
+
1052
+ /**
1053
+ * A Release a Change Set will cut when it is published (CONTEXT.md, Planned
1054
+ * Release). Its id is the Release's id from the moment it is planned.
1055
+ */
1056
+ declare interface PlannedRelease {
1057
+ id: string;
1058
+ contentId: string;
1059
+ kind: ReleaseKind;
1060
+ /** The editorial Branch whose head at Publish it pins; "" for a Branch-less kind. */
1061
+ branchId: string;
1062
+ /** The Revision a Branch-less kind re-pins; "" for a Branch's Release. */
1063
+ revisionId: string;
1064
+ /** Absent for undated Content. */
1065
+ validityPeriod?: ValidityPeriod;
1066
+ /** Its Release Spec values, keyed by the Release Spec Field's accessor; absent for none. */
1067
+ releaseSpec?: Record<string, unknown>;
1068
+ /** The Releases it will Supersede. */
1069
+ supersedesReleaseIds: string[];
1070
+ /**
1071
+ * Proposed by the Change Set, to end `proposedForReleaseId`, a Release a
1072
+ * planned Validity Period cuts into: recomputed from the plans, never
1073
+ * edited by hand.
1074
+ */
1075
+ proposed: boolean;
1076
+ proposedForReleaseId: string;
1077
+ /** For a proposed tail, the Release whose wording it resumes; "" otherwise. */
1078
+ splitFromReleaseId: string;
1079
+ /**
1080
+ * For a Carry-forward link's proposal, whether a plan supersedes the
1081
+ * Release it carries, so that Publish doesn't cut it.
1082
+ */
1083
+ withheld: boolean;
1084
+ }
1085
+
1086
+ /**
1087
+ * One Content as a Preview reads it (ReleaseService with a Change Set
1088
+ * previewed): its Release In Force on a day as if the Change Set were
1089
+ * published now.
1090
+ */
1091
+ declare interface PreviewedContent {
1092
+ /** The Release read, or the Planned Release the Change Set would cut. */
1093
+ releaseId: string;
1094
+ revisionId: string;
1095
+ /** Whether it is one of the previewed Change Set's Planned Releases. */
1096
+ planned: boolean;
1097
+ /** Restricted Content the caller isn't granted on: the Preview's change is withheld. */
1098
+ withheld: boolean;
1099
+ /** Absent for undated Content. */
1100
+ validityPeriod?: ValidityPeriod;
1101
+ data: Record<string, unknown>;
1102
+ }
1103
+
1104
+ /**
1105
+ * One reason a Publish is refused, or would be (ADR 0015). Which ids are set
1106
+ * depends on `code`; an id that doesn't apply is "".
1107
+ */
1108
+ declare interface PublishBlocker {
1109
+ /**
1110
+ * versionkit's (moved_head, unplanned_branch, several_branches,
1111
+ * behind_base, superseded_release, overlapping_periods, covers_release,
1112
+ * pending_merge, nothing_in_force, withdrawal_with_chain, dated_changed,
1113
+ * unpublished_dependency, discarded_dependency), the Content Graph's
1114
+ * (dangling_pin, second_toc_parent, missing_asset), mediahub's
1115
+ * (media_unavailable: it refused the caller's token), Required Checks'
1116
+ * (outstanding_check) or the values' (missing_required_field: a required
1117
+ * field `revisionId` leaves empty, at `path`). A code this build doesn't
1118
+ * know shows as its code.
1119
+ */
1120
+ code: string;
1121
+ contentId: string;
1122
+ branchId: string;
1123
+ expectedHeadId: string;
1124
+ currentHeadId: string;
1125
+ releaseId: string;
1126
+ overlapsReleaseId: string;
1127
+ dependencyChangeSetId: string;
1128
+ path: string;
1129
+ targetContentId: string;
1130
+ assetId: string;
1131
+ parentContentIds: string[];
1132
+ requiredCheckId: string;
1133
+ coveredReleaseId: string;
1134
+ revisionId: string;
1135
+ }
1136
+
1137
+ /** What a Publish did. */
1138
+ declare interface PublishOutcome {
1139
+ changeSet: ChangeSet;
1140
+ /** ISO 8601: the Publish Time every Release carries. */
1141
+ publishTime: string;
1142
+ releases: Release[];
1143
+ }
1144
+
1145
+ /**
1146
+ * Publish's dry run: what a Publish with the same reviewed heads would be
1147
+ * refused with now, and what it would cut.
1148
+ */
1149
+ declare interface PublishReview {
1150
+ /** None means a Publish with these heads would succeed now. */
1151
+ blockers: PublishBlocker[];
1152
+ releases: Release[];
1153
+ /** Published already with these heads: Publish would return its stored outcome. */
1154
+ published: boolean;
1155
+ }
1156
+
1157
+ /** One recorder grant: a member by name, or every member of the workspace. */
1158
+ declare type RecorderGrant = {
1159
+ userId: string;
1160
+ } | {
1161
+ everyMember: true;
1162
+ };
1163
+
1164
+ /** An incoming edge, and where it was found: a Release In Force, or an open Change Set's Branch. */
1165
+ declare interface Referrer {
1166
+ edge: Edge;
1167
+ /** "" when found in a Change Set. */
1168
+ releaseId: string;
1169
+ /** "" when found in a Release. */
1170
+ changeSetId: string;
1171
+ branchId: string;
1172
+ }
1173
+
1174
+ /** One page of `listReferrers`. */
1175
+ declare interface ReferrerList {
1176
+ referrers: Referrer[];
1177
+ /** Empty on the last page. */
1178
+ nextPageToken: string;
1179
+ }
1180
+
1181
+ /** One reason contenthub refused a write, as the rule's owner gave it. */
1182
+ declare interface RefusalReason {
1183
+ /** Whose rule: "fieldkit", "knkeditor", "format" or "contenthub". */
1184
+ source: string;
1185
+ /** The rule's own code, such as fieldkit's "required". */
1186
+ code: string;
1187
+ /** A /-separated location, such as "/title"; "" for the whole. */
1188
+ path: string;
1189
+ }
1190
+
1191
+ /** A Release a Publish cut, or would cut. */
1192
+ declare interface Release {
1193
+ id: string;
1194
+ contentId: string;
1195
+ kind: ReleaseKind;
1196
+ branchId: string;
1197
+ revisionId: string;
1198
+ /** The Content's first Release: the moment it exists outside its Change Set. */
1199
+ first: boolean;
1200
+ supersedesReleaseIds: string[];
1201
+ /** Absent for undated Content. */
1202
+ validityPeriod?: ValidityPeriod;
1203
+ }
1204
+
1205
+ /** How a Release comes to be (contenthub ADR 0016). */
1206
+ declare type ReleaseKind = "revision" | "date_only" | "repeal" | "withdrawal";
1207
+
1208
+ /** A condition the workspace requires before Publish (CONTEXT.md, Required Check). */
1209
+ declare interface RequiredCheck {
1210
+ id: string;
1211
+ name: string;
1212
+ scope: CheckScope;
1213
+ /** The Blueprints whose Content it applies to; empty for every one. */
1214
+ blueprintAccessors: string[];
1215
+ /** Passed and waived are refused from whoever authored what it covers. */
1216
+ independent: boolean;
1217
+ }
1218
+
1219
+ /** What a Required Check is declared with, on create and on update alike. */
1220
+ declare interface RequiredCheckInput {
1221
+ name: string;
1222
+ scope: CheckScope;
1223
+ /** Blueprint accessors; empty for every Blueprint. */
1224
+ blueprintAccessors: string[];
1225
+ independent: boolean;
1226
+ }
1227
+
1228
+ /**
1229
+ * Who records a Required Check's results (ADR 0017's 2026-10-03 amendment):
1230
+ * every member of the workspace, the people granted by name, or both while
1231
+ * an admin narrows it.
1232
+ */
1233
+ declare interface RequiredCheckRecorders {
1234
+ everyMember: boolean;
1235
+ /** Sorted odon user ids. */
1236
+ userIds: string[];
1237
+ }
1238
+
1239
+ /**
1240
+ * The Blueprint Release a Content's Revision pins, as contenthub validated
1241
+ * and cached it (blueprinthub's Resolved Release): what its form is drawn
1242
+ * from.
1243
+ */
1244
+ declare interface ResolvedBlueprintRelease {
1245
+ /** The Blueprint's name, as the released Revision holds it; "" if it holds none. */
1246
+ displayName: string;
1247
+ /** Whether its Content is Dated: its Releases carry a Validity Period. */
1248
+ dated: boolean;
1249
+ /** fieldkit's Resolved Spec fields, in Blueprint order. */
1250
+ fields: ResolvedField[];
1251
+ /**
1252
+ * Its Release Spec: the fields every Release of its Content carries
1253
+ * values for, which a Planned Release sets. Absent or empty for none.
1254
+ */
1255
+ releaseSpec?: ResolvedField[];
1256
+ /** The opaque parts the fields pin (fieldkit ADR-0020), by kind and Release id. */
1257
+ parts: Record<string, Record<string, unknown>>;
1258
+ }
1259
+
1260
+ /**
1261
+ * One field of a Blueprint Release, as its Resolved Release declares it:
1262
+ * fieldkit's Field. The screens read its name and accessor and hand the
1263
+ * rest to fieldkit untouched.
1264
+ */
1265
+ declare interface ResolvedField {
1266
+ field_type: string;
1267
+ config: {
1268
+ name: string;
1269
+ api_accessor: string;
1270
+ hidden?: boolean;
1271
+ } & Record<string, unknown>;
1272
+ children?: ResolvedField[];
1273
+ [key: string]: unknown;
1274
+ }
1275
+
1276
+ /** One immutable Revision of a Content, without its data. */
1277
+ declare interface Revision {
1278
+ id: string;
1279
+ contentId: string;
1280
+ /** The Revision it was built on; "" for the Content's first. */
1281
+ parentId: string;
1282
+ /** The other side of a merge Revision; "" otherwise. */
1283
+ secondParentId: string;
1284
+ blueprintReleaseId: string;
1285
+ /** ISO 8601. */
1286
+ createTime: string;
1287
+ /** The odon user who wrote it. */
1288
+ author: string;
1289
+ cause: RevisionCause;
1290
+ /** The Branch it was written on, and that Branch's Change Set. */
1291
+ branchId: string;
1292
+ changeSetId: string;
1293
+ }
1294
+
1295
+ /** Why a Revision was written. */
1296
+ declare type RevisionCause = "create" | "save" | "merge" | "resolve" | "upgrade" | "unspecified";
1297
+
1298
+ /** How one Revision differs from a base, field by field. */
1299
+ declare interface RevisionComparison {
1300
+ base: Revision;
1301
+ revision: Revision;
1302
+ changes: FieldChange[];
1303
+ }
1304
+
1305
+ /** One page of `listRevisions`, newest first. */
1306
+ declare interface RevisionList {
1307
+ revisions: Revision[];
1308
+ /** Empty on the last page. */
1309
+ nextPageToken: string;
1310
+ }
1311
+
1312
+ /**
1313
+ * The port a host fills with statushub (ADR 0020): the workspace's Change Set
1314
+ * statuses, for their display names and colours. contenthub-ui depends on no
1315
+ * statushub package. A host without statushub leaves the port empty, and the
1316
+ * screens then show the bare slug and offer no status picker.
1317
+ */
1318
+ declare interface StatusSource {
1319
+ /** Every status of the workspace's `contenthub/change_set` catalogue, in its order. */
1320
+ listChangeSetStatuses(): Promise<ChangeSetStatusInfo[]>;
1321
+ }
1322
+
1323
+ export declare const STUB_NOW = "2026-10-01T09:00:00.000Z";
1324
+
1325
+ /** A second member of the stub workspace, to assign and filter by. */
1326
+ export declare const STUB_OTHER_USER_ID = "user_ben";
1327
+
1328
+ export declare const STUB_USER_ID = "user_stub";
1329
+
1330
+ export declare const STUB_WORKSPACE_ID = "ws_stub";
1331
+
1332
+ /** A blocker naming only what `fields` sets; every other id "", as on the wire. */
1333
+ export declare function stubBlocker(code: string, fields?: Partial<PublishBlocker>): PublishBlocker;
1334
+
1335
+ /** A legal publisher's Block Reasons, in the service's order (by name). */
1336
+ export declare function stubBlockReasons(): BlockReason[];
1337
+
1338
+ /** Their Releases, newest first per Blueprint. */
1339
+ export declare function stubBlueprintReleases(): BlueprintReleaseInfo[];
1340
+
1341
+ /**
1342
+ * The workspace's content Blueprints, as a `BlueprintSource` answers, by
1343
+ * name. Commentary has had no Release cut yet, so contenthub's catalog
1344
+ * (the stub client's `searchBlueprints`) leaves it out.
1345
+ */
1346
+ export declare function stubBlueprints(): BlueprintInfo[];
1347
+
1348
+ /**
1349
+ * A gazette's Change Sets: one open with two Chains (§ 1 amended twice, so
1350
+ * two Branches, and a paragraph born in it), one open with nothing in it yet,
1351
+ * and one published correction. Newest first, as the service lists them.
1352
+ */
1353
+ export declare function stubChangeSets(): ChangeSet[];
1354
+
1355
+ /**
1356
+ * statushub's seed catalogue for `contenthub/change_set` (spec #56, story 9),
1357
+ * as the `StatusSource` port answers it. Colours are CSS names: statushub
1358
+ * holds whatever CSS colour an admin chose.
1359
+ */
1360
+ export declare function stubChangeSetStatuses(): ChangeSetStatusInfo[];
1361
+
1362
+ /**
1363
+ * A legal publisher's Collection tree, by name as the service lists it:
1364
+ * Laws holding BGB and StGB, and Commentary beside it.
1365
+ */
1366
+ export declare function stubCollections(): Collection[];
1367
+
1368
+ /** A merge an Update from Main would stop on (see `conflicts`). */
1369
+ export declare interface StubConflict {
1370
+ theirsRevisionId: string;
1371
+ fields: MergeField[];
1372
+ /** Every field but `fields`, merged. */
1373
+ merged: Record<string, unknown>;
1374
+ reason?: "conflict" | "invalid";
1375
+ /** Already stored on the Branch, as an earlier Update from Main left it. */
1376
+ pending?: boolean;
1377
+ }
1378
+
1379
+ /**
1380
+ * A Content of the stub workspace, every way the screens can read it. Not a
1381
+ * service type: it is what the stub keeps to answer its reads.
1382
+ */
1383
+ export declare interface StubContent {
1384
+ id: string;
1385
+ blueprintId: string;
1386
+ blueprintReleaseId: string;
1387
+ /** The Collections it is filed in, directly. */
1388
+ collectionIds: string[];
1389
+ /** Who maintains it; none when absent. */
1390
+ assigneeIds?: string[];
1391
+ /** Its Block; absent while it is not blocked. */
1392
+ block?: ContentBlock;
1393
+ /** When it was archived, ISO 8601; absent while it is not Archived. */
1394
+ archiveTime?: string;
1395
+ /** Its Main head; absent while it was born in an open Change Set. */
1396
+ main?: StubHead;
1397
+ /** The Release In Force today, and the head it pins; absent for none. */
1398
+ inForce?: StubHead & {
1399
+ releaseId: string;
1400
+ };
1401
+ /** The head of its Chain's last Branch in each Change Set it is in, by Change Set id. */
1402
+ branches: Record<string, StubHead & {
1403
+ branchId: string;
1404
+ }>;
1405
+ /** Its Timeline as known now; none when absent. */
1406
+ timeline?: TimelineEntry[];
1407
+ /** What open (or since closed) Change Sets plan for it, in the order planned. */
1408
+ plannedReleases?: {
1409
+ changeSetId: string;
1410
+ plannedRelease: PlannedRelease;
1411
+ }[];
1412
+ /** Its Revisions, newest first, beyond what its heads imply; none when absent. */
1413
+ revisions?: (Revision & {
1414
+ data?: Record<string, unknown>;
1415
+ })[];
1416
+ /** The outgoing edges of its Revisions, each naming the Revision holding it. */
1417
+ edges?: Edge[];
1418
+ }
1419
+
1420
+ export declare interface StubContenthubClientOptions {
1421
+ /** The workspace's Block Reasons to start from; the fixtures' by default. */
1422
+ blockReasons?: BlockReason[];
1423
+ /** The workspace's Change Sets to start from, newest first; the fixtures' by default. */
1424
+ changeSets?: ChangeSet[];
1425
+ /**
1426
+ * statushub's `contenthub/change_set` catalogue the stub checks status
1427
+ * moves against; the fixtures' by default.
1428
+ */
1429
+ statuses?: ChangeSetStatusInfo[];
1430
+ /** The moves statushub allows, from slug to slugs; the fixtures' by default. */
1431
+ statusTransitions?: Record<string, string[]>;
1432
+ /** The workspace's Collections, by name; the fixtures' by default. */
1433
+ collections?: Collection[];
1434
+ /** The workspace's Content, every way it can be read; the fixtures' by default. */
1435
+ contents?: StubContent[];
1436
+ /**
1437
+ * The workspace's content Blueprints Content can be made from, as
1438
+ * `searchBlueprints` lists them; the fixtures' by default.
1439
+ */
1440
+ blueprints?: BlueprintInfo[];
1441
+ /**
1442
+ * The Blueprint Releases, newest first per Blueprint, as
1443
+ * `listBlueprintReleases` lists them; the fixtures' by default.
1444
+ * `createContent` pins one without `refusals` and refuses the rest, as
1445
+ * contenthub refuses a Release it can't load.
1446
+ */
1447
+ blueprintReleases?: (Pick<BlueprintReleaseInfo, "id" | "blueprintId"> & Partial<BlueprintReleaseInfo>)[];
1448
+ /** The Resolved Releases `getContent` answers with, by Blueprint Release id; the fixtures' by default. */
1449
+ resolvedReleases?: Record<string, ResolvedBlueprintRelease>;
1450
+ /**
1451
+ * The workspace's Required Checks; the fixtures' by default. The stub
1452
+ * reads `blueprintAccessors` as Blueprint ids, the one name its Content
1453
+ * carries.
1454
+ */
1455
+ requiredChecks?: RequiredCheck[];
1456
+ /** Check Results recorded before, in the order they were recorded. */
1457
+ checkResults?: (CheckResult & {
1458
+ changeSetId: string;
1459
+ })[];
1460
+ /**
1461
+ * Blockers the stub can't derive itself (versionkit's and the Content
1462
+ * Graph's, such as covers_release or dangling_pin), by Change Set id:
1463
+ * `validatePublish` and `publish` report them beside the ones it does
1464
+ * derive (moved_head, the dependencies', outstanding_check).
1465
+ */
1466
+ publishBlockers?: Record<string, PublishBlocker[]>;
1467
+ /** The Pending Merges `updateFromMain` leaves, by Change Set id; none by default. */
1468
+ pendingMerges?: Record<string, PendingMergeRef[]>;
1469
+ /**
1470
+ * What an Update from Main would meet on a Branch, by Branch id: the
1471
+ * fields it can't merge and the rest merged. `updateFromMain` stores it
1472
+ * on the Branch as a Pending Merge; `pending` stores it from the start.
1473
+ */
1474
+ conflicts?: Record<string, StubConflict>;
1475
+ /**
1476
+ * contenthub's `authz.mode`: "guardian" by default. In "workspace" every
1477
+ * restriction and grant is refused, as contenthub refuses them, and
1478
+ * opening a Restricted Collection again is still allowed.
1479
+ */
1480
+ authzMode?: "workspace" | "guardian";
1481
+ /** Grants on Restricted Collections, by Collection id, the owner's included. */
1482
+ collectionGrants?: Record<string, CollectionGrant[]>;
1483
+ /**
1484
+ * The Change Sets, by id, holding work the caller can't see (Restricted
1485
+ * Content, ADR 0017): `validatePublish`, `publish`, `discardChangeSet`
1486
+ * and an `updateFromMain` without `chainId` refuse them with
1487
+ * `WithheldWorkError`, as contenthub does.
1488
+ */
1489
+ withheld?: string[];
1490
+ /**
1491
+ * Who records each Required Check, by check id; a check not named here,
1492
+ * and every new one, is recorded by every member. In guardian mode
1493
+ * `recordCheckResult` refuses a caller who is no recorder, as contenthub
1494
+ * does; in workspace mode every member records, and the recorder calls
1495
+ * are refused.
1496
+ */
1497
+ recorders?: Record<string, RequiredCheckRecorders>;
1498
+ /** The workspace's members, whom a grant may name; the stub identity's two by default. */
1499
+ memberIds?: string[];
1500
+ /**
1501
+ * Whether the caller is a workspace admin, who manages every Restricted
1502
+ * Collection's grants; true by default. Otherwise only an owner does.
1503
+ */
1504
+ admin?: boolean;
1505
+ /** Who the token names: the creator of a new Change Set. STUB_USER_ID by default. */
1506
+ userId?: string;
1507
+ /** The time every write stamps; STUB_NOW by default. */
1508
+ now?: () => string;
1509
+ }
1510
+
1511
+ /**
1512
+ * The Content behind the fixtures' Change Sets: § 1 BGB, amended in
1513
+ * BGBl. 2026 I Nr. 270; § 2 BGB; § 4a BGB, born in that Change Set; § 211
1514
+ * StGB; and a footnote filed nowhere, published but with no Release In Force
1515
+ * yet (dated to start next year).
1516
+ */
1517
+ export declare function stubContents(): StubContent[];
1518
+
1519
+ /** One head of a Content: its Revision, data and when it was written. */
1520
+ export declare interface StubHead {
1521
+ revisionId: string;
1522
+ data: Record<string, unknown>;
1523
+ /** ISO 8601. */
1524
+ revisionCreateTime: string;
1525
+ }
1526
+
1527
+ /** Who is looking, for anker's `TestHost` or `<HostProvider identity>`. */
1528
+ export declare function stubIdentity(): HostIdentity;
1529
+
1530
+ /**
1531
+ * § 3 BGB, repealed at the end of 2001: published, filed in BGB, with
1532
+ * nothing In Force today or later, so it may be archived. Not among
1533
+ * `stubContents()`, so the Library's fixtures stay as they are; pass it in
1534
+ * with `contents` to a stub that needs it.
1535
+ */
1536
+ export declare function stubRepealedContent(): StubContent;
1537
+
1538
+ /**
1539
+ * A legal publisher's Required Checks, by name: a print round trip once per
1540
+ * Change Set, and a lawyer's review of each Branch, by someone who wrote
1541
+ * none of it. Both apply to every Blueprint.
1542
+ */
1543
+ export declare function stubRequiredChecks(): RequiredCheck[];
1544
+
1545
+ /**
1546
+ * The Resolved Releases contenthub holds for the fixtures' Blueprint
1547
+ * Releases, by id, as `getContent` decodes them: a paragraph has a number
1548
+ * and a heading, a footnote a text. Both are Dated.
1549
+ */
1550
+ export declare function stubResolvedReleases(): Record<string, ResolvedBlueprintRelease>;
1551
+
1552
+ /**
1553
+ * The moves the stub's statushub allows, from slug to slugs. Anything else is
1554
+ * refused with a reason, as statushub refuses it; a move away from an unknown
1555
+ * status is never checked (contenthub ADR 0014).
1556
+ */
1557
+ export declare function stubStatusTransitions(): Record<string, string[]>;
1558
+
1559
+ /** One Release of a Content's Timeline (CONTEXT.md, Timeline), without its data. */
1560
+ declare interface TimelineEntry {
1561
+ /** Its place in the Timeline, from 1. */
1562
+ ordinal: number;
1563
+ releaseId: string;
1564
+ kind: ReleaseKind;
1565
+ revisionId: string;
1566
+ blueprintReleaseId: string;
1567
+ /** Absent for undated Content. */
1568
+ validityPeriod?: ValidityPeriod;
1569
+ /** ISO 8601. */
1570
+ publishTime: string;
1571
+ first: boolean;
1572
+ }
1573
+
1574
+ /** What Update from Main did to a Change Set. */
1575
+ declare interface UpdateFromMainOutcome {
1576
+ changeSet: ChangeSet;
1577
+ /** The Branches whose heads moved to a merge Revision. */
1578
+ mergedBranchIds: string[];
1579
+ /** The Branches this update left with a Pending Merge. */
1580
+ conflicted: PendingMergeRef[];
1581
+ /** Branches with a Pending Merge from before, which it left alone. */
1582
+ pending: PendingMergeRef[];
1583
+ /** Carry-forward links taken out of their Chains. */
1584
+ endedBranchIds: string[];
1585
+ }
1586
+
1587
+ /**
1588
+ * The calendar days a Release of Dated Content is In Force, both included,
1589
+ * as YYYY-MM-DD; `lastDay` is "" for an open end.
1590
+ */
1591
+ declare interface ValidityPeriod {
1592
+ firstDay: string;
1593
+ lastDay: string;
1594
+ }
1595
+
1596
+ export { }