@shiftleftpt/sbd-toe-mcp 0.10.1 → 0.20.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +113 -1
- package/dist/index.js.map +1 -1
- package/dist/serving/rdf/graph-store.d.ts +25 -0
- package/dist/serving/rdf/graph-store.js +56 -0
- package/dist/serving/rdf/graph-store.js.map +1 -0
- package/dist/serving/rdf/projection.d.ts +21 -0
- package/dist/serving/rdf/projection.js +76 -0
- package/dist/serving/rdf/projection.js.map +1 -0
- package/dist/tools/prepare-codegen-context.d.ts +565 -1
- package/dist/tools/prepare-codegen-context.js +1012 -50
- package/dist/tools/prepare-codegen-context.js.map +1 -1
- package/dist/tools/trace-graph.d.ts +17 -0
- package/dist/tools/trace-graph.js +84 -0
- package/dist/tools/trace-graph.js.map +1 -0
- package/package.json +3 -1
- package/prompts/sbd-toe-grounded-codegen.md +10 -0
|
@@ -32,6 +32,108 @@ import { getRegulatoryOverlay, resolveRegulatoryFramework } from "./regulatory-o
|
|
|
32
32
|
import { expandQueryWithAliases } from "../backend/semantic-index-gateway.js";
|
|
33
33
|
import { prepareCodegenAffordances } from "../serving/affordances.js";
|
|
34
34
|
const EVIDENCE_PATTERN_CAP = 25;
|
|
35
|
+
/**
|
|
36
|
+
* v2 token diet, s3 — evidence-pattern cap applied at `detail: "standard" |
|
|
37
|
+
* "minimal"` ON TOP of the classic cap: the dieted list is the deterministic
|
|
38
|
+
* PREFIX (relevance_score desc, id asc — the exact order the core already
|
|
39
|
+
* emits) of the classic top-{@link EVIDENCE_PATTERN_CAP} list. Never silent:
|
|
40
|
+
* `completeness_report` reports total/returned/capped and, when anything was
|
|
41
|
+
* cut, `evidence_patterns_rest` says how to retrieve the rest (same tool,
|
|
42
|
+
* `detail: "full"`).
|
|
43
|
+
*/
|
|
44
|
+
const STANDARD_EVIDENCE_PATTERN_CAP = 10;
|
|
45
|
+
/**
|
|
46
|
+
* v2 token diet, s3b (revised per the 2026-07-05 operator ADENDA in
|
|
47
|
+
* agentic/planeado/v2-token-diet/EPIC.md — no top-N, no subsetting of the
|
|
48
|
+
* activated set): at `detail: "minimal"` the evidence cap tightens 10→5 with
|
|
49
|
+
* the SAME s3 mechanism (deterministic prefix, never-silent counts +
|
|
50
|
+
* executable rest-reference). This cap — together with the minimal
|
|
51
|
+
* `manual_grounding` form — is the ONLY divergence from `standard`; the
|
|
52
|
+
* activated scope (requirements/controls/slices/entities, with the verbatim
|
|
53
|
+
* published descriptions) stays COMPLETE and byte-identical to `standard`.
|
|
54
|
+
*/
|
|
55
|
+
const MINIMAL_EVIDENCE_PATTERN_CAP = 5;
|
|
56
|
+
/**
|
|
57
|
+
* v2 token diet, s3c (operator ADENDA 2026-07-05, reactivated same day): at
|
|
58
|
+
* `detail: "ultrathin"` NO evidence pattern goes inline (cap 0) — the SAME s3
|
|
59
|
+
* never-silent mechanism still applies in full: `completeness_report` reports
|
|
60
|
+
* total/returned(=0)/capped(=total) and `evidence_patterns_rest` is the
|
|
61
|
+
* executable reference to the CHEAPEST level that returns them inline
|
|
62
|
+
* (detail="minimal" ⇒ top-5; "standard" ⇒ 10; "full" ⇒ classic 25).
|
|
63
|
+
*/
|
|
64
|
+
const ULTRATHIN_EVIDENCE_PATTERN_CAP = 0;
|
|
65
|
+
/**
|
|
66
|
+
* Section → source map for the dieted encoding: each list is
|
|
67
|
+
* source-homogeneous BY CONSTRUCTION (the projection types hardcode a single
|
|
68
|
+
* source literal per list), so one entry per list reconstitutes the `source`
|
|
69
|
+
* of every item with no exceptions. Published as part of the
|
|
70
|
+
* `sbd://toe/codegen-instructions/{mode}` resource (s3 moved the verbose
|
|
71
|
+
* legend out of every payload); the inline `provenance_legend` keeps a
|
|
72
|
+
* one-line pointer.
|
|
73
|
+
*/
|
|
74
|
+
const PROVENANCE_SOURCES = {
|
|
75
|
+
"activated_scope.requirements": "runtime_v0",
|
|
76
|
+
"activated_scope.controls": "runtime_v0",
|
|
77
|
+
"activated_scope.slices": "runtime_v1",
|
|
78
|
+
"activated_scope.regulatory_obligations": "overlay",
|
|
79
|
+
"g2_context.control_objectives": "runtime_v1",
|
|
80
|
+
"g2_context.mechanisms": "runtime_v1",
|
|
81
|
+
"g2_context.practices": "runtime_v1",
|
|
82
|
+
"g2_context.artifacts": "runtime_v1",
|
|
83
|
+
"g2_context.relations": "runtime_v1",
|
|
84
|
+
"g2_context.evidence_patterns": "runtime_v0",
|
|
85
|
+
"manual_grounding.groups": "runtime_v1",
|
|
86
|
+
"regulatory_overlay.frameworks": "overlay",
|
|
87
|
+
"regulatory_overlay.obligations": "overlay",
|
|
88
|
+
"regulatory_overlay.mappings": "overlay",
|
|
89
|
+
"regulatory_overlay.playbooks": "overlay"
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* Inline legend for `standard`/`minimal` (s3: slim pointer — the full legend,
|
|
93
|
+
* including the section→source table and every derivation rule of the dieted
|
|
94
|
+
* encoding, lives in the `sbd://toe/codegen-instructions/{mode}` resource,
|
|
95
|
+
* section `detail_encoding`).
|
|
96
|
+
*/
|
|
97
|
+
const PROVENANCE_LEGEND = {
|
|
98
|
+
note: "Deduplicated encoding (detail=standard/minimal): per-item `source` fields " +
|
|
99
|
+
"are elided (every list is source-homogeneous), requirement `category` = " +
|
|
100
|
+
"requirement_id prefix, g2_context entity lists are grouped as " +
|
|
101
|
+
"{slice_id: {entity_id: name|null}}, and citations ids are referenced via " +
|
|
102
|
+
"ids_from payload paths. Full legend: MCP resource " +
|
|
103
|
+
"sbd://toe/codegen-instructions/{mode}, section detail_encoding."
|
|
104
|
+
};
|
|
105
|
+
/**
|
|
106
|
+
* Inline legend for `ultrathin` (s3c) — the standard/minimal legend text is
|
|
107
|
+
* NEVER edited (snapshots are byte-frozen); ultrathin carries its own note
|
|
108
|
+
* with the extra cut rules. Full legend: same MCP resource, section
|
|
109
|
+
* `detail_encoding` (incl. the `ultrathin` entry).
|
|
110
|
+
*/
|
|
111
|
+
const PROVENANCE_LEGEND_ULTRATHIN = {
|
|
112
|
+
note: "Deduplicated encoding (detail=ultrathin): same rules as detail=standard/" +
|
|
113
|
+
"minimal — per-item `source` elided, requirement `category` = " +
|
|
114
|
+
"requirement_id prefix, g2_context entity lists grouped as " +
|
|
115
|
+
"{slice_id: {entity_id: name|null}}, citations ids via ids_from payload " +
|
|
116
|
+
"paths — PLUS: published `description` fields elided (executable " +
|
|
117
|
+
"activated_scope.descriptions_ref, detail='minimal'), evidence_patterns 0 " +
|
|
118
|
+
"inline (counts + rest-ref in completeness_report), manual_grounding " +
|
|
119
|
+
"aggregate-only (total + sha + groups_ref) and completeness diagnostics " +
|
|
120
|
+
"as exact counts (+ ref). Nothing silently dropped. Full legend: MCP " +
|
|
121
|
+
"resource sbd://toe/codegen-instructions/{mode}, section detail_encoding."
|
|
122
|
+
};
|
|
123
|
+
/**
|
|
124
|
+
* v2 token diet, s4 — cheap turns, not fewer turns: short note (≈50 tokens)
|
|
125
|
+
* appended to every `standard`/`minimal` ready payload. The production
|
|
126
|
+
* write-test-edit loop is legitimate; what must not repeat is the cost of
|
|
127
|
+
* re-requesting THIS payload — an identical call returns a byte-identical
|
|
128
|
+
* result (deterministic, tested), so the context already in the session is
|
|
129
|
+
* the source for the loop. Follow-ups that genuinely need more go through
|
|
130
|
+
* `detail: "minimal"` or a targeted `consult_security_requirements` call —
|
|
131
|
+
* never a repeat of the full payload. `full` carries NO hint (byte-identical
|
|
132
|
+
* to the classic payload, EPIC invariant 1).
|
|
133
|
+
*/
|
|
134
|
+
export const REPEAT_CALL_HINT = "Identical input returns this exact payload (deterministic) — reuse the " +
|
|
135
|
+
"context already received instead of re-calling; deepen via " +
|
|
136
|
+
"detail:'minimal' or a targeted consult_security_requirements.";
|
|
35
137
|
// ---------------------------------------------------------------------------
|
|
36
138
|
// Activation lexicon (small, auditable; WP6 layers semantic scoring on top)
|
|
37
139
|
// ---------------------------------------------------------------------------
|
|
@@ -926,32 +1028,236 @@ function resolveOverlay(input) {
|
|
|
926
1028
|
activatedFrameworks
|
|
927
1029
|
};
|
|
928
1030
|
}
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
1031
|
+
/**
|
|
1032
|
+
* Ordered instruction slots for a mode. The emission order of
|
|
1033
|
+
* {@link buildLlmInstructions} is EXACTLY this list filtered by active
|
|
1034
|
+
* conditions — the classic (pre-s3) output is byte-identical by construction.
|
|
1035
|
+
*/
|
|
1036
|
+
export function instructionSlotsForMode(mode) {
|
|
1037
|
+
const slots = [
|
|
1038
|
+
{
|
|
1039
|
+
when: "always",
|
|
1040
|
+
text: "Generate code or review changes ONLY against the deterministic IDs provided in `citation_map`. Do NOT invent SbD-ToE requirement, control, slice, mechanism or obligation IDs."
|
|
1041
|
+
},
|
|
1042
|
+
{
|
|
1043
|
+
when: "always",
|
|
1044
|
+
text: "For each non-trivial design decision, populate the `security_rationale_template.decisions[].cited_ids` with IDs from `citation_map`. If no ID applies, say so explicitly."
|
|
1045
|
+
},
|
|
1046
|
+
{
|
|
1047
|
+
when: "always",
|
|
1048
|
+
text: "List concrete validations in `security_rationale_template.validations` (surface, rule, rejection behaviour). Do NOT claim conformity without naming the validation."
|
|
1049
|
+
},
|
|
1050
|
+
{
|
|
1051
|
+
when: "always",
|
|
1052
|
+
text: "List expected evidence in `security_rationale_template.expected_evidence` (test paths, log shapes, SBOM, attestation, scan reports). Code on its own is NOT evidence of compliance."
|
|
1053
|
+
},
|
|
1054
|
+
{
|
|
1055
|
+
when: "regulatory_overlay",
|
|
1056
|
+
text: "Regulatory obligations are an EXTERNAL cross-check. Cite obligation IDs in security_rationale only when the change directly addresses them. Do NOT declare GDPR/DORA/CRA/NIS2 compliance."
|
|
1057
|
+
},
|
|
1058
|
+
{
|
|
1059
|
+
when: "always",
|
|
1060
|
+
text: "If the requested task does not match the activated scope, REPLY with `status: needs_clarification` and request specifics — do not fabricate IDs."
|
|
1061
|
+
}
|
|
1062
|
+
];
|
|
1063
|
+
if (mode === "review") {
|
|
1064
|
+
slots.push({
|
|
1065
|
+
when: "always",
|
|
1066
|
+
text: "Review mode: enumerate findings per changed_file, mapped to the activated_scope. Each finding must reference at least one citation_map ID or say 'no normative ID covers this'."
|
|
1067
|
+
});
|
|
947
1068
|
}
|
|
948
|
-
if (
|
|
949
|
-
|
|
1069
|
+
if (mode === "test-plan") {
|
|
1070
|
+
slots.push({
|
|
1071
|
+
when: "always",
|
|
1072
|
+
text: "Test-plan mode: produce a checklist of tests grouped by validated_id, with input/expectation, and reference evidence_patterns when available."
|
|
1073
|
+
});
|
|
950
1074
|
}
|
|
951
|
-
|
|
952
|
-
|
|
1075
|
+
for (const level of ["L1", "L2", "L3"]) {
|
|
1076
|
+
slots.push({
|
|
1077
|
+
when: `risk_level:${level}`,
|
|
1078
|
+
text: `Risk level ${level} is the active filter — do not propose controls applicable only at a higher level unless explicitly justified.`
|
|
1079
|
+
});
|
|
953
1080
|
}
|
|
954
|
-
|
|
1081
|
+
slots.push({
|
|
1082
|
+
when: "citation_map_empty",
|
|
1083
|
+
text: "Citation_map is empty. This is a strong signal the activated scope did not yield deterministic anchors — request clarification before generating code."
|
|
1084
|
+
});
|
|
1085
|
+
return slots;
|
|
1086
|
+
}
|
|
1087
|
+
/** Conditional slots active for a call (deterministic, from resolved inputs). */
|
|
1088
|
+
function activeInstructionConditions(args) {
|
|
1089
|
+
const active = [];
|
|
1090
|
+
if (args.hasOverlay)
|
|
1091
|
+
active.push("regulatory_overlay");
|
|
1092
|
+
if (args.riskLevel)
|
|
1093
|
+
active.push(`risk_level:${args.riskLevel}`);
|
|
1094
|
+
if (args.citationMapEmpty)
|
|
1095
|
+
active.push("citation_map_empty");
|
|
1096
|
+
return active;
|
|
1097
|
+
}
|
|
1098
|
+
function buildLlmInstructions(args) {
|
|
1099
|
+
const active = new Set(activeInstructionConditions({
|
|
1100
|
+
hasOverlay: args.hasOverlay,
|
|
1101
|
+
riskLevel: args.riskLevel,
|
|
1102
|
+
citationMapEmpty: args.citedIds.length === 0
|
|
1103
|
+
}));
|
|
1104
|
+
return instructionSlotsForMode(args.mode)
|
|
1105
|
+
.filter((slot) => slot.when === "always" || active.has(slot.when))
|
|
1106
|
+
.map((slot) => slot.text);
|
|
1107
|
+
}
|
|
1108
|
+
/** Constant part of the security_rationale_template (everything except `task`). */
|
|
1109
|
+
const SECURITY_RATIONALE_TEMPLATE_SKELETON = {
|
|
1110
|
+
decisions: [
|
|
1111
|
+
{
|
|
1112
|
+
decision: "<fill: what design choice was made>",
|
|
1113
|
+
rationale: "<fill: why, citing IDs from citation_map>",
|
|
1114
|
+
cited_ids: ["<requirement_id|control_id|slice_id|obligation_id>"]
|
|
1115
|
+
}
|
|
1116
|
+
],
|
|
1117
|
+
validations: [
|
|
1118
|
+
{
|
|
1119
|
+
surface: "<fill: code path being validated>",
|
|
1120
|
+
rule: "<fill: validation rule>",
|
|
1121
|
+
rejection_behaviour: "<fill: how invalid input is rejected>"
|
|
1122
|
+
}
|
|
1123
|
+
],
|
|
1124
|
+
expected_evidence: [
|
|
1125
|
+
{
|
|
1126
|
+
artefact: "<fill: test, log, doc, sbom, scan, attestation, ...>",
|
|
1127
|
+
location: "<fill: where to find it>",
|
|
1128
|
+
verifies: "<fill: which control/requirement id>"
|
|
1129
|
+
}
|
|
1130
|
+
],
|
|
1131
|
+
residual_risk: "<fill: anything NOT addressed by this change>"
|
|
1132
|
+
};
|
|
1133
|
+
function buildSecurityRationaleTemplate(task) {
|
|
1134
|
+
return { task, ...SECURITY_RATIONALE_TEMPLATE_SKELETON };
|
|
1135
|
+
}
|
|
1136
|
+
// ---------------------------------------------------------------------------
|
|
1137
|
+
// MCP resource: sbd://toe/codegen-instructions/{mode} (v2 token diet, s3)
|
|
1138
|
+
// ---------------------------------------------------------------------------
|
|
1139
|
+
export const CODEGEN_INSTRUCTION_MODES = [
|
|
1140
|
+
"codegen",
|
|
1141
|
+
"review",
|
|
1142
|
+
"test-plan"
|
|
1143
|
+
];
|
|
1144
|
+
export const CODEGEN_INSTRUCTIONS_RESOURCE_URI_PREFIX = "sbd://toe/codegen-instructions/";
|
|
1145
|
+
export function codegenInstructionsResourceUri(mode) {
|
|
1146
|
+
return `${CODEGEN_INSTRUCTIONS_RESOURCE_URI_PREFIX}${mode}`;
|
|
1147
|
+
}
|
|
1148
|
+
/**
|
|
1149
|
+
* Full legend of the dieted (`standard`/`minimal`) encoding, published in the
|
|
1150
|
+
* codegen-instructions resource. Every rule here is a lossless, deterministic
|
|
1151
|
+
* derivation over the SAME payload (or an executable reference) — nothing is
|
|
1152
|
+
* silently dropped (EPIC invariant 2) and no data changes, only serialization
|
|
1153
|
+
* (EPIC invariant 4).
|
|
1154
|
+
*/
|
|
1155
|
+
const DETAIL_ENCODING_LEGEND = {
|
|
1156
|
+
note: "How to read a detail=standard/minimal payload of prepare_sbd_toe_codegen_context. " +
|
|
1157
|
+
"Every rule below is a deterministic re-encoding of the same published data: " +
|
|
1158
|
+
"nothing is silently dropped, and detail=full always returns the classic inline payload.",
|
|
1159
|
+
sources: {
|
|
1160
|
+
note: "Per-item `source` fields are elided. Every list below is source-homogeneous " +
|
|
1161
|
+
"(no exceptions): apply the listed source to each of its items. " +
|
|
1162
|
+
"g2_context.relations applies only when relations come inline " +
|
|
1163
|
+
"(include_relations=true); otherwise g2_context.relations_ref is a derived " +
|
|
1164
|
+
"reference to trace_sbd_toe_graph calls, not a source list.",
|
|
1165
|
+
map: PROVENANCE_SOURCES
|
|
1166
|
+
},
|
|
1167
|
+
citations: "citations.<source>.source_data is an ordered run-length map file -> count. " +
|
|
1168
|
+
"The citable ids are NOT repeated: citations.<source>.ids_from is aligned 1:1 " +
|
|
1169
|
+
"with the source_data files, and names the payload path whose ids (in payload " +
|
|
1170
|
+
"order) form that file's run. Paths of the form " +
|
|
1171
|
+
"keys(g2_context.<list>[slice]) iterate the slice groups in order, then the " +
|
|
1172
|
+
"entity-id keys in order. If a file ever has no path mapping, the group " +
|
|
1173
|
+
"carries explicit `ids` instead (lossless fallback).",
|
|
1174
|
+
activated_scope_requirements: "requirement `category` is elided because it equals the requirement_id prefix " +
|
|
1175
|
+
"before the first '-' (verbatim bundle invariant; the field survives inline " +
|
|
1176
|
+
"on any future mismatch). `description` is the verbatim published field from " +
|
|
1177
|
+
"data/publish/runtime/requirements.json — never paraphrased.",
|
|
1178
|
+
activated_scope_controls: "controls with confidence='direct' carry the verbatim published `description` " +
|
|
1179
|
+
"from data/publish/runtime/controls.json.",
|
|
1180
|
+
g2_entities: "g2_context.control_objectives/mechanisms/practices/artifacts are grouped as " +
|
|
1181
|
+
"{slice_id: {entity_id: name|null}}. entity_type is the list the map lives in " +
|
|
1182
|
+
"(ControlObjective/Mechanism/Practice/Artifact), slice_id is the group key, " +
|
|
1183
|
+
"slice_family is activated_scope.slices[].objective_family for that slice_id, " +
|
|
1184
|
+
"and a null name means the entity is unnamed in the published rastreabilidade " +
|
|
1185
|
+
"(the full projection omits `name` for it).",
|
|
1186
|
+
evidence_patterns: "g2_context.evidence_patterns is the deterministic prefix (relevance desc, " +
|
|
1187
|
+
"then id asc; the tool-computed relevance_score is elided — the order carries " +
|
|
1188
|
+
"the ranking) of the classic detail=full list, capped per " +
|
|
1189
|
+
"completeness_report.evidence_pattern_cap. completeness_report reports " +
|
|
1190
|
+
"total/returned/capped and, when anything was cut, evidence_patterns_rest " +
|
|
1191
|
+
"says how to retrieve the rest (same input, detail='full'; with debug=true " +
|
|
1192
|
+
"the ids beyond the classic cap are listed in debug.rejected_candidates).",
|
|
1193
|
+
manual_grounding_minimal: "At detail=minimal, manual_grounding is the aggregated-provenance form " +
|
|
1194
|
+
"(s3b): total_entries, the manual_commit_sha shared by every group " +
|
|
1195
|
+
"(hoisted; a group keeps its own sha inline only if hoisting was not " +
|
|
1196
|
+
"possible), and the (rastreabilidade_role, manual_chapter, manual_file) " +
|
|
1197
|
+
"groups with the exact per-group `entries` COUNT instead of the " +
|
|
1198
|
+
"v1_entity_ids list (counts sum to total_entries — never silent). The " +
|
|
1199
|
+
"grounding id set is already in the same payload (g2_context entity-map " +
|
|
1200
|
+
"keys); manual_grounding.groups_ref is the executable reference (same " +
|
|
1201
|
+
"input, detail='standard') for the per-group id lists — groups align " +
|
|
1202
|
+
"1:1, same order. detail=standard keeps the full grouping inline.",
|
|
1203
|
+
activation_trace: "activation_trace is elided at detail=standard/minimal; " +
|
|
1204
|
+
"activation_trace_ref.entries keeps the exact count. Re-call with debug=true " +
|
|
1205
|
+
"to include the full trace (it is always inline at detail=full).",
|
|
1206
|
+
relations_ref: "Inline g2_context.relations are elided at detail=standard/minimal (re-call " +
|
|
1207
|
+
"with include_relations=true to restore them). Recover the elided graph edges " +
|
|
1208
|
+
"by executing trace_sbd_toe_graph with each {lens, anchor} pair listed " +
|
|
1209
|
+
"(anchors are activated slice/entity ids from the same payload); the " +
|
|
1210
|
+
"belongsToSlice edges counted as coverage.implicit_in_entities are already " +
|
|
1211
|
+
"encoded by the slice grouping key of every g2_context entity; any relation " +
|
|
1212
|
+
"covered by neither stays inline in residual_relations (never silent).",
|
|
1213
|
+
ultrathin: "detail=ultrathin (s3c) applies every rule above PLUS: (1) requirements " +
|
|
1214
|
+
"{requirement_id, name, type} and controls {control_id, name, domain, " +
|
|
1215
|
+
"control_type, confidence} keep the COMPLETE activated set (same ids, " +
|
|
1216
|
+
"same order, name always present) but elide the published `description` " +
|
|
1217
|
+
"— activated_scope.descriptions_ref is the executable reference (same " +
|
|
1218
|
+
"input, detail='minimal') for the verbatim descriptions; (2) " +
|
|
1219
|
+
"g2_context.evidence_patterns is empty (cap 0) — completeness_report " +
|
|
1220
|
+
"keeps total/returned=0/capped=total and evidence_patterns_rest points " +
|
|
1221
|
+
"to detail='minimal' (cheapest level returning patterns inline; " +
|
|
1222
|
+
"'standard' returns 10, 'full' the classic 25); (3) manual_grounding is " +
|
|
1223
|
+
"{total_entries, manual_commit_sha, groups_ref} — the group list is " +
|
|
1224
|
+
"elided (detail='standard' returns the full 1:1 grouping); (4) " +
|
|
1225
|
+
"v1_consistency_mismatches/v1_manifest_warnings text arrays are replaced " +
|
|
1226
|
+
"by exact *_count fields (+ v1_diagnostics_ref when any count > 0, " +
|
|
1227
|
+
"detail='minimal' returns the texts). The citable id set and the " +
|
|
1228
|
+
"citations/ids_from encoding are IDENTICAL to the other dieted levels."
|
|
1229
|
+
};
|
|
1230
|
+
/**
|
|
1231
|
+
* Content of the `sbd://toe/codegen-instructions/{mode}` MCP resource — the
|
|
1232
|
+
* static-per-mode boilerplate that detail=standard/minimal payloads reference
|
|
1233
|
+
* instead of carrying inline. Reconstructing the inline `full` content from
|
|
1234
|
+
* this resource is byte-exact (tested):
|
|
1235
|
+
* - llm_codegen_instructions = slots filtered by `when` ("always" +
|
|
1236
|
+
* codegen_instructions_ref.active_conditions), in order;
|
|
1237
|
+
* - security_rationale_template = template with `task` set to the trimmed
|
|
1238
|
+
* task string (input_echo.task.trim()).
|
|
1239
|
+
*/
|
|
1240
|
+
export function buildCodegenInstructionsResourceContent(mode) {
|
|
1241
|
+
return {
|
|
1242
|
+
resource: codegenInstructionsResourceUri(mode),
|
|
1243
|
+
mode,
|
|
1244
|
+
note: "Static per-mode boilerplate for prepare_sbd_toe_codegen_context at " +
|
|
1245
|
+
"detail=standard/minimal (kept inline at detail=full). Also carries the " +
|
|
1246
|
+
"detail_encoding legend for the dieted payload.",
|
|
1247
|
+
llm_codegen_instructions: {
|
|
1248
|
+
assembly: "Include each slot whose `when` is 'always' or appears in this call's " +
|
|
1249
|
+
"codegen_instructions_ref.active_conditions, in the listed order — the " +
|
|
1250
|
+
"result is byte-identical to the detail=full inline llm_codegen_instructions.",
|
|
1251
|
+
slots: instructionSlotsForMode(mode)
|
|
1252
|
+
},
|
|
1253
|
+
security_rationale_template: {
|
|
1254
|
+
assembly: "Set `task` to the trimmed task string (input_echo.task.trim()); every " +
|
|
1255
|
+
"other field is verbatim — the result is byte-identical to the " +
|
|
1256
|
+
"detail=full inline security_rationale_template.",
|
|
1257
|
+
template: { task: null, ...SECURITY_RATIONALE_TEMPLATE_SKELETON }
|
|
1258
|
+
},
|
|
1259
|
+
detail_encoding: DETAIL_ENCODING_LEGEND
|
|
1260
|
+
};
|
|
955
1261
|
}
|
|
956
1262
|
// ---------------------------------------------------------------------------
|
|
957
1263
|
// Main entry point
|
|
@@ -976,10 +1282,690 @@ function blocked(input, raw, status, reasons, suggestions, partial, debug) {
|
|
|
976
1282
|
}
|
|
977
1283
|
return result;
|
|
978
1284
|
}
|
|
1285
|
+
const DETAIL_LEVELS = new Set([
|
|
1286
|
+
"ultrathin",
|
|
1287
|
+
"minimal",
|
|
1288
|
+
"standard",
|
|
1289
|
+
"full"
|
|
1290
|
+
]);
|
|
1291
|
+
/**
|
|
1292
|
+
* Validate the `detail` input (v2 token diet, s1). Invalid values fail fast
|
|
1293
|
+
* with a JSON-RPC -32602 (same pattern as trace-graph's lens validation);
|
|
1294
|
+
* omission defaults to `full` — the classic, byte-identical payload.
|
|
1295
|
+
*/
|
|
1296
|
+
function parseDetail(raw) {
|
|
1297
|
+
const value = typeof raw === "object" && raw !== null
|
|
1298
|
+
? raw.detail
|
|
1299
|
+
: undefined;
|
|
1300
|
+
if (value === undefined)
|
|
1301
|
+
return "full";
|
|
1302
|
+
if (typeof value === "string" && DETAIL_LEVELS.has(value)) {
|
|
1303
|
+
return value;
|
|
1304
|
+
}
|
|
1305
|
+
throw Object.assign(new Error(`Invalid "detail": ${JSON.stringify(value)}. Use one of: ultrathin, minimal, standard, full.`), {
|
|
1306
|
+
rpcError: {
|
|
1307
|
+
code: -32602,
|
|
1308
|
+
message: 'Invalid "detail". Use one of: ultrathin, minimal, standard, full.'
|
|
1309
|
+
}
|
|
1310
|
+
});
|
|
1311
|
+
}
|
|
1312
|
+
/**
|
|
1313
|
+
* Validate the `include_relations` input (v2 token diet, s2). Only booleans
|
|
1314
|
+
* (or omission = false) are accepted — same fail-fast pattern as parseDetail.
|
|
1315
|
+
*/
|
|
1316
|
+
function parseIncludeRelations(raw) {
|
|
1317
|
+
const value = typeof raw === "object" && raw !== null
|
|
1318
|
+
? raw.include_relations
|
|
1319
|
+
: undefined;
|
|
1320
|
+
if (value === undefined)
|
|
1321
|
+
return false;
|
|
1322
|
+
if (typeof value === "boolean")
|
|
1323
|
+
return value;
|
|
1324
|
+
throw Object.assign(new Error(`Invalid "include_relations": ${JSON.stringify(value)}. Use a boolean.`), {
|
|
1325
|
+
rpcError: {
|
|
1326
|
+
code: -32602,
|
|
1327
|
+
message: 'Invalid "include_relations". Use a boolean.'
|
|
1328
|
+
}
|
|
1329
|
+
});
|
|
1330
|
+
}
|
|
1331
|
+
// Relation predicates published in data/publish/runtime/v1/relations.jsonl —
|
|
1332
|
+
// the same three the RDF projection exposes to trace_sbd_toe_graph lenses.
|
|
1333
|
+
const PRED_BELONGS_TO_SLICE = "belongsToSlice";
|
|
1334
|
+
const PRED_IMPLEMENTED_BY_MECHANISM = "objective_implemented_by_mechanism";
|
|
1335
|
+
const PRED_REALIZED_BY_PRACTICE = "objective_realized_by_practice";
|
|
1336
|
+
// s3: slimmed — the full explanation lives in the codegen-instructions MCP
|
|
1337
|
+
// resource (detail_encoding.relations_ref). Kept URI-free on purpose: the
|
|
1338
|
+
// no-leak gate scans relations_ref for any scheme://.
|
|
1339
|
+
const RELATIONS_REF_NOTE = "Inline g2_context.relations elided; execute each listed trace_sbd_toe_graph " +
|
|
1340
|
+
"{lens, anchor} call to recover them, or re-call with include_relations=true. " +
|
|
1341
|
+
"Encoding details: MCP resource codegen-instructions, detail_encoding.relations_ref.";
|
|
1342
|
+
/**
|
|
1343
|
+
* v2 token diet, s2 — build the `relations_ref` for `detail: "standard" |
|
|
1344
|
+
* "minimal"`. See the {@link RelationsRefLensCall} doc block for the full
|
|
1345
|
+
* relation-kind → lens mapping. Coverage is decided per relation and counted
|
|
1346
|
+
* (never-silent): every inline relation is either recoverable by executing
|
|
1347
|
+
* one of the referenced curated lenses, byte-redundant with an entity's
|
|
1348
|
+
* `slice_id` in this same payload, or kept inline in `residual_relations`.
|
|
1349
|
+
* Deterministic: lens order follows activated_scope.slices order, then sorted
|
|
1350
|
+
* fallback anchors.
|
|
1351
|
+
*/
|
|
1352
|
+
function buildRelationsRef(result) {
|
|
1353
|
+
const relations = result.g2_context.relations;
|
|
1354
|
+
const activatedSliceIds = result.activated_scope.slices.map((slice) => slice.slice_id);
|
|
1355
|
+
const activatedSliceIdSet = new Set(activatedSliceIds);
|
|
1356
|
+
const entitySliceById = new Map();
|
|
1357
|
+
const entityTypeById = new Map();
|
|
1358
|
+
for (const list of [
|
|
1359
|
+
result.g2_context.control_objectives,
|
|
1360
|
+
result.g2_context.mechanisms,
|
|
1361
|
+
result.g2_context.practices,
|
|
1362
|
+
result.g2_context.artifacts
|
|
1363
|
+
]) {
|
|
1364
|
+
for (const entity of list) {
|
|
1365
|
+
entitySliceById.set(entity.entity_id, entity.slice_id);
|
|
1366
|
+
entityTypeById.set(entity.entity_id, entity.entity_type);
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
// Objective → activated slice via an explicit belongsToSlice edge (the
|
|
1370
|
+
// pattern slice_implementation anchors on), and objectives that have at
|
|
1371
|
+
// least one mechanism/practice edge (required by that lens's UNION).
|
|
1372
|
+
const sliceEdgeBySubject = new Map();
|
|
1373
|
+
const subjectsWithTargets = new Set();
|
|
1374
|
+
for (const relation of relations) {
|
|
1375
|
+
if (relation.predicate === PRED_BELONGS_TO_SLICE &&
|
|
1376
|
+
activatedSliceIdSet.has(relation.object_id)) {
|
|
1377
|
+
if (!sliceEdgeBySubject.has(relation.subject_id)) {
|
|
1378
|
+
sliceEdgeBySubject.set(relation.subject_id, relation.object_id);
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
else if (relation.predicate === PRED_IMPLEMENTED_BY_MECHANISM ||
|
|
1382
|
+
relation.predicate === PRED_REALIZED_BY_PRACTICE) {
|
|
1383
|
+
subjectsWithTargets.add(relation.subject_id);
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
const sliceImplementationAnchors = new Set();
|
|
1387
|
+
const objectiveRealizationAnchors = new Set();
|
|
1388
|
+
const mechanismProvenanceAnchors = new Set();
|
|
1389
|
+
let viaLenses = 0;
|
|
1390
|
+
let implicitInEntities = 0;
|
|
1391
|
+
const residual = [];
|
|
1392
|
+
for (const relation of relations) {
|
|
1393
|
+
if (relation.predicate === PRED_BELONGS_TO_SLICE) {
|
|
1394
|
+
if (activatedSliceIdSet.has(relation.object_id) &&
|
|
1395
|
+
subjectsWithTargets.has(relation.subject_id)) {
|
|
1396
|
+
// slice_implementation(anchor=slice) rows carry this edge.
|
|
1397
|
+
sliceImplementationAnchors.add(relation.object_id);
|
|
1398
|
+
viaLenses += 1;
|
|
1399
|
+
}
|
|
1400
|
+
else if (entitySliceById.get(relation.subject_id) === relation.object_id) {
|
|
1401
|
+
// Redundant with the entity's own slice_id in g2_context.
|
|
1402
|
+
implicitInEntities += 1;
|
|
1403
|
+
}
|
|
1404
|
+
else {
|
|
1405
|
+
const { source: _source, ...rest } = relation;
|
|
1406
|
+
residual.push(rest);
|
|
1407
|
+
}
|
|
1408
|
+
continue;
|
|
1409
|
+
}
|
|
1410
|
+
if (relation.predicate === PRED_IMPLEMENTED_BY_MECHANISM ||
|
|
1411
|
+
relation.predicate === PRED_REALIZED_BY_PRACTICE) {
|
|
1412
|
+
const sliceAnchor = sliceEdgeBySubject.get(relation.subject_id);
|
|
1413
|
+
if (sliceAnchor !== undefined) {
|
|
1414
|
+
sliceImplementationAnchors.add(sliceAnchor);
|
|
1415
|
+
viaLenses += 1;
|
|
1416
|
+
}
|
|
1417
|
+
else if (entitySliceById.has(relation.subject_id)) {
|
|
1418
|
+
// Activated objective without a belongsToSlice edge (data gap).
|
|
1419
|
+
objectiveRealizationAnchors.add(relation.subject_id);
|
|
1420
|
+
viaLenses += 1;
|
|
1421
|
+
}
|
|
1422
|
+
else if ((relation.predicate === PRED_IMPLEMENTED_BY_MECHANISM &&
|
|
1423
|
+
entityTypeById.get(relation.object_id) === "Mechanism") ||
|
|
1424
|
+
(relation.predicate === PRED_REALIZED_BY_PRACTICE &&
|
|
1425
|
+
entityTypeById.get(relation.object_id) === "Practice")) {
|
|
1426
|
+
// Only the target is activated (cross-slice edge); the predicate is
|
|
1427
|
+
// recoverable from the target's entity_type in this payload.
|
|
1428
|
+
mechanismProvenanceAnchors.add(relation.object_id);
|
|
1429
|
+
viaLenses += 1;
|
|
1430
|
+
}
|
|
1431
|
+
else {
|
|
1432
|
+
const { source: _source, ...rest } = relation;
|
|
1433
|
+
residual.push(rest);
|
|
1434
|
+
}
|
|
1435
|
+
continue;
|
|
1436
|
+
}
|
|
1437
|
+
// Unknown predicate — never silently dropped.
|
|
1438
|
+
const { source: _source, ...rest } = relation;
|
|
1439
|
+
residual.push(rest);
|
|
1440
|
+
}
|
|
1441
|
+
const lenses = [
|
|
1442
|
+
...activatedSliceIds
|
|
1443
|
+
.filter((sliceId) => sliceImplementationAnchors.has(sliceId))
|
|
1444
|
+
.map((anchor) => ({ lens: "slice_implementation", anchor })),
|
|
1445
|
+
...[...objectiveRealizationAnchors]
|
|
1446
|
+
.sort()
|
|
1447
|
+
.map((anchor) => ({ lens: "objective_realization", anchor })),
|
|
1448
|
+
...[...mechanismProvenanceAnchors]
|
|
1449
|
+
.sort()
|
|
1450
|
+
.map((anchor) => ({ lens: "mechanism_provenance", anchor }))
|
|
1451
|
+
];
|
|
1452
|
+
const relationsRef = {
|
|
1453
|
+
tool: "trace_sbd_toe_graph",
|
|
1454
|
+
lenses,
|
|
1455
|
+
total_relations: relations.length,
|
|
1456
|
+
coverage: {
|
|
1457
|
+
via_lenses: viaLenses,
|
|
1458
|
+
implicit_in_entities: implicitInEntities,
|
|
1459
|
+
residual_inline: residual.length
|
|
1460
|
+
},
|
|
1461
|
+
note: RELATIONS_REF_NOTE
|
|
1462
|
+
};
|
|
1463
|
+
if (residual.length > 0)
|
|
1464
|
+
relationsRef.residual_relations = residual;
|
|
1465
|
+
return relationsRef;
|
|
1466
|
+
}
|
|
1467
|
+
function stripSource(items) {
|
|
1468
|
+
return items.map(({ source: _source, ...rest }) => rest);
|
|
1469
|
+
}
|
|
1470
|
+
/**
|
|
1471
|
+
* Static map published-file → payload path whose ids, in payload order, are
|
|
1472
|
+
* exactly the citation_map run for that file (the citation_map is BUILT by
|
|
1473
|
+
* iterating those very lists, in this order — see the core's citation block).
|
|
1474
|
+
* Paths use the mini-syntax documented in the resource's
|
|
1475
|
+
* `detail_encoding.citations` legend. No file outside this table is expected;
|
|
1476
|
+
* if one ever appears, the group falls back to explicit `ids` (lossless).
|
|
1477
|
+
*/
|
|
1478
|
+
const CITATION_FILE_TO_PAYLOAD_PATH = {
|
|
1479
|
+
"data/publish/runtime/requirements.json": "activated_scope.requirements[].requirement_id",
|
|
1480
|
+
"data/publish/runtime/controls.json": "activated_scope.controls[].control_id",
|
|
1481
|
+
"data/publish/runtime/v1/slices.json": "activated_scope.slices[].slice_id",
|
|
1482
|
+
"data/publish/runtime/v1/control_objectives.json": "keys(g2_context.control_objectives[slice])",
|
|
1483
|
+
"data/publish/runtime/v1/mechanisms.json": "keys(g2_context.mechanisms[slice])",
|
|
1484
|
+
"data/publish/runtime/v1/practices.json": "keys(g2_context.practices[slice])",
|
|
1485
|
+
"data/publish/runtime/v1/artifacts.json": "keys(g2_context.artifacts[slice])",
|
|
1486
|
+
"data/publish/overlay/external_frameworks.json": "regulatory_overlay.frameworks[].framework_id",
|
|
1487
|
+
"data/publish/overlay/external_obligations.json": "activated_scope.regulatory_obligations[].obligation_id"
|
|
1488
|
+
};
|
|
1489
|
+
/**
|
|
1490
|
+
* Invert the classic `citation_map` (id → {source, source_data}) into
|
|
1491
|
+
* source-grouped `citations` (see {@link CitationsGroup}). Pure re-encoding:
|
|
1492
|
+
* the exact per-id source and source_data are reconstructible from the
|
|
1493
|
+
* ordered run-length `source_data` map — nothing is dropped. s3: ids already
|
|
1494
|
+
* present verbatim in a payload section are referenced via `ids_from` instead
|
|
1495
|
+
* of repeated; a group keeps explicit `ids` only if one of its files has no
|
|
1496
|
+
* payload-path mapping (never expected for the published bundle).
|
|
1497
|
+
*/
|
|
1498
|
+
function invertCitationMap(citationMap) {
|
|
1499
|
+
const bySource = new Map();
|
|
1500
|
+
for (const [id, entry] of Object.entries(citationMap)) {
|
|
1501
|
+
let files = bySource.get(entry.source);
|
|
1502
|
+
if (!files) {
|
|
1503
|
+
files = new Map();
|
|
1504
|
+
bySource.set(entry.source, files);
|
|
1505
|
+
}
|
|
1506
|
+
let ids = files.get(entry.source_data);
|
|
1507
|
+
if (!ids) {
|
|
1508
|
+
ids = [];
|
|
1509
|
+
files.set(entry.source_data, ids);
|
|
1510
|
+
}
|
|
1511
|
+
ids.push(id);
|
|
1512
|
+
}
|
|
1513
|
+
const citations = {};
|
|
1514
|
+
for (const [source, files] of bySource) {
|
|
1515
|
+
const source_data = {};
|
|
1516
|
+
const ids = [];
|
|
1517
|
+
const idsFrom = [];
|
|
1518
|
+
let allFilesMapped = true;
|
|
1519
|
+
for (const [file, fileIds] of files) {
|
|
1520
|
+
source_data[file] = fileIds.length;
|
|
1521
|
+
ids.push(...fileIds);
|
|
1522
|
+
const path = CITATION_FILE_TO_PAYLOAD_PATH[file];
|
|
1523
|
+
if (path === undefined)
|
|
1524
|
+
allFilesMapped = false;
|
|
1525
|
+
else
|
|
1526
|
+
idsFrom.push(path);
|
|
1527
|
+
}
|
|
1528
|
+
citations[source] = allFilesMapped
|
|
1529
|
+
? { source_data, ids_from: idsFrom }
|
|
1530
|
+
: { source_data, ids };
|
|
1531
|
+
}
|
|
1532
|
+
return citations;
|
|
1533
|
+
}
|
|
1534
|
+
/**
|
|
1535
|
+
* Group the flat `manual_grounding` entries by the tuple that repeats
|
|
1536
|
+
* verbatim: (rastreabilidade_role, manual_chapter, manual_file,
|
|
1537
|
+
* manual_commit_sha). Names are elided ONLY when recoverable from the
|
|
1538
|
+
* `g2_context` entity lists in the same payload (they come from the same
|
|
1539
|
+
* rastreabilidade source); any non-recoverable name is kept explicitly in
|
|
1540
|
+
* `v1_entity_names`, so no information is lost.
|
|
1541
|
+
*/
|
|
1542
|
+
function groupManualGrounding(result) {
|
|
1543
|
+
const g2Names = new Map();
|
|
1544
|
+
for (const list of [
|
|
1545
|
+
result.g2_context.control_objectives,
|
|
1546
|
+
result.g2_context.mechanisms,
|
|
1547
|
+
result.g2_context.practices,
|
|
1548
|
+
result.g2_context.artifacts
|
|
1549
|
+
]) {
|
|
1550
|
+
for (const entity of list)
|
|
1551
|
+
g2Names.set(entity.entity_id, entity.name);
|
|
1552
|
+
}
|
|
1553
|
+
// Object sentinel: serializes unlike any string/null value, so an absent
|
|
1554
|
+
// field can never collide with a real published value in the group key.
|
|
1555
|
+
const ABSENT = { absent: true };
|
|
1556
|
+
const groups = new Map();
|
|
1557
|
+
const ungrouped = [];
|
|
1558
|
+
for (const entry of result.manual_grounding) {
|
|
1559
|
+
if (!entry.v1_entity_id) {
|
|
1560
|
+
// Lossless guard — the loader keys entries by v1_entity_id, so this is
|
|
1561
|
+
// not expected; if it ever happens the entry survives verbatim.
|
|
1562
|
+
const { source: _source, ...rest } = entry;
|
|
1563
|
+
ungrouped.push(rest);
|
|
1564
|
+
continue;
|
|
1565
|
+
}
|
|
1566
|
+
const hasChapter = "manual_chapter" in entry;
|
|
1567
|
+
const hasFile = "manual_file" in entry;
|
|
1568
|
+
const hasSha = entry.manual_commit_sha !== undefined;
|
|
1569
|
+
const key = JSON.stringify([
|
|
1570
|
+
entry.rastreabilidade_role,
|
|
1571
|
+
hasChapter ? entry.manual_chapter ?? null : ABSENT,
|
|
1572
|
+
hasFile ? entry.manual_file ?? null : ABSENT,
|
|
1573
|
+
hasSha ? entry.manual_commit_sha : ABSENT
|
|
1574
|
+
]);
|
|
1575
|
+
let group = groups.get(key);
|
|
1576
|
+
if (!group) {
|
|
1577
|
+
group = {
|
|
1578
|
+
rastreabilidade_role: entry.rastreabilidade_role,
|
|
1579
|
+
...(hasChapter ? { manual_chapter: entry.manual_chapter ?? null } : {}),
|
|
1580
|
+
...(hasFile ? { manual_file: entry.manual_file ?? null } : {}),
|
|
1581
|
+
...(hasSha ? { manual_commit_sha: entry.manual_commit_sha } : {}),
|
|
1582
|
+
v1_entity_ids: []
|
|
1583
|
+
};
|
|
1584
|
+
groups.set(key, group);
|
|
1585
|
+
}
|
|
1586
|
+
group.v1_entity_ids.push(entry.v1_entity_id);
|
|
1587
|
+
if (entry.v1_entity_name &&
|
|
1588
|
+
g2Names.get(entry.v1_entity_id) !== entry.v1_entity_name) {
|
|
1589
|
+
(group.v1_entity_names ??= {})[entry.v1_entity_id] = entry.v1_entity_name;
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
const grouped = {
|
|
1593
|
+
total_entries: result.manual_grounding.length,
|
|
1594
|
+
groups: [...groups.values()]
|
|
1595
|
+
};
|
|
1596
|
+
if (ungrouped.length > 0)
|
|
1597
|
+
grouped.ungrouped = ungrouped;
|
|
1598
|
+
return grouped;
|
|
1599
|
+
}
|
|
1600
|
+
// s3b: kept URI-free on purpose (no-leak discipline, same as RELATIONS_REF_NOTE).
|
|
1601
|
+
const GROUNDING_GROUPS_REF_NOTE = "Per-group v1_entity_ids elided at detail=minimal (each group carries its " +
|
|
1602
|
+
"exact `entries` count). The grounding id set is already in this payload — " +
|
|
1603
|
+
"every grounding id is an entity-id key of the g2_context maps. Re-call " +
|
|
1604
|
+
"with the same input at detail='standard' for the per-group id lists " +
|
|
1605
|
+
"(groups align 1:1, same order).";
|
|
1606
|
+
/**
|
|
1607
|
+
* v2 token diet, s3b (revised ADENDA 2026-07-05) — minimal-form
|
|
1608
|
+
* `manual_grounding`, derived from the detail="standard" grouping (so the 1:1
|
|
1609
|
+
* group alignment holds by construction). Serialization-only cut, never
|
|
1610
|
+
* silent:
|
|
1611
|
+
* - per-group `v1_entity_ids` → exact `entries` count (Σ == total_entries);
|
|
1612
|
+
* - `manual_commit_sha` hoisted to the top level iff EVERY group carries
|
|
1613
|
+
* the same sha (expected always: one published manual commit); otherwise
|
|
1614
|
+
* each group keeps its own sha inline (lossless guard);
|
|
1615
|
+
* - `v1_entity_names` (never expected) and `ungrouped` (never expected)
|
|
1616
|
+
* survive verbatim — no name or entry can be lost;
|
|
1617
|
+
* - `groups_ref` is the executable reference to the full grouping.
|
|
1618
|
+
* Invariant-3 note: grounding ids never feed `citations`/`ids_from`, and the
|
|
1619
|
+
* id set stays reconstructible from this same payload's g2_context entity
|
|
1620
|
+
* maps without any extra call.
|
|
1621
|
+
*/
|
|
1622
|
+
function buildMinimalGrounding(grouped) {
|
|
1623
|
+
const shas = grouped.groups.map((group) => group.manual_commit_sha);
|
|
1624
|
+
const hoistedSha = grouped.groups.length > 0 &&
|
|
1625
|
+
shas[0] !== undefined &&
|
|
1626
|
+
shas.every((sha) => sha === shas[0])
|
|
1627
|
+
? shas[0]
|
|
1628
|
+
: undefined;
|
|
1629
|
+
const minimal = {
|
|
1630
|
+
total_entries: grouped.total_entries,
|
|
1631
|
+
...(hoistedSha !== undefined ? { manual_commit_sha: hoistedSha } : {}),
|
|
1632
|
+
groups: grouped.groups.map((group) => ({
|
|
1633
|
+
rastreabilidade_role: group.rastreabilidade_role,
|
|
1634
|
+
...("manual_chapter" in group ? { manual_chapter: group.manual_chapter } : {}),
|
|
1635
|
+
...("manual_file" in group ? { manual_file: group.manual_file } : {}),
|
|
1636
|
+
...(hoistedSha === undefined && group.manual_commit_sha !== undefined
|
|
1637
|
+
? { manual_commit_sha: group.manual_commit_sha }
|
|
1638
|
+
: {}),
|
|
1639
|
+
entries: group.v1_entity_ids.length,
|
|
1640
|
+
...(group.v1_entity_names ? { v1_entity_names: group.v1_entity_names } : {})
|
|
1641
|
+
})),
|
|
1642
|
+
groups_ref: {
|
|
1643
|
+
tool: "prepare_sbd_toe_codegen_context",
|
|
1644
|
+
with: { detail: "standard" },
|
|
1645
|
+
note: GROUNDING_GROUPS_REF_NOTE
|
|
1646
|
+
}
|
|
1647
|
+
};
|
|
1648
|
+
if (grouped.ungrouped)
|
|
1649
|
+
minimal.ungrouped = grouped.ungrouped;
|
|
1650
|
+
return minimal;
|
|
1651
|
+
}
|
|
1652
|
+
// s3c notes — all NEW constants (the s1–s4 note texts are byte-frozen by the
|
|
1653
|
+
// standard/minimal golden snapshots and are never edited). Kept URI-free
|
|
1654
|
+
// (no-leak discipline, same as RELATIONS_REF_NOTE).
|
|
1655
|
+
const GROUNDING_GROUPS_REF_NOTE_ULTRATHIN = "Grounding group list elided at detail=ultrathin (total_entries is the " +
|
|
1656
|
+
"exact flat entry count; manual_commit_sha is the shared published manual " +
|
|
1657
|
+
"commit). The grounding id set is already in this payload — every " +
|
|
1658
|
+
"grounding id is an entity-id key of the g2_context maps. Re-call with the " +
|
|
1659
|
+
"same input at detail='standard' for the full (role, chapter, file) groups " +
|
|
1660
|
+
"with per-group v1_entity_ids; detail='minimal' returns the groups with " +
|
|
1661
|
+
"per-group counts.";
|
|
1662
|
+
const DESCRIPTIONS_REF_NOTE = "Published `description` fields (the 'how') elided at detail=ultrathin — " +
|
|
1663
|
+
"the requirement/control lists themselves are COMPLETE (same ids, same " +
|
|
1664
|
+
"order, name always present). Re-call with the same input at " +
|
|
1665
|
+
"detail='minimal' for the same complete scope WITH the verbatim published " +
|
|
1666
|
+
"descriptions (requirements + direct controls).";
|
|
1667
|
+
const EVIDENCE_PATTERNS_REST_NOTE_ULTRATHIN = "No evidence pattern goes inline at detail=ultrathin (returned=0; " +
|
|
1668
|
+
"capped=total). Re-call with the same input at detail='minimal' for the " +
|
|
1669
|
+
"deterministic top-5 (cheapest level that returns patterns inline); " +
|
|
1670
|
+
"detail='standard' returns the top-10 and detail='full' the classic " +
|
|
1671
|
+
"top-25 (each list is a deterministic prefix of the next).";
|
|
1672
|
+
const ACTIVATION_TRACE_REF_NOTE_ULTRATHIN = "activation_trace elided at detail=ultrathin — re-call with debug=true to " +
|
|
1673
|
+
"include it (always inline at detail=full).";
|
|
1674
|
+
const V1_DIAGNOSTICS_REF_NOTE = "v1_consistency_mismatches/v1_manifest_warnings texts elided at " +
|
|
1675
|
+
"detail=ultrathin (exact counts inline). Re-call with the same input at " +
|
|
1676
|
+
"detail='minimal' for the full text arrays in completeness_report.";
|
|
1677
|
+
/**
|
|
1678
|
+
* v2 token diet, s3c — ultrathin-form `manual_grounding`, derived from the
|
|
1679
|
+
* s3b minimal form (so `total_entries`, the hoisted sha and the ungrouped
|
|
1680
|
+
* guard are byte-identical by construction). Serialization-only cut, never
|
|
1681
|
+
* silent: the (role, chapter, file) group list with per-group counts is
|
|
1682
|
+
* elided; `total_entries` keeps the exact flat count and `groups_ref` is the
|
|
1683
|
+
* executable reference to the full grouping (same input, detail="standard").
|
|
1684
|
+
* Lossless guards (expected never): if the sha was not hoistable or any group
|
|
1685
|
+
* carries `v1_entity_names`, the minimal `groups` list survives inline.
|
|
1686
|
+
*/
|
|
1687
|
+
function buildUltrathinGrounding(minimal) {
|
|
1688
|
+
const mustKeepGroups = minimal.manual_commit_sha === undefined ||
|
|
1689
|
+
minimal.groups.some((group) => group.v1_entity_names !== undefined || group.manual_commit_sha !== undefined);
|
|
1690
|
+
const ultrathin = {
|
|
1691
|
+
total_entries: minimal.total_entries,
|
|
1692
|
+
...(minimal.manual_commit_sha !== undefined
|
|
1693
|
+
? { manual_commit_sha: minimal.manual_commit_sha }
|
|
1694
|
+
: {}),
|
|
1695
|
+
...(mustKeepGroups && minimal.groups.length > 0 ? { groups: minimal.groups } : {}),
|
|
1696
|
+
groups_ref: {
|
|
1697
|
+
tool: "prepare_sbd_toe_codegen_context",
|
|
1698
|
+
with: { detail: "standard" },
|
|
1699
|
+
note: GROUNDING_GROUPS_REF_NOTE_ULTRATHIN
|
|
1700
|
+
}
|
|
1701
|
+
};
|
|
1702
|
+
if (minimal.ungrouped)
|
|
1703
|
+
ultrathin.ungrouped = minimal.ungrouped;
|
|
1704
|
+
return ultrathin;
|
|
1705
|
+
}
|
|
1706
|
+
/**
|
|
1707
|
+
* v2 token diet, s3c — trim the completeness report for `detail: "ultrathin"`.
|
|
1708
|
+
* Every COUNT survives verbatim (never-silent backbone: expected/returned per
|
|
1709
|
+
* entity kind, m_recall, named/unnamed, evidence total/returned/capped/cap and
|
|
1710
|
+
* the rest-ref); only the two diagnostic TEXT arrays are re-encoded as exact
|
|
1711
|
+
* counts + the executable `v1_diagnostics_ref` (see
|
|
1712
|
+
* {@link UltrathinCompletenessReport} for what is cut and why).
|
|
1713
|
+
*/
|
|
1714
|
+
function trimCompletenessForUltrathin(report) {
|
|
1715
|
+
const { v1_consistency_mismatches, v1_manifest_warnings, ...kept } = report;
|
|
1716
|
+
return {
|
|
1717
|
+
...kept,
|
|
1718
|
+
v1_consistency_mismatches_count: v1_consistency_mismatches.length,
|
|
1719
|
+
v1_manifest_warnings_count: v1_manifest_warnings.length,
|
|
1720
|
+
...(v1_consistency_mismatches.length + v1_manifest_warnings.length > 0
|
|
1721
|
+
? {
|
|
1722
|
+
v1_diagnostics_ref: {
|
|
1723
|
+
tool: "prepare_sbd_toe_codegen_context",
|
|
1724
|
+
with: { detail: "minimal" },
|
|
1725
|
+
note: V1_DIAGNOSTICS_REF_NOTE
|
|
1726
|
+
}
|
|
1727
|
+
}
|
|
1728
|
+
: {})
|
|
1729
|
+
};
|
|
1730
|
+
}
|
|
1731
|
+
/** Slice-grouped, name-only entity encoding (see {@link SliceGroupedEntityNames}). */
|
|
1732
|
+
function groupEntitiesBySlice(entities) {
|
|
1733
|
+
const grouped = {};
|
|
1734
|
+
for (const entity of entities) {
|
|
1735
|
+
(grouped[entity.slice_id] ??= {})[entity.entity_id] = entity.name ?? null;
|
|
1736
|
+
}
|
|
1737
|
+
return grouped;
|
|
1738
|
+
}
|
|
1739
|
+
/** `category` is derivable iff it equals the requirement_id prefix before the
|
|
1740
|
+
* first "-" (bundle-wide invariant, guarded per item). */
|
|
1741
|
+
function categoryIsDerivable(requirementId, category) {
|
|
1742
|
+
const dash = requirementId.indexOf("-");
|
|
1743
|
+
return dash > 0 && requirementId.slice(0, dash) === category;
|
|
1744
|
+
}
|
|
1745
|
+
/** Dieted requirements: `source`/derivable `category` elided, verbatim
|
|
1746
|
+
* published `description` appended (s3 — the "how"). s3c: at
|
|
1747
|
+
* `detail: "ultrathin"` (`includeDescriptions: false`) the description is
|
|
1748
|
+
* elided too — each item is exactly {requirement_id, name, type} (plus the
|
|
1749
|
+
* unchanged `category` lossless guard) with the executable
|
|
1750
|
+
* `activated_scope.descriptions_ref` pointing at detail="minimal". */
|
|
1751
|
+
function dietRequirements(requirements, includeDescriptions) {
|
|
1752
|
+
const descriptionById = new Map();
|
|
1753
|
+
if (includeDescriptions) {
|
|
1754
|
+
for (const requirement of getOntologyData().requirements) {
|
|
1755
|
+
if (requirement.description) {
|
|
1756
|
+
descriptionById.set(requirement.requirement_id, requirement.description);
|
|
1757
|
+
}
|
|
1758
|
+
}
|
|
1759
|
+
}
|
|
1760
|
+
return requirements.map((item) => {
|
|
1761
|
+
const { source: _source, category, ...rest } = item;
|
|
1762
|
+
const description = descriptionById.get(item.requirement_id);
|
|
1763
|
+
return {
|
|
1764
|
+
...rest,
|
|
1765
|
+
...(categoryIsDerivable(item.requirement_id, category) ? {} : { category }),
|
|
1766
|
+
...(description ? { description } : {})
|
|
1767
|
+
};
|
|
1768
|
+
});
|
|
1769
|
+
}
|
|
1770
|
+
/** Dieted controls: `source` elided; `direct` controls carry the verbatim
|
|
1771
|
+
* published `description` (s3 — the "how"). s3c: at `detail: "ultrathin"`
|
|
1772
|
+
* (`includeDescriptions: false`) the description is elided — each item is
|
|
1773
|
+
* exactly {control_id, name, domain, control_type, confidence} (every
|
|
1774
|
+
* non-description published field: small, useful, and required to keep the
|
|
1775
|
+
* item more than id-only). */
|
|
1776
|
+
function dietControls(controls, includeDescriptions) {
|
|
1777
|
+
const descriptionById = new Map();
|
|
1778
|
+
if (includeDescriptions) {
|
|
1779
|
+
for (const control of getOntologyData().controls) {
|
|
1780
|
+
if (control.description)
|
|
1781
|
+
descriptionById.set(control.control_id, control.description);
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
return controls.map((item) => {
|
|
1785
|
+
const { source: _source, ...rest } = item;
|
|
1786
|
+
const description = item.confidence === "direct" ? descriptionById.get(item.control_id) : undefined;
|
|
1787
|
+
return { ...rest, ...(description ? { description } : {}) };
|
|
1788
|
+
});
|
|
1789
|
+
}
|
|
1790
|
+
/**
|
|
1791
|
+
* v2 token diet, s1+s2+s3 — dieted encoding for `detail: "standard" |
|
|
1792
|
+
* "minimal"`. Pure post-processing over the byte-identical full result. The
|
|
1793
|
+
* citable ID set is EXACTLY the full one (invariant 3; the omitted evidence
|
|
1794
|
+
* patterns carry no citation_map ids — verified by tests). Every cut is
|
|
1795
|
+
* either a lossless derivable-field re-encoding documented in the
|
|
1796
|
+
* codegen-instructions resource legend, or an explicit bound with
|
|
1797
|
+
* total/returned/omitted counts plus an executable reference to the rest
|
|
1798
|
+
* (invariant 2 — never silent):
|
|
1799
|
+
* - s1: inverted citations, grouped grounding, per-item `source` legend;
|
|
1800
|
+
* - s2: relations on-demand via `relations_ref` (include_relations restores);
|
|
1801
|
+
* - s3: evidence cap 25→10 (deterministic prefix; counts + rest-ref in
|
|
1802
|
+
* completeness_report), instructions/template → MCP resource, trace only
|
|
1803
|
+
* with debug, verbatim published `description` on requirements + direct
|
|
1804
|
+
* controls, and derivable-field dedup (category, entity_type/slice_family
|
|
1805
|
+
* via slice-grouped entity maps, relevance_score, citation id repeats);
|
|
1806
|
+
* - s3b (revised ADENDA 2026-07-05 — no top-N): `minimal` keeps the
|
|
1807
|
+
* activated scope byte-identical to `standard` and diverges ONLY on
|
|
1808
|
+
* traceability serialization — evidence cap 10→5 (same mechanism) and
|
|
1809
|
+
* `manual_grounding` in the minimal form (counts + hoisted sha +
|
|
1810
|
+
* executable groups_ref);
|
|
1811
|
+
* - s3c (`ultrathin`, operator reactivation 2026-07-05): same complete
|
|
1812
|
+
* activated set, but descriptions elided (descriptions_ref →
|
|
1813
|
+
* detail="minimal"), evidence cap 5→0 (rest-ref → detail="minimal"),
|
|
1814
|
+
* grounding aggregate-only and completeness diagnostics as counts + ref.
|
|
1815
|
+
*/
|
|
1816
|
+
function applyStructuralDiet(result, detail, includeRelations) {
|
|
1817
|
+
// s3/s3b/s3c evidence cap (standard 10, minimal 5, ultrathin 0):
|
|
1818
|
+
// deterministic prefix of the classic (already sorted: relevance_score desc,
|
|
1819
|
+
// id asc) list; each dieted list is by construction a prefix of the next
|
|
1820
|
+
// level's. Never-silent counts below; the rest-ref points to detail="full"
|
|
1821
|
+
// (classic top-25) at standard/minimal and to detail="minimal" (the
|
|
1822
|
+
// CHEAPEST level that returns patterns inline) at ultrathin.
|
|
1823
|
+
const ultrathin = detail === "ultrathin";
|
|
1824
|
+
const evidenceCap = ultrathin
|
|
1825
|
+
? ULTRATHIN_EVIDENCE_PATTERN_CAP
|
|
1826
|
+
: detail === "minimal"
|
|
1827
|
+
? MINIMAL_EVIDENCE_PATTERN_CAP
|
|
1828
|
+
: STANDARD_EVIDENCE_PATTERN_CAP;
|
|
1829
|
+
const evidenceKept = result.g2_context.evidence_patterns.slice(0, evidenceCap);
|
|
1830
|
+
const evidenceTotal = result.completeness_report.evidence_patterns_total;
|
|
1831
|
+
const evidenceCapped = evidenceTotal - evidenceKept.length;
|
|
1832
|
+
const completeness = {
|
|
1833
|
+
...result.completeness_report,
|
|
1834
|
+
evidence_patterns_returned: evidenceKept.length,
|
|
1835
|
+
evidence_patterns_capped: evidenceCapped,
|
|
1836
|
+
evidence_pattern_cap: evidenceCap,
|
|
1837
|
+
...(evidenceCapped > 0
|
|
1838
|
+
? {
|
|
1839
|
+
evidence_patterns_rest: (ultrathin
|
|
1840
|
+
? {
|
|
1841
|
+
tool: "prepare_sbd_toe_codegen_context",
|
|
1842
|
+
with: { detail: "minimal" },
|
|
1843
|
+
note: EVIDENCE_PATTERNS_REST_NOTE_ULTRATHIN
|
|
1844
|
+
}
|
|
1845
|
+
: {
|
|
1846
|
+
tool: "prepare_sbd_toe_codegen_context",
|
|
1847
|
+
with: { detail: "full" },
|
|
1848
|
+
note: "Re-call with the same input at detail='full' for the classic inline " +
|
|
1849
|
+
`top-${EVIDENCE_PATTERN_CAP} evidence_patterns (this list is its ` +
|
|
1850
|
+
"deterministic prefix); with debug=true, ids beyond the classic cap " +
|
|
1851
|
+
"are listed in debug.rejected_candidates."
|
|
1852
|
+
})
|
|
1853
|
+
}
|
|
1854
|
+
: {})
|
|
1855
|
+
};
|
|
1856
|
+
// s3 instructions → resource: conditions computed from the SAME resolved
|
|
1857
|
+
// inputs the core used, so resource + active_conditions reconstruct the
|
|
1858
|
+
// inline full content byte-identically.
|
|
1859
|
+
const echoedRisk = result.input_echo.risk_level;
|
|
1860
|
+
const riskLevel = echoedRisk === "L1" || echoedRisk === "L2" || echoedRisk === "L3"
|
|
1861
|
+
? echoedRisk
|
|
1862
|
+
: undefined;
|
|
1863
|
+
const instructionsRef = {
|
|
1864
|
+
resource: codegenInstructionsResourceUri(result.mode),
|
|
1865
|
+
active_conditions: activeInstructionConditions({
|
|
1866
|
+
hasOverlay: result.activated_scope.regulatory_obligations.length > 0,
|
|
1867
|
+
riskLevel,
|
|
1868
|
+
citationMapEmpty: Object.keys(result.citation_map).length === 0
|
|
1869
|
+
}),
|
|
1870
|
+
note: "Read this MCP resource for llm_codegen_instructions (slots filtered by " +
|
|
1871
|
+
"active_conditions) and security_rationale_template (task = " +
|
|
1872
|
+
"input_echo.task trimmed) — byte-identical to the detail=full inline " +
|
|
1873
|
+
"content — plus the detail_encoding legend for this payload."
|
|
1874
|
+
};
|
|
1875
|
+
const dieted = {
|
|
1876
|
+
status: result.status,
|
|
1877
|
+
mode: result.mode,
|
|
1878
|
+
// Echo the requested detail (and the include_relations escape hatch, when
|
|
1879
|
+
// active) for audit; the FULL result never echoes either (explicit "full"
|
|
1880
|
+
// must stay byte-identical to the omitted form).
|
|
1881
|
+
input_echo: {
|
|
1882
|
+
...result.input_echo,
|
|
1883
|
+
detail,
|
|
1884
|
+
...(includeRelations ? { include_relations: true } : {})
|
|
1885
|
+
},
|
|
1886
|
+
// s3: activation_trace only with debug=true; never-silent counter otherwise
|
|
1887
|
+
// (s3c: ultrathin carries its own note — the standard/minimal text is
|
|
1888
|
+
// byte-frozen by the golden snapshots).
|
|
1889
|
+
...(result.debug
|
|
1890
|
+
? { activation_trace: result.activation_trace }
|
|
1891
|
+
: {
|
|
1892
|
+
activation_trace_ref: {
|
|
1893
|
+
entries: result.activation_trace.length,
|
|
1894
|
+
note: ultrathin
|
|
1895
|
+
? ACTIVATION_TRACE_REF_NOTE_ULTRATHIN
|
|
1896
|
+
: "activation_trace elided at detail=standard/minimal — re-call with " +
|
|
1897
|
+
"debug=true to include it (always inline at detail=full)."
|
|
1898
|
+
}
|
|
1899
|
+
}),
|
|
1900
|
+
provenance_legend: ultrathin ? PROVENANCE_LEGEND_ULTRATHIN : PROVENANCE_LEGEND,
|
|
1901
|
+
// s3c: ultrathin elides the published descriptions (executable
|
|
1902
|
+
// descriptions_ref → detail="minimal"); the lists stay COMPLETE.
|
|
1903
|
+
activated_scope: {
|
|
1904
|
+
requirements: dietRequirements(result.activated_scope.requirements, !ultrathin),
|
|
1905
|
+
controls: dietControls(result.activated_scope.controls, !ultrathin),
|
|
1906
|
+
slices: stripSource(result.activated_scope.slices),
|
|
1907
|
+
regulatory_obligations: stripSource(result.activated_scope.regulatory_obligations),
|
|
1908
|
+
...(ultrathin
|
|
1909
|
+
? {
|
|
1910
|
+
descriptions_ref: {
|
|
1911
|
+
tool: "prepare_sbd_toe_codegen_context",
|
|
1912
|
+
with: { detail: "minimal" },
|
|
1913
|
+
note: DESCRIPTIONS_REF_NOTE
|
|
1914
|
+
}
|
|
1915
|
+
}
|
|
1916
|
+
: {})
|
|
1917
|
+
},
|
|
1918
|
+
g2_context: {
|
|
1919
|
+
control_objectives: groupEntitiesBySlice(result.g2_context.control_objectives),
|
|
1920
|
+
mechanisms: groupEntitiesBySlice(result.g2_context.mechanisms),
|
|
1921
|
+
practices: groupEntitiesBySlice(result.g2_context.practices),
|
|
1922
|
+
artifacts: groupEntitiesBySlice(result.g2_context.artifacts),
|
|
1923
|
+
...(includeRelations
|
|
1924
|
+
? { relations: stripSource(result.g2_context.relations) }
|
|
1925
|
+
: { relations_ref: buildRelationsRef(result) }),
|
|
1926
|
+
evidence_patterns: evidenceKept.map(({ source: _source, relevance_score: _score, ...rest }) => rest)
|
|
1927
|
+
},
|
|
1928
|
+
// s3b: minimal serves the count+provenance form (executable groups_ref);
|
|
1929
|
+
// standard keeps the full grouping; s3c: ultrathin serves the aggregate
|
|
1930
|
+
// form only (total + hoisted sha + groups_ref, group list elided).
|
|
1931
|
+
manual_grounding: ultrathin
|
|
1932
|
+
? buildUltrathinGrounding(buildMinimalGrounding(groupManualGrounding(result)))
|
|
1933
|
+
: detail === "minimal"
|
|
1934
|
+
? buildMinimalGrounding(groupManualGrounding(result))
|
|
1935
|
+
: groupManualGrounding(result),
|
|
1936
|
+
regulatory_overlay: {
|
|
1937
|
+
frameworks: stripSource(result.regulatory_overlay.frameworks),
|
|
1938
|
+
obligations: stripSource(result.regulatory_overlay.obligations),
|
|
1939
|
+
mappings: stripSource(result.regulatory_overlay.mappings),
|
|
1940
|
+
playbooks: stripSource(result.regulatory_overlay.playbooks)
|
|
1941
|
+
},
|
|
1942
|
+
citations: invertCitationMap(result.citation_map),
|
|
1943
|
+
// s3c: ultrathin trims the diagnostic text arrays to exact counts + ref.
|
|
1944
|
+
completeness_report: ultrathin
|
|
1945
|
+
? trimCompletenessForUltrathin(completeness)
|
|
1946
|
+
: completeness,
|
|
1947
|
+
codegen_instructions_ref: instructionsRef,
|
|
1948
|
+
// s4: identical re-call is deterministic — point the client back at the
|
|
1949
|
+
// context it already holds (full stays byte-identical: no hint there).
|
|
1950
|
+
repeat_call_hint: REPEAT_CALL_HINT,
|
|
1951
|
+
provenance: result.provenance
|
|
1952
|
+
};
|
|
1953
|
+
if (result.debug)
|
|
1954
|
+
dieted.debug = result.debug;
|
|
1955
|
+
return dieted;
|
|
1956
|
+
}
|
|
979
1957
|
export function handlePrepareCodegenContext(raw) {
|
|
980
|
-
//
|
|
1958
|
+
// v2 token diet (s1/s2): `detail` and `include_relations` select the
|
|
1959
|
+
// response ENCODING only — they are validated up-front and never influence
|
|
1960
|
+
// activation/resolution.
|
|
1961
|
+
const detail = parseDetail(raw);
|
|
1962
|
+
const includeRelations = parseIncludeRelations(raw);
|
|
981
1963
|
const result = prepareCodegenContextCore(raw);
|
|
982
|
-
|
|
1964
|
+
const shaped = detail !== "full" && result.status === "ready_for_codegen"
|
|
1965
|
+
? applyStructuralDiet(result, detail, includeRelations)
|
|
1966
|
+
: result;
|
|
1967
|
+
// RF-H: append the advisory band (status-aware, pure) around the deterministic result.
|
|
1968
|
+
return { ...shaped, next: prepareCodegenAffordances(result.status) };
|
|
983
1969
|
}
|
|
984
1970
|
function prepareCodegenContextCore(raw) {
|
|
985
1971
|
const input = normalizeInput(raw);
|
|
@@ -1262,31 +2248,7 @@ function prepareCodegenContextCore(raw) {
|
|
|
1262
2248
|
hasOverlay: overlayResolution.activatedObligations.length > 0,
|
|
1263
2249
|
riskLevel: input.risk_level
|
|
1264
2250
|
});
|
|
1265
|
-
const security_rationale_template =
|
|
1266
|
-
task: input.taskTrimmed,
|
|
1267
|
-
decisions: [
|
|
1268
|
-
{
|
|
1269
|
-
decision: "<fill: what design choice was made>",
|
|
1270
|
-
rationale: "<fill: why, citing IDs from citation_map>",
|
|
1271
|
-
cited_ids: ["<requirement_id|control_id|slice_id|obligation_id>"]
|
|
1272
|
-
}
|
|
1273
|
-
],
|
|
1274
|
-
validations: [
|
|
1275
|
-
{
|
|
1276
|
-
surface: "<fill: code path being validated>",
|
|
1277
|
-
rule: "<fill: validation rule>",
|
|
1278
|
-
rejection_behaviour: "<fill: how invalid input is rejected>"
|
|
1279
|
-
}
|
|
1280
|
-
],
|
|
1281
|
-
expected_evidence: [
|
|
1282
|
-
{
|
|
1283
|
-
artefact: "<fill: test, log, doc, sbom, scan, attestation, ...>",
|
|
1284
|
-
location: "<fill: where to find it>",
|
|
1285
|
-
verifies: "<fill: which control/requirement id>"
|
|
1286
|
-
}
|
|
1287
|
-
],
|
|
1288
|
-
residual_risk: "<fill: anything NOT addressed by this change>"
|
|
1289
|
-
};
|
|
2251
|
+
const security_rationale_template = buildSecurityRationaleTemplate(input.taskTrimmed);
|
|
1290
2252
|
const result = {
|
|
1291
2253
|
status: "ready_for_codegen",
|
|
1292
2254
|
mode: input.mode,
|