콘텐츠로 이동

채널과 알림(Channels and Notifications)

Core는 턴 종료 후 검토와 문서 의도 준비 접수를 채널·알림·승인 토픽과 분리해 감독합니다. 둘 다 독립 이벤트 버스 그룹을 사용하며 사람 채널, 알림 수신자, 승인 전송 경로 또는 권한을 추가하지 않습니다. 읽기 전용 ActionBuilder 규칙 맵은 ORR 인용을 검증하고 실제 검증기와 내장 비교 구간의 관측은 자체 상태에 입력됩니다. 둘 다 채널, 전달 증적, 승인 경로를 만들거나 알림 추가 기능을 선택하지 않습니다.

FDAI가 Teams, Slack, 이메일, 웹훅, 페이징 서비스, SMS, Direct Line 호환 사용자 지정 채널 및 명시적으로 선택한 브라우저 알림으로 사람과 소통하는 방법을 설명합니다. 이 문서는 채널 추상화, 신뢰 수준, 카테고리 경계, 라우팅 정책, 채널별 규칙을 정의하는 기준 원본입니다. tech-stack-ko.md의 “notifier 인터페이스” 자리 표시자를 구체화하고 경보 라우팅 계약의 경보 라우팅 조각들과 user-rbac-and-identity-ko.md의 Teams-특이 흐름을 통합합니다.

읽기 전용 콘솔의 신원 및 상호 작용 흐름은 이 문서 범위 밖이며, 아웃바운드 브라우저 알림 경계만 이 문서가 소유합니다. 콘솔 신원은 user-rbac-and-identity-ko.md에 있습니다.

제품 프로필: 관찰 우선 기본값은 채널을 조립하지 않습니다. 알림에는 명시적 notifications 추가 기능이 필요하며 A1 승인에는 governed-execution과 완전한 엔터프라이즈 신원 근거가 추가로 필요합니다. 환경 값만으로는 어떤 추가 기능도 선택할 수 없습니다.

운영 A3 유입, 발행, 수명 주기, 영속성 조립, 배포 및 롤백은 운영 A3 채널 런타임이 소유합니다.

Core는 Assurance Twin의 읽기 전용 보고서 및 검토 아웃박스를 A1 요청 아웃박스와 별도로 감독합니다. Twin의 근거 공백은 승인 결정이나 알림 채널의 대체 경로가 아닙니다. 어느 작업자도 다른 쪽의 레코드를 처리하거나 승인할 수 없습니다.

활성화되고 설정된 모든 채널로의 A2/A4 fan-out 전달, 이름이 있는 채널 바인딩, 채널별 영속 전달, Teams Workflows 웹훅 바인딩은 다중 채널 알림 전달이 소유합니다.

방향 범위. 아웃바운드 알림, A1 승인, 양방향 대화는 서로 다른 프로토콜입니다. 이 문서는 공통 신뢰, 카테고리, 라우팅 원칙과 아웃바운드 전달을 담당하고, 대화 도구/세션 의미 규칙은 operator-console.md가 소유합니다. 어댑터는 자격 증명을 공유할 수 있지만 영향 범위가 다른 세 계약을 합치지 않습니다.

고객 비종속: 아래 모든 채널 ID, 그룹 이름, 엔드포인트는 자리 표시자입니다. 포크가 구성을 통해 자체 테넌트, 작업 영역, 엔드포인트 값을 제공합니다 (generic-scope.instructions.md).

  1. 세 개의 좁은 추상화, 여러 어댑터. NotificationChannel은 A2/A4 push, HilChannel은 A1 전송/poll, ConversationChannelAdapter는 A3 인바운드/아웃바운드를 소유합니다. 코어는 Teams나 Slack을 이름 지정하지 않으며 새 벤더 어댑터는 추가적입니다.
  2. 벤더가 아니라 목적으로 분류합니다. 채널은 네 카테고리(§3) 중 하나 이상을 지원하며, 각 벤더는 안전하게 제공할 수 있는 카테고리만 지원합니다.
  3. 신뢰 계층을 구분합니다. 사람의 Entra 신원을 종단 간 검증할 수 없는 채널로는 승인 카테고리 트래픽(A1)을 전달하면 안 됩니다. 신뢰 수준이 낮은 채널은 정보를 전달할 수 있지만 결정은 전달할 수 없습니다(§4).
  4. 불확실할 때는 안전한 쪽을 선택합니다. 카테고리에 설정된 모든 채널이 실패하면 요청을 대기열에 넣고 운영 담당자에게 긴급 알림을 보냅니다. 자동으로 실행하지 않습니다. 카테고리 내 대체 경로도 신뢰 계층을 유지합니다(§6).
  5. 발신자가 민감정보를 제거합니다. 어떤 카테고리에서도 시크릿, 자격 증명, PII, 구독 ID, 원시 고객 페이로드가 채널 메시지에 담겨 신뢰 경계를 벗어나면 안 됩니다.

2. 아키텍처 상 채널의 위치. 주요 단계는 risk-gate, channel-router, observability, digest-writer, 채널 인터페이스, teams 어댑터, slack 어댑터, email 어댑터, webhook 어댑터, pager 어댑터, fdai-api입니다.

  • 아웃바운드 어댑터는 delivery/notifications/, bidirectional 어댑터는 delivery/channels/, A1 승인 어댑터는 delivery/chatops/에 있습니다. 계약은 각각 shared/providers/notifications/, conversation_channel.py, hil_channel.py에 있습니다.
  • channel-router는 카테고리와 메시지를 받아 포크의 라우팅 구성(§6)에 따라 채널을 선택하는 얇은 코어 모듈입니다. 벤더별 지식은 보유하지 않습니다.
  • 모든 어댑터의 승인 콜백은 fdai-api에 도달하며, API는 액션 전에 사람의 Entra 신원을 다시 검증합니다 (user-rbac-and-identity-ko.md). 어댑터 자체는 결정 권한을 부여하지 않습니다. 채널 승인은 전권 개발 프로필의 확인이 될 수 없습니다. FDAI Console 결정 경로만 토큰을 담지 않은 새 로그인 증명을 첨부할 수 있으며, 사람 승인 결정 페이로드가 이를 Core로 전달해 다시 검증합니다. Slack과 Teams 결정은 이 증명을 담지 않습니다. 구독 태그 pilot은 독립 Executor가 gateway 또는 직접 Azure 어댑터 중 어느 경로를 사용해도 이 Console 전용 경계를 유지합니다.
  • A1 승인 보류 항목은 활성 단독 운영자 프로덕션 승인 프로필 메타데이터를 담을 수 있습니다. 여기에는 프로필 개정, 정책 다이제스트, 원래 정족수, 유효 정족수, 운영자 principal, 실행기 principal이 포함됩니다. Core는 결정 시작 시 활성 Mimir 승인 정책 포인터에서, 활성화 전에는 부트스트랩 환경 폴백에서 다이제스트가 검증된 개정을 고정합니다. 채널 어댑터는 이를 콜백과 감사를 위한 변경 불가능한 문맥으로만 취급하며, Operator API와 Core가 승인을 받아들이기 전에 다시 검증합니다. 이 메타데이터는 Slack, Teams 또는 알림 경로가 프로필을 선택하거나 실행 권한을 부여하게 하지 않으며, 이후 활성화가 진행 중인 보류 항목을 다시 쓰지 않습니다.
  • governance.override-promote-action-type 승인은 같은 A1 경로를 사용합니다. 채널은 고정된 프로필과 정확한 승인 증적 문맥을 전달하고, Var 승인이 검증된 뒤 promotion registry를 쓰는 주체는 Thor뿐입니다.
  • A1 승인 보류 항목은 고정된 운영자 허용 정책 다이제스트도 담을 수 있습니다. 채널 어댑터는 이를 변경할 수 없는 승인 문맥으로 표시하거나 반환할 뿐이며, 정책 포인터를 다시 읽거나 Rego를 평가하지 않습니다.
  • Core 정책 개정 서명 검증은 채널 계층이 승인 문맥을 보기 전에 수행됩니다. 이 변경은 채널 신뢰 티어, 콜백 경로, 대체 동작 또는 알림 권한을 바꾸지 않습니다.
  • Core의 런타임 작업 감독은 라이선스 사용권 상태 게시자를 채널 outbox 작업과 별도의 백그라운드 작업으로 실행합니다. 이 게시자는 채널 메시지를 보내지 않으며, 게시가 느리거나 실패해도 Slack, Teams, 알림 전달을 지연시키지 않습니다.

모든 채널 메시지는 카테고리 태그를 포함하고 해당 카테고리의 규칙을 준수해야 합니다. 이 카테고리는 notification.a1부터 notification.a4까지의 머신 category입니다. 헌법의 autonomy.a0부터 autonomy.a4까지의 권한 등급과 무관하며 숫자 접미사는 두 enum 계열의 동등성 또는 변환을 의미하지 않습니다.

카테고리방향예시필요한 인증 강도
A1 - 사람 승인 승인양방향(결정 반환)고위험 액션 승인, enforce-promotion 승인, exemption 승인, 재정의 승인최고 - 검증된 Entra 아이덴티티, 액션-바인딩, 재생 없음
A2 - 운영 알림아웃바운드 onlySLO burn, DLQ 깊이, 검증기 실패율, cold-start miss, IaC 표류, 어댑터 불건강, canary miss, 긴급하거나 불완전한 코드 보안 스캔낮음 - 정보성
A3 - 채팅 명령양방향(쿼리/응답)읽기: /aw status, /aw shadow-report, /aw override list, /aw kill-switch status. 쓰기 (draft-PR only): /aw override draft, /aw exemption draft, /aw assignment param-tune중간 - 명령별 롤-게이팅(§3.1)
A4 - 다이제스트아웃바운드 only일간 관찰 모드-accuracy 리포트, 주간 재정의 회고, 주간 enforce-promotion 후보, 주간 거버넌스 PR aging, 주간 exemption 만료 lookahead, 월간 KPI + 비용 총결, break-glass 사용 요약, 일간 코드 보안 요약낮음 - 수신자 스코프만

카테고리 경계 (MUST)

  • A1 승인은 불투명한 결합 값만 운반하고 신원, 권한 또는 결정 페이로드를 운반하지 않습니다. 수신기는 인증된 전송과 위임 토큰에서 승인자를 도출한 뒤 정확한 카드 계약을 다시 검증합니다. Operator 승인 콜백 wire 계약을 참조하세요.
  • A3 쓰기 명령은 절대 라이브 카탈로그를 직접 변형하지 않음 - 콘솔과 같은 방식으로 초안 PR을 생산 (user-rbac-and-identity-ko.md §6), invoker의 Entra OID를 PR trailer에 운반. PR은 이후 표준 정족수 + 자기승인 없음 규칙을 따름.
  • A2/A4 메시지는 절대 승인 버튼이나 실행 링크를 포함하지 않음. 예약된 exemption 수명주기 점검은 만료 전 다이제스트를 이 라우터로 A2 알림 하나로 보내며, 정확한 exemption 개정에서 파생한 감사 id로 반복 전송을 막습니다(규칙 거버넌스).
  • 기본 프로필에서 학습된 패턴과 예측은 A1 요청을 만들지 않음. 통제된 실행 추가 기능이 없으면 Forseti는 예측과 용량 입력이 포함된 중재에 ActionType 없는 판정으로 응답하고, 컨트롤 루프는 학습된 T1 재사용을 Action을 만들기 전에 멈추므로 어떤 채널도 이에 대한 승인을 전달하지 않음. 학습 및 예측 결과 경계 참고.
  • T2 루브릭 판정은 A1 요청을 만들지 않음. 검증된 영수증이 ActionType에 대해 루브릭 구간을 강제 적용하더라도 구간은 신뢰도를 낮추거나 T2 후보를 보류할 뿐이며, T2 후보는 통제된 하위 연결이 생기기 전까지 Action이 되지 않음. 환각 루브릭 게이트 참고.
  • 인과 가설은 A1 요청을 만들지 않음. 투영되거나 종결된 CausalHypothesis 개정은 Forseti 판단의 근거일 뿐임. 런타임의 Forseti 투영과 Thor ActionRun 영수증 확인기는 채널 메시지를 보내거나 승인 요청을 만들지 않음.

각 A3 명령은 최소 역할과 읽기/쓰기 여부를 선언합니다. 봇 어댑터는 호출자의 Entra OID (Teams SSO / Slack 매핑)로 핸들러 실행 전에 권한을 검사합니다. 역할이 없으면 채널에 403 응답을 반환하고 감사 항목을 기록합니다.

명령타입최소 롤
/aw status, /aw shadow-report, /aw kpi읽기Reader
/aw override list, /aw exemption list, /aw kill-switch status읽기Reader
/aw override draft, /aw exemption draft, /aw assignment param-tune쓰기 → 초안 PRContributor
/aw kill-switch on/off쓰기 → 초안 PR + A1 승인Owner

채널의 허용 카테고리는 기술적으로 전달할 수 있는 범위와 인증으로 입증할 수 있는 범위의 교집합입니다.

채널Entra 테넌트인증 경로허용 카테고리
Teams (same 테넌트)✓Teams SSO → OBO 교환 → fdai-api 토큰A1, A2, A3, A4
Teams (게스트 테넌트)게스트게스트 OID로 OBOA2, A3, A4 (A1 거부 - user-rbac-and-identity-ko.md §10.5와 동일한 게스트 규칙)
Slack✗Slack OAuth, 포크 필수 Slack userId ↔ Entra OID 매핑, A1에는 서명된 상호작용과 브라우저 Entra 재인증 필요비어 있지 않은 매핑과 새 서명된 API 토큰의 auth_time이 있을 때만 A1, A2, A3, A4. 아웃바운드와 인계는 로컬에서 구현했지만 실제 운영 환경에서는 검증하지 않았습니다.
이메일 (SMTP / Graph)✗발신 전용, return 채널 없음A2, A4 only - 절대 A1 아님 (magic-link 승인 금지)
범용 웹훅✗HMAC-signed, timestamped, replay-guardedA2 only
PagerDuty / Opsgenie✗API 키, 모바일 앱에서 ackA2 only (운영 라인 paging)
SMS✗-A2 only (최소 페이로드; break-glass 도달성)
Direct Line 호환 사용자 지정 채널✗보호된 구성의 중계기 시크릿, activity 게시만 수행A2, A4 only - A1이나 A3는 허용되지 않음

매트릭스를 안전하게 유지하는 규칙 (MUST)

  • Magic-link 승인은 모든 채널에서 금지. 승인은 항상 fdai-api를 통한 재인증된 왕복이 필요.
  • A1 대체 경로는 A1 승인 가능한 채널로 제한합니다. 실패한 Teams A1 시도는 이메일로 전달하지 않습니다. 다른 승인 가능한 채널(대기 중인 Teams 또는 매핑, 브라우저 행위자 결합, 영속 발송 조정이 모두 갖춰진 Slack)을 사용하거나 사람 승인 큐에 남깁니다.
  • Slack A1 결정에는 userId↔OID 매핑이 필요합니다. 아웃바운드 단추에는 대기 중인 승인 ID만 담고 행위자, 역할, 작업 해시 또는 승인 결정은 담지 않습니다. 별도의 서명된 콜백은 응답한 Slack 사용자에 대한 비어 있지 않은 매핑이 없으면 결정을 승인하지 않습니다. 매핑이 없으면 “승인자 없음”으로 처리하고 사람 승인 큐에서 차단 상태를 유지합니다.

ChannelAccessService는 채널별 disabled, allowlist, pairing을 지원합니다. 영속 PostgreSQL 저장소는 channel-scoped 트랜잭션 잠금으로 요청 생성을 직렬화하고 pending 상한을 atomic하게 강제하고 만료된 요청을 상한에서 제외하고 저장된 다이제스트를 조건부 승인합니다. 승인된 발신자는 재등록해 principal 대응을 덮어쓸 수 없습니다.

NativePairingChallengeFlow는 평문 확인 코드를 같은 스레드의 채널 회신으로만 보냅니다. 저장소와 응답 메타데이터에는 SHA-256 다이제스트 및 만료만 남습니다. 전달 실패 시 일치하는 pending 다이제스트를 조건부 삭제하므로 전달되지 않은 코드가 용량을 소비하지 않습니다. Approval은 별도 authorized 행위자 및 기존 FDAI principal을 계속 요구합니다. Pairing은 신원 해석만 부여하며 역할을 부여하거나 조정기의 도구 RBAC을 우회하지 않습니다.

Cross-channel 신원 링크는 신원 병합이 아니라 pairing 위의 명시적 관계 기록입니다. 두 발신자는 같은 FDAI principal에 각각 독립 승인되어 있어야 하고 링크는 서로 다른 두 채널 종류를 연결해야 하며 별도 authorized 행위자가 승인해야 합니다. 발신자 대응이 서로 다른 두 principal을 가리키면 서비스는 쓰기 전에 요청을 거부합니다. 결정론적 링크 id로 재시도가 멱등적하고 PostgreSQL 기록은 어느 발신자 대응 또는 principal 역할도 변경하지 않으면서 재시작 후에도 유지됩니다.

채널 첨부는 instruction이 아니라 근거 입력입니다. Slack 및 Teams 어댑터는 범위가 제한된 파일 메타데이터와 불투명 벤더 id만 정규화하고 payload가 제공한 download URL은 버릴 수 있습니다. 현재 운영 A3 조립은 비공개 가져오기 도구나 보호 인제스트기를 연결하지 않습니다. 첨부 지원을 끄고 queue 유입 전에 422 attachments_unavailable을 반환합니다. FDAI_CHANNEL_ATTACHMENTS_ENABLED=1을 설정하면 버전이 지정된 문서 인제스트 전달이 연결될 때까지 시작에 실패합니다. 직접 queue 주입도 점유 또는 의미 게시 전에 실패합니다. 향후 가져오기와 인제스트는 원래 텍스트를 유지하고 준비된 doc: 인용만 허용하며 malware, protection, 인덱싱, 접근, 보존 및 에이전트 소유권 gate를 유지해야 합니다.

Teams 유입은 두 신원을 분리합니다. TeamsServiceTokenVerifier는 cached JWKS를 사용해 Bot Framework 서비스 토큰의 RS256 서명, 앱 대상, Bot Framework 발급자, 만료/not-before, 필수 serviceurl을 검증합니다. 그 다음 경로는 활동의 serviceUrl 및 channelId=msteams가 검증된 서비스 신원과 일치하도록 요구합니다. 이 검사를 통과한 뒤에만 ChannelPrincipalResolver가 활동 테넌트를 검증하고 from.aadObjectId를 범위가 제한된 구성된 정본 FDAI principal로 대응합니다. Service-token 실패는 401, 알 수 없음 테넌트 또는 user 연결은 403이며 둘 다 채널 큐에 도달하지 않습니다. 대화 게이트웨이가 턴을 보기 전에 벤더 id는 정본 principal로 교체됩니다.

운영 조립은 FDAI_TEAMS_APPLICATION_ID, 선택적 HTTPS 발급자/JWKS 재정의, FDAI_TEAMS_TENANT_ID, FDAI_TEAMS_PRINCIPAL_MAP_JSON을 읽습니다. 연결 지도는 비어 있지 않은 string-to-string JSON 객체이며 최대 1000 항목입니다. 누락된, malformed, unbounded 구성은 시작에서 실패합니다. Bot 서비스 토큰은 채널 서비스를 인증하며 운영자의 Entra principal을 대체하거나 FDAI 역할을 부여하지 않습니다.

ChannelEdgeRuntime은 standalone Operator-distribution edge process를 위한 구현된 library runtime입니다. Operator API process에 mount되지 않고 실행기 신원을 받지 않습니다. 운영 ASGI factory와 Terraform workload는 활성화된 범위 제한 유입 경로만 등록하고 adapter마다 Operator semantic-turn consumer 하나를 시작합니다. 자격 증명, Teams 신원, endpoint policy, JWT 구성, principal scope 또는 persistence가 누락되면 경로가 트래픽을 받기 전에 시작이 실패합니다. 운영 인제스트기 없이 첨부를 활성화해도 같은 경계에서 실패합니다. 종료는 queue, consumer, provider client 및 credential을 정확히 한 번 닫습니다.

채널 활성화에는 FDAI_CHANNEL_EDGE_ENABLED_CHANNELS를 사용하며 queue와 request 상한은 서버가 소유합니다. Container Apps native Key Vault reference는 secret 값을 source 또는 Terraform variable에 넣지 않고 Slack 및 Teams secret environment 값을 채웁니다. /health/live와 /health/ready는 content-free process 상태만 노출합니다.

OutboundResponse는 코어 코드에 Slack 또는 Teams 의존성을 주지 않고 벤더 중립적인 rich 전달 및 스레드 의도를 전달합니다. 기존 텍스트 회신이 기본값이며 scheduled 이어가기는 명시적 출처 또는 dedicated 스레드 모드와 opaque 기준점 id 메타데이터를 사용합니다. 응답은 범위가 제한된 mention과 rich 연산 하나를 추가할 수 있으며 모호하거나 큰 값은 게시 전에 차단됩니다. 읽기 관측 회신은 순서와 크기가 제한된 활동 순서도 전달할 수 있습니다. 인계는 대화 라우터인 Bragi와 책임 관찰기를 표시하고, 관찰된 실행은 관찰기, 정본 서버 도구, 정제된 명령, 안전한 결과 요약, 상태, 권한 및 시각을 기록합니다. 전체 순서는 영속 응답 재생에 포함되며 채널 어댑터에 도구 또는 실행 권한을 부여하지 않습니다. Per-field 한도 외에도 전체 활동 순서에 48,000자 예산을 적용하여 허용된 응답 하나가 영속 또는 벤더 페이로드 상한을 넘지 않게 합니다. Command, 출력, 라벨, 도구 및 권한 필드는 모두 같은 high-signal 시크릿 scanner를 통과하며, Bearer 자격 증명은 whitespace 및 일반적인 구분자 양식에서 차단됩니다. 영속 활동 decode는 type-strict하며 발행기가 응답을 받기 전에 ordered RFC 3339 시각을 검증합니다.

구체적인 발행기는 해당 의도를 다음과 같이 대응합니다.

발행기 전송 계층 및 확인 응답 처리는 pure하고 범위가 제한된 Slack 블록 키트 및 Teams Adaptive 카드 렌더링과 분리합니다. 이 분리는 wire 페이로드와 대체 경로 행동을 보존하고 벤더 표현을 독립적으로 테스트할 수 있게 합니다.

검증된 ChannelPresentationEnvelope 하나가 정본 텍스트, 버전이 있는 표현 산출물, 정확한 근거 참조, 제한 사항, 권한 및 선택적인 서버 소유 Web 링크를 전달합니다. 이 묶음만 ChannelPresentationRenderer에 입력할 수 있습니다. 렌더러는 범위가 제한된 벤더 페이로드와 완전한 읽기 가능 텍스트 대체 경로를 반환합니다. 근거를 가져오거나 도구를 실행하거나 요청을 승인하거나 사실을 바꿀 수 없습니다.

ChannelPresentationCapabilities는 텍스트 바이트, 직렬화 바이트, 블록 수, 필드, 액션, 이미지, 결정론적 sparkline, 편집, 스레드 및 진행 상황 지원 여부를 소유합니다. 코어 플래너는 벤더에 따라 분기하지 않습니다. 배포는 렌더러와 기능 프로필을 주입하며 알 수 없거나 실패한 렌더러는 정본 텍스트 대체 경로를 반환합니다.

화면Rich 렌더링차트 대체 경로반드시 유지할 내용
TeamsAdaptive Card FactSet, Container, ColumnSet검증된 이미지 렌더러가 없으면 텍스트 추세, 상위 사실, Web 액션제한, 근거 참조, 권한, 사용 불가 상태, 정본 텍스트 대체 경로
SlackBlock Kit section, fields, context, actions범위가 제한된 텍스트 요약, 검토된 결정론적 렌더러가 있을 때만 sparkline제한, 근거 참조, 권한, 사용 불가 상태, 최상위 text 대체 경로
사용자 지정주입형 렌더러와 기능 프로필렌더러가 선언한 범위가 제한된 대체 경로기본 렌더러와 같은 필수 내용 및 상한

대화 품질 보증은 독립 기능 프로필을 통해 이 변환 결과를 평가합니다. 정본 내용, 제한 사항, 근거 참조 및 권한 상태는 항상 적용합니다. 답변 준비 상태, 진행 상황, 활동 기록, rich 표현, 스레드 연속성 및 편집 연속성은 주입된 프로필이 지원을 선언할 때만 적용합니다. Direct Line과 사용자 지정 어댑터는 동일한 프로필 연결 지점을 사용합니다. 이 A3 표현 계약은 Direct Line 대화 전송을 구현하거나 Core에 벤더 분기를 추가하지 않습니다. A2/A4 Direct Line 호환 알림 전달은 다중 채널 알림 전달 § 9가 소유합니다.

렌더링은 사실을 골라 버리는 대신 손실 범위를 제한합니다. 읽기 가능한 대체 경로가 같은 정본 사실을 유지한 뒤에만 선택적인 시각 상세를 생략할 수 있습니다. 사용 불가를 0으로 바꾸거나 승인 또는 권한 경계를 삭제하거나 원시 산출물 JSON을 기본 답변으로 내보내지 않습니다. 전송 전에 프로바이더 바이트, 블록 및 필드 상한을 적용합니다. 상한을 넘으면 먼저 선택적 시각 상세를 제거하고 그다음 완전하며 범위가 제한된 텍스트 대체 경로를 사용합니다. 필수 내용만으로도 맞지 않으면 프로바이더 호출 전에 실패합니다.

순수 페이로드 builder와 가짜 사용자 지정 렌더러는 계속 독립적으로 테스트할 수 있습니다. Standalone Operator edge는 Slack 및 Teams builder를 인증된 유입, 고정 HTTP transport, 엄격한 확인 응답 및 영속 replay에 연결합니다. 통제된 프로바이더 및 배포 runtime 근거는 열린 작업으로 유지합니다.

행동SlackTeams텍스트 대체 경로
스레드 회신thread_ts를 사용하는 chat.postMessagereplyToId를 사용하는 메시지 활동같은 originating 스레드
Mention<@vendor-id><at> 텍스트 및 Bot Framework mention 개체@display-name; opaque 대상 id는 생략
스트리밍최초 chat.postMessage 후 cumulative chat.update최초 활동 POST 후 cumulative 활동 PUT최종 텍스트 회신 하나
편집선언된 메시지 id에 chat.update선언된 활동 id에 활동 PUTUpdate: 접두사가 있는 새 스레드 회신
Reaction인바운드 메시지에 reactions.add인바운드 메시지에 messageReaction 활동Reaction: 라벨이 있는 새 스레드 회신
에이전트 활동인계, plain-text 명령/결과, Bragi 답변 순서의 블록 키트 섹션; 게시, 스트림 갱신, 편집에서 같은 블록을 보존24,000-byte serialized 카드 예산 안의 같은 순서 Adaptive 카드 블록; multibyte 답변을 바이트 기준으로 제한하고 생략된 활동 수를 표시같은 에이전트 귀속 및 민감정보 제거 표시가 있는 범위가 제한된 텍스트

Slack은 알림 및 accessibility 대체 경로를 위해 top-level text 필드에 전체 활동 대체 경로를 유지합니다. 블록 키트는 각 활동을 자체 섹션에 한 번만 표시하고 최종 Bragi 섹션에는 정본 답변만 전달하므로 구조화된 클라이언트가 근거를 중복 렌더링하지 않습니다.

점진적 대화 전달은 완료된 답변을 인위적인 토큰 조각으로 나누는 대신 타입이 지정된 cumulative ChannelProgressUpdate 스냅샷을 사용합니다. 게이트웨이는 ordered 조정기 활동과 정본 최종 미리 보기에서만 스냅샷을 파생합니다. 개정 번호는 연속적이고 활동 개수는 감소하지 않으며, 마지막 스냅샷은 텍스트와 활동 개수가 영속 OutboundResponse와 정확히 일치할 때만 confirmed입니다. Slack과 Teams는 개정 번호 0을 한 번 게시한 후 동일한 acknowledged 메시지를 이후 개정 번호로 편집합니다. 관찰된 활동이 없는 응답은 최종 게시 하나로 유지됩니다. 스트리밍이 비활성화된 채널은 같은 정본 최종 텍스트 대체 경로를 전송합니다. Running 개정 번호에는 범위가 제한된 진행 상황 요약만 포함됩니다. 최종 confirmed 개정 번호에서 정본 민감정보가 제거된 활동 블록을 추가하므로 작업 진행 중 명령 및 출력 근거가 펼쳐지지 않으며 완료 후에는 확인할 수 있습니다. 운영 조립은 shared progressive-conversation 수집기를 두 발행기에 주입할 수 있습니다. 메시지, 대상 또는 신원 값을 보관하지 않고 집계 first-progress/confirmed 지연 시간, 잘림, 최종 완료 및 post-acknowledgement 모호함을 기록합니다. 잘림에는 벤더 필드 한도에 맞춰 잘린 출력과 Teams Adaptive 카드의 24,000-byte 예산을 지키기 위해 완전히 생략된 활동이 포함됩니다. 또한 4,000-character answer-block 한도에 맞춰 잘린 정본 Teams 답변과 mrkdwn escaping 후 2,900-character 블록 키트 섹션 한도에 맞춰 잘린 정본 Slack 답변도 포함됩니다. 메트릭은 잘림 발생 여부만 기록합니다. Omission 개수와 clipping 표시는 벤더 메시지에 표시됩니다. Multibyte JSON 인코딩이 Teams 카드 바이트 상한에 먼저 도달하면 같은 표시와 메트릭을 사용해 답변을 더 일찍 자릅니다. 원본 활동과 답변은 영속 응답 근거에 유지됩니다.

관찰된 출력은 명시적인 출처 이력 표시를 사용합니다. [UPSTREAM OUTPUT TRUNCATED]는 근거 생산자가 부분 출력을 제공했다는 뜻이고, [CHANNEL OUTPUT TRUNCATED]는 어댑터가 벤더 표현 한도에 맞춰 출력을 잘랐다는 뜻입니다. 두 조건이 모두 적용되면 두 표시를 함께 표시합니다.

구체적인 Slack 및 Teams 구성은 mention, 스트리밍, 편집, reaction 기능 플래그를 소유합니다. 비활성화된 기능이 코어에서 벤더 페이로드를 추측하게 하지 않으며 발행기가 문서화된 텍스트 대체 경로를 사용합니다. 모든 대체 경로에서 스레드 맥락을 보존합니다. 벤더 엔드포인트는 발행기 구성 또는 인증된 Teams 엔드포인트 해석기에 고정됩니다. 응답 데이터는 URL, 토큰, alternate API 메서드를 제공할 수 없습니다.

Accepted 전송은 요청한 연산, 벤더 메시지 id, 텍스트 성능 저하 여부를 포함하는 ChannelDeliveryReceipt를 반환합니다. Slack 게시는 ok=true 응답 및 메시지 시각을 요구합니다. Teams 메시지 생성은 Bot Framework 리소스 id를 요구합니다. 확인 응답이 누락되거나 malformed이면 전달을 보고하지 않고 전송을 실패시킵니다. 어댑터는 증적을 호출자에게 전달하며 전송 계층 실패는 계속 raise되어 기존 재시도/감사 경로를 따릅니다. Initial 게시가 acknowledged된 후의 갱신 실패는 모호한 중복 risk로 분류됩니다. 영속 전달은 완전한 스트림을 다시 시도하여 두 번째 메시지를 만들지 않습니다.

4.3 영속 회신 전달 및 어댑터 컨트롤

섹션 제목: “4.3 영속 회신 전달 및 어댑터 컨트롤”

외부 대화 회신은 durable-conversation-delivery-ko.md의 저장된 원장을 사용합니다. 프로바이더 HTTP 거절은 범위가 제한된 재시도가 가능한 definitive 실패입니다. 전송 계층 중단이나 확인 응답 누락 또는 형식 오류는 결과가 모호한 경우이며 자동으로 재시도하지 않습니다. Pause/재개 변경은 별도로 인증된 ChatOps 명령 경로에만 있고 콘솔에는 GET-only reliability 메트릭만 제공됩니다.

인증된 범용 웹훅은 TypedWebhookMapping을 명시적 선택할 수 있습니다. 대응은 하나의 허용 목록에 있는 정규화된 이벤트 타입 및 대상 에이전트를 구성에서 고정하고 페이로드가 제공한 이벤트, 에이전트, 명령, 세션 값은 이를 재정의할 수 없습니다. 서버가 소유한 dot 경로는 범위가 제한된 scalar 필드만 project합니다. 누락된 필드, 컨테이너, oversized 문자열, non-allowlisted 대상은 게시 전에 fail합니다. 범위가 제한된 세션 키는 명시적으로 선택된 scalar 값의 SHA-256 다이제스트이므로 raw 외부 신원 값이 세션 id가 되지 않습니다. Projected 이벤트는 여전히 event-ingest, trust 라우팅, risk gating, 감사에 진입하며 웹훅은 액션을 execute하지 않습니다.

명시적인 클라이언트 로컬 console-web 채널은 Console Web 알림이 소유합니다. Settings가 principal 및 브라우저 범위의 정식 선택 화면이고 헤더는 동기화된 바로가기입니다. runtime-observed 승인, 거부 및 실패 프레임만 알림 대상입니다. 범위가 제한된 로컬 표시·클릭 증적은 Core 전달, 승인 또는 실행 근거를 충족하지 않습니다. 조직에서 관리하는 A2/A4 경로는 별도의 channel-as-audience 바인딩으로 유지합니다. 서버 측 console-inbox A2 채널은 Console용 알림 레코드를 제한된 크기로 저장하는 별도의 Core 어댑터이며, https 전용 링크 규칙은 외부 채널의 것이므로 메시지 링크는 저장하지 않습니다. Console 알림함을 참고하세요.

  • A2/A4: NotificationChannel.send(NotificationMessage) -> DeliveryReceipt. NotificationMessage.category는 의미 경로 키이고 trust_tier가 A1-A4를 표현합니다.

  • A1: HilChannel.send(HilApprovalRequest) -> HilApprovalReceipt와 poll(receipt) -> HilResponse.

  • A3: ConversationChannelAdapter.receive() -> InboundTurn 및 send(OutboundResponse) -> ChannelDeliveryReceipt.

  • 어댑터는 절대 자체로 결정을 authorize 하지 않음. HilChannel.poll은 사용자가 클릭한 것을 반환; 코어 라우터가 그 raw 응답을 fdai-api로 넘기고, 그것이 유일한 권위 (아이덴티티 재검증, 재생 검사, 자기승인 없음).

  • 어댑터는 메시지 본문을 재스캔 해야 함 - 알려진 시크릿 패턴에 대해(CI 시크릿 스캐너가 쓰는 것과 같은 정규식 세트) 발송 전에 마지막 방어선으로.

  • 어댑터는 멱등 send를 구현 해야 함: 같은 correlation_id + audit_id + category로 재발행된 전송은 중복 포스트를 생성해선 안 됨.

  • Slack A1 아웃바운드 전송은 승인이 아닙니다. 전용 HilChannel은 고정된 HTTPS chat.postMessage 엔드포인트, 보호된 봇 토큰, 설정된 채널 ID와 공유 HTTP 클라이언트를 사용합니다. 크기가 제한된 Block Kit에는 fdai_hil_approve와 fdai_hil_reject 단추를 넣으며 값은 대기 중인 승인 ID뿐입니다. 카드에는 불투명한 전송 ID를 넣지만 브라우저 링크, 자원 상세 정보, 작업 해시, 역할 또는 행위자 주장은 넣지 않습니다. 클릭은 결정이 아니라 서명된 상호작용을 시작합니다. Operator 수신기는 검증된 Slack 묶음에서 행위자를 도출하고 새 Entra 재인증을 위한 서버 소유 Console 인계 URL을 돌려줍니다. 브라우저는 승인 권한을 제공할 수 없습니다. 수락하려면 Slack 응답의 ok=true, 크기가 제한된 메시지 시각, 설정된 채널이 모두 필요합니다. poll은 항상 PENDING을 반환합니다. Core가 소유한 요청 발신함은 HTTP 호출 전에 변경되지 않는 전송 식별자와 실제 렌더링된 본문의 다이제스트를 감사 기록과 함께 영속화합니다. 다이제스트는 단추 대상과 알림 재전송 ID까지 포함합니다. 최초 게시, 그룹 게시, 각각의 알림 재전송은 원자적으로 선점합니다. 설정된 채널에 대한 공급자의 수락 증적이 일치해야만 전달을 완료합니다. 확인 응답이 사라지거나 진행 중인 선점이 만료되면 재시작 후에도 영속 unknown 상태로 보류하고 무작정 재게시하지 않습니다. 아직 시도하지 않은 대기 요청은 원래 승인 대기 기록과 만료 시각을 다시 확인한 뒤에만 재시작 후 게시할 수 있습니다. Slack의 권위 있는 조회 증거나 실제 Slack에서 Entra 브라우저로 이어지는 인증 증적은 아직 없으며 이슈 #943에 남아 있습니다. 설정이나 게시만으로 승인 또는 실행 권한이 높아지지 않습니다.

  • A2/A4 기능 상태, 사전 렌더링 표현, 관찰 모드 전달 및 공급자 고유 rich 카드 payload는 다중 채널 알림 전달 § 8이 상세히 소유합니다.

  • A1 결정 반환: 정확한 Teams와 Slack 전송, 행위자, 맥락, 워크플로 라우팅, 감사 및 영속 전달 계약은 Operator 승인 콜백에서 소유합니다.

5.1 오디언스 파생 (channel-as-audience)

섹션 제목: “5.1 오디언스 파생 (channel-as-audience)”

라우터는 수신자 목록을 사용자별로 파생하지 않습니다. 각 채널 자체가 수신 대상이며, 멤버십은 컨트롤 플레인 밖에서 관리합니다. 일반적으로 채널을 Entra 보안 그룹에 연결합니다.

  • 기본 (옵션 A): Teams 채널/DL은 aw-* Entra 보안 그룹으로 백업된 group-connected team으로 생성. 멤버십은 Entra에서 자동 sync (“Owner가 Portal에서 aw-approvers에 사람 추가” → 그들은 즉시 다음 다이제스트와 모든 A1/A2/A3 포스트 보게 됨). 이는 관리를 하나의 표면에 유지 (user-rbac-and-identity-ko.md §4.2).
  • In-message @mentions은 채널 포스트 안에서 아티팩트-소유자를 호출하는 선언된 설계(예: 만료되는 exemption의 요청자)이며, 감사 스트림이 이미 운반하는 아티팩트 메타데이터 (requested_by, PR 작성자, 룰 작성자)에서 파생 - 다이제스트 시점에 Graph 조회 없음 - 하지만 matrix 로더는 이 모드를 아직 시행하지 않음.
  • 롤-파생 direct messaging은 break-glass 사용 요약에만 쓰이는 선언된 설계(채널 포스트로 충분하지 않은 작고 시간-임계 오디언스); 다른 모든 A4 다이제스트는 채널 전용으로 남고, 이 모드 역시 matrix 로더가 아직 시행하지 않음.

다이제스트 엔트리의 허용 오디언스 모드:

모드의미허용 위치
channel: <id>채널/DL에 포스트; Entra 그룹 바인딩으로 멤버십 관리A2, A3, A4 (기본)
mention-artifact-owner추가적: 채널 포스트 안에서 아티팩트 소유자 @mention선언된 설계; matrix 로더에서 아직 시행되지 않음
role-dm: <RoleName>aw-<role> 멤버 Graph 조회, 각각 DM선언된 설계; matrix 로더에서 아직 시행되지 않음

5.2 능동적 이해관계자 브리핑 (A4 종합)

섹션 제목: “5.2 능동적 이해관계자 브리핑 (A4 종합)”

조직에서는 경영진을 위해 “이번 구간에 무슨 일이 있었고, 어떻게 대응했으며, 어디에 위험이 있는가”를 정기적으로 요약합니다. core/notifications/briefing.py의 StakeholderBriefingComposer는 개별 이벤트의 잡음이 아니라 집계된 운영 수치로 A4 다이제스트를 결정론적으로 작성합니다. 심각도별 인시던트 수, auto / 사람 승인 / rolled-back / 관찰 모드-only로 구분한 액션 결과, 비용 추세 변화와 요인, 향후 예측 위험, 가드 메트릭 위반을 사용합니다.

  • 실패 시 차단하며 사실을 만들어 내지 않습니다. 작성기는 호출자가 제공한 감사 로그와 KPI 텔레메트리에서만 수치를 가져오며, 전달받지 않은 내용은 주장하지 않습니다. 대응할 활동이 없는 구간에는 “중요한 운영 활동 없음”이라는 제목과 has_significant_activity = False를 표시합니다. 호출자는 아무 일도 없었다는 이메일을 경영진에게 보내는 대신 전송을 억제합니다. 다른 활동 없이 비용만 1% 미만으로 변동했다면 브리핑 대상이 아닌 잡음으로 취급합니다.
  • 가드 위반은 에스컬레이션합니다. 경영진이 놓치면 안 되는 가드 메트릭 위반은 결과의 명시적 escalations와 브리핑 본문에 표시합니다. 호출자는 이를 더 높은 신뢰 계층으로 라우팅할 수 있습니다(goals-and-metrics.md).
  • 순수 함수이며 전달 방식에 종속되지 않습니다. 작성기는 벤더별 지식이 없고 직접 전달하지 않습니다. Markdown 본문과 섹션별 페이로드가 있는 StakeholderBriefing을 반환하며, 호출자는 위의 수신 대상 모드로 A4를 전달하도록 §6의 라우터에 넘깁니다. 같은 입력은 같은 브리핑을 만듭니다.

라우팅은 선언형 구성으로 정의하고 channel-router가 평가합니다. 채널 추가, 교체, 순서 변경은 코드가 아니라 구성을 변경해 수행합니다.

구성 위치: 아웃바운드 라우팅은 config/notifications-matrix.yaml에 있습니다. 라우팅 변경은 거버넌스 변경처럼 리뷰하며 A1 경로 변경에는 Owner-tier 검토가 필요합니다. 신뢰된 신원 attestation은 A1 라우팅을 바꿀 수 있는 변경에만 필요하며, A2~A4 라우트를 추가하거나 바꾸는 변경은 일반 리뷰를 따릅니다(규칙 거버넌스). 라우터는 A1이 아닌 라우트로 A1 메시지를 전달하지 않고 사람 승인 싱크로 에스컬레이션하므로, 새 라우트가 결정이 걸린 트래픽을 가로챌 수 없습니다. 대화 채널 활성화는 별도 환경/구성 계약을 사용합니다.

matrix:
version: 1
default_route: hil_approval
routes:
hil_approval:
trust_tier: a1_hil_approval
primary: teams-hil-prd
fallback: [teams-hil-standby, slack-hil-prd]
on_all_fail: hil_escalate
operational_alert:
trust_tier: a2_operational_alert
delivery_mode: fanout
channels: [teams-ops-prd, pagerduty-primary, email-oncall]
on_all_fail: hil_escalate
digest_shadow_accuracy_daily:
trust_tier: a4_digest
delivery_mode: fanout
channels: [teams-hil-prd, email-governance]
on_all_fail: hil_escalate

라우터 규칙 (MUST)

  • 카테고리 ⊆ 채널.categories - 라우터는 선언된 카테고리에 메시지 카테고리가 포함되지 않은 채널로 메시지 전송을 거부. 시작 구성 검증이 허용되지 않은 카테고리와 채널을 짝지은 라우팅 엔트리를 거부(deny-by-default; fail fast).
  • 대체 경로 시 신뢰 보존 - A1 기본 → A1 대체 경로만. 대체 경로에서 더 낮은 신뢰 레벨로 다운그레이드는 config-load 에러.
  • 인시던트 전달 준비 상태 - non-local control-plane 런타임은 operational_alert 경로에 A2 trust 계층을 지원하는 등록 채널이 없으면 시작에 실패합니다. 명시적 로컬 Azure CLI 프로파일은 외부 어댑터 없이 시작할 수 있지만 구조화된 notification_route_unavailable 수명 주기 기록을 INFO로 기록하고 실패 시 차단 사람 승인 에스컬레이션을 유지합니다.
  • 인시던트 심각도 에스컬레이션 - 커밋된 단조 증가 심각도 상향은 A2 severity_changed notice를 한 번 발행하며 고정된 감사 id가 immediate 전달과 시작 재생을 deduplicate합니다.
  • role-dm과 mention-artifact-owner는 선언된 설계일 뿐, 아직 시행되지 않습니다. matrix 로더(core/notifications/matrix.py)는 오늘날 선언된 채널, 신뢰 계층, on_all_fail/default_route 구조만 검증하며, break_glass_usage_summary로 제한된 role-dm deny-list와 mention-artifact-owner 메타데이터 필드 검사는 현재 config-load 동작이 아니라 미해결 후속 작업으로 남아 있습니다.
  • 범위가 제한된 재시도 - 각 어댑터는 자체 재시도 예산을 선언; 라우터는 소진 시 다음 채널 또는 on_all_fail로 escalate.
  • 영속 A1 결정 - fdai-api는 정규화한 승인자, 결정, 증적을 이벤트 버스에 게시하기 전에 기록합니다. 게시에는 설정된 시도 개수, per-attempt 시간 초과, 제한된 재시도 대기를 적용합니다. 성공하면 delivered를 체크포인트하고 resolved 항목을 pending 큐에서 제거합니다. 같은 행위자와 결정의 재시도는 미전달 증적을 이어서 보내거나 이미 전달된 증적을 다시 게시하지 않고 반환합니다. 다른 행위자 또는 결정은 충돌로 처리합니다. 시작 및 주기적 복구는 사람의 추가 작업 없이 미전달 증적을 배출합니다. 전달 시도는 영구 저장되며 설정된 상한에서 abandoned가 됩니다. 최종 전달 상태는 이전 상태로 돌아가지 않습니다. HilDecisionRecoveryConfig가 복구 간격, 시도별 게시 타임아웃, 전달 시도 상한을 생성자 기본값으로 설정하며, 복구 루프는 오늘날 운영 조립에서 환경 변수 재정의 연결을 갖지 않습니다.
  • 비율 정책은 배포가 소유 - 테넌트별 승인자 비율, quiet 시간, fatigue 제한은 인증된 유입과 라우팅 구성에 둡니다. 레지스트리 멱등성, 만료, 정족수, no-self-approval 검사를 약화하지 않습니다.
  • Approval 부하 계획은 작업을 폐기하지 않음 - 모든 A1 요청은 versioned 부하 정책이 (config/approval-load.yaml, 선택적 FDAI_APPROVAL_LOAD_POLICY) send_now, deferred, grouped 전달을 선택하기 전에 영속하게 보류됩니다. Critical 심각도는 항상 quiet 시간과 그룹화를 우회합니다. Non-critical 요청은 결정론적 액션 및 담당자 그룹 구간을 공유하거나 quiet 시간 종료까지 기다릴 수 있지만, 모든 구성원은 승인 큐에서 독립적으로 확인하고 결정할 수 있습니다. 담당자 fatigue 상한은 그룹의 알림 모드만 바꾸며 parking을 막거나 승인을 의미하지 않습니다. 그룹 구간 또는 quiet 시간 경계에서 런타임은 고정된 그룹 전달 id 하나를 점유하고 현재 pending 구성원 개수를 포함한 기준점 다이제스트 하나만 보냅니다. Deferred 그룹과 구성원은 같은 점유를 사용하므로 복제본 간 중복 initial 카드를 만들 수 없습니다. Initial 전달과 reminder는 서로 다른 메타데이터 및 감사 종류를 사용합니다.
  • Reminder는 범위가 제한된 시도 - 정책은 원래 승인 만료 이전의 결정론적 오프셋을 선언합니다. 런타임 워커는 복제본 전체에서 각 reminder id를 한 번 점유하고 기존 A1 채널을 시도합니다. 성공과 실패를 모두 감사하며 TTL을 연장하거나 pending 항목을 제거하거나 선언된 reminder 개수를 넘어 재시도하지 않습니다. Policy 시뮬레이션은 critical 요청이 defer/그룹되지 않고 입력 요청이 하나도 사라지지 않음을 증명해야 합니다.
  • 만료 spam 없는 TTL 실패 시 차단 - 런타임 워커는 만료된 pending 보류를 최종 timeout으로 atomically 변경하고 복제본 전체에서 감사 항목 하나만 덧붙이기합니다. Late 콜백은 기존 시간 초과를 반환하며 실행하지 않습니다. 부하 controller는 만료된 항목마다 A2 메시지를 보내지 않습니다. 알림 계층은 감사 신호를 범위가 제한된 A2/A4 요약으로 집계할 수 있습니다 (security-and-identity-ko.md).
채널노트
TeamsA1에 Adaptive Cards를 사용하고 OAuth 범위를 최소로 유지합니다(ChannelMessage.Send.Group + 봇 신호). SSO + OBO는 user-rbac-and-identity-ko.md §10.4를 따릅니다. aw-* Entra 보안 그룹이 뒷받침하는 그룹 연결 팀에 FDAI_TEAMS_APPROVAL_TEAM_ID, FDAI_TEAMS_APPROVAL_CHANNEL_ID, HTTPS FDAI_TEAMS_APPROVAL_ACTIVITY_URL, 전용 FDAI_TEAMS_BOT_MI_CLIENT_ID를 함께 구성합니다. Core는 실행 신원이 아닌 이 Bot 신원으로 전송하고 teams:<team-id>:<channel-id> 대상을 카드에 넣으며 Operator는 같은 값을 검증합니다. Incoming Webhook은 Action.Execute 콜백을 전달할 수 없으므로 A1에서 지원되지 않습니다. 수신기에는 FDAI_TEAMS_APPLICATION_ID, FDAI_TEAMS_TENANT_ID, FDAI_TEAMS_ALLOWED_SERVICE_URLS_JSON, FDAI_TEAMS_JWKS_URL, FDAI_TEAMS_PRINCIPAL_MAP_JSON, 공유 콜백 시크릿, 구성된 사람 승인 결정 토픽과 영속 outbox도 필요하며 모든 입력이 갖춰질 때까지 Teams A1은 닫혀 있습니다.
SlackA2/A3와 범위가 제한된 A1 단추에 Block Kit을 사용합니다. chat:write 게시에는 FDAI_SLACK_APPROVAL_API_URL(정확한 https://slack.com/api/chat.postMessage URL), FDAI_SLACK_APPROVAL_CHANNEL_ID, 보호된 FDAI_SLACK_APPROVAL_BOT_TOKEN이 필요합니다. Slack 전용 아웃바운드 구성에는 Teams 봇 신원이 필요하지 않습니다. 게시하거나 단추를 누르는 것만으로는 승인되지 않습니다. 서명된 상호작용 수신기에는 구성된 작업 영역, 사용자 ID와 OID의 매핑, 승인된 Console 출처 한 곳이 필요합니다. 브라우저 결정에는 새로 로그인한 시각을 담은 서명된 Entra API 토큰 auth_time이 필요합니다. Core는 Slack A1 request outbox를 report/review outbox와 별도로 감독하므로 전달 재시도가 승인으로 바뀔 수 없습니다. 연결 정보가 없으면 결정을 보류하며 실제 환경의 클릭부터 결정까지의 증적은 여전히 필요합니다.
이메일Azure Communication Services 이메일을 통한 send-only 채널입니다. 승인 링크는 포함하지 않고 다이제스트와 알림만 전달합니다. 어댑터는 모든 메시지에 plainText를 보내고 notice_kind=opened일 때 범위가 제한된 HTML을 추가합니다. 인시던트 템플릿은 인시던트 id, 상태, 심각도, opened 시간, 집계 구성원 개수, 배정 상태, audit_id 및 HTTPS Console 링크만 사용합니다. 상관관계 키, 리소스 페이로드, 행위자 신원 또는 free-form 사유는 렌더링하지 않습니다. Terraform은 Azure-managed 발신자 도메인과 Communication Services 리소스에 범위가 제한된 전용 알림 managed 신원을 프로비저닝합니다. FDAI_CONSOLE_BASE_URL이 Console 출처를 제공하며, 값이 없거나 완성된 링크가 absolute HTTPS가 아니면 렌더러는 CTA를 생략합니다. 어댑터는 단기 https://communication.azure.com/.default 토큰을 요청하고 프로바이더 연산이 Succeeded가 될 때까지 기다린 후 프로바이더 메시지 id를 기록합니다. Settings > Integrations는 합성 자리 표시자만 사용하는 인증된 GET으로 동일한 렌더러를 가져옵니다. 권장 수신자는 aw-approvers / aw-owners를 미러링하는 Entra 동적 분배 그룹입니다.
범용 웹훅HMAC-SHA256 서명, 단조 타임스탬프, 단발 nonce. Receiver 실패는 절대 블록 안 함; 코어가 어댑터 정책대로 재시도 후 이동.
PagerDuty / OpsgenieDedup 키 = observability 상관 id 이므로 버스트가 접힘. 런북 URL은 모든 알림에 필수.
SMS페이로드는 <severity> <audit_id> <short-url-to-runbook>로 제한. 시크릿 없음, 고객 이름 없음, 자유 텍스트 없음. 주로 break-glass 도달성.
Direct Line 호환 사용자 지정 채널배포가 소유한 중계기에 A2/A4 activity만 보냅니다. 기본 모드는 리허설이며, 중계기 수락은 전달 완료로 보지 않고, 승인, 명령, 답장은 사용할 수 없습니다. 업스트림은 외부 Direct Line 환경이 아닌 loopback 예제 CLI로 계약을 검증합니다. 다중 채널 알림 전달 § 9를 참조하세요.

7.1 Teams 준비 상태는 카테고리별로 보고합니다

섹션 제목: “7.1 Teams 준비 상태는 카테고리별로 보고합니다”

Teams에는 소유자가 다른 네 가지 계약이 있습니다. A1 계약은 발송과 callback 소유자가 분리되므로 Settings > Integrations는 하나의 집계된 “ChatOps” 상태 대신 다섯 개의 독립된 행을 보고합니다. 준비 상태는 한 런타임에서 관찰한 구성 완전성을 의미하며 프로바이더 상태 주장이 아닙니다.

행소유 런타임준비 조건
teams-a1-approval-sendCore 컨트롤 플레인Bot 액티비티 endpoint와 그룹 연결 팀 및 채널이 모두 존재
teams-a1-approval-callbackOperator ServiceBot 애플리케이션, 테넌트, JWKS, 허용 service URL, 주체 매핑, 콜백 시크릿, 사람 승인 결정 토픽, 영속 outbox가 모두 존재하고 구조가 유효
teams-a2-operational-alertCore 컨트롤 플레인a2_operational_alert를 선언한 Teams Workflows 바인딩이 활성화되고 endpoint가 초기 placeholder가 아님
teams-a4-digestCore 컨트롤 플레인a4_digest에 대해 동일
teams-a3-conversationOperator Service대화 채널 edge가 Teams를 활성화하고 신원 입력이 존재

일부만 구성된 상태는 성능 저하가 아니라 불완전입니다. 절반만 구성된 승인 경로는 조용히 축소되는 대신 승인을 사용할 수 없게 둡니다. 각 행은 소유 런타임과 observed 플래그도 함께 전달합니다. 한 프로세스가 만든 projection은 다른 프로세스의 입력을 볼 수 없기 때문입니다. 관찰되지 않은 행은 “구성되지 않음”이라고 주장하지 않고 “여기서 관찰되지 않음”으로 보고하여 운영자가 실제로 전제 조건을 소유한 런타임을 수정하도록 합니다.

8. 대체 경로와 비상 정지 상호작용

섹션 제목: “8. 대체 경로와 비상 정지 상호작용”
  • 글로벌 비상 정지는 모든 A1 전달을 즉시 중단하고 열린 A1 요청을 재큐잉; 비상 정지 상태 자체는 모든 운영 채널에서 A2로 공지.
  • 모든 A2 채널이 다운 이면, 어댑터 헬스 원격측정은 여전히 관측성에 랜딩하고 콘솔에 나타남; 비상 정지는 전용 break-glass 경로를 통해 조작 가능 (security-and-identity-ko.md).
  • 어댑터 불건강 자체는 A2 신호 - A1 딜리버리를 중단한 Teams 장애는 대체 경로 채널을 통해 운영 라인을 페이지.
항목상류 (이 리포)포크
세 프로바이더 계약과 메시지/증적 타입✓-
Teams 어댑터A1과 A2/A4 구현됨; A3 계획됨테넌트 / group-connected 팀 바인딩
Slack A1 콜백 및 아웃바운드서명된 브라우저 인계와 영속 단추 전달까지 로컬 구현, 연결 정보가 불완전하면 기본 비활성화작업 영역 자격 증명, 승인된 Console 출처 한 곳, API 토큰 auth_time, userId↔OID 매핑(필수)
ACS 이메일 어댑터✓ (A2/A4, managed 신원, 최종 상태 polling)수신자 바인딩 + 활성화
웹훅 / PagerDuty / SMS 어댑터✓ (구체적인 전달 어댑터)자격증명 + 활성화
Direct Line 호환 사용자 지정 채널 어댑터✓ (A2/A4, 기본값 리허설, 수락만 기록하는 증적, loopback 예제 CLI)운영 중계기, 대화, 시크릿, governed 활성화
라우팅-config 스키마 + 시작 검증✓배포별 연결/오버레이
사람 승인 에스컬레이션 싱크 (on_all_fail fail-safe 큐)✓ (StateStoreHilEscalationSink - StateStore 기반, tenant-무관)자체 큐 백엔드(선택)
7개 기본 다이제스트 + 오디언스 파생 규칙✓cron 타임존, 채널 id, 다이제스트별 on/off
Secret-scan 정규식 세트(어댑터가 재사용)✓필요 시 패턴 확장
Slack userId ↔ Entra OID 매핑 인터페이스✓매핑 데이터(P1 A1에 필수)
다이제스트 컨텐트 템플릿✓ (범용)브랜딩 / localization
  • 어댑터-헬스 알림 임계와 dedupe 윈도우.
  • 들어오는 웹훅 범위 - 인증된 TypedWebhookMapping만 허용 목록에 있는 이벤트/에이전트 대상을 publish하며 페이로드는 명령, 대상, 세션을 선택할 수 없습니다.
  • 아티팩트 소유자가 게스트 사용자일 때의 mention-artifact-owner 동작 (Teams에서 멘션은 여전히 해석하지만, 정보 유출을 줄이기 위해 다이제스트가 억제하거나 다르게 라우팅해야 하는가?).
  • kpi_and_cost_monthly GitHub-Issue 아카이브: 대상 리포/경로 (기본은 catalog-as-code 리포, docs/kpi-archive/).

알림은 L2 제품 표면입니다(참고: language.instructions.md): 소스 문자열은 영어이며 채널은 이를 다른 로케일로 렌더링할 수 있습니다.

  • 렌더링 방식(옵션 C). core는 최종 현지화 문자열을 하드코딩하지 않습니다. 모든 NotificationMessage는 template_key와 타입이 지정된 params를 전달하며, 라우터가 send 직전에 대상 채널의 로케일로 카탈로그 (services/core-control-plane/src/fdai/core/notifications/messages.{en,ko}.json)에서 title / body_markdown을 렌더링합니다. 어댑터는 바꾸지 않으며 여전히 title / body_markdown을 사용합니다.
  • 레이블만 현지화합니다. L0 값(결정 값, 규칙 ID, 리소스 유형, 모드)은 모든 언어에서 원문 그대로 대입하므로 기계가 읽는 데이터는 동일합니다. 감사 항목은 항상 영어 메시지를 사용하므로 재생과 상관관계는 언어에 종속되지 않습니다.
  • 영어 폴백은 필수. 로케일 키/필드가 없으면 영어 소스를, 영어 키마저 없으면 키 자체를 렌더링합니다. 빈칸으로 표시하지 않습니다.
  • 로케일은 채널 속성입니다. 알림은 여러 채널로 동시에 전달하므로 운영자별이 아니라 config/notifications-matrix.yaml의 matrix.channels에서 채널별로 로케일을 설정합니다 (<channel-id>: { locale: ko }). 항목이 없는 채널은 영어로 렌더링합니다.
알아볼 내용읽을 문서
구현 상태 및 남은 작업구현 원장