@unson/brainbase-mcp 0.44.0 → 0.44.2

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 (76) hide show
  1. package/contracts/canonical-task/operation-schema.sql +58 -0
  2. package/contracts/canonical-task/search-indexes.sql +10 -0
  3. package/contracts/canonical-task/store-schema.sql +77 -0
  4. package/dist/automation-run.d.ts +213 -0
  5. package/dist/automation-run.js +479 -0
  6. package/dist/canonical-task-operations.d.ts +205 -0
  7. package/dist/canonical-task-operations.js +700 -0
  8. package/dist/canonical-task-postgres.d.ts +139 -0
  9. package/dist/canonical-task-postgres.js +554 -0
  10. package/dist/canonical-task-service.d.ts +13 -0
  11. package/dist/canonical-task-service.js +55 -3
  12. package/dist/canonical-task-store-config.d.ts +31 -0
  13. package/dist/canonical-task-store-config.js +53 -0
  14. package/dist/config-parser.d.ts +298 -0
  15. package/dist/config-parser.js +593 -0
  16. package/dist/connected-onboarding.d.ts +57 -0
  17. package/dist/connected-onboarding.js +37 -0
  18. package/dist/durable-wait-http.d.ts +3 -1
  19. package/dist/durable-wait-http.js +73 -6
  20. package/dist/durable-waits.d.ts +93 -3
  21. package/dist/durable-waits.js +320 -8
  22. package/dist/honcho.d.ts +25 -0
  23. package/dist/honcho.js +81 -0
  24. package/dist/judgment-event-adapter.d.ts +72 -0
  25. package/dist/judgment-event-adapter.js +114 -0
  26. package/dist/judgment-event-outbox.d.ts +182 -0
  27. package/dist/judgment-event-outbox.js +568 -0
  28. package/dist/judgment-execution-outcome.d.ts +46 -0
  29. package/dist/judgment-execution-outcome.js +149 -0
  30. package/dist/judgment-host.d.ts +18 -7
  31. package/dist/judgment-host.js +82 -34
  32. package/dist/judgment-lifecycle-core.d.ts +198 -0
  33. package/dist/judgment-lifecycle-core.js +464 -0
  34. package/dist/judgment-node-evidence.d.ts +57 -0
  35. package/dist/judgment-node-evidence.js +297 -0
  36. package/dist/judgment-runtime-resolution.d.ts +264 -0
  37. package/dist/judgment-runtime-resolution.js +1248 -0
  38. package/dist/judgment-runtime-resolution.public.d.ts +255 -0
  39. package/dist/knowledge-event.d.ts +28 -0
  40. package/dist/knowledge-event.js +114 -0
  41. package/dist/mcp-runtime.d.ts +75 -0
  42. package/dist/mcp-runtime.js +188 -0
  43. package/dist/meeting-automation-runtime.d.ts +102 -0
  44. package/dist/meeting-automation-runtime.js +249 -0
  45. package/dist/meeting-automation.d.ts +155 -0
  46. package/dist/meeting-automation.js +341 -0
  47. package/dist/meeting-minutes-context-receipt.d.ts +91 -0
  48. package/dist/meeting-minutes-context-receipt.js +280 -0
  49. package/dist/meeting-source-sync.d.ts +89 -0
  50. package/dist/meeting-source-sync.js +230 -0
  51. package/dist/mesh.d.ts +58 -0
  52. package/dist/mesh.js +134 -0
  53. package/dist/onboarding-runtime.d.ts +47 -0
  54. package/dist/onboarding-runtime.js +4719 -0
  55. package/dist/routine-cycle.d.ts +51 -0
  56. package/dist/routine-cycle.js +153 -0
  57. package/dist/routine-liveness.d.ts +49 -0
  58. package/dist/routine-liveness.js +227 -0
  59. package/dist/routine-runtime.d.ts +101 -0
  60. package/dist/routine-runtime.js +237 -0
  61. package/dist/routines.d.ts +3 -0
  62. package/dist/routines.js +3 -0
  63. package/dist/run-receipt.d.ts +40 -0
  64. package/dist/run-receipt.js +635 -0
  65. package/dist/runtime-host.d.ts +64 -0
  66. package/dist/runtime-host.js +70 -0
  67. package/dist/world-work.d.ts +16 -0
  68. package/package.json +115 -3
  69. package/ui/objective-editor.js +47 -11
  70. package/ui/workspace-kit.css +33 -1
  71. package/ui/workspace-kit.js +13 -2
  72. package/ui/world/world-canvas-ui.css +4 -49
  73. package/ui/world/world-canvas-ui.js +26 -71
  74. package/ui/world/world-district.js +18 -3
  75. package/ui/world/world-placement.js +13 -0
  76. package/ui/world/world-work-rail.js +7 -1
@@ -0,0 +1,479 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { normalizeRunReceipt } from './run-receipt.js';
3
+ import { resolveRuntimeHostContext } from './runtime-host.js';
4
+ function assertRequest(request) {
5
+ if (!request || typeof request !== 'object') {
6
+ throw new Error('automation run request is required');
7
+ }
8
+ if (typeof request.workflowId !== 'string' || request.workflowId.trim() === '') {
9
+ throw new Error('automation run request workflowId must be a non-empty string');
10
+ }
11
+ if (typeof request.externalRunId !== 'string' || request.externalRunId.trim() === '') {
12
+ throw new Error('automation run request externalRunId must be a non-empty string');
13
+ }
14
+ }
15
+ function assertPorts(ports) {
16
+ if (!ports.runner || typeof ports.runner !== 'object' || typeof ports.runner.start !== 'function') {
17
+ throw new Error('automation runner port with start is required');
18
+ }
19
+ if (!ports.receipt || typeof ports.receipt !== 'object'
20
+ || typeof ports.receipt.persist !== 'function'
21
+ || typeof ports.receipt.query !== 'function') {
22
+ throw new Error('automation receipt port with persist and query is required');
23
+ }
24
+ if (!ports.runStore || typeof ports.runStore !== 'object'
25
+ || typeof ports.runStore.claim !== 'function'
26
+ || typeof ports.runStore.markRunnerStarted !== 'function'
27
+ || typeof ports.runStore.markReceiptPersisted !== 'function'
28
+ || typeof ports.runStore.complete !== 'function'
29
+ || typeof ports.runStore.release !== 'function'
30
+ || typeof ports.runStore.read !== 'function') {
31
+ throw new Error('automation runStore port with atomic claim, phase checkpoints, complete, release, and read is required');
32
+ }
33
+ if (ports.lifecycle !== undefined) {
34
+ if (!ports.lifecycle || typeof ports.lifecycle !== 'object') {
35
+ throw new Error('automation lifecycle port must be an object');
36
+ }
37
+ for (const [name, value] of [
38
+ ['lookup', ports.lifecycle.lookup],
39
+ ['designGate', ports.lifecycle.designGate],
40
+ ['checkpoint', ports.lifecycle.checkpoint],
41
+ ['humanGate', ports.lifecycle.humanGate],
42
+ ['transition', ports.lifecycle.transition]
43
+ ]) {
44
+ if (value !== undefined && typeof value !== 'function') {
45
+ throw new Error(`automation lifecycle port ${name} must be a function`);
46
+ }
47
+ }
48
+ }
49
+ }
50
+ function stableValue(value, seen = new WeakSet()) {
51
+ if (value === null || typeof value === 'string' || typeof value === 'boolean')
52
+ return value;
53
+ if (typeof value === 'number') {
54
+ if (Number.isNaN(value))
55
+ return { __type: 'number', value: 'NaN' };
56
+ if (value === Infinity)
57
+ return { __type: 'number', value: 'Infinity' };
58
+ if (value === -Infinity)
59
+ return { __type: 'number', value: '-Infinity' };
60
+ return value;
61
+ }
62
+ if (typeof value === 'bigint')
63
+ return { __type: 'bigint', value: value.toString() };
64
+ if (typeof value === 'undefined')
65
+ return { __type: 'undefined' };
66
+ if (typeof value === 'function' || typeof value === 'symbol') {
67
+ return { __type: typeof value, value: String(value) };
68
+ }
69
+ if (value instanceof Date)
70
+ return { __type: 'date', value: value.toISOString() };
71
+ if (typeof value !== 'object')
72
+ return String(value);
73
+ if (seen.has(value))
74
+ throw new Error('automation run request contains a cyclic identity value');
75
+ seen.add(value);
76
+ if (Array.isArray(value)) {
77
+ const result = value.map((entry) => stableValue(entry, seen));
78
+ seen.delete(value);
79
+ return result;
80
+ }
81
+ const result = {};
82
+ for (const key of Object.keys(value).sort()) {
83
+ result[key] = stableValue(value[key], seen);
84
+ }
85
+ seen.delete(value);
86
+ return result;
87
+ }
88
+ function digest(value) {
89
+ return createHash('sha256').update(JSON.stringify(stableValue(value))).digest('hex');
90
+ }
91
+ function stringFromRecord(value, keys) {
92
+ if (!value || typeof value !== 'object' || Array.isArray(value))
93
+ return undefined;
94
+ const record = value;
95
+ for (const key of keys) {
96
+ const candidate = record[key];
97
+ if (typeof candidate === 'string' && candidate.trim() !== '')
98
+ return candidate.trim();
99
+ }
100
+ return undefined;
101
+ }
102
+ function requiredNamespaceValue(name, ...values) {
103
+ for (const value of values) {
104
+ if (typeof value === 'string' && value.trim() !== '')
105
+ return value.trim();
106
+ }
107
+ throw new Error(`automation run namespace ${name} must be explicit`);
108
+ }
109
+ function runNamespace(request, context) {
110
+ const identity = context.identity;
111
+ const graph = context.graph;
112
+ return {
113
+ tenantId: requiredNamespaceValue('tenantId', context.tenantId),
114
+ projectId: requiredNamespaceValue('projectId', request.projectId),
115
+ organizationId: requiredNamespaceValue('organizationId', request.organizationId, request.orgId, stringFromRecord(identity, ['organizationId', 'organization_id', 'orgId', 'org_id']), stringFromRecord(graph, ['organizationId', 'organization_id', 'orgId', 'org_id'])),
116
+ actorId: requiredNamespaceValue('actorId', request.actorId, stringFromRecord(identity, ['actorId', 'actor_id', 'userId', 'user_id', 'subject', 'sub']))
117
+ };
118
+ }
119
+ function buildRunIdentity(operation, request, context, previousRunId) {
120
+ const namespace = runNamespace(request, context);
121
+ const base = {
122
+ operation,
123
+ namespace,
124
+ workflowId: request.workflowId,
125
+ automationId: request.automationId || null,
126
+ sourceType: request.sourceType || null,
127
+ externalRunId: request.externalRunId,
128
+ previousRunId: previousRunId || null,
129
+ payload: request.payload === undefined ? null : request.payload,
130
+ metadata: request.metadata === undefined ? null : request.metadata
131
+ };
132
+ const fingerprint = digest(base);
133
+ const key = `automation-run:${digest({
134
+ operation,
135
+ namespace,
136
+ workflowId: request.workflowId,
137
+ externalRunId: request.externalRunId
138
+ })}`;
139
+ return {
140
+ operation,
141
+ key,
142
+ fingerprint,
143
+ runnerIdempotencyKey: `automation-run:${fingerprint}`,
144
+ namespace
145
+ };
146
+ }
147
+ function conflictError(request, phase) {
148
+ const error = new Error(`automation run '${request.externalRunId}' is already in progress`);
149
+ Object.assign(error, {
150
+ code: 'automation_run_in_progress',
151
+ status: 409,
152
+ statusCode: 409,
153
+ externalRunId: request.externalRunId,
154
+ ...(phase ? { phase } : {})
155
+ });
156
+ return error;
157
+ }
158
+ function identityConflictError(request) {
159
+ const error = new Error(`automation run '${request.externalRunId}' conflicts with an existing identity`);
160
+ Object.assign(error, {
161
+ code: 'automation_run_identity_conflict',
162
+ status: 409,
163
+ statusCode: 409,
164
+ externalRunId: request.externalRunId
165
+ });
166
+ return error;
167
+ }
168
+ function uncertainError(request, phase) {
169
+ const error = new Error(`automation run '${request.externalRunId}' has an uncertain durable outcome`);
170
+ Object.assign(error, {
171
+ code: 'automation_run_uncertain',
172
+ status: 409,
173
+ statusCode: 409,
174
+ externalRunId: request.externalRunId,
175
+ ...(phase ? { phase } : {})
176
+ });
177
+ return error;
178
+ }
179
+ function invalidClaimError() {
180
+ return new Error('automation runStore claim returned an invalid state');
181
+ }
182
+ function replayResult(stored, context) {
183
+ return {
184
+ runnerResult: stored.runnerResult,
185
+ normalizedReceipt: stored.normalized || normalizeRunReceipt(stored.payload),
186
+ persistedReceipt: stored.persistedReceipt,
187
+ context,
188
+ replayed: true
189
+ };
190
+ }
191
+ function workflowLifecycleError(code, request, reason) {
192
+ const suffix = typeof reason === 'string' && reason.trim() !== '' ? `: ${reason}` : '';
193
+ const error = new Error(`${code}${suffix}`);
194
+ Object.assign(error, {
195
+ code,
196
+ status: 409,
197
+ statusCode: 409,
198
+ externalRunId: request.externalRunId
199
+ });
200
+ return error;
201
+ }
202
+ function requireWorkflowGate(decision, code, request) {
203
+ const allowed = decision === true
204
+ || (typeof decision === 'object'
205
+ && decision !== null
206
+ && decision.allowed === true);
207
+ if (!allowed) {
208
+ const reason = typeof decision === 'object' && decision !== null ? decision.reason : undefined;
209
+ throw workflowLifecycleError(code, request, reason);
210
+ }
211
+ }
212
+ export class AutomationRunService {
213
+ ports;
214
+ constructor(ports) {
215
+ assertPorts(ports);
216
+ this.ports = ports;
217
+ }
218
+ async start(request) {
219
+ return this.execute('automation.start', request, (context, idempotencyKey) => this.ports.runner.start({ request, context, idempotencyKey }));
220
+ }
221
+ async run(request) {
222
+ return this.start(request);
223
+ }
224
+ async retry(request, previousRunId) {
225
+ assertRequest(request);
226
+ if (typeof previousRunId !== 'string' || previousRunId.trim() === '') {
227
+ throw new Error('previousRunId must be a non-empty string');
228
+ }
229
+ if (typeof this.ports.runner.retry !== 'function') {
230
+ throw new Error('automation runner retry port is required');
231
+ }
232
+ return this.execute('automation.retry', request, (context, idempotencyKey) => this.ports.runner.retry({ request, context, previousRunId, idempotencyKey }), previousRunId);
233
+ }
234
+ async rerun(request, previousRunId) {
235
+ return this.retry(request, previousRunId);
236
+ }
237
+ async history(request) {
238
+ assertRequest(request);
239
+ const input = {
240
+ operation: 'automation.history',
241
+ automationId: request.automationId,
242
+ runId: request.externalRunId,
243
+ request
244
+ };
245
+ const context = await resolveRuntimeHostContext(this.ports, input);
246
+ return this.ports.receipt.query({ request, context });
247
+ }
248
+ async getRun(request) {
249
+ return this.history(request);
250
+ }
251
+ async execute(operation, request, run, previousRunId) {
252
+ assertRequest(request);
253
+ const input = {
254
+ operation,
255
+ automationId: request.automationId,
256
+ runId: request.externalRunId,
257
+ request
258
+ };
259
+ const context = await resolveRuntimeHostContext(this.ports, input);
260
+ const identity = buildRunIdentity(operation, request, context, previousRunId);
261
+ const claimInput = { ...identity, request, context };
262
+ const lifecycle = this.ports.lifecycle;
263
+ let workflow;
264
+ const preparedLifecycleInput = () => ({
265
+ request,
266
+ context,
267
+ identity,
268
+ phase: 'prepared',
269
+ workflow,
270
+ previousRunId
271
+ });
272
+ // Workflow lookup and policy gates are host-owned. They run before the
273
+ // durable claim so a denied design/HITL decision cannot leave a retryable
274
+ // record behind, while the core still fixes their order before the runner.
275
+ if (lifecycle?.lookup) {
276
+ workflow = await lifecycle.lookup(preparedLifecycleInput());
277
+ if (workflow === null || workflow === undefined) {
278
+ throw workflowLifecycleError('automation_workflow_lookup_failed', request);
279
+ }
280
+ }
281
+ if (lifecycle?.designGate) {
282
+ requireWorkflowGate(await lifecycle.designGate(preparedLifecycleInput()), 'automation_workflow_design_gate_denied', request);
283
+ }
284
+ if (lifecycle?.humanGate) {
285
+ requireWorkflowGate(await lifecycle.humanGate(preparedLifecycleInput()), 'automation_workflow_human_gate_denied', request);
286
+ }
287
+ // A rejected claim is not safe to release: the backing store may have
288
+ // committed it while the response was lost. Fail closed and let a durable
289
+ // reconciliation worker inspect the record.
290
+ const claim = await this.ports.runStore.claim(claimInput);
291
+ if (!claim || typeof claim !== 'object' || typeof claim.status !== 'string') {
292
+ throw invalidClaimError();
293
+ }
294
+ if (claim.status === 'conflict') {
295
+ if (claim.reason === 'identity_mismatch')
296
+ throw identityConflictError(request);
297
+ throw uncertainError(request, claim.phase);
298
+ }
299
+ if (claim.status === 'replay') {
300
+ const stored = await this.ports.runStore.read(claimInput);
301
+ if (stored.status === 'completed') {
302
+ if (lifecycle?.transition) {
303
+ try {
304
+ await lifecycle.transition({
305
+ ...preparedLifecycleInput(),
306
+ phase: 'completed',
307
+ from: 'receipt_persisted',
308
+ to: 'completed'
309
+ });
310
+ }
311
+ catch {
312
+ // The runStore has already committed the external side effect;
313
+ // retrying the runner is forbidden while the host transition is
314
+ // reconciled separately.
315
+ throw uncertainError(request, 'completed');
316
+ }
317
+ }
318
+ return replayResult(stored, context);
319
+ }
320
+ if (stored.status === 'uncertain')
321
+ throw uncertainError(request, stored.phase);
322
+ throw new Error(`automation runStore replay readback was not completed for ${request.externalRunId}`);
323
+ }
324
+ if (claim.status === 'in_progress') {
325
+ const stored = await this.ports.runStore.read(claimInput);
326
+ if (stored.status === 'completed') {
327
+ if (lifecycle?.transition) {
328
+ try {
329
+ await lifecycle.transition({
330
+ ...preparedLifecycleInput(),
331
+ phase: 'completed',
332
+ from: 'receipt_persisted',
333
+ to: 'completed'
334
+ });
335
+ }
336
+ catch {
337
+ throw uncertainError(request, 'completed');
338
+ }
339
+ }
340
+ return replayResult(stored, context);
341
+ }
342
+ if (stored.status === 'uncertain')
343
+ throw uncertainError(request, stored.phase);
344
+ throw conflictError(request, stored.phase || claim.phase);
345
+ }
346
+ if (claim.status !== 'claimed' || typeof claim.claimToken !== 'string' || claim.claimToken.trim() === '') {
347
+ throw invalidClaimError();
348
+ }
349
+ let phase = 'claimed';
350
+ try {
351
+ if (lifecycle?.checkpoint) {
352
+ phase = 'unknown';
353
+ await lifecycle.checkpoint({
354
+ ...preparedLifecycleInput(),
355
+ phase: 'claimed',
356
+ claimToken: claim.claimToken
357
+ });
358
+ phase = 'claimed';
359
+ }
360
+ // The marker is durable and precedes the external call. If it fails, the
361
+ // state is unknown and must never be made retryable automatically.
362
+ phase = 'unknown';
363
+ await this.ports.runStore.markRunnerStarted({ ...claimInput, claimToken: claim.claimToken });
364
+ phase = 'runner_started';
365
+ if (lifecycle?.transition) {
366
+ phase = 'unknown';
367
+ await lifecycle.transition({
368
+ ...preparedLifecycleInput(),
369
+ phase: 'runner_started',
370
+ claimToken: claim.claimToken,
371
+ from: 'claimed',
372
+ to: 'runner_started'
373
+ });
374
+ phase = 'runner_started';
375
+ }
376
+ const runnerResult = await run(context, identity.runnerIdempotencyKey);
377
+ if (!runnerResult || typeof runnerResult !== 'object' || !('receipt' in runnerResult)) {
378
+ throw new Error(`automation runner ${operation} must return a receipt`);
379
+ }
380
+ const result = runnerResult;
381
+ const normalizedReceipt = normalizeRunReceipt(result.receipt);
382
+ const persistedReceipt = await this.ports.receipt.persist({
383
+ payload: result.receipt,
384
+ normalized: normalizedReceipt,
385
+ context
386
+ });
387
+ phase = 'unknown';
388
+ await this.ports.runStore.markReceiptPersisted({
389
+ ...claimInput,
390
+ claimToken: claim.claimToken,
391
+ payload: result.receipt,
392
+ normalized: normalizedReceipt,
393
+ runnerResult: result.result,
394
+ persistedReceipt
395
+ });
396
+ phase = 'receipt_persisted';
397
+ if (lifecycle?.transition) {
398
+ phase = 'unknown';
399
+ await lifecycle.transition({
400
+ ...preparedLifecycleInput(),
401
+ phase: 'receipt_persisted',
402
+ claimToken: claim.claimToken,
403
+ from: 'runner_started',
404
+ to: 'receipt_persisted'
405
+ });
406
+ phase = 'receipt_persisted';
407
+ }
408
+ phase = 'unknown';
409
+ await this.ports.runStore.complete({
410
+ ...claimInput,
411
+ claimToken: claim.claimToken,
412
+ payload: result.receipt,
413
+ normalized: normalizedReceipt,
414
+ runnerResult: result.result,
415
+ persistedReceipt
416
+ });
417
+ phase = 'completed';
418
+ // Readback is part of the contract. A lost response after complete must
419
+ // not cause a caller to invoke the runner again.
420
+ phase = 'unknown';
421
+ const stored = await this.ports.runStore.read(claimInput);
422
+ if (!stored || stored.status !== 'completed') {
423
+ throw uncertainError(request, stored?.phase || 'unknown');
424
+ }
425
+ if (lifecycle?.transition) {
426
+ phase = 'unknown';
427
+ await lifecycle.transition({
428
+ ...preparedLifecycleInput(),
429
+ phase: 'completed',
430
+ claimToken: claim.claimToken,
431
+ from: 'receipt_persisted',
432
+ to: 'completed'
433
+ });
434
+ phase = 'completed';
435
+ }
436
+ phase = 'completed';
437
+ return {
438
+ runnerResult: stored.runnerResult,
439
+ normalizedReceipt: stored.normalized || normalizeRunReceipt(stored.payload),
440
+ persistedReceipt: stored.persistedReceipt,
441
+ context
442
+ };
443
+ }
444
+ catch (error) {
445
+ const outcome = phase === 'claimed' ? 'retryable' : 'uncertain';
446
+ const releasePhase = outcome === 'retryable' ? 'retryable' : phase;
447
+ await Promise.resolve(this.ports.runStore.release({
448
+ ...claimInput,
449
+ claimToken: claim.claimToken,
450
+ outcome,
451
+ phase: releasePhase,
452
+ error
453
+ })).catch(() => undefined);
454
+ throw error;
455
+ }
456
+ }
457
+ }
458
+ /** Thin compatibility facade for hosts that previously composed a control runtime. */
459
+ export class AutomationControlRuntime {
460
+ service;
461
+ constructor(service) {
462
+ this.service = service;
463
+ }
464
+ start(request) {
465
+ return this.service.start(request);
466
+ }
467
+ retry(request, previousRunId) {
468
+ return this.service.retry(request, previousRunId);
469
+ }
470
+ history(request) {
471
+ return this.service.history(request);
472
+ }
473
+ getRun(request) {
474
+ return this.service.getRun(request);
475
+ }
476
+ }
477
+ export function createAutomationRunService(ports) {
478
+ return new AutomationRunService(ports);
479
+ }
@@ -0,0 +1,205 @@
1
+ import type { CanonicalTaskPgClient, CanonicalTaskPgPool } from './canonical-task-postgres.js';
2
+ type JsonRecord = Record<string, unknown>;
3
+ export type CanonicalTaskOperationError = Error & {
4
+ code: string;
5
+ status: number;
6
+ details: JsonRecord;
7
+ };
8
+ export declare function canonicalTaskOperationError(code: string, message: string, status?: number, details?: JsonRecord): CanonicalTaskOperationError;
9
+ export interface CanonicalTaskOperationRepositoryOptions {
10
+ pool?: CanonicalTaskPgPool | null;
11
+ writerToken?: string | null;
12
+ processIdentity?: JsonRecord | null;
13
+ writerEnabled?: boolean;
14
+ operationWaitTimeoutMs?: number;
15
+ operationPollIntervalMs?: number;
16
+ }
17
+ export interface CanonicalTaskReadinessRow {
18
+ ready: boolean;
19
+ writer_token: string | null;
20
+ manifest_hash: string | null;
21
+ schema_version: string | null;
22
+ source_head: string | null;
23
+ evidence_hash: string | null;
24
+ evidence_path: string | null;
25
+ reason: string | null;
26
+ updated_at: unknown;
27
+ }
28
+ /**
29
+ * Single-writer coordination for Canonical Task mutations
30
+ * (`contracts/canonical-task/operation-schema.sql`). One process owns the
31
+ * writer token; every mutation is recorded by scope and operation key so a
32
+ * retry with the same input replays and a different input conflicts.
33
+ */
34
+ export declare class CanonicalTaskOperationRepository {
35
+ /**
36
+ * A PostgreSQL coordinator is a durable mutation boundary. Callers must
37
+ * bind it to the release readiness gate before exposing mutations.
38
+ */
39
+ readonly requiresReadiness = true;
40
+ readonly pool: CanonicalTaskPgPool | null;
41
+ readonly writerToken: string | null;
42
+ readonly processIdentity: JsonRecord | null;
43
+ readonly writerEnabled: boolean;
44
+ readonly operationWaitTimeoutMs: number;
45
+ readonly operationPollIntervalMs: number;
46
+ writerClaimed: boolean;
47
+ activeOperations: number;
48
+ constructor({ pool, writerToken, processIdentity, writerEnabled, operationWaitTimeoutMs, operationPollIntervalMs, }?: CanonicalTaskOperationRepositoryOptions);
49
+ private identityJson;
50
+ private connect;
51
+ waitForCompletedOperation({ scope, operationKey, fingerprint }: {
52
+ scope: string;
53
+ operationKey: string;
54
+ fingerprint: string;
55
+ }): Promise<unknown>;
56
+ assertConfigured(): void;
57
+ assertWriterEnabled(): void;
58
+ claimWriter({ sourceHead }?: {
59
+ sourceHead?: string | null;
60
+ }): Promise<JsonRecord>;
61
+ assertWriter(client?: CanonicalTaskPgClient | null): Promise<void>;
62
+ readReadiness(): Promise<CanonicalTaskReadinessRow | null>;
63
+ reconcileReadiness({ manifestHash, schemaVersion, sourceHead, allowWriterRebind }?: {
64
+ manifestHash?: string | null;
65
+ schemaVersion?: string | null;
66
+ sourceHead?: string | null;
67
+ allowWriterRebind?: boolean;
68
+ }): Promise<CanonicalTaskReadinessRow | null>;
69
+ rebindReadinessSourceHead({ manifestHash, schemaVersion, fromHead, toHead }?: {
70
+ manifestHash?: string | null;
71
+ schemaVersion?: string | null;
72
+ fromHead?: string | null;
73
+ toHead?: string | null;
74
+ }): Promise<CanonicalTaskReadinessRow>;
75
+ releaseWriter(): Promise<boolean>;
76
+ execute<T>(args: CanonicalTaskCoordinatedRequest<T>): Promise<T>;
77
+ executeCoordinated<T>({ scope, operationKey, fingerprint, recover, projectResult, run }: CanonicalTaskCoordinatedRequest<T>): Promise<T>;
78
+ executePreparedDelete<T>(args: CanonicalTaskPreparedDeleteCoordination<T>): Promise<T>;
79
+ executePreparedDeleteCoordinated<T>({ operationKey, versionClaimKey, fingerprint, versionFingerprint, principalNamespace, prepare, findTask, removeTask, authorizeReplay, }: CanonicalTaskPreparedDeleteCoordination<T>): Promise<T>;
80
+ completePreparedDelete<T>({ operationKey, versionClaimKey, row, findTask, removeTask, authorizeReplay }: {
81
+ operationKey: string;
82
+ versionClaimKey: string;
83
+ row: {
84
+ fingerprint: string;
85
+ result_json: unknown;
86
+ authorization_snapshot?: unknown;
87
+ };
88
+ findTask: () => Promise<unknown>;
89
+ removeTask: (task: any) => Promise<void>;
90
+ authorizeReplay?: (authorizationSnapshot: JsonRecord, result: T) => Promise<void>;
91
+ }): Promise<T>;
92
+ }
93
+ export interface CanonicalTaskCoordinatedRequest<T> {
94
+ scope: string;
95
+ operationKey: string;
96
+ fingerprint: string;
97
+ recover?: (() => Promise<{
98
+ recovered: boolean;
99
+ result?: T;
100
+ }>) | null;
101
+ projectResult?: (value: T) => unknown;
102
+ run: () => Promise<T>;
103
+ }
104
+ export interface CanonicalTaskPreparedDeleteCoordination<T> {
105
+ operationKey: string;
106
+ versionClaimKey: string;
107
+ fingerprint: string;
108
+ versionFingerprint: string;
109
+ principalNamespace: string;
110
+ prepare: () => Promise<{
111
+ result: T;
112
+ authorizationSnapshot?: JsonRecord;
113
+ task?: unknown;
114
+ }>;
115
+ findTask: () => Promise<unknown>;
116
+ removeTask: (task: any) => Promise<void>;
117
+ /** Reauthorize a completed or already-applied delete from its durable snapshot. */
118
+ authorizeReplay?: (authorizationSnapshot: JsonRecord, result: T) => Promise<void>;
119
+ }
120
+ export declare class CanonicalTaskReadinessError extends Error {
121
+ readonly code: string;
122
+ readonly status = 503;
123
+ details?: JsonRecord;
124
+ constructor(code?: string, message?: string);
125
+ }
126
+ export type CanonicalTaskSourceHeadRebindGuard = (input: {
127
+ fromHead?: string | null;
128
+ toHead?: string | null;
129
+ }) => Promise<{
130
+ allowed: boolean;
131
+ reason?: string;
132
+ changedPaths?: string[];
133
+ changedPackages?: string[];
134
+ error?: string;
135
+ }>;
136
+ interface CanonicalTaskReadinessLogger {
137
+ warn?(message: string): void;
138
+ }
139
+ /**
140
+ * Opens Canonical Task mutation only while the persisted readiness row was
141
+ * blessed for this release: the same writer, manifest, schema and source head.
142
+ */
143
+ export declare class CanonicalTaskReadiness {
144
+ readonly operationRepository: CanonicalTaskOperationRepository | null;
145
+ readonly expected: {
146
+ manifestHash: string | null;
147
+ schemaVersion: string | null;
148
+ sourceHead: string | null;
149
+ };
150
+ readonly sourceHeadRebindGuard: CanonicalTaskSourceHeadRebindGuard | null;
151
+ readonly logger: CanonicalTaskReadinessLogger | null;
152
+ ready: boolean;
153
+ reason: string | null;
154
+ evidence: JsonRecord | null;
155
+ constructor({ operationRepository, manifestHash, schemaVersion, sourceHead, sourceHeadRebindGuard, logger, }?: {
156
+ operationRepository?: CanonicalTaskOperationRepository | null;
157
+ manifestHash?: string | null;
158
+ schemaVersion?: string | null;
159
+ sourceHead?: string | null;
160
+ sourceHeadRebindGuard?: CanonicalTaskSourceHeadRebindGuard | null;
161
+ logger?: CanonicalTaskReadinessLogger | null;
162
+ });
163
+ open(evidence?: JsonRecord | null): void;
164
+ close(reason?: string): void;
165
+ matches(row: CanonicalTaskReadinessRow | null): boolean;
166
+ refresh({ allowWriterRebind }?: {
167
+ allowWriterRebind?: boolean | undefined;
168
+ }): Promise<{
169
+ ready: boolean;
170
+ reason?: string | null;
171
+ evidence?: JsonRecord | null;
172
+ }>;
173
+ attemptSourceHeadRebind(): Promise<boolean>;
174
+ initialize(): Promise<{
175
+ ready: boolean;
176
+ reason?: string | null;
177
+ evidence?: JsonRecord | null;
178
+ error?: unknown;
179
+ }>;
180
+ assertMutationReady(): Promise<void>;
181
+ }
182
+ type ExecFile = (file: string, args: string[]) => Promise<{
183
+ stdout: string | Buffer;
184
+ }>;
185
+ export interface CanonicalTaskSourceHeadGuardOptions {
186
+ repoDir?: string | null;
187
+ /** Repository paths whose change can alter Canonical Task mutation semantics. Tenant configuration. */
188
+ guardedPaths: string[];
189
+ /**
190
+ * Installed packages whose locked version can alter Canonical Task mutation
191
+ * semantics (for example this package). A change of the locked entry blocks
192
+ * the rebind even when no guarded path changed.
193
+ */
194
+ guardedPackages?: string[];
195
+ lockfilePath?: string;
196
+ execFile?: ExecFile;
197
+ }
198
+ /**
199
+ * Decides whether readiness blessed for one source head may follow a deploy
200
+ * to another head. It allows the rebind only when git proves that no guarded
201
+ * path and no guarded package changed between them; anything unknown stays
202
+ * fail-closed.
203
+ */
204
+ export declare function createCanonicalTaskSourceHeadGuard({ repoDir, guardedPaths, guardedPackages, lockfilePath, execFile, }: CanonicalTaskSourceHeadGuardOptions): CanonicalTaskSourceHeadRebindGuard;
205
+ export {};