b3-prosaic-harness-postgres 0.4.1__tar.gz → 0.4.2__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 (57) hide show
  1. {b3_prosaic_harness_postgres-0.4.1/src/b3_prosaic_harness_postgres.egg-info → b3_prosaic_harness_postgres-0.4.2}/PKG-INFO +57 -12
  2. {b3_prosaic_harness_postgres-0.4.1 → b3_prosaic_harness_postgres-0.4.2}/README.md +55 -10
  3. {b3_prosaic_harness_postgres-0.4.1 → b3_prosaic_harness_postgres-0.4.2}/pyproject.toml +2 -2
  4. {b3_prosaic_harness_postgres-0.4.1 → b3_prosaic_harness_postgres-0.4.2/src/b3_prosaic_harness_postgres.egg-info}/PKG-INFO +57 -12
  5. {b3_prosaic_harness_postgres-0.4.1 → b3_prosaic_harness_postgres-0.4.2}/src/b3_prosaic_harness_postgres.egg-info/SOURCES.txt +5 -0
  6. {b3_prosaic_harness_postgres-0.4.1 → b3_prosaic_harness_postgres-0.4.2}/src/b3_prosaic_harness_postgres.egg-info/requires.txt +1 -1
  7. {b3_prosaic_harness_postgres-0.4.1 → b3_prosaic_harness_postgres-0.4.2}/src/prosaic_harness_postgres/__init__.py +1 -1
  8. {b3_prosaic_harness_postgres-0.4.1 → b3_prosaic_harness_postgres-0.4.2}/src/prosaic_harness_postgres/cli.py +9 -1
  9. b3_prosaic_harness_postgres-0.4.2/src/prosaic_harness_postgres/pack_schema.py +31 -0
  10. {b3_prosaic_harness_postgres-0.4.1 → b3_prosaic_harness_postgres-0.4.2}/src/prosaic_harness_postgres/schema.py +17 -5
  11. {b3_prosaic_harness_postgres-0.4.1 → b3_prosaic_harness_postgres-0.4.2}/src/prosaic_harness_postgres/session_schema.py +17 -5
  12. {b3_prosaic_harness_postgres-0.4.1 → b3_prosaic_harness_postgres-0.4.2}/src/prosaic_harness_postgres/session_store.py +16 -4
  13. {b3_prosaic_harness_postgres-0.4.1 → b3_prosaic_harness_postgres-0.4.2}/src/prosaic_harness_postgres/store.py +17 -2
  14. {b3_prosaic_harness_postgres-0.4.1 → b3_prosaic_harness_postgres-0.4.2}/src/prosaic_harness_postgres/transport.py +7 -1
  15. {b3_prosaic_harness_postgres-0.4.1 → b3_prosaic_harness_postgres-0.4.2}/tests/support/local_cluster.py +40 -9
  16. {b3_prosaic_harness_postgres-0.4.1 → b3_prosaic_harness_postgres-0.4.2}/tests/support/session_documents.py +46 -0
  17. {b3_prosaic_harness_postgres-0.4.1 → b3_prosaic_harness_postgres-0.4.2}/tests/support/session_workers.py +2 -1
  18. b3_prosaic_harness_postgres-0.4.2/tests/test_context_pack_migrations.py +96 -0
  19. b3_prosaic_harness_postgres-0.4.2/tests/test_context_pack_platform.py +21 -0
  20. b3_prosaic_harness_postgres-0.4.2/tests/test_context_pack_recovery.py +112 -0
  21. {b3_prosaic_harness_postgres-0.4.1 → b3_prosaic_harness_postgres-0.4.2}/tests/test_packaging.py +25 -8
  22. b3_prosaic_harness_postgres-0.4.2/tests/test_renewal_contention.py +39 -0
  23. {b3_prosaic_harness_postgres-0.4.1 → b3_prosaic_harness_postgres-0.4.2}/LICENSE +0 -0
  24. {b3_prosaic_harness_postgres-0.4.1 → b3_prosaic_harness_postgres-0.4.2}/MANIFEST.in +0 -0
  25. {b3_prosaic_harness_postgres-0.4.1 → b3_prosaic_harness_postgres-0.4.2}/NOTICE +0 -0
  26. {b3_prosaic_harness_postgres-0.4.1 → b3_prosaic_harness_postgres-0.4.2}/setup.cfg +0 -0
  27. {b3_prosaic_harness_postgres-0.4.1 → b3_prosaic_harness_postgres-0.4.2}/src/b3_prosaic_harness_postgres.egg-info/dependency_links.txt +0 -0
  28. {b3_prosaic_harness_postgres-0.4.1 → b3_prosaic_harness_postgres-0.4.2}/src/b3_prosaic_harness_postgres.egg-info/entry_points.txt +0 -0
  29. {b3_prosaic_harness_postgres-0.4.1 → b3_prosaic_harness_postgres-0.4.2}/src/b3_prosaic_harness_postgres.egg-info/top_level.txt +0 -0
  30. {b3_prosaic_harness_postgres-0.4.1 → b3_prosaic_harness_postgres-0.4.2}/src/prosaic_harness_postgres/leases.py +0 -0
  31. {b3_prosaic_harness_postgres-0.4.1 → b3_prosaic_harness_postgres-0.4.2}/src/prosaic_harness_postgres/session_leases.py +0 -0
  32. {b3_prosaic_harness_postgres-0.4.1 → b3_prosaic_harness_postgres-0.4.2}/tests/conftest.py +0 -0
  33. {b3_prosaic_harness_postgres-0.4.1 → b3_prosaic_harness_postgres-0.4.2}/tests/support/linux_wheel_smoke.py +0 -0
  34. {b3_prosaic_harness_postgres-0.4.1 → b3_prosaic_harness_postgres-0.4.2}/tests/support/model_server.py +0 -0
  35. {b3_prosaic_harness_postgres-0.4.1 → b3_prosaic_harness_postgres-0.4.2}/tests/support/tcp_proxy.py +0 -0
  36. {b3_prosaic_harness_postgres-0.4.1 → b3_prosaic_harness_postgres-0.4.2}/tests/support/workers.py +0 -0
  37. {b3_prosaic_harness_postgres-0.4.1 → b3_prosaic_harness_postgres-0.4.2}/tests/test_cli.py +0 -0
  38. {b3_prosaic_harness_postgres-0.4.1 → b3_prosaic_harness_postgres-0.4.2}/tests/test_durability.py +0 -0
  39. {b3_prosaic_harness_postgres-0.4.1 → b3_prosaic_harness_postgres-0.4.2}/tests/test_durability_profiles.py +0 -0
  40. {b3_prosaic_harness_postgres-0.4.1 → b3_prosaic_harness_postgres-0.4.2}/tests/test_engine.py +0 -0
  41. {b3_prosaic_harness_postgres-0.4.1 → b3_prosaic_harness_postgres-0.4.2}/tests/test_examples.py +0 -0
  42. {b3_prosaic_harness_postgres-0.4.1 → b3_prosaic_harness_postgres-0.4.2}/tests/test_leases.py +0 -0
  43. {b3_prosaic_harness_postgres-0.4.1 → b3_prosaic_harness_postgres-0.4.2}/tests/test_local_cluster.py +0 -0
  44. {b3_prosaic_harness_postgres-0.4.1 → b3_prosaic_harness_postgres-0.4.2}/tests/test_native_factory.py +0 -0
  45. {b3_prosaic_harness_postgres-0.4.1 → b3_prosaic_harness_postgres-0.4.2}/tests/test_recovery.py +0 -0
  46. {b3_prosaic_harness_postgres-0.4.1 → b3_prosaic_harness_postgres-0.4.2}/tests/test_registration.py +0 -0
  47. {b3_prosaic_harness_postgres-0.4.1 → b3_prosaic_harness_postgres-0.4.2}/tests/test_routing.py +0 -0
  48. {b3_prosaic_harness_postgres-0.4.1 → b3_prosaic_harness_postgres-0.4.2}/tests/test_schema.py +0 -0
  49. {b3_prosaic_harness_postgres-0.4.1 → b3_prosaic_harness_postgres-0.4.2}/tests/test_session_context.py +0 -0
  50. {b3_prosaic_harness_postgres-0.4.1 → b3_prosaic_harness_postgres-0.4.2}/tests/test_session_fencing.py +0 -0
  51. {b3_prosaic_harness_postgres-0.4.1 → b3_prosaic_harness_postgres-0.4.2}/tests/test_session_recovery.py +0 -0
  52. {b3_prosaic_harness_postgres-0.4.1 → b3_prosaic_harness_postgres-0.4.2}/tests/test_session_schema.py +0 -0
  53. {b3_prosaic_harness_postgres-0.4.1 → b3_prosaic_harness_postgres-0.4.2}/tests/test_session_store.py +0 -0
  54. {b3_prosaic_harness_postgres-0.4.1 → b3_prosaic_harness_postgres-0.4.2}/tests/test_session_workers.py +0 -0
  55. {b3_prosaic_harness_postgres-0.4.1 → b3_prosaic_harness_postgres-0.4.2}/tests/test_store.py +0 -0
  56. {b3_prosaic_harness_postgres-0.4.1 → b3_prosaic_harness_postgres-0.4.2}/tests/test_transport.py +0 -0
  57. {b3_prosaic_harness_postgres-0.4.1 → b3_prosaic_harness_postgres-0.4.2}/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.1
3
+ Version: 0.4.2
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,>=0.9
10
+ Requires-Dist: b3-prosaic-harness<0.10,>=0.9.13
11
11
  Requires-Dist: psycopg[binary]<4,>=3.2.3
12
12
  Requires-Dist: psycopg-pool<4,>=3.2
13
13
  Provides-Extra: test
@@ -20,12 +20,20 @@ Dynamic: license-file
20
20
 
21
21
  # PostgreSQL persistence for Prosaic Harness
22
22
 
23
+ PostgreSQL adapter **0.4.2** requires Harness **0.9.13** or later
24
+ in the compatible family. Explicit owner migration enables
25
+ workflow storage contract2 or conversation contract3; readiness performs no DDL.
26
+ See [context-pack migration and rollback](../../docs/context-packs.md#current-authorization-and-durable-storage).
27
+
28
+ See [0.4.2 release and migration notes](../../docs/release-postgres-0.4.2.md)
29
+ and [compatible SDK versions](https://github.com/B3Cognition/prosaic-harness/blob/main/docs/releases.md).
30
+
23
31
  Run on worker A, inspect/resume on worker B, without a shared writable run
24
32
  directory. Core remains database-free; install this separate adapter only when
25
33
  you need shared durable state. The API is synchronous; a private I/O thread
26
34
  bounds network waits. Model execution holds no database connection/transaction.
27
- The adapter is independently versioned: `b3-prosaic-harness-postgres` **0.4.1**
28
- requires `b3-prosaic-harness>=0.9,<0.10`. PostgreSQL drivers remain optional for
35
+ The adapter is independently versioned: `b3-prosaic-harness-postgres` **0.4.2**
36
+ requires `b3-prosaic-harness>=0.9.13,<0.10`. PostgreSQL drivers remain optional for
29
37
  core Harness users. Imports and CLI names are unchanged.
30
38
 
31
39
  ## Durability profiles
@@ -58,12 +66,12 @@ persistence/restart/failover qualification before enabling production work.
58
66
 
59
67
  Python 3.11+, PostgreSQL 16 or 18 on a writable primary, and libpq 17+ are
60
68
  required. `psycopg[binary]` supplies libpq; older libpq is rejected.
61
- From the exact candidate Harness checkout, use a new environment and a curated
62
- wheelhouse containing only the manifest-selected b3 candidate wheels:
69
+ Use a fresh environment and the published compatible SDKs:
63
70
 
64
71
  ```sh
65
72
  python3 -m venv .venv-postgres
66
- .venv-postgres/bin/python -m pip install --find-links /absolute/path/to/curated-wheelhouse . ./adapters/postgres
73
+ .venv-postgres/bin/python -m pip install 'b3-prosaic-harness==0.9.11' \
74
+ 'b3-prosaic-runtime==0.9.19' 'b3-prosaic-harness-postgres==0.4.2'
67
75
  export PATH="$PWD/.venv-postgres/bin:$PATH"
68
76
  prosaic --version
69
77
  prosaic-harness-postgres --help
@@ -71,8 +79,13 @@ prosaic-harness-postgres --help
71
79
 
72
80
  ALWAYS migrate using a fresh environment or image; NEVER install legacy and b3
73
81
  distributions together because they own the same import paths. See
74
- [package migration](../../docs/package-migration.md). The Runtime 0.9 candidate
75
- must be available before this installation can resolve.
82
+ [package migration](../../docs/package-migration.md). For reproducible installs,
83
+ verify the complete selected manifest's wheel hashes before installation.
84
+
85
+ Agent reasoning effort is passed through Harness to Runtime; this store does
86
+ not turn thinking on or prove provider compliance. Frozen prose/configuration
87
+ and workflow identity still govern resume. See [reasoning effort](../../docs/reasoning-effort.md)
88
+ and the Integration Lab's separate blueprint and control-check lane.
76
89
 
77
90
  Harness does **not** provision PostgreSQL, create databases/users, start a
78
91
  service, or fall back to files. Your operator supplies two DSNs through a secret
@@ -181,6 +194,10 @@ remain supported; the adapter adds no new workflow interpreter.
181
194
  budget 10s, pool/connect 5s, row lock 1s, statement 5s. Deadlines cover pool,
182
195
  network and COMMIT waits. Stalled connections are quarantined; unresolved
183
196
  operations retain capacity and prevent unsafe close.
197
+ - Ordinary operations use the configured short lock timeout. Dedicated renewal
198
+ operations can wait for a same-owner write within the configured statement
199
+ and total operation deadlines. Failed renewal remains sticky; ownership and
200
+ generation checks still fence every write.
184
201
  - Default lazy pool bounds per store/process: 8 ordinary + 4 renewal connections,
185
202
  maximum 4 active leases. Renewal has reserved capacity. Multiply capacity
186
203
  across replicas/namespaces; do not create a pool per HTTP request. Applications
@@ -235,7 +252,7 @@ python adapters/postgres/tests/support/local_cluster.py stop --manifest "$FIXTUR
235
252
  Repeat with version 18. Stop validates exact IDs/names/ownership labels before
236
253
  removing only owned containers/volumes/network. Generated fixture networks and
237
254
  loopback ports use synthetic trust authentication, never production defaults.
238
- # Lease registration candidate
255
+ # Lease registration
239
256
 
240
257
  The adapter transfers acquisition capacity into registered
241
258
  session capacity atomically under the existing store mutex. A registering lease
@@ -245,7 +262,7 @@ an owned PostgreSQL 16/18 race regression cover the transition.
245
262
 
246
263
  ## Optional conversation sessions
247
264
 
248
- The conversation candidate adds `PostgresSessionStore` to this same optional
265
+ Conversation storage provides `PostgresSessionStore` to this same optional
249
266
  distribution. It implements the public Harness `SessionStore` contract and uses
250
267
  the independent, logged `prosaic_harness_conversations` schema, storage version 1.
251
268
  Existing `PostgresRunStore` tables, grants and CLI `init`/`doctor` behavior remain
@@ -337,7 +354,7 @@ The required adapter suite includes both the existing workflow backend and the
337
354
  conversation backend. Run the owned PostgreSQL 16 and 18 fixtures shown above
338
355
  with `--require-postgres`. Missing infrastructure or any skipped required case
339
356
  fails qualification. Installed Linux AMD64 and ARM64 acceptance is required
340
- before claiming the conversation candidate is ready for production.
357
+ before treating a conversation deployment as ready for production.
341
358
 
342
359
  ## Optional context storage contract
343
360
 
@@ -357,3 +374,31 @@ cannot update schema metadata. Old workers reject the upgraded storage contract.
357
374
  Keep migration-owner credentials separate from runtime credentials. Enable host
358
375
  context policies only after Runtime, Harness and store advertise the capability.
359
376
  No production token counter is bundled or certified by this storage adapter.
377
+
378
+ ## Context pack storage contracts
379
+
380
+ Context packs require workflow storage contract 2 or conversation storage
381
+ contract 3. A fresh installation can explicitly use
382
+ `store.initialize(context_packs=True)`. On an existing installation this call
383
+ refuses to upgrade: drain **all writers to the affected shared schema**, then
384
+ run the owner-only migration:
385
+
386
+ ```sh
387
+ prosaic-harness-postgres migrate-context-packs --store workflows --dsn-env HARNESS_MIGRATION_DSN
388
+ prosaic-harness-postgres migrate-context-packs --store conversations --dsn-env HARNESS_MIGRATION_DSN
389
+ ```
390
+
391
+ `store.migrate_context_pack_storage()` is the equivalent API for either store.
392
+ ALWAYS use migration-owner credentials and drain writers across namespaces.
393
+ NEVER migrate from a worker, readiness check, or model invocation. An active
394
+ lease in any namespace refuses migration. The schema-wide advisory lock and
395
+ transaction preserve existing record bytes, revisions and grants. Repeating a
396
+ completed migration is safe; older binaries reject the new schema version.
397
+ New workers continue reading legacy records. Runtime credentials cannot migrate.
398
+
399
+ The `context_packs_v1` capability appears only after explicit opt-in. Pack-aware
400
+ workflow documents use canonical UTF-8 JSON; existing workflow document bytes
401
+ and the conversation codec remain unchanged. Private snapshots and validation
402
+ facts remain inside the bounded checkpoint/receipt, subject to host scope and
403
+ retained-read authorization. No new database, blob table or automatic retention
404
+ policy is introduced.
@@ -1,11 +1,19 @@
1
1
  # PostgreSQL persistence for Prosaic Harness
2
2
 
3
+ PostgreSQL adapter **0.4.2** requires Harness **0.9.13** or later
4
+ in the compatible family. Explicit owner migration enables
5
+ workflow storage contract2 or conversation contract3; readiness performs no DDL.
6
+ See [context-pack migration and rollback](../../docs/context-packs.md#current-authorization-and-durable-storage).
7
+
8
+ See [0.4.2 release and migration notes](../../docs/release-postgres-0.4.2.md)
9
+ and [compatible SDK versions](https://github.com/B3Cognition/prosaic-harness/blob/main/docs/releases.md).
10
+
3
11
  Run on worker A, inspect/resume on worker B, without a shared writable run
4
12
  directory. Core remains database-free; install this separate adapter only when
5
13
  you need shared durable state. The API is synchronous; a private I/O thread
6
14
  bounds network waits. Model execution holds no database connection/transaction.
7
- The adapter is independently versioned: `b3-prosaic-harness-postgres` **0.4.1**
8
- requires `b3-prosaic-harness>=0.9,<0.10`. PostgreSQL drivers remain optional for
15
+ The adapter is independently versioned: `b3-prosaic-harness-postgres` **0.4.2**
16
+ requires `b3-prosaic-harness>=0.9.13,<0.10`. PostgreSQL drivers remain optional for
9
17
  core Harness users. Imports and CLI names are unchanged.
10
18
 
11
19
  ## Durability profiles
@@ -38,12 +46,12 @@ persistence/restart/failover qualification before enabling production work.
38
46
 
39
47
  Python 3.11+, PostgreSQL 16 or 18 on a writable primary, and libpq 17+ are
40
48
  required. `psycopg[binary]` supplies libpq; older libpq is rejected.
41
- From the exact candidate Harness checkout, use a new environment and a curated
42
- wheelhouse containing only the manifest-selected b3 candidate wheels:
49
+ Use a fresh environment and the published compatible SDKs:
43
50
 
44
51
  ```sh
45
52
  python3 -m venv .venv-postgres
46
- .venv-postgres/bin/python -m pip install --find-links /absolute/path/to/curated-wheelhouse . ./adapters/postgres
53
+ .venv-postgres/bin/python -m pip install 'b3-prosaic-harness==0.9.11' \
54
+ 'b3-prosaic-runtime==0.9.19' 'b3-prosaic-harness-postgres==0.4.2'
47
55
  export PATH="$PWD/.venv-postgres/bin:$PATH"
48
56
  prosaic --version
49
57
  prosaic-harness-postgres --help
@@ -51,8 +59,13 @@ prosaic-harness-postgres --help
51
59
 
52
60
  ALWAYS migrate using a fresh environment or image; NEVER install legacy and b3
53
61
  distributions together because they own the same import paths. See
54
- [package migration](../../docs/package-migration.md). The Runtime 0.9 candidate
55
- must be available before this installation can resolve.
62
+ [package migration](../../docs/package-migration.md). For reproducible installs,
63
+ verify the complete selected manifest's wheel hashes before installation.
64
+
65
+ Agent reasoning effort is passed through Harness to Runtime; this store does
66
+ not turn thinking on or prove provider compliance. Frozen prose/configuration
67
+ and workflow identity still govern resume. See [reasoning effort](../../docs/reasoning-effort.md)
68
+ and the Integration Lab's separate blueprint and control-check lane.
56
69
 
57
70
  Harness does **not** provision PostgreSQL, create databases/users, start a
58
71
  service, or fall back to files. Your operator supplies two DSNs through a secret
@@ -161,6 +174,10 @@ remain supported; the adapter adds no new workflow interpreter.
161
174
  budget 10s, pool/connect 5s, row lock 1s, statement 5s. Deadlines cover pool,
162
175
  network and COMMIT waits. Stalled connections are quarantined; unresolved
163
176
  operations retain capacity and prevent unsafe close.
177
+ - Ordinary operations use the configured short lock timeout. Dedicated renewal
178
+ operations can wait for a same-owner write within the configured statement
179
+ and total operation deadlines. Failed renewal remains sticky; ownership and
180
+ generation checks still fence every write.
164
181
  - Default lazy pool bounds per store/process: 8 ordinary + 4 renewal connections,
165
182
  maximum 4 active leases. Renewal has reserved capacity. Multiply capacity
166
183
  across replicas/namespaces; do not create a pool per HTTP request. Applications
@@ -215,7 +232,7 @@ python adapters/postgres/tests/support/local_cluster.py stop --manifest "$FIXTUR
215
232
  Repeat with version 18. Stop validates exact IDs/names/ownership labels before
216
233
  removing only owned containers/volumes/network. Generated fixture networks and
217
234
  loopback ports use synthetic trust authentication, never production defaults.
218
- # Lease registration candidate
235
+ # Lease registration
219
236
 
220
237
  The adapter transfers acquisition capacity into registered
221
238
  session capacity atomically under the existing store mutex. A registering lease
@@ -225,7 +242,7 @@ an owned PostgreSQL 16/18 race regression cover the transition.
225
242
 
226
243
  ## Optional conversation sessions
227
244
 
228
- The conversation candidate adds `PostgresSessionStore` to this same optional
245
+ Conversation storage provides `PostgresSessionStore` to this same optional
229
246
  distribution. It implements the public Harness `SessionStore` contract and uses
230
247
  the independent, logged `prosaic_harness_conversations` schema, storage version 1.
231
248
  Existing `PostgresRunStore` tables, grants and CLI `init`/`doctor` behavior remain
@@ -317,7 +334,7 @@ The required adapter suite includes both the existing workflow backend and the
317
334
  conversation backend. Run the owned PostgreSQL 16 and 18 fixtures shown above
318
335
  with `--require-postgres`. Missing infrastructure or any skipped required case
319
336
  fails qualification. Installed Linux AMD64 and ARM64 acceptance is required
320
- before claiming the conversation candidate is ready for production.
337
+ before treating a conversation deployment as ready for production.
321
338
 
322
339
  ## Optional context storage contract
323
340
 
@@ -337,3 +354,31 @@ cannot update schema metadata. Old workers reject the upgraded storage contract.
337
354
  Keep migration-owner credentials separate from runtime credentials. Enable host
338
355
  context policies only after Runtime, Harness and store advertise the capability.
339
356
  No production token counter is bundled or certified by this storage adapter.
357
+
358
+ ## Context pack storage contracts
359
+
360
+ Context packs require workflow storage contract 2 or conversation storage
361
+ contract 3. A fresh installation can explicitly use
362
+ `store.initialize(context_packs=True)`. On an existing installation this call
363
+ refuses to upgrade: drain **all writers to the affected shared schema**, then
364
+ run the owner-only migration:
365
+
366
+ ```sh
367
+ prosaic-harness-postgres migrate-context-packs --store workflows --dsn-env HARNESS_MIGRATION_DSN
368
+ prosaic-harness-postgres migrate-context-packs --store conversations --dsn-env HARNESS_MIGRATION_DSN
369
+ ```
370
+
371
+ `store.migrate_context_pack_storage()` is the equivalent API for either store.
372
+ ALWAYS use migration-owner credentials and drain writers across namespaces.
373
+ NEVER migrate from a worker, readiness check, or model invocation. An active
374
+ lease in any namespace refuses migration. The schema-wide advisory lock and
375
+ transaction preserve existing record bytes, revisions and grants. Repeating a
376
+ completed migration is safe; older binaries reject the new schema version.
377
+ New workers continue reading legacy records. Runtime credentials cannot migrate.
378
+
379
+ The `context_packs_v1` capability appears only after explicit opt-in. Pack-aware
380
+ workflow documents use canonical UTF-8 JSON; existing workflow document bytes
381
+ and the conversation codec remain unchanged. Private snapshots and validation
382
+ facts remain inside the bounded checkpoint/receipt, subject to host scope and
383
+ retained-read authorization. No new database, blob table or automatic retention
384
+ policy is introduced.
@@ -4,13 +4,13 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "b3-prosaic-harness-postgres"
7
- version = "0.4.1"
7
+ version = "0.4.2"
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.9,<0.10", "psycopg[binary]>=3.2.3,<4", "psycopg-pool>=3.2,<4"]
13
+ dependencies = ["b3-prosaic-harness>=0.9.13,<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.1
3
+ Version: 0.4.2
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,>=0.9
10
+ Requires-Dist: b3-prosaic-harness<0.10,>=0.9.13
11
11
  Requires-Dist: psycopg[binary]<4,>=3.2.3
12
12
  Requires-Dist: psycopg-pool<4,>=3.2
13
13
  Provides-Extra: test
@@ -20,12 +20,20 @@ Dynamic: license-file
20
20
 
21
21
  # PostgreSQL persistence for Prosaic Harness
22
22
 
23
+ PostgreSQL adapter **0.4.2** requires Harness **0.9.13** or later
24
+ in the compatible family. Explicit owner migration enables
25
+ workflow storage contract2 or conversation contract3; readiness performs no DDL.
26
+ See [context-pack migration and rollback](../../docs/context-packs.md#current-authorization-and-durable-storage).
27
+
28
+ See [0.4.2 release and migration notes](../../docs/release-postgres-0.4.2.md)
29
+ and [compatible SDK versions](https://github.com/B3Cognition/prosaic-harness/blob/main/docs/releases.md).
30
+
23
31
  Run on worker A, inspect/resume on worker B, without a shared writable run
24
32
  directory. Core remains database-free; install this separate adapter only when
25
33
  you need shared durable state. The API is synchronous; a private I/O thread
26
34
  bounds network waits. Model execution holds no database connection/transaction.
27
- The adapter is independently versioned: `b3-prosaic-harness-postgres` **0.4.1**
28
- requires `b3-prosaic-harness>=0.9,<0.10`. PostgreSQL drivers remain optional for
35
+ The adapter is independently versioned: `b3-prosaic-harness-postgres` **0.4.2**
36
+ requires `b3-prosaic-harness>=0.9.13,<0.10`. PostgreSQL drivers remain optional for
29
37
  core Harness users. Imports and CLI names are unchanged.
30
38
 
31
39
  ## Durability profiles
@@ -58,12 +66,12 @@ persistence/restart/failover qualification before enabling production work.
58
66
 
59
67
  Python 3.11+, PostgreSQL 16 or 18 on a writable primary, and libpq 17+ are
60
68
  required. `psycopg[binary]` supplies libpq; older libpq is rejected.
61
- From the exact candidate Harness checkout, use a new environment and a curated
62
- wheelhouse containing only the manifest-selected b3 candidate wheels:
69
+ Use a fresh environment and the published compatible SDKs:
63
70
 
64
71
  ```sh
65
72
  python3 -m venv .venv-postgres
66
- .venv-postgres/bin/python -m pip install --find-links /absolute/path/to/curated-wheelhouse . ./adapters/postgres
73
+ .venv-postgres/bin/python -m pip install 'b3-prosaic-harness==0.9.11' \
74
+ 'b3-prosaic-runtime==0.9.19' 'b3-prosaic-harness-postgres==0.4.2'
67
75
  export PATH="$PWD/.venv-postgres/bin:$PATH"
68
76
  prosaic --version
69
77
  prosaic-harness-postgres --help
@@ -71,8 +79,13 @@ prosaic-harness-postgres --help
71
79
 
72
80
  ALWAYS migrate using a fresh environment or image; NEVER install legacy and b3
73
81
  distributions together because they own the same import paths. See
74
- [package migration](../../docs/package-migration.md). The Runtime 0.9 candidate
75
- must be available before this installation can resolve.
82
+ [package migration](../../docs/package-migration.md). For reproducible installs,
83
+ verify the complete selected manifest's wheel hashes before installation.
84
+
85
+ Agent reasoning effort is passed through Harness to Runtime; this store does
86
+ not turn thinking on or prove provider compliance. Frozen prose/configuration
87
+ and workflow identity still govern resume. See [reasoning effort](../../docs/reasoning-effort.md)
88
+ and the Integration Lab's separate blueprint and control-check lane.
76
89
 
77
90
  Harness does **not** provision PostgreSQL, create databases/users, start a
78
91
  service, or fall back to files. Your operator supplies two DSNs through a secret
@@ -181,6 +194,10 @@ remain supported; the adapter adds no new workflow interpreter.
181
194
  budget 10s, pool/connect 5s, row lock 1s, statement 5s. Deadlines cover pool,
182
195
  network and COMMIT waits. Stalled connections are quarantined; unresolved
183
196
  operations retain capacity and prevent unsafe close.
197
+ - Ordinary operations use the configured short lock timeout. Dedicated renewal
198
+ operations can wait for a same-owner write within the configured statement
199
+ and total operation deadlines. Failed renewal remains sticky; ownership and
200
+ generation checks still fence every write.
184
201
  - Default lazy pool bounds per store/process: 8 ordinary + 4 renewal connections,
185
202
  maximum 4 active leases. Renewal has reserved capacity. Multiply capacity
186
203
  across replicas/namespaces; do not create a pool per HTTP request. Applications
@@ -235,7 +252,7 @@ python adapters/postgres/tests/support/local_cluster.py stop --manifest "$FIXTUR
235
252
  Repeat with version 18. Stop validates exact IDs/names/ownership labels before
236
253
  removing only owned containers/volumes/network. Generated fixture networks and
237
254
  loopback ports use synthetic trust authentication, never production defaults.
238
- # Lease registration candidate
255
+ # Lease registration
239
256
 
240
257
  The adapter transfers acquisition capacity into registered
241
258
  session capacity atomically under the existing store mutex. A registering lease
@@ -245,7 +262,7 @@ an owned PostgreSQL 16/18 race regression cover the transition.
245
262
 
246
263
  ## Optional conversation sessions
247
264
 
248
- The conversation candidate adds `PostgresSessionStore` to this same optional
265
+ Conversation storage provides `PostgresSessionStore` to this same optional
249
266
  distribution. It implements the public Harness `SessionStore` contract and uses
250
267
  the independent, logged `prosaic_harness_conversations` schema, storage version 1.
251
268
  Existing `PostgresRunStore` tables, grants and CLI `init`/`doctor` behavior remain
@@ -337,7 +354,7 @@ The required adapter suite includes both the existing workflow backend and the
337
354
  conversation backend. Run the owned PostgreSQL 16 and 18 fixtures shown above
338
355
  with `--require-postgres`. Missing infrastructure or any skipped required case
339
356
  fails qualification. Installed Linux AMD64 and ARM64 acceptance is required
340
- before claiming the conversation candidate is ready for production.
357
+ before treating a conversation deployment as ready for production.
341
358
 
342
359
  ## Optional context storage contract
343
360
 
@@ -357,3 +374,31 @@ cannot update schema metadata. Old workers reject the upgraded storage contract.
357
374
  Keep migration-owner credentials separate from runtime credentials. Enable host
358
375
  context policies only after Runtime, Harness and store advertise the capability.
359
376
  No production token counter is bundled or certified by this storage adapter.
377
+
378
+ ## Context pack storage contracts
379
+
380
+ Context packs require workflow storage contract 2 or conversation storage
381
+ contract 3. A fresh installation can explicitly use
382
+ `store.initialize(context_packs=True)`. On an existing installation this call
383
+ refuses to upgrade: drain **all writers to the affected shared schema**, then
384
+ run the owner-only migration:
385
+
386
+ ```sh
387
+ prosaic-harness-postgres migrate-context-packs --store workflows --dsn-env HARNESS_MIGRATION_DSN
388
+ prosaic-harness-postgres migrate-context-packs --store conversations --dsn-env HARNESS_MIGRATION_DSN
389
+ ```
390
+
391
+ `store.migrate_context_pack_storage()` is the equivalent API for either store.
392
+ ALWAYS use migration-owner credentials and drain writers across namespaces.
393
+ NEVER migrate from a worker, readiness check, or model invocation. An active
394
+ lease in any namespace refuses migration. The schema-wide advisory lock and
395
+ transaction preserve existing record bytes, revisions and grants. Repeating a
396
+ completed migration is safe; older binaries reject the new schema version.
397
+ New workers continue reading legacy records. Runtime credentials cannot migrate.
398
+
399
+ The `context_packs_v1` capability appears only after explicit opt-in. Pack-aware
400
+ workflow documents use canonical UTF-8 JSON; existing workflow document bytes
401
+ and the conversation codec remain unchanged. Private snapshots and validation
402
+ facts remain inside the bounded checkpoint/receipt, subject to host scope and
403
+ retained-read authorization. No new database, blob table or automatic retention
404
+ policy is introduced.
@@ -12,6 +12,7 @@ src/b3_prosaic_harness_postgres.egg-info/top_level.txt
12
12
  src/prosaic_harness_postgres/__init__.py
13
13
  src/prosaic_harness_postgres/cli.py
14
14
  src/prosaic_harness_postgres/leases.py
15
+ src/prosaic_harness_postgres/pack_schema.py
15
16
  src/prosaic_harness_postgres/schema.py
16
17
  src/prosaic_harness_postgres/session_leases.py
17
18
  src/prosaic_harness_postgres/session_schema.py
@@ -20,6 +21,9 @@ src/prosaic_harness_postgres/store.py
20
21
  src/prosaic_harness_postgres/transport.py
21
22
  tests/conftest.py
22
23
  tests/test_cli.py
24
+ tests/test_context_pack_migrations.py
25
+ tests/test_context_pack_platform.py
26
+ tests/test_context_pack_recovery.py
23
27
  tests/test_durability.py
24
28
  tests/test_durability_profiles.py
25
29
  tests/test_engine.py
@@ -30,6 +34,7 @@ tests/test_native_factory.py
30
34
  tests/test_packaging.py
31
35
  tests/test_recovery.py
32
36
  tests/test_registration.py
37
+ tests/test_renewal_contention.py
33
38
  tests/test_routing.py
34
39
  tests/test_schema.py
35
40
  tests/test_session_context.py
@@ -1,4 +1,4 @@
1
- b3-prosaic-harness<0.10,>=0.9
1
+ b3-prosaic-harness<0.10,>=0.9.13
2
2
  psycopg[binary]<4,>=3.2.3
3
3
  psycopg-pool<4,>=3.2
4
4
 
@@ -1,5 +1,5 @@
1
1
  """Separate PostgreSQL adapter for Prosaic Harness."""
2
- __version__ = '0.4.1'
2
+ __version__ = '0.4.2'
3
3
 
4
4
  from .store import PostgresRunStore
5
5
  from .session_store import PostgresSessionStore
@@ -44,15 +44,23 @@ 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', 'migrate-conversations'):
47
+ for name in ('init', 'doctor', 'migrate-conversations','migrate-context-packs'):
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
  command.add_argument('--durability-profile', choices=['postgresql', 'aurora-postgresql'],
51
51
  default='postgresql', help='explicit operator-verified database storage contract')
52
+ if name=='migrate-context-packs': command.add_argument('--store',choices=['workflows','conversations'],required=True)
52
53
  if name == 'doctor':
53
54
  command.add_argument('--check-write', action='store_true', help='explicit disposable write test; DELETE credentials required')
54
55
  args = parser.parse_args(argv)
55
56
  try:
57
+ if args.command=='migrate-context-packs':
58
+ from .session_store import PostgresSessionStore
59
+ cls=PostgresRunStore if args.store=='workflows' else PostgresSessionStore
60
+ with cls.from_env(args.dsn_env,namespace='pack-migration',durability_profile=args.durability_profile) as store:
61
+ store.migrate_context_pack_storage()
62
+ print(json.dumps({'migrated':True,'store':args.store,'storage_contract':2 if args.store=='workflows' else 3}))
63
+ return 0
56
64
  if args.command == 'migrate-conversations':
57
65
  from .session_store import PostgresSessionStore
58
66
  with PostgresSessionStore.from_env(args.dsn_env, namespace='conversation-migration', durability_profile=args.durability_profile) as store:
@@ -0,0 +1,31 @@
1
+ """Owner-only, schema-wide opt-in; never rewrite run or receipt documents."""
2
+ from prosaic_harness import StoreIncompatible,StoreUninitialized,StoreBusy
3
+
4
+
5
+ def migrate(transport,*,schema,target,versions,lock,ready):
6
+ # These identifiers are internal constants, never user/model data.
7
+ if (schema,target) not in {('prosaic_harness',2),('prosaic_harness_conversations',3)}:
8
+ raise StoreIncompatible()
9
+ async def operation(conn):
10
+ await conn.execute("SELECT pg_catalog.set_config('lock_timeout','25000ms',true), "
11
+ "pg_catalog.set_config('statement_timeout','28000ms',true)")
12
+ await conn.execute('SELECT pg_catalog.pg_advisory_xact_lock(%s)',(lock,))
13
+ owner=await (await conn.execute("SELECT pg_catalog.pg_has_role(nspowner,'USAGE') "
14
+ 'FROM pg_catalog.pg_namespace WHERE nspname=%s',(schema,))).fetchone()
15
+ if owner is None: raise StoreUninitialized('initialize storage explicitly first')
16
+ if owner[0] is not True: raise StoreIncompatible('migration role must own the adapter schema')
17
+ current=await (await conn.execute('SELECT schema_version,storage_contract_version FROM '+schema+'.schema_version')).fetchall()
18
+ if len(current)!=1 or current[0] not in versions: raise StoreIncompatible('migration requires intact known storage')
19
+ await ready(conn,expected_versions=current[0])
20
+ if current[0]==(target,target): return
21
+ # Includes every namespace. This transaction is brief and never spans
22
+ # inference, a counter, a validator or any host callback.
23
+ await conn.execute('LOCK TABLE '+schema+'.leases IN EXCLUSIVE MODE')
24
+ active=await (await conn.execute('SELECT EXISTS(SELECT 1 FROM '+schema+'.leases '
25
+ 'WHERE owner IS NOT NULL AND expires_at>pg_catalog.clock_timestamp())')).fetchone()
26
+ if active[0]: raise StoreBusy('drain all writers to the shared schema before migration')
27
+ changed=await conn.execute('UPDATE '+schema+'.schema_version '
28
+ 'SET schema_version=%s,storage_contract_version=%s WHERE singleton IS TRUE',(target,target))
29
+ if changed.rowcount!=1: raise StoreIncompatible()
30
+ await ready(conn,expected_versions=(target,target))
31
+ return transport.call(operation,timeout_s=30)
@@ -57,7 +57,7 @@ CONSTRAINTS = {
57
57
  }
58
58
 
59
59
 
60
- async def ready_on_connection(conn):
60
+ async def ready_on_connection(conn,*,expected_versions=None):
61
61
  exists = await (await conn.execute("SELECT pg_catalog.to_regclass('prosaic_harness.schema_version')")).fetchone()
62
62
  if not exists or exists[0] is None:
63
63
  raise StoreUninitialized('run init with migration credentials before accepting work')
@@ -95,7 +95,8 @@ async def ready_on_connection(conn):
95
95
  if actual_constraints != CONSTRAINTS:
96
96
  raise StoreIncompatible('adapter schema constraints are incompatible')
97
97
  version = await (await conn.execute('SELECT schema_version, storage_contract_version FROM prosaic_harness.schema_version')).fetchall()
98
- if version != [(SCHEMA_VERSION, STORAGE_CONTRACT_VERSION)]:
98
+ allowed=[expected_versions] if expected_versions is not None else [(1,1),(2,2)]
99
+ if len(version)!=1 or version[0] not in allowed:
99
100
  raise StoreIncompatible('deploy a compatible adapter/schema; no automatic downgrade')
100
101
  for table in COLUMNS:
101
102
  privileges = ['SELECT']
@@ -108,13 +109,15 @@ async def ready_on_connection(conn):
108
109
  ('prosaic_harness.' + table, privilege))).fetchone()
109
110
  if not allowed or not allowed[0]:
110
111
  raise StoreIncompatible('grant required runtime table privileges before accepting work')
112
+ return version[0]
111
113
 
112
114
 
113
115
  def check_ready(transport):
114
- transport.call(ready_on_connection)
116
+ return transport.call(ready_on_connection)
115
117
 
116
118
 
117
- def initialize(transport):
119
+ def initialize(transport,*,context_packs=False):
120
+ if type(context_packs) is not bool: raise ValueError('context_packs must be boolean')
118
121
  async def migrate(conn):
119
122
  await conn.execute("SELECT pg_catalog.set_config('lock_timeout','25000ms',true), "
120
123
  "pg_catalog.set_config('statement_timeout','28000ms',true)")
@@ -128,8 +131,17 @@ def initialize(transport):
128
131
  count = await (await conn.execute('SELECT count(*) FROM pg_catalog.pg_class WHERE relnamespace=%s',
129
132
  (namespace[0],))).fetchone()
130
133
  if count[0]:
131
- await ready_on_connection(conn)
134
+ actual=await ready_on_connection(conn)
135
+ if context_packs and actual!=(2,2): raise StoreIncompatible('migrate context pack storage explicitly')
132
136
  return
133
137
  await conn.execute(DDL)
138
+ if context_packs:
139
+ await conn.execute('UPDATE prosaic_harness.schema_version SET schema_version=2,storage_contract_version=2')
134
140
  await ready_on_connection(conn)
135
141
  transport.call(migrate, timeout_s=30)
142
+
143
+
144
+ def migrate_context_pack_storage(transport):
145
+ from .pack_schema import migrate
146
+ return migrate(transport,schema='prosaic_harness',target=2,versions={(1,1),(2,2)},
147
+ lock=MIGRATION_LOCK,ready=ready_on_connection)
@@ -154,15 +154,18 @@ async def ready_on_connection(conn, *, expected_versions=None):
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 != [expected_versions or (SCHEMA_VERSION, STORAGE_CONTRACT_VERSION)]:
157
+ allowed=[expected_versions] if expected_versions is not None else [(2,2),(3,3)]
158
+ if len(versions)!=1 or versions[0] not in allowed:
158
159
  raise StoreIncompatible('conversation schema version is incompatible; no automatic migration')
160
+ return versions[0]
159
161
 
160
162
 
161
163
  def check_ready(transport):
162
- transport.call(ready_on_connection)
164
+ return transport.call(ready_on_connection)
163
165
 
164
166
 
165
- def initialize(transport):
167
+ def initialize(transport,*,context_packs=False):
168
+ if type(context_packs) is not bool: raise ValueError('context_packs must be boolean')
166
169
  async def migrate(conn):
167
170
  await conn.execute("SELECT pg_catalog.set_config('lock_timeout','25000ms',true), "
168
171
  "pg_catalog.set_config('statement_timeout','28000ms',true)")
@@ -176,11 +179,14 @@ def initialize(transport):
176
179
  (namespace[0],))).fetchone()
177
180
  if count[0]:
178
181
  try:
179
- await ready_on_connection(conn)
182
+ actual=await ready_on_connection(conn)
183
+ if context_packs and actual!=(3,3): raise StoreIncompatible('migrate context pack storage explicitly')
180
184
  except StoreUninitialized:
181
185
  raise StoreIncompatible('partial conversation schema requires operator repair') from None
182
186
  return
183
187
  await conn.execute(DDL)
188
+ if context_packs:
189
+ await conn.execute('UPDATE prosaic_harness_conversations.schema_version SET schema_version=3,storage_contract_version=3')
184
190
  await ready_on_connection(conn)
185
191
  transport.call(migrate, timeout_s=30)
186
192
 
@@ -198,7 +204,7 @@ def migrate_context_storage(transport):
198
204
  raise StoreIncompatible('migration role must own conversation schema')
199
205
  versions = await (await conn.execute('SELECT schema_version,storage_contract_version '
200
206
  'FROM prosaic_harness_conversations.schema_version')).fetchall()
201
- if versions == [(2, 2)]:
207
+ if versions in ([(2,2)],[(3,3)]):
202
208
  await ready_on_connection(conn)
203
209
  return
204
210
  if versions != [(1, 1)]:
@@ -210,3 +216,9 @@ def migrate_context_storage(transport):
210
216
  raise StoreIncompatible('conversation version metadata is incompatible')
211
217
  await ready_on_connection(conn)
212
218
  transport.call(migrate, timeout_s=30)
219
+
220
+
221
+ def migrate_context_pack_storage(transport):
222
+ from .pack_schema import migrate
223
+ return migrate(transport,schema=SCHEMA,target=3,versions={(1,1),(2,2),(3,3)},
224
+ lock=MIGRATION_LOCK,ready=ready_on_connection)