b3-prosaic-harness-postgres 0.4.0__tar.gz → 0.4.1__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 (52) hide show
  1. {b3_prosaic_harness_postgres-0.4.0/src/b3_prosaic_harness_postgres.egg-info → b3_prosaic_harness_postgres-0.4.1}/PKG-INFO +30 -8
  2. {b3_prosaic_harness_postgres-0.4.0 → b3_prosaic_harness_postgres-0.4.1}/README.md +29 -7
  3. {b3_prosaic_harness_postgres-0.4.0 → b3_prosaic_harness_postgres-0.4.1}/pyproject.toml +1 -1
  4. {b3_prosaic_harness_postgres-0.4.0 → b3_prosaic_harness_postgres-0.4.1/src/b3_prosaic_harness_postgres.egg-info}/PKG-INFO +30 -8
  5. {b3_prosaic_harness_postgres-0.4.0 → b3_prosaic_harness_postgres-0.4.1}/src/b3_prosaic_harness_postgres.egg-info/SOURCES.txt +1 -0
  6. {b3_prosaic_harness_postgres-0.4.0 → b3_prosaic_harness_postgres-0.4.1}/src/prosaic_harness_postgres/__init__.py +1 -1
  7. {b3_prosaic_harness_postgres-0.4.0 → b3_prosaic_harness_postgres-0.4.1}/src/prosaic_harness_postgres/cli.py +4 -2
  8. {b3_prosaic_harness_postgres-0.4.0 → b3_prosaic_harness_postgres-0.4.1}/src/prosaic_harness_postgres/session_store.py +2 -2
  9. {b3_prosaic_harness_postgres-0.4.0 → b3_prosaic_harness_postgres-0.4.1}/src/prosaic_harness_postgres/store.py +2 -2
  10. {b3_prosaic_harness_postgres-0.4.0 → b3_prosaic_harness_postgres-0.4.1}/src/prosaic_harness_postgres/transport.py +7 -2
  11. {b3_prosaic_harness_postgres-0.4.0 → b3_prosaic_harness_postgres-0.4.1}/tests/support/local_cluster.py +3 -3
  12. b3_prosaic_harness_postgres-0.4.1/tests/test_durability_profiles.py +96 -0
  13. {b3_prosaic_harness_postgres-0.4.0 → b3_prosaic_harness_postgres-0.4.1}/tests/test_packaging.py +8 -7
  14. {b3_prosaic_harness_postgres-0.4.0 → b3_prosaic_harness_postgres-0.4.1}/LICENSE +0 -0
  15. {b3_prosaic_harness_postgres-0.4.0 → b3_prosaic_harness_postgres-0.4.1}/MANIFEST.in +0 -0
  16. {b3_prosaic_harness_postgres-0.4.0 → b3_prosaic_harness_postgres-0.4.1}/NOTICE +0 -0
  17. {b3_prosaic_harness_postgres-0.4.0 → b3_prosaic_harness_postgres-0.4.1}/setup.cfg +0 -0
  18. {b3_prosaic_harness_postgres-0.4.0 → b3_prosaic_harness_postgres-0.4.1}/src/b3_prosaic_harness_postgres.egg-info/dependency_links.txt +0 -0
  19. {b3_prosaic_harness_postgres-0.4.0 → b3_prosaic_harness_postgres-0.4.1}/src/b3_prosaic_harness_postgres.egg-info/entry_points.txt +0 -0
  20. {b3_prosaic_harness_postgres-0.4.0 → b3_prosaic_harness_postgres-0.4.1}/src/b3_prosaic_harness_postgres.egg-info/requires.txt +0 -0
  21. {b3_prosaic_harness_postgres-0.4.0 → b3_prosaic_harness_postgres-0.4.1}/src/b3_prosaic_harness_postgres.egg-info/top_level.txt +0 -0
  22. {b3_prosaic_harness_postgres-0.4.0 → b3_prosaic_harness_postgres-0.4.1}/src/prosaic_harness_postgres/leases.py +0 -0
  23. {b3_prosaic_harness_postgres-0.4.0 → b3_prosaic_harness_postgres-0.4.1}/src/prosaic_harness_postgres/schema.py +0 -0
  24. {b3_prosaic_harness_postgres-0.4.0 → b3_prosaic_harness_postgres-0.4.1}/src/prosaic_harness_postgres/session_leases.py +0 -0
  25. {b3_prosaic_harness_postgres-0.4.0 → b3_prosaic_harness_postgres-0.4.1}/src/prosaic_harness_postgres/session_schema.py +0 -0
  26. {b3_prosaic_harness_postgres-0.4.0 → b3_prosaic_harness_postgres-0.4.1}/tests/conftest.py +0 -0
  27. {b3_prosaic_harness_postgres-0.4.0 → b3_prosaic_harness_postgres-0.4.1}/tests/support/linux_wheel_smoke.py +0 -0
  28. {b3_prosaic_harness_postgres-0.4.0 → b3_prosaic_harness_postgres-0.4.1}/tests/support/model_server.py +0 -0
  29. {b3_prosaic_harness_postgres-0.4.0 → b3_prosaic_harness_postgres-0.4.1}/tests/support/session_documents.py +0 -0
  30. {b3_prosaic_harness_postgres-0.4.0 → b3_prosaic_harness_postgres-0.4.1}/tests/support/session_workers.py +0 -0
  31. {b3_prosaic_harness_postgres-0.4.0 → b3_prosaic_harness_postgres-0.4.1}/tests/support/tcp_proxy.py +0 -0
  32. {b3_prosaic_harness_postgres-0.4.0 → b3_prosaic_harness_postgres-0.4.1}/tests/support/workers.py +0 -0
  33. {b3_prosaic_harness_postgres-0.4.0 → b3_prosaic_harness_postgres-0.4.1}/tests/test_cli.py +0 -0
  34. {b3_prosaic_harness_postgres-0.4.0 → b3_prosaic_harness_postgres-0.4.1}/tests/test_durability.py +0 -0
  35. {b3_prosaic_harness_postgres-0.4.0 → b3_prosaic_harness_postgres-0.4.1}/tests/test_engine.py +0 -0
  36. {b3_prosaic_harness_postgres-0.4.0 → b3_prosaic_harness_postgres-0.4.1}/tests/test_examples.py +0 -0
  37. {b3_prosaic_harness_postgres-0.4.0 → b3_prosaic_harness_postgres-0.4.1}/tests/test_leases.py +0 -0
  38. {b3_prosaic_harness_postgres-0.4.0 → b3_prosaic_harness_postgres-0.4.1}/tests/test_local_cluster.py +0 -0
  39. {b3_prosaic_harness_postgres-0.4.0 → b3_prosaic_harness_postgres-0.4.1}/tests/test_native_factory.py +0 -0
  40. {b3_prosaic_harness_postgres-0.4.0 → b3_prosaic_harness_postgres-0.4.1}/tests/test_recovery.py +0 -0
  41. {b3_prosaic_harness_postgres-0.4.0 → b3_prosaic_harness_postgres-0.4.1}/tests/test_registration.py +0 -0
  42. {b3_prosaic_harness_postgres-0.4.0 → b3_prosaic_harness_postgres-0.4.1}/tests/test_routing.py +0 -0
  43. {b3_prosaic_harness_postgres-0.4.0 → b3_prosaic_harness_postgres-0.4.1}/tests/test_schema.py +0 -0
  44. {b3_prosaic_harness_postgres-0.4.0 → b3_prosaic_harness_postgres-0.4.1}/tests/test_session_context.py +0 -0
  45. {b3_prosaic_harness_postgres-0.4.0 → b3_prosaic_harness_postgres-0.4.1}/tests/test_session_fencing.py +0 -0
  46. {b3_prosaic_harness_postgres-0.4.0 → b3_prosaic_harness_postgres-0.4.1}/tests/test_session_recovery.py +0 -0
  47. {b3_prosaic_harness_postgres-0.4.0 → b3_prosaic_harness_postgres-0.4.1}/tests/test_session_schema.py +0 -0
  48. {b3_prosaic_harness_postgres-0.4.0 → b3_prosaic_harness_postgres-0.4.1}/tests/test_session_store.py +0 -0
  49. {b3_prosaic_harness_postgres-0.4.0 → b3_prosaic_harness_postgres-0.4.1}/tests/test_session_workers.py +0 -0
  50. {b3_prosaic_harness_postgres-0.4.0 → b3_prosaic_harness_postgres-0.4.1}/tests/test_store.py +0 -0
  51. {b3_prosaic_harness_postgres-0.4.0 → b3_prosaic_harness_postgres-0.4.1}/tests/test_transport.py +0 -0
  52. {b3_prosaic_harness_postgres-0.4.0 → b3_prosaic_harness_postgres-0.4.1}/tests/test_workers.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: b3-prosaic-harness-postgres
3
- Version: 0.4.0
3
+ Version: 0.4.1
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
@@ -24,13 +24,35 @@ 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 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
- installed and versioned separately, keeping PostgreSQL drivers optional for
30
- Harness users. Imports and CLI names remain unchanged. Final qualification and
31
- publication of this conversation release train are pending. The historical
32
- [Harness v0.6.1 release](https://github.com/B3Cognition/prosaic-harness/releases/tag/v0.6.1)
33
- retains its original 0.6.1/0.1.1 package names and receipts.
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
29
+ core Harness users. Imports and CLI names are unchanged.
30
+
31
+ ## Durability profiles
32
+
33
+ The default `durability_profile="postgresql"` requires `full_page_writes=on`.
34
+ An operator who has independently verified an Amazon Aurora PostgreSQL cluster
35
+ may explicitly select `durability_profile="aurora-postgresql"` on both
36
+ `PostgresRunStore` and `PostgresSessionStore` (including `from_env`). This accepts
37
+ `full_page_writes=off` under Aurora's log-based storage contract. It does not
38
+ weaken fsync, synchronous commit, writable-primary, logged-table, lease or
39
+ unknown-commit checks. The guard runs for every operation and connection reuse.
40
+ Invalid profiles fail before connecting. This profile is an operator assertion,
41
+ not engine autodetection or proof that arbitrary storage is safe. NEVER select
42
+ it for ordinary PostgreSQL just to suppress a readiness failure.
43
+
44
+ For every CLI command, pass `--durability-profile aurora-postgresql` explicitly.
45
+ Migration and runtime must select the same profile. Verify the actual engine and
46
+ version with AWS RDS metadata/DBA evidence; AWS also documents `aurora_version()`.
47
+ Capture a failed SQL probe's actual error and inspect its schema/search path;
48
+ NEVER convert an unavailable probe into permission to bypass durability checks.
49
+ No AWS credentials or model calls are needed by the adapter.
50
+
51
+ AWS describes the storage contract in
52
+ [its architecture guide](https://aws.amazon.com/blogs/database/is-amazon-rds-for-postgresql-or-amazon-aurora-postgresql-a-better-choice-for-me/).
53
+ Native PostgreSQL tests qualify the policy and all existing recovery behavior;
54
+ they do not emulate Aurora storage. An actual Aurora deployment needs synthetic
55
+ persistence/restart/failover qualification before enabling production work.
34
56
 
35
57
  ## 1. Install and supply an existing database
36
58
 
@@ -4,13 +4,35 @@ 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 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
- installed and versioned separately, keeping PostgreSQL drivers optional for
10
- Harness users. Imports and CLI names remain unchanged. Final qualification and
11
- publication of this conversation release train are pending. The historical
12
- [Harness v0.6.1 release](https://github.com/B3Cognition/prosaic-harness/releases/tag/v0.6.1)
13
- retains its original 0.6.1/0.1.1 package names and receipts.
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
9
+ core Harness users. Imports and CLI names are unchanged.
10
+
11
+ ## Durability profiles
12
+
13
+ The default `durability_profile="postgresql"` requires `full_page_writes=on`.
14
+ An operator who has independently verified an Amazon Aurora PostgreSQL cluster
15
+ may explicitly select `durability_profile="aurora-postgresql"` on both
16
+ `PostgresRunStore` and `PostgresSessionStore` (including `from_env`). This accepts
17
+ `full_page_writes=off` under Aurora's log-based storage contract. It does not
18
+ weaken fsync, synchronous commit, writable-primary, logged-table, lease or
19
+ unknown-commit checks. The guard runs for every operation and connection reuse.
20
+ Invalid profiles fail before connecting. This profile is an operator assertion,
21
+ not engine autodetection or proof that arbitrary storage is safe. NEVER select
22
+ it for ordinary PostgreSQL just to suppress a readiness failure.
23
+
24
+ For every CLI command, pass `--durability-profile aurora-postgresql` explicitly.
25
+ Migration and runtime must select the same profile. Verify the actual engine and
26
+ version with AWS RDS metadata/DBA evidence; AWS also documents `aurora_version()`.
27
+ Capture a failed SQL probe's actual error and inspect its schema/search path;
28
+ NEVER convert an unavailable probe into permission to bypass durability checks.
29
+ No AWS credentials or model calls are needed by the adapter.
30
+
31
+ AWS describes the storage contract in
32
+ [its architecture guide](https://aws.amazon.com/blogs/database/is-amazon-rds-for-postgresql-or-amazon-aurora-postgresql-a-better-choice-for-me/).
33
+ Native PostgreSQL tests qualify the policy and all existing recovery behavior;
34
+ they do not emulate Aurora storage. An actual Aurora deployment needs synthetic
35
+ persistence/restart/failover qualification before enabling production work.
14
36
 
15
37
  ## 1. Install and supply an existing database
16
38
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "b3-prosaic-harness-postgres"
7
- version = "0.4.0"
7
+ version = "0.4.1"
8
8
  description = "Explicit PostgreSQL run and conversation storage for Prosaic Harness"
9
9
  requires-python = ">=3.11"
10
10
  readme = "README.md"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: b3-prosaic-harness-postgres
3
- Version: 0.4.0
3
+ Version: 0.4.1
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
@@ -24,13 +24,35 @@ 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 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
- installed and versioned separately, keeping PostgreSQL drivers optional for
30
- Harness users. Imports and CLI names remain unchanged. Final qualification and
31
- publication of this conversation release train are pending. The historical
32
- [Harness v0.6.1 release](https://github.com/B3Cognition/prosaic-harness/releases/tag/v0.6.1)
33
- retains its original 0.6.1/0.1.1 package names and receipts.
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
29
+ core Harness users. Imports and CLI names are unchanged.
30
+
31
+ ## Durability profiles
32
+
33
+ The default `durability_profile="postgresql"` requires `full_page_writes=on`.
34
+ An operator who has independently verified an Amazon Aurora PostgreSQL cluster
35
+ may explicitly select `durability_profile="aurora-postgresql"` on both
36
+ `PostgresRunStore` and `PostgresSessionStore` (including `from_env`). This accepts
37
+ `full_page_writes=off` under Aurora's log-based storage contract. It does not
38
+ weaken fsync, synchronous commit, writable-primary, logged-table, lease or
39
+ unknown-commit checks. The guard runs for every operation and connection reuse.
40
+ Invalid profiles fail before connecting. This profile is an operator assertion,
41
+ not engine autodetection or proof that arbitrary storage is safe. NEVER select
42
+ it for ordinary PostgreSQL just to suppress a readiness failure.
43
+
44
+ For every CLI command, pass `--durability-profile aurora-postgresql` explicitly.
45
+ Migration and runtime must select the same profile. Verify the actual engine and
46
+ version with AWS RDS metadata/DBA evidence; AWS also documents `aurora_version()`.
47
+ Capture a failed SQL probe's actual error and inspect its schema/search path;
48
+ NEVER convert an unavailable probe into permission to bypass durability checks.
49
+ No AWS credentials or model calls are needed by the adapter.
50
+
51
+ AWS describes the storage contract in
52
+ [its architecture guide](https://aws.amazon.com/blogs/database/is-amazon-rds-for-postgresql-or-amazon-aurora-postgresql-a-better-choice-for-me/).
53
+ Native PostgreSQL tests qualify the policy and all existing recovery behavior;
54
+ they do not emulate Aurora storage. An actual Aurora deployment needs synthetic
55
+ persistence/restart/failover qualification before enabling production work.
34
56
 
35
57
  ## 1. Install and supply an existing database
36
58
 
@@ -21,6 +21,7 @@ src/prosaic_harness_postgres/transport.py
21
21
  tests/conftest.py
22
22
  tests/test_cli.py
23
23
  tests/test_durability.py
24
+ tests/test_durability_profiles.py
24
25
  tests/test_engine.py
25
26
  tests/test_examples.py
26
27
  tests/test_leases.py
@@ -1,5 +1,5 @@
1
1
  """Separate PostgreSQL adapter for Prosaic Harness."""
2
- __version__ = '0.4.0'
2
+ __version__ = '0.4.1'
3
3
 
4
4
  from .store import PostgresRunStore
5
5
  from .session_store import PostgresSessionStore
@@ -47,18 +47,20 @@ def main(argv=None):
47
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
+ command.add_argument('--durability-profile', choices=['postgresql', 'aurora-postgresql'],
51
+ default='postgresql', help='explicit operator-verified database storage contract')
50
52
  if name == 'doctor':
51
53
  command.add_argument('--check-write', action='store_true', help='explicit disposable write test; DELETE credentials required')
52
54
  args = parser.parse_args(argv)
53
55
  try:
54
56
  if args.command == 'migrate-conversations':
55
57
  from .session_store import PostgresSessionStore
56
- with PostgresSessionStore.from_env(args.dsn_env, namespace='conversation-migration') as store:
58
+ with PostgresSessionStore.from_env(args.dsn_env, namespace='conversation-migration', durability_profile=args.durability_profile) as store:
57
59
  store.migrate_context_storage()
58
60
  print(json.dumps({'migrated': True, 'conversation_storage_contract': 2}))
59
61
  return 0
60
62
  namespace = 'prosaic-diagnostic-' + uuid.uuid4().hex
61
- with PostgresRunStore.from_env(args.dsn_env, namespace=namespace) as store:
63
+ with PostgresRunStore.from_env(args.dsn_env, namespace=namespace, durability_profile=args.durability_profile) as store:
62
64
  if args.command == 'init':
63
65
  store.initialize()
64
66
  result = {'initialized': True, 'schema_version': 1}
@@ -33,7 +33,7 @@ class PostgresSessionStore:
33
33
 
34
34
  def __init__(self, dsn, *, namespace, lease_s=60, renew_s=15,
35
35
  operation_timeout_s=10, pool_timeout_s=5, connect_timeout_s=5,
36
- lock_timeout_s=1, statement_timeout_s=5, pool_size=8, max_active_leases=4):
36
+ lock_timeout_s=1, statement_timeout_s=5, pool_size=8, max_active_leases=4, durability_profile="postgresql"):
37
37
  self._namespace = validate_namespace(namespace)
38
38
  positive(lease_s, 'lease_s')
39
39
  positive(renew_s, 'renew_s')
@@ -49,7 +49,7 @@ class PostgresSessionStore:
49
49
  self._transport = PostgresTransport(dsn, operation_timeout_s=operation_timeout_s,
50
50
  pool_timeout_s=pool_timeout_s, connect_timeout_s=connect_timeout_s,
51
51
  lock_timeout_s=lock_timeout_s, statement_timeout_s=statement_timeout_s,
52
- pool_size=pool_size, max_active_leases=max_active_leases)
52
+ pool_size=pool_size, max_active_leases=max_active_leases, durability_profile=durability_profile)
53
53
 
54
54
  @property
55
55
  def namespace(self):
@@ -29,7 +29,7 @@ class PostgresRunStore:
29
29
 
30
30
  def __init__(self, dsn, *, namespace, lease_s=60, renew_s=15,
31
31
  operation_timeout_s=10, pool_timeout_s=5, connect_timeout_s=5,
32
- lock_timeout_s=1, statement_timeout_s=5, pool_size=8, max_active_leases=4):
32
+ lock_timeout_s=1, statement_timeout_s=5, pool_size=8, max_active_leases=4, durability_profile="postgresql"):
33
33
  self.namespace = validate_namespace(namespace)
34
34
  positive(lease_s, 'lease_s')
35
35
  positive(renew_s, 'renew_s')
@@ -46,7 +46,7 @@ class PostgresRunStore:
46
46
  self._transport = PostgresTransport(dsn, operation_timeout_s=operation_timeout_s,
47
47
  pool_timeout_s=pool_timeout_s, connect_timeout_s=connect_timeout_s,
48
48
  lock_timeout_s=lock_timeout_s, statement_timeout_s=statement_timeout_s,
49
- pool_size=pool_size, max_active_leases=max_active_leases)
49
+ pool_size=pool_size, max_active_leases=max_active_leases, durability_profile=durability_profile)
50
50
 
51
51
  def initialize(self):
52
52
  schema.initialize(self._transport)
@@ -47,7 +47,10 @@ class _Operation:
47
47
  class PostgresTransport:
48
48
  def __init__(self, dsn, *, operation_timeout_s=10, pool_timeout_s=5,
49
49
  connect_timeout_s=5, lock_timeout_s=1, statement_timeout_s=5,
50
- pool_size=8, max_active_leases=4):
50
+ pool_size=8, max_active_leases=4, durability_profile="postgresql"):
51
+ if type(durability_profile) is not str or durability_profile not in {'postgresql', 'aurora-postgresql'}:
52
+ raise ValueError('durability_profile must be postgresql or aurora-postgresql')
53
+ self.durability_profile = durability_profile
51
54
  for name, value in locals().copy().items():
52
55
  if name.endswith('_s'):
53
56
  positive(value, name)
@@ -138,7 +141,9 @@ class PostgresTransport:
138
141
  pg_catalog.current_setting('transaction_read_only'),
139
142
  pg_catalog.current_setting('fsync'), pg_catalog.current_setting('full_page_writes'),
140
143
  pg_catalog.current_setting('synchronous_commit')""")).fetchone()
141
- if not row or row[0] or row[1:4] != ('off', 'on', 'on') or row[4] not in {'on', 'remote_apply'}:
144
+ full_page_values = {'on', 'off'} if self.durability_profile == 'aurora-postgresql' else {'on'}
145
+ if (not row or row[0] or row[1:3] != ('off', 'on')
146
+ or row[3] not in full_page_values or row[4] not in {'on', 'remote_apply'}):
142
147
  raise StoreIncompatible('require writable primary, logged durability and safe WAL settings')
143
148
 
144
149
  async def _sql(self, operation, callback, renewal, budget):
@@ -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.4-py3-none-any.whl']
43
+ wheelhouse / 'b3_prosaic_runtime-0.9.7-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.9.0-py3-none-any.whl >/dev/null
180
+ /tmp/core/bin/python -m pip install /core-wheels/b3_prosaic_harness-0.9.4-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.9.0-py3-none-any.whl /adapter-wheels/b3_prosaic_harness_postgres-0.4.0-py3-none-any.whl >/dev/null
185
+ /tmp/both/bin/python -m pip install /core-wheels/b3_prosaic_harness-0.9.4-py3-none-any.whl /adapter-wheels/b3_prosaic_harness_postgres-0.4.1-py3-none-any.whl >/dev/null
186
186
  /tmp/both/bin/python -I /fixture/linux_wheel_smoke.py
187
187
  '''
188
188
  if dependencies:
@@ -0,0 +1,96 @@
1
+ """An explicit storage contract changes only the full-page-write guard."""
2
+ import asyncio
3
+ from unittest.mock import AsyncMock
4
+
5
+ import pytest
6
+ from prosaic_harness import StoreIncompatible
7
+ from prosaic_harness_postgres.transport import PostgresTransport
8
+
9
+
10
+ @pytest.mark.parametrize('profile', ['postgresql', 'aurora-postgresql'])
11
+ @pytest.mark.parametrize('row,accepted', [
12
+ ((False, 'off', 'on', 'on', 'on'), True),
13
+ ((False, 'off', 'on', 'off', 'on'), 'aurora'),
14
+ ((False, 'off', 'off', 'off', 'on'), False),
15
+ ((False, 'off', 'on', 'off', 'off'), False),
16
+ ((True, 'off', 'on', 'off', 'on'), False),
17
+ ((False, 'on', 'on', 'off', 'on'), False),
18
+ ((False, 'off', 'on', 'on', 'remote_apply'), True),
19
+ ((False, 'off', 'on', 'on', 'local'), False),
20
+ ((False, 'off', 'on', 'invalid', 'on'), False),
21
+ (None, False),
22
+ ])
23
+ def test_profile_changes_only_full_page_requirement(profile, row, accepted):
24
+ transport = object.__new__(PostgresTransport)
25
+ transport.durability_profile = profile
26
+ cursor = AsyncMock()
27
+ cursor.fetchone.return_value = row
28
+ connection = AsyncMock()
29
+ connection.execute.return_value = cursor
30
+ allowed = accepted is True or (accepted == 'aurora' and profile == 'aurora-postgresql')
31
+ if allowed:
32
+ asyncio.run(transport._guard(connection))
33
+ else:
34
+ with pytest.raises(StoreIncompatible):
35
+ asyncio.run(transport._guard(connection))
36
+
37
+
38
+ @pytest.mark.parametrize('profile', ['', 'storage', 'Aurora', None, True])
39
+ def test_unknown_profile_rejected_before_connection(profile):
40
+ with pytest.raises(ValueError, match='durability_profile'):
41
+ PostgresTransport('unused', durability_profile=profile)
42
+
43
+
44
+ @pytest.mark.parametrize('module_name,class_name', [('store', 'PostgresRunStore'), ('session_store', 'PostgresSessionStore')])
45
+ def test_public_store_and_from_env_forward_profile(monkeypatch, module_name, class_name):
46
+ import importlib
47
+ module = importlib.import_module('prosaic_harness_postgres.' + module_name)
48
+ captured = []
49
+ monkeypatch.setattr(module, 'PostgresTransport', lambda dsn, **kw: captured.append(kw))
50
+ monkeypatch.setenv('SYNTHETIC_DSN', 'synthetic')
51
+ cls = getattr(module, class_name)
52
+ cls('synthetic', namespace='fixture')
53
+ cls.from_env('SYNTHETIC_DSN', namespace='fixture', durability_profile='aurora-postgresql')
54
+ assert [kw['durability_profile'] for kw in captured] == ['postgresql', 'aurora-postgresql']
55
+
56
+
57
+ @pytest.mark.parametrize('command', ['init', 'doctor', 'migrate-conversations'])
58
+ def test_cli_forwards_explicit_profile(monkeypatch, command):
59
+ from prosaic_harness_postgres import cli, session_store
60
+ calls = []
61
+ class Store:
62
+ def __enter__(self): return self
63
+ def __exit__(self, *args): pass
64
+ def initialize(self): pass
65
+ def check_ready(self): pass
66
+ def migrate_context_storage(self): pass
67
+ def create(name, **kwargs):
68
+ calls.append(kwargs['durability_profile'])
69
+ return Store()
70
+ monkeypatch.setattr(cli.PostgresRunStore, 'from_env', create)
71
+ monkeypatch.setattr(session_store.PostgresSessionStore, 'from_env', create)
72
+ assert cli.main([command, '--dsn-env', 'SYNTHETIC_DSN', '--durability-profile', 'aurora-postgresql']) == 0
73
+ assert calls == ['aurora-postgresql']
74
+
75
+
76
+ @pytest.mark.parametrize('class_name', ['PostgresRunStore', 'PostgresSessionStore'])
77
+ def test_explicit_profile_retains_real_connection_guards(db_admin_dsn, class_name):
78
+ import prosaic_harness_postgres
79
+ cls = getattr(prosaic_harness_postgres, class_name)
80
+ with cls(db_admin_dsn, namespace='fixture', durability_profile='aurora-postgresql', pool_size=1) as store:
81
+ store.initialize()
82
+ store.check_ready()
83
+ store._transport.call(lambda conn: conn.execute('SET synchronous_commit=off'))
84
+ with pytest.raises(StoreIncompatible):
85
+ store.check_ready()
86
+
87
+
88
+ def test_public_version_matches_distribution():
89
+ import ast
90
+ from pathlib import Path
91
+ import tomllib
92
+ root = Path(__file__).resolve().parents[1]
93
+ version = tomllib.loads((root/'pyproject.toml').read_text())['project']['version']
94
+ module = ast.parse((root/'src/prosaic_harness_postgres/__init__.py').read_text())
95
+ assert next(ast.literal_eval(n.value) for n in module.body if isinstance(n, ast.Assign)
96
+ and any(isinstance(t, ast.Name) and t.id=='__version__' for t in n.targets)) == version
@@ -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.9.0-py3-none-any.whl'
17
- ADAPTER = ROOT / 'adapters/postgres/dist/b3_prosaic_harness_postgres-0.4.0-py3-none-any.whl'
16
+ CORE = ROOT / 'dist/b3_prosaic_harness-0.9.4-py3-none-any.whl'
17
+ ADAPTER = ROOT / 'adapters/postgres/dist/b3_prosaic_harness_postgres-0.4.1-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,12 +73,12 @@ 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.9.0'
76
+ assert core['Version'] == '0.9.4'
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.4.0'
81
+ assert adapter['Version'] == '0.4.1'
82
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')
@@ -134,7 +134,8 @@ def test_installed_memory_file_conversations_and_saved_receipt_recovery(wheel_en
134
134
  report = json.loads(result.stdout)
135
135
  assert set(report['stores']) == {'memory', 'file'}
136
136
  for evidence in report['stores'].values():
137
- assert evidence['providerRequests'] == 4
137
+ assert evidence['providerRequests'] == 6
138
+ assert evidence['sameMessageFailedRetry'] is True
138
139
  assert evidence['savedReceiptAdoptedWithoutDispatch'] is True
139
140
  assert evidence['failedCorrectionRetained'] is True
140
141
 
@@ -184,7 +185,7 @@ def curated_candidates(release_tool, tmp_path):
184
185
  candidates = []
185
186
  for repo, expected in (
186
187
  ('B3Cognition/prosaic', {'b3-prosaic': '0.4.1'}),
187
- ('B3Cognition/prosaic-runtime', {'b3-prosaic-runtime': '0.9.4',
188
+ ('B3Cognition/prosaic-runtime', {'b3-prosaic-runtime': '0.9.7',
188
189
  'b3-prosaic-runtime-postgres': '0.3.0'})):
189
190
  artifacts = []
190
191
  for name, version in expected.items():
@@ -252,4 +253,4 @@ def test_container_mounts_verified_dependency_bytes_read_only(
252
253
  create = calls[0]
253
254
  assert str(wheelhouse) + ':/candidate-wheels:ro' in create
254
255
  assert 'PIP_FIND_LINKS=/candidate-wheels' in create[-1]
255
- assert '/candidate-wheels/b3_prosaic_runtime-0.9.4-py3-none-any.whl' in create[-1]
256
+ assert '/candidate-wheels/b3_prosaic_runtime-0.9.7-py3-none-any.whl' in create[-1]