@capybara-hq/kernel 0.1.0-rc.13 → 0.1.0-rc.15

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 (170) hide show
  1. package/dist/admin/admin.schemas.d.ts +5 -13
  2. package/dist/catalog/admin-catalog.controller.d.ts +39 -5
  3. package/dist/catalog/admin-catalog.controller.js +75 -6
  4. package/dist/catalog/admin-catalog.schemas.d.ts +10 -0
  5. package/dist/catalog/admin-catalog.schemas.js +7 -0
  6. package/dist/catalog/appearance.schemas.d.ts +10 -0
  7. package/dist/catalog/appearance.schemas.js +20 -0
  8. package/dist/catalog/appearance.service.d.ts +105 -0
  9. package/dist/catalog/appearance.service.js +306 -0
  10. package/dist/catalog/catalog-content.d.ts +9 -1
  11. package/dist/catalog/catalog-content.js +2 -1
  12. package/dist/catalog/catalog-files.d.ts +3 -2
  13. package/dist/catalog/catalog-files.js +8 -5
  14. package/dist/catalog/catalog-import.module.js +3 -0
  15. package/dist/catalog/catalog-items.d.ts +11 -0
  16. package/dist/catalog/catalog-items.js +21 -0
  17. package/dist/catalog/catalog-scope.service.d.ts +7 -0
  18. package/dist/catalog/catalog-scope.service.js +55 -15
  19. package/dist/catalog/catalog.service.js +10 -2
  20. package/dist/catalog/cli.js +3 -0
  21. package/dist/catalog/component-scope.service.d.ts +14 -1
  22. package/dist/catalog/component-scope.service.js +12 -2
  23. package/dist/catalog/marketplaces.service.d.ts +1 -1
  24. package/dist/catalog/org-catalog.controller.d.ts +5 -4
  25. package/dist/catalog/org-catalog.controller.js +5 -4
  26. package/dist/catalog/org-catalog.schemas.d.ts +18 -2
  27. package/dist/catalog/org-catalog.schemas.js +16 -2
  28. package/dist/catalog/org-catalog.service.js +8 -2
  29. package/dist/catalog/org-extension.controller.d.ts +27 -7
  30. package/dist/catalog/org-extension.controller.js +48 -6
  31. package/dist/catalog/plugin-import.d.ts +28 -4
  32. package/dist/catalog/plugin-import.js +96 -20
  33. package/dist/catalog/plugin-package.d.ts +4 -2
  34. package/dist/catalog/plugin-package.js +66 -12
  35. package/dist/catalog/plugin-upgrade.d.ts +15 -1
  36. package/dist/catalog/plugin-upgrade.js +18 -5
  37. package/dist/channels/connections.controller.d.ts +1 -1
  38. package/dist/channels/connections.controller.js +1 -1
  39. package/dist/channels/connections.service.d.ts +22 -5
  40. package/dist/channels/connections.service.js +56 -10
  41. package/dist/channels/webhook.d.ts +21 -1
  42. package/dist/channels/webhook.js +39 -4
  43. package/dist/client/client-access.service.d.ts +101 -0
  44. package/dist/client/client-access.service.js +294 -0
  45. package/dist/client/client-builds.service.d.ts +109 -0
  46. package/dist/client/client-builds.service.js +229 -0
  47. package/dist/client/client-delivery.controller.d.ts +25 -0
  48. package/dist/client/client-delivery.controller.js +66 -0
  49. package/dist/client/client-files.d.ts +48 -0
  50. package/dist/client/client-files.js +136 -0
  51. package/dist/client/client-reports.cron.d.ts +13 -0
  52. package/dist/client/client-reports.cron.js +42 -0
  53. package/dist/client/client-reports.service.d.ts +30 -0
  54. package/dist/client/client-reports.service.js +102 -0
  55. package/dist/client/client.controller.d.ts +45 -0
  56. package/dist/client/client.controller.js +121 -0
  57. package/dist/client/client.module.d.ts +7 -0
  58. package/dist/client/client.module.js +26 -0
  59. package/dist/client/client.schemas.d.ts +55 -0
  60. package/dist/client/client.schemas.js +27 -0
  61. package/dist/ctx/create-ctx.d.ts +19 -4
  62. package/dist/ctx/create-ctx.js +51 -3
  63. package/dist/db/kernel-schema.d.ts +58 -2
  64. package/dist/errors.d.ts +33 -0
  65. package/dist/errors.js +33 -0
  66. package/dist/extensions/admin-ops.d.ts +4 -1
  67. package/dist/extensions/admin-ops.js +22 -1
  68. package/dist/extensions/admin-view.d.ts +8 -0
  69. package/dist/extensions/admin-view.js +78 -0
  70. package/dist/extensions/blobs.d.ts +14 -1
  71. package/dist/extensions/blobs.js +48 -5
  72. package/dist/extensions/guide-places.d.ts +66 -0
  73. package/dist/extensions/guide-places.js +152 -0
  74. package/dist/extensions/host.d.ts +6 -1
  75. package/dist/extensions/host.js +23 -12
  76. package/dist/extensions/instance.d.ts +5 -1
  77. package/dist/extensions/instance.js +8 -0
  78. package/dist/extensions/ledger.d.ts +1 -1
  79. package/dist/extensions/package.d.ts +4 -3
  80. package/dist/extensions/package.js +5 -1
  81. package/dist/extensions/permission-set.d.ts +35 -5
  82. package/dist/extensions/permission-set.js +36 -5
  83. package/dist/extensions/permission-view.d.ts +38 -7
  84. package/dist/extensions/permission-view.js +27 -8
  85. package/dist/extensions/plugin-guides.d.ts +207 -54
  86. package/dist/extensions/plugin-guides.js +436 -165
  87. package/dist/extensions/sources.d.ts +6 -0
  88. package/dist/extensions/sources.js +2 -0
  89. package/dist/extensions/state.module.js +9 -1
  90. package/dist/http/api.module.js +16 -0
  91. package/dist/identity/settings.d.ts +41 -26
  92. package/dist/identity/settings.js +87 -4
  93. package/dist/infra/lock/lock.service.d.ts +8 -0
  94. package/dist/infra/lock/lock.service.js +27 -0
  95. package/dist/infra/throttler/named-throttlers.d.ts +8 -1
  96. package/dist/infra/throttler/named-throttlers.js +30 -1
  97. package/dist/infra/throttler/throttler.guard.js +1 -1
  98. package/dist/kernel.module.js +3 -0
  99. package/dist/orgs/settings.service.d.ts +2 -26
  100. package/dist/overrides.d.ts +3 -1
  101. package/dist/overrides.js +2 -0
  102. package/dist/platform/exclusions.js +37 -6
  103. package/dist/platform/operation.js +4 -0
  104. package/dist/platform/ops/admin.ops.js +17 -2
  105. package/dist/platform/ops/catalog-admin.ops.d.ts +19 -1
  106. package/dist/platform/ops/catalog-admin.ops.js +85 -2
  107. package/dist/platform/ops/connectors.ops.js +2 -0
  108. package/dist/platform/ops/org-plugins.ops.d.ts +7 -1
  109. package/dist/platform/ops/org-plugins.ops.js +63 -4
  110. package/dist/platform/ops/org-settings.ops.js +2 -2
  111. package/dist/platform/ops/orgs.ops.js +1 -0
  112. package/dist/platform/ops/setup.ops.d.ts +24 -0
  113. package/dist/platform/ops/setup.ops.js +212 -0
  114. package/dist/platform/ops/ui.ops.js +1 -1
  115. package/dist/platform/platform-ops.module.d.ts +2 -1
  116. package/dist/platform/platform-ops.module.js +7 -0
  117. package/dist/platform/route-schemas.js +8 -0
  118. package/dist/roles/worker.module.js +4 -0
  119. package/dist/setup/kernel-checks.d.ts +91 -0
  120. package/dist/setup/kernel-checks.js +295 -0
  121. package/dist/setup/oauth-token-probe.d.ts +47 -0
  122. package/dist/setup/oauth-token-probe.js +108 -0
  123. package/dist/setup/redact-check.d.ts +18 -0
  124. package/dist/setup/redact-check.js +46 -0
  125. package/dist/setup/setup-check-registry.d.ts +86 -0
  126. package/dist/setup/setup-check-registry.js +75 -0
  127. package/dist/setup/setup-checks.controller.d.ts +28 -0
  128. package/dist/setup/setup-checks.controller.js +78 -0
  129. package/dist/setup/setup-checks.schemas.d.ts +38 -0
  130. package/dist/setup/setup-checks.schemas.js +43 -0
  131. package/dist/setup/setup-checks.service.d.ts +144 -0
  132. package/dist/setup/setup-checks.service.js +683 -0
  133. package/dist/setup/setup-guides.controller.d.ts +18 -0
  134. package/dist/setup/setup-guides.controller.js +48 -0
  135. package/dist/setup/setup-guides.schemas.d.ts +10 -0
  136. package/dist/setup/setup-guides.schemas.js +6 -0
  137. package/dist/setup/setup-guides.service.d.ts +126 -0
  138. package/dist/setup/setup-guides.service.js +315 -0
  139. package/dist/setup/setup.module.d.ts +8 -0
  140. package/dist/setup/setup.module.js +31 -0
  141. package/dist/themes/render-css.d.ts +2 -1
  142. package/dist/themes/render-css.js +5 -2
  143. package/dist/themes/theme-delivery.service.d.ts +4 -2
  144. package/dist/themes/theme-delivery.service.js +8 -3
  145. package/dist/themes/theme-state.d.ts +28 -7
  146. package/dist/themes/theme-state.js +24 -6
  147. package/dist/themes/themes-public.controller.d.ts +7 -2
  148. package/dist/themes/themes-public.controller.js +11 -5
  149. package/dist/themes/themes.service.js +2 -2
  150. package/dist/tools/connectors.controller.d.ts +28 -11
  151. package/dist/tools/connectors.controller.js +49 -14
  152. package/dist/tools/connectors.schemas.d.ts +4 -0
  153. package/dist/tools/connectors.schemas.js +4 -0
  154. package/dist/tools/connectors.service.d.ts +30 -2
  155. package/dist/tools/connectors.service.js +98 -3
  156. package/dist/tools/credentials.d.ts +20 -0
  157. package/dist/tools/credentials.js +90 -1
  158. package/dist/tools/oauth/tokens.d.ts +20 -0
  159. package/dist/tools/oauth/tokens.js +49 -24
  160. package/dist/tools/pipeline.d.ts +3 -2
  161. package/dist/ui/arrangement.d.ts +41 -50
  162. package/dist/ui/arrangement.js +88 -15
  163. package/dist/ui/registry.d.ts +13 -3
  164. package/dist/ui/registry.js +15 -4
  165. package/dist/ui/ui.schemas.d.ts +4 -0
  166. package/dist/ui/views.d.ts +36 -9
  167. package/dist/ui/views.js +58 -28
  168. package/package.json +3 -3
  169. package/src/migrations/0106_setup_guides_v2.sql +10 -0
  170. package/src/migrations/0107_extension_client_builds.sql +50 -0
@@ -0,0 +1,18 @@
1
+ import type { z } from "zod";
2
+ import { type Principal } from "../identity/principal.js";
3
+ import { type OrgContext } from "../orgs/org.guard.js";
4
+ import { SetupGuidesQuery } from "./setup-guides.schemas.js";
5
+ import { type SetupGuideListEntry, SetupGuidesService } from "./setup-guides.service.js";
6
+ /**
7
+ * The org's setup guides as the caller sees them (spec 2026-11-09-setup-guides-v2-design.md §12):
8
+ * per guide its targets, their drift, a summary of the cached check states and the web page of
9
+ * each. `catalog.read` here; which guides and targets a role sees is the service's (the guide
10
+ * route's place and audience actions).
11
+ */
12
+ export declare class SetupGuidesController {
13
+ private readonly guides;
14
+ constructor(guides: SetupGuidesService);
15
+ list(principal: Principal, org: OrgContext, q: z.infer<typeof SetupGuidesQuery>): Promise<{
16
+ guides: SetupGuideListEntry[];
17
+ }>;
18
+ }
@@ -0,0 +1,48 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
8
+ return function (target, key) { decorator(target, key, paramIndex); }
9
+ };
10
+ import { Controller, Get, Inject } from "@nestjs/common";
11
+ import { CurrentPrincipal, requireUser } from "../identity/principal.js";
12
+ import { createZodDto } from "../infra/validation/create-zod-dto.js";
13
+ import { ZQuery } from "../infra/validation/zod-params.js";
14
+ import { CurrentOrg, OrgAction } from "../orgs/org.guard.js";
15
+ import { SetupGuidesQuery } from "./setup-guides.schemas.js";
16
+ import { SetupGuidesService } from "./setup-guides.service.js";
17
+ class SetupGuidesQueryDto extends createZodDto(SetupGuidesQuery, "SetupGuidesQuery") {
18
+ }
19
+ /**
20
+ * The org's setup guides as the caller sees them (spec 2026-11-09-setup-guides-v2-design.md §12):
21
+ * per guide its targets, their drift, a summary of the cached check states and the web page of
22
+ * each. `catalog.read` here; which guides and targets a role sees is the service's (the guide
23
+ * route's place and audience actions).
24
+ */
25
+ let SetupGuidesController = class SetupGuidesController {
26
+ guides;
27
+ constructor(guides) {
28
+ this.guides = guides;
29
+ }
30
+ async list(principal, org, q) {
31
+ const p = requireUser(principal);
32
+ return {
33
+ guides: await this.guides.list({ orgId: org.orgId, role: org.role, accountId: p.account.id }, q),
34
+ };
35
+ }
36
+ };
37
+ __decorate([
38
+ Get(),
39
+ OrgAction("catalog.read"),
40
+ __param(0, CurrentPrincipal()),
41
+ __param(1, CurrentOrg()),
42
+ __param(2, ZQuery(SetupGuidesQueryDto))
43
+ ], SetupGuidesController.prototype, "list", null);
44
+ SetupGuidesController = __decorate([
45
+ Controller("api/v1/orgs/:orgId/setup-guides"),
46
+ __param(0, Inject(SetupGuidesService))
47
+ ], SetupGuidesController);
48
+ export { SetupGuidesController };
@@ -0,0 +1,10 @@
1
+ import { z } from "zod";
2
+ import type { RouteSchemas } from "../platform/route-schemas.js";
3
+ /** `GET /orgs/:orgId/setup-guides`: the language of the guide titles (zh falls back to en). */
4
+ export declare const SetupGuidesQuery: z.ZodObject<{
5
+ locale: z.ZodOptional<z.ZodEnum<{
6
+ en: "en";
7
+ zh: "zh";
8
+ }>>;
9
+ }, z.core.$strict>;
10
+ export declare const SETUP_GUIDE_ROUTES: readonly RouteSchemas[];
@@ -0,0 +1,6 @@
1
+ import { z } from "zod";
2
+ /** `GET /orgs/:orgId/setup-guides`: the language of the guide titles (zh falls back to en). */
3
+ export const SetupGuidesQuery = z.object({ locale: z.enum(["en", "zh"]).optional() }).strict();
4
+ export const SETUP_GUIDE_ROUTES = [
5
+ { route: "GET /api/v1/orgs/:orgId/setup-guides", query: SetupGuidesQuery },
6
+ ];
@@ -0,0 +1,126 @@
1
+ import type { SetupGuideAudience, SetupGuideBlock, SetupGuidePlace } from "@capybara-hq/plugin-sdk";
2
+ import { ComponentScopeService } from "../catalog/component-scope.service.js";
3
+ import { type GuideQuery, PluginGuides, type UnresolvedReason } from "../extensions/plugin-guides.js";
4
+ import { KernelDb } from "../infra/database/kernel-db.js";
5
+ import type { KernelLogger } from "../log.js";
6
+ import { type CheckCaller, SetupChecksService } from "./setup-checks.service.js";
7
+ /** The cached check states of one target, counted (`running` while a run holds the lock). */
8
+ export interface SetupCheckSummary {
9
+ ok: number;
10
+ failing: number;
11
+ unknown: number;
12
+ running: number;
13
+ notRun: number;
14
+ }
15
+ /** Something a guide is for: a channel connection, a connector, or a template without one yet. */
16
+ export interface SetupGuideTarget {
17
+ kind: "channel-connection" | "connector" | "connector-template";
18
+ /** The connection or connector id; the template id for `connector-template`. */
19
+ id: string;
20
+ name: string;
21
+ /** The guide changed since the target was set up (its ack): the versions; null otherwise. */
22
+ drift: {
23
+ from: string;
24
+ to: string;
25
+ } | null;
26
+ /** Null when there is nothing to check yet (a template without a connector) or it is unreadable. */
27
+ checks: SetupCheckSummary | null;
28
+ /** The web page where the person does this setup (enters values, acks the guide). */
29
+ webPath: string;
30
+ }
31
+ /** One guide the caller may see in the org, with its targets (`GET /orgs/:orgId/setup-guides`). */
32
+ export interface SetupGuideListEntry {
33
+ itemId: string;
34
+ plugin: string;
35
+ guideId: string;
36
+ place: SetupGuidePlace;
37
+ /** The connector template of a `connector-template` guide; null for other places. */
38
+ template: string | null;
39
+ /** The section rendered: org admins get the admin guides, members their member sections. */
40
+ audience: SetupGuideAudience;
41
+ title: string;
42
+ targets: SetupGuideTarget[];
43
+ }
44
+ /** A rendered guide as one Markdown document, for an agent assisting the person (`setup.read`). */
45
+ export interface SetupGuideDocument {
46
+ itemId: string;
47
+ plugin: string;
48
+ guideId: string;
49
+ place: SetupGuidePlace;
50
+ title: string;
51
+ /** Blocks joined; checks as `- [check <id>] <label>` lines. */
52
+ markdown: string;
53
+ modes: string[];
54
+ mode: string | null;
55
+ audiences: SetupGuideAudience[];
56
+ audience: SetupGuideAudience;
57
+ checks: {
58
+ id: string;
59
+ label: string;
60
+ }[];
61
+ unresolved: {
62
+ name: string;
63
+ reason: UnresolvedReason;
64
+ }[];
65
+ target: {
66
+ kind: "channel-connection" | "connector";
67
+ id: string;
68
+ } | null;
69
+ webPath: string;
70
+ /** What stays with the person, and where they do it. */
71
+ handover: string;
72
+ }
73
+ /** The web page of a target (`apps/web` routes; the guide sections live there). */
74
+ export declare const setupWebPath: {
75
+ connection: (id: string) => string;
76
+ newConnection: () => string;
77
+ connector: (id: string) => string;
78
+ /** A member's connector section: the connector's row on their connections page (§9.3). */
79
+ memberConnections: () => string;
80
+ template: (itemId: string) => string;
81
+ };
82
+ /** The blocks of a rendered guide as one Markdown document (checks as `- [check <id>]` lines). */
83
+ export declare function guideMarkdown(title: string, blocks: readonly SetupGuideBlock[]): string;
84
+ /**
85
+ * The setup guides of an org as a person sees them (spec 2026-11-09-setup-guides-v2-design.md §12:
86
+ * `GET /orgs/:orgId/setup-guides`, the `Platform` tool's `setup.guides` and `setup.read`). Nothing
87
+ * new is decided here: guides, visibility, drift and actions are `PluginGuides`' (the guide route's
88
+ * rules), check states are `SetupChecksService.states` (cached, nothing runs), the templates on in
89
+ * the org are `ComponentScopeService.orgList`'s. Rendered text carries no secret (§6.3) and check
90
+ * states only their fixed, redacted fields (§10.8). Stateless: everything comes from PG and Redis.
91
+ */
92
+ export declare class SetupGuidesService {
93
+ private readonly db;
94
+ private readonly guides;
95
+ private readonly checks;
96
+ private readonly components;
97
+ private readonly log;
98
+ constructor(db: KernelDb, guides: Pick<PluginGuides, "render" | "connectionStates" | "connectorStates">, checks: Pick<SetupChecksService, "states">, components: Pick<ComponentScopeService, "orgList">, log: Pick<KernelLogger, "warn">);
99
+ /**
100
+ * The guides the caller may see, each with its targets: org admins (`channels.manage`) the
101
+ * channel connections; org admins (`connectors.manage`) the connectors made from templates and
102
+ * the templates on in the org without a connector, admin audience; members (`connectors.use`
103
+ * without `connectors.manage`) the connectors whose template guide has a member section, member
104
+ * audience, without drift (only an admin acks). Each target carries its cached check summary.
105
+ * A guide that cannot be rendered (gone, a stored document unreadable) is left out and logged.
106
+ */
107
+ list(caller: CheckCaller, q?: {
108
+ locale?: "en" | "zh" | undefined;
109
+ }): Promise<SetupGuideListEntry[]>;
110
+ /** The cached check summary of a target, or null when the guide cannot be read for it. */
111
+ private summary;
112
+ private renderOrSkip;
113
+ /** The org's channel connections whose plugin has a guide the org sees. */
114
+ private connections;
115
+ /**
116
+ * The org's connectors made from a template with a guide; for org admins also the templates on
117
+ * in the org that have a guide and no connector yet.
118
+ */
119
+ private connectors;
120
+ /**
121
+ * One guide rendered for the caller as a Markdown document (`setup.read`): the guide route's
122
+ * rendering (`PluginGuides.render`, its visibility, action and target rules), joined, with the
123
+ * page where the person enters values and acks.
124
+ */
125
+ read(caller: CheckCaller, itemId: string, guideId: string, q: GuideQuery): Promise<SetupGuideDocument>;
126
+ }
@@ -0,0 +1,315 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
8
+ return function (target, key) { decorator(target, key, paramIndex); }
9
+ };
10
+ import { Inject, Injectable } from "@nestjs/common";
11
+ import { ComponentScopeService } from "../catalog/component-scope.service.js";
12
+ import { ApiError } from "../errors.js";
13
+ import { PluginGuides, } from "../extensions/plugin-guides.js";
14
+ import { KernelDb } from "../infra/database/kernel-db.js";
15
+ import { KERNEL_LOGGER } from "../infra/tokens.js";
16
+ import { can } from "../orgs/roles.js";
17
+ import { SetupChecksService } from "./setup-checks.service.js";
18
+ const enc = encodeURIComponent;
19
+ /** The web page of a target (`apps/web` routes; the guide sections live there). */
20
+ export const setupWebPath = {
21
+ connection: (id) => `/channels/${enc(id)}`,
22
+ newConnection: () => "/channels/new",
23
+ connector: (id) => `/connectors/${enc(id)}?tab=guide`,
24
+ /** A member's connector section: the connector's row on their connections page (§9.3). */
25
+ memberConnections: () => "/account/connections",
26
+ template: (itemId) => `/connectors/install?template=${enc(itemId)}`,
27
+ };
28
+ /**
29
+ * The page a rendered guide belongs on: a member's connections page for the member audience, else
30
+ * its target's, else where a target is created.
31
+ */
32
+ function webPathOf(g) {
33
+ if (g.audience === "member")
34
+ return setupWebPath.memberConnections();
35
+ if (g.target?.kind === "channel-connection")
36
+ return setupWebPath.connection(g.target.id);
37
+ if (g.target?.kind === "connector")
38
+ return setupWebPath.connector(g.target.id);
39
+ return g.place === "channel-connection"
40
+ ? setupWebPath.newConnection()
41
+ : setupWebPath.template(g.itemId);
42
+ }
43
+ /** A fence longer than any run of backticks in the text. */
44
+ function fenceFor(text) {
45
+ const longest = Math.max(0, ...[...text.matchAll(/`+/g)].map((m) => m[0].length));
46
+ return "`".repeat(Math.max(3, longest + 1));
47
+ }
48
+ /** The blocks of a rendered guide as one Markdown document (checks as `- [check <id>]` lines). */
49
+ export function guideMarkdown(title, blocks) {
50
+ const parts = [`# ${title}`];
51
+ for (const b of blocks) {
52
+ if (b.kind === "markdown")
53
+ parts.push(b.text.trimEnd());
54
+ else if (b.kind === "check")
55
+ parts.push(`- [check ${b.id}] ${b.label}`);
56
+ else {
57
+ const fence = fenceFor(b.text);
58
+ const code = `${fence}${b.language}\n${b.text.replace(/\n$/, "")}\n${fence}`;
59
+ parts.push(b.title === null ? code : `**${b.title}**\n\n${code}`);
60
+ }
61
+ }
62
+ return `${parts.join("\n\n")}\n`;
63
+ }
64
+ function summarize(states) {
65
+ const out = { ok: 0, failing: 0, unknown: 0, running: 0, notRun: 0 };
66
+ for (const s of Object.values(states)) {
67
+ if (s.status === "ok")
68
+ out.ok += 1;
69
+ else if (s.status === "failing")
70
+ out.failing += 1;
71
+ else if (s.status === "running")
72
+ out.running += 1;
73
+ else if (s.status === "not_run")
74
+ out.notRun += 1;
75
+ else
76
+ out.unknown += 1;
77
+ }
78
+ return out;
79
+ }
80
+ /**
81
+ * The setup guides of an org as a person sees them (spec 2026-11-09-setup-guides-v2-design.md §12:
82
+ * `GET /orgs/:orgId/setup-guides`, the `Platform` tool's `setup.guides` and `setup.read`). Nothing
83
+ * new is decided here: guides, visibility, drift and actions are `PluginGuides`' (the guide route's
84
+ * rules), check states are `SetupChecksService.states` (cached, nothing runs), the templates on in
85
+ * the org are `ComponentScopeService.orgList`'s. Rendered text carries no secret (§6.3) and check
86
+ * states only their fixed, redacted fields (§10.8). Stateless: everything comes from PG and Redis.
87
+ */
88
+ let SetupGuidesService = class SetupGuidesService {
89
+ db;
90
+ guides;
91
+ checks;
92
+ components;
93
+ log;
94
+ constructor(db, guides, checks, components, log) {
95
+ this.db = db;
96
+ this.guides = guides;
97
+ this.checks = checks;
98
+ this.components = components;
99
+ this.log = log;
100
+ }
101
+ /**
102
+ * The guides the caller may see, each with its targets: org admins (`channels.manage`) the
103
+ * channel connections; org admins (`connectors.manage`) the connectors made from templates and
104
+ * the templates on in the org without a connector, admin audience; members (`connectors.use`
105
+ * without `connectors.manage`) the connectors whose template guide has a member section, member
106
+ * audience, without drift (only an admin acks). Each target carries its cached check summary.
107
+ * A guide that cannot be rendered (gone, a stored document unreadable) is left out and logged.
108
+ */
109
+ async list(caller, q = {}) {
110
+ const org = { orgId: caller.orgId, role: caller.role };
111
+ const pending = [
112
+ ...(can(caller.role, "channels.manage") ? await this.connections(org) : []),
113
+ ...(await this.connectors(org)),
114
+ ];
115
+ const entries = new Map();
116
+ for (const p of pending) {
117
+ const key = `${p.itemId}/${p.guideId}/${p.audience}`;
118
+ let entry = entries.get(key);
119
+ if (entry === undefined) {
120
+ const g = await this.renderOrSkip(org, p.itemId, p.guideId, {
121
+ audience: p.audience,
122
+ locale: q.locale ?? "en",
123
+ });
124
+ // members get a guide only when it has a member section (the web's How to connect rule, §9.3);
125
+ // text outside every section applies to all audiences, so admins need no section
126
+ if (g === null || (p.audience === "member" && !g.audiences.includes("member")))
127
+ continue;
128
+ entry = {
129
+ itemId: g.itemId,
130
+ plugin: g.plugin,
131
+ guideId: g.id,
132
+ place: g.place,
133
+ template: g.template,
134
+ audience: p.audience,
135
+ title: g.title,
136
+ targets: [],
137
+ };
138
+ entries.set(key, entry);
139
+ }
140
+ const { checked, ...target } = p.target;
141
+ entry.targets.push({
142
+ ...target,
143
+ checks: checked ? await this.summary(caller, p) : null,
144
+ });
145
+ }
146
+ return [...entries.values()];
147
+ }
148
+ /** The cached check summary of a target, or null when the guide cannot be read for it. */
149
+ async summary(caller, p) {
150
+ try {
151
+ return summarize(await this.checks.states(caller, p.itemId, p.guideId, {
152
+ audience: p.audience,
153
+ connectionId: p.query.connectionId,
154
+ connectorId: p.query.connectorId,
155
+ }));
156
+ }
157
+ catch (err) {
158
+ if (err instanceof ApiError)
159
+ return null;
160
+ throw err;
161
+ }
162
+ }
163
+ async renderOrSkip(org, itemId, guideId, q) {
164
+ try {
165
+ return await this.guides.render(org, itemId, guideId, q);
166
+ }
167
+ catch (err) {
168
+ if (!(err instanceof ApiError))
169
+ throw err;
170
+ this.log.warn("setup guide left out of the list", { itemId, guideId, code: err.code });
171
+ return null;
172
+ }
173
+ }
174
+ /** The org's channel connections whose plugin has a guide the org sees. */
175
+ async connections(org) {
176
+ const rows = await this.db
177
+ .selectFrom("channel_connections")
178
+ .select(["id", "plugin", "name", "guide_ack"])
179
+ .where("org_id", "=", org.orgId)
180
+ .where("deleted_at", "is", null)
181
+ .orderBy("created_at")
182
+ .orderBy("id")
183
+ .execute();
184
+ const states = await this.guides.connectionStates(rows.map((r) => ({ id: r.id, plugin: r.plugin, guideAck: r.guide_ack ?? null })), org);
185
+ return rows.flatMap((r) => pendingOf(states.get(r.id), "admin", {
186
+ kind: "channel-connection",
187
+ id: r.id,
188
+ name: r.name,
189
+ query: { connectionId: r.id },
190
+ webPath: setupWebPath.connection(r.id),
191
+ }));
192
+ }
193
+ /**
194
+ * The org's connectors made from a template with a guide; for org admins also the templates on
195
+ * in the org that have a guide and no connector yet.
196
+ */
197
+ async connectors(org) {
198
+ const admin = can(org.role, "connectors.manage");
199
+ if (!admin && !can(org.role, "connectors.use"))
200
+ return [];
201
+ const audience = admin ? "admin" : "member";
202
+ const rows = await this.db
203
+ .selectFrom("connectors as c")
204
+ .leftJoin("platform_connector_templates as t", "t.id", "c.platform_template_id")
205
+ .select([
206
+ "c.id",
207
+ "c.name",
208
+ "c.guide_ack",
209
+ "c.platform_template_id",
210
+ "t.catalog_item_id",
211
+ "t.template_id",
212
+ ])
213
+ .where("c.org_id", "=", org.orgId)
214
+ .where("c.deleted_at", "is", null)
215
+ .orderBy("c.created_at")
216
+ .orderBy("c.id")
217
+ .execute();
218
+ const states = await this.guides.connectorStates(rows.map((r) => ({
219
+ id: r.id,
220
+ itemId: r.catalog_item_id ?? null,
221
+ templateId: r.template_id ?? null,
222
+ guideAck: r.guide_ack ?? null,
223
+ })), org);
224
+ const out = rows.flatMap((r) => {
225
+ const state = states.get(r.id);
226
+ return pendingOf(state && (admin ? state : { ...state, guideDrift: null }), audience, {
227
+ kind: "connector",
228
+ id: r.id,
229
+ name: r.name,
230
+ query: { connectorId: r.id },
231
+ webPath: admin ? setupWebPath.connector(r.id) : setupWebPath.memberConnections(),
232
+ });
233
+ });
234
+ if (!admin)
235
+ return out;
236
+ const made = new Set(rows.flatMap((r) => (r.platform_template_id ? [r.platform_template_id] : [])));
237
+ const templates = await this.components.orgList(org.orgId, { kind: "connector_template" });
238
+ for (const t of templates) {
239
+ if (!t.on || made.has(t.id) || !t.guide || t.itemId === null)
240
+ continue;
241
+ out.push({
242
+ itemId: t.guide.itemId,
243
+ guideId: t.guide.guideId,
244
+ audience,
245
+ query: {},
246
+ target: {
247
+ kind: "connector-template",
248
+ id: t.name,
249
+ name: t.displayName?.en ?? t.name,
250
+ drift: null,
251
+ webPath: setupWebPath.template(t.itemId),
252
+ checked: false,
253
+ },
254
+ });
255
+ }
256
+ return out;
257
+ }
258
+ /**
259
+ * One guide rendered for the caller as a Markdown document (`setup.read`): the guide route's
260
+ * rendering (`PluginGuides.render`, its visibility, action and target rules), joined, with the
261
+ * page where the person enters values and acks.
262
+ */
263
+ async read(caller, itemId, guideId, q) {
264
+ const g = await this.guides.render({ orgId: caller.orgId, role: caller.role }, itemId, guideId, q);
265
+ const webPath = webPathOf(g);
266
+ return {
267
+ itemId: g.itemId,
268
+ plugin: g.plugin,
269
+ guideId: g.id,
270
+ place: g.place,
271
+ title: g.title,
272
+ markdown: guideMarkdown(g.title, g.blocks),
273
+ modes: g.modes,
274
+ mode: g.mode,
275
+ audiences: g.audiences,
276
+ audience: g.audience,
277
+ checks: g.checks.map((c) => ({ id: c.id, label: c.label })),
278
+ unresolved: g.unresolved,
279
+ target: g.target,
280
+ webPath,
281
+ handover: `Secrets (tokens, signing secrets, client secrets) are typed by the person into their fields on ${webPath}; never ask for one in the conversation. Connecting a personal account and confirming the app was updated (the guide acknowledgement) also happen on that page.`,
282
+ };
283
+ }
284
+ };
285
+ SetupGuidesService = __decorate([
286
+ Injectable(),
287
+ __param(0, Inject(KernelDb)),
288
+ __param(1, Inject(PluginGuides)),
289
+ __param(2, Inject(SetupChecksService)),
290
+ __param(3, Inject(ComponentScopeService)),
291
+ __param(4, Inject(KERNEL_LOGGER))
292
+ ], SetupGuidesService);
293
+ export { SetupGuidesService };
294
+ /** A pending target from a guide state; none when the target has no guide the org sees. */
295
+ function pendingOf(state, audience, t) {
296
+ const ref = state?.setupGuide;
297
+ if (!ref)
298
+ return [];
299
+ return [
300
+ {
301
+ itemId: ref.itemId,
302
+ guideId: ref.guideId,
303
+ audience,
304
+ query: t.query,
305
+ target: {
306
+ kind: t.kind,
307
+ id: t.id,
308
+ name: t.name,
309
+ drift: state.guideDrift,
310
+ webPath: t.webPath,
311
+ checked: true,
312
+ },
313
+ },
314
+ ];
315
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Setup guide checks (spec 2026-11-09-setup-guides-v2-design.md §10): the registry of kernel and
3
+ * extension checks, the kernel's own `capybara:*` checks (registered on init) and the service that runs them (cache, single flight, cooldown, the org's
4
+ * concurrency cap, the time limit, redaction), and the org's guide list (`SetupGuidesService`). Its
5
+ * controllers are the api role's; the `Platform` tool's `setup` operations use the same services.
6
+ */
7
+ export declare class SetupModule {
8
+ }
@@ -0,0 +1,31 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { Module } from "@nestjs/common";
8
+ import { CatalogModule } from "../catalog/catalog.module.js";
9
+ import { ChannelsModule } from "../channels/channels.module.js";
10
+ import { ExtensionsModule } from "../extensions/extensions.module.js";
11
+ import { ToolsModule } from "../tools/tools.module.js";
12
+ import { KernelChecks } from "./kernel-checks.js";
13
+ import { SetupCheckRegistry } from "./setup-check-registry.js";
14
+ import { SetupChecksService } from "./setup-checks.service.js";
15
+ import { SetupGuidesService } from "./setup-guides.service.js";
16
+ /**
17
+ * Setup guide checks (spec 2026-11-09-setup-guides-v2-design.md §10): the registry of kernel and
18
+ * extension checks, the kernel's own `capybara:*` checks (registered on init) and the service that runs them (cache, single flight, cooldown, the org's
19
+ * concurrency cap, the time limit, redaction), and the org's guide list (`SetupGuidesService`). Its
20
+ * controllers are the api role's; the `Platform` tool's `setup` operations use the same services.
21
+ */
22
+ let SetupModule = class SetupModule {
23
+ };
24
+ SetupModule = __decorate([
25
+ Module({
26
+ imports: [ExtensionsModule, ChannelsModule, ToolsModule, CatalogModule],
27
+ providers: [SetupCheckRegistry, SetupChecksService, SetupGuidesService, KernelChecks],
28
+ exports: [SetupCheckRegistry, SetupChecksService, SetupGuidesService],
29
+ })
30
+ ], SetupModule);
31
+ export { SetupModule };
@@ -7,6 +7,7 @@ import { type ParsedTheme } from "@capybara-hq/plugin-sdk";
7
7
  * the theme's families first, then the built-in stack. Every stored value is re-validated (token
8
8
  * names against the overridable list, values against the SDK grammar, radius 0-16, families,
9
9
  * paths, weights, styles, roles); one that fails is left out, and `onSkipped` (when given and
10
- * something was skipped) is called once with the paths of every skipped value.
10
+ * something was skipped) is called once with the paths of every skipped value. The whole
11
+ * stylesheet is one `@layer theme { … }` block (client extensions spec §7.2).
11
12
  */
12
13
  export declare function renderThemeCss(theme: ParsedTheme, assetUrl: (pluginPath: string) => string, onSkipped?: (paths: string[]) => void): string;
@@ -73,7 +73,8 @@ const declarations = (d, extra = []) => [...extra, ...[...d].map(([k, v]) => `${
73
73
  * the theme's families first, then the built-in stack. Every stored value is re-validated (token
74
74
  * names against the overridable list, values against the SDK grammar, radius 0-16, families,
75
75
  * paths, weights, styles, roles); one that fails is left out, and `onSkipped` (when given and
76
- * something was skipped) is called once with the paths of every skipped value.
76
+ * something was skipped) is called once with the paths of every skipped value. The whole
77
+ * stylesheet is one `@layer theme { … }` block (client extensions spec §7.2).
77
78
  */
78
79
  export function renderThemeCss(theme, assetUrl, onSkipped) {
79
80
  const skipped = [];
@@ -128,5 +129,7 @@ export function renderThemeCss(theme, assetUrl, onSkipped) {
128
129
  rules.push(`:root { ${stacks.join(" ")} }`);
129
130
  if (skipped.length > 0)
130
131
  onSkipped?.(skipped);
131
- return `${rules.join("\n")}\n`;
132
+ // One block in the `theme` cascade layer (client extensions spec §7.2): the design stylesheet
133
+ // declares the order design, theme, extensions; an unlayered theme would beat plugin CSS.
134
+ return `@layer theme {\n${rules.join("\n")}\n}\n`;
132
135
  }
@@ -22,9 +22,11 @@ export declare class ThemeDeliveryService {
22
22
  /**
23
23
  * The stylesheet of version `version` of theme `id` ({@link renderThemeCss}, asset URLs from
24
24
  * {@link themeUrl}); a stored value that fails re-validation is left out with one warning per
25
- * render naming every skipped value. Unknown theme or version: 404 `theme.not_found`.
25
+ * render naming every skipped value. Unknown theme or version, or a `revision` other than the
26
+ * current `r<THEME_CSS_REVISION>` (an older one promised another format; a later one belongs to a
27
+ * newer replica): 404 `theme.not_found`, never cached.
26
28
  */
27
- stylesheet(id: string, version: string): Promise<string>;
29
+ stylesheet(id: string, version: string, revision: string): Promise<string>;
28
30
  /**
29
31
  * Asset `file` (a path inside the plugin, as stored: `fonts/MiSans.woff2`) of version `version`
30
32
  * of theme `id`: only a font or logo the version's definition names. Unknown theme, version or
@@ -15,7 +15,7 @@ import { KERNEL_LOGGER } from "../infra/tokens.js";
15
15
  import { packagePath } from "../packages/tgz.js";
16
16
  import { StorageService } from "../storage/storage.service.js";
17
17
  import { renderThemeCss } from "./render-css.js";
18
- import { themeUrl } from "./theme-state.js";
18
+ import { THEME_CSS_REVISION, themeUrl } from "./theme-state.js";
19
19
  const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
20
20
  /** A version number as it appears in a delivery URL: a positive int4. */
21
21
  const VERSION_RE = /^[1-9]\d{0,9}$/;
@@ -61,9 +61,14 @@ let ThemeDeliveryService = class ThemeDeliveryService {
61
61
  /**
62
62
  * The stylesheet of version `version` of theme `id` ({@link renderThemeCss}, asset URLs from
63
63
  * {@link themeUrl}); a stored value that fails re-validation is left out with one warning per
64
- * render naming every skipped value. Unknown theme or version: 404 `theme.not_found`.
64
+ * render naming every skipped value. Unknown theme or version, or a `revision` other than the
65
+ * current `r<THEME_CSS_REVISION>` (an older one promised another format; a later one belongs to a
66
+ * newer replica): 404 `theme.not_found`, never cached.
65
67
  */
66
- async stylesheet(id, version) {
68
+ async stylesheet(id, version, revision) {
69
+ if (revision !== `r${THEME_CSS_REVISION}`) {
70
+ throw notFound(`Theme ${id} version ${version} stylesheet revision ${revision}`);
71
+ }
67
72
  const { definition } = await this.version(id, version);
68
73
  return renderThemeCss(definition, (path) => themeUrl(id, version, path), (skipped) => this.log.warn("theme stylesheet: stored values failed re-validation and were left out", {
69
74
  themeId: id,