Proposal Control Plane
실행 전 의도 보관
- 소유하는 것
- pending 제안, 대상, artifact 경로, 비용·rollback 설명과 proposal ID
- 볼 수 있는 것
- MCP가 등록한 제안과 Talkak Approvals UI가 읽는 동일한 대기열
- 보장하지 못하는 것
- 제안을 저장했다는 사실만으로 외부 배포가 실행되거나 성공하지 않는다.
REPO ≠ RUNTIME ≠ AUTHORITY ≠ PROOF
독립된 사이트 repo의 Codex 세션이 어떻게 Talkak의 Approvals에 제안을 만들고, 실행 중인 앱에서 승인까지 눌렀는지 실제 사건을 끝까지 추적한다. 마법도, repo 합체도 아니다. 공유된 도구·제안 통로·실행 권한 경계다.
이 세션의 파일·도구 응답·실제 UI에서 확인했다.
관측 사실을 바탕으로 한 권장 아키텍처다.
소스·E2E·권한 inventory가 더 필요하다.
01 · SIX DIFFERENT BOUNDARIES
노드를 누르면 각 층이 무엇을 소유하고, 무엇을 볼 수 있고, 무엇을 보장하지 못하는지 분리해서 본다.
실행 전 의도 보관
Codex가 Talkak repo를 몰래 읽은 것이 아니다. 이 세션에 Dalkkak MCP와 실행 중인 앱을 조작할 수 있는 별도 통로가 연결되어 있었고, Talkak UI가 같은 proposal store를 읽었다.
02 · THE ACTUAL INCIDENT
이것은 일반적인 이상형이 아니라 이번 세션에서 직접 관측한 trace다.
승인 의도와 실행 권한이 분리되어 있음을 실제 실패로 확인했다. 외부 배포는 시작되지 않았다.
Sites 연결에서는 기존 사이트를 읽을 수 있었지만 Talkak managed runner는 access token을 받지 못했다. 한 connector의 로그인 상태가 다른 runner에 자동 상속되지 않았다는 실제 증거다.
03 · HOOK, RULE, GATE, AUTHORITY
Hook은 lifecycle event에서 코드를 실행하는 adapter다. host가 그 결과를 존중하면 특정 경로를 차단할 수 있지만, 등록되지 않은 다른 경로와 credential 자체까지 사라지게 하지는 않는다.
이 문으로 가라고 알려준다.
등록된 경로에서 검사·차단한다.
정확한 작업에 사람 결정을 결합한다.
유효한 승인 없이는 권한을 주지 않는다.
확인된 것은 proposal-only MCP 경로, durable pending 상태, Approvals UI, 승인 시도, Runner의 인증 거부다. 따라서 ‘숨은 hook 하나가 전부 막았다’고 설명하면 증거보다 앞서간다.
| 메커니즘 | 분류 | 할 수 있음 | 못 하는 것 | 강도 |
|---|---|---|---|---|
| System·developer·AGENTS 지침 | Guidance | 정상 절차와 금지사항을 모델에게 알려준다. | 모델 오류, 컨텍스트 손실, 별도 credential 경로를 물리적으로 막지 못한다. | |
| Skill·runbook | Procedure | 선택된 작업을 반복 가능한 순서로 수행하게 한다. | 선택되지 않은 경로나 실제 권한을 통제하지 않는다. | |
| Hook·interceptor | Path-scoped control | 등록된 lifecycle 지점에서 allow·deny·rewrite·approval을 결정한다. | 그 hook을 지나지 않는 CLI·HTTP·다른 MCP·worker를 자동으로 막지 못한다. | |
| Proposal-only tool surface | Interface control | 에이전트가 직접 실행 대신 구조화된 pending 제안만 만들게 한다. | 에이전트가 별도 배포 credential이나 우회 도구를 보유한 상황을 해결하지 못한다. | |
| Human approval + backend validation | Decision gate | 사람의 결정을 정확한 proposal·대상·revision에 결합한다. | 모호한 화면, 변경된 artifact, replay를 추가 검증 없이 해결하지 못한다. | |
| Runner-only credential + egress 제한 | Authority boundary | 유효한 승인 없이는 실제 외부 변경 권한을 얻지 못하게 한다. | 잘못된 downstream 동작이나 idempotency 결함까지 자동 해결하지 않는다. | |
| Status·receipt·reconciliation | Proof | 무엇이 승인되고 실제로 실행됐는지 사후에 증명한다. | 나쁜 실행을 사전에 차단하지 않는다. 대신 재현·감사·복구 근거를 준다. |
04 · BYPASS & FAILURE LAB
실패를 선택하면 어디에서 멈춰야 하고 어떤 테스트가 필요한지 본다.
Approve는 눌렸지만 managed runner가 인증되지 않았다.
05 · HARD-GATE REFERENCE DESIGN
아래는 이번 사건의 관측 사실이 아니라, 우회를 막기 위한 공급자 중립 권장 설계다.
type ProposalEnvelope = {
proposalId: string;
projectId: string;
target: string;
payloadDigest: string;
artifactDigest: string;
risk: "low" | "medium" | "high";
expiresAt: string;
};
type ApprovalGrant = {
proposalId: string;
payloadDigest: string;
approverId: string;
decision: "approved" | "rejected";
nonce: string;
expiresAt: string;
};
async function executeApproved(
proposal: ProposalEnvelope,
grant: ApprovalGrant,
) {
verifyGrant(grant);
assert(grant.proposalId === proposal.proposalId);
assert(grant.payloadDigest === proposal.payloadDigest);
assertNotExpired(proposal, grant);
await verifyArtifactDigest(proposal.artifactDigest);
const claim = await executions.claimOnce({
proposalId: proposal.proposalId,
idempotencyKey: proposal.payloadDigest,
});
if (!claim.acquired) return claim.previousResult;
const credential = await credentialBroker.issueScoped({
projectId: proposal.projectId,
target: proposal.target,
});
try {
const result = await adapter.execute(proposal, credential);
const verified = await adapter.verify(result);
return receipts.recordExecuted(proposal, grant, verified);
} catch (error) {
if (couldHaveCompletedExternally(error)) {
return executions.markUnknownOutcome(proposal.proposalId);
}
return receipts.recordFailedBeforeEffect(proposal, grant, error);
}
}DRAFT → PENDINGPENDING → REJECTED | EXPIRED | APPROVEDAPPROVED → EXECUTINGEXECUTING → EXECUTED | FAILED_BEFORE_EFFECT | UNKNOWN_OUTCOMEUNKNOWN_OUTCOME → RECONCILING → EXECUTED_CONFIRMED | NOT_EXECUTED_CONFIRMED금지: REJECTED→EXECUTING, EXPIRED→EXECUTING, UNKNOWN_OUTCOME→blind retry, digest 변경 후 옛 승인 재사용.
모든 외부 변경 경로가 승인 검증 Runner로 수렴하고, 그 Runner만 최소 권한 credential을 소유하는 구조가 신뢰의 뿌리다.
06 · TEST THE GUARANTEE
제안 생성은 로컬 pending 상태만 만들고 외부 network call은 0건이다.
승인 grant가 없거나 만료·거절·scope 불일치면 Runner가 실행하지 않는다.
승인 후 payload·target·artifact digest가 바뀌면 이전 승인은 무효다.
동시 승인과 retry에서도 외부 효과는 최대 한 번이다.
직접 CLI·SDK·HTTP·다른 MCP 경로는 같은 정책과 권한 경계에서 차단된다.
인증 실패는 외부 변경 없이 FAILED_BEFORE_EFFECT와 복구 안내를 남긴다.
외부 성공 여부가 불명확하면 blind retry 대신 UNKNOWN_OUTCOME으로 간다.
proposal·approval·execution·provider status·receipt가 같은 digest를 가리킨다.
AI가 지침을 잊는 eval에서도 승인 없는 실제 변경은 0건이다.
사람이 승인 전에 대상·효과·비용·rollback을 정확히 설명할 수 있다.
제안·승인·거절·인증 실패·우회 차단·단 한 번의 실행·receipt 일치를 실행한다.
사람이 대상·효과·위험·rollback을 승인 전에 오해 없이 이해하는지 측정한다.
한 번의 명확한 결정으로 실제 작업이 완료되고 실패 시 복구되는지 확인한다. 이번 사건은 token 오류 때문에 아직 미검증이다.
07 · EVIDENCE LEDGER
모델에게 ‘하지 마’라고 말하는 것은 규칙이다. 모델에게 그 권한을 주지 않고, 승인된 Runner만 그 권한을 갖게 하는 것이 강제다.