diff --git a/AGENTS.md b/AGENTS.md index 35a593547..36fac35c8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -95,6 +95,17 @@ in this repo. knowledge-graph pipeline (DOM decomposition, entity/relation extraction, grounded graph retrieval) should ground itself in the relevant layout-analysis and knowledge-graph / grounded-retrieval literature. + +### Structural topic-model boundary + +- Do not implement or describe hard-coded term lists, term frequency, + embeddings, or LLM-assigned labels as structural topic modeling (STM). + Fixed business labels are not topic-posterior estimates, and the explicitly + lexical `keyword_extractor` must not be used as topic evidence. +- Topic inference requires a versioned fitted TEPP model and its frozen + preprocessing and vocabulary contract. If that fitted model is unavailable, + fail closed; do not return a default label, template agenda, or substitute + keyword/embedding/LLM result presented as STM. ## Release governance defaults @@ -425,6 +436,15 @@ in this repo. - Public audit/event identifiers that may use human-readable prefixes must not be stored in artificially short `varchar(n)` columns; use opaque source UIDs that fit seeded smoke data and provider evidence without truncation. +- Conceptual ERDs, API schemas, persistence models, and fixtures must not mark a + reusable business identifier such as `document_ref`, `model_id`, `topic_id`, + or `label_id` as an unscoped primary or foreign key. Use an opaque immutable + reference that binds the full scope or an explicit composite identity with the + applicable snapshot revision, model version, request/result scope, or label + version. Define the required identity tuple for each entity; require only the + dimensions relevant to that entity. Never join snapshots, model artifacts, + topic components, or label evidence by a bare document, model, topic, rank, + label, or display value. - When reviews find public/private identifier leaks, stale API fixture shapes, or recurring bug patterns, update tests, frontend mocks, E2E mocks, README examples, architecture docs, and explicitly record the anti-pattern in `AGENTS.md` so the same bug pattern does not reappear in copied examples. - When reviews find missing browser security headers or tabnabbing hardening, update both backend header tests and frontend link tests. Global backend diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 2139d7984..9d2cbba18 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -20,6 +20,21 @@ Runtime database connectivity is secret-injected: `backend/core/config.py` has no fallback `DATABASE_URL`, so missing database configuration fails at startup rather than silently using shared development credentials. +## Topic-intelligence boundary + +Naruon has no live Structural Topic Modeling endpoint, fitted topic artifact, +or topic-result persistence. The retained `keyword_extractor` is deterministic +lexical metadata and must not feed topic, agenda, search, or norm-group +inference. A future adapter may consume a separately accepted, versioned TEPP +artifact/API only when frozen preprocessing and vocabulary, covariate design, +mixed-membership posterior uncertainty, diagnostics, provenance, and explicit +abstention are all available. Missing or incompatible scientific authority +fails closed. Naruon owns authentication, authorization, request validation, +the adapter envelope, and disclosure policy; TEPP would own the scientific +payload. See the canonical documentation graph in +[`docs/topic-intelligence/README.md`](docs/topic-intelligence/README.md) and +[`ADR-0001`](docs/adr/0001-topic-measurement-authority.md). + ## Workspace navigation boundary The Next.js shell opens the Today execution dashboard for first-run sessions and diff --git a/CHANGELOG.md b/CHANGELOG.md index 3dedb0b53..23ef6f82a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,20 @@ ## [Unreleased] - EmailDetail 테스트가 지원하지 않는 스레드 병합/분리 버튼을 `textContent`뿐 아니라 `aria-label`과 `title` 접근 가능 이름으로도 검출하도록 바꿔, 아이콘 전용 버튼 회귀를 놓치지 않습니다. +### 주제 측정 경계 (Topic Measurement) + +- STM 결과로 오인될 수 있었던 하드코딩 용어표 기반 + `email_categorizer`와 `meeting_agenda_generator`를 도구 레지스트리에서 + 제거했습니다. `keyword_extractor`는 결정론적 단어 빈도 유틸리티로 유지하되 + 주제 posterior 근거로 사용하지 않는 경계를 문서화했습니다. 현재 Naruon에는 + fitted TEPP 모델 기반 production 주제 측정 API가 없으므로, 모델 부재 시 + 기본 라벨이나 템플릿으로 대체하지 않고 fail closed 합니다. +- 이 경계의 PRD, TRD, ADR, Architecture, API 계약, JSON Schema, UML, + 개념 ERD, 보안·위협 모델, 테스트·운영 전략, 추적성 및 문서 적합성 평가를 + `docs/topic-intelligence/`에 하나의 상태 표시 문서 그래프로 정리했습니다. + 이는 미래 계약의 설계 근거이며, 현재 runtime 구현이나 물리 DB 엔터티가 + 존재한다는 주장이 아닙니다. - UUID V4 제너레이터(`uuid_v4_generator`) 도구를 추가하여 런타임에서 범용 고유 식별자 버전 4를 랜덤으로 생성할 수 있게 하였습니다. 테스트 커버리지 100%를 보장합니다. + ### 보안 패치 (CodeQL extended current-head) - `cryptography`를 `50.0.0`으로 갱신해 공격자 제공 PKCS#7 EnvelopedData 복호화 결과의 오류·타이밍 차이로 발생하는 Bleichenbacher oracle(`CVE-2026-69247`, `GHSA-g6cj-pr64-35w5`)을 제거하고, backend·uv lock·hash lock·Strix CI 의존성 증거를 같은 버전으로 동기화했습니다. Strix 잠금은 `google-cloud-aiplatform==1.160.0`의 `<7` 제약을 위반하던 `protobuf==7.35.1`을 이미 검증된 `6.33.6`으로 복구해 다시 해석·설치 가능하게 했습니다. diff --git a/CLAUDE.md b/CLAUDE.md index 896963575..be67bc80c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -146,6 +146,13 @@ Next.js frontend ──> FastAPI backend (control plane) ──> Postgres + pgve test/lint/build), plus `bandit`, `codeql`, `trivy`, `scorecard`, `pr-governance`, `docker-publish` (GHCR on `v*` tags matching `VERSION`), and `mail-smoke`. Actions are pinned to full commit SHAs. +- Topic intelligence is **not implemented**. Never use lexical frequencies, + embeddings, zero-shot labels, or request-time LLM labels as an STM result. + The retained `keyword_extractor` is lexical metadata only. Any future adapter + is blocked on a versioned fitted TEPP artifact/API with frozen preprocessing, + mixed-membership uncertainty and diagnostics; absence or incompatibility + fails closed. Start at `docs/topic-intelligence/README.md` and + `docs/adr/0001-topic-measurement-authority.md`. ## Key conventions diff --git a/README.md b/README.md index a5cc6252f..ff64840e9 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,8 @@ mail/calendar/file systems. ## Quick Links - [Installation & Setup](#five-minute-local-path) - [Architecture](docs/architecture/) +- [Topic-intelligence documentation set](docs/topic-intelligence/README.md) +- [Architecture decisions](docs/adr/README.md) - [Contributing](CONTRIBUTING.md) - [Code of Conduct](CODE_OF_CONDUCT.md) - [Security Policy](SECURITY.md) @@ -48,6 +50,12 @@ mail/calendar/file systems. auto-merge, and mechanical merge actions run as the target repository's `github-actions[bot]` through the central workflow. Pending CodeRabbit or required-check evidence is a wait state, not a hard blocker. +- Topic intelligence is not currently a live Naruon capability. The lexical + `keyword_extractor` is metadata only; Naruon fails closed rather than present + keyword, embedding, or LLM labels as Structural Topic Modeling. The product, + technical, architecture, contract, security, UML, conceptual ERD, test, and + operability records are indexed in + [`docs/topic-intelligence/`](docs/topic-intelligence/README.md). - Security governance is source-backed through signed `/api/security/access-surface`. The endpoint reads scoped WebDAV, CalDAV, and connector evidence plus durable `security_audit_events`, reuses the deny-first diff --git a/backend/api/tools.py b/backend/api/tools.py index 248996af7..bd15abfac 100644 --- a/backend/api/tools.py +++ b/backend/api/tools.py @@ -706,26 +706,6 @@ async def base64_decoder_handler(params: Dict[str, Any]) -> Dict[str, str]: "합니다", } ) -_CATEGORY_TERMS = ( - ("Urgent", ("urgent", "asap", "immediate", "긴급", "시급", "빨리")), - ("Finance", ("invoice", "billing", "payment", "결제", "청구", "송금")), - ("Scheduling", ("meeting", "schedule", "appointment", "회의", "일정", "약속")), -) -_AGENDA_TOPICS = ( - ("Project Status Update", ("project", "프로젝트", "과제")), - ("Discuss Pending Issues", ("issue", "bug", "blocker", "문제", "오류", "장애")), - ("Decisions Required", ("decision", "approve", "결정", "승인")), - ( - "Timeline and Milestones", - ("deadline", "milestone", "timeline", "마감", "기한", "일정"), - ), - ( - "Budget and Resource Review", - ("budget", "cost", "resource", "예산", "비용", "자원"), - ), -) - - def _normalize_analysis_text(value: str) -> str: """Normalize user text for deterministic, multilingual rule matching.""" if len(value) > ANALYSIS_TEXT_MAX_CHARS: @@ -740,44 +720,8 @@ def _analysis_tokens(value: str) -> list[str]: return _ANALYSIS_TOKEN_PATTERN.findall(_normalize_analysis_text(value)) -def _contains_analysis_term(normalized_text: str, term: str) -> bool: - """Match ASCII terms on word boundaries and Korean terms as morpheme stems.""" - normalized_term = _normalize_analysis_text(term) - if normalized_term.isascii(): - pattern = rf"(? Any: - """Categorize email text with deterministic Korean and English rules.""" - content = _normalize_analysis_text(params.get("email_content", "")) - categories = [ - category - for category, terms in _CATEGORY_TERMS - if any(_contains_analysis_term(content, term) for term in terms) - ] - - if not categories: - categories = ["General"] - - return {"categories": categories, "primary_category": categories[0]} - - -registry.register( - ToolInfo( - code="email_categorizer", - name="이메일 자동 분류기 (Email Categorizer)", - description="이메일 내용을 분석하여 알맞은 카테고리로 자동 분류합니다.", - category="이메일 분석", - parameters={"email_content": "string"}, - ), - email_categorizer_handler, -) - - async def keyword_extractor_handler(params: Dict[str, Any]) -> Any: - """Extract stable keywords ranked by frequency and first occurrence.""" + """Extract deterministic lexical terms by frequency and first occurrence.""" candidates = [ token for token in _analysis_tokens(params.get("text", "")) @@ -801,7 +745,7 @@ async def keyword_extractor_handler(params: Dict[str, Any]) -> Any: ToolInfo( code="keyword_extractor", name="주요 키워드 추출기 (Keyword Extractor)", - description="텍스트 본문에서 가장 중요한 키워드를 추출합니다.", + description="텍스트 본문에서 빈도와 최초 출현 순으로 반복 용어를 추출합니다.", category="이메일 분석", parameters={"text": "string"}, ), @@ -809,38 +753,6 @@ async def keyword_extractor_handler(params: Dict[str, Any]) -> Any: ) -async def meeting_agenda_generator_handler(params: Dict[str, Any]) -> Any: - """Generate a deterministic agenda from Korean or English discussion topics.""" - context = _normalize_analysis_text(params.get("discussion_context", "")) - if len(_analysis_tokens(context)) < 2: - return { - "agenda_items": ["Introductions", "Open Discussion"], - "estimated_duration_minutes": 30, - } - - items = ["Review previous action items"] - items.extend( - agenda_item - for agenda_item, terms in _AGENDA_TOPICS - if any(_contains_analysis_term(context, term) for term in terms) - ) - items.append("Next Steps and Action Items") - - return {"agenda_items": items, "estimated_duration_minutes": len(items) * 15} - - -registry.register( - ToolInfo( - code="meeting_agenda_generator", - name="회의 아젠다 생성기 (Meeting Agenda Generator)", - description="논의 컨텍스트를 바탕으로 적절한 회의 아젠다를 자동으로 생성합니다.", - category="일정 관리", - parameters={"discussion_context": "string"}, - ), - meeting_agenda_generator_handler, -) - - async def uuid_v4_generator_handler(params: Dict[str, Any]) -> Dict[str, str]: return {"uuid": str(uuid.uuid4())} @@ -857,6 +769,7 @@ async def uuid_v4_generator_handler(params: Dict[str, Any]) -> Dict[str, str]: ) + @router.get("/tools", response_model=list[ToolInfo]) def get_tools() -> list[ToolInfo]: """ diff --git a/backend/tests/test_tools_api.py b/backend/tests/test_tools_api.py index 8af3435e3..8e537cef7 100644 --- a/backend/tests/test_tools_api.py +++ b/backend/tests/test_tools_api.py @@ -112,6 +112,21 @@ def test_get_tool_not_found(): assert response.json() == {"detail": "Tool not found"} +@pytest.mark.parametrize( + "tool_code", ["email_categorizer", "meeting_agenda_generator"] +) +def test_registry_omits_lexical_pseudo_topic_tools(tool_code): + assert registry.get(tool_code) is None + + +def test_keyword_extractor_is_disclosed_as_lexical_term_frequency(): + tool = registry.get("keyword_extractor") + assert tool is not None + assert tool.description == ( + "텍스트 본문에서 빈도와 최초 출현 순으로 반복 용어를 추출합니다." + ) + + @pytest.mark.asyncio async def test_execute_tool_success(): with TestClient(app) as client: @@ -1155,52 +1170,6 @@ def test_detect_text_language_ko(): assert _detect_text_language("안녕하세요") == "ko" -@pytest.mark.asyncio -async def test_email_categorizer_handler(): - from api.tools import email_categorizer_handler - - # Test Finance category - result = await email_categorizer_handler( - {"email_content": "Please pay this invoice soon."} - ) - assert "Finance" in result["categories"] - - # Test Scheduling category - result = await email_categorizer_handler( - {"email_content": "Let's schedule a meeting."} - ) - assert "Scheduling" in result["categories"] - - # Test Urgent category - result = await email_categorizer_handler({"email_content": "This is urgent!"}) - assert "Urgent" in result["categories"] - - # Test General category (fallback) - result = await email_categorizer_handler({"email_content": "Hello, how are you?"}) - assert "General" in result["categories"] - - # Test multiple categories - result = await email_categorizer_handler( - {"email_content": "URGENT: Meeting to discuss invoice payment"} - ) - assert result == { - "categories": ["Urgent", "Finance", "Scheduling"], - "primary_category": "Urgent", - } - - # ASCII category rules use token boundaries instead of substring matching. - result = await email_categorizer_handler( - {"email_content": "The prepayment plan is documented."} - ) - assert result["categories"] == ["General"] - - # Unicode compatibility forms and Korean stems remain matchable. - result = await email_categorizer_handler( - {"email_content": "긴급 회의에서 청구 금액을 검토합니다."} - ) - assert result["categories"] == ["Urgent", "Finance", "Scheduling"] - - @pytest.mark.asyncio async def test_keyword_extractor_handler(): from api.tools import keyword_extractor_handler @@ -1224,41 +1193,6 @@ async def test_keyword_extractor_handler(): assert empty == {"keywords": [], "keyword_count": 0} -@pytest.mark.asyncio -async def test_meeting_agenda_generator_handler(): - from api.tools import meeting_agenda_generator_handler - - # Test with short context - result = await meeting_agenda_generator_handler({"discussion_context": "short"}) - assert result["agenda_items"] == ["Introductions", "Open Discussion"] - assert result["estimated_duration_minutes"] == 30 - - # Test with project and issue context - result = await meeting_agenda_generator_handler( - {"discussion_context": "The project has an issue that needs fixing."} - ) - assert "Review previous action items" in result["agenda_items"] - assert "Project Status Update" in result["agenda_items"] - assert "Discuss Pending Issues" in result["agenda_items"] - assert "Next Steps and Action Items" in result["agenda_items"] - assert result["estimated_duration_minutes"] == len(result["agenda_items"]) * 15 - - # Korean context covers decision, timeline, and resource agenda paths. - result = await meeting_agenda_generator_handler( - {"discussion_context": "프로젝트 예산 승인과 마감 일정 문제를 결정합니다."} - ) - assert result["agenda_items"] == [ - "Review previous action items", - "Project Status Update", - "Discuss Pending Issues", - "Decisions Required", - "Timeline and Milestones", - "Budget and Resource Review", - "Next Steps and Action Items", - ] - assert result["estimated_duration_minutes"] == 105 - - def test_execute_analysis_tool_rejects_oversized_text(): from api.tools import ANALYSIS_TEXT_MAX_CHARS diff --git a/backend/tests/test_topic_intelligence_documentation.py b/backend/tests/test_topic_intelligence_documentation.py new file mode 100644 index 000000000..f1f930a87 --- /dev/null +++ b/backend/tests/test_topic_intelligence_documentation.py @@ -0,0 +1,256 @@ +"""Machine-check the topic-intelligence documentation authority graph.""" + +from __future__ import annotations + +import json +from pathlib import Path + +REPO_ROOT = Path(__file__).resolve().parents[2] +DOC_ROOT = REPO_ROOT / "docs" / "topic-intelligence" +SCHEMA_PATH = DOC_ROOT / "schema" / "topic-inference-result-v1.schema.json" + +REQUIRED_DOCUMENTS = ( + "README.md", + "PRD.md", + "TRD.md", + "ARCHITECTURE.md", + "UML.md", + "DATA_MODEL.md", + "API_CONTRACT.md", + "SECURITY.md", + "THREAT_MODEL.md", + "TEST_STRATEGY.md", + "OPERABILITY.md", + "TRACEABILITY.md", + "DOCUMENTATION_FITNESS.md", + "REFERENCES.md", +) + + +def _read(path: Path) -> str: + return path.read_text(encoding="utf-8") + + +def test_topic_intelligence_package_is_complete_and_indexed() -> None: + index = _read(DOC_ROOT / "README.md") + + for filename in REQUIRED_DOCUMENTS: + assert (DOC_ROOT / filename).is_file() + if filename != "README.md": + assert f"({filename})" in index + + assert SCHEMA_PATH.is_file() + assert "(schema/topic-inference-result-v1.schema.json)" in _read( + DOC_ROOT / "API_CONTRACT.md" + ) + + +def test_topic_intelligence_package_is_discoverable_from_root_docs() -> None: + for path in ( + REPO_ROOT / "README.md", + REPO_ROOT / "ARCHITECTURE.md", + REPO_ROOT / "CLAUDE.md", + ): + assert "docs/topic-intelligence/" in _read(path) + + +def test_maturity_vocabulary_separates_runtime_truth_from_design() -> None: + index = _read(DOC_ROOT / "README.md") + + for status in ( + "IMPLEMENTED-ON-PROTECTED-DEVELOP", + "ACTIVE-PR", + "ACCEPTED-NARUON-POLICY", + "PLANNED", + "BLOCKED-UPSTREAM", + ): + assert status in index + + assert "not evidence that STM is available in Naruon" in " ".join(index.split()) + + +def test_platform_plan_does_not_claim_live_stm_signals() -> None: + plan = " ".join( + _read(REPO_ROOT / "docs" / "planning" / "naruon-platform-plan.md").split() + ) + + assert "structured topic modeling (STM) feeds search" not in plan + assert "account, STM topic, past patterns" not in plan + assert "PLANNED, not LIVE" in plan + assert "keyword_extractor` is never topic evidence" in plan + + +def test_contract_separates_errors_from_scientific_abstention() -> None: + contract = _read(DOC_ROOT / "API_CONTRACT.md") + normalized_contract = " ".join(contract.split()) + + for status_code in ("`409`", "`422`", "`502`", "`503`"): + assert status_code in contract + assert "`error_code` is a required Naruon extension" in contract + assert "`status=abstained`" in contract + assert "must never return HTTP `200` or `status=abstained`" in normalized_contract + + +def test_uml_and_erd_are_conceptual_and_fail_closed() -> None: + uml = _read(DOC_ROOT / "UML.md") + data_model = _read(DOC_ROOT / "DATA_MODEL.md") + + assert uml.count("```mermaid") >= 4 + assert "no fallback transition" in uml + assert "**Persistence status:** `NOT-APPLICABLE`" in data_model + assert "no Alembic migration is authorized" in data_model + assert data_model.count("```mermaid") >= 3 + + +def test_conceptual_erd_uses_scoped_immutable_identities() -> None: + data_model = _read(DOC_ROOT / "DATA_MODEL.md") + agents = " ".join(_read(REPO_ROOT / "AGENTS.md").split()) + + for scoped_reference in ( + "snapshot_ref PK", + "model_artifact_ref PK", + "component_ref PK", + "label_evidence_ref PK", + ): + assert scoped_reference in data_model + + for unscoped_identity in ( + "string document_ref PK", + "string model_id PK", + "int topic_id PK", + "int topic_id FK", + ): + assert unscoped_identity not in data_model + + assert "must not mark a reusable business identifier" in agents + assert "as an unscoped primary or foreign key" in agents + + +def test_digest_contract_defines_one_schema_digest_and_raw_byte_boundary() -> None: + contract = " ".join(_read(DOC_ROOT / "API_CONTRACT.md").split()) + index = " ".join(_read(DOC_ROOT / "README.md").split()) + + assert "naruon.topic-inference.schema.v1" in contract + assert ( + "the complete parsed JSON value of the immutable schema resource " + "identified by the pinned `$id`" + ) in contract + assert "exactly 14 canonical digest fields" in contract + assert "artifact_digest` binds the fitted-artifact **descriptor**" in contract + assert "do not by themselves verify descriptor truth or completeness" in index + assert "does not add a canonical digest field to this inventory" in index + + +def test_security_treats_every_derived_digest_as_sensitive() -> None: + security = " ".join(_read(DOC_ROOT / "SECURITY.md").split()) + + assert ( + "every content-, evidence-, covariate-, membership-, temporal-, design-, " + "or label-derived digest. Such digests are sensitive pseudonymous linkage " + "values" + ) in security + assert "sensitive pseudonymous linkage values" in security + assert "ecological-fallacy" in security + + +def test_planned_schema_has_closed_revision_and_ownership_metadata() -> None: + schema = json.loads(_read(SCHEMA_PATH)) + + assert schema["$schema"] == "https://json-schema.org/draft/2020-12/schema" + assert "2026-08-09.1" in schema["$id"] + assert schema["x-owner"] == "NARUON" + assert "x-upstream-owner" not in schema + assert schema["x-expected-upstream-producer"] == "TEPP" + assert schema["x-runtime-status"] == "NOT_IMPLEMENTED" + assert schema["x-schema-digest-required"] is True + assert schema["additionalProperties"] is False + assert schema["properties"]["status"]["enum"] == ["inferred", "abstained"] + + +def test_every_typed_schema_object_is_closed() -> None: + schema = json.loads(_read(SCHEMA_PATH)) + + def visit(value: object, location: str) -> None: + if isinstance(value, dict): + if value.get("type") == "object": + assert value.get("additionalProperties") is False, location + for key, child in value.items(): + visit(child, f"{location}/{key}") + elif isinstance(value, list): + for index, child in enumerate(value): + visit(child, f"{location}/{index}") + + visit(schema, "#") + + +def test_schema_requires_input_and_numerical_diagnostics() -> None: + schema = json.loads(_read(SCHEMA_PATH)) + definitions = schema["$defs"] + + input_required = set(definitions["inputDiagnostics"]["required"]) + assert {"retained_token_count", "out_of_vocabulary_ratio"} <= input_required + + posterior_required = set(definitions["posteriorDiagnostics"]["required"]) + assert { + "convergence_code", + "numerical_status", + "quality_codes", + } <= posterior_required + + +def test_schema_declares_required_runtime_cross_field_validation() -> None: + schema = json.loads(_read(SCHEMA_PATH)) + invariants = " ".join(schema["x-runtime-invariants"]) + + for requirement in ( + "fitted_topic_count", + "observed_topic_count", + "number of topic_components", + "snapshot_revision", + "scope_binding_ref", + "availability_time is at or before knowledge_cutoff_time", + "unknown registry version or code is an upstream protocol error", + ): + assert requirement in invariants + + +def test_public_contract_preserves_semantics_and_fail_closed_errors() -> None: + contract = _read(DOC_ROOT / "API_CONTRACT.md") + + for semantic_field in ( + '"model_id"', + '"model_version"', + '"analysis_unit"', + '"estimand_id"', + '"causal_design"', + '"covariate_level"', + ): + assert semantic_field in contract + + for error_code in ( + "topic_authentication_required", + "topic_evidence_forbidden", + "topic_rate_limited", + "topic_upstream_timeout", + "topic_upstream_protocol_error", + ): + assert error_code in contract + + +def test_traceability_covers_every_product_requirement() -> None: + prd = _read(DOC_ROOT / "PRD.md") + traceability = _read(DOC_ROOT / "TRACEABILITY.md") + + for number in range(1, 11): + requirement_id = f"TI-REQ-{number:03d}" + assert requirement_id in prd + assert requirement_id in traceability + + +def test_references_pin_the_inspected_tepp_evidence() -> None: + references = _read(DOC_ROOT / "REFERENCES.md") + + assert "b8e26aae334397daa1974d4a24c9015cfd682600" in references + assert "2026-08-06T11:33:18+09:00" in references + assert "There is no corresponding" in references + assert "production topic-measurement crate or endpoint" in references diff --git a/docs/adr/0001-topic-measurement-authority.md b/docs/adr/0001-topic-measurement-authority.md new file mode 100644 index 000000000..e076ebe72 --- /dev/null +++ b/docs/adr/0001-topic-measurement-authority.md @@ -0,0 +1,77 @@ +# ADR-0001: Naruon-local policy for consuming structural topic measurement + +**Status:** Accepted (Naruon-local consumption policy) +**Date:** 2026-08-09 +**Decision owner:** Naruon maintainers +**Scope:** Naruon's product behavior and any future Naruon adapter. This ADR does not transfer product or scientific authority to TEPP, govern TEPP, or record TEPP's acceptance of a Naruon contract. + +**Related records:** the complete documentation graph is indexed in +[`docs/topic-intelligence/README.md`](../topic-intelligence/README.md). Proposed +implementation decisions are split into [ADR-0002](0002-fitted-topic-artifact-consumption.md) +and [ADR-0003](0003-separate-topic-measurement-from-agenda-generation.md). + +## Upstream direction evidence + +[TEPP's protected-`main` architecture at commit `b8e26aae334397daa1974d4a24c9015cfd682600`](https://github.com/ContextualWisdomLab/TEPP/blob/b8e26aae334397daa1974d4a24c9015cfd682600/ARCHITECTURE.md#bounded-services-and-rust-crates) lists `topic_measurement` and states that its boundaries expose versioned integration contracts. This is direction evidence for Naruon's future-consumption policy only. It is not TEPP's acceptance of this ADR, not a transfer of authority, and not evidence of a production API or contract. + +## Context + +Naruon historically exposed `email_categorizer` and `meeting_agenda_generator` from small hard-coded Korean/English term tables. Those outputs were deterministic, but they were lexical rules presented through product names that implied semantic topic inference. That is not a Structural Topic Model and provides no fitted corpus-level topic identity, mixed-membership posterior, uncertainty, prevalence/content covariate effect, multilingual measurement evidence, or model-artifact provenance. + +The upstream architecture is compatible with a future fitted-model integration, but Naruon has no independently published TEPP production artifact/API/contract to consume today. This ADR therefore makes a local product-truth decision: Naruon will not present lexical, embedding, zero-shot, or LLM output as Structural Topic Modeling (STM), and it will fail closed until a separately accepted upstream contract is available. + +## Decision + +1. Naruon's retained `keyword_extractor` remains explicitly lexical metadata only. It must never be described as a topic model or semantic classifier. +2. Naruon will not replace removed pseudo-topic tools with a larger keyword table, embedding cluster, zero-shot labeler, or LLM prompt while naming the result Structural Topic Modeling. +3. A Naruon adapter remains blocked until TEPP independently publishes a versioned production fitted-model artifact, API, or contract and its own acceptance evidence. If Naruon later chooses to consume that published contract, it must use a stable typed integration boundary and must not refit an STM per request. +4. Naruon's acceptance criteria for any future consumed inference contract include: model artifact/version and digest; immutable source/document identity; frozen preprocessing and vocabulary; OOV/retained-token diagnostics; language profile/support status; relevant prevalence/content and multilevel/cross-classified/multiple-membership covariates; event/document/availability/knowledge-cutoff time semantics when the model uses them; mixed-membership topic proportions; posterior uncertainty/diagnostics; and explicit abstention/failure status. +5. Human-readable topic labels and generated agenda/action summaries are presentation/generation artifacts. They are never the numeric topic identity and cannot change the fitted posterior. +6. If a required published model/API/artifact is unavailable, incompatible, under-supported for the document language, or cannot produce an evidence-valid posterior, Naruon fails closed. It does not fabricate `General`, empty agenda semantics, or an embedding/LLM substitute under the same contract. +7. Naruon remains useful without topic inference. Any future integration is optional and versioned; Naruon must not read an upstream service's private database directly. This ADR imposes no obligations on TEPP. + +## Alternatives rejected + +### Keep deterministic keyword categories + +Rejected because deterministic lexical matching is not mixed-membership topic measurement and would preserve the original product-truth defect. + +### Use embeddings or clustering as a drop-in STM replacement + +Rejected as a semantic product substitution. Such methods may be useful in separate features, but equal semantic usefulness does not make them an STM posterior or preserve the same prevalence/content/uncertainty contract. + +### Ask an LLM for topic labels at request time + +Rejected as the statistical authority. LLMs may interpret or label fitted evidence behind a separate bounded contract, but request-time labels do not replace a fitted corpus-level model and its uncertainty. + +### Fit a fresh topic model for every Naruon request + +Rejected because new-document inference must be comparable against a stable fitted model. Per-request refits destroy topic identity, reproducibility, governance, and longitudinal comparability. + +## Consequences + +- PR #1297 removes the misleading pseudo-topic tools rather than shipping an unvalidated replacement. +- A Naruon adapter cannot be proposed until TEPP independently publishes a versioned production artifact/API/contract and its own acceptance evidence. +- Naruon tests must keep lexical utilities labelled lexical and must fail if removed pseudo-topic registry entries reappear without a locally accepted replacement contract. +- Any future adapter must carry model/provenance/uncertainty/diagnostic fields rather than only a label string. +- Product documentation must distinguish `implemented on protected develop`, + `active PR`, `accepted Naruon-local policy`, `proposed target`, and + `blocked-upstream`; this ADR neither claims that TEPP topic inference exists + today nor that TEPP accepted Naruon's consumption policy. + +## Naruon adapter acceptance criteria + +A future Naruon topic-measurement adapter remains blocked unless TEPP independently publishes a versioned production artifact/API/contract and its own acceptance evidence. Once that upstream precondition exists, Naruon may evaluate an adapter against these local criteria before promoting it to protected `develop`: + +- a published TEPP production artifact/inference API at a versioned contract, plus TEPP's own acceptance evidence; +- fitted-model and preprocessing/vocabulary identity validation; +- positive, negative, OOV/insufficient-text, unsupported-language and model-unavailable tests; +- posterior normalization and uncertainty/diagnostic tests; +- multilevel/multiple-membership and temporal-covariate contract tests when those inputs are part of the fitted model; +- tenant/source authorization at the Naruon boundary; +- exact-head CI/security/coverage and independent review; +- no claim that topic labels or LLM interpretations are the numeric topic identity. + +## Supersession rule + +Changing this Naruon-local consumption policy, changing new-document topic identity semantics, or authorizing Naruon to fit its own production topic models requires a superseding Naruon ADR plus synchronized product/technical/architecture/test/operability documentation and scientific validation evidence. diff --git a/docs/adr/0002-fitted-topic-artifact-consumption.md b/docs/adr/0002-fitted-topic-artifact-consumption.md new file mode 100644 index 000000000..76460ede9 --- /dev/null +++ b/docs/adr/0002-fitted-topic-artifact-consumption.md @@ -0,0 +1,81 @@ +# ADR-0002: Consume only a versioned fitted topic artifact + +**Status:** Proposed + +**Date:** 2026-08-09 + +**Decision owner:** Naruon maintainers + +**Capability maturity:** target `PLANNED`; runtime `BLOCKED-UPSTREAM` + +**Scope:** a possible future Naruon consumption decision only. This ADR does not +assign an external scientific owner, impose obligations on TEPP or another +publisher, or record upstream acceptance. + +**Trigger for acceptance:** an upstream publisher independently releases a +versioned production inference contract and its own acceptance evidence, and +Naruon approves that exact contract in an implementing PR. + +**Related requirements:** [TI-REQ-003, TI-REQ-004, and +TI-REQ-006](../topic-intelligence/PRD.md#product-requirements) + +## Context + +New-document structural topic inference is meaningful only relative to a stable +fitted corpus-level model. A request-time refit, an embedding cluster, a keyword +table, or an LLM label cannot preserve topic identity, covariate design, +uncertainty, or longitudinal comparability. Naruon currently has no production +topic endpoint and no fitted topic artifact to consume. + +## Proposed decision + +Naruon will add no topic adapter until an independently published contract can +bind all of the following in one result: + +- every exact field in the [canonical 14-field digest + inventory](../topic-intelligence/README.md#canonical-digest-inventory), including + the model-card, validation-report, evidence-time-manifest, + covariate-snapshot, and design-row digests; +- immutable source/snapshot, model, artifact, contract, preprocessing, + vocabulary, design, lineage, model-card, and validation-report identity; +- explicit language support, retained-token count, OOV rate, covariate design, + temporal semantics, multilevel and multiple-membership inputs when fitted; +- a mixed-membership topic vector, conditional posterior uncertainty, + convergence/numerical diagnostics, and stable quality codes; and +- an explicit `inferred` or scientifically `abstained` outcome, never a + fabricated default topic. + +This proposed decision assigns only Naruon responsibilities: tenant +authorization, input bounds, disclosure policy, request and response envelopes, +transport resilience, compatibility validation, activation, and error mapping. +Naruon would consume only scientific evidence accompanied by the publisher's +independently issued acceptance evidence; this ADR neither determines who holds +external scientific authority nor delegates Naruon's compatibility decision. +Naruon must not read an upstream private database or refit the model per request. + +Preflight incompatibility is an error: invalid language, insufficient retained +tokens, excessive OOV, missing fitted covariates, or an incompatible design row +returns a stable `422` problem. No active compatible deployment is `503`. +Request/version conflicts are `409`. Only a compatible active model's posterior, +diagnostic, or policy rejection may return HTTP `200` with `status=abstained`. + +## Consequences + +- The proposed schema and HTTP contract are design artifacts, not live API + claims. +- The 14-field digest inventory is a Naruon acceptance profile, not evidence of + upstream adoption, scientific validity, retained objects, or replayability. +- Any implementation requires a superseding or acceptance edit to this ADR, + an upstream compatibility fixture, tenant-boundary tests, scientific + calibration evidence, and exact-head CI/security review. +- Absence, incompatibility, malformed results, or upstream failure remains a + visible fail-closed condition. + +## Alternatives rejected + +- **Per-request fitting:** destroys stable topic identity and is operationally + unbounded. +- **Keyword/embedding/LLM substitution:** may support separately named product + features but is not the same estimand. +- **Best-effort fallback:** converts missing scientific evidence into false + certainty. diff --git a/docs/adr/0003-separate-topic-measurement-from-agenda-generation.md b/docs/adr/0003-separate-topic-measurement-from-agenda-generation.md new file mode 100644 index 000000000..ec2bcda7d --- /dev/null +++ b/docs/adr/0003-separate-topic-measurement-from-agenda-generation.md @@ -0,0 +1,63 @@ +# ADR-0003: Separate topic measurement from agenda generation + +**Status:** Proposed + +**Date:** 2026-08-09 + +**Decision owner:** Naruon maintainers + +**Capability maturity:** target and future agenda capability `PLANNED`; no +implementation is authorized + +**Scope:** a possible future Naruon agenda-generation decision only. This ADR +does not govern a model or generation provider, assign external ownership, or +record provider acceptance. + +**Trigger for acceptance:** a separately reviewed agenda-generation product +contract and implementation PR. + +**Related requirement:** +[TI-REQ-009](../topic-intelligence/PRD.md#product-requirements) + +## Context + +The removed `meeting_agenda_generator` mapped words directly to a fixed agenda +template. That coupled a lexical trigger, an implied topic assertion, and a +generated action artifact. Even a valid fitted topic posterior would be +descriptive evidence, not authorization to create or execute an agenda. + +## Proposed decision + +ADR-0001 already supplies the accepted Naruon-local separation policy. This ADR +is the proposed implementation decision for a future bounded agenda capability; +it remains a proposed target rather than accepted architecture. If Naruon +reintroduces agenda generation, the Naruon capability must: + +1. consume tenant-authorized source evidence and, optionally, a versioned topic + posterior by reference; +2. preserve every cited source and model provenance field without converting a + display label into numeric topic identity; +3. declare the generation provider/model and return `review_required=true`; +4. treat source text, labels, and posterior metadata as untrusted data rather + than instructions; and +5. create no calendar/task/provider write unless a separate explicit intent, + capability, consent, and conflict check succeeds. + +The generator may abstain or fail, but it must not fall back to a template and +describe that output as source-backed topic inference. + +## Consequences + +- A statistical posterior can inform a draft but never authorizes a write. +- Human-readable labels are presentation metadata and can be revised without + changing the fitted topic identity. +- Agenda quality, grounding, prompt-injection resistance, and provider-write + safety require tests independent of topic-model validation. + +## Alternatives rejected + +- **One endpoint for measurement and generation:** obscures error ownership and + makes a generative failure look like scientific inference. +- **Template fallback:** recreates the misleading behavior removed by PR #1297. +- **Direct provider write:** bypasses Naruon's source, consent, capability, and + conflict boundaries. diff --git a/docs/adr/README.md b/docs/adr/README.md new file mode 100644 index 000000000..097ff8598 --- /dev/null +++ b/docs/adr/README.md @@ -0,0 +1,29 @@ +# Naruon Architecture Decision Records + +This index records cross-cutting Naruon decisions that must survive beyond an +individual pull request, implementation plan, or chat. `Accepted` means only that +the Naruon decision governs its stated local scope; it does not transfer authority +to an external service or mean a future integration is implemented on protected +`develop`. `Proposed` records a discoverable target for later review and does not +govern implementation. + +| ADR | Decision | Status | Capability effect | +|---|---|---|---| +| [ADR-0001](0001-topic-measurement-authority.md) | Naruon-local policy for consuming structural topic measurement, never a keyword/label heuristic | Accepted | `ACCEPTED-NARUON-POLICY`; no runtime promotion | +| [ADR-0002](0002-fitted-topic-artifact-consumption.md) | Conditionally consume only a versioned fitted topic artifact through a fail-closed adapter | Proposed | Target `PLANNED`; runtime `BLOCKED-UPSTREAM` | +| [ADR-0003](0003-separate-topic-measurement-from-agenda-generation.md) | Keep statistical measurement separate from agenda generation | Proposed | Target and future capability `PLANNED`; no implementation authorization | + +The complete topic-intelligence requirements, architecture, contract, UML, +conceptual ERD, security, test, and operability graph is indexed at +[`docs/topic-intelligence/README.md`](../topic-intelligence/README.md). +Its [canonical digest inventory](../topic-intelligence/README.md#canonical-digest-inventory) +is the single cross-document list for the planned adapter profile. + +## Change rule + +Create or update an ADR when a Naruon change adopts or declines an external service contract, introduces a new scientific/statistical inference contract, changes persistence or tenant authority, changes model/credential trust boundaries, or replaces a fail-closed product capability with a different production dependency. A Naruon ADR records Naruon's decision only; it cannot assign authority to, or accept a decision for, another service. + +Every implementing PR must keep the corresponding source, tests, doctoring, +architecture/operability contract, and CHANGELOG maturity truthful. An active PR, +accepted local policy, or proposed target must not be described as protected- +branch implementation before it is integrated and independently verified. diff --git a/docs/doctoring/structural-topic-model-boundary.md b/docs/doctoring/structural-topic-model-boundary.md new file mode 100644 index 000000000..307856433 --- /dev/null +++ b/docs/doctoring/structural-topic-model-boundary.md @@ -0,0 +1,94 @@ +# Structural topic-model boundary + +**Architecture decision:** [`ADR-0001`](../adr/0001-topic-measurement-authority.md) defines Naruon's local policy for truthful topic-measurement consumption. This doctoring record supplies the scientific rationale and evidence; neither record assigns authority to TEPP, records TEPP acceptance, or promotes a future integration to protected-branch implementation. + +## Defect record + +Naruon previously exposed `email_categorizer` and +`meeting_agenda_generator`, whose outputs came from small hard-coded +Korean/English term lists rather than a fitted topic model. The traceable record +is deliberately narrow: commit +`c070c8d19f01ccfe46a5ee7e8a577b08e587bb14` described basic length/keyword +parsing and a 100%-coverage goal; commit +`699d7ef9d1285c8c2c5a1a38c6732117d0ff703e` made the tables deterministic; +commit `11a329fa3950a529d3df607e33ae09f55117a09d` established the later bound; and +the first merge to `develop` was +`eae74e215d99af49764a765b74e9679037b8fbbe` (PR #1075). These facts describe +the observable history, not unrecorded author intent. + +The two pseudo-model tools are now removed on PR #1297. `keyword_extractor` +remains because it honestly exposes deterministic term-frequency extraction. Its +output is lexical metadata, not topic-posterior evidence. Until PR #1297 merges, +this removal remains active-PR behavior rather than a protected-`develop` claim. + +## Measurement boundary + +Structural topic modeling estimates a mixed-membership vector +\(\theta_d\) for each document: multiple latent topics can contribute to one +document, and metadata may affect topic prevalence or content. A fixed-label +classifier instead selects or scores predefined business labels. Even when a +classifier uses keywords, embeddings, or an LLM, its label or score is not an +STM posterior and must not be presented as one. + +New-document STM inference also depends on a fitted corpus-level model and its +frozen vocabulary and preprocessing. Naruon must not fit a topic model inside a +single API request, substitute a larger dictionary, or degrade to embeddings or +LLM labels while calling the result STM. + +## Potential future Naruon consumption + +Naruon has no independently published TEPP production topic-measurement +artifact/API/contract or TEPP acceptance evidence to consume. The present change +therefore fails closed: when a fitted model is unavailable, no default `General` +label, agenda template, or synthetic posterior is returned. + +A future Naruon adapter remains blocked until TEPP independently publishes a +versioned production fitted-model artifact/API/contract and its own acceptance +evidence. If Naruon later evaluates such a published contract, its local +acceptance criteria include: + +- immutable document and model-artifact identifiers, model version, and content + and vocabulary digests; +- document, event, assertion, availability, and knowledge-cutoff times; +- frozen preprocessing, retained-token rules, frozen vocabulary, explicit OOV + handling, and language identification/support status; +- prevalence/content design specifications and relevant multilevel or + cross-classified multiple-membership covariates; +- mixed-membership topic proportions summing to one, inference method, + posterior uncertainty, diagnostics, and explicit abstention criteria/status; +- evidence-backed human-readable labels kept separate from numeric topic + identity; and +- explicit model-unavailable, incompatible-language, insufficient-retained- + token, and out-of-vocabulary errors. + +The integration is an optional typed service/model-artifact boundary. Naruon must +not read TEPP's private database, infer model compatibility from a display label, +or persist a generated human-readable topic label as the numeric topic identity. + +Agenda generation, if reintroduced, belongs behind a separate decision and +generation boundary that consumes source evidence and the versioned posterior. +No copyrighted paper is attached here; redistribution permission has not been +established. + +## References + +Roberts, M. E., Stewart, B. M., & Tingley, D. (2019). stm: An R package for +structural topic models. *Journal of Statistical Software, 91*(2), 1–40. +https://doi.org/10.18637/jss.v091.i02 + +This paper specifies the fitted STM workflow, prevalence/content covariates, +posterior quantities, and diagnostics implemented by the `stm` package. It +supports the boundary because a term lookup lacks those fitted-model and +uncertainty semantics. + +Roberts, M. E., Stewart, B. M., Tingley, D., Lucas, C., Leder-Luis, J., +Gadarian, S. K., Albertson, B., & Rand, D. G. (2014). Structural topic models +for open-ended survey responses. *American Journal of Political Science, +58*(4), 1064–1082. https://doi.org/10.1111/ajps.12103 + +This paper introduces STM for open-ended responses and demonstrates how +document metadata enters topic prevalence/content while documents remain mixed +memberships. It supports separating corpus-level measurement from fixed-label +classification. Redistribution permission for either article has not been +established, so this PR cites, links, and summarizes them without committing +copies. diff --git a/docs/planning/naruon-platform-plan.md b/docs/planning/naruon-platform-plan.md index 9e7cdfaa0..7e93ecbd2 100644 --- a/docs/planning/naruon-platform-plan.md +++ b/docs/planning/naruon-platform-plan.md @@ -56,7 +56,12 @@ Every commitment carries a status on the axis **{confirmed | tentative | desired Contexts — **personal / work→{former employer, current employer} / per-project / per-band** — are **segregated by default**, classified by **content, not by account**. A private fact may affect another context only by propagating the **necessary consequence** (e.g., *"unavailable Tue–Thu"*), **never the private reason** (e.g., *"hospitalized"*). The user controls the disclosure level per bridge (minimum by default); data minimization and purpose limitation are enforced structurally at the boundary; every bridge is consent-gated, revocable, and audited. **Two further constants** apply everywhere and are folded into the above: -- **Language-agnostic (G6):** entity/relation extraction, resolution, and search work consistently across EN/KO/JA/ZH/VI via LLM extraction + multilingual embeddings + cross-lingual structured topic modeling — **no dependency on morphological analyzers** (Kiwi/Nori-style), which cause performance cliffs. +- **Language-agnostic (G6):** entity/relation extraction, resolution, and search + work consistently across EN/KO/JA/ZH/VI through language-agnostic lexical and + multilingual dense retrieval plus source-backed extraction — **no dependency + on morphological analyzers** (Kiwi/Nori-style), which cause performance + cliffs. Cross-lingual structural topic measurement is a **PLANNED** optional + TEPP integration, not a current Naruon search or inference signal. - **À-la-carte plugins:** nothing is mandatory; verticals/capabilities slot into fixed extension points; a user's enabled set reshapes their navigation. **Cross-cutting definition of done.** A unit of work is "done" only when it demonstrably honors all disciplines together: the happy path asked **zero questions** (CP-2); **no confirmed commitment was silently broken** (CP-4); every inference was made and labeled at the **correct level of analysis** (CP-3); and **no private reason crossed a context boundary** — only the necessary consequence, with consent and audit (CP-5). @@ -694,12 +699,12 @@ Hooks are **typed and ordered** (each point has a Pydantic input/output contract **Node & edge taxonomy.** Node types: `person`, `org`, `norm_group`, `project` (incl. Band), `thread`, `message`, `attachment`, `content_node`, `event`, `commitment`, `deliverable`, `requirement`, `wbs_item`, `erd_candidate`. Edge axes (density comes from many *simultaneous* relation axes): Social (`person—person`, `person—org`, `person—norm_group` **multi-membership**), Communication (`message—thread`, in_reply_to/references, sender/recipient), Temporal/event (`event—event` **enables/conflicts/unrelated**, resolved by density not asking; `event—commitment`), Commitment (status axis {confirmed|tentative|desired} + RSVP direction), Provenance (`object—content_segment` cited evidence, `object—extractor`, `correction—object`). Every semantic node/edge stores `confidence`, `extractor_name`, `extractor_version`, and cited `source_segment_uids` — auditable back to the exact DOM segment. -**Language-agnostic extraction.** LLM-based entity/relation extraction (via contextual-orchestrator) replaces today's deterministic rule extractor; extractors register through `kg.extractor`, emit candidates with confidence, cite segments; deterministic rules remain as a cheap first pass / offline-deterministic test fallback. Multilingual embeddings + subword/byte tokenization; **no morphological-analyzer dependency** (Kiwi/Nori cause performance cliffs); cross-lingual **structured topic modeling (STM)** feeds search and norm-group inference. Attachment DOM parsing is first-class (PDF→DOM via newsdom-api / MinerU Apache-2.0; audio/video via codec-carver), parsed into the same content_node/segment space so extraction and search treat body and attachment uniformly. +**Language-agnostic extraction.** LLM-based entity/relation extraction (via contextual-orchestrator) replaces today's deterministic rule extractor; extractors register through `kg.extractor`, emit candidates with confidence, cite segments; deterministic rules remain as a cheap first pass / offline-deterministic test fallback. Multilingual embeddings + subword/byte tokenization; **no morphological-analyzer dependency** (Kiwi/Nori cause performance cliffs). Cross-lingual **structural topic measurement is PLANNED, not LIVE**: it may feed search or norm-group research only after a separately accepted TEPP fitted artifact/API publishes frozen preprocessing and vocabulary, applicable multilevel/multiple-membership and temporal covariates, mixed-membership uncertainty and diagnostics, and fail-closed compatibility rules. The lexical `keyword_extractor` is never topic evidence. Attachment DOM parsing is first-class (PDF→DOM via newsdom-api / MinerU Apache-2.0; audio/video via codec-carver), parsed into the same content_node/segment space so extraction and search treat body and attachment uniformly. **Hybrid dense + sparse search.** LIVE: `api/search.hybrid_search` combines Postgres FTS (`to_tsvector`/`ts_rank_cd`) with pgvector `cosine_distance` (`_search_score = fts_score − vector_distance`), degrading gracefully to FTS-only; scoped over `email_records`/`email_attachments` bodies. TARGET: extend to `content_segments` and typed `project_graph_objects` (search the *meaning*); expose rank fusion (e.g., reciprocal-rank fusion) as a `search.ranker` extension point; move embedding from **inline per-import** to a **batch embedding pipeline** driven by contextual-orchestrator / pg-llm-batch (`batch_embedding_service` does not exist today) so re-embedding and high-volume ingest don't block the ingest transaction. **The Inference Layer** (turns a dense graph into judgment; where the architect-level rigor lives): -- **Norm-group resolution (before any inference)** — resolve which norm-group(s) an interaction belongs to by graph evidence (sender's `member_of` edges, thread project scope, account, STM topic, past patterns); a person is in **N overlapping groups** → a weighted set, not a label; all downstream norms evaluated relative to the resolved group(s). +- **Norm-group resolution (before any inference)** — resolve which norm-group(s) an interaction belongs to by implemented graph evidence (sender's `member_of` edges, thread project scope, account, past patterns); a person is in **N overlapping groups** → a weighted set, not a label; all downstream norms evaluated relative to the resolved group(s). A future fitted topic posterior may become an additional, non-causal signal only after the separately governed contract in `docs/topic-intelligence/` is implemented and validated; no lexical substitute is permitted. - **Ecological-fallacy-safe estimation** — `posterior ∝ prior(norm_group) × likelihood(individual content)`; never report a group base rate as an individual's property, never generalize an individual to their group; confidence is honest and propagated. - **Status-weighted conflict detection** — event↔event conflicts resolved by density (travel time, venue vs hotel, host = partner vs work), not by asking; `confirmed` wins and is never silently broken; `desired` over `confirmed` = surfaced conflict; RSVP direction matters; e-approval outcomes are first-class KG events linked to the events they enable (anticipatory). - **Output contract** — never a question; emits a **DecisionPoint** (resolved connection + recommendation + cited evidence + honest confidence, rendered by `DecisionPointCard.tsx`); the human corrects by exception; corrections land in `project_graph_corrections` and become training signal + higher-priority evidence. @@ -840,4 +845,4 @@ The single highest-leverage move: the semantic graph exists but is empty. ### Phase 5 — Verticals 15. **BandScope** (the flagship UC-09 demo — reuses Phase 3's conflict engine + Phase 4's per-band isolation), then **pg-erd-cloud**, **scopeweave**, **Inkspan**, **codec-carver** (audio minutes), plus **legal/contract** and **code-integration** capabilities — all as à-la-carte plugins on the Phase 1 SDK. -**Cross-cutting throughout every phase:** deepen OpenTelemetry distributed tracing and KG-quality/inference-confidence metrics; enforce the licensing gate (permissive-only), 2+word `snake_case` for new DB objects, and KV-registry (not `os.getenv`) secrets; route all LLM traffic through contextual-orchestrator; and hold the four disciplines (no-ask, status-weighted, ecological-fallacy-safe, minimal-disclosure) as the definition of done for every unit of work. \ No newline at end of file +**Cross-cutting throughout every phase:** deepen OpenTelemetry distributed tracing and KG-quality/inference-confidence metrics; enforce the licensing gate (permissive-only), 2+word `snake_case` for new DB objects, and KV-registry (not `os.getenv`) secrets; route all LLM traffic through contextual-orchestrator; and hold the four disciplines (no-ask, status-weighted, ecological-fallacy-safe, minimal-disclosure) as the definition of done for every unit of work. diff --git a/docs/superpowers/plans/2026-08-09-structural-topic-boundary.md b/docs/superpowers/plans/2026-08-09-structural-topic-boundary.md new file mode 100644 index 000000000..1dd902260 --- /dev/null +++ b/docs/superpowers/plans/2026-08-09-structural-topic-boundary.md @@ -0,0 +1,228 @@ +# Structural Topic Boundary Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Remove keyword-triggered pseudo-topic classification from Naruon's +product surface and document the fail-closed TEPP STM boundary. + +**Architecture:** Naruon's generic tool registry will retain honest lexical +utilities but will no longer expose fixed dictionaries as topic inference or +agenda generation. Corpus-level STM remains an external Rust-first TEPP +measurement boundary whose future posterior contract is documented here rather +than simulated in the request handler. + +**Tech Stack:** Python 3.12+, FastAPI tool registry, pytest, Ruff, Markdown. + +## Global Constraints + +- Do not introduce keyword, embedding, or LLM fallback topic classification. +- Do not claim that a fixed business label is an STM posterior probability. +- Preserve `ANALYSIS_TEXT_MAX_CHARS` enforcement for the retained lexical tool. +- Treat every warning as a verification failure. +- Keep the change atomic and avoid unrelated tool-registry refactoring. + +- [x] **Preflight: read the repository root `AGENTS.md` completely before any + change.** + +--- + +### Task 1: Lock out lexical pseudo-topic tools + +**Files:** +- Modify: `backend/tests/test_tools_api.py` +- Modify: `backend/api/tools.py` + +**Interfaces:** +- Consumes: the existing module-level `registry: ToolRegistry`. +- Produces: a registry without `email_categorizer` or + `meeting_agenda_generator`; `keyword_extractor` remains registered and is + explicitly described as term-frequency extraction. + +- [x] **Step 1: Write the failing registry-contract test** + +```python +@pytest.mark.parametrize( + "tool_code", ["email_categorizer", "meeting_agenda_generator"] +) +def test_registry_omits_lexical_pseudo_topic_tools(tool_code): + assert registry.get(tool_code) is None + + +def test_keyword_extractor_is_disclosed_as_lexical_term_frequency(): + tool = registry.get("keyword_extractor") + assert tool is not None + assert tool.description == ( + "텍스트 본문에서 빈도와 최초 출현 순으로 반복 용어를 추출합니다." + ) +``` + +- [x] **Step 2: Run the focused tests and verify RED** + +Run: +`PYTHONWARNINGS=error DISABLE_BACKGROUND_WORKERS=1 python -m pytest backend/tests/test_tools_api.py::test_registry_omits_lexical_pseudo_topic_tools backend/tests/test_tools_api.py::test_keyword_extractor_is_disclosed_as_lexical_term_frequency -q` + +Expected: the first test fails because both pseudo-topic tools are registered; +the second fails because the current description overclaims importance. + +- [x] **Step 3: Remove the pseudo-model implementation** + +Delete `_CATEGORY_TERMS`, `_AGENDA_TOPICS`, `_contains_analysis_term`, both +handlers, both `registry.register(...)` blocks, and their behavior-locking tests. +Change the retained handler docstring to +`"""Extract deterministic lexical terms by frequency and first occurrence."""` +and its tool description to +`"텍스트 본문에서 빈도와 최초 출현 순으로 반복 용어를 추출합니다."`. + +- [x] **Step 4: Run the focused test file and verify GREEN** + +Run: +`PYTHONWARNINGS=error DISABLE_BACKGROUND_WORKERS=1 python -m pytest backend/tests/test_tools_api.py -q` + +Expected: all tests pass with no warning-class output. + +### Task 2: Record the scientific and governance boundary + +**Files:** +- Modify: `AGENTS.md` +- Modify: `CHANGELOG.md` +- Modify: `docs/adr/README.md` +- Create: `docs/adr/0001-topic-measurement-authority.md` +- Create: `docs/doctoring/structural-topic-model-boundary.md` + +**Interfaces:** +- Consumes: the decision in + `docs/superpowers/specs/2026-08-09-structural-topic-boundary-design.md`. +- Produces: a durable anti-pattern rule, user-visible change record, and APA 7 + research note. + +- [x] **Step 1: Add the anti-pattern rule** + +State that topic inference must not be implemented with hard-coded term lists, +term frequency, embeddings, or LLM labels presented as STM; unavailability of a +fitted TEPP model must fail closed. + +- [x] **Step 2: Add the changelog entry** + +Under the current unreleased section, record removal of the two misleading tools +and preservation of the honest lexical utility. + +- [x] **Step 3: Add the doctoring note** + +Document the defect history, distinction between STM and classification, future +TEPP contract, and APA 7 references. Check redistribution permission for each +relevant paper: commit the PDF only when redistribution is permitted; otherwise +include its citation, DOI link, and a concise summary of how it supports the +boundary. Permission was not established for the two cited articles, so this PR +uses citations, links, and summaries rather than copies. + +### Task 3: Complete the decision-to-operation documentation graph + +**Files:** +- Modify: `README.md`, `ARCHITECTURE.md`, `CLAUDE.md`, `CHANGELOG.md` +- Modify: `docs/planning/naruon-platform-plan.md` +- Modify: the boundary design and this implementation plan +- Create: companion ADRs and `docs/topic-intelligence/` requirements, + architecture, contract/schema, UML, conceptual ERD, security, threat, test, + operability, traceability, references, and fitness records +- Create: `backend/tests/test_topic_intelligence_documentation.py` + +- [x] **Step 1: Audit the pre-change documentation set** + +Record whether each requested artifact exists, is discoverable, is internally +consistent, and distinguishes implemented behavior from a planned contract. + +- [x] **Step 2: Add the missing or stale records** + +Make the deletion decision reviewable and the future integration discoverable, +without claiming a runtime endpoint, physical topic persistence, accepted TEPP +contract, or reproducible replay where only digests are available. + +- [x] **Step 3: Add machine-readable fitness checks** + +Validate required files and links, schema revision/ownership/status markers, +error-versus-abstention semantics, conceptual-only data modeling, sensitive +digest treatment, and removal of stale platform-plan claims. + +### Task 4: Verify and publish + +**Files:** +- Verify all files changed by Tasks 1 and 2. + +**Interfaces:** +- Consumes: the completed atomic diff. +- Produces: exact local evidence and a GitHub pull request based on the current + `develop` head. + +- [x] **Step 1: Run Ruff** + +Run: +`python -m ruff check backend/api/tools.py backend/tests/test_tools_api.py backend/tests/test_topic_intelligence_documentation.py` + +Expected: exit 0 and no diagnostics. + +Observed on the complete candidate tree: Ruff passed for the affected tool and +documentation-fitness test files. + +- [x] **Step 2: Run the complete backend suite** + +Run: +`PYTHONWARNINGS=error DISABLE_BACKGROUND_WORKERS=1 python -m pytest backend -q` + +Expected: exit 0 with no `Timeout`, `Fatal`, `Warn`, or `Denied` output. + +Observed after merging the protected-base security remediation, with proxy +variables removed: `1711 passed, 33 skipped`; the focused tool/documentation +suite reported `79 passed`. + +- [x] **Step 3: Inspect the exact diff** + +Run: `git diff --check`, `git diff --stat`, and compare the exact changed paths +against this allowlist (including every file under `docs/topic-intelligence/`): + +```text +AGENTS.md +ARCHITECTURE.md +CHANGELOG.md +CLAUDE.md +README.md +backend/api/tools.py +backend/tests/test_tools_api.py +backend/tests/test_topic_intelligence_documentation.py +docs/adr/0001-topic-measurement-authority.md +docs/adr/0002-fitted-topic-artifact-consumption.md +docs/adr/0003-separate-topic-measurement-from-agenda-generation.md +docs/adr/README.md +docs/doctoring/structural-topic-model-boundary.md +docs/planning/naruon-platform-plan.md +docs/superpowers/plans/2026-08-09-structural-topic-boundary.md +docs/superpowers/specs/2026-08-09-structural-topic-boundary-design.md +docs/topic-intelligence/API_CONTRACT.md +docs/topic-intelligence/ARCHITECTURE.md +docs/topic-intelligence/DATA_MODEL.md +docs/topic-intelligence/DOCUMENTATION_FITNESS.md +docs/topic-intelligence/OPERABILITY.md +docs/topic-intelligence/PRD.md +docs/topic-intelligence/README.md +docs/topic-intelligence/REFERENCES.md +docs/topic-intelligence/SECURITY.md +docs/topic-intelligence/TEST_STRATEGY.md +docs/topic-intelligence/THREAT_MODEL.md +docs/topic-intelligence/TRACEABILITY.md +docs/topic-intelligence/TRD.md +docs/topic-intelligence/UML.md +docs/topic-intelligence/schema/topic-inference-result-v1.schema.json +``` + +Expected: no whitespace errors; only the scoped source, tests, governance, and +research/design documents changed. + +Observed: `git diff --check` passed and the complete base-to-candidate plus +working-tree path set exactly matched all 31 allowlisted paths. + +- [x] **Step 4: Commit and open a pull request** + +The predecessor source/test head passed local validation and PR #1297 was +opened. Its body must distinguish predecessor evidence from eventual exact-head +evidence and link the current-head CI, security, and review results before +merge. Push documentation and review fixes to the same +`fix/remove-lexical-topic-heuristics` branch; do not open a duplicate PR. diff --git a/docs/superpowers/specs/2026-08-09-structural-topic-boundary-design.md b/docs/superpowers/specs/2026-08-09-structural-topic-boundary-design.md new file mode 100644 index 000000000..0e68e4778 --- /dev/null +++ b/docs/superpowers/specs/2026-08-09-structural-topic-boundary-design.md @@ -0,0 +1,127 @@ +# Structural Topic Boundary Design + +**Status:** Active PR deletion design for PR #1297; removal is not +protected-`develop` behavior until merge. This file's former future-integration +summary is `SUPERSEDED` by the canonical package below. The Naruon-local policy +is accepted, the target decisions remain proposed, and runtime topic inference +is `BLOCKED-UPSTREAM` and unimplemented. + +**Canonical documentation graph:** +[`docs/topic-intelligence/README.md`](../../topic-intelligence/README.md) + +That package and its linked ADR index govern maturity, authority, requirements, +the 14-field digest inventory, and error-versus-abstention semantics. This legacy +design remains useful for the deletion history only. It does not assign product +or scientific authority to TEPP or another producer, impose an external +obligation, record upstream acceptance, or establish a production contract. + +## Context + +Protected `develop` exposes `email_categorizer` and +`meeting_agenda_generator` as analysis tools, but both derive their outputs from +small hard-coded Korean/English term lists. The behavior entered in commit +`c070c8d19f01ccfe46a5ee7e8a577b08e587bb14` as demonstration logic and was +later made deterministic and better tested without correcting the underlying +measurement error. The tests consequently canonized lexical hits as topic +evidence. + +Structural topic modeling (STM) is not fixed-label keyword classification. It +estimates mixed-membership topic proportions over a corpus and can model how +document metadata affects topic prevalence or content. Inference for a new +document requires a previously fitted model and its frozen vocabulary; the +result is a topic mixture with uncertainty, not a calibrated probability for a +business label. + +## Decision + +1. Remove `email_categorizer` and `meeting_agenda_generator` from Naruon's tool + registry. They have no callers outside their own tests, so removal eliminates + misleading product behavior without breaking an integrated workflow. +2. Remove `_CATEGORY_TERMS`, `_AGENDA_TOPICS`, and the substring matcher that + exists only to support those pseudo-models. +3. Retain `keyword_extractor` as an explicitly lexical utility, but describe it + honestly as deterministic term-frequency extraction. Its output must never + be treated as topic posterior evidence. +4. Do not add an embedding, LLM, or larger dictionary fallback and do not call + any such fallback STM. +5. Keep corpus-level topic estimation outside this Naruon deletion change. + TEPP's Rust-first `topic_measurement` architecture is directional evidence, + not an assignment of authority. Naruon may evaluate any independently + published, compatible fitted-model boundary only after its publisher releases + a versioned, source-backed artifact/inference contract and acceptance evidence, + and Naruon separately accepts the integration. Until then, absence of a fitted + model fails closed rather than returning `General` or a template agenda. + +## Conditional future Naruon acceptance profile + +The accepted local policy is [ADR-0001](../../adr/0001-topic-measurement-authority.md). +The fitted-artifact and agenda target decisions remain proposed in +[ADR-0002](../../adr/0002-fitted-topic-artifact-consumption.md) and +[ADR-0003](../../adr/0003-separate-topic-measurement-from-agenda-generation.md). +The following are conditions Naruon would apply to its own consumption decision; +they do not govern an upstream publisher. + +Any later Naruon integration must bind all 14 exact fields in the canonical +[digest inventory](../../topic-intelligence/README.md#canonical-digest-inventory), +including the schema, source snapshot, complete scientific payload, artifact, +manifest, vocabulary, preprocessing, design, lineage, model card, validation +report, evidence-time manifest, covariate snapshot, and design row. It must also +carry, at minimum: + +- immutable document, snapshot, model, artifact, and contract identities; +- document, event, assertion, availability, and knowledge-cutoff times; +- language and multilevel/cross-classified membership covariates; +- the frozen preprocessing and prevalence/content design specifications; +- topic proportions that sum to one, posterior uncertainty, inference method, + model version, and diagnostic status; +- evidence-backed topic labels kept separate from the numeric topic identity; +- explicit input, incompatibility, integrity, availability, and protocol errors; + and +- `abstained` only for a compatible active model's declared posterior or + diagnostic-policy rejection, never for an error or fallback. + +If Naruon later accepts and implements agenda generation, that capability must +consume authorized source evidence and, optionally, a versioned posterior through +a separate decision/generation boundary. It must not map raw words directly to +agenda templates. Proposed ADR-0003 is not implementation authorization. + +## Alternatives rejected + +- **Expand the dictionaries:** deterministic but still lexical, brittle across + language and domain, and unable to represent mixed membership or uncertainty. +- **Use embeddings or an LLM as a drop-in replacement:** potentially useful for + semantic labeling, but neither is STM and neither supplies the required + corpus-level estimand or covariate effects. +- **Fit a model inside each API request:** statistically invalid for a single + document, operationally expensive, and incompatible with reproducible model + artifacts. + +## Verification + +- The pre-change regression test failed because the two misleading tool codes + were registered. +- On PR #1297, the registry omits both codes while retaining the + explicitly lexical term-frequency utility. +- Focused tools tests, the complete backend test suite with warnings promoted to + errors, and Ruff must pass. + +## References + +Roberts, M. E., Stewart, B. M., & Tingley, D. (2019). stm: An R package for +structural topic models. *Journal of Statistical Software, 91*(2), 1–40. +https://doi.org/10.18637/jss.v091.i02 + +The package paper defines a fitted STM workflow with covariate-aware prevalence +and content, posterior quantities, and diagnostics. Those requirements are why +a deterministic term table cannot satisfy the measurement contract. + +Roberts, M. E., Stewart, B. M., Tingley, D., Lucas, C., Leder-Luis, J., +Gadarian, S. K., Albertson, B., & Rand, D. G. (2014). Structural topic models +for open-ended survey responses. *American Journal of Political Science, +58*(4), 1064–1082. https://doi.org/10.1111/ajps.12103 + +The application paper establishes mixed-membership topics whose prevalence or +content can vary with document metadata. It grounds the design's separation of +corpus-level inference from fixed business labels. Redistribution permission +for either article has not been established; citations, links, and summaries +are supplied instead of paper copies. diff --git a/docs/topic-intelligence/API_CONTRACT.md b/docs/topic-intelligence/API_CONTRACT.md new file mode 100644 index 000000000..6ee28e047 --- /dev/null +++ b/docs/topic-intelligence/API_CONTRACT.md @@ -0,0 +1,361 @@ +# Planned topic-inference API contract + +- **Capability maturity:** `BLOCKED-UPSTREAM` +- **Document status:** `PRESENT-CURRENT` +- **Contract version:** `topic-inference-result-v1` +- **Contract revision:** `2026-08-09.1` + +There is no shipped topic-inference endpoint in Naruon today. This document +defines the Naruon-side contract that may be implemented only after TEPP +independently publishes a compatible production fitted-model artifact and +inference boundary with its own acceptance evidence. + +## Contract layers + +Three representations must not be conflated: + +1. **Authenticated Naruon API.** A browser or API client submits opaque source + references. Naruon reauthorizes and resolves server-authoritative evidence. +2. **Internal Naruon adapter envelope.** Naruon binds an immutable snapshot, + schema/deployment pins, sensitive canonical digests, error mapping, and + scientific acceptance policy. The result schema in this package applies to + this internal envelope. +3. **Expected upstream scientific payload.** Naruon expects the independently + accepted producer to own fitted-model inference evidence nested under + `tepp_payload`. TEPP is only the expected producer if it separately publishes + a compatible production contract and accepts that responsibility; this local + schema does not assign it or claim adoption. + +The authenticated API returns a redacted projection of the internal envelope. +Sensitive digests, tenant/workspace bindings, covariate values, design rows, and +raw evidence locations must not cross the public boundary. + +## Planned endpoint + +`POST /api/topic-intelligence/inferences` + +The route is private and signed-session authenticated. Its final implementation +must scope every lookup by the authenticated owner, organization, and workspace. +Elevated platform roles do not automatically become the mailbox/document owner. + +### Request + +```json +{ + "document_ref": "doc_Q7mWQVp1jJHq5J3e", + "evidence_ref": "ev_q9BH7d4eMG3A2x6n", + "request_revision": "reqrev_01", + "idempotency_key": "a-client-generated-bounded-opaque-value", + "expected_snapshot_revision": "snaprev_184", + "language": "ko-KR", + "purpose": "topic_assistance" +} +``` + +| Field | Rule | +|---|---| +| `document_ref` | Opaque source identifier. It is not a database primary key, provider message ID, URL, or path. | +| `evidence_ref` | Opaque, audience-bound, snapshot-bound, tenant/workspace-bound, expiring capability reference. Naruon reauthorizes it at use time and never dereferences client-controlled URLs or paths. | +| `request_revision` | Bounded opaque revision used for optimistic request compatibility. Reusing it for different canonical content is a conflict. | +| `idempotency_key` | Bounded opaque client token. Naruon stores/compares only a protected tenant-keyed representation; reuse with a different canonical request returns `409`. | +| `expected_snapshot_revision` | Optional optimistic pin. The route compares it with the newly authorized server snapshot; mismatch returns `409`. | +| `language` | Required BCP 47 tag selected or confirmed by the caller. A homemade keyword/script detector must not silently override it. Model support is checked during preflight. | +| `purpose` | Must equal an allowlisted, consented purpose. Revision `2026-08-09.1` defines only `topic_assistance`. | + +The public request never contains raw email/document content, topic labels, +tenant identifiers, model display names, covariate values, membership labels, +or upstream endpoints. Naruon derives the canonical internal request from the +reauthorized snapshot and active deployment. + +### Successful public projection + +HTTP `200` has exactly two semantic states: + +- `inferred`: an accepted mixed-membership posterior is available; +- `abstained`: compatible input and model reached inference, but the attempted + posterior or a declared diagnostic/acceptance rule declined publication. + +```json +{ + "request_id": "tir_3FQn1v8H2zK6cP4m", + "status": "inferred", + "model_id": "opaque-versioned-model-id", + "model_version": "opaque-versioned-model-ref", + "analysis_context": { + "analysis_unit": "document", + "estimand_id": "document_topic_mixture", + "causal_design": "non_causal", + "covariate_level": "analysis_unit" + }, + "credible_level": 0.95, + "interval_method": "upstream-declared-method", + "uncertainty_scope": "conditional_on_fitted_artifact", + "topics": [ + { + "topic_id": 17, + "rank": 1, + "proportion": 0.62, + "credible_interval": {"lower": 0.51, "upper": 0.72} + }, + { + "topic_id": 4, + "rank": 2, + "proportion": 0.38, + "credible_interval": {"lower": 0.28, "upper": 0.49} + } + ], + "diagnostic_status": "accepted", + "completed_at": "2026-08-09T12:00:00Z" +} +``` + +Human-readable labels, if later exposed, use a separate `presentation` object +with their own version and evidence references. They do not replace `topic_id` +or alter posterior values. + +An abstention has no usable topic components: + +```json +{ + "request_id": "tir_B7p4K2m9Q5x1V8nD", + "status": "abstained", + "model_id": "opaque-versioned-model-id", + "model_version": "opaque-versioned-model-ref", + "analysis_context": { + "analysis_unit": "document", + "estimand_id": "document_topic_mixture", + "causal_design": "non_causal", + "covariate_level": "analysis_unit" + }, + "credible_level": 0.95, + "interval_method": "upstream-declared-method", + "uncertainty_scope": "conditional_on_fitted_artifact", + "topics": [], + "diagnostic_status": "rejected", + "abstention_reasons": ["posterior_uncertainty_exceeds_policy"], + "completed_at": "2026-08-09T12:00:00Z" +} +``` + +The response must carry `Cache-Control: no-store`. The UI must render an +abstention as unavailable evidence, not as a zero-probability topic, successful +classification, or reason to invoke agenda generation. + +`model_id`, `model_version`, and `analysis_context` are required safe semantic +context, not optional UI decoration. Consumers join numeric topic IDs only within +that model identity and must display/use the analysis unit, estimand, coarse +covariate level, and `non_causal` designation so group-level effects cannot be +presented as an individual's trait or causal outcome. No group value, membership +identifier, raw covariate, or sensitive digest is exposed. + +## Internal adapter envelope + +The internal response validates against +[`topic-inference-result-v1.schema.json`](schema/topic-inference-result-v1.schema.json), +whose immutable identifier is: + +`https://naruon.net/schemas/topic-intelligence/topic-inference-result-v1/2026-08-09.1` + +The adapter configuration pins both that `$id` and the `schema_digest` +construction defined in [Digest contract](#digest-contract). A response repeats +the ID, revision, and complete canonical-digest record. The schema intentionally +does not embed its own expected digest value because that value is distributed +out of band with the adapter configuration. + +The envelope is Naruon-owned and requires: + +- opaque request, document, snapshot, expiring evidence-reference identity, and + matching server-created snapshot/scope bindings; +- schema revision/digest and adapter version; +- source-snapshot and nested scientific-payload digests; +- the independently accepted expected-upstream scientific payload nested under + `tepp_payload`, including all scientific provenance, design, posterior, + uncertainty, and diagnostics fields; and +- optional versioned presentation labels outside the scientific payload. + +The envelope and its digests are internal validation data. The public projection +above deliberately omits them. + +## HTTP and abstention semantics + +| HTTP | `error_code` or status | When it applies | Retry rule | +|---:|---|---|---| +| `200` | `inferred` | Compatible request, active verified deployment, valid payload, and accepted posterior/diagnostics | Normal result | +| `200` | `abstained` | Compatible request/model reached inference, but posterior uncertainty, diagnostics, or pinned publication policy rejected release | Do not retry unchanged input/model/policy | +| `401` | `topic_authentication_required` | No valid signed session or service identity is present | Authenticate; do not reveal resource existence | +| `403` | `topic_evidence_forbidden` | Evidence authorization fails for the current owner/tenant/workspace | Do not retry without a new authorization decision | +| `403` | `topic_purpose_forbidden`, `topic_consent_required`, `topic_region_forbidden` | Purpose, consent, or region policy denies processing | Policy/consent remediation; no upstream call | +| `409` | `topic_source_snapshot_conflict` | Expected and current authorized snapshot revisions differ | Refresh source state | +| `409` | `topic_request_revision_conflict` | A trusted request revision is incompatible with the canonical request | Create a new revision after refresh | +| `409` | `topic_idempotency_conflict` | An idempotency key was previously bound to different canonical request material | Use a new key only for a genuinely new operation | +| `409` | `topic_schema_revision_conflict` | Client/adapter revision pin conflicts with the active immutable contract | Negotiate a supported revision; never coerce | +| `408` | `topic_deadline_exceeded` | Naruon's bounded request budget expires before an upstream timeout can be classified | Retry only within bounded client policy | +| `422` | `topic_input_invalid` | Bounded shape or source snapshot cannot satisfy the inference request | Correct the request/source | +| `422` | `topic_language_unsupported` | Active fitted artifact does not support the declared language/profile | Select a compatible model only through deployment policy | +| `422` | `topic_input_insufficient_tokens` | Frozen preprocessing retains fewer tokens than the active threshold | More evidence is required | +| `422` | `topic_input_out_of_vocabulary` | OOV count/ratio violates the active artifact policy | Use compatible source evidence/model; no fallback | +| `422` | `topic_temporal_context_invalid` | Event, availability, assertion, or knowledge-cutoff evidence violates the temporal policy | Correct authoritative time evidence | +| `422` | `topic_covariate_contract_invalid` | Required covariate, level, membership weight, or design row is missing/incompatible | Correct authoritative covariate evidence | +| `429` | `topic_rate_limited` | Tenant/workspace quota, concurrency, or repeated-query policy denies work | Honor `Retry-After`; do not change measurement method | +| `503` | `topic_deployment_unavailable` | No verified active deployment exists | Retry only after operator activation | +| `503` | `topic_model_artifact_unavailable` | Pinned artifact or required retained manifest cannot be resolved | Operator/upstream remediation | +| `503` | `topic_model_artifact_integrity_failed` | Artifact/provenance digest validation fails | Quarantine deployment; do not retry blindly | +| `502` | `topic_upstream_inference_failed` | Compatible request reached the upstream boundary but transport/runtime failed | Retry according to bounded service policy | +| `502` | `topic_upstream_protocol_error` | Upstream response fails schema, digest, asserted date-time format, known code-registry, or cross-field validation | Quarantine/review; never turn into abstention | +| `504` | `topic_upstream_timeout` | The bounded expected-upstream deadline expires and work is cancelled | Retry only within bounded service policy | +| `500` | `topic_adapter_internal_error` | Unexpected Naruon defect after safe classification | Incident handling; no internal detail in response | + +A client disconnect or explicit cancellation may make an HTTP response +impossible. The adapter must cancel bounded work, emit no result, and record only +the internal stable outcome `topic_request_cancelled` in approved redacted +telemetry. It must not serialize a partial posterior or retry after cancellation. + +Authentication/authorization/purpose/consent/region denial, rate limiting, +unsupported language, token/OOV insufficiency, temporal/covariate incompatibility, +missing deployment/artifact, integrity failure, trusted conflicts, timeout, +cancellation, and upstream protocol failures must never return HTTP `200` or +`status=abstained`. +These error conditions must never return HTTP `200` or `status=abstained`. + +## RFC 9457 problem details + +Every non-`200` response uses `application/problem+json` and a stable RFC 9457 +problem type. `error_code` is a required Naruon extension; clients branch on the +code, not on localized `title` or `detail` text. + +```json +{ + "type": "https://naruon.net/problems/topic-language-unsupported", + "title": "Topic inference language is unsupported", + "status": 422, + "detail": "The active fitted model cannot infer this language profile.", + "instance": "/api/topic-intelligence/inferences/tir_3FQn1v8H2zK6cP4m", + "error_code": "topic_language_unsupported", + "request_id": "tir_3FQn1v8H2zK6cP4m", + "retryable": false +} +``` + +Problem responses must not include raw source content, topic candidates, +canonical digests, tenant/workspace IDs, covariates, membership identities, +upstream URLs, stack traces, provider errors, or arbitrary evidence references. + +## Scientific payload requirements + +The nested `tepp_payload` contains no presentation label. It must provide: + +- fitted model ID/version/topic count; +- artifact, manifest, vocabulary, preprocessing, design, lineage, model-card, + validation-report, evidence-time manifest, covariate snapshot, and design-row + canonical digests; +- estimator, analysis unit, estimand, prevalence/content formulas, contrasts, + versioned covariate schema, typed covariate level/missingness policy, + membership structure/normalization, unseen-level policy, validation profile, + temporal policy version, explicit document/event/assertion/availability/ + knowledge-cutoff time values, the pinned temporal missingness rule, an asserted + availability-at-cutoff result, and `causal_design=non_causal`; +- inference method, implementation/version, numerical backend, credible level, + interval method, and + `uncertainty_scope=conditional_on_fitted_artifact`; +- non-negative integer topic IDs, ranks, proportions, and per-topic intervals for + accepted results; +- input diagnostics for language, original/retained/OOV tokens and their pinned + thresholds, temporal context, and covariates; +- posterior diagnostics with an immutable diagnostic-code registry version, + convergence and its stable known code, numerical status, bounded stable known + quality codes, iteration count, finite values, interval + validity, observed component count, posterior sum, and normalization tolerance; + and +- an acceptance-policy version, immutable reason-code registry version, boolean + decision, and stable known reason codes. + +The adapter recomputes and cross-checks unique topic IDs/ranks; for `inferred`, +equality of fitted, declared, observed, and actual component counts; sum, +interval containment, finite values, method copies, status, and diagnostic +consistency. It also checks request/evidence snapshot and scope-binding equality, +current tenant/workspace/purpose/consent/region authorization, input thresholds, +membership-structure/normalization coupling, typed covariate level/missingness, +RFC 3339 format assertion, and `availability_time <= knowledge_cutoff_time`. +Unknown code registry versions or codes and any failed cross-check are `502` +protocol errors. Schema validation alone is insufficient. + +## Digest contract + +The complete internal inventory is the schema, source snapshot, scientific +payload, artifact descriptor, artifact manifest, vocabulary, preprocessing, +design, lineage, model card, validation report, evidence-time manifest, +covariate snapshot, and design row. Each of those exactly 14 canonical digest +fields, including `schema_digest`, is a `canonicalDigest` record whose `value` +is: + +`SHA-256(UTF8(domain) || 0x00 || UTF8(RFC8785(value)))` + +with lowercase hexadecimal output. `schema_digest` has one construction: +`domain` is exactly `naruon.topic-inference.schema.v1`, and the formula's +`value` input is the complete parsed JSON value of the immutable schema resource +identified by the pinned `$id`, including its annotations and definitions. +Whitespace, JSON member order, source-file encoding, and other raw-file +serialization details are therefore not separate schema-digest inputs. The +adapter's out-of-band pin stores the resulting canonical-digest record, and the +response repeats that record. + +All 14 contract fields bind canonical JSON values. In particular, +`artifact_digest` binds the fitted-artifact **descriptor**, not the raw fitted +artifact bytes. An independently published artifact manifest may additionally +contain a distinct optional raw-byte hash record, but that record must declare +its algorithm and the exact byte serialization or package it covers. It is +manifest content protected through `manifest_digest`; it is neither +`artifact_digest`, a substitute for any canonical field, nor a fifteenth field +in the inventory. Without that distinct record, the contract makes no raw +artifact-byte integrity claim. RFC 8785 does not normalize Unicode, so +normalization belongs only to the pinned preprocessing contract. + +The no-covariate canonical representations are fixed: + +- `{"covariates":[],"memberships":[]}` under + `naruon.topic-inference.covariate-snapshot.v1`; +- `{"columns":[],"values":[]}` under + `tepp.topic-measurement.design-row.v1`. + +A canonical digest verifies equality with the exact retained canonical JSON +value; it does not prove that a descriptor is truthful or complete and cannot +reconstruct or retrieve the described material. Reproduction requires the +authorized source snapshot and every pinned model, vocabulary, preprocessing, +design, lineage, temporal, covariate, and design-row object to remain resolvable +under retention policy. Raw artifact-byte equality additionally requires the +separate manifest-owned byte hash described above. + +All content-, evidence-, covariate-, membership-, temporal-, design-, and +label-derived digests are sensitive pseudonymous linkage data. They are never +public and never appear in ordinary logs, metrics, traces, or audit events. A +restricted audit record may be referenced only by a tenant-keyed opaque handle. + +## Evidence reference rules + +An `evidence_ref`: + +- is opaque and contains no source/provider identifier, URL, or path; +- is bound to one audience, tenant, workspace, document snapshot, and purpose; +- has an expiry and is rejected when expired; +- is reauthorized on every use instead of treated as a bearer shortcut; +- cannot be exchanged across organizations or workspaces; and +- resolves only through a server-side registry that returns the retained + immutable snapshot or fails closed. + +The internal `evidence_ref.snapshot_revision` must equal +`request.source_snapshot_revision`, and its `scope_binding_ref` must equal the +request binding. Resolution must reproduce the current authenticated tenant, +workspace, purpose, consent, region, and authorization binding; equality of the +opaque strings alone is insufficient. + +## Compatibility and change control + +Revision `2026-08-09.1` is immutable. Backward-compatible clarifications require +a new revision and schema digest; semantic changes to topic identity, +uncertainty, abstention, provenance, error mapping, or ownership require a new +contract version and a superseding Naruon ADR. Naruon must not silently coerce a +expected-upstream payload from an unrecognized revision. + +No route can move from `BLOCKED-UPSTREAM` to implemented until the requirements +and evidence in [Traceability](TRACEABILITY.md) and the operability/security +gates are satisfied on the exact candidate revision. diff --git a/docs/topic-intelligence/ARCHITECTURE.md b/docs/topic-intelligence/ARCHITECTURE.md new file mode 100644 index 000000000..e2a268267 --- /dev/null +++ b/docs/topic-intelligence/ARCHITECTURE.md @@ -0,0 +1,249 @@ +# Topic intelligence architecture + +- **Capability maturity:** `BLOCKED-UPSTREAM` +- **Document status:** `PRESENT-CURRENT` +- **Contract revision:** `2026-08-09.1` + +This is a proposed target profile governed by Naruon's accepted local policy for +a future topic-intelligence adapter. It is not a description of a shipped route, +active TEPP deployment, or published TEPP production contract. Naruon currently +has no fitted topic model to call and therefore exposes no topic-inference +fallback. + +The governing local decision is +[`ADR-0001`](../adr/0001-topic-measurement-authority.md). The adapter remains +blocked until TEPP independently publishes a compatible, versioned fitted-model +artifact and inference contract with its own acceptance evidence. + +## Authority and ownership + +The integration deliberately separates the product envelope from scientific +authority. `TEPP` below is an expected upstream producer, not a present owner or +commitment: that responsibility exists only if TEPP independently publishes a +compatible production contract, artifact, and acceptance evidence. + +| Boundary | Owner | Responsibilities | Must not do | +|---|---|---|---| +| Authenticated product request | Naruon | Reauthorize the tenant/workspace-scoped source, resolve an immutable snapshot, enforce purpose/consent policy, and run preflight checks | Accept a browser-supplied body, tenant identifier, path, URL, or model label as authoritative | +| Adapter envelope | Naruon | Pin schema revision/digest, assign opaque request identity, map failures, validate the upstream payload, redact public projections, and enforce acceptance policy | Refit a model, synthesize a posterior, or reinterpret a label as numeric topic identity | +| Scientific payload | Expected upstream producer; TEPP only after independent publication | Identify the fitted artifact and frozen preprocessing/vocabulary/design, perform new-document inference, and return mixed-membership estimates, uncertainty, provenance, and diagnostics | Depend on Naruon's UI labels or agenda templates as model inputs, or treat this Naruon acceptance profile as an assigned TEPP obligation | +| Presentation labels | Naruon, from versioned evidence | Attach evidence-backed human-readable labels after inference | Mutate topic identifiers, proportions, intervals, or diagnostic outcomes | +| Agenda/action generation | A separate future contract | Consume an authorized source snapshot and an accepted posterior | Infer topics from raw keywords or run when topic inference abstained | + +The JSON Schema in +[`schema/topic-inference-result-v1.schema.json`](schema/topic-inference-result-v1.schema.json) +defines Naruon's **internal adapter envelope** and the scientific payload shape +that Naruon would require before consumption. It does not claim that TEPP has +adopted that schema. A public Naruon API may expose only a redacted projection; +canonical digests and scope-binding evidence are internal validation material. +That projection still carries the opaque model ID/version plus analysis unit, +estimand, coarse covariate level, and causal/non-causal designation needed to +prevent ecological or causal over-interpretation. + +## Planned components + +```mermaid +flowchart TD + Client["Authenticated Naruon client"] --> API["Naruon topic API"] + API --> Snapshot["Authorized immutable snapshot"] + API --> Adapter["Naruon topic adapter"] + Adapter --> TEPP["Expected upstream inference boundary"] + TEPP --> Artifact["Versioned fitted artifact"] + Snapshot --> Adapter + Adapter --> API +``` + +- The client supplies only opaque source and evidence references plus a bounded + request revision. It never selects a model by display label. +- The Naruon API reauthorizes the evidence reference on every request and + resolves the current server-authoritative snapshot. +- The adapter pins the accepted schema, deployment, model artifact, + preprocessing, vocabulary, design, temporal policy, and validation policy. +- The independently accepted upstream producer would perform inference against + an already fitted artifact. Training or per-request refitting is outside this + request path. +- Naruon validates contract and scientific invariants before producing either + `inferred` or the narrowly defined `abstained` result. + +No component reads another service's private database. The future integration +must use a versioned typed boundary and an explicitly deployed artifact. + +## Request and result flow + +```mermaid +sequenceDiagram + participant C as Client + participant N as Naruon API + participant A as Naruon adapter + participant T as Expected upstream producer + + C->>N: Opaque evidence ref + request revision + N->>N: Reauthorize and freeze snapshot + N->>A: Canonical internal request + A->>A: Preflight and deployment pin + alt Input is ineligible + A-->>N: RFC 9457 problem (422) + else Deployment or artifact is unavailable + A-->>N: RFC 9457 problem (503) + else Trusted revision or idempotency conflicts + A-->>N: RFC 9457 problem (409) + else Compatible request + A->>T: Versioned inference request + alt Upstream deadline expires + A-->>N: RFC 9457 problem (504) + else Scientific payload returned + T-->>A: Scientific payload + A->>A: Verify digests, schema, codes, cross-fields + alt Transport or payload validation fails + A-->>N: RFC 9457 problem (502) + else Posterior and policy accept + A-->>N: 200 inferred + else Posterior or diagnostic policy declines + A-->>N: 200 abstained + end + end + end + N-->>C: Redacted response or safe problem detail +``` + +`200 abstained` is not a generic failure bucket. It is permitted only after the +request, language, snapshot, model deployment, artifact, temporal inputs, and +covariates are compatible and an attempted posterior or its diagnostic policy +does not meet the declared acceptance criteria. Preflight failures never appear +as abstentions. + +## Trust boundaries and fail-closed behavior + +| Condition | Boundary that detects it | Contract result | +|---|---|---| +| Missing/expired/wrong-audience evidence reference | Naruon authorization | Authentication/authorization failure; no upstream call | +| Purpose, consent, region, tenant, or workspace denial | Naruon authorization | `403` RFC 9457 problem; existence remains undisclosed | +| Tenant/workspace quota or rate policy exceeded | Naruon edge/adapter | `429` RFC 9457 problem; no upstream call | +| Unsupported language, insufficient retained tokens, excessive OOV, invalid temporal context, or invalid covariates | Naruon/expected-upstream preflight | `422` RFC 9457 problem with stable `error_code` | +| No active deployment, missing artifact, or failed artifact-integrity validation | Naruon adapter | `503` RFC 9457 problem | +| Snapshot revision, request revision, schema pin, or idempotency conflict | Naruon adapter | `409` RFC 9457 problem | +| Upstream transport fails or a schema, digest, format, code-registry, or cross-field result cannot be validated | Naruon adapter | `502` problem; never an abstention or fabricated posterior | +| Upstream deadline expires | Naruon adapter | `504` RFC 9457 problem; bounded cancellation and no result | +| Client cancels or disconnects | Naruon edge/adapter | Cancel work and record only a stable internal cancellation outcome; no HTTP result may be deliverable | +| Compatible inference produces a posterior rejected by declared diagnostic/acceptance policy | Naruon adapter | `200`, `status=abstained`, no usable topic components | +| Valid posterior satisfies the pinned policy | Naruon adapter | `200`, `status=inferred` | + +There is no keyword, embedding, zero-shot, LLM-label, default-topic, or template- +agenda fallback under this contract. + +## Scientific invariants + +The adapter must enforce invariants that JSON Schema alone cannot express: + +1. Topic identifiers are non-negative JSON integers. For `inferred`, + `fitted_topic_count`, `topic_count`, `observed_topic_count`, and the number of + components are equal; topic IDs and ranks are independently unique. For + `abstained`, the latter three counts are zero while `fitted_topic_count` + remains the active artifact's topic count. +2. Every proportion and credible-interval bound is finite and in `[0, 1]`. +3. Each estimate lies within its own interval. +4. For an `inferred` result, component proportions sum to one within the pinned + `normalization_tolerance`; the diagnostic `posterior_sum` agrees with the + recomputed value. +5. `credible_level`, `interval_method`, and + `uncertainty_scope=conditional_on_fitted_artifact` apply to every component. + They do not claim to include model-selection, corpus-selection, or label + uncertainty. +6. `inferred` requires accepted diagnostics, a non-empty component vector, and + no abstention reasons. Its posterior diagnostics include a stable + `convergence_code`, `numerical_status=valid`, and bounded stable + `quality_codes`. `abstained` requires rejected diagnostics, at least one + posterior/policy reason, and an empty component vector. +7. The declared fitted topic count, design row, membership structure, temporal + policy, and preprocessing/vocabulary identities match the active deployment. +8. Any multilevel, multiple-membership, cross-classified, temporal, prevalence, + or content extension names its estimator, analysis unit, estimand, formulas, + contrasts, membership-weight normalization, unseen-level policy, and + validation profile. `causal_design` remains `non_causal` unless a separate + causal design is approved and documented. +9. Multiple-membership structures require weights that sum to one per analysis + unit. A structure without multiple membership requires + `membership_weight_normalization=not_applicable`. Covariate schema version, + level, and typed missingness policy must match the retained covariate snapshot + and model card; missing values are never silently assigned to a default level. +10. The evidence reference snapshot and scope bindings equal the enclosing + request bindings, and use-time reauthorization resolves the same current + tenant, workspace, purpose, consent, region, and authorization context. +11. Validators assert RFC 3339 `date-time` formats, recompute + `availability_time <= knowledge_cutoff_time`, and enforce the pinned temporal + missingness policy. A producer's `availability_at_knowledge_cutoff=true` + assertion is evidence to verify, not a substitute for that check. +12. The inference-method copy and every diagnostic or reason code agree with the + exact pinned immutable code registries. An unknown registry version or code + is a `502` protocol error, never an abstention. + +## Canonical provenance and reproduction + +All contract digest fields are SHA-256 over an RFC 8785 canonical JSON +descriptor with domain separation: + +`SHA-256(UTF8(domain) || 0x00 || UTF8(RFC8785(value)))` + +RFC 8785 does not normalize Unicode. Producers must apply the frozen +preprocessing contract before constructing a value to digest; consumers must +not add an undocumented normalization pass. + +For a model with no covariates, the canonical empty values are: + +- covariate snapshot: + `{"covariates":[],"memberships":[]}` with domain + `naruon.topic-inference.covariate-snapshot.v1`; +- design row: `{"columns":[],"values":[]}` with domain + `tepp.topic-measurement.design-row.v1`. + +Digests prove equality with retained material; they do not make deleted or +unavailable material reproducible. Reproduction additionally requires an +authorized, resolvable retained source snapshot, model artifact, manifests, +vocabulary, preprocessing/design specifications, temporal evidence, and +covariate/design-row material. + +Every content-, evidence-, covariate-, membership-, temporal-, design-, and +label-derived digest is sensitive pseudonymous linkage data. It is for internal +validation only and must never be +placed in a public response, ordinary audit event, application log, metric, or +trace. Restricted audit records may hold a tenant-keyed opaque reference to a +protected validation record, subject to retention and deletion policy. + +## Deployment and operability gates + +A deployment may become active only when all of the following are pinned and +verified as one compatible set. The complete canonical-digest inventory is the +schema, source snapshot, nested scientific payload, artifact descriptor, +artifact manifest, vocabulary, preprocessing, design, lineage, model card, +validation report, evidence-time manifest, covariate snapshot, and design row: + +- immutable schema ID, revision, and externally configured schema digest; +- independently accepted upstream service and scientific contract version; +- model artifact, artifact manifest, vocabulary, preprocessing, design, + lineage, model-card, and validation-report digests; +- temporal policy, temporal missingness rule, evidence-time manifest digest, + and asserted/recomputed availability-at-cutoff ordering; +- covariate-schema version, typed level/missingness policy, covariate-snapshot + digest, design-row digest, and scope-binding policy; +- estimator, analysis unit, estimand, formulas/contrasts, membership and unseen- + level policy, plus the validation profile; +- language, retained-token, OOV, posterior-normalization, uncertainty, + diagnostic acceptance thresholds, immutable diagnostic/reason-code registry + versions, and a validator with `date-time` format assertion enabled; and +- tenant/workspace purpose, consent, retention, deletion, evidence-reference, + log-redaction, and restricted-audit controls. + +Activation, rollback, artifact revocation, validation drift, latency/error SLOs, +and incident playbooks belong to the operability contract. Until those gates +and the independently published upstream capability exist, the runtime maturity +remains `BLOCKED-UPSTREAM`. + +## Related records + +- [Product requirements](PRD.md) +- [Technical requirements](TRD.md) +- [API contract](API_CONTRACT.md) +- [UML views](UML.md) +- [Conceptual data model](DATA_MODEL.md) +- [Requirements traceability](TRACEABILITY.md) +- [ADR-0001](../adr/0001-topic-measurement-authority.md) diff --git a/docs/topic-intelligence/DATA_MODEL.md b/docs/topic-intelligence/DATA_MODEL.md new file mode 100644 index 000000000..0cc805347 --- /dev/null +++ b/docs/topic-intelligence/DATA_MODEL.md @@ -0,0 +1,302 @@ +# Topic intelligence conceptual data model + +- **Capability maturity:** `BLOCKED-UPSTREAM` +- **Document status:** `PRESENT-CURRENT` +- **Persistence status:** `NOT-APPLICABLE` + +This document is a conceptual integration model, not a physical database model. +There is no current Naruon table or persistence authorized for any entity below. +The names describe messages, immutable artifacts, and bounded references needed +to reason about the planned adapter. + +Names prefixed `TEPP_` denote the expected shape of independently published +upstream evidence. They do not assign present ownership to TEPP; TEPP becomes the +producer only if it separately publishes and accepts a compatible production +contract and artifact. + +Any future persistence requires a separate accepted ADR, threat model, retention +and deletion design, tenant/workspace row-level authorization, migration, rollback +plan, and database tests. A diagram here must never be used as permission to add +tables or columns. + +## Integration entities + +```mermaid +erDiagram + NARUON_DOCUMENT_SNAPSHOT ||--o{ TOPIC_INFERENCE_REQUEST : supplies + TEPP_MODEL_ARTIFACT ||--o{ TEPP_MODEL_DEPLOYMENT : realizes + TEPP_MODEL_DEPLOYMENT ||--o{ TOPIC_INFERENCE_REQUEST : selected_for + TOPIC_INFERENCE_REQUEST ||--o| TOPIC_INFERENCE_RESULT : produces + + NARUON_DOCUMENT_SNAPSHOT { + string snapshot_ref PK + string document_ref + string snapshot_revision + string source_snapshot_digest + datetime knowledge_cutoff_time + } + TOPIC_INFERENCE_REQUEST { + string request_id PK + string request_revision + string snapshot_ref FK + string deployment_ref FK + string evidence_ref + string scope_binding_ref + string purpose_code + } + TEPP_MODEL_ARTIFACT { + string model_artifact_ref PK + string model_id + string model_version + string artifact_descriptor_digest + string schema_revision + } + TEPP_MODEL_DEPLOYMENT { + string deployment_ref PK + string model_artifact_ref FK + string validation_profile_version + string deployment_state + } + TOPIC_INFERENCE_RESULT { + string request_id PK + string result_status + string payload_digest + datetime completed_at + } +``` + +These relationships express authority, not storage foreign keys: + +- `NARUON_DOCUMENT_SNAPSHOT` is a server-authoritative immutable view. Its + opaque `snapshot_ref` binds the exact document reference, snapshot revision, + and source-snapshot digest resolved after owner, organization, workspace, + purpose, and consent checks. +- `TOPIC_INFERENCE_REQUEST` binds exactly one snapshot revision to one active + deployment and one idempotent request revision. +- `TEPP_MODEL_ARTIFACT` is a conditional expected-upstream evidence role. Naruon + may consume it only after independent publication and compatibility review; it + does not currently assign TEPP an obligation or own/mutate such an artifact. + Its opaque `model_artifact_ref` binds the exact model ID, model version, + artifact-descriptor digest, and schema revision. +- `TEPP_MODEL_DEPLOYMENT` is Naruon's compatibility/activation record for a + particular immutable upstream evidence set. A mutable display tag is not an + identity. +- `TOPIC_INFERENCE_RESULT` exists only for HTTP `200` outcomes (`inferred` or + narrowly defined `abstained`). RFC 9457 problems are errors, not result rows. + +## Scientific result entities + +```mermaid +erDiagram + TOPIC_INFERENCE_RESULT ||--o{ TOPIC_POSTERIOR_COMPONENT : contains + TOPIC_INFERENCE_RESULT ||--|| SCIENTIFIC_PROVENANCE : validates_with + TOPIC_INFERENCE_RESULT ||--|| DIAGNOSTIC_BUNDLE : qualifies + TOPIC_POSTERIOR_COMPONENT ||--o{ TOPIC_LABEL_EVIDENCE : may_present_as + + TOPIC_INFERENCE_RESULT { + string request_id PK + string model_id + string model_version + string result_status + string scientific_payload_digest + } + TOPIC_POSTERIOR_COMPONENT { + string component_ref PK + string request_id FK + string model_id + string model_version + int topic_id + int rank + number proportion + number interval_lower + number interval_upper + } + SCIENTIFIC_PROVENANCE { + string artifact_descriptor_digest + string artifact_manifest_digest + string vocabulary_digest + string preprocessing_digest + string design_digest + string lineage_digest + string model_card_digest + string validation_report_digest + string evidence_time_manifest_digest + string covariate_snapshot_digest + string design_row_digest + string analysis_unit + string estimand_id + string causal_design + } + DIAGNOSTIC_BUNDLE { + string diagnostic_status + string diagnostic_code_registry_version + string reason_code_registry_version + number posterior_sum + boolean policy_accepted + string policy_version + } + TOPIC_LABEL_EVIDENCE { + string label_evidence_ref PK + string component_ref FK + string model_id + string model_version + int topic_id + string label_id + string label_version + string opaque_evidence_refs + string review_method + } +``` + +The `PK` and `FK` labels above are conceptual message identities, not proposed +SQL columns or additions to the public wire contract. Each opaque reference is +immutable and resolves only when every bound scope value agrees: + +| Entity | Required immutable identity binding | Forbidden unscoped shortcut | +|---|---|---| +| Document snapshot | `snapshot_ref` -> (`document_ref`, `snapshot_revision`, `source_snapshot_digest`) | `document_ref` alone | +| Model artifact | `model_artifact_ref` -> (`model_id`, `model_version`, `artifact_descriptor_digest`, `schema_revision`) | `model_id` or a display tag alone | +| Posterior component | `component_ref` -> (`request_id`, `model_id`, `model_version`, `topic_id`) | `topic_id`, rank, or label alone | +| Label evidence | `label_evidence_ref` -> (`model_id`, `model_version`, `topic_id`, `label_id`, `label_version`, `opaque_evidence_refs`) | `topic_id`, `label_id`, or label text alone | + +A resolver must fail closed when an opaque reference and its supplied scope tuple +disagree. Numeric topic identity is reusable only within its exact model ID and +model version; a result component adds request/result scope, and presentation +evidence additionally adds label ID and label version. + +An `abstained` result has zero `TOPIC_POSTERIOR_COMPONENT` instances, rejected +diagnostics, and one or more posterior/diagnostic-policy reason codes. Input, +language, temporal, covariate, deployment, artifact, revision, and protocol +failures are not represented as abstained results. + +For `inferred`, the fitted artifact count, declared inference count, observed +diagnostic count, and number of components are equal. For `abstained`, the latter +three are zero while the fitted artifact count remains unchanged. Numeric topic +identity is a non-negative JSON integer scoped by model ID and model version; +joins to a result also require its request/result scope. + +`TOPIC_LABEL_EVIDENCE` is presentation metadata owned by Naruon. Its relationship +to a component is referential only: the model ID, model version, numeric topic +ID, label ID, and label version must all agree, and labels cannot become the +topic identifier or alter any estimate. Agenda generation is not an entity in +this model because it belongs to a separate downstream authorized contract. + +## Covariate and temporal evidence + +```mermaid +erDiagram + NARUON_DOCUMENT_SNAPSHOT ||--o| COVARIATE_SNAPSHOT : contextualizes + COVARIATE_SNAPSHOT ||--o{ MEMBERSHIP_WEIGHT : contains + COVARIATE_SNAPSHOT ||--|| DESIGN_ROW : compiles_to + DESIGN_ROW ||--|| EVIDENCE_TIME_MANIFEST : constrained_by + + COVARIATE_SNAPSHOT { + string snapshot_digest PK + string covariate_schema_version + string covariate_level + string missingness_policy + string membership_structure + string unseen_level_policy + } + MEMBERSHIP_WEIGHT { + string membership_ref PK + number weight + string level_ref + } + DESIGN_ROW { + string design_row_digest PK + string estimator_id + string analysis_unit + string estimand_id + } + EVIDENCE_TIME_MANIFEST { + string manifest_digest PK + string temporal_policy_version + string temporal_missingness_policy + datetime document_time + datetime event_time + datetime assertion_time + datetime availability_time + datetime knowledge_cutoff_time + boolean availability_at_knowledge_cutoff + } +``` + +Membership weights must obey the model's pinned normalization rule. New or +unknown levels follow only the declared unseen-level policy; they are never +silently mapped to a familiar group. The design row must be reproducible from +the retained authorized covariate snapshot and pinned design specification. +`multiple_membership` and `cross_classified_multiple_membership` require weights +that sum to one per analysis unit; all other structures require the explicit +`not_applicable` normalization value. Covariates carry a versioned typed level +and missingness policy, and missing state is never inferred from an absent field. + +The adapter enables RFC 3339 `date-time` format assertion and independently +checks `availability_time <= knowledge_cutoff_time`. Only `document_time` and +`event_time` may be null under revision `2026-08-09.1`; the pinned temporal +missingness policy governs their interpretation. + +For a model with no covariates, the entities still have deterministic canonical +empty values rather than missing or implementation-specific sentinels: + +| Concept | RFC 8785 value | Digest domain | +|---|---|---| +| Covariate snapshot | `{"covariates":[],"memberships":[]}` | `naruon.topic-inference.covariate-snapshot.v1` | +| Design row | `{"columns":[],"values":[]}` | `tepp.topic-measurement.design-row.v1` | + +The digest input is +`UTF8(domain) || 0x00 || UTF8(RFC8785(value))`, hashed with SHA-256 and encoded +as lowercase hexadecimal. + +## Concept glossary and ownership + +| Concept | Authority | Identity and lifecycle | +|---|---|---| +| Document snapshot | Naruon source boundary | Opaque document reference plus immutable snapshot revision and canonical digest; resolvable only under current authorization | +| Evidence reference | Naruon authorization boundary | Opaque, tenant/snapshot/audience-bound, expiring, and reauthorized on every use; never a URL or filesystem path | +| Model artifact | Expected upstream producer; TEPP only after independent publication | Immutable fitted artifact with independently published version, manifest, scientific validation, and digest evidence | +| Deployment | Naruon adapter | Compatibility and activation decision binding one exact upstream artifact/contract set to one Naruon validation profile | +| Scientific payload | Expected upstream producer; TEPP only after independent publication | Mixed-membership estimate, uncertainty, scientific provenance, and diagnostics returned from the fitted artifact | +| Adapter envelope | Naruon | Request identity, schema pin, payload digest, result status, acceptance decision, and safe error mapping | +| Presentation label | Naruon from versioned evidence | Human-readable aid with separate version and evidence references; never numeric topic identity | + +The request and evidence reference each carry the same opaque scope-binding and +snapshot revision. Equality is a runtime invariant; use-time reauthorization +must resolve that binding to the current tenant, workspace, purpose, consent, +region, and authorization context. Neither the binding nor its protected record +is a public identifier. + +The complete internal digest inventory is: schema, source snapshot, scientific +payload, artifact descriptor, artifact manifest, vocabulary, preprocessing, +design, lineage, model card, validation report, evidence-time manifest, +covariate snapshot, and design row. Every item uses its schema-defined domain. +The public projection omits those digests but retains opaque model ID/version, +analysis unit, estimand, coarse covariate level, and causal/non-causal status so +consumers cannot silently reinterpret a group-level or non-causal estimand. + +## Privacy classification + +Source text is not part of this data model and must not be copied into request +logs, metrics, traces, errors, or unrestricted audit events. Every content-, +evidence-, covariate-, membership-, temporal-, design-, and label-derived digest +is sensitive pseudonymous linkage data even though it is one-way. Such digests +are internal validation material only. + +Where auditability is required, an audit event may contain a tenant-keyed opaque +reference to a restricted validation record. Resolution must re-check owner, +organization, workspace, purpose, consent, retention, and deletion policy. The +public API and UI receive a redacted projection without canonical digests, +tenant bindings, raw covariates, membership identifiers, or arbitrary evidence +locations. + +## Non-persistence decision + +At revision `2026-08-09.1`: + +- no Alembic migration is authorized; +- none of these conceptual names is a SQL table or ORM model; +- no posterior, label, covariate row, or digest is retained by default; +- a request may be processed transiently only after the upstream and runtime + gates in [Architecture](ARCHITECTURE.md) are satisfied; and +- a future persistence proposal must prove why transient processing and a + restricted audit reference are insufficient before adding durable storage. diff --git a/docs/topic-intelligence/DOCUMENTATION_FITNESS.md b/docs/topic-intelligence/DOCUMENTATION_FITNESS.md new file mode 100644 index 000000000..48e0b43b7 --- /dev/null +++ b/docs/topic-intelligence/DOCUMENTATION_FITNESS.md @@ -0,0 +1,105 @@ +# Documentation fitness assessment + +- **Assessment date:** 2026-08-09 +- **Protected-base snapshot:** `develop@5425ce4f55b2cf16b2c82a4fd661c9d0bd0660c7` +- **Candidate:** PR #1297 +- **Verdict before this package:** insufficient +- **Verdict after this package:** design-sufficient for deletion review and + future contract discovery; partial for runtime implementation; insufficient + to claim a live STM capability + +Fitness terms are `PRESENT-CURRENT`, `PRESENT-STALE`, `PARTIAL`, `MISSING`, +`NOT-APPLICABLE`, and `SUPERSEDED`. These terms assess documentation fitness, +not implementation maturity. + +The maturity split is explicit: ADR-0001 is an +`ACCEPTED-NARUON-POLICY`; ADR-0002 and ADR-0003 are `Proposed` Naruon target +decisions; the target acceptance profile is `PLANNED`; and the runtime capability +remains `BLOCKED-UPSTREAM`. A proposed target or complete document package is +not an accepted runtime architecture and cannot promote the capability. + +## Assessment matrix + +| Artifact | Before | After | Evidence and remaining limit | +| --- | --- | --- | --- | +| Topic-specific PRD | `PRESENT-STALE` | `PRESENT-CURRENT` | Requirement IDs, users, non-goals, failure/abstention journeys, and explicit maturity are consolidated. | +| Topic-specific TRD | `PARTIAL` | `PRESENT-CURRENT` | Naruon ownership, upstream non-authority, artifact, input/result, error/abstention, provenance, security, compatibility, and gates are explicit. | +| Naruon ADR | `PARTIAL` | `PRESENT-CURRENT` | ADR-0001 records only Naruon's accepted local policy; the ADR index and package separately expose ADR-0002 and ADR-0003 as proposed targets, not upstream acceptance or runtime implementation. | +| Future adapter decisions | `MISSING` | `PARTIAL` | Proposed ADR-0002 covers conditional fitted-artifact consumption and proposed ADR-0003 covers agenda separation. Transport/authentication, artifact signing/registry, cache, retention/deletion, rate limit, sensitive-covariate, and downstream-authorization ADRs still await a real upstream boundary. | +| Architecture | `PARTIAL` | `PRESENT-CURRENT` | Current/candidate/target ownership, trust, and failure boundaries are separated; the target views are a proposed acceptance profile governed only by the accepted local policy. | +| UML | `PARTIAL` | `PRESENT-CURRENT` | Conceptual component, class, success/error/abstention, artifact-state, and deployment views are available without claiming runtime code. | +| ERD/data model | `PRESENT-STALE` | `PRESENT-CURRENT` | Contract concepts are modeled; physical Naruon persistence remains correctly `NOT-APPLICABLE`. | +| API/schema/versioning | `PARTIAL` | `PRESENT-CURRENT` | Planned Naruon adapter validation shape, closed revision rules, errors, abstention, and cross-field invariants are documented; no live transport is claimed. | +| Canonical digest inventory | `MISSING` | `PRESENT-CURRENT` | One 14-field inventory names the three envelope and eleven scientific-provenance digests, including model card, validation report, covariate snapshot, and design row; schema/API remain the machine-readable and formula authorities. | +| Security and threat model | `PARTIAL` | `PRESENT-CURRENT` | Assets, misuse cases, privacy/statistical risks, controls, residual decisions, and refresh triggers are explicit. | +| Test strategy | `PARTIAL` | `PRESENT-CURRENT` | Naruon product/integration evidence is separated from upstream scientific validation. | +| Operability | `PARTIAL` | `PRESENT-CURRENT` | Readiness, safe signals, promotion, incidents, rollback, recovery, and replay gates are documented without invented SLOs. | +| Traceability | `PARTIAL` | `PRESENT-CURRENT` | Requirements map to the Naruon decision, design/contract, code/tests, and maturity. | +| References | `PARTIAL` | `PRESENT-CURRENT` | Scientific, standards, and dated repository evidence are separated from implementation claims. | +| Machine documentation fitness | `MISSING` | `PARTIAL` | File/link/schema-maturity/source-absence checks are useful, but balanced fences are not Mermaid parsing and JSON parsing is not Draft 2020-12 metaschema or fixture validation. | + +## Why the verdict is not “implementation-ready” + +The package defines what Naruon would require; it does not supply the upstream +dependency or implementation evidence. In particular: + +- Naruon has no independently published upstream production topic artifact, + inference API/contract, or publisher acceptance evidence to consume. +- The planned Naruon envelope is not an upstream publisher's canonical payload + and cannot assign obligations or ownership to TEPP or another producer. +- Transport, service authentication, artifact signing/registry, cache, + retention/deletion, rate limit, sensitive-covariate, and downstream- + authorization decisions are unresolved. +- No physical Naruon topic persistence, migration, retention contract, or + resolvable replay snapshot has been approved. Digests support verification, + not reconstruction. +- No fitted production artifact, model card, validation thresholds, interval + calibration/coverage evidence, drift baseline, signed promotion record, + representative capacity study, or numeric SLO exists. +- The 14 digest fields specify verification bindings only. No retained object, + upstream adoption, scientific validity, or replay capability follows from the + inventory itself. +- No live OpenAPI route, adapter, real-service E2E evidence, or topic UI exists. + +These are intentional gates while runtime integration is `BLOCKED-UPSTREAM`, +not permission to describe the capability as implemented. + +## Completeness decision + +The deletion change is adequately specified when reviewers can verify all of the +following: + +1. The two lexical pseudo-topic tools disappear from registry and source on the + candidate branch. +2. The retained keyword utility remains bounded and explicitly lexical. +3. No substitute topic handler, default label, template agenda, or network/model + dependency is introduced. +4. Protected-base, active-PR, accepted-local-policy, planned, and upstream- + blocked claims remain distinct. +5. Error and scientific-abstention semantics do not overlap. +6. Any future integration is blocked on an independently published compatible + fitted artifact/API/contract and scientific acceptance evidence. + +The documentation is therefore sufficient for PR #1297's deletion decision and +for initiating later contract discovery. It is insufficient to authorize a +runtime adapter, persistence, downstream topic use, or product UI. + +## Reassessment triggers + +Re-run this assessment when any of the following occurs: + +- an upstream publisher independently publishes or changes a production topic- + measurement artifact/API/contract or its acceptance evidence; +- Naruon selects a transport, schema revision, fitted artifact, cache, audit, or + persistence design; +- topic output is consumed by search, norm-group inference, labels, agenda + generation, or another downstream decision; +- a UI, sensitive covariate, temporal/multilevel estimator, or causal claim is + proposed; or +- an incident, drift result, validation result, or retention requirement changes + the accepted Naruon boundary. + +When maturity changes, update the PRD requirement row, TRD, ADR status/scope, +architecture and contract, tests/evidence, traceability, changelog, and this +fitness matrix in the same PR. A document-only status promotion without +protected-branch runtime evidence is invalid. diff --git a/docs/topic-intelligence/OPERABILITY.md b/docs/topic-intelligence/OPERABILITY.md new file mode 100644 index 000000000..773dc8b20 --- /dev/null +++ b/docs/topic-intelligence/OPERABILITY.md @@ -0,0 +1,122 @@ +# Topic intelligence operability + +**Status:** target operating design `PLANNED`; runtime integration +`BLOCKED-UPSTREAM`; no runtime runbook, dashboard, threshold, or SLO is claimed + +The safest current operating state is “integration absent.” The pseudo-topic +removal introduces no external dependency. Everything below is a release gate +for a future adapter, not evidence that a TEPP topic service or fitted model is +available. + +## Readiness gates + +- Naruon receives and accepts an independently published production contract, + immutable fitted-artifact manifest, validation packet, model card, promotion + evidence, and named upstream owners. This is a Naruon consumption gate, not an + assignment of work or ownership to TEPP. +- Naruon approves transport/service-authentication, evidence-reference, + artifact-signing/registry, cache/idempotency, retention/deletion, + sensitive-covariate, privacy/rate-limit, and downstream-authorization ADRs. +- Representative capacity tests establish request bytes, retained tokens, + concurrency, queue, deadline, cancellation, retry, circuit-breaker, and quota + limits. +- Authentication/authorization denial, rate limiting, deadline expiry, and + cancellation have tested stable non-`200` mappings, bounded retry rules, and no + scientific-abstention or fallback transition. +- Dashboards and alerts are verified with synthetic traffic and contain no raw + content, labels, sensitive covariates, direct identifiers, or unkeyed derived + digests. +- Operators drill artifact promotion, signer revocation, quarantine, rollback, + tenant disable, deletion propagation, cache eviction, and full service disable. +- Every disabled, unavailable, timeout, schema, artifact, or policy state is + verified to have no keyword, embedding, LLM, cached-other-model, category, or + agenda fallback. + +## Planned signals + +| Signal | Safe dimensions | Excluded dimensions | +| --- | --- | --- | +| Request and result counts | contract/schema revision, model version, coarse status/error/abstention code, tenant-safe aggregate | content, label, direct user/source ID, raw request/result ID | +| Latency and deadline | operation, model version, coarse outcome | raw content size or rare tenant dimensions unless privacy-reviewed | +| Scientific diagnostics | pass/abstain code, privacy-reviewed aggregate retained-token/OOV bands, artifact version | terms, excerpts, per-user/group values, posterior vector | +| Artifact state | candidate/validated/approved/active/quarantined/retired and opaque registry reference | mutable filesystem path, model bytes, signing secret, raw manifest/content digest | +| Policy and audit | opaque restricted reference, purpose and decision code | credentials, provider URL, body, label, sensitive membership, raw derived digest | + +Every content-, evidence-, covariate-, membership-, temporal-, design-, and +label-derived digest is a sensitive pseudonymous linkage value. It is excluded +from ordinary logs, metrics, traces, dashboards, and product payloads. +Restricted audit uses an opaque reference or tenant-scoped keyed digest with a +documented canonical representation, domain separator, TTL, deletion, and key +rotation. + +Numeric objectives and alert thresholds remain `TBD` until a production TEPP +service and representative workload produce measurements. Placeholder 99.x% +targets would be false precision. + +## Model and contract promotion + +1. Register immutable candidate model bytes, manifest, schema, validation packet, + model card, and build/signing provenance. +2. Verify exact schema ID/revision/digest and code-registry versions, raw artifact + bytes, manifest, vocabulary, preprocessing, design, lineage, model-card, + validation-report, build, signer, and promotion identities and digests. +3. Verify scientific, security, privacy, temporal, and extended-STM evidence for + the exact candidate; reject any unreviewed method or covariate change. +4. Complete independent approval of the exact artifact and signer state. +5. Exercise shadow or restricted-tenant validation without using output for + product decisions. +6. Promote by immutable reference; never mutate an artifact or reuse `latest`. +7. Monitor version-specific errors, abstention, diagnostic-code registry, drift, + privacy, and capacity signals using safe dimensions. +8. Quarantine immediately on integrity, isolation, signer, material validity, + harmful-label, temporal, or deletion concern. + +Schema deployment is coordinated: producers must not send a new closed revision +until consumers pin and negotiate it. A revision uses an immutable schema +identifier and digest; cache identity must not collapse different revisions. + +## Incident response + +| Incident | Immediate action | Recovery evidence | +| --- | --- | --- | +| Artifact/validation-report/digest/signature/signer failure | Quarantine the exact deployment and signer, disable affected inference, preserve minimal restricted evidence | Root cause, key disposition, clean rebuilt artifact and validation report, every binding reverified, full revalidation and authorized promotion | +| Cross-tenant or purpose disclosure | Disable integration, invoke security/privacy response, stop downstream use, propagate deletion | Isolation fix, notification/deletion disposition, adversarial regression tests and controlled re-enable | +| Raw content or derived digest in telemetry | Stop emission and access, preserve only necessary incident evidence, rotate keyed material if applicable | Purge/retention disposition, redaction fix, historical search, rotation and regression evidence | +| Invalid posterior, interval, diagnostics, or unknown code | Reject as a protocol error and disable the exact model/schema/code-registry combination | Producer evidence, closed code-registry review, schema/numerical/scientific revalidation | +| Temporal or ecological misuse | Stop affected consumer and result presentation | Estimand/temporal correction, model-card review, consumer and copy tests | +| Elevated timeout/error/resource use | Open circuit, cancel bounded work, return unavailable | Capacity/root-cause evidence and controlled re-enable | +| Drift, poisoning, or harmful labels | Stop downstream use; retire label or quarantine model independently as applicable | Corpus/model/label review and new immutable version | +| TEPP unavailable | Return stable unavailable error | Health, authorization, schema, artifact, and compatibility verified before re-enable | + +## Rollback principle + +Rollback means disabling the adapter or selecting a previously approved, +compatible immutable artifact under an explicit audited policy. It never means +restoring removed keyword tables, calling an LLM, returning `General`, reusing a +posterior from another tenant/artifact/purpose, or generating an agenda template. +Topic identity remains model-version scoped; consumers must not compare or join +topics across model versions without a separately validated alignment. + +## Recovery, replay, and deletion + +A request is replayable only when the exact authorized snapshot, purpose, +consent, artifact, vocabulary, preprocessing, design, inference version, +analysis unit, estimand, temporal policy, and knowledge cutoff remain valid. An +idempotency key binds retries to that tuple and tenant scope. Replaying after +retention, consent, source access, tenant, model, signer, or policy invalidation +is forbidden even if bytes remain technically available. + +Deletion must cover transient snapshots, queues, caches, persisted results, +restricted audit references, and derived linkage material under their approved +policies. Key rotation is not a substitute for deleting retained content, and +deleting product output does not by itself prove that TEPP-side state is gone. + +## Ownership and handoff + +Naruon operators own Naruon tenant policy, adapter enablement, product projection, +and incident coordination. Before Naruon consumes any external capability, its +published evidence must identify upstream ownership for service health, artifact +promotion/quarantine, scientific validation, deletion, incident escalation, and +a tested disable path. This document assigns no responsibility to TEPP. Naruon +keeps its own full-disable path, and a protocol failure at the ownership seam +fails closed rather than being assigned to the user or hidden by a fallback. diff --git a/docs/topic-intelligence/PRD.md b/docs/topic-intelligence/PRD.md new file mode 100644 index 000000000..9ed5980c4 --- /dev/null +++ b/docs/topic-intelligence/PRD.md @@ -0,0 +1,129 @@ +# Product requirements: topic intelligence + +- **Status:** removal `ACTIVE-PR`; local policy `ACCEPTED-NARUON-POLICY`; + runtime integration `BLOCKED-UPSTREAM` +- **Date:** 2026-08-09 +- **Related change:** PR #1297 +- **Accepted local decision:** [ADR-0001](../adr/0001-topic-measurement-authority.md) +- **Proposed target decisions:** + [ADR-0002](../adr/0002-fitted-topic-artifact-consumption.md) and + [ADR-0003](../adr/0003-separate-topic-measurement-from-agenda-generation.md) + +## Problem + +Naruon exposed `email_categorizer` and `meeting_agenda_generator` through product +names that suggested topic understanding, although both used small fixed Korean +and English term tables. Deterministic output made those rules reproducible; it +did not make them a fitted topic model. The behavior hid uncertainty, confused +business labels with latent topic identity, and failed across languages and +domains. + +Users need an honest boundary between lexical utilities and corpus-derived topic +measurement. They also need Naruon to withhold a topic result when the required +fitted model, contract, input support, or evidence is absent. + +## Users and needs + +| User | Need | +| --- | --- | +| Knowledge worker | Know whether a result is lexical metadata, an evidence-valid posterior, an abstention, or an error. | +| Workspace administrator | Ensure tenant content is purpose-bound and never sent to an unapproved model or corpus. | +| Analyst or research owner | Verify a result against a versioned model, frozen preprocessing/vocabulary, design, times, and diagnostics. | +| Operator | Detect incompatibility, integrity failure, abstention, drift, and service failure without logging message bodies. | +| Developer or reviewer | Prevent lexical, embedding, clustering, or LLM shortcuts from being mislabeled as STM. | + +## Goals + +1. Remove executable product behavior that implies topic inference without a + fitted corpus-level model. +2. Preserve useful keyword extraction only under an explicit lexical contract. +3. Establish a Naruon-local, fail-closed acceptance boundary for any future + independently published fitted-model integration. +4. Keep numeric topic identity, human labels, downstream decisions, and agenda + generation separate. +5. Make future results verifiable against authorized source evidence and an + immutable compatible model artifact. + +## Non-goals + +- Fitting a topic model inside an API request or training models inside Naruon. +- Assigning responsibilities to TEPP or claiming that TEPP accepted this PRD, + ADR, or a Naruon-authored contract. +- Calling keyword counts, embeddings, clustering, classifiers, zero-shot output, + or LLM labels “STM.” +- Adding a Naruon topic route, table, migration, public response, or UI before a + real upstream contract and release evidence exist. +- Reintroducing agenda generation as a topic-measurement side effect. +- Claiming causal effects or individual attributes from group-level topic + prevalence. + +## Product requirements + +| ID | Requirement | Acceptance evidence | Maturity | +| --- | --- | --- | --- | +| `TI-REQ-001` | Remove `email_categorizer` and `meeting_agenda_generator` from the tool registry and implementation. | Registry regression tests and source absence. | `ACTIVE-PR` | +| `TI-REQ-002` | Describe retained `keyword_extractor` output as deterministic lexical frequency/first-occurrence metadata, never topic evidence. | Registry description and handler tests. | `ACTIVE-PR` | +| `TI-REQ-003` | Fail closed until an independently published, compatible fitted-model artifact/API/contract exists; never substitute a default category, synthetic posterior, keyword/embedding/LLM result, or agenda template. | Accepted ADR-0001, proposed ADR-0002, and future negative-path adapter tests. | `ACCEPTED-NARUON-POLICY`; target `PLANNED`; runtime `BLOCKED-UPSTREAM` | +| `TI-REQ-004` | A future valid result returns a mixed-membership topic vector with diagnostics and intervals whose level, method, and uncertainty scope are explicit; compatible-model abstention is a distinct vector-free state. | Independently published calibration/coverage evidence plus Naruon schema and invariant tests. | `BLOCKED-UPSTREAM` | +| `TI-REQ-005` | Any temporal, multilevel, multiple-membership, or cross-classified STM extension names its estimator, analysis unit, estimand, prevalence/content formula and contrasts, membership weights/normalization/unseen-level policy, non-causal status, and validation evidence. | Model card, design manifest, known-truth simulation, and downstream suppression tests. | `BLOCKED-UPSTREAM` | +| `TI-REQ-006` | Bind every result to all 14 fields in the [canonical digest inventory](README.md#canonical-digest-inventory), including `model_card_digest`, `validation_report_digest`, `covariate_snapshot_digest`, and `design_row_digest`. Treat digests as verification, not reconstruction; later reproducibility also requires a resolvable retained snapshot. | Fourteen-field schema/inventory parity, digest/provenance, temporal-leakage, retention, and replay tests. | `BLOCKED-UPSTREAM` | +| `TI-REQ-007` | Keep numeric topic identity separate from evidence-backed, language-aware, versioned human-readable labels. | Schema, presentation, and UI contract tests. | `BLOCKED-UPSTREAM` | +| `TI-REQ-008` | Enforce tenant, workspace, source, purpose, consent, region, retention, deletion, digest-handling, and log/metric/trace-redaction controls before inference. | Authorization, isolation, deletion, restricted-audit, and redaction tests. | `BLOCKED-UPSTREAM` | +| `TI-REQ-009` | Treat agenda generation as a separately authorized downstream decision/generation capability with its own evidence and audit contract. | Accepted separation policy in ADR-0001; proposed ADR-0003 plus separate product/technical contract, endpoint, permissions, and tests before release. | `ACCEPTED-NARUON-POLICY`; target decision and future capability `PLANNED` | +| `TI-REQ-010` | Expose a product UI only after the runtime contract, compatible artifact, uncertainty language, abstention/error states, security controls, and operational gates are real. | Release-readiness review and source-backed E2E tests. | `PLANNED` | + +## User journeys + +### Current candidate + +1. A user or agent lists available analysis tools. +2. The two pseudo-topic tools are absent. +3. Keyword extraction, when selected, is described as lexical frequency rather + than inferred topics. + +### Future valid inference + +1. An authorized user requests topic intelligence for a bounded document + snapshot and declared purpose. +2. Naruon validates scope, minimization, language metadata, time semantics, and + an operator-approved upstream model policy. +3. An independently published upstream interface supplies publisher-accepted + evidence for a compatible active fitted artifact and returns either a mixed- + membership result or a narrowly defined scientific abstention. +4. Naruon validates the pinned contract and numerical invariants before + presenting permitted posterior, provenance, uncertainty, and label evidence. + +### Future input or operational error + +Unsupported language, insufficient retained tokens, excessive OOV input, +invalid temporal/covariate data, missing/incompatible model, integrity failure, +authorization denial, or timeout returns a stable error. No posterior, label, or +fallback is produced. + +### Future scientific abstention + +Only after a compatible active model accepts the input contract may its declared +posterior or diagnostic acceptance rule return `abstained`. The result contains +a stable reason and no topic vector or label. + +## Success and release gates + +- Zero registered pseudo-topic tools and zero production references to their + handlers or fixed dictionaries on the candidate head. +- Lexical extraction remains bounded, deterministic, and honestly named. +- Every future result can be verified against exact source/artifact identities + and digests; any replay or reproducibility claim also proves the authorized + immutable snapshot remains resolvable under an approved retention contract. +- Topic proportions, interval coverage/calibration, diagnostics, and any + extended-STM structures have independently published scientific-validation + evidence before a Naruon adapter is enabled. This is a Naruon acceptance gate, + not an assignment of duties to the publisher. +- Tenant isolation, purpose, redaction, incompatibility, integrity, timeout, + rollback, and no-fallback tests pass with warnings treated as failures. +- Product copy distinguishes lexical terms, numeric topics, human labels, + uncertainty, scientific abstention, and operational/input errors. + +No numeric latency, availability, or scientific-quality target is invented in +this PR. Such targets require an independently published production contract, +representative corpus/workload, capacity study, model card, and approved release +evidence. diff --git a/docs/topic-intelligence/README.md b/docs/topic-intelligence/README.md new file mode 100644 index 000000000..17d9b89de --- /dev/null +++ b/docs/topic-intelligence/README.md @@ -0,0 +1,159 @@ +# Topic intelligence decision package + +- **Snapshot:** 2026-08-09 +- **Change candidate:** [PR #1297](https://github.com/ContextualWisdomLab/naruon/pull/1297) +- **Protected-base evidence:** `develop@5425ce4f55b2cf16b2c82a4fd661c9d0bd0660c7` +- **Scope owner:** Naruon maintainers + +This directory is Naruon's authority graph for removing lexical pseudo-topic +tools and for evaluating any later structural-topic-model (STM) integration. It +does not govern TEPP, assign scientific authority to TEPP, record TEPP acceptance, +or claim that an upstream production contract exists. + +The package is design-sufficient for the deletion review and for future contract +discovery. It is intentionally partial for runtime implementation and is not +evidence that STM is available in Naruon. + +## Maturity vocabulary + +| Term | Meaning | +| --- | --- | +| `IMPLEMENTED-ON-PROTECTED-DEVELOP` | Observable behavior on the pinned protected-base snapshot | +| `ACTIVE-PR` | Implemented only on PR #1297's candidate branch | +| `ACCEPTED-NARUON-POLICY` | An accepted Naruon-local architecture/product rule; not runtime evidence or upstream acceptance | +| `PLANNED` | Designed or required, but not implemented | +| `BLOCKED-UPSTREAM` | Naruon work cannot start until an independently published, versioned upstream production contract and acceptance evidence exist | +| `OUT-OF-SCOPE` | Deliberately excluded from this change | + +Documentation fitness uses a separate vocabulary: +`PRESENT-CURRENT`, `PRESENT-STALE`, `PARTIAL`, `MISSING`, `NOT-APPLICABLE`, +and `SUPERSEDED`. + +ADR status is separate again. [ADR-0001](../adr/0001-topic-measurement-authority.md) +is the accepted Naruon-local policy. [ADR-0002](../adr/0002-fitted-topic-artifact-consumption.md) +and [ADR-0003](../adr/0003-separate-topic-measurement-from-agenda-generation.md) +are proposed target decisions, not accepted architecture or runtime evidence. +`PLANNED` may describe their design work, but it never overrides the runtime +capability gate `BLOCKED-UPSTREAM`. + +## Current truth + +| Concern | Maturity | Evidence-backed statement | +| --- | --- | --- | +| Protected `develop` behavior at the pinned base | `IMPLEMENTED-ON-PROTECTED-DEVELOP` | `email_categorizer` and `meeting_agenda_generator` are registered lexical heuristics. | +| Candidate behavior | `ACTIVE-PR` | PR #1297 removes both tools and retains `keyword_extractor` only as an explicitly lexical utility. | +| Naruon consumption rule | `ACCEPTED-NARUON-POLICY` | Naruon does not present keywords, embeddings, clustering, zero-shot output, or LLM labels as an STM posterior. The ADR becomes protected-branch authority only when the candidate is accepted and merged. | +| Upstream fitted-model dependency | `BLOCKED-UPSTREAM` | TEPP architecture provides direction, but Naruon has no independently published TEPP production topic artifact/API/contract or TEPP acceptance evidence to consume. | +| Proposed Naruon STM target profile | `PLANNED`; capability `BLOCKED-UPSTREAM` | The acceptance profile is design material. No production handler, endpoint, table, model artifact, migration, or UI exists, and runtime work cannot start without the independently published upstream dependency. | +| Agenda generation from topic evidence | `PLANNED` | It is a separate downstream decision/generation capability, never part of topic measurement. | + +## Authority graph + +```mermaid +flowchart TD + ADR1["ADR-0001: accepted local policy"] --> PRD["PRD: product intent"] + ADR2["ADR-0002: proposed adapter"] -.-> TRD + ADR3["ADR-0003: proposed agenda boundary"] -.-> PRD + PRD --> TRD["TRD: technical obligations"] + TRD --> DESIGN["Architecture, UML, and data model"] + TRD --> CONTRACT["Planned adapter contract"] + DESIGN --> ASSURANCE["Security, tests, and operations"] + CONTRACT --> ASSURANCE + ASSURANCE --> TRACE["Traceability and fitness"] +``` + +Solid arrows descend from the accepted local policy. Dotted arrows identify +proposed Naruon decisions whose acceptance triggers have not been satisfied. + +When documents conflict, the accepted [Naruon-local +ADR](../adr/0001-topic-measurement-authority.md) governs Naruon's decision, the +PRD governs product intent, and the TRD governs proposed implementation +obligations. Runtime code and deployed OpenAPI remain the authority for shipped +behavior. A checked planned schema is not a deployed API and cannot stand in for +the future upstream contract. + +## Document map + +| Document | Purpose | +| --- | --- | +| [PRD](PRD.md) | Product problem, users, requirements, non-goals, and release gates | +| [TRD](TRD.md) | Technical ownership, artifact, result, failure, security, and implementation obligations | +| [Documentation fitness](DOCUMENTATION_FITNESS.md) | Before/after completeness assessment and intentional gaps | +| [ADR index](../adr/README.md) | Status and change rules for all Naruon architecture decisions | +| [Naruon ADR-0001](../adr/0001-topic-measurement-authority.md) | Accepted local consumption policy and upstream non-authority boundary | +| [Proposed ADR-0002](../adr/0002-fitted-topic-artifact-consumption.md) | Conditional fitted-artifact consumption and fail-closed adapter decision | +| [Proposed ADR-0003](../adr/0003-separate-topic-measurement-from-agenda-generation.md) | Conditional downstream agenda-generation separation decision | +| [Architecture](ARCHITECTURE.md) | Current and target components, trust boundaries, and failure architecture | +| [UML](UML.md) | Conceptual component, class, sequence, state, and deployment views | +| [Conceptual ERD](DATA_MODEL.md) | Contract relationships without inventing physical persistence | +| [Planned adapter contract](API_CONTRACT.md) | Closed-version envelope, errors, abstention, and compatibility semantics | +| [Security](SECURITY.md) | Data protection and control requirements | +| [Threat model](THREAT_MODEL.md) | Design-time misuse cases, mitigations, and residual decisions | +| [Test strategy](TEST_STRATEGY.md) | Naruon integration evidence separated from upstream scientific validation | +| [Operability](OPERABILITY.md) | Promotion, monitoring, incident, rollback, and recovery gates | +| [Traceability](TRACEABILITY.md) | Requirement-to-decision-to-contract-to-evidence mapping | +| [References](REFERENCES.md) | Scientific, standards, and repository evidence | + +## Canonical digest inventory + +Revision `2026-08-09.1` has exactly 14 canonical digest fields. This table is the +single cross-document inventory; the planned +[JSON Schema](schema/topic-inference-result-v1.schema.json) is the machine-readable +definition, and the [API contract](API_CONTRACT.md#digest-contract) defines the +canonicalization formula. The Naruon-authored field name `tepp_payload_digest` +is part of the local acceptance profile and does not assert that an upstream +publisher adopted the name or assigned ownership to TEPP. + +| Scope | Exact field | Bound evidence | +| --- | --- | --- | +| Envelope | `schema_digest` | Complete parsed immutable schema JSON value named by the pinned `$id`; its sole construction is defined in the API contract | +| Envelope | `source_snapshot_digest` | Authorized immutable source-snapshot descriptor | +| Envelope | `tepp_payload_digest` | Complete nested scientific-payload descriptor | +| Scientific provenance | `artifact_digest` | Canonical fitted-artifact descriptor, not raw artifact bytes | +| Scientific provenance | `manifest_digest` | Canonical artifact manifest, including any separately declared optional raw-byte hash record | +| Scientific provenance | `vocabulary_digest` | Frozen vocabulary | +| Scientific provenance | `preprocessing_digest` | Frozen preprocessing contract | +| Scientific provenance | `design_digest` | Statistical design specification | +| Scientific provenance | `lineage_digest` | Training and build lineage descriptor | +| Scientific provenance | `model_card_digest` | Model card | +| Scientific provenance | `validation_report_digest` | Scientific validation report | +| Scientific provenance | `evidence_time_manifest_digest` | Evidence-time manifest | +| Scientific provenance | `covariate_snapshot_digest` | Authorized covariate and membership snapshot | +| Scientific provenance | `design_row_digest` | Compiled design row | + +Aliases and shortened subsets are not contract-equivalent. Any field addition, +removal, rename, canonicalization change, or domain-separator change requires a +new immutable schema revision and synchronized updates to requirements, +decisions, tests, traceability, and this inventory. + +These 14 fields verify equality with exact canonical JSON values under the API +formula. They do not by themselves verify descriptor truth or completeness, +evidence availability, authorization, or raw fitted-artifact bytes. Raw-byte +integrity exists only when an independently published manifest carries a +distinct optional hash record that declares both its algorithm and the exact +byte serialization or package covered. That record is not `artifact_digest` +and does not add a canonical digest field to this inventory. + +## Non-negotiable behavior + +No keyword table, term-frequency score, embedding cluster, zero-shot label, or +LLM-generated label may be represented as an STM posterior. New-document STM +inference requires an independently published, compatible fitted artifact with +frozen preprocessing and vocabulary, declared input/covariate semantics, +uncertainty, diagnostics, provenance, and validation evidence. + +Model or service unavailability, incompatibility, integrity failure, unsupported +language, insufficient retained tokens, excessive out-of-vocabulary input, +invalid temporal/covariate input, authorization denial, and timeout are explicit +errors. `abstained` is reserved for a compatible active model that accepted the +input contract but withheld a posterior under a declared diagnostic or posterior +acceptance rule. Neither path may invoke a lexical, embedding, LLM, default-label, +or agenda fallback. + +Canonical contract digests verify equality with retained canonical JSON values; +they do not establish the truth of those values, prove raw-byte equality, or +reconstruct source content. Every content-, evidence-, covariate-, membership-, +temporal-, design-, and label-derived digest is sensitive pseudonymous linkage +data. Later reproducibility requires a separately approved, resolvable immutable +snapshot/evidence reference and retention contract. Raw fitted-artifact bytes +also require the separate manifest-owned byte hash described above. diff --git a/docs/topic-intelligence/REFERENCES.md b/docs/topic-intelligence/REFERENCES.md new file mode 100644 index 000000000..31062e7e3 --- /dev/null +++ b/docs/topic-intelligence/REFERENCES.md @@ -0,0 +1,100 @@ +# Topic intelligence references + +**Snapshot date:** 2026-08-09 (Asia/Seoul) + +**Maturity:** reference design `PLANNED`; runtime integration +`BLOCKED-UPSTREAM` + +These sources ground the scientific, provenance, risk, security-development, +and wire-contract boundaries. A citation does not establish Naruon or TEPP +conformity, certification, production readiness, or implementation. + +## Structural topic modeling + +Roberts, M. E., Stewart, B. M., Tingley, D., Lucas, C., Leder-Luis, J., +Gadarian, S. K., Albertson, B., & Rand, D. G. (2014). Structural topic models +for open-ended survey responses. *American Journal of Political Science, 58*(4), +1064–1082. https://doi.org/10.1111/ajps.12103 + +Roberts, M. E., Stewart, B. M., & Tingley, D. (2019). stm: An R package for +structural topic models. *Journal of Statistical Software, 91*(2), 1–40. +https://doi.org/10.18637/jss.v091.i02 + +These works support mixed-membership topic estimation, document-level metadata, +and uncertainty-aware analysis. They do not by themselves establish a +multilevel, multiple-membership, cross-classified, longitudinal, multilingual, +or production-serving estimator. Naruon remains blocked from accepting any such +upstream extended-STM design unless independently published evidence names the +method and estimand, freezes formulas/contrasts, and supplies separate known-truth +validation. This acceptance condition assigns no obligation to TEPP. + +## Risk, security, and provenance standards + +National Institute of Standards and Technology. (2023). *Artificial +Intelligence Risk Management Framework (AI RMF 1.0)* (NIST AI 100-1). +https://doi.org/10.6028/NIST.AI.100-1 + +National Institute of Standards and Technology. (2022). *Secure Software +Development Framework (SSDF) version 1.1: Recommendations for mitigating the +risk of software vulnerabilities* (NIST SP 800-218). +https://doi.org/10.6028/NIST.SP.800-218 + +National Institute of Standards and Technology. (2024). *Secure software +development practices for generative AI and dual-use foundation models: An SSDF +community profile* (NIST SP 800-218A). +https://doi.org/10.6028/NIST.SP.800-218A + +International Organization for Standardization. (2023). *ISO/IEC 42001:2023— +Information technology—Artificial intelligence—Management system*. +https://www.iso.org/standard/42001.html + +International Organization for Standardization. (2023). *ISO/IEC 23894:2023— +Information technology—Artificial intelligence—Guidance on risk management*. +https://www.iso.org/standard/77304.html + +World Wide Web Consortium. (2013). *PROV-O: The PROV ontology* (W3C +Recommendation). https://www.w3.org/TR/prov-o/ + +These sources inform risk ownership, lifecycle evidence, secure development, +and provenance. The documents in this package use them as design guidance and +make no audit or certification claim. + +## Wire-contract standards + +Nottingham, M., Wilde, E., & Dalal, S. (2023). *Problem details for HTTP APIs* +(RFC 9457). RFC Editor. https://www.rfc-editor.org/rfc/rfc9457.html + +Rundgren, A., Jordan, B., & Erdtman, S. (2020). *JSON Canonicalization Scheme +(JCS)* (RFC 8785). RFC Editor. https://www.rfc-editor.org/rfc/rfc8785.html + +JSON Schema. (2020). *JSON Schema specification: Draft 2020-12*. +https://json-schema.org/draft/2020-12 + +JSON Schema. (2020). *JSON Schema validation: A vocabulary for structural +validation of JSON* (Draft 2020-12). +https://json-schema.org/draft/2020-12/json-schema-validation + +RFC 9457 grounds the planned HTTP problem-details shape only after a transport +ADR selects HTTP. RFC 8785 grounds deterministic canonical JSON bytes for the +planned domain-separated digest contract; it does not normalize Unicode. Draft +2020-12 grounds the planned Naruon adapter schema. Its `format` keyword does not +by itself prove that a chosen validator asserts date-time validity; Naruon's +future validator and fixtures must exercise the required format behavior. Exact +schema identity, revision, and digest must be immutable and pinned; the checked +schema is not a deployed OpenAPI component or TEPP's canonical payload. + +## Inspected TEPP repository evidence + +- Repository: [ContextualWisdomLab/tepp](https://github.com/ContextualWisdomLab/tepp) +- Exact inspected protected-`main` revision: + [`b8e26aae334397daa1974d4a24c9015cfd682600`](https://github.com/ContextualWisdomLab/tepp/commit/b8e26aae334397daa1974d4a24c9015cfd682600) +- Commit timestamp: `2026-08-06T11:33:18+09:00` +- Inspection date: `2026-08-09` (Asia/Seoul) + +At that exact revision, `crates/evidence_core/` contains immutable evidence +domain primitives and the JSON wire boundary. `ARCHITECTURE.md` names +`topic_measurement` only in the target architecture. There is no corresponding +production topic-measurement crate or endpoint, and +`crates/tepp_api/src/lib.rs` explicitly states that the foundation slice exposes +no production behavior. The observation is revision-bound and must be refreshed +before any implementation or maturity claim. diff --git a/docs/topic-intelligence/SECURITY.md b/docs/topic-intelligence/SECURITY.md new file mode 100644 index 000000000..01b004112 --- /dev/null +++ b/docs/topic-intelligence/SECURITY.md @@ -0,0 +1,115 @@ +# Topic intelligence security requirements + +**Status:** pseudo-topic removal `ACTIVE-PR`; target security design `PLANNED`; +runtime integration `BLOCKED-UPSTREAM` + +This document supplements the repository-wide [security policy](../../SECURITY.md). +It does not claim NIST or ISO conformity. The safest current state is that no +Naruon-to-TEPP topic-inference boundary exists. The current change removes +misleading local behavior and introduces no new network, persistence, or model +execution surface. + +## Protected assets + +- message and document content, exact source evidence, and bounded snapshots; +- tenant, workspace, user, purpose, consent, region, time, group, and membership + metadata; +- fitted model bytes, manifests, frozen vocabulary and preprocessing/design + specifications, validation reports, label evidence, and promotion decisions; +- posterior topic mixtures, uncertainty, diagnostics, and downstream decisions; +- service credentials, artifact-signing and verification material, audit events, + retention/deletion records; and +- every content-, evidence-, covariate-, membership-, temporal-, design-, or + label-derived digest. Such digests are sensitive pseudonymous linkage values, + not anonymous or generally safe telemetry. + +## Input authority + +| Input class | Authority and treatment | +| --- | --- | +| User intent | Attacker-controlled until a verified Naruon session and policy authorize the exact source and purpose. | +| Document content | Attacker-controlled data, never instructions; bounded before crossing the service boundary. | +| Tenant/workspace/source scope | Resolved from verified server-side identity and records, never trusted from public headers or caller ownership fields. | +| Covariates and membership | Server-resolved, typed, purpose-approved, level-aware, and explicit about observed/missing state. Caller-supplied group identity or weight is not authority. | +| Model and artifact selection | Operator-controlled allowlist of immutable versions and digests. A tenant cannot provide a URL, path, mutable alias, or signing key. | +| `evidence_ref` | If a future contract permits it, an opaque, audience-bound, tenant-bound, expiring capability that is reauthorized at resolution; never an arbitrary URI or filesystem path. | + +## Required controls + +| Control area | Requirement | +| --- | --- | +| Identity | Accept only verified Naruon session and mutually authenticated service identity. Public identity headers and payload ownership claims are not authority. | +| Authorization | Apply deny-first RBAC/ABAC for tenant, workspace, user, source, purpose, consent, region, group, and customer policy before snapshot creation. On every use, require the evidence reference, document reference, source-snapshot revision, audience, expiry, authorization-policy version, and opaque authorization binding to resolve to the same current server-verified tenant/workspace/source/purpose scope; a schema-valid reference is never authority by itself. | +| Minimization | Send only bounded evidence and covariates required by the approved estimand. Exclude unrelated history, credentials, provider URLs, and sequential database identifiers. | +| Tenant isolation | Partition authorization, caches, idempotency, artifact policy, telemetry, rate limits, audit, retention, and deletion. Never reuse a content-bearing or derived-result cache entry across tenants. | +| Transport | Encrypt in transit, mutually authenticate services, bind audience and operation, enforce deadlines, and reject replay outside the idempotency contract. | +| Evidence references | Resolve only opaque server-issued references after rechecking tenant/source/purpose scope. Do not fetch caller URLs or follow redirects. | +| Artifact integrity | Resolve an allowlisted immutable artifact; verify raw artifact bytes and the manifest, vocabulary, preprocessing, design, lineage, model-card, validation-report, build-provenance, and promotion-state identities and digests before inference. Tampering with any one binding, signer state, or retained validation report quarantines the exact deployment. Support signer revocation, downgrade prevention, quarantine, and key rotation. | +| Contract integrity | Pin the exact contract major, immutable schema revision/identifier and digest, diagnostic/quality/reason-code registry versions, and acceptance-policy version. Reject unknown fields, unnegotiated revisions, unknown codes, incompatible runtime state, and malformed numerical output; an unknown upstream code is a protocol error, never an inferred result or abstention. | +| Output control | Keep model-scoped non-semantic topic identity separate from labels, authorize label evidence through its own audience- and model/topic-bound reference, validate uncertainty and diagnostics, and project only product-approved fields. A public projection retains the non-sensitive safety semantics needed to interpret it: model identity/version, analysis unit, versioned estimand, covariate level when applicable, and non-causal designation. It must not expose raw covariates, tenant bindings, or sensitive digests. | +| Derived digests | Treat all content/evidence/covariate/design/label digests as sensitive. Keep raw digests out of product responses, logs, metrics, and traces. Restricted audit should prefer opaque references or tenant-scoped keyed digests with domain separation. | +| Logging | Record only opaque request/result/model references, versions, outcome codes, latency, and redacted aggregate diagnostics. Never log raw content, excerpts, direct identifiers, credentials, sensitive covariates, group values, or unkeyed derived digests. | +| Retention/deletion | Establish purpose-specific TTLs, deletion propagation, cache eviction, audit retention, and keyed-digest rotation before persisting a snapshot or posterior. No topic-specific Naruon persistence is approved today. | +| Availability | Use size/token/time/concurrency bounds, quotas, cancellation, circuit breaking, and bounded retry. Authentication/authorization denial, rate limiting, deadline expiry, and cancellation have stable non-`200` error semantics and never become scientific abstention. Degradation fails closed and never activates keyword, embedding, LLM, cached-other-model, category, or agenda fallback. | +| Secrets and supply chain | Use the repository's operator-managed credential path; pin TEPP build and schema provenance; never place credentials or signing keys in model manifests. | + +## Privacy and statistical safety + +Topic mixtures can disclose health, political, labor, legal, financial, or other +sensitive themes without exposing source text. Membership covariates and rare +groups increase re-identification, stigmatization, and ecological-fallacy risk. +Therefore: + +- every result and model card binds an analysis unit, a versioned estimand, the + covariate level, membership semantics, and an explicit causal/non-causal + designation; +- every public result preserves those non-sensitive interpretation fields, or the + public endpoint is constrained by a versioned contract to one fixed analysis + unit and estimand and communicates that constraint explicitly; +- group-level prevalence or covariate effects MUST NOT be presented as an + individual's trait, intent, diagnosis, or causal outcome; +- individual content MUST NOT be generalized back to a group without a separate + approved estimand, privacy review, and downstream authorization; +- sensitive covariates require a documented purpose, minimum necessary fields, + explicit missingness, access policy, and model-card disclosure; +- multiple-membership weights require an opaque membership set and group/level, + a frozen normalization rule, and an unseen-level policy; missing membership is + never converted to a default group; +- evidence availability, knowledge cutoff, assertion time, and any nullable event + or document time follow an immutable temporal policy with explicit missingness, + canonical time-zone handling, and validated ordering; a declared `valid` status + is not a substitute for recomputing those relations; +- aggregate displays require approved minimum-cell and sparse-group suppression + rules, with tests against differencing and repeated-query attacks; +- training membership, representative documents, source excerpts, and label + evidence are not exposed to ordinary users; and +- a display label or excerpt requires separate authorization and safe rendering. + Its evidence reference has a label-specific audience and is bound to the exact + model, topic, label version, and language. It never changes model-scoped numeric + topic identity or becomes executable HTML. + +## Audit evidence + +A future restricted audit record may contain an opaque actor/workspace scope, +purpose code, opaque request/result reference, selected model and contract +versions, verified artifact-manifest reference, outcome/abstention/error code, +policy-decision reference, assertion time, and a redacted diagnostic summary. +It must not contain source text, label excerpts, sensitive group values, or raw +derived digests. When exact binding is required, use an opaque audit reference or +a tenant-scoped keyed digest with a documented algorithm, domain separator, +canonical empty representation, retention, deletion propagation, and key +rotation. Audit access and retention are separate from product-result access. + +## Security release gate + +No adapter can be enabled until the real transport, service authentication, +artifact registry/signing, result retention, cache, rate-limit, covariate, and +downstream-consumer decisions have approved ADRs and this threat model is +refreshed against them. Tenant-isolation, confused-deputy, authorization and +reference cross-binding, artifact/validation-report/digest tamper and downgrade, +schema and diagnostic-code confusion, public scientific-semantics projection, +temporal ordering, digest-linkage, log-redaction, deletion, authentication, +rate-limit, deadline, cancellation, retry, cache, label-evidence/rendering, and +rollback tests must pass. +An operator must be able to quarantine one artifact or disable the entire +integration immediately without reactivating pseudo-topic behavior. diff --git a/docs/topic-intelligence/TEST_STRATEGY.md b/docs/topic-intelligence/TEST_STRATEGY.md new file mode 100644 index 000000000..72edb970b --- /dev/null +++ b/docs/topic-intelligence/TEST_STRATEGY.md @@ -0,0 +1,156 @@ +# Topic intelligence test strategy + +**Status:** pseudo-topic removal tests `ACTIVE-PR`; target integration-test design +`PLANNED`; runtime integration evidence `BLOCKED-UPSTREAM` + +## Test ownership + +Naruon verifies product/API correctness, authorization, tenant isolation, +integration safety, contract enforcement, and honest presentation. Before +consumption, Naruon requires independently published upstream evidence covering +model estimation, new-document inference, conditional uncertainty, temporal and +extended-STM behavior, artifact reproducibility, and implementation parity. +Naruon may consume signed validation evidence; it must not duplicate a toy +estimator and claim that it proves upstream scientific validity. This requirement +governs Naruon's acceptance decision and assigns no obligation to TEPP. + +## Current removal evidence + +| Contract | Test or evidence | +| --- | --- | +| Pseudo-topic tools absent | `test_registry_omits_lexical_pseudo_topic_tools` | +| Lexical utility described honestly | `test_keyword_extractor_is_disclosed_as_lexical_term_frequency` | +| Lexical determinism, language, and empty input | Existing `keyword_extractor_handler` tests | +| Analysis input bound retained | Existing oversized-analysis-text tests | +| Documentation authority graph and planned schema | `test_topic_intelligence_documentation.py` | + +All Python checks run with warnings promoted to failures. Ruff, balanced +documentation checks, and `git diff --check` are required. These checks prove +the deletion and documentation contract only; they do not prove STM behavior. + +## Future Naruon contract tests + +- exact accepted/rejected contract majors, immutable schema IDs/revisions and + schema digests; unknown fields and unnegotiated revisions fail closed; +- inferred versus abstained result shapes, stable error classes, malformed + diagnostics, exact accepted diagnostic-code registry versions, unknown-code + rejection as a protocol error, and top-level/diagnostic status agreement; +- topic proportion range and sum tolerance; model-scoped non-semantic topic IDs; + rank uniqueness and ordering; equality among fitted, declared, observed, and + serialized component counts; credible-interval ordering and containment; and + declared interval level, method, and uncertainty scope; +- exact model, manifest, artifact, vocabulary, preprocessing, design, lineage, + model-card, evidence-time, covariate-snapshot, and design-row binding; +- canonical empty covariate/design representation and domain-separated digest + behavior when the model uses no covariates; +- model unavailable, trusted-request conflict, deployment incompatibility, + artifact integrity failure, unsupported language, insufficient tokens, + excessive OOV, temporal/covariate invalidity, diagnostic abstention, + authentication/authorization denial, rate limiting, deadline expiry, + cancellation, bounded retry, and idempotency mismatch, each with its stable + non-`200` mapping and retry rule where applicable; +- no keyword, embedding, LLM, cached-other-artifact, category, or agenda fallback + on every failure, cancellation, disabled, quarantine, and rollback path; +- verified identity and tenant/workspace/user/source scope, purpose, consent, + region, role, group, and customer-policy deny precedence; +- cross-tenant request/result/cache/idempotency/rate-limit/audit isolation; +- opaque evidence-reference audience, tenant, workspace, source, purpose, expiry, + replay, reauthorization, redirect, SSRF, and file-path rejection behavior; + mismatched document/evidence snapshot revisions or authorization bindings fail + before any upstream call; +- analysis-unit, estimand, non-causal designation, covariate level, membership + structure/normalization conditional combinations, missingness and unseen-level + policy, minimum-cell/sparse-group suppression, public projection of required + non-sensitive semantics, and prohibition on individual-attribute claims from + group effects; +- immutable temporal-policy identity; asserted date-time format; nullable-time + missingness; and ordering tests including evidence unavailable at the knowledge + cutoff; +- safe label rendering, rejection of semantic labels as topic IDs, component/topic + referential integrity, and independent label-evidence audience/authorization; +- one-at-a-time tampering of payload, source-snapshot, schema, raw artifact, + manifest, vocabulary, preprocessing, design, lineage, model-card, + validation-report, evidence-time, covariate-snapshot, design-row, signature, + signer-state, build-provenance, and promotion-state bindings; and +- fixtures captured from the exact production TEPP implementation. Mocks alone + are not release evidence. + +## Future independently published scientific evidence + +Naruon's acceptance decision requires an independently published validation +packet that includes: + +- known-truth corpus simulation with label switching resolved explicitly before + topic-wise comparison; +- topic-proportion bias and RMSE plus interval coverage/calibration for the + declared interval level, method, and uncertainty scope; +- disclosure that new-document intervals are conditional on the frozen fitted + artifact unless broader model/training uncertainty is separately implemented + and validated; +- prevalence and content covariate recovery with explicit missingness; +- multilevel, multiple-membership, cross-classified, or longitudinal recovery + only for a documented upstream extended-STM estimator, analysis unit, estimand, + formula/contrasts, weight normalization, and unseen-level policy; +- temporal train/validation splits and knowledge-cutoff leakage checks using + evidence availability rather than only event time; +- preprocessing, vocabulary, artifact, design, and new-document inference + reproducibility from immutable manifests; +- unsupported-language, low-token, OOV, degenerate-document, adversarial input, + covariate, and temporal rejection, separately from posterior/diagnostic + abstention; +- convergence and diagnostic rejection plus immutable promotion thresholds; +- determinism within declared tolerance and CPU/GPU/alternate-runtime parity; + and +- corpus drift and label-evidence review without silently changing numeric topic + identity across model versions. + +Scientific thresholds must come from representative data and be recorded in the +model card. This document intentionally invents no quality target. + +## Security and privacy adversarial tests + +- attempt cross-tenant source, result, cache, idempotency, and deletion access; +- tamper independently with the schema, payload and snapshot digests, raw artifact, + manifest, vocabulary, preprocessing, design, lineage, model-card, + validation-report, evidence-time, covariate/design-row, signature, signer state, + build provenance, and promotion state; +- request a mutable alias, older artifact, revoked signer, arbitrary endpoint, + provider URL, redirecting evidence reference, local/private address, or file + path; +- submit oversized, multilingual, prompt-like, low-token, high-OOV, crafted + membership, non-finite weight, missingness, and future-availability inputs; +- inspect logs, metrics, traces, problem details, audit, fixtures, and snapshots + for raw content, excerpts, direct identifiers, credentials, sensitive group + values, or any unkeyed content/evidence/covariate/design/label digest; +- prove tenant-keyed digests use canonical bytes and domain separation, cannot be + compared across tenants, rotate safely, and disappear under deletion policy; +- measure repeated-query membership/model inference risk and confirm rate/query + controls and aggregate suppression resist differencing; and +- quarantine/disable the integration during in-flight work and prove no fallback + or stale result reaches a consumer. + +## Test data + +Use synthetic or appropriately licensed and de-identified corpora for CI. +Production message bodies, tenant identifiers, secrets, sensitive membership +attributes, and production-derived digests must not enter fixtures, snapshots, +logs, or external evaluation services. Multilingual and code-switching fixtures +are allowed only after the artifact declares support. Redistributable research +PDFs may be committed; otherwise cite and summarize the official source. + +## Release matrix + +| Gate | Removal PR | Future adapter | Future UI/downstream consumer | +| --- | --- | --- | --- | +| Focused unit and contract tests | Required | Required | Required | +| Full warnings-as-errors suite | Required | Required | Required | +| Independently published scientific validation packet | Not applicable | Required | Required | +| Tenant/security/privacy/threat tests | No new runtime boundary | Required | Required | +| Exact real-service contract E2E | Not applicable | Required | Required | +| Real PostgreSQL smoke path if persistence is added | Not applicable | Required when applicable | Required when applicable | +| Load, capacity, and numeric SLO evidence | Not applicable | Required before enablement | Required | +| Artifact quarantine, service disable, and recovery drill | No integration | Required | Required | + +An unavailable external reviewer or pending GitHub check is a wait state, not +permission to weaken evidence. Merge remains subject to the repository's +current-head branch-protection and review contract. diff --git a/docs/topic-intelligence/THREAT_MODEL.md b/docs/topic-intelligence/THREAT_MODEL.md new file mode 100644 index 000000000..b408df320 --- /dev/null +++ b/docs/topic-intelligence/THREAT_MODEL.md @@ -0,0 +1,113 @@ +# Threat model: topic intelligence + +- **Status:** design-time model `PLANNED`; runtime integration `BLOCKED-UPSTREAM` +- **Scope:** the future Naruon-to-TEPP topic-intelligence boundary +- **Review trigger:** a real TEPP transport, artifact store, persistence design, + covariate, downstream consumer, or UI + +## Overview + +Naruon is a tenant-scoped email/PIM hub. A future topic-intelligence path may +authorize a bounded document snapshot, send it to a separately deployed TEPP +measurement service, validate a fitted-model result, and expose a policy-filtered +posterior or explicit abstention. That runtime path does not exist today. The +current pseudo-topic removal reduces attack surface and introduces no new +network or persistence boundary. + +This model is intentionally narrower than the repository-wide security policy. +It covers confidentiality, tenant isolation, scientific integrity, statistical +misuse, provenance, model supply chain, and availability at the planned boundary. +TEPP training internals remain out of scope until TEPP implements and publishes +their production contracts, but Naruon release gates still require evidence +about those controls. + +## Threat model, trust boundaries, and assumptions + +### Actors + +- an ordinary or malicious tenant user, including a tenant administrator; +- an attacker controlling imported email/document content; +- a compromised Naruon or TEPP service or service credential; +- a compromised artifact publisher, registry, signer, or verification key; +- an insider with model, corpus, label-evidence, or audit access; and +- a network attacker capable of observing, replaying, or tampering with traffic. + +### Trust boundaries + +| Boundary | Data crossing | Security invariant | +| --- | --- | --- | +| Browser/client to Naruon | Opaque source selection and processing intent | Verified signed session; server-resolved tenant/workspace/source/purpose; document/evidence/snapshot/audience/expiry/policy bindings cross-checked on every use; deny before snapshot creation. | +| Naruon records to snapshot | Minimized content, times, and approved covariates | Re-read ownership and policy; enforce bounds; content remains attacker-controlled data. | +| Naruon to TEPP | Bounded content or opaque evidence capability, model policy, provenance, idempotency | Mutual authentication, audience binding, encryption, schema and deadline bounds, no arbitrary URL/path. | +| Artifact registry to TEPP | Immutable manifest, model, vocabulary, preprocessing/design, validation evidence | Allowlist, signature/digest verification, signer revocation, downgrade protection, quarantine. | +| TEPP to Naruon | Posterior or abstention, diagnostics, model and provenance binding | Exact schema and code-registry revisions/digests, request/result binding, numerical and scientific invariant checks, unknown-code rejection, no fallback. | +| Result to product/audit | Policy-filtered output and restricted metadata | Preserve model-scoped topic identity, analysis unit, estimand, covariate level when applicable, and non-causal status; separate permissions and retention; no source text or raw derived digest in ordinary telemetry. | + +Attacker-controlled inputs include document bytes, language-like content, +prompt-like strings, repeated query patterns, oversized/OOV documents, and user +intent. Operator-controlled inputs include allowed service endpoints, model and +schema versions, verification keys, promotion state, quotas, and feature-disable +controls. Developer-controlled inputs include code, contract fixtures, migrations, +and release configuration; they are not trusted merely because they are local. + +## Attack surface, mitigations, and attacker stories + +| ID | Threat | Example impact | Required mitigation | Residual disposition | +| --- | --- | --- | --- | --- | +| `TI-T01` | Identity or scope spoofing | A caller references another tenant's source or model policy. | Verified session/service identity; server-side scope re-read; deny-first RBAC/ABAC. | Reassess with real auth protocol. | +| `TI-T02` | Artifact, validation-evidence, digest, downgrade, or signer compromise | A poisoned or stale model or substituted validation report is served as approved. | Verify raw artifact bytes and every manifest, vocabulary, preprocessing, design, lineage, model-card, validation-report, build, signer, and promotion binding; signer revocation, monotonic policy, quarantine, and rollback. | Signing/registry ADR required. | +| `TI-T03` | Repudiation | An operator cannot prove which model, purpose, and policy produced a result. | Append-only restricted audit with opaque refs, versions, artifact-manifest ref, policy decision, and times. | Durable audit design is planned. | +| `TI-T04` | Content or posterior disclosure | Logs, labels, caches, responses, or evidence reveal sensitive themes or cross-tenant data. | Minimization, output projection, tenant-partitioned caches, separate label/evidence permission, deletion tests. | Corpus-specific sensitivity review required. | +| `TI-T05` | Digest linkage or dictionary attack | A raw content, covariate, membership, evidence, design, or label digest links records or reveals a low-entropy value. | Exclude raw derived digests from product/telemetry; use restricted opaque refs or tenant-keyed, domain-separated digests with TTL and rotation. | Canonicalization/key design required. | +| `TI-T06` | Denial of service | Oversized/OOV documents, expensive inference, or retry storms exhaust capacity. | Input/token/concurrency limits, quotas, deadlines, cancellation, bounded retry, circuit breaker, and stable rate/deadline/cancellation errors that cannot become abstention. | Numeric limits require load evidence. | +| `TI-T07` | Privilege escalation | A member invokes an admin-only model/purpose or selects an arbitrary artifact/endpoint. | Server-owned policy allowlist, role and purpose checks, no caller URL/path or mutable alias. | Policy mapping is planned. | +| `TI-T08` | Training or label poisoning | Malicious corpus data shifts topics, labels, or downstream decisions. | Corpus provenance, quality checks, held-out and known-truth validation, independent promotion, label evidence review, rollback. | Naruon requires concrete independently published upstream controls and evidence before consumption. | +| `TI-T09` | Membership or model inference | Repeated queries reveal corpus membership or reconstruct model properties. | Per-principal/tenant query controls, coarse diagnostics, no exemplars, abuse monitoring, empirical privacy tests before exposure. | Privacy test method is unresolved. | +| `TI-T10` | Semantic or diagnostic-code confusion | A keyword, embedding, LLM label, old model, truncated vector, unknown quality code, or another tenant's cache is accepted as an STM posterior. | Strict model-scoped non-semantic topic identity, exact fitted/result/observed component counts, versioned closed diagnostic-code registries, artifact and request binding, label separation, partitioned cache, compatibility tests, no fallback. | Guarded by ADR and contract tests. | +| `TI-T11` | Ecological fallacy or stigmatization | A group prevalence estimate becomes an asserted individual trait or individual content stigmatizes a group. | Public and internal results preserve analysis unit/estimand/covariate level/non-causal status; model-card review, minimum-cell/sparse-group suppression, product-copy and downstream tests. | Human governance remains required. | +| `TI-T12` | Temporal leakage | A model or covariate uses evidence unavailable at the asserted knowledge cutoff. | Immutable temporal-policy identity/digest, evidence-time/covariate/design-row binding, explicit missingness and canonical time parsing, availability-at-cutoff ordering recomputed by Naruon, time-sliced validation. | Naruon requires independently published upstream temporal-validation evidence before consumption. | +| `TI-T13` | Confused deputy or unsafe evidence reference | An external service uses Naruon's authority to fetch unrelated content, follows an attacker URL, or Naruon accepts an unbound result. | Push bounded content or use opaque audience/tenant/workspace/source/purpose/snapshot-bound expiring capabilities; cross-check document and snapshot revisions, reauthorize resolution, no redirects, exact request/result binding. | Reassess with transport. | +| `TI-T14` | Covariate or membership manipulation | A caller supplies a privileged group, fabricated missingness, or weights that change the estimate. | Server-resolved typed covariates; frozen formula/contrast, normalization and unseen-level policy; finite/range checks. | Extended-STM contract is planned. | +| `TI-T15` | Label/evidence injection | Prompt-like corpus text manipulates a generated label, a semantic label is smuggled in as topic identity, or active markup reaches a UI. | Model-scoped non-semantic topic IDs; label-specific evidence reference and audience bound to model/topic/version/language; component referential checks; constrained output, escaping/sanitization, provenance and human review. | UI/label pipeline does not exist. | +| `TI-T16` | Incomplete deletion or cross-purpose replay | Revoked content remains in snapshots, caches, audit, or an idempotent replay. | Purpose TTL, deletion propagation, cache eviction, consent/policy recheck before replay, keyed-digest rotation. | Retention ADR required. | + +Representative abuse cases include crafted multilingual/OOV documents intended +to force a convenient default label, repeated near-duplicate queries intended to +extract corpus membership, a deprecated artifact requested through a mutable +alias, and timeouts intended to activate a cheaper keyword path. Every case must +end in denial, a stable error, or an explicit model-governed abstention. None may +change the measurement method. + +Out of scope for the current removal are attacks requiring a deployed TEPP +endpoint, model registry, topic store, or topic UI because none exists. They are +still release blockers for the future integration, not evidence that the threat +is impossible. + +## Severity calibration + +- **Critical:** cross-tenant source/posterior disclosure at scale; compromise of + an artifact-signing root that silently promotes attacker-controlled models; + service identity compromise that grants unrestricted tenant corpus access. +- **High:** unauthorized inference of sensitive themes; persistent raw content + or low-entropy derived digests in broadly accessible logs; poisoning that + materially alters product decisions; bypass of purpose, consent, or region + policy; arbitrary evidence-reference network/file access. +- **Medium:** tenant-local resource exhaustion with bounded recovery; harmful or + misleading labels that do not alter numeric identity; incomplete redaction in + a restricted operator surface; reproducibility or temporal defects that block + scientific use but do not expose another tenant. +- **Low:** documentation-only inconsistency while the runtime remains disabled, + or a non-sensitive diagnostic formatting defect with no policy, integrity, + availability, or disclosure impact. + +Repository policy requires remediation of Medium-and-higher validated findings. +Severity must be reassessed against the real transport, data volume, privileges, +and downstream decisions. + +## Security decisions still required + +Before implementation, approve ADRs for service authentication, transport and +evidence-reference semantics, artifact signing/registry and signer revocation, +cache/idempotency partitioning, result/audit retention and deletion, sensitive +covariates, privacy testing, rate limits, and downstream-consumer authorization. +The conceptual schema silently decides none of these. diff --git a/docs/topic-intelligence/TRACEABILITY.md b/docs/topic-intelligence/TRACEABILITY.md new file mode 100644 index 000000000..9b2875964 --- /dev/null +++ b/docs/topic-intelligence/TRACEABILITY.md @@ -0,0 +1,116 @@ +# Topic intelligence requirements traceability + +- **Document status:** `PRESENT-CURRENT` +- **Assessment date:** 2026-08-09 +- **Contract revision:** `2026-08-09.1` + +This matrix connects the product requirements to decisions, planned contracts, +verification, and release evidence. A documentation link proves only that a +requirement is specified. It is not evidence that a runtime capability, TEPP +production contract, fitted artifact, scientific validation, or UI exists. + +Capability maturity uses only: +`IMPLEMENTED-ON-PROTECTED-DEVELOP`, `ACTIVE-PR`, +`ACCEPTED-NARUON-POLICY`, `PLANNED`, `BLOCKED-UPSTREAM`, and `OUT-OF-SCOPE`. + +## Requirement matrix + +| ID | Requirement summary | Decision and contract coverage | Verification or release evidence | Current maturity | +|---|---|---|---|---| +| `TI-REQ-001` | Remove `email_categorizer` and `meeting_agenda_generator`. | [ADR-0001](../adr/0001-topic-measurement-authority.md); [PRD](PRD.md); `backend/api/tools.py` candidate diff | `backend/tests/test_tools_api.py::test_registry_omits_lexical_pseudo_topic_tools`; source-symbol absence; [PR #1297](https://github.com/ContextualWisdomLab/naruon/pull/1297) exact-head checks | `ACTIVE-PR` | +| `TI-REQ-002` | Retain `keyword_extractor` only as lexical frequency/first-occurrence metadata. | [ADR-0001](../adr/0001-topic-measurement-authority.md); [PRD](PRD.md); [Architecture](ARCHITECTURE.md) no-fallback boundary | `backend/tests/test_tools_api.py::test_keyword_extractor_is_disclosed_as_lexical_term_frequency`; bounded-input handler tests | `ACTIVE-PR` | +| `TI-REQ-003` | Fail closed until a compatible independently published fitted-model contract exists; no default or substitute method. | [ADR-0001](../adr/0001-topic-measurement-authority.md); [TRD](TRD.md); [API errors](API_CONTRACT.md#http-and-abstention-semantics); [UML state model](UML.md#result-state-model) | Current registry omissions; future no-deployment, incompatible-input, upstream-fault, and no-fallback adapter tests | `ACCEPTED-NARUON-POLICY`; runtime `BLOCKED-UPSTREAM` | +| `TI-REQ-004` | Return a complete mixed-membership vector with numeric identity, explicit interval level/method/scope and diagnostics; narrowly define vector-free abstention. | [Architecture scientific invariants](ARCHITECTURE.md#scientific-invariants); [API result semantics](API_CONTRACT.md#successful-public-projection); schema `$defs.inferenceResult`, `$defs.posteriorComponent`, `$defs.diagnosticBundle` | Future schema fixtures, fitted/declared/observed/actual count equality, unique numeric-ID/rank, sum/interval-containment, code-registry, calibration/coverage, and status/diagnostic cross-checks | `BLOCKED-UPSTREAM` | +| `TI-REQ-005` | Fully specify temporal, multilevel, multiple-membership, and cross-classified extensions and keep claims non-causal. | [TRD fitted-artifact requirements](TRD.md#fitted-artifact-requirements); [Architecture scientific invariants](ARCHITECTURE.md#scientific-invariants); schema `$defs.scientificProvenance` and `$defs.designContract`; [Data model](DATA_MODEL.md#covariate-and-temporal-evidence) | Future model card/design manifest, known-truth simulation, estimator/formula/contrast tests, membership-weight normalization, unseen-level, temporal-leakage, and downstream-suppression tests | `BLOCKED-UPSTREAM` | +| `TI-REQ-006` | Bind the result to schema/source/payload/artifact/manifest/vocabulary/preprocessing/design/lineage/model-card/validation-report/evidence-time/covariate/design-row provenance; digest verifies but does not reconstruct. | [Architecture canonical provenance](ARCHITECTURE.md#canonical-provenance-and-reproduction); [API digest contract](API_CONTRACT.md#digest-contract); schema `$defs.requestIdentity` and `$defs.scientificProvenance`; [Data model](DATA_MODEL.md) | Future RFC 8785 known-answer/domain-separation, complete inventory, digest mismatch, immutable-artifact, snapshot/scope-binding, retained-snapshot replay, and deletion/retention tests | `BLOCKED-UPSTREAM` | +| `TI-REQ-007` | Keep numeric topic identity separate from versioned evidence-backed labels. | [ADR-0001](../adr/0001-topic-measurement-authority.md); [UML contract structure](UML.md#contract-structure); schema `$defs.posteriorComponent`, `$defs.presentation`, and `$defs.presentationLabel` | Future label/topic join tests, label-version/evidence tests, absent-label tests, and tests proving labels cannot alter numeric posterior fields | `BLOCKED-UPSTREAM` | +| `TI-REQ-008` | Enforce tenant/workspace/source/purpose/consent/region/retention/deletion/digest/redaction controls. | [Security](SECURITY.md); [Threat model](THREAT_MODEL.md); [API evidence rules](API_CONTRACT.md#evidence-reference-rules); schema `$defs.opaqueEvidenceRef`; [Data privacy classification](DATA_MODEL.md#privacy-classification) | Future cross-tenant/workspace denial, expiry/audience/snapshot binding, reauthorization, region/purpose/consent, deletion, cache isolation, restricted-audit, and no-log/metric/trace leakage tests | `BLOCKED-UPSTREAM` | +| `TI-REQ-009` | Keep agenda generation in a separately authorized downstream contract. | [ADR-0001](../adr/0001-topic-measurement-authority.md); [Architecture ownership](ARCHITECTURE.md#authority-and-ownership); [PRD non-goals](PRD.md#non-goals) | A separate future ADR/PRD/TRD/API/threat model plus source authorization, abstention suppression, evidence, audit, and E2E tests | `ACCEPTED-NARUON-POLICY`; future capability `PLANNED` | +| `TI-REQ-010` | Add UI only after the real runtime, uncertainty, abstention/error, security, and operational evidence exists. | [PRD success gates](PRD.md#success-and-release-gates); [Operability](OPERABILITY.md); [API public projection](API_CONTRACT.md#successful-public-projection) | Future source-backed E2E tests for loading, inferred, abstained, each error family, permission denial, rollback, accessibility, redaction, and no-fallback copy | `PLANNED` | + +## Contract-to-test map + +The names below are proposed acceptance tests, not current test functions unless +an existing path is explicitly named. + +| Contract obligation | Proposed verification | Expected evidence owner | +|---|---|---| +| Immutable schema ID/revision and out-of-band digest pin | `test_topic_schema_id_revision_and_digest_pin`; RFC 8785 canonical known-answer fixture | Naruon adapter | +| Closed envelope and required scientific payload | `test_topic_result_schema_rejects_unknown_or_missing_fields` | Naruon adapter | +| Expected producer is conditional, not assigned ownership | Assert `x-owner=NARUON`, absence of `x-upstream-owner`, and conditional `x-expected-upstream-producer=TEPP` copy | Naruon architecture review | +| `inferred` status consistency | `test_inferred_requires_components_and_accepted_diagnostics` | Naruon adapter | +| `abstained` status consistency | `test_abstained_requires_empty_vector_and_posterior_policy_reason` | Naruon adapter | +| Numeric topic IDs/ranks and complete component count | `test_topic_components_use_numeric_identity`; `test_fitted_declared_observed_and_actual_counts_match` | Naruon adapter | +| Proportions sum to one within pinned tolerance | `test_topic_proportions_and_reported_sum_match` | Naruon adapter plus upstream numerical evidence | +| Interval containment and explicit uncertainty semantics | `test_topic_estimates_lie_inside_declared_intervals`; calibration/coverage report | Naruon adapter and independently published expected-upstream scientific evidence | +| Unsupported language/token/OOV/temporal/covariate input is an error | Parameterized route tests asserting `422` and stable RFC 9457 `error_code` | Naruon adapter | +| No active deployment/artifact/integrity is unavailable | Parameterized route tests asserting `503` and no substitute fallback | Naruon adapter/operator | +| Snapshot/revision/schema/idempotency conflict | Parameterized route tests asserting `409` | Naruon adapter | +| Invalid upstream schema/digest/cross-field result | `test_invalid_upstream_payload_is_502_not_abstention` | Naruon adapter | +| Unknown diagnostic/reason registry or code | `test_unknown_diagnostic_code_fails_closed_as_502`; exact registry-version fixtures | Naruon adapter and expected upstream producer | +| Snapshot/scope binding | Mismatched snapshot and scope refs plus current tenant/workspace/purpose/consent/region reauthorization tests | Naruon authorization boundary | +| Temporal assertion and ordering | RFC 3339 format-assertion, nullable-field policy, and availability-at-knowledge-cutoff tests | Naruon adapter and expected upstream evidence | +| Covariate/membership coupling | Typed level/missingness fixtures and invalid structure/normalization combinations | Naruon adapter and expected upstream evidence | +| No keyword/embedding/LLM/default-label/agenda fallback | `test_every_topic_failure_path_has_no_substitute_result` | Naruon adapter | +| Canonical no-covariate representation | Known-answer hashes for `{"covariates":[],"memberships":[]}` and `{"columns":[],"values":[]}` under their fixed domains | Naruon adapter and contract fixture producer | +| Retained evidence is required for replay | `test_digest_without_resolvable_snapshot_cannot_replay` | Naruon retention boundary | +| Evidence reference binding | Expired, wrong audience/snapshot/tenant/workspace/purpose tests plus reauthorization-on-use assertion | Naruon authorization boundary | +| Sensitive digest handling | Log/metric/trace/public response capture tests and restricted-audit opaque-reference test | Naruon security/observability | +| Multilevel/membership/design contract | Known-truth recovery, weight normalization, unseen-level rejection, formula/contrast and temporal-leakage fixtures | Independently published expected-upstream evidence plus Naruon compatibility validator | +| Labels remain presentation-only | Mutation and serialization tests proving labels cannot change component identity/posterior | Naruon adapter/UI | + +## Error-code traceability + +| Family | HTTP | Stable codes | Requirement | +|---|---:|---|---| +| Trusted request conflict | `409` | `topic_source_snapshot_conflict`, `topic_request_revision_conflict`, `topic_idempotency_conflict`, `topic_schema_revision_conflict` | `TI-REQ-003`, `TI-REQ-006`, `TI-REQ-008` | +| Authentication/authorization policy | `401`, `403` | `topic_authentication_required`, `topic_evidence_forbidden`, `topic_purpose_forbidden`, `topic_consent_required`, `topic_region_forbidden` | `TI-REQ-008` | +| Naruon request deadline | `408` | `topic_deadline_exceeded` | `TI-REQ-003`, `TI-REQ-008` | +| Input/model preflight | `422` | `topic_input_invalid`, `topic_language_unsupported`, `topic_input_insufficient_tokens`, `topic_input_out_of_vocabulary`, `topic_temporal_context_invalid`, `topic_covariate_contract_invalid` | `TI-REQ-003`, `TI-REQ-005`, `TI-REQ-008` | +| Deployment/artifact availability | `503` | `topic_deployment_unavailable`, `topic_model_artifact_unavailable`, `topic_model_artifact_integrity_failed` | `TI-REQ-003`, `TI-REQ-006` | +| Upstream execution/protocol | `502` | `topic_upstream_inference_failed`, `topic_upstream_protocol_error` | `TI-REQ-003`, `TI-REQ-004`, `TI-REQ-006` | +| Quota/rate policy | `429` | `topic_rate_limited` | `TI-REQ-008` | +| Upstream deadline | `504` | `topic_upstream_timeout` | `TI-REQ-003`, `TI-REQ-008` | +| Client cancellation | no deliverable response | internal redacted outcome `topic_request_cancelled` | `TI-REQ-003`, `TI-REQ-008` | +| Adapter defect | `500` | `topic_adapter_internal_error` | `TI-REQ-003`, `TI-REQ-008` | +| Scientific publication decline | `200` | `status=abstained` plus `posterior_*` policy reason | `TI-REQ-004` | + +Authorization failures use Naruon's existing authenticated API security contract +and intentionally do not reveal whether a document, evidence reference, tenant, +workspace, or deployment exists. + +## Current evidence versus blockers + +| Claim | Evidence available on 2026-08-09 | Missing before runtime/UI release | +|---|---|---| +| Pseudo-topic behavior is removed | Candidate source/tests and PR #1297 | Merge and exact protected-`develop` verification | +| Lexical keyword extraction is honestly scoped | Candidate description and handler tests | Merge and protected-branch verification | +| Naruon has a local no-fallback policy | Accepted ADR-0001, AGENTS rule, PRD/TRD/architecture package | Runtime adapter negative-path tests after upstream capability exists | +| A planned Naruon envelope is specified | Revisioned JSON Schema, API/architecture/UML/data-model documents | Independently published compatible expected-upstream production contract and joint fixture review; TEPP only if it accepts that role | +| A fitted model can serve Naruon | No | Published artifact/deployment/API, model card, scientific validation, signatures/integrity, capacity and operability evidence | +| Topic results are scientifically valid | No | Representative and known-truth validation, interval calibration/coverage, diagnostics, temporal/membership validation, model promotion evidence | +| Multi-tenant handling is production safe | No topic runtime exists | Implemented authorization, evidence-reference, isolation, consent/region/retention/deletion/redaction tests | +| Topic UI is releasable | No | Real runtime, safe public projection, E2E states, accessibility, security and operational release gates | + +## Release evidence bundle + +Promotion from `BLOCKED-UPSTREAM` requires one exact-revision evidence bundle: + +1. independently published expected-upstream production contract, fitted + artifact, manifest, model card, scientific-validation report, and acceptance + evidence; TEPP occupies that role only if it separately publishes and accepts + the compatible responsibility; +2. Naruon ADR review of that exact upstream revision, including any differences + from this planned acceptance profile; +3. schema fixtures and all cross-field/error/abstention tests above; +4. tenant/workspace/source/purpose/consent/region/retention/deletion/evidence- + reference and sensitive-digest security evidence; +5. activation, revocation, rollback, drift, latency, availability, rate-limit, + capacity, incident, and deletion operability evidence; +6. exact-head CI, security scans, warning-free full tests, and independent code + review; and +7. only after items 1–6, source-backed UI and E2E evidence. + +If any item is unavailable, the product remains useful without topic inference +and the topic capability stays disabled. Documentation completeness must never +be used as a substitute for upstream or runtime evidence. diff --git a/docs/topic-intelligence/TRD.md b/docs/topic-intelligence/TRD.md new file mode 100644 index 000000000..94186a0b2 --- /dev/null +++ b/docs/topic-intelligence/TRD.md @@ -0,0 +1,206 @@ +# Technical requirements: topic intelligence + +- **Status:** deletion `ACTIVE-PR`; Naruon-local policy + `ACCEPTED-NARUON-POLICY`; runtime adapter `BLOCKED-UPSTREAM` +- **Normative language:** MUST, MUST NOT, SHOULD, and MAY express obligations for + a future Naruon implementation. +- **Accepted local decision:** [ADR-0001](../adr/0001-topic-measurement-authority.md) +- **Proposed target decisions:** + [ADR-0002](../adr/0002-fitted-topic-artifact-consumption.md) and + [ADR-0003](../adr/0003-separate-topic-measurement-from-agenda-generation.md) + +## Current deliverable + +PR #1297 MUST remove `email_categorizer`, `meeting_agenda_generator`, their fixed +dictionaries, matching helpers used only by them, registry entries, and +behavior-locking tests. It MUST retain the existing input bound for the honest +lexical utility and describe that utility as deterministic lexical frequency and +first-occurrence metadata. + +This change MUST NOT add a replacement topic handler, route, table, migration, +model fit, network dependency, embedding/LLM fallback, default label, template +agenda, or simulated success response. + +## Authority and ownership + +This TRD records Naruon requirements only. It does not govern an upstream +publisher, transfer scientific authority, or claim that TEPP or another producer +accepted a Naruon envelope. A Naruon adapter remains blocked until a publisher +independently publishes a versioned production fitted artifact/API/contract and +its own acceptance evidence. Every reference below to published upstream +evidence is a condition on Naruon's consumption decision, not an obligation this +TRD assigns to the publisher. + +| Boundary | Naruon responsibility | Published upstream evidence Naruon requires before consumption | +| --- | --- | --- | +| Authorization | Tenant/workspace/user/source/purpose checks | Documented service authentication and authorization at upstream ingress | +| Input | Bounded, minimized, immutable authorized snapshot or evidence reference | Published input schema and frozen-preprocessing compatibility rules | +| Model | Select only an operator-approved published model policy | Published fitting, validation, versioning, promotion, and serving evidence | +| Result | Pin schema revision; validate, policy-filter, present, and audit metadata | Published posterior/abstention, uncertainty, diagnostic, and scientific-validation contract | +| Downstream action | Govern search, norm-group use, labels, or agenda generation separately | No implied Naruon product action | + +Naruon MUST NOT read an upstream private database or mount a mutable model path as +an implicit contract. A versioned authenticated service, event, or artifact +boundary MUST be the only integration seam. + +## Input requirements + +A future request MUST include or resolve server-side: + +- an opaque document snapshot/evidence ID, content digest, one bounded content or + evidence representation, language support signal, declared purpose, and + event/assertion/availability/knowledge-cutoff times where applicable; +- tenant/workspace/source authority derived from verified server-side identity, + never public identity headers or caller-supplied ownership; +- an operator-approved model policy and exact contract/schema compatibility + requirement; and +- only purpose-approved covariates, with typed observed/missing state and, when + applicable, level, membership-set, weight, normalization, and unseen-level + semantics. + +Credentials, provider URLs, sequential internal database IDs, unrelated +messages, and unbounded conversation history MUST NOT cross the boundary. + +## Fitted-artifact requirements + +Naruon MUST accept a published fitted artifact only when it is immutable and +content-addressed. Naruon MUST require the integrity-protected evidence bundle to +contain and match every field in the [canonical 14-field digest +inventory](README.md#canonical-digest-inventory), including +`model_card_digest`, `validation_report_digest`, +`covariate_snapshot_digest`, and `design_row_digest`. Beyond those canonical +bindings, the published evidence must identify at least: + +- model ID/version, training-corpus lineage, training cutoff, and knowledge + policy; +- preprocessing implementation/version, token-retention rules, supported + languages, and frozen vocabulary; +- topic count and numeric identities, prevalence/content designs, covariate and + missing-value schemas; +- inference implementation/version, numerical backend, diagnostics, validation + report, model-card identity, and promotion state; and +- separately versioned label evidence, never used as numeric topic identity. + +Naruon MUST reject any digest, schema, language, vocabulary, design, runtime, or +signature mismatch. Naruon MUST NOT silently choose an older or “closest” model +unless a separately accepted, audited compatibility policy names it. + +Standard STM references do not establish temporal, multilevel, multiple- +membership, or cross-classified estimation automatically. To satisfy Naruon's +acceptance criteria, Naruon MUST accept a published model claiming any extension +only when its artifact, model card, and validation evidence name the estimator, +analysis unit, estimand, prevalence/content formula and contrasts, opaque level +and membership semantics, weight normalization, unseen-level policy, non-causal +status unless a causal design is independently established, and known-truth +validation for the extension. + +## Result requirements + +An `inferred` result MUST contain non-negative topic proportions that sum to one +within a versioned tolerance; unique numeric topic IDs; inference implementation +and numerical backend; diagnostic status, stable convergence code, explicit +numerical status, and bounded stable quality codes; and exact request, model, +analysis-unit, estimand, purpose, and knowledge-cutoff provenance. It MUST carry +all 14 canonical digest fields, rather than a shortened or aliased subset, so the +schema, source snapshot, complete scientific payload, artifact, manifest, +vocabulary, preprocessing, design, lineage, model card, validation report, +evidence-time manifest, covariate snapshot, and design row are each bound. + +Each credible interval MUST state its level, method, and uncertainty scope. The +default scope is conditional on the frozen fitted artifact. Product copy MUST NOT +imply that it covers model selection, training-corpus, label, or all parameter- +estimation uncertainty unless the published model card and calibration evidence +support that broader claim. + +Labels MAY be absent. If present, each label MUST carry a label identity, +version, language, and evidence reference/digest separate from +`(model_id, model_version, topic_id)`. Consumers MUST join on numeric topic +identity and model version, not display text. + +## Error and abstention requirements + +The future adapter MUST distinguish: + +- model/service unavailable; +- unsupported contract or schema revision; +- request/idempotency/model-policy conflict; +- deployment, preprocessing, vocabulary, design, or runtime incompatibility; +- artifact/manifest integrity failure; +- unsupported language, insufficient retained tokens, excessive OOV input, or + invalid temporal/covariate input; +- authorization/purpose/consent/region denial; and +- timeout or cancellation. + +Those conditions are errors and MUST produce no posterior, label, or agenda. +HTTP bindings SHOULD use RFC 9457 problem details with a stable Naruon-defined +`error_code` extension and redacted public detail. + +`abstained` is a successful scientific state only after a compatible active +model accepts the input contract but a declared posterior or diagnostic +acceptance rule declines. It MUST contain a stable reason and MUST NOT contain a +topic vector or label. + +Every error and abstention path MUST preserve the no-fallback boundary. Naruon +MUST NOT change the measurement method to keywords, embeddings, clustering, +zero-shot/LLM labels, a cached result from another artifact, a default category, +or a template agenda. + +## Verification, replay, and retention + +All 14 fields in the canonical digest inventory verify that available bytes and +definitions match approved evidence; they do not reconstruct missing content. +Every content-, evidence-, +covariate-, membership-, temporal-, design-, and label-derived digest is +sensitive pseudonymous linkage data. A later claim of replay or +reproducibility MUST additionally prove that the exact authorized snapshot or +evidence reference, model artifact, manifest, vocabulary, preprocessing, design, +lineage, model card, validation report, evidence-time manifest, covariate +snapshot, design row, inference version, purpose, consent, retention, and +knowledge-cutoff context remain resolvable and valid. + +An idempotency key MUST bind retries to that tuple. Replay after consent, +retention, tenant, source, model, or policy invalidation is forbidden even if +the original bytes remain technically accessible. + +## Security and privacy requirements + +- Enforce deny-first RBAC/ABAC, tenant isolation, purpose limitation, consent, + region, retention, and deletion before snapshot materialization. +- Mutually authenticate and authorize the service boundary and protect content + and metadata in transit. +- Resolve only allowlisted immutable artifact references and verify integrity + before inference. +- Exclude raw content, plain content digests, excerpts, credentials, direct user + identifiers, and sensitive covariate values from ordinary logs, metrics, + traces, and public errors. +- Treat plain content digests as sensitive pseudonymous linkage values. A + restricted audit store SHOULD prefer opaque references or tenant-scoped keyed + digests with bounded retention, rotation, and deletion propagation. +- Partition caches, artifact policy, idempotency, telemetry, and deletion work by + tenant and purpose. + +## Compatibility and implementation gates + +The future adapter MUST use an explicit contract major and exact closed schema +revision. Unknown fields are rejected. An additive field may stay in one major +only after a new closed revision is published, pinned, deployed to consumers, +and explicitly negotiated before the producer sends it. Changed meaning, topic +identity, required fields, or preprocessing semantics requires a new major or +artifact version. + +Runtime work remains blocked until all of the following are true: + +1. An upstream publisher independently publishes a production fitted-model + artifact/API/contract and its own acceptance evidence. +2. Naruon reviews that exact contract against ADR-0001 and updates this package + without treating the current planned envelope as upstream authority. +3. Upstream scientific evidence covers estimation, calibration/coverage, + diagnostics, model card, promotion, and any extended-STM claims. +4. Naruon accepts separate transport/authentication, artifact-signing/registry, + retention/deletion, cache, rate-limit, sensitive-covariate, and downstream- + authorization decisions. +5. Contract fixtures from the exact upstream implementation pass Naruon schema, + invariant, failure, abstention, isolation, redaction, timeout, rollback, and + real-service E2E tests with warnings treated as failures. +6. Representative load establishes numeric limits and SLOs. +7. A UI is considered only after all preceding gates are real. diff --git a/docs/topic-intelligence/UML.md b/docs/topic-intelligence/UML.md new file mode 100644 index 000000000..b6f2ebed2 --- /dev/null +++ b/docs/topic-intelligence/UML.md @@ -0,0 +1,253 @@ +# Topic intelligence UML views + +- **Capability maturity:** `BLOCKED-UPSTREAM` +- **Document status:** `PRESENT-CURRENT` +- **Contract revision:** `2026-08-09.1` + +These diagrams describe a planned integration boundary. They do not represent +deployed classes, routes, tables, or an accepted TEPP production API. TEPP is +only the expected upstream producer if it independently publishes a compatible +production contract, fitted artifact, and acceptance evidence. + +## Contract structure + +The Naruon-owned envelope controls authorization, revisioning, validation, and +safe projection. The nested scientific payload carries fitted-model evidence +and estimates; presentation labels stay outside that payload. + +```mermaid +classDiagram + class TopicInferenceEnvelope { + +ContractIdentity contract + +RequestIdentity request + +ResultStatus status + +datetime completed_at + +CanonicalDigest tepp_payload_digest + } + class TEPPScientificPayload { + +ScientificProvenance provenance + +InferenceResult inference + +DiagnosticBundle diagnostics + } + class InferenceResult { + +number credible_level + +string interval_method + +string uncertainty_scope + +integer topic_count + } + class PosteriorComponent { + +integer topic_id + +integer rank + +number proportion + +CredibleInterval credible_interval + } + class PresentationLabel { + +integer topic_id + +string label_id + +string label_version + +string language + +string label + +OpaqueEvidenceRef[] evidence_refs + } + + TopicInferenceEnvelope *-- TEPPScientificPayload + TEPPScientificPayload *-- InferenceResult + InferenceResult *-- PosteriorComponent + TopicInferenceEnvelope o-- PresentationLabel +``` + +`PresentationLabel.topic_id` may reference a posterior component but cannot +change its identifier, rank, proportion, interval, or diagnostic outcome. +The public projection preserves opaque model ID/version, analysis unit, +estimand, coarse covariate level, and causal/non-causal designation while +redacting canonical digests, scope bindings, raw covariates, and group values. + +## Provenance and diagnostics + +```mermaid +classDiagram + class ScientificProvenance { + +string model_id + +string model_version + +integer fitted_topic_count + +string temporal_policy_version + +string estimator_id + +string analysis_unit + +string estimand_id + +string causal_design + } + class CanonicalDigest { + +string algorithm + +string canonicalization + +string domain + +string value + } + class DesignContract { + +string covariate_schema_version + +string covariate_level + +string covariate_missingness_policy + +string prevalence_formula + +string content_formula + +string contrast_specification + +string membership_structure + +string membership_weight_normalization + +string unseen_level_policy + } + class DiagnosticBundle { + +string diagnostic_status + +InputDiagnostics input + +PosteriorDiagnostics posterior + +PolicyDiagnostics policy + } + class PosteriorDiagnostics { + +string diagnostic_code_registry_version + +boolean converged + +string convergence_code + +string numerical_status + +string[] quality_codes + } + class PolicyDiagnostics { + +string policy_version + +string reason_code_registry_version + +boolean accepted + +string[] reason_codes + } + + ScientificProvenance *-- CanonicalDigest + ScientificProvenance *-- DesignContract + DiagnosticBundle *-- PosteriorDiagnostics + DiagnosticBundle *-- PolicyDiagnostics +``` + +The single `CanonicalDigest` association represents the required schema, +snapshot, scientific payload, artifact descriptor, artifact manifest, vocabulary, +preprocessing, design, lineage, model-card, validation-report, evidence-time, +covariate-snapshot, and design-row digests. Each use has its own fixed domain +separator. + +## Planned request sequence + +```mermaid +sequenceDiagram + participant U as Naruon client + participant R as Naruon route + participant A as Topic adapter + participant T as Expected upstream boundary + + U->>R: document_ref, evidence_ref, revision + R->>R: Authenticate and reauthorize + alt Authentication or scope denied + R-->>U: 401 or 403 Problem + error_code + else Rate policy exceeded + R-->>U: 429 Problem + error_code + else Authorized + R->>A: Immutable canonical snapshot request + A->>A: Preflight and pin deployment + alt Preflight ineligible + A-->>R: 422 Problem + error_code + else No active model or artifact + A-->>R: 503 Problem + error_code + else Revision or idempotency conflict + A-->>R: 409 Problem + error_code + else Compatible + A->>T: Versioned scientific request + alt Upstream deadline expires + A-->>R: 504 Problem + bounded cancellation + else Scientific payload returned + T-->>A: Expected scientific payload + A->>A: Verify schema, digests, codes, cross-fields + alt Payload validation fails + A-->>R: 502 Protocol Problem + error_code + else Accepted posterior + A-->>R: 200 inferred envelope + else Posterior or policy rejected + A-->>R: 200 abstained envelope + end + end + end + R-->>U: Redacted safe projection or Problem + end +``` + +An expired, wrong-audience, wrong-snapshot, or wrong-tenant evidence reference +is rejected before the adapter call. The route must resolve the reference +server-side; it must never dereference an arbitrary client URL or path. + +## Result state model + +```mermaid +stateDiagram-v2 + [*] --> Received + Received --> Rejected422: Ineligible preflight + Received --> RejectedAuth: Authentication or scope denial + Received --> RateLimited429: Quota or rate denial + Received --> Conflict409: Trusted request conflict + Received --> Unavailable503: No active deployment + Received --> Eligible: Compatible input and model + Eligible --> Inferring + Inferring --> ProtocolFault502: Unusable upstream response + Inferring --> Deadline504: Upstream deadline + Inferring --> Cancelled: Client cancellation + Inferring --> Validating: Scientific payload returned + Validating --> ProtocolFault502: Schema, digest, code, or invariant fails + Validating --> Inferred: Posterior accepted + Validating --> Abstained: Posterior or policy rejected + Rejected422 --> [*] + RejectedAuth --> [*] + RateLimited429 --> [*] + Conflict409 --> [*] + Unavailable503 --> [*] + ProtocolFault502 --> [*] + Deadline504 --> [*] + Cancelled --> [*] + Inferred --> [*] + Abstained --> [*] +``` + +The state model intentionally has no fallback transition from an error or +abstention to a default topic, lexical classifier, embedding cluster, LLM label, +or agenda template. + +## Deployment compatibility state + +```mermaid +stateDiagram-v2 + [*] --> Discovered + Discovered --> Quarantined: Missing upstream evidence + Discovered --> Verifying: Published contract found + Verifying --> Quarantined: Digest or validation failure + Verifying --> Inactive: Compatible evidence verified + Inactive --> Active: Operator activation + Active --> Revoked: Artifact or policy revoked + Active --> Inactive: Controlled rollback + Revoked --> Verifying: New immutable revision +``` + +Only `Active` can serve inference. A display name, mutable tag, or previously +seen model ID is not sufficient deployment evidence. + +## Cross-field validation obligations + +The schema validates local types, bounds, and status-dependent shape. The +adapter must additionally validate: + +- non-negative integer topic IDs, rank uniqueness, and, for `inferred`, equality + of fitted, declared, observed, and actual component counts; +- component sum within the pinned tolerance; +- estimate containment within each credible interval; +- equality between recomputed and reported diagnostic counts/sums; +- status, diagnostic acceptance, and reason-code consistency; +- request/evidence snapshot equality, scope-binding equality, expiry, and current + tenant/workspace/purpose/consent/region reauthorization; +- RFC 3339 format assertion, availability-at-knowledge-cutoff ordering, and the + pinned temporal missingness rule; +- exact diagnostic/reason-code registry versions and known-code membership, with + unknown versions or codes mapped to `502`; +- deployment identity and every pinned provenance digest; +- design formula/contrast, estimator, analysis-unit, estimand, covariate schema, + level/missingness, membership structure/normalization coupling, unseen-level, + temporal, and validation-profile compatibility; and +- reauthorization of each opaque evidence reference at the time of use. + +See [API contract](API_CONTRACT.md) for HTTP semantics and +[conceptual data model](DATA_MODEL.md) for ownership relationships. diff --git a/docs/topic-intelligence/schema/topic-inference-result-v1.schema.json b/docs/topic-intelligence/schema/topic-inference-result-v1.schema.json new file mode 100644 index 000000000..f86f0b6e3 --- /dev/null +++ b/docs/topic-intelligence/schema/topic-inference-result-v1.schema.json @@ -0,0 +1,1228 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://naruon.net/schemas/topic-intelligence/topic-inference-result-v1/2026-08-09.1", + "title": "Naruon internal topic-inference result envelope", + "description": "PLANNED Naruon adapter envelope for consuming an independently published compatible scientific payload. TEPP is only the expected upstream producer if it separately publishes and accepts that responsibility; this is not a shipped Naruon endpoint or an assertion that such a TEPP contract exists. A candidate payload that fails this schema or any declared runtime invariant is an upstream protocol error mapped to HTTP 502, not an inferred or abstained result.", + "x-maturity": "PLANNED", + "x-capability-status": "BLOCKED-UPSTREAM", + "x-owner": "NARUON", + "x-expected-upstream-producer": "TEPP", + "x-runtime-status": "NOT_IMPLEMENTED", + "x-schema-digest-required": true, + "x-validator-requirements": [ + "JSON Schema Draft 2020-12", + "date-time format assertion enabled", + "all x-runtime-invariants enforced after schema validation" + ], + "x-runtime-invariants": [ + "For inferred results: provenance.fitted_topic_count equals inference.topic_count, diagnostics.posterior.observed_topic_count, and the number of topic_components; topic_id and rank are each unique.", + "For abstained results: inference.topic_count, diagnostics.posterior.observed_topic_count, and the number of topic_components are zero while fitted_topic_count remains the deployed artifact topic count.", + "request.evidence_ref.snapshot_revision equals request.source_snapshot_revision and request.evidence_ref.scope_binding_ref equals request.scope_binding_ref; reauthorization resolves the same current tenant, workspace, purpose, and authorization binding.", + "request.language equals diagnostics.input.language_tag; retained_token_count meets its minimum and out_of_vocabulary_ratio does not exceed its maximum.", + "availability_time is at or before knowledge_cutoff_time and nullable temporal fields follow the pinned temporal missingness policy.", + "diagnostic and reason codes are members of the exact pinned registries; any unknown registry version or code is an upstream protocol error, never abstention.", + "inference.inference_method equals diagnostics.posterior.inference_method and all reported counts, sums, intervals, and status-dependent diagnostics agree with recomputed values." + ], + "type": "object", + "additionalProperties": false, + "required": [ + "contract", + "request", + "status", + "completed_at", + "tepp_payload_digest", + "tepp_payload" + ], + "properties": { + "contract": { + "$ref": "#/$defs/contractIdentity" + }, + "request": { + "$ref": "#/$defs/requestIdentity" + }, + "status": { + "type": "string", + "enum": [ + "inferred", + "abstained" + ] + }, + "completed_at": { + "type": "string", + "format": "date-time" + }, + "tepp_payload_digest": { + "allOf": [ + { + "$ref": "#/$defs/canonicalDigest" + }, + { + "properties": { + "domain": { + "const": "naruon.topic-inference.tepp-payload.v1" + } + } + } + ] + }, + "tepp_payload": { + "$ref": "#/$defs/teppScientificPayload" + }, + "presentation": { + "$ref": "#/$defs/presentation" + } + }, + "allOf": [ + { + "if": { + "properties": { + "status": { + "const": "inferred" + } + }, + "required": [ + "status" + ] + }, + "then": { + "properties": { + "tepp_payload": { + "allOf": [ + { + "properties": { + "inference": { + "properties": { + "topic_count": { + "minimum": 1 + }, + "topic_components": { + "minItems": 1 + } + } + } + } + }, + { + "properties": { + "diagnostics": { + "properties": { + "diagnostic_status": { + "const": "accepted" + }, + "posterior": { + "properties": { + "converged": { + "const": true + }, + "numerical_status": { + "const": "valid" + }, + "finite_values": { + "const": true + }, + "intervals_valid": { + "const": true + } + } + }, + "policy": { + "properties": { + "accepted": { + "const": true + }, + "reason_codes": { + "maxItems": 0 + } + } + } + } + } + } + } + ] + } + } + } + }, + { + "if": { + "properties": { + "status": { + "const": "abstained" + } + }, + "required": [ + "status" + ] + }, + "then": { + "not": { + "required": [ + "presentation" + ] + }, + "properties": { + "tepp_payload": { + "allOf": [ + { + "properties": { + "inference": { + "properties": { + "topic_count": { + "const": 0 + }, + "topic_components": { + "maxItems": 0 + } + } + } + } + }, + { + "properties": { + "diagnostics": { + "properties": { + "diagnostic_status": { + "const": "rejected" + }, + "policy": { + "properties": { + "accepted": { + "const": false + }, + "reason_codes": { + "minItems": 1 + } + } + } + } + } + } + } + ] + } + } + } + } + ], + "$defs": { + "contractIdentity": { + "type": "object", + "additionalProperties": false, + "required": [ + "schema_id", + "schema_revision", + "schema_digest", + "adapter_name", + "adapter_version", + "tepp_contract_version" + ], + "properties": { + "schema_id": { + "const": "https://naruon.net/schemas/topic-intelligence/topic-inference-result-v1/2026-08-09.1" + }, + "schema_revision": { + "const": "2026-08-09.1" + }, + "schema_digest": { + "allOf": [ + { + "$ref": "#/$defs/canonicalDigest" + }, + { + "properties": { + "domain": { + "const": "naruon.topic-inference.schema.v1" + } + } + } + ] + }, + "adapter_name": { + "const": "naruon-topic-intelligence-adapter" + }, + "adapter_version": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "tepp_contract_version": { + "type": "string", + "minLength": 1, + "maxLength": 128 + } + } + }, + "requestIdentity": { + "type": "object", + "additionalProperties": false, + "required": [ + "request_id", + "request_revision", + "idempotency_binding_ref", + "document_ref", + "source_snapshot_revision", + "source_snapshot_digest", + "evidence_ref", + "scope_binding_ref", + "language", + "purpose" + ], + "properties": { + "request_id": { + "type": "string", + "pattern": "^tir_[A-Za-z0-9_-]{16,64}$" + }, + "request_revision": { + "type": "string", + "pattern": "^reqrev_[A-Za-z0-9_-]{1,64}$" + }, + "idempotency_binding_ref": { + "type": "string", + "pattern": "^idem_[A-Za-z0-9_-]{16,128}$" + }, + "document_ref": { + "type": "string", + "pattern": "^doc_[A-Za-z0-9_-]{16,128}$" + }, + "source_snapshot_revision": { + "type": "string", + "pattern": "^snaprev_[A-Za-z0-9_-]{1,128}$" + }, + "source_snapshot_digest": { + "allOf": [ + { + "$ref": "#/$defs/canonicalDigest" + }, + { + "properties": { + "domain": { + "const": "naruon.topic-inference.source-snapshot.v1" + } + } + } + ] + }, + "evidence_ref": { + "$ref": "#/$defs/opaqueEvidenceRef" + }, + "scope_binding_ref": { + "type": "string", + "pattern": "^scopebind_[A-Za-z0-9_-]{16,128}$", + "description": "Opaque server-created binding for the currently authorized tenant, workspace, and purpose. It must equal evidence_ref.scope_binding_ref at runtime." + }, + "language": { + "type": "string", + "pattern": "^[A-Za-z]{2,8}(?:-[A-Za-z0-9]{1,8})*$", + "maxLength": 63 + }, + "purpose": { + "const": "topic_assistance" + } + } + }, + "opaqueEvidenceRef": { + "type": "object", + "additionalProperties": false, + "required": [ + "ref", + "audience", + "snapshot_revision", + "scope_binding_ref", + "authorization_binding_ref", + "purpose", + "expires_at" + ], + "properties": { + "ref": { + "type": "string", + "pattern": "^ev_[A-Za-z0-9_-]{16,128}$" + }, + "audience": { + "const": "naruon-topic-intelligence-adapter" + }, + "snapshot_revision": { + "type": "string", + "pattern": "^snaprev_[A-Za-z0-9_-]{1,128}$", + "description": "Must equal the enclosing request source_snapshot_revision at runtime." + }, + "scope_binding_ref": { + "type": "string", + "pattern": "^scopebind_[A-Za-z0-9_-]{16,128}$", + "description": "Opaque tenant/workspace/purpose binding. It must equal the enclosing request scope_binding_ref and be reauthorized at use time." + }, + "authorization_binding_ref": { + "type": "string", + "pattern": "^authz_[A-Za-z0-9_-]{16,128}$" + }, + "purpose": { + "const": "topic_assistance" + }, + "expires_at": { + "type": "string", + "format": "date-time" + } + } + }, + "teppScientificPayload": { + "type": "object", + "additionalProperties": false, + "required": [ + "provenance", + "inference", + "diagnostics" + ], + "properties": { + "provenance": { + "$ref": "#/$defs/scientificProvenance" + }, + "inference": { + "$ref": "#/$defs/inferenceResult" + }, + "diagnostics": { + "$ref": "#/$defs/diagnosticBundle" + } + } + }, + "scientificProvenance": { + "type": "object", + "additionalProperties": false, + "required": [ + "deployment_ref", + "model_id", + "model_version", + "fitted_topic_count", + "artifact_digest", + "manifest_digest", + "vocabulary_digest", + "preprocessing_digest", + "design_digest", + "lineage_digest", + "model_card_digest", + "validation_report_digest", + "temporal_policy_version", + "evidence_times", + "evidence_time_manifest_digest", + "covariate_snapshot_digest", + "design_row_digest", + "estimator_id", + "analysis_unit", + "estimand_id", + "causal_design", + "design_contract" + ], + "properties": { + "deployment_ref": { + "type": "string", + "pattern": "^deploy_[A-Za-z0-9_-]{16,128}$" + }, + "model_id": { + "type": "string", + "minLength": 1, + "maxLength": 256 + }, + "model_version": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "fitted_topic_count": { + "type": "integer", + "minimum": 1, + "maximum": 10000 + }, + "artifact_digest": { + "allOf": [ + { + "$ref": "#/$defs/canonicalDigest" + }, + { + "properties": { + "domain": { + "const": "tepp.topic-measurement.artifact-descriptor.v1" + } + } + } + ] + }, + "manifest_digest": { + "allOf": [ + { + "$ref": "#/$defs/canonicalDigest" + }, + { + "properties": { + "domain": { + "const": "tepp.topic-measurement.artifact-manifest.v1" + } + } + } + ] + }, + "vocabulary_digest": { + "allOf": [ + { + "$ref": "#/$defs/canonicalDigest" + }, + { + "properties": { + "domain": { + "const": "tepp.topic-measurement.vocabulary.v1" + } + } + } + ] + }, + "preprocessing_digest": { + "allOf": [ + { + "$ref": "#/$defs/canonicalDigest" + }, + { + "properties": { + "domain": { + "const": "tepp.topic-measurement.preprocessing.v1" + } + } + } + ] + }, + "design_digest": { + "allOf": [ + { + "$ref": "#/$defs/canonicalDigest" + }, + { + "properties": { + "domain": { + "const": "tepp.topic-measurement.design.v1" + } + } + } + ] + }, + "lineage_digest": { + "allOf": [ + { + "$ref": "#/$defs/canonicalDigest" + }, + { + "properties": { + "domain": { + "const": "tepp.topic-measurement.lineage.v1" + } + } + } + ] + }, + "model_card_digest": { + "allOf": [ + { + "$ref": "#/$defs/canonicalDigest" + }, + { + "properties": { + "domain": { + "const": "tepp.topic-measurement.model-card.v1" + } + } + } + ] + }, + "validation_report_digest": { + "allOf": [ + { + "$ref": "#/$defs/canonicalDigest" + }, + { + "properties": { + "domain": { + "const": "tepp.topic-measurement.validation-report.v1" + } + } + } + ] + }, + "temporal_policy_version": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "evidence_times": { + "$ref": "#/$defs/temporalEvidence" + }, + "evidence_time_manifest_digest": { + "allOf": [ + { + "$ref": "#/$defs/canonicalDigest" + }, + { + "properties": { + "domain": { + "const": "naruon.topic-inference.evidence-time-manifest.v1" + } + } + } + ] + }, + "covariate_snapshot_digest": { + "allOf": [ + { + "$ref": "#/$defs/canonicalDigest" + }, + { + "properties": { + "domain": { + "const": "naruon.topic-inference.covariate-snapshot.v1" + } + } + } + ] + }, + "design_row_digest": { + "allOf": [ + { + "$ref": "#/$defs/canonicalDigest" + }, + { + "properties": { + "domain": { + "const": "tepp.topic-measurement.design-row.v1" + } + } + } + ] + }, + "estimator_id": { + "type": "string", + "minLength": 1, + "maxLength": 256 + }, + "analysis_unit": { + "type": "string", + "minLength": 1, + "maxLength": 256 + }, + "estimand_id": { + "type": "string", + "minLength": 1, + "maxLength": 256 + }, + "causal_design": { + "const": "non_causal" + }, + "design_contract": { + "$ref": "#/$defs/designContract" + } + } + }, + "designContract": { + "type": "object", + "additionalProperties": false, + "required": [ + "covariate_schema_version", + "covariate_level", + "covariate_missingness_policy", + "prevalence_formula", + "content_formula", + "contrast_specification", + "membership_structure", + "membership_weight_normalization", + "unseen_level_policy", + "validation_profile_version" + ], + "properties": { + "covariate_schema_version": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "covariate_level": { + "type": "string", + "enum": [ + "not_applicable", + "analysis_unit", + "group", + "multiple_membership", + "cross_classified" + ] + }, + "covariate_missingness_policy": { + "type": "string", + "enum": [ + "not_applicable", + "reject_missing", + "explicit_missing_indicator", + "explicit_missing_level" + ] + }, + "prevalence_formula": { + "type": "string", + "minLength": 1, + "maxLength": 4096 + }, + "content_formula": { + "type": "string", + "minLength": 1, + "maxLength": 4096 + }, + "contrast_specification": { + "type": "string", + "minLength": 1, + "maxLength": 4096 + }, + "membership_structure": { + "type": "string", + "enum": [ + "none", + "multilevel", + "multiple_membership", + "cross_classified", + "cross_classified_multiple_membership" + ] + }, + "membership_weight_normalization": { + "type": "string", + "enum": [ + "not_applicable", + "sum_to_one_per_analysis_unit" + ] + }, + "unseen_level_policy": { + "type": "string", + "enum": [ + "reject", + "predeclared_other_level" + ] + }, + "validation_profile_version": { + "type": "string", + "minLength": 1, + "maxLength": 128 + } + }, + "allOf": [ + { + "if": { + "properties": { + "membership_structure": { + "enum": [ + "multiple_membership", + "cross_classified_multiple_membership" + ] + } + }, + "required": [ + "membership_structure" + ] + }, + "then": { + "properties": { + "membership_weight_normalization": { + "const": "sum_to_one_per_analysis_unit" + } + } + }, + "else": { + "properties": { + "membership_weight_normalization": { + "const": "not_applicable" + } + } + } + }, + { + "if": { + "properties": { + "covariate_level": { + "const": "not_applicable" + } + }, + "required": [ + "covariate_level" + ] + }, + "then": { + "properties": { + "covariate_missingness_policy": { + "const": "not_applicable" + } + } + }, + "else": { + "properties": { + "covariate_missingness_policy": { + "enum": [ + "reject_missing", + "explicit_missing_indicator", + "explicit_missing_level" + ] + } + } + } + } + ] + }, + "temporalEvidence": { + "type": "object", + "additionalProperties": false, + "required": [ + "document_time", + "event_time", + "assertion_time", + "availability_time", + "knowledge_cutoff_time", + "temporal_missingness_policy", + "availability_at_knowledge_cutoff" + ], + "properties": { + "document_time": { + "type": [ + "string", + "null" + ], + "format": "date-time" + }, + "event_time": { + "type": [ + "string", + "null" + ], + "format": "date-time" + }, + "assertion_time": { + "type": "string", + "format": "date-time" + }, + "availability_time": { + "type": "string", + "format": "date-time" + }, + "knowledge_cutoff_time": { + "type": "string", + "format": "date-time" + }, + "temporal_missingness_policy": { + "const": "document_event_nullable_assertion_availability_cutoff_required" + }, + "availability_at_knowledge_cutoff": { + "const": true, + "description": "Expected-upstream producer assertion that availability_time is at or before knowledge_cutoff_time. Naruon must parse and recompute this ordering; the assertion alone is insufficient." + } + } + }, + "inferenceResult": { + "type": "object", + "additionalProperties": false, + "required": [ + "inference_method", + "inference_implementation", + "inference_version", + "numerical_backend", + "credible_level", + "interval_method", + "uncertainty_scope", + "topic_count", + "topic_components" + ], + "properties": { + "inference_method": { + "type": "string", + "minLength": 1, + "maxLength": 256 + }, + "inference_implementation": { + "type": "string", + "minLength": 1, + "maxLength": 256 + }, + "inference_version": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "numerical_backend": { + "type": "string", + "minLength": 1, + "maxLength": 256 + }, + "credible_level": { + "type": "number", + "exclusiveMinimum": 0, + "exclusiveMaximum": 1 + }, + "interval_method": { + "type": "string", + "minLength": 1, + "maxLength": 256 + }, + "uncertainty_scope": { + "const": "conditional_on_fitted_artifact" + }, + "topic_count": { + "type": "integer", + "minimum": 0, + "maximum": 10000 + }, + "topic_components": { + "type": "array", + "maxItems": 10000, + "uniqueItems": true, + "items": { + "$ref": "#/$defs/posteriorComponent" + } + } + } + }, + "posteriorComponent": { + "type": "object", + "additionalProperties": false, + "required": [ + "topic_id", + "rank", + "proportion", + "credible_interval" + ], + "properties": { + "topic_id": { + "type": "integer", + "minimum": 0, + "maximum": 9007199254740991 + }, + "rank": { + "type": "integer", + "minimum": 1, + "maximum": 10000 + }, + "proportion": { + "type": "number", + "minimum": 0, + "maximum": 1 + }, + "credible_interval": { + "$ref": "#/$defs/credibleInterval" + } + } + }, + "credibleInterval": { + "type": "object", + "additionalProperties": false, + "required": [ + "lower", + "upper" + ], + "properties": { + "lower": { + "type": "number", + "minimum": 0, + "maximum": 1 + }, + "upper": { + "type": "number", + "minimum": 0, + "maximum": 1 + } + } + }, + "diagnosticBundle": { + "type": "object", + "additionalProperties": false, + "required": [ + "diagnostic_status", + "input", + "posterior", + "policy" + ], + "properties": { + "diagnostic_status": { + "type": "string", + "enum": [ + "accepted", + "rejected" + ] + }, + "input": { + "$ref": "#/$defs/inputDiagnostics" + }, + "posterior": { + "$ref": "#/$defs/posteriorDiagnostics" + }, + "policy": { + "$ref": "#/$defs/policyDiagnostics" + } + } + }, + "inputDiagnostics": { + "type": "object", + "additionalProperties": false, + "required": [ + "language_tag", + "language_support_status", + "original_token_count", + "retained_token_count", + "minimum_retained_token_count", + "out_of_vocabulary_token_count", + "out_of_vocabulary_ratio", + "maximum_out_of_vocabulary_ratio", + "temporal_context_status", + "covariate_contract_status" + ], + "properties": { + "language_tag": { + "type": "string", + "pattern": "^[A-Za-z]{2,8}(?:-[A-Za-z0-9]{1,8})*$", + "maxLength": 63 + }, + "language_support_status": { + "const": "supported" + }, + "original_token_count": { + "type": "integer", + "minimum": 1 + }, + "retained_token_count": { + "type": "integer", + "minimum": 1 + }, + "minimum_retained_token_count": { + "type": "integer", + "minimum": 1 + }, + "out_of_vocabulary_token_count": { + "type": "integer", + "minimum": 0 + }, + "out_of_vocabulary_ratio": { + "type": "number", + "minimum": 0, + "maximum": 1 + }, + "maximum_out_of_vocabulary_ratio": { + "type": "number", + "minimum": 0, + "maximum": 1 + }, + "temporal_context_status": { + "const": "valid" + }, + "covariate_contract_status": { + "const": "valid" + } + } + }, + "posteriorDiagnostics": { + "type": "object", + "additionalProperties": false, + "required": [ + "inference_method", + "diagnostic_code_registry_version", + "converged", + "convergence_code", + "numerical_status", + "quality_codes", + "iteration_count", + "finite_values", + "intervals_valid", + "observed_topic_count", + "posterior_sum", + "normalization_tolerance" + ], + "properties": { + "inference_method": { + "type": "string", + "minLength": 1, + "maxLength": 256 + }, + "diagnostic_code_registry_version": { + "type": "string", + "minLength": 1, + "maxLength": 128, + "description": "Exact immutable registry for convergence_code and quality_codes. Unknown versions or codes fail closed as an upstream protocol error." + }, + "converged": { + "type": "boolean" + }, + "convergence_code": { + "type": "string", + "pattern": "^[a-z][a-z0-9_]{0,95}$" + }, + "numerical_status": { + "type": "string", + "enum": [ + "valid", + "invalid" + ] + }, + "quality_codes": { + "type": "array", + "maxItems": 32, + "uniqueItems": true, + "items": { + "type": "string", + "pattern": "^[a-z][a-z0-9_]{0,95}$" + } + }, + "iteration_count": { + "type": "integer", + "minimum": 0 + }, + "finite_values": { + "type": "boolean" + }, + "intervals_valid": { + "type": "boolean" + }, + "observed_topic_count": { + "type": "integer", + "minimum": 0, + "maximum": 10000 + }, + "posterior_sum": { + "type": "number", + "minimum": 0, + "maximum": 10000 + }, + "normalization_tolerance": { + "type": "number", + "exclusiveMinimum": 0, + "maximum": 0.1 + } + } + }, + "policyDiagnostics": { + "type": "object", + "additionalProperties": false, + "required": [ + "policy_version", + "reason_code_registry_version", + "accepted", + "reason_codes" + ], + "properties": { + "policy_version": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "reason_code_registry_version": { + "type": "string", + "minLength": 1, + "maxLength": 128, + "description": "Exact immutable registry for reason_codes. Unknown versions or codes fail closed as an upstream protocol error." + }, + "accepted": { + "type": "boolean" + }, + "reason_codes": { + "type": "array", + "maxItems": 32, + "uniqueItems": true, + "items": { + "type": "string", + "pattern": "^posterior_[a-z0-9_]{1,96}$" + } + } + } + }, + "presentation": { + "type": "object", + "additionalProperties": false, + "required": [ + "labels" + ], + "properties": { + "labels": { + "type": "array", + "minItems": 1, + "maxItems": 10000, + "uniqueItems": true, + "items": { + "$ref": "#/$defs/presentationLabel" + } + } + } + }, + "presentationLabel": { + "type": "object", + "additionalProperties": false, + "required": [ + "topic_id", + "label_id", + "label_version", + "language", + "label", + "review_method", + "evidence_refs" + ], + "properties": { + "topic_id": { + "type": "integer", + "minimum": 0, + "maximum": 9007199254740991 + }, + "label_id": { + "type": "string", + "pattern": "^label_[A-Za-z0-9_-]{16,128}$" + }, + "label_version": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "language": { + "type": "string", + "pattern": "^[A-Za-z]{2,8}(?:-[A-Za-z0-9]{1,8})*$", + "maxLength": 63 + }, + "label": { + "type": "string", + "minLength": 1, + "maxLength": 256 + }, + "review_method": { + "type": "string", + "enum": [ + "human_curated", + "model_assisted_human_reviewed" + ] + }, + "evidence_refs": { + "type": "array", + "minItems": 1, + "maxItems": 64, + "uniqueItems": true, + "items": { + "$ref": "#/$defs/opaqueEvidenceRef" + } + } + } + }, + "canonicalDigest": { + "type": "object", + "additionalProperties": false, + "required": [ + "algorithm", + "canonicalization", + "domain", + "value" + ], + "properties": { + "algorithm": { + "const": "sha-256" + }, + "canonicalization": { + "const": "RFC8785" + }, + "domain": { + "type": "string", + "pattern": "^[a-z0-9][a-z0-9._-]+\\.v[0-9]+$", + "maxLength": 256 + }, + "value": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + } + } + } + } +}