@deveye/types 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +23 -0
  3. package/package.json +68 -0
  4. package/src/domain/audience.ts +549 -0
  5. package/src/domain/backup.ts +355 -0
  6. package/src/domain/credential.ts +55 -0
  7. package/src/domain/database.ts +467 -0
  8. package/src/domain/deploy.ts +231 -0
  9. package/src/domain/device.ts +172 -0
  10. package/src/domain/deviceFiles.ts +84 -0
  11. package/src/domain/deviceLogs.ts +82 -0
  12. package/src/domain/featureRegistry.ts +392 -0
  13. package/src/domain/finance.ts +477 -0
  14. package/src/domain/git.ts +419 -0
  15. package/src/domain/home.ts +314 -0
  16. package/src/domain/live.ts +272 -0
  17. package/src/domain/logs.ts +117 -0
  18. package/src/domain/mail.ts +394 -0
  19. package/src/domain/metrics.ts +127 -0
  20. package/src/domain/note.ts +202 -0
  21. package/src/domain/notifications.ts +268 -0
  22. package/src/domain/packages.ts +35 -0
  23. package/src/domain/password.ts +36 -0
  24. package/src/domain/presence.ts +21 -0
  25. package/src/domain/project.ts +168 -0
  26. package/src/domain/projectBoard.ts +130 -0
  27. package/src/domain/projectChat.ts +46 -0
  28. package/src/domain/projectHistory.ts +82 -0
  29. package/src/domain/projectLink.ts +87 -0
  30. package/src/domain/projectPlan.ts +68 -0
  31. package/src/domain/report.ts +492 -0
  32. package/src/domain/role.ts +8 -0
  33. package/src/domain/secrecy.ts +66 -0
  34. package/src/domain/sentinel.ts +623 -0
  35. package/src/domain/sharing.ts +186 -0
  36. package/src/domain/syncProtocol.ts +116 -0
  37. package/src/domain/twoFactor.ts +40 -0
  38. package/src/domain/uptime.ts +216 -0
  39. package/src/domain/user.ts +141 -0
  40. package/src/domain/workspace.ts +56 -0
  41. package/src/domain/workspaceRole.ts +251 -0
  42. package/src/features/admin.ts +112 -0
  43. package/src/features/audience.ts +275 -0
  44. package/src/features/backup.ts +230 -0
  45. package/src/features/database.ts +461 -0
  46. package/src/features/deploy.ts +245 -0
  47. package/src/features/device.ts +292 -0
  48. package/src/features/deviceFiles.ts +83 -0
  49. package/src/features/deviceLogs.ts +36 -0
  50. package/src/features/deviceTerminal.ts +57 -0
  51. package/src/features/finance.ts +360 -0
  52. package/src/features/git.ts +368 -0
  53. package/src/features/home.ts +32 -0
  54. package/src/features/live.ts +113 -0
  55. package/src/features/logs.ts +86 -0
  56. package/src/features/mail.ts +374 -0
  57. package/src/features/metrics.ts +185 -0
  58. package/src/features/note.ts +189 -0
  59. package/src/features/notify.ts +164 -0
  60. package/src/features/password.ts +67 -0
  61. package/src/features/project.ts +709 -0
  62. package/src/features/registry.ts +103 -0
  63. package/src/features/secrecy.ts +120 -0
  64. package/src/features/sentinel.ts +233 -0
  65. package/src/features/sharing.ts +79 -0
  66. package/src/features/twoFactor.ts +47 -0
  67. package/src/features/uptime.ts +186 -0
  68. package/src/features/user.ts +91 -0
  69. package/src/features/workspace.ts +200 -0
  70. package/src/http/auth.ts +94 -0
  71. package/src/http/device.ts +222 -0
  72. package/src/http/status.ts +45 -0
  73. package/src/index.ts +1700 -0
  74. package/src/protocol/agent.ts +1171 -0
  75. package/src/protocol/envelope.ts +46 -0
  76. package/src/protocol/error.ts +27 -0
  77. package/src/protocol/result.ts +17 -0
  78. package/src/protocol/version.ts +6 -0
  79. package/src/sdk/client-ambient.d.ts +238 -0
  80. package/src/sdk/client.ts +66 -0
  81. package/src/sdk/ids.ts +25 -0
  82. package/src/sdk/index.ts +11 -0
  83. package/src/sdk/manifest.ts +326 -0
  84. package/src/sdk/providers.ts +48 -0
  85. package/src/sdk/server.ts +378 -0
  86. package/src/sdk/testing.ts +179 -0
  87. package/src/utils/version.ts +28 -0
@@ -0,0 +1,202 @@
1
+ import { z } from 'zod';
2
+
3
+ /**
4
+ * A note's body is a modular list of typed blocks rather than free text. This
5
+ * keeps the editor "à la Apple" (one clean surface) while still supporting
6
+ * paragraphs and checklists in any order:
7
+ * - `text` → a paragraph.
8
+ * - `check` → a checklist item, with its own `done` state.
9
+ *
10
+ * - `bullet` → a bulleted (unordered) list item.
11
+ * - `number` → a numbered (ordered) list item; its visible index is derived
12
+ * from its position in the run of consecutive `number` blocks (not stored).
13
+ * - `heading` → a section title, `level` 1 (largest) to 5 (smallest).
14
+ * - `divider` → a horizontal separator; carries no text.
15
+ *
16
+ * Inline emphasis (bold `**`, italic `*`, underline `__`, strikethrough `~~`)
17
+ * is kept as markdown markers inside a block's `text`, not as separate blocks.
18
+ *
19
+ * The shape is intentionally small and forward-compatible: adding a new block
20
+ * kind later (heading, divider, …) only widens this union.
21
+ */
22
+ /**
23
+ * Named colour palette shared by the two colouring features:
24
+ * - inline text colour, carried as `{c:name}…{/c}` markers inside a block's
25
+ * `text` (so it lives in the freeform string, not the schema);
26
+ * - the block-level `color` below, which tints a marker (bullet dot, ordinal,
27
+ * checkbox, divider rule).
28
+ *
29
+ * A *named* palette (not a free hex) keeps the stored value tied to a theme
30
+ * token (`--note-<name>`), so colours stay coherent with the app's design and
31
+ * adapt if the palette is retuned. Widen this enum to add a colour.
32
+ */
33
+ export const noteColorSchema = z.enum(['red', 'orange', 'yellow', 'green', 'blue', 'purple']);
34
+
35
+ export type NoteColor = z.infer<typeof noteColorSchema>;
36
+
37
+ export const noteTextBlockSchema = z.object({
38
+ type: z.literal('text'),
39
+ text: z.string()
40
+ });
41
+
42
+ export const noteCheckBlockSchema = z.object({
43
+ type: z.literal('check'),
44
+ text: z.string(),
45
+ done: z.boolean(),
46
+ /** Optional tint for the checkbox marker (theme token `--note-<color>`). */
47
+ color: noteColorSchema.optional()
48
+ });
49
+
50
+ export const noteBulletBlockSchema = z.object({
51
+ type: z.literal('bullet'),
52
+ text: z.string(),
53
+ /** Optional tint for the bullet dot (theme token `--note-<color>`). */
54
+ color: noteColorSchema.optional()
55
+ });
56
+
57
+ export const noteNumberBlockSchema = z.object({
58
+ type: z.literal('number'),
59
+ text: z.string(),
60
+ /** Optional tint for the ordinal marker (theme token `--note-<color>`). */
61
+ color: noteColorSchema.optional()
62
+ });
63
+
64
+ export const noteHeadingBlockSchema = z.object({
65
+ type: z.literal('heading'),
66
+ text: z.string(),
67
+ /** Heading level, 1 (largest) to 5 (smallest). */
68
+ level: z.number().int().min(1).max(5)
69
+ });
70
+
71
+ export const noteDividerBlockSchema = z.object({
72
+ type: z.literal('divider'),
73
+ /** Optional tint for the horizontal rule (theme token `--note-<color>`). */
74
+ color: noteColorSchema.optional()
75
+ });
76
+
77
+ export const noteBlockSchema = z.discriminatedUnion('type', [
78
+ noteTextBlockSchema,
79
+ noteCheckBlockSchema,
80
+ noteBulletBlockSchema,
81
+ noteNumberBlockSchema,
82
+ noteHeadingBlockSchema,
83
+ noteDividerBlockSchema
84
+ ]);
85
+
86
+ export type NoteTextBlock = z.infer<typeof noteTextBlockSchema>;
87
+ export type NoteCheckBlock = z.infer<typeof noteCheckBlockSchema>;
88
+ export type NoteBulletBlock = z.infer<typeof noteBulletBlockSchema>;
89
+ export type NoteNumberBlock = z.infer<typeof noteNumberBlockSchema>;
90
+ export type NoteHeadingBlock = z.infer<typeof noteHeadingBlockSchema>;
91
+ export type NoteDividerBlock = z.infer<typeof noteDividerBlockSchema>;
92
+ export type NoteBlock = z.infer<typeof noteBlockSchema>;
93
+
94
+ /** Upper bounds, enforced both client- and server-side, to keep rows sane. */
95
+ export const NOTE_TITLE_MAX_LENGTH = 200;
96
+ export const NOTE_FOLDER_MAX_LENGTH = 80;
97
+ export const NOTE_BLOCK_TEXT_MAX_LENGTH = 5_000;
98
+ export const NOTE_MAX_BLOCKS = 500;
99
+
100
+ /**
101
+ * A folder is a first-class, server-persisted bucket. Its `name` is stored
102
+ * encrypted server-side (with the open key, so the folder tree is readable
103
+ * without a password); only the linkage (`folderId` on notes) is kept in clear.
104
+ * `id` is stable across renames, so notes reference it directly.
105
+ */
106
+ export const noteFolderSchema = z.object({
107
+ id: z.number().int().positive(),
108
+ name: z.string().max(NOTE_FOLDER_MAX_LENGTH),
109
+ /** Rank within the user's folders; lower comes first. Manually reorderable. */
110
+ sortOrder: z.number().int().nonnegative()
111
+ });
112
+
113
+ export type NoteFolder = z.infer<typeof noteFolderSchema>;
114
+
115
+ /**
116
+ * A full note as exchanged with the client. `folderId` references a
117
+ * {@link noteFolderSchema}; `null` means "no folder" (Sans dossier).
118
+ *
119
+ * A **private** note is encrypted with the password-wrapped DEK, so reading or
120
+ * writing it requires the session to be unlocked; a regular note is encrypted
121
+ * with the user's open key, which the server can always resolve — that's what
122
+ * lets the feature open without any prompt.
123
+ */
124
+ export const noteSchema = z.object({
125
+ id: z.number().int().nonnegative(),
126
+ title: z.string().max(NOTE_TITLE_MAX_LENGTH),
127
+ folderId: z.number().int().positive().nullable(),
128
+ blocks: z.array(noteBlockSchema).max(NOTE_MAX_BLOCKS),
129
+ /** Rank within its folder; lower comes first. Only drag & drop changes it. */
130
+ sortOrder: z.number().int().nonnegative(),
131
+ /** True when the note is encrypted with the password-protected key. */
132
+ private: z.boolean(),
133
+ /** Epoch seconds; set by the server, surfaced for sorting/display. */
134
+ updated: z.number().int().nonnegative(),
135
+ /** Epoch seconds the note was first created. */
136
+ created: z.number().int().nonnegative()
137
+ });
138
+
139
+ export type Note = z.infer<typeof noteSchema>;
140
+
141
+ /**
142
+ * Lightweight list variant. A private note listed while the session is locked
143
+ * comes back **masked** — metadata only, no `title`/`preview` — so the UI can
144
+ * render a padlock placeholder without the body ever being decrypted.
145
+ */
146
+ export const noteSummarySchema = z.object({
147
+ id: z.number().int().nonnegative(),
148
+ title: z.string(),
149
+ folderId: z.number().int().positive().nullable(),
150
+ /** Rank within its folder; lower comes first. Only drag & drop changes it. */
151
+ sortOrder: z.number().int().nonnegative(),
152
+ /** Present only when the body is readable; absent for masked notes. */
153
+ preview: z.string().optional(),
154
+ /** Total checklist items / how many are done — for an at-a-glance summary. */
155
+ checkTotal: z.number().int().nonnegative(),
156
+ checkDone: z.number().int().nonnegative(),
157
+ /** True when the note is encrypted with the password-protected key. */
158
+ private: z.boolean(),
159
+ /** True when the body stayed encrypted for this response (private + locked). */
160
+ masked: z.boolean(),
161
+ /** Epoch seconds the note was archived, or `null` while it is active. */
162
+ archivedAt: z.number().int().nonnegative().nullable(),
163
+ updated: z.number().int().nonnegative(),
164
+ /** Epoch seconds the note was first created. */
165
+ created: z.number().int().nonnegative()
166
+ });
167
+
168
+ export type NoteSummary = z.infer<typeof noteSummarySchema>;
169
+
170
+ export interface NoteRow {
171
+ id: number;
172
+ user_id: number;
173
+ workspace_id: number | null;
174
+ folder_id: number | null;
175
+ /**
176
+ * Encrypted JSON payload (title + blocks), keyed by the private DEK when
177
+ * `is_private`, by the user's open DEK otherwise.
178
+ */
179
+ content: string;
180
+ /** Manual rank within its folder; lower comes first. */
181
+ sort_order: number;
182
+ /** 1 when the body is encrypted with the password-protected key. */
183
+ is_private: number;
184
+ /**
185
+ * Epoch seconds the note was archived; `NULL` while active. Deleting a note
186
+ * archives it — only an already-archived note can be destroyed for good.
187
+ */
188
+ archived_at: number | null;
189
+ updated: number;
190
+ created: number;
191
+ }
192
+
193
+ export interface NoteFolderRow {
194
+ id: number;
195
+ user_id: number;
196
+ workspace_id: number | null;
197
+ /** Encrypted JSON payload (`{ name }`). */
198
+ content: string;
199
+ /** Manual rank within the user's folders; lower comes first. */
200
+ sort_order: number;
201
+ created: number;
202
+ }
@@ -0,0 +1,268 @@
1
+ import { z } from 'zod';
2
+
3
+ import { NOTIFYING_FEATURES } from './featureRegistry';
4
+ import { externalFeatureIdSchema } from './workspaceRole';
5
+
6
+ /**
7
+ * Les canaux d'alerte d'un espace — **une liste, et des liaisons vers elle**.
8
+ *
9
+ * ## Ce que le modèle précédent ne savait pas faire
10
+ *
11
+ * Il portait deux canaux binaires — un mail, un webhook — par couple
12
+ * `(espace, feature)`. Trois conséquences, toutes rencontrées :
13
+ *
14
+ * 1. **Le même salon Discord était redéclaré cinq fois.** Le corriger demandait
15
+ * d'ouvrir cinq écrans, et en oublier un ne se voyait qu'à la première alerte
16
+ * qui n'arrivait plus.
17
+ * 2. **Deux destinataires étaient impossibles.** Une équipe pour la production,
18
+ * une autre pour la recette : il fallait choisir.
19
+ * 3. **Aucun routage par élément.** Toutes les bases d'un espace prévenaient les
20
+ * mêmes gens, quel que soit le projet derrière.
21
+ *
22
+ * ## La forme retenue
23
+ *
24
+ * Un **canal** est une destination nommée : un type, un libellé, une cible. Il
25
+ * appartient à **une fonctionnalité** (091) : c'est une source de cette
26
+ * fonctionnalité, au même titre qu'un jeton Dokploy pour le Déploiement, et il
27
+ * se gère dans ses réglages à elle. La 087 l'avait fait vivre à l'échelle de
28
+ * l'espace, partagé par les cinq émetteurs ; on retrouvait alors une liste
29
+ * commune gérée depuis cinq endroits, l'inverse du patron des sources. Le prix
30
+ * assumé du retour : deux features qui préviennent le même salon le déclarent
31
+ * deux fois. On en déclare autant qu'on veut, et on les corrige **à un seul
32
+ * endroit** : les réglages de leur fonctionnalité.
33
+ *
34
+ * Une **route** dit qui écrit vers quels canaux, et la sélection vit **sur
35
+ * l'élément** (092) : chaque cible coche un ou plusieurs canaux de sa feature
36
+ * dans ses propres réglages, et sans sélection rien ne part. L'héritage
37
+ * d'une « route de la fonctionnalité » a été essayé (087) puis retiré : cocher
38
+ * un canal à l'échelle de la feature ne visait aucun élément nommable, et les
39
+ * cases des éléments, grisées tant qu'ils « suivaient » la feature, semblaient
40
+ * ne jamais pouvoir se cocher. Une route de fonctionnalité (`itemId` absent)
41
+ * ne subsiste que pour les émetteurs **sans éléments** (Sentinelle), dont les
42
+ * alertes ne visent rien de plus fin.
43
+ *
44
+ * Ce qui n'a pas changé, et qui compte : **tout est éteint par défaut.** Sans
45
+ * canal ni route, rien ne part. Une fonctionnalité qui se met à écrire à des
46
+ * gens sans qu'ils l'aient demandé reste le travers que ce modèle refuse.
47
+ */
48
+
49
+ /**
50
+ * Le type d'un canal, et ce qu'il change à l'envoi.
51
+ *
52
+ * `webhook` et `discord` **séparent ce que l'URL devinait**. Le module d'envoi
53
+ * reconnaissait Discord en analysant l'adresse, ce qui marchait mais décidait à
54
+ * la place de l'utilisateur : un point d'entrée maison hébergé derrière un
55
+ * domaine Discord aurait reçu des embeds au lieu de son texte, et rien ne
56
+ * permettait de demander l'inverse. C'est désormais une déclaration.
57
+ *
58
+ * - `email` — un compte Mail « open » de l'espace expédie vers une adresse.
59
+ * - `webhook` — un POST JSON générique : le message lisible est répété dans
60
+ * `content` (Discord) et `text` (Slack), les champs structurés suivent pour
61
+ * un point d'entrée maison. Aucune des trois têtes ne gêne les autres.
62
+ * - `discord` — la mise en page riche de Discord (embeds, couleurs, champs),
63
+ * et pour le déploiement le **suivi vivant** : un seul message qui se met à
64
+ * jour du début à la fin.
65
+ */
66
+ export const notificationChannelKindSchema = z.enum(['email', 'webhook', 'discord']);
67
+ export type NotificationChannelKind = z.infer<typeof notificationChannelKindSchema>;
68
+
69
+ export const NOTIFICATION_CHANNEL_KINDS = notificationChannelKindSchema.options;
70
+
71
+ /**
72
+ * Les fonctionnalités **natives** qui savent prévenir.
73
+ *
74
+ * Enum fermé plutôt que chaîne libre : c'est lui qui garde une route d'être
75
+ * posée sur une fonctionnalité qui n'écrira jamais. Il double le drapeau
76
+ * `notifies` du registre, et le contrôle en bas de fichier interdit qu'ils
77
+ * divergent.
78
+ */
79
+ export const nativeNotificationFeatureSchema = z.enum([
80
+ 'uptime',
81
+ 'sentinel',
82
+ 'database',
83
+ 'deploy',
84
+ 'backup'
85
+ ]);
86
+ export type NativeNotificationFeature = z.infer<typeof nativeNotificationFeatureSchema>;
87
+
88
+ /**
89
+ * Un module externe peut prévenir aussi. Le schéma n'atteste que la **forme**
90
+ * de l'id : la garde de fond (« ce module déclare bien `notifies` ») ne peut
91
+ * pas vivre ici, elle dépend de l'installation ; le serveur la tient contre le
92
+ * registre fusionné, au même endroit que `assertChannels`.
93
+ */
94
+ export const notificationFeatureSchema = z.union([
95
+ nativeNotificationFeatureSchema,
96
+ externalFeatureIdSchema
97
+ ]);
98
+ export type NotificationFeature = z.infer<typeof notificationFeatureSchema>;
99
+
100
+ export const NOTIFICATION_LABEL_MAX = 64;
101
+ export const NOTIFICATION_TARGET_MAX = 2048;
102
+ export const NOTIFICATION_EMAIL_MAX = 320;
103
+
104
+ /**
105
+ * Un canal tel que le client le reçoit.
106
+ *
107
+ * `target` sort **en clair** : c'est une adresse que son auteur a saisie et doit
108
+ * pouvoir relire pour la corriger. Elle est chiffrée au repos (étage ouvert),
109
+ * comme l'étaient déjà les réglages qu'elle remplace.
110
+ */
111
+ export const notificationChannelSchema = z.object({
112
+ id: z.number().int().positive(),
113
+ kind: notificationChannelKindSchema,
114
+ /** Nom donné par l'utilisateur — « Astreinte », « #ops », « Webhook Grafana ». */
115
+ label: z.string().min(1).max(NOTIFICATION_LABEL_MAX),
116
+ /**
117
+ * Adresse destinataire (`email`) ou URL appelée en POST (`webhook`,
118
+ * `discord`) : **vide pour qui n'a pas la gestion des canaux de la
119
+ * fonctionnalité** (le champ `channels` de son grant, migration 093).
120
+ *
121
+ * La liste est lisible avec la fonctionnalité, parce qu'il faut voir les
122
+ * destinations pour router vers l'une d'elles. Leur *contenu* ne l'est
123
+ * pas : confier le réglage d'Uptime ne confie pas l'adresse de l'astreinte
124
+ * ni l'URL du salon de production. On voit donc « Astreinte · e-mail », on
125
+ * peut y router, et on ne peut ni la lire ni la modifier.
126
+ */
127
+ target: z.string().max(NOTIFICATION_TARGET_MAX),
128
+ /** Le compte Mail expéditeur ; `null` hors des canaux `email`. */
129
+ mailAccountId: z.number().int().positive().nullable(),
130
+ /**
131
+ * Ce canal partirait-il **maintenant** ?
132
+ *
133
+ * Faux quand le compte expéditeur manque, a disparu, est désactivé ou n'est
134
+ * pas au palier « open ». L'interface le dit au lieu de laisser croire à un
135
+ * canal actif — l'avertissement n'existait à l'origine que dans Uptime, et
136
+ * son absence ailleurs faisait passer un canal muet pour un canal réglé.
137
+ */
138
+ ready: z.boolean(),
139
+ /** Éteint sans être supprimé : ses routes restent, rien ne part. */
140
+ enabled: z.boolean(),
141
+ position: z.number().int().nonnegative(),
142
+ /**
143
+ * Combien de routes le désignent — ce que l'écran affiche en « utilisé par
144
+ * N ». Compté côté serveur : le client n'a pas les routes des éléments sous
145
+ * la main, et les demander toutes pour afficher un nombre serait une
146
+ * requête par ligne.
147
+ */
148
+ usageCount: z.number().int().nonnegative()
149
+ });
150
+ export type NotificationChannel = z.infer<typeof notificationChannelSchema>;
151
+
152
+ /** Ce qu'accepte `notify.channelAdd` / `channelUpdate`. */
153
+ export const notificationChannelInputSchema = z.object({
154
+ kind: notificationChannelKindSchema,
155
+ label: z.string().min(1).max(NOTIFICATION_LABEL_MAX),
156
+ /** Adresse ou URL. Vide sur un `email` = l'adresse du compte expéditeur. */
157
+ target: z.string().max(NOTIFICATION_TARGET_MAX),
158
+ mailAccountId: z.number().int().positive().nullable()
159
+ });
160
+ export type NotificationChannelInput = z.infer<typeof notificationChannelInputSchema>;
161
+
162
+ /**
163
+ * La cible d'une route : une fonctionnalité, ou un de ses éléments.
164
+ *
165
+ * `itemId` absent vaut « la fonctionnalité elle-même ». En base il devient `0`,
166
+ * parce qu'une colonne d'une clé primaire ne peut pas être nulle ; le contrat,
167
+ * lui, n'a pas à porter cette contrainte de stockage.
168
+ */
169
+ export const notificationRouteTargetSchema = z.object({
170
+ feature: notificationFeatureSchema,
171
+ itemId: z.number().int().positive().optional()
172
+ });
173
+ export type NotificationRouteTarget = z.infer<typeof notificationRouteTargetSchema>;
174
+
175
+ /**
176
+ * Où écrit une cible : sa sélection de canaux, rien de plus.
177
+ *
178
+ * Vide, elle ne prévient personne — il n'y a plus d'héritage à distinguer
179
+ * (092), donc plus de drapeau `inherits` : une sélection vide et une sélection
180
+ * jamais faite disent la même chose, le silence.
181
+ */
182
+ export const notificationRouteSchema = z.object({
183
+ channelIds: z.array(z.number().int().positive())
184
+ });
185
+ export type NotificationRoute = z.infer<typeof notificationRouteSchema>;
186
+
187
+ /** Ce qu'accepte `notify.routeSet`. Une sélection vide efface la route. */
188
+ export const notificationRouteInputSchema = notificationRouteTargetSchema.extend({
189
+ channelIds: z.array(z.number().int().positive()).max(32)
190
+ });
191
+ export type NotificationRouteInput = z.infer<typeof notificationRouteInputSchema>;
192
+
193
+ /**
194
+ * Ce que rend un envoi d'essai : parti, ou pourquoi non.
195
+ *
196
+ * `sent: false` avec un `error` n'est pas une exception — « aucun canal activé »
197
+ * est une réponse, pas une panne, et la remonter comme telle laisserait l'écran
198
+ * afficher « échec » là où il n'y a rien à échouer.
199
+ */
200
+ export const notificationTestSchema = z.object({ sent: z.boolean(), error: z.string().nullable() });
201
+ export type NotificationTest = z.infer<typeof notificationTestSchema>;
202
+
203
+ /**
204
+ * Ce qu'une suppression de canal emporte avec elle.
205
+ *
206
+ * Rendu **avant** la suppression pour que la confirmation nomme ce qui va
207
+ * cesser de prévenir, plutôt que de demander « êtes-vous sûr ? » sans dire de
208
+ * quoi. Une liste vide veut dire qu'aucune route ne le désigne.
209
+ */
210
+ export const notificationChannelUsageSchema = z.object({
211
+ channelId: z.number().int().positive(),
212
+ routes: z.array(
213
+ z.object({
214
+ feature: notificationFeatureSchema,
215
+ itemId: z.number().int().positive().nullable(),
216
+ /** Nom de l'élément, déchiffré par le serveur ; `null` sur une route de feature. */
217
+ itemLabel: z.string().nullable()
218
+ })
219
+ )
220
+ });
221
+ export type NotificationChannelUsage = z.infer<typeof notificationChannelUsageSchema>;
222
+
223
+ /** Ligne de `notification_channels` (serveur uniquement). */
224
+ export interface NotificationChannelRow {
225
+ id: number;
226
+ workspace_id: number;
227
+ /** La fonctionnalité propriétaire : un canal est une source de SA feature (091). */
228
+ feature: NotificationFeature;
229
+ kind: NotificationChannelKind;
230
+ label_enc: string;
231
+ target_enc: string;
232
+ mail_account_id: number | null;
233
+ enabled: number;
234
+ position: number;
235
+ created: number;
236
+ }
237
+
238
+ /** Ligne de `notification_routes` (serveur uniquement). */
239
+ export interface NotificationRouteRow {
240
+ id: number;
241
+ workspace_id: number;
242
+ feature: NotificationFeature;
243
+ /** `0` = la fonctionnalité elle-même. */
244
+ item_id: number;
245
+ }
246
+
247
+ /**
248
+ * Contrôle de cohérence, au chargement du module.
249
+ *
250
+ * `notifies` dans le registre et cet enum répondent à la même question ; les
251
+ * tenir séparés est un choix (l'un décrit, l'autre valide), les laisser diverger
252
+ * n'en est pas un. Une fonctionnalité marquée `notifies` mais absente de l'enum
253
+ * afficherait un onglet Notifications dont toutes les commandes seraient
254
+ * refusées — un écran qui ment, découvert à la première alerte attendue.
255
+ *
256
+ * Même esprit que le contrôle des sujets `mutates` côté serveur : attraper
257
+ * l'oubli au démarrage plutôt qu'en production.
258
+ */
259
+ {
260
+ const registry = [...NOTIFYING_FEATURES].sort().join(', ');
261
+ const declared = [...nativeNotificationFeatureSchema.options].sort().join(', ');
262
+ if (registry !== declared) {
263
+ throw new Error(
264
+ `nativeNotificationFeatureSchema et FEATURE_REGISTRY.notifies divergent : ` +
265
+ `registre : [${registry}], enum : [${declared}]`
266
+ );
267
+ }
268
+ }
@@ -0,0 +1,35 @@
1
+ import { z } from 'zod';
2
+
3
+ /**
4
+ * Update tooling DevEye can detect and drive on a monitored machine. Covers OS
5
+ * package managers and the two heavyweight system updaters (macOS softwareupdate,
6
+ * Windows Update). The agent only reports the ones actually present on the host.
7
+ */
8
+ export const packageManagerIdSchema = z.enum([
9
+ 'apt',
10
+ 'dnf',
11
+ 'pacman',
12
+ 'pamac',
13
+ 'flatpak',
14
+ 'snap',
15
+ 'zypper',
16
+ 'brew',
17
+ 'softwareupdate',
18
+ 'winget',
19
+ 'windowsupdate'
20
+ ]);
21
+
22
+ export type PackageManagerId = z.infer<typeof packageManagerIdSchema>;
23
+
24
+ /** One detected manager + its pending-update state. */
25
+ export const packageManagerSchema = z.object({
26
+ id: packageManagerIdSchema,
27
+ /** Pending update count; `null` when unknown (slow/best-effort probe). */
28
+ pendingCount: z.number().int().nonnegative().nullable(),
29
+ /** Applying this manager's updates requires root/admin. */
30
+ needsRoot: z.boolean(),
31
+ /** A reboot is pending for updates this manager already applied. */
32
+ rebootRequired: z.boolean().default(false)
33
+ });
34
+
35
+ export type PackageManager = z.infer<typeof packageManagerSchema>;
@@ -0,0 +1,36 @@
1
+ import { z } from 'zod';
2
+
3
+ export const passwordStatusSchema = z.enum(['active', 'inactive', 'none']);
4
+ export type PasswordStatus = z.infer<typeof passwordStatusSchema>;
5
+
6
+ export const passwordEntrySchema = z.object({
7
+ id: z.number().int().nonnegative(),
8
+ category: z.string(),
9
+ service: z.string(),
10
+ email: z.string(),
11
+ password: z.string(),
12
+ status: passwordStatusSchema
13
+ });
14
+
15
+ export type PasswordEntry = z.infer<typeof passwordEntrySchema>;
16
+
17
+ /**
18
+ * Variant without the secret payload, used when listing entries before unlock.
19
+ * `hasPassword` tells the UI whether a (still hidden) secret actually exists, so
20
+ * a genuinely empty password renders as a blank cell instead of fake dots with
21
+ * reveal/copy controls that would yield nothing.
22
+ */
23
+ export const passwordEntryMaskedSchema = passwordEntrySchema.extend({
24
+ password: z.literal(''),
25
+ hasPassword: z.boolean()
26
+ });
27
+
28
+ export type PasswordEntryMasked = z.infer<typeof passwordEntryMaskedSchema>;
29
+
30
+ export interface PasswordRow {
31
+ id: number;
32
+ user_id: number;
33
+ workspace_id: number | null;
34
+ content: string;
35
+ date: number;
36
+ }
@@ -0,0 +1,21 @@
1
+ import { z } from 'zod';
2
+
3
+ /**
4
+ * Agent connectivity history. The server records a transition each time an agent
5
+ * connects or disconnects; the UI replays these to draw an online/offline
6
+ * timeline (the "frise") and compute uptime over a window.
7
+ */
8
+ export const presenceEventSchema = z.object({
9
+ /** Unix ms of the transition. */
10
+ ts: z.number().int().positive(),
11
+ online: z.boolean()
12
+ });
13
+
14
+ export type PresenceEvent = z.infer<typeof presenceEventSchema>;
15
+
16
+ export interface PresenceRow {
17
+ id: number;
18
+ device_id: string;
19
+ ts: number;
20
+ online: 0 | 1;
21
+ }