feat(product-scope): gate Navigator facts, then reuse discover_scope (step 3)
Connects the Navigator's fact-gate to the existing reasoning discover_scope — the Scope Engine decides only once the minimum (P0) facts are released. - resolve_product_scope(canonical): if not ready_for_scope -> NEEDS_FACTS (missing_facts + suggested_questions, discover_scope NOT run); else project canonical->reasoning profile and run the EXISTING discover_scope exactly once -> RESOLVED with applicable/excluded/uncertain regulations. - Environmental triggers surface ONLY as unsupported_domains (future_corpus_needed), never as a legal evaluation — transparency, no false completeness. - POST /reasoning/product-scope (thin handler) returns case NEEDS_FACTS or RESOLVED. - No new scope rules, no new regulations, no environmental-law evaluation, no UI, no Go, no RAG, no percent-compliance. Response types are application-level, not meta-model classes (freeze v1.0 untouched). - 6 tests incl. discover_scope spy (0 calls when gated, exactly 1 when ready), category separation, environmental-as-unsupported-only. 47 tests green (existing reasoning MVP tests stay green), mypy clean, LOC ok. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
"""Response schemas for the product-scope orchestrator (step 3).
|
||||
|
||||
These are application/API types — NOT compliance-meta-model classes (architecture
|
||||
freeze v1.0 untouched). The scope verdict itself is produced by the existing
|
||||
`discover_scope`; nothing here adds scope rules.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import Enum
|
||||
from typing import List, Optional
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from compliance.navigator.engine import CompletenessSummary
|
||||
from compliance.navigator.questions import NavigatorQuestion
|
||||
from compliance.profile.canonical import CanonicalProductRegulatoryProfile
|
||||
from compliance.reasoning.enums import Confidence
|
||||
from compliance.reasoning.schemas import (
|
||||
ApplicableRegulation,
|
||||
ExcludedRegulation,
|
||||
UncertainRegulation,
|
||||
)
|
||||
|
||||
|
||||
class ScopeStatus(str, Enum):
|
||||
NEEDS_FACTS = "needs_facts" # P0 facts missing -> ask, do not decide
|
||||
RESOLVED = "resolved" # minimum facts present -> scope decided
|
||||
|
||||
|
||||
class UnsupportedDomain(BaseModel):
|
||||
"""A domain the product triggers but the corpus does not yet cover.
|
||||
|
||||
Surfaced for transparency (no false completeness) — NEVER a legal evaluation.
|
||||
"""
|
||||
|
||||
domain: str
|
||||
trigger: str
|
||||
status: str = "future_corpus_needed"
|
||||
note: str = ""
|
||||
|
||||
|
||||
class RegulatoryScopeResult(BaseModel):
|
||||
applicable_regulations: List[ApplicableRegulation] = Field(default_factory=list)
|
||||
excluded_regulations: List[ExcludedRegulation] = Field(default_factory=list)
|
||||
uncertain_regulations: List[UncertainRegulation] = Field(default_factory=list)
|
||||
unsupported_domains: List[UnsupportedDomain] = Field(default_factory=list)
|
||||
reasoning_summary: str = ""
|
||||
confidence: Confidence = Confidence.MEDIUM
|
||||
|
||||
|
||||
class ProductScopeRequest(BaseModel):
|
||||
product_profile: CanonicalProductRegulatoryProfile
|
||||
|
||||
|
||||
class ProductScopeResponse(BaseModel):
|
||||
status: ScopeStatus
|
||||
completeness_summary: CompletenessSummary
|
||||
# case NEEDS_FACTS
|
||||
missing_facts: List[str] = Field(default_factory=list)
|
||||
suggested_questions: List[NavigatorQuestion] = Field(default_factory=list)
|
||||
# case RESOLVED
|
||||
regulatory_scope: Optional[RegulatoryScopeResult] = None
|
||||
Reference in New Issue
Block a user