feat(control-pipeline): add anchor backfill endpoint + normalize target_audience
- Add POST /v1/canonical/generate/backfill-anchors endpoint for batch populating open_anchors on controls generated with skip_web_search=true - Uses AnchorFinder Stage A (RAG search) to find OWASP/NIST/ENISA refs - Background job with progress tracking (same pattern as other backfills) - Promotes needs_review controls that gain anchors to draft state - Target audience normalization (enterprise/authority/provider → JSON arrays) already applied via SQL Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1555,14 +1555,15 @@ Gib ein JSON-Array zurueck mit GENAU {len(chunks)} Elementen. Fuer Aspekte ohne
|
||||
final.append(control)
|
||||
continue
|
||||
|
||||
# Anchor search
|
||||
try:
|
||||
from .anchor_finder import AnchorFinder
|
||||
finder = AnchorFinder(self.rag)
|
||||
anchors = await finder.find_anchors(control, skip_web=config.skip_web_search)
|
||||
control.open_anchors = [asdict(a) if hasattr(a, '__dataclass_fields__') else a for a in anchors]
|
||||
except Exception as e:
|
||||
logger.warning("Anchor search failed: %s", e)
|
||||
# Anchor search — skip entirely when skip_web_search=true (backfilled later)
|
||||
if not config.skip_web_search:
|
||||
try:
|
||||
from .anchor_finder import AnchorFinder
|
||||
finder = AnchorFinder(self.rag)
|
||||
anchors = await finder.find_anchors(control, skip_web=False)
|
||||
control.open_anchors = [asdict(a) if hasattr(a, '__dataclass_fields__') else a for a in anchors]
|
||||
except Exception as e:
|
||||
logger.warning("Anchor search failed: %s", e)
|
||||
|
||||
# Release state
|
||||
if control.license_rule in (1, 2):
|
||||
@@ -2402,14 +2403,15 @@ Kategorien: {CATEGORY_LIST_STR}"""
|
||||
control.generation_metadata["similar_controls"] = duplicates
|
||||
return control
|
||||
|
||||
# Stage 5: Anchor Search (imported from anchor_finder)
|
||||
try:
|
||||
from .anchor_finder import AnchorFinder
|
||||
finder = AnchorFinder(self.rag)
|
||||
anchors = await finder.find_anchors(control, skip_web=config.skip_web_search)
|
||||
control.open_anchors = [asdict(a) if hasattr(a, '__dataclass_fields__') else a for a in anchors]
|
||||
except Exception as e:
|
||||
logger.warning("Anchor search failed: %s", e)
|
||||
# Stage 5: Anchor Search — skip entirely when skip_web_search=true (backfilled later)
|
||||
if not config.skip_web_search:
|
||||
try:
|
||||
from .anchor_finder import AnchorFinder
|
||||
finder = AnchorFinder(self.rag)
|
||||
anchors = await finder.find_anchors(control, skip_web=False)
|
||||
control.open_anchors = [asdict(a) if hasattr(a, '__dataclass_fields__') else a for a in anchors]
|
||||
except Exception as e:
|
||||
logger.warning("Anchor search failed: %s", e)
|
||||
|
||||
# Determine release state
|
||||
if control.license_rule in (1, 2):
|
||||
|
||||
Reference in New Issue
Block a user