import { ListParam } from '@things-factory/shell'; export declare class TwinCommandResolver { /** * 커맨드 목록 — 기본은 **결정을 기다리는 것부터**. * * 정렬을 최근순으로 두지 않는다. 승인 대기 줄에서 사람이 찾는 것은 「새로 온 것」이 아니라 * **「오래 기다린 것」**이다 — 오래 기다린 조치가 뒤로 밀리면 그것이 가장 늦게 처리된다. */ twinCommands(params: ListParam, context: ResolverContext): Promise; /** * **이 조치에 대해 트윈이 지금 보는 것과 AI 의견** — 승인 화면이 부른다. * * ── 왜 조회인가 (2026-09-01) ─────────────────────────────────────────── * 결재선에 올릴 때 묻지 않는다. 그 자리는 트랜잭션 안이고 거기서 LLM 을 기다리면 DB 연결 하나를 몇 초 * 붙잡는다. 그리고 **승인하는 사람은 지금 판단한다** — 올린 시각의 의견은 낡았다. * * AI 는 판정하지 않는다. 이 값을 읽어 승인·거절을 막는 코드가 없다. 반대해도 승인되고 동의해도 * 거절된다 — 판정에 쓰면 AI 가 사실상 승인자가 되고, 그러면 사고 뒤에 책임을 물을 자리가 사라진다. */ twinActuationOpinion(id: string, /** * AI 에게도 물을 것인가 — **누르면 묻는다.** * * 기본은 안 묻는다. 화면을 열 때마다 부르면 승인자 다섯 명이 두 번씩 열 때 한 건에 열 번이고, * 대부분의 지시는 평범해서 그중 아홉 번은 같은 말을 다시 받는 것이다. */ askAi: boolean, locale: string, context: ResolverContext): Promise; /** * 조치를 낸다 — **대기 줄에 무언가가 들어오는 유일한 자리.** * * 결재선이 등록된 배포에서는 여기서 바로 결재선에 올라간다(§`ApprovalGateway`). 등록되지 않았으면 * 제안 상태로 남고, 이 리졸버의 직접 승인이 유일한 문이 된다. 어느 쪽인지는 답에 적어 준다 — 화면이 * 「어디서 승인하는가」를 사람에게 말해야 한다. * * 되돌릴 수 있는 조치는 결재선에 올리지 않는다. 계약이 그 판단을 든다(§`requiresApproval`). */ proposeTwinCommand(instanceId: string, type: string, payload: any, context: ResolverContext): Promise; /** * **사람이 결재선에 올린다** — 규칙이 낸 조치가 승인 앞에 서는 유일한 길. * * ── 왜 규칙이 직접 올리지 않나 ───────────────────────────────────────── * 업무 목록의 결재선은 **올린 사람 기준**으로 풀린다 — `Myself` · `MyDepartment` · `MySupervisor` 가 * 전부 로그인한 사람을 본다. 배경 루프에는 「나」가 없으므로, 시스템 사용자를 지어내 올리면 그 셋이 * 시스템 사용자를 가리키고 결재선이 뜻을 잃는다. * * 그래서 규칙은 내는 것까지 하고, 여기서 사람이 올린다. **올린 사람이 「이것을 승인자들 앞에 놓았다」의 * 주체**가 되고, 그 사람 기준으로 결재선이 풀린다. * * 올린 사람은 자기가 올린 것을 승인할 수 있다 — 규칙이 낸 조치에는 낸 사람이 없으므로 승인자가 곧 * 두 번째 사람이다. 올린 것과 낸 것은 다르다. */ raiseTwinCommand(id: string, context: ResolverContext): Promise; /** * 승인한다 — **누가 승인했는지는 로그인한 사람에서 온다.** * * 인자로 받지 않는다. 받으면 다른 사람 이름으로 승인할 수 있고, 그러면 그 기록이 사고 뒤에 쓸 수 * 없어진다. 승인 기록의 값어치가 거기서 나온다. */ approveTwinCommand(id: string, note: string, context: ResolverContext): Promise; /** * 거절한다 — 여기서 끝난다. * * 사유를 요구한다. 거절은 현장이 기다리던 조치를 취소하는 것이고, 이유 없이 거절되면 낸 쪽이 다시 * 낸다. 그러면 같은 것이 대기 줄에 계속 돌아온다. */ rejectTwinCommand(id: string, reason: string, context: ResolverContext): Promise; /** * 어댑터에 넘긴다 — **승인을 지나지 않은 것은 여기서 막힌다.** * * ── 이름이 왜 `dispatchTwinCommand` 가 아닌가 (2026-09-01) ───────────── * 그 이름은 이미 **커널 자극**이 쓴다(`TwinControlMutation.dispatchTwinCommand` — * `order.hold`·`resource.repair` 를 커널에 넣는다). 같은 이름을 두 번 선언하면 type-graphql 이 * 오류 없이 하나만 남기고, 실제로 이 뮤테이션이 통째로 가려져 있었다 — 걸어 보고 알았다. * * 자극과 조치가 다른 어휘라는 것을 계약에서 갈라 놓고(§`propose` 의 거절), 정작 API 이름에서 겹쳐 * 두었다. 여기서는 조치임을 이름에 적는다. * * 화면이 이것을 따로 부르는 이유는, 승인과 넘김이 다른 순간이기 때문이다. 승인한 사람과 넘긴 시각이 * 같아야 할 이유가 없고, 어댑터가 죽어 있으면 승인은 남아 있어야 한다. * * ── 여기가 끊겨 있었다 (2026-09-02) ────────────────────────────────── * 이 자리가 **어댑터를 찾지도 않고** 「구현이 없습니다」를 답했다. MES 커넥터는 그때 이미 `actuate` * 를 다 만들어 두었다. 그 답을 사실로 읽어서 설계 문서에도, 다른 레인에도 그렇게 알렸다 — 둘 다 * 틀렸다. * * 이제 원본을 찾아 그 원본이 선언한 능력으로 판단한다(§`routeActuation`). 보내지 못하면 **무엇이 * 없어서인지** 말한다. 조용히 성공을 답하면 화면은 나간 줄로 읽고, 현장은 오지 않는 지시를 기다린다. * * ── 여기에는 트랜잭션을 걸지 않는다 ────────────────────────────────── * 넘기기 전에 「넘기는 중」을 먼저 적는 것이 이 함수의 요점이다. 한 트랜잭션에 묶으면 그 기록이 * 커밋 전까지 아무에게도 보이지 않고, 넘기는 사이에 프로세스가 죽으면 그대로 사라진다. 그러면 저쪽은 * 받았는데 우리는 안 보낸 줄 알고 다시 보낸다 — 현장이 두 번 움직인다. */ /** * **이 조치를 넘길 수 있나** — 누르기 전에 답한다. * * ── 왜 이 자리가 필요한가 (2026-09-03) ──────────────────────────────────── * 판정 함수 둘을 만들어 두고 **부르는 자리를 안 만들었다**(`actuationTargetOf`· * `actuationReadinessOf`). 그래서 화면이 물을 방법이 없었고, 못 넘기는 조치도 눌러 보고 알게 됐다. * 인티그레이션 레인이 그 사실을 두 번 짚어 줬다 — 자리는 있고 길이 없는 그 부류다. * * 그동안 값이 실제로 없어졌다. 저쪽에 비밀값이 없던 날, 사람이 승인 화면까지 가서 누르고 실패를 * 보고 다시 로그인했다. **승인은 살아 있지만 사람의 시간은 돌아오지 않는다.** * * ── 둘을 함께 답한다 ────────────────────────────────────────────────────── * 화면이 묻는 것은 하나다 — 「지금 넘겨도 되나」. 그 답이 두 겹이라 둘을 함께 싣는다. * * target 보낼 대상이 정해지나 — 망을 타지 않는다(원본·능력·사이트) * readiness 저쪽이 받을 준비가 됐나 — 커넥터가 망을 탈 수 있다 * * **문이 아니다.** 이 답으로 넘김을 막지 않는다(§`actuationReadinessOf`) — 화면이 미리 말하는 * 데까지다. 그래서 조회이고 뮤테이션이 아니다. * * 화면이 **목록을 그릴 때 부르지 말 것.** 커넥터가 저쪽에 물을 수 있어서 목록 한 줄마다 망을 탄다. * 사람이 「넘기기」를 열 때 한 번이다. */ twinActuationReadiness(id: string, context: ResolverContext): Promise; twinActuationOutcome(id: string, context: ResolverContext): Promise; dispatchTwinActuation(id: string, context: ResolverContext): Promise; }