@unson/brainbase-mcp 0.43.0 → 0.44.1

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 (74) 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/meeting-minutes.css +122 -0
  70. package/ui/meeting-minutes.js +316 -0
  71. package/ui/objective-editor.js +47 -11
  72. package/ui/world/world-district.js +18 -3
  73. package/ui/world/world-placement.js +13 -0
  74. package/ui/world/world-work-rail.js +7 -1
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Portable lifecycle for a routine cycle.
3
+ *
4
+ * The consumer owns each routine's Graph, tenant, credentials, storage, and
5
+ * systemd behavior. This core only selects the injected handler, converts a
6
+ * thrown handler error into the consumer's error result, and invokes the same
7
+ * finalizer for both success and failure. That keeps the cycle boundary
8
+ * reusable without moving Brainbase policy into the OSS package.
9
+ */
10
+ export type RoutineCycleInput = Record<string, unknown>;
11
+ export type RoutineCycleHandler = (input: RoutineCycleInput, context?: unknown) => unknown | PromiseLike<unknown>;
12
+ export type RoutineCycleErrorResult = (error: unknown, routine: string) => unknown;
13
+ export type RoutineCycleSummary = (routine: string, result: unknown) => unknown;
14
+ export type RoutineCycleContentAvailability = (routine: string, result: unknown) => boolean | undefined;
15
+ export type RoutineCycleContentEmptyAnomaly = (routine: string) => Record<string, unknown>;
16
+ export type RoutineCyclePostprocessor = (routine: string, result: unknown) => unknown;
17
+ export interface RoutineCycleRuntimeOptions {
18
+ handlers: Record<string, RoutineCycleHandler>;
19
+ onError?: RoutineCycleErrorResult;
20
+ summarize?: RoutineCycleSummary;
21
+ /** Consumer may report whether a completed routine produced usable content. */
22
+ contentAvailable?: RoutineCycleContentAvailability;
23
+ /** Consumer may preserve its public wording for an empty completed result. */
24
+ contentEmptyAnomaly?: RoutineCycleContentEmptyAnomaly;
25
+ /** Consumer-specific decoration runs after the portable finalizer. */
26
+ postprocess?: RoutineCyclePostprocessor;
27
+ }
28
+ export interface RoutineCycleFinalizeOptions {
29
+ /** A consumer may report that a completed routine had no usable content. */
30
+ contentAvailable?: boolean;
31
+ contentEmptyAnomaly?: Record<string, unknown>;
32
+ }
33
+ /**
34
+ * Normalize the lifecycle fields shared by every routine consumer.
35
+ *
36
+ * Consumer code remains responsible for redacting and shaping its domain
37
+ * output. This function owns the portable status/coverage/anomaly contract so
38
+ * a consumer cannot accidentally drop an anomaly while adding its own summary.
39
+ */
40
+ export declare function finalizeRoutineCycleResult(routine: string, result: unknown, options?: RoutineCycleFinalizeOptions): Record<string, unknown>;
41
+ export declare class RoutineCycleRuntime {
42
+ private readonly handlers;
43
+ private readonly onError;
44
+ private readonly summarize;
45
+ private readonly contentAvailable?;
46
+ private readonly contentEmptyAnomaly?;
47
+ private readonly postprocess?;
48
+ constructor(options: RoutineCycleRuntimeOptions);
49
+ execute(input?: RoutineCycleInput, context?: unknown): Promise<unknown>;
50
+ }
51
+ export declare function createRoutineCycleRuntime(options: RoutineCycleRuntimeOptions): RoutineCycleRuntime;
@@ -0,0 +1,153 @@
1
+ /**
2
+ * Portable lifecycle for a routine cycle.
3
+ *
4
+ * The consumer owns each routine's Graph, tenant, credentials, storage, and
5
+ * systemd behavior. This core only selects the injected handler, converts a
6
+ * thrown handler error into the consumer's error result, and invokes the same
7
+ * finalizer for both success and failure. That keeps the cycle boundary
8
+ * reusable without moving Brainbase policy into the OSS package.
9
+ */
10
+ function unsupportedRoutine(routine) {
11
+ return new Error(`unsupported routine: ${routine}`);
12
+ }
13
+ function defaultErrorResult(error, stage = 'routine_execution') {
14
+ const message = error instanceof Error ? error.message : String(error);
15
+ return {
16
+ status: 'failed',
17
+ anomalies: [{ code: 'routine_execution_failed', stage, summary: message }]
18
+ };
19
+ }
20
+ function errorWithStage(error, stage) {
21
+ if (error && typeof error === 'object') {
22
+ try {
23
+ const existingStage = error.stage;
24
+ if (typeof existingStage !== 'string' || existingStage.trim() === '') {
25
+ Object.assign(error, { stage });
26
+ }
27
+ return error;
28
+ }
29
+ catch {
30
+ // A frozen host error still gets a stable wrapper below.
31
+ }
32
+ }
33
+ const wrapped = new Error(error instanceof Error ? error.message : String(error));
34
+ Object.assign(wrapped, { stage, cause: error });
35
+ return wrapped;
36
+ }
37
+ function safeErrorResult(onError, error, routine, stage) {
38
+ const staged = errorWithStage(error, stage);
39
+ try {
40
+ return onError(staged, routine);
41
+ }
42
+ catch (handlerError) {
43
+ return defaultErrorResult(handlerError, stage);
44
+ }
45
+ }
46
+ /**
47
+ * Normalize the lifecycle fields shared by every routine consumer.
48
+ *
49
+ * Consumer code remains responsible for redacting and shaping its domain
50
+ * output. This function owns the portable status/coverage/anomaly contract so
51
+ * a consumer cannot accidentally drop an anomaly while adding its own summary.
52
+ */
53
+ export function finalizeRoutineCycleResult(routine, result, options = {}) {
54
+ const source = result && typeof result === 'object' && !Array.isArray(result)
55
+ ? result
56
+ : {};
57
+ let status = typeof source.status === 'string' ? source.status : 'failed';
58
+ const anomalies = Array.isArray(source.anomalies) ? [...source.anomalies] : [];
59
+ if (status === 'completed' && options.contentAvailable === false) {
60
+ status = 'partial';
61
+ anomalies.push(options.contentEmptyAnomaly || {
62
+ code: 'routine_content_empty',
63
+ routine,
64
+ summary: 'routine completed without usable content'
65
+ });
66
+ }
67
+ const coverage = options.contentAvailable === false
68
+ ? 'partial'
69
+ : typeof source.coverage === 'string'
70
+ ? source.coverage
71
+ : status === 'failed'
72
+ ? 'unavailable'
73
+ : anomalies.length > 0 || status === 'partial' ? 'partial' : 'confirmed';
74
+ return {
75
+ ...source,
76
+ status,
77
+ anomalies,
78
+ coverage,
79
+ summary: {
80
+ routine,
81
+ status,
82
+ coverage,
83
+ anomaly_count: anomalies.length
84
+ }
85
+ };
86
+ }
87
+ export class RoutineCycleRuntime {
88
+ handlers;
89
+ onError;
90
+ summarize;
91
+ contentAvailable;
92
+ contentEmptyAnomaly;
93
+ postprocess;
94
+ constructor(options) {
95
+ if (!options || typeof options !== 'object' || !options.handlers || typeof options.handlers !== 'object') {
96
+ throw new Error('routine cycle handlers are required');
97
+ }
98
+ this.handlers = options.handlers;
99
+ this.onError = options.onError || ((error) => defaultErrorResult(error));
100
+ this.summarize = options.summarize || ((_routine, result) => result);
101
+ this.contentAvailable = options.contentAvailable;
102
+ this.contentEmptyAnomaly = options.contentEmptyAnomaly;
103
+ this.postprocess = options.postprocess;
104
+ }
105
+ async execute(input = {}, context) {
106
+ const routine = typeof input?.routine === 'string' ? input.routine : String(input?.routine || '');
107
+ let result;
108
+ try {
109
+ const handler = this.handlers[routine];
110
+ if (typeof handler !== 'function')
111
+ throw unsupportedRoutine(routine);
112
+ result = await handler(input, context);
113
+ }
114
+ catch (error) {
115
+ result = safeErrorResult(this.onError, error, routine, 'routine_execution');
116
+ }
117
+ let summarized;
118
+ try {
119
+ summarized = await this.summarize(routine, result);
120
+ }
121
+ catch (error) {
122
+ summarized = safeErrorResult(this.onError, error, routine, 'summary');
123
+ }
124
+ let finalized;
125
+ try {
126
+ finalized = finalizeRoutineCycleResult(routine, summarized, {
127
+ contentAvailable: this.contentAvailable?.(routine, summarized),
128
+ contentEmptyAnomaly: this.contentEmptyAnomaly?.(routine) || {
129
+ code: 'routine_content_empty',
130
+ routine,
131
+ summary: 'routine completed without usable content'
132
+ }
133
+ });
134
+ }
135
+ catch (error) {
136
+ finalized = finalizeRoutineCycleResult(routine, safeErrorResult(this.onError, error, routine, 'finalize'));
137
+ }
138
+ if (!this.postprocess)
139
+ return finalized;
140
+ try {
141
+ return await this.postprocess(routine, finalized);
142
+ }
143
+ catch (error) {
144
+ // Decoration is consumer-owned, but it remains inside the portable
145
+ // cycle boundary so a decoration failure cannot escape as an untracked
146
+ // successful routine.
147
+ return finalizeRoutineCycleResult(routine, safeErrorResult(this.onError, error, routine, 'postprocess'));
148
+ }
149
+ }
150
+ }
151
+ export function createRoutineCycleRuntime(options) {
152
+ return new RoutineCycleRuntime(options);
153
+ }
@@ -0,0 +1,49 @@
1
+ import type { Awaitable } from './runtime-host.js';
2
+ import type { RoutineExpectation } from './routine-runtime.js';
3
+ export interface RoutineReceiptHistoryQuery {
4
+ projectId: string;
5
+ sourceType: string;
6
+ sourceIdentity: string;
7
+ limit: number;
8
+ }
9
+ export interface RoutineReceiptHistoryPort {
10
+ listHistory(input: RoutineReceiptHistoryQuery, context?: unknown): Awaitable<{
11
+ items?: unknown[];
12
+ } | unknown>;
13
+ }
14
+ export interface RoutineLivenessExceptionPort {
15
+ (context?: unknown): Awaitable<unknown[]>;
16
+ }
17
+ export interface RoutineLivenessRuntimeOptions {
18
+ expectations: RoutineExpectation[];
19
+ runReceiptQueryService: RoutineReceiptHistoryPort;
20
+ listDeadLetters?: RoutineLivenessExceptionPort;
21
+ listKnowledgeEventDeadLetters?: RoutineLivenessExceptionPort;
22
+ listKnowledgeEventOutboxExceptions?: RoutineLivenessExceptionPort;
23
+ listRuntimeIdentityExceptions?: RoutineLivenessExceptionPort;
24
+ /** Consumers may add domain-specific priorities without moving them here. */
25
+ priority?: Readonly<Record<string, number>>;
26
+ now?: () => Date;
27
+ }
28
+ /** Convert a recurring local wall-clock schedule to an instant. */
29
+ export declare function scheduledAtFor(expectation: RoutineExpectation, now: Date): Date;
30
+ /**
31
+ * Portable liveness/reconciliation over routine expectations and receipts.
32
+ * Graph, tenant, storage and dead-letter sources remain injected consumer
33
+ * ports; only schedule, receipt and required-artifact semantics live here.
34
+ */
35
+ export declare class RoutineLivenessRuntime {
36
+ private readonly expectations;
37
+ private readonly runReceiptQueryService;
38
+ private readonly listDeadLetters;
39
+ private readonly listKnowledgeEventDeadLetters;
40
+ private readonly listKnowledgeEventOutboxExceptions;
41
+ private readonly listRuntimeIdentityExceptions;
42
+ private readonly priority;
43
+ private readonly now;
44
+ constructor(options: RoutineLivenessRuntimeOptions);
45
+ listExceptions({ limit }?: {
46
+ limit?: number;
47
+ }, context?: unknown): Promise<unknown[]>;
48
+ }
49
+ export declare function createRoutineLivenessRuntime(options: RoutineLivenessRuntimeOptions): RoutineLivenessRuntime;
@@ -0,0 +1,227 @@
1
+ const DEFAULT_PRIORITY = Object.freeze({
2
+ dead_letter: 1,
3
+ missing_receipt: 2,
4
+ required_artifact_missing: 3,
5
+ blocked_receipt: 4
6
+ });
7
+ function asDate(value, fieldName) {
8
+ const date = value instanceof Date ? new Date(value.getTime()) : new Date(value);
9
+ if (Number.isNaN(date.getTime()))
10
+ throw new Error(`${fieldName} must be a valid date`);
11
+ return date;
12
+ }
13
+ function zonedParts(date, timezone) {
14
+ const parts = new Intl.DateTimeFormat('en-US', {
15
+ timeZone: timezone,
16
+ hour12: false,
17
+ hourCycle: 'h23',
18
+ year: 'numeric',
19
+ month: '2-digit',
20
+ day: '2-digit',
21
+ hour: '2-digit',
22
+ minute: '2-digit'
23
+ }).formatToParts(date);
24
+ const values = Object.fromEntries(parts.map((part) => [part.type, part.value]));
25
+ return {
26
+ year: Number(values.year),
27
+ month: Number(values.month),
28
+ day: Number(values.day),
29
+ hour: Number(values.hour),
30
+ minute: Number(values.minute)
31
+ };
32
+ }
33
+ function timezoneOffsetMs(date, timezone) {
34
+ const parts = zonedParts(date, timezone);
35
+ const localAsUtc = Date.UTC(parts.year, parts.month - 1, parts.day, parts.hour, parts.minute, 0, 0);
36
+ return localAsUtc - date.getTime();
37
+ }
38
+ /** Convert a recurring local wall-clock schedule to an instant. */
39
+ export function scheduledAtFor(expectation, now) {
40
+ const current = asDate(now, 'now');
41
+ const local = zonedParts(current, expectation.timezone);
42
+ const localNowMs = Date.UTC(local.year, local.month - 1, local.day, local.hour, local.minute, 0, 0);
43
+ const scheduled = new Date(localNowMs);
44
+ scheduled.setUTCHours(expectation.schedule.hour, expectation.schedule.minute, 0, 0);
45
+ if (expectation.schedule.kind === 'daily') {
46
+ if (scheduled.getTime() > localNowMs)
47
+ scheduled.setUTCDate(scheduled.getUTCDate() - 1);
48
+ }
49
+ else if (expectation.schedule.kind === 'weekly') {
50
+ const dayOfWeek = expectation.schedule.day_of_week;
51
+ if (dayOfWeek === undefined) {
52
+ throw new Error('weekly routine schedule day_of_week is required');
53
+ }
54
+ const weekday = new Date(localNowMs).getUTCDay();
55
+ const daysSinceSchedule = (weekday - dayOfWeek + 7) % 7;
56
+ scheduled.setUTCDate(scheduled.getUTCDate() - daysSinceSchedule);
57
+ if (scheduled.getTime() > localNowMs)
58
+ scheduled.setUTCDate(scheduled.getUTCDate() - 7);
59
+ }
60
+ else {
61
+ throw new Error(`Unsupported routine schedule kind: ${String(expectation.schedule.kind)}`);
62
+ }
63
+ // Resolve the offset at the candidate date. Two iterations cover DST
64
+ // transitions while keeping the core independent of a timezone library.
65
+ let instantMs = scheduled.getTime();
66
+ for (let attempt = 0; attempt < 3; attempt += 1) {
67
+ instantMs = scheduled.getTime() - timezoneOffsetMs(new Date(instantMs), expectation.timezone);
68
+ }
69
+ return new Date(instantMs);
70
+ }
71
+ function isBlockedReceipt(receipt) {
72
+ return ['blocked', 'failed', 'waiting_human'].includes(String(receipt.source_status))
73
+ || ['no_data', 'unconfirmed'].includes(String(receipt.evidence_state));
74
+ }
75
+ function normalizeArtifactName(value) {
76
+ return String(value || '').trim().toLowerCase().replace(/[\s-]+/g, '_');
77
+ }
78
+ function missingRequiredArtifacts(expectation, receipt) {
79
+ const evidenceRefs = Array.isArray(receipt.evidence_refs) ? receipt.evidence_refs : [];
80
+ return expectation.required_artifacts.filter((requiredArtifact) => !evidenceRefs.some((evidence) => {
81
+ if (!evidence || typeof evidence !== 'object' || Array.isArray(evidence))
82
+ return false;
83
+ const item = evidence;
84
+ if (item.kind !== 'artifact_ref')
85
+ return false;
86
+ if (normalizeArtifactName(item.label) === normalizeArtifactName(requiredArtifact))
87
+ return true;
88
+ if (requiredArtifact === 'routine_summary') {
89
+ const ref = String(item.ref || '').toLowerCase();
90
+ return /(?:^|[/:_-])routine(?:[/:_-])summary(?:$|[/:_-])/.test(ref)
91
+ || /(?:^|[/:_-])summary(?:$|[/:_-])/.test(ref);
92
+ }
93
+ return false;
94
+ }));
95
+ }
96
+ function exceptionOverdueMs(exceptionItem, now) {
97
+ const reference = exceptionItem.grace_deadline_at
98
+ || exceptionItem.finished_at
99
+ || exceptionItem.created_at;
100
+ return reference ? Math.max(0, now.getTime() - asDate(reference, 'exception timestamp').getTime()) : 0;
101
+ }
102
+ function record(value) {
103
+ return value && typeof value === 'object' && !Array.isArray(value)
104
+ ? value
105
+ : {};
106
+ }
107
+ /**
108
+ * Portable liveness/reconciliation over routine expectations and receipts.
109
+ * Graph, tenant, storage and dead-letter sources remain injected consumer
110
+ * ports; only schedule, receipt and required-artifact semantics live here.
111
+ */
112
+ export class RoutineLivenessRuntime {
113
+ expectations;
114
+ runReceiptQueryService;
115
+ listDeadLetters;
116
+ listKnowledgeEventDeadLetters;
117
+ listKnowledgeEventOutboxExceptions;
118
+ listRuntimeIdentityExceptions;
119
+ priority;
120
+ now;
121
+ constructor(options) {
122
+ if (!options || !Array.isArray(options.expectations)) {
123
+ throw new Error('routine liveness expectations must be an array');
124
+ }
125
+ if (!options.runReceiptQueryService || typeof options.runReceiptQueryService.listHistory !== 'function') {
126
+ throw new Error('routine liveness runReceiptQueryService.listHistory is required');
127
+ }
128
+ this.expectations = options.expectations;
129
+ this.runReceiptQueryService = options.runReceiptQueryService;
130
+ this.listDeadLetters = options.listDeadLetters || (async () => []);
131
+ this.listKnowledgeEventDeadLetters = options.listKnowledgeEventDeadLetters || (async () => []);
132
+ this.listKnowledgeEventOutboxExceptions = options.listKnowledgeEventOutboxExceptions || (async () => []);
133
+ this.listRuntimeIdentityExceptions = options.listRuntimeIdentityExceptions || (async () => []);
134
+ this.priority = { ...DEFAULT_PRIORITY, ...(options.priority || {}) };
135
+ this.now = options.now || (() => new Date());
136
+ }
137
+ async listExceptions({ limit = 3 } = {}, context) {
138
+ const current = asDate(this.now(), 'now');
139
+ const [deadLetters, knowledgeEventDeadLetters, knowledgeEventOutboxExceptions, runtimeIdentityExceptions] = await Promise.all([
140
+ this.listDeadLetters(context),
141
+ this.listKnowledgeEventDeadLetters(context),
142
+ this.listKnowledgeEventOutboxExceptions(context),
143
+ this.listRuntimeIdentityExceptions(context)
144
+ ]);
145
+ const deadLettersByAutomation = new Map();
146
+ for (const item of Array.isArray(deadLetters) ? deadLetters : []) {
147
+ const value = record(item);
148
+ const automationId = typeof value.automation_id === 'string' ? value.automation_id : '';
149
+ if (automationId && !deadLettersByAutomation.has(automationId))
150
+ deadLettersByAutomation.set(automationId, value);
151
+ }
152
+ const exceptions = [
153
+ ...(Array.isArray(runtimeIdentityExceptions) ? runtimeIdentityExceptions : []),
154
+ ...(Array.isArray(knowledgeEventDeadLetters) ? knowledgeEventDeadLetters : []),
155
+ ...(Array.isArray(knowledgeEventOutboxExceptions) ? knowledgeEventOutboxExceptions : [])
156
+ ].map((item) => {
157
+ const value = record(item);
158
+ return {
159
+ ...value,
160
+ ...(value.path ? { path: String(value.path).split(/[\\/]/).pop() } : {})
161
+ };
162
+ });
163
+ for (const expectation of this.expectations) {
164
+ const scheduledAt = scheduledAtFor(expectation, current);
165
+ const graceDeadline = new Date(scheduledAt.getTime() + expectation.grace_minutes * 60 * 1000);
166
+ const deadLetter = deadLettersByAutomation.get(expectation.automation_id);
167
+ if (deadLetter) {
168
+ exceptions.push({
169
+ code: 'dead_letter',
170
+ automation_id: expectation.automation_id,
171
+ created_at: deadLetter.created_at,
172
+ path: deadLetter.path,
173
+ scheduled_at: scheduledAt.toISOString(),
174
+ grace_deadline_at: graceDeadline.toISOString()
175
+ });
176
+ }
177
+ const historyInput = {
178
+ projectId: expectation.project_id,
179
+ sourceType: expectation.source_type,
180
+ sourceIdentity: expectation.automation_id,
181
+ limit: 1
182
+ };
183
+ const history = record(await this.runReceiptQueryService.listHistory(historyInput, context));
184
+ const items = Array.isArray(history.items) ? history.items : [];
185
+ const latest = record(items[0]);
186
+ const latestFinishedAt = latest.finished_at ? asDate(latest.finished_at, 'receipt.finished_at') : null;
187
+ const receiptForSchedule = latestFinishedAt && latestFinishedAt >= scheduledAt ? latest : null;
188
+ if (!receiptForSchedule) {
189
+ if (current.getTime() > graceDeadline.getTime()) {
190
+ exceptions.push({
191
+ code: 'missing_receipt',
192
+ automation_id: expectation.automation_id,
193
+ scheduled_at: scheduledAt.toISOString(),
194
+ grace_deadline_at: graceDeadline.toISOString()
195
+ });
196
+ }
197
+ continue;
198
+ }
199
+ const missingArtifacts = missingRequiredArtifacts(expectation, receiptForSchedule);
200
+ if (isBlockedReceipt(receiptForSchedule) || missingArtifacts.length > 0) {
201
+ exceptions.push({
202
+ code: isBlockedReceipt(receiptForSchedule) ? 'blocked_receipt' : 'required_artifact_missing',
203
+ automation_id: expectation.automation_id,
204
+ source_status: receiptForSchedule.source_status,
205
+ evidence_state: receiptForSchedule.evidence_state,
206
+ ...(missingArtifacts.length > 0 ? { missing_required_artifacts: missingArtifacts } : {}),
207
+ finished_at: receiptForSchedule.finished_at,
208
+ scheduled_at: scheduledAt.toISOString(),
209
+ grace_deadline_at: graceDeadline.toISOString()
210
+ });
211
+ }
212
+ }
213
+ const safeLimit = Number.isFinite(limit) ? Math.max(0, Math.floor(limit)) : 0;
214
+ return exceptions
215
+ .map((item) => ({ item, overdueMs: exceptionOverdueMs(item, current) }))
216
+ .sort((left, right) => (this.priority[String(left.item.code)] ?? Number.MAX_SAFE_INTEGER)
217
+ - (this.priority[String(right.item.code)] ?? Number.MAX_SAFE_INTEGER)
218
+ || right.overdueMs - left.overdueMs
219
+ || String(left.item.automation_id || left.item.event_id || '')
220
+ .localeCompare(String(right.item.automation_id || right.item.event_id || '')))
221
+ .slice(0, safeLimit)
222
+ .map(({ item }) => item);
223
+ }
224
+ }
225
+ export function createRoutineLivenessRuntime(options) {
226
+ return new RoutineLivenessRuntime(options);
227
+ }
@@ -0,0 +1,101 @@
1
+ import { type NormalizedRunReceipt } from './run-receipt.js';
2
+ import { type Awaitable, type RuntimeHostContext, type RuntimeHostPorts } from './runtime-host.js';
3
+ export type RoutineScheduleKind = 'daily' | 'weekly';
4
+ export interface RoutineExpectationSchedule {
5
+ kind: RoutineScheduleKind;
6
+ hour: number;
7
+ minute: number;
8
+ day_of_week?: number;
9
+ }
10
+ export interface RoutineExpectation {
11
+ routine: string;
12
+ automation_id: string;
13
+ source_type: string;
14
+ project_id: string;
15
+ timezone: string;
16
+ schedule: RoutineExpectationSchedule;
17
+ grace_minutes: number;
18
+ required_artifacts: string[];
19
+ }
20
+ export interface RoutineExpectationConstraints {
21
+ sourceType?: string;
22
+ projectId?: string;
23
+ timezone?: string;
24
+ }
25
+ /**
26
+ * Parse a routine expectation document without assuming an organization,
27
+ * tenant, timezone, or scheduler. Consumers that need those constraints pass
28
+ * them explicitly, which keeps the common package portable.
29
+ */
30
+ export declare function parseRoutineExpectations(value: unknown, constraints?: RoutineExpectationConstraints): RoutineExpectation[];
31
+ export declare function loadRoutineExpectations(filePath: string, constraints?: RoutineExpectationConstraints): RoutineExpectation[];
32
+ export interface RoutineDeadLetter {
33
+ automation_id: string;
34
+ created_at: string;
35
+ path: string;
36
+ }
37
+ /** Read routine dead-letter files without interpreting organization data. */
38
+ export declare function listRoutineDeadLetters({ directory }: {
39
+ directory: string;
40
+ }): Promise<RoutineDeadLetter[]>;
41
+ export declare function countRoutineOutbox({ directory }: {
42
+ directory: string;
43
+ }): Promise<number>;
44
+ export interface RoutineRunRequest {
45
+ routine: string;
46
+ automationId: string;
47
+ projectId?: string;
48
+ sourceType?: string;
49
+ externalRunId?: string;
50
+ payload?: unknown;
51
+ metadata?: Record<string, unknown>;
52
+ }
53
+ export interface RoutineRunnerStartInput {
54
+ request: RoutineRunRequest;
55
+ context: RuntimeHostContext;
56
+ }
57
+ export interface RoutineRunnerRetryInput extends RoutineRunnerStartInput {
58
+ previousRunId: string;
59
+ }
60
+ export interface RoutineRunnerResult {
61
+ /** Canonical run_receipt.v1 payload. The host persists it through the receipt port. */
62
+ receipt: unknown;
63
+ result?: unknown;
64
+ }
65
+ export interface RoutineRunnerPort {
66
+ start(input: RoutineRunnerStartInput): Awaitable<RoutineRunnerResult>;
67
+ retry?(input: RoutineRunnerRetryInput): Awaitable<RoutineRunnerResult>;
68
+ }
69
+ export interface RoutineReceiptPersistenceInput {
70
+ payload: unknown;
71
+ normalized: NormalizedRunReceipt;
72
+ context: RuntimeHostContext;
73
+ }
74
+ export interface RoutineReceiptPort {
75
+ persist(input: RoutineReceiptPersistenceInput): Awaitable<unknown>;
76
+ query(input: {
77
+ request: RoutineRunRequest;
78
+ context: RuntimeHostContext;
79
+ }): Awaitable<unknown>;
80
+ }
81
+ export interface RoutineRuntimePorts extends RuntimeHostPorts {
82
+ runner: RoutineRunnerPort;
83
+ receipt: RoutineReceiptPort;
84
+ }
85
+ export interface RoutineRunResult {
86
+ runnerResult: unknown;
87
+ normalizedReceipt: NormalizedRunReceipt;
88
+ persistedReceipt: unknown;
89
+ context: RuntimeHostContext;
90
+ }
91
+ export declare class RoutineRuntimeService {
92
+ private readonly ports;
93
+ constructor(ports: RoutineRuntimePorts);
94
+ start(request: RoutineRunRequest): Promise<RoutineRunResult>;
95
+ retry(request: RoutineRunRequest, previousRunId: string): Promise<RoutineRunResult>;
96
+ history(request: RoutineRunRequest): Promise<unknown>;
97
+ private execute;
98
+ }
99
+ export declare function createRoutineRuntime(ports: RoutineRuntimePorts): RoutineRuntimeService;
100
+ /** Return whether an expectation's scheduled minute matches the supplied instant. */
101
+ export declare function routineScheduleMatches(expectation: RoutineExpectation, instant: Date): boolean;