@pattern-stack/codegen 0.4.1 → 0.4.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 (133) hide show
  1. package/package.json +2 -1
  2. package/runtime/analytics/index.ts +31 -0
  3. package/runtime/analytics/metrics.ts +85 -0
  4. package/runtime/analytics/packs/crm-entity-measures.ts +20 -0
  5. package/runtime/analytics/packs/index.ts +5 -0
  6. package/runtime/analytics/packs/monetary-measures.ts +20 -0
  7. package/runtime/analytics/specs.ts +54 -0
  8. package/runtime/analytics/types.ts +105 -0
  9. package/runtime/base-classes/activity-entity-repository.ts +50 -0
  10. package/runtime/base-classes/activity-entity-service.ts +48 -0
  11. package/runtime/base-classes/base-read-use-cases.ts +88 -0
  12. package/runtime/base-classes/base-repository.ts +289 -0
  13. package/runtime/base-classes/base-service.ts +183 -0
  14. package/runtime/base-classes/index.ts +38 -0
  15. package/runtime/base-classes/knowledge-entity-repository.ts +12 -0
  16. package/runtime/base-classes/knowledge-entity-service.ts +14 -0
  17. package/runtime/base-classes/lifecycle-events.ts +152 -0
  18. package/runtime/base-classes/metadata-entity-repository.ts +80 -0
  19. package/runtime/base-classes/metadata-entity-service.ts +48 -0
  20. package/runtime/base-classes/synced-entity-repository.ts +57 -0
  21. package/runtime/base-classes/synced-entity-service.ts +50 -0
  22. package/runtime/base-classes/with-analytics.ts +22 -0
  23. package/runtime/constants/tokens.ts +29 -0
  24. package/runtime/eav-helpers.ts +74 -0
  25. package/runtime/pipes/zod-validation.pipe.ts +64 -0
  26. package/runtime/shared/openapi/error-response.dto.ts +24 -0
  27. package/runtime/shared/openapi/errors.ts +39 -0
  28. package/runtime/shared/openapi/index.ts +20 -0
  29. package/runtime/shared/openapi/registry.tokens.ts +13 -0
  30. package/runtime/shared/openapi/registry.ts +151 -0
  31. package/runtime/subsystems/analytics/analytics-query.protocol.ts +37 -0
  32. package/runtime/subsystems/analytics/analytics.module.ts +64 -0
  33. package/runtime/subsystems/analytics/analytics.tokens.ts +24 -0
  34. package/runtime/subsystems/analytics/cube-backend.ts +75 -0
  35. package/runtime/subsystems/analytics/index.ts +15 -0
  36. package/runtime/subsystems/analytics/noop-backend.ts +27 -0
  37. package/runtime/subsystems/auth/auth.module.ts +91 -0
  38. package/runtime/subsystems/auth/auth.tokens.ts +27 -0
  39. package/runtime/subsystems/auth/backends/encryption-key/env.ts +76 -0
  40. package/runtime/subsystems/auth/backends/oauth-state-store/in-memory.ts +42 -0
  41. package/runtime/subsystems/auth/index.ts +77 -0
  42. package/runtime/subsystems/auth/protocols/auth-strategy.ts +46 -0
  43. package/runtime/subsystems/auth/protocols/encryption-key.ts +21 -0
  44. package/runtime/subsystems/auth/protocols/integration-store.ts +66 -0
  45. package/runtime/subsystems/auth/protocols/oauth-state-store.ts +16 -0
  46. package/runtime/subsystems/auth/runtime/integration-broken.error.ts +21 -0
  47. package/runtime/subsystems/auth/runtime/oauth2-refresh.strategy.ts +189 -0
  48. package/runtime/subsystems/auth/runtime/session-expired.error.ts +39 -0
  49. package/runtime/subsystems/auth/runtime/with-auth-retry.ts +50 -0
  50. package/runtime/subsystems/bridge/assert-tenant-id.ts +57 -0
  51. package/runtime/subsystems/bridge/bridge-delivery-handler.ts +220 -0
  52. package/runtime/subsystems/bridge/bridge-delivery.drizzle-backend.ts +149 -0
  53. package/runtime/subsystems/bridge/bridge-delivery.memory-backend.ts +140 -0
  54. package/runtime/subsystems/bridge/bridge-delivery.schema.ts +142 -0
  55. package/runtime/subsystems/bridge/bridge-errors.ts +112 -0
  56. package/runtime/subsystems/bridge/bridge-outbox-drain-hook.ts +175 -0
  57. package/runtime/subsystems/bridge/bridge.module.ts +160 -0
  58. package/runtime/subsystems/bridge/bridge.protocol.ts +351 -0
  59. package/runtime/subsystems/bridge/bridge.tokens.ts +68 -0
  60. package/runtime/subsystems/bridge/event-flow.service.ts +175 -0
  61. package/runtime/subsystems/bridge/generated/.gitkeep +0 -0
  62. package/runtime/subsystems/bridge/generated/registry.ts +6 -0
  63. package/runtime/subsystems/bridge/index.ts +84 -0
  64. package/runtime/subsystems/bridge/reserved-pools.ts +36 -0
  65. package/runtime/subsystems/cache/cache.drizzle-backend.ts +150 -0
  66. package/runtime/subsystems/cache/cache.memory-backend.ts +116 -0
  67. package/runtime/subsystems/cache/cache.module.ts +115 -0
  68. package/runtime/subsystems/cache/cache.protocol.ts +45 -0
  69. package/runtime/subsystems/cache/cache.schema.ts +27 -0
  70. package/runtime/subsystems/cache/cache.tokens.ts +17 -0
  71. package/runtime/subsystems/cache/index.ts +22 -0
  72. package/runtime/subsystems/events/domain-events.schema.ts +77 -0
  73. package/runtime/subsystems/events/event-bus.drizzle-backend.ts +327 -0
  74. package/runtime/subsystems/events/event-bus.memory-backend.ts +142 -0
  75. package/runtime/subsystems/events/event-bus.protocol.ts +86 -0
  76. package/runtime/subsystems/events/event-bus.redis-backend.ts +304 -0
  77. package/runtime/subsystems/events/events-errors.ts +30 -0
  78. package/runtime/subsystems/events/events.module.ts +230 -0
  79. package/runtime/subsystems/events/events.tokens.ts +62 -0
  80. package/runtime/subsystems/events/generated/bus.ts +103 -0
  81. package/runtime/subsystems/events/generated/index.ts +7 -0
  82. package/runtime/subsystems/events/generated/registry.ts +84 -0
  83. package/runtime/subsystems/events/generated/schemas.ts +59 -0
  84. package/runtime/subsystems/events/generated/types.ts +94 -0
  85. package/runtime/subsystems/events/index.ts +21 -0
  86. package/runtime/subsystems/index.ts +63 -0
  87. package/runtime/subsystems/jobs/generated/job-orchestration.schema.multi-tenant.ts +217 -0
  88. package/runtime/subsystems/jobs/generated/job-orchestration.schema.single-tenant.ts +217 -0
  89. package/runtime/subsystems/jobs/generated/scope-entity-type.ts +10 -0
  90. package/runtime/subsystems/jobs/index.ts +120 -0
  91. package/runtime/subsystems/jobs/job-handler.base.ts +206 -0
  92. package/runtime/subsystems/jobs/job-orchestration.schema.ts +217 -0
  93. package/runtime/subsystems/jobs/job-orchestrator.drizzle-backend.ts +536 -0
  94. package/runtime/subsystems/jobs/job-orchestrator.memory-backend.ts +850 -0
  95. package/runtime/subsystems/jobs/job-orchestrator.protocol.ts +179 -0
  96. package/runtime/subsystems/jobs/job-run-service.drizzle-backend.ts +171 -0
  97. package/runtime/subsystems/jobs/job-run-service.memory-backend.ts +165 -0
  98. package/runtime/subsystems/jobs/job-run-service.protocol.ts +79 -0
  99. package/runtime/subsystems/jobs/job-step-service.drizzle-backend.ts +66 -0
  100. package/runtime/subsystems/jobs/job-step-service.memory-backend.ts +119 -0
  101. package/runtime/subsystems/jobs/job-step-service.protocol.ts +53 -0
  102. package/runtime/subsystems/jobs/job-worker.module.ts +302 -0
  103. package/runtime/subsystems/jobs/job-worker.ts +615 -0
  104. package/runtime/subsystems/jobs/jobs-domain.module.ts +119 -0
  105. package/runtime/subsystems/jobs/jobs-domain.tokens.ts +30 -0
  106. package/runtime/subsystems/jobs/jobs-errors.ts +150 -0
  107. package/runtime/subsystems/jobs/memory-job-store.ts +35 -0
  108. package/runtime/subsystems/jobs/pool-config.loader.ts +218 -0
  109. package/runtime/subsystems/storage/index.ts +18 -0
  110. package/runtime/subsystems/storage/storage.local-backend.ts +113 -0
  111. package/runtime/subsystems/storage/storage.memory-backend.ts +78 -0
  112. package/runtime/subsystems/storage/storage.module.ts +60 -0
  113. package/runtime/subsystems/storage/storage.protocol.ts +78 -0
  114. package/runtime/subsystems/storage/storage.tokens.ts +9 -0
  115. package/runtime/subsystems/storage/storage.utils.ts +20 -0
  116. package/runtime/subsystems/sync/deep-equal.differ.ts +198 -0
  117. package/runtime/subsystems/sync/execute-sync.use-case.ts +334 -0
  118. package/runtime/subsystems/sync/index.ts +98 -0
  119. package/runtime/subsystems/sync/sync-audit.schema.ts +300 -0
  120. package/runtime/subsystems/sync/sync-change-source.protocol.ts +99 -0
  121. package/runtime/subsystems/sync/sync-cursor-store.drizzle-backend.ts +104 -0
  122. package/runtime/subsystems/sync/sync-cursor-store.memory-backend.ts +64 -0
  123. package/runtime/subsystems/sync/sync-cursor-store.protocol.ts +53 -0
  124. package/runtime/subsystems/sync/sync-errors.ts +54 -0
  125. package/runtime/subsystems/sync/sync-field-diff.protocol.ts +61 -0
  126. package/runtime/subsystems/sync/sync-loopback.protocol.ts +33 -0
  127. package/runtime/subsystems/sync/sync-run-recorder.drizzle-backend.ts +123 -0
  128. package/runtime/subsystems/sync/sync-run-recorder.memory-backend.ts +143 -0
  129. package/runtime/subsystems/sync/sync-run-recorder.protocol.ts +86 -0
  130. package/runtime/subsystems/sync/sync-sink.protocol.ts +55 -0
  131. package/runtime/subsystems/sync/sync.module.ts +156 -0
  132. package/runtime/subsystems/sync/sync.tokens.ts +57 -0
  133. package/runtime/types/drizzle.ts +23 -0
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Subsystems barrel export
3
+ *
4
+ * Infrastructure subsystems following Protocol → Backend → Factory pattern (ADR-008).
5
+ */
6
+
7
+ // Events
8
+ export { EVENT_BUS } from './events';
9
+ export type { DomainEvent, IEventBus } from './events';
10
+ export { EventsModule, DrizzleEventBus, MemoryEventBus } from './events';
11
+
12
+ // Jobs — orchestration schema only (JOB-1). Protocols / modules land in JOB-2 / JOB-5.
13
+ export { jobs, jobRuns, jobSteps } from './jobs';
14
+ export type { JobDefinitionRow, JobRunRow, JobStepRow } from './jobs';
15
+ export {
16
+ jobRunStatusEnum,
17
+ jobStepKindEnum,
18
+ jobStepStatusEnum,
19
+ collisionModeEnum,
20
+ replayFromEnum,
21
+ parentClosePolicyEnum,
22
+ waitKindEnum,
23
+ triggerSourceEnum,
24
+ } from './jobs';
25
+
26
+ // Cache
27
+ export { CACHE } from './cache';
28
+ export type { ICacheService } from './cache';
29
+ export { CacheModule, DrizzleCacheService, MemoryCacheService } from './cache';
30
+
31
+ // Storage
32
+ export { STORAGE } from './storage';
33
+ export type { IStorageService } from './storage';
34
+ export { StorageModule, LocalStorageBackend, MemoryStorageBackend } from './storage';
35
+
36
+ // Auth
37
+ export {
38
+ ENCRYPTION_KEY,
39
+ OAUTH_STATE_STORE,
40
+ AUTH_INTEGRATION_READER,
41
+ AUTH_INTEGRATION_TOKEN_WRITER,
42
+ AuthModule,
43
+ OAuth2RefreshStrategy,
44
+ withAuthRetry,
45
+ IntegrationBrokenError,
46
+ SessionExpiredError,
47
+ isSessionExpiredError,
48
+ EnvEncryptionKey,
49
+ InMemoryOAuthStateStore,
50
+ } from './auth';
51
+ export type {
52
+ IAuthStrategy,
53
+ IEncryptionKey,
54
+ IOAuthStateStore,
55
+ IIntegrationReader,
56
+ IIntegrationTokenWriter,
57
+ AuthCredentials,
58
+ AuthResolveOptions,
59
+ DecryptedIntegration,
60
+ OAuthStateEntry,
61
+ IntegrationTokenUpdate,
62
+ ParsedRefreshResponse,
63
+ } from './auth';
@@ -0,0 +1,217 @@
1
+ /**
2
+ * Drizzle schema for the job orchestration domain (ADR-022).
3
+ *
4
+ * Three tables model the lifecycle of a durable job:
5
+ * - `job` — definitions keyed by handler type (e.g. 'onboarding').
6
+ * - `job_run` — one row per attempt to execute a job; worker claims
7
+ * rows directly via SELECT ... FOR UPDATE SKIP LOCKED.
8
+ * - `job_step` — individual steps within a run; memoises output for replay.
9
+ *
10
+ * Phase 1 ships only this layer. There is no `job_queue` table, no executor
11
+ * port — see ADR-022 and `.claude/skills/jobs/SKILL.md` for the rationale.
12
+ */
13
+ import {
14
+ pgEnum,
15
+ pgTable,
16
+ uuid,
17
+ text,
18
+ jsonb,
19
+ integer,
20
+ timestamp,
21
+ index,
22
+ uniqueIndex,
23
+ } from 'drizzle-orm/pg-core';
24
+ import { sql } from 'drizzle-orm';
25
+ import type { InferSelectModel } from 'drizzle-orm';
26
+
27
+ // ─── Internal $type<> helpers ───────────────────────────────────────────────
28
+ // Annotation types for jsonb columns only. JOB-2 defines the public protocol
29
+ // types; these remain private to this file.
30
+
31
+ type RetryPolicy = {
32
+ attempts: number;
33
+ backoff: 'fixed' | 'exponential';
34
+ baseMs: number;
35
+ nonRetryableErrors?: string[];
36
+ };
37
+
38
+ type JobRunError = {
39
+ message: string;
40
+ stack?: string;
41
+ retryable: boolean;
42
+ attempt: number;
43
+ };
44
+
45
+ // ─── Enums ──────────────────────────────────────────────────────────────────
46
+
47
+ export const jobRunStatusEnum = pgEnum('job_run_status', [
48
+ 'pending',
49
+ 'running',
50
+ 'waiting',
51
+ 'completed',
52
+ 'failed',
53
+ 'timed_out',
54
+ 'canceled',
55
+ ]);
56
+
57
+ // extended in ADR-027: tool_call | llm_call | wait | checkpoint | message
58
+ export const jobStepKindEnum = pgEnum('job_step_kind', ['task']);
59
+
60
+ export const jobStepStatusEnum = pgEnum('job_step_status', [
61
+ 'pending',
62
+ 'running',
63
+ 'completed',
64
+ 'failed',
65
+ 'skipped',
66
+ ]);
67
+
68
+ export const collisionModeEnum = pgEnum('job_collision_mode', [
69
+ 'queue',
70
+ 'reject',
71
+ 'replace',
72
+ ]);
73
+
74
+ export const replayFromEnum = pgEnum('job_replay_from', [
75
+ 'scratch',
76
+ 'last_step',
77
+ 'last_checkpoint',
78
+ ]);
79
+
80
+ export const parentClosePolicyEnum = pgEnum('job_parent_close_policy', [
81
+ 'terminate',
82
+ 'cancel',
83
+ 'abandon',
84
+ ]);
85
+
86
+ // Phase 3 placeholder — see ADR-025
87
+ export const waitKindEnum = pgEnum('job_wait_kind', ['signal']);
88
+
89
+ // Phase 2 may add more sources; requires Atlas migration
90
+ export const triggerSourceEnum = pgEnum('job_trigger_source', [
91
+ 'manual',
92
+ 'schedule',
93
+ 'event',
94
+ 'parent',
95
+ ]);
96
+
97
+ // ─── job ────────────────────────────────────────────────────────────────────
98
+
99
+ export const jobs = pgTable('job', {
100
+ type: text('type').primaryKey(),
101
+ version: integer('version').notNull().default(1),
102
+ pool: text('pool').notNull(),
103
+ scopeEntityType: text('scope_entity_type'),
104
+ retryPolicy: jsonb('retry_policy').notNull().$type<RetryPolicy>(),
105
+ timeoutMs: integer('timeout_ms'),
106
+ concurrencyKeyTemplate: text('concurrency_key_template'),
107
+ collisionMode: collisionModeEnum('collision_mode').notNull().default('queue'),
108
+ dedupeKeyTemplate: text('dedupe_key_template'),
109
+ dedupeWindowMs: integer('dedupe_window_ms'),
110
+ priorityDefault: integer('priority_default').notNull().default(0),
111
+ replayFrom: replayFromEnum('replay_from').notNull().default('last_checkpoint'),
112
+ createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
113
+ updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
114
+ });
115
+
116
+ export type JobDefinitionRow = InferSelectModel<typeof jobs>;
117
+
118
+ // ─── job_run ────────────────────────────────────────────────────────────────
119
+
120
+ export const jobRuns = pgTable(
121
+ 'job_run',
122
+ {
123
+ id: uuid('id').primaryKey().defaultRandom(),
124
+ jobType: text('job_type').notNull().references(() => jobs.type),
125
+ jobVersion: integer('job_version').notNull(),
126
+ parentRunId: uuid('parent_run_id').references((): any => jobRuns.id),
127
+ /**
128
+ * Service generates `id` client-side via randomUUID() and sets
129
+ * root_run_id = id for root runs (single INSERT, no self-FK race).
130
+ */
131
+ rootRunId: uuid('root_run_id').notNull(),
132
+ parentClosePolicy: parentClosePolicyEnum('parent_close_policy')
133
+ .notNull()
134
+ .default('terminate'),
135
+ scopeEntityType: text('scope_entity_type'),
136
+ scopeEntityId: text('scope_entity_id'),
137
+ tenantId: text('tenant_id'), // F9: always emitted (nullable) — runtime enforces on boundary via JOBS_MULTI_TENANT
138
+ tags: jsonb('tags').notNull().default({}).$type<Record<string, string>>(),
139
+ pool: text('pool').notNull(),
140
+ priority: integer('priority').notNull().default(0),
141
+ concurrencyKey: text('concurrency_key'),
142
+ dedupeKey: text('dedupe_key'),
143
+ status: jobRunStatusEnum('status').notNull().default('pending'),
144
+ input: jsonb('input').notNull().$type<Record<string, unknown>>(),
145
+ output: jsonb('output').$type<Record<string, unknown>>(),
146
+ error: jsonb('error').$type<JobRunError>(),
147
+ triggerSource: triggerSourceEnum('trigger_source').notNull(),
148
+ triggerRef: text('trigger_ref'),
149
+ runAt: timestamp('run_at', { withTimezone: true }).notNull().defaultNow(),
150
+ startedAt: timestamp('started_at', { withTimezone: true }),
151
+ finishedAt: timestamp('finished_at', { withTimezone: true }),
152
+ claimedAt: timestamp('claimed_at', { withTimezone: true }),
153
+ attempts: integer('attempts').notNull().default(0),
154
+ // Phase 3 placeholder — see ADR-025
155
+ waitKind: waitKindEnum('wait_kind'),
156
+ // Phase 3 placeholder — see ADR-025
157
+ resumeToken: text('resume_token'),
158
+ // Phase 3 placeholder — see ADR-025
159
+ waitDeadline: timestamp('wait_deadline', { withTimezone: true }),
160
+ createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
161
+ updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
162
+ },
163
+ (t) => ({
164
+ /** Claim query: ORDER BY priority DESC, run_at ASC. */
165
+ idxJobRunClaim: index('idx_job_run_claim').on(t.status, t.pool, t.runAt),
166
+ /** Tree traversal / cascade cancel. */
167
+ idxJobRunRoot: index('idx_job_run_root').on(t.rootRunId),
168
+ /** listForScope query. */
169
+ idxJobRunScope: index('idx_job_run_scope').on(t.scopeEntityType, t.scopeEntityId),
170
+ /** Idempotency collapse — partial index. */
171
+ idxJobRunDedupe: index('idx_job_run_dedupe')
172
+ .on(t.jobType, t.dedupeKey)
173
+ .where(sql`${t.dedupeKey} IS NOT NULL`),
174
+ /** Collision check — partial index. */
175
+ idxJobRunConcurrency: index('idx_job_run_concurrency')
176
+ .on(t.concurrencyKey)
177
+ .where(
178
+ sql`${t.concurrencyKey} IS NOT NULL AND ${t.status} IN ('pending','running')`,
179
+ ),
180
+ }),
181
+ );
182
+
183
+ export type JobRunRow = InferSelectModel<typeof jobRuns>;
184
+
185
+ // ─── job_step ───────────────────────────────────────────────────────────────
186
+
187
+ export const jobSteps = pgTable(
188
+ 'job_step',
189
+ {
190
+ id: uuid('id').primaryKey().defaultRandom(),
191
+ jobRunId: uuid('job_run_id').notNull().references(() => jobRuns.id),
192
+ stepId: text('step_id').notNull(),
193
+ kind: jobStepKindEnum('kind').notNull().default('task'),
194
+ /**
195
+ * Monotonic within run. integer (max ~2B per run) is sufficient —
196
+ * downgraded from ADR-022's bigint; revisit only if a single run
197
+ * ever exceeds 2 billion steps.
198
+ */
199
+ seq: integer('seq').notNull(),
200
+ status: jobStepStatusEnum('status').notNull().default('pending'),
201
+ input: jsonb('input').$type<Record<string, unknown>>(),
202
+ /** Memoised on success for replay. */
203
+ output: jsonb('output').$type<Record<string, unknown>>(),
204
+ error: jsonb('error').$type<JobRunError>(),
205
+ attempts: integer('attempts').notNull().default(0),
206
+ startedAt: timestamp('started_at', { withTimezone: true }),
207
+ finishedAt: timestamp('finished_at', { withTimezone: true }),
208
+ },
209
+ (t) => ({
210
+ /** No duplicate step IDs per run. */
211
+ idxJobStepRunStep: uniqueIndex('idx_job_step_run_step').on(t.jobRunId, t.stepId),
212
+ /** Ordered timeline reads. */
213
+ idxJobStepTimeline: index('idx_job_step_timeline').on(t.jobRunId, t.seq),
214
+ }),
215
+ );
216
+
217
+ export type JobStepRow = InferSelectModel<typeof jobSteps>;
@@ -0,0 +1,217 @@
1
+ /**
2
+ * Drizzle schema for the job orchestration domain (ADR-022).
3
+ *
4
+ * Three tables model the lifecycle of a durable job:
5
+ * - `job` — definitions keyed by handler type (e.g. 'onboarding').
6
+ * - `job_run` — one row per attempt to execute a job; worker claims
7
+ * rows directly via SELECT ... FOR UPDATE SKIP LOCKED.
8
+ * - `job_step` — individual steps within a run; memoises output for replay.
9
+ *
10
+ * Phase 1 ships only this layer. There is no `job_queue` table, no executor
11
+ * port — see ADR-022 and `.claude/skills/jobs/SKILL.md` for the rationale.
12
+ */
13
+ import {
14
+ pgEnum,
15
+ pgTable,
16
+ uuid,
17
+ text,
18
+ jsonb,
19
+ integer,
20
+ timestamp,
21
+ index,
22
+ uniqueIndex,
23
+ } from 'drizzle-orm/pg-core';
24
+ import { sql } from 'drizzle-orm';
25
+ import type { InferSelectModel } from 'drizzle-orm';
26
+
27
+ // ─── Internal $type<> helpers ───────────────────────────────────────────────
28
+ // Annotation types for jsonb columns only. JOB-2 defines the public protocol
29
+ // types; these remain private to this file.
30
+
31
+ type RetryPolicy = {
32
+ attempts: number;
33
+ backoff: 'fixed' | 'exponential';
34
+ baseMs: number;
35
+ nonRetryableErrors?: string[];
36
+ };
37
+
38
+ type JobRunError = {
39
+ message: string;
40
+ stack?: string;
41
+ retryable: boolean;
42
+ attempt: number;
43
+ };
44
+
45
+ // ─── Enums ──────────────────────────────────────────────────────────────────
46
+
47
+ export const jobRunStatusEnum = pgEnum('job_run_status', [
48
+ 'pending',
49
+ 'running',
50
+ 'waiting',
51
+ 'completed',
52
+ 'failed',
53
+ 'timed_out',
54
+ 'canceled',
55
+ ]);
56
+
57
+ // extended in ADR-027: tool_call | llm_call | wait | checkpoint | message
58
+ export const jobStepKindEnum = pgEnum('job_step_kind', ['task']);
59
+
60
+ export const jobStepStatusEnum = pgEnum('job_step_status', [
61
+ 'pending',
62
+ 'running',
63
+ 'completed',
64
+ 'failed',
65
+ 'skipped',
66
+ ]);
67
+
68
+ export const collisionModeEnum = pgEnum('job_collision_mode', [
69
+ 'queue',
70
+ 'reject',
71
+ 'replace',
72
+ ]);
73
+
74
+ export const replayFromEnum = pgEnum('job_replay_from', [
75
+ 'scratch',
76
+ 'last_step',
77
+ 'last_checkpoint',
78
+ ]);
79
+
80
+ export const parentClosePolicyEnum = pgEnum('job_parent_close_policy', [
81
+ 'terminate',
82
+ 'cancel',
83
+ 'abandon',
84
+ ]);
85
+
86
+ // Phase 3 placeholder — see ADR-025
87
+ export const waitKindEnum = pgEnum('job_wait_kind', ['signal']);
88
+
89
+ // Phase 2 may add more sources; requires Atlas migration
90
+ export const triggerSourceEnum = pgEnum('job_trigger_source', [
91
+ 'manual',
92
+ 'schedule',
93
+ 'event',
94
+ 'parent',
95
+ ]);
96
+
97
+ // ─── job ────────────────────────────────────────────────────────────────────
98
+
99
+ export const jobs = pgTable('job', {
100
+ type: text('type').primaryKey(),
101
+ version: integer('version').notNull().default(1),
102
+ pool: text('pool').notNull(),
103
+ scopeEntityType: text('scope_entity_type'),
104
+ retryPolicy: jsonb('retry_policy').notNull().$type<RetryPolicy>(),
105
+ timeoutMs: integer('timeout_ms'),
106
+ concurrencyKeyTemplate: text('concurrency_key_template'),
107
+ collisionMode: collisionModeEnum('collision_mode').notNull().default('queue'),
108
+ dedupeKeyTemplate: text('dedupe_key_template'),
109
+ dedupeWindowMs: integer('dedupe_window_ms'),
110
+ priorityDefault: integer('priority_default').notNull().default(0),
111
+ replayFrom: replayFromEnum('replay_from').notNull().default('last_checkpoint'),
112
+ createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
113
+ updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
114
+ });
115
+
116
+ export type JobDefinitionRow = InferSelectModel<typeof jobs>;
117
+
118
+ // ─── job_run ────────────────────────────────────────────────────────────────
119
+
120
+ export const jobRuns = pgTable(
121
+ 'job_run',
122
+ {
123
+ id: uuid('id').primaryKey().defaultRandom(),
124
+ jobType: text('job_type').notNull().references(() => jobs.type),
125
+ jobVersion: integer('job_version').notNull(),
126
+ parentRunId: uuid('parent_run_id').references((): any => jobRuns.id),
127
+ /**
128
+ * Service generates `id` client-side via randomUUID() and sets
129
+ * root_run_id = id for root runs (single INSERT, no self-FK race).
130
+ */
131
+ rootRunId: uuid('root_run_id').notNull(),
132
+ parentClosePolicy: parentClosePolicyEnum('parent_close_policy')
133
+ .notNull()
134
+ .default('terminate'),
135
+ scopeEntityType: text('scope_entity_type'),
136
+ scopeEntityId: text('scope_entity_id'),
137
+ tenantId: text('tenant_id'), // F9: always emitted (nullable) — runtime enforces on boundary via JOBS_MULTI_TENANT
138
+ tags: jsonb('tags').notNull().default({}).$type<Record<string, string>>(),
139
+ pool: text('pool').notNull(),
140
+ priority: integer('priority').notNull().default(0),
141
+ concurrencyKey: text('concurrency_key'),
142
+ dedupeKey: text('dedupe_key'),
143
+ status: jobRunStatusEnum('status').notNull().default('pending'),
144
+ input: jsonb('input').notNull().$type<Record<string, unknown>>(),
145
+ output: jsonb('output').$type<Record<string, unknown>>(),
146
+ error: jsonb('error').$type<JobRunError>(),
147
+ triggerSource: triggerSourceEnum('trigger_source').notNull(),
148
+ triggerRef: text('trigger_ref'),
149
+ runAt: timestamp('run_at', { withTimezone: true }).notNull().defaultNow(),
150
+ startedAt: timestamp('started_at', { withTimezone: true }),
151
+ finishedAt: timestamp('finished_at', { withTimezone: true }),
152
+ claimedAt: timestamp('claimed_at', { withTimezone: true }),
153
+ attempts: integer('attempts').notNull().default(0),
154
+ // Phase 3 placeholder — see ADR-025
155
+ waitKind: waitKindEnum('wait_kind'),
156
+ // Phase 3 placeholder — see ADR-025
157
+ resumeToken: text('resume_token'),
158
+ // Phase 3 placeholder — see ADR-025
159
+ waitDeadline: timestamp('wait_deadline', { withTimezone: true }),
160
+ createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
161
+ updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
162
+ },
163
+ (t) => ({
164
+ /** Claim query: ORDER BY priority DESC, run_at ASC. */
165
+ idxJobRunClaim: index('idx_job_run_claim').on(t.status, t.pool, t.runAt),
166
+ /** Tree traversal / cascade cancel. */
167
+ idxJobRunRoot: index('idx_job_run_root').on(t.rootRunId),
168
+ /** listForScope query. */
169
+ idxJobRunScope: index('idx_job_run_scope').on(t.scopeEntityType, t.scopeEntityId),
170
+ /** Idempotency collapse — partial index. */
171
+ idxJobRunDedupe: index('idx_job_run_dedupe')
172
+ .on(t.jobType, t.dedupeKey)
173
+ .where(sql`${t.dedupeKey} IS NOT NULL`),
174
+ /** Collision check — partial index. */
175
+ idxJobRunConcurrency: index('idx_job_run_concurrency')
176
+ .on(t.concurrencyKey)
177
+ .where(
178
+ sql`${t.concurrencyKey} IS NOT NULL AND ${t.status} IN ('pending','running')`,
179
+ ),
180
+ }),
181
+ );
182
+
183
+ export type JobRunRow = InferSelectModel<typeof jobRuns>;
184
+
185
+ // ─── job_step ───────────────────────────────────────────────────────────────
186
+
187
+ export const jobSteps = pgTable(
188
+ 'job_step',
189
+ {
190
+ id: uuid('id').primaryKey().defaultRandom(),
191
+ jobRunId: uuid('job_run_id').notNull().references(() => jobRuns.id),
192
+ stepId: text('step_id').notNull(),
193
+ kind: jobStepKindEnum('kind').notNull().default('task'),
194
+ /**
195
+ * Monotonic within run. integer (max ~2B per run) is sufficient —
196
+ * downgraded from ADR-022's bigint; revisit only if a single run
197
+ * ever exceeds 2 billion steps.
198
+ */
199
+ seq: integer('seq').notNull(),
200
+ status: jobStepStatusEnum('status').notNull().default('pending'),
201
+ input: jsonb('input').$type<Record<string, unknown>>(),
202
+ /** Memoised on success for replay. */
203
+ output: jsonb('output').$type<Record<string, unknown>>(),
204
+ error: jsonb('error').$type<JobRunError>(),
205
+ attempts: integer('attempts').notNull().default(0),
206
+ startedAt: timestamp('started_at', { withTimezone: true }),
207
+ finishedAt: timestamp('finished_at', { withTimezone: true }),
208
+ },
209
+ (t) => ({
210
+ /** No duplicate step IDs per run. */
211
+ idxJobStepRunStep: uniqueIndex('idx_job_step_run_step').on(t.jobRunId, t.stepId),
212
+ /** Ordered timeline reads. */
213
+ idxJobStepTimeline: index('idx_job_step_timeline').on(t.jobRunId, t.seq),
214
+ }),
215
+ );
216
+
217
+ export type JobStepRow = InferSelectModel<typeof jobSteps>;
@@ -0,0 +1,10 @@
1
+ // AUTO-GENERATED by @pattern-stack/codegen. Do not edit.
2
+ // Run `codegen entity new --all` to refresh.
3
+
4
+ import { z } from 'zod';
5
+
6
+ export type ScopeEntityType = 'opportunity';
7
+
8
+ export const SCOPE_ENTITY_TYPES = ['opportunity'] as const;
9
+
10
+ export const scopeEntityTypeSchema = z.enum(['opportunity']);
@@ -0,0 +1,120 @@
1
+ // ─── JOB-1: Drizzle schema (tables, enums, row types) ──────────────────────
2
+ export { jobs, jobRuns, jobSteps } from './job-orchestration.schema';
3
+ export type {
4
+ JobDefinitionRow,
5
+ JobRunRow,
6
+ JobStepRow,
7
+ } from './job-orchestration.schema';
8
+ export {
9
+ jobRunStatusEnum,
10
+ jobStepKindEnum,
11
+ jobStepStatusEnum,
12
+ collisionModeEnum,
13
+ replayFromEnum,
14
+ parentClosePolicyEnum,
15
+ waitKindEnum,
16
+ triggerSourceEnum,
17
+ } from './job-orchestration.schema';
18
+
19
+ // ─── JOB-2 + JOB-8: domain tokens ──────────────────────────────────────────
20
+ export {
21
+ JOB_ORCHESTRATOR,
22
+ JOB_RUN_SERVICE,
23
+ JOB_STEP_SERVICE,
24
+ JOBS_MULTI_TENANT,
25
+ } from './jobs-domain.tokens';
26
+
27
+ // ─── JOB-2: orchestrator protocol ──────────────────────────────────────────
28
+ export type {
29
+ IJobOrchestrator,
30
+ StartOptions,
31
+ CancelOptions,
32
+ JobRun,
33
+ JobUpsertEntry,
34
+ JobPoolDef,
35
+ } from './job-orchestrator.protocol';
36
+
37
+ // ─── JOB-2: run-service protocol ───────────────────────────────────────────
38
+ export type {
39
+ IJobRunService,
40
+ ListForScopeOptions,
41
+ CancelForScopeOptions,
42
+ RescheduleForScopeOptions,
43
+ } from './job-run-service.protocol';
44
+
45
+ // ─── JOB-2: step-service protocol ──────────────────────────────────────────
46
+ export type {
47
+ IJobStepService,
48
+ RecordStepInput,
49
+ JobStep,
50
+ } from './job-step-service.protocol';
51
+
52
+ // ─── JOB-2: handler base, decorator, registry, policy types ────────────────
53
+ export {
54
+ ParentClosePolicy,
55
+ JobHandlerBase,
56
+ JobHandler,
57
+ JOB_HANDLER_REGISTRY,
58
+ JOB_HANDLER_METADATA_KEY,
59
+ HandlerRegistry,
60
+ } from './job-handler.base';
61
+ export type {
62
+ RetryPolicy,
63
+ ConcurrencyPolicy,
64
+ DedupePolicy,
65
+ ScopeRef,
66
+ JobHandlerMeta,
67
+ StepOptions,
68
+ SpawnChildOptions,
69
+ JobContext,
70
+ HandlerRegistryEntry,
71
+ } from './job-handler.base';
72
+
73
+ // ─── JOB-3: Drizzle backends + JobWorker ────────────────────────────────
74
+ export { DrizzleJobOrchestrator } from './job-orchestrator.drizzle-backend';
75
+ export { DrizzleJobRunService } from './job-run-service.drizzle-backend';
76
+ export { DrizzleJobStepService } from './job-step-service.drizzle-backend';
77
+ export {
78
+ JobWorker,
79
+ JOB_WORKER_OPTIONS,
80
+ computeBackoff,
81
+ classifyError,
82
+ buildClaimQuery,
83
+ buildStaleSweepQuery,
84
+ } from './job-worker';
85
+ export type { JobWorkerOptions } from './job-worker';
86
+ export {
87
+ JobCollisionError,
88
+ JobNotReplayableError,
89
+ JobTemplateFieldMissingError,
90
+ JobTypeNotFoundError,
91
+ MissingTenantIdError,
92
+ BootValidationError,
93
+ ReservedPoolViolationError,
94
+ } from './jobs-errors';
95
+
96
+ // ─── JOB-4: Memory backends + shared in-memory store ───────────────────────
97
+ export { MemoryJobStore } from './memory-job-store';
98
+ export { MemoryJobOrchestrator } from './job-orchestrator.memory-backend';
99
+ export { MemoryJobRunService } from './job-run-service.memory-backend';
100
+ export { MemoryJobStepService } from './job-step-service.memory-backend';
101
+
102
+ // ─── JOB-5: domain + worker modules + pool config loader ───────────────────
103
+ export {
104
+ JobsDomainModule,
105
+ type JobsDomainModuleOptions,
106
+ type DrizzleBackendExtensions,
107
+ } from './jobs-domain.module';
108
+ export {
109
+ JobWorkerModule,
110
+ JobWorkerOrchestrator,
111
+ type JobWorkerModuleOptions,
112
+ } from './job-worker.module';
113
+ export {
114
+ loadPoolConfig,
115
+ allNonReservedPoolNames,
116
+ FRAMEWORK_POOLS,
117
+ RESERVED_POOL_NAMES,
118
+ type PoolConfig,
119
+ type PoolDefinition,
120
+ } from './pool-config.loader';