b3-prosaic-harness-postgres 0.3.0__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.3.0 → b3_prosaic_harness_postgres-0.4.0}/PKG-INFO +21 -2
- b3_prosaic_harness_postgres-0.3.0/src/b3_prosaic_harness_postgres.egg-info/PKG-INFO → b3_prosaic_harness_postgres-0.4.0/README.md +19 -20
- {b3_prosaic_harness_postgres-0.3.0 → b3_prosaic_harness_postgres-0.4.0}/pyproject.toml +2 -2
- b3_prosaic_harness_postgres-0.3.0/README.md → b3_prosaic_harness_postgres-0.4.0/src/b3_prosaic_harness_postgres.egg-info/PKG-INFO +39 -0
- {b3_prosaic_harness_postgres-0.3.0 → b3_prosaic_harness_postgres-0.4.0}/src/b3_prosaic_harness_postgres.egg-info/SOURCES.txt +1 -0
- {b3_prosaic_harness_postgres-0.3.0 → b3_prosaic_harness_postgres-0.4.0}/src/b3_prosaic_harness_postgres.egg-info/requires.txt +1 -1
- {b3_prosaic_harness_postgres-0.3.0 → b3_prosaic_harness_postgres-0.4.0}/src/prosaic_harness_postgres/__init__.py +1 -1
- {b3_prosaic_harness_postgres-0.3.0 → b3_prosaic_harness_postgres-0.4.0}/src/prosaic_harness_postgres/cli.py +7 -1
- {b3_prosaic_harness_postgres-0.3.0 → b3_prosaic_harness_postgres-0.4.0}/src/prosaic_harness_postgres/session_schema.py +31 -4
- {b3_prosaic_harness_postgres-0.3.0 → b3_prosaic_harness_postgres-0.4.0}/src/prosaic_harness_postgres/session_store.py +7 -0
- {b3_prosaic_harness_postgres-0.3.0 → b3_prosaic_harness_postgres-0.4.0}/tests/support/local_cluster.py +6 -5
- {b3_prosaic_harness_postgres-0.3.0 → b3_prosaic_harness_postgres-0.4.0}/tests/support/session_documents.py +26 -0
- {b3_prosaic_harness_postgres-0.3.0 → b3_prosaic_harness_postgres-0.4.0}/tests/test_packaging.py +7 -7
- b3_prosaic_harness_postgres-0.4.0/tests/test_session_context.py +112 -0
- {b3_prosaic_harness_postgres-0.3.0 → b3_prosaic_harness_postgres-0.4.0}/LICENSE +0 -0
- {b3_prosaic_harness_postgres-0.3.0 → b3_prosaic_harness_postgres-0.4.0}/MANIFEST.in +0 -0
- {b3_prosaic_harness_postgres-0.3.0 → b3_prosaic_harness_postgres-0.4.0}/NOTICE +0 -0
- {b3_prosaic_harness_postgres-0.3.0 → b3_prosaic_harness_postgres-0.4.0}/setup.cfg +0 -0
- {b3_prosaic_harness_postgres-0.3.0 → b3_prosaic_harness_postgres-0.4.0}/src/b3_prosaic_harness_postgres.egg-info/dependency_links.txt +0 -0
- {b3_prosaic_harness_postgres-0.3.0 → b3_prosaic_harness_postgres-0.4.0}/src/b3_prosaic_harness_postgres.egg-info/entry_points.txt +0 -0
- {b3_prosaic_harness_postgres-0.3.0 → b3_prosaic_harness_postgres-0.4.0}/src/b3_prosaic_harness_postgres.egg-info/top_level.txt +0 -0
- {b3_prosaic_harness_postgres-0.3.0 → b3_prosaic_harness_postgres-0.4.0}/src/prosaic_harness_postgres/leases.py +0 -0
- {b3_prosaic_harness_postgres-0.3.0 → b3_prosaic_harness_postgres-0.4.0}/src/prosaic_harness_postgres/schema.py +0 -0
- {b3_prosaic_harness_postgres-0.3.0 → b3_prosaic_harness_postgres-0.4.0}/src/prosaic_harness_postgres/session_leases.py +0 -0
- {b3_prosaic_harness_postgres-0.3.0 → b3_prosaic_harness_postgres-0.4.0}/src/prosaic_harness_postgres/store.py +0 -0
- {b3_prosaic_harness_postgres-0.3.0 → b3_prosaic_harness_postgres-0.4.0}/src/prosaic_harness_postgres/transport.py +0 -0
- {b3_prosaic_harness_postgres-0.3.0 → b3_prosaic_harness_postgres-0.4.0}/tests/conftest.py +0 -0
- {b3_prosaic_harness_postgres-0.3.0 → b3_prosaic_harness_postgres-0.4.0}/tests/support/linux_wheel_smoke.py +0 -0
- {b3_prosaic_harness_postgres-0.3.0 → b3_prosaic_harness_postgres-0.4.0}/tests/support/model_server.py +0 -0
- {b3_prosaic_harness_postgres-0.3.0 → b3_prosaic_harness_postgres-0.4.0}/tests/support/session_workers.py +0 -0
- {b3_prosaic_harness_postgres-0.3.0 → b3_prosaic_harness_postgres-0.4.0}/tests/support/tcp_proxy.py +0 -0
- {b3_prosaic_harness_postgres-0.3.0 → b3_prosaic_harness_postgres-0.4.0}/tests/support/workers.py +0 -0
- {b3_prosaic_harness_postgres-0.3.0 → b3_prosaic_harness_postgres-0.4.0}/tests/test_cli.py +0 -0
- {b3_prosaic_harness_postgres-0.3.0 → b3_prosaic_harness_postgres-0.4.0}/tests/test_durability.py +0 -0
- {b3_prosaic_harness_postgres-0.3.0 → b3_prosaic_harness_postgres-0.4.0}/tests/test_engine.py +0 -0
- {b3_prosaic_harness_postgres-0.3.0 → b3_prosaic_harness_postgres-0.4.0}/tests/test_examples.py +0 -0
- {b3_prosaic_harness_postgres-0.3.0 → b3_prosaic_harness_postgres-0.4.0}/tests/test_leases.py +0 -0
- {b3_prosaic_harness_postgres-0.3.0 → b3_prosaic_harness_postgres-0.4.0}/tests/test_local_cluster.py +0 -0
- {b3_prosaic_harness_postgres-0.3.0 → b3_prosaic_harness_postgres-0.4.0}/tests/test_native_factory.py +0 -0
- {b3_prosaic_harness_postgres-0.3.0 → b3_prosaic_harness_postgres-0.4.0}/tests/test_recovery.py +0 -0
- {b3_prosaic_harness_postgres-0.3.0 → b3_prosaic_harness_postgres-0.4.0}/tests/test_registration.py +0 -0
- {b3_prosaic_harness_postgres-0.3.0 → b3_prosaic_harness_postgres-0.4.0}/tests/test_routing.py +0 -0
- {b3_prosaic_harness_postgres-0.3.0 → b3_prosaic_harness_postgres-0.4.0}/tests/test_schema.py +0 -0
- {b3_prosaic_harness_postgres-0.3.0 → b3_prosaic_harness_postgres-0.4.0}/tests/test_session_fencing.py +0 -0
- {b3_prosaic_harness_postgres-0.3.0 → b3_prosaic_harness_postgres-0.4.0}/tests/test_session_recovery.py +0 -0
- {b3_prosaic_harness_postgres-0.3.0 → b3_prosaic_harness_postgres-0.4.0}/tests/test_session_schema.py +0 -0
- {b3_prosaic_harness_postgres-0.3.0 → b3_prosaic_harness_postgres-0.4.0}/tests/test_session_store.py +0 -0
- {b3_prosaic_harness_postgres-0.3.0 → b3_prosaic_harness_postgres-0.4.0}/tests/test_session_workers.py +0 -0
- {b3_prosaic_harness_postgres-0.3.0 → b3_prosaic_harness_postgres-0.4.0}/tests/test_store.py +0 -0
- {b3_prosaic_harness_postgres-0.3.0 → b3_prosaic_harness_postgres-0.4.0}/tests/test_transport.py +0 -0
- {b3_prosaic_harness_postgres-0.3.0 → 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.
|
|
3
|
+
Version: 0.4.0
|
|
4
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
|
|
@@ -316,3 +316,22 @@ conversation backend. Run the owned PostgreSQL 16 and 18 fixtures shown above
|
|
|
316
316
|
with `--require-postgres`. Missing infrastructure or any skipped required case
|
|
317
317
|
fails qualification. Installed Linux AMD64 and ARM64 acceptance is required
|
|
318
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.
|
|
@@ -1,23 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: b3-prosaic-harness-postgres
|
|
3
|
-
Version: 0.3.0
|
|
4
|
-
Summary: Explicit PostgreSQL run and conversation storage for Prosaic Harness
|
|
5
|
-
License-Expression: Apache-2.0
|
|
6
|
-
Requires-Python: >=3.11
|
|
7
|
-
Description-Content-Type: text/markdown
|
|
8
|
-
License-File: LICENSE
|
|
9
|
-
License-File: NOTICE
|
|
10
|
-
Requires-Dist: b3-prosaic-harness<0.9,>=0.8
|
|
11
|
-
Requires-Dist: psycopg[binary]<4,>=3.2.3
|
|
12
|
-
Requires-Dist: psycopg-pool<4,>=3.2
|
|
13
|
-
Provides-Extra: test
|
|
14
|
-
Requires-Dist: pytest>=8; extra == "test"
|
|
15
|
-
Requires-Dist: build>=1; extra == "test"
|
|
16
|
-
Requires-Dist: setuptools>=77; extra == "test"
|
|
17
|
-
Requires-Dist: wheel; extra == "test"
|
|
18
|
-
Requires-Dist: packaging; extra == "test"
|
|
19
|
-
Dynamic: license-file
|
|
20
|
-
|
|
21
1
|
# PostgreSQL persistence for Prosaic Harness
|
|
22
2
|
|
|
23
3
|
Run on worker A, inspect/resume on worker B, without a shared writable run
|
|
@@ -316,3 +296,22 @@ conversation backend. Run the owned PostgreSQL 16 and 18 fixtures shown above
|
|
|
316
296
|
with `--require-postgres`. Missing infrastructure or any skipped required case
|
|
317
297
|
fails qualification. Installed Linux AMD64 and ARM64 acceptance is required
|
|
318
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.
|
|
7
|
+
version = "0.4.0"
|
|
8
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,3 +1,23 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: b3-prosaic-harness-postgres
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: Explicit PostgreSQL run and conversation storage for Prosaic Harness
|
|
5
|
+
License-Expression: Apache-2.0
|
|
6
|
+
Requires-Python: >=3.11
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
License-File: NOTICE
|
|
10
|
+
Requires-Dist: b3-prosaic-harness<0.10,>=0.9
|
|
11
|
+
Requires-Dist: psycopg[binary]<4,>=3.2.3
|
|
12
|
+
Requires-Dist: psycopg-pool<4,>=3.2
|
|
13
|
+
Provides-Extra: test
|
|
14
|
+
Requires-Dist: pytest>=8; extra == "test"
|
|
15
|
+
Requires-Dist: build>=1; extra == "test"
|
|
16
|
+
Requires-Dist: setuptools>=77; extra == "test"
|
|
17
|
+
Requires-Dist: wheel; extra == "test"
|
|
18
|
+
Requires-Dist: packaging; extra == "test"
|
|
19
|
+
Dynamic: license-file
|
|
20
|
+
|
|
1
21
|
# PostgreSQL persistence for Prosaic Harness
|
|
2
22
|
|
|
3
23
|
Run on worker A, inspect/resume on worker B, without a shared writable run
|
|
@@ -296,3 +316,22 @@ conversation backend. Run the owned PostgreSQL 16 and 18 fixtures shown above
|
|
|
296
316
|
with `--require-postgres`. Missing infrastructure or any skipped required case
|
|
297
317
|
fails qualification. Installed Linux AMD64 and ARM64 acceptance is required
|
|
298
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.
|
|
@@ -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':
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Independent, explicit version-1 conversation storage and readiness."""
|
|
2
2
|
from prosaic_harness import StoreUninitialized, StoreIncompatible
|
|
3
3
|
|
|
4
|
-
SCHEMA_VERSION = STORAGE_CONTRACT_VERSION =
|
|
4
|
+
SCHEMA_VERSION = STORAGE_CONTRACT_VERSION = 2
|
|
5
5
|
MIGRATION_LOCK = 0x50524348415401
|
|
6
6
|
SCHEMA = 'prosaic_harness_conversations'
|
|
7
7
|
|
|
@@ -33,7 +33,7 @@ CREATE TABLE prosaic_harness_conversations.receipts (
|
|
|
33
33
|
PRIMARY KEY(namespace, session_id, invocation_id),
|
|
34
34
|
FOREIGN KEY(namespace, session_id, scope_identity) REFERENCES
|
|
35
35
|
prosaic_harness_conversations.sessions(namespace, session_id, scope_identity));
|
|
36
|
-
INSERT INTO prosaic_harness_conversations.schema_version VALUES(TRUE,
|
|
36
|
+
INSERT INTO prosaic_harness_conversations.schema_version VALUES(TRUE, 2, 2);
|
|
37
37
|
"""
|
|
38
38
|
|
|
39
39
|
COLUMNS = {
|
|
@@ -69,7 +69,7 @@ CONSTRAINTS = {
|
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
|
|
72
|
-
async def ready_on_connection(conn):
|
|
72
|
+
async def ready_on_connection(conn, *, expected_versions=None):
|
|
73
73
|
namespace = await (await conn.execute("SELECT oid,pg_catalog.has_schema_privilege(oid,'USAGE'), "
|
|
74
74
|
"pg_catalog.pg_has_role(nspowner,'USAGE') "
|
|
75
75
|
"FROM pg_catalog.pg_namespace WHERE nspname=%s", (SCHEMA,))).fetchone()
|
|
@@ -154,7 +154,7 @@ async def ready_on_connection(conn):
|
|
|
154
154
|
raise StoreIncompatible('use least-privilege conversation runtime grants')
|
|
155
155
|
versions = await (await conn.execute('SELECT schema_version,storage_contract_version '
|
|
156
156
|
'FROM prosaic_harness_conversations.schema_version')).fetchall()
|
|
157
|
-
if versions != [(SCHEMA_VERSION, STORAGE_CONTRACT_VERSION)]:
|
|
157
|
+
if versions != [expected_versions or (SCHEMA_VERSION, STORAGE_CONTRACT_VERSION)]:
|
|
158
158
|
raise StoreIncompatible('conversation schema version is incompatible; no automatic migration')
|
|
159
159
|
|
|
160
160
|
|
|
@@ -183,3 +183,30 @@ def initialize(transport):
|
|
|
183
183
|
await conn.execute(DDL)
|
|
184
184
|
await ready_on_connection(conn)
|
|
185
185
|
transport.call(migrate, timeout_s=30)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def migrate_context_storage(transport):
|
|
189
|
+
async def migrate(conn):
|
|
190
|
+
await conn.execute("SELECT pg_catalog.set_config('lock_timeout','25000ms',true), "
|
|
191
|
+
"pg_catalog.set_config('statement_timeout','28000ms',true)")
|
|
192
|
+
await conn.execute('SELECT pg_catalog.pg_advisory_xact_lock(%s)', (MIGRATION_LOCK,))
|
|
193
|
+
namespace = await (await conn.execute("SELECT pg_catalog.pg_has_role(nspowner,'USAGE') "
|
|
194
|
+
'FROM pg_catalog.pg_namespace WHERE nspname=%s', (SCHEMA,))).fetchone()
|
|
195
|
+
if namespace is None:
|
|
196
|
+
raise StoreUninitialized('initialize conversation storage explicitly first')
|
|
197
|
+
if not namespace[0]:
|
|
198
|
+
raise StoreIncompatible('migration role must own conversation schema')
|
|
199
|
+
versions = await (await conn.execute('SELECT schema_version,storage_contract_version '
|
|
200
|
+
'FROM prosaic_harness_conversations.schema_version')).fetchall()
|
|
201
|
+
if versions == [(2, 2)]:
|
|
202
|
+
await ready_on_connection(conn)
|
|
203
|
+
return
|
|
204
|
+
if versions != [(1, 1)]:
|
|
205
|
+
raise StoreIncompatible('conversation migration requires intact version-1 storage')
|
|
206
|
+
await ready_on_connection(conn, expected_versions=(1, 1))
|
|
207
|
+
cursor = await conn.execute('UPDATE prosaic_harness_conversations.schema_version '
|
|
208
|
+
'SET schema_version=2,storage_contract_version=2 WHERE singleton IS TRUE')
|
|
209
|
+
if cursor.rowcount != 1:
|
|
210
|
+
raise StoreIncompatible('conversation version metadata is incompatible')
|
|
211
|
+
await ready_on_connection(conn)
|
|
212
|
+
transport.call(migrate, timeout_s=30)
|
|
@@ -60,6 +60,13 @@ class PostgresSessionStore:
|
|
|
60
60
|
|
|
61
61
|
def check_ready(self):
|
|
62
62
|
session_schema.check_ready(self._transport)
|
|
63
|
+
@property
|
|
64
|
+
def capabilities(self):
|
|
65
|
+
self.check_ready()
|
|
66
|
+
return frozenset({'context_assembly_v1'})
|
|
67
|
+
|
|
68
|
+
def migrate_context_storage(self):
|
|
69
|
+
session_schema.migrate_context_storage(self._transport)
|
|
63
70
|
|
|
64
71
|
def _scope(self, scope):
|
|
65
72
|
if type(scope) is not ConversationScope or scope.namespace != self.namespace:
|
|
@@ -40,7 +40,7 @@ def candidate_wheels(root):
|
|
|
40
40
|
spec.loader.exec_module(release)
|
|
41
41
|
release.verify_upstream(Path(receipt_directory).resolve(strict=True), wheelhouse)
|
|
42
42
|
return [wheelhouse / 'b3_prosaic-0.4.1-py3-none-any.whl',
|
|
43
|
-
wheelhouse / 'b3_prosaic_runtime-0.9.
|
|
43
|
+
wheelhouse / 'b3_prosaic_runtime-0.9.4-py3-none-any.whl']
|
|
44
44
|
|
|
45
45
|
|
|
46
46
|
class LocalCluster:
|
|
@@ -177,12 +177,12 @@ class LocalCluster:
|
|
|
177
177
|
set -eu
|
|
178
178
|
apt-get update -qq
|
|
179
179
|
python -m venv /tmp/core
|
|
180
|
-
/tmp/core/bin/python -m pip install /core-wheels/b3_prosaic_harness-0.
|
|
180
|
+
/tmp/core/bin/python -m pip install /core-wheels/b3_prosaic_harness-0.9.0-py3-none-any.whl >/dev/null
|
|
181
181
|
/tmp/core/bin/python -I -c 'import importlib.util,prosaic_harness; assert importlib.util.find_spec("psycopg") is None; assert importlib.util.find_spec("prosaic_harness_postgres") is None'
|
|
182
182
|
/tmp/core/bin/python -I /fixture/workflow_factory_smoke.py
|
|
183
183
|
/tmp/core/bin/python -I /fixture/conversation_smoke.py
|
|
184
184
|
python -m venv /tmp/both
|
|
185
|
-
/tmp/both/bin/python -m pip install /core-wheels/b3_prosaic_harness-0.
|
|
185
|
+
/tmp/both/bin/python -m pip install /core-wheels/b3_prosaic_harness-0.9.0-py3-none-any.whl /adapter-wheels/b3_prosaic_harness_postgres-0.4.0-py3-none-any.whl >/dev/null
|
|
186
186
|
/tmp/both/bin/python -I /fixture/linux_wheel_smoke.py
|
|
187
187
|
'''
|
|
188
188
|
if dependencies:
|
|
@@ -266,12 +266,13 @@ def main():
|
|
|
266
266
|
cluster.stop()
|
|
267
267
|
else:
|
|
268
268
|
env = os.environ | {'HARNESS_TEST_DATABASE_URL': cluster.dsn,
|
|
269
|
-
'HARNESS_TEST_CONTAINER_ENGINE': cluster.engine, 'HARNESS_TEST_POSTGRES_VERSION': cluster.version
|
|
269
|
+
'HARNESS_TEST_CONTAINER_ENGINE': cluster.engine, 'HARNESS_TEST_POSTGRES_VERSION': cluster.version,
|
|
270
|
+
'PROSAIC_LIFECYCLE_TEST_DSN': cluster.dsn}
|
|
270
271
|
root = Path(__file__).resolve().parents[4]
|
|
271
272
|
report = (['--junitxml=' + env['HARNESS_TEST_JUNIT_XML']]
|
|
272
273
|
if env.get('HARNESS_TEST_JUNIT_XML') else [])
|
|
273
274
|
raise SystemExit(subprocess.run([sys.executable, '-m', 'pytest', '-q',
|
|
274
|
-
'adapters/postgres/tests', '--require-postgres', *report],
|
|
275
|
+
'adapters/postgres/tests', 'tests/test_conversation_postgres_lifecycle.py', '--require-postgres', *report],
|
|
275
276
|
cwd=root, env=env, timeout=600).returncode)
|
|
276
277
|
|
|
277
278
|
|
|
@@ -38,3 +38,29 @@ def receipt(state, *, invocation_id='b' * 32, failure=None, uncertain=False):
|
|
|
38
38
|
result = TurnResult(Result(1 if failure else 0, '' if failure else 'Done', failure or ''),
|
|
39
39
|
completed, evidence)
|
|
40
40
|
return make_receipt(state, invocation_id, result)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def context_document(scope, *, session_id='a'*32):
|
|
44
|
+
from prosaic_runtime import (ContextPolicy, TokenCounterDescriptor,
|
|
45
|
+
TokenCounterRegistry, TokenCounterRegistration)
|
|
46
|
+
base = bindings()
|
|
47
|
+
class Counter:
|
|
48
|
+
def count_tokens(self, request): return 10
|
|
49
|
+
descriptor = TokenCounterDescriptor('fixture-v1', 'openai-compatible',
|
|
50
|
+
'synthetic', 'openai-chat-v1', 4096, '1', '0'*64, '1'*64,
|
|
51
|
+
'exact', 'Synthetic fixed fixture grammar only.')
|
|
52
|
+
counters = TokenCounterRegistry({'fixture-v1': TokenCounterRegistration(descriptor, Counter())})
|
|
53
|
+
policy = ContextPolicy('fullHistory', 4096, 128, 0, 'fixture-v1')
|
|
54
|
+
binding = ConversationBindings(base.agent, version='context-v1', config=base.config,
|
|
55
|
+
policy=base.policy, run_policy=base.run_policy,
|
|
56
|
+
context_policy=policy, token_counters=counters)
|
|
57
|
+
state = new_session(session_id, scope, binding)
|
|
58
|
+
prepared = binding.runtime().prepare_turn(binding.agent, message='Hello ž',
|
|
59
|
+
policy=binding.run_policy, limits=binding.turn_limits,
|
|
60
|
+
context_policy=policy, token_counters=counters)
|
|
61
|
+
state = reserve_attempt(state, message_id='message', message='Hello ž',
|
|
62
|
+
invocation_id='b'*32, context_identity=prepared.context_identity,
|
|
63
|
+
encoding_version=prepared.encoding_version, input_bytes=prepared.input_bytes,
|
|
64
|
+
provider_allowance=2, tool_allowance=0, context_selection=prepared.context_selection,
|
|
65
|
+
context_report=prepared.context_report, context_work=prepared.context_work)
|
|
66
|
+
return state, prepared
|
{b3_prosaic_harness_postgres-0.3.0 → b3_prosaic_harness_postgres-0.4.0}/tests/test_packaging.py
RENAMED
|
@@ -13,8 +13,8 @@ import pytest
|
|
|
13
13
|
import yaml
|
|
14
14
|
|
|
15
15
|
ROOT = Path(__file__).resolve().parents[3]
|
|
16
|
-
CORE = ROOT / 'dist/b3_prosaic_harness-0.
|
|
17
|
-
ADAPTER = ROOT / 'adapters/postgres/dist/b3_prosaic_harness_postgres-0.
|
|
16
|
+
CORE = ROOT / 'dist/b3_prosaic_harness-0.9.0-py3-none-any.whl'
|
|
17
|
+
ADAPTER = ROOT / 'adapters/postgres/dist/b3_prosaic_harness_postgres-0.4.0-py3-none-any.whl'
|
|
18
18
|
|
|
19
19
|
def test_owned_fixture_uses_free_explicit_subnet_when_default_pool_is_exhausted():
|
|
20
20
|
from support.local_cluster import owned_subnet
|
|
@@ -73,13 +73,13 @@ def metadata(path):
|
|
|
73
73
|
def test_wheel_contents_and_dependency_boundaries():
|
|
74
74
|
core, names = metadata(CORE)
|
|
75
75
|
assert core['Name'] == 'b3-prosaic-harness'
|
|
76
|
-
assert core['Version'] == '0.
|
|
76
|
+
assert core['Version'] == '0.9.0'
|
|
77
77
|
assert not any(name.startswith('prosaic_harness_postgres/') for name in names)
|
|
78
78
|
assert not any('psycopg' in value.lower() for value in core.get_all('Requires-Dist'))
|
|
79
79
|
adapter, names = metadata(ADAPTER)
|
|
80
80
|
assert adapter['Name'] == 'b3-prosaic-harness-postgres'
|
|
81
|
-
assert adapter['Version'] == '0.
|
|
82
|
-
assert any(value.startswith('b3-prosaic-harness<0.
|
|
81
|
+
assert adapter['Version'] == '0.4.0'
|
|
82
|
+
assert any(value.startswith('b3-prosaic-harness<0.10,>=0.9') for value in adapter.get_all('Requires-Dist'))
|
|
83
83
|
assert 'prosaic_harness_postgres/cli.py' in names
|
|
84
84
|
assert any(value.startswith('psycopg[binary]<4,>=3.2.3')
|
|
85
85
|
for value in adapter.get_all('Requires-Dist'))
|
|
@@ -184,7 +184,7 @@ def curated_candidates(release_tool, tmp_path):
|
|
|
184
184
|
candidates = []
|
|
185
185
|
for repo, expected in (
|
|
186
186
|
('B3Cognition/prosaic', {'b3-prosaic': '0.4.1'}),
|
|
187
|
-
('B3Cognition/prosaic-runtime', {'b3-prosaic-runtime': '0.9.
|
|
187
|
+
('B3Cognition/prosaic-runtime', {'b3-prosaic-runtime': '0.9.4',
|
|
188
188
|
'b3-prosaic-runtime-postgres': '0.3.0'})):
|
|
189
189
|
artifacts = []
|
|
190
190
|
for name, version in expected.items():
|
|
@@ -252,4 +252,4 @@ def test_container_mounts_verified_dependency_bytes_read_only(
|
|
|
252
252
|
create = calls[0]
|
|
253
253
|
assert str(wheelhouse) + ':/candidate-wheels:ro' in create
|
|
254
254
|
assert 'PIP_FIND_LINKS=/candidate-wheels' in create[-1]
|
|
255
|
-
assert '/candidate-wheels/b3_prosaic_runtime-0.9.
|
|
255
|
+
assert '/candidate-wheels/b3_prosaic_runtime-0.9.4-py3-none-any.whl' in create[-1]
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"""Owner-only explicit v1->v2 migration; real PostgreSQL transactional evidence."""
|
|
2
|
+
import pytest
|
|
3
|
+
import psycopg
|
|
4
|
+
from prosaic_harness import StoreIncompatible, ConversationScope
|
|
5
|
+
from prosaic_harness.session_contracts import new_session
|
|
6
|
+
import prosaic_harness_postgres as pg
|
|
7
|
+
from prosaic_harness_postgres import session_schema
|
|
8
|
+
from test_session_schema import grant_conversations
|
|
9
|
+
from support.session_documents import bindings
|
|
10
|
+
from dataclasses import replace
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def legacy_storage(db_admin_dsn):
|
|
14
|
+
with pg.PostgresSessionStore(db_admin_dsn, namespace='context-test') as owner:
|
|
15
|
+
owner.initialize()
|
|
16
|
+
# Table/role contract is identical in v1; only explicit contract metadata changes.
|
|
17
|
+
with psycopg.connect(db_admin_dsn) as connection:
|
|
18
|
+
connection.execute('UPDATE prosaic_harness_conversations.schema_version '
|
|
19
|
+
'SET schema_version=1,storage_contract_version=1')
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_owner_migration_is_explicit_repeatable_and_keeps_v1_records(db_admin_dsn):
|
|
23
|
+
scope = ConversationScope('context-test', 'sandbox', 'app', 'tenant', 'actor')
|
|
24
|
+
state = new_session('a'*32, scope, bindings())
|
|
25
|
+
with pg.PostgresSessionStore(db_admin_dsn, namespace='context-test') as owner:
|
|
26
|
+
owner.initialize()
|
|
27
|
+
with owner.lease('a'*32, scope=scope) as lease:
|
|
28
|
+
owner.create_session('a'*32, state, lease, scope=scope)
|
|
29
|
+
before = owner.load_session('a'*32, scope=scope)
|
|
30
|
+
with psycopg.connect(db_admin_dsn) as connection:
|
|
31
|
+
connection.execute('UPDATE prosaic_harness_conversations.schema_version '
|
|
32
|
+
'SET schema_version=1,storage_contract_version=1')
|
|
33
|
+
with pg.PostgresSessionStore(db_admin_dsn, namespace='context-test') as owner:
|
|
34
|
+
with pytest.raises(StoreIncompatible): owner.check_ready()
|
|
35
|
+
with pytest.raises(StoreIncompatible): owner.initialize()
|
|
36
|
+
owner.migrate_context_storage()
|
|
37
|
+
owner.migrate_context_storage()
|
|
38
|
+
assert 'context_assembly_v1' in owner.capabilities
|
|
39
|
+
assert owner.load_session('a'*32, scope=scope) == before
|
|
40
|
+
with pytest.raises(StoreIncompatible):
|
|
41
|
+
owner._transport.call(lambda connection:
|
|
42
|
+
session_schema.ready_on_connection(connection, expected_versions=(1, 1)))
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_runtime_role_cannot_migrate_or_gain_owner_privileges(db_admin_dsn, runtime_dsn):
|
|
46
|
+
legacy_storage(db_admin_dsn)
|
|
47
|
+
grant_conversations(db_admin_dsn, runtime_dsn)
|
|
48
|
+
with pg.PostgresSessionStore(runtime_dsn, namespace='context-test') as runtime:
|
|
49
|
+
with pytest.raises(StoreIncompatible): runtime.migrate_context_storage()
|
|
50
|
+
with psycopg.connect(db_admin_dsn) as connection:
|
|
51
|
+
assert connection.execute('SELECT schema_version,storage_contract_version '
|
|
52
|
+
'FROM prosaic_harness_conversations.schema_version').fetchone() == (1, 1)
|
|
53
|
+
with pg.PostgresSessionStore(db_admin_dsn, namespace='context-test') as owner:
|
|
54
|
+
owner.migrate_context_storage()
|
|
55
|
+
with pg.PostgresSessionStore(runtime_dsn, namespace='context-test') as runtime:
|
|
56
|
+
assert 'context_assembly_v1' in runtime.capabilities
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def test_v2_checkpoint_is_fenced_and_owner_filtered(db_admin_dsn, runtime_dsn):
|
|
60
|
+
from prosaic_runtime import ContextWorkState
|
|
61
|
+
from prosaic_harness import LeaseLost, SessionNotFound
|
|
62
|
+
from prosaic_harness.session_context import checkpoint_context_work
|
|
63
|
+
from support.session_documents import context_document
|
|
64
|
+
scope = ConversationScope('context-test', 'sandbox', 'app', 'tenant', 'actor')
|
|
65
|
+
with pg.PostgresSessionStore(db_admin_dsn, namespace='context-test') as owner:
|
|
66
|
+
owner.initialize()
|
|
67
|
+
grant_conversations(db_admin_dsn, runtime_dsn)
|
|
68
|
+
state, prepared = context_document(scope)
|
|
69
|
+
with pg.PostgresSessionStore(runtime_dsn, namespace='context-test') as first, \
|
|
70
|
+
pg.PostgresSessionStore(runtime_dsn, namespace='context-test') as replica:
|
|
71
|
+
with first.lease('a'*32, scope=scope) as lease:
|
|
72
|
+
revision = first.create_session('a'*32, state, lease, scope=scope)
|
|
73
|
+
before = prepared.context_work.to_dict()
|
|
74
|
+
work = ContextWorkState.from_dict({**before, 'checkpointSequence': 1,
|
|
75
|
+
'reservedActiveMicros': 2_000_000 - before['activeAssemblyMicros']})
|
|
76
|
+
changed = checkpoint_context_work(state, 'b'*32, work)
|
|
77
|
+
current = first.save_session('a'*32, changed, revision, lease, scope=scope)
|
|
78
|
+
assert replica.load_session('a'*32, scope=scope).state == changed
|
|
79
|
+
with pytest.raises(SessionNotFound):
|
|
80
|
+
replica.load_session('a'*32, scope=replace(scope, actor='foreign'))
|
|
81
|
+
with psycopg.connect(db_admin_dsn) as admin:
|
|
82
|
+
admin.execute('UPDATE prosaic_harness_conversations.leases '
|
|
83
|
+
'SET generation=generation+1 WHERE namespace=%s AND session_id=%s',
|
|
84
|
+
('context-test', 'a'*32))
|
|
85
|
+
with pytest.raises(LeaseLost):
|
|
86
|
+
first.save_session('a'*32, changed, current, lease, scope=scope)
|
|
87
|
+
def test_failed_postupdate_validation_rolls_back_metadata(db_admin_dsn, monkeypatch):
|
|
88
|
+
legacy_storage(db_admin_dsn)
|
|
89
|
+
original = session_schema.ready_on_connection
|
|
90
|
+
async def reject_v2(connection, **kwargs):
|
|
91
|
+
row = await (await connection.execute('SELECT schema_version FROM '
|
|
92
|
+
'prosaic_harness_conversations.schema_version')).fetchone()
|
|
93
|
+
if row == (2,): raise StoreIncompatible('synthetic postupdate validation failure')
|
|
94
|
+
return await original(connection, **kwargs)
|
|
95
|
+
monkeypatch.setattr(session_schema, 'ready_on_connection', reject_v2)
|
|
96
|
+
with pg.PostgresSessionStore(db_admin_dsn, namespace='context-test') as owner:
|
|
97
|
+
with pytest.raises(StoreIncompatible): owner.migrate_context_storage()
|
|
98
|
+
with psycopg.connect(db_admin_dsn) as connection:
|
|
99
|
+
assert connection.execute('SELECT schema_version,storage_contract_version '
|
|
100
|
+
'FROM prosaic_harness_conversations.schema_version').fetchone() == (1, 1)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def test_operator_cli_migrates_without_exposing_connection_details(db_admin_dsn, monkeypatch, capsys):
|
|
104
|
+
import json
|
|
105
|
+
from prosaic_harness_postgres.cli import main
|
|
106
|
+
legacy_storage(db_admin_dsn)
|
|
107
|
+
monkeypatch.setenv('CONTEXT_TEST_DSN', db_admin_dsn)
|
|
108
|
+
assert main(['migrate-conversations', '--dsn-env', 'CONTEXT_TEST_DSN']) == 0
|
|
109
|
+
output = capsys.readouterr()
|
|
110
|
+
assert json.loads(output.out) == {'migrated': True, 'conversation_storage_contract': 2}
|
|
111
|
+
assert output.err == ''
|
|
112
|
+
assert db_admin_dsn not in output.out
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{b3_prosaic_harness_postgres-0.3.0 → b3_prosaic_harness_postgres-0.4.0}/tests/support/tcp_proxy.py
RENAMED
|
File without changes
|
{b3_prosaic_harness_postgres-0.3.0 → b3_prosaic_harness_postgres-0.4.0}/tests/support/workers.py
RENAMED
|
File without changes
|
|
File without changes
|
{b3_prosaic_harness_postgres-0.3.0 → b3_prosaic_harness_postgres-0.4.0}/tests/test_durability.py
RENAMED
|
File without changes
|
{b3_prosaic_harness_postgres-0.3.0 → b3_prosaic_harness_postgres-0.4.0}/tests/test_engine.py
RENAMED
|
File without changes
|
{b3_prosaic_harness_postgres-0.3.0 → b3_prosaic_harness_postgres-0.4.0}/tests/test_examples.py
RENAMED
|
File without changes
|
{b3_prosaic_harness_postgres-0.3.0 → b3_prosaic_harness_postgres-0.4.0}/tests/test_leases.py
RENAMED
|
File without changes
|
{b3_prosaic_harness_postgres-0.3.0 → b3_prosaic_harness_postgres-0.4.0}/tests/test_local_cluster.py
RENAMED
|
File without changes
|
{b3_prosaic_harness_postgres-0.3.0 → b3_prosaic_harness_postgres-0.4.0}/tests/test_native_factory.py
RENAMED
|
File without changes
|
{b3_prosaic_harness_postgres-0.3.0 → b3_prosaic_harness_postgres-0.4.0}/tests/test_recovery.py
RENAMED
|
File without changes
|
{b3_prosaic_harness_postgres-0.3.0 → b3_prosaic_harness_postgres-0.4.0}/tests/test_registration.py
RENAMED
|
File without changes
|
{b3_prosaic_harness_postgres-0.3.0 → b3_prosaic_harness_postgres-0.4.0}/tests/test_routing.py
RENAMED
|
File without changes
|
{b3_prosaic_harness_postgres-0.3.0 → b3_prosaic_harness_postgres-0.4.0}/tests/test_schema.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{b3_prosaic_harness_postgres-0.3.0 → b3_prosaic_harness_postgres-0.4.0}/tests/test_session_schema.py
RENAMED
|
File without changes
|
{b3_prosaic_harness_postgres-0.3.0 → b3_prosaic_harness_postgres-0.4.0}/tests/test_session_store.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{b3_prosaic_harness_postgres-0.3.0 → b3_prosaic_harness_postgres-0.4.0}/tests/test_transport.py
RENAMED
|
File without changes
|
{b3_prosaic_harness_postgres-0.3.0 → b3_prosaic_harness_postgres-0.4.0}/tests/test_workers.py
RENAMED
|
File without changes
|