@oondemand/oon-core-back 0.4.1 → 0.4.2

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oondemand/oon-core-back",
3
- "version": "0.4.1",
3
+ "version": "0.4.2",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/oondemand/oon-platform.git",
@@ -18,6 +18,7 @@ const {
18
18
  validateRootCredentials,
19
19
  encrypt,
20
20
  instanceHeaders,
21
+ operationalIdentity,
21
22
  validateParams,
22
23
  providerPost,
23
24
  providerPayload,
@@ -83,13 +84,11 @@ function safeContext(data) {
83
84
 
84
85
  function requireRootBootstrapResult(result) {
85
86
  const usuarioId = result?.usuario?.id || result?.usuario?._id;
86
- const tenantId = result?.tenant?.id || result?.tenant?._id;
87
87
  const appId = result?.app?.id || result?.app?._id;
88
- const licencaId = result?.licenca?.id || result?.licenca?._id;
89
88
 
90
- if (!usuarioId || !tenantId || !appId || !licencaId) {
89
+ if (!usuarioId || !appId) {
91
90
  throw new GenericError(
92
- "O bootstrap root não retornou a estrutura mínima do ecossistema.",
91
+ "O bootstrap root não retornou Usuario e App de sistema.",
93
92
  { statusCode: 500 },
94
93
  );
95
94
  }
@@ -106,9 +105,8 @@ function requireRootBootstrapResult(result) {
106
105
 
107
106
  return {
108
107
  usuarioId: String(usuarioId),
109
- tenantId: String(tenantId),
110
108
  appId: String(appId),
111
- licencaId: String(licencaId),
109
+ systemAccessId: idOf(result?.systemAccess) ? String(idOf(result.systemAccess)) : undefined,
112
110
  };
113
111
  }
114
112
 
@@ -141,11 +139,17 @@ function idOf(value) {
141
139
  return value?.id || value?._id || value;
142
140
  }
143
141
 
144
- function summarizeDevInstance(data, appCode) {
142
+ function summarizeOperationalIdentity(data, appCode, { allowSynthetic = false } = {}) {
145
143
  const appId = idOf(data?.app);
144
+ const deploymentId = idOf(data?.deployment);
145
+ const instanceId = idOf(data?.instancia || data?.instance);
146
146
  return {
147
- instanceId: `dev-local:${appId || appCode}`,
148
- instanceCode: "dev-local",
147
+ instanceId: instanceId ? String(instanceId) : (allowSynthetic ? `dev-local:${appId || appCode}` : undefined),
148
+ instanceCode: data?.instancia?.codigo || data?.instance?.codigo || (allowSynthetic ? "dev-local" : undefined),
149
+ deploymentId: deploymentId ? String(deploymentId) : undefined,
150
+ bindingId: idOf(data?.binding) ? String(idOf(data.binding)) : undefined,
151
+ entitlementId: idOf(data?.entitlement) ? String(idOf(data.entitlement)) : undefined,
152
+ credentialType: deploymentId ? "deployment" : "instance",
149
153
  appId: appId ? String(appId) : undefined,
150
154
  tenantId: idOf(data?.tenant) ? String(idOf(data.tenant)) : undefined,
151
155
  licencaId: idOf(data?.licenca) ? String(idOf(data.licenca)) : undefined,
@@ -171,7 +175,7 @@ router.get(
171
175
  const canActivateMember =
172
176
  instance.ecosystemRole === "member" &&
173
177
  ["nao_ativada", "erro"].includes(instance.status) &&
174
- !instance.instanceId;
178
+ !operationalIdentity(instance);
175
179
 
176
180
  sendResponse({
177
181
  res,
@@ -182,7 +186,7 @@ router.get(
182
186
  podeAtivar: canActivateRoot || canActivateMember,
183
187
  podeTentarNovamente:
184
188
  instance.ecosystemRole === "member" &&
185
- Boolean(instance.instanceId) &&
189
+ Boolean(operationalIdentity(instance)) &&
186
190
  ["configurando", "erro"].includes(instance.status),
187
191
  motivoSuspensao: instance.motivoSuspensao,
188
192
  motivoRevogacao: instance.motivoRevogacao,
@@ -284,14 +288,14 @@ router.post(
284
288
  instanceCode: result.instanceCode || "root",
285
289
  appCode: getAppCode(),
286
290
  appId: ids.appId,
287
- tenantId: ids.tenantId,
288
- licencaId: ids.licencaId,
291
+ tenantId: undefined,
292
+ licencaId: undefined,
289
293
  ambiente: ambiente(),
290
294
  nomeInstancia: ctx.config.name || ctx.config.slug || getAppCode(),
291
295
  publicUrl: publicUrl(req),
292
- tenant: result.tenant,
293
296
  app: result.app,
294
- licenca: result.licenca,
297
+ tenant: undefined,
298
+ licenca: undefined,
295
299
  parametrosGlobais: {},
296
300
  parametrosAplicacao: {},
297
301
  configurationVersion: config.configurationVersion,
@@ -389,6 +393,13 @@ router.post(
389
393
  branch: req.body?.branch,
390
394
  nomeUsuario: req.body?.nomeUsuario,
391
395
  nomeTenant: req.body?.nomeTenant,
396
+ ecosystemRole: ctx.config.ecosystem?.role,
397
+ tenancyModel: ctx.config.tenancyModel || ctx.config.ecosystem?.tenancyModel,
398
+ availability: ctx.config.availability || ctx.config.ecosystem?.availability,
399
+ accessMode: ctx.config.accessMode || ctx.config.ecosystem?.accessMode,
400
+ authorizationMode:
401
+ ctx.config.authorizationMode || ctx.config.ecosystem?.authorizationMode,
402
+ deploymentMode: ctx.config.deploymentMode || ctx.config.ecosystem?.deploymentMode,
392
403
  },
393
404
  { Authorization: authorization },
394
405
  );
@@ -399,7 +410,8 @@ router.post(
399
410
  const resolvedAppCode = String(
400
411
  data.app?.codigo || data.appCode || appCode,
401
412
  ).toLowerCase();
402
- const ids = summarizeDevInstance(data, resolvedAppCode);
413
+ const ids = summarizeOperationalIdentity(data, resolvedAppCode, { allowSynthetic: true });
414
+ const operationalToken = data?.credencial?.token || data?.deploymentCredential?.token;
403
415
  const now = new Date();
404
416
 
405
417
  Object.assign(instance, {
@@ -407,6 +419,10 @@ router.post(
407
419
  ecosystemRole: "member",
408
420
  instanceId: ids.instanceId,
409
421
  instanceCode: ids.instanceCode,
422
+ deploymentId: ids.deploymentId,
423
+ bindingId: ids.bindingId,
424
+ entitlementId: ids.entitlementId,
425
+ credentialType: ids.credentialType,
410
426
  appCode: resolvedAppCode,
411
427
  appId: ids.appId,
412
428
  tenantId: ids.tenantId,
@@ -417,13 +433,22 @@ router.post(
417
433
  publicUrl: urlPublica,
418
434
  coreVersion: process.env.npm_package_version || "0.0.0",
419
435
  appVersion: process.env.SERVICE_VERSION || "0.1.0",
420
- credentialEncrypted: undefined,
421
- credentialIv: undefined,
422
- credentialAuthTag: undefined,
423
- credentialVersion: undefined,
436
+ ...(operationalToken ? encrypt(operationalToken) : {
437
+ credentialEncrypted: undefined,
438
+ credentialIv: undefined,
439
+ credentialAuthTag: undefined,
440
+ }),
441
+ credentialVersion:
442
+ data?.credencial?.versao ||
443
+ data?.credencial?.version ||
444
+ data?.deploymentCredential?.versao ||
445
+ data?.deploymentCredential?.version,
424
446
  tenant: data.tenant,
425
447
  app: data.app,
426
448
  licenca: data.licenca,
449
+ deployment: data.deployment,
450
+ binding: data.binding,
451
+ entitlement: data.entitlement,
427
452
  parametrosGlobais: {
428
453
  ...globalDefaults,
429
454
  nomeInstancia: nomeCentral,
@@ -455,6 +480,7 @@ router.post(
455
480
  sucesso: true,
456
481
  status: "ativa",
457
482
  instanceId: instance.instanceId,
483
+ deploymentId: instance.deploymentId,
458
484
  });
459
485
  } catch (error) {
460
486
  instance.status = "erro";
@@ -550,7 +576,7 @@ router.post(
550
576
  const instance = await ensureInstance();
551
577
 
552
578
  if (
553
- instance.instanceId &&
579
+ operationalIdentity(instance) &&
554
580
  ["configurando", "erro"].includes(instance.status)
555
581
  ) {
556
582
  return res.redirect(307, "/ativacao/tentar-novamente");
@@ -589,8 +615,9 @@ router.post(
589
615
  );
590
616
  assertApp(activated.data);
591
617
 
592
- const token = activated.data?.credencial?.token;
593
- if (!activated.data?.instancia?.id || !token) {
618
+ const token = activated.data?.credencial?.token || activated.data?.deploymentCredential?.token;
619
+ const ids = summarizeOperationalIdentity(activated.data, getAppCode());
620
+ if (!(ids.deploymentId || ids.instanceId) || !token) {
594
621
  throw new GenericError("Resposta de ativação inválida.", {
595
622
  statusCode: 502,
596
623
  });
@@ -598,9 +625,17 @@ router.post(
598
625
 
599
626
  Object.assign(instance, encrypt(token), {
600
627
  status: "configurando",
601
- instanceId: activated.data.instancia.id,
602
- instanceCode: activated.data.instancia.codigo,
603
- credentialVersion: activated.data.credencial.versao,
628
+ instanceId: ids.instanceId,
629
+ instanceCode: ids.instanceCode,
630
+ deploymentId: ids.deploymentId,
631
+ bindingId: ids.bindingId,
632
+ entitlementId: ids.entitlementId,
633
+ credentialType: ids.credentialType,
634
+ credentialVersion:
635
+ activated.data.credencial?.versao ||
636
+ activated.data.credencial?.version ||
637
+ activated.data.deploymentCredential?.versao ||
638
+ activated.data.deploymentCredential?.version,
604
639
  appCode: getAppCode(),
605
640
  appId: activated.data.app?.id || preview.data?.app?.id,
606
641
  tenantId: activated.data.tenant?.id || preview.data?.tenant?.id,
@@ -611,6 +646,9 @@ router.post(
611
646
  tenant: activated.data.tenant || preview.data?.tenant,
612
647
  app: activated.data.app || preview.data?.app,
613
648
  licenca: activated.data.licenca || preview.data?.licenca,
649
+ deployment: activated.data.deployment || preview.data?.deployment,
650
+ binding: activated.data.binding || preview.data?.binding,
651
+ entitlement: activated.data.entitlement || preview.data?.entitlement,
614
652
  parametrosGlobais,
615
653
  parametrosAplicacao,
616
654
  configurationVersion: config.configurationVersion,
@@ -629,6 +667,7 @@ router.post(
629
667
  sucesso: true,
630
668
  status: "ativa",
631
669
  instanceId: instance.instanceId,
670
+ deploymentId: instance.deploymentId,
632
671
  });
633
672
  }),
634
673
  );
@@ -687,7 +726,7 @@ router.post(
687
726
  const config = activationConfig();
688
727
 
689
728
  if (
690
- !instance.instanceId ||
729
+ !operationalIdentity(instance) ||
691
730
  !["configurando", "erro"].includes(instance.status)
692
731
  ) {
693
732
  throw new GenericError(
@@ -15,6 +15,7 @@ const {
15
15
  } = require("../core/utils/activationProvider");
16
16
 
17
17
  const DEV_ACTIVATION_PATH = "/integracao/dev-local/ativar-central";
18
+ const TENANT_HEADER = "x-oon-tenant-id";
18
19
  const router = express.Router();
19
20
 
20
21
  function readBasicAuthorization(req) {
@@ -41,6 +42,15 @@ function translateProviderError(error) {
41
42
  return new GenericError(message, { statusCode });
42
43
  }
43
44
 
45
+ function tenantForLogin(req, instance) {
46
+ const tenancyModel =
47
+ ctx.config.tenancyModel || ctx.config.ecosystem?.tenancyModel;
48
+ if (tenancyModel === "multi_tenant") {
49
+ return req.headers[TENANT_HEADER] || instance.tenantId;
50
+ }
51
+ return instance.tenantId;
52
+ }
53
+
44
54
  async function authenticateRoot(req, res, authorization, instance) {
45
55
  const authenticate = ctx.config.auth?.authenticate;
46
56
  if (instance.ecosystemRole === "root" && typeof authenticate === "function") {
@@ -62,6 +72,7 @@ router.post(
62
72
  if (await authenticateRoot(req, res, authorization, instance)) return;
63
73
  const baseUrl = getBaseUrl(req);
64
74
  const appCode = getAppCode();
75
+ const tenantId = tenantForLogin(req, instance);
65
76
  try {
66
77
  const response = await axios.post(
67
78
  `${baseUrl}${LOGIN_PATH}`,
@@ -73,6 +84,7 @@ router.post(
73
84
  Authorization: authorization,
74
85
  "Content-Type": "application/json",
75
86
  "x-app-code": appCode,
87
+ ...(tenantId ? { [TENANT_HEADER]: tenantId } : {}),
76
88
  },
77
89
  },
78
90
  );
@@ -104,6 +116,13 @@ router.post(
104
116
  branch: req.body?.branch,
105
117
  nomeUsuario: req.body?.nomeUsuario,
106
118
  nomeTenant: req.body?.nomeTenant,
119
+ ecosystemRole: ctx.config.ecosystem?.role,
120
+ tenancyModel: ctx.config.tenancyModel || ctx.config.ecosystem?.tenancyModel,
121
+ availability: ctx.config.availability || ctx.config.ecosystem?.availability,
122
+ accessMode: ctx.config.accessMode || ctx.config.ecosystem?.accessMode,
123
+ authorizationMode:
124
+ ctx.config.authorizationMode || ctx.config.ecosystem?.authorizationMode,
125
+ deploymentMode: ctx.config.deploymentMode || ctx.config.ecosystem?.deploymentMode,
107
126
  },
108
127
  {
109
128
  timeout: getTimeout(),
@@ -10,6 +10,10 @@ const schema = new mongoose.Schema(
10
10
  ecosystemRole: { type: String, enum: roles, default: "member", index: true },
11
11
  instanceId: { type: String, index: true },
12
12
  instanceCode: String,
13
+ deploymentId: { type: String, index: true },
14
+ bindingId: String,
15
+ entitlementId: String,
16
+ credentialType: { type: String, enum: ["instance", "deployment"] },
13
17
  appCode: { type: String, index: true },
14
18
  appId: String,
15
19
  tenantId: String,
@@ -30,6 +34,9 @@ const schema = new mongoose.Schema(
30
34
  parametrosAplicacao: { type: mongoose.Schema.Types.Mixed, default: {} },
31
35
  tenant: { type: mongoose.Schema.Types.Mixed, default: {} },
32
36
  app: { type: mongoose.Schema.Types.Mixed, default: {} },
37
+ deployment: { type: mongoose.Schema.Types.Mixed, default: {} },
38
+ binding: { type: mongoose.Schema.Types.Mixed, default: {} },
39
+ entitlement: { type: mongoose.Schema.Types.Mixed, default: {} },
33
40
  licenca: { type: mongoose.Schema.Types.Mixed, default: {} },
34
41
  ativadaEm: Date,
35
42
  concluidaEm: Date,
@@ -5,6 +5,7 @@ const GenericError = require("../errors/GenericError");
5
5
  const {
6
6
  ensureInstance,
7
7
  instanceHeaders,
8
+ operationalIdentity,
8
9
  providerPost,
9
10
  } = require("../utils/instanceActivation");
10
11
 
@@ -15,10 +16,10 @@ const IDENTITY_PATHS = Object.freeze({
15
16
 
16
17
  async function defaultProviderCall(req, path, payload) {
17
18
  const instance = await ensureInstance();
18
- if (!instance?.instanceId || !instance?.credentialEncrypted) {
19
- throw new GenericError("A instância precisa estar ativa para gerenciar identidades.", {
19
+ if (!operationalIdentity(instance) || !instance?.credentialEncrypted) {
20
+ throw new GenericError("O deployment precisa estar ativo para gerenciar identidades.", {
20
21
  statusCode: 423,
21
- code: "RBAC_INSTANCE_CREDENTIAL_REQUIRED",
22
+ code: "RBAC_OPERATIONAL_CREDENTIAL_REQUIRED",
22
23
  });
23
24
  }
24
25
 
@@ -43,6 +44,7 @@ async function provisionInvitation({ req, invitation, password }) {
43
44
  return defaultProviderCall(req, IDENTITY_PATHS.provisionInvitation, {
44
45
  email: invitation.email,
45
46
  nome: invitation.name,
47
+ roleCode: invitation.roleCode,
46
48
  password,
47
49
  invitationFingerprint: invitation.tokenFingerprint,
48
50
  });
@@ -217,6 +217,10 @@ async function domainHasActiveRootApp() {
217
217
  function clearInstanceIdentity(instance) {
218
218
  instance.instanceId = undefined;
219
219
  instance.instanceCode = undefined;
220
+ instance.deploymentId = undefined;
221
+ instance.bindingId = undefined;
222
+ instance.entitlementId = undefined;
223
+ instance.credentialType = undefined;
220
224
  instance.appId = undefined;
221
225
  instance.tenantId = undefined;
222
226
  instance.licencaId = undefined;
@@ -393,6 +397,14 @@ function decrypt(instance) {
393
397
  }
394
398
 
395
399
  function instanceHeaders(instance) {
400
+ if (instance.deploymentId) {
401
+ return {
402
+ "x-oon-deployment-id": instance.deploymentId,
403
+ "x-oon-deployment-token": decrypt(instance),
404
+ "x-app-code": getAppCode(),
405
+ ...(instance.tenantId ? { "x-oon-tenant-id": instance.tenantId } : {}),
406
+ };
407
+ }
396
408
  return {
397
409
  "x-oon-instance-id": instance.instanceId,
398
410
  "x-oon-instance-token": decrypt(instance),
@@ -400,6 +412,10 @@ function instanceHeaders(instance) {
400
412
  };
401
413
  }
402
414
 
415
+ function operationalIdentity(instance) {
416
+ return instance?.deploymentId || instance?.instanceId;
417
+ }
418
+
403
419
  function validateUrl(value) {
404
420
  const url = new URL(value);
405
421
  if (!["http:", "https:"].includes(url.protocol)) throw new Error();
@@ -468,6 +484,11 @@ function providerPayload(req, codigoAtivacao, extra = {}) {
468
484
  coreVersion: version(),
469
485
  appVersion: version(),
470
486
  fingerprint: fingerprint(),
487
+ tenancyModel: ctx.config.tenancyModel || ctx.config.ecosystem?.tenancyModel,
488
+ availability: ctx.config.availability || ctx.config.ecosystem?.availability,
489
+ accessMode: ctx.config.accessMode || ctx.config.ecosystem?.accessMode,
490
+ authorizationMode: ctx.config.authorizationMode || ctx.config.ecosystem?.authorizationMode,
491
+ deploymentMode: ctx.config.deploymentMode || ctx.config.ecosystem?.deploymentMode,
471
492
  ...extra,
472
493
  };
473
494
  }
@@ -491,6 +512,7 @@ module.exports = {
491
512
  encrypt,
492
513
  decrypt,
493
514
  instanceHeaders,
515
+ operationalIdentity,
494
516
  validateParams,
495
517
  providerPost,
496
518
  providerPayload,
@@ -14,6 +14,9 @@ const SUPPORTED_MODULES = Object.freeze([
14
14
  ]);
15
15
  const SUPPORTED_TENANCY_MODELS = Object.freeze(["none", "single_tenant", "multi_tenant"]);
16
16
  const SUPPORTED_AVAILABILITIES = Object.freeze(["system", "common", "licensed"]);
17
+ const SUPPORTED_ACCESS_MODES = Object.freeze(["tenant_members", "explicit_users"]);
18
+ const SUPPORTED_AUTHORIZATION_MODES = Object.freeze(["none", "rbac"]);
19
+ // Compatibilidade de leitura para manifestos anteriores à separação das duas dimensões.
17
20
  const SUPPORTED_ACCESS_POLICIES = Object.freeze(["tenant_members", "explicit_users", "rbac"]);
18
21
  const SUPPORTED_DEPLOYMENT_MODES = Object.freeze(["singleton", "shared", "dedicated", "hybrid"]);
19
22
  const APP_KIND_ROLE = Object.freeze({
@@ -25,19 +28,22 @@ const APP_KIND_DIMENSIONS = Object.freeze({
25
28
  "root-central": Object.freeze({
26
29
  tenancyModel: "none",
27
30
  availability: "system",
28
- accessPolicy: "rbac",
31
+ accessMode: "explicit_users",
32
+ authorizationMode: "rbac",
29
33
  deploymentMode: "singleton",
30
34
  }),
31
35
  "member-central": Object.freeze({
32
36
  tenancyModel: "single_tenant",
33
37
  availability: "licensed",
34
- accessPolicy: "rbac",
38
+ accessMode: "explicit_users",
39
+ authorizationMode: "rbac",
35
40
  deploymentMode: "dedicated",
36
41
  }),
37
42
  "portal-cockpit": Object.freeze({
38
43
  tenancyModel: "multi_tenant",
39
44
  availability: "common",
40
- accessPolicy: "tenant_members",
45
+ accessMode: "tenant_members",
46
+ authorizationMode: "none",
41
47
  deploymentMode: "shared",
42
48
  }),
43
49
  });
@@ -136,12 +142,22 @@ function validateDimension(value, field, supported, issues) {
136
142
  }
137
143
  }
138
144
 
145
+ function legacyAccessDimensions(accessPolicy) {
146
+ return {
147
+ tenant_members: { accessMode: "tenant_members", authorizationMode: "none" },
148
+ explicit_users: { accessMode: "explicit_users", authorizationMode: "none" },
149
+ rbac: { accessMode: "explicit_users", authorizationMode: "rbac" },
150
+ }[accessPolicy];
151
+ }
152
+
139
153
  function dimensionsFor(manifest) {
140
154
  const defaults = APP_KIND_DIMENSIONS[manifest.appKind] || APP_KIND_DIMENSIONS["member-central"];
155
+ const legacy = legacyAccessDimensions(manifest.accessPolicy) || {};
141
156
  return {
142
157
  tenancyModel: manifest.tenancyModel || defaults.tenancyModel,
143
158
  availability: manifest.availability || defaults.availability,
144
- accessPolicy: manifest.accessPolicy || defaults.accessPolicy,
159
+ accessMode: manifest.accessMode || legacy.accessMode || defaults.accessMode,
160
+ authorizationMode: manifest.authorizationMode || legacy.authorizationMode || defaults.authorizationMode,
145
161
  deploymentMode: manifest.deploymentMode || defaults.deploymentMode,
146
162
  };
147
163
  }
@@ -153,7 +169,7 @@ function validateAppManifest(input, { coreVersion = corePackage.version } = {})
153
169
  }
154
170
  const allowedTopLevel = new Set([
155
171
  "$schema", "schemaVersion", "id", "name", "slug", "appKind",
156
- "tenancyModel", "availability", "accessPolicy", "deploymentMode",
172
+ "tenancyModel", "availability", "accessMode", "authorizationMode", "accessPolicy", "deploymentMode",
157
173
  "modules", "capabilities", "compatibility", "activation", "rbac",
158
174
  ]);
159
175
  for (const key of Object.keys(input)) {
@@ -174,8 +190,30 @@ function validateAppManifest(input, { coreVersion = corePackage.version } = {})
174
190
  }
175
191
  validateDimension(input.tenancyModel, "tenancyModel", SUPPORTED_TENANCY_MODELS, issues);
176
192
  validateDimension(input.availability, "availability", SUPPORTED_AVAILABILITIES, issues);
193
+ validateDimension(input.accessMode, "accessMode", SUPPORTED_ACCESS_MODES, issues);
194
+ validateDimension(input.authorizationMode, "authorizationMode", SUPPORTED_AUTHORIZATION_MODES, issues);
177
195
  validateDimension(input.accessPolicy, "accessPolicy", SUPPORTED_ACCESS_POLICIES, issues);
178
196
  validateDimension(input.deploymentMode, "deploymentMode", SUPPORTED_DEPLOYMENT_MODES, issues);
197
+ if (input.accessPolicy !== undefined && (input.accessMode !== undefined || input.authorizationMode !== undefined)) {
198
+ addIssue(issues, "accessPolicy", "é legado e não pode ser combinado com accessMode/authorizationMode.");
199
+ }
200
+ const dimensions = dimensionsFor(input);
201
+ const role = APP_KIND_ROLE[input.appKind];
202
+ if (role === "root" && (
203
+ dimensions.tenancyModel !== "none" ||
204
+ dimensions.availability !== "system" ||
205
+ dimensions.accessMode !== "explicit_users" ||
206
+ dimensions.authorizationMode !== "rbac" ||
207
+ dimensions.deploymentMode !== "singleton"
208
+ )) {
209
+ addIssue(issues, "appKind", "root-central exige none/system/explicit_users/rbac/singleton.");
210
+ }
211
+ if (role === "member" && dimensions.availability === "system") {
212
+ addIssue(issues, "availability", "Apps member não podem usar availability=system.");
213
+ }
214
+ if (dimensions.tenancyModel === "none" && dimensions.accessMode === "tenant_members") {
215
+ addIssue(issues, "accessMode", "tenant_members exige uma aplicação tenant-aware.");
216
+ }
179
217
  if (!isPlainObject(input.modules)) {
180
218
  addIssue(issues, "modules", "deve ser um objeto.");
181
219
  } else {
@@ -218,6 +256,7 @@ function appManifestToConfig(manifest, manifestPath) {
218
256
  slug: manifest.slug,
219
257
  appCode: manifest.id,
220
258
  appKind: manifest.appKind,
259
+ ...dimensions,
221
260
  ecosystem: {
222
261
  role: APP_KIND_ROLE[manifest.appKind],
223
262
  ...dimensions,
@@ -260,12 +299,15 @@ module.exports = {
260
299
  SUPPORTED_MODULES,
261
300
  SUPPORTED_TENANCY_MODELS,
262
301
  SUPPORTED_AVAILABILITIES,
302
+ SUPPORTED_ACCESS_MODES,
303
+ SUPPORTED_AUTHORIZATION_MODES,
263
304
  SUPPORTED_ACCESS_POLICIES,
264
305
  SUPPORTED_DEPLOYMENT_MODES,
265
306
  APP_KIND_DIMENSIONS,
266
307
  AppManifestError,
267
308
  parseVersion,
268
309
  compareVersions,
310
+ legacyAccessDimensions,
269
311
  dimensionsFor,
270
312
  validateAppManifest,
271
313
  resolveAppManifestPath,