devcontour-harness 0.0.0-stage → 0.1.0-alpha.5

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 (197) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +161 -2
  3. package/docs/architecture.md +473 -0
  4. package/docs/compatibility.md +192 -0
  5. package/docs/contracts.md +575 -0
  6. package/docs/quickstart.md +182 -0
  7. package/docs/upgrade.md +166 -0
  8. package/examples/README.md +26 -0
  9. package/examples/external-connector/README.md +19 -0
  10. package/examples/external-connector/connector.mjs +55 -0
  11. package/examples/files-only/README.md +28 -0
  12. package/examples/files-only/context/norms/rules.md +4 -0
  13. package/examples/files-only/graph.json +23 -0
  14. package/examples/files-only/policy.json +30 -0
  15. package/examples/one-vendor/README.md +19 -0
  16. package/examples/one-vendor/policy.json +27 -0
  17. package/examples/single-task/README.md +18 -0
  18. package/examples/single-task/graph.json +27 -0
  19. package/examples/single-task/policy-same-vendor.json +55 -0
  20. package/examples/single-task/policy.json +52 -0
  21. package/examples/single-task/src/.gitkeep +0 -0
  22. package/examples/single-task/test/run.js +19 -0
  23. package/examples/two-vendors/policy.json +30 -0
  24. package/lib/agents/cli-adapter.d.ts +48 -0
  25. package/lib/agents/cli-adapter.js +384 -0
  26. package/lib/agents/inspect.d.ts +6 -0
  27. package/lib/agents/inspect.js +46 -0
  28. package/lib/agents/probe-server.d.ts +2 -0
  29. package/lib/agents/probe-server.js +13 -0
  30. package/lib/agents/review-probe.d.ts +123 -0
  31. package/lib/agents/review-probe.js +279 -0
  32. package/lib/agents/runtime-events.d.ts +11 -0
  33. package/lib/agents/runtime-events.js +21 -0
  34. package/lib/agents/schemas.d.ts +215 -0
  35. package/lib/agents/schemas.js +120 -0
  36. package/lib/agents/usage.d.ts +8 -0
  37. package/lib/agents/usage.js +83 -0
  38. package/lib/cli.d.ts +2 -0
  39. package/lib/cli.js +331 -0
  40. package/lib/connectors/files.d.ts +108 -0
  41. package/lib/connectors/files.js +133 -0
  42. package/lib/contracts/checks.d.ts +307 -0
  43. package/lib/contracts/checks.js +181 -0
  44. package/lib/contracts/cli.d.ts +3086 -0
  45. package/lib/contracts/cli.js +425 -0
  46. package/lib/contracts/context.d.ts +135 -0
  47. package/lib/contracts/context.js +129 -0
  48. package/lib/contracts/digest.d.ts +9 -0
  49. package/lib/contracts/digest.js +22 -0
  50. package/lib/contracts/errors.d.ts +79 -0
  51. package/lib/contracts/errors.js +68 -0
  52. package/lib/contracts/events.d.ts +795 -0
  53. package/lib/contracts/events.js +249 -0
  54. package/lib/contracts/execution.d.ts +3048 -0
  55. package/lib/contracts/execution.js +864 -0
  56. package/lib/contracts/failure.d.ts +67 -0
  57. package/lib/contracts/failure.js +122 -0
  58. package/lib/contracts/index.d.ts +17 -0
  59. package/lib/contracts/index.js +17 -0
  60. package/lib/contracts/policy.d.ts +1065 -0
  61. package/lib/contracts/policy.js +534 -0
  62. package/lib/contracts/ports.d.ts +367 -0
  63. package/lib/contracts/ports.js +1 -0
  64. package/lib/contracts/primitives.d.ts +63 -0
  65. package/lib/contracts/primitives.js +73 -0
  66. package/lib/contracts/profiles.d.ts +322 -0
  67. package/lib/contracts/profiles.js +105 -0
  68. package/lib/contracts/published.d.ts +3014 -0
  69. package/lib/contracts/published.js +34 -0
  70. package/lib/contracts/result.d.ts +1109 -0
  71. package/lib/contracts/result.js +273 -0
  72. package/lib/contracts/review.d.ts +210 -0
  73. package/lib/contracts/review.js +153 -0
  74. package/lib/contracts/version.d.ts +17 -0
  75. package/lib/contracts/version.js +17 -0
  76. package/lib/contracts/work.d.ts +173 -0
  77. package/lib/contracts/work.js +178 -0
  78. package/lib/control/base-move.d.ts +44 -0
  79. package/lib/control/base-move.js +90 -0
  80. package/lib/control/doctor.d.ts +35 -0
  81. package/lib/control/doctor.js +71 -0
  82. package/lib/control/harness.d.ts +1079 -0
  83. package/lib/control/harness.js +714 -0
  84. package/lib/control/provenance.d.ts +17 -0
  85. package/lib/control/provenance.js +25 -0
  86. package/lib/control/recovery.d.ts +235 -0
  87. package/lib/control/recovery.js +236 -0
  88. package/lib/control/review.d.ts +62 -0
  89. package/lib/control/review.js +170 -0
  90. package/lib/control/workflow.d.ts +247 -0
  91. package/lib/control/workflow.js +259 -0
  92. package/lib/engine/budget.d.ts +345 -0
  93. package/lib/engine/budget.js +278 -0
  94. package/lib/engine/classify.d.ts +8 -0
  95. package/lib/engine/classify.js +31 -0
  96. package/lib/engine/engine.d.ts +1503 -0
  97. package/lib/engine/engine.js +2259 -0
  98. package/lib/engine/failure.d.ts +1 -0
  99. package/lib/engine/failure.js +3 -0
  100. package/lib/engine/joint-runner.d.ts +257 -0
  101. package/lib/engine/joint-runner.js +254 -0
  102. package/lib/engine/measured.d.ts +15 -0
  103. package/lib/engine/measured.js +82 -0
  104. package/lib/engine/prompt.d.ts +38 -0
  105. package/lib/engine/prompt.js +74 -0
  106. package/lib/engine/property-recovery.d.ts +50 -0
  107. package/lib/engine/property-recovery.js +169 -0
  108. package/lib/engine/repair.d.ts +33 -0
  109. package/lib/engine/repair.js +82 -0
  110. package/lib/engine/result.d.ts +9 -0
  111. package/lib/engine/result.js +166 -0
  112. package/lib/engine/reuse.d.ts +80 -0
  113. package/lib/engine/reuse.js +97 -0
  114. package/lib/engine/runner.d.ts +335 -0
  115. package/lib/engine/runner.js +1467 -0
  116. package/lib/engine/standalone.d.ts +6 -0
  117. package/lib/engine/standalone.js +48 -0
  118. package/lib/engine/usage.d.ts +87 -0
  119. package/lib/engine/usage.js +165 -0
  120. package/lib/environment/isolation.d.ts +110 -0
  121. package/lib/environment/isolation.js +238 -0
  122. package/lib/environment/lifecycle.d.ts +40 -0
  123. package/lib/environment/lifecycle.js +191 -0
  124. package/lib/environment/live-calls.d.ts +14 -0
  125. package/lib/environment/live-calls.js +72 -0
  126. package/lib/environment/process.d.ts +32 -0
  127. package/lib/environment/process.js +368 -0
  128. package/lib/environment/redaction.d.ts +22 -0
  129. package/lib/environment/redaction.js +102 -0
  130. package/lib/environment/resources.d.ts +31 -0
  131. package/lib/environment/resources.js +120 -0
  132. package/lib/index.d.ts +65 -0
  133. package/lib/index.js +67 -0
  134. package/lib/package.d.ts +2 -0
  135. package/lib/package.js +3 -0
  136. package/lib/profiles/resolve.d.ts +186 -0
  137. package/lib/profiles/resolve.js +242 -0
  138. package/lib/storage/document.d.ts +1186 -0
  139. package/lib/storage/document.js +80 -0
  140. package/lib/storage/sqlite.d.ts +35 -0
  141. package/lib/storage/sqlite.js +260 -0
  142. package/lib/testing/index.d.ts +5 -0
  143. package/lib/testing/index.js +5 -0
  144. package/lib/testing/store-contract.d.ts +8 -0
  145. package/lib/testing/store-contract.js +167 -0
  146. package/lib/validate.d.ts +36 -0
  147. package/lib/validate.js +175 -0
  148. package/lib/verification/acceptance.d.ts +268 -0
  149. package/lib/verification/acceptance.js +654 -0
  150. package/lib/verification/checks.d.ts +72 -0
  151. package/lib/verification/checks.js +190 -0
  152. package/lib/verification/confirm.d.ts +125 -0
  153. package/lib/verification/confirm.js +411 -0
  154. package/lib/verification/corpus.d.ts +1303 -0
  155. package/lib/verification/corpus.js +744 -0
  156. package/lib/verification/dependencies.d.ts +66 -0
  157. package/lib/verification/dependencies.js +144 -0
  158. package/lib/verification/gates.d.ts +144 -0
  159. package/lib/verification/gates.js +559 -0
  160. package/lib/verification/joint.d.ts +63 -0
  161. package/lib/verification/joint.js +121 -0
  162. package/lib/verification/proof.d.ts +54 -0
  163. package/lib/verification/proof.js +96 -0
  164. package/lib/verification/property.d.ts +86 -0
  165. package/lib/verification/property.js +214 -0
  166. package/lib/verification/requirements.d.ts +25 -0
  167. package/lib/verification/requirements.js +69 -0
  168. package/lib/verification/self-check.d.ts +29 -0
  169. package/lib/verification/self-check.js +99 -0
  170. package/lib/verification/spike.d.ts +79 -0
  171. package/lib/verification/spike.js +124 -0
  172. package/package.json +79 -4
  173. package/profiles/expo-mobile.json +15 -0
  174. package/profiles/go-api.json +15 -0
  175. package/profiles/mobile-maestro.json +29 -0
  176. package/profiles/nest-api.json +26 -0
  177. package/profiles/next-product.json +72 -0
  178. package/profiles/node-checks.json +77 -0
  179. package/profiles/python-api.json +32 -0
  180. package/profiles/react-native.json +50 -0
  181. package/profiles/react-vite-admin.json +71 -0
  182. package/schemas/context-snapshot.schema.json +231 -0
  183. package/schemas/contract-document.schema.json +385 -0
  184. package/schemas/execution-document.schema.json +3540 -0
  185. package/schemas/execution-policy.schema.json +2148 -0
  186. package/schemas/joint-verification.schema.json +617 -0
  187. package/schemas/result-envelope.schema.json +1440 -0
  188. package/schemas/review-record.schema.json +177 -0
  189. package/schemas/run-event.schema.json +2365 -0
  190. package/schemas/run-result.schema.json +1363 -0
  191. package/schemas/stack-profile.schema.json +492 -0
  192. package/schemas/work-graph.schema.json +381 -0
  193. package/schemas/work-order.schema.json +256 -0
  194. package/skills/harness/SKILL.md +64 -0
  195. package/templates/AGENTS.md +13 -0
  196. package/templates/graph.json +22 -0
  197. package/templates/policy.json +28 -0
@@ -0,0 +1,1503 @@
1
+ import { type ReproductionRecord, type AttemptState, type Author, type CallRecord, type ContractDocument, type EvidenceRecord, type ExecutionHost, type ExecutionPolicy, type ExecutionTransaction, type ExecutionView, type FailureKind, type GraphState, type JointVerification, type JointVerificationState, type NotPromise, type Prerequisite, type PropertyRecovery, type ReviewRecord, type TechnicalApproval, type TransactionKind, type WorkGraph, type WorkState } from '../contracts/index.ts';
2
+ import { type BudgetContext, type CallMeta } from './budget.ts';
3
+ /** Префикс причины неудавшегося продления: первая причина не затирается следствиями. */
4
+ export declare const HEARTBEAT_FAILED = "\u041F\u0440\u043E\u0434\u043B\u0435\u043D\u0438\u0435 \u0432\u043B\u0430\u0434\u0435\u043D\u0438\u044F \u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C";
5
+ export interface EngineOptions {
6
+ host: ExecutionHost;
7
+ policy: ExecutionPolicy;
8
+ /** Версия движка в попытках; по умолчанию — версия пакета. */
9
+ version?: string;
10
+ now?: () => number;
11
+ id?: () => string;
12
+ /**
13
+ * Источник политики не менялся с её загрузки: `undefined` — да, иначе
14
+ * причина. Без источника (политика передана объектом) право записать свою
15
+ * политику как применённую ограничено остальными условиями.
16
+ */
17
+ provenance?: () => string | undefined;
18
+ /**
19
+ * Тождество применённой политики по формуле хоста — digest (64 hex),
20
+ * например, файла конфигурации, из которого хост строит политику. По умолчанию —
21
+ * `policyDigest(policy)`. Им сравниваются применённая и загруженная
22
+ * политика в барьере и reconfigure; изменение политики в памяти после
23
+ * создания движка проверяется по-прежнему по её содержимому.
24
+ */
25
+ policyIdentity?: string;
26
+ /**
27
+ * Записывать политику процесса применённой при получении роли controller
28
+ * (по умолчанию — да). Хост, который записывает применённую политику сам
29
+ * при запуске своего планировщика, выключает это: прямая выдача попытки
30
+ * без запуска очереди тогда не меняет применённую политику.
31
+ */
32
+ recordPolicyOnLead?: boolean;
33
+ }
34
+ export type Phase = 'running' | 'verifying' | 'reviewing' | 'integrating';
35
+ /**
36
+ * Правила исполнения и технической приёмки. Все решения — внутри одной
37
+ * транзакции хранилища: состояние, на котором решение принято, и запись
38
+ * решения неразделимы. Хост может только добавить запрет (`HostGuards`) и
39
+ * дать основания (`BasisProvider`); снять отказ движка он не может.
40
+ */
41
+ export declare class Engine {
42
+ readonly host: ExecutionHost;
43
+ readonly policy: ExecutionPolicy;
44
+ readonly version: string;
45
+ private readonly now;
46
+ private readonly id;
47
+ /** Digest политики процесса на момент создания движка. */
48
+ readonly loadedPolicy: string;
49
+ /** Содержимое политики при создании движка: изменение в памяти — отказ применения. */
50
+ private readonly loadedContent;
51
+ /** Поколение применённой политики, которое процесс застал при запуске. */
52
+ private readonly observedGeneration;
53
+ private readonly provenance;
54
+ /** Владелец роли controller, под которым этот процесс её получил. */
55
+ private ownLeader?;
56
+ private readonly recordOnLead;
57
+ constructor(options: EngineOptions);
58
+ /** Часы движка: по ним считаются сроки владения. */
59
+ clock(): number;
60
+ /** Версия проверок поручения: формула хоста, если он её задал, иначе harness. */
61
+ private checksOf;
62
+ /**
63
+ * Основание эпизода разбора по свойству: формула хоста, если он её задал,
64
+ * иначе harness. Хост с принятой историей держит свою — ею подписаны его
65
+ * прежние эпизоды и попытки, и они не закрываются как «новое основание».
66
+ */
67
+ episodeOf(work: WorkState, view: ExecutionView): string;
68
+ private tx;
69
+ private repository;
70
+ read<T>(fn: (view: ExecutionView) => NotPromise<T>): T;
71
+ /**
72
+ * Регистрация графа. Повтор той же редакции источника с тем же digest —
73
+ * продолжение прежней работы; другой digest той же редакции или того же
74
+ * ID графа — `REVISION_CONFLICT`. Предпосылки должны быть подтверждены:
75
+ * `engine` — успешной попыткой этого хранилища с тем же result SHA,
76
+ * `trusted` — решением источника (`trusted`), переданным вызывающим вместе
77
+ * с проверкой доступности SHA в target ref (`reachable`).
78
+ */
79
+ register(graph: WorkGraph, prerequisites: {
80
+ trusted: (p: Prerequisite) => boolean;
81
+ reachable: (p: Prerequisite) => boolean;
82
+ }): {
83
+ graph: GraphState;
84
+ resumed: boolean;
85
+ replaced?: boolean;
86
+ };
87
+ /** Раунд технического ревью плана или контракта сохраняется в истории предмета. */
88
+ /**
89
+ * `document` — документ контракта, который ревью рассматривало: он
90
+ * сохраняется по своему digest, и из него движок берёт договор проверки.
91
+ */
92
+ /**
93
+ * `historyRounds` — раунды предмета, проведённые хостом до harness
94
+ * (`TechnicalReviewInput.history`): первый раунд движка следует за ними, и
95
+ * дальше нумерация идёт подряд от него.
96
+ */
97
+ recordReview(record: ReviewRecord, document?: ContractDocument, historyRounds?: number): {
98
+ id: string;
99
+ kind: "plan" | "contract";
100
+ subject: string;
101
+ digest: string;
102
+ round: number;
103
+ at: string;
104
+ approved: boolean;
105
+ summary: string;
106
+ authorRuntime: "codex" | "claude" | "fixture" | "external";
107
+ reviewerRuntime: "codex" | "claude" | "fixture";
108
+ sameVendor: boolean;
109
+ findings: {
110
+ id: string;
111
+ severity: "blocking" | "note";
112
+ message: string;
113
+ status: "open" | "closed" | "withdrawn";
114
+ openedIn: number;
115
+ changedIn: number;
116
+ rule?: string | undefined;
117
+ path?: string | null | undefined;
118
+ line?: number | null | undefined;
119
+ basis?: string | undefined;
120
+ }[];
121
+ artifact: string;
122
+ reviewerModel?: string | undefined;
123
+ reviewerBinding?: string | undefined;
124
+ contextDigest?: string | undefined;
125
+ };
126
+ reviews(kind: 'plan' | 'contract', subject: string): ReviewRecord[];
127
+ /**
128
+ * Digest назначения reviewer вида `kind` для автора по текущей политике:
129
+ * runtime, модель, effort, содержимое профиля и правило вендора. Раунд ревью
130
+ * подтверждает предмет только при том же назначении.
131
+ */
132
+ reviewerBinding(kind: 'plan' | 'contract' | 'code', author: Author): string | undefined;
133
+ /**
134
+ * Контракты поручения: последний раунд ревью каждого контракта — его digest,
135
+ * одобрен, при текущем назначении reviewer; у хоста с портом `contracts` —
136
+ * редакция этого digest согласована хостом. Иначе поручение не выдаётся и не
137
+ * принимается.
138
+ */
139
+ contractBlocker(work: WorkState, view: ExecutionView): string | undefined;
140
+ /**
141
+ * Согласование графа. Привязано к digest проверенного предложения: в
142
+ * режиме `agent` нужен одобренный последний раунд ревью этого digest, в
143
+ * режиме `operator` — проведённое ревью этого digest и решение оператора.
144
+ * Другого способа сделать поручения готовыми к выдаче нет.
145
+ */
146
+ approve(graphId: string, actor: TechnicalApproval['actor'], options?: {
147
+ /**
148
+ * Связать согласование со снимком контекста последнего раунда ревью
149
+ * плана: другой снимок при том же графе — новое согласование.
150
+ */
151
+ bindContext?: boolean;
152
+ }): {
153
+ graph: {
154
+ id: string;
155
+ digest: string;
156
+ graph: {
157
+ apiVersion: "devcontour-harness/v1alpha1";
158
+ id: string;
159
+ source: {
160
+ connector: string;
161
+ ref: string;
162
+ revision: string;
163
+ };
164
+ orders: {
165
+ apiVersion: "devcontour-harness/v1alpha1";
166
+ id: string;
167
+ source: {
168
+ connector: string;
169
+ ref: string;
170
+ revision: string;
171
+ };
172
+ title: string;
173
+ goal: string;
174
+ acceptance: string[];
175
+ role: string;
176
+ repositoryId: string;
177
+ relatedRepositories: string[];
178
+ writeScope: string[];
179
+ dependsOn: string[];
180
+ requirements: {
181
+ id: string;
182
+ source: string;
183
+ digest: string;
184
+ text: string;
185
+ gate: string;
186
+ scenario: string;
187
+ testId?: string | undefined;
188
+ }[];
189
+ contracts: {
190
+ id: string;
191
+ digest: string;
192
+ }[];
193
+ context: string[];
194
+ gates?: string[] | undefined;
195
+ consumers?: {
196
+ repositoryId: string;
197
+ gates: string[];
198
+ }[] | undefined;
199
+ properties?: string[] | undefined;
200
+ resources?: string[] | undefined;
201
+ }[];
202
+ prerequisites: {
203
+ id: string;
204
+ repositoryId: string;
205
+ resultSha: string;
206
+ receipt: {
207
+ kind: "engine";
208
+ runId: string;
209
+ } | {
210
+ kind: "trusted";
211
+ issuer: string;
212
+ digest: string;
213
+ };
214
+ }[];
215
+ };
216
+ policyDigest: string;
217
+ registeredAt: string;
218
+ approval?: {
219
+ actor: "agent" | "operator";
220
+ at: string;
221
+ authorRuntime?: "codex" | "claude" | "fixture" | undefined;
222
+ reviewerRuntime?: "codex" | "claude" | "fixture" | undefined;
223
+ artifact?: string | undefined;
224
+ digest?: string | undefined;
225
+ context?: {
226
+ digest: string | null;
227
+ } | undefined;
228
+ } | undefined;
229
+ };
230
+ changed: boolean;
231
+ };
232
+ /**
233
+ * Роль принадлежит этому процессу: тот же владелец и та же версия движка
234
+ * (роль прежней версии под тем же именем держит другой процесс); живая,
235
+ * если не `expired`.
236
+ */
237
+ private mine;
238
+ /** Захват или продление роли единственного controller. */
239
+ lead(owner: string): boolean;
240
+ release(owner: string): boolean;
241
+ /**
242
+ * Пауза оператора или штатной остановки: новые claims запрещены (семантика
243
+ * DevContour `pause`). Штатная остановка поверх существующей паузы её не
244
+ * подменяет, кроме паузы операции reconfigure. Пауза оператора — решение
245
+ * человека: она заменяет любую паузу, и классы отказов прежней паузы с ней
246
+ * не переживают.
247
+ */
248
+ pause(reason: 'operator' | 'shutdown'): {
249
+ control: {
250
+ paused: boolean;
251
+ pauseReason?: "runtime" | "operator" | "shutdown" | "base-check" | "reconfigure" | undefined;
252
+ pausedAt?: string | undefined;
253
+ pauseOperation?: string | undefined;
254
+ pauseFailures?: ("gate" | "unknown" | "review" | "environment" | "cancelled" | "timeout" | "provider-auth" | "stale-base" | "empty-result" | "scope-violation" | "merge" | "budget")[] | undefined;
255
+ waits?: {
256
+ kind: "base-check" | "context-lock";
257
+ detail: string;
258
+ at: string;
259
+ operation?: string | undefined;
260
+ }[] | undefined;
261
+ appliedPolicy?: {
262
+ digest: string;
263
+ at: string;
264
+ by: string;
265
+ generation?: number | undefined;
266
+ } | undefined;
267
+ reconfigure?: {
268
+ operation: string;
269
+ digest: string;
270
+ requestedAt: string;
271
+ appliedAt?: string | undefined;
272
+ appliedBy?: string | undefined;
273
+ error?: string | undefined;
274
+ } | undefined;
275
+ };
276
+ changed: boolean;
277
+ };
278
+ /**
279
+ * Снять паузу — решение человека (семантика DevContour `pause(false)`):
280
+ * снимает паузу оператора, штатной остановки и отказа runtime вместе с её
281
+ * классами отказов. Паузу операции reconfigure снимает только сама
282
+ * операция: `PAUSED`.
283
+ */
284
+ resume(): {
285
+ control: {
286
+ paused: boolean;
287
+ pauseReason?: "runtime" | "operator" | "shutdown" | "base-check" | "reconfigure" | undefined;
288
+ pausedAt?: string | undefined;
289
+ pauseOperation?: string | undefined;
290
+ pauseFailures?: ("gate" | "unknown" | "review" | "environment" | "cancelled" | "timeout" | "provider-auth" | "stale-base" | "empty-result" | "scope-violation" | "merge" | "budget")[] | undefined;
291
+ waits?: {
292
+ kind: "base-check" | "context-lock";
293
+ detail: string;
294
+ at: string;
295
+ operation?: string | undefined;
296
+ }[] | undefined;
297
+ appliedPolicy?: {
298
+ digest: string;
299
+ at: string;
300
+ by: string;
301
+ generation?: number | undefined;
302
+ } | undefined;
303
+ reconfigure?: {
304
+ operation: string;
305
+ digest: string;
306
+ requestedAt: string;
307
+ appliedAt?: string | undefined;
308
+ appliedBy?: string | undefined;
309
+ error?: string | undefined;
310
+ } | undefined;
311
+ };
312
+ changed: boolean;
313
+ };
314
+ /** Снять паузу отказа после устранения причины: только названные классы. */
315
+ clearFailure(kinds: FailureKind[]): {
316
+ paused: boolean;
317
+ pauseReason?: "runtime" | "operator" | "shutdown" | "base-check" | "reconfigure" | undefined;
318
+ pausedAt?: string | undefined;
319
+ pauseOperation?: string | undefined;
320
+ pauseFailures?: ("gate" | "unknown" | "review" | "environment" | "cancelled" | "timeout" | "provider-auth" | "stale-base" | "empty-result" | "scope-violation" | "merge" | "budget")[] | undefined;
321
+ waits?: {
322
+ kind: "base-check" | "context-lock";
323
+ detail: string;
324
+ at: string;
325
+ operation?: string | undefined;
326
+ }[] | undefined;
327
+ appliedPolicy?: {
328
+ digest: string;
329
+ at: string;
330
+ by: string;
331
+ generation?: number | undefined;
332
+ } | undefined;
333
+ reconfigure?: {
334
+ operation: string;
335
+ digest: string;
336
+ requestedAt: string;
337
+ appliedAt?: string | undefined;
338
+ appliedBy?: string | undefined;
339
+ error?: string | undefined;
340
+ } | undefined;
341
+ };
342
+ /** Правила и области бюджетов: политика движка и группы хоста. */
343
+ budgetContext(): BudgetContext;
344
+ /**
345
+ * Бюджетное удержание готовых поручений. Бюджеты считаются по сохранённым
346
+ * записям вызовов: сброс попыток и перезапуск их не уменьшают. Удержанное
347
+ * поручение несёт причину, пока бюджет не позволит снова; выключенные
348
+ * бюджеты удержание снимают.
349
+ */
350
+ private budgetBlocks;
351
+ /**
352
+ * Начало вызова модели: атрибуция на момент вызова и проверка бюджетов — в
353
+ * одной транзакции с регистрацией идущего вызова. Превышенный или
354
+ * неопределённый бюджет — отказ `budget` до вызова, без записи; два процесса
355
+ * не проходят проверку оба, не видя друг друга. Процесс не с применённой
356
+ * политикой — отказ `APPROVAL_STALE` до вызова.
357
+ */
358
+ beginCall(record: CallRecord, meta: CallMeta): CallRecord;
359
+ /** Ход или итог вызова: запись заменяется целиком. */
360
+ saveCall(record: CallRecord): void;
361
+ /**
362
+ * Закрывает записи вызовов, чей процесс умер, исходом `abandoned`. Запись
363
+ * без процесса закрывается, когда её попытка уже не активна, а без попытки —
364
+ * спустя два предела времени вызова. Под транзакцией запись перечитывается:
365
+ * вызов мог завершиться между сканированием и закрытием, и его исход и
366
+ * стоимость не затираются устаревшим снимком.
367
+ */
368
+ abandonCalls(at?: number): string[];
369
+ /**
370
+ * Закрепляет атрибуцию прежних записей вызовов (без закреплённых поручения и
371
+ * группы) по текущему составу — до того, как новая редакция графа его
372
+ * изменит: иначе перенос поручения снял бы или разделил учтённый расход
373
+ * группы. Уже закреплённое не меняется; запись перечитывается под
374
+ * транзакцией.
375
+ */
376
+ pinLegacyCalls(): number;
377
+ /** Обзор бюджетов для статуса; `undefined` — бюджеты не заданы. */
378
+ budgets(): {
379
+ onUnknown: "stop" | "continue";
380
+ budgets: {
381
+ reason?: string | undefined;
382
+ scope: import("./budget.ts").BudgetScope;
383
+ state: "exceeded" | "undetermined" | "within";
384
+ limit: {
385
+ usd?: number;
386
+ timeMs?: number;
387
+ };
388
+ reportedUsd: number;
389
+ estimatedUsd: number;
390
+ unknownCalls: number;
391
+ unfinishedCalls: number;
392
+ abandonedCalls: number;
393
+ errorCalls: number;
394
+ uncoveredRuns: number;
395
+ durationMs: number;
396
+ remainingUsd: number | null;
397
+ remainingMs: number | null;
398
+ }[];
399
+ } | undefined;
400
+ /** Почему поручение сейчас не выдаётся; пусто — может быть выдано. */
401
+ blockers(work: WorkState, view: ExecutionView): {
402
+ code: "NOT_READY" | "PAUSED" | "ATTEMPTS_EXHAUSTED" | "APPROVAL_STALE" | "APPROVAL_REQUIRED" | "BUDGET_EXHAUSTED" | "REVIEWER_SAME_VENDOR" | "REVIEWER_MISSING" | "INVALID_INPUT";
403
+ message: string;
404
+ }[];
405
+ /**
406
+ * Назначение ревью кода для роли поручения недопустимо политикой (reviewer
407
+ * того же вендора без явного разрешения, нет reviewer или роли). Такое
408
+ * поручение не выдаётся и видно блокером; остальная очередь идёт дальше.
409
+ */
410
+ private reviewerBlocker;
411
+ /**
412
+ * Выдать следующую готовую работу. Только действующий controller; не на
413
+ * паузе; не сверх `concurrency`; первое готовое поручение в порядке
414
+ * регистрации. Поручение с согласованием, не совпадающим с текущим
415
+ * основанием, — отказ `APPROVAL_STALE`, а не выдача.
416
+ */
417
+ claim(owner: string): AttemptState | undefined;
418
+ /**
419
+ * Единственная точка fencing: операция попытки выполняется, только если
420
+ * попытка активна, токен её, срок владения не истёк, она — текущая попытка
421
+ * поручения, политика не сменилась и хост не возражает.
422
+ */
423
+ withAttempt<T>(id: string, token: string, kind: TransactionKind, fn: (attempt: AttemptState, work: WorkState, tx: ExecutionTransaction) => NotPromise<T>): T;
424
+ heartbeat(id: string, token: string): void;
425
+ /** Причина неудавшегося продления: записывается первой и не затирается. Истёкший срок допустим, чужой токен — нет. */
426
+ heartbeatFailed(id: string, token: string, reason: string): boolean;
427
+ phase(id: string, token: string, phase: Phase, data?: Partial<Pick<AttemptState, 'baseSha' | 'candidateSha' | 'integrationSha' | 'worktree'>>): void;
428
+ /** Дополнить попытку сведениями её хода (контекст, ресурсы, замеры) под тем же fencing. */
429
+ update(id: string, token: string, kind: TransactionKind, patch: Partial<Pick<AttemptState, 'context' | 'contextSnapshot' | 'resources' | 'model' | 'timings'>>): void;
430
+ /** Снимок зависимостей, против которого идёт попытка; закрепляется до реализации. */
431
+ dependencies(id: string, token: string, snapshots: NonNullable<AttemptState['dependencies']>): void;
432
+ /** Свежесть снимка зависимостей на момент окончания: записывается, не отклоняет. */
433
+ dependencyFreshness(id: string, token: string, freshness: NonNullable<AttemptState['dependencyFreshness']>): void;
434
+ /** Что проверено у потребителя: его голова и снимок всех его зависимостей. */
435
+ consumerChecked(id: string, token: string, repositoryId: string, checked: NonNullable<NonNullable<AttemptState['consumers']>[number]['checked']>): void;
436
+ /**
437
+ * Gates репозиториев, не пройденные на integration ни одним из принятых
438
+ * поручений: покрытие — по фактическому собственному evidence на принятом
439
+ * SHA попытки под текущим основанием политики, для любой области. Evidence потребителя — проверка другого
440
+ * компонента и не в счёт; запись ревью не закрывает одноимённый test gate.
441
+ * Решение о приёмке набора поручений — за хостом.
442
+ */
443
+ uncoveredGates(workIds: string[]): {
444
+ repositoryId: string;
445
+ gates: string[];
446
+ }[];
447
+ /**
448
+ * Основания реализации записываются до неё: по ним следующая попытка решит,
449
+ * можно ли взять этого кандидата. В той же транзакции решается, берётся ли
450
+ * кандидат предыдущей попытки вместо новой реализации.
451
+ */
452
+ implementationBasis(id: string, token: string, basis: string): {
453
+ reusedFrom: {
454
+ runId: string;
455
+ candidateSha: string;
456
+ };
457
+ reason?: undefined;
458
+ } | {
459
+ reason: string;
460
+ reusedFrom?: undefined;
461
+ };
462
+ /** Черновик реализации, оборванной до кандидата (см. `AttemptState.partial`). */
463
+ recordPartial(id: string, token: string, partial: NonNullable<AttemptState['partial']>): void;
464
+ /** Попытка начала с черновика предыдущей (см. `AttemptState.continuedFrom`). */
465
+ continuation(id: string, token: string, from: NonNullable<AttemptState['continuedFrom']>): void;
466
+ /** Начало стадии попытки; `timingFinished` закрывает её исходом. */
467
+ timingStarted(id: string, token: string, stage: string): string;
468
+ timingFinished(id: string, token: string, timingId: string, outcome: 'passed' | 'failed'): void;
469
+ /**
470
+ * Записать воспроизведения из находок ревью попытки. Одно свойство на одном
471
+ * входе — одна запись поручения: повтор добавляет прогон, а не запись;
472
+ * подтверждение запуском не отменяется последующим неподтверждённым.
473
+ * Реестр ограничен: старые записи вытесняются, а не копятся без предела.
474
+ */
475
+ recordReproductions(id: string, token: string, sha: string, items: Omit<ReproductionRecord, 'key' | 'inputHash' | 'runs'>[]): number;
476
+ /** Воспроизведения поручения, от старых к новым. */
477
+ reproductions(workId: string): ReproductionRecord[];
478
+ evidence(id: string, token: string, evidence: Omit<EvidenceRecord, 'id' | 'runId' | 'createdAt'>): {
479
+ id: string;
480
+ runId: string;
481
+ kind: "test" | "review";
482
+ phase: "candidate" | "integration";
483
+ sha: string;
484
+ gate: string;
485
+ passed: boolean;
486
+ createdAt: string;
487
+ command: string[];
488
+ exitCode: number;
489
+ log: string;
490
+ digest: string;
491
+ summary: string;
492
+ consumer?: string | undefined;
493
+ inspection?: {
494
+ mode: "commands" | "diff-only";
495
+ source: "codex-events" | "unavailable";
496
+ commands: {
497
+ command: string;
498
+ exitCode: number;
499
+ output: string;
500
+ }[];
501
+ reason: string | null;
502
+ } | undefined;
503
+ findings?: {
504
+ severity: string;
505
+ message: string;
506
+ path?: string | null | undefined;
507
+ line?: number | null | undefined;
508
+ }[] | undefined;
509
+ tests?: {
510
+ id: string;
511
+ status: "passed" | "failed" | "skipped";
512
+ opaque?: true | undefined;
513
+ }[] | undefined;
514
+ testsTruncated?: boolean | undefined;
515
+ property?: {
516
+ seed: number;
517
+ reproduce: string;
518
+ contracts: Record<string, string>;
519
+ artifacts: {
520
+ path: string;
521
+ blob: string;
522
+ }[];
523
+ report?: {
524
+ version: 1;
525
+ properties: {
526
+ testId: string;
527
+ status: "passed" | "failed" | "running";
528
+ cases: number;
529
+ families?: Record<string, number> | undefined;
530
+ error?: string | undefined;
531
+ current?: unknown;
532
+ counterexample?: {
533
+ original: unknown;
534
+ reduced?: unknown;
535
+ expected?: unknown;
536
+ actual?: unknown;
537
+ reductionTimedOut?: boolean | undefined;
538
+ case?: string | undefined;
539
+ } | undefined;
540
+ }[];
541
+ seed?: number | undefined;
542
+ corpus?: {
543
+ digest: string;
544
+ executed: number;
545
+ passed: number;
546
+ witness: string;
547
+ failed: {
548
+ id: string;
549
+ property: string;
550
+ expected?: unknown;
551
+ actual?: unknown;
552
+ }[];
553
+ } | undefined;
554
+ } | undefined;
555
+ problem?: string | undefined;
556
+ level?: "manifest" | "legacy" | undefined;
557
+ manifest?: {
558
+ digest: string;
559
+ properties: string[];
560
+ corpus?: {
561
+ digest: string;
562
+ cases: number;
563
+ } | undefined;
564
+ } | undefined;
565
+ coverage?: string[] | undefined;
566
+ } | undefined;
567
+ checks?: string | undefined;
568
+ };
569
+ /**
570
+ * Техническая приёмка. Требует фазу интеграции и integration SHA, PASS
571
+ * каждого обязательного gate в обеих фазах на точных SHA, PASS
572
+ * независимого ревью в обеих фазах; затем условие хоста `beforeAccept`.
573
+ * `publish` — CAS продвижения target ref — выполняется внутри той же
574
+ * транзакции до фиксации: его отказ откатывает приёмку целиком.
575
+ */
576
+ finish(id: string, token: string, sha: string, publish?: () => void): {
577
+ workId: string;
578
+ runId: string;
579
+ sha: string;
580
+ };
581
+ /**
582
+ * Отказ попытки. Класс и отпечаток записываются вместе с отказом. Отказ
583
+ * окружения до кандидата (`blocked`) попытку не засчитывает. Отказ,
584
+ * который повторится на любой работе (`repairPolicy.blocksQueue`), ставит
585
+ * паузу `runtime` с классом отказа — снимает её устранение причины.
586
+ */
587
+ fail(id: string, token: string, error: string, options?: {
588
+ kind?: FailureKind;
589
+ blocked?: boolean;
590
+ heartbeatCause?: string;
591
+ /** Свойства отказа: `bound` — объявленные поручением, `unbound` — прочие названные. */
592
+ properties?: {
593
+ bound: string[];
594
+ unbound: string[];
595
+ };
596
+ }): {
597
+ recovery?: {
598
+ id: string;
599
+ status: "stopped" | "closed" | "awaiting-lead" | "analyzing" | "applying" | "retry" | "retrying" | "awaiting-user";
600
+ } | undefined;
601
+ properties?: string[] | undefined;
602
+ workId: string;
603
+ runId: string;
604
+ kind: "gate" | "unknown" | "review" | "environment" | "cancelled" | "timeout" | "provider-auth" | "stale-base" | "empty-result" | "scope-violation" | "merge" | "budget";
605
+ blocked: boolean;
606
+ };
607
+ private recoveryEvent;
608
+ /** Принятие корпуса, ждущее оператора, удерживает выдачу поручения. */
609
+ private awaitsOperator;
610
+ private episode;
611
+ /**
612
+ * Учесть разбор ведущего до вызова модели. Один автоматический разбор на
613
+ * эпизод: повторная регистрация, рестарт или дубликат бесплатного вызова
614
+ * не получают. Исчерпанные попытки — решение человека.
615
+ */
616
+ recoveryReserve(workId: string, recoveryId: string): {
617
+ id: string;
618
+ property: string;
619
+ basis: string;
620
+ runs: string[];
621
+ openedAt: string;
622
+ status: "stopped" | "closed" | "awaiting-lead" | "analyzing" | "applying" | "retry" | "retrying" | "awaiting-user";
623
+ calls: number;
624
+ history: {
625
+ at: string;
626
+ event: string;
627
+ detail?: string | undefined;
628
+ }[];
629
+ version: number;
630
+ decision?: {
631
+ action: "environment" | "add-cases" | "new-approach" | "strengthen-checker" | "split-task" | "clarify-obligation";
632
+ reason: string;
633
+ evidence: string[];
634
+ source: {
635
+ kind: "operator" | "lead";
636
+ runtime?: string | undefined;
637
+ model?: string | undefined;
638
+ };
639
+ decidedAt: string;
640
+ hypothesis?: string | undefined;
641
+ plan?: string | undefined;
642
+ cases?: {
643
+ input: string;
644
+ expected?: string | null | undefined;
645
+ }[] | undefined;
646
+ proposal?: string | undefined;
647
+ conflict?: string | undefined;
648
+ } | undefined;
649
+ retryRun?: string | undefined;
650
+ reason?: string | undefined;
651
+ };
652
+ /**
653
+ * Записать решение разбора — ведущего или ручное — и перевести эпизод в
654
+ * состояние разрешённого действия. Ручное решение сверяет версию записи:
655
+ * устаревшее не применяется.
656
+ */
657
+ recoveryDecide(workId: string, recoveryId: string, decision: NonNullable<PropertyRecovery['decision']>, expectedVersion?: number): {
658
+ id: string;
659
+ property: string;
660
+ basis: string;
661
+ runs: string[];
662
+ openedAt: string;
663
+ status: "stopped" | "closed" | "awaiting-lead" | "analyzing" | "applying" | "retry" | "retrying" | "awaiting-user";
664
+ calls: number;
665
+ history: {
666
+ at: string;
667
+ event: string;
668
+ detail?: string | undefined;
669
+ }[];
670
+ version: number;
671
+ decision?: {
672
+ action: "environment" | "add-cases" | "new-approach" | "strengthen-checker" | "split-task" | "clarify-obligation";
673
+ reason: string;
674
+ evidence: string[];
675
+ source: {
676
+ kind: "operator" | "lead";
677
+ runtime?: string | undefined;
678
+ model?: string | undefined;
679
+ };
680
+ decidedAt: string;
681
+ hypothesis?: string | undefined;
682
+ plan?: string | undefined;
683
+ cases?: {
684
+ input: string;
685
+ expected?: string | null | undefined;
686
+ }[] | undefined;
687
+ proposal?: string | undefined;
688
+ conflict?: string | undefined;
689
+ } | undefined;
690
+ retryRun?: string | undefined;
691
+ reason?: string | undefined;
692
+ };
693
+ /** Итог добавления случаев: подтверждённый случай разрешает один повтор. */
694
+ recoveryApplied(workId: string, recoveryId: string, outcomes: {
695
+ outcome: string;
696
+ reason: string;
697
+ }[]): {
698
+ id: string;
699
+ property: string;
700
+ basis: string;
701
+ runs: string[];
702
+ openedAt: string;
703
+ status: "stopped" | "closed" | "awaiting-lead" | "analyzing" | "applying" | "retry" | "retrying" | "awaiting-user";
704
+ calls: number;
705
+ history: {
706
+ at: string;
707
+ event: string;
708
+ detail?: string | undefined;
709
+ }[];
710
+ version: number;
711
+ decision?: {
712
+ action: "environment" | "add-cases" | "new-approach" | "strengthen-checker" | "split-task" | "clarify-obligation";
713
+ reason: string;
714
+ evidence: string[];
715
+ source: {
716
+ kind: "operator" | "lead";
717
+ runtime?: string | undefined;
718
+ model?: string | undefined;
719
+ };
720
+ decidedAt: string;
721
+ hypothesis?: string | undefined;
722
+ plan?: string | undefined;
723
+ cases?: {
724
+ input: string;
725
+ expected?: string | null | undefined;
726
+ }[] | undefined;
727
+ proposal?: string | undefined;
728
+ conflict?: string | undefined;
729
+ } | undefined;
730
+ retryRun?: string | undefined;
731
+ reason?: string | undefined;
732
+ };
733
+ /** Разбор прервался без решения: вызов учтён, повтор не бесплатен. */
734
+ recoveryInterrupted(workId: string, recoveryId: string, reason: string): {
735
+ id: string;
736
+ property: string;
737
+ basis: string;
738
+ runs: string[];
739
+ openedAt: string;
740
+ status: "stopped" | "closed" | "awaiting-lead" | "analyzing" | "applying" | "retry" | "retrying" | "awaiting-user";
741
+ calls: number;
742
+ history: {
743
+ at: string;
744
+ event: string;
745
+ detail?: string | undefined;
746
+ }[];
747
+ version: number;
748
+ decision?: {
749
+ action: "environment" | "add-cases" | "new-approach" | "strengthen-checker" | "split-task" | "clarify-obligation";
750
+ reason: string;
751
+ evidence: string[];
752
+ source: {
753
+ kind: "operator" | "lead";
754
+ runtime?: string | undefined;
755
+ model?: string | undefined;
756
+ };
757
+ decidedAt: string;
758
+ hypothesis?: string | undefined;
759
+ plan?: string | undefined;
760
+ cases?: {
761
+ input: string;
762
+ expected?: string | null | undefined;
763
+ }[] | undefined;
764
+ proposal?: string | undefined;
765
+ conflict?: string | undefined;
766
+ } | undefined;
767
+ retryRun?: string | undefined;
768
+ reason?: string | undefined;
769
+ };
770
+ /**
771
+ * Применённая политика в хранилище — единственный источник правды; у
772
+ * процесса нет своего «поколения», которое мог бы рассинхронизировать
773
+ * откат транзакции. Процесс текущий, только если его политика совпадает с
774
+ * применённой. Записать свою вместо применённой (перезапуск с другой
775
+ * политикой) он может, только если нет живого чужого controller, политика
776
+ * не менялась в памяти после создания движка, её источник не менялся с
777
+ * загрузки и после запуска процесса никто не применил другую: поколение
778
+ * считается по записям, а не по часам.
779
+ */
780
+ private mayAdopt;
781
+ private adoptPolicy;
782
+ /** Почему процесс не вправе действовать под своей политикой; `undefined` — вправе. */
783
+ private staleReason;
784
+ private policyStale;
785
+ /**
786
+ * Общий барьер: в транзакциях выдачи, согласования, приёмки и совместной
787
+ * проверки процесс сверяет свою политику с применённой. Первый процесс,
788
+ * действующий после законной смены политики без живого controller,
789
+ * записывает свою как применённую в той же транзакции — откат отменит и её.
790
+ */
791
+ assertCurrentPolicy(tx: ExecutionTransaction): void;
792
+ /**
793
+ * Запуск controller записывает его политику применённой, если её ещё нет
794
+ * или процесс вправе её сменить. Неприменённый запрос операции остаётся.
795
+ * Хост с `recordPolicyOnLead: false` вызывает это сам при запуске controller.
796
+ */
797
+ recordPolicy(owner: string): void;
798
+ private reconfiguring;
799
+ /** Сведения о политике процесса и применённой. */
800
+ policyState(): {
801
+ stale?: string | undefined;
802
+ reconfigure?: {
803
+ operation: string;
804
+ digest: string;
805
+ requestedAt: string;
806
+ appliedAt?: string | undefined;
807
+ appliedBy?: string | undefined;
808
+ error?: string | undefined;
809
+ } | undefined;
810
+ applied?: {
811
+ digest: string;
812
+ at: string;
813
+ by: string;
814
+ generation?: number | undefined;
815
+ } | undefined;
816
+ loaded: string;
817
+ };
818
+ /**
819
+ * Пауза операции: ставится, только если очередь не остановлена, и
820
+ * помечается операцией. Пауза человека не перезаписывается.
821
+ */
822
+ operationPause(operation: string): boolean;
823
+ /** Снять паузу операции — только свою; паузу человека она не трогает. */
824
+ operationResume(operation: string): boolean;
825
+ /**
826
+ * Запросить применение политики с ожидаемым digest (`policyDigest`
827
+ * загруженной политики). Идемпотентно; повтор той же операцией сбрасывает
828
+ * прежний отказ — политику могли исправить. Пока запрос не применён, новые
829
+ * попытки не выдаются.
830
+ */
831
+ requestReconfigure(operation: string, digest: string): {
832
+ operation: string;
833
+ digest: string;
834
+ changed: boolean;
835
+ retried?: undefined;
836
+ } | {
837
+ operation: string;
838
+ digest: string;
839
+ changed: boolean;
840
+ retried: boolean;
841
+ };
842
+ /**
843
+ * Работы, идущие под действующей политикой. Запись с истёкшим сроком не
844
+ * держит применение: её результат и так не будет принят (владение
845
+ * проверяется при каждой записи), а держать применение из-за неё значило
846
+ * бы ждать вечно.
847
+ */
848
+ busyWork(view: ExecutionView): string[];
849
+ /**
850
+ * Применить запрос этим процессом: только если его политика — ровно
851
+ * ожидаемая и работ под прежней не осталось. Политика другого digest —
852
+ * отказ с обоими digest (повтор операции его сбросит). Применение и снятие
853
+ * паузы операции — одна транзакция.
854
+ */
855
+ applyReconfigure(by: string): {
856
+ status: 'none';
857
+ } | {
858
+ status: 'busy';
859
+ busy: string[];
860
+ } | {
861
+ status: 'waiting';
862
+ expected: string;
863
+ } | {
864
+ status: 'failed';
865
+ error: string;
866
+ } | {
867
+ status: 'applied';
868
+ generation: number;
869
+ };
870
+ /**
871
+ * Процесс, работающий с прежней политикой, применить запрос не может: он
872
+ * ждёт перезапуска с ожидаемой. Такой процесс применение не отклоняет.
873
+ */
874
+ serveReconfigure(by: string): {
875
+ status: "none";
876
+ } | {
877
+ status: "busy";
878
+ busy: string[];
879
+ } | {
880
+ status: "waiting";
881
+ expected: string;
882
+ } | {
883
+ status: "failed";
884
+ error: string;
885
+ } | {
886
+ status: "applied";
887
+ generation: number;
888
+ };
889
+ /**
890
+ * Записать отказ применения с причиной хоста (например, файл политики не
891
+ * читается или не совпадает с ожидаемым). Только для неприменённого запроса
892
+ * той же операции и digest; повтор операции сбрасывает отказ.
893
+ */
894
+ rejectReconfigure(operation: string, digest: string, error: string): boolean;
895
+ /** Снять завершённый или отменённый запрос операции. */
896
+ clearReconfigure(operation: string): boolean;
897
+ /**
898
+ * Удержать поручения от выдачи на время операции. Поручение, удерживаемое
899
+ * другой операцией, — конфликт: две операции не меняют одно поручение
900
+ * одновременно. Повтор той же операции ничего не меняет. `fence` —
901
+ * проверка владения операцией в той же транзакции: утративший владение
902
+ * ничего не пишет.
903
+ */
904
+ hold(ids: string[], operation: string, reason: string, fence?: (view: ExecutionView) => void): string[];
905
+ /** Снять удержание операции — со всех или с перечисленных поручений; чужие не трогаются. */
906
+ releaseHold(operation: string, ids?: string[], fence?: (view: ExecutionView) => void): string[];
907
+ /** Передать удержание от одной операции другой без окна выдачи. */
908
+ transferHold(from: string, to: string, reason: string, fence?: (view: ExecutionView) => void): string[];
909
+ /** Попытки, ещё владеющие поручениями (живой срок): операция ждёт их окончания. */
910
+ activeAttempts(workIds?: string[]): {
911
+ id: string;
912
+ workId: string;
913
+ leaseUntil: number;
914
+ }[];
915
+ /** Digest политики совместной проверки: формула хоста, если он её задаёт. */
916
+ private jointPolicy;
917
+ /** Основание совместной проверки набора: формула хоста, если он её задаёт. */
918
+ private jointSpec;
919
+ private liveJoint;
920
+ /**
921
+ * Начать совместную проверку набора принятых поручений. Нужны gates уровня
922
+ * workspace с JUnit, у каждого продуктового репозитория — свой; идущих
923
+ * попыток и другой живой проверки быть не должно; истёкшая проверка
924
+ * закрывается отказом. `baseline` — успешная проверка, которую хост принял
925
+ * (для режима `affected`).
926
+ */
927
+ jointStart(input: {
928
+ workIds: string[];
929
+ baseline?: string;
930
+ }): JointVerificationState;
931
+ /**
932
+ * Fencing совместной проверки: активна, токен её, срок не истёк, политика
933
+ * и основание (поручения, их постановки и результаты) не изменились.
934
+ */
935
+ private jointUpdate;
936
+ jointHeartbeat(id: string, token: string): void;
937
+ /**
938
+ * Закрепить manifest: SHA и tree каждого репозитория политики. Закрепляется
939
+ * один раз; по нему же выбирается область проверки.
940
+ */
941
+ jointManifest(id: string, token: string, manifest: NonNullable<JointVerification['manifest']>): {
942
+ manifest: Record<string, {
943
+ sha: string;
944
+ tree: string;
945
+ }>;
946
+ manifestDigest: string;
947
+ impact: {
948
+ changed: string[];
949
+ affected: string[];
950
+ gateIds: string[];
951
+ mode: "all" | "affected";
952
+ reason: string;
953
+ baseline?: string | undefined;
954
+ };
955
+ status: "active";
956
+ apiVersion: "devcontour-harness/v1alpha1";
957
+ id: string;
958
+ startedAt: string;
959
+ policyDigest: string;
960
+ specDigest: string;
961
+ workIds: string[];
962
+ evidence: {
963
+ gate: string;
964
+ passed: boolean;
965
+ command: string[];
966
+ exitCode: number;
967
+ log: string;
968
+ digest: string;
969
+ summary: string;
970
+ artifacts: {
971
+ path: string;
972
+ digest: string;
973
+ }[];
974
+ durationMs?: number | undefined;
975
+ }[];
976
+ token: string;
977
+ leaseUntil: number;
978
+ engineVersion?: string | undefined;
979
+ } | {
980
+ manifest: Record<string, {
981
+ sha: string;
982
+ tree: string;
983
+ }>;
984
+ manifestDigest: string;
985
+ impact: {
986
+ changed: string[];
987
+ affected: string[];
988
+ gateIds: string[];
989
+ mode: "all" | "affected";
990
+ reason: string;
991
+ baseline?: string | undefined;
992
+ };
993
+ status: "failed";
994
+ finishedAt: string;
995
+ error: string;
996
+ apiVersion: "devcontour-harness/v1alpha1";
997
+ id: string;
998
+ startedAt: string;
999
+ policyDigest: string;
1000
+ specDigest: string;
1001
+ workIds: string[];
1002
+ evidence: {
1003
+ gate: string;
1004
+ passed: boolean;
1005
+ command: string[];
1006
+ exitCode: number;
1007
+ log: string;
1008
+ digest: string;
1009
+ summary: string;
1010
+ artifacts: {
1011
+ path: string;
1012
+ digest: string;
1013
+ }[];
1014
+ durationMs?: number | undefined;
1015
+ }[];
1016
+ token: string;
1017
+ leaseUntil: number;
1018
+ engineVersion?: string | undefined;
1019
+ } | {
1020
+ manifest: Record<string, {
1021
+ sha: string;
1022
+ tree: string;
1023
+ }>;
1024
+ manifestDigest: string;
1025
+ impact: {
1026
+ changed: string[];
1027
+ affected: string[];
1028
+ gateIds: string[];
1029
+ mode: "all" | "affected";
1030
+ reason: string;
1031
+ baseline?: string | undefined;
1032
+ };
1033
+ status: "passed";
1034
+ finishedAt: string;
1035
+ apiVersion: "devcontour-harness/v1alpha1";
1036
+ id: string;
1037
+ startedAt: string;
1038
+ policyDigest: string;
1039
+ specDigest: string;
1040
+ workIds: string[];
1041
+ evidence: {
1042
+ gate: string;
1043
+ passed: boolean;
1044
+ command: string[];
1045
+ exitCode: number;
1046
+ log: string;
1047
+ digest: string;
1048
+ summary: string;
1049
+ artifacts: {
1050
+ path: string;
1051
+ digest: string;
1052
+ }[];
1053
+ durationMs?: number | undefined;
1054
+ }[];
1055
+ token: string;
1056
+ leaseUntil: number;
1057
+ engineVersion?: string | undefined;
1058
+ };
1059
+ jointEvidence(id: string, token: string, evidence: JointVerification['evidence'][number]): void;
1060
+ private jointMissing;
1061
+ jointFinish(id: string, token: string): {
1062
+ status: "active";
1063
+ apiVersion: "devcontour-harness/v1alpha1";
1064
+ id: string;
1065
+ startedAt: string;
1066
+ policyDigest: string;
1067
+ specDigest: string;
1068
+ workIds: string[];
1069
+ evidence: {
1070
+ gate: string;
1071
+ passed: boolean;
1072
+ command: string[];
1073
+ exitCode: number;
1074
+ log: string;
1075
+ digest: string;
1076
+ summary: string;
1077
+ artifacts: {
1078
+ path: string;
1079
+ digest: string;
1080
+ }[];
1081
+ durationMs?: number | undefined;
1082
+ }[];
1083
+ manifest?: Record<string, {
1084
+ sha: string;
1085
+ tree: string;
1086
+ }> | undefined;
1087
+ manifestDigest?: string | undefined;
1088
+ impact?: {
1089
+ changed: string[];
1090
+ affected: string[];
1091
+ gateIds: string[];
1092
+ mode: "all" | "affected";
1093
+ reason: string;
1094
+ baseline?: string | undefined;
1095
+ } | undefined;
1096
+ } | {
1097
+ status: "failed";
1098
+ finishedAt: string;
1099
+ error: string;
1100
+ apiVersion: "devcontour-harness/v1alpha1";
1101
+ id: string;
1102
+ startedAt: string;
1103
+ policyDigest: string;
1104
+ specDigest: string;
1105
+ workIds: string[];
1106
+ evidence: {
1107
+ gate: string;
1108
+ passed: boolean;
1109
+ command: string[];
1110
+ exitCode: number;
1111
+ log: string;
1112
+ digest: string;
1113
+ summary: string;
1114
+ artifacts: {
1115
+ path: string;
1116
+ digest: string;
1117
+ }[];
1118
+ durationMs?: number | undefined;
1119
+ }[];
1120
+ manifest?: Record<string, {
1121
+ sha: string;
1122
+ tree: string;
1123
+ }> | undefined;
1124
+ manifestDigest?: string | undefined;
1125
+ impact?: {
1126
+ changed: string[];
1127
+ affected: string[];
1128
+ gateIds: string[];
1129
+ mode: "all" | "affected";
1130
+ reason: string;
1131
+ baseline?: string | undefined;
1132
+ } | undefined;
1133
+ } | {
1134
+ status: "passed";
1135
+ finishedAt: string;
1136
+ manifest: Record<string, {
1137
+ sha: string;
1138
+ tree: string;
1139
+ }>;
1140
+ manifestDigest: string;
1141
+ apiVersion: "devcontour-harness/v1alpha1";
1142
+ id: string;
1143
+ startedAt: string;
1144
+ policyDigest: string;
1145
+ specDigest: string;
1146
+ workIds: string[];
1147
+ evidence: {
1148
+ gate: string;
1149
+ passed: boolean;
1150
+ command: string[];
1151
+ exitCode: number;
1152
+ log: string;
1153
+ digest: string;
1154
+ summary: string;
1155
+ artifacts: {
1156
+ path: string;
1157
+ digest: string;
1158
+ }[];
1159
+ durationMs?: number | undefined;
1160
+ }[];
1161
+ impact?: {
1162
+ changed: string[];
1163
+ affected: string[];
1164
+ gateIds: string[];
1165
+ mode: "all" | "affected";
1166
+ reason: string;
1167
+ baseline?: string | undefined;
1168
+ } | undefined;
1169
+ };
1170
+ /** Отказ проверки её владельцем; истёкший срок допустим, чужой токен — нет. */
1171
+ jointFail(id: string, token: string, error: string): boolean;
1172
+ /**
1173
+ * Актуальный результат совместной проверки набора: последняя проверка этого
1174
+ * набора, успешная, с целым manifest, под текущей политикой, на неизменном
1175
+ * основании, с PASS каждого gate своей области. Иначе — причина; прежний
1176
+ * результат как актуальный не выдаётся.
1177
+ */
1178
+ jointResult(workIds: string[],
1179
+ /** Оценить эту проверку набора, а не последнюю (хост выбирает её сам). */
1180
+ verificationId?: string): {
1181
+ verification?: JointVerification;
1182
+ current: boolean;
1183
+ reason?: string;
1184
+ };
1185
+ /** Истёкшие попытки: отказ окружения, работа ждёт явного повтора. */
1186
+ /**
1187
+ * Истечение владения на момент `at` (по умолчанию — сейчас). Хост передаёт
1188
+ * момент в будущем, когда снимает всё владение восстановленной копии.
1189
+ */
1190
+ expire(at?: number): string[];
1191
+ /** Восстановление: новый владелец истёкшей активной попытки получает новый токен. Старый токен недействителен. */
1192
+ adopt(id: string, owner: string): AttemptState;
1193
+ /** Recovery отклонил продолжение: попытка истечёт на следующем `expire`. */
1194
+ rejectRecovery(id: string, reason: string): boolean;
1195
+ /** Отмена: активная попытка теряет владение, поручение — cancelled. Принятое не отменяется. */
1196
+ cancel(workId: string, reason?: string): {
1197
+ work: {
1198
+ id: string;
1199
+ graphId: string;
1200
+ order: {
1201
+ apiVersion: "devcontour-harness/v1alpha1";
1202
+ id: string;
1203
+ source: {
1204
+ connector: string;
1205
+ ref: string;
1206
+ revision: string;
1207
+ };
1208
+ title: string;
1209
+ goal: string;
1210
+ acceptance: string[];
1211
+ role: string;
1212
+ repositoryId: string;
1213
+ relatedRepositories: string[];
1214
+ writeScope: string[];
1215
+ dependsOn: string[];
1216
+ requirements: {
1217
+ id: string;
1218
+ source: string;
1219
+ digest: string;
1220
+ text: string;
1221
+ gate: string;
1222
+ scenario: string;
1223
+ testId?: string | undefined;
1224
+ }[];
1225
+ contracts: {
1226
+ id: string;
1227
+ digest: string;
1228
+ }[];
1229
+ context: string[];
1230
+ gates?: string[] | undefined;
1231
+ consumers?: {
1232
+ repositoryId: string;
1233
+ gates: string[];
1234
+ }[] | undefined;
1235
+ properties?: string[] | undefined;
1236
+ resources?: string[] | undefined;
1237
+ };
1238
+ status: "ready" | "failed" | "cancelled" | "draft" | "running" | "verifying" | "reviewing" | "integrating" | "done";
1239
+ attempt: number;
1240
+ activeRunId?: string | undefined;
1241
+ resultSha?: string | undefined;
1242
+ failure?: string | undefined;
1243
+ failureKind?: "gate" | "unknown" | "review" | "environment" | "cancelled" | "timeout" | "provider-auth" | "stale-base" | "empty-result" | "scope-violation" | "merge" | "budget" | undefined;
1244
+ failureFingerprint?: string | undefined;
1245
+ approvedAt?: string | undefined;
1246
+ approvedDigest?: string | undefined;
1247
+ approvedScope?: string | undefined;
1248
+ approval?: {
1249
+ actor: "agent" | "operator";
1250
+ authorRuntime?: "codex" | "claude" | "fixture" | undefined;
1251
+ reviewerRuntime?: "codex" | "claude" | "fixture" | undefined;
1252
+ artifact?: string | undefined;
1253
+ digest?: string | undefined;
1254
+ context?: {
1255
+ digest: string | null;
1256
+ } | undefined;
1257
+ } | undefined;
1258
+ hold?: {
1259
+ operation: string;
1260
+ reason: string;
1261
+ } | undefined;
1262
+ budgetBlock?: {
1263
+ reason: string;
1264
+ at: string;
1265
+ } | undefined;
1266
+ checkVersion?: {
1267
+ suite: string;
1268
+ number: number;
1269
+ digest: string;
1270
+ } | undefined;
1271
+ recovery?: {
1272
+ id: string;
1273
+ property: string;
1274
+ basis: string;
1275
+ runs: string[];
1276
+ openedAt: string;
1277
+ status: "stopped" | "closed" | "awaiting-lead" | "analyzing" | "applying" | "retry" | "retrying" | "awaiting-user";
1278
+ calls: number;
1279
+ history: {
1280
+ at: string;
1281
+ event: string;
1282
+ detail?: string | undefined;
1283
+ }[];
1284
+ version: number;
1285
+ decision?: {
1286
+ action: "environment" | "add-cases" | "new-approach" | "strengthen-checker" | "split-task" | "clarify-obligation";
1287
+ reason: string;
1288
+ evidence: string[];
1289
+ source: {
1290
+ kind: "operator" | "lead";
1291
+ runtime?: string | undefined;
1292
+ model?: string | undefined;
1293
+ };
1294
+ decidedAt: string;
1295
+ hypothesis?: string | undefined;
1296
+ plan?: string | undefined;
1297
+ cases?: {
1298
+ input: string;
1299
+ expected?: string | null | undefined;
1300
+ }[] | undefined;
1301
+ proposal?: string | undefined;
1302
+ conflict?: string | undefined;
1303
+ } | undefined;
1304
+ retryRun?: string | undefined;
1305
+ reason?: string | undefined;
1306
+ } | undefined;
1307
+ recoveryHistory?: {
1308
+ id: string;
1309
+ property: string;
1310
+ basis: string;
1311
+ runs: string[];
1312
+ openedAt: string;
1313
+ status: "stopped" | "closed" | "awaiting-lead" | "analyzing" | "applying" | "retry" | "retrying" | "awaiting-user";
1314
+ calls: number;
1315
+ history: {
1316
+ at: string;
1317
+ event: string;
1318
+ detail?: string | undefined;
1319
+ }[];
1320
+ version: number;
1321
+ decision?: {
1322
+ action: "environment" | "add-cases" | "new-approach" | "strengthen-checker" | "split-task" | "clarify-obligation";
1323
+ reason: string;
1324
+ evidence: string[];
1325
+ source: {
1326
+ kind: "operator" | "lead";
1327
+ runtime?: string | undefined;
1328
+ model?: string | undefined;
1329
+ };
1330
+ decidedAt: string;
1331
+ hypothesis?: string | undefined;
1332
+ plan?: string | undefined;
1333
+ cases?: {
1334
+ input: string;
1335
+ expected?: string | null | undefined;
1336
+ }[] | undefined;
1337
+ proposal?: string | undefined;
1338
+ conflict?: string | undefined;
1339
+ } | undefined;
1340
+ retryRun?: string | undefined;
1341
+ reason?: string | undefined;
1342
+ }[] | undefined;
1343
+ };
1344
+ changed: boolean;
1345
+ };
1346
+ /**
1347
+ * Повтор неуспешного или отменённого поручения. Пока попытки не
1348
+ * исчерпаны — без сброса; исчерпаны — только явный сброс с причиной.
1349
+ * Класс и отпечаток прошлого отказа снимаются.
1350
+ */
1351
+ retry(workId: string, reset?: {
1352
+ reason: string;
1353
+ }): {
1354
+ work: {
1355
+ id: string;
1356
+ graphId: string;
1357
+ order: {
1358
+ apiVersion: "devcontour-harness/v1alpha1";
1359
+ id: string;
1360
+ source: {
1361
+ connector: string;
1362
+ ref: string;
1363
+ revision: string;
1364
+ };
1365
+ title: string;
1366
+ goal: string;
1367
+ acceptance: string[];
1368
+ role: string;
1369
+ repositoryId: string;
1370
+ relatedRepositories: string[];
1371
+ writeScope: string[];
1372
+ dependsOn: string[];
1373
+ requirements: {
1374
+ id: string;
1375
+ source: string;
1376
+ digest: string;
1377
+ text: string;
1378
+ gate: string;
1379
+ scenario: string;
1380
+ testId?: string | undefined;
1381
+ }[];
1382
+ contracts: {
1383
+ id: string;
1384
+ digest: string;
1385
+ }[];
1386
+ context: string[];
1387
+ gates?: string[] | undefined;
1388
+ consumers?: {
1389
+ repositoryId: string;
1390
+ gates: string[];
1391
+ }[] | undefined;
1392
+ properties?: string[] | undefined;
1393
+ resources?: string[] | undefined;
1394
+ };
1395
+ status: "ready" | "failed" | "cancelled" | "draft" | "running" | "verifying" | "reviewing" | "integrating" | "done";
1396
+ attempt: number;
1397
+ activeRunId?: string | undefined;
1398
+ resultSha?: string | undefined;
1399
+ failure?: string | undefined;
1400
+ failureKind?: "gate" | "unknown" | "review" | "environment" | "cancelled" | "timeout" | "provider-auth" | "stale-base" | "empty-result" | "scope-violation" | "merge" | "budget" | undefined;
1401
+ failureFingerprint?: string | undefined;
1402
+ approvedAt?: string | undefined;
1403
+ approvedDigest?: string | undefined;
1404
+ approvedScope?: string | undefined;
1405
+ approval?: {
1406
+ actor: "agent" | "operator";
1407
+ authorRuntime?: "codex" | "claude" | "fixture" | undefined;
1408
+ reviewerRuntime?: "codex" | "claude" | "fixture" | undefined;
1409
+ artifact?: string | undefined;
1410
+ digest?: string | undefined;
1411
+ context?: {
1412
+ digest: string | null;
1413
+ } | undefined;
1414
+ } | undefined;
1415
+ hold?: {
1416
+ operation: string;
1417
+ reason: string;
1418
+ } | undefined;
1419
+ budgetBlock?: {
1420
+ reason: string;
1421
+ at: string;
1422
+ } | undefined;
1423
+ checkVersion?: {
1424
+ suite: string;
1425
+ number: number;
1426
+ digest: string;
1427
+ } | undefined;
1428
+ recovery?: {
1429
+ id: string;
1430
+ property: string;
1431
+ basis: string;
1432
+ runs: string[];
1433
+ openedAt: string;
1434
+ status: "stopped" | "closed" | "awaiting-lead" | "analyzing" | "applying" | "retry" | "retrying" | "awaiting-user";
1435
+ calls: number;
1436
+ history: {
1437
+ at: string;
1438
+ event: string;
1439
+ detail?: string | undefined;
1440
+ }[];
1441
+ version: number;
1442
+ decision?: {
1443
+ action: "environment" | "add-cases" | "new-approach" | "strengthen-checker" | "split-task" | "clarify-obligation";
1444
+ reason: string;
1445
+ evidence: string[];
1446
+ source: {
1447
+ kind: "operator" | "lead";
1448
+ runtime?: string | undefined;
1449
+ model?: string | undefined;
1450
+ };
1451
+ decidedAt: string;
1452
+ hypothesis?: string | undefined;
1453
+ plan?: string | undefined;
1454
+ cases?: {
1455
+ input: string;
1456
+ expected?: string | null | undefined;
1457
+ }[] | undefined;
1458
+ proposal?: string | undefined;
1459
+ conflict?: string | undefined;
1460
+ } | undefined;
1461
+ retryRun?: string | undefined;
1462
+ reason?: string | undefined;
1463
+ } | undefined;
1464
+ recoveryHistory?: {
1465
+ id: string;
1466
+ property: string;
1467
+ basis: string;
1468
+ runs: string[];
1469
+ openedAt: string;
1470
+ status: "stopped" | "closed" | "awaiting-lead" | "analyzing" | "applying" | "retry" | "retrying" | "awaiting-user";
1471
+ calls: number;
1472
+ history: {
1473
+ at: string;
1474
+ event: string;
1475
+ detail?: string | undefined;
1476
+ }[];
1477
+ version: number;
1478
+ decision?: {
1479
+ action: "environment" | "add-cases" | "new-approach" | "strengthen-checker" | "split-task" | "clarify-obligation";
1480
+ reason: string;
1481
+ evidence: string[];
1482
+ source: {
1483
+ kind: "operator" | "lead";
1484
+ runtime?: string | undefined;
1485
+ model?: string | undefined;
1486
+ };
1487
+ decidedAt: string;
1488
+ hypothesis?: string | undefined;
1489
+ plan?: string | undefined;
1490
+ cases?: {
1491
+ input: string;
1492
+ expected?: string | null | undefined;
1493
+ }[] | undefined;
1494
+ proposal?: string | undefined;
1495
+ conflict?: string | undefined;
1496
+ } | undefined;
1497
+ retryRun?: string | undefined;
1498
+ reason?: string | undefined;
1499
+ }[] | undefined;
1500
+ };
1501
+ changed: boolean;
1502
+ };
1503
+ }