@shiftleftpt/sbd-toe-mcp 0.20.0-beta.4 → 0.20.0-beta.6

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 (46) hide show
  1. package/assets/agent-guide.md +33 -0
  2. package/consumed-bundle.json +8 -8
  3. package/data/entities/proportionality.json +2 -2
  4. package/data/entities/sdlc_integration.json +2 -2
  5. package/data/publish/indexes/canonical_chunks.jsonl +4903 -4903
  6. package/data/publish/indexes/publication_manifest.json +4 -4
  7. package/data/publish/ontology/sbdtoe-ontology.yaml +1699 -21
  8. package/data/publish/overlay/framework_overlay_index.json +121 -11
  9. package/data/publish/overlay/overlay_mappings.jsonl +935 -646
  10. package/data/publish/runtime/artifact_requirements.json +218 -311
  11. package/data/publish/runtime/artifacts.json +82 -55
  12. package/data/publish/runtime/controls.json +870 -311
  13. package/data/publish/runtime/deterministic_manifest.json +2 -2
  14. package/data/publish/runtime/evidence_patterns.json +162 -162
  15. package/data/publish/runtime/requirement_control_links.json +2271 -1708
  16. package/data/publish/runtime/v1/manual_threat_mitigation.jsonl +233 -233
  17. package/data/publish/runtime/v1/v1_manifest.json +6 -4
  18. package/data/reports/run_manifest.json +4 -4
  19. package/dist/index.js +66 -3
  20. package/dist/index.js.map +1 -1
  21. package/dist/serving/affordances.d.ts +1 -0
  22. package/dist/serving/affordances.js +12 -3
  23. package/dist/serving/affordances.js.map +1 -1
  24. package/dist/serving/selection.d.ts +56 -0
  25. package/dist/serving/selection.js +289 -0
  26. package/dist/serving/selection.js.map +1 -0
  27. package/dist/tools/consult-security-requirements.d.ts +7 -0
  28. package/dist/tools/consult-security-requirements.js +38 -0
  29. package/dist/tools/consult-security-requirements.js.map +1 -1
  30. package/dist/tools/generate-sbd-toe-skill.js +7 -2
  31. package/dist/tools/generate-sbd-toe-skill.js.map +1 -1
  32. package/dist/tools/get-threat-landscape.js +36 -13
  33. package/dist/tools/get-threat-landscape.js.map +1 -1
  34. package/dist/tools/map-review-scope.d.ts +6 -0
  35. package/dist/tools/map-review-scope.js +19 -0
  36. package/dist/tools/map-review-scope.js.map +1 -1
  37. package/dist/tools/ontology-loader.d.ts +8 -0
  38. package/dist/tools/ontology-loader.js +9 -0
  39. package/dist/tools/ontology-loader.js.map +1 -1
  40. package/dist/tools/prepare-codegen-context.d.ts +55 -1
  41. package/dist/tools/prepare-codegen-context.js +151 -28
  42. package/dist/tools/prepare-codegen-context.js.map +1 -1
  43. package/dist/tools/select-requirements.d.ts +53 -0
  44. package/dist/tools/select-requirements.js +99 -0
  45. package/dist/tools/select-requirements.js.map +1 -0
  46. package/package.json +3 -2
@@ -32,6 +32,7 @@ import { getRegulatoryOverlay, resolveRegulatoryFramework } from "./regulatory-o
32
32
  import { expandQueryWithAliases } from "../backend/semantic-index-gateway.js";
33
33
  import { requirementCategoryOf } from "../serving/requirement-id.js";
34
34
  import { prepareCodegenAffordances } from "../serving/affordances.js";
35
+ import { runSelectionWithActivation } from "../serving/selection.js";
35
36
  const EVIDENCE_PATTERN_CAP = 25;
36
37
  /**
37
38
  * v2 token diet, s3 — evidence-pattern cap applied at `detail: "standard" |
@@ -177,7 +178,7 @@ const TASK_TERM_TO_CONCERNS = [
177
178
  ["auth", ["auth"]],
178
179
  ["authentication", ["auth"]],
179
180
  ["authorization", ["auth"]],
180
- ["login", ["auth"]],
181
+ ["login", ["auth", "encryption"]],
181
182
  ["session", ["auth"]],
182
183
  ["jwt", ["auth"]],
183
184
  ["oauth", ["auth"]],
@@ -213,8 +214,8 @@ const TASK_TERM_TO_CONCERNS = [
213
214
  ["release", ["release"]],
214
215
  ["deploy", ["deployment", "release"]],
215
216
  ["rollback", ["release"]],
216
- ["terraform", ["iac", "deployment"]],
217
- ["ansible", ["iac", "deployment"]],
217
+ ["terraform", ["iac"]],
218
+ ["ansible", ["iac"]],
218
219
  ["kubernetes", ["deployment", "config"]],
219
220
  ["docker", ["deployment", "config"]],
220
221
  ["container", ["deployment"]],
@@ -242,6 +243,15 @@ const TASK_TERM_TO_CONCERNS = [
242
243
  ["rpc", ["integration", "api"]],
243
244
  ["webhook", ["integration", "api"]],
244
245
  ["queue", ["integration"]],
246
+ // pós-P2 2026-08-31: integração por mensageria exige registo de eventos críticos → logging.
247
+ ["message queue", ["integration", "logging"]],
248
+ // pós-P2 2026-08-31: mTLS = gestão de material criptográfico → secrets (CFG/ENC).
249
+ ["mtls", ["encryption", "integration", "secrets"]],
250
+ ["signature", ["integrity", "encryption"]],
251
+ ["signing", ["integrity"]],
252
+ ["image", ["deployment", "distribution"]],
253
+ ["spa", ["validation", "api"]],
254
+ ["frontend", ["validation"]],
245
255
  ["pubsub", ["integration"]],
246
256
  ["monitoring", ["monitoring", "logging"]],
247
257
  ["metric", ["monitoring"]],
@@ -275,7 +285,9 @@ const CONCERN_TO_V0_CATEGORIES_SUPPLEMENT = {
275
285
  threat_modeling: ["THR"],
276
286
  monitoring: ["LOG", "OPS"],
277
287
  release: ["DPL", "OPS"],
278
- deployment: ["DPL", "IAC", "CNT"],
288
+ // pós-P2 2026-08-31: deploy activa também a categoria base DST (cap. 02 — "Deploy
289
+ // apenas via pipeline validado" e afins); supplement do serving, loader inalterado.
290
+ deployment: ["DPL", "IAC", "CNT", "DST"],
279
291
  integration: ["API", "INT"],
280
292
  // `agents` → AGN comes from ontology.concernsMap (loader); nothing to supplement.
281
293
  agents: []
@@ -328,7 +340,17 @@ const CODEGEN_PT_ALIASES = [
328
340
  ["integração", ["integration"]],
329
341
  ["fronteira", ["boundary"]],
330
342
  ["arquitetura", ["architecture"]],
343
+ // R3 do ciclo MP1 (2026-08-31, crescimento por semântica do Manual — cap. 02
344
+ // categoria SES = sessões; nunca por caso do oráculo): sessão/sessões → session.
345
+ ["sessão", ["session"]],
346
+ ["sessões", ["session"]],
331
347
  ["chave de api", ["api key"]],
348
+ ["chave de cliente", ["api key"]],
349
+ ["chaves de cliente", ["api key"]],
350
+ ["mensageria", ["message queue"]],
351
+ ["fila de mensagens", ["message queue"]],
352
+ ["assinatura", ["signature"]],
353
+ ["imagem", ["image"]],
332
354
  ["variável de ambiente", ["environment variable"]]
333
355
  ];
334
356
  /**
@@ -350,6 +372,8 @@ const COMPOUND_TERM_TO_CONCERNS = [
350
372
  ["ci pipeline", ["build", "supply_chain"]],
351
373
  ["trust boundary", ["architecture"]],
352
374
  ["fronteira de confiança", ["architecture"]],
375
+ ["formulário de registo", ["auth", "validation"]],
376
+ ["registration form", ["auth", "validation"]],
353
377
  ["service to service", ["integration", "architecture"]],
354
378
  ["serviço a serviço", ["integration", "architecture"]],
355
379
  ["secret rotation", ["secrets"]],
@@ -390,7 +414,7 @@ function taskMatchesKeyword(taskLower, keyword) {
390
414
  }
391
415
  const VAGUE_PATTERNS = [
392
416
  {
393
- pattern: /\b(torna|make).{0,40}\b(seguro|secure)\b/i,
417
+ pattern: /\b(torna|make).{0,40}\b(segur[ao]|secure)\b/i,
394
418
  reason: "Pedido excessivamente abrangente ('make secure' / 'tornar seguro')"
395
419
  },
396
420
  {
@@ -424,7 +448,7 @@ const VAGUE_PATTERNS = [
424
448
  * these, so the request is unsupported rather than decomposable.
425
449
  */
426
450
  const UNSUPPORTED_TECH_PATTERN = /\b(homomorphic|quantum[- ]?(resistant|safe)?|post[- ]?quantum|blockchain|smart contract|zero[- ]?knowledge|zk[- ]?(snark|stark|proof)s?|secure multiparty|federated learning)\b/i;
427
- function normalizeInput(raw) {
451
+ export function normalizeInput(raw) {
428
452
  const data = (typeof raw === "object" && raw !== null ? raw : {});
429
453
  const task = typeof data.task === "string" ? data.task : "";
430
454
  const taskTrimmed = task.trim();
@@ -536,7 +560,7 @@ function recordActivation(trace, concerns, scores, rejected, entry, targetConcer
536
560
  trace.push(entry);
537
561
  }
538
562
  }
539
- function activate(input) {
563
+ export function activate(input) {
540
564
  const trace = [];
541
565
  const rejected = [];
542
566
  const notes = [];
@@ -670,6 +694,45 @@ function activate(input) {
670
694
  reason: `Concern '${concern}' maps to AppSec Core slice family '${family}'.`
671
695
  });
672
696
  }
697
+ // 4b) Declared context activators (G-mp1a / D3, 2026-08-31): exposure and
698
+ // data_sensitivity stop being decorative — they activate concerns by DECLARED
699
+ // rule (each with its own trace source), because the reference selection
700
+ // semantics says an authenticated/public surface must be auditable and a
701
+ // personal/regulated data context must carry crypto+masking+validation.
702
+ const EXPOSURE_CONCERNS = {
703
+ internal: ["auth", "logging"],
704
+ authenticated: ["auth", "logging"],
705
+ public: ["auth", "logging", "api", "validation", "architecture"]
706
+ };
707
+ if (input.exposure && EXPOSURE_CONCERNS[input.exposure]) {
708
+ for (const concern of EXPOSURE_CONCERNS[input.exposure] ?? []) {
709
+ recordActivation(trace, concerns, concernScores, rejected, {
710
+ source: "exposure",
711
+ produced: concern,
712
+ trigger: input.exposure,
713
+ score: 0.9,
714
+ confidence: "deterministic",
715
+ reason: `exposure='${input.exposure}' activates ${concern} by declared rule (auditable exposed surface).`
716
+ }, concern, { capDuplicates: true });
717
+ }
718
+ }
719
+ const SENSITIVITY_CONCERNS = {
720
+ personal: ["encryption", "validation", "logging"],
721
+ regulated: ["encryption", "validation", "logging"],
722
+ secrets: ["secrets"]
723
+ };
724
+ if (input.data_sensitivity && SENSITIVITY_CONCERNS[input.data_sensitivity]) {
725
+ for (const concern of SENSITIVITY_CONCERNS[input.data_sensitivity] ?? []) {
726
+ recordActivation(trace, concerns, concernScores, rejected, {
727
+ source: "data_sensitivity",
728
+ produced: concern,
729
+ trigger: input.data_sensitivity,
730
+ score: 0.9,
731
+ confidence: "deterministic",
732
+ reason: `data_sensitivity='${input.data_sensitivity}' activates ${concern} by declared rule (ENC/masking/validation for personal or regulated data).`
733
+ }, concern, { capDuplicates: true });
734
+ }
735
+ }
673
736
  // 5) Risk level (informational trace entry, no concern activation).
674
737
  if (input.risk_level) {
675
738
  trace.push({
@@ -681,8 +744,39 @@ function activate(input) {
681
744
  reason: `Risk level ${input.risk_level} filters runtime v0 requirements.`
682
745
  });
683
746
  }
747
+ // P3 (2026-08-31): primary-concern families for the decomposition gate.
748
+ const primaryOfSignal = new Map();
749
+ for (const [term, mapped] of TASK_TERM_TO_CONCERNS) {
750
+ if (mapped.length > 0)
751
+ primaryOfSignal.set(term, mapped[0]);
752
+ }
753
+ for (const [phrase, mapped] of COMPOUND_TERM_TO_CONCERNS) {
754
+ if (mapped.length > 0)
755
+ primaryOfSignal.set(phrase, mapped[0]);
756
+ }
757
+ const primaryConcerns = new Set();
758
+ for (const entry of trace) {
759
+ if (entry.source === "risk_level" ||
760
+ entry.source === "exposure" ||
761
+ entry.source === "data_sensitivity" ||
762
+ entry.source === "scope_gate") {
763
+ continue; // contexto/informativos — não são superfícies
764
+ }
765
+ if (!concerns.has(entry.produced))
766
+ continue;
767
+ const rowPrimary = primaryOfSignal.get(entry.trigger);
768
+ if (rowPrimary === undefined || rowPrimary === entry.produced) {
769
+ primaryConcerns.add(entry.produced);
770
+ }
771
+ }
772
+ const decompositionFamilies = [
773
+ ...new Set([...primaryConcerns]
774
+ .map((concern) => CONCERN_TO_SLICE_FAMILY[concern])
775
+ .filter((family) => typeof family === "string"))
776
+ ].sort();
684
777
  return {
685
778
  concerns: [...concerns],
779
+ decompositionFamilies,
686
780
  sliceFamilies: [...sliceFamilyScores.keys()].sort((a, b) => (sliceFamilyScores.get(b) ?? 0) - (sliceFamilyScores.get(a) ?? 0) ||
687
781
  a.localeCompare(b)),
688
782
  trace,
@@ -736,21 +830,24 @@ function gateAfterActivation(args) {
736
830
  const { input, activation, estimatedRequirements } = args;
737
831
  const reasons = [];
738
832
  const suggestions = [];
739
- if (activation.sliceFamilies.length > 3) {
740
- reasons.push(`Pedido activa ${activation.sliceFamilies.length} slice families (${activation.sliceFamilies.join(", ")})máximo recomendado: 3.`);
833
+ // P3 do ciclo MP1 (2026-08-31): o gate conta SUPERFÍCIES (famílias dos concerns
834
+ // primários de cada sinal), não o total de famílias activadas concerns de
835
+ // suporte de um mesmo sinal (mtls→secrets, mensageria→logging) não pedem
836
+ // decomposição. GC-10 é o caso de referência: 1 integração legítima.
837
+ if (activation.decompositionFamilies.length > 3) {
838
+ reasons.push(`Pedido activa ${activation.decompositionFamilies.length} superfícies (famílias primárias: ${activation.decompositionFamilies.join(", ")}) — máximo recomendado: 3. Concerns de suporte do mesmo sinal não contam.`);
741
839
  suggestions.push("Reparte por slice family. Cada PR/PR-step deve ficar em 1–3 slices.");
742
840
  }
743
- // Hard requirement cap: above ~50 requirements the LLM context becomes
744
- // unfocused and asks should be decomposed. Slice-family count (max 3) is the
745
- // primary decomposition signal; this cap catches multi-concern asks that
746
- // sneak under the slice-family threshold.
747
- if (estimatedRequirements > 50) {
748
- reasons.push(`Pedido activaria ${estimatedRequirements} requisitos v0 — máximo permitido para codegen: 50.`);
749
- suggestions.push("Reduz o âmbito (risk_level mais baixo, concerns mais específicos, ou divide o endpoint).");
750
- }
751
- if (activation.concerns.length === 0 &&
752
- input.tokenCount >= 4 &&
753
- activation.trace.length === 0) {
841
+ // G-mp1a decision 2 (2026-08-31, D1): the former hard cap "max 50 activated
842
+ // requirements" is GONE a legitimate L2 task activates >50 by design (the
843
+ // cap 02 baseline is a real catalogue). The gate guards TASK scope (vague /
844
+ // multi-family asks above) and PAYLOAD (the detail diet + budgets), never a
845
+ // requirement count. estimatedRequirements stays as a debug figure only.
846
+ void estimatedRequirements;
847
+ // D1 (G-mp1a): with the requirement-count cap gone, the no-signal guard is the
848
+ // vagueness catch-all. The informational risk_level trace entry must not defeat
849
+ // it — only real signals (concerns) count.
850
+ if (activation.concerns.length === 0 && input.tokenCount >= 4) {
754
851
  return {
755
852
  status: "needs_clarification",
756
853
  reasons: [
@@ -804,7 +901,7 @@ function projectRelation(relation) {
804
901
  source: "runtime_v1"
805
902
  };
806
903
  }
807
- function categoriesForConcerns(concerns) {
904
+ export function categoriesForConcerns(concerns) {
808
905
  const ontology = getOntologyData();
809
906
  const categories = new Set();
810
907
  for (const concern of concerns) {
@@ -820,12 +917,20 @@ function categoriesForConcerns(concerns) {
820
917
  function resolveRuntimeV0(args) {
821
918
  const ontology = getOntologyData();
822
919
  const concernCategories = categoriesForConcerns(args.concerns);
823
- let filteredRequirements = ontology.requirements;
824
- if (args.riskLevel) {
825
- filteredRequirements = filteredRequirements.filter((requirement) => requirement.applicable_levels?.[args.riskLevel] === true);
920
+ let filteredRequirements;
921
+ if (args.selectedRequirements) {
922
+ // MP1 engine (G-mp1a O2): the selection operation already produced the set
923
+ // (baseline ∪ context ⊕ narrowing, all declared) — use it verbatim.
924
+ filteredRequirements = args.selectedRequirements;
826
925
  }
827
- if (concernCategories.size > 0) {
828
- filteredRequirements = filteredRequirements.filter((requirement) => concernCategories.has(requirement.category));
926
+ else {
927
+ filteredRequirements = ontology.requirements;
928
+ if (args.riskLevel) {
929
+ filteredRequirements = filteredRequirements.filter((requirement) => requirement.applicable_levels?.[args.riskLevel] === true);
930
+ }
931
+ if (concernCategories.size > 0) {
932
+ filteredRequirements = filteredRequirements.filter((requirement) => concernCategories.has(requirement.category));
933
+ }
829
934
  }
830
935
  const links = ontology.requirementControlLinks ?? [];
831
936
  const requirementIds = new Set(filteredRequirements.map((r) => r.requirement_id));
@@ -2009,7 +2114,11 @@ function prepareCodegenContextCore(raw) {
2009
2114
  : deterministicConcerns.length > 0
2010
2115
  ? deterministicConcerns
2011
2116
  : activation.concerns;
2012
- const estimatedRequirements = estimateV0RequirementCount(input.risk_level, focusConcerns);
2117
+ void focusConcerns; // kept for the debug notes below; the gate no longer counts requirements
2118
+ // MP1 selection (G-mp1a O2): the engine composes baseline ∪ context and narrows
2119
+ // by the task's declared signals — this is the requirement set served.
2120
+ const selection = runSelectionWithActivation(input, activation);
2121
+ const estimatedRequirements = selection.selected.length;
2013
2122
  const postGate = gateAfterActivation({
2014
2123
  input,
2015
2124
  activation,
@@ -2041,9 +2150,12 @@ function prepareCodegenContextCore(raw) {
2041
2150
  ], activation.trace, { rejected: activation.rejected, notes: activation.notes });
2042
2151
  }
2043
2152
  // ----- Resolve activated scope ----------------------------------------
2153
+ const ontologyForSelection = getOntologyData();
2154
+ const selectedIds = new Set(selection.selected.map((r) => r.requirement_id));
2044
2155
  const v0 = resolveRuntimeV0({
2045
2156
  riskLevel: input.risk_level,
2046
- concerns: activation.concerns
2157
+ concerns: activation.concerns,
2158
+ selectedRequirements: ontologyForSelection.requirements.filter((r) => selectedIds.has(r.requirement_id))
2047
2159
  });
2048
2160
  const activatedSlices = resolveActivatedSlices(g2Data, activation.sliceFamilies);
2049
2161
  const activatedSliceIds = new Set(activatedSlices.map((slice) => slice.slice_id));
@@ -2252,6 +2364,17 @@ function prepareCodegenContextCore(raw) {
2252
2364
  returned_artifacts: activatedArtifacts.length,
2253
2365
  named_v1_entities: namedV1,
2254
2366
  unnamed_v1_entities: totalV1 - namedV1,
2367
+ selection: {
2368
+ eligible: selection.eligible_count,
2369
+ selected: selection.selected.length,
2370
+ narrowed_out_categories: selection.narrowed_out.length,
2371
+ narrowed_out_requirements: selection.narrowed_out.reduce((n, g) => n + g.count, 0),
2372
+ narrowed_out_ref: {
2373
+ tool: "select_sbd_toe_requirements",
2374
+ note: "Categorias elegíveis sem sinal na tarefa foram excluídas pelo narrowing MP1 — " +
2375
+ "a lista completa (por categoria, com razão) vem de select_sbd_toe_requirements com o mesmo contexto."
2376
+ }
2377
+ },
2255
2378
  v1_consistency_mismatches: g2Data.consistency.mismatches,
2256
2379
  v1_manifest_warnings: g2Data.consistency.warnings,
2257
2380
  evidence_patterns_total: scoredEvidencePatterns.length,