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 산출물은 커밋에서 제외했다.
This commit is contained in:
Yun Chan 2026-08-08 01:30:53 +09:00
parent 93dd8f82d7
commit 16e791e044
390 changed files with 243188 additions and 499 deletions

View file

@ -191,6 +191,79 @@ class SessionTurnPersistenceTest(unittest.IsolatedAsyncioTestCase):
self.assertEqual(conn.insert_args[17], list(turn.visible_to))
self.assertEqual(turn.turn_id, "00000000-0000-0000-0000-000000009999")
async def test_create_session_binds_goal_stages_as_jsonb_array(self) -> None:
class FakeTransaction:
async def __aenter__(self) -> None:
return None
async def __aexit__(self, exc_type, exc, tb) -> None:
return None
class FakeConn:
def __init__(self) -> None:
self.session_insert_args: tuple[object, ...] = ()
def transaction(self) -> FakeTransaction:
return FakeTransaction()
async def fetchrow(self, query: str, *args: object) -> dict[str, object]:
if "UPDATE app.case_profile" in query:
return {"last_session_no": 2}
if "INSERT INTO app.sessions" in query:
self.session_insert_args = args
return {
"id": "00000000-0000-0000-0000-000000000301",
"runtime_case_id": args[0],
"case_id": args[1],
"learner_id": args[2],
"persona_code": args[5],
"session_no": args[8],
"theory_mode": args[9],
"started_at": session_persistence.datetime.fromtimestamp(
1_000.0, tz=session_persistence.timezone.utc
),
"ended_at": None,
"prev_rapport_credit": args[10],
}
raise AssertionError(f"unexpected query: {query}")
async def execute(self, query: str, *args: object) -> str:
self.assert_jsonb_object(query, args)
return "INSERT 0 1"
@staticmethod
def assert_jsonb_object(query: str, args: tuple[object, ...]) -> None:
if "INSERT INTO app.session_state" in query:
assert isinstance(args[8], dict)
conn = FakeConn()
goals = ["라포", "탐색"]
with (
patch.object(session_persistence, "get_pool", return_value=object()),
patch.object(
session_persistence,
"acquire",
return_value=_AsyncConnContext(conn),
),
):
created = await session_persistence.create_session(
learner_id=_principal().user_id,
card=persona_service.P1,
theory_mode="humanistic",
state=state_machine.SessionState(),
session_no=2,
carry_rapport=0.25,
persona_id="00000000-0000-0000-0000-000000000201",
persona_version=1,
case_id="00000000-0000-0000-0000-000000000202",
goal_stages=goals,
)
self.assertIsNotNone(created)
self.assertIsInstance(conn.session_insert_args[11], list)
self.assertEqual(conn.session_insert_args[11], goals)
self.assertNotIsInstance(conn.session_insert_args[11], str)
async def test_generate_turn_engine_failure_does_not_append_learner_turn(
self,
) -> None:
@ -745,6 +818,8 @@ class SessionTurnPersistenceTest(unittest.IsolatedAsyncioTestCase):
)
)
review_turn = review.turns[0]
self.assertEqual(review_turn.id, "t1")
self.assertEqual(review_turn.turn_id, learner_turn.turn_id)
self.assertIsNotNone(review_turn.note)
assert review_turn.note is not None
self.assertEqual(review_turn.note.title, "턴 직후 평가 실패")