b3-prosaic-harness-postgres 0.2.1__tar.gz → 0.4.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.
- {b3_prosaic_harness_postgres-0.2.1/src/b3_prosaic_harness_postgres.egg-info → b3_prosaic_harness_postgres-0.4.0}/PKG-INFO +122 -7
- {b3_prosaic_harness_postgres-0.2.1 → b3_prosaic_harness_postgres-0.4.0}/README.md +119 -4
- {b3_prosaic_harness_postgres-0.2.1 → b3_prosaic_harness_postgres-0.4.0}/pyproject.toml +3 -3
- {b3_prosaic_harness_postgres-0.2.1 → b3_prosaic_harness_postgres-0.4.0/src/b3_prosaic_harness_postgres.egg-info}/PKG-INFO +122 -7
- {b3_prosaic_harness_postgres-0.2.1 → b3_prosaic_harness_postgres-0.4.0}/src/b3_prosaic_harness_postgres.egg-info/SOURCES.txt +11 -0
- {b3_prosaic_harness_postgres-0.2.1 → b3_prosaic_harness_postgres-0.4.0}/src/b3_prosaic_harness_postgres.egg-info/requires.txt +1 -1
- b3_prosaic_harness_postgres-0.4.0/src/prosaic_harness_postgres/__init__.py +7 -0
- {b3_prosaic_harness_postgres-0.2.1 → b3_prosaic_harness_postgres-0.4.0}/src/prosaic_harness_postgres/cli.py +7 -1
- b3_prosaic_harness_postgres-0.4.0/src/prosaic_harness_postgres/session_leases.py +10 -0
- b3_prosaic_harness_postgres-0.4.0/src/prosaic_harness_postgres/session_schema.py +212 -0
- b3_prosaic_harness_postgres-0.4.0/src/prosaic_harness_postgres/session_store.py +326 -0
- {b3_prosaic_harness_postgres-0.2.1 → b3_prosaic_harness_postgres-0.4.0}/tests/support/linux_wheel_smoke.py +13 -1
- {b3_prosaic_harness_postgres-0.2.1 → b3_prosaic_harness_postgres-0.4.0}/tests/support/local_cluster.py +8 -5
- b3_prosaic_harness_postgres-0.4.0/tests/support/session_documents.py +66 -0
- b3_prosaic_harness_postgres-0.4.0/tests/support/session_workers.py +94 -0
- {b3_prosaic_harness_postgres-0.2.1 → b3_prosaic_harness_postgres-0.4.0}/tests/test_packaging.py +21 -8
- b3_prosaic_harness_postgres-0.4.0/tests/test_session_context.py +112 -0
- b3_prosaic_harness_postgres-0.4.0/tests/test_session_fencing.py +137 -0
- b3_prosaic_harness_postgres-0.4.0/tests/test_session_recovery.py +110 -0
- b3_prosaic_harness_postgres-0.4.0/tests/test_session_schema.py +230 -0
- b3_prosaic_harness_postgres-0.4.0/tests/test_session_store.py +160 -0
- b3_prosaic_harness_postgres-0.4.0/tests/test_session_workers.py +87 -0
- b3_prosaic_harness_postgres-0.2.1/src/prosaic_harness_postgres/__init__.py +0 -6
- {b3_prosaic_harness_postgres-0.2.1 → b3_prosaic_harness_postgres-0.4.0}/LICENSE +0 -0
- {b3_prosaic_harness_postgres-0.2.1 → b3_prosaic_harness_postgres-0.4.0}/MANIFEST.in +0 -0
- {b3_prosaic_harness_postgres-0.2.1 → b3_prosaic_harness_postgres-0.4.0}/NOTICE +0 -0
- {b3_prosaic_harness_postgres-0.2.1 → b3_prosaic_harness_postgres-0.4.0}/setup.cfg +0 -0
- {b3_prosaic_harness_postgres-0.2.1 → b3_prosaic_harness_postgres-0.4.0}/src/b3_prosaic_harness_postgres.egg-info/dependency_links.txt +0 -0
- {b3_prosaic_harness_postgres-0.2.1 → b3_prosaic_harness_postgres-0.4.0}/src/b3_prosaic_harness_postgres.egg-info/entry_points.txt +0 -0
- {b3_prosaic_harness_postgres-0.2.1 → b3_prosaic_harness_postgres-0.4.0}/src/b3_prosaic_harness_postgres.egg-info/top_level.txt +0 -0
- {b3_prosaic_harness_postgres-0.2.1 → b3_prosaic_harness_postgres-0.4.0}/src/prosaic_harness_postgres/leases.py +0 -0
- {b3_prosaic_harness_postgres-0.2.1 → b3_prosaic_harness_postgres-0.4.0}/src/prosaic_harness_postgres/schema.py +0 -0
- {b3_prosaic_harness_postgres-0.2.1 → b3_prosaic_harness_postgres-0.4.0}/src/prosaic_harness_postgres/store.py +0 -0
- {b3_prosaic_harness_postgres-0.2.1 → b3_prosaic_harness_postgres-0.4.0}/src/prosaic_harness_postgres/transport.py +0 -0
- {b3_prosaic_harness_postgres-0.2.1 → b3_prosaic_harness_postgres-0.4.0}/tests/conftest.py +0 -0
- {b3_prosaic_harness_postgres-0.2.1 → b3_prosaic_harness_postgres-0.4.0}/tests/support/model_server.py +0 -0
- {b3_prosaic_harness_postgres-0.2.1 → b3_prosaic_harness_postgres-0.4.0}/tests/support/tcp_proxy.py +0 -0
- {b3_prosaic_harness_postgres-0.2.1 → b3_prosaic_harness_postgres-0.4.0}/tests/support/workers.py +0 -0
- {b3_prosaic_harness_postgres-0.2.1 → b3_prosaic_harness_postgres-0.4.0}/tests/test_cli.py +0 -0
- {b3_prosaic_harness_postgres-0.2.1 → b3_prosaic_harness_postgres-0.4.0}/tests/test_durability.py +0 -0
- {b3_prosaic_harness_postgres-0.2.1 → b3_prosaic_harness_postgres-0.4.0}/tests/test_engine.py +0 -0
- {b3_prosaic_harness_postgres-0.2.1 → b3_prosaic_harness_postgres-0.4.0}/tests/test_examples.py +0 -0
- {b3_prosaic_harness_postgres-0.2.1 → b3_prosaic_harness_postgres-0.4.0}/tests/test_leases.py +0 -0
- {b3_prosaic_harness_postgres-0.2.1 → b3_prosaic_harness_postgres-0.4.0}/tests/test_local_cluster.py +0 -0
- {b3_prosaic_harness_postgres-0.2.1 → b3_prosaic_harness_postgres-0.4.0}/tests/test_native_factory.py +0 -0
- {b3_prosaic_harness_postgres-0.2.1 → b3_prosaic_harness_postgres-0.4.0}/tests/test_recovery.py +0 -0
- {b3_prosaic_harness_postgres-0.2.1 → b3_prosaic_harness_postgres-0.4.0}/tests/test_registration.py +0 -0
- {b3_prosaic_harness_postgres-0.2.1 → b3_prosaic_harness_postgres-0.4.0}/tests/test_routing.py +0 -0
- {b3_prosaic_harness_postgres-0.2.1 → b3_prosaic_harness_postgres-0.4.0}/tests/test_schema.py +0 -0
- {b3_prosaic_harness_postgres-0.2.1 → b3_prosaic_harness_postgres-0.4.0}/tests/test_store.py +0 -0
- {b3_prosaic_harness_postgres-0.2.1 → b3_prosaic_harness_postgres-0.4.0}/tests/test_transport.py +0 -0
- {b3_prosaic_harness_postgres-0.2.1 → b3_prosaic_harness_postgres-0.4.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.
|
|
4
|
-
Summary: Explicit PostgreSQL run storage for Prosaic Harness
|
|
3
|
+
Version: 0.4.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.
|
|
10
|
+
Requires-Dist: b3-prosaic-harness<0.10,>=0.9
|
|
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
|
|
28
|
-
`b3-prosaic-harness>=0.
|
|
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
|
|
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.
|
|
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,118 @@ 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.
|
|
319
|
+
|
|
320
|
+
## Optional context storage contract
|
|
321
|
+
|
|
322
|
+
Conversation storage contract 2 supports `context_assembly_v1`, reads both v1
|
|
323
|
+
and v2 records, and preserves existing documents. New database initialization
|
|
324
|
+
creates contract 2. Existing contract-1 databases require an explicit migration;
|
|
325
|
+
runtime readiness never performs it. Drain old writers, deploy upgraded workers,
|
|
326
|
+
then use migration-owner credentials:
|
|
327
|
+
|
|
328
|
+
```sh
|
|
329
|
+
prosaic-harness-postgres migrate-conversations --dsn-env HARNESS_MIGRATION_DSN
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
`PostgresSessionStore.migrate_context_storage()` is the equivalent operator API.
|
|
333
|
+
The migration is locked, transactional and repeatable; restricted runtime roles
|
|
334
|
+
cannot update schema metadata. Old workers reject the upgraded storage contract.
|
|
335
|
+
Keep migration-owner credentials separate from runtime credentials. Enable host
|
|
336
|
+
context policies only after Runtime, Harness and store advertise the capability.
|
|
337
|
+
No production token counter is bundled or certified by this storage adapter.
|
|
@@ -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
|
|
8
|
-
`b3-prosaic-harness>=0.
|
|
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
|
|
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.
|
|
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,118 @@ 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.
|
|
299
|
+
|
|
300
|
+
## Optional context storage contract
|
|
301
|
+
|
|
302
|
+
Conversation storage contract 2 supports `context_assembly_v1`, reads both v1
|
|
303
|
+
and v2 records, and preserves existing documents. New database initialization
|
|
304
|
+
creates contract 2. Existing contract-1 databases require an explicit migration;
|
|
305
|
+
runtime readiness never performs it. Drain old writers, deploy upgraded workers,
|
|
306
|
+
then use migration-owner credentials:
|
|
307
|
+
|
|
308
|
+
```sh
|
|
309
|
+
prosaic-harness-postgres migrate-conversations --dsn-env HARNESS_MIGRATION_DSN
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
`PostgresSessionStore.migrate_context_storage()` is the equivalent operator API.
|
|
313
|
+
The migration is locked, transactional and repeatable; restricted runtime roles
|
|
314
|
+
cannot update schema metadata. Old workers reject the upgraded storage contract.
|
|
315
|
+
Keep migration-owner credentials separate from runtime credentials. Enable host
|
|
316
|
+
context policies only after Runtime, Harness and store advertise the capability.
|
|
317
|
+
No production token counter is bundled or certified by this storage adapter.
|
|
@@ -4,13 +4,13 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "b3-prosaic-harness-postgres"
|
|
7
|
-
version = "0.
|
|
8
|
-
description = "Explicit PostgreSQL run storage for Prosaic Harness"
|
|
7
|
+
version = "0.4.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.
|
|
13
|
+
dependencies = ["b3-prosaic-harness>=0.9,<0.10", "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.
|
|
4
|
-
Summary: Explicit PostgreSQL run storage for Prosaic Harness
|
|
3
|
+
Version: 0.4.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.
|
|
10
|
+
Requires-Dist: b3-prosaic-harness<0.10,>=0.9
|
|
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
|
|
28
|
-
`b3-prosaic-harness>=0.
|
|
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
|
|
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.
|
|
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,118 @@ 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.
|
|
319
|
+
|
|
320
|
+
## Optional context storage contract
|
|
321
|
+
|
|
322
|
+
Conversation storage contract 2 supports `context_assembly_v1`, reads both v1
|
|
323
|
+
and v2 records, and preserves existing documents. New database initialization
|
|
324
|
+
creates contract 2. Existing contract-1 databases require an explicit migration;
|
|
325
|
+
runtime readiness never performs it. Drain old writers, deploy upgraded workers,
|
|
326
|
+
then use migration-owner credentials:
|
|
327
|
+
|
|
328
|
+
```sh
|
|
329
|
+
prosaic-harness-postgres migrate-conversations --dsn-env HARNESS_MIGRATION_DSN
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
`PostgresSessionStore.migrate_context_storage()` is the equivalent operator API.
|
|
333
|
+
The migration is locked, transactional and repeatable; restricted runtime roles
|
|
334
|
+
cannot update schema metadata. Old workers reject the upgraded storage contract.
|
|
335
|
+
Keep migration-owner credentials separate from runtime credentials. Enable host
|
|
336
|
+
context policies only after Runtime, Harness and store advertise the capability.
|
|
337
|
+
No production token counter is bundled or certified by this storage adapter.
|
|
@@ -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,19 @@ 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_context.py
|
|
35
|
+
tests/test_session_fencing.py
|
|
36
|
+
tests/test_session_recovery.py
|
|
37
|
+
tests/test_session_schema.py
|
|
38
|
+
tests/test_session_store.py
|
|
39
|
+
tests/test_session_workers.py
|
|
31
40
|
tests/test_store.py
|
|
32
41
|
tests/test_transport.py
|
|
33
42
|
tests/test_workers.py
|
|
34
43
|
tests/support/linux_wheel_smoke.py
|
|
35
44
|
tests/support/local_cluster.py
|
|
36
45
|
tests/support/model_server.py
|
|
46
|
+
tests/support/session_documents.py
|
|
47
|
+
tests/support/session_workers.py
|
|
37
48
|
tests/support/tcp_proxy.py
|
|
38
49
|
tests/support/workers.py
|
|
@@ -44,13 +44,19 @@ def check_write(store):
|
|
|
44
44
|
def main(argv=None):
|
|
45
45
|
parser = argparse.ArgumentParser(description='Initialize/check an existing Harness database')
|
|
46
46
|
commands = parser.add_subparsers(dest='command', required=True)
|
|
47
|
-
for name in ('init', 'doctor'):
|
|
47
|
+
for name in ('init', 'doctor', 'migrate-conversations'):
|
|
48
48
|
command = commands.add_parser(name)
|
|
49
49
|
command.add_argument('--dsn-env', required=True, help='name of the DSN environment variable, never its value')
|
|
50
50
|
if name == 'doctor':
|
|
51
51
|
command.add_argument('--check-write', action='store_true', help='explicit disposable write test; DELETE credentials required')
|
|
52
52
|
args = parser.parse_args(argv)
|
|
53
53
|
try:
|
|
54
|
+
if args.command == 'migrate-conversations':
|
|
55
|
+
from .session_store import PostgresSessionStore
|
|
56
|
+
with PostgresSessionStore.from_env(args.dsn_env, namespace='conversation-migration') as store:
|
|
57
|
+
store.migrate_context_storage()
|
|
58
|
+
print(json.dumps({'migrated': True, 'conversation_storage_contract': 2}))
|
|
59
|
+
return 0
|
|
54
60
|
namespace = 'prosaic-diagnostic-' + uuid.uuid4().hex
|
|
55
61
|
with PostgresRunStore.from_env(args.dsn_env, namespace=namespace) as store:
|
|
56
62
|
if args.command == 'init':
|
|
@@ -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
|