@deveye/types 0.15.2 → 0.16.1

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 (73) hide show
  1. package/package.json +6 -6
  2. package/src/domain/device.ts +4 -29
  3. package/src/domain/featureRegistry.ts +40 -108
  4. package/src/domain/home.ts +40 -101
  5. package/src/domain/live.ts +36 -87
  6. package/src/domain/metrics.ts +10 -14
  7. package/src/domain/notifications.ts +39 -110
  8. package/src/domain/project.ts +3 -162
  9. package/src/domain/report.ts +54 -95
  10. package/src/domain/secrecy.ts +3 -5
  11. package/src/domain/sharing.ts +33 -70
  12. package/src/domain/syncProtocol.ts +5 -12
  13. package/src/domain/user.ts +8 -14
  14. package/src/domain/workspace.ts +0 -3
  15. package/src/domain/workspaceRole.ts +34 -82
  16. package/src/features/agent.ts +306 -0
  17. package/src/features/live.ts +17 -37
  18. package/src/features/notify.ts +19 -57
  19. package/src/features/registry.ts +7 -44
  20. package/src/features/secrecy.ts +4 -9
  21. package/src/features/sharing.ts +12 -26
  22. package/src/features/user.ts +6 -11
  23. package/src/features/workspace.ts +6 -11
  24. package/src/http/auth.ts +6 -13
  25. package/src/http/device.ts +13 -17
  26. package/src/http/status.ts +6 -10
  27. package/src/index.ts +29 -932
  28. package/src/protocol/agent.ts +40 -70
  29. package/src/protocol/envelope.ts +3 -10
  30. package/src/sdk/client-ambient.d.ts +228 -21
  31. package/src/sdk/client.ts +277 -17
  32. package/src/sdk/devb.ts +120 -0
  33. package/src/sdk/manifest.test.ts +0 -1
  34. package/src/sdk/manifest.ts +94 -25
  35. package/src/sdk/providers.ts +310 -5
  36. package/src/sdk/server.ts +483 -19
  37. package/src/sdk/testing.test.ts +2 -2
  38. package/src/sdk/testing.ts +302 -26
  39. package/src/utils/version.ts +5 -8
  40. package/src/domain/audience.ts +0 -549
  41. package/src/domain/backup.ts +0 -355
  42. package/src/domain/credential.ts +0 -55
  43. package/src/domain/database.ts +0 -467
  44. package/src/domain/deploy.ts +0 -231
  45. package/src/domain/finance.ts +0 -477
  46. package/src/domain/git.ts +0 -419
  47. package/src/domain/mail.ts +0 -394
  48. package/src/domain/note.ts +0 -202
  49. package/src/domain/password.ts +0 -36
  50. package/src/domain/projectBoard.ts +0 -130
  51. package/src/domain/projectChat.ts +0 -46
  52. package/src/domain/projectHistory.ts +0 -82
  53. package/src/domain/projectLink.ts +0 -87
  54. package/src/domain/projectPlan.ts +0 -68
  55. package/src/domain/sentinel.ts +0 -623
  56. package/src/domain/uptime.ts +0 -216
  57. package/src/features/audience.ts +0 -275
  58. package/src/features/backup.ts +0 -230
  59. package/src/features/database.ts +0 -461
  60. package/src/features/deploy.ts +0 -245
  61. package/src/features/device.ts +0 -292
  62. package/src/features/deviceFiles.ts +0 -83
  63. package/src/features/deviceLogs.ts +0 -36
  64. package/src/features/deviceTerminal.ts +0 -57
  65. package/src/features/finance.ts +0 -360
  66. package/src/features/git.ts +0 -368
  67. package/src/features/mail.ts +0 -374
  68. package/src/features/metrics.ts +0 -185
  69. package/src/features/note.ts +0 -189
  70. package/src/features/password.ts +0 -67
  71. package/src/features/project.ts +0 -709
  72. package/src/features/sentinel.ts +0 -233
  73. package/src/features/uptime.ts +0 -186
@@ -1,19 +1,14 @@
1
1
  import { z } from 'zod';
2
2
 
3
3
  /**
4
- * "Latest known state" report for a device distinct from the time-series
5
- * metric snapshots. It carries slow-moving signals (OS info, security posture)
6
- * that don't belong in the per-cycle metric stream.
4
+ * "Latest known state" report for a device, distinct from the metric snapshots:
5
+ * slow-moving signals (OS info, security posture). The agent emits one on
6
+ * connect and then periodically; the server keeps only the most recent per
7
+ * device (`devices.report_json`) and fans it out live.
7
8
  *
8
- * The agent emits one on connect and then periodically. The server persists only
9
- * the most recent report per device (`devices.report_json`) and fans it out live.
10
- *
11
- * Every security field is nullable: collectors are best-effort and shell out to
12
- * OS tools that may be absent or require privileges. `null` means "unknown".
13
- *
14
- * Processes are *not* in the report: they ride along with each metric snapshot
15
- * (`metricSnapshotSchema.processes`) so every graph point has the process list of
16
- * that exact instant, and are historised under the same `ts`.
9
+ * Every security field is nullable: collectors are best-effort, `null` means
10
+ * "unknown". Processes are not in the report: they ride along with each metric
11
+ * snapshot (`metricSnapshotSchema.processes`).
17
12
  */
18
13
 
19
14
  /**
@@ -30,27 +25,28 @@ import { z } from 'zod';
30
25
  export const reportProcessSchema = z.object({
31
26
  name: z.string().min(1).max(128),
32
27
  /**
33
- * Chemin de l'exécutable, et **seconde moitié de la clé d'agrégation**.
34
- *
35
- * Agréger sur le seul nom fusionnait deux binaires homonymes rangés à des
36
- * endroits différents exactement ce derrière quoi un imposteur se cache.
37
- * La clé est donc `(name, execPath)`, et deux `nginx` de chemins distincts
38
- * forment désormais deux entrées, ce qui est l'information utile.
39
- *
40
- * `null` = inconnu : agent trop ancien pour le renvoyer, ou chemin illisible
41
- * faute de droits. Les règles qui en dépendent restent alors muettes plutôt
42
- * que de conclure dans le vide (invariant 6 de Monitoring).
28
+ * Chemin de l'exécutable, seconde moitié de la clé d'agrégation
29
+ * `(name, execPath)` : deux binaires homonymes de chemins distincts sont
30
+ * deux entrées, ce derrière quoi un imposteur se cache. `null` = inconnu
31
+ * (agent trop ancien, ou chemin illisible faute de droits) ; les règles qui
32
+ * en dépendent restent alors muettes.
43
33
  */
44
34
  execPath: z.string().max(512).nullable().default(null),
45
35
  /**
46
36
  * L'exécutable a été effacé du disque mais le processus tourne toujours
47
- * (`/proc/<pid>/exe` pointe vers un chemin suffixé « (deleted) »).
48
- *
49
- * Un des indicateurs les plus francs d'un implant résident en mémoire, et il
50
- * ne coûte rien : le lien symbolique est déjà lu pour `execPath`. `null` là
51
- * où la plateforme ne l'expose pas (macOS, Windows).
37
+ * (`/proc/<pid>/exe` suffixé « (deleted) ») : un des indicateurs les plus
38
+ * francs d'un implant résident en mémoire. `null` là où la plateforme ne
39
+ * l'expose pas (macOS, Windows).
52
40
  */
53
41
  deleted: z.boolean().nullable().default(null),
42
+ /**
43
+ * Fil du noyau (`PF_KTHREAD`), qui n'a par nature ni exécutable ni durée de
44
+ * vie stable : son nom encode un CPU et un index que le noyau recycle. Les
45
+ * règles de dérive l'écartent, sans quoi ce recyclage passerait pour des
46
+ * programmes qui apparaissent et disparaissent. `null` là où la plateforme
47
+ * ne l'expose pas (macOS, Windows) ou sur un agent trop ancien.
48
+ */
49
+ kernel: z.boolean().nullable().default(null),
54
50
  /** Number of PIDs aggregated under this name. */
55
51
  instances: z.number().int().positive().default(1),
56
52
  /** Summed CPU%, cumulative across cores (can exceed 100 — divide by `os.cores`). */
@@ -98,9 +94,8 @@ export const processKindSchema = z.enum(['top', 'all']);
98
94
  export type ProcessKind = z.infer<typeof processKindSchema>;
99
95
 
100
96
  /**
101
- * A stored process list at one instant. **Read model only**: the agent no longer
102
- * emits it on its own — processes travel inside `metricSnapshotSchema.processes`
103
- * so a graph point and its process list always share one `ts`. This is what
97
+ * A stored process list at one instant. Read model only: processes travel
98
+ * inside `metricSnapshotSchema.processes`, and this is what
104
99
  * `metrics.processesAt` returns when reading history back.
105
100
  */
106
101
  export const processSampleSchema = z.object({
@@ -134,12 +129,9 @@ export const deviceSecuritySchema = z.object({
134
129
  /** Count of pending OS updates (null when not collected, e.g. macOS). */
135
130
  pendingUpdates: z.number().int().nonnegative().nullable(),
136
131
  /**
137
- * Correctifs de **sécurité** en attente, distingués du total.
138
- *
139
- * La distinction porte toute la valeur du signal : quarante mises à jour
140
- * dont aucune de sécurité n'est qu'un retard d'entretien, tandis qu'une
141
- * seule faille non corrigée est une porte. Ces champs sont facultatifs et
142
- * défaillent à `null` — un agent antérieur à Sentinelle n'en dit rien, et
132
+ * Correctifs de sécurité en attente, distingués du total : quarante mises à
133
+ * jour sans sécurité ne sont qu'un retard d'entretien, une seule faille non
134
+ * corrigée est une porte. `null` quand l'agent ne le dit pas, et
143
135
  * `posture.updates_stale` reste alors muette.
144
136
  */
145
137
  pendingSecurityUpdates: z.number().int().nonnegative().nullable().default(null),
@@ -226,20 +218,10 @@ export const agentInfoSchema = z.object({
226
218
  /** True when launched by a service manager (so a self-update just exits to be relaunched). */
227
219
  managed: z.boolean().default(false),
228
220
  /**
229
- * Ce que cet agent sait relever, déclaré par lui-même.
230
- *
231
- * Sans cette liste, rien ne distingue « la sonde a échoué » d'« un agent
232
- * trop ancien pour l'avoir ». Les deux rendent `null`, et l'interface
233
- * afficherait le même vide pour deux situations qui n'appellent pas la même
234
- * réaction — mettre l'agent à jour, ou aller regarder la machine.
235
- *
236
- * On ne peut pas s'en remettre à la version : elle est injectée à la
237
- * compilation par la CI et vaut `0.0.0` sur une construction locale. Une
238
- * capacité déclarée est de toute façon plus honnête qu'un numéro dont on
239
- * déduirait ce qu'il contient.
240
- *
241
- * Vide par défaut : un agent antérieur à Sentinelle ne dit rien, et c'est
242
- * exactement ce qu'il faut comprendre.
221
+ * Ce que cet agent sait relever, déclaré par lui-même : distingue « la
222
+ * sonde a échoué » d'« un agent trop ancien pour l'avoir », que la version
223
+ * ne dit pas (elle vaut `0.0.0` sur une construction locale). Vide par
224
+ * défaut : un agent qui ne dit rien n'a pas ces sondes.
243
225
  */
244
226
  probes: z.array(z.string().max(32)).max(16).default([])
245
227
  });
@@ -320,11 +302,9 @@ export const deviceHardwareSchema = z.object({
320
302
  .catch([])
321
303
  .default([]),
322
304
  /**
323
- * Network interfaces (best-effort; may be empty). A container host can expose
324
- * dozens of virtual `veth*`/`br-*` devices, so an over-long list is *truncated*
325
- * (and any residual error degrades to `[]`) rather than rejecting the whole
326
- * report — one noisy field must never drop the agent's entire posture, which is
327
- * validated at the agent socket's ingress (`deviceReportSchema`).
305
+ * Network interfaces (best-effort). A container host can expose dozens of
306
+ * virtual `veth*`/`br-*` devices, so an over-long list is truncated (and a
307
+ * residual error degrades to `[]`) rather than rejecting the whole report.
328
308
  */
329
309
  network: z
330
310
  .preprocess(
@@ -352,15 +332,9 @@ export const deviceReportSchema = z.object({
352
332
  security: deviceSecuritySchema,
353
333
  /** Per-disk usage (deduped across shared APFS volumes). Empty if unknown. */
354
334
  disks: z.array(reportDiskSchema).default([]),
355
- /**
356
- * The agent's runtime identity (privilege level + account). `null` on legacy
357
- * reports stored before this field existed; the agent always sends it now.
358
- */
335
+ /** The agent's runtime identity. `null` on reports stored before this field existed. */
359
336
  agent: agentInfoSchema.nullable().default(null),
360
- /**
361
- * Static hardware inventory (CPU, RAM, GPU, network, bluetooth). `null` on
362
- * legacy reports stored before this field existed; the agent always sends it.
363
- */
337
+ /** Static hardware inventory. `null` on reports stored before this field existed. */
364
338
  hardware: deviceHardwareSchema.nullable().default(null),
365
339
  /**
366
340
  * Listening sockets, one entry per bind address. `null` = not collected
@@ -378,25 +352,16 @@ export const deviceReportSchema = z.object({
378
352
 
379
353
  export type DeviceReport = z.infer<typeof deviceReportSchema>;
380
354
 
381
- // ─────────────────────── relevés Sentinelle (persistance, auth) ──────────────
382
- //
383
- // Deux relevés de plus, volontairement **hors** de `deviceReportSchema`.
384
- //
385
- // Le rapport est un « dernier état connu » : le serveur n'en garde qu'un par
386
- // appareil, écrasé à chaque envoi. Cela convient à la posture, pas à ces
387
- // deux-là. Le manifeste de persistance est trop gros pour être réécrit en
388
- // entier chaque heure dans `devices.report_json`, et la fenêtre
389
- // d'authentification est **additive** — l'écraser perdrait des tentatives, ce
390
- // qui est précisément ce qu'on cherche à compter.
355
+ // Relevés Sentinelle, volontairement hors de `deviceReportSchema` : le rapport
356
+ // est un « dernier état connu » écrasé à chaque envoi, alors que le manifeste
357
+ // de persistance est trop gros pour être réécrit chaque heure et que la
358
+ // fenêtre d'authentification est additive (l'écraser perdrait des tentatives).
391
359
 
392
360
  /**
393
- * Une entrée d'une surface de persistance : l'endroit où un programme s'installe
394
- * pour survivre au redémarrage.
395
- *
396
- * **Jamais le contenu du fichier** — seulement son empreinte et ses métadonnées.
397
- * C'est ce qui rend la sonde acceptable sur une machine partagée : elle prouve
398
- * qu'un fichier a changé sans jamais révéler ce qu'il contient, et un `sha256`
399
- * suffit entièrement au diff que le serveur en fait.
361
+ * Une entrée d'une surface de persistance : l'endroit où un programme
362
+ * s'installe pour survivre au redémarrage. Jamais le contenu du fichier,
363
+ * seulement son empreinte et ses métadonnées : la sonde prouve qu'un fichier a
364
+ * changé sans révéler ce qu'il contient.
400
365
  */
401
366
  export const persistenceEntrySchema = z.object({
402
367
  /** Famille d'origine : `cron`, `systemd`, `launchd`, `authorized_keys`, `sudoers`, `run_key`, `scheduled_task`… */
@@ -430,11 +395,9 @@ export const integrityReportSchema = z.object({
430
395
  export type IntegrityReport = z.infer<typeof integrityReportSchema>;
431
396
 
432
397
  /**
433
- * Une adresse et ce qu'elle a tenté, sur la fenêtre écoulée.
434
- *
435
- * `users` porte les comptes **visés**, pas les comptes d'utilisateurs suivis :
436
- * savoir qu'une adresse chinoise a essayé `root`, `admin` puis `oracle` est ce
437
- * qui distingue un balayage automatique d'une erreur de frappe.
398
+ * Une adresse et ce qu'elle a tenté sur la fenêtre écoulée. `users` porte les
399
+ * comptes visés : `root`, `admin` puis `oracle` distingue un balayage d'une
400
+ * erreur de frappe.
438
401
  */
439
402
  export const authSourceSchema = z.object({
440
403
  address: z.string().min(1).max(64),
@@ -458,13 +421,10 @@ export const AUTH_SOURCE_LIMIT = 50;
458
421
  export const AUTH_LOGIN_LIMIT = 50;
459
422
 
460
423
  /**
461
- * Les issues d'authentification sur une fenêtre glissante.
462
- *
463
- * Des **compteurs**, pas un flux de journal : l'agent lit les journaux, en
464
- * extrait des totaux et une liste bornée d'adresses, et n'envoie que cela. Ce
465
- * n'est pas une optimisation de taille, c'est la frontière de la feature — un
466
- * flux brut aurait remonté des lignes de commande sudo et des noms de service,
467
- * c'est-à-dire l'activité des gens.
424
+ * Les issues d'authentification sur une fenêtre glissante. Des compteurs, pas
425
+ * un flux de journal : c'est la frontière de la feature, un flux brut aurait
426
+ * remonté des lignes de commande sudo et des noms de service, c'est-à-dire
427
+ * l'activité des gens.
468
428
  */
469
429
  export const authWindowSchema = z.object({
470
430
  /** Bornes de la fenêtre, unix ms. `from` = fin de la fenêtre précédente. */
@@ -483,9 +443,8 @@ export const authWindowSchema = z.object({
483
443
  topSources: z.array(authSourceSchema).max(AUTH_SOURCE_LIMIT).default([]),
484
444
  logins: z.array(authLoginSchema).max(AUTH_LOGIN_LIMIT).default([]),
485
445
  /**
486
- * La source n'a pas pu être lue (pas de journal, pas les droits). Distinguer
487
- * « zéro tentative » de « je n'ai pas pu regarder » : sans ce drapeau, une
488
- * machine aveugle passerait pour une machine tranquille.
446
+ * La source n'a pas pu être lue (pas de journal, pas les droits) : sans ce
447
+ * drapeau, une machine aveugle passerait pour une machine tranquille.
489
448
  */
490
449
  unavailable: z.boolean().default(false)
491
450
  });
@@ -26,11 +26,9 @@ export const secrecyStatusSchema = z.object({
26
26
  */
27
27
  reAuthInterval: z.number().int().min(0).nullable(),
28
28
  /**
29
- * Epoch ms at which the current grace window expires (when the cached DEK
30
- * will be flushed if no further activity slides it forward). `null` when the
31
- * session isn't unlocked, when the feature is off, or in "validate on every
32
- * action" mode — i.e. whenever there is no countdown to display. Lets the
33
- * topbar timer widget render a live progress bar without guessing the window.
29
+ * Epoch ms at which the grace window expires (cached DEK flushed unless
30
+ * activity slides it forward). `null` when there is no countdown: session
31
+ * locked, feature off, or "validate on every action" mode.
34
32
  */
35
33
  unlockedUntil: z.number().int().nullable()
36
34
  });
@@ -1,37 +1,19 @@
1
1
  import { z } from 'zod';
2
2
 
3
- import { featureAccessSchema, workspaceFeatureIdSchema } from './workspaceRole';
3
+ import { featureAccessSchema, featureIdSchema } from './workspaceRole';
4
4
 
5
5
  /**
6
- * Rendre un élément visible depuis un autre espace, **sans le déplacer**.
6
+ * Rendre un élément visible depuis un autre espace, sans le déplacer.
7
7
  *
8
- * ## L'invariant, avant tout le reste
8
+ * Invariant : un élément partagé ne change jamais de clé. Il reste chiffré sous
9
+ * celle de son espace d'origine et, servi ailleurs, est déchiffré avec le codec
10
+ * ouvert de cet espace-là (levier L3 de `WORKSPACES.md`). Partager est une
11
+ * projection, pas un transfert. Seule la clé de l'étage ouvert étant résoluble
12
+ * par le serveur seul, un élément de l'étage gardé ne peut pas être partagé
13
+ * (voir `shareTier` dans le registre).
9
14
  *
10
- * Un élément partagé **ne change jamais de clé**. Il reste chiffré sous celle de
11
- * son espace d'origine ; servi ailleurs, il est déchiffré avec le codec ouvert
12
- * de cet espace-là. C'est le prolongement direct du levier L3 de
13
- * `WORKSPACES.md`, « chaque espace a sa clé, et un blob n'en change jamais »,
14
- * et la raison pour laquelle ce chantier ne re-chiffre rien.
15
- *
16
- * `WORKSPACES.md` §10 range **déplacer** un élément hors périmètre, précisément
17
- * parce que ce serait la seule opération à exiger un déchiffrement clé A puis un
18
- * re-chiffrement clé B sous session vivante. Partager ne l'exige pas : c'est une
19
- * **projection**, pas un transfert. L'élément a un seul domicile, et des
20
- * fenêtres ailleurs.
21
- *
22
- * ## Ce qui en découle, et qu'il faut assumer
23
- *
24
- * Seule la clé de l'étage ouvert est résoluble par le serveur seul. Un élément
25
- * de l'étage gardé ne peut donc pas être partagé — pas par prudence, par
26
- * impossibilité mécanique. Voir `shareTier` dans le registre.
27
- *
28
- * ## On ne partage qu'avec soi-même
29
- *
30
- * La liste proposée est celle des espaces **dont l'appelant est membre**. Ce
31
- * n'est pas une restriction d'interface mais la règle : partager vers un espace
32
- * où l'on n'entre pas reviendrait à y déposer une donnée sans pouvoir en
33
- * répondre, et à contourner l'appartenance — qui est la frontière absolue du
34
- * modèle (`WORKSPACES.md` §3).
15
+ * On ne partage qu'avec les espaces dont l'appelant est membre : l'appartenance
16
+ * est la frontière absolue du modèle (`WORKSPACES.md` §3).
35
17
  */
36
18
 
37
19
  export const itemShareSchema = z.object({
@@ -42,25 +24,19 @@ export const itemShareSchema = z.object({
42
24
  isHome: z.boolean(),
43
25
  shared: z.boolean(),
44
26
  /**
45
- * L'appelant peut régler, **depuis ici**, ce que chaque rôle de cet espace
46
- * voit de l'élément (`share.grantList` / `share.grantSet` avec ce
47
- * `workspaceId`).
48
- *
49
- * Vrai quand l'élément y est visible, que l'espace est partagé (un espace
50
- * personnel n'a pas de rôles) et que l'appelant y tient `workspace.roles`.
51
- * C'est ce qui permet de gérer les permissions de toutes les fenêtres
52
- * depuis l'onglet Partage, sans changer d'espace.
27
+ * L'appelant peut régler d'ici ce que chaque rôle de cet espace voit de
28
+ * l'élément (`share.grantList` / `share.grantSet` avec ce `workspaceId`) :
29
+ * l'élément y est visible, l'espace est partagé et l'appelant y tient
30
+ * `workspace.roles`.
53
31
  */
54
32
  grantsManageable: z.boolean()
55
33
  });
56
34
  export type ItemShare = z.infer<typeof itemShareSchema>;
57
35
 
58
36
  /**
59
- * Pourquoi un élément ne peut pas être partagé, quand c'est le cas.
60
- *
61
- * Une phrase plutôt qu'un booléen : « impossible » sans raison donne à chercher
62
- * un réglage qui n'existe pas. Ici la cause est toujours structurelle, et la
63
- * dire évite qu'on la prenne pour une panne.
37
+ * Pourquoi un élément ne peut pas être partagé. Une raison plutôt qu'un
38
+ * booléen : la cause est toujours structurelle, et la dire évite qu'on la
39
+ * prenne pour une panne.
64
40
  */
65
41
  export const shareBlockerSchema = z.enum([
66
42
  /** La fonctionnalité entière vit à l'étage gardé, ou n'a pas de sens ici. */
@@ -86,16 +62,10 @@ export const itemShareStateSchema = z.object({
86
62
  export type ItemShareState = z.infer<typeof itemShareStateSchema>;
87
63
 
88
64
  /**
89
- * Ce qu'un rôle peut faire sur **un** élément.
90
- *
91
- * Volontairement **restrictif seulement** : `none` ou `read` abaissent ce que le
92
- * rôle a sur la fonctionnalité, jamais l'inverse. Le droit de feature reste le
93
- * plafond, ici comme pour les droits fins.
94
- *
95
- * L'alternative — permettre d'élever — a été écartée : l'accès effectif à une
96
- * fonctionnalité deviendrait « le maximum entre le rôle et le meilleur droit
97
- * d'élément », donc une requête de plus dans la résolution d'accès, et surtout
98
- * un écran des rôles qui ne dirait plus à lui seul qui voit quoi.
65
+ * Ce qu'un rôle peut faire sur un élément. Restrictif seulement : `none` ou
66
+ * `read` abaissent ce que le rôle a sur la fonctionnalité, jamais l'inverse ;
67
+ * le droit de feature reste le plafond, et l'écran des rôles dit à lui seul
68
+ * qui voit quoi.
99
69
  */
100
70
  export const itemAccessSchema = z.enum(['none', 'read']);
101
71
  export type ItemAccess = z.infer<typeof itemAccessSchema>;
@@ -108,13 +78,9 @@ export const itemRoleGrantSchema = z.object({
108
78
  export type ItemRoleGrant = z.infer<typeof itemRoleGrantSchema>;
109
79
 
110
80
  /**
111
- * Un rôle d'un espace, vu depuis l'écran des restrictions d'un élément.
112
- *
113
- * Porte tout ce que l'écran affiche, pour qu'il n'ait **aucun** recoupement à
114
- * faire : l'identité du rôle, ce que la fonctionnalité lui donne (le droit
115
- * *hérité*, affiché même quand aucune exception n'est posée — une vue
116
- * d'ensemble qui ne montre que les exceptions oblige à deviner le reste), et
117
- * l'exception posée s'il y en a une.
81
+ * Un rôle d'un espace, vu depuis l'écran des restrictions d'un élément : son
82
+ * identité, le droit hérité de la fonctionnalité (affiché même sans exception)
83
+ * et l'exception posée s'il y en a une.
118
84
  */
119
85
  export const itemRoleGrantViewSchema = z.object({
120
86
  roleId: z.number().int().positive(),
@@ -138,24 +104,21 @@ export const itemGrantStateSchema = z.object({
138
104
  });
139
105
  export type ItemGrantState = z.infer<typeof itemGrantStateSchema>;
140
106
 
141
- /** La cible d'un partage ou d'une restriction. */
107
+ /**
108
+ * La cible d'un partage ou d'une restriction. `featureIdSchema` et non l'enum
109
+ * natif : les éléments d'un module se projettent comme ceux d'une native.
110
+ */
142
111
  export const itemRefSchema = z.object({
143
- feature: workspaceFeatureIdSchema,
112
+ feature: featureIdSchema,
144
113
  itemId: z.number().int().positive()
145
114
  });
146
115
  export type ItemRef = z.infer<typeof itemRefSchema>;
147
116
 
148
117
  /**
149
- * Une référence qu'on voit sans pouvoir la lire.
150
- *
151
- * Le cas : un élément partagé vers B pointe une donnée de A un compte mail, un
152
- * appareil, un canal d'alerte. Un membre de B qui n'est pas membre de A doit
153
- * **savoir que le lien existe** sans en connaître le contenu. Le masquer
154
- * entièrement ferait croire à un élément mal réglé ; le montrer ferait fuiter
155
- * l'espace d'origine.
156
- *
157
- * Il peut la **retirer** si ses droits le permettent — retirer un lien ne
158
- * demande pas de le lire. Il ne peut ni le voir ni le modifier.
118
+ * Une référence qu'on voit sans pouvoir la lire : un élément partagé vers B
119
+ * pointe une donnée de A (compte mail, appareil, canal). Un membre de B non
120
+ * membre de A doit savoir que le lien existe sans en connaître le contenu ; il
121
+ * peut le retirer si ses droits le permettent, ni le voir ni le modifier.
159
122
  */
160
123
  export const foreignRefSchema = z.object({
161
124
  kind: z.literal('inaccessible'),
@@ -32,13 +32,10 @@ export const SYNC_FINGERPRINT_SEP = '\u0001';
32
32
  export const syncScanModeSchema = z.enum(['auto', 'full']);
33
33
  export type SyncScanMode = z.infer<typeof syncScanModeSchema>;
34
34
  /**
35
- * Empreinte d'un index : `{nombre}.{octets}.{sha256hex}`.
36
- *
37
- * Le pli est un XOR des hachages par ligne, donc INDÉPENDANT DE L'ORDRE, et
38
- * c'est délibéré : un tri obligerait Rust (ordre octet UTF-8) et TypeScript
39
- * (ordre unité UTF-16) à s'accorder sur les caractères hors BMP, ce qu'ils ne
40
- * font pas. Un seul emoji dans un nom de fichier aurait alors désactivé le
41
- * chemin rapide pour toujours, sans que rien ne le signale.
35
+ * Empreinte d'un index : `{nombre}.{octets}.{sha256hex}`. Le pli est un XOR des
36
+ * hachages par ligne, donc INDÉPENDANT DE L'ORDRE, à dessein : un tri obligerait
37
+ * Rust (ordre octet UTF-8) et TypeScript (ordre unité UTF-16) à s'accorder sur
38
+ * les caractères hors BMP, ce qu'ils ne font pas.
42
39
  */
43
40
  export const syncIndexFingerprintSchema = z.string().regex(/^\d+\.\d+\.[0-9a-f]{64}$/);
44
41
  /** SHA-256 hexadécimal (du clair d'un fichier, ou d'un chemin normalisé). */
@@ -93,11 +90,7 @@ export const cloudSyncProgressSchema = z.object({
93
90
  bytesDone: z.number().int().nonnegative(),
94
91
  /** Fichier en cours de transfert, pour la ligne discrète de l'UI. */
95
92
  currentPath: z.string().max(SYNC_REL_PATH_MAX).nullable(),
96
- /**
97
- * Avancement DANS le fichier en cours. Sans ça, un fichier de plusieurs Go
98
- * laissait la barre parfaitement figée du début à la fin de son transfert :
99
- * les octets n'étaient comptés qu'une fois le fichier terminé.
100
- */
93
+ /** Avancement dans le fichier en cours, pour qu'un gros fichier ne fige pas la barre. */
101
94
  currentBytes: z.number().int().nonnegative(),
102
95
  currentTotal: z.number().int().nonnegative(),
103
96
  direction: syncDirectionSchema.nullable(),
@@ -30,14 +30,12 @@ export const userColorSchema = z.enum([
30
30
  ]);
31
31
  export type UserColor = z.infer<typeof userColorSchema>;
32
32
 
33
- /** L'ordre fait foi : `defaultUserColor` et la migration 059 l'indexent tous deux. */
33
+ /** L'ordre fait foi : il est indexé par `defaultUserColor` et en base. */
34
34
  export const USER_COLORS = userColorSchema.options;
35
35
 
36
36
  /**
37
- * Teinte attribuée d'office à un compte, depuis son identifiant. Deux comptes
38
- * créés à la suite n'ont pas la même, et aucun compte n'existe sans couleur —
39
- * la migration 059 colorie l'existant, `usersRepo.create` fait de même à
40
- * l'inscription.
37
+ * Teinte attribuée d'office à un compte depuis son identifiant : deux comptes
38
+ * créés à la suite n'ont pas la même.
41
39
  */
42
40
  export function defaultUserColor(userId: number): UserColor {
43
41
  return USER_COLORS[Math.abs(userId) % USER_COLORS.length];
@@ -76,17 +74,13 @@ export const userSecuritySchema = z.object({
76
74
  export type UserSecurity = z.infer<typeof userSecuritySchema>;
77
75
 
78
76
  /**
79
- * Drapeaux de compte, rangés dans `users.settings` un simple sac de chaînes.
80
- *
81
- * L'**absence** d'un drapeau est sa valeur par défaut : un compte créé avant
82
- * qu'un drapeau existe se comporte donc comme le reste, sans migration ni
83
- * rattrapage. C'est ce qui fait de cette colonne le bon endroit pour un réglage
84
- * booléen privé, là où une colonne dédiée (le modèle de `color`) se justifie
85
- * quand la valeur n'est pas binaire ou qu'elle intéresse d'autres comptes.
77
+ * Drapeaux de compte, rangés dans `users.settings` (un sac de chaînes).
78
+ * L'absence d'un drapeau est sa valeur par défaut : aucune migration quand un
79
+ * drapeau apparaît.
86
80
  *
87
81
  * - `hideLiveCursors` : ne pas afficher les curseurs des autres membres. La
88
- * coupure est **réciproque** le client cesse aussi d'émettre le sien (voir
89
- * `live/LiveProvider.tsx`), si bien qu'on ne peut pas regarder sans être vu.
82
+ * coupure est réciproque : le client cesse aussi d'émettre le sien, on ne
83
+ * peut pas regarder sans être vu.
90
84
  */
91
85
  export const userSettingFlagSchema = z.enum(['hideLiveCursors']);
92
86
  export type UserSettingFlag = z.infer<typeof userSettingFlagSchema>;
@@ -8,9 +8,6 @@ import { minimalUserSchema } from './user';
8
8
  * quittable ni supprimable, jamais partageable. C'est le repli implicite quand
9
9
  * une commande ne vise aucun espace en particulier.
10
10
  * - `shared` : créé à la demande, plusieurs membres, rôles et invitations.
11
- *
12
- * Les deux sont de vraies lignes de `workspaces` : il n'existe plus d'espace
13
- * virtuel d'id 0.
14
11
  */
15
12
  export const workspaceKindSchema = z.enum(['personal', 'shared']);
16
13
  export type WorkspaceKind = z.infer<typeof workspaceKindSchema>;