@semacode/cli 1.2.0 → 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 (49) hide show
  1. package/README.md +2 -2
  2. package/SEMA_BRIEF.curto.txt +9 -9
  3. package/SEMA_BRIEF.md +49 -49
  4. package/SEMA_BRIEF.micro.txt +7 -7
  5. package/SEMA_INDEX.json +501 -546
  6. package/dist/drift.d.ts +3 -3
  7. package/dist/drift.js +213 -22
  8. package/dist/drift.js.map +1 -1
  9. package/dist/importador.d.ts +1 -1
  10. package/dist/importador.js +0 -60
  11. package/dist/importador.js.map +1 -1
  12. package/dist/index.js +1334 -1360
  13. package/dist/index.js.map +1 -1
  14. package/dist/projeto.js +0 -6
  15. package/dist/projeto.js.map +1 -1
  16. package/dist/tipos.d.ts +1 -1
  17. package/docs/AGENT_STARTER.md +102 -102
  18. package/docs/instalacao-e-primeiro-uso.md +196 -198
  19. package/docs/sintaxe.md +95 -1
  20. package/node_modules/@sema/gerador-dart/package.json +1 -1
  21. package/node_modules/@sema/gerador-lua/dist/index.js +49 -81
  22. package/node_modules/@sema/gerador-lua/dist/index.js.map +1 -1
  23. package/node_modules/@sema/gerador-lua/package.json +1 -1
  24. package/node_modules/@sema/gerador-python/package.json +1 -1
  25. package/node_modules/@sema/gerador-typescript/package.json +1 -1
  26. package/node_modules/@sema/nucleo/dist/ast/tipos.d.ts +8 -2
  27. package/node_modules/@sema/nucleo/dist/formatador/index.js +65 -21
  28. package/node_modules/@sema/nucleo/dist/formatador/index.js.map +1 -1
  29. package/node_modules/@sema/nucleo/dist/ir/conversor.js +91 -5
  30. package/node_modules/@sema/nucleo/dist/ir/conversor.js.map +1 -1
  31. package/node_modules/@sema/nucleo/dist/ir/modelos.d.ts +74 -3
  32. package/node_modules/@sema/nucleo/dist/lexer/tokens.js +6 -0
  33. package/node_modules/@sema/nucleo/dist/lexer/tokens.js.map +1 -1
  34. package/node_modules/@sema/nucleo/dist/parser/parser.js +12 -2
  35. package/node_modules/@sema/nucleo/dist/parser/parser.js.map +1 -1
  36. package/node_modules/@sema/nucleo/dist/semantico/analisador.d.ts +2 -2
  37. package/node_modules/@sema/nucleo/dist/semantico/analisador.js +410 -11
  38. package/node_modules/@sema/nucleo/dist/semantico/analisador.js.map +1 -1
  39. package/node_modules/@sema/nucleo/dist/semantico/estruturas.d.ts +6 -1
  40. package/node_modules/@sema/nucleo/dist/semantico/estruturas.js +63 -13
  41. package/node_modules/@sema/nucleo/dist/semantico/estruturas.js.map +1 -1
  42. package/node_modules/@sema/nucleo/dist/semantico/seguranca.d.ts +91 -0
  43. package/node_modules/@sema/nucleo/dist/semantico/seguranca.js +258 -0
  44. package/node_modules/@sema/nucleo/dist/semantico/seguranca.js.map +1 -0
  45. package/node_modules/@sema/nucleo/package.json +1 -1
  46. package/node_modules/@sema/padroes/dist/index.js +18 -1
  47. package/node_modules/@sema/padroes/dist/index.js.map +1 -1
  48. package/node_modules/@sema/padroes/package.json +1 -1
  49. 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",
@@ -64,8 +100,6 @@ function normalizarOrigemImplementacao(valor) {
64
100
  return "py";
65
101
  case "dart":
66
102
  return "dart";
67
- case "lua":
68
- return "lua";
69
103
  case "cs":
70
104
  case "csharp":
71
105
  case "dotnet":
@@ -111,9 +145,15 @@ function validarCamposDeTipos(campos, tiposConhecidos, diagnosticos, contexto) {
111
145
  }
112
146
  }
113
147
  function localizarBloco(corpo, nome) {
114
- 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));
115
152
  }
116
153
  function localizarCampo(bloco, ...nomes) {
154
+ if (!bloco) {
155
+ return undefined;
156
+ }
117
157
  return bloco.campos.find((campo) => nomes.includes(campo.nome));
118
158
  }
119
159
  function valorCampoCompleto(campo) {
@@ -209,7 +249,7 @@ function validarImplementacoesTask(task, diagnosticos) {
209
249
  for (const campo of task.impl.campos) {
210
250
  const origem = normalizarOrigemImplementacao(campo.nome);
211
251
  if (!origem) {
212
- diagnosticos.push(criarDiagnostico("SEM059", `Task "${task.nome}" declarou implementacao externa invalida em impl: "${campo.nome}".`, "erro", campo.intervalo, "Use apenas ts, py, dart, lua, cs, java, go, rust ou cpp dentro do bloco impl."));
252
+ diagnosticos.push(criarDiagnostico("SEM059", `Task "${task.nome}" declarou implementacao externa invalida em impl: "${campo.nome}".`, "erro", campo.intervalo, "Use apenas ts, py, dart, cs, java, go, rust ou cpp dentro do bloco impl."));
213
253
  continue;
214
254
  }
215
255
  if (origens.has(origem)) {
@@ -232,23 +272,280 @@ function validarVinculos(bloco, diagnosticos, contexto) {
232
272
  }
233
273
  }
234
274
  }
235
- function validarExecucao(task, diagnosticos) {
236
- 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) {
237
327
  return;
238
328
  }
239
- for (const campo of task.execucao.campos) {
329
+ for (const campo of execucao.campos) {
240
330
  if (!CAMPOS_EXECUCAO_SUPORTADOS.has(campo.nome)) {
241
- 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."));
242
332
  continue;
243
333
  }
244
334
  if (campo.nome === "criticidade_operacional") {
245
335
  const criticidade = valorCampoCompleto(campo);
246
336
  if (criticidade && !CRITICIDADES_OPERACIONAIS.has(criticidade)) {
247
- 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."));
248
464
  }
249
465
  }
250
466
  }
251
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
+ }
252
549
  function validarErroOperacional(task, diagnosticos) {
253
550
  if (!task.error) {
254
551
  return;
@@ -291,6 +588,7 @@ function validarSuperficie(superficie, tipoSuperficie, tasksConhecidas, tiposCon
291
588
  const effects = localizarBloco(superficie, "effects");
292
589
  const impl = localizarBloco(superficie, "impl");
293
590
  const vinculos = localizarBloco(superficie, "vinculos");
591
+ const execucao = localizarBloco(superficie, "execucao");
294
592
  if (!task && !impl && !vinculos) {
295
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."));
296
594
  }
@@ -306,7 +604,13 @@ function validarSuperficie(superficie, tipoSuperficie, tasksConhecidas, tiposCon
306
604
  if (effects) {
307
605
  validarEfeitosDeclarados(effects.linhas, diagnosticos, `effects da superficie ${tipoSuperficie} ${nomeSuperficie}`);
308
606
  }
607
+ validarExecucaoBloco(execucao, diagnosticos, `superficie ${tipoSuperficie} "${nomeSuperficie}"`);
309
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
+ });
310
614
  }
311
615
  function recomporCaminhoRoute(campo) {
312
616
  if (!campo) {
@@ -389,15 +693,23 @@ function validarEfeitosDeclarados(linhas, diagnosticos, contexto) {
389
693
  for (const linha of linhas) {
390
694
  const efeito = parsearEfeitoSemantico(linha.conteudo);
391
695
  if (!efeito) {
392
- 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=...."));
393
697
  continue;
394
698
  }
395
699
  if (!ehCategoriaEfeitoSemantico(efeito.categoria)) {
396
- 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."));
397
701
  }
398
702
  if (efeito.criticidadeTexto && !ehCriticidadeEfeitoSemantico(efeito.criticidadeTexto)) {
399
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."));
400
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
+ }
401
713
  }
402
714
  }
403
715
  function validarState(state, tiposConhecidos, enumsConhecidos, diagnosticos) {
@@ -685,6 +997,11 @@ function validarRoute(route, tasksConhecidas, tarefasDetalhadas, diagnosticos) {
685
997
  validarEfeitosDeclarados(effects.linhas, diagnosticos, `effects da route ${route.nome}`);
686
998
  }
687
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
+ });
688
1005
  if (task && !tasksConhecidas.has(task.valor)) {
689
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."));
690
1007
  return;
@@ -693,6 +1010,82 @@ function validarRoute(route, tasksConhecidas, tarefasDetalhadas, diagnosticos) {
693
1010
  validarContratoRoute(route, task.valor, tarefasDetalhadas, diagnosticos);
694
1011
  }
695
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
+ }
696
1089
  export function criarContextoLocal(modulo) {
697
1090
  const simbolos = new Map();
698
1091
  const tiposConhecidos = new Set(TIPOS_PRIMITIVOS);
@@ -831,6 +1224,11 @@ function validarTask(task, tiposConhecidos, statesConhecidos, diagnosticos) {
831
1224
  }
832
1225
  validarVinculos(task.vinculos, diagnosticos, `task ${task.nome}`);
833
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
+ });
834
1232
  validarImplementacoesTask(task, diagnosticos);
835
1233
  if (task.tests) {
836
1234
  for (const bloco of task.tests.blocos) {
@@ -1062,6 +1460,7 @@ export function analisarSemantica(modulo, opcoes = {}) {
1062
1460
  for (const policy of modulo.policies) {
1063
1461
  validarSuperficie(policy, "policy", tasksConhecidas, tiposConhecidos, diagnosticos);
1064
1462
  }
1463
+ validarGuardrailsSeguranca(modulo, diagnosticos);
1065
1464
  const assinaturasRoute = new Map();
1066
1465
  for (const route of modulo.routes) {
1067
1466
  const metodo = (localizarCampo(route.corpo, "metodo")?.valor ?? "").toUpperCase();