vignette/scripts/smoke-multimodal-alliance-ledger.py
Yun Chan 16e791e044 G0~G8 성과·동맹 측정 OS 작업 일괄 고정
8월 7일까지 워킹트리에만 남아 있던 미커밋 작업을 커밋한다. 여러 사본
폴더(worktree·clone)에 흩어져 있던 중간 스냅샷을 정리하기 전에 원본을
git 이력으로 고정하는 것이 목적이다.

- contracts/routes/services: measurement, outcome_trajectory, rupture_repair,
  deliberate_practice, calibration_transfer, supervision_research,
  multimodal_alliance, continuous_improvement 계열 신규 모듈과 테스트
- infra/db/init: 07~16 마이그레이션(측정 기반~calibration transfer 실행)
- apps/web: 세션 리뷰 카드·관리 화면·E2E 스펙 추가
- docs/ops: G0~G8 라이브 통합·배포·롤백 증거 문서와 evidence JSON/PNG
- scripts: smoke·ledger·릴리스 에이전트·NAS 프리뷰 운영 스크립트

engine.public 로그 .bak과 apps/web/test-results 산출물은 커밋에서 제외했다.
2026-08-08 01:30:53 +09:00

486 lines
18 KiB
Python

#!/usr/bin/env python3
"""Real-Postgres G7 smoke using synthetic WAV metadata and the store boundary.
Run only against an expendable database. The script creates fixture users/session
and intentionally leaves them for the caller to discard with the database.
"""
from __future__ import annotations
# smoke 러너는 앱 패키지를 불러오기 전에 apps/api를 sys.path에 추가해야 한다.
# ruff: noqa: E402
import asyncio
import hashlib
import io
import math
import struct
import sys
import wave
from datetime import UTC, datetime, timedelta
from pathlib import Path
from uuid import UUID
API_ROOT = Path(__file__).resolve().parents[1] / "apps" / "api"
if str(API_ROOT) not in sys.path:
sys.path.insert(0, str(API_ROOT))
from app import db
from app.contracts.multimodal_alliance import (
FusionCalibration,
ModalityAxisMeasurement,
)
from app.deps import Principal, Role
from app.services import multimodal_alliance_store
from app.services.multimodal_alliance import align_voice_timeline
LEARNER_ID = UUID("77000000-0000-4000-8000-000000000001")
TEACHER_ID = UUID("77000000-0000-4000-8000-000000000002")
OUTSIDER_ID = UUID("77000000-0000-4000-8000-000000000003")
ADMIN_ID = UUID("77000000-0000-4000-8000-000000000004")
SESSION_ID = UUID("77000000-0000-4000-8000-000000000010")
CONSENT_SUBMISSION_ID = UUID("77000000-0000-4000-8000-000000000011")
TIMELINE_SUBMISSION_ID = UUID("77000000-0000-4000-8000-000000000012")
TEXT_ONLY_SUBMISSION_ID = UUID("77000000-0000-4000-8000-000000000013")
FUSION_SUBMISSION_ID = UUID("77000000-0000-4000-8000-000000000014")
WITHDRAW_SUBMISSION_ID = UUID("77000000-0000-4000-8000-000000000015")
COMPLETION_SUBMISSION_ID = UUID("77000000-0000-4000-8000-000000000016")
def principal(user_id: UUID, role: Role, cohort: str) -> Principal:
return Principal(
user_id=str(user_id),
role=role,
cohort_ids=[cohort],
consent_at=1.0,
profile_completed_at=1.0,
)
def synthetic_wav() -> bytes:
sample_rate = 16_000
duration_ms = 1200
frames = sample_rate * duration_ms // 1000
output = io.BytesIO()
with wave.open(output, "wb") as writer:
writer.setnchannels(1)
writer.setsampwidth(2)
writer.setframerate(sample_rate)
payload = bytearray()
for index in range(frames):
value = int(3200 * math.sin(2 * math.pi * 220 * index / sample_rate))
payload.extend(struct.pack("<h", value))
writer.writeframes(bytes(payload))
return output.getvalue()
def timeline():
return align_voice_timeline(
audio_duration_ms=1200,
words=(
{
"word_index": 0,
"start_ms": 40,
"end_ms": 220,
"speaker": "learner",
"token_hash": "a" * 64,
},
{
"word_index": 1,
"start_ms": 500,
"end_ms": 710,
"speaker": "client",
"token_hash": "b" * 64,
},
),
events=(
{
"event_id": "oas-g7-event-smoke-silence",
"event_type": "silence",
"start_ms": 220,
"end_ms": 500,
"actor": "both",
"observed_feature": "280ms turn transition silence",
"uncertainty": 0.05,
"source": "stt_word_timestamps",
},
{
"event_id": "oas-g7-event-smoke-overlap",
"event_type": "overlap",
"start_ms": 710,
"end_ms": 770,
"actor": "both",
"observed_feature": "60ms simultaneous speech segment",
"uncertainty": 0.1,
"source": "observed_audio_runtime",
},
{
"event_id": "oas-g7-event-smoke-interruption",
"event_type": "interruption",
"start_ms": 770,
"end_ms": 840,
"actor": "learner",
"observed_feature": "learner segment began before client segment ended",
"uncertainty": 0.12,
"source": "observed_audio_runtime",
},
{
"event_id": "oas-g7-event-smoke-prosody",
"event_type": "prosody",
"start_ms": 840,
"end_ms": 1080,
"actor": "learner",
"observed_feature": "median intensity decreased by 3dB",
"uncertainty": 0.2,
"source": "observed_audio_runtime",
},
),
)
async def seed_fixture() -> None:
pool = db.get_pool()
async with pool.acquire() as conn:
for user_id, role, cohort in (
(LEARNER_ID, "learner", "g7-cohort"),
(TEACHER_ID, "instructor", "g7-cohort"),
(OUTSIDER_ID, "instructor", "other-cohort"),
(ADMIN_ID, "admin", "admin-cohort"),
):
await conn.execute(
"""
INSERT INTO app.app_user (
user_id, external_id, email, display_name, role, cohort,
consent_at, profile_completed_at
) VALUES ($1,$2,$3,$4,$5,$6,now(),now())
ON CONFLICT (user_id) DO NOTHING
""",
user_id,
f"g7-smoke:{user_id}",
f"{user_id}@g7-smoke.invalid",
"G7 Smoke",
role,
cohort,
)
async with db.acquire(
role="learner",
user_id=str(LEARNER_ID),
cohort_ids=["g7-cohort"],
) as conn:
await conn.execute(
"""
INSERT INTO app.sessions (id, learner_id, theory_mode)
VALUES ($1,$2,'integrative')
ON CONFLICT (id) DO NOTHING
""",
SESSION_ID,
LEARNER_ID,
)
def measurement(
*,
measurement_id: str,
modality: str,
value: float,
evidence_ref: str,
model_run_id: UUID,
) -> ModalityAxisMeasurement:
return ModalityAxisMeasurement(
measurement_id=measurement_id,
axis="bond",
modality=modality,
status="ready",
value=value,
confidence=0.82,
uncertainty=0.18,
evidence_refs=(evidence_ref,),
model_run_id=model_run_id,
)
async def main() -> None:
await db.init_pool()
try:
await seed_fixture()
learner = principal(LEARNER_ID, Role.LEARNER, "g7-cohort")
teacher = principal(TEACHER_ID, Role.TEACHER, "g7-cohort")
outsider = principal(OUTSIDER_ID, Role.TEACHER, "other-cohort")
admin = principal(ADMIN_ID, Role.ADMIN, "admin-cohort")
consent = await multimodal_alliance_store.append_consent_snapshot(
principal=learner,
session_id=SESSION_ID,
submission_id=CONSENT_SUBMISSION_ID,
consent_status="granted",
retain_audio=True,
retain_derived_features=True,
transcript_retained=True,
retention_days=30,
policy_version="g7-smoke-v1",
reason_code=None,
)
replay = await multimodal_alliance_store.append_consent_snapshot(
principal=learner,
session_id=SESSION_ID,
submission_id=CONSENT_SUBMISSION_ID,
consent_status="granted",
retain_audio=True,
retain_derived_features=True,
transcript_retained=True,
retention_days=30,
policy_version="g7-smoke-v1",
reason_code=None,
)
assert replay["idempotent_replay"]
assert consent["consent_snapshot_id"] == replay["consent_snapshot_id"]
audio = synthetic_wav()
aligned = timeline()
audio_asset = {
"audio_ref": "g7-smoke://synthetic/one.wav",
"audio_sha256": hashlib.sha256(audio).hexdigest(),
"media_type": "audio/wav",
"byte_size": len(audio),
}
async with db.acquire(ai_view="evaluator", ai_context=True) as conn:
first = await multimodal_alliance_store.append_timeline(
conn=conn,
session_id=SESSION_ID,
submission_id=TIMELINE_SUBMISSION_ID,
timeline=aligned,
audio_asset=audio_asset,
)
async with db.acquire(ai_view="evaluator", ai_context=True) as conn:
second = await multimodal_alliance_store.append_timeline(
conn=conn,
session_id=SESSION_ID,
submission_id=TIMELINE_SUBMISSION_ID,
timeline=aligned,
audio_asset=audio_asset,
)
assert second["idempotent_replay"]
assert first["timeline_id"] == second["timeline_id"]
changed = aligned.model_copy(update={"audio_duration_ms": 1300})
try:
async with db.acquire(ai_view="evaluator", ai_context=True) as conn:
await multimodal_alliance_store.append_timeline(
conn=conn,
session_id=SESSION_ID,
submission_id=TIMELINE_SUBMISSION_ID,
timeline=changed,
audio_asset=audio_asset,
)
except multimodal_alliance_store.MultimodalAllianceConflictError:
pass
else:
raise AssertionError("changed idempotency payload was not rejected")
provenance = {
"instrument_id": "alliance-axis-observer",
"instrument_version": "g7-smoke-v1",
"model_name": "synthetic-observer",
"prompt_version": "g7-smoke-v1",
}
text = measurement(
measurement_id="oas-g7-measurement-smoke-text-no-gain",
modality="text",
value=0.62,
evidence_ref="turn:learner:1",
model_run_id=UUID("77000000-0000-4000-8000-000000000020"),
)
voice = measurement(
measurement_id="oas-g7-measurement-smoke-voice-no-gain",
modality="voice",
value=0.7,
evidence_ref="timeline:oas-g7-event-smoke-prosody",
model_run_id=UUID("77000000-0000-4000-8000-000000000021"),
)
no_gain = FusionCalibration(
calibration_id="oas-g7-fusion-smoke-no-gain",
axis="bond",
text_weight=0.75,
voice_weight=0.25,
text_only_accuracy=0.81,
fused_accuracy=0.815,
benchmark_version="g7-benchmark-smoke-v1",
minimum_incremental_gain=0.01,
)
async with db.acquire(ai_view="evaluator", ai_context=True) as conn:
text_only = await multimodal_alliance_store.append_measurement_fusion(
conn=conn,
session_id=SESSION_ID,
submission_id=TEXT_ONLY_SUBMISSION_ID,
text=text,
voice=voice,
calibration=no_gain,
text_provenance=provenance,
voice_provenance=provenance,
)
assert text_only["result"]["modalities_used"] == ["text"]
assert not text_only["result"]["fusion_applied"]
text_gain = text.model_copy(
update={"measurement_id": "oas-g7-measurement-smoke-text-gain"}
)
voice_gain = voice.model_copy(
update={"measurement_id": "oas-g7-measurement-smoke-voice-gain"}
)
gain = no_gain.model_copy(
update={
"calibration_id": "oas-g7-fusion-smoke-gain",
"fused_accuracy": 0.84,
}
)
async with db.acquire(ai_view="evaluator", ai_context=True) as conn:
fused = await multimodal_alliance_store.append_measurement_fusion(
conn=conn,
session_id=SESSION_ID,
submission_id=FUSION_SUBMISSION_ID,
text=text_gain,
voice=voice_gain,
calibration=gain,
text_provenance=provenance,
voice_provenance=provenance,
)
assert fused["result"]["fusion_applied"]
assert set(fused["result"]["modalities_used"]) == {"text", "voice"}
learner_view = await multimodal_alliance_store.read_session_metadata(
principal=learner, session_id=SESSION_ID
)
teacher_view = await multimodal_alliance_store.read_session_metadata(
principal=teacher, session_id=SESSION_ID
)
assert len(learner_view["voice_events"]) == 4
assert len(teacher_view["voice_events"]) == 4
assert await multimodal_alliance_store.read_raw_audio_access(
principal=learner, session_id=SESSION_ID
)
try:
await multimodal_alliance_store.read_raw_audio_access(
principal=teacher, session_id=SESSION_ID
)
except multimodal_alliance_store.MultimodalAllianceStateError:
pass
else:
raise AssertionError("teacher unexpectedly received raw audio access")
try:
await multimodal_alliance_store.read_session_metadata(
principal=outsider, session_id=SESSION_ID
)
except multimodal_alliance_store.MultimodalAllianceNotFoundError:
pass
else:
raise AssertionError("out-of-cohort teacher unexpectedly saw G7 metadata")
assert await multimodal_alliance_store.read_raw_audio_access(
principal=admin, session_id=SESSION_ID
)
async with db.acquire(ai_view="evaluator", ai_context=True) as conn:
expiry_work = (
await multimodal_alliance_store.request_expired_retention_deletions(
conn=conn,
as_of=datetime.now(UTC) + timedelta(days=31),
limit=10,
)
)
assert len(expiry_work) == 1
assert not expiry_work[0]["idempotent_replay"]
async with db.acquire(ai_view="evaluator", ai_context=True) as conn:
expiry_replay = (
await multimodal_alliance_store.request_expired_retention_deletions(
conn=conn,
as_of=datetime.now(UTC) + timedelta(days=31),
limit=10,
)
)
assert expiry_replay[0]["idempotent_replay"]
assert (
expiry_work[0]["deletion_request_id"]
== expiry_replay[0]["deletion_request_id"]
)
pool = db.get_pool()
async with pool.acquire() as conn:
async with conn.transaction():
await conn.execute("SET LOCAL ROLE vignette")
public_count = await conn.fetchval(
"SELECT count(*) FROM app.multimodal_session_metadata_v"
)
assert public_count == 0
withdrawal = await multimodal_alliance_store.append_consent_snapshot(
principal=learner,
session_id=SESSION_ID,
submission_id=WITHDRAW_SUBMISSION_ID,
consent_status="withdrawn",
retain_audio=False,
retain_derived_features=False,
transcript_retained=True,
retention_days=None,
policy_version="g7-smoke-v1",
reason_code="learner_withdrawal",
)
assert withdrawal["deletion_request_id"] is not None
try:
await multimodal_alliance_store.assert_voice_processing_allowed(
principal=learner,
session_id=str(SESSION_ID),
)
except multimodal_alliance_store.MultimodalConsentWithdrawnError:
pass
else:
raise AssertionError("withdrawal did not block new voice processing")
target_hash = hashlib.sha256(
audio_asset["audio_ref"].encode("utf-8")
).hexdigest()
async with db.acquire(ai_view="evaluator", ai_context=True) as conn:
completed = await multimodal_alliance_store.complete_deletion(
conn=conn,
deletion_request_id=withdrawal["deletion_request_id"],
submission_id=COMPLETION_SUBMISSION_ID,
tombstones=(
{
"scope": "audio",
"target_ref_hash": target_hash,
"deletion_proof": "synthetic object-store delete acknowledged",
"deleted_at": datetime.now(UTC),
},
{
"scope": "derived_features",
"target_ref_hash": hashlib.sha256(
str(first["timeline_id"]).encode("utf-8")
).hexdigest(),
"deletion_proof": "derived feature access tombstoned",
"deleted_at": datetime.now(UTC),
},
),
actor_uid=None,
actor_kind="retention_worker",
)
assert len(completed["tombstone_ids"]) == 2
assert not await multimodal_alliance_store.read_raw_audio_access(
principal=learner, session_id=SESSION_ID
)
after = await multimodal_alliance_store.read_session_metadata(
principal=learner, session_id=SESSION_ID
)
assert not after["word_timestamps"] and not after["voice_events"]
assert all(item["modality"] == "text" for item in after["measurements"])
assert all(
"voice" not in item["modalities_used"] for item in after["fusion_decisions"]
)
print(
"G7 real DB smoke passed: consent, clock alignment, idempotency, "
"fusion gate, RLS, retention sweep, withdrawal, and tombstones"
)
finally:
await db.close_pool()
if __name__ == "__main__":
asyncio.run(main())