import type { RefusalCode, RefusalParams } from '@operato/ops-contract'; import { Domain } from '@things-factory/shell'; /** 조치 검증 상태 — 「아직 안 해 봤다」는 실패가 아니라 그 자체로 하나의 사실이다. */ export type ActuationState = 'unverified' | 'verified' | 'failed'; /** 마지막 거절 — 언어 중립 코드와 그 값들. 사람 문장은 담지 않는다. */ export interface ActuationFailure { code: RefusalCode; /** 거절된 시각 (ISO 8601). */ at: string; params?: RefusalParams; } export declare class TwinReference { readonly id: string; domain?: Domain; domainId?: string; source: string; system?: string; siteName?: string; description?: string; adapterType: string; /** * 이 연결에 필요한 값 — **자격 증명이 여기 들어온다. 그래서 저장될 때 암호화된다.** * * ── 왜 표에 두나 (2026-09-09) ───────────────────────────────────────────── * 비밀값은 환경에 두고 이름으로 고르는 것이 원칙이다(§`webhookSecretCandidates`). 그 원칙은 * **설치본마다 하나인 키**에 맞는다 — 배포할 때 넣으면 된다. * * 트윈의 연결은 그 모양이 아니다. **사람이 화면에서 연결을 만든다.** 새 공장을 붙이려고 재배포할 * 수는 없으므로, 연결마다 다른 이 값은 표에 있어야 한다. 그 자리에서 원칙이 갈린다: * * 설치본마다 하나인 키 환경변수. 이름이 어느 상대 것인지 말한다 * 연결마다 다른 값 표. 대신 저장될 때 암호화한다 * * ── 무엇을 지키고 무엇을 못 지키나 ──────────────────────────────────────── * 저장된 것을 지킨다 — DB 파일 · 백업 · 복제본. 2026-09-09 에 이 칸에서 `hookSecret` 과 접속 * 토큰을 명령 두 개로 읽었다. * * **프로세스를 돌릴 수 있는 사람에게서는 못 지킨다** — 그 사람은 키를 갖고 있다. 그리고 값이 * 화면으로 나가는 것도 이 칸이 막지 않는다: 내보내는 쪽이 어댑터 스키마의 `secret: true` 를 보고 * 가린다(§`reference-resolver` 의 상세 조회). **두 가지가 갈려 있으므로 한쪽만 하면 반쪽이다.** * * 기존 평문 행은 안 깨진다 — 변환기가 암호문 모양이 아닌 값을 알아보고 파싱하며 경고를 남기고, * 다시 저장될 때 암호화된다. */ connectionConfig?: any; mappingSpec?: any; scopeSpec?: any; status?: string; lastSyncedAt?: string; lastError?: string; /** * **조치를 이 주소로 실제로 넘겨 봤나** (ADR-0046 ②). * * ── 왜 세 값인가 ─────────────────────────────────────────────────────────── * 설정이 맞는 것과 저쪽이 받는 것은 다르다. 주소도 비밀값도 다 적혀 있는데 저쪽 프로세스가 그 값을 * 잃은 일이 실제로 있었다(§`actuationReadiness`). 그것은 **한 번 넘겨 봐야** 알 수 있고, 그래서 * 「아직 안 해 봤다」와 「해 봤고 됐다」와 「해 봤고 안 됐다」가 다른 사실이다. * * unverified 아직 왕복이 없다 — 실패가 아니다. 새 연결과 설정이 바뀐 연결이 여기 선다 * verified `actuate` 가 받아들여졌다. 그 시각이 `actuationVerifiedAt` * failed 거절됐다. 사유는 `actuationFailure` 에 **코드로** * * `unverified` 를 `failed` 로 접지 않는다 — 안 해 본 것을 실패로 그리면 사람이 고칠 것을 찾으러 * 가고, 고칠 것이 없다. */ actuationState?: ActuationState; actuationVerifiedAt?: string; /** * 마지막 거절 — **코드와 값만 저장한다. 문장은 저장하지 않는다.** * * `lastError` 가 사람 문장을 저장해 두고 화면이 그것을 그대로 뿌렸다. 그러면 번역이 안 되고, 무엇이 * 몇 번 났는지 세지도 못하고, 커넥터가 문장을 고치는 날 옛 행과 새 행이 서로 다른 말을 한다. * 코드는 `CommandAck.errorCode` 와 같은 낱말이다(ADR-0042 ③) — 표현층이 문장을 만든다. */ actuationFailure?: ActuationFailure; /** * 마지막 동기에서 **못 옮긴 값의 수**. `null` = 세지 않았다(0 과 다르다). * * 세지 않은 구조 인제스트는 도크를 랙으로 바꿔 놓고도 성공이라 답하면서 아무 일도 하지 않는다. 그 사실을 사후에 물을 수 * 있어야 단계 판정이 「구조는 됐다」를 말할 수 있다 — 없는 동안 판정은 늘 `unmeasured` 였다. * 기본값을 두지 않는다: 0 을 기본으로 깔면 「경고 없었다」와 「세지 않았다」가 같아진다. */ lastWarningCount?: number; /** * **어디까지 읽었나** — 라이브 피드의 읽기 커서(§`LiveFeedContinuity`). * * ── 왜 저장하나 (2026-08-23 실측) ────────────────────────────────────────── * 어댑터가 붙을 때마다 `Date.now() − 되돌아볼 날수` 로 창을 새로 만들고 있었다. 그래서 **재기동마다 * 미러의 과거가 잘렸다** — 작업 2,855 → 2,820(8시간 흐른 만큼). 미러가 아는 것이 「원본의 사실」이 * 아니라 「창의 함수」였고, 그 사실을 아무도 말하지 않았다. * * 커널이 그 갈림을 이미 적어 두었다(§`hydrateContinuity`): 「원천이 애초에 다시 말해 주지 않는 축」은 * 재기동 연속성으로 이어받는다. 「우리가 어디까지 읽었나」가 정확히 그 성질이므로 **어댑터의 사물함이 * 아니라 이 층**에 있다 — 원본이 늘 때마다 저장 기제가 늘고 그중 하나가 알리지 않고 다르게 동작하지 않게. * * ── 왜 캐시가 아니라 표인가 ──────────────────────────────────────────────── * 만료되면 창이 다시 미끄러지고, 그 손실은 오류 없이 들어온 것이 없다. 스냅샷 체크포인트와 성질이 다르다 * (그쪽은 잃어도 원천이 정정해 준다 — 이 값은 **잃으면 원천에 묻지 않게 된다**). * * 모양은 `{ streams: { [흐름]: { since?, seen[] } }, firstAttachedAt? }` 다. **흐름 이름은 어댑터가 * 정한다** — 원본마다 흐름 수와 뜻이 다르므로 이 층은 열쇠로만 다룬다. `simple-json` 이라 드라이버 * 다섯을 그대로 지난다. * * `firstAttachedAt` 은 「언제부터 아는가」다. 커널은 상한만 안다(`nowTime` = 마지막으로 들은 시각). * 둘이 함께 「이 트윈이 아는 구간」이고, 화면이 수를 보일 때 그 구간을 말해야 한다. */ liveCursor?: any; createdAt?: Date; updatedAt?: Date; }