feat: Silent Knowledge Pass — recognise before asking (Phase 0, before the endpoint)

Not the endpoint yet — the bigger knowledge lever first. The Advisor can say "I need 5 answers" but
does not yet decide what it can find out by ITSELF. The Silent Knowledge Pass runs in front of the
Advisor and, from signals existing scanners/parsers already produce (website, repository, documents,
product data), deterministically derives capabilities the company demonstrably HAS + product facts
that drive scope — so every recognised item shrinks the delta and removes a question.

compliance/onboarding/silent_intake.py: silent_intake(signals, signal_map) -> detected_capabilities
(+ evidence already in hand) + product_facts. The signal->conclusion map is curated DATA
(knowledge/onboarding/intake_signal_map.yaml), signals are injected (scanners are upstream). Pure,
deterministic, no LLM. advisor_start gains detected_capabilities (folded into the profile at HIGH
confidence -> covered, not asked) and an auto_detected result + headline.

The experience flips from a question wall to "we already recognised 4 capabilities, 2 product facts
and have 4 pieces of evidence in hand — only these few remain". Order now: Silent Pass -> #58
endpoint/frontend -> #59 empirical loop. NOT new architecture, just an orchestration step in front.
Non-runtime (no app caller) -> no deploy. 15 onboarding tests pass, mypy --strict clean, check-loc 0.
This commit is contained in:
Benjamin Admin
2026-06-28 14:34:27 +02:00
parent 23d977e26b
commit 9c33582412
8 changed files with 290 additions and 30 deletions
@@ -49,15 +49,21 @@ _GAIN = {"high": 3, "medium": 2, "low": 1}
_RISK = {"high": 2, "medium": 1, "low": 0}
def _profile(inp: OnboardingInput, cert_hypotheses: Dict[str, List[str]]) -> CompanyCapabilityProfile:
def _profile(
inp: OnboardingInput, cert_hypotheses: Dict[str, List[str]],
detected: Optional[Sequence[str]] = None,
) -> CompanyCapabilityProfile:
cmap = {
cert: CapabilityMappingEntry(capability_ids=list(caps), confidence=Confidence.MEDIUM)
for cert, caps in cert_hypotheses.items()
if cert in inp.certifications and caps
}
ctx = CompanyContext(company_id=inp.company or "company",
certifications=[Certification(certification_id=c) for c in cmap])
return build_company_profile(ctx, cmap)
certs = [Certification(certification_id=c) for c in cmap]
if detected: # Silent Pass: concrete findings -> HIGH confidence
cmap["__detected__"] = CapabilityMappingEntry(
capability_ids=list(dict.fromkeys(detected)), confidence=Confidence.HIGH)
certs.append(Certification(certification_id="__detected__"))
return build_company_profile(CompanyContext(company_id=inp.company or "company", certifications=certs), cmap)
def advisor_start(
@@ -68,15 +74,18 @@ def advisor_start(
covers_targets: Optional[Dict[str, List[str]]] = None,
corpus_status: Optional[Dict[str, str]] = None,
uncertain: Optional[List[Dict[str, str]]] = None,
detected_capabilities: Optional[Sequence[str]] = None,
) -> AdvisorResult:
"""Run the onboarding flow: certs -> profile -> delta -> ranked next-best questions + measures.
"""Run the onboarding flow: (silent intake +) certs -> profile -> delta -> ranked questions + measures.
Pure orchestration; deterministic. `cert_hypotheses` (cert -> probable cap ids) and
`target_requirements` are INJECTED. `covers_targets` (cap -> targets it closes) drives leverage.
Pure orchestration; deterministic. `cert_hypotheses` (cert -> probable cap ids), `target_requirements`
and `detected_capabilities` (from the Silent Knowledge Pass) are INJECTED. Detected capabilities are
recognised WITHOUT asking -> they shrink the delta and remove questions.
"""
covers_targets = covers_targets or {}
required = {r.capability_id for r in target_requirements}
profile = _profile(inp, cert_hypotheses)
profile = _profile(inp, cert_hypotheses, detected_capabilities)
auto_detected = sorted(set(detected_capabilities or []) & required)
assess = assess_transition(
TransitionContext(company_id=inp.company or "company", target=TransitionGoal(target_id=target_id)),
list(target_requirements), profile)
@@ -123,13 +132,14 @@ def advisor_start(
rep = assess_completeness(applicable, corpus_status or {}, uncertain=uncertain or [])
unsupported = [e.subject for e in rep.exclusions]
probably = assess.summary.probably_covered
probably = [c for c in assess.summary.probably_covered if c not in set(auto_detected)]
return AdvisorResult(
inferred_assumptions=inferred, rejected_assumptions=rejected, next_best_questions=next_q,
capability_delta=delta, top_measures=measures, evidence_requests=evidence,
unsupported_domains=unsupported, completeness_summary=rep.completeness_summary,
headline="%d Anforderungen erkannt · %d wahrscheinlich abgedeckt · %d zu klären"
% (len(assess.coverage), len(probably), len(next_q)))
inferred_assumptions=inferred, rejected_assumptions=rejected, auto_detected=auto_detected,
next_best_questions=next_q, capability_delta=delta, top_measures=measures,
evidence_requests=evidence, unsupported_domains=unsupported,
completeness_summary=rep.completeness_summary,
headline="%d Anforderungen erkannt · %d automatisch erkannt (Intake) · %d wahrscheinlich (Zertifikate) · %d zu klären"
% (len(assess.coverage), len(auto_detected), len(probably), len(next_q)))
def apply_answer(known_capabilities: Sequence[str], capability_id: str, answer: str) -> List[str]: