@shiftleftpt/sbd-toe-mcp 0.20.0-beta.5 → 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.
@@ -80,7 +80,7 @@ export interface PrepareCodegenContextInput {
80
80
  export type PrepareCodegenStatus = "ready_for_codegen" | "needs_clarification" | "needs_decomposition" | "unsupported_scope";
81
81
  export interface ActivationTraceEntry {
82
82
  /** Triggered by: explicit concern, task term, changed file, framework hint, semantic alias, compound term, or intent keyword. */
83
- source: "explicit_concern" | "task_term" | "compound_term" | "alias_expansion" | "intent_keyword" | "changed_file" | "regulatory_framework" | "risk_level" | "scope_gate";
83
+ source: "explicit_concern" | "task_term" | "compound_term" | "alias_expansion" | "intent_keyword" | "changed_file" | "regulatory_framework" | "risk_level" | "exposure" | "data_sensitivity" | "context_chapter" | "scope_gate";
84
84
  /** What the activation produced (concern, slice_family, framework_id, decision). */
85
85
  produced: string;
86
86
  /** The literal token or input that triggered the activation. */
@@ -237,6 +237,22 @@ export interface CompletenessReport {
237
237
  evidence_patterns_capped: number;
238
238
  /** Cap value applied during this resolution. */
239
239
  evidence_pattern_cap: number;
240
+ /**
241
+ * MP1 selection summary (G-mp1a O2, 2026-08-31): the requirement set comes from
242
+ * the selection engine (baseline ∪ context-activated chapters, narrowed by the
243
+ * task's declared signals). Never-silent: what the narrowing excluded is counted
244
+ * here and fully listed by the executable ref. Additive key.
245
+ */
246
+ selection?: {
247
+ eligible: number;
248
+ selected: number;
249
+ narrowed_out_categories: number;
250
+ narrowed_out_requirements: number;
251
+ narrowed_out_ref: {
252
+ tool: "select_sbd_toe_requirements";
253
+ note: string;
254
+ };
255
+ };
240
256
  }
241
257
  export interface SecurityRationaleTemplate {
242
258
  task: string;
@@ -744,6 +760,44 @@ export interface PrepareCodegenContextResultReadyDieted {
744
760
  export type PrepareCodegenContextResult = PrepareCodegenContextResultReady | PrepareCodegenContextResultReadyDieted | PrepareCodegenContextResultBlocked;
745
761
  declare const VALID_CONCERNS: readonly ["auth", "logging", "validation", "api", "config", "integrity", "distribution", "ide", "requirements", "architecture", "iac", "encryption", "secrets", "build", "supply_chain", "testing", "threat_modeling", "monitoring", "release", "deployment", "integration", "agents"];
746
762
  export type Concern = (typeof VALID_CONCERNS)[number];
763
+ export interface NormalizedInput {
764
+ task: string;
765
+ taskTrimmed: string;
766
+ taskLower: string;
767
+ tokenCount: number;
768
+ mode: CodegenMode;
769
+ risk_level?: RiskLevel;
770
+ stack?: string;
771
+ exposure?: PrepareCodegenContextInput["exposure"];
772
+ data_sensitivity?: PrepareCodegenContextInput["data_sensitivity"];
773
+ concerns: Concern[];
774
+ unknownConcerns: string[];
775
+ changed_files: string[];
776
+ regulatory_frameworks: string[];
777
+ include_regulatory_overlay: boolean;
778
+ debug: boolean;
779
+ }
780
+ export declare function normalizeInput(raw: unknown): NormalizedInput;
781
+ export interface ActivationResult {
782
+ concerns: Concern[];
783
+ sliceFamilies: string[];
784
+ /** P3 do ciclo MP1 (2026-08-31): famílias contadas para o gate de decomposição —
785
+ * UM SINAL = UMA SUPERFÍCIE. Só o concern PRIMÁRIO de cada sinal (posição 0 do
786
+ * mapeamento do termo/frase; explícitos/intents/ficheiros contam por si) contribui
787
+ * a sua família; concerns de suporte (posições secundárias, ex.: mtls→secrets,
788
+ * message queue→logging) activam categorias mas não são superfícies novas.
789
+ * `sliceFamilies` (grounding) fica intocado. */
790
+ decompositionFamilies: string[];
791
+ trace: ActivationTraceEntry[];
792
+ rejected: ActivationTraceEntry[];
793
+ notes: string[];
794
+ /** Per-concern aggregated score (max over contributing trace entries). */
795
+ concernScores: Map<Concern, number>;
796
+ /** Per-slice-family aggregated score. */
797
+ sliceFamilyScores: Map<string, number>;
798
+ }
799
+ export declare function activate(input: NormalizedInput): ActivationResult;
800
+ export declare function categoriesForConcerns(concerns: Concern[]): Set<string>;
747
801
  /**
748
802
  * Conditions under which a conditional instruction slot is included inline at
749
803
  * `detail: "full"`. The dieted `codegen_instructions_ref.active_conditions`
@@ -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,