659b37cc21
CI / detect-changes (pull_request) Successful in 6s
CI / branch-name (pull_request) Successful in 1s
CI / guardrail-integrity (pull_request) Successful in 6s
CI / secret-scan (pull_request) Successful in 5s
CI / dep-audit (pull_request) Failing after 55s
CI / sbom-scan (pull_request) Failing after 58s
CI / build-sha-integrity (pull_request) Successful in 6s
CI / validate-canonical-controls (pull_request) Successful in 3s
CI / loc-budget (pull_request) Successful in 18s
CI / go-lint (pull_request) Successful in 43s
CI / python-lint (pull_request) Failing after 14s
CI / nodejs-lint (pull_request) Failing after 1m6s
CI / nodejs-build (pull_request) Successful in 3m0s
CI / test-go (pull_request) Successful in 58s
CI / iace-gt-coverage (pull_request) Successful in 16s
CI / test-python-backend (pull_request) Successful in 26s
CI / test-python-document-crawler (pull_request) Successful in 13s
CI / test-python-dsms-gateway (pull_request) Successful in 9s
Live gate test showed control-intent (#36/#37) was inert for the EU cyber corpus: "Welche Controls passen zu Security Updates?" recalls ENISA good-practices (relevant measures, but source_class=supervisory_guidance) + binding regs, never NIST — so lifting technical_standard above binding did nothing. Per the finalized control-corpus model (User 2026-06-24): add source_role (functional role) ORTHOGONAL to source_class (legal authority). source_class still decides rank; source_role decides CONTROL-POOL membership. classifyRole derives 7 roles from markers (no re-tagging): obligation / operational_requirement / procedural_requirement / control_standard / implementation_guidance / interpretation / definition. Control-intent now boosts the control-pool (operational/procedural requirement, control standard, implementation guidance) over the abstract obligation, soft- ordered op_req > procedural > standard > guidance (controlPoolGain + role bonus) — replacing "lift technical_standard above binding". So CRA Annex I (operational_requirement) wins over NIST (control_standard) for "which measures", and ENISA (implementation_guidance) enters the pool while staying guidance. Recall of not-retrieved standards (NIST) for generic control queries = next step (searchControls). Tested: classifyRole table, role-preference, op_req-Top-1. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
95 lines
4.2 KiB
Go
95 lines
4.2 KiB
Go
package ucca
|
|
|
|
import "strings"
|
|
|
|
// source_role is the FUNCTIONAL role of a chunk — WHAT must be done (obligation),
|
|
// HOW to implement it (operational/procedural requirement, control standard,
|
|
// implementation guidance), or how to READ the norm (interpretation/definition).
|
|
// It is ORTHOGONAL to source_class (legal authority): source_class decides RANK,
|
|
// source_role decides CONTROL-POOL membership for implementation questions.
|
|
// Derived deterministically from markers, so the untagged corpus needs no re-tag.
|
|
const (
|
|
roleObligation = "obligation" // the abstract duty (the WHAT)
|
|
roleOperationalReq = "operational_requirement" // concrete binding requirement (CRA Annex I)
|
|
roleProceduralReq = "procedural_requirement" // a process: notification/registration/DPIA/incident report
|
|
roleControlStandard = "control_standard" // best-practice control catalog (NIST/OWASP/ISO/CIS)
|
|
roleImplGuidance = "implementation_guidance" // advisory how-to (ENISA good practices, BSI)
|
|
roleInterpretation = "interpretation" // interprets the norm's MEANING (EDPB guideline)
|
|
roleDefinition = "definition" // definitions / scope / recitals
|
|
)
|
|
|
|
var (
|
|
proceduralMarkers = []string{
|
|
"Meldung", "Meldepflicht", "Notification", "Notifizierung", "Registrierung",
|
|
"Registration", "Konformitätserklärung", "Declaration of Conformity", "Incident",
|
|
"Berichterstattung", "Reporting", "Folgenabschätzung", "DSFA", "DPIA", "Anzeigepflicht",
|
|
}
|
|
annexMarkers = []string{"Anhang", "Annex", "Appendix", "Anlage"}
|
|
operationalMarkers = []string{"Anforderung", "Requirement", "essential", "wesentliche"}
|
|
implMarkers = []string{
|
|
"Good Practice", "Best Practice", "Standards Mapping", "Umsetzung", "Implementation",
|
|
"Handreichung", "Maßnahmenkatalog", "ICS", "SCADA", "Technical Guideline", "TIG",
|
|
}
|
|
definitionMarkers = []string{"Begriffsbestimmung", "Definition"}
|
|
)
|
|
|
|
// classifyRole derives the functional source_role from chunk metadata + the authority
|
|
// class. technical_standard is always a control_standard; guidance splits into
|
|
// implementation_guidance (how-to) vs interpretation (meaning); binding splits into
|
|
// procedural / operational requirement / definition / plain obligation.
|
|
func classifyRole(r LegalSearchResult) string {
|
|
cls := classifyAuthority(r).sourceClass
|
|
hay := strings.ToLower(r.ArticleLabel + " " + r.RegulationShort + " " + r.RegulationName + " " + r.Article)
|
|
switch {
|
|
case r.IsRecital:
|
|
return roleDefinition
|
|
case cls == "technical_standard":
|
|
return roleControlStandard
|
|
case cls == "supervisory_guidance":
|
|
if containsAnyLower(hay, implMarkers) {
|
|
return roleImplGuidance
|
|
}
|
|
return roleInterpretation
|
|
case cls == "binding_law":
|
|
switch {
|
|
case containsAnyLower(hay, definitionMarkers):
|
|
return roleDefinition
|
|
case containsAnyLower(hay, proceduralMarkers):
|
|
return roleProceduralReq
|
|
case containsAnyLower(hay, annexMarkers) || containsAnyLower(hay, operationalMarkers):
|
|
return roleOperationalReq
|
|
default:
|
|
return roleObligation
|
|
}
|
|
default:
|
|
return roleObligation
|
|
}
|
|
}
|
|
|
|
// controlRoleBonus is the soft intra-pool preference (User 2026-06-24):
|
|
// operational_requirement > procedural_requirement > control_standard > implementation_guidance.
|
|
var controlRoleBonus = map[string]float64{
|
|
roleOperationalReq: 0.100,
|
|
roleProceduralReq: 0.075,
|
|
roleControlStandard: 0.050,
|
|
roleImplGuidance: 0.000,
|
|
}
|
|
|
|
// controlPoolGain lifts EVERY control-pool role over the non-control roles (obligation/
|
|
// interpretation/definition) on an implementation question, so the binding abstract
|
|
// obligation does not dominate by authority alone. The obligation is not removed — it
|
|
// stays visible as "Rechtsgrundlage" context below the recommended measures.
|
|
const controlPoolGain = 0.15
|
|
|
|
// applyControlRoles boosts the control-pool (the four implementation roles) for an
|
|
// EXPLICIT implementation question, soft-ordered op_req > procedural > standard > guidance.
|
|
// Replaces the earlier "lift technical_standard above binding" — controls are not only
|
|
// technical_standard, and the binding operational_requirement (e.g. CRA Annex I) should win.
|
|
func applyControlRoles(out []LegalSearchResult) {
|
|
for i := range out {
|
|
if bonus, ok := controlRoleBonus[classifyRole(out[i])]; ok {
|
|
out[i].Score += controlPoolGain + bonus
|
|
}
|
|
}
|
|
}
|