@asc-agent/runtime 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (210) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +22 -0
  3. package/dist/adapters/claude-code/binding.d.ts +5 -0
  4. package/dist/adapters/claude-code/binding.js +14 -0
  5. package/dist/adapters/claude-code/guard.d.ts +54 -0
  6. package/dist/adapters/claude-code/guard.js +295 -0
  7. package/dist/adapters/claude-code/install.d.ts +67 -0
  8. package/dist/adapters/claude-code/install.js +231 -0
  9. package/dist/adapters/claude-code/observer.d.ts +38 -0
  10. package/dist/adapters/claude-code/observer.js +77 -0
  11. package/dist/adapters/claude-code/probe.d.ts +53 -0
  12. package/dist/adapters/claude-code/probe.js +129 -0
  13. package/dist/adapters/claude-code/skill.d.ts +8 -0
  14. package/dist/adapters/claude-code/skill.js +267 -0
  15. package/dist/adapters/fixture-work/index.d.ts +34 -0
  16. package/dist/adapters/fixture-work/index.js +78 -0
  17. package/dist/adapters/github/adapter.d.ts +32 -0
  18. package/dist/adapters/github/adapter.js +95 -0
  19. package/dist/adapters/github/client.d.ts +30 -0
  20. package/dist/adapters/github/client.js +90 -0
  21. package/dist/adapters/github/context.d.ts +44 -0
  22. package/dist/adapters/github/context.js +170 -0
  23. package/dist/adapters/github/event-source.d.ts +55 -0
  24. package/dist/adapters/github/event-source.js +183 -0
  25. package/dist/adapters/github/scm.d.ts +50 -0
  26. package/dist/adapters/github/scm.js +104 -0
  27. package/dist/adapters/gitlab/adapter.d.ts +36 -0
  28. package/dist/adapters/gitlab/adapter.js +117 -0
  29. package/dist/adapters/gitlab/client.d.ts +30 -0
  30. package/dist/adapters/gitlab/client.js +47 -0
  31. package/dist/adapters/gitlab/ports.d.ts +40 -0
  32. package/dist/adapters/gitlab/ports.js +186 -0
  33. package/dist/adapters/jam/adapter.d.ts +53 -0
  34. package/dist/adapters/jam/adapter.js +144 -0
  35. package/dist/adapters/jam/event-source.d.ts +28 -0
  36. package/dist/adapters/jam/event-source.js +65 -0
  37. package/dist/adapters/jam/mcp-client.d.ts +50 -0
  38. package/dist/adapters/jam/mcp-client.js +218 -0
  39. package/dist/adapters/jam/ports.d.ts +51 -0
  40. package/dist/adapters/jam/ports.js +167 -0
  41. package/dist/adapters/local/identity.d.ts +27 -0
  42. package/dist/adapters/local/identity.js +40 -0
  43. package/dist/adapters/local/presentation.d.ts +14 -0
  44. package/dist/adapters/local/presentation.js +42 -0
  45. package/dist/adapters/markdown/layout.d.ts +15 -0
  46. package/dist/adapters/markdown/layout.js +49 -0
  47. package/dist/adapters/markdown/serialize.d.ts +9 -0
  48. package/dist/adapters/markdown/serialize.js +120 -0
  49. package/dist/adapters/markdown/state-store.d.ts +23 -0
  50. package/dist/adapters/markdown/state-store.js +316 -0
  51. package/dist/adapters/mattermost/client.d.ts +23 -0
  52. package/dist/adapters/mattermost/client.js +54 -0
  53. package/dist/adapters/mattermost/presentation.d.ts +43 -0
  54. package/dist/adapters/mattermost/presentation.js +88 -0
  55. package/dist/adapters/memory/mocks.d.ts +87 -0
  56. package/dist/adapters/memory/mocks.js +163 -0
  57. package/dist/adapters/memory/runtime-binding.d.ts +23 -0
  58. package/dist/adapters/memory/runtime-binding.js +118 -0
  59. package/dist/adapters/memory/state-store.d.ts +17 -0
  60. package/dist/adapters/memory/state-store.js +125 -0
  61. package/dist/adapters/text/renderer.d.ts +7 -0
  62. package/dist/adapters/text/renderer.js +81 -0
  63. package/dist/adapters/webhook/ingress.d.ts +103 -0
  64. package/dist/adapters/webhook/ingress.js +150 -0
  65. package/dist/cli/asc.d.ts +16 -0
  66. package/dist/cli/asc.js +2914 -0
  67. package/dist/cli/identity-config.d.ts +7 -0
  68. package/dist/cli/identity-config.js +31 -0
  69. package/dist/composition/observe.d.ts +21 -0
  70. package/dist/composition/observe.js +72 -0
  71. package/dist/composition/registry.d.ts +33 -0
  72. package/dist/composition/registry.js +62 -0
  73. package/dist/composition/runtime.d.ts +63 -0
  74. package/dist/composition/runtime.js +155 -0
  75. package/dist/core/approval/service.d.ts +17 -0
  76. package/dist/core/approval/service.js +129 -0
  77. package/dist/core/attach/bootstrap.d.ts +102 -0
  78. package/dist/core/attach/bootstrap.js +178 -0
  79. package/dist/core/attach/init.d.ts +29 -0
  80. package/dist/core/attach/init.js +100 -0
  81. package/dist/core/attach/setup-plan.d.ts +125 -0
  82. package/dist/core/attach/setup-plan.js +177 -0
  83. package/dist/core/attach/setup.d.ts +40 -0
  84. package/dist/core/attach/setup.js +140 -0
  85. package/dist/core/binding/types.d.ts +93 -0
  86. package/dist/core/binding/types.js +74 -0
  87. package/dist/core/distribution/release.d.ts +15 -0
  88. package/dist/core/distribution/release.js +27 -0
  89. package/dist/core/distribution/runtime-install.d.ts +50 -0
  90. package/dist/core/distribution/runtime-install.js +90 -0
  91. package/dist/core/distribution/runtime-select.d.ts +74 -0
  92. package/dist/core/distribution/runtime-select.js +149 -0
  93. package/dist/core/execution/executor.d.ts +51 -0
  94. package/dist/core/execution/executor.js +106 -0
  95. package/dist/core/execution/grant.d.ts +42 -0
  96. package/dist/core/execution/grant.js +78 -0
  97. package/dist/core/model/entities.d.ts +872 -0
  98. package/dist/core/model/entities.js +285 -0
  99. package/dist/core/model/ids.d.ts +41 -0
  100. package/dist/core/model/ids.js +50 -0
  101. package/dist/core/model/transitions.d.ts +26 -0
  102. package/dist/core/model/transitions.js +124 -0
  103. package/dist/core/monitor/coverage.d.ts +111 -0
  104. package/dist/core/monitor/coverage.js +129 -0
  105. package/dist/core/monitor/engine.d.ts +134 -0
  106. package/dist/core/monitor/engine.js +574 -0
  107. package/dist/core/monitor/health-alerts.d.ts +35 -0
  108. package/dist/core/monitor/health-alerts.js +88 -0
  109. package/dist/core/monitor/investigation.d.ts +82 -0
  110. package/dist/core/monitor/investigation.js +232 -0
  111. package/dist/core/monitor/observation.d.ts +79 -0
  112. package/dist/core/monitor/observation.js +110 -0
  113. package/dist/core/monitor/relevance.d.ts +47 -0
  114. package/dist/core/monitor/relevance.js +100 -0
  115. package/dist/core/monitor/signals.d.ts +59 -0
  116. package/dist/core/monitor/signals.js +105 -0
  117. package/dist/core/operator/local-operator.d.ts +60 -0
  118. package/dist/core/operator/local-operator.js +82 -0
  119. package/dist/core/operator/preflight.d.ts +60 -0
  120. package/dist/core/operator/preflight.js +139 -0
  121. package/dist/core/operator/proceed.d.ts +98 -0
  122. package/dist/core/operator/proceed.js +167 -0
  123. package/dist/core/operator/progress.d.ts +124 -0
  124. package/dist/core/operator/progress.js +135 -0
  125. package/dist/core/operator/render.d.ts +34 -0
  126. package/dist/core/operator/render.js +170 -0
  127. package/dist/core/operator/runtime-binding.d.ts +61 -0
  128. package/dist/core/operator/runtime-binding.js +22 -0
  129. package/dist/core/policy/ownership.d.ts +44 -0
  130. package/dist/core/policy/ownership.js +46 -0
  131. package/dist/core/policy/policy.d.ts +99 -0
  132. package/dist/core/policy/policy.js +147 -0
  133. package/dist/core/policy/remote-freeze.d.ts +101 -0
  134. package/dist/core/policy/remote-freeze.js +151 -0
  135. package/dist/core/policy/scope.d.ts +23 -0
  136. package/dist/core/policy/scope.js +93 -0
  137. package/dist/core/presentation/digest.d.ts +98 -0
  138. package/dist/core/presentation/digest.js +160 -0
  139. package/dist/core/resolver/load.d.ts +121 -0
  140. package/dist/core/resolver/load.js +246 -0
  141. package/dist/core/resolver/render.d.ts +4 -0
  142. package/dist/core/resolver/render.js +62 -0
  143. package/dist/core/resolver/resolve.d.ts +42 -0
  144. package/dist/core/resolver/resolve.js +51 -0
  145. package/dist/core/resolver/version.d.ts +22 -0
  146. package/dist/core/resolver/version.js +49 -0
  147. package/dist/core/runtime/audit.d.ts +374 -0
  148. package/dist/core/runtime/audit.js +454 -0
  149. package/dist/core/runtime/claims.d.ts +115 -0
  150. package/dist/core/runtime/claims.js +153 -0
  151. package/dist/core/runtime/closure.d.ts +73 -0
  152. package/dist/core/runtime/closure.js +162 -0
  153. package/dist/core/runtime/controller.d.ts +40 -0
  154. package/dist/core/runtime/controller.js +121 -0
  155. package/dist/core/runtime/escalation.d.ts +188 -0
  156. package/dist/core/runtime/escalation.js +322 -0
  157. package/dist/core/runtime/execution-state.d.ts +43 -0
  158. package/dist/core/runtime/execution-state.js +81 -0
  159. package/dist/core/runtime/front.d.ts +95 -0
  160. package/dist/core/runtime/front.js +144 -0
  161. package/dist/core/runtime/orchestrator.d.ts +54 -0
  162. package/dist/core/runtime/orchestrator.js +98 -0
  163. package/dist/core/runtime/query.d.ts +184 -0
  164. package/dist/core/runtime/query.js +213 -0
  165. package/dist/core/runtime/report.d.ts +33 -0
  166. package/dist/core/runtime/report.js +108 -0
  167. package/dist/core/runtime/session.d.ts +156 -0
  168. package/dist/core/runtime/session.js +281 -0
  169. package/dist/core/runtime/store-ops.d.ts +22 -0
  170. package/dist/core/runtime/store-ops.js +26 -0
  171. package/dist/core/view/build-view.d.ts +36 -0
  172. package/dist/core/view/build-view.js +131 -0
  173. package/dist/core/view/decision-view.d.ts +564 -0
  174. package/dist/core/view/decision-view.js +103 -0
  175. package/dist/core/workspace/identity.d.ts +82 -0
  176. package/dist/core/workspace/identity.js +133 -0
  177. package/dist/core/workspace/index-store.d.ts +140 -0
  178. package/dist/core/workspace/index-store.js +125 -0
  179. package/dist/core/workspace/migrate.d.ts +73 -0
  180. package/dist/core/workspace/migrate.js +123 -0
  181. package/dist/core/workspace/resolve.d.ts +34 -0
  182. package/dist/core/workspace/resolve.js +89 -0
  183. package/dist/ports/adapter.d.ts +35 -0
  184. package/dist/ports/adapter.js +12 -0
  185. package/dist/ports/approval.d.ts +98 -0
  186. package/dist/ports/approval.js +7 -0
  187. package/dist/ports/change-context.d.ts +22 -0
  188. package/dist/ports/change-context.js +8 -0
  189. package/dist/ports/event-source.d.ts +42 -0
  190. package/dist/ports/event-source.js +6 -0
  191. package/dist/ports/inventory.d.ts +53 -0
  192. package/dist/ports/inventory.js +12 -0
  193. package/dist/ports/presentation.d.ts +62 -0
  194. package/dist/ports/presentation.js +13 -0
  195. package/dist/ports/renderer.d.ts +23 -0
  196. package/dist/ports/renderer.js +7 -0
  197. package/dist/ports/resource-context.d.ts +51 -0
  198. package/dist/ports/resource-context.js +9 -0
  199. package/dist/ports/scm.d.ts +40 -0
  200. package/dist/ports/scm.js +6 -0
  201. package/dist/ports/state-store.d.ts +97 -0
  202. package/dist/ports/state-store.js +12 -0
  203. package/dist/presets/balanced.json +8 -0
  204. package/dist/presets/conservative.json +9 -0
  205. package/dist/presets/lightweight.json +8 -0
  206. package/dist/profiles/example-team/profile.json +101 -0
  207. package/dist/profiles/pilot-local/profile.json +56 -0
  208. package/dist/schemas/profile.d.ts +631 -0
  209. package/dist/schemas/profile.js +287 -0
  210. package/package.json +55 -0
@@ -0,0 +1,285 @@
1
+ // Core가 이해하는 Logical Entity Model. 파일 배치·Markdown 표현이 아니라 이 타입들이
2
+ // Core Contract다 — State Store Adapter는 이걸 저장 방식으로 투영할 뿐이다 (OM §7.0).
3
+ // 스키마가 곧 타입의 정본이며 (z.infer로 타입 도출) 이중 정본을 두지 않는다.
4
+ import { z } from 'zod';
5
+ import { BlockId, EventKey, GrantId, QueueItemId, RequestId, SessionId, Timestamp, Version } from "./ids.js";
6
+ // ── 공통 ────────────────────────────────────────────────────────────────────
7
+ /** 전이를 수행할 수 있는 주체. Writer 규칙(OM §7.2)과 상태 전이 권한의 기준. */
8
+ export const ActorRole = z.enum(['controller', 'monitor', 'executor', 'session']);
9
+ export const Priority = z.enum(['P0', 'P1', 'P2']);
10
+ /**
11
+ * 이벤트 유형 — Phase B 조사 깊이를 결정한다 (OM §10.3).
12
+ * informational 정보형 / actionable 대응형 / work 작업형.
13
+ */
14
+ export const EventType = z.enum(['informational', 'actionable', 'work']);
15
+ /**
16
+ * Canonical source별 baseline. Project는 canonical 하나가 아니라 Session이 소비하는
17
+ * source 집합을 갖는다 (OM §8) — snapshot도 단일 commit이 아니라 source별로 기록한다.
18
+ */
19
+ export const CanonicalSnapshot = z.object({
20
+ sourceId: z.string().min(1),
21
+ baseline: z.string().min(1), // commit hash / event id 등 provider가 정하는 baseline
22
+ });
23
+ // ── Session / Checkpoint / Handoff ──────────────────────────────────────────
24
+ /** Logical Session 상태 (OM §6.2). Physical Run과 1:1이 아니다. */
25
+ export const SessionStatus = z.enum(['READY', 'ACTIVE', 'PAUSED', 'BLOCKED', 'DONE', 'FAILED']);
26
+ export const SessionRole = z.enum(['planner', 'researcher', 'implementer', 'verifier']);
27
+ /** 중단 시 남기는 Run 승계 정보 — 다른 Physical Run이 같은 Logical Session을 이어받는다. */
28
+ export const Checkpoint = z.object({
29
+ position: z.string().min(1),
30
+ completedTasks: z.array(z.string()).default([]),
31
+ nextAction: z.string().min(1),
32
+ uncommittedChanges: z.array(z.string()).default([]),
33
+ /**
34
+ * 의미 있는 전환을 남기기 위한 필드들 (C-10 §2.1). 시간 경과는 전환이 아니다 —
35
+ * "지금 무엇이 사실이라고 보는가"와 "그 판단의 근거"가 있어야 다음 사람이 이어받는다.
36
+ *
37
+ * 전부 optional·default다: 기존 세션 파일이 그대로 읽혀야 한다 (doneCriteria 선례).
38
+ * 퍼센트는 여기 없다 — 필요하면 렌더가 만드는 projection이지 기록이 아니다.
39
+ */
40
+ currentJudgment: z.string().optional(),
41
+ blockers: z.array(z.string()).default([]),
42
+ risks: z.array(z.string()).default([]),
43
+ evidenceRefs: z.array(z.string()).default([]),
44
+ /** 누가 적었는가 (execution principal). 소유권 검사와 함께 의미를 갖는다. */
45
+ writtenBy: z.string().optional(),
46
+ recordedAt: Timestamp,
47
+ });
48
+ /** 세션 종료 산출물. 계약↔결과 짝이 어긋나지 않도록 Session entity 안에 둔다 (OM §7.1). */
49
+ export const Handoff = z.object({
50
+ done: z.array(z.string()).default([]),
51
+ changed: z.array(z.string()).default([]),
52
+ verified: z.string(), // self-check임을 본문에 명시 (Verifier 독립 검증과 구분)
53
+ unresolved: z.array(z.string()).default([]),
54
+ next: z.string(),
55
+ snapshot: z.array(CanonicalSnapshot).default([]),
56
+ recordedAt: Timestamp,
57
+ });
58
+ export const Session = z.object({
59
+ id: SessionId,
60
+ version: Version,
61
+ status: SessionStatus,
62
+ role: SessionRole,
63
+ blockId: BlockId.optional(),
64
+ goal: z.string().min(1),
65
+ /**
66
+ * 검증 가능한 완료조건 (C-03 §2). "무엇을 할지"(goal)와 "언제 끝인지"를 분리한다 —
67
+ * Host completion loop projection과 Verifier test plan의 근거가 된다.
68
+ * default([])라 기존 entity 파일이 그대로 읽힌다.
69
+ */
70
+ doneCriteria: z.array(z.string()).default([]),
71
+ /**
72
+ * 이 일을 끝까지 끌고 갈 주체 (C-04 §1.1). 다른 파트에 무언가를 물었다는 이유로
73
+ * 바뀌지 않는다 — 바꾸는 경로 자체를 두지 않는다.
74
+ */
75
+ owner: z.string().min(1).optional(),
76
+ /**
77
+ * 이 일에 걸린 결정 영역. "무엇을 정해야 하는가"의 목록이며 결정권자는 아니다.
78
+ * 비어 있는 것이 정상이다 — 대부분의 구현 세션은 cross-part 결정을 요구하지 않는다.
79
+ */
80
+ decisionDomains: z.array(z.string()).default([]),
81
+ /**
82
+ * 이번 세션에 한해 정한 결정권자 (domain → role). Profile ownership으로 풀리지 않는
83
+ * 영역을 여기서 명시한다. OM §7.5의 `Authority:`(실행 자율도)와 다른 축이라 이름을
84
+ * 나눴다 — 저쪽은 "이 세션이 무엇을 자율로 해도 되는가", 이쪽은 "이 결정이 누구 것인가".
85
+ */
86
+ decisionAuthority: z.record(z.string()).default({}),
87
+ /** 외부에서 받아야 할 입력. 받는다는 사실이지 ownership 이전이 아니다 (C-04 §1.2). */
88
+ dependencies: z.array(z.string()).default([]),
89
+ taskPointer: z.string().optional(), // 공식 작업 목록 포인터 — 내용 복제 금지 (OM §1.3)
90
+ canonicalSources: z.array(CanonicalSnapshot).default([]),
91
+ readScope: z.array(z.string()).default([]),
92
+ writeBoundary: z.array(z.string()).default([]),
93
+ outOfScope: z.array(z.string()).default([]),
94
+ /** Controller가 이 세션에 한해 허용한 SOFT DENY 항목 (OM §5.1 Policy Exception). */
95
+ policyExceptions: z.array(z.string()).default([]),
96
+ checkpoint: Checkpoint.optional(),
97
+ handoff: Handoff.optional(),
98
+ });
99
+ // ── ApprovalRequest / ApprovalDecision ──────────────────────────────────────
100
+ /** Inbox lifecycle (OM §11.2). APPROVED는 승인 완료일 뿐 외부 write 권한이 아니다. */
101
+ export const RequestStatus = z.enum([
102
+ 'AWAITING_APPROVAL',
103
+ 'APPROVED',
104
+ 'QUEUED',
105
+ 'DEFERRED',
106
+ 'DISMISSED',
107
+ 'DONE',
108
+ ]);
109
+ export const DecisionKind = z.enum(['approve', 'revise', 'defer', 'dismiss', 'queue']);
110
+ /**
111
+ * Controller가 원 Thread를 다시 읽지 않고 판단할 수 있도록 Monitor가 준비한 Decision
112
+ * Packet (OM §11.1). 이 내용은 생성 시점의 분석 snapshot이며, 조회 시점의 현재 상태와
113
+ * 섞지 않는다 — 현재 맥락은 Derived View로 따로 만든다 (C-01 §6).
114
+ */
115
+ export const ApprovalRequest = z.object({
116
+ id: RequestId,
117
+ version: Version,
118
+ status: RequestStatus,
119
+ type: EventType,
120
+ priority: Priority,
121
+ title: z.string().min(1),
122
+ detectedAt: Timestamp,
123
+ source: z.object({
124
+ eventKey: EventKey,
125
+ reference: z.string().min(1), // Issue #19, PR #50 등 사람이 읽는 참조
126
+ threadLastEventId: z.string().optional(),
127
+ }),
128
+ situation: z.string(),
129
+ context: z.string().default(''),
130
+ impact: z.object({
131
+ interruptRequired: z.boolean(),
132
+ affectedSessions: z.array(SessionId).default([]),
133
+ rationale: z.string().default(''),
134
+ }),
135
+ recommendation: z.string().default(''),
136
+ draft: z.string().optional(), // 대응형만. 게시는 Grant 경로로만 (OM §11.5)
137
+ snapshot: z.array(CanonicalSnapshot).default([]),
138
+ /** 이 요청을 결정할 수 있는 Controller identity (OM §11.6). */
139
+ authorizedApprover: z.string().min(1),
140
+ allowedDecisions: z.array(DecisionKind).nonempty(),
141
+ expiresAt: Timestamp.optional(),
142
+ /**
143
+ * 이 요청이 Agent의 상신에서 왔다면 그 근거 (C-13 §2).
144
+ *
145
+ * optional인 이유: Monitor packet 경로(외부 사건 감지)는 escalation이 아니므로 이 값이
146
+ * 없다. 기존 요청 파일도 그대로 읽힌다.
147
+ */
148
+ escalation: z
149
+ .object({
150
+ escalationId: z.string().min(1),
151
+ predicates: z.array(z.string()).nonempty(),
152
+ evidenceRefs: z.array(z.string()).nonempty(),
153
+ affectedNodes: z.array(z.string()).default([]),
154
+ /** 지금 못 하는 작업 노드. 경계 영역(blockedScope)과 다른 축이다. */
155
+ blockedNodes: z.array(z.string()).nonempty(),
156
+ blockedScope: z.array(z.string()).default([]),
157
+ stillRunnableNodes: z.array(z.string()).default([]),
158
+ previousEscalationId: z.string().optional(),
159
+ })
160
+ .optional(),
161
+ decision: z
162
+ .object({
163
+ kind: DecisionKind,
164
+ actor: z.string().min(1), // Adapter가 인증한 actor
165
+ channel: z.string().min(1), // 'local' 등 — 채널 id는 Adapter가 정한다
166
+ revision: z.string().optional(), // "마지막 문장 빼고 승인" 류 수정본
167
+ decidedAt: Timestamp,
168
+ })
169
+ .optional(),
170
+ resultRef: z.string().optional(), // 외부 반영 결과 (comment URL 등)
171
+ });
172
+ /**
173
+ * 채널 무관 결정 DTO. 모든 채널이 동일 requestId를 쓰며, 최초 유효 Decision 이후의
174
+ * 입력은 CAS 실패로 거절된다 (C-01 §7~8).
175
+ */
176
+ export const ApprovalDecision = z.object({
177
+ requestId: RequestId,
178
+ expectedVersion: Version,
179
+ kind: DecisionKind,
180
+ actor: z.string().min(1),
181
+ channel: z.string().min(1),
182
+ revision: z.string().optional(),
183
+ decidedAt: Timestamp,
184
+ });
185
+ // ── ExecutionGrant ──────────────────────────────────────────────────────────
186
+ /** Grant lifecycle (OM §11.5). 성공한 Grant는 재소비 불가 — replay guard의 근간. */
187
+ export const GrantStatus = z.enum(['READY', 'CLAIMED', 'EXECUTED', 'INVALIDATED', 'EXPIRED']);
188
+ /**
189
+ * Policy hierarchy의 하위 override가 아니라, Controller가 hierarchy 밖에서 생성하는
190
+ * one-shot execution contract (OM §5.2). Session 권한은 그대로 두고 별도 Executor에게만
191
+ * 단일 Action을 허용한다.
192
+ */
193
+ export const ExecutionGrant = z.object({
194
+ id: GrantId,
195
+ version: Version,
196
+ requestId: RequestId,
197
+ status: GrantStatus,
198
+ issuedBy: z.string().min(1),
199
+ issuedAt: Timestamp,
200
+ expiresAt: Timestamp.optional(),
201
+ singleUse: z.boolean().default(true),
202
+ action: z.string().min(1), // '<adapter>.<행위>' 형태의 행위 키 — Adapter가 해석한다
203
+ target: z.string().min(1), // 대상 참조. 문법은 Adapter가 정한다
204
+ payload: z.string(), // 승인된 내용 그대로. Executor가 재작성하지 않는다
205
+ /** 게시 직전 Drift Guard가 대조할 기준 (OM §11.9). */
206
+ snapshot: z.array(CanonicalSnapshot).default([]),
207
+ threadLastEventId: z.string().optional(),
208
+ allowedWrites: z.array(z.string()).default([]), // 명시된 것 외 모든 write 금지
209
+ claimedBy: z.string().optional(),
210
+ consumedAt: Timestamp.optional(),
211
+ resultRef: z.string().optional(),
212
+ });
213
+ // ── QueueItem / MonitorEvent / State ────────────────────────────────────────
214
+ export const QueueState = z.enum(['READY', 'ACTIVE', 'BLOCKED', 'DONE']);
215
+ /** 승인되어 수행하기로 한 작업 (OM §4.8). inbox = 판단 대기, queue = 승인된 작업. */
216
+ export const QueueItem = z.object({
217
+ id: QueueItemId,
218
+ version: Version,
219
+ state: QueueState,
220
+ title: z.string().min(1),
221
+ sourceRequestId: RequestId.optional(),
222
+ blockId: BlockId.optional(),
223
+ sessionId: SessionId.optional(),
224
+ });
225
+ /** Phase B 처리 결과 — 부분 실패해도 cursor는 전진하고 실패분만 재시도한다 (OM §10.5). */
226
+ export const EventProcessing = z.enum(['LOGGED', 'PROCESSED', 'PENDING_RETRY']);
227
+ export const MonitorEvent = z.object({
228
+ eventKey: EventKey,
229
+ version: Version,
230
+ detectedAt: Timestamp,
231
+ type: EventType,
232
+ suggestedPriority: Priority,
233
+ processing: EventProcessing,
234
+ inboxCandidate: z.boolean(),
235
+ requestId: RequestId.optional(), // Phase B가 패킷을 만든 경우
236
+ /**
237
+ * 관련성 판정과 그 근거 (C-07 §3). 숫자가 아니라 문장을 남기는 이유는, 판정이 틀렸을 때
238
+ * 사람이 어디가 틀렸는지 보고 뒤집을 수 있어야 하기 때문이다.
239
+ * optional이라 기존 event 파일이 그대로 읽힌다.
240
+ */
241
+ relevance: z
242
+ .object({
243
+ explicit: z.enum(['HIGH', 'LOW']),
244
+ actual: z.enum(['HIGH', 'LOW']),
245
+ disposition: z.enum(['INBOX', 'SHADOW']),
246
+ evidence: z.array(z.string()).default([]),
247
+ })
248
+ .optional(),
249
+ /**
250
+ * 조사를 다시 하려면 원본이 있어야 한다. cursor는 이미 지나갔고 provider가 같은 것을
251
+ * 또 주리라는 보장이 없으므로, 재시도에 필요한 만큼을 여기 남긴다 (OM §10.5).
252
+ */
253
+ replay: z
254
+ .object({
255
+ reference: z.string().min(1),
256
+ raw: z.unknown().optional(),
257
+ hints: z.record(z.unknown()).optional(),
258
+ /**
259
+ * 이미 끝난 조사 단계 (C-07 §6.3). 재시도가 처음부터 다시 하면 비싼 단계에서
260
+ * 실패한 사건은 영영 넘지 못한다.
261
+ */
262
+ steps: z
263
+ .array(z.object({
264
+ id: z.string().min(1),
265
+ kind: z.enum(['DONE', 'UNDECIDABLE', 'SKIPPED']),
266
+ findings: z.array(z.string()).default([]),
267
+ detail: z.string().optional(),
268
+ }))
269
+ .optional(),
270
+ })
271
+ .optional(),
272
+ });
273
+ /**
274
+ * Controller single-writer 상태 문서 (OM §7.2). Monitoring 절에는 포인터만 두고
275
+ * 갱신자 없는 숫자·시각을 두지 않는다.
276
+ */
277
+ export const ControlState = z.object({
278
+ version: Version,
279
+ activeBlock: BlockId.optional(),
280
+ activeSessions: z.array(SessionId).default([]),
281
+ recentHandoff: SessionId.optional(),
282
+ writeBoundaryOccupancy: z.array(z.object({ sessionId: SessionId, paths: z.array(z.string()) })).default([]),
283
+ awaitingController: z.array(z.string()).default([]),
284
+ controllerAttention: z.array(z.string()).default([]),
285
+ });
@@ -0,0 +1,41 @@
1
+ import { z } from 'zod';
2
+ /** REQ-0042 — 전 채널(MM/Local/Web)이 공유하는 ApprovalRequest identity. */
3
+ export declare const RequestId: z.ZodString;
4
+ /** G-0007 — Execution Grant. */
5
+ export declare const GrantId: z.ZodString;
6
+ /** S-20260822-01 — Logical Session (날짜 + 당일 순번). */
7
+ export declare const SessionId: z.ZodString;
8
+ /** B-05 — Block. */
9
+ export declare const BlockId: z.ZodString;
10
+ /**
11
+ * X-20260826-04 — Bounded Query (날짜 + 당일 순번). 세션 id와 같은 모양으로 읽힌다.
12
+ * 정규식을 따로 내보내는 이유: 이 id는 entity가 아니라 Adapter scope의 키가 되므로
13
+ * (C-04 §0.1) 파일명 변환 전에 문법을 확인하는 자리가 zod 밖에도 필요하다.
14
+ */
15
+ export declare const QUERY_ID: RegExp;
16
+ export declare const QueryId: z.ZodString;
17
+ /** Q-0003 — Queue item. */
18
+ export declare const QueueItemId: z.ZodString;
19
+ /**
20
+ * Monitor event key — dedupe는 log tail 대조가 아니라 이 key의 exact lookup으로 한다
21
+ * (OM §10.4). 문법은 `<kind>:<opaque>` 이며 **kind는 adapter가 정한다** (C-07 §9).
22
+ *
23
+ * 처음에는 provider 4종(notification·comment·review·review_comment)을 열거했는데, 그러면
24
+ * 새 adapter의 키도 회수 경로가 만든 키도 통과하지 못한다 — Core가 provider의 사건 어휘를
25
+ * 아는 셈이기도 하다. 요구는 둘뿐이다: **같은 외부 변화는 같은 키, 다른 변화는 다른 키.**
26
+ * 기존 4종은 이 문법 안에 그대로 든다.
27
+ *
28
+ * kind를 소문자 토큰으로 좁히는 이유는 Markdown Adapter의 파일명 변환 때문이다 —
29
+ * 대소문자만 다른 키가 같은 파일이 되면 dedupe가 조용히 오판한다.
30
+ */
31
+ export declare const EVENT_KEY: RegExp;
32
+ export declare const EventKey: z.ZodString;
33
+ /**
34
+ * entity 낙관적 동시성 토큰. 결정·전이는 expectedVersion CAS로만 수행한다 (C-01 §8).
35
+ * 단조 증가 정수 — 전이 1회당 +1.
36
+ */
37
+ export declare const Version: z.ZodNumber;
38
+ /** ISO8601 시각 문자열. */
39
+ export declare const Timestamp: z.ZodString;
40
+ /** `REQ-0042` → `REQ-0043`. 순번 폭이 넘치면 자리수를 늘린다. */
41
+ export declare function nextId(prefix: string, existing: readonly string[], width?: number): string;
@@ -0,0 +1,50 @@
1
+ // ASC의 모든 Logical Entity가 쓰는 식별자 형식을 한곳에 고정하는 파일.
2
+ // 정본: docs/contracts/C-01_approval-port.md §1 (Core identity = request_id,
3
+ // 파일명·채널 표시는 전부 projection), §14 (exact 형식은 구현 단계 확정 — 여기가 그 지점).
4
+ import { z } from 'zod';
5
+ /** REQ-0042 — 전 채널(MM/Local/Web)이 공유하는 ApprovalRequest identity. */
6
+ export const RequestId = z.string().regex(/^REQ-\d{4,}$/);
7
+ /** G-0007 — Execution Grant. */
8
+ export const GrantId = z.string().regex(/^G-\d{4,}$/);
9
+ /** S-20260822-01 — Logical Session (날짜 + 당일 순번). */
10
+ export const SessionId = z.string().regex(/^S-\d{8}-\d{2}$/);
11
+ /** B-05 — Block. */
12
+ export const BlockId = z.string().regex(/^B-\d{2,}$/);
13
+ /**
14
+ * X-20260826-04 — Bounded Query (날짜 + 당일 순번). 세션 id와 같은 모양으로 읽힌다.
15
+ * 정규식을 따로 내보내는 이유: 이 id는 entity가 아니라 Adapter scope의 키가 되므로
16
+ * (C-04 §0.1) 파일명 변환 전에 문법을 확인하는 자리가 zod 밖에도 필요하다.
17
+ */
18
+ export const QUERY_ID = /^X-\d{8}-\d{2}$/;
19
+ export const QueryId = z.string().regex(QUERY_ID);
20
+ /** Q-0003 — Queue item. */
21
+ export const QueueItemId = z.string().regex(/^Q-\d{4,}$/);
22
+ /**
23
+ * Monitor event key — dedupe는 log tail 대조가 아니라 이 key의 exact lookup으로 한다
24
+ * (OM §10.4). 문법은 `<kind>:<opaque>` 이며 **kind는 adapter가 정한다** (C-07 §9).
25
+ *
26
+ * 처음에는 provider 4종(notification·comment·review·review_comment)을 열거했는데, 그러면
27
+ * 새 adapter의 키도 회수 경로가 만든 키도 통과하지 못한다 — Core가 provider의 사건 어휘를
28
+ * 아는 셈이기도 하다. 요구는 둘뿐이다: **같은 외부 변화는 같은 키, 다른 변화는 다른 키.**
29
+ * 기존 4종은 이 문법 안에 그대로 든다.
30
+ *
31
+ * kind를 소문자 토큰으로 좁히는 이유는 Markdown Adapter의 파일명 변환 때문이다 —
32
+ * 대소문자만 다른 키가 같은 파일이 되면 dedupe가 조용히 오판한다.
33
+ */
34
+ export const EVENT_KEY = /^[a-z][a-z0-9_-]*:.+$/;
35
+ export const EventKey = z.string().regex(EVENT_KEY);
36
+ /**
37
+ * entity 낙관적 동시성 토큰. 결정·전이는 expectedVersion CAS로만 수행한다 (C-01 §8).
38
+ * 단조 증가 정수 — 전이 1회당 +1.
39
+ */
40
+ export const Version = z.number().int().nonnegative();
41
+ /** ISO8601 시각 문자열. */
42
+ export const Timestamp = z.string().datetime({ offset: true });
43
+ const counterOf = (id) => Number(id.slice(id.lastIndexOf('-') + 1));
44
+ /** `REQ-0042` → `REQ-0043`. 순번 폭이 넘치면 자리수를 늘린다. */
45
+ export function nextId(prefix, existing, width = 4) {
46
+ const max = existing
47
+ .filter((id) => id.startsWith(`${prefix}-`))
48
+ .reduce((acc, id) => Math.max(acc, counterOf(id) || 0), 0);
49
+ return `${prefix}-${String(max + 1).padStart(width, '0')}`;
50
+ }
@@ -0,0 +1,26 @@
1
+ import type { ActorRole, ApprovalRequest, ExecutionGrant, MonitorEvent, QueueItem, Session } from './entities.ts';
2
+ export type TransitionRule<S extends string> = {
3
+ from: S;
4
+ to: S;
5
+ /** 이 전이를 수행할 수 있는 주체. 비어 있는 전이는 만들지 않는다. */
6
+ actors: readonly ActorRole[];
7
+ /** 전이 성립에 필요한 부가 조건의 이름 — 래퍼가 실제 값 존재를 확인한다. */
8
+ requires?: 'checkpoint' | 'handoff' | 'decision' | 'result';
9
+ };
10
+ export type TransitionFailure = 'ILLEGAL_TRANSITION' | 'FORBIDDEN_ACTOR' | 'MISSING_REQUIREMENT';
11
+ export declare class TransitionError extends Error {
12
+ readonly reason: TransitionFailure;
13
+ constructor(reason: TransitionFailure, message: string);
14
+ }
15
+ /** 전이표에서 terminal 상태(나가는 전이가 없는 상태) 목록. */
16
+ export declare function terminalStates<S extends string>(table: readonly TransitionRule<S>[], all: readonly S[]): S[];
17
+ export declare const SESSION_TRANSITIONS: readonly TransitionRule<Session['status']>[];
18
+ export declare function transitionSession(session: Session, to: Session['status'], actor: ActorRole, patch?: Partial<Pick<Session, 'checkpoint' | 'handoff'>>): Session;
19
+ export declare const REQUEST_TRANSITIONS: readonly TransitionRule<ApprovalRequest['status']>[];
20
+ export declare function transitionRequest(request: ApprovalRequest, to: ApprovalRequest['status'], actor: ActorRole, patch?: Partial<Pick<ApprovalRequest, 'decision' | 'resultRef'>>): ApprovalRequest;
21
+ export declare const GRANT_TRANSITIONS: readonly TransitionRule<ExecutionGrant['status']>[];
22
+ export declare function transitionGrant(grant: ExecutionGrant, to: ExecutionGrant['status'], actor: ActorRole, patch?: Partial<Pick<ExecutionGrant, 'claimedBy' | 'consumedAt' | 'resultRef'>>): ExecutionGrant;
23
+ export declare const QUEUE_TRANSITIONS: readonly TransitionRule<QueueItem['state']>[];
24
+ export declare function transitionQueueItem(item: QueueItem, to: QueueItem['state'], actor: ActorRole, patch?: Partial<Pick<QueueItem, 'sessionId' | 'blockId'>>): QueueItem;
25
+ export declare const EVENT_TRANSITIONS: readonly TransitionRule<MonitorEvent['processing']>[];
26
+ export declare function transitionEvent(event: MonitorEvent, to: MonitorEvent['processing'], actor: ActorRole, patch?: Partial<Pick<MonitorEvent, 'requestId'>>): MonitorEvent;
@@ -0,0 +1,124 @@
1
+ // Entity 상태머신 — 어떤 전이가 가능하고 누가 수행할 수 있는지를 데이터로 선언한다.
2
+ // 전이표가 설계 정본(OM §6.2 Session / §11.2 Inbox / §11.5·§11.9 Grant / §10.5 Monitor)과
3
+ // 1:1 대응하는지는 tests/model.test.ts가 검증한다.
4
+ // 저장·동시성은 여기 없다 — 순수 함수로 다음 상태를 계산할 뿐이고, 실제 CAS는
5
+ // State Store Port가 수행한다 (OM §7.0·§7.2).
6
+ export class TransitionError extends Error {
7
+ reason;
8
+ constructor(reason, message) {
9
+ super(message);
10
+ this.name = 'TransitionError';
11
+ this.reason = reason;
12
+ }
13
+ }
14
+ function resolve(table, from, to, actor) {
15
+ const candidates = table.filter((r) => r.from === from && r.to === to);
16
+ if (candidates.length === 0) {
17
+ throw new TransitionError('ILLEGAL_TRANSITION', `${from} → ${to} is not a legal transition`);
18
+ }
19
+ const allowed = candidates.find((r) => r.actors.includes(actor));
20
+ if (!allowed) {
21
+ throw new TransitionError('FORBIDDEN_ACTOR', `${actor} may not perform ${from} → ${to}`);
22
+ }
23
+ return allowed;
24
+ }
25
+ /** 전이표에서 terminal 상태(나가는 전이가 없는 상태) 목록. */
26
+ export function terminalStates(table, all) {
27
+ return all.filter((s) => !table.some((r) => r.from === s));
28
+ }
29
+ // ── Session (OM §6.2) ───────────────────────────────────────────────────────
30
+ // Logical Session은 여러 Physical Run에 걸친다. PAUSED는 Checkpoint 없이 만들 수 없고,
31
+ // DONE은 Handoff 없이 만들 수 없다 — 인수인계 누락을 상태머신이 막는다.
32
+ export const SESSION_TRANSITIONS = [
33
+ { from: 'READY', to: 'ACTIVE', actors: ['session', 'controller'] },
34
+ { from: 'ACTIVE', to: 'PAUSED', actors: ['session'], requires: 'checkpoint' },
35
+ { from: 'ACTIVE', to: 'BLOCKED', actors: ['session'] },
36
+ { from: 'ACTIVE', to: 'DONE', actors: ['session'], requires: 'handoff' },
37
+ { from: 'ACTIVE', to: 'FAILED', actors: ['session', 'controller'] },
38
+ { from: 'PAUSED', to: 'ACTIVE', actors: ['session', 'controller'] },
39
+ { from: 'PAUSED', to: 'FAILED', actors: ['controller'] },
40
+ { from: 'BLOCKED', to: 'ACTIVE', actors: ['controller'] },
41
+ { from: 'BLOCKED', to: 'FAILED', actors: ['controller'] },
42
+ ];
43
+ export function transitionSession(session, to, actor, patch = {}) {
44
+ const rule = resolve(SESSION_TRANSITIONS, session.status, to, actor);
45
+ const next = { ...session, ...patch, status: to, version: session.version + 1 };
46
+ if (rule.requires === 'checkpoint' && !next.checkpoint) {
47
+ throw new TransitionError('MISSING_REQUIREMENT', `${session.id}: PAUSED requires a checkpoint`);
48
+ }
49
+ if (rule.requires === 'handoff' && !next.handoff) {
50
+ throw new TransitionError('MISSING_REQUIREMENT', `${session.id}: DONE requires a handoff`);
51
+ }
52
+ return next;
53
+ }
54
+ // ── ApprovalRequest (OM §11.2) ──────────────────────────────────────────────
55
+ // AWAITING_APPROVAL은 Monitor만 만들고(생성 시점), 처분은 Controller 전용,
56
+ // DONE 전환은 Grant를 수행한 Executor만 한다. APPROVED는 승인 상태일 뿐
57
+ // 외부 write 권한이 아니다 — 실제 write는 ExecutionGrant에만 있다.
58
+ export const REQUEST_TRANSITIONS = [
59
+ { from: 'AWAITING_APPROVAL', to: 'APPROVED', actors: ['controller'], requires: 'decision' },
60
+ { from: 'AWAITING_APPROVAL', to: 'QUEUED', actors: ['controller'], requires: 'decision' },
61
+ { from: 'AWAITING_APPROVAL', to: 'DEFERRED', actors: ['controller'], requires: 'decision' },
62
+ { from: 'AWAITING_APPROVAL', to: 'DISMISSED', actors: ['controller'], requires: 'decision' },
63
+ { from: 'DEFERRED', to: 'APPROVED', actors: ['controller'], requires: 'decision' },
64
+ { from: 'DEFERRED', to: 'QUEUED', actors: ['controller'], requires: 'decision' },
65
+ { from: 'DEFERRED', to: 'DISMISSED', actors: ['controller'], requires: 'decision' },
66
+ { from: 'APPROVED', to: 'DONE', actors: ['executor'], requires: 'result' },
67
+ ];
68
+ export function transitionRequest(request, to, actor, patch = {}) {
69
+ const rule = resolve(REQUEST_TRANSITIONS, request.status, to, actor);
70
+ const next = { ...request, ...patch, status: to, version: request.version + 1 };
71
+ if (rule.requires === 'decision' && !next.decision) {
72
+ throw new TransitionError('MISSING_REQUIREMENT', `${request.id}: ${to} requires a recorded decision`);
73
+ }
74
+ if (rule.requires === 'result' && !next.resultRef) {
75
+ throw new TransitionError('MISSING_REQUIREMENT', `${request.id}: DONE requires a result reference`);
76
+ }
77
+ return next;
78
+ }
79
+ // ── ExecutionGrant (OM §11.5·§11.9) ─────────────────────────────────────────
80
+ // 성공한 Grant는 재소비 불가(EXECUTED terminal). CLAIMED에서도 게시 직전 Drift Guard가
81
+ // 대상 변화를 발견하면 INVALIDATED로 끝난다 — 오래된 초안이 나가지 않게 하는 마지막 장치.
82
+ export const GRANT_TRANSITIONS = [
83
+ { from: 'READY', to: 'CLAIMED', actors: ['executor'] },
84
+ { from: 'READY', to: 'INVALIDATED', actors: ['controller', 'executor'] },
85
+ { from: 'READY', to: 'EXPIRED', actors: ['controller', 'executor'] },
86
+ { from: 'CLAIMED', to: 'EXECUTED', actors: ['executor'], requires: 'result' },
87
+ // CLAIM 이후의 무효화는 Drift Guard를 돌린 Executor만 한다 (OM §11.9). 정본에 Controller가
88
+ // 진행 중인 Grant를 가로채는 경로가 없고, 실행 직전 상태 판단은 Executor 손에 있다.
89
+ { from: 'CLAIMED', to: 'INVALIDATED', actors: ['executor'] },
90
+ ];
91
+ export function transitionGrant(grant, to, actor, patch = {}) {
92
+ const rule = resolve(GRANT_TRANSITIONS, grant.status, to, actor);
93
+ const next = { ...grant, ...patch, status: to, version: grant.version + 1 };
94
+ if (rule.requires === 'result' && !next.resultRef) {
95
+ throw new TransitionError('MISSING_REQUIREMENT', `${grant.id}: EXECUTED requires a result reference`);
96
+ }
97
+ return next;
98
+ }
99
+ // ── QueueItem (OM §4.8) — Controller ONLY ───────────────────────────────────
100
+ export const QUEUE_TRANSITIONS = [
101
+ { from: 'READY', to: 'ACTIVE', actors: ['controller'] },
102
+ { from: 'READY', to: 'BLOCKED', actors: ['controller'] },
103
+ { from: 'ACTIVE', to: 'BLOCKED', actors: ['controller'] },
104
+ { from: 'ACTIVE', to: 'DONE', actors: ['controller'] },
105
+ { from: 'BLOCKED', to: 'READY', actors: ['controller'] },
106
+ { from: 'BLOCKED', to: 'ACTIVE', actors: ['controller'] },
107
+ ];
108
+ export function transitionQueueItem(item, to, actor, patch = {}) {
109
+ resolve(QUEUE_TRANSITIONS, item.state, to, actor);
110
+ return { ...item, ...patch, state: to, version: item.version + 1 };
111
+ }
112
+ // ── MonitorEvent (OM §10.5) ─────────────────────────────────────────────────
113
+ // Phase B 실패는 그 이벤트만 PENDING_RETRY로 남는다 — cursor는 전진하고 다음 Run이
114
+ // 실패분만 재시도한다.
115
+ export const EVENT_TRANSITIONS = [
116
+ { from: 'LOGGED', to: 'PROCESSED', actors: ['monitor'] },
117
+ { from: 'LOGGED', to: 'PENDING_RETRY', actors: ['monitor'] },
118
+ { from: 'PENDING_RETRY', to: 'PROCESSED', actors: ['monitor'] },
119
+ { from: 'PENDING_RETRY', to: 'PENDING_RETRY', actors: ['monitor'] },
120
+ ];
121
+ export function transitionEvent(event, to, actor, patch = {}) {
122
+ resolve(EVENT_TRANSITIONS, event.processing, to, actor);
123
+ return { ...event, ...patch, processing: to, version: event.version + 1 };
124
+ }
@@ -0,0 +1,111 @@
1
+ import { z } from 'zod';
2
+ import type { InventoryItem } from '../../ports/inventory.ts';
3
+ import type { ScopedStore } from '../../ports/state-store.ts';
4
+ export declare const CoverageRecord: z.ZodObject<{
5
+ reference: z.ZodString;
6
+ state: z.ZodString;
7
+ revisionMarker: z.ZodString;
8
+ updatedAt: z.ZodString;
9
+ /** 마지막으로 목록에서 본 시각. missing 판정의 기준이 된다. */
10
+ seenAt: z.ZodString;
11
+ }, "strip", z.ZodTypeAny, {
12
+ reference: string;
13
+ state: string;
14
+ revisionMarker: string;
15
+ updatedAt: string;
16
+ seenAt: string;
17
+ }, {
18
+ reference: string;
19
+ state: string;
20
+ revisionMarker: string;
21
+ updatedAt: string;
22
+ seenAt: string;
23
+ }>;
24
+ export type CoverageRecord = z.infer<typeof CoverageRecord>;
25
+ /**
26
+ * 지금까지 어디까지 확인했는가 (C-07 §8.2). **100% 보장을 대신하는 값이 아니라, 무엇을
27
+ * 확인했고 무엇을 모르는지 사람이 볼 수 있게 하는 값이다.**
28
+ */
29
+ export declare const CoverageHealth: z.ZodObject<{
30
+ lastHotEventAt: z.ZodOptional<z.ZodString>;
31
+ lastReconcileAt: z.ZodOptional<z.ZodString>;
32
+ lastCensusAt: z.ZodOptional<z.ZodString>;
33
+ /** 이 시각까지의 변경은 회수했다고 보는 기준선. */
34
+ coverageWatermark: z.ZodOptional<z.ZodString>;
35
+ /**
36
+ * 마지막 열거가 목록을 빠짐없이 훑었는가. **모르면 false다** — 이 값이 false인 열거로
37
+ * missing을 판정하면 없는 상실을 만들어낸다.
38
+ */
39
+ paginationComplete: z.ZodDefault<z.ZodBoolean>;
40
+ sourceHealthy: z.ZodDefault<z.ZodBoolean>;
41
+ /** 마지막 실패 이유. 상태만 주면 고칠 수가 없다. */
42
+ detail: z.ZodOptional<z.ZodString>;
43
+ }, "strip", z.ZodTypeAny, {
44
+ paginationComplete: boolean;
45
+ sourceHealthy: boolean;
46
+ detail?: string | undefined;
47
+ lastHotEventAt?: string | undefined;
48
+ lastReconcileAt?: string | undefined;
49
+ lastCensusAt?: string | undefined;
50
+ coverageWatermark?: string | undefined;
51
+ }, {
52
+ detail?: string | undefined;
53
+ lastHotEventAt?: string | undefined;
54
+ lastReconcileAt?: string | undefined;
55
+ lastCensusAt?: string | undefined;
56
+ coverageWatermark?: string | undefined;
57
+ paginationComplete?: boolean | undefined;
58
+ sourceHealthy?: boolean | undefined;
59
+ }>;
60
+ export type CoverageHealth = z.infer<typeof CoverageHealth>;
61
+ export type SweepKind = 'reconcile' | 'census';
62
+ export type CoverageDiff =
63
+ /** 처음 보는 리소스. */
64
+ {
65
+ kind: 'NEW';
66
+ item: InventoryItem;
67
+ }
68
+ /** 지난번과 달라졌다. */
69
+ | {
70
+ kind: 'CHANGED';
71
+ item: InventoryItem;
72
+ previous: CoverageRecord;
73
+ }
74
+ /**
75
+ * 알고 있었는데 이번 목록에 없다. **원인을 추측하지 않는다** (C-07 §1.5) —
76
+ * 삭제일 수도, 권한·가시성 변화일 수도, filter나 provider 오류일 수도 있다.
77
+ */
78
+ | {
79
+ kind: 'RESOURCE_MISSING';
80
+ reference: string;
81
+ previous: CoverageRecord;
82
+ };
83
+ export declare class CoverageLedger {
84
+ #private;
85
+ constructor(scope: ScopedStore, now?: () => string);
86
+ get(reference: string): Promise<CoverageRecord | null>;
87
+ list(): Promise<CoverageRecord[]>;
88
+ /** 이번에 본 모습을 남긴다. 빠른 경로도 이걸 갱신해야 회수 경로가 같은 것을 또 올리지 않는다. */
89
+ record(item: {
90
+ reference: string;
91
+ state?: string;
92
+ revisionMarker: string;
93
+ updatedAt?: string;
94
+ }): Promise<void>;
95
+ /** 이번 목록과 지난 기록을 견준다. 달라진 것만 돌려준다. */
96
+ diff(items: readonly InventoryItem[]): Promise<CoverageDiff[]>;
97
+ /**
98
+ * 알던 것 중 이번 목록에 없는 것.
99
+ *
100
+ * **빠짐없이 훑은 열거에서만 판정한다.** 페이지를 다 돌지 못한 목록으로 비교하면
101
+ * 멀쩡한 리소스가 사라졌다고 나온다.
102
+ */
103
+ missing(seen: ReadonlySet<string>, paginationComplete: boolean): Promise<CoverageDiff[]>;
104
+ health(): Promise<CoverageHealth>;
105
+ updateHealth(patch: Partial<CoverageHealth>): Promise<CoverageHealth>;
106
+ }
107
+ /**
108
+ * 사람이 읽는 상태. **"100% 감지 보장"이라고 쓰지 않는다** (C-07 §8.1) — provider 장애·자격
109
+ * 문제·전달 실패·가시성 변화 중 어느 것도 ASC가 통제하지 못한다.
110
+ */
111
+ export declare function renderHealth(sourceId: string, health: CoverageHealth): string[];