@liorian/sdk 0.38.0-alpha.7aa2e5b → 0.46.0-alpha.63c4079

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 (44) hide show
  1. package/package.json +1 -1
  2. package/schemas/module.schema.json +106 -10
  3. package/src/application/service/auth-api-service.ts +2 -2
  4. package/src/application/service/config-module-api.service.ts +2 -2
  5. package/src/application/service/module-activation-api.service.ts +2 -2
  6. package/src/application/service/module-installation-api.service.ts +19 -2
  7. package/src/application/service/notifications-api-service.ts +2 -2
  8. package/src/application/service/organizations-api-service.ts +2 -2
  9. package/src/application/service/otp-api-service.ts +2 -2
  10. package/src/application/service/storage-api-service.ts +2 -2
  11. package/src/application/service/storefront-api.service.ts +5 -5
  12. package/src/application/service/user-preferences-api.service.ts +2 -2
  13. package/src/domain/config/app.config.ts +7 -0
  14. package/src/domain/entities/index.ts +2 -0
  15. package/src/domain/entities/module-installation.interface.ts +25 -12
  16. package/src/domain/entities/module-runtime.interface.ts +108 -0
  17. package/src/domain/entities/module-scope.interface.ts +32 -0
  18. package/src/domain/entities/module.interface.ts +3 -2
  19. package/src/domain/enums/module-category.enum.ts +1 -1
  20. package/src/infrastructure/hooks/module-installation.ts +18 -3
  21. package/src/infrastructure/module-installer/__tests__/module-installer.service.test.ts +135 -0
  22. package/src/infrastructure/module-installer/index.ts +2 -0
  23. package/src/infrastructure/module-installer/module-installer.service.ts +79 -10
  24. package/src/infrastructure/module-runtime/__tests__/bridge-protocol.test.ts +33 -0
  25. package/src/infrastructure/module-runtime/__tests__/module-bridge-client.test.ts +289 -0
  26. package/src/infrastructure/module-runtime/__tests__/module-transport.test.ts +94 -0
  27. package/src/infrastructure/module-runtime/bridge-protocol.ts +115 -0
  28. package/src/infrastructure/module-runtime/module-bootstrap.ts +69 -0
  29. package/src/infrastructure/module-runtime/module-bridge-client.ts +311 -0
  30. package/src/infrastructure/module-runtime/module-transport.ts +70 -0
  31. package/src/infrastructure/stores/__tests__/module-installation.store.test.ts +130 -0
  32. package/src/infrastructure/stores/module-installation.store.ts +126 -28
  33. package/src/infrastructure/utilities/__tests__/api-service.test.ts +141 -0
  34. package/src/infrastructure/utilities/activity-lock.service.ts +4 -4
  35. package/src/infrastructure/utilities/api-service.ts +55 -248
  36. package/src/infrastructure/utilities/auth-logout.util.ts +2 -2
  37. package/src/infrastructure/utilities/client-api-service.ts +140 -0
  38. package/src/infrastructure/utilities/index.tsx +2 -0
  39. package/src/infrastructure/utilities/session-events.ts +1 -1
  40. package/src/infrastructure/utilities/socket-service.ts +17 -1
  41. package/src/infrastructure/utilities/socle-api-service.ts +326 -0
  42. package/src/infrastructure/utilities/sso-auth.util.ts +2 -2
  43. package/src/presentation/components/module-runtime/iframe-host.tsx +118 -1
  44. package/src/presentation/components/module-runtime/use-module-route.ts +24 -4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@liorian/sdk",
3
- "version": "0.38.0-alpha.7aa2e5b",
3
+ "version": "0.46.0-alpha.63c4079",
4
4
  "description": "Core package for the Liora (application shell, domain, infrastructure, UI design system).",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -10,7 +10,7 @@
10
10
  {
11
11
  "schemaVersion": 1,
12
12
  "id": "hello-world",
13
- "domain": "mod.liorian.helloworld",
13
+ "domain": "mod.liorian.hello-world",
14
14
  "key": "HELLO_WORLD",
15
15
  "name": "Hello World",
16
16
  "description": "Module d'exemple pour l'onboarding des développeurs",
@@ -59,6 +59,7 @@
59
59
  "platforms",
60
60
  "compatibility",
61
61
  "permissions",
62
+ "userScope",
62
63
  "oauth",
63
64
  "capabilities",
64
65
  "isEnabled",
@@ -86,11 +87,11 @@
86
87
  },
87
88
  "domain": {
88
89
  "type": "string",
89
- "pattern": "^mod\\.[a-z0-9]+(\\.[a-z0-9-]+)*$",
90
+ "pattern": "^mod\\.[a-z0-9]+(-[a-z0-9]+)*\\.[a-z0-9]+(-[a-z0-9]+)*$",
90
91
  "minLength": 5,
91
- "title": "Domaine / identifiant pleinement qualifié",
92
- "description": "Identifiant de type reverse-DNS unique du module, préfixé par « mod. ». Utilisé comme `identifier` dans la déclaration (ModuleDeclarationInterface) et comme clé de synchronisation avec le backend.",
93
- "examples": ["mod.liorian.accounting", "mod.liorian.pos-management"]
92
+ "title": "Identifiant canonique du module",
93
+ "description": "Identifiant canonique unique du module : mod.<organization-slug>.<module-identifier> — exactement trois labels, deux slugs kebab-case. Le slug d'organisation est obligatoirement configuré avant tout accès au socle, à Connect et à la CLI. Utilisé comme `identifier` dans la déclaration (ModuleDeclarationInterface) et comme clé de synchronisation avec le backend.",
94
+ "examples": ["mod.liorian.hello-world", "mod.acme.crm"]
94
95
  },
95
96
  "key": {
96
97
  "type": "string",
@@ -159,10 +160,15 @@
159
160
  "entry": {
160
161
  "type": "string",
161
162
  "pattern": "^[a-zA-Z0-9_./-]+\\.tsx?$",
162
- "default": "index.tsx",
163
+ "default": "entry.tsx",
163
164
  "title": "Point d'entrée",
164
- "description": "Chemin relatif du fichier contenant l'export default de la `ModuleDeclarationInterface` du module.",
165
- "examples": ["index.tsx"]
165
+ "description": "Chemin relatif du fichier d'entrée TypeScript du module. Pour un module à artefact isolé (runtime iframe), c'est le fichier exportant `mount(el, ctx)` / `unmount()` (spec module-isolated-runtime.md §8.2) ; pour un module déclaratif du socle, il conserve l'export default de la `ModuleDeclarationInterface`.",
166
+ "examples": ["entry.tsx", "index.tsx"]
167
+ },
168
+ "artifact": {
169
+ "$ref": "#/definitions/module-artifact",
170
+ "title": "Artefact exécutable",
171
+ "description": "Localisation du bundle autonome et du document hôte de l'iframe (D4/D7). Obligatoire au `pack` pour un module `WEB_APP_LOCAL` ; un module purement déclaratif garde le squelette mais n'est pas servi depuis l'artefact."
166
172
  },
167
173
  "uri": {
168
174
  "type": "string",
@@ -220,6 +226,26 @@
220
226
  "description": "Accès accordés au module par rôle et par verbe HTTP, au format `<ROLE>:<VERBE>` (ex. `User:Get`, `Editor:Post`, `Editor:Put`, `Admin:Delete`). Rôles et actions alignés sur les rôles/permissions du backend (PermissionsConfig) et sur les verbes HTTP des contrôleurs Raiton.",
221
227
  "examples": [["User:Get", "Editor:Post", "Editor:Put", "Admin:Delete"]]
222
228
  },
229
+ "backends": {
230
+ "type": "array",
231
+ "items": {
232
+ "$ref": "#/definitions/backend-declaration"
233
+ },
234
+ "uniqueItems": true,
235
+ "title": "Backends tiers déclarés (tier 1, D9)",
236
+ "description": "Déclaration de l'éditeur : « ce module a besoin de ces backends ». L'autorisation réelle relève du tier 2 — la liste maîtresse `ModuleEgressBackend` de l'exploitant `api-core` (§6.3). Un module qui n'appelle que `api-core` via `ctx.api` ne déclare rien.",
237
+ "examples": [[{"key": "erp-prod", "url": "https://erp.acme.com/api", "scopes": ["Accounting:read"], "description": "ERP de production (lecture seule)"}]]
238
+ },
239
+ "userScope": {
240
+ "type": "array",
241
+ "items": {
242
+ "$ref": "#/definitions/role-permission-code"
243
+ },
244
+ "uniqueItems": true,
245
+ "title": "Périmètre de données utilisateur (D15)",
246
+ "description": "Données utilisateur que le module lit via `ctx.api`, même grammaire que `permissions`. Le consentement est tout-ou-rien : l'utilisateur accorde l'ensemble ou refuse le module. Obligatoire au `pack` pour tous les modules, first-party compris ; `[]` est valide et signifie « aucune donnée utilisateur ».",
247
+ "examples": [["User:Get", "UserActivity:Get"]]
248
+ },
223
249
  "oauth": {
224
250
  "type": "object",
225
251
  "required": ["scopes"],
@@ -507,11 +533,81 @@
507
533
  },
508
534
  "role-permission-code": {
509
535
  "type": "string",
510
- "pattern": "^(Root|SuperAdmin|Admin|Manager|Editor|Moderator|Analyst|Commercial|Operator|Contributor|Caissier|User|Guest|Viewer):(Get|Post|Put|Delete)$",
536
+ "pattern": "^[A-Za-z][A-Za-z0-9]*:(Get|Post|Put|Delete)$",
511
537
  "title": "Permission rôle:verbe",
512
- "description": "Accès accordé au format `<ROLE>:<VERBE>`. `ROLE` est un nom de rôle existant du backend (`Root`, `SuperAdmin`, `Admin`, `Manager`, `Editor`, `Moderator`, `Analyst`, `Commercial`, `Operator`, `Contributor`, `Caissier`, `User`, `Guest`, `Viewer`) et `VERBE` un verbe HTTP (`Get`, `Post`, `Put`, `Delete`).",
538
+ "description": "Accès accordé au format `<DOMAINE>:<VERBE>`. `DOMAINE` est un nom PascalCase (rôle du backend — `Root`, `SuperAdmin`, `Admin`, … — ou domaine de permissions — `User`, `UserActivity`, `Organization`, …) et `VERBE` un verbe HTTP (`Get`, `Post`, `Put`, `Delete`).",
513
539
  "examples": ["User:Get", "Editor:Post", "Editor:Put", "Admin:Delete"]
514
540
  },
541
+ "module-artifact": {
542
+ "type": "object",
543
+ "required": ["dir", "bundle", "document"],
544
+ "additionalProperties": false,
545
+ "properties": {
546
+ "dir": {
547
+ "type": "string",
548
+ "const": "artifact",
549
+ "title": "Dossier de sortie",
550
+ "description": "Dossier de build du module (D7 : `artifact/` et non `dist/`)."
551
+ },
552
+ "bundle": {
553
+ "type": "string",
554
+ "pattern": "^[a-zA-Z0-9_./-]+\\.js$",
555
+ "default": "module.js",
556
+ "title": "Bundle autonome",
557
+ "description": "Bundle ES unique produit par `bun build`, React et dépendances inlinés : l'origine opaque de l'iframe interdit tout partage de runtime (§11)."
558
+ },
559
+ "document": {
560
+ "type": "string",
561
+ "pattern": "^[a-zA-Z0-9_./-]+\\.html$",
562
+ "default": "index.html",
563
+ "title": "Document hôte",
564
+ "description": "Document HTML chargé dans l'iframe ; référence le bundle par chemin relatif."
565
+ }
566
+ },
567
+ "title": "Artefact exécutable",
568
+ "description": "Sortie de build installée sous `library/modules/<id>/<version>/artifact/**` et servie par `/library/**` (web) ou `liormod://` (Tauri)."
569
+ },
570
+ "backend-declaration": {
571
+ "type": "object",
572
+ "required": ["key", "url"],
573
+ "additionalProperties": false,
574
+ "properties": {
575
+ "key": {
576
+ "type": "string",
577
+ "pattern": "^[a-z0-9]+(-[a-z0-9]+)*$",
578
+ "minLength": 2,
579
+ "maxLength": 64,
580
+ "title": "Clé du backend",
581
+ "description": "Identifiant stable kebab-case, repris tel quel dans la liste maîtresse du tier 2 et dans `ClientApiService`."
582
+ },
583
+ "url": {
584
+ "type": "string",
585
+ "pattern": "^https://",
586
+ "maxLength": 2048,
587
+ "title": "URL de base",
588
+ "description": "Origine + chemin de base du backend. `https://` obligatoire (`http://` refusé sauf loopback explicitement listé au tier 2). Aucun userinfo (`user:pass@host`), aucun segment `..` ni `%2e%2e` : contraintes complétées par le validateur du `pack`, le pattern JSON ne peut pas les exprimer.",
589
+ "examples": ["https://erp.acme.com/api"]
590
+ },
591
+ "scopes": {
592
+ "type": "array",
593
+ "items": {
594
+ "$ref": "#/definitions/role-permission-code"
595
+ },
596
+ "minItems": 1,
597
+ "uniqueItems": true,
598
+ "title": "Permissions déclaratives",
599
+ "description": "Permissions que le module s'attend à exercer sur ce backend. Déclaratif : affiché dans l'écran d'approbation, non appliqué par la passerelle."
600
+ },
601
+ "description": {
602
+ "type": "string",
603
+ "maxLength": 200,
604
+ "title": "Description",
605
+ "description": "Libellé lisible affiché à l'utilisateur dans l'écran d'approbation (§6.10.5)."
606
+ }
607
+ },
608
+ "title": "Déclaration de backend tiers",
609
+ "description": "Tier 1 du contrôle réseau (D9) : déclaration de l'éditeur. Persisté dans `ModuleInstallation.manifest` et couvert par `manifestChecksum` de la signature (§6.9)."
610
+ },
515
611
  "oauth-scope": {
516
612
  "type": "string",
517
613
  "enum": ["openid", "profile", "email", "organizations", "roles", "permissions"],
@@ -1,4 +1,4 @@
1
- import {ApiService} from "../../infrastructure/utilities/api-service";
1
+ import {SocleApiService} from "../../infrastructure/utilities/socle-api-service";
2
2
  import {AuthUserService} from "./auth-user.service";
3
3
  import {
4
4
  type CreateUserAccountInterface,
@@ -72,7 +72,7 @@ function prepareWebAuthnOptions<T extends WebAuthnOptions>(options: T): T {
72
72
  return options;
73
73
  }
74
74
 
75
- export class AuthApiService extends ApiService {
75
+ export class AuthApiService extends SocleApiService {
76
76
  static async fetchAvailableSessions() {
77
77
  // Les tokens/infos de l'utilisateur doivent être disponibles en local
78
78
  // avant de vérifier la validité de la session.
@@ -1,4 +1,4 @@
1
- import {ApiService} from "../../infrastructure/utilities/api-service";
1
+ import {SocleApiService} from "../../infrastructure/utilities/socle-api-service";
2
2
  import {FetchResponseInterface} from "../../domain/typing/response";
3
3
 
4
4
  /**
@@ -45,7 +45,7 @@ export interface ConfigModuleAggregateGrouped {
45
45
 
46
46
  export type ConfigModuleAggregateResponse = ConfigModuleAggregateScalar | ConfigModuleAggregateGrouped;
47
47
 
48
- export class ConfigModuleApiService extends ApiService {
48
+ export class ConfigModuleApiService extends SocleApiService {
49
49
 
50
50
  /** Lister les enregistrements d'une ressource (`?search=`). */
51
51
  static async list(moduleIdentifier: string, resource: string, options?: {search?: string}) {
@@ -1,4 +1,4 @@
1
- import {ApiService} from "../../infrastructure/utilities/api-service";
1
+ import {SocleApiService} from "../../infrastructure/utilities/socle-api-service";
2
2
  import {FetchResponseInterface} from "../../domain/typing/response";
3
3
  import {
4
4
  ActivateModulePayloadInterface,
@@ -19,7 +19,7 @@ import {
19
19
  * validation, clés sérielles, synchronisation et analytics d'usage.
20
20
  * Le catalogue public est porté par {@link StorefrontApiService} (`/catalog`).
21
21
  */
22
- export class ModuleActivationApiService extends ApiService {
22
+ export class ModuleActivationApiService extends SocleApiService {
23
23
 
24
24
  /** Activer un module pour une organisation */
25
25
  static async activateModule(organizationId: string, payload: ActivateModulePayloadInterface) {
@@ -1,4 +1,4 @@
1
- import {ApiService} from "../../infrastructure/utilities/api-service";
1
+ import {SocleApiService} from "../../infrastructure/utilities/socle-api-service";
2
2
  import {FetchResponseInterface} from "../../domain/typing/response";
3
3
  import type {
4
4
  InstallModulePayloadInterface,
@@ -17,7 +17,7 @@ import type {
17
17
  * Cible `liorian-api-core` (base `/module-installation`) : résolution,
18
18
  * installation, cycle de vie, sync et jetons d'artefacts.
19
19
  */
20
- export class ModuleInstallationApiService extends ApiService {
20
+ export class ModuleInstallationApiService extends SocleApiService {
21
21
 
22
22
  private static org(organizationId: string) {
23
23
  return `/module-installation/organizations/${organizationId}`;
@@ -115,4 +115,21 @@ export class ModuleInstallationApiService extends ApiService {
115
115
  {deviceId},
116
116
  );
117
117
  }
118
+
119
+ /**
120
+ * Relais de l'archive vérifiée (spec `module-isolated-runtime` §5.2).
121
+ *
122
+ * `api-core` re-vérifie les octets (checksum, Ed25519, manifeste, audit)
123
+ * **au moment du relais** et ne répond que s'ils sont intacts ; le socle
124
+ * devient l'installateur d'octets sans devenir une autorité de confiance.
125
+ * Invariant : un artefact obtenu hors de ce canal n'est pas installable.
126
+ */
127
+ static async fetchVerifiedArchive(organizationId: string, moduleIdentifier: string, version: string): Promise<ArrayBuffer> {
128
+ const response = await this.get<ArrayBuffer>(
129
+ `${this.org(organizationId)}/modules/${moduleIdentifier}/versions/${version}/verified-archive`,
130
+ undefined,
131
+ {responseType: "arraybuffer"},
132
+ );
133
+ return response.data;
134
+ }
118
135
  }
@@ -1,4 +1,4 @@
1
- import {ApiService} from "../../infrastructure/utilities/api-service";
1
+ import {SocleApiService} from "../../infrastructure/utilities/socle-api-service";
2
2
  import {PaginationWithSearchOptions} from "../../domain/typing/response";
3
3
 
4
4
  export interface PushSubscriptionKeys {
@@ -11,7 +11,7 @@ export interface PushSubscriptionPayload {
11
11
  keys: PushSubscriptionKeys;
12
12
  }
13
13
 
14
- export class NotificationsApiService extends ApiService {
14
+ export class NotificationsApiService extends SocleApiService {
15
15
  static async getAll(options?: PaginationWithSearchOptions & { read?: boolean }) {
16
16
  return await this.get('/notifications/', options);
17
17
  }
@@ -1,8 +1,8 @@
1
- import {ApiService} from "../../infrastructure/utilities/api-service";
1
+ import {SocleApiService} from "../../infrastructure/utilities/socle-api-service";
2
2
  import {FetchResponseInterface} from "../../domain/typing/response";
3
3
  import {OrganizationApiAccessResponseInterface} from "../../domain/entities/organization.interface";
4
4
 
5
- export class OrganizationsApiService extends ApiService {
5
+ export class OrganizationsApiService extends SocleApiService {
6
6
  // Organizations
7
7
  static async create(payload: any) {
8
8
  return await this.post('/organizations/', payload);
@@ -1,6 +1,6 @@
1
- import {ApiService} from "../../infrastructure/utilities/api-service";
1
+ import {SocleApiService} from "../../infrastructure/utilities/socle-api-service";
2
2
 
3
- export class OtpApiService extends ApiService {
3
+ export class OtpApiService extends SocleApiService {
4
4
  static async createOtp(payload: { phone: string; domain?: string }) {
5
5
  return await this.post('/otp/create', payload);
6
6
  }
@@ -1,9 +1,9 @@
1
- import {ApiService} from "../../infrastructure/utilities/api-service";
1
+ import {SocleApiService} from "../../infrastructure/utilities/socle-api-service";
2
2
  import {MediaLabelService} from "../../infrastructure/utilities/media-label.service";
3
3
  import type {MediaSection, MediaUploadOptions} from "../../domain/entities/media";
4
4
  import type {AxiosProgressEvent, AxiosRequestConfig} from "axios";
5
5
 
6
- export class StorageApiService extends ApiService {
6
+ export class StorageApiService extends SocleApiService {
7
7
  static async uploadFile(file: File, options?: MediaUploadOptions, onUploadProgress?: (progressEvent: AxiosProgressEvent) => void, config?: AxiosRequestConfig<any>) {
8
8
  const formData = new FormData();
9
9
  formData.append("file", file);
@@ -1,4 +1,4 @@
1
- import {ApiService} from "../../infrastructure/utilities/api-service";
1
+ import {SocleApiService} from "../../infrastructure/utilities/socle-api-service";
2
2
  import {AppConfig} from "../../domain/config/app.config";
3
3
  import {FetchResponseInterface} from "../../domain/typing/response";
4
4
  import {
@@ -17,7 +17,7 @@ import {
17
17
  * sans auth ; les mutations (fiche produit / catégorie) sont réservées au
18
18
  * SuperAdmin (JWT partagé émis par core).
19
19
  */
20
- export class StorefrontApiService extends ApiService {
20
+ export class StorefrontApiService extends SocleApiService {
21
21
 
22
22
  protected static baseUrl: string = (AppConfig.STORE_API_HOST || AppConfig.CORE_API_HOST) as string;
23
23
 
@@ -28,9 +28,9 @@ export class StorefrontApiService extends ApiService {
28
28
  return await this.get<FetchResponseInterface<CatalogModuleInterface[]>>('/catalog/modules');
29
29
  }
30
30
 
31
- /** Récupérer un module du catalogue par son ID */
32
- static async getCatalogModule(moduleId: string) {
33
- return await this.get<FetchResponseInterface<CatalogModuleInterface>>(`/catalog/modules/${moduleId}`);
31
+ /** Récupérer un module du catalogue par son identifiant technique (`mod.<organization-slug>.<module-identifier>`) */
32
+ static async getCatalogModule(identifier: string) {
33
+ return await this.get<FetchResponseInterface<CatalogModuleInterface>>(`/catalog/modules/${identifier}`);
34
34
  }
35
35
 
36
36
  /** Créer une fiche produit (SuperAdmin) */
@@ -1,4 +1,4 @@
1
- import {ApiService} from "../../infrastructure/utilities/api-service";
1
+ import {SocleApiService} from "../../infrastructure/utilities/socle-api-service";
2
2
 
3
3
  export interface UserPreferenceInterface {
4
4
  id: string;
@@ -7,7 +7,7 @@ export interface UserPreferenceInterface {
7
7
  value: string;
8
8
  }
9
9
 
10
- export class UserPreferencesApiService extends ApiService {
10
+ export class UserPreferencesApiService extends SocleApiService {
11
11
 
12
12
  static async getByUserId(userId: string) {
13
13
  return await this.get(`/user-preferences/user/${userId}`);
@@ -10,6 +10,13 @@ export const AppConfig = {
10
10
  APP_NAME: process.env.NEXT_PUBLIC_APP_NAME as unknown as string,
11
11
  APP_VERSION: process.env.NEXT_PUBLIC_APP_VERSION as unknown as string,
12
12
  APP_VERSION_NUMBER: process.env.NEXT_PUBLIC_APP_VERSION_NUMBER as unknown as number,
13
+ /**
14
+ * Version de l'API core exposée au socle. Distincte d'`APP_VERSION` (version
15
+ * du socle) : la résolution de module compare ces deux axes séparément
16
+ * (`compat.socle` / `compat.api`), envoyer la version du socle sur l'axe
17
+ * `api` rend toute version publiée incompatible.
18
+ */
19
+ CORE_API_VERSION: process.env.NEXT_PUBLIC_CORE_API_VERSION as unknown as string,
13
20
 
14
21
  /** Alias conservé pour compatibilité : renvoie la même cadence que `APP_REFRESH_UI`. */
15
22
  UI_REFRESH_TIMEOUT: toPositiveMilliseconds(
@@ -5,6 +5,8 @@ export * from "./catalog.interface";
5
5
  export * from "./module-activation.interface";
6
6
  export * from "./module-configuration.interface";
7
7
  export * from "./module.interface";
8
+ export * from "./module-runtime.interface";
9
+ export * from "./module-scope.interface";
8
10
  export * from "./notification.interface";
9
11
  export * from "./objectable.type";
10
12
  export * from "./organization.interface";
@@ -28,36 +28,49 @@ export interface ResolveModulePayloadInterface {
28
28
  deviceId?: string;
29
29
  }
30
30
 
31
+ /**
32
+ * Version résolue par le core. Le contrat est volontairement aligné sur ce que
33
+ * le registre renvoie réellement : la résolution s'appuie sur le catalogue
34
+ * **public** du store (`CatalogModuleVersionPublicVm`), qui ne publie ni la
35
+ * signature brute ni les empreintes ni les URL d'artefact. Ces champs ne sont
36
+ * matérialisés qu'au moment de l'installation, sur le manifeste interne
37
+ * consommé par le core (FR-004 / FR-005). Le client doit donc les traiter
38
+ * comme absents et ne jamais en supposer la présence.
39
+ */
31
40
  export interface ModuleResolutionItemInterface {
32
41
  id: string;
33
- moduleIdentifier: string;
42
+ moduleIdentifier?: string;
34
43
  version: string;
35
44
  type: string;
36
- external: boolean;
45
+ external?: boolean;
37
46
  uri: string;
38
47
  entry: string;
39
- name: string;
48
+ name?: string;
40
49
  description?: string | null;
41
50
  icon?: string | null;
42
51
  logoUrl?: string | null;
43
52
  bannerUrl?: string | null;
44
53
  category?: string | null;
45
- checksum: string;
46
- manifestChecksum: string;
47
- signature: string;
48
- signatureKeyId: string;
54
+ /** Empreinte SHA-256 de l'archive — non publiée par le catalogue public. */
55
+ checksum?: string;
56
+ /** Empreinte SHA-256 du manifeste canonicalisé — non publiée par le catalogue public. */
57
+ manifestChecksum?: string;
58
+ /** Signature Ed25519 (base64) — jamais publiée au client. */
59
+ signature?: string;
60
+ /** Identifiant de la clé de signature — non publié par le catalogue public. */
61
+ signatureKeyId?: string;
49
62
  sizeBytes: number;
50
63
  capabilities?: string[];
51
64
  permissions?: string[];
52
65
  platforms?: Record<string, any> | null;
53
66
  compatibility?: Record<string, any> | null;
54
- manifestUrl: string;
55
- downloadUrl: string;
56
- runtimeUrlTemplate: string;
57
- installable: boolean;
67
+ manifestUrl?: string;
68
+ downloadUrl?: string;
69
+ runtimeUrlTemplate?: string;
70
+ installable?: boolean;
58
71
  reason?: string | null;
59
72
  installedVersion?: string | null;
60
- updateAvailable: boolean;
73
+ updateAvailable?: boolean;
61
74
  }
62
75
 
63
76
  export interface ModuleResolutionResponseInterface {
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Contrat d'exécution d'un module isolé (docs/specs/applications/
3
+ * module-isolated-runtime.md §4.2).
4
+ *
5
+ * Ce fichier doit rester transport-agnostique : aucun import `next/*`, aucun
6
+ * accès au DOM du socle, aucune lecture de jeton. Tout ce que le module
7
+ * reçoit est injecté par l'hôte via `mount(el, ctx)`.
8
+ */
9
+
10
+ import type {ModuleRuntimeType} from "./module-installation.interface";
11
+ import type {ModuleScopeUserInterface} from "./module-scope.interface";
12
+
13
+ /** Chemin logique du socle, indispensable en iframe où usePathname() est faux. */
14
+ export interface ModuleRouteInterface {
15
+ soclePath: string;
16
+ segments: string[];
17
+ pageId?: string;
18
+ }
19
+
20
+ /** Capacités RPC offertes par le socle via postMessage — des capacités, jamais des identifiants. */
21
+ export interface ModuleBridgeInterface {
22
+ navigate(href: string): void;
23
+ notify(message: string, kind?: "info" | "success" | "warning" | "error"): void;
24
+ openMediaPicker(): Promise<PickedMediaInterface | null>;
25
+ setTitle(title: string): void;
26
+ getTheme(): Promise<"light" | "dark">;
27
+ reload(): void;
28
+ on(event: ModuleBridgeEvent, listener: () => void): void;
29
+ off(event: ModuleBridgeEvent, listener: () => void): void;
30
+ }
31
+
32
+ export type ModuleBridgeEvent = "reload" | "theme-changed";
33
+
34
+ export interface PickedMediaInterface {
35
+ id: string;
36
+ url: string;
37
+ name?: string;
38
+ mimeType?: string;
39
+ }
40
+
41
+ /**
42
+ * Point de passage réseau vers api-core (D16). Instance injectée par l'hôte,
43
+ * adossée à un jeton modulaire (session × module × périmètre accordé) : le
44
+ * module ne voit jamais le jeton de session et n'agit pas au-delà du
45
+ * périmètre accordé. La forme exacte est livrée par le refactor d'`ApiService`
46
+ * (§4.5, phase 1b) ; le contrat reste minimal et sans hôte.
47
+ */
48
+ export interface ModuleApiClientInterface {
49
+ get<T>(path: string, query?: Record<string, unknown>): Promise<T>;
50
+ post<T>(path: string, body?: unknown): Promise<T>;
51
+ put<T>(path: string, body?: unknown): Promise<T>;
52
+ patch<T>(path: string, body?: unknown): Promise<T>;
53
+ delete<T>(path: string): Promise<T>;
54
+ }
55
+
56
+ /**
57
+ * Client egress vers la passerelle api-core (D17, modules tiers uniquement).
58
+ * Absent pour un module first-party.
59
+ *
60
+ * Callable direct : `egress(backendKey, path, init)` — le module ne fournit
61
+ * jamais d'URL absolue, la passerelle joint le chemin sous le chemin de base
62
+ * autorisé de la liste maîtresse (§6.5). `ClientApiService` l'encapsule.
63
+ */
64
+ export type ModuleEgressInterface = (
65
+ backendKey: string,
66
+ path: string,
67
+ init?: ModuleEgressRequestInit,
68
+ ) => Promise<unknown>;
69
+
70
+ export interface ModuleEgressRequestInit {
71
+ method?: string;
72
+ body?: unknown;
73
+ query?: Record<string, unknown>;
74
+ headers?: Record<string, string>;
75
+ }
76
+
77
+ export interface ModuleContextUserInterface {
78
+ id: string;
79
+ displayName: string;
80
+ }
81
+
82
+ export interface ModuleContextOrganizationInterface {
83
+ id: string;
84
+ name: string;
85
+ }
86
+
87
+ export interface ModuleContext {
88
+ /** Transport détecté (`detectModuleRuntime()`). */
89
+ runtime: ModuleRuntimeType;
90
+ /** Santé de la connectivité api-core — false → bandeau socle, dégradation du module. */
91
+ core: boolean;
92
+ route: ModuleRouteInterface;
93
+ bridge: ModuleBridgeInterface;
94
+ /** Instance d'`ApiService` construite et signée par l'hôte — jamais par le module. */
95
+ api: ModuleApiClientInterface;
96
+ /** Consentement utilisateur (D15). Injecté par l'hôte, déduit de l'accord. */
97
+ scope: {
98
+ user: ModuleScopeUserInterface;
99
+ };
100
+ /** Passerelle egress (D17). Absent pour un module first-party. */
101
+ egress?: ModuleEgressInterface;
102
+ user: ModuleContextUserInterface;
103
+ organization: ModuleContextOrganizationInterface;
104
+ }
105
+
106
+ export interface ModuleHandle {
107
+ unmount: () => void | Promise<void>;
108
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Périmètre de données utilisateur accordé à un module (D15 — consentement
3
+ * tout-ou-rien). Voir docs/specs/applications/module-isolated-runtime.md §6.10.
4
+ *
5
+ * Le grant vit dans api-core ; ce que voit le module n'est qu'une projection
6
+ * déclarative, interrogable et opposable — jamais un jeton.
7
+ */
8
+ export interface ModuleScopeUserInterface {
9
+ /** true si le module est approuvé par l'utilisateur (tout-ou-rien). */
10
+ readonly granted: boolean;
11
+
12
+ /** Périmètres accordés, même grammaire que `permissions` (ex. `User:Get`). */
13
+ readonly scopes: readonly string[];
14
+
15
+ /** Le périmètre demandé est-il couvert par l'accord ? */
16
+ can(permission: string): boolean;
17
+
18
+ /**
19
+ * Lève une exception typée si le périmètre n'est pas couvert. Seul idiome
20
+ * autorisé dans un chemin de données : un `if (can())` oublié rend un refus
21
+ * invisible.
22
+ */
23
+ require(permission: string): void;
24
+ }
25
+
26
+ /** Exception levée par `ctx.scope.user.require()` hors périmètre accordé. */
27
+ export class ModuleScopeDeniedError extends Error {
28
+ constructor(readonly permission: string) {
29
+ super(`Périmètre utilisateur non accordé au module : ${permission}`);
30
+ this.name = "ModuleScopeDeniedError";
31
+ }
32
+ }
@@ -1,5 +1,4 @@
1
1
  import React from "react";
2
- import {ApiService} from "../../infrastructure/utilities/api-service";
3
2
  import {IconKey} from "../../presentation/icons/types";
4
3
  import {RoutineInterface} from "../typing/routine.types";
5
4
  import {AppRouterInstance} from "next/dist/shared/lib/app-router-context.shared-runtime";
@@ -7,6 +6,7 @@ import {ModuleStoreStateType, ModuleConfigSettingsFieldInterface} from "./module
7
6
  import type {ModuleConfigurationInterface} from "./module-configuration.interface";
8
7
  import {ModuleCategory} from "../enums/module-category.enum";
9
8
  import type {ModuleType} from "../enums/module-type.enum";
9
+ import type {ModuleApiClientInterface} from "./module-runtime.interface";
10
10
 
11
11
  export type {ModuleType} from "../enums/module-type.enum";
12
12
 
@@ -25,7 +25,8 @@ export interface ModuleInstanceInterface {
25
25
  }
26
26
 
27
27
  export interface ModuleDeclarationServicesInterface {
28
- fetch?: ApiService
28
+ /** Client réseau injecté par l'hôte (D16) — jamais l'`ApiService` statique du socle. */
29
+ fetch?: ModuleApiClientInterface
29
30
  }
30
31
 
31
32
  export interface ModuleDeclarationProvidersInterface {
@@ -1,4 +1,4 @@
1
- import {IconKey} from "../../presentation/icons/types";
1
+ import type {IconKey} from "../../presentation/icons/types";
2
2
 
3
3
  /**
4
4
  * Catégories de modules du Module Store.