b3-prosaic-harness-postgres 0.2.1__tar.gz → 0.3.0__tar.gz

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 (51) hide show
  1. {b3_prosaic_harness_postgres-0.2.1/src/b3_prosaic_harness_postgres.egg-info → b3_prosaic_harness_postgres-0.3.0}/PKG-INFO +103 -7
  2. {b3_prosaic_harness_postgres-0.2.1 → b3_prosaic_harness_postgres-0.3.0}/README.md +100 -4
  3. {b3_prosaic_harness_postgres-0.2.1 → b3_prosaic_harness_postgres-0.3.0}/pyproject.toml +3 -3
  4. {b3_prosaic_harness_postgres-0.2.1 → b3_prosaic_harness_postgres-0.3.0/src/b3_prosaic_harness_postgres.egg-info}/PKG-INFO +103 -7
  5. {b3_prosaic_harness_postgres-0.2.1 → b3_prosaic_harness_postgres-0.3.0}/src/b3_prosaic_harness_postgres.egg-info/SOURCES.txt +10 -0
  6. {b3_prosaic_harness_postgres-0.2.1 → b3_prosaic_harness_postgres-0.3.0}/src/b3_prosaic_harness_postgres.egg-info/requires.txt +1 -1
  7. b3_prosaic_harness_postgres-0.3.0/src/prosaic_harness_postgres/__init__.py +7 -0
  8. b3_prosaic_harness_postgres-0.3.0/src/prosaic_harness_postgres/session_leases.py +10 -0
  9. b3_prosaic_harness_postgres-0.3.0/src/prosaic_harness_postgres/session_schema.py +185 -0
  10. b3_prosaic_harness_postgres-0.3.0/src/prosaic_harness_postgres/session_store.py +319 -0
  11. {b3_prosaic_harness_postgres-0.2.1 → b3_prosaic_harness_postgres-0.3.0}/tests/support/linux_wheel_smoke.py +13 -1
  12. {b3_prosaic_harness_postgres-0.2.1 → b3_prosaic_harness_postgres-0.3.0}/tests/support/local_cluster.py +5 -3
  13. b3_prosaic_harness_postgres-0.3.0/tests/support/session_documents.py +40 -0
  14. b3_prosaic_harness_postgres-0.3.0/tests/support/session_workers.py +94 -0
  15. {b3_prosaic_harness_postgres-0.2.1 → b3_prosaic_harness_postgres-0.3.0}/tests/test_packaging.py +21 -8
  16. b3_prosaic_harness_postgres-0.3.0/tests/test_session_fencing.py +137 -0
  17. b3_prosaic_harness_postgres-0.3.0/tests/test_session_recovery.py +110 -0
  18. b3_prosaic_harness_postgres-0.3.0/tests/test_session_schema.py +230 -0
  19. b3_prosaic_harness_postgres-0.3.0/tests/test_session_store.py +160 -0
  20. b3_prosaic_harness_postgres-0.3.0/tests/test_session_workers.py +87 -0
  21. b3_prosaic_harness_postgres-0.2.1/src/prosaic_harness_postgres/__init__.py +0 -6
  22. {b3_prosaic_harness_postgres-0.2.1 → b3_prosaic_harness_postgres-0.3.0}/LICENSE +0 -0
  23. {b3_prosaic_harness_postgres-0.2.1 → b3_prosaic_harness_postgres-0.3.0}/MANIFEST.in +0 -0
  24. {b3_prosaic_harness_postgres-0.2.1 → b3_prosaic_harness_postgres-0.3.0}/NOTICE +0 -0
  25. {b3_prosaic_harness_postgres-0.2.1 → b3_prosaic_harness_postgres-0.3.0}/setup.cfg +0 -0
  26. {b3_prosaic_harness_postgres-0.2.1 → b3_prosaic_harness_postgres-0.3.0}/src/b3_prosaic_harness_postgres.egg-info/dependency_links.txt +0 -0
  27. {b3_prosaic_harness_postgres-0.2.1 → b3_prosaic_harness_postgres-0.3.0}/src/b3_prosaic_harness_postgres.egg-info/entry_points.txt +0 -0
  28. {b3_prosaic_harness_postgres-0.2.1 → b3_prosaic_harness_postgres-0.3.0}/src/b3_prosaic_harness_postgres.egg-info/top_level.txt +0 -0
  29. {b3_prosaic_harness_postgres-0.2.1 → b3_prosaic_harness_postgres-0.3.0}/src/prosaic_harness_postgres/cli.py +0 -0
  30. {b3_prosaic_harness_postgres-0.2.1 → b3_prosaic_harness_postgres-0.3.0}/src/prosaic_harness_postgres/leases.py +0 -0
  31. {b3_prosaic_harness_postgres-0.2.1 → b3_prosaic_harness_postgres-0.3.0}/src/prosaic_harness_postgres/schema.py +0 -0
  32. {b3_prosaic_harness_postgres-0.2.1 → b3_prosaic_harness_postgres-0.3.0}/src/prosaic_harness_postgres/store.py +0 -0
  33. {b3_prosaic_harness_postgres-0.2.1 → b3_prosaic_harness_postgres-0.3.0}/src/prosaic_harness_postgres/transport.py +0 -0
  34. {b3_prosaic_harness_postgres-0.2.1 → b3_prosaic_harness_postgres-0.3.0}/tests/conftest.py +0 -0
  35. {b3_prosaic_harness_postgres-0.2.1 → b3_prosaic_harness_postgres-0.3.0}/tests/support/model_server.py +0 -0
  36. {b3_prosaic_harness_postgres-0.2.1 → b3_prosaic_harness_postgres-0.3.0}/tests/support/tcp_proxy.py +0 -0
  37. {b3_prosaic_harness_postgres-0.2.1 → b3_prosaic_harness_postgres-0.3.0}/tests/support/workers.py +0 -0
  38. {b3_prosaic_harness_postgres-0.2.1 → b3_prosaic_harness_postgres-0.3.0}/tests/test_cli.py +0 -0
  39. {b3_prosaic_harness_postgres-0.2.1 → b3_prosaic_harness_postgres-0.3.0}/tests/test_durability.py +0 -0
  40. {b3_prosaic_harness_postgres-0.2.1 → b3_prosaic_harness_postgres-0.3.0}/tests/test_engine.py +0 -0
  41. {b3_prosaic_harness_postgres-0.2.1 → b3_prosaic_harness_postgres-0.3.0}/tests/test_examples.py +0 -0
  42. {b3_prosaic_harness_postgres-0.2.1 → b3_prosaic_harness_postgres-0.3.0}/tests/test_leases.py +0 -0
  43. {b3_prosaic_harness_postgres-0.2.1 → b3_prosaic_harness_postgres-0.3.0}/tests/test_local_cluster.py +0 -0
  44. {b3_prosaic_harness_postgres-0.2.1 → b3_prosaic_harness_postgres-0.3.0}/tests/test_native_factory.py +0 -0
  45. {b3_prosaic_harness_postgres-0.2.1 → b3_prosaic_harness_postgres-0.3.0}/tests/test_recovery.py +0 -0
  46. {b3_prosaic_harness_postgres-0.2.1 → b3_prosaic_harness_postgres-0.3.0}/tests/test_registration.py +0 -0
  47. {b3_prosaic_harness_postgres-0.2.1 → b3_prosaic_harness_postgres-0.3.0}/tests/test_routing.py +0 -0
  48. {b3_prosaic_harness_postgres-0.2.1 → b3_prosaic_harness_postgres-0.3.0}/tests/test_schema.py +0 -0
  49. {b3_prosaic_harness_postgres-0.2.1 → b3_prosaic_harness_postgres-0.3.0}/tests/test_store.py +0 -0
  50. {b3_prosaic_harness_postgres-0.2.1 → b3_prosaic_harness_postgres-0.3.0}/tests/test_transport.py +0 -0
  51. {b3_prosaic_harness_postgres-0.2.1 → b3_prosaic_harness_postgres-0.3.0}/tests/test_workers.py +0 -0
@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: b3-prosaic-harness-postgres
3
- Version: 0.2.1
4
- Summary: Explicit PostgreSQL run storage for Prosaic Harness
3
+ Version: 0.3.0
4
+ Summary: Explicit PostgreSQL run and conversation storage for Prosaic Harness
5
5
  License-Expression: Apache-2.0
6
6
  Requires-Python: >=3.11
7
7
  Description-Content-Type: text/markdown
8
8
  License-File: LICENSE
9
9
  License-File: NOTICE
10
- Requires-Dist: b3-prosaic-harness<0.8,>=0.7
10
+ Requires-Dist: b3-prosaic-harness<0.9,>=0.8
11
11
  Requires-Dist: psycopg[binary]<4,>=3.2.3
12
12
  Requires-Dist: psycopg-pool<4,>=3.2
13
13
  Provides-Extra: test
@@ -24,11 +24,11 @@ Run on worker A, inspect/resume on worker B, without a shared writable run
24
24
  directory. Core remains database-free; install this separate adapter only when
25
25
  you need shared durable state. The API is synchronous; a private I/O thread
26
26
  bounds network waits. Model execution holds no database connection/transaction.
27
- The current candidate is `b3-prosaic-harness-postgres` **0.2.0**, selecting
28
- `b3-prosaic-harness>=0.7,<0.8`. The adapter is maintained in this repository but
27
+ The conversation candidate is `b3-prosaic-harness-postgres` **0.3.0**, selecting
28
+ `b3-prosaic-harness>=0.8,<0.9`. The adapter is maintained in this repository but
29
29
  installed and versioned separately, keeping PostgreSQL drivers optional for
30
30
  Harness users. Imports and CLI names remain unchanged. Final qualification and
31
- publication of the b3 release train are pending. The historical
31
+ publication of this conversation release train are pending. The historical
32
32
  [Harness v0.6.1 release](https://github.com/B3Cognition/prosaic-harness/releases/tag/v0.6.1)
33
33
  retains its original 0.6.1/0.1.1 package names and receipts.
34
34
 
@@ -49,7 +49,7 @@ prosaic-harness-postgres --help
49
49
 
50
50
  ALWAYS migrate using a fresh environment or image; NEVER install legacy and b3
51
51
  distributions together because they own the same import paths. See
52
- [package migration](../../docs/package-migration.md). The Runtime 0.8 candidate
52
+ [package migration](../../docs/package-migration.md). The Runtime 0.9 candidate
53
53
  must be available before this installation can resolve.
54
54
 
55
55
  Harness does **not** provision PostgreSQL, create databases/users, start a
@@ -220,3 +220,99 @@ session capacity atomically under the existing store mutex. A registering lease
220
220
  can no longer be counted twice while maintenance is scheduled. The database
221
221
  storage/fencing contract is unchanged. A unit scheduling-boundary regression and
222
222
  an owned PostgreSQL 16/18 race regression cover the transition.
223
+
224
+ ## Optional conversation sessions
225
+
226
+ The conversation candidate adds `PostgresSessionStore` to this same optional
227
+ distribution. It implements the public Harness `SessionStore` contract and uses
228
+ the independent, logged `prosaic_harness_conversations` schema, storage version 1.
229
+ Existing `PostgresRunStore` tables, grants and CLI `init`/`doctor` behavior remain
230
+ compatible; those CLI commands continue to address workflow storage.
231
+
232
+ Initialize conversation storage once with the schema owner's credentials:
233
+
234
+ ```python
235
+ from prosaic_harness_postgres import PostgresSessionStore
236
+
237
+ with PostgresSessionStore.from_env(
238
+ 'HARNESS_MIGRATION_DSN', namespace='my-chat'
239
+ ) as owner:
240
+ owner.initialize()
241
+ ```
242
+
243
+ Apply these grants as the owner, replacing `chat_runtime` with the existing
244
+ application role. Role, database and secret creation remain operator tasks.
245
+
246
+ ```sql
247
+ GRANT USAGE ON SCHEMA prosaic_harness_conversations TO chat_runtime;
248
+ GRANT SELECT ON prosaic_harness_conversations.schema_version,
249
+ prosaic_harness_conversations.leases,
250
+ prosaic_harness_conversations.sessions,
251
+ prosaic_harness_conversations.receipts TO chat_runtime;
252
+ GRANT INSERT, UPDATE ON prosaic_harness_conversations.leases,
253
+ prosaic_harness_conversations.sessions TO chat_runtime;
254
+ GRANT INSERT ON prosaic_harness_conversations.receipts TO chat_runtime;
255
+ ```
256
+
257
+ The runtime role needs no schema CREATE, schema-version UPDATE, receipt UPDATE
258
+ or DELETE. A role with only workflow grants can continue using `PostgresRunStore`
259
+ and cannot use conversation storage until it receives the separate grants.
260
+ Conversation readiness rejects forbidden effective grants on non-owner runtime
261
+ roles, including inherited and column-only grants. It also rejects schema-version
262
+ INSERT/DELETE and table DELETE/TRUNCATE privileges. Revoke accidental extra
263
+ permissions before accepting work.
264
+
265
+ Schema owners and roles that immediately inherit schema-owner privileges remain
266
+ administrative credentials: `initialize()` and operator `check_ready()` can
267
+ validate their installation. Those checks cannot make an owner a restricted
268
+ role. ALWAYS configure worker processes with separate non-owner runtime
269
+ credentials and verify their ownership/grants; NEVER use migration credentials
270
+ as the application's runtime DSN.
271
+ Readiness performs no migration or fallback:
272
+
273
+ ```python
274
+ from prosaic_harness import Conversation, ConversationScope
275
+ from prosaic_harness_postgres import PostgresSessionStore
276
+
277
+ scope = ConversationScope('my-chat', 'production', 'my-service', tenant, actor)
278
+ with PostgresSessionStore.from_env(
279
+ 'HARNESS_RUNTIME_DSN', namespace=scope.namespace
280
+ ) as sessions:
281
+ sessions.check_ready()
282
+ chat = Conversation(approved_bindings, store=sessions, scope=scope)
283
+ created = chat.create()
284
+ ```
285
+
286
+ Derive `tenant`, `actor` and any billing account from current authenticated host
287
+ state before each public operation. Scope ownership filters every session and
288
+ receipt read; leases, revisions and writes also bind to the exact scope and
289
+ address. A foreign owner sees `session_not_found`. The SQL role can still access
290
+ its granted tables directly: namespaces and scope hashes do not replace host
291
+ authorization or database row-level security.
292
+
293
+ Use the same sealed bindings and logical workspace/journal identities on every
294
+ worker. The default bounded pools and lease timings documented above apply.
295
+ Each operation checks the writable primary and WAL settings, acquires a short
296
+ transaction, then returns its connection. A conversation's model, tool and
297
+ linked workflow execution holds no transaction or checked-out connection.
298
+ Database generations fence late writes after ownership changes; they cannot
299
+ cancel an external effect already in progress.
300
+
301
+ Snapshots and immutable receipts use the common finite canonical UTF-8 session
302
+ codec, with at most 8 MiB each. Their addresses and receipts are checked against
303
+ allocated attempts, dispatch markers and the frozen scope/binding/input/context.
304
+ Exact receipt repeats are idempotent under a live lease; conflicts fail without
305
+ overwriting evidence. A lost COMMIT acknowledgement raises `store_unavailable`
306
+ with `outcome_unknown` and invalidates the lease. Recover by reading authoritative
307
+ state with fresh ownership; never blindly repeat inference or business effects.
308
+
309
+ The session policy bounds retained/reserved logical records. PostgreSQL indexes,
310
+ WAL, tool journals, replicas and backups add physical storage. The host must also
311
+ enforce retained-session quotas and own any explicit retention/deletion policy;
312
+ this adapter never automatically expires or deletes records.
313
+
314
+ The required adapter suite includes both the existing workflow backend and the
315
+ conversation backend. Run the owned PostgreSQL 16 and 18 fixtures shown above
316
+ with `--require-postgres`. Missing infrastructure or any skipped required case
317
+ fails qualification. Installed Linux AMD64 and ARM64 acceptance is required
318
+ before claiming the conversation candidate is ready for production.
@@ -4,11 +4,11 @@ Run on worker A, inspect/resume on worker B, without a shared writable run
4
4
  directory. Core remains database-free; install this separate adapter only when
5
5
  you need shared durable state. The API is synchronous; a private I/O thread
6
6
  bounds network waits. Model execution holds no database connection/transaction.
7
- The current candidate is `b3-prosaic-harness-postgres` **0.2.0**, selecting
8
- `b3-prosaic-harness>=0.7,<0.8`. The adapter is maintained in this repository but
7
+ The conversation candidate is `b3-prosaic-harness-postgres` **0.3.0**, selecting
8
+ `b3-prosaic-harness>=0.8,<0.9`. The adapter is maintained in this repository but
9
9
  installed and versioned separately, keeping PostgreSQL drivers optional for
10
10
  Harness users. Imports and CLI names remain unchanged. Final qualification and
11
- publication of the b3 release train are pending. The historical
11
+ publication of this conversation release train are pending. The historical
12
12
  [Harness v0.6.1 release](https://github.com/B3Cognition/prosaic-harness/releases/tag/v0.6.1)
13
13
  retains its original 0.6.1/0.1.1 package names and receipts.
14
14
 
@@ -29,7 +29,7 @@ prosaic-harness-postgres --help
29
29
 
30
30
  ALWAYS migrate using a fresh environment or image; NEVER install legacy and b3
31
31
  distributions together because they own the same import paths. See
32
- [package migration](../../docs/package-migration.md). The Runtime 0.8 candidate
32
+ [package migration](../../docs/package-migration.md). The Runtime 0.9 candidate
33
33
  must be available before this installation can resolve.
34
34
 
35
35
  Harness does **not** provision PostgreSQL, create databases/users, start a
@@ -200,3 +200,99 @@ session capacity atomically under the existing store mutex. A registering lease
200
200
  can no longer be counted twice while maintenance is scheduled. The database
201
201
  storage/fencing contract is unchanged. A unit scheduling-boundary regression and
202
202
  an owned PostgreSQL 16/18 race regression cover the transition.
203
+
204
+ ## Optional conversation sessions
205
+
206
+ The conversation candidate adds `PostgresSessionStore` to this same optional
207
+ distribution. It implements the public Harness `SessionStore` contract and uses
208
+ the independent, logged `prosaic_harness_conversations` schema, storage version 1.
209
+ Existing `PostgresRunStore` tables, grants and CLI `init`/`doctor` behavior remain
210
+ compatible; those CLI commands continue to address workflow storage.
211
+
212
+ Initialize conversation storage once with the schema owner's credentials:
213
+
214
+ ```python
215
+ from prosaic_harness_postgres import PostgresSessionStore
216
+
217
+ with PostgresSessionStore.from_env(
218
+ 'HARNESS_MIGRATION_DSN', namespace='my-chat'
219
+ ) as owner:
220
+ owner.initialize()
221
+ ```
222
+
223
+ Apply these grants as the owner, replacing `chat_runtime` with the existing
224
+ application role. Role, database and secret creation remain operator tasks.
225
+
226
+ ```sql
227
+ GRANT USAGE ON SCHEMA prosaic_harness_conversations TO chat_runtime;
228
+ GRANT SELECT ON prosaic_harness_conversations.schema_version,
229
+ prosaic_harness_conversations.leases,
230
+ prosaic_harness_conversations.sessions,
231
+ prosaic_harness_conversations.receipts TO chat_runtime;
232
+ GRANT INSERT, UPDATE ON prosaic_harness_conversations.leases,
233
+ prosaic_harness_conversations.sessions TO chat_runtime;
234
+ GRANT INSERT ON prosaic_harness_conversations.receipts TO chat_runtime;
235
+ ```
236
+
237
+ The runtime role needs no schema CREATE, schema-version UPDATE, receipt UPDATE
238
+ or DELETE. A role with only workflow grants can continue using `PostgresRunStore`
239
+ and cannot use conversation storage until it receives the separate grants.
240
+ Conversation readiness rejects forbidden effective grants on non-owner runtime
241
+ roles, including inherited and column-only grants. It also rejects schema-version
242
+ INSERT/DELETE and table DELETE/TRUNCATE privileges. Revoke accidental extra
243
+ permissions before accepting work.
244
+
245
+ Schema owners and roles that immediately inherit schema-owner privileges remain
246
+ administrative credentials: `initialize()` and operator `check_ready()` can
247
+ validate their installation. Those checks cannot make an owner a restricted
248
+ role. ALWAYS configure worker processes with separate non-owner runtime
249
+ credentials and verify their ownership/grants; NEVER use migration credentials
250
+ as the application's runtime DSN.
251
+ Readiness performs no migration or fallback:
252
+
253
+ ```python
254
+ from prosaic_harness import Conversation, ConversationScope
255
+ from prosaic_harness_postgres import PostgresSessionStore
256
+
257
+ scope = ConversationScope('my-chat', 'production', 'my-service', tenant, actor)
258
+ with PostgresSessionStore.from_env(
259
+ 'HARNESS_RUNTIME_DSN', namespace=scope.namespace
260
+ ) as sessions:
261
+ sessions.check_ready()
262
+ chat = Conversation(approved_bindings, store=sessions, scope=scope)
263
+ created = chat.create()
264
+ ```
265
+
266
+ Derive `tenant`, `actor` and any billing account from current authenticated host
267
+ state before each public operation. Scope ownership filters every session and
268
+ receipt read; leases, revisions and writes also bind to the exact scope and
269
+ address. A foreign owner sees `session_not_found`. The SQL role can still access
270
+ its granted tables directly: namespaces and scope hashes do not replace host
271
+ authorization or database row-level security.
272
+
273
+ Use the same sealed bindings and logical workspace/journal identities on every
274
+ worker. The default bounded pools and lease timings documented above apply.
275
+ Each operation checks the writable primary and WAL settings, acquires a short
276
+ transaction, then returns its connection. A conversation's model, tool and
277
+ linked workflow execution holds no transaction or checked-out connection.
278
+ Database generations fence late writes after ownership changes; they cannot
279
+ cancel an external effect already in progress.
280
+
281
+ Snapshots and immutable receipts use the common finite canonical UTF-8 session
282
+ codec, with at most 8 MiB each. Their addresses and receipts are checked against
283
+ allocated attempts, dispatch markers and the frozen scope/binding/input/context.
284
+ Exact receipt repeats are idempotent under a live lease; conflicts fail without
285
+ overwriting evidence. A lost COMMIT acknowledgement raises `store_unavailable`
286
+ with `outcome_unknown` and invalidates the lease. Recover by reading authoritative
287
+ state with fresh ownership; never blindly repeat inference or business effects.
288
+
289
+ The session policy bounds retained/reserved logical records. PostgreSQL indexes,
290
+ WAL, tool journals, replicas and backups add physical storage. The host must also
291
+ enforce retained-session quotas and own any explicit retention/deletion policy;
292
+ this adapter never automatically expires or deletes records.
293
+
294
+ The required adapter suite includes both the existing workflow backend and the
295
+ conversation backend. Run the owned PostgreSQL 16 and 18 fixtures shown above
296
+ with `--require-postgres`. Missing infrastructure or any skipped required case
297
+ fails qualification. Installed Linux AMD64 and ARM64 acceptance is required
298
+ before claiming the conversation candidate is ready for production.
@@ -4,13 +4,13 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "b3-prosaic-harness-postgres"
7
- version = "0.2.1"
8
- description = "Explicit PostgreSQL run storage for Prosaic Harness"
7
+ version = "0.3.0"
8
+ description = "Explicit PostgreSQL run and conversation storage for Prosaic Harness"
9
9
  requires-python = ">=3.11"
10
10
  readme = "README.md"
11
11
  license = "Apache-2.0"
12
12
  license-files = ["LICENSE", "NOTICE"]
13
- dependencies = ["b3-prosaic-harness>=0.7,<0.8", "psycopg[binary]>=3.2.3,<4", "psycopg-pool>=3.2,<4"]
13
+ dependencies = ["b3-prosaic-harness>=0.8,<0.9", "psycopg[binary]>=3.2.3,<4", "psycopg-pool>=3.2,<4"]
14
14
 
15
15
  [project.optional-dependencies]
16
16
  test = ["pytest>=8", "build>=1", "setuptools>=77", "wheel", "packaging"]
@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: b3-prosaic-harness-postgres
3
- Version: 0.2.1
4
- Summary: Explicit PostgreSQL run storage for Prosaic Harness
3
+ Version: 0.3.0
4
+ Summary: Explicit PostgreSQL run and conversation storage for Prosaic Harness
5
5
  License-Expression: Apache-2.0
6
6
  Requires-Python: >=3.11
7
7
  Description-Content-Type: text/markdown
8
8
  License-File: LICENSE
9
9
  License-File: NOTICE
10
- Requires-Dist: b3-prosaic-harness<0.8,>=0.7
10
+ Requires-Dist: b3-prosaic-harness<0.9,>=0.8
11
11
  Requires-Dist: psycopg[binary]<4,>=3.2.3
12
12
  Requires-Dist: psycopg-pool<4,>=3.2
13
13
  Provides-Extra: test
@@ -24,11 +24,11 @@ Run on worker A, inspect/resume on worker B, without a shared writable run
24
24
  directory. Core remains database-free; install this separate adapter only when
25
25
  you need shared durable state. The API is synchronous; a private I/O thread
26
26
  bounds network waits. Model execution holds no database connection/transaction.
27
- The current candidate is `b3-prosaic-harness-postgres` **0.2.0**, selecting
28
- `b3-prosaic-harness>=0.7,<0.8`. The adapter is maintained in this repository but
27
+ The conversation candidate is `b3-prosaic-harness-postgres` **0.3.0**, selecting
28
+ `b3-prosaic-harness>=0.8,<0.9`. The adapter is maintained in this repository but
29
29
  installed and versioned separately, keeping PostgreSQL drivers optional for
30
30
  Harness users. Imports and CLI names remain unchanged. Final qualification and
31
- publication of the b3 release train are pending. The historical
31
+ publication of this conversation release train are pending. The historical
32
32
  [Harness v0.6.1 release](https://github.com/B3Cognition/prosaic-harness/releases/tag/v0.6.1)
33
33
  retains its original 0.6.1/0.1.1 package names and receipts.
34
34
 
@@ -49,7 +49,7 @@ prosaic-harness-postgres --help
49
49
 
50
50
  ALWAYS migrate using a fresh environment or image; NEVER install legacy and b3
51
51
  distributions together because they own the same import paths. See
52
- [package migration](../../docs/package-migration.md). The Runtime 0.8 candidate
52
+ [package migration](../../docs/package-migration.md). The Runtime 0.9 candidate
53
53
  must be available before this installation can resolve.
54
54
 
55
55
  Harness does **not** provision PostgreSQL, create databases/users, start a
@@ -220,3 +220,99 @@ session capacity atomically under the existing store mutex. A registering lease
220
220
  can no longer be counted twice while maintenance is scheduled. The database
221
221
  storage/fencing contract is unchanged. A unit scheduling-boundary regression and
222
222
  an owned PostgreSQL 16/18 race regression cover the transition.
223
+
224
+ ## Optional conversation sessions
225
+
226
+ The conversation candidate adds `PostgresSessionStore` to this same optional
227
+ distribution. It implements the public Harness `SessionStore` contract and uses
228
+ the independent, logged `prosaic_harness_conversations` schema, storage version 1.
229
+ Existing `PostgresRunStore` tables, grants and CLI `init`/`doctor` behavior remain
230
+ compatible; those CLI commands continue to address workflow storage.
231
+
232
+ Initialize conversation storage once with the schema owner's credentials:
233
+
234
+ ```python
235
+ from prosaic_harness_postgres import PostgresSessionStore
236
+
237
+ with PostgresSessionStore.from_env(
238
+ 'HARNESS_MIGRATION_DSN', namespace='my-chat'
239
+ ) as owner:
240
+ owner.initialize()
241
+ ```
242
+
243
+ Apply these grants as the owner, replacing `chat_runtime` with the existing
244
+ application role. Role, database and secret creation remain operator tasks.
245
+
246
+ ```sql
247
+ GRANT USAGE ON SCHEMA prosaic_harness_conversations TO chat_runtime;
248
+ GRANT SELECT ON prosaic_harness_conversations.schema_version,
249
+ prosaic_harness_conversations.leases,
250
+ prosaic_harness_conversations.sessions,
251
+ prosaic_harness_conversations.receipts TO chat_runtime;
252
+ GRANT INSERT, UPDATE ON prosaic_harness_conversations.leases,
253
+ prosaic_harness_conversations.sessions TO chat_runtime;
254
+ GRANT INSERT ON prosaic_harness_conversations.receipts TO chat_runtime;
255
+ ```
256
+
257
+ The runtime role needs no schema CREATE, schema-version UPDATE, receipt UPDATE
258
+ or DELETE. A role with only workflow grants can continue using `PostgresRunStore`
259
+ and cannot use conversation storage until it receives the separate grants.
260
+ Conversation readiness rejects forbidden effective grants on non-owner runtime
261
+ roles, including inherited and column-only grants. It also rejects schema-version
262
+ INSERT/DELETE and table DELETE/TRUNCATE privileges. Revoke accidental extra
263
+ permissions before accepting work.
264
+
265
+ Schema owners and roles that immediately inherit schema-owner privileges remain
266
+ administrative credentials: `initialize()` and operator `check_ready()` can
267
+ validate their installation. Those checks cannot make an owner a restricted
268
+ role. ALWAYS configure worker processes with separate non-owner runtime
269
+ credentials and verify their ownership/grants; NEVER use migration credentials
270
+ as the application's runtime DSN.
271
+ Readiness performs no migration or fallback:
272
+
273
+ ```python
274
+ from prosaic_harness import Conversation, ConversationScope
275
+ from prosaic_harness_postgres import PostgresSessionStore
276
+
277
+ scope = ConversationScope('my-chat', 'production', 'my-service', tenant, actor)
278
+ with PostgresSessionStore.from_env(
279
+ 'HARNESS_RUNTIME_DSN', namespace=scope.namespace
280
+ ) as sessions:
281
+ sessions.check_ready()
282
+ chat = Conversation(approved_bindings, store=sessions, scope=scope)
283
+ created = chat.create()
284
+ ```
285
+
286
+ Derive `tenant`, `actor` and any billing account from current authenticated host
287
+ state before each public operation. Scope ownership filters every session and
288
+ receipt read; leases, revisions and writes also bind to the exact scope and
289
+ address. A foreign owner sees `session_not_found`. The SQL role can still access
290
+ its granted tables directly: namespaces and scope hashes do not replace host
291
+ authorization or database row-level security.
292
+
293
+ Use the same sealed bindings and logical workspace/journal identities on every
294
+ worker. The default bounded pools and lease timings documented above apply.
295
+ Each operation checks the writable primary and WAL settings, acquires a short
296
+ transaction, then returns its connection. A conversation's model, tool and
297
+ linked workflow execution holds no transaction or checked-out connection.
298
+ Database generations fence late writes after ownership changes; they cannot
299
+ cancel an external effect already in progress.
300
+
301
+ Snapshots and immutable receipts use the common finite canonical UTF-8 session
302
+ codec, with at most 8 MiB each. Their addresses and receipts are checked against
303
+ allocated attempts, dispatch markers and the frozen scope/binding/input/context.
304
+ Exact receipt repeats are idempotent under a live lease; conflicts fail without
305
+ overwriting evidence. A lost COMMIT acknowledgement raises `store_unavailable`
306
+ with `outcome_unknown` and invalidates the lease. Recover by reading authoritative
307
+ state with fresh ownership; never blindly repeat inference or business effects.
308
+
309
+ The session policy bounds retained/reserved logical records. PostgreSQL indexes,
310
+ WAL, tool journals, replicas and backups add physical storage. The host must also
311
+ enforce retained-session quotas and own any explicit retention/deletion policy;
312
+ this adapter never automatically expires or deletes records.
313
+
314
+ The required adapter suite includes both the existing workflow backend and the
315
+ conversation backend. Run the owned PostgreSQL 16 and 18 fixtures shown above
316
+ with `--require-postgres`. Missing infrastructure or any skipped required case
317
+ fails qualification. Installed Linux AMD64 and ARM64 acceptance is required
318
+ before claiming the conversation candidate is ready for production.
@@ -13,6 +13,9 @@ src/prosaic_harness_postgres/__init__.py
13
13
  src/prosaic_harness_postgres/cli.py
14
14
  src/prosaic_harness_postgres/leases.py
15
15
  src/prosaic_harness_postgres/schema.py
16
+ src/prosaic_harness_postgres/session_leases.py
17
+ src/prosaic_harness_postgres/session_schema.py
18
+ src/prosaic_harness_postgres/session_store.py
16
19
  src/prosaic_harness_postgres/store.py
17
20
  src/prosaic_harness_postgres/transport.py
18
21
  tests/conftest.py
@@ -28,11 +31,18 @@ tests/test_recovery.py
28
31
  tests/test_registration.py
29
32
  tests/test_routing.py
30
33
  tests/test_schema.py
34
+ tests/test_session_fencing.py
35
+ tests/test_session_recovery.py
36
+ tests/test_session_schema.py
37
+ tests/test_session_store.py
38
+ tests/test_session_workers.py
31
39
  tests/test_store.py
32
40
  tests/test_transport.py
33
41
  tests/test_workers.py
34
42
  tests/support/linux_wheel_smoke.py
35
43
  tests/support/local_cluster.py
36
44
  tests/support/model_server.py
45
+ tests/support/session_documents.py
46
+ tests/support/session_workers.py
37
47
  tests/support/tcp_proxy.py
38
48
  tests/support/workers.py
@@ -1,4 +1,4 @@
1
- b3-prosaic-harness<0.8,>=0.7
1
+ b3-prosaic-harness<0.9,>=0.8
2
2
  psycopg[binary]<4,>=3.2.3
3
3
  psycopg-pool<4,>=3.2
4
4
 
@@ -0,0 +1,7 @@
1
+ """Separate PostgreSQL adapter for Prosaic Harness."""
2
+ __version__ = '0.3.0'
3
+
4
+ from .store import PostgresRunStore
5
+ from .session_store import PostgresSessionStore
6
+
7
+ __all__ = ['PostgresRunStore', 'PostgresSessionStore']
@@ -0,0 +1,10 @@
1
+ """Scoped conversation leases use the existing bounded renewal lifecycle."""
2
+ from .leases import PostgresLease
3
+
4
+
5
+ class PostgresSessionLease(PostgresLease):
6
+ def __init__(self, store, session_id, scope_content, scope_identity, started):
7
+ super().__init__(store, session_id, started)
8
+ self.session_id = session_id
9
+ self.scope_content = scope_content
10
+ self.scope_identity = scope_identity