aiflow-math-ink-06-intermediate / src /ink06_source_registry.py
cwLeeDev's picture
Audit federation leakage and enforce source provenance
f770448 verified
Raw
History Blame Contribute Delete
8.01 kB
"""Math Ink 0.6 ๋ฐ์ดํ„ฐ ์ถœ์ฒ˜์˜ ๋ฐœ๊ฒฌยท๊ถŒ๋ฆฌยท์ค‘๋ณตยทํ•™์Šต ์ค€๋น„ ๋‹จ๊ณ„๋ฅผ ๊ฒ€์ฆํ•œ๋‹ค."""
from __future__ import annotations
from dataclasses import dataclass
import json
from pathlib import Path
from typing import Sequence
SOURCE_STAGES = ("discovered", "rights_review", "deduplicated", "approved")
TRAINING_ROLES = (
"supervised_symbol", "geometry_pretrain", "raster_verifier", "raster_pseudo_stroke", "evaluation_only",
)
VOCABULARY_POLICIES = (
"exact_378", "intersection_378", "digits_latin_intersection", "geometry_only", "evaluation_only",
)
@dataclass(frozen=True, slots=True)
class SourceRegistryEntry06:
"""ํ•„์š” ๋ณ€์ˆ˜: ์ถœ์ฒ˜ ๊ถŒ๋ฆฌยท์ค‘๋ณตยท๋กœ์ปฌ ์ƒํƒœ. ์ž‘๋™ ์›๋ฆฌ: ๋ฐฐํฌ checkpoint ์œ ์ž… ์—ฌ๋ถ€๋ฅผ ๋ช…์‹œ์ ์ธ ํ•œ ํ–‰์œผ๋กœ ํ‘œํ˜„ํ•œ๋‹ค."""
source_id: str
stage: str
official_url: str
license_id: str | None
commercial_allowed: bool | None
allowed_tracks: tuple[str, ...]
independent_source_group: str | None
deployment_role: str | None
vocabulary_policy: str | None
local_materialized: bool
notes: str
def load_source_registry06(path: Path) -> tuple[SourceRegistryEntry06, ...]:
"""ํ•„์š” ๋ณ€์ˆ˜: UTF-8 source registry ๊ฒฝ๋กœ. ์ž‘๋™ ์›๋ฆฌ: fail-closed schema ๊ฒ€์ฆ ํ›„ ๋ถˆ๋ณ€ entry ๋ฌถ์Œ์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค."""
payload = json.loads(path.read_text(encoding="utf-8"))
if payload.get("schema_version") != "0.6":
raise ValueError("Math Ink source registry schema_version์€ 0.6์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.")
raw_sources = payload.get("sources")
if not isinstance(raw_sources, list):
raise ValueError("Math Ink source registry sources๊ฐ€ ๋ฐฐ์—ด์ด ์•„๋‹™๋‹ˆ๋‹ค.")
entries: list[SourceRegistryEntry06] = []
seen: set[str] = set()
for raw in raw_sources:
source_id = str(raw.get("source_id") or "").strip()
if not source_id or source_id in seen:
raise ValueError(f"source_id๊ฐ€ ๋น„์—ˆ๊ฑฐ๋‚˜ ์ค‘๋ณต์ž…๋‹ˆ๋‹ค: {source_id!r}")
seen.add(source_id)
stage = str(raw.get("stage") or "")
if stage not in SOURCE_STAGES:
raise ValueError(f"{source_id}์˜ stage๊ฐ€ ์˜ฌ๋ฐ”๋ฅด์ง€ ์•Š์Šต๋‹ˆ๋‹ค: {stage}")
official_url = str(raw.get("official_url") or "")
if not official_url.startswith("https://"):
raise ValueError(f"{source_id}์˜ ๊ณต์‹ HTTPS URL์ด ์—†์Šต๋‹ˆ๋‹ค.")
license_id = raw.get("license_id")
commercial_allowed = raw.get("commercial_allowed")
group = raw.get("independent_source_group")
role = raw.get("deployment_role")
vocabulary_policy = raw.get("vocabulary_policy")
if SOURCE_STAGES.index(stage) >= SOURCE_STAGES.index("rights_review"):
if not license_id or not isinstance(commercial_allowed, bool):
raise ValueError(f"{source_id}์˜ ๊ถŒ๋ฆฌ ๊ฒ€ํ†  ๊ฒฐ๊ณผ๊ฐ€ ๋ถˆ์™„์ „ํ•ฉ๋‹ˆ๋‹ค.")
if SOURCE_STAGES.index(stage) >= SOURCE_STAGES.index("deduplicated") and not group:
raise ValueError(f"{source_id}์˜ independent_source_group์ด ์—†์Šต๋‹ˆ๋‹ค.")
if stage == "approved":
if commercial_allowed is not True or "P" not in raw.get("allowed_tracks", []):
raise ValueError(f"{source_id}๋Š” ์ƒ์šฉ P-track ์Šน์ธ ์กฐ๊ฑด์„ ์ถฉ์กฑํ•˜์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค.")
if role not in TRAINING_ROLES or vocabulary_policy not in VOCABULARY_POLICIES:
raise ValueError(f"{source_id}์˜ ๋ฐฐํฌ ์—ญํ•  ๋˜๋Š” vocabulary ์ •์ฑ…์ด ์˜ฌ๋ฐ”๋ฅด์ง€ ์•Š์Šต๋‹ˆ๋‹ค.")
if not bool(raw.get("local_materialized", False)):
raise ValueError(f"{source_id}๋Š” content dedup ์ „์ด๋ผ approved๊ฐ€ ๋  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.")
entries.append(SourceRegistryEntry06(
source_id=source_id, stage=stage, official_url=official_url,
license_id=str(license_id) if license_id else None,
commercial_allowed=commercial_allowed if isinstance(commercial_allowed, bool) else None,
allowed_tracks=tuple(str(value) for value in raw.get("allowed_tracks", [])),
independent_source_group=str(group) if group else None,
deployment_role=str(role) if role else None,
vocabulary_policy=str(vocabulary_policy) if vocabulary_policy else None,
local_materialized=bool(raw.get("local_materialized", False)), notes=str(raw.get("notes") or ""),
))
return tuple(entries)
def approved_training_source_ids06(
entries: Sequence[SourceRegistryEntry06], *, role: str = "supervised_symbol",
) -> tuple[str, ...]:
"""ํ•„์š” ๋ณ€์ˆ˜: ๊ฒ€์ฆ๋œ entryยทํ•™์Šต ์—ญํ• . ์ž‘๋™ ์›๋ฆฌ: ์Šน์ธยท์ƒ์šฉยท๋กœ์ปฌ ์ค€๋น„ ์กฐ๊ฑด์„ ๋ชจ๋‘ ๋งŒ์กฑํ•œ ์ถœ์ฒ˜๋งŒ ๋ฐ˜ํ™˜ํ•œ๋‹ค."""
if role not in TRAINING_ROLES:
raise ValueError(f"์ง€์›ํ•˜์ง€ ์•Š๋Š” deployment role์ž…๋‹ˆ๋‹ค: {role}")
return tuple(sorted(
entry.source_id for entry in entries
if entry.stage == "approved" and entry.commercial_allowed is True and "P" in entry.allowed_tracks
and entry.local_materialized and entry.deployment_role == role
))
def source_registry_audit06(
entries: Sequence[SourceRegistryEntry06],
*,
required_discovered_sources: int = 200,
required_approved_groups: int = 30,
) -> dict:
"""ํ•„์š” ๋ณ€์ˆ˜: ๊ฒ€์ฆ๋œ ์ถœ์ฒ˜ยท๋ชฉํ‘œ ์ˆ˜. ์ž‘๋™ ์›๋ฆฌ: ์กฐ์‚ฌ ์ˆ˜์™€ ๋ฏธ๋Ÿฌ ์ œ๊ฑฐ ๋…๋ฆฝ ๋ฐฐํฌ ๊ทธ๋ฃน์„ ๋ณ„๋„ hard gate๋กœ ๊ณ„์‚ฐํ•œ๋‹ค."""
approved = [entry for entry in entries if entry.stage == "approved"]
approved_deployment = [
entry for entry in approved
if entry.deployment_role != "evaluation_only"
]
rights_cleared = [
entry for entry in entries
if entry.commercial_allowed is True and entry.license_id is not None
]
groups = {entry.independent_source_group for entry in approved if entry.independent_source_group}
deployment_groups = {
entry.independent_source_group
for entry in approved_deployment
if entry.independent_source_group
}
group_members: dict[str, list[str]] = {}
for entry in entries:
if entry.independent_source_group:
group_members.setdefault(entry.independent_source_group, []).append(entry.source_id)
ready = approved_training_source_ids06(entries)
materialized_by_role = {
role: sorted(entry.source_id for entry in approved if entry.local_materialized and entry.deployment_role == role)
for role in TRAINING_ROLES
}
return {
"discovered_sources": len(entries),
"required_discovered_sources": required_discovered_sources,
"discovery_gate_passed": len(entries) >= required_discovered_sources,
"stage_counts": {stage: sum(entry.stage == stage for entry in entries) for stage in SOURCE_STAGES},
"approved_sources": len(approved),
"approved_independent_groups": len(groups),
"approved_deployment_sources": len(approved_deployment),
"approved_deployment_independent_groups": len(deployment_groups),
"required_approved_independent_groups": required_approved_groups,
"approved_group_gate_passed": len(deployment_groups) >= required_approved_groups,
"source_release_gate_passed": (
len(entries) >= required_discovered_sources
and len(deployment_groups) >= required_approved_groups
),
"shared_independent_groups": {
group: sorted(members)
for group, members in sorted(group_members.items())
if len(members) > 1
},
"rights_cleared_sources": sorted(entry.source_id for entry in rights_cleared),
"rights_cleared_source_count": len(rights_cleared),
"rights_cleared_pending_materialization": sorted(
entry.source_id for entry in rights_cleared if entry.stage != "approved"
),
"materialized_supervised_sources": list(ready),
"materialized_supervised_source_count": len(ready),
"materialized_by_role": materialized_by_role,
}