External Effects
서버 전용 외부 작업 기록, 설치 암호문 보관과 고객 연결.
External Effects
명시된 호출자 환경에서만 사용하세요. 먼저 자격 증명을 설정하세요. 패키지 surface는 SDK README가 소유합니다.
아래 operation에는 활성화되고 admission을 통과한 production-purpose workspace와 각 API의 명시적 server-key binding이 필요합니다. Test tenant에는 실행 가능한 외부 작업 기록·설치 암호문 보관·외부 Customer 연결을 허용하지 않습니다. 운영자에게 실제 제공 여부를 확인하세요. 공개 SDK 메서드가 있다는 사실은 test 환경의 실행 권한이 아닙니다. 허용 환경은 tenant-only 또는 current-ready Live뿐입니다. environmentKind: 'sandbox'의 정책 결과는 capture이며 이 작업의 허용을 뜻하지 않습니다. Sandbox와 test tenant는 지원하지 않습니다.
외부 작업 기록 (서버 전용)
createServerClient().externalEffectClaims는 쿠폰 발급이나 외부 고객 등급 변경처럼
워크스페이스 서버가 실행하는 작업의 중복 방지 상태를 기록합니다. 관리자가 연동을 등록하고
활성 sk01_ 키를 연결해야 사용할 수 있습니다. 워크스페이스는 서버 키로 결정되며 요청
본문에는 tenantId가 없습니다. 작업 기록은 제공자 실행 사실의 증명이나 실행기가
아닙니다.
직접 HTTP 호출에서는 연결된 서버 키만으로 인증할 수 있고, 일치하는 공개 키를 함께
보낼 수 있습니다. SDK의 createServerClient 설정에는 두 키가 모두 필요합니다.
import { createServerClient } from '@01.software/sdk/server'
type Server = ReturnType<typeof createServerClient>
export async function armExternalEffect(
server: Server,
input: Parameters<Server['externalEffectClaims']['claim']>[0],
) {
const claim = await server.externalEffectClaims.claim(input)
if (!claim.leaseGranted || !claim.leaseToken)
return { claim, armed: false as const }
await server.externalEffectClaims.arm({
integrationId: input.integrationId,
claimId: claim.id,
leaseToken: claim.leaseToken,
leaseGeneration: claim.leaseGeneration,
})
// Only this acknowledged branch may dispatch the original safe provider operation.
return { claim, armed: true as const }
}논리 키, 요청 지문, 복구용 식별자는 해시해 전달하세요. 제공자 비밀, 쿠폰 코드,
고객 데이터, 요청·응답 원문은 워크스페이스 서비스에 보관합니다. arm 응답이나 제공자
응답이 유실되면 같은 작업의 안전한 재생 키 또는 권위 있는 조회로 확인해야 합니다.
armed_unknown은 새 호출 권한을 주지 않습니다. report에는 비밀을 제외한 증거의
SHA-256이 필요합니다. no_effect 보고에는 원래 작업 식별자 또는 제공자 작업 ID의
SHA-256도 필요합니다. 둘 다 없다면 미확정 상태로 두고 운영자가 대조해야 합니다.
보고는 여전히 워크스페이스가 제출한 증거이며 제공자 사실을 콘솔이 검증한 것이 아닙니다.
recovery는 원래 requestFingerprintSha256을 요구하며 조회
또는 동일 작업의 안전한 재생 관찰만 기록합니다.
결제·환불·Membership 청구·외부 웹훅 배송은 각 도메인 원장을 사용합니다. 월간
한도는 새 논리 작업 기록에만 적용되며 기존 작업 조회와 결과 보고는 허용됩니다.
데이터베이스 권한 저장소의 확인된 장애는 HTTP 503과 authority_unavailable,
예상치 못한 실패는 HTTP 500과 internal_error를 반환합니다. SDK 오류에는
getExternalEffectClaimErrorReason(error)로 알려진 작업 오류 사유를 확인할 수
있습니다. 운영자는 콘솔에서 정확한 퇴역 워크스페이스의 과거 미확정 증거를 대조할 수
있으며, 이 작업은 새 claim이나 arm 권한을 열지 않습니다.
설치 자격 암호문 보관 (서버 전용)
createServerClient().installationCredentials는 비결제 OAuth 설치 한 건의
제한된 암호문을 보관합니다. 워크스페이스 관리자가 설치를 등록하고 해당 서버 키에
별도 권한을 부여해야 합니다. 일반 sk01_ 키나 외부 작업 claim 권한만으로는
암호문을 읽을 수 없습니다.
import { createServerClient } from '@01.software/sdk/server'
type Server = ReturnType<typeof createServerClient>
export async function replaceInstallationCredential(
server: Server,
command: Parameters<Server['installationCredentials']['replace']>[0],
) {
const replacement = await server.installationCredentials.replace(command)
const readback = await server.installationCredentials.get({
installationId: command.installationId,
})
return { replacement, readback }
}암호화와 복호화는 워크스페이스 서버에서만 수행합니다. OAuth callback, 토큰 갱신,
외부 서비스 호출과 그쪽의 폐기도 워크스페이스 서버가 맡습니다. 교체 응답을 잃었다면
재시도 전에 다시 읽어 버전을 확인하세요. dispose는 플랫폼 사본을 지우지만
이미 서버가 읽은 자격까지 회수하지는 못합니다. 데이터베이스 복원 후에는
기존 설치를 다시 인가해야 읽기를 재개할 수 있습니다.
교체 내용을 검토하기 전에 server.installationCredentials.get({ installationId })로 읽고, 그 expectedVersion과 새 ciphertext가 담긴 command를 보존하세요. 위 함수에 그 command를 그대로 전달하며 실패 뒤 version을 조용히 갱신하지 마세요.
외부 Customer 연결
신뢰할 수 있는 서버 코드에서 server.externalCustomerLinks로 플랫폼
Customer와 외부 Customer ID를 연결합니다. 활성 integration 등록에서
namespace와 environment를 도출합니다. 앱이 정규화한 안정적인
accountScope는 신원을 예약하는 값입니다. namespace에 바인딩된 key는 해당
namespace의 모든 계정에 권한을 가지며 account scope는 계정별 ACL이 아닙니다.
Customer 읽기/쓰기 권한도 필요합니다. 브라우저 client에는 이 API가 없습니다.
import { createServerClient } from '@01.software/sdk/server'
type Server = ReturnType<typeof createServerClient>
export async function connectCustomer(
server: Server,
input: Parameters<Server['externalCustomerLinks']['connect']>[0],
) {
const link = await server.externalCustomerLinks.connect(input)
return server.externalCustomerLinks.get({
integrationId: input.integrationId,
customerId: input.customerId,
linkId: link.id,
})
}
export async function captureCustomerPreviousState(
server: Server,
command: Parameters<
Server['externalCustomerLinks']['capturePreviousState']
>[0],
) {
const receipt =
await server.externalCustomerLinks.capturePreviousState(command)
const readback = await server.externalCustomerLinks.get({
integrationId: command.integrationId,
customerId: command.customerId,
linkId: command.linkId,
})
return { receipt, readback }
}get으로 연결 하나를 조회합니다. list는 선택적인 customerId,
accountScope, externalCustomerId, state 필터(외부 ID 필터에는 account scope
필수)와 limit(기본 25, 최대 100), afterId를 받습니다. 다음 페이지는 nextCursor를 afterId로 전달합니다. unlink,
capturePreviousState, clearPreviousState는 요청 UUID와 expectedVersion이
필요합니다. 응답을 받지 못했다면 최초 version을 포함한 같은 요청으로 재시도하세요.
이전 상태는 평평한 string/number/boolean/null 필드 최대 16개, 문자열당
최대 512자, UTF-8 JSON 최대 2048바이트로 제한됩니다. 플랫폼은 외부 provider를
호출하지 않습니다. 외부 상태를 복원하기 전에
list({ integrationId, accountScope, externalCustomerId, state: 'active' })로
현재 활성 소유자를 조회하고 복원이 여전히 적절한지 판단하세요. 개인정보가 삭제된 응답은
상태가 erased이고 externalCustomerId와 previousState는 null입니다.
input.requestId는 connect 전에 생성·보존합니다. 반환된 link를 검토한 뒤 integrationId, customerId, linkId, 원래 expectedVersion, 새 request UUID, 제한된 previousState(예: { tier: 'silver' })를 담은 별도 capture command를 보존하세요. 그 command를 captureCustomerPreviousState에 그대로 전달하며 connect replay에서 version을 다시 만들지 마세요. Connect replay는 link의 현재 상태를 반환하고 capture replay는 원래 command/receipt를 사용합니다.