effect-jobs 0.1.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (157) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/LICENSE +22 -0
  3. package/README.md +142 -0
  4. package/dist/Job.d.ts +33 -0
  5. package/dist/Job.d.ts.map +1 -0
  6. package/dist/Job.js +66 -0
  7. package/dist/Job.js.map +1 -0
  8. package/dist/JobConsumer.d.ts +34 -0
  9. package/dist/JobConsumer.d.ts.map +1 -0
  10. package/dist/JobConsumer.js +28 -0
  11. package/dist/JobConsumer.js.map +1 -0
  12. package/dist/JobContract.d.ts +128 -0
  13. package/dist/JobContract.d.ts.map +1 -0
  14. package/dist/JobContract.js +15 -0
  15. package/dist/JobContract.js.map +1 -0
  16. package/dist/JobFailure.d.ts +51 -0
  17. package/dist/JobFailure.d.ts.map +1 -0
  18. package/dist/JobFailure.js +34 -0
  19. package/dist/JobFailure.js.map +1 -0
  20. package/dist/JobId.d.ts +5 -0
  21. package/dist/JobId.d.ts.map +1 -0
  22. package/dist/JobId.js +4 -0
  23. package/dist/JobId.js.map +1 -0
  24. package/dist/JobIdentity.d.ts +54 -0
  25. package/dist/JobIdentity.d.ts.map +1 -0
  26. package/dist/JobIdentity.js +82 -0
  27. package/dist/JobIdentity.js.map +1 -0
  28. package/dist/JobLifecycle.d.ts +173 -0
  29. package/dist/JobLifecycle.d.ts.map +1 -0
  30. package/dist/JobLifecycle.js +234 -0
  31. package/dist/JobLifecycle.js.map +1 -0
  32. package/dist/JobPayload.d.ts +18 -0
  33. package/dist/JobPayload.d.ts.map +1 -0
  34. package/dist/JobPayload.js +10 -0
  35. package/dist/JobPayload.js.map +1 -0
  36. package/dist/JobPayloadCodec.d.ts +14 -0
  37. package/dist/JobPayloadCodec.d.ts.map +1 -0
  38. package/dist/JobPayloadCodec.js +78 -0
  39. package/dist/JobPayloadCodec.js.map +1 -0
  40. package/dist/JobPolicy.d.ts +50 -0
  41. package/dist/JobPolicy.d.ts.map +1 -0
  42. package/dist/JobPolicy.js +85 -0
  43. package/dist/JobPolicy.js.map +1 -0
  44. package/dist/JobProducer.d.ts +14 -0
  45. package/dist/JobProducer.d.ts.map +1 -0
  46. package/dist/JobProducer.js +23 -0
  47. package/dist/JobProducer.js.map +1 -0
  48. package/dist/JobQueue.d.ts +16 -0
  49. package/dist/JobQueue.d.ts.map +1 -0
  50. package/dist/JobQueue.js +16 -0
  51. package/dist/JobQueue.js.map +1 -0
  52. package/dist/JobRegistry.d.ts +37 -0
  53. package/dist/JobRegistry.d.ts.map +1 -0
  54. package/dist/JobRegistry.js +47 -0
  55. package/dist/JobRegistry.js.map +1 -0
  56. package/dist/JobStore.d.ts +348 -0
  57. package/dist/JobStore.d.ts.map +1 -0
  58. package/dist/JobStore.js +19 -0
  59. package/dist/JobStore.js.map +1 -0
  60. package/dist/JobTelemetry.d.ts +7 -0
  61. package/dist/JobTelemetry.d.ts.map +1 -0
  62. package/dist/JobTelemetry.js +61 -0
  63. package/dist/JobTelemetry.js.map +1 -0
  64. package/dist/JobTransaction.d.ts +24 -0
  65. package/dist/JobTransaction.d.ts.map +1 -0
  66. package/dist/JobTransaction.js +7 -0
  67. package/dist/JobTransaction.js.map +1 -0
  68. package/dist/JobWorker.d.ts +24 -0
  69. package/dist/JobWorker.d.ts.map +1 -0
  70. package/dist/JobWorker.js +155 -0
  71. package/dist/JobWorker.js.map +1 -0
  72. package/dist/JobWorkerRuntime.d.ts +18 -0
  73. package/dist/JobWorkerRuntime.d.ts.map +1 -0
  74. package/dist/JobWorkerRuntime.js +5 -0
  75. package/dist/JobWorkerRuntime.js.map +1 -0
  76. package/dist/PollingJobWorker.d.ts +9 -0
  77. package/dist/PollingJobWorker.d.ts.map +1 -0
  78. package/dist/PollingJobWorker.js +54 -0
  79. package/dist/PollingJobWorker.js.map +1 -0
  80. package/dist/PostgreSqlDrizzleSchema.d.ts +432 -0
  81. package/dist/PostgreSqlDrizzleSchema.d.ts.map +1 -0
  82. package/dist/PostgreSqlDrizzleSchema.js +72 -0
  83. package/dist/PostgreSqlDrizzleSchema.js.map +1 -0
  84. package/dist/PostgreSqlJobs.d.ts +25 -0
  85. package/dist/PostgreSqlJobs.d.ts.map +1 -0
  86. package/dist/PostgreSqlJobs.js +38 -0
  87. package/dist/PostgreSqlJobs.js.map +1 -0
  88. package/dist/PostgreSqlSchema.d.ts +30 -0
  89. package/dist/PostgreSqlSchema.d.ts.map +1 -0
  90. package/dist/PostgreSqlSchema.js +63 -0
  91. package/dist/PostgreSqlSchema.js.map +1 -0
  92. package/dist/PostgreSqlTransaction.d.ts +42 -0
  93. package/dist/PostgreSqlTransaction.d.ts.map +1 -0
  94. package/dist/PostgreSqlTransaction.js +9 -0
  95. package/dist/PostgreSqlTransaction.js.map +1 -0
  96. package/dist/index.d.ts +7 -0
  97. package/dist/index.d.ts.map +1 -0
  98. package/dist/index.js +7 -0
  99. package/dist/index.js.map +1 -0
  100. package/dist/internal/JobTransaction.d.ts +16 -0
  101. package/dist/internal/JobTransaction.d.ts.map +1 -0
  102. package/dist/internal/JobTransaction.js +21 -0
  103. package/dist/internal/JobTransaction.js.map +1 -0
  104. package/dist/internal/codec/CanonicalJson.d.ts +9 -0
  105. package/dist/internal/codec/CanonicalJson.d.ts.map +1 -0
  106. package/dist/internal/codec/CanonicalJson.js +184 -0
  107. package/dist/internal/codec/CanonicalJson.js.map +1 -0
  108. package/dist/internal/codec/ProtectedProjection.d.ts +12 -0
  109. package/dist/internal/codec/ProtectedProjection.d.ts.map +1 -0
  110. package/dist/internal/codec/ProtectedProjection.js +121 -0
  111. package/dist/internal/codec/ProtectedProjection.js.map +1 -0
  112. package/dist/internal/lifecycle/Artifacts.d.ts +6 -0
  113. package/dist/internal/lifecycle/Artifacts.d.ts.map +1 -0
  114. package/dist/internal/lifecycle/Artifacts.js +49 -0
  115. package/dist/internal/lifecycle/Artifacts.js.map +1 -0
  116. package/dist/internal/lifecycle/Reconciliation.d.ts +82 -0
  117. package/dist/internal/lifecycle/Reconciliation.d.ts.map +1 -0
  118. package/dist/internal/lifecycle/Reconciliation.js +98 -0
  119. package/dist/internal/lifecycle/Reconciliation.js.map +1 -0
  120. package/dist/internal/postgresql/Producer.d.ts +14 -0
  121. package/dist/internal/postgresql/Producer.d.ts.map +1 -0
  122. package/dist/internal/postgresql/Producer.js +55 -0
  123. package/dist/internal/postgresql/Producer.js.map +1 -0
  124. package/dist/internal/postgresql/Readiness.d.ts +5 -0
  125. package/dist/internal/postgresql/Readiness.d.ts.map +1 -0
  126. package/dist/internal/postgresql/Readiness.js +53 -0
  127. package/dist/internal/postgresql/Readiness.js.map +1 -0
  128. package/dist/internal/postgresql/Rows.d.ts +45 -0
  129. package/dist/internal/postgresql/Rows.d.ts.map +1 -0
  130. package/dist/internal/postgresql/Rows.js +75 -0
  131. package/dist/internal/postgresql/Rows.js.map +1 -0
  132. package/dist/internal/postgresql/Store.d.ts +5 -0
  133. package/dist/internal/postgresql/Store.d.ts.map +1 -0
  134. package/dist/internal/postgresql/Store.js +211 -0
  135. package/dist/internal/postgresql/Store.js.map +1 -0
  136. package/dist/internal/worker/Capability.d.ts +106 -0
  137. package/dist/internal/worker/Capability.d.ts.map +1 -0
  138. package/dist/internal/worker/Capability.js +11 -0
  139. package/dist/internal/worker/Capability.js.map +1 -0
  140. package/dist/internal/worker/Drain.d.ts +22 -0
  141. package/dist/internal/worker/Drain.d.ts.map +1 -0
  142. package/dist/internal/worker/Drain.js +59 -0
  143. package/dist/internal/worker/Drain.js.map +1 -0
  144. package/dist/internal/worker/Execution.d.ts +7 -0
  145. package/dist/internal/worker/Execution.d.ts.map +1 -0
  146. package/dist/internal/worker/Execution.js +125 -0
  147. package/dist/internal/worker/Execution.js.map +1 -0
  148. package/dist/internal/worker/Results.d.ts +108 -0
  149. package/dist/internal/worker/Results.d.ts.map +1 -0
  150. package/dist/internal/worker/Results.js +3 -0
  151. package/dist/internal/worker/Results.js.map +1 -0
  152. package/package.json +169 -0
  153. package/specs/architecture-decisions.md +841 -0
  154. package/specs/core-contract.md +323 -0
  155. package/specs/drizzle-compatibility.md +167 -0
  156. package/specs/postgresql-backend.md +291 -0
  157. package/specs/release-qualification.md +107 -0
@@ -0,0 +1,323 @@
1
+ # Shared core contracts
2
+
3
+ Implemented serial prerequisite for UPVE-1031, against accepted D1–D8 at
4
+ `0e10e8a5a0b21101c00ea064e87ac641b73743c3`. The earlier
5
+ `core-contract-proposal.md` is historical, not a merge input. This document freezes
6
+ the contracts consumed by definitions/producer, lifecycle/store and codec units.
7
+ It does not implement or qualify their runtimes or activate those tasks.
8
+
9
+ ## Modules and import directions
10
+
11
+ The package exports these exact public subpaths (ESM and declarations):
12
+
13
+ ```text
14
+ effect-jobs/JobId
15
+ effect-jobs/JobIdentity
16
+ effect-jobs/JobPolicy
17
+ effect-jobs/JobFailure
18
+ effect-jobs/JobContract
19
+ effect-jobs/JobTransaction
20
+ ```
21
+
22
+ The root remains empty, not an all-modules barrel. No subpath imports auth,
23
+ PostgreSQL, Drizzle, environment configuration, pools, workers or registry Layers.
24
+ No import starts work. The backend must not enter core through a default Layer.
25
+
26
+ ```text
27
+ Effect <- JobId / JobIdentity / JobPolicy
28
+ JobPolicy <- JobFailure
29
+ JobId + JobIdentity + JobPolicy <- JobContract
30
+ JobContract <- JobTransaction (public opaque capability and errors)
31
+ JobContract + JobTransaction <- internal/JobTransaction (lifetime mechanics)
32
+ JobContract + JobIdentity + JobPolicy + internal/JobTransaction <- later Job/JobProducer
33
+ JobContract <- later JobPayload/JobPayloadCodec
34
+ JobContract + JobFailure + JobPolicy <- later JobStore/internal/JobLifecycle
35
+ shared contracts + qualified active application handle <- later backend bridges
36
+ ```
37
+
38
+ `JobTransaction` has only a **type-only** reference to the internal symbol; the
39
+ internal leaf imports its public errors at runtime. There is no runtime cycle.
40
+ Only the later definition implementation and qualified backend bridges consume
41
+ the internal leaf. `internal/*` is not a package export or custom-adapter API.
42
+ The transaction declaration references the internal symbol declaration; that file
43
+ must remain in the build artifact even though package export resolution denies
44
+ runtime deep imports. Do not duplicate symbols, brands or errors in later units.
45
+
46
+ ## Identity and synchronous declaration validation
47
+
48
+ `JobId.JobId` is a nonempty string Schema branded `effect-jobs/JobId`; the Schema
49
+ and its inferred type share the name. IDs are library-assigned; their generation
50
+ and uniqueness are backend obligations. No UUID algorithm or caller ID override
51
+ is selected here. Schema decoding brands persisted IDs; it is not production.
52
+
53
+ `JobIdentity` exports Schemas `QueueName`, `JobKind`, `JobVersion`, `OperationName`,
54
+ `SlotName`, `OperationId`, `CatalogIdentity` and `ProducerIdentity`. The last two
55
+ also have literal-preserving structural interfaces:
56
+
57
+ ```ts
58
+ interface CatalogIdentity<
59
+ Q extends string = string,
60
+ K extends string = string,
61
+ V extends number = number
62
+ > {
63
+ readonly queue: Q
64
+ readonly kind: K
65
+ readonly version: V
66
+ }
67
+ interface ProducerIdentity<O extends string = string, S extends string = string> {
68
+ readonly operation: O
69
+ readonly operationId: string
70
+ readonly slot: S
71
+ }
72
+ ```
73
+
74
+ Queue names are 1–64 characters; kind/operation/slot names 1–128, all ASCII
75
+ `^[a-z][a-z0-9.-]*$`. Version is integer 1–32,767. These preserve source bounds.
76
+ Operation IDs are valid scalar strings without NUL, 1–256 UTF-8 bytes, with exact
77
+ equality: no trimming, normalization or case folding. Application-branded strings
78
+ are accepted. Durable collation/equality must preserve this behavior.
79
+
80
+ `validateOperationId(value: string): void` throws `InvalidOperationId` with only
81
+ `rule: "unicode-scalars" | "no-nul" | "utf8-length"` and length-rule
82
+ `byteLength`. It never includes the supplied ID. Untrusted non-string values use
83
+ Schema validation at the consumer boundary; do not export raw Schema diagnostics
84
+ containing sensitive inputs as enqueue errors.
85
+
86
+ `validateProducerDeclaration({ operation: string, slots: ReadonlyArray<string> }):
87
+ void` validates names, nonempty slots and uniqueness synchronously. It throws
88
+ `JobDeclarationError` containing `field` (`operation`, `slots`, or `slots[index]`),
89
+ the invalid declaration `value` where applicable, and actionable `expected` text.
90
+ The producer unit **must call this before returning its immutable declaration**;
91
+ there is no producer implementation here. It must preserve inferred operation and
92
+ slot literals, reject undeclared slots, and copy/freeze its slot array. No global
93
+ registry or mutable builder is needed.
94
+
95
+ ## Complete immutable policy
96
+
97
+ Use `import * as JobPolicy from "effect-jobs/JobPolicy"` and
98
+ `JobPolicy.make(options?: JobPolicyOptions): JobPolicy.JobPolicy`.
99
+ Every public input field is optional:
100
+
101
+ | Field | Input | Default |
102
+ | ------------------ | ------------------------------------ | --------------- |
103
+ | leaseDuration | Effect Duration | 90 seconds |
104
+ | attemptTimeout | Effect Duration | 30 seconds |
105
+ | retryDelay | Effect Duration | fixed 5 seconds |
106
+ | maxAttempts | integer | 3 |
107
+ | maxStalledCount | integer | 1 |
108
+ | completedRetention | Effect Duration or Duration.infinity | 90 days |
109
+ | deadRetention | Effect Duration or Duration.infinity | infinity |
110
+
111
+ No raw millisecond input or hand-written `_tag` is required publicly. `make` is a
112
+ synchronous configuration factory and throws `JobPolicyConfigurationError` with
113
+ the exact `field` and `expected` constraint. Counters retain source limits:
114
+ maxAttempts 1–100, maxStalledCount 0–100. Finite durations must resolve to integer
115
+ milliseconds 1–253,402,300,799,999; no rounding, truncation or clamping. Zero,
116
+ negative, fractional-millisecond and negative-infinity retention are rejected.
117
+ Only positive infinity is Forever, and only for retention.
118
+
119
+ `attemptTimeoutMillis < leaseDurationMillis` is mandatory. Response budget is
120
+ backend configuration, not a policy field; later backend qualification must
121
+ ensure the stored lease can support timeout plus its budget.
122
+
123
+ The returned object and every nested configuration object are frozen:
124
+
125
+ ```ts
126
+ interface JobPolicy {
127
+ readonly leaseDurationMillis: number
128
+ readonly attemptTimeoutMillis: number
129
+ readonly maxAttempts: number
130
+ readonly maxStalledCount: number
131
+ readonly retrySchedule: { readonly _tag: "FixedDelayV1"; readonly delayMillis: number }
132
+ readonly completedRetention: JobRetention
133
+ readonly deadRetention: JobRetention
134
+ }
135
+ type JobRetention =
136
+ { readonly _tag: "Forever" } | { readonly _tag: "Duration"; readonly millis: number }
137
+ ```
138
+
139
+ `PersistedJobPolicy`, `FixedDelayV1`, `JobRetention`, `PositiveMillis` and
140
+ `EpochMillis` are runtime Schemas. Persisted decode requires the **complete**
141
+ resolved configuration and the cross-field constraint; it never fills current
142
+ defaults into old rows. The layout is the V1 policy representation; concrete
143
+ backend storage/version discriminator belongs to its schema task. Updated
144
+ deployment defaults cannot rewrite retained rows.
145
+
146
+ `EpochMillis` preserves the source positive integer bound 1–253,402,300,799,999
147
+ for availableAt/notAfter; accepting epoch zero would be a policy change, not
148
+ silently inferred from the superseded proposal. Ordinary past positive timestamps
149
+ are valid. The backend must validate computed timestamp additions against the
150
+ same bound rather than overflowing/clamping. No database-time arithmetic is
151
+ implemented in this unit.
152
+
153
+ ## Handler and enqueue contracts
154
+
155
+ `JobContract.HandlerInput<Payload>` is `{ readonly payload: Payload; readonly
156
+ context: HandlerContext }`. Context contains exactly jobId, catalog, producer and
157
+ attemptNumber, all readonly. No client, lease token, transaction, finalizer or
158
+ ambient handler dependency service is exposed.
159
+
160
+ attemptNumber is `attemptsMade + 1`. Claim/release do not increment attempts;
161
+ confirmed fenced Complete/Retry/Dead/Isolate increments exactly once. Unknown
162
+ leaves attempts unchanged; stalled recovery has its own counter. Consequently
163
+ attempt numbers may repeat and are not external idempotency keys or invocation
164
+ counts. Enforcing those transitions belongs to the lifecycle unit.
165
+
166
+ `JobFailure` is a Schema/type union. `JobFailures` supplies checked frozen factories:
167
+
168
+ ```ts
169
+ JobFailures.Retry({ code: string, notAfter?: number })
170
+ JobFailures.Dead({ code: string })
171
+ JobFailures.Isolate({ code: string })
172
+ JobFailures.OutcomeUnknown({ code: string })
173
+ ```
174
+
175
+ Codes are 1–64 characters matching `^[a-z0-9][a-z0-9_-]*$`; cutoffs use
176
+ EpochMillis. Invalid factories throw `InvalidJobFailure { field: "code" |
177
+ "notAfter" }`, without recording input text. Domain errors are mapped by ordinary
178
+ Effect composition. Unknown is not a seventh lifecycle state; mixed causes,
179
+ defects, interruption or timeout never imply known-safe Retry. Runtime boundaries
180
+ must validate even structurally constructed failure values with `JobFailure`.
181
+
182
+ `EnqueueInput<Payload>` has required producer, decoded payload, resolved policy
183
+ and optional availableAt. No job ID is supplied. `EnqueueResults` is a
184
+ `Data.taggedEnum` factory for `Inserted { jobId } | AlreadyPresent { jobId }`.
185
+ Results remain provisional until application commit.
186
+
187
+ For matching producer tuples, duplicate equality compares catalog and validated
188
+ canonical semantic payload/fingerprints only. It excludes generated IDs, policy,
189
+ initial/current availability, ciphertext randomness and lifecycle state. Matching
190
+ duplicates return the original ID and retain first policy/availability. Omitted
191
+ availability uses DB time only on first insertion. Conflicts are typed
192
+ `JobIntegrityConflict` with no raw payload/identifiers; they cannot be swallowed
193
+ to commit partial application writes.
194
+
195
+ `InvalidJobInput { field }` and `JobPayloadCodecError { reason }` are bounded
196
+ operation-owned errors. `JobTransaction.JobEnqueueError` unites those with
197
+ JobIntegrityConflict and JobsTransactionClosed. Provider errors stay in the
198
+ backend's explicit E channel, not raw public diagnostic strings.
199
+
200
+ ## Codec and prepared-payload seam
201
+
202
+ `EncodedJobPayload` contains `formatVersion: 1`, `payloadBytes: Uint8Array` and
203
+ `semanticProjectionBytes: Uint8Array`. `PreparedJob` contains catalog, producer,
204
+ complete policy, optional availableAt and encoded. It has no generated candidate
205
+ ID. Readonly byte properties **do not make Uint8Array immutable**: the codec owns
206
+ its output buffers, and a receiver must copy before retaining them. Internal
207
+ forwarding deliberately does not implement serialization or pretend to validate
208
+ fake byte fixtures.
209
+
210
+ Shared constants: payload and projection each 65,536 bytes; maximum depth 32;
211
+ maximum protected subtrees 16; maximum UTF-8 schema path 256 bytes; formatVersion 1.
212
+ The codec implements exported function-type contracts:
213
+
214
+ ```ts
215
+ type JobPayloadEncoder = <S extends Schema.Top>(
216
+ schema: S,
217
+ payload: S["Type"]
218
+ ) => Effect.Effect<EncodedJobPayload, JobPayloadCodecError, S["EncodingServices"]>
219
+ type JobPayloadDecoder = <S extends Schema.Top>(
220
+ schema: S,
221
+ encoded: EncodedJobPayload
222
+ ) => Effect.Effect<S["Type"], JobPayloadCodecError, S["DecodingServices"]>
223
+ ```
224
+
225
+ The later codec exposes matching encode/decode functions; definitions must carry
226
+ encoding services into enqueue and capture decoding/handler services through
227
+ ordinary Layers. No `EncodingServices: never` limitation or hidden provisioning.
228
+
229
+ Protected markers describe application-owned envelope/fingerprint Schemas.
230
+ Require an **own** fingerprint with validated canonical JSON representation;
231
+ semantic projection excludes the remainder of that subtree. Marking proves no
232
+ encryption/authenticity. No projection callbacks, mandated crypto envelope, keys,
233
+ automatic encryption or generic AAD are introduced here. Full marker/codec runtime
234
+ and own-field checks belong to UPVE-857, not a parallel stub in contracts.
235
+
236
+ Codec qualification must cover finite fractional JSON numbers, nonfinite rejects,
237
+ locale-independent object order, meaningful array order, supported structs/arrays/
238
+ optionals/unions and explicit unsupported transformed/recursive mapping. Decode
239
+ validates format, bounds, UTF-8, schema and projection consistency before dispatch.
240
+ Canonical projection comparison is not an authenticity proof.
241
+
242
+ ## Scoped explicit transaction seam
243
+
244
+ `JobsTransaction<E = never, R = never>` is an opaque callback capability. It has
245
+ no public constructor, arbitrary insert callback, query client, transaction
246
+ runner, savepoint, commit, rollback or replay. Its symbol is internal only.
247
+
248
+ The internal `withJoinedTransaction(insertOrCompare, body)` is a lifetime helper
249
+ for **qualified backend callbacks**, not a generic public extension point:
250
+
251
+ ```ts
252
+ withJoinedTransaction<A, E, R, BackendError, BackendRequirements>(
253
+ insertOrCompare: (prepared: PreparedJob) =>
254
+ Effect.Effect<EnqueueResult, BackendError | JobIntegrityConflict, BackendRequirements>,
255
+ body: (tx: JobsTransaction<BackendError, BackendRequirements>) => Effect.Effect<A, E, R>
256
+ ): Effect.Effect<A, E, R>
257
+
258
+ insertPrepared<E, R>(tx: JobsTransaction<E, R>, prepared: PreparedJob):
259
+ Effect.Effect<EnqueueResult, E | JobIntegrityConflict | JobsTransactionClosed, R>
260
+ ```
261
+
262
+ It allocates a fresh frozen capability per Effect execution, scopes the callback
263
+ and invalidates it at callback exit on success, typed failure, defect, synchronous
264
+ throw or interruption. Deferred/escaped enqueue Effects check liveness when run,
265
+ before invoking the backend. Closed failure carries reason `callback-exited` and
266
+ an actionable message directing enqueue into the joinTransaction callback.
267
+ Callback-scoped fibers are scoped normally; unscoped escaping fibers and concurrent
268
+ transaction operations are not supported. Branding/lifetime checks are atomicity
269
+ correctness, not a sandbox against trusted application code or casts.
270
+
271
+ The backend must bind the supplied exact active connection/source. The helper
272
+ cannot prove connection provenance, insert correctness, rollback or durable
273
+ atomicity, and these tests do not claim that it can. Custom adapters need a later
274
+ explicit qualified backend extension contract, not a public re-export of this
275
+ helper. No Effect SQL/Drizzle/pg compatibility claim exists yet.
276
+
277
+ `JobTransactionsService<ManagerError, ManagerRequirements = never>` is the explicit
278
+ configured manager integration contract, not an implementation or Context tag:
279
+
280
+ ```ts
281
+ withTransaction<A, E, R>(
282
+ body: (tx: JobsTransaction<ManagerError>) => Effect.Effect<A, E, R>
283
+ ): Effect.Effect<A, E | ManagerError, R | ManagerRequirements>
284
+ ```
285
+
286
+ A qualified integration delegates join-or-establish to the application's manager;
287
+ it must not instantiate a second engine/pool. Explicit backend join must never
288
+ open a new connection/transaction/savepoint, or independently commit/replay.
289
+ Application owner retains whole-operation commit knowledge and replay authority.
290
+
291
+ ## Qualification and ownership
292
+
293
+ Tests under `tests/core` exercise identity diagnostics, defaults and policy bounds,
294
+ strict timeout edge, failure bounds, forwarding and capability lifetimes.
295
+ `tests/types/contracts.ts` is included by the existing strict compiler gate,
296
+ using positive assertions, checked `@ts-expect-error` cases and type-level
297
+ service/error-negative assertions. Tests cover distinct Schema encode/decode
298
+ requirements against real Effect 4.0.0 declarations.
299
+
300
+ Source reference inspected read-only at auth revision
301
+ `5c2e2b81c1aabb102d71894ecb6977611028b78a`: JobQueue, AuthJobs, AuthJobData and
302
+ JobPayloadCodec bounds. Auth remains independent and untouched.
303
+
304
+ This serial unit owns these six public contract modules, their internal
305
+ transaction leaf, tests/core and tests/types/contracts, this specification and
306
+ required package/root export integration. Later units must not edit shared
307
+ contracts, package/lock/config or exports concurrently; request serial changes.
308
+ No dependency/toolchain upgrade, worker, store, producer/registry, full codec,
309
+ generic crypto, migration or backend is included.
310
+
311
+ The current repository has no tracked `.github` workflow or extra gate scripts.
312
+ The actual local CI-equivalent gate is package `verify`: check, lint, whole-tree
313
+ format check, build and tests. Harness-generated untracked files are not owned:
314
+ if whole-tree format fails on them, report that failure and independently check
315
+ tracked/owned files without modifying runtime files. Exact command results,
316
+ tool versions, read manifest and immutable base/head/tree are delivered in the
317
+ issue report; this document does not invent successful future receipts.
318
+
319
+ No PG or packed worker/backend qualification, database operation, push, PR,
320
+ publication, runtime modification or merge is authorized. Temporal runtime edges
321
+ (due `<=`, owned `>`, expiry `<=`, usable lease `>= timeout + budget`, retry cutoff
322
+ `>= notAfter`, one DB-time snapshot) remain accepted obligations of later units;
323
+ contract tests qualify only the policy inequality, not those unimplemented writes.
@@ -0,0 +1,167 @@
1
+ # Effect-native Drizzle compatibility
2
+
3
+ This qualification targets exactly `drizzle-orm@1.0.0-rc.5-169397b`,
4
+ `effect@4.0.0` and the compatible native peer `@effect/sql-pg@4.0.0`, on
5
+ Node 24.15.0, Bun 1.4.2 and PostgreSQL 16.15. The owner accepted this dependency
6
+ direction in UPVE-1032; UPVE-1165 owns its isolated compatibility qualification.
7
+ The coordinator's prepared package/lock changes are part of the tested inputs.
8
+ The existing production schema and backend need no runtime changes for this pin.
9
+
10
+ ## Actual driver and application bridge
11
+
12
+ `drizzle-orm/effect-postgres.makeWithDefaults` requires the native `PgClient`
13
+ service. It creates a session using that client; queries are Effects, executed
14
+ through `client.unsafe(...).values`, `.withoutTransform` or `.raw`. The session
15
+ wraps a transaction callback in `client.withTransaction`. A nested Drizzle
16
+ transaction delegates to the session again: native Effect SQL emits a savepoint.
17
+ The executable negative control confirms this on the real server.
18
+
19
+ The qualification application registrar captures the exact connection published
20
+ by **that client's** `transactionService`, inside its owning callback. A private
21
+ WeakMap binds an opaque handle to source object, connection and active lifetime.
22
+ `PostgreSqlJobs.joinTransaction` validates that registration and executes through
23
+ the captured connection. It neither borrows nor calls another native transaction
24
+ wrapper. A separate registrar around the same client is foreign. A Drizzle
25
+ transaction object or structural query object alone is unqualified.
26
+
27
+ Native raw int8 results are bigint. The application query adapter converts those
28
+ top-level values to exact decimal strings, preserving the existing backend row
29
+ contract and its bounds validation. It does not convert arbitrary int8 directly
30
+ to potentially rounded JavaScript numbers. Drizzle's own query mapper handles
31
+ the declared number-mode bigint columns. The existing custom bytea codec accepts
32
+ the native Uint8Array result and copies it; its Buffer parameter encoding and
33
+ binary bytes including zero and values above 127 round-trip on both runtimes.
34
+
35
+ Native SQL control failures can occur as defects because Effect SQL uses `orDie`
36
+ at the control boundary. Native COMMIT checks its actual command tag and rejects
37
+ ROLLBACK. The fixture sanitizes recognized SQL provider reasons to bounded
38
+ `PostgreSqlFailure`, conservatively Unknown for controls, and recognized Drizzle
39
+ query failures to NotCommitted. Caller errors, defects, interruption and other
40
+ reasons in mixed Causes remain caller-owned. It performs no retry or independent
41
+ receipt reconciliation. The outer application manager owns durable commit.
42
+
43
+ This is a concrete qualification application, **not a shipped universal native
44
+ Drizzle adapter**. Applications must supply their actual registered capability
45
+ and the complete documented application adapter contract. The probe does not
46
+ qualify auth's executor, arbitrary native SQL configurations, other native driver
47
+ versions, independent foreign unregistered transaction managers, concurrent
48
+ operations on one connection, or exhaustive native control/network fault recovery.
49
+ It adds no public constructor or changes to the core capability, lifecycle,
50
+ worker, codecs, migrations, pool ownership or accepted D1–D8 decisions.
51
+
52
+ ## Executable qualification
53
+
54
+ From the repository, with the private resource directory in
55
+ `D6_RESOURCE_DIRECTORY` and the pinned runtimes on PATH:
56
+
57
+ ```sh
58
+ timeout 60s bun install --frozen-lockfile
59
+ # Run each fixture install in its directory, also with --frozen-lockfile.
60
+ timeout 300s bun run build
61
+ export EFFECT_JOBS_ARCHIVE=/path/to/immutable-versioned-effect-jobs.tgz
62
+ timeout 300s node tests/qualification/drizzle-native/Run.mjs
63
+ timeout 600s bun run verify
64
+ timeout 300s bun run check:postgresql
65
+ timeout 300s bun run test:postgresql:node
66
+ timeout 300s bun run test:postgresql:bun
67
+ timeout 600s bun run qualify:postgresql:installed
68
+ ```
69
+
70
+ The release runner consumes the immutable `EFFECT_JOBS_ARCHIVE`, creates a fresh
71
+ independent consumer, and installs that same archive with npm's normal peer resolver alongside
72
+ the exact pins. It uses no force or legacy-peer-deps. It retains a lockfile,
73
+ archive hash, root manifest hash, logs, consumer declarations and sanitized
74
+ runtime receipts. Each child command is foreground and bounded to 60 seconds.
75
+ It compiles the `.mts` fixture inside that consumer, outside the repository's
76
+ core-only TypeScript include set; no native optional peer becomes a root
77
+ development requirement. The runner prepares an ignored fixture-local dependency
78
+ link for subsequent repository lint in a fresh checkout. All public exports are typechecked and imported on
79
+ Node and Bun. Existing installed qualification separately proves imports without
80
+ pg/Drizzle peers and strict non-Drizzle backend declarations.
81
+
82
+ The 17 native assertions per runtime cover:
83
+
84
+ - Native Drizzle domain + production enqueue/job/payload + receipt atomic commit.
85
+ - Duplicate equality, original durable evidence and caught integrity conflict
86
+ causing actual outer COMMIT rejection, with one callback invocation.
87
+ - Caller Context requirements and custom binary bytea read/write round trips.
88
+ - Real CHECK failures at each of the four insertion boundaries, with zero
89
+ remaining writes and sanitized provider failures.
90
+ - Matching physical backend PID, zero extra joined control statements, inner
91
+ success invisible to an independent observer and outer rejection rollback.
92
+ - A real nested Drizzle savepoint as the negative control.
93
+ - Private registration, foreign source, inactive handles and owned-operation
94
+ rejection inside an active native transaction.
95
+ - Escaped capability rejection after success/failure/defect/interruption and
96
+ interruption after all four writes rolling back the complete operation.
97
+
98
+ The original compatibility source PostgreSQL matrix passed 27 tests per runtime;
99
+ installed production qualification passed 25 real-PG tests per runtime. The alpha
100
+ release adds a public worker pipeline and records its larger final counts separately. The root test gate passes
101
+ 388 tests with 47 optional PG tests skipped when it has no PG environment; those
102
+ skips are NotTested in that invocation. The explicit source and installed gates
103
+ above supply the required real-PG evidence. Historical D6 receipts remain tied to
104
+ their original snapshots and were not rerun for confirmation.
105
+
106
+ ## Declaration limits and reproducibility
107
+
108
+ Consumer declarations pass with `skipLibCheck: true`, matching the repository's
109
+ existing Drizzle setting. The runner also executes the full upstream declaration
110
+ check with `skipLibCheck: false` and retains its **failure**, rather than claiming
111
+ complete declaration compatibility. In this version Drizzle still imports the
112
+ removed `effect/unstable/sql/SqlError` path; policy/role declarations also fail
113
+ exact optional property checks. The fixture restores only the transaction
114
+ function's error declaration to the actual pinned native `SqlError` at its
115
+ explicit typed invocation boundary. It does not patch installed dependencies or
116
+ hide that full upstream checking failed. Intentionally version-pinned unstable
117
+ SQL API warnings are documented and disabled only in these two qualification
118
+ modules; any/unknown Effect-channel diagnostics remain enabled.
119
+
120
+ The initial fresh-worktree `verify` failed lint before build because an existing
121
+ portable codec smoke imports absent `dist` declarations. Building first resolves
122
+ that bootstrap issue; the subsequent complete `verify` passes. No shared test,
123
+ manifest, root registrar or lint policy was changed to make it pass.
124
+
125
+ The fixtures verify the exact task-owned container and deadline before use.
126
+ Native objects use only the `drizzle_native` schema and `drizzle_native_runtime`
127
+ role in `effect_jobs_d6`; application setup owns DDL and runtime uses its separate
128
+ login. Native client scopes and observer/admin pools close in finally, and both
129
+ runtime receipts confirm zero remaining fixture schemas and roles. Existing
130
+ backend fixtures run serially. Container lifecycle stays with the coordinator.
131
+ Credentials are read only in-process and are absent from argv, Git and receipts.
132
+
133
+ No unresolved architecture decision or additional package dependency is needed
134
+ for the qualified schema and producer path. The coordinator may register the
135
+ new runner as a shared CI/package gate; that registry is outside author ownership.
136
+ Publication, deployment, push, PR, merge and review dispatch remain outside this
137
+ author's scope. Final exact Git and artifact receipts accompany the issue handoff.
138
+
139
+ ## Documentation read manifest
140
+
141
+ All 16 recursive ai-docs files were read before implementation:
142
+
143
+ - `ai-docs/README.md`
144
+ - `ai-docs/tsconfig.json`
145
+ - `ai-docs/src/index.md`
146
+ - `ai-docs/src/01_services/10_services-and-layers.ts`
147
+ - `ai-docs/src/01_services/20_module-consumption.ts`
148
+ - `ai-docs/src/01_services/30_layer-composition.ts`
149
+ - `ai-docs/src/02_modeling/10_schema-tagged-match.ts`
150
+ - `ai-docs/src/03_errors/10_errors-and-causes.ts`
151
+ - `ai-docs/src/04_observability/10_operation-observability.ts`
152
+ - `ai-docs/src/05_resources/10_resources-interruption-secrets.ts`
153
+ - `ai-docs/src/06_persistence/10_mutation-boundaries.ts`
154
+ - `ai-docs/src/07_transport/10_wire-errors.ts`
155
+ - `ai-docs/src/08_style/10_visible-code-and-modules.ts`
156
+ - `ai-docs/src/09_testing/10_testing-and-review.ts`
157
+ - `ai-docs/src/10_boundaries/10_external-time-regex.ts`
158
+ - `ai-docs/src/fixtures/Auth.ts`
159
+
160
+ Also read canonical UPVE-1020, assigned issue and required empty thread scan,
161
+ the owner's parent decision thread, `specs/multica-workflow.md`, accepted
162
+ `specs/architecture-decisions.md`, `specs/core-contract.md`,
163
+ `specs/postgresql-backend.md`, `specs/d6-qualification.md`, current manifests,
164
+ TypeScript configuration, production schema/producer/transaction seams, retained
165
+ application-manager fixture, installed gate and actual installed Drizzle/native
166
+ driver/codecs/Effect SQL transaction implementations. The author worktree has no
167
+ repository-owned AGENTS.md. No auth repository or issue was written.