PII 표시 토큰 누출 보정
This commit is contained in:
parent
61a41d1f08
commit
7cece90e33
19 changed files with 284 additions and 197 deletions
|
|
@ -247,6 +247,8 @@ async def _retrieve_live_coach_grounding(
|
||||||
) -> list[live_coach.LiveCoachGrounding]:
|
) -> list[live_coach.LiveCoachGrounding]:
|
||||||
"""라이브 코치용 평가 근거 회수. 미가용 시 빈 리스트로 진행한다."""
|
"""라이브 코치용 평가 근거 회수. 미가용 시 빈 리스트로 진행한다."""
|
||||||
learner_masked = guardrail.mask_pii(learner_text).text_masked
|
learner_masked = guardrail.mask_pii(learner_text).text_masked
|
||||||
|
# LiveCoachRequest.client_reply는 브라우저가 보내는 제어 입력이므로 합성 출력이
|
||||||
|
# 아니다. 일반 입력용 PII 게이트를 유지해 문맥 없는 실제 이름도 차단한다.
|
||||||
client_masked = guardrail.mask_pii(client_reply or "").text_masked
|
client_masked = guardrail.mask_pii(client_reply or "").text_masked
|
||||||
query = " ".join(
|
query = " ".join(
|
||||||
part for part in [stage, theory_mode, learner_masked, client_masked] if part
|
part for part in [stage, theory_mode, learner_masked, client_masked] if part
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,7 @@ from ..persona_repository import (
|
||||||
from ..runtime_policy import require_runtime_fallback_allowed
|
from ..runtime_policy import require_runtime_fallback_allowed
|
||||||
from ..services import (
|
from ..services import (
|
||||||
evaluator,
|
evaluator,
|
||||||
|
guardrail,
|
||||||
multimodal_alliance,
|
multimodal_alliance,
|
||||||
multimodal_alliance_store,
|
multimodal_alliance_store,
|
||||||
orchestrator,
|
orchestrator,
|
||||||
|
|
@ -1512,7 +1513,7 @@ def _client_turn_text_for_speech(sess: InProcSession, turn_seq: int) -> str | No
|
||||||
match = visible_clients[index] if 0 <= index < len(visible_clients) else None
|
match = visible_clients[index] if 0 <= index < len(visible_clients) else None
|
||||||
if match is not None:
|
if match is not None:
|
||||||
text = (match.text_masked or match.text).strip()
|
text = (match.text_masked or match.text).strip()
|
||||||
return text or None
|
return guardrail.humanize_pii_placeholders(text) or None
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -520,7 +520,9 @@ def build_fast_messages(ctx: "TurnContext", client_reply: str) -> list[EngineMes
|
||||||
"""fast-loop 평가 프롬프트(L0 역할 + 후보 라벨 + 이번 턴 맥락)."""
|
"""fast-loop 평가 프롬프트(L0 역할 + 후보 라벨 + 이번 턴 맥락)."""
|
||||||
st = ctx.state_after or ctx.state_before
|
st = ctx.state_after or ctx.state_before
|
||||||
theory = _theory_mode(ctx)
|
theory = _theory_mode(ctx)
|
||||||
client_reply_masked = guardrail.mask_pii(client_reply).text_masked
|
client_reply_masked = guardrail.mask_synthetic_generated_pii(
|
||||||
|
client_reply
|
||||||
|
).text_masked
|
||||||
recent = (
|
recent = (
|
||||||
"\n".join(
|
"\n".join(
|
||||||
f"{speaker_ko_label(t.get('speaker'))}: {t.get('text', '')}"
|
f"{speaker_ko_label(t.get('speaker'))}: {t.get('text', '')}"
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ _KOREAN_CONTEXTLESS_NAME = rf"[{_KOREAN_SURNAME_CHARS}][가-힣]{{2,3}}"
|
||||||
# 인명 마지막 글자로 사실상 쓰이지 않는 용언 활용/명사화 꼬리 글자. 문맥 단서가
|
# 인명 마지막 글자로 사실상 쓰이지 않는 용언 활용/명사화 꼬리 글자. 문맥 단서가
|
||||||
# 없는 인명 패턴이 "유의점은·방치되는·마무리했을·연결감과·표현함을·어지러움은"
|
# 없는 인명 패턴이 "유의점은·방치되는·마무리했을·연결감과·표현함을·어지러움은"
|
||||||
# 같은 일반 단어를 이름으로 오탐해 평가 문장을 훼손하는 것을 막는다(정밀도 가드).
|
# 같은 일반 단어를 이름으로 오탐해 평가 문장을 훼손하는 것을 막는다(정밀도 가드).
|
||||||
_KOREAN_NAME_TAIL_GUARD = "(?<![했됐되된될것듯점음움됨함감])"
|
_KOREAN_NAME_TAIL_GUARD = "(?<![했됐되된될겠것듯점음움됨함감])"
|
||||||
_KOREAN_NAME_STOPWORDS = {
|
_KOREAN_NAME_STOPWORDS = {
|
||||||
"연락",
|
"연락",
|
||||||
"연락처",
|
"연락처",
|
||||||
|
|
@ -458,14 +458,18 @@ _DISPLAY_PLACEHOLDER_STREAM_TAIL = 16
|
||||||
def _adjust_particle(value: str, particle: str | None) -> str:
|
def _adjust_particle(value: str, particle: str | None) -> str:
|
||||||
if particle is None:
|
if particle is None:
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
last = value[-1] if value else ""
|
||||||
|
codepoint = ord(last) - 0xAC00 if "가" <= last <= "힣" else -1
|
||||||
|
has_batchim = codepoint >= 0 and codepoint % 28 != 0
|
||||||
if particle in ("은", "는"):
|
if particle in ("은", "는"):
|
||||||
return value + "은"
|
return value + ("은" if has_batchim else "는")
|
||||||
if particle in ("이", "가"):
|
if particle in ("이", "가"):
|
||||||
return value + "이"
|
return value + ("이" if has_batchim else "가")
|
||||||
if particle in ("을", "를"):
|
if particle in ("을", "를"):
|
||||||
return value + "을"
|
return value + ("을" if has_batchim else "를")
|
||||||
if particle in ("와", "과"):
|
if particle in ("와", "과"):
|
||||||
return value + "과"
|
return value + ("과" if has_batchim else "와")
|
||||||
return value + particle
|
return value + particle
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -621,6 +625,7 @@ __all__ = [
|
||||||
"PiiEntitySpan",
|
"PiiEntitySpan",
|
||||||
"set_ko_pii_recognizer",
|
"set_ko_pii_recognizer",
|
||||||
"mask_pii",
|
"mask_pii",
|
||||||
|
"mask_synthetic_generated_pii",
|
||||||
"CrisisKind",
|
"CrisisKind",
|
||||||
"CrisisResult",
|
"CrisisResult",
|
||||||
"classify_crisis",
|
"classify_crisis",
|
||||||
|
|
|
||||||
|
|
@ -221,7 +221,8 @@ class OrchestratorMaskingGateTest(unittest.IsolatedAsyncioTestCase):
|
||||||
text = (
|
text = (
|
||||||
"유의점은 세 가지다. 정서 신호가 방치되는 것을 피하고, 회기를 "
|
"유의점은 세 가지다. 정서 신호가 방치되는 것을 피하고, 회기를 "
|
||||||
"마무리했을 때 연결감과 작업동맹을 지키며, 감정을 표현함을 존중한다. "
|
"마무리했을 때 연결감과 작업동맹을 지키며, 감정을 표현함을 존중한다. "
|
||||||
"어지러움은 신체 신호로 다룬다."
|
"어지러움은 신체 신호로 다룬다. 뭘 할 수 있게 되는 건지 잘 "
|
||||||
|
"모르겠는데요."
|
||||||
)
|
)
|
||||||
|
|
||||||
masked = guardrail.mask_pii(text)
|
masked = guardrail.mask_pii(text)
|
||||||
|
|
|
||||||
|
|
@ -376,7 +376,10 @@ class SessionTurnPersistenceTest(unittest.IsolatedAsyncioTestCase):
|
||||||
turn_seq=ctx.state_after.turn_seq,
|
turn_seq=ctx.state_after.turn_seq,
|
||||||
stage=ctx.state_after.stage.value,
|
stage=ctx.state_after.stage.value,
|
||||||
effective_openness=ctx.state_after.effective_openness,
|
effective_openness=ctx.state_after.effective_openness,
|
||||||
client_reply="저는 김서연 씨고 한신대학교 상담심리학과 학생이에요.",
|
client_reply=(
|
||||||
|
"저는 김서연 씨고 한신대학교 상담심리학과 학생이에요. "
|
||||||
|
"여기서 뭘 해야 하는지 잘 모르겠는데요."
|
||||||
|
),
|
||||||
safety_flagged=False,
|
safety_flagged=False,
|
||||||
state_after=ctx.state_after,
|
state_after=ctx.state_after,
|
||||||
llm_provider="claude_cli",
|
llm_provider="claude_cli",
|
||||||
|
|
@ -395,19 +398,24 @@ class SessionTurnPersistenceTest(unittest.IsolatedAsyncioTestCase):
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
response.client_reply,
|
response.client_reply,
|
||||||
"저는 김서연 씨고 한신대학교 상담심리학과 학생이에요.",
|
"저는 김서연 씨고 한신대학교 상담심리학과 학생이에요. "
|
||||||
|
"여기서 뭘 해야 하는지 잘 모르겠는데요.",
|
||||||
)
|
)
|
||||||
self.assertEqual(len(sess.turns), 2)
|
self.assertEqual(len(sess.turns), 2)
|
||||||
learner_turn, client_turn = sess.turns
|
learner_turn, client_turn = sess.turns
|
||||||
self.assertIsNone(learner_turn.llm_provider)
|
self.assertIsNone(learner_turn.llm_provider)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
client_turn.text, "저는 김서연 씨고 한신대학교 상담심리학과 학생이에요."
|
client_turn.text,
|
||||||
|
"저는 김서연 씨고 한신대학교 상담심리학과 학생이에요. "
|
||||||
|
"여기서 뭘 해야 하는지 잘 모르겠는데요.",
|
||||||
)
|
)
|
||||||
self.assertNotIn("김서연", client_turn.text_masked)
|
self.assertNotIn("김서연", client_turn.text_masked)
|
||||||
self.assertNotIn("한신대학교", client_turn.text_masked)
|
self.assertNotIn("한신대학교", client_turn.text_masked)
|
||||||
self.assertNotIn("상담심리학과", client_turn.text_masked)
|
self.assertNotIn("상담심리학과", client_turn.text_masked)
|
||||||
self.assertIn("[NAME]", client_turn.text_masked)
|
self.assertIn("[NAME]", client_turn.text_masked)
|
||||||
self.assertIn("[ORG]", client_turn.text_masked)
|
self.assertIn("[ORG]", client_turn.text_masked)
|
||||||
|
self.assertIn("잘 모르겠는데요", client_turn.text_masked)
|
||||||
|
self.assertNotIn("[NAME]는데요", client_turn.text_masked)
|
||||||
self.assertEqual(client_turn.llm_provider, "claude_cli")
|
self.assertEqual(client_turn.llm_provider, "claude_cli")
|
||||||
self.assertEqual(client_turn.model, "gateway-default")
|
self.assertEqual(client_turn.model, "gateway-default")
|
||||||
self.assertEqual(client_turn.tokens_in, 17)
|
self.assertEqual(client_turn.tokens_in, 17)
|
||||||
|
|
@ -991,6 +999,7 @@ class SessionTurnPersistenceTest(unittest.IsolatedAsyncioTestCase):
|
||||||
latency_ms=12,
|
latency_ms=12,
|
||||||
query_text="정리 humanistic 자살 위험 최근성 보호요인",
|
query_text="정리 humanistic 자살 위험 최근성 보호요인",
|
||||||
)
|
)
|
||||||
|
retrieve_grounding = AsyncMock(return_value=retrieval)
|
||||||
|
|
||||||
with (
|
with (
|
||||||
patch.object(
|
patch.object(
|
||||||
|
|
@ -999,13 +1008,16 @@ class SessionTurnPersistenceTest(unittest.IsolatedAsyncioTestCase):
|
||||||
patch.object(
|
patch.object(
|
||||||
sessions.rag,
|
sessions.rag,
|
||||||
"retrieve_eval_grounding",
|
"retrieve_eval_grounding",
|
||||||
AsyncMock(return_value=retrieval),
|
retrieve_grounding,
|
||||||
),
|
),
|
||||||
patch.object(sessions.rag, "log_retrieval", AsyncMock()),
|
patch.object(sessions.rag, "log_retrieval", AsyncMock()),
|
||||||
):
|
):
|
||||||
grounding = await sessions._retrieve_live_coach_grounding(
|
grounding = await sessions._retrieve_live_coach_grounding(
|
||||||
learner_text="자살 위험 최근성과 보호요인을 확인했어요.",
|
learner_text="자살 위험 최근성과 보호요인을 확인했어요.",
|
||||||
client_reply="그래도 오늘은 친구에게 연락할 수 있어요.",
|
client_reply=(
|
||||||
|
"박민수에게 연락할지 잘 모르겠는데요. 그래도 오늘은 "
|
||||||
|
"친구에게 연락할 수 있어요."
|
||||||
|
),
|
||||||
stage="정리",
|
stage="정리",
|
||||||
theory_mode="humanistic",
|
theory_mode="humanistic",
|
||||||
)
|
)
|
||||||
|
|
@ -1017,6 +1029,11 @@ class SessionTurnPersistenceTest(unittest.IsolatedAsyncioTestCase):
|
||||||
self.assertEqual(source.source_type, "official_guideline_summary")
|
self.assertEqual(source.source_type, "official_guideline_summary")
|
||||||
self.assertEqual(source.version, "2026-06-15")
|
self.assertEqual(source.version, "2026-06-15")
|
||||||
self.assertEqual(source.citation, "허가된 공식 지침 요약")
|
self.assertEqual(source.citation, "허가된 공식 지침 요약")
|
||||||
|
query = retrieve_grounding.await_args.kwargs["query"]
|
||||||
|
self.assertNotIn("박민수", query)
|
||||||
|
self.assertIn("[NAME]에게", query)
|
||||||
|
self.assertIn("잘 모르겠는데요", query)
|
||||||
|
self.assertNotIn("[NAME]는데요", query)
|
||||||
|
|
||||||
async def test_live_coach_persistence_failure_is_not_swallowed(self) -> None:
|
async def test_live_coach_persistence_failure_is_not_swallowed(self) -> None:
|
||||||
principal = _principal()
|
principal = _principal()
|
||||||
|
|
|
||||||
|
|
@ -240,7 +240,9 @@ class VoiceWebSocketContractTest(unittest.IsolatedAsyncioTestCase):
|
||||||
speaker="client",
|
speaker="client",
|
||||||
stage="초기",
|
stage="초기",
|
||||||
text="raw client reply",
|
text="raw client reply",
|
||||||
text_masked="마스킹된 내담자 응답",
|
text_masked=(
|
||||||
|
"[NAME]은 [ORG]에서 상담받고 있고 [PHONE]는 [DATE]와 달라요."
|
||||||
|
),
|
||||||
),
|
),
|
||||||
TurnRecord(
|
TurnRecord(
|
||||||
turn_seq=3,
|
turn_seq=3,
|
||||||
|
|
@ -255,7 +257,7 @@ class VoiceWebSocketContractTest(unittest.IsolatedAsyncioTestCase):
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
voice_routes._client_turn_text_for_speech(session, 2),
|
voice_routes._client_turn_text_for_speech(session, 2),
|
||||||
"마스킹된 내담자 응답",
|
"그 이름은 그 기관에서 상담받고 있고 연락처는 그 날짜와 달라요.",
|
||||||
)
|
)
|
||||||
self.assertIsNone(voice_routes._client_turn_text_for_speech(session, 3))
|
self.assertIsNone(voice_routes._client_turn_text_for_speech(session, 3))
|
||||||
self.assertIsNone(voice_routes._client_turn_text_for_speech(session, 99))
|
self.assertIsNone(voice_routes._client_turn_text_for_speech(session, 99))
|
||||||
|
|
|
||||||
|
|
@ -122,7 +122,10 @@ async def record_completed_turn(
|
||||||
context=f"{context_prefix} turn append",
|
context=f"{context_prefix} turn append",
|
||||||
)
|
)
|
||||||
if result.client_reply:
|
if result.client_reply:
|
||||||
client_mask = guardrail.mask_pii(result.client_reply)
|
# result.client_reply는 출력 가드레일을 통과한 합성 내담자 발화다.
|
||||||
|
# 입력용의 광범위한 성씨 휴리스틱을 다시 적용하면 "모르겠는데요" 같은
|
||||||
|
# 일반 용언이 [NAME]으로 오탐되어 저장·재개 화면에 누출된다.
|
||||||
|
client_mask = guardrail.mask_synthetic_generated_pii(result.client_reply)
|
||||||
await append_completed_turn(
|
await append_completed_turn(
|
||||||
sess,
|
sess,
|
||||||
TurnRecord(
|
TurnRecord(
|
||||||
|
|
|
||||||
|
|
@ -145,8 +145,8 @@ async function readMetrics(page: Page): Promise<DashboardMetrics> {
|
||||||
|
|
||||||
function expectDerivedMetrics(metrics: DashboardMetrics) {
|
function expectDerivedMetrics(metrics: DashboardMetrics) {
|
||||||
expect(metrics.total).toBeGreaterThan(0);
|
expect(metrics.total).toBeGreaterThan(0);
|
||||||
expect(metrics.done).toBe(32);
|
expect(metrics.done).toBe(33);
|
||||||
expect(metrics.doing).toBe(3);
|
expect(metrics.doing).toBe(2);
|
||||||
expect(metrics.planned).toBe(0);
|
expect(metrics.planned).toBe(0);
|
||||||
expect(metrics.total).toBe(metrics.done + metrics.doing + metrics.planned);
|
expect(metrics.total).toBe(metrics.done + metrics.doing + metrics.planned);
|
||||||
expect(metrics.ownerBoard).toBe(
|
expect(metrics.ownerBoard).toBe(
|
||||||
|
|
|
||||||
|
|
@ -192,6 +192,24 @@ async function routeSessionFixtureApi(page: Page, options: SessionFixtureOptions
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await page.route(
|
||||||
|
`**/api/sessions/${fixtureSessionId}/multimodal-alliance/consent`,
|
||||||
|
async (route) => {
|
||||||
|
const consentBody = route.request().postDataJSON() as Record<string, unknown>;
|
||||||
|
await route.fulfill({
|
||||||
|
status: 201,
|
||||||
|
contentType: "application/json",
|
||||||
|
body: JSON.stringify({
|
||||||
|
submission_id: consentBody.submission_id,
|
||||||
|
consent_snapshot_id: "00000000-0000-4000-8000-000000000778",
|
||||||
|
consent_status: "granted",
|
||||||
|
deletion_request_id: null,
|
||||||
|
idempotent_replay: false,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
await page.route(`**/api/sessions/${fixtureSessionId}/alliance-pulses`, async (route) => {
|
await page.route(`**/api/sessions/${fixtureSessionId}/alliance-pulses`, async (route) => {
|
||||||
await route.fulfill({
|
await route.fulfill({
|
||||||
status: 200,
|
status: 200,
|
||||||
|
|
@ -319,6 +337,14 @@ async function routeSessionFixtureApi(page: Page, options: SessionFixtureOptions
|
||||||
return { liveCoachRequests, consentRequests };
|
return { liveCoachRequests, consentRequests };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function acceptVoiceInputConsent(page: Page) {
|
||||||
|
const consentDialog = page.getByRole("dialog", { name: "음성 입력을 사용하기 전에" });
|
||||||
|
await expect(consentDialog).toBeVisible();
|
||||||
|
await expect(consentDialog).toContainText("원음은 보존하지 않습니다");
|
||||||
|
await consentDialog.getByRole("button", { name: "동의하고 마이크 켜기" }).click();
|
||||||
|
await expect(consentDialog).toHaveCount(0);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 음성 마이크·WebSocket mock — session-mvp.spec.ts 패턴.
|
* 음성 마이크·WebSocket mock — session-mvp.spec.ts 패턴.
|
||||||
* audio_end 수신 시 transcript→reply→state:speaking 을 흘려
|
* audio_end 수신 시 transcript→reply→state:speaking 을 흘려
|
||||||
|
|
@ -615,6 +641,56 @@ test.describe("full sweep — counseling session", () => {
|
||||||
await expect(compose).toHaveAttribute("placeholder", "종료된 회기입니다.");
|
await expect(compose).toHaveAttribute("placeholder", "종료된 회기입니다.");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("naturalizes privacy placeholders when a persisted session is resumed", async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const startedAt = new Date(Date.now() - 60_000).toISOString();
|
||||||
|
await routeSessionFixtureApi(page, {
|
||||||
|
detailOverrides: {
|
||||||
|
started_at: startedAt,
|
||||||
|
turns: [
|
||||||
|
{
|
||||||
|
speaker: "learner",
|
||||||
|
text:
|
||||||
|
"[NAME] 이야기와 [PHONE]·[EMAIL]·[RRN]·[NUMID]·[DATE]·[MONEY]·[ADDR]·[ADDRESS]를 확인할까요?",
|
||||||
|
turn_seq: 1,
|
||||||
|
created_at: new Date(Date.now() - 50_000).toISOString(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
speaker: "client",
|
||||||
|
text: "여기서 뭘 할 수 있게 [NAME]는 건지 잘 [NAME]는데요. [ORG]에서 오라고 했어요.",
|
||||||
|
turn_seq: 2,
|
||||||
|
created_at: new Date(Date.now() - 40_000).toISOString(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.goto(`/learn/session/${fixtureSessionId}`);
|
||||||
|
|
||||||
|
const transcript = page.locator(".sx-transcript");
|
||||||
|
await expect(transcript.locator(".sx-utt")).toHaveCount(2);
|
||||||
|
for (const token of [
|
||||||
|
"[NAME]",
|
||||||
|
"[ORG]",
|
||||||
|
"[PHONE]",
|
||||||
|
"[EMAIL]",
|
||||||
|
"[RRN]",
|
||||||
|
"[NUMID]",
|
||||||
|
"[DATE]",
|
||||||
|
"[MONEY]",
|
||||||
|
"[ADDR]",
|
||||||
|
"[ADDRESS]",
|
||||||
|
]) {
|
||||||
|
await expect(transcript).not.toContainText(token);
|
||||||
|
}
|
||||||
|
await expect(transcript).toContainText("익명 내담자 이야기");
|
||||||
|
await expect(transcript).toContainText(
|
||||||
|
"뭘 할 수 있게 되는 건지 잘 모르겠는데요",
|
||||||
|
);
|
||||||
|
await expect(transcript).toContainText("소속 기관에서 오라고 했어요");
|
||||||
|
});
|
||||||
|
|
||||||
// checklist: session-transcript-autoscroll
|
// checklist: session-transcript-autoscroll
|
||||||
test("releases autoscroll when scrolling up, jumps back with the latest button, and follows new turns", async ({
|
test("releases autoscroll when scrolling up, jumps back with the latest button, and follows new turns", async ({
|
||||||
page,
|
page,
|
||||||
|
|
@ -966,6 +1042,7 @@ test.describe("full sweep — counseling session", () => {
|
||||||
const micButton = page.getByRole("button", { name: "마이크 켜기" });
|
const micButton = page.getByRole("button", { name: "마이크 켜기" });
|
||||||
await expect(micButton).toBeEnabled();
|
await expect(micButton).toBeEnabled();
|
||||||
await micButton.click();
|
await micButton.click();
|
||||||
|
await acceptVoiceInputConsent(page);
|
||||||
await page.getByRole("button", { name: "발화 보내기" }).click();
|
await page.getByRole("button", { name: "발화 보내기" }).click();
|
||||||
|
|
||||||
// reply + state:speaking → 재생 중에만 '건너뛰기'가 노출된다.
|
// reply + state:speaking → 재생 중에만 '건너뛰기'가 노출된다.
|
||||||
|
|
@ -1018,11 +1095,14 @@ test.describe("full sweep — counseling session", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// checklist: session-keyboard-shortcuts
|
// checklist: session-keyboard-shortcuts
|
||||||
test("toggles pause with P and mic with Space only outside text inputs", async ({ page }) => {
|
test("toggles pause with P and mic with Alt+M only outside text inputs", async ({ page }) => {
|
||||||
await routeSessionFixtureApi(page);
|
await routeSessionFixtureApi(page);
|
||||||
await installVoiceSpeakingFixture(page, voiceTranscript, voiceClientReply);
|
await installVoiceSpeakingFixture(page, voiceTranscript, voiceClientReply);
|
||||||
|
|
||||||
await startFixtureSession(page);
|
await startFixtureSession(page);
|
||||||
|
await page.evaluate(() => {
|
||||||
|
if (document.activeElement instanceof HTMLElement) document.activeElement.blur();
|
||||||
|
});
|
||||||
|
|
||||||
// P — 일시정지 토글.
|
// P — 일시정지 토글.
|
||||||
await page.keyboard.press("p");
|
await page.keyboard.press("p");
|
||||||
|
|
@ -1037,9 +1117,10 @@ test.describe("full sweep — counseling session", () => {
|
||||||
await expect(compose).toHaveValue("p");
|
await expect(compose).toHaveValue("p");
|
||||||
await expect(page.getByRole("button", { name: "일시정지" })).toBeVisible();
|
await expect(page.getByRole("button", { name: "일시정지" })).toBeVisible();
|
||||||
|
|
||||||
// 포커스를 입력창 밖으로 옮긴 뒤 Space — 마이크 시작.
|
// 포커스를 입력창 밖으로 옮긴 뒤 Alt+M — 마이크 시작.
|
||||||
await compose.blur();
|
await compose.blur();
|
||||||
await page.keyboard.press("Space");
|
await page.keyboard.press("Alt+m");
|
||||||
|
await acceptVoiceInputConsent(page);
|
||||||
await expect(page.getByRole("button", { name: "발화 보내기" })).toBeVisible();
|
await expect(page.getByRole("button", { name: "발화 보내기" })).toBeVisible();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
30
apps/web/src/lib/piiDisplay.ts
Normal file
30
apps/web/src/lib/piiDisplay.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
const DISPLAY_PLACEHOLDERS: Record<string, string> = {
|
||||||
|
"[NAME]": "익명 내담자",
|
||||||
|
"[ORG]": "소속 기관",
|
||||||
|
"[PHONE]": "연락처",
|
||||||
|
"[EMAIL]": "이메일",
|
||||||
|
"[RRN]": "주민등록번호",
|
||||||
|
"[NUMID]": "식별번호",
|
||||||
|
"[DATE]": "날짜",
|
||||||
|
"[MONEY]": "금액",
|
||||||
|
"[ADDR]": "주소",
|
||||||
|
"[ADDRESS]": "주소",
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 저장/API의 privacy-proof 토큰은 유지하고, 사람이 읽는 일반 대화 표면에서만
|
||||||
|
* 토큰을 안전한 설명으로 낮춘다. 근거 인용·내보내기에는 사용하지 않는다.
|
||||||
|
*/
|
||||||
|
export function displayPiiSafeText(text: string) {
|
||||||
|
// 2026-08-09 이전 저장본에서 광범위한 성씨 휴리스틱이 평범한 용언을
|
||||||
|
// NAME으로 오탐한 두 실관측 문형. 원문은 복원할 수 없으므로 표시층에서만
|
||||||
|
// 결정적으로 복구하고 저장/API privacy proof는 그대로 둔다.
|
||||||
|
let next = text.replace(
|
||||||
|
/\[NAME\]는 건지\s+잘 \[NAME\]는데요/g,
|
||||||
|
"되는 건지 잘 모르겠는데요",
|
||||||
|
);
|
||||||
|
for (const [placeholder, label] of Object.entries(DISPLAY_PLACEHOLDERS)) {
|
||||||
|
next = next.split(placeholder).join(label);
|
||||||
|
}
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
@ -43,6 +43,7 @@ import {
|
||||||
} from "../lib/api";
|
} from "../lib/api";
|
||||||
import { useAuth } from "../lib/auth";
|
import { useAuth } from "../lib/auth";
|
||||||
import { formatElapsed, formatTimecode, clamp01 } from "../lib/format";
|
import { formatElapsed, formatTimecode, clamp01 } from "../lib/format";
|
||||||
|
import { displayPiiSafeText } from "../lib/piiDisplay";
|
||||||
import {
|
import {
|
||||||
parseVoicePracticeContext,
|
parseVoicePracticeContext,
|
||||||
voicePracticeSearch,
|
voicePracticeSearch,
|
||||||
|
|
@ -1001,7 +1002,7 @@ export default function Session() {
|
||||||
(detail.turns ?? []).map((turn) => ({
|
(detail.turns ?? []).map((turn) => ({
|
||||||
id: nextId(),
|
id: nextId(),
|
||||||
speaker: turn.speaker === "client" ? "client" : "learner",
|
speaker: turn.speaker === "client" ? "client" : "learner",
|
||||||
text: turn.text,
|
text: displayPiiSafeText(turn.text),
|
||||||
turnSeq: turn.turn_seq,
|
turnSeq: turn.turn_seq,
|
||||||
at: secondsBetween(detail.started_at, turn.created_at),
|
at: secondsBetween(detail.started_at, turn.created_at),
|
||||||
})),
|
})),
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import type {
|
||||||
UserPrepostMeasureItem,
|
UserPrepostMeasureItem,
|
||||||
UserPrepostMeasuresResponse,
|
UserPrepostMeasuresResponse,
|
||||||
} from "../../lib/api";
|
} from "../../lib/api";
|
||||||
|
import { displayPiiSafeText } from "../../lib/piiDisplay";
|
||||||
|
|
||||||
export type PrepostMeasureName = UserPrepostMeasureItem["measure_name"];
|
export type PrepostMeasureName = UserPrepostMeasureItem["measure_name"];
|
||||||
export type PrepostTimepoint = UserPrepostMeasureItem["timepoint"];
|
export type PrepostTimepoint = UserPrepostMeasureItem["timepoint"];
|
||||||
|
|
@ -132,23 +133,8 @@ export function displayEvaluationRetryError(message: string) {
|
||||||
return "AI 평가 재시도를 완료하지 못했습니다. 잠시 뒤 다시 실행해 주세요.";
|
return "AI 평가 재시도를 완료하지 못했습니다. 잠시 뒤 다시 실행해 주세요.";
|
||||||
}
|
}
|
||||||
|
|
||||||
const REVIEW_DISPLAY_PLACEHOLDERS: Record<string, string> = {
|
|
||||||
"[NAME]": "익명 내담자",
|
|
||||||
"[ORG]": "소속 기관",
|
|
||||||
"[PHONE]": "연락처",
|
|
||||||
"[EMAIL]": "이메일",
|
|
||||||
"[RRN]": "주민등록번호",
|
|
||||||
"[ADDRESS]": "주소",
|
|
||||||
};
|
|
||||||
|
|
||||||
export function displayTranscriptText(text: string) {
|
export function displayTranscriptText(text: string) {
|
||||||
let next = text;
|
return displayPiiSafeText(text);
|
||||||
for (const [placeholder, label] of Object.entries(
|
|
||||||
REVIEW_DISPLAY_PLACEHOLDERS,
|
|
||||||
)) {
|
|
||||||
next = next.split(placeholder).join(label);
|
|
||||||
}
|
|
||||||
return next;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function shouldPollReviewReady(data: SessionReviewResponse) {
|
export function shouldPollReviewReady(data: SessionReviewResponse) {
|
||||||
|
|
|
||||||
171
docs/HANDOFF.md
171
docs/HANDOFF.md
|
|
@ -1,15 +1,14 @@
|
||||||
# Vignette G0~G8 완료 추진 핸드오프
|
# Vignette G0~G8 완료 추진 핸드오프
|
||||||
|
|
||||||
> Updated: 2026-08-09 KST (다른 에이전트 실행용 최종 인계 · G7 외부-proof P0 코드 완료 · current-source fixture 분할 GREEN/clean HEAD 재실행)
|
> Updated: 2026-08-09 KST (G8 clean-head NAS-origin 종료 · 남은 핵심은 G7 external proof)
|
||||||
> Workspace: `D:\workspace\vignette`
|
> Workspace: `D:\workspace\vignette`
|
||||||
> 이 문서는 다음 에이전트가 **G0~G8 아홉 목표 중 남은 G7 외부 게이트와 G8 NAS-origin 재검증을 닫기 위한** 실행 기준이다.
|
> 이 문서는 다음 에이전트가 **G0~G8 아홉 목표 중 마지막 G7 외부 게이트를 닫기 위한** 실행 기준이다.
|
||||||
|
|
||||||
## 0. 현재 상태 한 줄 요약
|
## 0. 현재 상태 한 줄 요약
|
||||||
|
|
||||||
**G0~G6의 내부 구현·증거는 종료됐다. 단, 최종 clean source 전체 회귀는 다시 실행해야 한다. G8 실제 rollback
|
**G0~G6의 내부 구현·증거와 G8 clean-head NAS-origin runtime은 종료됐다. G8은 source HEAD
|
||||||
receipt는 완료됐지만 current source clean-head 승격은 아직 0회다.
|
`61a41d1f…6af`·tree `87dec55d…3b77`·archive `4d15d055…119d4d`로 candidate 112/112와 actual NAS-origin
|
||||||
두 execute는 모두 NAS mutation 전에 안전하게 중단됐다. naive `started_at`의 9시간 오차 P0는 UTC/KST 계약과
|
112/112를 모두 통과했다. 앞선 fail-closed·verified rollback 이력과 previous `6030a677…c611`은 보존했다. G7 공개 런처의
|
||||||
실DB 브라우저 회귀로 수정됐고, 이제 새 clean commit 결속과 NAS-origin 전체 E2E가 남았다. G7 공개 런처의
|
|
||||||
local sidecar lifecycle·provider/model readiness·Uvicorn queue, fresh PID/source provenance, 3,120초 runner와
|
local sidecar lifecycle·provider/model readiness·Uvicorn queue, fresh PID/source provenance, 3,120초 runner와
|
||||||
3,000초 공통 시간창·canonical exit 계약은 소스에서 완료됐지만, current source 공개 배포와 외부 4-artifact 게이트는 남아 있다.**
|
3,000초 공통 시간창·canonical exit 계약은 소스에서 완료됐지만, current source 공개 배포와 외부 4-artifact 게이트는 남아 있다.**
|
||||||
|
|
||||||
|
|
@ -23,28 +22,24 @@ local sidecar lifecycle·provider/model readiness·Uvicorn queue, fresh PID/sour
|
||||||
| G5 Calibration & Transfer | DONE | actual transfer execution, independent/model-run/instrument/evidence provenance, DB/browser idempotency |
|
| G5 Calibration & Transfer | DONE | actual transfer execution, independent/model-run/instrument/evidence provenance, DB/browser idempotency |
|
||||||
| G6 Supervision & Research | DONE | safety event metadata-only projection, safety priority 1 > deterioration 2, raw detail 0 |
|
| G6 Supervision & Research | DONE | safety event metadata-only projection, safety priority 1 > deterioration 2, raw detail 0 |
|
||||||
| G7 Multimodal Alliance | **internal DONE · external GATE** | consent/withdrawal, synthetic soak 59/59, G7 runner/checker/topology 86/86, launcher/sidecar 80/80, API voice 71/71 |
|
| G7 Multimodal Alliance | **internal DONE · external GATE** | consent/withdrawal, synthetic soak 59/59, G7 runner/checker/topology 86/86, launcher/sidecar 80/80, API voice 71/71 |
|
||||||
| G8 Continuous Improvement | **receipt DONE · runtime REVALIDATION** | 실제 image rollback 2회 + executed receipt 2건. timestamp 수정 실DB GREEN, 새 clean commit·NAS-origin E2E 미완료 |
|
| G8 Continuous Improvement | **DONE · CLEAN-HEAD NAS VERIFIED** | 실제 image rollback 2회 + executed receipt 2건, source/archive 결속, candidate 112/112, actual NAS-origin 112/112 |
|
||||||
|
|
||||||
대시보드 status count는 **32 done / 3 doing / 0 planned**다.
|
대시보드 status count는 **33 done / 2 doing / 0 planned**다.
|
||||||
|
|
||||||
Git 기준선은 branch `master`, HEAD `94c681d450d2f3b4df55c036e333cd817db3f7fb`이고 `origin/master`보다 2 commit
|
G8 배포 source 기준선은 branch `master`, HEAD `61a41d1f08239b5f9e99cfae42a206be250416af`·tree
|
||||||
앞서 있다. 공유 worktree에는
|
`87dec55daf0a22ca5c2e489cd803a2f731e63b77`이며 2회 동일 archive SHA는
|
||||||
문서/대시보드/G7·G8 런북, 완결된 clean-head 릴리스 에이전트·테스트, UTC/KST session timestamp 수정과
|
`4d15d055d74f33f1fa2ff470afbf900d565ab238b19c79df34ca7ba240119d4d`다. 공유 worktree의 후속 변경은 이
|
||||||
browser 회귀가 tracked dirty로 남아 있다. Git 작업은 stage/commit/push하지 않았다.
|
배포 source에 포함되지 않으므로 G8 완료 증거와 섞지 않는다.
|
||||||
|
|
||||||
### 이 문서가 인계된 정확한 중단점
|
### 이 문서가 인계된 정확한 중단점
|
||||||
|
|
||||||
- 사용자가 **현재 구현을 잠시 멈추고 다른 에이전트로 넘길 핸드오프를 만들라**고 요청했다. 핸드오프 정리 중
|
- G8 clean-head execute는 격리 NAS preview만 변경했고 공개 API/DB·물리 마이크는 건드리지 않았다. source HEAD/tree와
|
||||||
public/NAS/DB/마이크/프로세스의 수동 mutation은 0회다. 5분 Scheduled Task는 한 번씩 자동 실행됐으나 아래
|
archive 2회 결정성, fresh dump, exact images, candidate/actual-origin 결과를 모두 결속했다.
|
||||||
source-pin gate에서 안전하게 exit 1로 중단됐다.
|
|
||||||
- active goal은 계속 `active`다. 목표는 “문서 작성”이 아니라 **G0~G8 아홉 목표를 실제 runtime과 E2E로
|
- active goal은 계속 `active`다. 목표는 “문서 작성”이 아니라 **G0~G8 아홉 목표를 실제 runtime과 E2E로
|
||||||
모두 닫는 것**이다. G7 canonical checker exit 0과 G8 current clean source의 NAS-origin 0-failure가 없으면
|
모두 닫는 것**이다. G8은 종료됐고 G7 canonical checker exit 0이 없으면 `goal complete`를 호출하지 않는다.
|
||||||
`goal complete`를 호출하지 않는다.
|
- 마지막 완료 작업은 G8 source HEAD `61a41d1f…6af`·tree `87dec55d…3b77`·archive `4d15d055…119d4d`의
|
||||||
- 마지막 완료 작업은 G7 runner/checker/topology·fresh public launcher provenance P0, session timestamp UTC/KST 계약,
|
candidate 112/112와 actual NAS-origin 112/112다. API/Web `d5021950…e4b1`/`9796c092…4b36`, health
|
||||||
API full 921/921, gateway 58/58, Web API types·typecheck·build, 자기주도 route fixture desktop/mobile 6/6이다.
|
ok·db/engine true·auth 401·OpenAPI 126, fresh dump `36ec8748…24db8`을 확인했다.
|
||||||
fixture desktop/mobile은 마지막 단일
|
|
||||||
120/120 뒤 dashboard spec/HTML이 다시 바뀌었고 그 변경의 dashboard 10/10을 별도로 확인했으므로,
|
|
||||||
**현재 트리는 불변 110 + 최신 dashboard 10의 분할 GREEN**이다. 새 clean HEAD에서 exact 120을 다시 실행한다.
|
|
||||||
- G7 Windows topology mode, 3,000초 공통 시간창, canonical checker-bound exit와 공개 런처의 local
|
- G7 Windows topology mode, 3,000초 공통 시간창, canonical checker-bound exit와 공개 런처의 local
|
||||||
Whisper/MeloTTS lifecycle·exact readiness·Uvicorn queue·fresh API/cloudflared provenance 계약은 코드와 회귀가
|
Whisper/MeloTTS lifecycle·exact readiness·Uvicorn queue·fresh API/cloudflared provenance 계약은 코드와 회귀가
|
||||||
끝났지만 public launcher 실행과 capture는 수행하지 않았다.
|
끝났지만 public launcher 실행과 capture는 수행하지 않았다.
|
||||||
|
|
@ -61,8 +56,8 @@ browser 회귀가 tracked dirty로 남아 있다. Git 작업은 stage/commit/pus
|
||||||
`status=ok, db=true, engine=true`다. clean commit 뒤 두 task를 동일한 detached stable
|
`status=ok, db=true, engine=true`다. clean commit 뒤 두 task를 동일한 detached stable
|
||||||
release root로 재등록하고 action pin·watchdog result 0·로그온/reboot smoke를 확인하기 전에는 public runtime
|
release root로 재등록하고 action pin·watchdog result 0·로그온/reboot smoke를 확인하기 전에는 public runtime
|
||||||
자동복구를 DONE으로 부르지 않는다.
|
자동복구를 DONE으로 부르지 않는다.
|
||||||
- 다음 에이전트의 첫 mutation은 **사용자 승인 아래 새 clean commit 생성**이어야 한다. 그 clean source만 G8 NAS
|
- 다음 mutation 대상은 G7 public runtime이다. G8 NAS는 새 material milestone이 생기기 전까지 읽기 전용 기준선으로
|
||||||
preview 또는 G7 public runtime의 배포 입력으로 쓰고, 두 환경을 동시에 변경하지 말며 mutation owner는 한 명만 둔다.
|
두고, public/DB mutation owner는 한 명만 둔다.
|
||||||
|
|
||||||
### 완료 조건 판정 (2026-08-09 재검증)
|
### 완료 조건 판정 (2026-08-09 재검증)
|
||||||
|
|
||||||
|
|
@ -70,9 +65,9 @@ browser 회귀가 tracked dirty로 남아 있다. Git 작업은 stage/commit/pus
|
||||||
|
|
||||||
| # | 조건 | 판정 | 근거 |
|
| # | 조건 | 판정 | 근거 |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| 1 | G0~G8 각각의 코드 계약과 실제 runtime 증거 | **부분 충족** | G0~G6 충족. G8 receipt는 충족했지만 NAS-origin 회기 재검증이 남았고 G7 외부 GATE가 열려 있다 |
|
| 1 | G0~G8 각각의 코드 계약과 실제 runtime 증거 | **부분 충족** | G0~G6과 G8 충족. G7 외부 GATE만 열려 있다 |
|
||||||
| 2 | 학생 홈→회기→리뷰→처방→재연습→전이 폐루프가 실제 API/DB/browser 통과 | **충족** | 일회용 클론에서 `returned-practice-db-closed-loop` desktop 2/2 + mobile 2/2, route mock 0 |
|
| 2 | 학생 홈→회기→리뷰→처방→재연습→전이 폐루프가 실제 API/DB/browser 통과 | **충족** | 일회용 클론에서 `returned-practice-db-closed-loop` desktop 2/2 + mobile 2/2, route mock 0 |
|
||||||
| 3 | current source 증거와 대시보드·TODO·backlog 일치 | **충족** | SSOT checker FAIL 0, unit 5/5, dashboard E2E 10/10, count 32/3/0이 카드와 일치 |
|
| 3 | current source 증거와 대시보드·TODO·backlog 일치 | **충족** | SSOT checker FAIL 0, dashboard E2E 10/10, count 33/2/0이 카드와 일치 |
|
||||||
| 4 | G7을 가짜·합성 증거로 DONE 처리하지 않음 | **충족** | `check-g7-external-proof.py` exit 1 유지, G7 카드는 `GATE` |
|
| 4 | G7을 가짜·합성 증거로 DONE 처리하지 않음 | **충족** | `check-g7-external-proof.py` exit 1 유지, G7 카드는 `GATE` |
|
||||||
|
|
||||||
재검증 수치: API **921 passed**, gateway **58**, executor **28**, probe **11**, helper launcher **37**,
|
재검증 수치: API **921 passed**, gateway **58**, executor **28**, probe **11**, helper launcher **37**,
|
||||||
|
|
@ -89,8 +84,8 @@ live health 테스트는 React StrictMode 중복 조회의 첫 응답을 고정
|
||||||
재검증됐다. 따라서 current tree의 정직한 판정은 **110 불변 + 10 최신 분할 GREEN**이며, 새 clean commit에서
|
재검증됐다. 따라서 current tree의 정직한 판정은 **110 불변 + 10 최신 분할 GREEN**이며, 새 clean commit에서
|
||||||
같은 120건을 한 번 재실행해 archive/source 결속 뒤의 결과를 고정한다.
|
같은 120건을 한 번 재실행해 archive/source 결속 뒤의 결과를 고정한다.
|
||||||
|
|
||||||
**따라서 `update_goal complete`를 호출하지 않는다.** 조건 1이 G7과 G8 NAS-origin 재검증 때문에 미충족이고,
|
**따라서 `update_goal complete`를 호출하지 않는다.** 조건 1이 G7 external proof 때문에 미충족이고,
|
||||||
조건 4가 G7을 합성 증거로 메우는 것을 금지한다. G8 receipt 자체는 종료됐다(§3).
|
조건 4가 G7을 합성 증거로 메우는 것을 금지한다. G8은 clean-head NAS-origin까지 종료됐다(§3·§5).
|
||||||
|
|
||||||
### G0~G8 목표별 인수 기준
|
### G0~G8 목표별 인수 기준
|
||||||
|
|
||||||
|
|
@ -104,7 +99,7 @@ live health 테스트는 React StrictMode 중복 조회의 첫 응답을 고정
|
||||||
| G5 | 자기평가 잠금, calibration 감소, unseen transfer 유지, subgroup drift | actual transfer execution과 independent/model-run/instrument/evidence provenance | G4와 같은 disposable DB에서 actual POST·read reload·1→1 멱등 재확인 |
|
| G5 | 자기평가 잠금, calibration 감소, unseen transfer 유지, subgroup drift | actual transfer execution과 independent/model-run/instrument/evidence provenance | G4와 같은 disposable DB에서 actual POST·read reload·1→1 멱등 재확인 |
|
||||||
| G6 | 3-click queue, 원장 drilldown, 버전 재현, 역할/RLS/audit | safety metadata-only, safety priority 1 > deterioration 2, raw detail 0 | current HEAD producer/store/API와 supervisor UI 회귀 재실행 |
|
| G6 | 3-click queue, 원장 drilldown, 버전 재현, 역할/RLS/audit | safety metadata-only, safety priority 1 > deterioration 2, raw detail 0 | current HEAD producer/store/API와 supervisor UI 회귀 재실행 |
|
||||||
| G7 | 시간 정렬, **물리 마이크+공개 WSS 장시간 E2E**, text-only 대비 음성 이득, 동의/삭제 정책 | 내부 계약·synthetic soak만 완료. canonical checker는 의도적으로 exit 1 | current 공개 배포→인증 WSS→명시 동의 mic 50분→동시 runtime/topology→독립 human pack→checker exit 0 |
|
| G7 | 시간 정렬, **물리 마이크+공개 WSS 장시간 E2E**, text-only 대비 음성 이득, 동의/삭제 정책 | 내부 계약·synthetic soak만 완료. canonical checker는 의도적으로 exit 1 | current 공개 배포→인증 WSS→명시 동의 mic 50분→동시 runtime/topology→독립 human pack→checker exit 0 |
|
||||||
| G8 | source→draft→red-team→benchmark→catalog 재현, 누수/PII/무근거 0, 자동 calibration+rollback, incident→test/backlog 환류 | agentic worker·human gate·실제 rollback/restore receipt 2건, timestamp focused 실DB/browser GREEN | 사용자 승인 아래 새 clean commit→NAS 배포→**실제 NAS origin** 전체 회기 E2E GREEN→SSOT 동기화 |
|
| G8 | source→draft→red-team→benchmark→catalog 재현, 누수/PII/무근거 0, 자동 calibration+rollback, incident→test/backlog 환류 | agentic worker·human gate·실제 rollback/restore receipt 2건, clean-head candidate/NAS-origin 112/112 | **종료.** 새 material milestone 전까지 NAS 기준선 보존 |
|
||||||
|
|
||||||
## 1. 작업 시작 전 반드시 읽을 파일
|
## 1. 작업 시작 전 반드시 읽을 파일
|
||||||
|
|
||||||
|
|
@ -126,13 +121,11 @@ live health 테스트는 React StrictMode 중복 조회의 첫 응답을 고정
|
||||||
- Windows 11 + PowerShell 환경이다. 실행 전 OS·셸·도구 경로를 다시 확인한다.
|
- Windows 11 + PowerShell 환경이다. 실행 전 OS·셸·도구 경로를 다시 확인한다.
|
||||||
- 명령은 PowerShell 5.1 호환 문법을 기본으로 작성한다. NAS 원격 스크립트는 **UTF-8 base64 전달 방식**을 쓴다.
|
- 명령은 PowerShell 5.1 호환 문법을 기본으로 작성한다. NAS 원격 스크립트는 **UTF-8 base64 전달 방식**을 쓴다.
|
||||||
(`$()`가 PowerShell에서 먼저 확장되는 것을 막는다.)
|
(`$()`가 PowerShell에서 먼저 확장되는 것을 막는다.)
|
||||||
- 이 핸드오프 작성 시 branch/HEAD는 `master` / `94c681d450d2f3b4df55c036e333cd817db3f7fb`이고 include 후보는
|
- G8 배포 source는 `master` / `61a41d1f08239b5f9e99cfae42a206be250416af`이며 공유 worktree의 후속 변경은
|
||||||
52개다: UTC/KST timestamp, G7 voice/topology/public launcher, pinned `psutil`, G8 clean-head controller, public task source pin,
|
이 clean release와 분리한다. stage/commit 시 `git add -A`를 쓰지 않고 명시적 경로만 다룬다.
|
||||||
학생 boot recovery, current fixture 안정화, dashboard/TODO/backlog/HANDOFF·런북 동기화다. 이 묶음들은 아직
|
|
||||||
commit에 들어 있지 않다.
|
|
||||||
- clean-head 구현은 unit 30/30, G7 runner/checker/topology는 86/86, G7 public launcher/sidecar는 80/80,
|
- clean-head 구현은 unit 30/30, G7 runner/checker/topology는 86/86, G7 public launcher/sidecar는 80/80,
|
||||||
public task source pin은 provenance 11개를 포함한 combined 22/22, timestamp·voice 계약을 포함한 API는
|
public task source pin은 provenance 11개를 포함한 combined 22/22, timestamp·voice 계약을 포함한 API는
|
||||||
921/921와 KST 실DB browser 1/1, current fixture는 110 불변 + 최신 dashboard 10 분할 GREEN,
|
921/921와 KST 실DB browser 1/1, G8 candidate/NAS-origin은 각각 112/112,
|
||||||
SSOT는 FAIL 0와 dashboard E2E 10/10을 통과했다. 다음 에이전트는 최초 diff review 뒤 동일한 focused gate를
|
SSOT는 FAIL 0와 dashboard E2E 10/10을 통과했다. 다음 에이전트는 최초 diff review 뒤 동일한 focused gate를
|
||||||
다시 실행하고, 서로 다른 lane의 파일을
|
다시 실행하고, 서로 다른 lane의 파일을
|
||||||
누락한 채 clean commit을 만들지 않는다.
|
누락한 채 clean commit을 만들지 않는다.
|
||||||
|
|
@ -148,7 +141,7 @@ live health 테스트는 React StrictMode 중복 조회의 첫 응답을 고정
|
||||||
리스너를 정리할 수 있으므로 공개 런타임이 떠 있는 동안 실행하지 않는다.
|
리스너를 정리할 수 있으므로 공개 런타임이 떠 있는 동안 실행하지 않는다.
|
||||||
- 비밀값, 이메일, cookie, 사용자 UUID가 포함된 raw response, `.env` 내용은 로그·증거·채팅에 출력하지 않는다.
|
- 비밀값, 이메일, cookie, 사용자 UUID가 포함된 raw response, `.env` 내용은 로그·증거·채팅에 출력하지 않는다.
|
||||||
|
|
||||||
## 3. G8 receipt — 종료됨 · NAS-origin runtime 재검증은 열림
|
## 3. G8 receipt + clean-head NAS-origin — 종료됨
|
||||||
|
|
||||||
### Gate6 계약 정정 (성립 불가능했던 이전 계약을 코드로 교체)
|
### Gate6 계약 정정 (성립 불가능했던 이전 계약을 코드로 교체)
|
||||||
|
|
||||||
|
|
@ -216,29 +209,27 @@ UI가 active DB container에 직접 stop을 보내 같은 증상이 재발했다
|
||||||
|
|
||||||
공개 DB에는 current dirty source를 배포하지 않는다.
|
공개 DB에는 current dirty source를 배포하지 않는다.
|
||||||
|
|
||||||
## 5. NAS 배포 기준선 — 현재 HEAD가 아니라 2026-08-07 릴리스
|
## 5. NAS 배포 기준선 — 2026-08-09 clean-head DONE
|
||||||
|
|
||||||
- **2026-08-09 17:07 KST read-only snapshot:** URL `http://100.116.83.60:8088` · project
|
- URL `http://100.116.83.60:8088` · project `vignette-preview-20260807` · remote root
|
||||||
`vignette-preview-20260807` · remote root `/volume1/docker/vignette-preview-20260807` · active release SHA
|
`/volume1/docker/vignette-preview-20260807`.
|
||||||
`6030a677af7e87cbfabc422b553d108d53414fd3c446548734a13b036d35c611`.
|
- source HEAD `61a41d1f08239b5f9e99cfae42a206be250416af` · tree
|
||||||
- API `sha256:52e0e816…8b2d` / Web `sha256:6fdbb646…f215` exact image가 실행 중이고 previous
|
`87dec55daf0a22ca5c2e489cd803a2f731e63b77` · 2회 동일 active archive
|
||||||
`79ec731f…0450` / `c530213f…62f28`도 보존돼 있다. Compose `api/web/db/proxy` 4개는 모두 running,
|
`4d15d055d74f33f1fa2ff470afbf900d565ab238b19c79df34ca7ba240119d4d`.
|
||||||
restart policy는 `unless-stopped`다.
|
- API `sha256:d502195021beafe869c555f3ec80269426062ef4586dc15a3e049f9c8660e4b1` / Web
|
||||||
- DB volume `vignette-preview-20260807_pgdata` · pre-proof dump `vignette-preview-g8-preproof-20260807T110610Z.dump`
|
`sha256:9796c0925f6a984cb846b61a4f13fda4d403bda7b3a2b6232b728cebbcdc4b36` exact image가 실행 중이다.
|
||||||
SHA256 `92ed4736…1570f`, 960,970 bytes · pre-clean-head dump
|
- candidate session E2E **112/112**, 실제 NAS 평문 origin browser review **112/112**(11 specs,
|
||||||
`vignette-preview-pre-clean-head-20260809T051611Z.dump` SHA256 `726982…5d41c`, 1,015,222 bytes.
|
desktop/mobile/single-run), health `status=ok·db=true·engine=true`, auth 401, OpenAPI 126.
|
||||||
- health 3/3(`db=true`, `engine=true`), auth 401, OpenAPI 126 + G1~G8 route, root/JS/CSS 200이다.
|
- fresh custom dump `vignette-preview-pre-clean-head-retry-61a41d1f-20260809T112038Z.dump`, SHA256
|
||||||
- helper container 0, NAS listener 8018/18149 0, Windows tunnel listener 18018 0이다.
|
`36ec8748cf28c520d9489d108dee9d4a154e6260f6996b4da3e0fe3b8a624db8`, 1,097,100 bytes,
|
||||||
- **P1 권한 관측:** remote release root와 Compose 파일이 mode `0777`이다. 다음 execute 전에 owner/mode를
|
TOC 1,738 / TABLE DATA 129를 보존했다.
|
||||||
최소권한으로 좁히고 release agent preflight가 이를 fail-closed로 검증해야 한다. 현재 상태를 안전한 배포 신뢰
|
- previous active `6030a677af7e87cbfabc422b553d108d53414fd3c446548734a13b036d35c611`과 이전 images,
|
||||||
경계로 간주하지 않는다.
|
named DB volume을 삭제하지 않았다.
|
||||||
- localhost candidate browser E2E 108/108(634.578s), postdeploy SSE→DB review PASS
|
|
||||||
- **G8 rollback/restore 이후 재검증:** 실제 브라우저 SSE→DB review 재실행 PASS(35.0s)
|
|
||||||
|
|
||||||
배포 중 첫 108 gate가 103/5로 fail-closed된 이력(G5 fixture drift, StrictMode retry fixture, `shell.css` manifest 누락)은
|
배포 중 첫 108 gate가 103/5로 fail-closed된 이력(G5 fixture drift, StrictMode retry fixture, `shell.css` manifest 누락)은
|
||||||
지우지 않는다.
|
지우지 않는다.
|
||||||
|
|
||||||
### ⚠ 배포된 SHA는 비-secure origin 결함을 갖고 있다 (수정은 소스에만 있음)
|
### 과거 비-secure origin 결함 — 수정·배포 검증 완료
|
||||||
|
|
||||||
배포 후 NAS 프리뷰(`http://100.116.83.60:8088`, 평문 HTTP·비-localhost)에 전체 회기 스펙을 돌려보니 24건이
|
배포 후 NAS 프리뷰(`http://100.116.83.60:8088`, 평문 HTTP·비-localhost)에 전체 회기 스펙을 돌려보니 24건이
|
||||||
실패했고, 원인은 단 하나였다. 페이지 스냅샷의 실제 예외는 `crypto.randomUUID is not a function`이다.
|
실패했고, 원인은 단 하나였다. 페이지 스냅샷의 실제 예외는 `crypto.randomUUID is not a function`이다.
|
||||||
|
|
@ -250,14 +241,13 @@ UI가 active DB container에 직접 stop을 보내 같은 증상이 재발했다
|
||||||
- 수정: `apps/web/src/lib/uuid.ts`의 `randomUuid()`로 통일. fallback도 `crypto.getRandomValues`를 우선
|
- 수정: `apps/web/src/lib/uuid.ts`의 `randomUuid()`로 통일. fallback도 `crypto.getRandomValues`를 우선
|
||||||
사용해 idempotency key의 예측 불가능성을 유지하고, Web Crypto가 아예 없을 때만 `Math.random`으로 내려간다.
|
사용해 idempotency key의 예측 불가능성을 유지하고, Web Crypto가 아예 없을 때만 `Math.random`으로 내려간다.
|
||||||
- 회귀: `apps/web/e2e/insecure-context-uuid.spec.ts` **6/6** (직접 호출 0건 검사 포함), typecheck·build 통과.
|
- 회귀: `apps/web/e2e/insecure-context-uuid.spec.ts` **6/6** (직접 호출 0건 검사 포함), typecheck·build 통과.
|
||||||
- **아직 NAS에 배포하지 않았다.** 배포된 SHA `6030a677…c611`은 여전히 결함 빌드다. 다음 배포 때
|
- UUID와 portable SHA-256 수정은 archive `4d15d055…119d4d`로 NAS에 배포했고 actual-origin 112/112를 통과했다.
|
||||||
release agent로 승격하고, 승격 뒤에는 NAS origin에서 회기 스펙을 다시 돌려 24건이 사라지는지 확인한다.
|
|
||||||
- 교훈: candidate gate를 localhost에서만 돌리면 secure-context 전용 API 결함을 못 잡는다. 배포 대상과
|
- 교훈: candidate gate를 localhost에서만 돌리면 secure-context 전용 API 결함을 못 잡는다. 배포 대상과
|
||||||
같은 scheme/host 형태에서 최소 한 번은 회기 리뷰 라우트를 열어봐야 한다.
|
같은 scheme/host 형태에서 최소 한 번은 회기 리뷰 라우트를 열어봐야 한다.
|
||||||
|
|
||||||
## 6. 다음 실행 순서 — G8 runtime 재검증 후 G7 외부 종료
|
## 6. 다음 실행 순서 — G8 종료, G7 외부 종료
|
||||||
|
|
||||||
### 6.1 G8: clean-head 릴리스 경로부터 완성
|
### 6.1 G8: clean-head 릴리스 경로 — DONE
|
||||||
|
|
||||||
`scripts/run-outcome-os-release-agent.py`의 명시적 clean-head 경로는 구현됐다. 기존 patch mode를 기본값으로
|
`scripts/run-outcome-os-release-agent.py`의 명시적 clean-head 경로는 구현됐다. 기존 patch mode를 기본값으로
|
||||||
보존하며, tracked-clean source worktree의 exact HEAD/tree/archive만 candidate로 사용한다. 수동 복사나 NAS 직접
|
보존하며, tracked-clean source worktree의 exact HEAD/tree/archive만 candidate로 사용한다. 수동 복사나 NAS 직접
|
||||||
|
|
@ -275,18 +265,16 @@ UI가 active DB container에 직접 stop을 보내 같은 증상이 재발했다
|
||||||
production candidate/NAS-origin은 실제 배포 번들에서 동작하는 11개 회기 스펙만 실행한다.
|
production candidate/NAS-origin은 실제 배포 번들에서 동작하는 11개 회기 스펙만 실행한다.
|
||||||
7. unit/fake runner가 tracked dirty, archive SHA drift, evidence binding, patch-mode 무회귀를 검증해야 한다.
|
7. unit/fake runner가 tracked dirty, archive SHA drift, evidence binding, patch-mode 무회귀를 검증해야 한다.
|
||||||
|
|
||||||
검증: release-agent unit 30/30, source-only insecure-context gate 12/12, Ruff, py_compile, `git diff --check` PASS. 공유 worktree에는
|
검증: release-agent unit 30/30, source-only insecure-context gate 12/12, Ruff, py_compile, `git diff --check` PASS.
|
||||||
인계 문서와 controller 변경이 있으므로 이 경로 자체가 의도대로 tracked-dirty를 차단한다. 실제 배포는 새 commit을
|
최종 실행은 detached clean HEAD `61a41d1f…6af`에서 candidate 112/112와 actual NAS-origin 112/112를 통과했다.
|
||||||
가리키는 별도 detached clean worktree를 만들고 `--source-repo-root <clean-worktree>`로 지정한 뒤
|
이하 preflight와 실패 기록은 최종 성공에 이르기까지의 역사 증거이며 다시 실행할 지시가 아니다.
|
||||||
dry-run→execute 순서로 수행한다.
|
|
||||||
|
|
||||||
#### clean commit preflight — include 52 / exclude 6
|
#### 역사 기록: 첫 clean commit preflight — include 52 / exclude 6
|
||||||
|
|
||||||
2026-08-09 최종 경로 목록 SHA-256은 ordinal 정렬된 UTF-8 LF 경로를 줄바꿈으로 연결하고 마지막 개행 없이 계산해
|
2026-08-09 최종 경로 목록 SHA-256은 ordinal 정렬된 UTF-8 LF 경로를 줄바꿈으로 연결하고 마지막 개행 없이 계산해
|
||||||
include `4d94694ccf9def6945272c61f673cac0eaa9fb59f4f9b6c165d661d819f891a1`, exclude
|
include `4d94694ccf9def6945272c61f673cac0eaa9fb59f4f9b6c165d661d819f891a1`, exclude
|
||||||
`1ff8c26732772df6b5857bf84de01a53d7da9c3b7d52df08938e77f3a527f022`다. 아래 include 52개만
|
`1ff8c26732772df6b5857bf84de01a53d7da9c3b7d52df08938e77f3a527f022`였다. 아래 목록은 첫 clean commit 범위를
|
||||||
사용자 승인 후 **명시적으로** stage한다. 그 직전 `git status --porcelain=v1 -uall`로 목록·해시를 다시 계산하고,
|
감사하기 위해 보존한 이력이며 현재 worktree staging 목록이 아니다.
|
||||||
새 경로가 하나라도 있으면 멈춘다. `git add -A`는 금지한다.
|
|
||||||
|
|
||||||
```text
|
```text
|
||||||
apps/api/app/config.py
|
apps/api/app/config.py
|
||||||
|
|
@ -410,15 +398,15 @@ apps/web/test-results/g7-voice-consent-mobile.png
|
||||||
- **테스트 강제 클릭이나 모달 닫기 우회는 사용하지 않았다.** 첫 clean source `5221f79e…1c69`에는 제품 수정과
|
- **테스트 강제 클릭이나 모달 닫기 우회는 사용하지 않았다.** 첫 clean source `5221f79e…1c69`에는 제품 수정과
|
||||||
controller 변경이 포함됐고, production entry 차단 회귀 2건의 테스트 수정만 후속 clean commit으로 다시 고정한다.
|
controller 변경이 포함됐고, production entry 차단 회귀 2건의 테스트 수정만 후속 clean commit으로 다시 고정한다.
|
||||||
|
|
||||||
### 6.2 G8: NAS preview 배포와 실제 origin E2E
|
### 6.2 G8: NAS preview 배포와 실제 origin E2E — DONE
|
||||||
|
|
||||||
clean-head release mode가 GREEN이면 전용 NAS preview만 대상으로 백업→dry-run→배포→검증한다. 공개 DB·공개 API는
|
전용 NAS preview만 대상으로 백업→dry-run→배포→검증했고 공개 DB·공개 API는 건드리지 않았다. 아래 성공 조건을
|
||||||
이 단계에서 건드리지 않는다. 성공 조건은 다음 전부다.
|
전부 충족했다.
|
||||||
|
|
||||||
직전 새 backup은
|
최종 fresh backup은
|
||||||
`/volume1/docker/vignette-preview-20260807/backups/vignette-preview-pre-clean-head-20260809T051611Z.dump`,
|
`/volume1/docker/vignette-preview-20260807/backups/vignette-preview-pre-clean-head-retry-61a41d1f-20260809T112038Z.dump`,
|
||||||
SHA-256 `72698232d132a8f848d81ff9d8602430e7e623c7646cc236cb39f978aab5d41c`,
|
SHA-256 `36ec8748cf28c520d9489d108dee9d4a154e6260f6996b4da3e0fe3b8a624db8`,
|
||||||
1,015,222 bytes, TOC 1,752다. 하지만 release agent 자체에는 `pg_dump`/`pg_restore` 실행 계약이 없다.
|
1,097,100 bytes, TOC 1,738 / TABLE DATA 129다. release agent 자체에는 `pg_dump`/`pg_restore` 실행 계약이 없다.
|
||||||
따라서 **매 execute 직전** 별도 승인된 백업 단계에서 fresh custom dump를 만들고 SHA-256·byte size·TOC parse/count와
|
따라서 **매 execute 직전** 별도 승인된 백업 단계에서 fresh custom dump를 만들고 SHA-256·byte size·TOC parse/count와
|
||||||
current DB identity를 증거에 결속한 뒤에만 승격한다. 기존 dump 존재만으로 이 단계를 생략하지 않는다.
|
current DB identity를 증거에 결속한 뒤에만 승격한다. 기존 dump 존재만으로 이 단계를 생략하지 않는다.
|
||||||
|
|
||||||
|
|
@ -426,12 +414,13 @@ release agent의 자동 rollback 범위는 exact API/Web image, Compose 적용
|
||||||
DB migration이나 데이터 변경은 자동 복구하지 않는다. migration/data restore는 별도 owner 승인·대상 DB identity·dump
|
DB migration이나 데이터 변경은 자동 복구하지 않는다. migration/data restore는 별도 owner 승인·대상 DB identity·dump
|
||||||
hash를 갖춘 복원 절차로만 수행한다. 이미지 rollback 성공을 DB rollback 성공으로 기록하면 안 된다.
|
hash를 갖춘 복원 절차로만 수행한다. 이미지 rollback 성공을 DB rollback 성공으로 기록하면 안 된다.
|
||||||
|
|
||||||
- 새로운 active SHA와 exact API/Web image ID, predeploy dump SHA, previous image 보존.
|
- active archive `4d15d055…119d4d`, exact API/Web `d5021950…e4b1`/`9796c092…4b36`, fresh dump SHA,
|
||||||
- health 3/3, db/engine true, auth 401, OpenAPI와 G0~G8 route 존재.
|
previous `6030a677…c611` 보존.
|
||||||
- `http://100.116.83.60:8088` **실제 평문 NAS origin**에서 §8의 회기 E2E 묶음 0 failure.
|
- health ok, db/engine true, auth 401, OpenAPI 126.
|
||||||
|
- `http://100.116.83.60:8088` **실제 평문 NAS origin**에서 11-spec browser review 112/112.
|
||||||
- `crypto.randomUUID` 예외 0, error boundary 0, SSE→DB review PASS.
|
- `crypto.randomUUID` 예외 0, error boundary 0, SSE→DB review PASS.
|
||||||
- 학생 홈→회기→리뷰→G4/G5 반환 연습의 desktop/mobile·DB read reload·멱등·UUID 비노출 유지.
|
- 학생 홈→회기→리뷰→G4/G5 반환 연습의 desktop/mobile·DB read reload·멱등·UUID 비노출 유지.
|
||||||
- 실패 시 자동 rollback과 previous image 복구를 검증하고, 성공 전에는 G8 카드 전체를 DONE으로 바꾸지 않는다.
|
- 앞선 실패 시 자동 rollback과 previous image 복구를 검증했고 최종 성공 뒤 G8 카드를 DONE으로 바꿨다.
|
||||||
|
|
||||||
### 6.3 G7: 외부 종료 Gate
|
### 6.3 G7: 외부 종료 Gate
|
||||||
|
|
||||||
|
|
@ -694,7 +683,7 @@ node.exe .\node_modules\@playwright\test\cli.js test e2e/session-layout.spec.ts
|
||||||
--project=chromium-desktop --project=chromium-mobile --project=chromium-single-run --workers=1 --reporter=line
|
--project=chromium-desktop --project=chromium-mobile --project=chromium-single-run --workers=1 --reporter=line
|
||||||
```
|
```
|
||||||
|
|
||||||
전체 Playwright inventory는 620 tests / 44 files다. 실행 환경/API/DB를 정확히 맞추지 않고 fixture failure를
|
전체 Playwright inventory는 622 tests / 44 files다. 실행 환경/API/DB를 정확히 맞추지 않고 fixture failure를
|
||||||
제품 failure로 오인하지 않는다. 같은 blocker가 두 번 반복되면 전체 재시도 대신 원인·증거·수정 계획을 먼저 보고한다.
|
제품 failure로 오인하지 않는다. 같은 blocker가 두 번 반복되면 전체 재시도 대신 원인·증거·수정 계획을 먼저 보고한다.
|
||||||
|
|
||||||
## 9. 핵심 변경 파일
|
## 9. 핵심 변경 파일
|
||||||
|
|
@ -738,9 +727,9 @@ node.exe .\node_modules\@playwright\test\cli.js test e2e/session-layout.spec.ts
|
||||||
대체 불가능한 동의·인증 세션·실제 참가자/평가자 승인만 요청한다. 동시 작업은 최대 세 하위 lane으로 나누되,
|
대체 불가능한 동의·인증 세션·실제 참가자/평가자 승인만 요청한다. 동시 작업은 최대 세 하위 lane으로 나누되,
|
||||||
NAS/public/DB mutation owner는 항상 한 에이전트만 둔다.
|
NAS/public/DB mutation owner는 항상 한 에이전트만 둔다.
|
||||||
|
|
||||||
1. **G8 release lane:** clean-head release mode 구현·unit·dry-run·NAS preview 배포·NAS-origin E2E. 이 lane만 NAS mutation.
|
1. **G7 proof lane:** current public route/config 감사, local provider readiness, `--rehearse`, 네 artifact 수집 준비.
|
||||||
2. **G7 proof lane:** current public route/config 감사, local provider readiness, `--rehearse`, 네 artifact 수집 준비.
|
실제 마이크는 사용자 명시 동의 전 0회, public mutation은 단일 owner에게 인계.
|
||||||
실제 마이크는 사용자 명시 동의 전 0회, public mutation은 G8 lane과 시간 겹치지 않게 단일 owner에게 인계.
|
2. **G8/NAS monitor lane:** 완료된 archive·images·health·자동화 drift를 읽기 전용으로 감시. 새 material milestone 전 mutation 0.
|
||||||
3. **E2E/SSOT lane:** local fixture, disposable DB browser, visual/accessibility, dashboard/TODO/backlog 정합을 읽기 전용으로 감사.
|
3. **E2E/SSOT lane:** local fixture, disposable DB browser, visual/accessibility, dashboard/TODO/backlog 정합을 읽기 전용으로 감사.
|
||||||
|
|
||||||
루트 에이전트는 각 lane의 증거 SHA와 실패 원인을 합쳐 게이트를 판정한다. 같은 blocker가 두 번 반복되면 세 번째
|
루트 에이전트는 각 lane의 증거 SHA와 실패 원인을 합쳐 게이트를 판정한다. 같은 blocker가 두 번 반복되면 세 번째
|
||||||
|
|
@ -752,17 +741,13 @@ NAS/public/DB mutation owner는 항상 한 에이전트만 둔다.
|
||||||
D:\workspace\vignette의 docs/HANDOFF.md를 인수인계 SSOT로 읽고, AGENTS.md → docs/README.md →
|
D:\workspace\vignette의 docs/HANDOFF.md를 인수인계 SSOT로 읽고, AGENTS.md → docs/README.md →
|
||||||
docs/dev_dashboard.html → docs/TODO.md → docs/ops/backlog-2026-06-26.md 순서로 현재 상태를 재확인해.
|
docs/dev_dashboard.html → docs/TODO.md → docs/ops/backlog-2026-06-26.md 순서로 현재 상태를 재확인해.
|
||||||
|
|
||||||
현재 master는 HEAD 94c681d…7fb이고 origin/master보다 2 commit 앞서며, timestamp·voice provider/model,
|
G8 배포 source는 HEAD `61a41d1f…6af`·tree `87dec55d…3b77`·archive `4d15d055…119d4d`다. 격리 NAS의
|
||||||
G7 topology·public launcher·G8 clean-head·SSOT가
|
API/Web `d5021950…e4b1`/`9796c092…4b36`, candidate 112/112, actual NAS-origin 112/112, health·auth 401·OpenAPI
|
||||||
아직 tracked dirty다. 기존 D:\workspace\vignette-clean-head-94c681d와 g8-focus-head-94c681d.tar는 이 최신 변경을
|
126을 통과했고 fresh dump `36ec8748…24db8`과 previous `6030a677…c611`을 보존했다. G8은 DONE이며 새 material
|
||||||
포함하지 않으므로 배포 입력으로 사용하지 마. 먼저 git status/diff와 각 lane의 focused test를 재확인해.
|
milestone 전에는 NAS를 mutation하지 마. 현재 공유 worktree의 후속 dirty 변경은 이 배포 source와 분리해서 다뤄.
|
||||||
|
|
||||||
최종 목표는 G0~G8 아홉 목표를 코드-only가 아니라 실제 runtime 증거로 모두 닫는 것이다. G0~G6은 현재 내부 DONE
|
최종 목표는 G0~G8 아홉 목표를 코드-only가 아니라 실제 runtime 증거로 모두 닫는 것이다. G0~G6과 G8은 DONE이다.
|
||||||
증거를 보존하되 새 clean HEAD 전체 회귀로 재확인해. G8 rollback receipt와 clean-head controller는 구현됐지만
|
G7은 내부 구현만 DONE이다. current source 공개 배포,
|
||||||
2026-08-09 execute 두 번은 모두 NAS mutation 전에 fail-closed됐다. docs/HANDOFF.md §6.1의 naive
|
|
||||||
session timestamp 버그는 UTC/KST 계약과 focused disposable DB/API/browser E2E로 GREEN이다. 테스트 강제 클릭 우회는
|
|
||||||
없다. 사용자 승인을 받아 이 변경을 새 clean commit으로 만들고 HEAD/tree/archive를 다시 결속해 격리 NAS preview에
|
|
||||||
배포한 다음 실제 NAS HTTP origin의 전체 회기 E2E를 0 failure로 통과시켜. G7은 내부 구현만 DONE이다. current source 공개 배포,
|
|
||||||
local_whisper/melotts ready, authenticated public WSS, 사용자의 명시 동의를 받은 물리 마이크 50분 soak,
|
local_whisper/melotts ready, authenticated public WSS, 사용자의 명시 동의를 받은 물리 마이크 50분 soak,
|
||||||
같은 시간창의 runtime/topology high-water, 독립 human voice-gain pack까지 모아 canonical checker exit 0을 만들어야 한다.
|
같은 시간창의 runtime/topology high-water, 독립 human voice-gain pack까지 모아 canonical checker exit 0을 만들어야 한다.
|
||||||
G7 runner exit의 checker exit 0/gate_closed 결속, browser Origin과 API/WSS host 분리, 3,120초 capture와
|
G7 runner exit의 checker exit 0/gate_closed 결속, browser Origin과 API/WSS host 분리, 3,120초 capture와
|
||||||
|
|
@ -779,12 +764,6 @@ worktree와 legacy 인자를 가리키며, 5분 watchdog은 최신 실행에서
|
||||||
현재 health ok지만 자동복구는 의도적으로 fail-closed다. clean commit 뒤 동일 stable release root로 두 task를
|
현재 health ok지만 자동복구는 의도적으로 fail-closed다. clean commit 뒤 동일 stable release root로 두 task를
|
||||||
재등록하고 action pin·watchdog result 0·로그온/reboot smoke를 증명한 뒤에만 runtime DONE으로 바꿔.
|
재등록하고 action pin·watchdog result 0·로그온/reboot smoke를 증명한 뒤에만 runtime DONE으로 바꿔.
|
||||||
|
|
||||||
NAS 17:07 기준선은 active 6030a677…c611, API/Web 52e0…8b2d/6fdb…f215, previous images 보존,
|
|
||||||
Compose 4개 running+unless-stopped, volume vignette-preview-20260807_pgdata, health/routes/assets GREEN, helper/listener 0이다.
|
|
||||||
release agent는 DB dump/restore를 지원하지 않으므로 execute 직전 fresh custom dump SHA/size/TOC와 DB identity를
|
|
||||||
별도로 결속해. 자동 rollback은 image/Compose/active-state뿐이고 migration/data restore는 별도 owner 승인이야.
|
|
||||||
remote release root/Compose mode 0777 P1도 최소권한 preflight로 닫기 전에는 execute하지 마.
|
|
||||||
|
|
||||||
학생 자기주도 폐루프는 홈 추천→회기→리뷰→G4 처방/G5 전이→별도 재연습→completed-session observer→
|
학생 자기주도 폐루프는 홈 추천→회기→리뷰→G4 처방/G5 전이→별도 재연습→completed-session observer→
|
||||||
actual attempt/execution→read-model reload→before/after·멱등까지 desktop/mobile 실제 DB/browser로 검증해.
|
actual attempt/execution→read-model reload→before/after·멱등까지 desktop/mobile 실제 DB/browser로 검증해.
|
||||||
최종 검증은 API full, gateway full, schema/provenance, Web API types/typecheck/build, fixture E2E, disposable DB E2E,
|
최종 검증은 API full, gateway full, schema/provenance, Web API types/typecheck/build, fixture E2E, disposable DB E2E,
|
||||||
|
|
|
||||||
40
docs/TODO.md
40
docs/TODO.md
|
|
@ -44,9 +44,9 @@
|
||||||
`RuptureRepairCard.tsx`는 렌더 시점(`useRef`)에 호출해 화면 전체가 죽었다. 릴리스 게이트는
|
`RuptureRepairCard.tsx`는 렌더 시점(`useRef`)에 호출해 화면 전체가 죽었다. 릴리스 게이트는
|
||||||
localhost 후보 스택(secure context)에서만 돌아 이 경로를 밟은 적이 없다. `src/lib/uuid.ts`의
|
localhost 후보 스택(secure context)에서만 돌아 이 경로를 밟은 적이 없다. `src/lib/uuid.ts`의
|
||||||
`randomUuid()`로 통일했고 fallback도 `crypto.getRandomValues`를 우선 사용해 idempotency key의
|
`randomUuid()`로 통일했고 fallback도 `crypto.getRandomValues`를 우선 사용해 idempotency key의
|
||||||
예측 불가능성을 유지한다. 회귀는 `e2e/insecure-context-uuid.spec.ts` 6/6으로 고정했다(직접 호출
|
예측 불가능성을 유지한다. 문장 fingerprint의 `crypto.subtle.digest` 의존도 portable SHA-256 fallback으로
|
||||||
0건 검사 포함). **주의: 이 수정은 아직 NAS 프리뷰에 배포되지 않았다** — 배포된 SHA
|
교체했다. source-only UUID/SHA 회귀 12/12와 secure/insecure exact digest 회귀를 고정했고, 최종 archive
|
||||||
`6030a677…c611`은 여전히 결함이 있는 빌드다.
|
`4d15d055…119d4d`의 실제 NAS 평문 origin browser review 112/112로 배포 검증까지 닫았다.
|
||||||
- [x] **워치독 engine readiness 사각지대** — 2026-08-07 22:0x KST 소유자 승인으로 9099 재기동과
|
- [x] **워치독 engine readiness 사각지대** — 2026-08-07 22:0x KST 소유자 승인으로 9099 재기동과
|
||||||
판정 기준 교정을 모두 마쳤다. 복구 실측: 9099 `/ready?force=true` 200 `ok:true, detail:OK`,
|
판정 기준 교정을 모두 마쳤다. 복구 실측: 9099 `/ready?force=true` 200 `ok:true, detail:OK`,
|
||||||
API 8001과 공개 `api-vignette.chanpaca.net` 모두 `status:ok, engine:true, engine_detail:OK`.
|
API 8001과 공개 `api-vignette.chanpaca.net` 모두 `status:ok, engine:true, engine_detail:OK`.
|
||||||
|
|
@ -132,18 +132,18 @@
|
||||||
fail-closed 경계(동의 없음·pack 없음·3,120초 미만·host/Origin/scheme 불일치)는 CLI로 실증했다. 상세:
|
fail-closed 경계(동의 없음·pack 없음·3,120초 미만·host/Origin/scheme 불일치)는 CLI로 실증했다. 상세:
|
||||||
`ops/outcome-os-g7-external-proof-readiness-2026-08-07.md`.
|
`ops/outcome-os-g7-external-proof-readiness-2026-08-07.md`.
|
||||||
|
|
||||||
> **격리 NAS current baseline / G8 `runtime REVALIDATION`:** 2026-08-09 17:07 KST 프리뷰
|
> **격리 NAS current baseline / G8 `DONE`:** 2026-08-09 clean-head 프리뷰
|
||||||
> `http://100.116.83.60:8088`, project `vignette-preview-20260807`은 active SHA `6030a677…c611`, exact
|
> `http://100.116.83.60:8088`, project `vignette-preview-20260807`은 source HEAD `61a41d1f…6af`·tree
|
||||||
> API/Web `52e0…8b2d`/`6fdb…f215`로 실행 중이며 previous `79ec…4450`/`c530…2f28`도 보존한다.
|
> `87dec55d…3b77`의 active archive `4d15d055…119d4d`, exact API/Web `d5021950…e4b1`/
|
||||||
|
> `9796c092…4b36`로 실행 중이며 previous active `6030a677…c611`과 이전 images도 보존한다.
|
||||||
> `api/web/db/proxy` 4개는 running + `unless-stopped`, volume은 `vignette-preview-20260807_pgdata`다.
|
> `api/web/db/proxy` 4개는 running + `unless-stopped`, volume은 `vignette-preview-20260807_pgdata`다.
|
||||||
> health 3/3·db/engine true·auth 401·OpenAPI 126 + G1~G8 routes·root/JS/CSS 200, helper/listener는 모두 0이다.
|
> candidate session E2E 112/112와 실제 NAS-origin browser review 112/112(11 specs, desktop/mobile/single-run),
|
||||||
> 보존 dump는 `92ed…1570f` 960,970 bytes와 `726982…5d41c` 1,015,222 bytes다. release agent는 DB
|
> health ok·db/engine true·auth 401·OpenAPI 126을 통과했다. fresh dump `36ec8748…24db8`은 1,097,100 bytes,
|
||||||
|
> TOC 1,738 / TABLE DATA 129다. release agent는 DB
|
||||||
> dump/restore를 수행하지 않으므로 매 execute 직전 fresh custom dump SHA/size/TOC와 DB identity를 별도 결속한다.
|
> dump/restore를 수행하지 않으므로 매 execute 직전 fresh custom dump SHA/size/TOC와 DB identity를 별도 결속한다.
|
||||||
> 자동 rollback은 image/Compose/active-state만 복구하며 적용 migration/data restore는 별도 owner 승인이 필요하다.
|
> 자동 rollback은 image/Compose/active-state만 복구하며 적용 migration/data restore는 별도 owner 승인이 필요하다.
|
||||||
> remote release root/Compose mode `0777`은 P1 권한 위험이므로 execute 전 최소권한·preflight를 닫는다. localhost
|
> 과거 `6030a677…c611`의 localhost 108/108·평문 origin UUID 24건 실패와 후속 verified rollback은 이력으로 보존한다.
|
||||||
> candidate 108/108과 postdeploy SSE→DB review는 과거 증거이고, 같은 SHA의 평문 origin은 UUID 결함 24건 실패라
|
> 매일 04:30 KST `vignette-e2e` 자동화는 ACTIVE다.
|
||||||
> current source의 완료 증거가 아니다. `randomUuid()` 수정 배포와 NAS-origin 전체 회기·SSE→DB·G4/G5
|
|
||||||
> returned-practice 0 failure 전에는 G8을 DONE으로 바꾸지 않는다. 매일 04:30 KST `vignette-e2e` 자동화는 ACTIVE다.
|
|
||||||
> 증거: [배포 증거](./ops/nas-preview-deployment-evidence-2026-08-07.md), [기계 판독 증거](./ops/evidence/nas-preview-current-deploy-2026-08-07.json), [브라우저 증거](./ops/evidence/nas-preview-live-turn-2026-08-07.png). 이는 G7 물리 마이크·독립 사람 평가 증거를 대체하지 않는다.
|
> 증거: [배포 증거](./ops/nas-preview-deployment-evidence-2026-08-07.md), [기계 판독 증거](./ops/evidence/nas-preview-current-deploy-2026-08-07.json), [브라우저 증거](./ops/evidence/nas-preview-live-turn-2026-08-07.png). 이는 G7 물리 마이크·독립 사람 평가 증거를 대체하지 않는다.
|
||||||
- [ ] **claude_cli ↔ Anthropic API live 동일성** — provider 라우팅·모델 탐색·설정 저장 경로는 구현 완료. 남은 게이트는 연구팀/기관 `ANTHROPIC_API_KEY`를 게이트웨이 호스트에 주입한 뒤 같은 프롬프트의 live 응답·계량·오류 표면화를 비교하는 것이다.
|
- [ ] **claude_cli ↔ Anthropic API live 동일성** — provider 라우팅·모델 탐색·설정 저장 경로는 구현 완료. 남은 게이트는 연구팀/기관 `ANTHROPIC_API_KEY`를 게이트웨이 호스트에 주입한 뒤 같은 프롬프트의 live 응답·계량·오류 표면화를 비교하는 것이다.
|
||||||
|
|
||||||
|
|
@ -497,7 +497,7 @@
|
||||||
증거 binding, source-only insecure-context 12/12 gate(UUID 6 + SHA-256 6), commit 전 NAS-origin 11-spec rollback gate를 unit
|
증거 binding, source-only insecure-context 12/12 gate(UUID 6 + SHA-256 6), commit 전 NAS-origin 11-spec rollback gate를 unit
|
||||||
30/30·Ruff·py_compile로 고정했다. 첫 execute는 archive CRLF를 candidate에서 정규화하도록 fail-closed했고,
|
30/30·Ruff·py_compile로 고정했다. 첫 execute는 archive CRLF를 candidate에서 정규화하도록 fail-closed했고,
|
||||||
두 번째 execute는 API/types/build 뒤 109/5에서 fail-closed해 둘 다 NAS mutation 0이다.
|
두 번째 execute는 API/types/build 뒤 109/5에서 fail-closed해 둘 다 NAS mutation 0이다.
|
||||||
- [ ] **G8 NAS-origin 재검증** — `randomUuid()`와 portable SHA-256 수정, insecure-context 12/12 회귀를 포함한 current HEAD를
|
- [x] **G8 NAS-origin 재검증** — `randomUuid()`와 portable SHA-256 수정, insecure-context 12/12 회귀를 포함한 current HEAD를
|
||||||
위 clean-head mode로 격리 NAS preview에 배포한다. `started_at`의 9시간 오차 P0는 UTC `+00:00`, KST 달력 날짜,
|
위 clean-head mode로 격리 NAS preview에 배포한다. `started_at`의 9시간 오차 P0는 UTC `+00:00`, KST 달력 날짜,
|
||||||
KST browser focused 실DB E2E로 수정했고 API 전체 921 passed와 코칭 이력 1/1을 통과했다. 시간종료 모달 강제
|
KST browser focused 실DB E2E로 수정했고 API 전체 921 passed와 코칭 이력 1/1을 통과했다. 시간종료 모달 강제
|
||||||
닫기 우회는 없다. 첫 clean commit `5221f79e` execute는 API/types/build와 candidate DB-backed 회기 후반까지
|
닫기 우회는 없다. 첫 clean commit `5221f79e` execute는 API/types/build와 candidate DB-backed 회기 후반까지
|
||||||
|
|
@ -505,11 +505,11 @@
|
||||||
mutation 없이 중단됐다. source와 production preview에서 수정 회귀 2/2씩 통과했다. 이 테스트 수정의 새 clean
|
mutation 없이 중단됐다. source와 production preview에서 수정 회귀 2/2씩 통과했다. 이 테스트 수정의 새 clean
|
||||||
commit `21461ab3` candidate 112/112 뒤 실제 NAS-origin G4 2건이 `crypto.subtle` 부재로 실패했고, release agent는
|
commit `21461ab3` candidate 112/112 뒤 실제 NAS-origin G4 2건이 `crypto.subtle` 부재로 실패했고, release agent는
|
||||||
이전 image·health·auth·OpenAPI를 검증해 rollback했다. 문장 원문을 보내지 않는 portable SHA-256 fallback은
|
이전 image·health·auth·OpenAPI를 검증해 rollback했다. 문장 원문을 보내지 않는 portable SHA-256 fallback은
|
||||||
secure localhost 2/2와 Tailnet insecure origin 2/2에서 동일 표준 digest를 통과했다. 이 수정의 새 clean
|
secure localhost 2/2와 Tailnet insecure origin 2/2에서 동일 표준 digest를 통과했다. 최종 clean source HEAD
|
||||||
commit·HEAD/tree/archive를 결속해 실제
|
`61a41d1f…6af`·tree `87dec55d…3b77`·2회 동일 archive `4d15d055…119d4d`를 NAS에 승격했고 candidate
|
||||||
`http://100.116.83.60:8088` origin에서 전체 회기
|
112/112와 실제 `http://100.116.83.60:8088` origin browser review 112/112(11 specs, desktop/mobile/single-run),
|
||||||
E2E 0 failure·SSE→DB review·student returned-practice desktop/mobile을 다시 검증하기 전에는 G8 전체를 DONE으로
|
health ok·db/engine true·auth 401·OpenAPI 126을 통과했다. API/Web는 `d5021950…e4b1`/`9796c092…4b36`, fresh dump는
|
||||||
올리지 않는다.
|
`36ec8748…24db8` 1,097,100 bytes·TOC 1,738/TABLE DATA 129이며 previous `6030a677…c611`을 보존했다.
|
||||||
|
|
||||||
### I-DAG. 첫 에이전트 실행팩 AOS-001~012
|
### I-DAG. 첫 에이전트 실행팩 AOS-001~012
|
||||||
|
|
||||||
|
|
@ -531,7 +531,7 @@
|
||||||
## 지금 임계경로 (한 줄)
|
## 지금 임계경로 (한 줄)
|
||||||
납품(공개 데모)의 핵심 블로커는 **A(공개 `/turn` proof + 배포 secrets)**. 그 다음이 **B(한신대 거버넌스)**와
|
납품(공개 데모)의 핵심 블로커는 **A(공개 `/turn` proof + 배포 secrets)**. 그 다음이 **B(한신대 거버넌스)**와
|
||||||
**C(Phase 3 파일럿)** — C는 2026-07-13 회의로 **7월 말~8월 초 파일럿 일정 확정**, 그 전제로 **H의 P1(회기 종료 시간 기반)**이
|
**C(Phase 3 파일럿)** — C는 2026-07-13 회의로 **7월 말~8월 초 파일럿 일정 확정**, 그 전제로 **H의 P1(회기 종료 시간 기반)**이
|
||||||
새 구현 게이트다. **I(Outcome & Alliance OS)는 현재 감사 기준 G0~G6이 internal DONE이다.
|
새 구현 게이트다. **I(Outcome & Alliance OS)는 현재 감사 기준 G0~G6과 G8이 DONE이다.
|
||||||
G8 receipt-bound 실제 NAS rollback은 완료됐고 UUID 수정 배포·NAS-origin 회기 재검증이 남았다. G7은 내부 구현 DONE과 외부 proof GATE를 분리하며,
|
G8은 receipt-bound 실제 rollback과 current clean source NAS-origin 112/112를 완료했다. G7은 내부 구현 DONE과 외부 proof GATE를 분리하며,
|
||||||
current source 공개 배포·`local_whisper`/`melotts` ready·auth public WSS·명시 동의 mic·50분 soak·독립 라벨 benchmark·운영 resource high-water가 남았다.** D~G는
|
current source 공개 배포·`local_whisper`/`melotts` ready·auth public WSS·명시 동의 mic·50분 soak·독립 라벨 benchmark·운영 resource high-water가 남았다.** D~G는
|
||||||
품질·확장 작업으로 게이트가 아니다. 상세 상태·검증 증거는 SSOT 대시보드가 소유한다.
|
품질·확장 작업으로 게이트가 아니다. 상세 상태·검증 증거는 SSOT 대시보드가 소유한다.
|
||||||
|
|
|
||||||
|
|
@ -290,15 +290,15 @@
|
||||||
<section class="pulse">
|
<section class="pulse">
|
||||||
<div class="pulse-main">
|
<div class="pulse-main">
|
||||||
<span class="eyebrow">project pulse</span>
|
<span class="eyebrow">project pulse</span>
|
||||||
<h1>지금 위치: <em>G0~G6 internal DONE</em>.<br><em>G8 runtime 재검증</em>과 <em>G7 external proof</em>를 닫는 중.</h1>
|
<h1>지금 위치: <em>G0~G6 · G8 DONE</em>.<br><em>G7 external proof</em>를 닫는 중.</h1>
|
||||||
<p class="pulse-state"><b>현재:</b> 18:02 KST Docker Desktop UI의 직접 DB stop을 같은 container·recovered named volume 재시작으로 복구했고 public DB·engine은 다시 정상이다. rollback receipt 2건도 보존했다. current fixture는 불변 110 + 최신 dashboard 10 분할 GREEN이고 자기주도 desktop/mobile 6/6을 재확인했다. <b>다음:</b> clean commit에서 exact 120 재실행 → NAS-origin 0 failure → G7 공개 음성·물리 마이크·독립 사람 증거 순서로 진행한다.</p>
|
<p class="pulse-state"><b>현재:</b> G8 clean-head는 source HEAD <code>61a41d1f…</code>·tree <code>87dec55d…</code>·2회 결정적 archive <code>4d15d055…119d4d</code>로 격리 NAS에 승격됐다. candidate 112/112와 실제 NAS 평문 origin browser review 112/112가 모두 GREEN이고 health·db·engine, auth 401, OpenAPI 126도 통과했다. <b>다음:</b> G7 current source 공개 배포 → 인증 WSS → 명시 동의 물리 마이크·운영 high-water·독립 사람 증거 순서로 진행한다.</p>
|
||||||
<details class="pulse-details">
|
<details class="pulse-details">
|
||||||
<summary>운영 복구·watchdog·배포 상세 보기</summary>
|
<summary>운영 복구·watchdog·배포 상세 보기</summary>
|
||||||
<p class="pulse-state">2026-08-07 18:26 KST 공개 DB를 <code>vignette_recovered_prod_20260807</code> named volume으로 전환했고 원본 container·dump·volume은 삭제하지 않았다. 기존 auth session은 복사하지 않아 사용자는 Google 재로그인이 필요하다. 2026-08-09 18:02:55 KST Docker Desktop UI가 active DB container에 직접 stop을 보내 public <code>db=false</code>가 재발했지만 같은 container ID와 recovered volume만 18:05 KST 재시작했다. 재기동 뒤 owner read-only 집계는 users 356·sessions 193·turns 726, Google users 16·Google-owned sessions 31이고 public health는 다시 <code>status=ok·db=true·engine=true</code>다. public task source pin 코드는 완료됐다. watchdog·로그온 boot는 detached clean release와 exact Git commit/tree·watchdog|boot/start SHA가 없으면 health·Docker·process mutation 전에 실패하고 hidden VBS도 pinned task만 trigger한다(provenance 11개 포함 combined 22/22, PS5.1·WSH PASS). 다만 실제 두 Scheduled Task action은 아직 shared worktree라 watchdog은 필수 pin 인자 부재로 <code>LastTaskResult=1</code>이며 자동복구가 의도적으로 fail-closed다. clean commit 뒤 두 task를 동일 stable release root로 재등록하고 action pin·result 0·로그온/reboot smoke를 확인해야 한다. G8 NAS SHA <code>6030a677…c611</code>은 localhost candidate와 SSE→DB review는 통과했지만 평문 origin UUID 결함 빌드이므로 current clean source 재배포가 필요하다. G7 source는 runner/checker/topology 86/86, launcher/sidecar 80/80, 통합 166/166, API voice 71/71이지만 public은 아직 OpenAI provider·Git 없는 냉동 release라 external GATE다.</p>
|
<p class="pulse-state">2026-08-07 18:26 KST 공개 DB를 <code>vignette_recovered_prod_20260807</code> named volume으로 전환했고 원본 container·dump·volume은 삭제하지 않았다. 기존 auth session은 복사하지 않아 사용자는 Google 재로그인이 필요하다. 2026-08-09 18:02:55 KST Docker Desktop UI가 active DB container에 직접 stop을 보내 public <code>db=false</code>가 재발했지만 같은 container ID와 recovered volume만 18:05 KST 재시작했다. 재기동 뒤 owner read-only 집계는 users 356·sessions 193·turns 726, Google users 16·Google-owned sessions 31이고 public health는 다시 <code>status=ok·db=true·engine=true</code>다. public task source pin 코드는 완료됐다. watchdog·로그온 boot는 detached clean release와 exact Git commit/tree·watchdog|boot/start SHA가 없으면 health·Docker·process mutation 전에 실패하고 hidden VBS도 pinned task만 trigger한다(provenance 11개 포함 combined 22/22, PS5.1·WSH PASS). 다만 실제 두 Scheduled Task action은 아직 shared worktree라 watchdog은 필수 pin 인자 부재로 <code>LastTaskResult=1</code>이며 자동복구가 의도적으로 fail-closed다. clean commit 뒤 두 task를 동일 stable release root로 재등록하고 action pin·result 0·로그온/reboot smoke를 확인해야 한다. G8 NAS는 archive <code>4d15d055…119d4d</code>, API/Web <code>d5021950…e4b1</code>/<code>9796c092…4b36</code>으로 current clean source 승격과 actual-origin 112/112를 완료했다. 이전 <code>6030a677…c611</code>과 이미지는 복구 기준으로 보존했다. G7 source는 runner/checker/topology 86/86, launcher/sidecar 80/80, 통합 166/166, API voice 71/71이지만 public은 아직 OpenAI provider·Git 없는 냉동 release라 external GATE다.</p>
|
||||||
</details>
|
</details>
|
||||||
<div class="phase-rail" id="phase-rail" aria-label="Phase 진행 추정"></div>
|
<div class="phase-rail" id="phase-rail" aria-label="Phase 진행 추정"></div>
|
||||||
<div class="mstrip" aria-label="검증 지표">
|
<div class="mstrip" aria-label="검증 지표">
|
||||||
<span class="mchip warn">Fixture E2E <b>110 + dashboard 10 분할 GREEN</b> · clean HEAD 120/120 대기</span>
|
<span class="mchip good">G8 candidate/NAS-origin E2E <b>112/112 · 112/112</b></span>
|
||||||
<span class="mchip good">현재 전체 회귀 · gateway <b>58</b> · backend <b>921</b></span>
|
<span class="mchip good">현재 전체 회귀 · gateway <b>58</b> · backend <b>921</b></span>
|
||||||
<span class="mchip good">Outcome clean E2E <b>126</b> · visual <b>15/15</b></span>
|
<span class="mchip good">Outcome clean E2E <b>126</b> · visual <b>15/15</b></span>
|
||||||
<span class="mchip good">Alliance G1 baseline 1.2 <b>9/9</b> · 내부 DONE</span>
|
<span class="mchip good">Alliance G1 baseline 1.2 <b>9/9</b> · 내부 DONE</span>
|
||||||
|
|
@ -657,6 +657,7 @@
|
||||||
<p class="dg-note">189차 적용(2026-08-09): G7 외부 실행 전 P0를 소스에서 닫았다. production runner exit 0은 canonical checker 0·<code>gate_closed=true</code>에 결속하고, browser Origin allowlist를 API/WSS/admin/topology transport host·scheme과 분리했다. capture는 최소 3,120초·ceil+terminal sample이며 checker는 voice/runtime/topology 공통 교집합 3,000초를 강제한다. Windows 증거는 detached-clean commit/tree, runner/collector/checker SHA, <code>psutil==6.1.1</code>을 매 sample 전후 검증한다. fresh public launcher는 mutation 전에 source·Python·cloudflared·config SHA를 고정하고, legacy API와 exact-config cloudflared를 bounded 교체해 새 PID/start/exe·command SHA/실제 cwd의 raw command-line 없는 receipt를 남긴다. receipt destination은 runtime mutation 전에 create/flush/atomic-replace 권한을 검사하고 기존 receipt를 보존한 채 원자 게시한다. 검증: G7 runner/checker/topology 86/86, launcher/sidecar 80/80, 통합 166/166, API 921, gateway 58, Web api-types·typecheck·build, 자기주도 desktop/mobile 6/6, Ruff·compile·PS5.1 parser·diff-check PASS. 실제 public 실행·마이크·사람 평가는 하지 않았으므로 G7은 external GATE다.</p>
|
<p class="dg-note">189차 적용(2026-08-09): G7 외부 실행 전 P0를 소스에서 닫았다. production runner exit 0은 canonical checker 0·<code>gate_closed=true</code>에 결속하고, browser Origin allowlist를 API/WSS/admin/topology transport host·scheme과 분리했다. capture는 최소 3,120초·ceil+terminal sample이며 checker는 voice/runtime/topology 공통 교집합 3,000초를 강제한다. Windows 증거는 detached-clean commit/tree, runner/collector/checker SHA, <code>psutil==6.1.1</code>을 매 sample 전후 검증한다. fresh public launcher는 mutation 전에 source·Python·cloudflared·config SHA를 고정하고, legacy API와 exact-config cloudflared를 bounded 교체해 새 PID/start/exe·command SHA/실제 cwd의 raw command-line 없는 receipt를 남긴다. receipt destination은 runtime mutation 전에 create/flush/atomic-replace 권한을 검사하고 기존 receipt를 보존한 채 원자 게시한다. 검증: G7 runner/checker/topology 86/86, launcher/sidecar 80/80, 통합 166/166, API 921, gateway 58, Web api-types·typecheck·build, 자기주도 desktop/mobile 6/6, Ruff·compile·PS5.1 parser·diff-check PASS. 실제 public 실행·마이크·사람 평가는 하지 않았으므로 G7은 external GATE다.</p>
|
||||||
<p class="dg-note">190차 적용(2026-08-09): exact include 52개를 clean commit <code>5221f79e…1c69</code>으로 고정하고 tree <code>e4f15001…308b</code>, 2회 동일 archive <code>1109bf86…0f4b</code>를 결속했다. fresh NAS dump <code>6f4b95a7…b529f</code>(1,015,222 bytes·TOC 1,752/TABLE DATA 129) 뒤 execute는 API 921·types·typecheck·build·insecure-context 6/6·candidate DB 회기 후반까지 통과했지만 <b>110/112</b>에서 승격 전에 fail-closed했다. 실패 2건은 Vite entry <code>/src/main.tsx</code>만 차단하던 boot 진단 테스트가 production hashed entry <code>/assets/index-*.js</code>를 차단하지 못한 환경 계약 누락이었다. 두 entry를 함께 차단하도록 고쳐 Vite source 2/2와 production preview 2/2를 통과했다. NAS mutation·rollback·DB migration은 0이며, 테스트 수정의 새 clean commit을 재결속한 한 번의 execute가 남았다.</p>
|
<p class="dg-note">190차 적용(2026-08-09): exact include 52개를 clean commit <code>5221f79e…1c69</code>으로 고정하고 tree <code>e4f15001…308b</code>, 2회 동일 archive <code>1109bf86…0f4b</code>를 결속했다. fresh NAS dump <code>6f4b95a7…b529f</code>(1,015,222 bytes·TOC 1,752/TABLE DATA 129) 뒤 execute는 API 921·types·typecheck·build·insecure-context 6/6·candidate DB 회기 후반까지 통과했지만 <b>110/112</b>에서 승격 전에 fail-closed했다. 실패 2건은 Vite entry <code>/src/main.tsx</code>만 차단하던 boot 진단 테스트가 production hashed entry <code>/assets/index-*.js</code>를 차단하지 못한 환경 계약 누락이었다. 두 entry를 함께 차단하도록 고쳐 Vite source 2/2와 production preview 2/2를 통과했다. NAS mutation·rollback·DB migration은 0이며, 테스트 수정의 새 clean commit을 재결속한 한 번의 execute가 남았다.</p>
|
||||||
<p class="dg-note">191차 적용(2026-08-09): 후속 clean commit <code>21461ab3…6fde</code>·tree <code>5c5f12a8…524e</code>·archive <code>20d49694…fa1a</code>는 candidate <b>112/112</b>를 통과하고 NAS 새 API/Web <code>d5021950…</code>/<code>376a3aa8…</code>를 올렸다. 실제 NAS-origin postdeploy G4 desktop/mobile 2건이 110/112에서 실패하자 active-state commit 전에 이전 <code>52e0e816…</code>/<code>6fdbb646…</code>로 자동 rollback했고 health·auth 401·OpenAPI 126·G0~G8 route·image ID를 재검증해 rollback <b>verified</b>로 종료했다. exact-image 평문 origin에서 문장 fingerprint의 <code>crypto.subtle.digest</code> 의존이 요청 전 예외를 내는 원인을 확인했다. 원문 외부 전송 없이 portable SHA-256 fallback을 추가하고 표준 digest를 payload에 exact 고정해 secure localhost 2/2와 Tailnet insecure origin 2/2를 통과했다. 새 clean commit의 전체 candidate+NAS-origin 0 failure 전까지 G8은 runtime REVALIDATION이다.</p>
|
<p class="dg-note">191차 적용(2026-08-09): 후속 clean commit <code>21461ab3…6fde</code>·tree <code>5c5f12a8…524e</code>·archive <code>20d49694…fa1a</code>는 candidate <b>112/112</b>를 통과하고 NAS 새 API/Web <code>d5021950…</code>/<code>376a3aa8…</code>를 올렸다. 실제 NAS-origin postdeploy G4 desktop/mobile 2건이 110/112에서 실패하자 active-state commit 전에 이전 <code>52e0e816…</code>/<code>6fdbb646…</code>로 자동 rollback했고 health·auth 401·OpenAPI 126·G0~G8 route·image ID를 재검증해 rollback <b>verified</b>로 종료했다. exact-image 평문 origin에서 문장 fingerprint의 <code>crypto.subtle.digest</code> 의존이 요청 전 예외를 내는 원인을 확인했다. 원문 외부 전송 없이 portable SHA-256 fallback을 추가하고 표준 digest를 payload에 exact 고정해 secure localhost 2/2와 Tailnet insecure origin 2/2를 통과했다. 새 clean commit의 전체 candidate+NAS-origin 0 failure 전까지 G8은 runtime REVALIDATION이다.</p>
|
||||||
|
<p class="dg-note">192차 적용(2026-08-09): G8 clean-head 최종 승격을 완료했다. source HEAD <code>61a41d1f…6af</code>·tree <code>87dec55d…3b77</code>에서 2회 동일하게 만든 archive <code>4d15d055d74f…119d4d</code>를 active release로 고정했고 candidate session E2E <b>112/112</b>와 실제 <code>http://100.116.83.60:8088</code> 평문 NAS-origin browser review <b>112/112</b>(11 specs, desktop/mobile/single-run)를 모두 통과했다. 실행 이미지는 API <code>d5021950…e4b1</code>·Web <code>9796c092…4b36</code>이며 health는 <code>status=ok·db=true·engine=true</code>, 비인증 auth 401, OpenAPI 126을 재확인했다. execute 직전 fresh custom dump <code>36ec8748…24db8</code>(1,097,100 bytes, TOC 1,738 / TABLE DATA 129)를 보존했고 이전 active <code>6030a677…c611</code>과 이전 이미지는 rollback 기준으로 남겼다. 앞선 fail-closed·verified rollback 이력은 삭제하지 않는다. 이 증거로 G8을 <b>DONE</b>으로 승격하며 남은 Outcome OS gate는 G7 external proof다.</p>
|
||||||
<div class="dg-principles" aria-label="디자인 생성 가드레일">
|
<div class="dg-principles" aria-label="디자인 생성 가드레일">
|
||||||
<div><b>래스터만 사용</b><span>이미지 생성 도구 산출물은 PNG 기반 시안이다. SVG·벡터·와이어프레임·로고 시트로 해석하지 않는다.</span></div>
|
<div><b>래스터만 사용</b><span>이미지 생성 도구 산출물은 PNG 기반 시안이다. SVG·벡터·와이어프레임·로고 시트로 해석하지 않는다.</span></div>
|
||||||
<div><b>기능 우선</b><span>메인 라우트의 실제 액션과 정보 구조를 먼저 반영한다. 장식은 기능을 가리지 않는 수준에서만 쓴다.</span></div>
|
<div><b>기능 우선</b><span>메인 라우트의 실제 액션과 정보 구조를 먼저 반영한다. 장식은 기능을 가리지 않는 수준에서만 쓴다.</span></div>
|
||||||
|
|
@ -913,9 +914,9 @@
|
||||||
<button class="scard-head" aria-expanded="false"><span class="chip c-doing">G7 GATE · INTERNAL DONE · EXTERNAL PROOF</span><span class="scard-mid"><span class="scard-title">Multimodal Alliance — 음성·비언어 동맹 신호</span><span class="scard-sum">내부 안전·복구·용량 계약은 닫았고 current 공개 runtime·동의 마이크·사람 평가 증거를 기다린다.</span></span><span class="caret" aria-hidden="true"></span></button>
|
<button class="scard-head" aria-expanded="false"><span class="chip c-doing">G7 GATE · INTERNAL DONE · EXTERNAL PROOF</span><span class="scard-mid"><span class="scard-title">Multimodal Alliance — 음성·비언어 동맹 신호</span><span class="scard-sum">내부 안전·복구·용량 계약은 닫았고 current 공개 runtime·동의 마이크·사람 평가 증거를 기다린다.</span></span><span class="caret" aria-hidden="true"></span></button>
|
||||||
<div class="scard-body"><div class="kv k-good"><b>내부 구현 DONE</b><p>consent→단일 clock→독립 text/voice/fusion→철회·tombstone, streaming interim/final·word timestamp·provider event, HMAC word pseudonym, 열린 stream의 1초 동의 재검사·abort, 텍스트 보존·음성 재연결 UX를 연결했다. 운영 기본 provider는 노트북 상주 <code>local_whisper</code>/<code>melotts</code>이고 외부 Deepgram/OpenAI adapter는 fallback으로 보존한다. 격리 synthetic PCM soak는 59/59 연결 종료·성공 발화 55·철회 차단 2·미동의 차단 1·overflow 거부/복구 1/1·10MiB 선초과 거부를 통과했다. Windows host topology는 detached-clean commit/tree·도구 SHA·<code>psutil</code> version과 PID/start/exe·command SHA/cwd·process/TCP high-water를 fail-closed로 수집하고, 3,120초 capture의 공통 3,000초를 canonical exit와 결속한다. current source의 public launcher도 sidecar-before-API lifecycle, <code>local_whisper/small/cpu-int8</code>·<code>melotts/melotts-korean</code> exact readiness, provider env, Uvicorn <code>--ws websockets --ws-max-queue 4</code>, fresh API/cloudflared provenance receipt를 연결했다. runner/checker/topology 86/86·launcher/sidecar 80/80·통합 166/166·API voice 71/71을 통과했다.</p></div><div class="kv k-warn"><b>외부 종료 GATE</b><p>새 launcher는 아직 public에 실행하지 않았다. 현재 public API는 Git metadata가 없는 냉동 release cwd와 OpenAI voice provider로 실행돼 새 Windows provenance gate를 통과하지 못한다. 검증된 clean commit 기반 runtime으로 정렬하고 <code>/admin/voice-runtime</code>과 local provider/model ready를 실제 공개 경로에서 확인한 뒤 authenticated public WSS, 실행 직전 명시 동의 물리 마이크 50분 양방향 soak, 같은 public host·공통 3,000초 시간창의 worker/Uvicorn queue와 process/TCP high-water, 30명/50회기/150축 독립 라벨 held-out voice-gain을 수집해야 한다. 네 artifact가 <code>scripts/check-g7-external-proof.py</code> exit 0을 만들기 전에는 메인 상태를 DONE으로 바꾸지 않는다.</p></div></div>
|
<div class="scard-body"><div class="kv k-good"><b>내부 구현 DONE</b><p>consent→단일 clock→독립 text/voice/fusion→철회·tombstone, streaming interim/final·word timestamp·provider event, HMAC word pseudonym, 열린 stream의 1초 동의 재검사·abort, 텍스트 보존·음성 재연결 UX를 연결했다. 운영 기본 provider는 노트북 상주 <code>local_whisper</code>/<code>melotts</code>이고 외부 Deepgram/OpenAI adapter는 fallback으로 보존한다. 격리 synthetic PCM soak는 59/59 연결 종료·성공 발화 55·철회 차단 2·미동의 차단 1·overflow 거부/복구 1/1·10MiB 선초과 거부를 통과했다. Windows host topology는 detached-clean commit/tree·도구 SHA·<code>psutil</code> version과 PID/start/exe·command SHA/cwd·process/TCP high-water를 fail-closed로 수집하고, 3,120초 capture의 공통 3,000초를 canonical exit와 결속한다. current source의 public launcher도 sidecar-before-API lifecycle, <code>local_whisper/small/cpu-int8</code>·<code>melotts/melotts-korean</code> exact readiness, provider env, Uvicorn <code>--ws websockets --ws-max-queue 4</code>, fresh API/cloudflared provenance receipt를 연결했다. runner/checker/topology 86/86·launcher/sidecar 80/80·통합 166/166·API voice 71/71을 통과했다.</p></div><div class="kv k-warn"><b>외부 종료 GATE</b><p>새 launcher는 아직 public에 실행하지 않았다. 현재 public API는 Git metadata가 없는 냉동 release cwd와 OpenAI voice provider로 실행돼 새 Windows provenance gate를 통과하지 못한다. 검증된 clean commit 기반 runtime으로 정렬하고 <code>/admin/voice-runtime</code>과 local provider/model ready를 실제 공개 경로에서 확인한 뒤 authenticated public WSS, 실행 직전 명시 동의 물리 마이크 50분 양방향 soak, 같은 public host·공통 3,000초 시간창의 worker/Uvicorn queue와 process/TCP high-water, 30명/50회기/150축 독립 라벨 held-out voice-gain을 수집해야 한다. 네 artifact가 <code>scripts/check-g7-external-proof.py</code> exit 0을 만들기 전에는 메인 상태를 DONE으로 바꾸지 않는다.</p></div></div>
|
||||||
</article>
|
</article>
|
||||||
<article class="scard" data-status="doing" data-cat="Outcome OS·에이전틱" data-owner="0">
|
<article class="scard" data-status="done" data-cat="Outcome OS·에이전틱" data-owner="0">
|
||||||
<button class="scard-head" aria-expanded="false"><span class="chip c-doing">G8 BUILD · RECEIPT DONE · RUNTIME REVALIDATION</span><span class="scard-mid"><span class="scard-title">Autonomous Content & Continuous Improvement</span><span class="scard-sum">실제 rollback receipt는 완료됐다. 비-secure origin WebCrypto 호환 수정 배포와 NAS-origin 회기 E2E 재검증이 남았다.</span></span><span class="caret" aria-hidden="true"></span></button>
|
<button class="scard-head" aria-expanded="false"><span class="chip c-done">G8 DONE · CLEAN-HEAD NAS VERIFIED</span><span class="scard-mid"><span class="scard-title">Autonomous Content & Continuous Improvement</span><span class="scard-sum">agentic worker·human gate·실제 rollback과 current clean source의 NAS-origin 112/112를 모두 닫았다.</span></span><span class="caret" aria-hidden="true"></span></button>
|
||||||
<div class="scard-body"><div class="kv"><b>agentic worker + 운영 환류 + human gate</b><p>승인 source의 실제 hash·PII를 engine 호출 전에 검사하고 structured generator 1개, 결과를 공유하지 않는 red-team 2개, variant generator와 variant별 독립 judge 3개 이상을 실행한다. 정답 누수·PII·unknown source claim·open finding·안전/보상 해킹·모델/structured 실패는 store 호출 전에 차단한다. repo-approved source는 기본 비활성 scheduler가 durable lease/retry job으로 관리한다. 실제 <code>claude_cli/claude-opus-4-8</code> 7 calls와 dev PostgreSQL reviewer 2·variant 3·pass rate 1.0을 거쳐 synthetic payload를 <code>pending_human_approval</code>로 저장했다. G6 canonical drift trigger는 metadata-only incident→DAG→job을 원자 enqueue하고, 실제 pending 후보의 관리자 검수는 catalog 0→1·approval 1과 역할 403·멱등/409·민감정보 비노출을 통과했다. 별도 누수 후보는 422·저장 0이다. 상세: <code>ops/outcome-os-g8-agentic-worker-evidence-2026-08-07.md</code>.</p></div><div class="kv k-warn"><b>NAS-origin runtime 재검증</b><p>SHA <code>6030a677…c611</code>의 API/Web 이미지는 health 3/3·db/engine true, OpenAPI 126·auth 401·실제 SSE→DB-backed review를 통과했다. 그러나 108/108은 localhost candidate 결과였고 같은 NAS 평문 origin에서는 <code>crypto.randomUUID</code> 예외로 24건이 실패했다. 소스는 UUID fallback에 더해 문장 fingerprint의 portable SHA-256 fallback을 추가했고 source-only Vite 12/12·typecheck·build를 통과했다. clean-head controller도 HEAD/tree/archive 2회 결정성·archive-only candidate·patch 무회귀와 commit 전 actual-origin 11-spec rollback gate를 unit 30/30로 고정했다. 첫 current clean commit <code>5221f79e</code>는 production entry 차단 테스트 2건으로 110/112에서 승격 전 중단됐고, 후속 <code>21461ab3</code>는 candidate 112/112 뒤 실제 NAS-origin G4 2건에서 실패해 이전 이미지로 verified rollback됐다. entry 회귀와 secure/insecure SHA 회귀는 모두 GREEN이다. 새 clean commit을 결속하고 동일 NAS-origin 회기 묶음이 GREEN이 되기 전에는 G8 전체를 DONE으로 올리지 않는다.</p></div><div class="kv k-good"><b>실제 rollback receipt DONE</b><p>runtime+release_gate 전용 executor는 승인·idempotency·artifact·Compose/env/resolved-policy SHA와 exact API/Web image를 일회성 manifest에 결속하고 HMAC journal·crash recovery·previous-image 복구를 고정한다. 2026-08-07 격리 NAS에서 별도 loopback control-plane API(<code>127.0.0.1:8018</code>)와 별도 executor(<code>127.0.0.1:18149</code>)로 <b>실제 image rollback 2회</b>를 실행했다. <code>rollback-old</code>는 receipt <code>nas-g8-723eeef2…</code>로 previous 이미지 <code>79ec…4450</code>/<code>c530…2f28</code>를, <code>restore-current</code>는 receipt <code>nas-g8-2738846c…</code>로 current 이미지 <code>52e0…8b2d</code>/<code>6fdb…f215</code>를 활성화했다. release gate·approval 각각 2회 멱등, lifecycle <code>rollback/executed</code> 2건, 승인 <code>authorize_rollback</code> 2건, silent auto-promotion 0, HMAC journal 6-record 체인 검증, health 3/3·OpenAPI 126·auth 401·Web 200을 확인했다. helper는 image 상속 compose label을 baseline으로 두고 project/service를 격리 override하는 계약으로 검증했다(<code>launch-nas-preview-g8-helpers.py</code>, 37/37). 상세: <a href="./ops/nas-preview-g8-rollback-proof-runbook.md">런북</a> · <a href="./ops/evidence/nas-preview-g8-actual-rollback-2026-08-07.json">기계 판독 증거</a>.</p></div></div>
|
<div class="scard-body"><div class="kv"><b>agentic worker + 운영 환류 + human gate</b><p>승인 source의 실제 hash·PII를 engine 호출 전에 검사하고 structured generator 1개, 결과를 공유하지 않는 red-team 2개, variant generator와 variant별 독립 judge 3개 이상을 실행한다. 정답 누수·PII·unknown source claim·open finding·안전/보상 해킹·모델/structured 실패는 store 호출 전에 차단한다. repo-approved source는 기본 비활성 scheduler가 durable lease/retry job으로 관리한다. 실제 <code>claude_cli/claude-opus-4-8</code> 7 calls와 dev PostgreSQL reviewer 2·variant 3·pass rate 1.0을 거쳐 synthetic payload를 <code>pending_human_approval</code>로 저장했다. G6 canonical drift trigger는 metadata-only incident→DAG→job을 원자 enqueue하고, 실제 pending 후보의 관리자 검수는 catalog 0→1·approval 1과 역할 403·멱등/409·민감정보 비노출을 통과했다. 별도 누수 후보는 422·저장 0이다. 상세: <code>ops/outcome-os-g8-agentic-worker-evidence-2026-08-07.md</code>.</p></div><div class="kv k-good"><b>clean-head NAS-origin 종료 증거</b><p>source HEAD <code>61a41d1f…6af</code>·tree <code>87dec55d…3b77</code>·2회 동일 archive <code>4d15d055…119d4d</code>를 active release에 결속했다. candidate session E2E <b>112/112</b>와 실제 평문 NAS-origin browser review <b>112/112</b>(11 specs, desktop/mobile/single-run)가 GREEN이며 API/Web <code>d5021950…e4b1</code>/<code>9796c092…4b36</code>, health ok·db/engine true, auth 401, OpenAPI 126을 확인했다. fresh dump <code>36ec8748…24db8</code>(1,097,100 bytes, TOC 1,738 / TABLE DATA 129)와 previous <code>6030a677…c611</code>을 보존했다.</p></div><div class="kv k-good"><b>실제 rollback receipt DONE</b><p>runtime+release_gate 전용 executor는 승인·idempotency·artifact·Compose/env/resolved-policy SHA와 exact API/Web image를 일회성 manifest에 결속하고 HMAC journal·crash recovery·previous-image 복구를 고정한다. 2026-08-07 격리 NAS에서 별도 loopback control-plane API(<code>127.0.0.1:8018</code>)와 별도 executor(<code>127.0.0.1:18149</code>)로 <b>실제 image rollback 2회</b>를 실행했다. <code>rollback-old</code>는 receipt <code>nas-g8-723eeef2…</code>로 previous 이미지 <code>79ec…4450</code>/<code>c530…2f28</code>를, <code>restore-current</code>는 receipt <code>nas-g8-2738846c…</code>로 current 이미지 <code>52e0…8b2d</code>/<code>6fdb…f215</code>를 활성화했다. release gate·approval 각각 2회 멱등, lifecycle <code>rollback/executed</code> 2건, 승인 <code>authorize_rollback</code> 2건, silent auto-promotion 0, HMAC journal 6-record 체인 검증을 확인했다. 상세: <a href="./ops/nas-preview-g8-rollback-proof-runbook.md">런북</a> · <a href="./ops/evidence/nas-preview-g8-actual-rollback-2026-08-07.json">기계 판독 증거</a>.</p></div></div>
|
||||||
</article>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -939,7 +940,7 @@
|
||||||
<article class="phase-card"><div class="phase-top"><span class="phase-id">Phase 2a</span><span class="tag info">planned</span></div><h3>3역할 / OAuth / 페르소나</h3><p>학습자, 교수자, 관리자 권한 분리와 페르소나 확장.</p><ul class="phase-list"><li><b>DoD:</b> 타인 session 직접 접근 403</li><li><b>범위:</b> OAuth BFF, RBAC, 교수 검수 루프</li><li><b>주의:</b> visible_to와 인간 권한은 별도 레이어</li></ul></article>
|
<article class="phase-card"><div class="phase-top"><span class="phase-id">Phase 2a</span><span class="tag info">planned</span></div><h3>3역할 / OAuth / 페르소나</h3><p>학습자, 교수자, 관리자 권한 분리와 페르소나 확장.</p><ul class="phase-list"><li><b>DoD:</b> 타인 session 직접 접근 403</li><li><b>범위:</b> OAuth BFF, RBAC, 교수 검수 루프</li><li><b>주의:</b> visible_to와 인간 권한은 별도 레이어</li></ul></article>
|
||||||
<article class="phase-card"><div class="phase-top"><span class="phase-id">Phase 2b</span><span class="tag info">planned</span></div><h3>음성 캐스케이드</h3><p>STT → 엔진 → TTS 왕복, WSS/터널 안정성, barge-in 로깅.</p><ul class="phase-list"><li><b>DoD:</b> 50분 WSS 끊김 실측</li><li><b>범위:</b> local_whisper STT + MeloTTS 기본, Deepgram/OpenAI fallback</li><li><b>리스크:</b> 실시간 레이턴시는 실측 전 완료 표기 금지</li></ul></article>
|
<article class="phase-card"><div class="phase-top"><span class="phase-id">Phase 2b</span><span class="tag info">planned</span></div><h3>음성 캐스케이드</h3><p>STT → 엔진 → TTS 왕복, WSS/터널 안정성, barge-in 로깅.</p><ul class="phase-list"><li><b>DoD:</b> 50분 WSS 끊김 실측</li><li><b>범위:</b> local_whisper STT + MeloTTS 기본, Deepgram/OpenAI fallback</li><li><b>리스크:</b> 실시간 레이턴시는 실측 전 완료 표기 금지</li></ul></article>
|
||||||
<article class="phase-card"><div class="phase-top"><span class="phase-id">Phase 3</span><span class="tag info">planned</span></div><h3>교육용 파일럿 / 효과성</h3><p>수련생 파일럿, 사전사후 효과 측정, 재귀학습 데이터셋 산출.</p><ul class="phase-list"><li><b>DoD:</b> 20명 완주 + KPI 충족</li><li><b>측정:</b> SUS, 자기효능감, κ/ICC, 환각률</li><li><b>정정:</b> IRB 게이트가 아니라 데이터 활용동의/개인정보 게이트</li></ul></article>
|
<article class="phase-card"><div class="phase-top"><span class="phase-id">Phase 3</span><span class="tag info">planned</span></div><h3>교육용 파일럿 / 효과성</h3><p>수련생 파일럿, 사전사후 효과 측정, 재귀학습 데이터셋 산출.</p><ul class="phase-list"><li><b>DoD:</b> 20명 완주 + KPI 충족</li><li><b>측정:</b> SUS, 자기효능감, κ/ICC, 환각률</li><li><b>정정:</b> IRB 게이트가 아니라 데이터 활용동의/개인정보 게이트</li></ul></article>
|
||||||
<article class="phase-card next"><div class="phase-top"><span class="phase-id">Outcome OS</span><span class="tag doing">G7 external · G8 runtime</span></div><h3>성과·동맹·수련 폐루프</h3><p>측정 → 이상 탐지 → 원인 가설 → 재연습 → 전이 검증 → 숙련도 갱신.</p><ul class="phase-list"><li><b>상태:</b> G0~G6 internal DONE · G8 실제 NAS rollback receipt DONE/runtime 재검증 중 · G7 외부 voice/resource/human 대기</li><li><b>완료 DAG:</b> AOS-001~012</li><li><b>운영:</b> NAS SHA <code>6030a677…c611</code>은 UUID 수정 전 빌드 · G8 receipt 2건 완료 · 수정 배포/NAS-origin E2E와 G7 canonical four-artifact checker exit 0 필요</li></ul></article>
|
<article class="phase-card next"><div class="phase-top"><span class="phase-id">Outcome OS</span><span class="tag doing">G7 external</span></div><h3>성과·동맹·수련 폐루프</h3><p>측정 → 이상 탐지 → 원인 가설 → 재연습 → 전이 검증 → 숙련도 갱신.</p><ul class="phase-list"><li><b>상태:</b> G0~G6 internal DONE · G8 clean-head NAS-origin DONE · G7 외부 voice/resource/human 대기</li><li><b>완료 DAG:</b> AOS-001~012</li><li><b>운영:</b> NAS archive <code>4d15d055…119d4d</code> · candidate/NAS-origin 112/112 · G7 canonical four-artifact checker exit 0 필요</li></ul></article>
|
||||||
</div>
|
</div>
|
||||||
<div class="source-note">정정 기준: <code>MASTERPLAN</code> 원본의 IRB 신청/승인 게이트는 <code>MASTERPLAN_REVISIONS</code> 기준으로 제거한다. Phase 2는 2a(3역할/OAuth/페르소나)와 2b(음성 캐스케이드)로 분리하고, Phase 1 DoD에는 얇은 음성 왕복 1턴 PoC를 포함한다.</div>
|
<div class="source-note">정정 기준: <code>MASTERPLAN</code> 원본의 IRB 신청/승인 게이트는 <code>MASTERPLAN_REVISIONS</code> 기준으로 제거한다. Phase 2는 2a(3역할/OAuth/페르소나)와 2b(음성 캐스케이드)로 분리하고, Phase 1 DoD에는 얇은 음성 왕복 1턴 PoC를 포함한다.</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -1067,7 +1068,7 @@
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr><td>Web typecheck</td><td><code>npm run typecheck</code></td><td>Passed</td></tr>
|
<tr><td>Web typecheck</td><td><code>npm run typecheck</code></td><td>Passed</td></tr>
|
||||||
<tr><td>Design SSOT / auth visual</td><td><code>npm run check:design-ssot</code> / <code>npx playwright test e2e/auth-visual.spec.ts --project=chromium-single-run --reporter=line</code> / <code>npx playwright test e2e/layout-visual-gate.spec.ts --project=chromium-single-run --reporter=line</code></td><td>SSOT checker passed; login/onboarding light-dark desktop-mobile 1 passed; 14 core screens × 7 widths visual gate 14 passed.</td></tr>
|
<tr><td>Design SSOT / auth visual</td><td><code>npm run check:design-ssot</code> / <code>npx playwright test e2e/auth-visual.spec.ts --project=chromium-single-run --reporter=line</code> / <code>npx playwright test e2e/layout-visual-gate.spec.ts --project=chromium-single-run --reporter=line</code></td><td>SSOT checker passed; login/onboarding light-dark desktop-mobile 1 passed; 14 core screens × 7 widths visual gate 14 passed.</td></tr>
|
||||||
<tr><td>Full Playwright E2E baseline</td><td><code>npm run e2e:parallel</code> / <code>npm run e2e:single-run</code> / <code>npm run e2e:list</code></td><td>2026-08-09 현재 수집은 <b>620 tests / 44 files</b>다. 현 작업트리의 620개 전체 GREEN은 아직 검증 전이며, current 핵심 fixture는 불변 110 + 최신 dashboard 10의 분할 GREEN이다. 이전 단일 120/120과 2026-07-15의 fixture desktop/mobile 166/166 + DB/engine/provider 직렬 49/49 = 215/215는 범위가 다른 역사 기준선으로 보존한다.</td></tr>
|
<tr><td>Full Playwright E2E baseline</td><td><code>npm run e2e:parallel</code> / <code>npm run e2e:single-run</code> / <code>npm run e2e:list</code></td><td>2026-08-09 현재 수집은 <b>622 tests / 44 files</b>다. 이 숫자는 수집량이며 현 작업트리 전체 GREEN과 동일하지 않다. G8 clean-head release gate는 candidate 112/112와 실제 NAS-origin 112/112를 통과했다. 이전 단일 120/120과 2026-07-15의 fixture desktop/mobile 166/166 + DB/engine/provider 직렬 49/49 = 215/215는 범위가 다른 역사 기준선으로 보존한다.</td></tr>
|
||||||
<tr><td>Refactor governance P1~P8</td><td><code>ruff check app</code> / <code>pytest -q app</code> / <code>pytest -q engine_gateway</code> / <code>npm run typecheck</code> / <code>npm run check:api-types</code> / <code>npm run check:design-ssot</code> / <code>npm run check:dead-code</code> / <code>npm run check:duplication</code> / <code>npm run build</code> / <code>npm audit --audit-level=high</code> / full Playwright</td><td>Backend 400 passed, gateway 29 passed, web gates/build/audit passed, vulnerabilities 0, production duplication 1 clone/15 lines/0.03%, Playwright 215/215 passed. 상세 근거는 <code>ops/refactor-governance-2026-07-15.md</code>.</td></tr>
|
<tr><td>Refactor governance P1~P8</td><td><code>ruff check app</code> / <code>pytest -q app</code> / <code>pytest -q engine_gateway</code> / <code>npm run typecheck</code> / <code>npm run check:api-types</code> / <code>npm run check:design-ssot</code> / <code>npm run check:dead-code</code> / <code>npm run check:duplication</code> / <code>npm run build</code> / <code>npm audit --audit-level=high</code> / full Playwright</td><td>Backend 400 passed, gateway 29 passed, web gates/build/audit passed, vulnerabilities 0, production duplication 1 clone/15 lines/0.03%, Playwright 215/215 passed. 상세 근거는 <code>ops/refactor-governance-2026-07-15.md</code>.</td></tr>
|
||||||
<tr><td>API typegen SSOT</td><td><code>npm run check:api-types</code></td><td>Passed; FastAPI OpenAPI → <code>src/lib/api.gen.ts</code> stale check</td></tr>
|
<tr><td>API typegen SSOT</td><td><code>npm run check:api-types</code></td><td>Passed; FastAPI OpenAPI → <code>src/lib/api.gen.ts</code> stale check</td></tr>
|
||||||
<tr><td>Outcome & Alliance OS G0</td><td><code>py -3.11 -X utf8 -m pytest -p no:cacheprovider apps/api/app/test_measurement_contract.py apps/api/app/test_runtime_schema_ssot.py -q</code> / <code>scripts/check-measurement-ledger.sql</code> / measurement·API contract checks / web typecheck / DB-backed <code>session-persistence</code> focused E2E 3종</td><td>G0 contract/schema 11 passed, 기존 backend 100 passed, auth 39 passed. Python→JSON Schema→TypeScript→PostgreSQL enum·필수필드 계약이 일치하고 8개 deterministic benchmark가 검증됐다. Live PostgreSQL에서 learner/client/evaluator 가시 행 1/1/2, 교차 누수 0, append-only guard 2를 확인했다. 학습자 턴→교수자 대시보드, 워크시트 검수, 종료 deep 평가→durable 리뷰 E2E는 각각 1 passed. G0/AOS-001~004 완료.</td></tr>
|
<tr><td>Outcome & Alliance OS G0</td><td><code>py -3.11 -X utf8 -m pytest -p no:cacheprovider apps/api/app/test_measurement_contract.py apps/api/app/test_runtime_schema_ssot.py -q</code> / <code>scripts/check-measurement-ledger.sql</code> / measurement·API contract checks / web typecheck / DB-backed <code>session-persistence</code> focused E2E 3종</td><td>G0 contract/schema 11 passed, 기존 backend 100 passed, auth 39 passed. Python→JSON Schema→TypeScript→PostgreSQL enum·필수필드 계약이 일치하고 8개 deterministic benchmark가 검증됐다. Live PostgreSQL에서 learner/client/evaluator 가시 행 1/1/2, 교차 누수 0, append-only guard 2를 확인했다. 학습자 턴→교수자 대시보드, 워크시트 검수, 종료 deep 평가→durable 리뷰 E2E는 각각 1 passed. G0/AOS-001~004 완료.</td></tr>
|
||||||
|
|
@ -1144,12 +1145,12 @@
|
||||||
<tr><td>Docker image smoke</td><td><code>docker build -f apps/api/Dockerfile .</code> / <code>docker run ... python -c "import app.main"</code> / <code>docker build -f apps/web/Dockerfile apps/web</code></td><td>API/Web image build and API import smoke pass after packaging cleanup. Web build uses npm lockfile and ignores host <code>node_modules</code>; API image excludes local <code>.env</code> files.</td></tr>
|
<tr><td>Docker image smoke</td><td><code>docker build -f apps/api/Dockerfile .</code> / <code>docker run ... python -c "import app.main"</code> / <code>docker build -f apps/web/Dockerfile apps/web</code></td><td>API/Web image build and API import smoke pass after packaging cleanup. Web build uses npm lockfile and ignores host <code>node_modules</code>; API image excludes local <code>.env</code> files.</td></tr>
|
||||||
<tr><td>Deploy preflight</td><td><code>python scripts\check-deploy-preflight.py --skip-db --env-file infra\.env.example --allow-placeholder-secrets</code> / DB mode with local <code>DATABASE_URL</code></td><td>Passed: exact-pinned API requirements, live coaching <code>data/kb</code> source pack, env template keys, and DB readiness (<code>current_user=vignette</code>). DB mode can additionally check app-role DSN with <code>--require-app-role</code> and now verifies session read-model columns including <code>app.turns</code> voice metadata columns(<code>audio_ref</code>/<code>silence_ms</code>/<code>speech_rate</code>/<code>barge_in</code>/<code>provider_events</code>) plus worksheet review columns.</td></tr>
|
<tr><td>Deploy preflight</td><td><code>python scripts\check-deploy-preflight.py --skip-db --env-file infra\.env.example --allow-placeholder-secrets</code> / DB mode with local <code>DATABASE_URL</code></td><td>Passed: exact-pinned API requirements, live coaching <code>data/kb</code> source pack, env template keys, and DB readiness (<code>current_user=vignette</code>). DB mode can additionally check app-role DSN with <code>--require-app-role</code> and now verifies session read-model columns including <code>app.turns</code> voice metadata columns(<code>audio_ref</code>/<code>silence_ms</code>/<code>speech_rate</code>/<code>barge_in</code>/<code>provider_events</code>) plus worksheet review columns.</td></tr>
|
||||||
<tr><td>Fresh compose smoke</td><td><code>docker compose -p vignette-packaging-smoke -f infra/docker-compose.yml up -d --build</code> + proxy <code>/api/health</code></td><td>Passed with dummy production-safe env: API healthy, DB healthy, web/proxy up, <code>http://localhost:18080/api/health</code> 200 with <code>db:true</code>, <code>engine:true</code>, <code>engine_mode:"claude_cli"</code>. Smoke volumes/network removed after run.</td></tr>
|
<tr><td>Fresh compose smoke</td><td><code>docker compose -p vignette-packaging-smoke -f infra/docker-compose.yml up -d --build</code> + proxy <code>/api/health</code></td><td>Passed with dummy production-safe env: API healthy, DB healthy, web/proxy up, <code>http://localhost:18080/api/health</code> 200 with <code>db:true</code>, <code>engine:true</code>, <code>engine_mode:"claude_cli"</code>. Smoke volumes/network removed after run.</td></tr>
|
||||||
<tr><td>격리 NAS 프리뷰 · 회기 E2E 자동화</td><td><code>http://100.116.83.60:8088</code> / <code>vignette-e2e</code> 매일 04:30 KST / <a href="./ops/nas-preview-deployment-evidence-2026-08-07.md">배포 증거</a> / <a href="./ops/evidence/nas-preview-current-deploy-2026-08-07.json">기계 판독 증거</a></td><td>SHA <code>6030a677…c611</code>을 전용 Compose 프로젝트·포트·네트워크·named volume에 승격했다. localhost candidate browser E2E 108/108, health 3/3, OpenAPI 126·auth 401·G0~G8, postdeploy SSE→DB review를 통과했고 predeploy dump와 이전 exact images를 보존했다. 같은 NAS 평문 origin은 UUID 결함으로 24건 실패했으므로 현재 소스 수정 배포와 NAS-origin 재검증이 남았다. 자동화는 ACTIVE이며 material milestone+release gate+배포 SHA 변경 때만 프리뷰를 갱신한다. G7 외부 mic/provider/human 증거는 별도다.</td></tr>
|
<tr><td>격리 NAS 프리뷰 · 회기 E2E 자동화</td><td><code>http://100.116.83.60:8088</code> / <code>vignette-e2e</code> 매일 04:30 KST / <a href="./ops/nas-preview-deployment-evidence-2026-08-07.md">배포 증거</a> / <a href="./ops/evidence/nas-preview-current-deploy-2026-08-07.json">기계 판독 증거</a></td><td>current clean source HEAD <code>61a41d1f…6af</code>·tree <code>87dec55d…3b77</code>의 archive <code>4d15d055…119d4d</code>를 전용 Compose 프로젝트·포트·네트워크·named volume에 승격했다. candidate session E2E 112/112와 실제 NAS 평문 origin browser review 112/112, health ok·db/engine true, OpenAPI 126·auth 401을 통과했고 fresh dump와 이전 active/images를 보존했다. 자동화는 ACTIVE이며 material milestone+release gate·NAS preflight+배포 SHA 변경 때만 프리뷰를 갱신한다. G7 외부 mic/provider/human 증거는 별도다.</td></tr>
|
||||||
<tr><td>G7 외부 증거 3-artifact 동시 시간창 오케스트레이터</td><td><code>scripts/run-g7-external-proof-window.py</code> · <a href="./ops/outcome-os-g7-external-proof-readiness-2026-08-07.md">준비 문서</a></td><td>soak·runtime·topology 세 캡처를 같은 host의 공통 3,000초 시간창으로 실행하고 checker까지 잇는다. CLI는 동의·장치 없음, human pack 없음, 3,120초 미만, host 불일치를 전부 fail-closed하며 <code>--rehearse</code>는 마이크를 열지 않는다. 기존 Linux Compose gate를 보존한 채 Windows host Uvicorn+cloudflared topology mode를 추가해 detached-clean source/tool SHA와 PID/start/exe·command SHA/cwd·process/TCP high-water를 검증했고 runner/checker/topology 86/86을 통과했다. current source launcher도 local sidecar/provider/queue와 fresh PID provenance를 fail-closed로 연결했지만 아직 공개에 실행하지 않았다. <b>남은 선행 조건:</b> 공개 런타임은 119 paths 구버전이고 <code>/admin/voice-runtime</code>이 없으며 <code>/voice/health</code>는 OpenAI STT/TTS, local sidecar 9882/9883은 내려가 있다. API는 Git 없는 냉동 release cwd라 provenance gate도 닫혀 있다. clean committed source runtime으로 정렬한 뒤에만 <b>물리 마이크 50분</b>과 <b>독립 human voice-gain pack</b>을 수집한다.</td></tr>
|
<tr><td>G7 외부 증거 3-artifact 동시 시간창 오케스트레이터</td><td><code>scripts/run-g7-external-proof-window.py</code> · <a href="./ops/outcome-os-g7-external-proof-readiness-2026-08-07.md">준비 문서</a></td><td>soak·runtime·topology 세 캡처를 같은 host의 공통 3,000초 시간창으로 실행하고 checker까지 잇는다. CLI는 동의·장치 없음, human pack 없음, 3,120초 미만, host 불일치를 전부 fail-closed하며 <code>--rehearse</code>는 마이크를 열지 않는다. 기존 Linux Compose gate를 보존한 채 Windows host Uvicorn+cloudflared topology mode를 추가해 detached-clean source/tool SHA와 PID/start/exe·command SHA/cwd·process/TCP high-water를 검증했고 runner/checker/topology 86/86을 통과했다. current source launcher도 local sidecar/provider/queue와 fresh PID provenance를 fail-closed로 연결했지만 아직 공개에 실행하지 않았다. <b>남은 선행 조건:</b> 공개 런타임은 119 paths 구버전이고 <code>/admin/voice-runtime</code>이 없으며 <code>/voice/health</code>는 OpenAI STT/TTS, local sidecar 9882/9883은 내려가 있다. API는 Git 없는 냉동 release cwd라 provenance gate도 닫혀 있다. clean committed source runtime으로 정렬한 뒤에만 <b>물리 마이크 50분</b>과 <b>독립 human voice-gain pack</b>을 수집한다.</td></tr>
|
||||||
<tr><td>G7 external GATE · 최종 감사 정정</td><td><code>run-g7-external-proof-window.py</code> → <code>check-g7-external-proof.py</code></td><td><b>상태는 external GATE 유지.</b> 물리 마이크 전 코드 P0 3건은 완료됐다. runner 프로세스 exit는 canonical checker <code>exit 0</code>·<code>gate_closed=true</code>에 결속되고, browser Origin <code>vignette.chanpaca.net</code>은 API/WSS/admin/topology host <code>api-vignette.chanpaca.net</code>과 분리된 HTTPS allowlist로 검증되며, 세 artifact 교집합은 <code>≥3000s</code> + 120초 sampler margin으로 강제된다. Windows proof는 detached-clean HEAD/tree·runner/collector/checker script SHA·exact <code>psutil==6.1.1</code>을 pin한다. 공개 창에서는 legacy PID를 재사용하지 않고 API/cloudflared를 pinned executable/config와 stable release cwd에서 강제 재시작해 safe provenance receipt를 결속한다. 남은 것은 clean-source 실제 배포와 4-artifact 실증이다.</td></tr>
|
<tr><td>G7 external GATE · 최종 감사 정정</td><td><code>run-g7-external-proof-window.py</code> → <code>check-g7-external-proof.py</code></td><td><b>상태는 external GATE 유지.</b> 물리 마이크 전 코드 P0 3건은 완료됐다. runner 프로세스 exit는 canonical checker <code>exit 0</code>·<code>gate_closed=true</code>에 결속되고, browser Origin <code>vignette.chanpaca.net</code>은 API/WSS/admin/topology host <code>api-vignette.chanpaca.net</code>과 분리된 HTTPS allowlist로 검증되며, 세 artifact 교집합은 <code>≥3000s</code> + 120초 sampler margin으로 강제된다. Windows proof는 detached-clean HEAD/tree·runner/collector/checker script SHA·exact <code>psutil==6.1.1</code>을 pin한다. 공개 창에서는 legacy PID를 재사용하지 않고 API/cloudflared를 pinned executable/config와 stable release cwd에서 강제 재시작해 safe provenance receipt를 결속한다. 남은 것은 clean-source 실제 배포와 4-artifact 실증이다.</td></tr>
|
||||||
<tr><td>G8 runtime REVALIDATION · 17:07 NAS 기준선</td><td><code>vignette-preview-20260807</code> · <code>run-outcome-os-release-agent.py</code></td><td><b>상태는 runtime REVALIDATION 유지.</b> active SHA <code>6030a677…c611</code>, API/Web <code>52e0…8b2d</code>/<code>6fdb…f215</code> exact running, previous <code>79ec…4450</code>/<code>c530…2f28</code> 보존. <code>api/web/db/proxy</code> 4개 running + <code>unless-stopped</code>, volume <code>vignette-preview-20260807_pgdata</code>, health 3/3·db/engine true·auth 401·OpenAPI 126 + G1~G8 routes·root/JS/CSS 200, helper/listener 0이다. dump는 <code>92ed…1570f</code> 960,970 bytes와 <code>726982…5d41c</code> 1,015,222 bytes. release agent는 DB dump/restore를 지원하지 않으므로 매 execute 직전 fresh dump SHA/size/TOC·DB identity를 별도 결속한다. 자동 rollback은 image/Compose/active-state뿐이고 migration/data restore는 별도 owner 승인이다. remote release root/Compose mode <code>0777</code> P1은 최소권한 preflight로 닫는다.</td></tr>
|
<tr><td>G8 DONE · clean-head NAS 기준선</td><td><code>vignette-preview-20260807</code> · <code>run-outcome-os-release-agent.py</code></td><td><b>상태는 DONE.</b> active archive <code>4d15d055…119d4d</code>, source HEAD/tree <code>61a41d1f…6af</code>/<code>87dec55d…3b77</code>, API/Web <code>d5021950…e4b1</code>/<code>9796c092…4b36</code> exact running. candidate 112/112와 actual NAS-origin 112/112, health ok·db/engine true·auth 401·OpenAPI 126을 통과했다. fresh dump <code>36ec8748…24db8</code> 1,097,100 bytes, TOC 1,738 / TABLE DATA 129와 previous active <code>6030a677…c611</code>을 보존했다. release agent의 DB restore 비소유·image/Compose/active-state rollback 경계는 유지한다.</td></tr>
|
||||||
<tr><td>노트북 로컬 음성 스택 — faster-whisper STT + MeloTTS TTS</td><td><code>scripts/local-whisper-stt-server.py</code> · <code>scripts/start-local-whisper-stt.ps1</code> · <code>voice_stt_provider=local_whisper</code></td><td>2026-08-08 소유자 결정으로 음성 양쪽을 노트북 상주 로컬 모델로 정했다. TTS는 처음 Higgs로 잡았으나 연구/비상업 라이선스라 <code>config.py</code>가 운영에서 차단하고 있었고 그 가드를 푸는 건 법적 판단이라, 상업 사용이 허용된 <b>MeloTTS Korean(MIT)</b>으로 바꿔 가드 자체를 불필요하게 만들었다. Kokoro-82M은 Apache 2.0이지만 공식 <code>VOICES.md</code>에 <b>한국어가 없어</b> 탈락했다. local Whisper는 현재 호스트의 cuDNN 부재를 반영해 <code>small/cpu-int8</code>를 운영 기본으로 고정했고, API health도 MeloTTS model을 실제 provider와 맞춰 보고한다. 합성 시드 8.72초에서 interim 9·final 5·word timestamp 12개, 한 글자 차이 전사를 확인했고 MeloTTS CPU RTF는 0.27~0.28, 합성음→local STT 왕복은 완전 일치했다. current 통합은 launcher/sidecar 80/80·G7 전체 166/166·API voice 71/71·API 전체 921·gateway 58을 통과했다. Higgs 운영 사용은 라이선스 가드가 남아 dev 전용이다.</td></tr>
|
<tr><td>노트북 로컬 음성 스택 — faster-whisper STT + MeloTTS TTS</td><td><code>scripts/local-whisper-stt-server.py</code> · <code>scripts/start-local-whisper-stt.ps1</code> · <code>voice_stt_provider=local_whisper</code></td><td>2026-08-08 소유자 결정으로 음성 양쪽을 노트북 상주 로컬 모델로 정했다. TTS는 처음 Higgs로 잡았으나 연구/비상업 라이선스라 <code>config.py</code>가 운영에서 차단하고 있었고 그 가드를 푸는 건 법적 판단이라, 상업 사용이 허용된 <b>MeloTTS Korean(MIT)</b>으로 바꿔 가드 자체를 불필요하게 만들었다. Kokoro-82M은 Apache 2.0이지만 공식 <code>VOICES.md</code>에 <b>한국어가 없어</b> 탈락했다. local Whisper는 현재 호스트의 cuDNN 부재를 반영해 <code>small/cpu-int8</code>를 운영 기본으로 고정했고, API health도 MeloTTS model을 실제 provider와 맞춰 보고한다. 합성 시드 8.72초에서 interim 9·final 5·word timestamp 12개, 한 글자 차이 전사를 확인했고 MeloTTS CPU RTF는 0.27~0.28, 합성음→local STT 왕복은 완전 일치했다. current 통합은 launcher/sidecar 80/80·G7 전체 166/166·API voice 71/71·API 전체 921·gateway 58을 통과했다. Higgs 운영 사용은 라이선스 가드가 남아 dev 전용이다.</td></tr>
|
||||||
<tr><td>비-secure origin 회기 리뷰 크래시 수정</td><td><code>apps/web/src/lib/uuid.ts</code> · <code>apps/web/e2e/insecure-context-uuid.spec.ts</code></td><td>격리 NAS 프리뷰(평문 HTTP·비-localhost)에서 회기 리뷰 라우트가 <code>crypto.randomUUID is not a function</code>으로 error boundary에 잡혔다. 이 API는 secure context 전용인데 제품 코드 18곳이 fallback 없이 호출했고 <code>RuptureRepairCard</code>는 렌더 시점 호출이라 화면 전체가 죽었다. 릴리스 게이트는 localhost 후보 스택에서만 돌아 미검출. <code>randomUuid()</code>로 통일하고 fallback도 <code>getRandomValues</code> 우선으로 예측 불가능성을 유지했다. 회귀 <code>6/6</code>(직접 호출 0건 검사 포함), typecheck·build 통과. <b>NAS 프리뷰에는 아직 미배포</b>이며 배포된 SHA <code>6030a677…c611</code>은 여전히 결함 빌드다.</td></tr>
|
<tr><td>비-secure origin 회기 리뷰 크래시 수정</td><td><code>apps/web/src/lib/uuid.ts</code> · <code>apps/web/src/lib/sha256.ts</code> · <code>apps/web/e2e/insecure-context-uuid.spec.ts</code></td><td>격리 NAS 프리뷰(평문 HTTP·비-localhost)에서 드러난 <code>crypto.randomUUID</code>와 <code>crypto.subtle.digest</code> secure-context 의존을 portable fallback으로 교체했다. source-only UUID/SHA 회귀 12/12와 secure/insecure exact digest 회귀를 통과했고, 최종 archive <code>4d15d055…119d4d</code>의 actual NAS-origin 112/112로 배포 검증까지 닫았다.</td></tr>
|
||||||
<tr><td>공개 워치독 engine readiness 사각지대(해결)</td><td><code>scripts/watch-public-runtime.ps1</code> · <code>scripts/start-public-runtime.ps1</code> · <code>apps/api/engine_gateway/gateway.py</code></td><td>2026-08-07 22:0x KST 소유자 승인으로 9099를 재기동해 복구했다. 재기동 직후 <code>/ready?force=true</code> 200 <code>ok:true·detail:OK</code>, API 8001과 공개 <code>api-vignette.chanpaca.net</code> 모두 <code>status:ok·engine:true·engine_detail:OK</code>다. 원인은 16:06부터 상주한 9099 프로세스 하나였다(CLI·코드·cwd는 정상 — 동일 인자 직접 실행과 <code>EngineSession</code> 재현이 <code>"OK"</code>를 반환). 재발 방지 3건: (1) 워치독·기동 스크립트의 engine 판정을 <code>/health</code>(프로세스 liveness)에서 <code>/ready</code>(실제 생성)로 바꾸고 API 판정에 <code>engine</code>을 추가, shared secret 인스턴스용 토큰 헤더와 503 본문 로깅 포함. (2) 게이트웨이가 자식 <code>claude -p</code>의 stderr를 상시 드레인해(PIPE 미독 시 자식 블록도 차단) 실패 detail에 <code>exit</code>·stderr를 붙인다 — 실증: <code>empty engine response (error: unknown option '--vignette-nonexistent-flag-xyz')</code>. (3) 기동 시 게이트웨이 로그 회전, <code>Stop-UvicornByPort</code>에 <code>Name -like python*</code> 추가(호출자 자기 자신 종료 방지). 검증: <code>pytest engine_gateway</code> 58/58, 워치독 <code>-CheckOnly</code> healthy 5/5, 장애 재현(9299·<code>CLAUDE_BIN</code> 부재)에서 <code>/health</code>는 <code>ok:true</code>로 통과하고 <code>/ready</code>는 503으로 검출되어 워치독이 <code>unhealthy (1/3)</code> + 503 본문을 남겼다. <b>NAS용 9100(06:27 상주)은 실사용 세션 7개가 있어 미접촉</b>이라 게이트웨이 stderr 개선은 다음 재기동 때 반영된다.</td></tr>
|
<tr><td>공개 워치독 engine readiness 사각지대(해결)</td><td><code>scripts/watch-public-runtime.ps1</code> · <code>scripts/start-public-runtime.ps1</code> · <code>apps/api/engine_gateway/gateway.py</code></td><td>2026-08-07 22:0x KST 소유자 승인으로 9099를 재기동해 복구했다. 재기동 직후 <code>/ready?force=true</code> 200 <code>ok:true·detail:OK</code>, API 8001과 공개 <code>api-vignette.chanpaca.net</code> 모두 <code>status:ok·engine:true·engine_detail:OK</code>다. 원인은 16:06부터 상주한 9099 프로세스 하나였다(CLI·코드·cwd는 정상 — 동일 인자 직접 실행과 <code>EngineSession</code> 재현이 <code>"OK"</code>를 반환). 재발 방지 3건: (1) 워치독·기동 스크립트의 engine 판정을 <code>/health</code>(프로세스 liveness)에서 <code>/ready</code>(실제 생성)로 바꾸고 API 판정에 <code>engine</code>을 추가, shared secret 인스턴스용 토큰 헤더와 503 본문 로깅 포함. (2) 게이트웨이가 자식 <code>claude -p</code>의 stderr를 상시 드레인해(PIPE 미독 시 자식 블록도 차단) 실패 detail에 <code>exit</code>·stderr를 붙인다 — 실증: <code>empty engine response (error: unknown option '--vignette-nonexistent-flag-xyz')</code>. (3) 기동 시 게이트웨이 로그 회전, <code>Stop-UvicornByPort</code>에 <code>Name -like python*</code> 추가(호출자 자기 자신 종료 방지). 검증: <code>pytest engine_gateway</code> 58/58, 워치독 <code>-CheckOnly</code> healthy 5/5, 장애 재현(9299·<code>CLAUDE_BIN</code> 부재)에서 <code>/health</code>는 <code>ok:true</code>로 통과하고 <code>/ready</code>는 503으로 검출되어 워치독이 <code>unhealthy (1/3)</code> + 503 본문을 남겼다. <b>NAS용 9100(06:27 상주)은 실사용 세션 7개가 있어 미접촉</b>이라 게이트웨이 stderr 개선은 다음 재기동 때 반영된다.</td></tr>
|
||||||
<tr><td>G8 실제 receipt-bound image rollback</td><td><code>scripts/launch-nas-preview-g8-helpers.py</code> / <code>scripts/serve-nas-preview-rollback-executor.py</code> / <code>scripts/probe-nas-preview-g8-rollback.py</code> / <a href="./ops/nas-preview-g8-rollback-proof-runbook.md">런북</a> / <a href="./ops/evidence/nas-preview-g8-actual-rollback-2026-08-07.json">기계 판독 증거</a></td><td>격리 NAS에서 별도 executor(18149)·control-plane(8018) helper로 실제 image rollback 2회를 실행했다. receipt <code>nas-g8-723eeef2…</code>로 previous <code>79ec…4450</code>/<code>c530…2f28</code>, receipt <code>nas-g8-2738846c…</code>로 current <code>52e0…8b2d</code>/<code>6fdb…f215</code>를 활성화했다. release gate·approval 각각 2회 멱등, <code>audit.ci_lifecycle_event</code> rollback/executed 2건과 receipt id 결속, silent auto-promotion 0, HMAC journal 6-record 체인 검증, health 3/3·OpenAPI 126·auth 401·Web 200, helper 0·listener 0 복귀. helper 격리 계약은 image 상속 label baseline 대비 검증이며 fake-runner 37/37로 고정했다. <code>down</code>/<code>volume rm</code>/<code>prune</code> 미실행, 공개 런타임 미접촉.</td></tr>
|
<tr><td>G8 실제 receipt-bound image rollback</td><td><code>scripts/launch-nas-preview-g8-helpers.py</code> / <code>scripts/serve-nas-preview-rollback-executor.py</code> / <code>scripts/probe-nas-preview-g8-rollback.py</code> / <a href="./ops/nas-preview-g8-rollback-proof-runbook.md">런북</a> / <a href="./ops/evidence/nas-preview-g8-actual-rollback-2026-08-07.json">기계 판독 증거</a></td><td>격리 NAS에서 별도 executor(18149)·control-plane(8018) helper로 실제 image rollback 2회를 실행했다. receipt <code>nas-g8-723eeef2…</code>로 previous <code>79ec…4450</code>/<code>c530…2f28</code>, receipt <code>nas-g8-2738846c…</code>로 current <code>52e0…8b2d</code>/<code>6fdb…f215</code>를 활성화했다. release gate·approval 각각 2회 멱등, <code>audit.ci_lifecycle_event</code> rollback/executed 2건과 receipt id 결속, silent auto-promotion 0, HMAC journal 6-record 체인 검증, health 3/3·OpenAPI 126·auth 401·Web 200, helper 0·listener 0 복귀. helper 격리 계약은 image 상속 label baseline 대비 검증이며 fake-runner 37/37로 고정했다. <code>down</code>/<code>volume rm</code>/<code>prune</code> 미실행, 공개 런타임 미접촉.</td></tr>
|
||||||
<tr><td>Actual deployment env</td><td><code>infra/.env</code> owner-secret fill-in</td><td>Remaining external step: deployment target must provide real <code>APP_DB_PASSWORD</code>, OAuth client id/secret, <code>OPENAI_API_KEY</code>, <code>SESSION_SECRET</code>, and production-safe engine/voice flags. Current local stray values such as <code>ENGINE_MODE=claude_cli</code> and prod sample TTS must not be copied.</td></tr>
|
<tr><td>Actual deployment env</td><td><code>infra/.env</code> owner-secret fill-in</td><td>Remaining external step: deployment target must provide real <code>APP_DB_PASSWORD</code>, OAuth client id/secret, <code>OPENAI_API_KEY</code>, <code>SESSION_SECRET</code>, and production-safe engine/voice flags. Current local stray values such as <code>ENGINE_MODE=claude_cli</code> and prod sample TTS must not be copied.</td></tr>
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ Vignette 저장소의 모든 검증 수단(백엔드 단위 테스트, 웹 타
|
||||||
| API 타입 생성 체크 | `apps/web` | `npm run check:api-types` | 불필요 | 불필요 | 불필요 | 불필요 | 불필요 | pass |
|
| API 타입 생성 체크 | `apps/web` | `npm run check:api-types` | 불필요 | 불필요 | 불필요 | 불필요 | 불필요 | pass |
|
||||||
| 웹 타입체크 | `apps/web` | `npm run typecheck` | 불필요 | 불필요 | 불필요 | 불필요 | 불필요 | pass |
|
| 웹 타입체크 | `apps/web` | `npm run typecheck` | 불필요 | 불필요 | 불필요 | 불필요 | 불필요 | pass |
|
||||||
| 웹 빌드 | `apps/web` | `npm run build` | 불필요 | 불필요 | 불필요 | 불필요 | 불필요 | pass |
|
| 웹 빌드 | `apps/web` | `npm run build` | 불필요 | 불필요 | 불필요 | 불필요 | 불필요 | pass |
|
||||||
| Playwright E2E(전체) | `apps/web` | `npm run e2e` | **필요(+시드)** | **필요** | 자동기동 | 일부만 | **필요** | 현재 수집 620 tests / 44 files · 현 작업트리 전체 GREEN 미검증 |
|
| Playwright E2E(전체) | `apps/web` | `npm run e2e` | **필요(+시드)** | **필요** | 자동기동 | 일부만 | **필요** | 현재 수집 622 tests / 44 files · 현 작업트리 전체 GREEN 미검증 |
|
||||||
|
|
||||||
핵심 원칙: **단위 테스트(pytest)와 타입체크/빌드는 외부 서비스 없이 단독 실행된다.**
|
핵심 원칙: **단위 테스트(pytest)와 타입체크/빌드는 외부 서비스 없이 단독 실행된다.**
|
||||||
**E2E만 풀스택(DB+API+웹+브라우저)을 요구한다.** 아래 각 절에서 근거와 절차를 설명한다.
|
**E2E만 풀스택(DB+API+웹+브라우저)을 요구한다.** 아래 각 절에서 근거와 절차를 설명한다.
|
||||||
|
|
@ -271,8 +271,8 @@ VITE_API_BASE=http://127.0.0.1:8000 npm run e2e # 프록시 대신 API
|
||||||
|
|
||||||
### 3.6 실측 테스트 개수 (현재)
|
### 3.6 실측 테스트 개수 (현재)
|
||||||
|
|
||||||
2026-08-09 `npx playwright test --list` 기준 **현재 수집 620 tests / 44 files**다.
|
2026-08-09 `npx playwright test --list` 기준 **현재 수집 622 tests / 44 files**다.
|
||||||
이 숫자는 수집량이지 통과량이 아니다. 현 작업트리 전체 620개 완주는 아직 증거가 없으며,
|
이 숫자는 수집량이지 통과량이 아니다. 현 작업트리 전체 622개 완주는 아직 증거가 없으며,
|
||||||
과거 전체 GREEN 기록과 이번 focused/release gate 결과를 구분해 적는다.
|
과거 전체 GREEN 기록과 이번 focused/release gate 결과를 구분해 적는다.
|
||||||
|
|
||||||
- **병렬 시나리오**: 166 tests (desktop 83 + mobile 83)
|
- **병렬 시나리오**: 166 tests (desktop 83 + mobile 83)
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@
|
||||||
> 선제 구축했고, 임상 문안·평가기준·골든셋 콘텐츠는 임상팀(구훈정·어유경) 외부 정의로 받는다.
|
> 선제 구축했고, 임상 문안·평가기준·골든셋 콘텐츠는 임상팀(구훈정·어유경) 외부 정의로 받는다.
|
||||||
>
|
>
|
||||||
> **Outcome & Alliance OS** — 2026-08-06 정식 전략 실행 트랙으로 승격했다. G0 Measurement Truth, G1
|
> **Outcome & Alliance OS** — 2026-08-06 정식 전략 실행 트랙으로 승격했다. G0 Measurement Truth, G1
|
||||||
> 현재 소스·실행 증거 재감사에서는 G0~G6이 internal DONE이다. G1 승격 prompt 1.2+read-skew/JSON 복구는 24/24 ready·방향 9/9·오류 0을 재확인했고, G0 census 29/29·위반 0, G4/G5 실제 API/DB/브라우저 폐루프, G6 safety metadata-only 최우선 runtime을 disposable clone에서 확인했다. G8의 실제 receipt-bound image rollback 2회(`nas-g8-723eeef2…`/`nas-g8-2738846c…`)는 executed lifecycle receipt로 완료됐지만, 같은 NAS SHA의 평문 origin 회기 E2E 24건 실패 때문에 UUID 수정 배포·NAS-origin 재검증 전까지 전체 상태는 runtime REVALIDATION이다. 과거 clean release/NAS의 route·asset·auth·browser 증거는 배포 존재 이력으로 보존하지만 현재 소스의 완료 증거로 재사용하지 않는다. G7 Multimodal Alliance는 내부 구현 DONE과 외부 proof GATE를 분리하며 current source 공개 배포, 운영 기본 `local_whisper`/`melotts` ready, authenticated public WSS, 명시 동의 물리 마이크·50분 soak, 독립 라벨 voice-gain benchmark와 운영 topology resource high-water를 추적한다. 외부 Deepgram/OpenAI adapter는 fallback으로 보존한다. G0~G8과
|
> 현재 소스·실행 증거 재감사에서는 G0~G6과 G8이 DONE이다. G1 승격 prompt 1.2+read-skew/JSON 복구는 24/24 ready·방향 9/9·오류 0을 재확인했고, G0 census 29/29·위반 0, G4/G5 실제 API/DB/브라우저 폐루프, G6 safety metadata-only 최우선 runtime을 disposable clone에서 확인했다. G8은 실제 receipt-bound image rollback 2회(`nas-g8-723eeef2…`/`nas-g8-2738846c…`)에 더해 source HEAD `61a41d1f…6af`·tree `87dec55d…3b77`·archive `4d15d055…119d4d`의 candidate 112/112와 실제 NAS 평문 origin 112/112를 통과했다. 과거 `6030a677…c611`의 UUID 24건 실패와 후속 SHA 결함 rollback은 이력으로 보존하며 현재 완료 증거로 재사용하지 않는다. G7 Multimodal Alliance는 내부 구현 DONE과 외부 proof GATE를 분리하며 current source 공개 배포, 운영 기본 `local_whisper`/`melotts` ready, authenticated public WSS, 명시 동의 물리 마이크·50분 soak, 독립 라벨 voice-gain benchmark와 운영 topology resource high-water를 추적한다. 외부 Deepgram/OpenAI adapter는 fallback으로 보존한다. G0~G8과
|
||||||
> AOS-001~012는 `docs/TODO.md` I절에서 전건 추적하고, 상태는 SSOT 대시보드의 9개 계획 카드가 소유한다.
|
> AOS-001~012는 `docs/TODO.md` I절에서 전건 추적하고, 상태는 SSOT 대시보드의 9개 계획 카드가 소유한다.
|
||||||
> 이 얇은 백로그에는 그중 외부·환경 증거가 필요한 항목만 기존 B2/B4/Phase 3 게이트와 합쳐 유지한다.
|
> 이 얇은 백로그에는 그중 외부·환경 증거가 필요한 항목만 기존 B2/B4/Phase 3 게이트와 합쳐 유지한다.
|
||||||
|
|
||||||
|
|
@ -24,26 +24,6 @@
|
||||||
- [ ] **운영 티켓 자동 분류·처리 후속** — Claude Recipe headless 자동 수정 후보, 관리자 승인 후 이슈 등록·PR/작업
|
- [ ] **운영 티켓 자동 분류·처리 후속** — Claude Recipe headless 자동 수정 후보, 관리자 승인 후 이슈 등록·PR/작업
|
||||||
스레드 생성, 처리 결과 audit trail 확장은 아직 설계/승인 필요. 담당 그룹 자동 배정·우선순위 escalation·raw/rollup
|
스레드 생성, 처리 결과 audit trail 확장은 아직 설계/승인 필요. 담당 그룹 자동 배정·우선순위 escalation·raw/rollup
|
||||||
보존기간 같은 운영 정책은 B3에서 이미 결정됨(수동 승인·미도입 고정). 자동 수정은 운영자 승인 전까지 실행하지 않는다.
|
보존기간 같은 운영 정책은 B3에서 이미 결정됨(수동 승인·미도입 고정). 자동 수정은 운영자 승인 전까지 실행하지 않는다.
|
||||||
- [ ] **G8 clean-head 릴리스와 NAS-origin 재검증** — explicit clean-head mode와 source-only insecure-context gate는
|
|
||||||
unit 30/30·UUID 6/6·SHA-256 6/6으로 구현됐다. 2026-08-09 execute 두 번은 각각 archive CRLF와 session E2E에서
|
|
||||||
fail-closed되어 NAS mutation 0이었다. timezone 없는 `started_at`의 약 9시간 오차는 UTC/KST 계약, API 전체
|
|
||||||
921 passed, KST focused DB/browser 1/1로 수정했다. 사용자 승인 아래 새 clean commit의 HEAD/tree/archive를
|
|
||||||
결속한 뒤 격리 NAS preview에 승격해 실제 평문 origin의 전체 회기 E2E·SSE→DB review·student returned-practice를
|
|
||||||
0 failure로 재검증한다. fixture-only desktop/mobile 묶음은 16:49 KST 단일 120/120 뒤 dashboard 변경분 10/10을
|
|
||||||
따로 재검증해 current tree는 불변 110 + 최신 dashboard 10의 분할 GREEN이며, clean HEAD에서 exact 120을 다시
|
|
||||||
실행한다. 어느 쪽도 NAS runtime 증거를 대신하지 않는다. release agent는 DB dump/restore를 지원하지 않으므로 매 execute 직전 fresh custom dump의
|
|
||||||
SHA/size/TOC와 DB identity를 별도 증거로 결속한다. agent 자동 rollback은 image/Compose/active-state 범위뿐이며,
|
|
||||||
적용 migration/data restore는 별도 owner 승인을 받는다. remote release root/Compose mode `0777` P1도 최소권한
|
|
||||||
preflight로 닫는다. 이때만 G8 전체 DONE이 가능하다.
|
|
||||||
첫 current clean commit `5221f79e`는 HEAD/tree/archive와 fresh NAS dump를 결속했지만 candidate E2E
|
|
||||||
110/112에서 production hashed entry를 차단하지 못한 boot-diagnostic 테스트 desktop/mobile 2건으로 승격 전
|
|
||||||
중단됐다. NAS mutation 0이다. `/src/main.tsx`와 `/assets/index-*.js`를 함께 차단하는 수정은 Vite source 2/2,
|
|
||||||
production preview 2/2를 통과했으며 새 clean commit으로 한 번만 재실행한다.
|
|
||||||
후속 `21461ab3` candidate는 112/112를 통과했지만 실제 NAS-origin deliberate-practice 2건이
|
|
||||||
`crypto.subtle.digest` 부재로 실패했고 이전 이미지 rollback·health/auth/OpenAPI 검증은 완료됐다. 원문을 서버로
|
|
||||||
보내지 않는 portable SHA-256 fallback과 exact digest assertion은 secure localhost 2/2·Tailnet insecure 2/2를
|
|
||||||
통과했다. 새 clean commit을 결속한 전체 execute 0 failure가 여전히 종료조건이다.
|
|
||||||
|
|
||||||
> B1 완료 항목(학생 리뷰 1~5 척도 320px 반응형 재배치, 셸 구분선, 세션 종료 UX/다크테마, 아바타 SVG 리그 복귀와 래스터 비활성화, SEO/공유 카드, 레이아웃 정렬, 권한 위임,
|
> B1 완료 항목(학생 리뷰 1~5 척도 320px 반응형 재배치, 셸 구분선, 세션 종료 UX/다크테마, 아바타 SVG 리그 복귀와 래스터 비활성화, SEO/공유 카드, 레이아웃 정렬, 권한 위임,
|
||||||
> 메일링, 아카이브 API, TTS voice map, 빈상태 레이아웃, 평가 실패 복구 UX, SSE 저장, live-coach 표면화,
|
> 메일링, 아카이브 API, TTS voice map, 빈상태 레이아웃, 평가 실패 복구 UX, SSE 저장, live-coach 표면화,
|
||||||
> 음성 메타, 위기 게이트 UI, 운영 티켓/헬스 샘플러 등)의 상세·검증 로그는 아카이브 full-history 참조.
|
> 음성 메타, 위기 게이트 UI, 운영 티켓/헬스 샘플러 등)의 상세·검증 로그는 아카이브 full-history 참조.
|
||||||
|
|
@ -113,11 +93,7 @@
|
||||||
G7 통합 166/166을 통과했다. 이제 clean source 공개 배포·rehearse 전에는 물리 마이크를 열지 않는다.
|
G7 통합 166/166을 통과했다. 이제 clean source 공개 배포·rehearse 전에는 물리 마이크를 열지 않는다.
|
||||||
격리 NAS 프리뷰 `http://100.116.83.60:8088`은 전용 Compose 프로젝트·포트·네트워크·볼륨에 배포했고, 실제 브라우저 회기와
|
격리 NAS 프리뷰 `http://100.116.83.60:8088`은 전용 Compose 프로젝트·포트·네트워크·볼륨에 배포했고, 실제 브라우저 회기와
|
||||||
review API 저장 축어록 2턴을 확인했다. 기존 프로젝트 중단·재생성 명령은 실행하지 않았다. 매일 04:30 KST `Vignette 회기 E2E 정기 검증`
|
review API 저장 축어록 2턴을 확인했다. 기존 프로젝트 중단·재생성 명령은 실행하지 않았다. 매일 04:30 KST `Vignette 회기 E2E 정기 검증`
|
||||||
(automation id `vignette-e2e`)은 ACTIVE이며 material milestone+release gate·NAS preflight+배포 SHA 변경 때만 프리뷰를 갱신한다. 2026-08-07 SHA `6030a677af7e87cbfabc422b553d108d53414fd3c446548734a13b036d35c611`은 API/Web 새 이미지, localhost candidate E2E 108/108, OpenAPI 126, auth 401, G0~G8, postdeploy browser SSE→DB review와 health 3/3을 통과했다. 같은 NAS 평문 origin에서는 UUID 결함으로 24건 실패했으므로 current source 배포 증거로 재사용하지 않는다. 실제 receipt-bound rollback은 같은 프리뷰에서 별도 helper로 두 번 실행해 종료했다([런북](./nas-preview-g8-rollback-proof-runbook.md), [기계 판독 증거](./evidence/nas-preview-g8-actual-rollback-2026-08-07.json)). 첫 예약 실행 이력은 대기 상태다. 증거: [배포 증거](./nas-preview-deployment-evidence-2026-08-07.md),
|
(automation id `vignette-e2e`)은 ACTIVE이며 material milestone+release gate·NAS preflight+배포 SHA 변경 때만 프리뷰를 갱신한다. 2026-08-07 SHA `6030a677af7e87cbfabc422b553d108d53414fd3c446548734a13b036d35c611`의 localhost 108/108과 평문 origin UUID 24건 실패는 역사 기준선으로 보존한다. 실제 receipt-bound rollback은 같은 프리뷰에서 별도 helper로 두 번 실행해 종료했다([런북](./nas-preview-g8-rollback-proof-runbook.md), [기계 판독 증거](./evidence/nas-preview-g8-actual-rollback-2026-08-07.json)). 현재는 source HEAD/tree `61a41d1f…6af`/`87dec55d…3b77`의 active archive `4d15d055…119d4d`, exact API/Web `d5021950…e4b1`/`9796c092…4b36`이 실행 중이다. candidate 112/112와 실제 NAS-origin 112/112, health ok·db/engine true·auth 401·OpenAPI 126을 통과했고 fresh dump `36ec8748…24db8` 1,097,100 bytes·TOC 1,738/TABLE DATA 129와 previous `6030a677…c611`을 보존했다. 첫 예약 실행 이력은 대기 상태다. 증거: [배포 증거](./nas-preview-deployment-evidence-2026-08-07.md),
|
||||||
2026-08-09 17:07 KST current baseline은 exact API/Web `52e0…8b2d`/`6fdb…f215`, previous
|
|
||||||
`79ec…4450`/`c530…2f28` 보존, `api/web/db/proxy` 4개 running + `unless-stopped`, DB volume
|
|
||||||
`vignette-preview-20260807_pgdata`, health 3/3·db/engine true·auth 401·OpenAPI 126 + G1~G8 routes·root/JS/CSS
|
|
||||||
200, helper/listener 0이다. dump `92ed…1570f` 960,970 bytes와 `726982…5d41c` 1,015,222 bytes도 존재한다.
|
|
||||||
[브라우저 증거](./evidence/nas-preview-live-turn-2026-08-07.png). 명시 동의 물리 마이크와 독립 human voice-gain 증거는 아직 없으므로 G7은 external GATE로 유지한다.
|
[브라우저 증거](./evidence/nas-preview-live-turn-2026-08-07.png). 명시 동의 물리 마이크와 독립 human voice-gain 증거는 아직 없으므로 G7은 external GATE로 유지한다.
|
||||||
- [ ] **claude_cli ↔ Anthropic API live 동일성** — provider 라우팅·Anthropic `/v1/models` 탐색·지원 추론 강도·관리자 fail-closed 저장 경로는 구현 완료. 남은 범위는 연구팀/기관 `ANTHROPIC_API_KEY`를 게이트웨이 호스트에 주입한 live 응답·계량·오류 표면화 비교다. Claude CLI·Codex CLI(Terra/Medium)·Agy CLI(Gemini 3.6 Flash/High)는 로컬 live probe를 통과했다.
|
- [ ] **claude_cli ↔ Anthropic API live 동일성** — provider 라우팅·Anthropic `/v1/models` 탐색·지원 추론 강도·관리자 fail-closed 저장 경로는 구현 완료. 남은 범위는 연구팀/기관 `ANTHROPIC_API_KEY`를 게이트웨이 호스트에 주입한 live 응답·계량·오류 표면화 비교다. Claude CLI·Codex CLI(Terra/Medium)·Agy CLI(Gemini 3.6 Flash/High)는 로컬 live probe를 통과했다.
|
||||||
- [ ] **stable-source task 재등록 + 재부팅 후 watchdog smoke** — 사용자 승인 clean commit의 detached release
|
- [ ] **stable-source task 재등록 + 재부팅 후 watchdog smoke** — 사용자 승인 clean commit의 detached release
|
||||||
|
|
|
||||||
|
|
@ -23,8 +23,8 @@ DEFAULT_LOCAL_DEVELOPMENT = REPO_ROOT / "docs" / "guides" / "local-development.m
|
||||||
DEFAULT_TODO = REPO_ROOT / "docs" / "TODO.md"
|
DEFAULT_TODO = REPO_ROOT / "docs" / "TODO.md"
|
||||||
|
|
||||||
EXPECTED_STATUS_COUNTS = {
|
EXPECTED_STATUS_COUNTS = {
|
||||||
"done": 32,
|
"done": 33,
|
||||||
"doing": 3,
|
"doing": 2,
|
||||||
"planned": 0,
|
"planned": 0,
|
||||||
}
|
}
|
||||||
EXPECTED_OWNER_COLUMN_COUNTS = {
|
EXPECTED_OWNER_COLUMN_COUNTS = {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue