@semacode/cli 1.2.9 → 1.2.11

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 (30) hide show
  1. package/README.md +2 -2
  2. package/dist/drift.d.ts +1 -1
  3. package/dist/drift.js +213 -2
  4. package/dist/drift.js.map +1 -1
  5. package/docs/sintaxe.md +95 -1
  6. package/node_modules/@sema/gerador-dart/package.json +1 -1
  7. package/node_modules/@sema/gerador-lua/package.json +1 -1
  8. package/node_modules/@sema/gerador-python/package.json +1 -1
  9. package/node_modules/@sema/gerador-typescript/package.json +1 -1
  10. package/node_modules/@sema/nucleo/dist/ast/tipos.d.ts +7 -1
  11. package/node_modules/@sema/nucleo/dist/formatador/index.js +65 -21
  12. package/node_modules/@sema/nucleo/dist/formatador/index.js.map +1 -1
  13. package/node_modules/@sema/nucleo/dist/ir/conversor.js +91 -1
  14. package/node_modules/@sema/nucleo/dist/ir/conversor.js.map +1 -1
  15. package/node_modules/@sema/nucleo/dist/ir/modelos.d.ts +71 -0
  16. package/node_modules/@sema/nucleo/dist/lexer/tokens.js +6 -0
  17. package/node_modules/@sema/nucleo/dist/lexer/tokens.js.map +1 -1
  18. package/node_modules/@sema/nucleo/dist/parser/parser.js +12 -0
  19. package/node_modules/@sema/nucleo/dist/parser/parser.js.map +1 -1
  20. package/node_modules/@sema/nucleo/dist/semantico/analisador.js +409 -8
  21. package/node_modules/@sema/nucleo/dist/semantico/analisador.js.map +1 -1
  22. package/node_modules/@sema/nucleo/dist/semantico/estruturas.d.ts +6 -1
  23. package/node_modules/@sema/nucleo/dist/semantico/estruturas.js +63 -13
  24. package/node_modules/@sema/nucleo/dist/semantico/estruturas.js.map +1 -1
  25. package/node_modules/@sema/nucleo/dist/semantico/seguranca.d.ts +91 -0
  26. package/node_modules/@sema/nucleo/dist/semantico/seguranca.js +258 -0
  27. package/node_modules/@sema/nucleo/dist/semantico/seguranca.js.map +1 -0
  28. package/node_modules/@sema/nucleo/package.json +1 -1
  29. package/node_modules/@sema/padroes/package.json +1 -1
  30. package/package.json +7 -7
@@ -1,5 +1,6 @@
1
1
  import { criarDiagnostico } from "../diagnosticos/index.js";
2
2
  import { ehCategoriaEfeitoSemantico, ehCriticidadeEfeitoSemantico, extrairReferenciasDaExpressao, parsearEfeitoSemantico, parsearEtapaFlow, parsearExpressaoSemantica, parsearTransicaoEstado, } from "./estruturas.js";
3
+ import { CLASSIFICACOES_DADO_SUPORTADAS, MODOS_AUTH_SUPORTADOS, MOTIVOS_AUDIT_SUPORTADOS, ORIGENS_AUTH_SUPORTADAS, PRINCIPAIS_AUTH_SUPORTADOS, REDACOES_LOG_SUPORTADAS, TENANTS_AUTHZ_SUPORTADOS, contratoDadosTemSegredoOuCredencial, contratoDadosTemSensivel, extrairContratoAudit, extrairContratoAuth, extrairContratoAuthz, extrairContratoDados, extrairContratoForbidden, extrairContratoSegredos, efeitoEhPrivilegiado, efeitoRequerSegredo, forbiddenContemRegra, } from "./seguranca.js";
3
4
  function ehUseInterop(use) {
4
5
  return use.origem !== "sema";
5
6
  }
@@ -44,6 +45,41 @@ const CAMPOS_EXECUCAO_SUPORTADOS = new Set([
44
45
  "compensacao",
45
46
  "criticidade_operacional",
46
47
  ]);
48
+ const CAMPOS_AUTH_SUPORTADOS = new Set([
49
+ "modo",
50
+ "estrategia",
51
+ "principal",
52
+ "origem",
53
+ ]);
54
+ const CAMPOS_AUTHZ_SUPORTADOS = new Set([
55
+ "papel",
56
+ "papeis",
57
+ "escopo",
58
+ "escopos",
59
+ "politica",
60
+ "tenant",
61
+ ]);
62
+ const CAMPOS_DADOS_SUPORTADOS = new Set([
63
+ "classificacao_padrao",
64
+ "redacao_log",
65
+ "retencao",
66
+ ]);
67
+ const CAMPOS_AUDIT_SUPORTADOS = new Set([
68
+ "evento",
69
+ "ator",
70
+ "correlacao",
71
+ "retencao",
72
+ "motivo",
73
+ ]);
74
+ const CAMPOS_SEGREDO_SUPORTADOS = new Set([
75
+ "origem",
76
+ "escopo",
77
+ "acesso",
78
+ "rotacao",
79
+ "nao_logar",
80
+ "nao_retornar",
81
+ "mascarar",
82
+ ]);
47
83
  const CAMPOS_ERRO_OPERACIONAL = new Set([
48
84
  "mensagem",
49
85
  "categoria",
@@ -109,9 +145,15 @@ function validarCamposDeTipos(campos, tiposConhecidos, diagnosticos, contexto) {
109
145
  }
110
146
  }
111
147
  function localizarBloco(corpo, nome) {
112
- return corpo.blocos.find((bloco) => bloco.tipo === "bloco_generico" && bloco.palavraChave === nome);
148
+ if (!corpo) {
149
+ return undefined;
150
+ }
151
+ return corpo.blocos.find((bloco) => bloco.tipo === "bloco_generico" && (bloco.palavraChave === nome || bloco.nome === nome));
113
152
  }
114
153
  function localizarCampo(bloco, ...nomes) {
154
+ if (!bloco) {
155
+ return undefined;
156
+ }
115
157
  return bloco.campos.find((campo) => nomes.includes(campo.nome));
116
158
  }
117
159
  function valorCampoCompleto(campo) {
@@ -230,23 +272,280 @@ function validarVinculos(bloco, diagnosticos, contexto) {
230
272
  }
231
273
  }
232
274
  }
233
- function validarExecucao(task, diagnosticos) {
234
- if (!task.execucao) {
275
+ function extrairPerfilCompatibilidade(bloco, padrao = "interno") {
276
+ const perfil = bloco
277
+ ? valorCampoCompleto(localizarCampo(bloco, "perfil", "compatibilidade"))?.toLowerCase()
278
+ : undefined;
279
+ if (perfil === "publico"
280
+ || perfil === "interno"
281
+ || perfil === "experimental"
282
+ || perfil === "legado"
283
+ || perfil === "deprecado") {
284
+ return perfil;
285
+ }
286
+ return padrao;
287
+ }
288
+ function coletarSuperficiesModulo(modulo) {
289
+ return [
290
+ ...modulo.workers.map((superficie) => ({ tipo: "worker", superficie })),
291
+ ...modulo.eventos.map((superficie) => ({ tipo: "evento", superficie })),
292
+ ...modulo.filas.map((superficie) => ({ tipo: "fila", superficie })),
293
+ ...modulo.crons.map((superficie) => ({ tipo: "cron", superficie })),
294
+ ...modulo.webhooks.map((superficie) => ({ tipo: "webhook", superficie })),
295
+ ...modulo.caches.map((superficie) => ({ tipo: "cache", superficie })),
296
+ ...modulo.storages.map((superficie) => ({ tipo: "storage", superficie })),
297
+ ...modulo.policies.map((superficie) => ({ tipo: "policy", superficie })),
298
+ ];
299
+ }
300
+ function superficieEhPublica(superficie, tipoSuperficie) {
301
+ return extrairPerfilCompatibilidade(superficie, tipoSuperficie === "webhook" ? "publico" : "interno") === "publico";
302
+ }
303
+ function taskEhSensivel(task) {
304
+ const criticidadeOperacional = task.execucao
305
+ ? valorCampoCompleto(localizarCampo(task.execucao, "criticidade_operacional"))
306
+ : undefined;
307
+ if (criticidadeOperacional === "alta" || criticidadeOperacional === "critica") {
308
+ return true;
309
+ }
310
+ return (task.effects?.linhas ?? []).some((linha) => {
311
+ const efeito = parsearEfeitoSemantico(linha.conteudo);
312
+ if (!efeito) {
313
+ return false;
314
+ }
315
+ return efeito.categoria === "persistencia" || efeito.criticidade === "alta" || efeito.criticidade === "critica" || efeitoEhPrivilegiado(efeito);
316
+ });
317
+ }
318
+ function taskTemRastreabilidade(task) {
319
+ return Boolean(task.impl || task.vinculos);
320
+ }
321
+ function routeEhMutante(route) {
322
+ const metodo = (localizarCampo(route.corpo, "metodo")?.valor ?? "").toUpperCase();
323
+ return ["POST", "PUT", "PATCH", "DELETE"].includes(metodo);
324
+ }
325
+ function validarExecucaoBloco(execucao, diagnosticos, contexto) {
326
+ if (!execucao) {
235
327
  return;
236
328
  }
237
- for (const campo of task.execucao.campos) {
329
+ for (const campo of execucao.campos) {
238
330
  if (!CAMPOS_EXECUCAO_SUPORTADOS.has(campo.nome)) {
239
- diagnosticos.push(criarDiagnostico("SEM065", `Campo de execucao "${campo.nome}" nao e suportado na task "${task.nome}".`, "erro", campo.intervalo, "Use apenas idempotencia, timeout, retry, compensacao ou criticidade_operacional."));
331
+ diagnosticos.push(criarDiagnostico("SEM065", `Campo de execucao "${campo.nome}" nao e suportado em ${contexto}.`, "erro", campo.intervalo, "Use apenas idempotencia, timeout, retry, compensacao ou criticidade_operacional."));
240
332
  continue;
241
333
  }
242
334
  if (campo.nome === "criticidade_operacional") {
243
335
  const criticidade = valorCampoCompleto(campo);
244
336
  if (criticidade && !CRITICIDADES_OPERACIONAIS.has(criticidade)) {
245
- diagnosticos.push(criarDiagnostico("SEM066", `Task "${task.nome}" declarou criticidade_operacional invalida: "${criticidade}".`, "erro", campo.intervalo, "Use apenas baixa, media, alta ou critica em execucao."));
337
+ diagnosticos.push(criarDiagnostico("SEM066", `Execucao de ${contexto} declarou criticidade_operacional invalida: "${criticidade}".`, "erro", campo.intervalo, "Use apenas baixa, media, alta ou critica em execucao."));
338
+ }
339
+ }
340
+ }
341
+ }
342
+ function validarExecucao(task, diagnosticos) {
343
+ validarExecucaoBloco(task.execucao, diagnosticos, `task "${task.nome}"`);
344
+ }
345
+ function validarAuthBloco(bloco, diagnosticos, contexto) {
346
+ if (!bloco) {
347
+ return;
348
+ }
349
+ for (const campo of bloco.campos) {
350
+ if (!CAMPOS_AUTH_SUPORTADOS.has(campo.nome)) {
351
+ diagnosticos.push(criarDiagnostico("SEM074", `Campo de auth "${campo.nome}" nao e suportado em ${contexto}.`, "erro", campo.intervalo, "Use apenas modo, estrategia, principal ou origem em auth."));
352
+ }
353
+ }
354
+ const auth = extrairContratoAuth(bloco);
355
+ if (auth.modo && !MODOS_AUTH_SUPORTADOS.has(auth.modo)) {
356
+ diagnosticos.push(criarDiagnostico("SEM075", `Auth em ${contexto} declarou modo invalido: "${auth.modo}".`, "erro", bloco.intervalo, "Use obrigatorio, opcional, anonimo, interno ou m2m."));
357
+ }
358
+ if (auth.principal && !PRINCIPAIS_AUTH_SUPORTADOS.has(auth.principal)) {
359
+ diagnosticos.push(criarDiagnostico("SEM076", `Auth em ${contexto} declarou principal invalido: "${auth.principal}".`, "erro", bloco.intervalo, "Use usuario, servico, sistema ou anonimo."));
360
+ }
361
+ if (auth.origem && !ORIGENS_AUTH_SUPORTADAS.has(auth.origem)) {
362
+ diagnosticos.push(criarDiagnostico("SEM077", `Auth em ${contexto} declarou origem invalida: "${auth.origem}".`, "erro", bloco.intervalo, "Use publica, interna, worker, webhook, fila ou cron."));
363
+ }
364
+ }
365
+ function validarAuthzBloco(bloco, diagnosticos, contexto) {
366
+ if (!bloco) {
367
+ return;
368
+ }
369
+ for (const campo of bloco.campos) {
370
+ if (!CAMPOS_AUTHZ_SUPORTADOS.has(campo.nome)) {
371
+ diagnosticos.push(criarDiagnostico("SEM078", `Campo de authz "${campo.nome}" nao e suportado em ${contexto}.`, "erro", campo.intervalo, "Use papel, papeis, escopo, escopos, politica ou tenant em authz."));
372
+ }
373
+ }
374
+ const authz = extrairContratoAuthz(bloco);
375
+ if (authz.tenant && !TENANTS_AUTHZ_SUPORTADOS.has(authz.tenant)) {
376
+ diagnosticos.push(criarDiagnostico("SEM079", `Authz em ${contexto} declarou tenant invalido: "${authz.tenant}".`, "erro", bloco.intervalo, "Use obrigatorio, opcional ou isolado."));
377
+ }
378
+ if (authz.papeis.length === 0 && authz.escopos.length === 0 && !authz.politica) {
379
+ diagnosticos.push(criarDiagnostico("SEM080", `Authz em ${contexto} precisa declarar papeis, escopos ou politica.`, "erro", bloco.intervalo, "Explicite ao menos um papel, escopo ou politica para a autorizacao nao virar enfeite."));
380
+ }
381
+ }
382
+ function validarDadosBloco(bloco, diagnosticos, contexto) {
383
+ if (!bloco) {
384
+ return;
385
+ }
386
+ for (const campo of bloco.campos) {
387
+ const valor = valorCampoCompleto(campo);
388
+ if (CAMPOS_DADOS_SUPORTADOS.has(campo.nome)) {
389
+ continue;
390
+ }
391
+ if (valor && !CLASSIFICACOES_DADO_SUPORTADAS.has(valor)) {
392
+ diagnosticos.push(criarDiagnostico("SEM081", `Dados em ${contexto} declarou classificacao invalida para "${campo.nome}": "${valor}".`, "erro", campo.intervalo, "Use publico, interno, pii, financeiro, credencial ou segredo."));
393
+ }
394
+ }
395
+ const dados = extrairContratoDados(bloco);
396
+ if (dados.classificacaoPadrao && !CLASSIFICACOES_DADO_SUPORTADAS.has(dados.classificacaoPadrao)) {
397
+ diagnosticos.push(criarDiagnostico("SEM081", `Dados em ${contexto} declarou classificacao_padrao invalida: "${dados.classificacaoPadrao}".`, "erro", bloco.intervalo, "Use publico, interno, pii, financeiro, credencial ou segredo."));
398
+ }
399
+ if (dados.redacaoLog && !REDACOES_LOG_SUPORTADAS.has(dados.redacaoLog)) {
400
+ diagnosticos.push(criarDiagnostico("SEM082", `Dados em ${contexto} declarou redacao_log invalida: "${dados.redacaoLog}".`, "erro", bloco.intervalo, "Use livre, parcial, obrigatoria ou proibida."));
401
+ }
402
+ for (const subbloco of bloco.blocos) {
403
+ if (subbloco.tipo !== "bloco_generico") {
404
+ continue;
405
+ }
406
+ const nomeSubbloco = subbloco.nome ?? subbloco.palavraChave;
407
+ if (nomeSubbloco !== "input" && nomeSubbloco !== "output") {
408
+ diagnosticos.push(criarDiagnostico("SEM083", `Dados em ${contexto} nao suporta o subbloco "${nomeSubbloco}".`, "erro", subbloco.intervalo, "Use apenas campos diretos ou subblocos input/output para classificar dados."));
409
+ continue;
410
+ }
411
+ for (const campo of subbloco.campos) {
412
+ const classificacao = valorCampoCompleto(campo);
413
+ if (classificacao && !CLASSIFICACOES_DADO_SUPORTADAS.has(classificacao)) {
414
+ diagnosticos.push(criarDiagnostico("SEM081", `Dados em ${contexto} declarou classificacao invalida para "${nomeSubbloco}.${campo.nome}": "${classificacao}".`, "erro", campo.intervalo, "Use publico, interno, pii, financeiro, credencial ou segredo."));
415
+ }
416
+ }
417
+ }
418
+ }
419
+ function validarAuditBloco(bloco, diagnosticos, contexto) {
420
+ if (!bloco) {
421
+ return;
422
+ }
423
+ for (const campo of bloco.campos) {
424
+ if (!CAMPOS_AUDIT_SUPORTADOS.has(campo.nome)) {
425
+ diagnosticos.push(criarDiagnostico("SEM084", `Campo de audit "${campo.nome}" nao e suportado em ${contexto}.`, "erro", campo.intervalo, "Use evento, ator, correlacao, retencao ou motivo em audit."));
426
+ }
427
+ }
428
+ const audit = extrairContratoAudit(bloco);
429
+ if (!audit.evento) {
430
+ diagnosticos.push(criarDiagnostico("SEM085", `Audit em ${contexto} precisa declarar evento.`, "erro", bloco.intervalo, "Explique qual evento auditavel sera registrado para a operacao."));
431
+ }
432
+ if (audit.motivo && !MOTIVOS_AUDIT_SUPORTADOS.has(audit.motivo)) {
433
+ diagnosticos.push(criarDiagnostico("SEM086", `Audit em ${contexto} declarou motivo invalido: "${audit.motivo}".`, "erro", bloco.intervalo, "Use obrigatorio, opcional ou dispensado."));
434
+ }
435
+ }
436
+ function validarSegredosBloco(bloco, diagnosticos, contexto) {
437
+ if (!bloco) {
438
+ return;
439
+ }
440
+ const segredos = extrairContratoSegredos(bloco);
441
+ if (segredos.itens.length === 0) {
442
+ diagnosticos.push(criarDiagnostico("SEM087", `Segredos em ${contexto} precisa declarar ao menos um segredo nomeado.`, "erro", bloco.intervalo, "Use segredos { nome_do_segredo { origem: vault escopo: runtime ... } }."));
443
+ return;
444
+ }
445
+ for (const item of bloco.blocos) {
446
+ if (item.tipo !== "bloco_generico") {
447
+ continue;
448
+ }
449
+ for (const campo of item.campos) {
450
+ if (!CAMPOS_SEGREDO_SUPORTADOS.has(campo.nome)) {
451
+ diagnosticos.push(criarDiagnostico("SEM087", `Segredo "${item.nome ?? item.palavraChave}" em ${contexto} usa o campo "${campo.nome}", que nao e suportado.`, "erro", campo.intervalo, "Use origem, escopo, acesso, rotacao, nao_logar, nao_retornar ou mascarar."));
452
+ }
453
+ }
454
+ const nomeSegredo = item.nome ?? item.palavraChave;
455
+ const origem = valorCampoCompleto(localizarCampo(item, "origem"));
456
+ if (!origem) {
457
+ diagnosticos.push(criarDiagnostico("SEM088", `Segredo "${nomeSegredo}" em ${contexto} precisa declarar origem.`, "erro", item.intervalo, "Explicite a origem do segredo, como vault, env, secret_manager ou runtime."));
458
+ }
459
+ for (const nomeBooleano of ["nao_logar", "nao_retornar", "mascarar"]) {
460
+ const campo = localizarCampo(item, nomeBooleano);
461
+ const valor = valorCampoCompleto(campo);
462
+ if (campo && valor !== "verdadeiro" && valor !== "true" && valor !== "falso" && valor !== "false") {
463
+ diagnosticos.push(criarDiagnostico("SEM089", `Segredo "${nomeSegredo}" em ${contexto} declarou "${nomeBooleano}" com valor invalido: "${valor}".`, "erro", campo.intervalo, "Use verdadeiro/falso para campos booleanos de segredos."));
246
464
  }
247
465
  }
248
466
  }
249
467
  }
468
+ function validarForbiddenBloco(bloco, efeitos, diagnosticos, contexto) {
469
+ const forbidden = extrairContratoForbidden(bloco);
470
+ if (!bloco) {
471
+ return forbidden;
472
+ }
473
+ for (const regra of forbidden.regras) {
474
+ if (!/^[A-Za-z_][A-Za-z0-9_.-]*$/u.test(regra)) {
475
+ diagnosticos.push(criarDiagnostico("SEM090", `Forbidden em ${contexto} declarou regra invalida: "${regra}".`, "erro", bloco.intervalo, "Use regras simples como network.egress, shell.exec, retorno.credencial ou log.segredo."));
476
+ }
477
+ }
478
+ for (const linha of efeitos) {
479
+ const efeito = parsearEfeitoSemantico(linha.conteudo);
480
+ if (efeito && forbiddenContemRegra(forbidden, efeito.categoria)) {
481
+ diagnosticos.push(criarDiagnostico("SEM091", `Forbidden em ${contexto} proibe "${efeito.categoria}", mas effects ainda declara esse efeito.`, "erro", linha.intervalo, "Remova o efeito proibido ou ajuste o bloco forbidden para refletir a operacao permitida de verdade."));
482
+ }
483
+ }
484
+ return forbidden;
485
+ }
486
+ function coletarPerfilSegurancaDeclarado(corpo, effects, diagnosticos, contexto) {
487
+ const authBloco = localizarBloco(corpo, "auth");
488
+ const authzBloco = localizarBloco(corpo, "authz");
489
+ const dadosBloco = localizarBloco(corpo, "dados");
490
+ const auditBloco = localizarBloco(corpo, "audit");
491
+ const segredosBloco = localizarBloco(corpo, "segredos");
492
+ const forbiddenBloco = localizarBloco(corpo, "forbidden");
493
+ if (diagnosticos) {
494
+ validarAuthBloco(authBloco, diagnosticos, contexto);
495
+ validarAuthzBloco(authzBloco, diagnosticos, contexto);
496
+ validarDadosBloco(dadosBloco, diagnosticos, contexto);
497
+ validarAuditBloco(auditBloco, diagnosticos, contexto);
498
+ validarSegredosBloco(segredosBloco, diagnosticos, contexto);
499
+ }
500
+ const forbidden = diagnosticos
501
+ ? validarForbiddenBloco(forbiddenBloco, effects?.linhas ?? [], diagnosticos, contexto)
502
+ : extrairContratoForbidden(forbiddenBloco);
503
+ const auth = extrairContratoAuth(authBloco);
504
+ const authz = extrairContratoAuthz(authzBloco);
505
+ const dados = extrairContratoDados(dadosBloco);
506
+ const audit = extrairContratoAudit(auditBloco);
507
+ const segredos = extrairContratoSegredos(segredosBloco);
508
+ const efeitosEstruturados = (effects?.linhas ?? [])
509
+ .map((linha) => parsearEfeitoSemantico(linha.conteudo))
510
+ .filter((efeito) => Boolean(efeito));
511
+ return {
512
+ auth,
513
+ authz,
514
+ dados,
515
+ audit,
516
+ segredos,
517
+ forbidden,
518
+ efeitoPrivilegiado: efeitosEstruturados.some((efeito) => efeitoEhPrivilegiado(efeito)),
519
+ dadosSensiveis: contratoDadosTemSensivel(dados),
520
+ exigeSegredos: efeitosEstruturados.some((efeito) => efeitoRequerSegredo(efeito)) || contratoDadosTemSegredoOuCredencial(dados),
521
+ };
522
+ }
523
+ function emitirGuardrailsSeguranca(contexto, intervalo, perfil, diagnosticos, opcoes) {
524
+ const exigeAuth = opcoes.publico;
525
+ const exigeAuthz = opcoes.publico || opcoes.sensivel || perfil.efeitoPrivilegiado || perfil.dadosSensiveis;
526
+ const exigeDados = opcoes.publico || opcoes.sensivel || perfil.efeitoPrivilegiado;
527
+ const exigeAudit = opcoes.publico || opcoes.sensivel || perfil.efeitoPrivilegiado || perfil.dadosSensiveis;
528
+ const exigeSegredos = perfil.exigeSegredos;
529
+ const exigeForbidden = perfil.efeitoPrivilegiado || perfil.dadosSensiveis;
530
+ if (exigeAuth && !perfil.auth.explicita) {
531
+ diagnosticos.push(criarDiagnostico("SEM094", `${contexto} deveria declarar auth explicita para reduzir ambiguidade de seguranca na borda publica.`, "aviso", intervalo, "Declare auth { modo: obrigatorio|anonimo ... } para deixar a intencao da exposicao publica cristalina."));
532
+ }
533
+ if (exigeAuthz && !perfil.authz.explicita) {
534
+ diagnosticos.push(criarDiagnostico("SEM095", `${contexto} deveria declarar authz explicita porque opera com risco, privilegio ou exposicao publica.`, "aviso", intervalo, "Declare papeis, escopos ou politica em authz para nao empurrar autorizacao para o limbo do codigo vivo."));
535
+ }
536
+ if (exigeDados && !perfil.dados.explicita) {
537
+ diagnosticos.push(criarDiagnostico("SEM096", `${contexto} deveria classificar dados de forma explicita em dados { ... }.`, "aviso", intervalo, "Classifique input/output com publico, interno, pii, financeiro, credencial ou segredo."));
538
+ }
539
+ if (exigeAudit && !perfil.audit.explicita) {
540
+ diagnosticos.push(criarDiagnostico("SEM097", `${contexto} deveria declarar audit explicita para operar com trilha semantica de seguranca.`, "aviso", intervalo, "Declare audit { evento: ... correlacao: ... motivo: ... } para nao depender de adivinhacao operacional."));
541
+ }
542
+ if (exigeSegredos && !perfil.segredos.explicita) {
543
+ diagnosticos.push(criarDiagnostico("SEM098", `${contexto} deveria declarar segredos explicitos porque toca credencial, segredo ou secret.read.`, "aviso", intervalo, "Use segredos { nome { origem: vault escopo: runtime ... } } para governar acesso sensivel."));
544
+ }
545
+ if (exigeForbidden && !perfil.forbidden.explicita) {
546
+ diagnosticos.push(criarDiagnostico("SEM099", `${contexto} deveria declarar forbidden explicito para proibir operacoes perigosas ou vazamento semantico.`, "aviso", intervalo, "Use forbidden { network.egress shell.exec log.segredo retorno.credencial } conforme o risco da operacao."));
547
+ }
548
+ }
250
549
  function validarErroOperacional(task, diagnosticos) {
251
550
  if (!task.error) {
252
551
  return;
@@ -289,6 +588,7 @@ function validarSuperficie(superficie, tipoSuperficie, tasksConhecidas, tiposCon
289
588
  const effects = localizarBloco(superficie, "effects");
290
589
  const impl = localizarBloco(superficie, "impl");
291
590
  const vinculos = localizarBloco(superficie, "vinculos");
591
+ const execucao = localizarBloco(superficie, "execucao");
292
592
  if (!task && !impl && !vinculos) {
293
593
  diagnosticos.push(criarDiagnostico("SEM069", `Superficie ${tipoSuperficie} "${nomeSuperficie}" precisa declarar task, impl ou vinculos para nao virar bloco decorativo.`, "erro", superficie.intervalo, "Declare ao menos uma task associada, um impl explicito ou vinculos rastreaveis com codigo vivo."));
294
594
  }
@@ -304,7 +604,13 @@ function validarSuperficie(superficie, tipoSuperficie, tasksConhecidas, tiposCon
304
604
  if (effects) {
305
605
  validarEfeitosDeclarados(effects.linhas, diagnosticos, `effects da superficie ${tipoSuperficie} ${nomeSuperficie}`);
306
606
  }
607
+ validarExecucaoBloco(execucao, diagnosticos, `superficie ${tipoSuperficie} "${nomeSuperficie}"`);
307
608
  validarVinculos(vinculos, diagnosticos, `${tipoSuperficie} ${nomeSuperficie}`);
609
+ const perfilSeguranca = coletarPerfilSegurancaDeclarado(superficie, effects, diagnosticos, `superficie ${tipoSuperficie} "${nomeSuperficie}"`);
610
+ emitirGuardrailsSeguranca(`Superficie ${tipoSuperficie} "${nomeSuperficie}"`, superficie.intervalo, perfilSeguranca, diagnosticos, {
611
+ publico: superficieEhPublica(superficie, tipoSuperficie),
612
+ sensivel: perfilSeguranca.efeitoPrivilegiado || perfilSeguranca.dadosSensiveis,
613
+ });
308
614
  }
309
615
  function recomporCaminhoRoute(campo) {
310
616
  if (!campo) {
@@ -387,15 +693,23 @@ function validarEfeitosDeclarados(linhas, diagnosticos, contexto) {
387
693
  for (const linha of linhas) {
388
694
  const efeito = parsearEfeitoSemantico(linha.conteudo);
389
695
  if (!efeito) {
390
- diagnosticos.push(criarDiagnostico("SEM023", `Declaracao invalida de efeito em ${contexto}: "${linha.conteudo}".`, "erro", linha.intervalo, "Use o formato \"categoria alvo\" ou \"categoria alvo detalhe\", com categorias como persistencia, consulta, evento, notificacao ou auditoria."));
696
+ diagnosticos.push(criarDiagnostico("SEM023", `Declaracao invalida de efeito em ${contexto}: "${linha.conteudo}".`, "erro", linha.intervalo, "Use o formato \"categoria alvo\" ou \"categoria alvo detalhe\", podendo adicionar criticidade=..., privilegio=... e isolamento=...."));
391
697
  continue;
392
698
  }
393
699
  if (!ehCategoriaEfeitoSemantico(efeito.categoria)) {
394
- diagnosticos.push(criarDiagnostico("SEM048", `Categoria de efeito "${efeito.categoria}" nao e suportada em ${contexto}.`, "erro", linha.intervalo, "Use apenas persistencia, consulta, evento, notificacao ou auditoria."));
700
+ diagnosticos.push(criarDiagnostico("SEM048", `Categoria de efeito "${efeito.categoria}" nao e suportada em ${contexto}.`, "erro", linha.intervalo, "Use categorias como persistencia, consulta, evento, auditoria, db.write, queue.publish, fs.write, network.egress, secret.read ou shell.exec."));
395
701
  }
396
702
  if (efeito.criticidadeTexto && !ehCriticidadeEfeitoSemantico(efeito.criticidadeTexto)) {
397
703
  diagnosticos.push(criarDiagnostico("SEM052", `Criticidade de efeito "${efeito.criticidadeTexto}" nao e suportada em ${contexto}.`, "erro", linha.intervalo, "Use apenas criticidade=baixa, criticidade=media, criticidade=alta ou criticidade=critica."));
398
704
  }
705
+ if (efeito.privilegioTexto
706
+ && !["leitura", "escrita", "publicacao", "execucao", "admin", "egress"].includes(efeito.privilegioTexto)) {
707
+ diagnosticos.push(criarDiagnostico("SEM092", `Privilegio de efeito "${efeito.privilegioTexto}" nao e suportado em ${contexto}.`, "erro", linha.intervalo, "Use privilegio=leitura, privilegio=escrita, privilegio=publicacao, privilegio=execucao, privilegio=admin ou privilegio=egress."));
708
+ }
709
+ if (efeito.isolamentoTexto
710
+ && !["tenant", "processo", "host", "vps", "global"].includes(efeito.isolamentoTexto)) {
711
+ diagnosticos.push(criarDiagnostico("SEM093", `Isolamento de efeito "${efeito.isolamentoTexto}" nao e suportado em ${contexto}.`, "erro", linha.intervalo, "Use isolamento=tenant, isolamento=processo, isolamento=host, isolamento=vps ou isolamento=global."));
712
+ }
399
713
  }
400
714
  }
401
715
  function validarState(state, tiposConhecidos, enumsConhecidos, diagnosticos) {
@@ -683,6 +997,11 @@ function validarRoute(route, tasksConhecidas, tarefasDetalhadas, diagnosticos) {
683
997
  validarEfeitosDeclarados(effects.linhas, diagnosticos, `effects da route ${route.nome}`);
684
998
  }
685
999
  validarVinculos(route.vinculos, diagnosticos, `route ${route.nome}`);
1000
+ const perfilSeguranca = coletarPerfilSegurancaDeclarado(route.corpo, effects, diagnosticos, `route "${route.nome}"`);
1001
+ emitirGuardrailsSeguranca(`Route "${route.nome}"`, route.intervalo, perfilSeguranca, diagnosticos, {
1002
+ publico: extrairPerfilCompatibilidade(route.corpo, "publico") === "publico",
1003
+ sensivel: routeEhMutante(route) || perfilSeguranca.efeitoPrivilegiado || perfilSeguranca.dadosSensiveis,
1004
+ });
686
1005
  if (task && !tasksConhecidas.has(task.valor)) {
687
1006
  diagnosticos.push(criarDiagnostico("SEM018", `Route "${route.nome}" referencia task "${task.valor}" que nao existe.`, "erro", task.intervalo, "Ajuste o campo task da route para apontar para uma task declarada no modulo."));
688
1007
  return;
@@ -691,6 +1010,82 @@ function validarRoute(route, tasksConhecidas, tarefasDetalhadas, diagnosticos) {
691
1010
  validarContratoRoute(route, task.valor, tarefasDetalhadas, diagnosticos);
692
1011
  }
693
1012
  }
1013
+ function validarGuardrailsSeguranca(modulo, diagnosticos) {
1014
+ const superficies = coletarSuperficiesModulo(modulo);
1015
+ for (const task of modulo.tasks) {
1016
+ const motivos = new Set();
1017
+ const rotasPublicasAssociadas = modulo.routes.filter((route) => localizarCampo(route.corpo, "task", "tarefa")?.valor === task.nome
1018
+ && extrairPerfilCompatibilidade(route.corpo, "publico") === "publico");
1019
+ const superficiesPublicasAssociadas = superficies.filter((item) => localizarCampo(item.superficie, "task", "tarefa")?.valor === task.nome
1020
+ && superficieEhPublica(item.superficie, item.tipo));
1021
+ const perfilTask = coletarPerfilSegurancaDeclarado(task.corpo, task.effects, undefined, `task "${task.nome}"`);
1022
+ if (taskEhSensivel(task)) {
1023
+ motivos.add("criticidade operacional alta/critica ou efeito sensivel");
1024
+ }
1025
+ for (const route of rotasPublicasAssociadas) {
1026
+ motivos.add(`route publica "${route.nome}"`);
1027
+ }
1028
+ for (const item of superficiesPublicasAssociadas) {
1029
+ motivos.add(`superficie publica ${item.tipo} "${item.superficie.nome ?? item.tipo}"`);
1030
+ }
1031
+ const motivosOrdenados = [...motivos];
1032
+ if (motivosOrdenados.length === 0) {
1033
+ continue;
1034
+ }
1035
+ const perfisPublicos = [
1036
+ ...rotasPublicasAssociadas.map((route) => coletarPerfilSegurancaDeclarado(route.corpo, localizarBloco(route.corpo, "effects"), undefined, `route "${route.nome}"`)),
1037
+ ...superficiesPublicasAssociadas.map((item) => coletarPerfilSegurancaDeclarado(item.superficie, localizarBloco(item.superficie, "effects"), undefined, `superficie ${item.tipo} "${item.superficie.nome ?? item.tipo}"`)),
1038
+ ];
1039
+ const perfilPublico = {
1040
+ auth: { ...perfilTask.auth, explicita: perfilTask.auth.explicita || perfisPublicos.some((perfil) => perfil.auth.explicita) },
1041
+ authz: {
1042
+ ...perfilTask.authz,
1043
+ explicita: perfilTask.authz.explicita || perfisPublicos.some((perfil) => perfil.authz.explicita),
1044
+ papeis: [...new Set([perfilTask.authz.papeis, ...perfisPublicos.map((perfil) => perfil.authz.papeis)].flat())],
1045
+ escopos: [...new Set([perfilTask.authz.escopos, ...perfisPublicos.map((perfil) => perfil.authz.escopos)].flat())],
1046
+ },
1047
+ dados: {
1048
+ ...perfilTask.dados,
1049
+ explicita: perfilTask.dados.explicita || perfisPublicos.some((perfil) => perfil.dados.explicita),
1050
+ campos: [...perfilTask.dados.campos, ...perfisPublicos.flatMap((perfil) => perfil.dados.campos)],
1051
+ },
1052
+ audit: { ...perfilTask.audit, explicita: perfilTask.audit.explicita || perfisPublicos.some((perfil) => perfil.audit.explicita) },
1053
+ segredos: {
1054
+ ...perfilTask.segredos,
1055
+ explicita: perfilTask.segredos.explicita || perfisPublicos.some((perfil) => perfil.segredos.explicita),
1056
+ itens: [...perfilTask.segredos.itens, ...perfisPublicos.flatMap((perfil) => perfil.segredos.itens)],
1057
+ },
1058
+ forbidden: {
1059
+ ...perfilTask.forbidden,
1060
+ explicita: perfilTask.forbidden.explicita || perfisPublicos.some((perfil) => perfil.forbidden.explicita),
1061
+ regras: [...new Set([perfilTask.forbidden.regras, ...perfisPublicos.map((perfil) => perfil.forbidden.regras)].flat())],
1062
+ },
1063
+ efeitoPrivilegiado: perfilTask.efeitoPrivilegiado || perfisPublicos.some((perfil) => perfil.efeitoPrivilegiado),
1064
+ dadosSensiveis: perfilTask.dadosSensiveis || perfisPublicos.some((perfil) => perfil.dadosSensiveis),
1065
+ exigeSegredos: perfilTask.exigeSegredos || perfisPublicos.some((perfil) => perfil.exigeSegredos),
1066
+ };
1067
+ if (rotasPublicasAssociadas.length > 0 || superficiesPublicasAssociadas.length > 0) {
1068
+ emitirGuardrailsSeguranca(`Task "${task.nome}" exposta publicamente`, task.intervalo, perfilPublico, diagnosticos, { publico: true, sensivel: false });
1069
+ }
1070
+ const resumoMotivos = motivosOrdenados.join(", ");
1071
+ if (!task.execucao) {
1072
+ diagnosticos.push(criarDiagnostico("SEM071", `Task "${task.nome}" exige execucao explicita para producao por causa de ${resumoMotivos}, mas ainda opera com execucao implicita.`, "aviso", task.intervalo, "Declare timeout, retry, compensacao, idempotencia e criticidade_operacional no bloco execucao da task."));
1073
+ }
1074
+ if (!taskTemRastreabilidade(task)) {
1075
+ diagnosticos.push(criarDiagnostico("SEM072", `Task "${task.nome}" exige rastreabilidade forte por causa de ${resumoMotivos}, mas ainda nao declara impl nem vinculos.`, "aviso", task.intervalo, "Adicione impl e/ou vinculos para apontar arquivo, simbolo, recurso ou superficie real do codigo vivo."));
1076
+ }
1077
+ }
1078
+ for (const item of superficies) {
1079
+ if (!superficieEhPublica(item.superficie, item.tipo)) {
1080
+ continue;
1081
+ }
1082
+ const execucao = localizarBloco(item.superficie, "execucao");
1083
+ if (!execucao) {
1084
+ const nomeSuperficie = item.superficie.nome ?? item.tipo;
1085
+ diagnosticos.push(criarDiagnostico("SEM073", `Superficie publica ${item.tipo} "${nomeSuperficie}" deveria declarar execucao explicita para producao, mas ainda depende do padrao implicito.`, "aviso", item.superficie.intervalo, `Declare timeout, retry, compensacao e criticidade_operacional no proprio bloco ${item.tipo}.`));
1086
+ }
1087
+ }
1088
+ }
694
1089
  export function criarContextoLocal(modulo) {
695
1090
  const simbolos = new Map();
696
1091
  const tiposConhecidos = new Set(TIPOS_PRIMITIVOS);
@@ -829,6 +1224,11 @@ function validarTask(task, tiposConhecidos, statesConhecidos, diagnosticos) {
829
1224
  }
830
1225
  validarVinculos(task.vinculos, diagnosticos, `task ${task.nome}`);
831
1226
  validarExecucao(task, diagnosticos);
1227
+ const perfilSeguranca = coletarPerfilSegurancaDeclarado(task.corpo, task.effects, diagnosticos, `task "${task.nome}"`);
1228
+ emitirGuardrailsSeguranca(`Task "${task.nome}"`, task.intervalo, perfilSeguranca, diagnosticos, {
1229
+ publico: false,
1230
+ sensivel: taskEhSensivel(task) || perfilSeguranca.efeitoPrivilegiado || perfilSeguranca.dadosSensiveis || perfilSeguranca.exigeSegredos,
1231
+ });
832
1232
  validarImplementacoesTask(task, diagnosticos);
833
1233
  if (task.tests) {
834
1234
  for (const bloco of task.tests.blocos) {
@@ -1060,6 +1460,7 @@ export function analisarSemantica(modulo, opcoes = {}) {
1060
1460
  for (const policy of modulo.policies) {
1061
1461
  validarSuperficie(policy, "policy", tasksConhecidas, tiposConhecidos, diagnosticos);
1062
1462
  }
1463
+ validarGuardrailsSeguranca(modulo, diagnosticos);
1063
1464
  const assinaturasRoute = new Map();
1064
1465
  for (const route of modulo.routes) {
1065
1466
  const metodo = (localizarCampo(route.corpo, "metodo")?.valor ?? "").toUpperCase();