b3-prosaic-harness-postgres 0.2.1__py3-none-any.whl

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.
@@ -0,0 +1,135 @@
1
+ """Explicit, transactional version-1 setup and read-only readiness checks."""
2
+ from prosaic_harness import StoreUninitialized, StoreIncompatible
3
+ from prosaic_harness.run_store import STORAGE_CONTRACT_VERSION
4
+
5
+ SCHEMA_VERSION = 1
6
+ MIGRATION_LOCK = 0x50524f53414943
7
+
8
+ DDL = """
9
+ CREATE SCHEMA IF NOT EXISTS prosaic_harness;
10
+ CREATE TABLE prosaic_harness.schema_version (
11
+ singleton BOOLEAN PRIMARY KEY CHECK (singleton), schema_version INTEGER NOT NULL,
12
+ storage_contract_version INTEGER NOT NULL);
13
+ CREATE TABLE prosaic_harness.leases (
14
+ namespace TEXT NOT NULL CHECK (namespace ~ '^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$'),
15
+ run_id TEXT NOT NULL CHECK (run_id ~ '^[0-9a-f]{32}$'),
16
+ generation BIGINT NOT NULL DEFAULT 0 CHECK (generation >= 0), owner TEXT,
17
+ expires_at TIMESTAMPTZ, PRIMARY KEY(namespace, run_id),
18
+ CHECK ((owner IS NULL) = (expires_at IS NULL)));
19
+ CREATE TABLE prosaic_harness.runs (
20
+ namespace TEXT NOT NULL, run_id TEXT NOT NULL,
21
+ revision BIGINT NOT NULL CHECK(revision > 0),
22
+ document TEXT NOT NULL CHECK(octet_length(document) <= 8388608),
23
+ PRIMARY KEY(namespace, run_id),
24
+ FOREIGN KEY(namespace, run_id) REFERENCES prosaic_harness.leases(namespace, run_id));
25
+ CREATE TABLE prosaic_harness.receipts (
26
+ namespace TEXT NOT NULL, run_id TEXT NOT NULL,
27
+ invocation_id TEXT NOT NULL CHECK(invocation_id ~ '^[0-9a-f]{32}$'),
28
+ document TEXT NOT NULL CHECK(octet_length(document) <= 8388608),
29
+ sha256 TEXT NOT NULL CHECK(sha256 ~ '^[0-9a-f]{64}$'),
30
+ PRIMARY KEY(namespace, run_id, invocation_id),
31
+ FOREIGN KEY(namespace, run_id) REFERENCES prosaic_harness.runs(namespace, run_id));
32
+ INSERT INTO prosaic_harness.schema_version VALUES(TRUE, 1, 1);
33
+ """
34
+
35
+ COLUMNS = {
36
+ 'schema_version': {'singleton': 'boolean', 'schema_version': 'integer', 'storage_contract_version': 'integer'},
37
+ 'leases': {'namespace': 'text', 'run_id': 'text', 'generation': 'bigint', 'owner': 'text', 'expires_at': 'timestamp with time zone'},
38
+ 'runs': {'namespace': 'text', 'run_id': 'text', 'revision': 'bigint', 'document': 'text'},
39
+ 'receipts': {'namespace': 'text', 'run_id': 'text', 'invocation_id': 'text', 'document': 'text', 'sha256': 'text'},
40
+ }
41
+
42
+ CONSTRAINTS = {
43
+ 'schema_version': {'CHECK (singleton)', 'PRIMARY KEY (singleton)'},
44
+ 'leases': {
45
+ "CHECK ((namespace ~ '^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$'::text))",
46
+ "CHECK ((run_id ~ '^[0-9a-f]{32}$'::text))",
47
+ 'CHECK ((generation >= 0))', 'CHECK (((owner IS NULL) = (expires_at IS NULL)))',
48
+ 'PRIMARY KEY (namespace, run_id)'},
49
+ 'runs': {'CHECK ((revision > 0))', 'CHECK ((octet_length(document) <= 8388608))',
50
+ 'PRIMARY KEY (namespace, run_id)',
51
+ 'FOREIGN KEY (namespace, run_id) REFERENCES prosaic_harness.leases(namespace, run_id)'},
52
+ 'receipts': {"CHECK ((invocation_id ~ '^[0-9a-f]{32}$'::text))",
53
+ "CHECK ((sha256 ~ '^[0-9a-f]{64}$'::text))",
54
+ 'CHECK ((octet_length(document) <= 8388608))',
55
+ 'PRIMARY KEY (namespace, run_id, invocation_id)',
56
+ 'FOREIGN KEY (namespace, run_id) REFERENCES prosaic_harness.runs(namespace, run_id)'},
57
+ }
58
+
59
+
60
+ async def ready_on_connection(conn):
61
+ exists = await (await conn.execute("SELECT pg_catalog.to_regclass('prosaic_harness.schema_version')")).fetchone()
62
+ if not exists or exists[0] is None:
63
+ raise StoreUninitialized('run init with migration credentials before accepting work')
64
+ tables = await (await conn.execute("""SELECT c.relname, c.relpersistence, c.relkind
65
+ FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON n.oid=c.relnamespace
66
+ WHERE n.nspname='prosaic_harness' AND c.relname = ANY(%s)""", (list(COLUMNS),))).fetchall()
67
+ if {r[0] for r in tables} != set(COLUMNS) or any(r[1:] != ('p', 'r') for r in tables):
68
+ raise StoreIncompatible('owned schema must contain permanent logged adapter tables')
69
+ columns = await (await conn.execute("""SELECT c.relname, a.attname, pg_catalog.format_type(a.atttypid,a.atttypmod), a.attnotnull
70
+ FROM pg_catalog.pg_attribute a JOIN pg_catalog.pg_class c ON c.oid=a.attrelid
71
+ JOIN pg_catalog.pg_namespace n ON n.oid=c.relnamespace
72
+ WHERE n.nspname='prosaic_harness' AND c.relname = ANY(%s)
73
+ AND a.attnum>0 AND NOT a.attisdropped""", (list(COLUMNS),))).fetchall()
74
+ actual = {name: {} for name in COLUMNS}
75
+ for table, column, kind, notnull in columns:
76
+ actual[table][column] = kind
77
+ if notnull != ((table, column) not in {('leases', 'owner'), ('leases', 'expires_at')}):
78
+ raise StoreIncompatible('adapter schema nullability is incompatible')
79
+ if actual != COLUMNS:
80
+ raise StoreIncompatible('adapter schema columns are incompatible')
81
+ constraints = await (await conn.execute("""SELECT c.relname,
82
+ pg_catalog.pg_get_constraintdef(k.oid), k.convalidated, k.contype
83
+ FROM pg_catalog.pg_constraint k JOIN pg_catalog.pg_class c ON c.oid=k.conrelid
84
+ JOIN pg_catalog.pg_namespace n ON n.oid=c.relnamespace
85
+ WHERE n.nspname='prosaic_harness' AND c.relname = ANY(%s)""", (list(COLUMNS),))).fetchall()
86
+ actual_constraints = {name: set() for name in COLUMNS}
87
+ for table, definition, validated, kind in constraints:
88
+ if not validated:
89
+ raise StoreIncompatible('adapter schema constraints must be validated')
90
+ # PostgreSQL 18 catalogs NOT NULL separately; attnotnull above checks
91
+ # the identical contract on both supported major versions.
92
+ if kind == 'n':
93
+ continue
94
+ actual_constraints[table].add(definition)
95
+ if actual_constraints != CONSTRAINTS:
96
+ raise StoreIncompatible('adapter schema constraints are incompatible')
97
+ version = await (await conn.execute('SELECT schema_version, storage_contract_version FROM prosaic_harness.schema_version')).fetchall()
98
+ if version != [(SCHEMA_VERSION, STORAGE_CONTRACT_VERSION)]:
99
+ raise StoreIncompatible('deploy a compatible adapter/schema; no automatic downgrade')
100
+ for table in COLUMNS:
101
+ privileges = ['SELECT']
102
+ if table in {'leases', 'runs', 'receipts'}:
103
+ privileges.append('INSERT')
104
+ if table in {'leases', 'runs'}:
105
+ privileges.append('UPDATE')
106
+ for privilege in privileges:
107
+ allowed = await (await conn.execute('SELECT pg_catalog.has_table_privilege(%s,%s)',
108
+ ('prosaic_harness.' + table, privilege))).fetchone()
109
+ if not allowed or not allowed[0]:
110
+ raise StoreIncompatible('grant required runtime table privileges before accepting work')
111
+
112
+
113
+ def check_ready(transport):
114
+ transport.call(ready_on_connection)
115
+
116
+
117
+ def initialize(transport):
118
+ async def migrate(conn):
119
+ await conn.execute("SELECT pg_catalog.set_config('lock_timeout','25000ms',true), "
120
+ "pg_catalog.set_config('statement_timeout','28000ms',true)")
121
+ await conn.execute('SELECT pg_catalog.pg_advisory_xact_lock(%s)', (MIGRATION_LOCK,))
122
+ namespace = await (await conn.execute("""SELECT n.oid,
123
+ pg_catalog.pg_has_role(n.nspowner,'USAGE') FROM pg_catalog.pg_namespace n
124
+ WHERE n.nspname='prosaic_harness'""")).fetchone()
125
+ if namespace:
126
+ if not namespace[1]:
127
+ raise StoreIncompatible('migration role must own the adapter schema')
128
+ count = await (await conn.execute('SELECT count(*) FROM pg_catalog.pg_class WHERE relnamespace=%s',
129
+ (namespace[0],))).fetchone()
130
+ if count[0]:
131
+ await ready_on_connection(conn)
132
+ return
133
+ await conn.execute(DDL)
134
+ await ready_on_connection(conn)
135
+ transport.call(migrate, timeout_s=30)
@@ -0,0 +1,277 @@
1
+ """Namespace-bound RunStore with short transactions and no inference retries."""
2
+ from contextlib import contextmanager
3
+ from datetime import timedelta
4
+ import hashlib
5
+ import os
6
+ import re
7
+ import threading
8
+ import time
9
+
10
+ from prosaic_harness import (RunSnapshot, StoreBusy, StoreCorrupt, StoreUnavailable,
11
+ LeaseLost, RunBusy, RunNotFound, RunAlreadyExists,
12
+ RevisionConflict, ReceiptConflict)
13
+ from prosaic_harness.run_store import (validate_namespace, validate_id, validate_revision,
14
+ encode_document, decode_document, revision_token)
15
+ from .transport import PostgresTransport, positive
16
+ from . import schema
17
+ from .leases import PostgresLease
18
+
19
+
20
+ class PostgresRunStore:
21
+ @classmethod
22
+ def from_env(cls, name, *, namespace, **kwargs):
23
+ if not isinstance(name, str) or not re.fullmatch(r'[A-Za-z_][A-Za-z0-9_]{0,127}', name):
24
+ raise ValueError('dsn environment name must be a bounded identifier')
25
+ dsn = os.environ.get(name)
26
+ if not dsn:
27
+ raise StoreUnavailable('required database environment variable is missing')
28
+ return cls(dsn, namespace=namespace, **kwargs)
29
+
30
+ def __init__(self, dsn, *, namespace, lease_s=60, renew_s=15,
31
+ operation_timeout_s=10, pool_timeout_s=5, connect_timeout_s=5,
32
+ lock_timeout_s=1, statement_timeout_s=5, pool_size=8, max_active_leases=4):
33
+ self.namespace = validate_namespace(namespace)
34
+ positive(lease_s, 'lease_s')
35
+ positive(renew_s, 'renew_s')
36
+ positive(operation_timeout_s, 'operation_timeout_s')
37
+ if (lease_s < 15 or renew_s < 1 or renew_s > lease_s / 3
38
+ or renew_s + operation_timeout_s + 1 >= lease_s):
39
+ raise ValueError('lease timing requires duration >=15, renewal >=1 and adequate deadline margin')
40
+ self.lease_s, self.renew_s = lease_s, renew_s
41
+ self._pid = os.getpid()
42
+ self._mutex = threading.RLock()
43
+ self._sessions = set()
44
+ self._reservations = set()
45
+ self._max_active_leases = max_active_leases
46
+ self._transport = PostgresTransport(dsn, operation_timeout_s=operation_timeout_s,
47
+ pool_timeout_s=pool_timeout_s, connect_timeout_s=connect_timeout_s,
48
+ lock_timeout_s=lock_timeout_s, statement_timeout_s=statement_timeout_s,
49
+ pool_size=pool_size, max_active_leases=max_active_leases)
50
+
51
+ def initialize(self):
52
+ schema.initialize(self._transport)
53
+
54
+ def check_ready(self):
55
+ schema.check_ready(self._transport)
56
+
57
+ def _local(self, run_id, lease):
58
+ self._transport._check_process()
59
+ validate_id(run_id)
60
+ if (not isinstance(lease, PostgresLease) or lease.store is not self
61
+ or lease.run_id != run_id or lease not in self._sessions):
62
+ raise LeaseLost('lease does not own this store/run')
63
+ lease.check_valid()
64
+
65
+ async def _lease_row(self, conn, run_id):
66
+ return await (await conn.execute('SELECT owner,generation,expires_at FROM prosaic_harness.leases '
67
+ 'WHERE namespace=%s AND run_id=%s FOR UPDATE', (self.namespace, run_id))).fetchone()
68
+
69
+ async def _guard_lease(self, conn, lease):
70
+ self._local(lease.run_id, lease)
71
+ row = await self._lease_row(conn, lease.run_id)
72
+ now = (await (await conn.execute('SELECT pg_catalog.clock_timestamp()')).fetchone())[0]
73
+ if (not row or row[0] != lease.owner or row[1] != lease.generation
74
+ or row[2] is None or row[2] <= now):
75
+ lease._lost = True
76
+ raise LeaseLost('database execution ownership expired or changed')
77
+ lease.check_valid()
78
+ return now
79
+
80
+ async def _acquire(self, conn, lease):
81
+ await conn.execute('INSERT INTO prosaic_harness.leases(namespace,run_id) VALUES(%s,%s) '
82
+ 'ON CONFLICT DO NOTHING', (self.namespace, lease.run_id))
83
+ row = await self._lease_row(conn, lease.run_id)
84
+ now = (await (await conn.execute('SELECT pg_catalog.clock_timestamp()')).fetchone())[0]
85
+ if row[0] is not None and row[2] > now:
86
+ raise RunBusy('run is owned by another execution')
87
+ generation = row[1] + 1
88
+ await conn.execute('UPDATE prosaic_harness.leases SET owner=%s,generation=%s,expires_at=%s '
89
+ 'WHERE namespace=%s AND run_id=%s',
90
+ (lease.owner, generation, now + timedelta(seconds=self.lease_s),
91
+ self.namespace, lease.run_id))
92
+ return generation
93
+
94
+ async def _renew(self, conn, lease):
95
+ now = await self._guard_lease(conn, lease)
96
+ cursor = await conn.execute('UPDATE prosaic_harness.leases SET expires_at=%s '
97
+ 'WHERE namespace=%s AND run_id=%s AND owner=%s AND generation=%s',
98
+ (now + timedelta(seconds=self.lease_s), self.namespace, lease.run_id, lease.owner, lease.generation))
99
+ if cursor.rowcount != 1:
100
+ lease._lost = True
101
+ raise LeaseLost('database renewal ownership changed')
102
+
103
+ async def _release(self, conn, lease):
104
+ await conn.execute('UPDATE prosaic_harness.leases SET owner=NULL,expires_at=NULL '
105
+ 'WHERE namespace=%s AND run_id=%s AND owner=%s AND generation=%s',
106
+ (self.namespace, lease.run_id, lease.owner, lease.generation))
107
+
108
+ @contextmanager
109
+ def lease(self, run_id):
110
+ self._transport._check_process()
111
+ validate_id(run_id)
112
+ reservation = object()
113
+ session = None
114
+ primary = None
115
+ try:
116
+ with self._mutex:
117
+ if len(self._reservations) >= self._max_active_leases:
118
+ raise StoreBusy('active execution capacity exhausted')
119
+ # One token owns capacity throughout setup, execution and
120
+ # teardown. Registration never transfers or decrements it.
121
+ self._reservations.add(reservation)
122
+ session = PostgresLease(self, run_id, time.monotonic())
123
+ session.generation = self._transport.call(lambda conn: self._acquire(conn, session))
124
+ session.check_valid()
125
+ with self._mutex:
126
+ self._sessions.add(session)
127
+ # Retain the coroutine even if submission is interrupted before it
128
+ # returns. stop() closes an unstarted runner on its owning loop or
129
+ # drains a started renewal before ownership can be released.
130
+ session._renewal = session.maintain()
131
+ self._transport.submit(session._renewal)
132
+ yield session
133
+ except BaseException as exc:
134
+ primary = exc
135
+ raise
136
+ finally:
137
+ try:
138
+ if session is not None and session.generation is not None:
139
+ if session._renewal is None:
140
+ session._stopped.set()
141
+ else:
142
+ session.stop()
143
+ self._transport.call(lambda conn: self._release(conn, session), renewal=True)
144
+ except BaseException:
145
+ session._lost = True
146
+ if primary is None:
147
+ raise
148
+ primary.add_note('database lease cleanup failed; reconcile ownership before retrying')
149
+ finally:
150
+ if session is not None:
151
+ session._active = False
152
+ with self._mutex:
153
+ if (session is None or session.generation is None
154
+ or session._stopped.is_set()):
155
+ self._reservations.discard(reservation)
156
+ if session is not None and session._stopped.is_set():
157
+ self._sessions.discard(session)
158
+
159
+ def _token(self, run_id, revision):
160
+ return revision_token('postgres', self.namespace, run_id, revision)
161
+
162
+ async def _read_run(self, conn, run_id, *, lock=False):
163
+ row = await (await conn.execute('SELECT revision,CASE WHEN octet_length(document)<=8388608 '
164
+ 'THEN document ELSE NULL END FROM prosaic_harness.runs WHERE namespace=%s AND run_id=%s'
165
+ + (' FOR UPDATE' if lock else ''), (self.namespace, run_id))).fetchone()
166
+ if not row:
167
+ raise RunNotFound('run document not found')
168
+ state = decode_document(row[1])
169
+ if state.get('run_id') != run_id:
170
+ raise StoreCorrupt('checkpoint identity does not match its address')
171
+ return RunSnapshot(state, self._token(run_id, row[0])), row[0]
172
+
173
+ def load_run(self, run_id):
174
+ validate_id(run_id)
175
+ async def read(conn):
176
+ return (await self._read_run(conn, run_id))[0]
177
+ return self._transport.call(read)
178
+
179
+ def _document(self, run_id, state):
180
+ content = encode_document(state)
181
+ if state.get('run_id') != run_id:
182
+ raise StoreCorrupt('checkpoint identity does not match its address')
183
+ return content.decode('utf-8')
184
+
185
+ def _mutate(self, run_id, lease, callback):
186
+ self._local(run_id, lease)
187
+ async def guarded(conn):
188
+ await self._guard_lease(conn, lease)
189
+ return await callback(conn)
190
+ try:
191
+ result = self._transport.call(guarded)
192
+ lease.check_valid()
193
+ return result
194
+ except StoreUnavailable:
195
+ lease._lost = True
196
+ raise
197
+
198
+ def create_run(self, run_id, state, lease):
199
+ self._local(run_id, lease)
200
+ document = self._document(run_id, state)
201
+ async def create(conn):
202
+ row = await (await conn.execute('INSERT INTO prosaic_harness.runs(namespace,run_id,revision,document) '
203
+ 'VALUES(%s,%s,1,%s) ON CONFLICT DO NOTHING RETURNING revision',
204
+ (self.namespace, run_id, document))).fetchone()
205
+ if not row:
206
+ raise RunAlreadyExists('run already exists; use resume or a new identity')
207
+ return self._token(run_id, row[0])
208
+ return self._mutate(run_id, lease, create)
209
+
210
+ def save_run(self, run_id, state, expected_revision, lease):
211
+ self._local(run_id, lease)
212
+ validate_revision(expected_revision)
213
+ document = self._document(run_id, state)
214
+ async def save(conn):
215
+ snapshot, revision = await self._read_run(conn, run_id, lock=True)
216
+ if snapshot.revision != expected_revision:
217
+ raise RevisionConflict('checkpoint changed; refresh status')
218
+ row = await (await conn.execute('UPDATE prosaic_harness.runs SET document=%s,revision=revision+1 '
219
+ 'WHERE namespace=%s AND run_id=%s AND revision=%s RETURNING revision',
220
+ (document, self.namespace, run_id, revision))).fetchone()
221
+ if not row:
222
+ raise RevisionConflict('checkpoint changed; refresh status')
223
+ return self._token(run_id, row[0])
224
+ return self._mutate(run_id, lease, save)
225
+
226
+ async def _read_receipt(self, conn, run_id, invocation_id):
227
+ row = await (await conn.execute('SELECT CASE WHEN octet_length(document)<=8388608 THEN document '
228
+ 'ELSE NULL END,sha256 FROM prosaic_harness.receipts WHERE namespace=%s AND run_id=%s AND invocation_id=%s',
229
+ (self.namespace, run_id, invocation_id))).fetchone()
230
+ if row is None:
231
+ return None
232
+ receipt = decode_document(row[0])
233
+ if (receipt.get('id') != invocation_id or receipt.get('run_id') != run_id
234
+ or hashlib.sha256(encode_document(receipt)).hexdigest() != row[1]):
235
+ raise StoreCorrupt('receipt integrity or identity does not match its address')
236
+ return receipt
237
+
238
+ def load_receipt(self, run_id, invocation_id):
239
+ validate_id(run_id)
240
+ validate_id(invocation_id)
241
+ return self._transport.call(lambda conn: self._read_receipt(conn, run_id, invocation_id))
242
+
243
+ def save_receipt(self, run_id, invocation_id, receipt, lease):
244
+ self._local(run_id, lease)
245
+ validate_id(invocation_id)
246
+ content = encode_document(receipt)
247
+ if receipt.get('id') != invocation_id or receipt.get('run_id') != run_id:
248
+ raise StoreCorrupt('receipt identity does not match its address')
249
+ async def save(conn):
250
+ await self._read_run(conn, run_id, lock=True)
251
+ await conn.execute('INSERT INTO prosaic_harness.receipts(namespace,run_id,invocation_id,document,sha256) '
252
+ 'VALUES(%s,%s,%s,%s,%s) ON CONFLICT DO NOTHING',
253
+ (self.namespace, run_id, invocation_id, content.decode('utf-8'), hashlib.sha256(content).hexdigest()))
254
+ existing = await self._read_receipt(conn, run_id, invocation_id)
255
+ if encode_document(existing) != content:
256
+ raise ReceiptConflict('immutable receipt differs from committed evidence')
257
+ self._mutate(run_id, lease, save)
258
+
259
+ def close(self):
260
+ if self._pid != os.getpid():
261
+ raise StoreBusy('construct database stores after worker fork')
262
+ with self._mutex:
263
+ if self._sessions or self._reservations:
264
+ raise StoreBusy('drain active executions before closing the store')
265
+ self._transport.close()
266
+
267
+ def __enter__(self):
268
+ self._transport._check_process()
269
+ return self
270
+
271
+ def __exit__(self, exc_type, exc, traceback):
272
+ try:
273
+ self.close()
274
+ except Exception:
275
+ if exc is None:
276
+ raise
277
+ exc.add_note('database store cleanup failed; drain pending operations')