iris-devtester 1.19.2__tar.gz → 1.19.4__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 (289) hide show
  1. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/CHANGELOG.md +20 -0
  2. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/CONSTITUTION.md +85 -14
  3. {iris_devtester-1.19.2/iris_devtester.egg-info → iris_devtester-1.19.4}/PKG-INFO +1 -1
  4. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/iris_devtester/__init__.py +1 -1
  5. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/iris_devtester/containers/cpf_manager.py +6 -3
  6. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/iris_devtester/containers/iris_container.py +76 -18
  7. {iris_devtester-1.19.2 → iris_devtester-1.19.4/iris_devtester.egg-info}/PKG-INFO +1 -1
  8. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/iris_devtester.egg-info/SOURCES.txt +16 -0
  9. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/pyproject.toml +1 -1
  10. iris_devtester-1.19.4/tests/unit/test_adapter_langchain_diag.py +1028 -0
  11. iris_devtester-1.19.4/tests/unit/test_auto_discovery.py +304 -0
  12. iris_devtester-1.19.4/tests/unit/test_autodiscovery_dbapi.py +537 -0
  13. iris_devtester-1.19.4/tests/unit/test_config_models_unit.py +1327 -0
  14. iris_devtester-1.19.4/tests/unit/test_connections_unit.py +1423 -0
  15. iris_devtester-1.19.4/tests/unit/test_container_state.py +368 -0
  16. iris_devtester-1.19.4/tests/unit/test_container_validation.py +632 -0
  17. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/unit/test_cpf_manager.py +4 -2
  18. iris_devtester-1.19.4/tests/unit/test_fixtures_extra.py +1034 -0
  19. iris_devtester-1.19.4/tests/unit/test_fixtures_unit.py +770 -0
  20. iris_devtester-1.19.4/tests/unit/test_health_checks.py +944 -0
  21. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/unit/test_iris_container.py +79 -35
  22. iris_devtester-1.19.4/tests/unit/test_iris_container_extra.py +957 -0
  23. iris_devtester-1.19.4/tests/unit/test_monitoring_perf_diag.py +1466 -0
  24. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/unit/test_password_preconfig.py +152 -2
  25. iris_devtester-1.19.4/tests/unit/test_password_utils.py +989 -0
  26. iris_devtester-1.19.4/tests/unit/test_ports.py +639 -0
  27. iris_devtester-1.19.4/tests/unit/test_progress.py +256 -0
  28. iris_devtester-1.19.4/tests/unit/test_utils_subprocess.py +810 -0
  29. iris_devtester-1.19.4/tests/unit/test_wait_dev_unit.py +1143 -0
  30. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/unit/test_wait_strategies.py +1 -34
  31. iris_devtester-1.19.4/tests/unit/utils/test_dbapi_compat.py +555 -0
  32. iris_devtester-1.19.2/tests/unit/test_auto_discovery.py +0 -456
  33. iris_devtester-1.19.2/tests/unit/utils/test_dbapi_compat.py +0 -183
  34. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/AGENTS.md +0 -0
  35. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/CLAUDE.md +0 -0
  36. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/LICENSE +0 -0
  37. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/MANIFEST.in +0 -0
  38. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/README.md +0 -0
  39. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/docs/GETTING_STARTED.md +0 -0
  40. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/docs/LANGCHAIN_INTEGRATION_QUICKSTART.md +0 -0
  41. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/docs/ROADMAP.md +0 -0
  42. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/docs/SQL_VS_OBJECTSCRIPT.md +0 -0
  43. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/docs/TROUBLESHOOTING.md +0 -0
  44. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/docs/WHATS_NEW_v1.4.0.md +0 -0
  45. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/docs/archive/AGENTIC_SANDBOX_ENHANCEMENT_REPORT.md +0 -0
  46. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/docs/archive/IMPACT_ANALYSIS.md +0 -0
  47. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/docs/archive/IRIS_DEVTESTER_POSITIONING_REPORT.md +0 -0
  48. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/docs/archive/LANGCHAIN_INTEGRATION_STRATEGY.md +0 -0
  49. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/docs/archive/LANGCHAIN_INTEGRATION_TEST_RESULTS.md +0 -0
  50. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/docs/archive/PHASE_2_PLAN.md +0 -0
  51. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/docs/archive/RAG_TEMPLATES_ANALYSIS.md +0 -0
  52. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/docs/archive/README.md +0 -0
  53. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/docs/archive/STRATEGIC_ROADMAP_SUMMARY.md +0 -0
  54. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/docs/archive/feature-009-refactor-plan.md +0 -0
  55. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/docs/development/BUG_REPORT.md +0 -0
  56. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/docs/development/EXTRACTION_SUMMARY.md +0 -0
  57. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/docs/development/FEATURE_006_SUMMARY.md +0 -0
  58. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/docs/development/PHASE2_RESULTS.md +0 -0
  59. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/docs/development/PRE_PUBLISH_REVIEW.md +0 -0
  60. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/docs/development/PROGRESS.md +0 -0
  61. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/docs/development/PYPI_READINESS.md +0 -0
  62. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/docs/development/SESSION-INTEGRATION-TESTS.md +0 -0
  63. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/docs/development/SESSION-SUMMARY.md +0 -0
  64. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/docs/development/STATUS-002-PIVOTED.md +0 -0
  65. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/docs/development/STATUS-003-COMPLETE.md +0 -0
  66. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/docs/development/STATUS.md +0 -0
  67. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/docs/development/V1_COMPLETION_PLAN.md +0 -0
  68. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/docs/development/V1_RELEASE_SUMMARY.md +0 -0
  69. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/docs/features/WSGI-ASGI-SERVER-SETUP.md +0 -0
  70. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/docs/features/agent-skills.md +0 -0
  71. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/docs/features/dat-fixtures.md +0 -0
  72. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/docs/features/docker-compose.md +0 -0
  73. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/docs/features/performance-monitoring.md +0 -0
  74. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/docs/features/testcontainers.md +0 -0
  75. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/docs/learnings/api-contract-synchronization.md +0 -0
  76. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/docs/learnings/callin-service-requirement.md +0 -0
  77. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/docs/learnings/cpf-merge-and-container-restarts.md +0 -0
  78. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/docs/learnings/dat-fixtures-docker-exec-pattern.md +0 -0
  79. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/docs/learnings/dat-restore-database-isolation-problem.md +0 -0
  80. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/docs/learnings/dbapi-bulk-insert-performance-issue.md +0 -0
  81. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/docs/learnings/dbapi-objectscript-limitation.md +0 -0
  82. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/docs/learnings/dbapi-password-reset-limitation.md +0 -0
  83. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/docs/learnings/dbapi-private-module-myth.md +0 -0
  84. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/docs/learnings/docker-hub-image-naming.md +0 -0
  85. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/docs/learnings/embedded-python-considerations.md +0 -0
  86. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/docs/learnings/feature-002-pivot-summary.md +0 -0
  87. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/docs/learnings/integration-test-dbapi-limitation.md +0 -0
  88. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/docs/learnings/iris-backup-patterns.md +0 -0
  89. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/docs/learnings/iris-connection-access-denied-bug.md +0 -0
  90. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/docs/learnings/iris-container-dind-port-mapping.md +0 -0
  91. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/docs/learnings/iris-container-graceful-shutdown.md +0 -0
  92. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/docs/learnings/iris-container-performance.md +0 -0
  93. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/docs/learnings/iris-container-readiness.md +0 -0
  94. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/docs/learnings/iris-container-volume-permissions.md +0 -0
  95. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/docs/learnings/iris-docker-sdk-environment-variables.md +0 -0
  96. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/docs/learnings/iris-performance-monitoring-landscape.md +0 -0
  97. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/docs/learnings/iris-security-users-api.md +0 -0
  98. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/docs/learnings/irishealth-editions.md +0 -0
  99. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/docs/learnings/namespace-sql-vs-iris-connect.md +0 -0
  100. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/docs/learnings/password-reset-changeflag-fix.md +0 -0
  101. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/docs/learnings/rag-templates-production-patterns.md +0 -0
  102. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/docs/learnings/sql-task-manager-operations.md +0 -0
  103. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/docs/learnings/test-isolation-fix-summary.md +0 -0
  104. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/docs/learnings/test-isolation-investigation.md +0 -0
  105. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/docs/learnings/testcontainers-ryuk-lifecycle.md +0 -0
  106. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/docs/troubleshooting/table-not-found.md +0 -0
  107. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/examples/01_quickstart.py +0 -0
  108. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/examples/02_connection_management.py +0 -0
  109. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/examples/04_pytest_fixtures.py +0 -0
  110. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/examples/05_ci_cd.py +0 -0
  111. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/examples/08_auto_discovery.py +0 -0
  112. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/examples/09_enterprise.py +0 -0
  113. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/examples/10_docker_compose_integration.py +0 -0
  114. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/examples/langchain_integration_example.py +0 -0
  115. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/iris_devtester/__main__.py +0 -0
  116. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/iris_devtester/cli/__init__.py +0 -0
  117. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/iris_devtester/cli/__main__.py +0 -0
  118. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/iris_devtester/cli/connection_commands.py +0 -0
  119. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/iris_devtester/cli/container.py +0 -0
  120. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/iris_devtester/cli/container_commands.py +0 -0
  121. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/iris_devtester/cli/dev_commands.py +0 -0
  122. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/iris_devtester/cli/fixture_commands.py +0 -0
  123. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/iris_devtester/config/__init__.py +0 -0
  124. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/iris_devtester/config/auto_discovery.py +0 -0
  125. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/iris_devtester/config/container_config.py +0 -0
  126. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/iris_devtester/config/container_state.py +0 -0
  127. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/iris_devtester/config/defaults.py +0 -0
  128. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/iris_devtester/config/discovery.py +0 -0
  129. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/iris_devtester/config/models.py +0 -0
  130. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/iris_devtester/config/presets.py +0 -0
  131. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/iris_devtester/config/yaml_loader.py +0 -0
  132. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/iris_devtester/connections/__init__.py +0 -0
  133. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/iris_devtester/connections/auto_discovery.py +0 -0
  134. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/iris_devtester/connections/connection.py +0 -0
  135. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/iris_devtester/connections/cursor_wrapper.py +0 -0
  136. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/iris_devtester/connections/dbapi.py +0 -0
  137. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/iris_devtester/connections/jdbc.py +0 -0
  138. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/iris_devtester/connections/manager.py +0 -0
  139. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/iris_devtester/connections/models.py +0 -0
  140. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/iris_devtester/connections/retry.py +0 -0
  141. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/iris_devtester/containers/__init__.py +0 -0
  142. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/iris_devtester/containers/connection_info.py +0 -0
  143. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/iris_devtester/containers/dev_instance.py +0 -0
  144. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/iris_devtester/containers/models.py +0 -0
  145. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/iris_devtester/containers/monitor_utils.py +0 -0
  146. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/iris_devtester/containers/monitoring.py +0 -0
  147. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/iris_devtester/containers/performance.py +0 -0
  148. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/iris_devtester/containers/validation.py +0 -0
  149. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/iris_devtester/containers/wait_strategies.py +0 -0
  150. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/iris_devtester/diagnostics.py +0 -0
  151. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/iris_devtester/fixtures/__init__.py +0 -0
  152. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/iris_devtester/fixtures/creator.py +0 -0
  153. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/iris_devtester/fixtures/loader.py +0 -0
  154. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/iris_devtester/fixtures/manifest.py +0 -0
  155. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/iris_devtester/fixtures/obj_export.py +0 -0
  156. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/iris_devtester/fixtures/validator.py +0 -0
  157. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/iris_devtester/integrations/__init__.py +0 -0
  158. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/iris_devtester/integrations/langchain.py +0 -0
  159. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/iris_devtester/ports/__init__.py +0 -0
  160. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/iris_devtester/ports/assignment.py +0 -0
  161. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/iris_devtester/ports/exceptions.py +0 -0
  162. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/iris_devtester/ports/registry.py +0 -0
  163. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/iris_devtester/testing/__init__.py +0 -0
  164. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/iris_devtester/testing/fixtures.py +0 -0
  165. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/iris_devtester/testing/helpers.py +0 -0
  166. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/iris_devtester/testing/models.py +0 -0
  167. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/iris_devtester/testing/schema_reset.py +0 -0
  168. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/iris_devtester/utils/__init__.py +0 -0
  169. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/iris_devtester/utils/container_port.py +0 -0
  170. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/iris_devtester/utils/container_status.py +0 -0
  171. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/iris_devtester/utils/dbapi_compat.py +0 -0
  172. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/iris_devtester/utils/enable_callin.py +0 -0
  173. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/iris_devtester/utils/health_checks.py +0 -0
  174. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/iris_devtester/utils/iris_container_adapter.py +0 -0
  175. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/iris_devtester/utils/namespace.py +0 -0
  176. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/iris_devtester/utils/password.py +0 -0
  177. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/iris_devtester/utils/progress.py +0 -0
  178. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/iris_devtester/utils/test_connection.py +0 -0
  179. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/iris_devtester.egg-info/dependency_links.txt +0 -0
  180. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/iris_devtester.egg-info/entry_points.txt +0 -0
  181. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/iris_devtester.egg-info/requires.txt +0 -0
  182. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/iris_devtester.egg-info/top_level.txt +0 -0
  183. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/setup.cfg +0 -0
  184. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/__init__.py +0 -0
  185. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/conftest.py +0 -0
  186. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/contract/__init__.py +0 -0
  187. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/contract/cli/test_container_logs_contract.py +0 -0
  188. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/contract/cli/test_container_remove_contract.py +0 -0
  189. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/contract/cli/test_container_restart_contract.py +0 -0
  190. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/contract/cli/test_container_start_contract.py +0 -0
  191. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/contract/cli/test_container_status_contract.py +0 -0
  192. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/contract/cli/test_container_stop_contract.py +0 -0
  193. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/contract/cli/test_container_up_contract.py +0 -0
  194. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/contract/test_031_diagnostics.py +0 -0
  195. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/contract/test_032_health_check.py +0 -0
  196. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/contract/test_033_health_editions.py +0 -0
  197. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/contract/test_034_cpf_password.py +0 -0
  198. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/contract/test_035_graceful_shutdown.py +0 -0
  199. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/contract/test_agent_skills.py +0 -0
  200. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/contract/test_cli_container_commands.py +0 -0
  201. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/contract/test_cli_fixture_commands.py +0 -0
  202. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/contract/test_cli_ux_contract.py +0 -0
  203. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/contract/test_connection_api.py +0 -0
  204. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/contract/test_container_api.py +0 -0
  205. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/contract/test_container_integration_contract.py +0 -0
  206. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/contract/test_container_status_api.py +0 -0
  207. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/contract/test_container_validation_api.py +0 -0
  208. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/contract/test_dind_port_mapping.py +0 -0
  209. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/contract/test_enable_callin_api.py +0 -0
  210. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/contract/test_fixture_creator_api.py +0 -0
  211. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/contract/test_fixture_loader_api.py +0 -0
  212. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/contract/test_fixture_validator_api.py +0 -0
  213. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/contract/test_iriscontainer_attach.py +0 -0
  214. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/contract/test_legacy_package_contract.py +0 -0
  215. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/contract/test_modern_package_contract.py +0 -0
  216. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/contract/test_monitoring_config_api.py +0 -0
  217. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/contract/test_package_detection.py +0 -0
  218. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/contract/test_port_registry_contract.py +0 -0
  219. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/contract/test_preconfig_contract.py +0 -0
  220. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/contract/test_reset_verification_contract.py +0 -0
  221. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/contract/test_resource_monitoring_api.py +0 -0
  222. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/contract/test_retry_logic_contract.py +0 -0
  223. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/contract/test_task_manager_api.py +0 -0
  224. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/contract/test_test_connection_api.py +0 -0
  225. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/contract/test_testing_fixtures_api.py +0 -0
  226. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/e2e/__init__.py +0 -0
  227. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/e2e/test_port_registry_e2e.py +0 -0
  228. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/e2e/test_skill_workflow.py +0 -0
  229. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/integration/__init__.py +0 -0
  230. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/integration/conftest.py +0 -0
  231. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/integration/ports/__init__.py +0 -0
  232. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/integration/ports/test_manual_port_override.py +0 -0
  233. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/integration/ports/test_multi_project_isolation.py +0 -0
  234. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/integration/ports/test_port_exhaustion.py +0 -0
  235. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/integration/ports/test_port_persistence.py +0 -0
  236. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/integration/ports/test_stale_cleanup.py +0 -0
  237. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/integration/test_bug_fixes.py +0 -0
  238. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/integration/test_bug_fixes_011.py +0 -0
  239. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/integration/test_cli_container_integration.py +0 -0
  240. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/integration/test_connection_info_integration.py +0 -0
  241. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/integration/test_connection_integration.py +0 -0
  242. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/integration/test_container_health_monitor.py +0 -0
  243. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/integration/test_container_status_integration.py +0 -0
  244. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/integration/test_container_validation.py +0 -0
  245. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/integration/test_cpf_merge.py +0 -0
  246. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/integration/test_cpf_presets.py +0 -0
  247. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/integration/test_cpf_security.py +0 -0
  248. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/integration/test_custom_user_remediation.py +0 -0
  249. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/integration/test_dev_instance.py +0 -0
  250. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/integration/test_enable_callin_integration.py +0 -0
  251. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/integration/test_fixture_export_patterns.py +0 -0
  252. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/integration/test_fixture_performance.py +0 -0
  253. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/integration/test_fixture_refresh.py +0 -0
  254. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/integration/test_fixtures_integration.py +0 -0
  255. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/integration/test_implicit_namespace.py +0 -0
  256. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/integration/test_iriscontainer_attach_integration.py +0 -0
  257. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/integration/test_monitor_utils_integration.py +0 -0
  258. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/integration/test_monitoring_integration.py +0 -0
  259. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/integration/test_password_preconfig_integration.py +0 -0
  260. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/integration/test_password_reset_both_editions.py +0 -0
  261. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/integration/test_password_reset_integration.py +0 -0
  262. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/integration/test_password_reset_macos.py +0 -0
  263. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/integration/test_password_reset_timing.py +0 -0
  264. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/integration/test_password_reset_validation.py +0 -0
  265. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/integration/test_proactive_password_reset.py +0 -0
  266. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/integration/test_real_world_problems.py +0 -0
  267. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/integration/test_reliable_password_reset.py +0 -0
  268. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/integration/test_test_connection_integration.py +0 -0
  269. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/unit/__init__.py +0 -0
  270. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/unit/config/test_config_hierarchy.py +0 -0
  271. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/unit/config/test_container_config.py +0 -0
  272. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/unit/config/test_yaml_loader.py +0 -0
  273. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/unit/test_config_discovery.py +0 -0
  274. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/unit/test_connection_fallback.py +0 -0
  275. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/unit/test_connection_info.py +0 -0
  276. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/unit/test_dev_instance_logic.py +0 -0
  277. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/unit/test_iris_config.py +0 -0
  278. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/unit/test_iris_connection_info.py +0 -0
  279. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/unit/test_manifest.py +0 -0
  280. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/unit/test_monitoring_policy.py +0 -0
  281. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/unit/test_namespace_utils.py +0 -0
  282. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/unit/test_password_preconfig_unit.py +0 -0
  283. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/unit/test_performance_metrics.py +0 -0
  284. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/unit/test_resource_thresholds.py +0 -0
  285. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/unit/test_retry.py +0 -0
  286. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/unit/test_schema_models.py +0 -0
  287. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/unit/test_schema_reset_logic.py +0 -0
  288. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/unit/test_validation_results.py +0 -0
  289. {iris_devtester-1.19.2 → iris_devtester-1.19.4}/tests/unit/utils/test_iris_container_adapter.py +0 -0
@@ -5,6 +5,26 @@ All notable changes to iris-devtester will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.19.4] - 2026-08-25 - Fix plaintext password in CPF merge + wildcard unexpire on attach
9
+
10
+ ### Fixed
11
+
12
+ - **`start()` passes plaintext password into `PasswordHash=` CPF field** (`containers/iris_container.py`): when `with_preconfigured_password()` was used, `start()` built a custom CPF merge that set `PasswordHash={plaintext}`. The `PasswordHash=` field in a CPF merge file expects a `hash,salt` PBKDF2 format string — passing plaintext produces a corrupt stored hash that causes every subsequent login to fail silently with `<ACCESS VIOLATION>`. Fixed by always using `CPFPreset.SECURE_DEFAULTS` for the CPF merge (handles CallIn enablement and expiry flags correctly) and applying any custom password post-start via `reset_password()` which uses `PasswordExternal` — the IRIS Security API field that accepts plaintext and handles PBKDF2 hashing internally.
13
+
14
+ ### Added
15
+
16
+ - **`IRISContainer.attach(unexpire_passwords=True)`** (`containers/iris_container.py`): enterprise and HealthShare images commonly ship with expired `_SYSTEM` passwords or `ChangePassword=1` set. Without wildcard unexpire, the first programmatic login silently fails. `attach()` now calls `unexpire_all_passwords(container_name)` after successful container inspection by default. Failure is swallowed — the container is already attached and the caller may have valid credentials. Pass `unexpire_passwords=False` to skip when credentials are known-good and you want to avoid the overhead.
17
+
18
+ ### Documentation
19
+
20
+ - **`CONSTITUTION.md` Principle 6** — added `_SYSTEM` Password Assumption section documenting: (1) community vs. enterprise default credential differences, (2) why `PasswordHash=` in CPF must never receive plaintext, (3) how IDT's CPF + post-start `reset_password()` dance works, and (4) the wildcard unexpire backstop in `attach()`.
21
+
22
+ ## [1.19.3] - 2026-08-02 - Fix attach() ignoring explicit port override
23
+
24
+ ### Fixed
25
+
26
+ - **`IRISContainer.attach()` ignores explicit port, always uses docker port lookup** (`containers/iris_container.py`): `attach()` had no port parameter. It always derived the host-side SuperServer port from `docker port <container> 1972/tcp`, which fails on macOS Docker Desktop with irishealth enterprise: Docker Desktop NAT rewrites the source IP from `127.0.0.1` to a bridge address, so IRIS RSTs the connection even though the port binding exists. The user-supplied `IVG_PORT=31971` was silently ignored. Added `port: Optional[int]` to `attach()`; when supplied, `_mapped_port` is pinned immediately so `get_mapped_port(1972)` returns it without calling `get_exposed_port()`. Also reads `IVG_PORT` and `IRIS_PORT` env vars as fallbacks when `port=` is not passed explicitly.
27
+
8
28
  ## [1.19.2] - 2026-07-30 - Fix connect_function ImportError when module absent from sys.modules
9
29
 
10
30
  ### Fixed
@@ -15,10 +15,12 @@ This constitution codifies the hard-won lessons, blind alleys avoided, and battl
15
15
  **ABSOLUTE REQUIREMENT**: This project uses `intersystems-irispython` as the ONLY Python package for IRIS connectivity.
16
16
 
17
17
  **FORBIDDEN PACKAGES**:
18
+
18
19
  - ❌ `intersystems-iris` (old package, deprecated)
19
20
  - ❌ Any other IRIS Python packages
20
21
 
21
22
  **Why This Matters**:
23
+
22
24
  - `intersystems-irispython` is the modern, maintained package (v5.3.0+)
23
25
  - `intersystems-iris` is the legacy package (v3.0.0+) with known issues
24
26
  - Mixing packages causes import conflicts and mysterious failures
@@ -35,6 +37,7 @@ This constitution codifies the hard-won lessons, blind alleys avoided, and battl
35
37
  This single document answers the most critical question in IRIS development: "How do I execute this operation?"
36
38
 
37
39
  **Why This Matters**:
40
+
38
41
  - Using the wrong execution method causes mysterious failures
39
42
  - 53 integration tests failed because of this misunderstanding
40
43
  - DBAPI is 3x faster for SQL but CANNOT execute ObjectScript
@@ -50,11 +53,13 @@ This single document answers the most critical question in IRIS development: "Ho
50
53
  **The Principle**: Infrastructure problems must be automatically detected and remediated without developer intervention.
51
54
 
52
55
  **Why It Matters**:
56
+
53
57
  - Password expiration errors have wasted hundreds of developer hours
54
58
  - Manual remediation breaks CI/CD pipelines
55
59
  - "Works on my machine" scenarios damage team productivity
56
60
 
57
61
  **Implementation Requirements**:
62
+
58
63
  - ✅ Password change required → automatic reset
59
64
  - ✅ Container not found → automatic start
60
65
  - ✅ Port conflicts → automatic port reassignment
@@ -62,11 +67,13 @@ This single document answers the most critical question in IRIS development: "Ho
62
67
  - ✅ Connection failures → automatic retry with backoff
63
68
 
64
69
  **Forbidden**:
70
+
65
71
  - ❌ Error messages without remediation steps
66
72
  - ❌ Requiring manual Docker commands
67
73
  - ❌ Silent failures without auto-recovery attempts
68
74
 
69
75
  **Example**:
76
+
70
77
  ```python
71
78
  # WRONG: Manual intervention required
72
79
  raise ConnectionError("Password change required. Run: docker exec...")
@@ -82,6 +89,7 @@ if "Password change required" in str(error):
82
89
  **The Principle**: Use the appropriate connection method based on what operation you need to perform.
83
90
 
84
91
  **Why It Matters**:
92
+
85
93
  - IRIS has **two distinct execution paths**: SQL (via DBAPI) and ObjectScript (via iris.connect())
86
94
  - Using the wrong tool causes mysterious failures
87
95
  - DBAPI is 3x faster for SQL operations
@@ -90,17 +98,18 @@ if "Password change required" in str(error):
90
98
 
91
99
  **The Decision Matrix**:
92
100
 
93
- | Operation Type | Use | Speed | Example |
94
- |----------------|-----|-------|---------|
95
- | SQL queries (SELECT, INSERT, UPDATE, DELETE) | **DBAPI** | Fast (3x) | `cursor.execute("SELECT * FROM MyTable")` |
96
- | SQL DDL (CREATE TABLE, DROP TABLE) | **DBAPI** | Fast (3x) | `cursor.execute("CREATE TABLE ...")` |
97
- | Backup/Restore namespace | **DBAPI** + $SYSTEM.OBJ.Execute() | Medium | `cursor.execute("SELECT $SYSTEM.OBJ.Execute('...')")` |
98
- | Create/Delete namespace | **iris.connect()** | Medium | `iris_obj.classMethodValue("Config.Namespaces", "Create", "TEST")` |
99
- | Task Manager operations | **iris.connect()** | Medium | `iris_obj.execute("Set task = ##class(%SYS.Task).%New()")` |
100
- | Global variables | **iris.connect()** | Medium | `iris_obj.set("^MyGlobal", "value")` |
101
- | User/password management | **docker exec** | Slow | `docker exec iris_db iris session IRIS ...` |
101
+ | Operation Type | Use | Speed | Example |
102
+ | -------------------------------------------- | --------------------------------- | --------- | ------------------------------------------------------------------ |
103
+ | SQL queries (SELECT, INSERT, UPDATE, DELETE) | **DBAPI** | Fast (3x) | `cursor.execute("SELECT * FROM MyTable")` |
104
+ | SQL DDL (CREATE TABLE, DROP TABLE) | **DBAPI** | Fast (3x) | `cursor.execute("CREATE TABLE ...")` |
105
+ | Backup/Restore namespace | **DBAPI** + $SYSTEM.OBJ.Execute() | Medium | `cursor.execute("SELECT $SYSTEM.OBJ.Execute('...')")` |
106
+ | Create/Delete namespace | **iris.connect()** | Medium | `iris_obj.classMethodValue("Config.Namespaces", "Create", "TEST")` |
107
+ | Task Manager operations | **iris.connect()** | Medium | `iris_obj.execute("Set task = ##class(%SYS.Task).%New()")` |
108
+ | Global variables | **iris.connect()** | Medium | `iris_obj.set("^MyGlobal", "value")` |
109
+ | User/password management | **docker exec** | Slow | `docker exec iris_db iris session IRIS ...` |
102
110
 
103
111
  **Implementation Requirements**:
112
+
104
113
  - ✅ Use DBAPI for all SQL operations (SELECT, INSERT, UPDATE, DELETE, CREATE TABLE)
105
114
  - ✅ Use iris.connect() for ObjectScript operations (namespaces, Task Manager, globals)
106
115
  - ✅ Use docker exec only for admin operations (password reset, system config)
@@ -108,6 +117,7 @@ if "Password change required" in str(error):
108
117
  - ✅ Log which connection type is used for each operation
109
118
 
110
119
  **Forbidden**:
120
+
111
121
  - ❌ `cursor.execute("DO ##class(...)...")` - DBAPI cannot execute ObjectScript
112
122
  - ❌ `cursor.execute("Set ^GlobalData = 'value'")` - DBAPI cannot set globals
113
123
  - ❌ Using iris.connect() for simple SQL queries (3x slower than DBAPI)
@@ -144,6 +154,7 @@ cursor.execute("SELECT $SYSTEM.OBJ.Execute('Do ##class(Config.Namespaces).Create
144
154
  ```
145
155
 
146
156
  **Performance Evidence**:
157
+
147
158
  ```
148
159
  Benchmark Results (1000 simple queries):
149
160
  - DBAPI: 2.3 seconds
@@ -163,23 +174,27 @@ For admin ops → Use docker exec (when iris.connect() unavailable)
163
174
  **The Principle**: Every test suite gets its own isolated IRIS instance unless explicitly shared.
164
175
 
165
176
  **Why It Matters**:
177
+
166
178
  - Shared databases cause test pollution
167
179
  - Parallel test execution requires isolation
168
180
  - Cleanup failures cascade to other tests
169
181
  - "Works alone but fails in suite" mysteries
170
182
 
171
183
  **Implementation Requirements**:
184
+
172
185
  - ✅ Testcontainers for test isolation
173
186
  - ✅ Unique namespaces per test class
174
187
  - ✅ test_run_id tracking for data cleanup
175
188
  - ✅ Automatic cleanup even on test failure
176
189
 
177
190
  **Forbidden**:
191
+
178
192
  - ❌ Shared databases without cleanup
179
193
  - ❌ Assuming tests run sequentially
180
194
  - ❌ Leaving test data behind
181
195
 
182
196
  **Scope Guidelines**:
197
+
183
198
  ```python
184
199
  # Module scope: Fast, shared state acceptable
185
200
  @pytest.fixture(scope="module")
@@ -199,12 +214,14 @@ def iris_db_isolated():
199
214
  **The Principle**: `pip install iris-devtester && pytest` must work without configuration.
200
215
 
201
216
  **Why It Matters**:
217
+
202
218
  - Reduces onboarding friction
203
219
  - Enables quick experimentation
204
220
  - Makes examples self-contained
205
221
  - CI/CD "just works"
206
222
 
207
223
  **Implementation Requirements**:
224
+
208
225
  - ✅ Sensible defaults for all configuration
209
226
  - ✅ Auto-discovery of IRIS instances
210
227
  - ✅ Community edition defaults
@@ -212,11 +229,13 @@ def iris_db_isolated():
212
229
  - ✅ Explicit configuration always possible
213
230
 
214
231
  **Forbidden**:
232
+
215
233
  - ❌ Required configuration files
216
234
  - ❌ Mandatory environment variables
217
235
  - ❌ Undocumented prerequisites
218
236
 
219
237
  **Configuration Hierarchy** (highest priority first):
238
+
220
239
  1. Explicit constructor arguments
221
240
  2. Environment variables
222
241
  3. .env files in project root
@@ -228,11 +247,13 @@ def iris_db_isolated():
228
247
  **The Principle**: Errors must be detected immediately with clear remediation steps.
229
248
 
230
249
  **Why It Matters**:
250
+
231
251
  - Debugging time is expensive
232
252
  - Stack traces without context are useless
233
253
  - Developers need actionable guidance
234
254
 
235
255
  **Implementation Requirements**:
256
+
236
257
  - ✅ Detect errors at initialization, not first use
237
258
  - ✅ Include "What went wrong" explanation
238
259
  - ✅ Include "How to fix it" remediation
@@ -240,11 +261,13 @@ def iris_db_isolated():
240
261
  - ✅ Link to relevant documentation
241
262
 
242
263
  **Forbidden**:
264
+
243
265
  - ❌ Generic error messages
244
266
  - ❌ Stack traces without explanation
245
267
  - ❌ "Contact administrator" without details
246
268
 
247
269
  **Example**:
270
+
248
271
  ```python
249
272
  # WRONG
250
273
  raise ConnectionError("Failed to connect")
@@ -275,12 +298,14 @@ raise ConnectionError(
275
298
  **The Principle**: Support both Community and Enterprise editions equally well.
276
299
 
277
300
  **Why It Matters**:
301
+
278
302
  - Different projects have different needs
279
303
  - Development often uses Community, production uses Enterprise
280
304
  - License management is complex
281
305
  - Mirror configurations are enterprise-only
282
306
 
283
307
  **Implementation Requirements**:
308
+
284
309
  - ✅ Community edition as default
285
310
  - ✅ Enterprise edition via license_key parameter
286
311
  - ✅ Auto-discovery of license files
@@ -288,28 +313,51 @@ raise ConnectionError(
288
313
  - ✅ Clear documentation of edition differences
289
314
 
290
315
  **Forbidden**:
316
+
291
317
  - ❌ Hardcoded edition assumptions
292
318
  - ❌ Enterprise-only code paths without community fallback
293
319
  - ❌ Obscure license errors
294
320
 
295
321
  **License Discovery Order**:
322
+
296
323
  1. Explicit `license_key` parameter
297
324
  2. `IRIS_LICENSE_KEY` environment variable
298
325
  3. `~/.iris/iris.key`
299
326
  4. `./iris.key` in project root
300
327
  5. Auto-discovered from Docker volume mounts
301
328
 
329
+ **`_SYSTEM` Password Assumption — Read Before Using Enterprise Images**:
330
+
331
+ IDT's default credential (`_SYSTEM`/`SYS`) reflects the factory default shipped in InterSystems community images. Enterprise and HealthShare images often ship with:
332
+
333
+ - Expired `_SYSTEM` password requiring immediate change at first login
334
+ - Different default password or no default password at all
335
+ - `ChangePassword=1` flag set, blocking programmatic login
336
+
337
+ `attach()` calls `unexpire_all_passwords()` by default (wildcard unexpire via
338
+ `UnExpireUserPasswords("*")`) to handle this silently. For `start()`-managed
339
+ containers the CPF merge sets `PasswordNeverExpires=1` for `SuperUser` and
340
+ `_SYSTEM` pre-boot, then `reset_password()` applies any custom password
341
+ post-start via `PasswordExternal` (accepts plaintext — **never** put plaintext
342
+ into `PasswordHash=` in a CPF file, which expects `hash,salt` PBKDF2 format).
343
+
344
+ If login fails against an enterprise image with "password expired" or "access
345
+ denied" even after attach, call `unexpire_all_passwords(container_name)` manually
346
+ or pass `unexpire_passwords=True` explicitly.
347
+
302
348
  ### 7. MEDICAL-GRADE RELIABILITY
303
349
 
304
350
  **The Principle**: All code must be battle-tested in production scenarios with comprehensive error handling.
305
351
 
306
352
  **Why It Matters**:
353
+
307
354
  - Healthcare applications require 99.9%+ uptime
308
355
  - Silent failures are unacceptable
309
356
  - Diagnostic data saves hours of debugging
310
357
  - Idempotency prevents cascading failures
311
358
 
312
359
  **Implementation Requirements**:
360
+
313
361
  - ✅ 95%+ test coverage
314
362
  - ✅ All error paths tested
315
363
  - ✅ Idempotent operations (safe to retry)
@@ -319,12 +367,14 @@ raise ConnectionError(
319
367
  - ✅ Health check endpoints
320
368
 
321
369
  **Forbidden**:
370
+
322
371
  - ❌ Untested error paths
323
372
  - ❌ Non-idempotent operations
324
373
  - ❌ Silent failures
325
374
  - ❌ Assumptions about state
326
375
 
327
376
  **Reliability Checklist**:
377
+
328
378
  ```python
329
379
  # Every operation must answer:
330
380
  - [ ] What happens if this fails?
@@ -340,24 +390,28 @@ raise ConnectionError(
340
390
  **The Principle**: MUST use the official `iris.connect()` interface from InterSystems documentation. NEVER use undocumented private attributes like `_DBAPI`.
341
391
 
342
392
  **Why It Matters**:
393
+
343
394
  - `_DBAPI` does NOT exist in intersystems-irispython (tested in v5.1.2 and v5.3.0)
344
395
  - Using non-existent attributes causes mysterious import failures
345
396
  - Official API is `iris.connect()` per InterSystems documentation
346
397
  - Private attributes are subject to change without warning
347
398
 
348
399
  **Implementation Requirements**:
400
+
349
401
  - ✅ Use `iris.connect()` for DBAPI connections (official DB-API 2.0 interface)
350
- - ✅ Follow official InterSystems documentation: https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=BPYNAT_pyapi
402
+ - ✅ Follow official InterSystems documentation: <https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=BPYNAT_pyapi>
351
403
  - ✅ Never import from private modules (anything starting with `_`)
352
404
  - ✅ Test compatibility with both v5.1.2 and v5.3.0
353
405
 
354
406
  **Forbidden**:
355
- - ❌ `from intersystems_iris.dbapi._DBAPI import connect` - **_DBAPI does not exist!**
407
+
408
+ - ❌ `from intersystems_iris.dbapi._DBAPI import connect` - **\_DBAPI does not exist!**
356
409
  - ❌ `iris._DBAPI.connect()` - **No such attribute!**
357
410
  - ❌ Any code relying on undocumented internal APIs
358
411
  - ❌ Assuming package structure without verification
359
412
 
360
413
  **Correct API Usage**:
414
+
361
415
  ```python
362
416
  # ✅ CORRECT - Official DB-API 2.0 interface
363
417
  import iris
@@ -378,6 +432,7 @@ iris._DBAPI.connect(...) # AttributeError!
378
432
  ```
379
433
 
380
434
  **Empirical Evidence**:
435
+
381
436
  ```python
382
437
  # Tested on intersystems-irispython v5.1.2 and v5.3.0
383
438
  import iris
@@ -386,8 +441,9 @@ hasattr(iris, 'connect') # True in BOTH versions!
386
441
  ```
387
442
 
388
443
  **Official Documentation**:
389
- - InterSystems Python API: https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=BPYNAT_pyapi
390
- - DB-API 2.0 Specification: https://peps.python.org/pep-0249/
444
+
445
+ - InterSystems Python API: <https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=BPYNAT_pyapi>
446
+ - DB-API 2.0 Specification: <https://peps.python.org/pep-0249/>
391
447
 
392
448
  **Reference**: See hipporag2-pipeline CONSTITUTION.md Principle 9 for detailed empirical testing results.
393
449
 
@@ -396,12 +452,14 @@ hasattr(iris, 'connect') # True in BOTH versions!
396
452
  **The Principle**: IRIS setup and connectivity should be invisible. A developer should go from "nothing" to "connected" with zero manual configuration.
397
453
 
398
454
  **Why It Matters**:
455
+
399
456
  - SQLite is the benchmark for "zero-friction" development
400
457
  - Infrastructure setup is a major barrier to IRIS adoption
401
458
  - Developer "flow" is interrupted by infrastructure management
402
459
  - If a user has to think about ports, passwords, or container lifecycles, we have more work to do.
403
460
 
404
461
  **Implementation Requirements**:
462
+
405
463
  - ✅ `get_connection()` with zero-config auto-discovery
406
464
  - ✅ Automatic container lifecycle management (transparent to the user)
407
465
  - ✅ Standardized default credentials (`_SYSTEM`/`SYS`)
@@ -409,11 +467,13 @@ hasattr(iris, 'connect') # True in BOTH versions!
409
467
  - ✅ Silent infrastructure: infrastructure "noise" (logs, status) is hidden unless an error occurs
410
468
 
411
469
  **Forbidden**:
470
+
412
471
  - ❌ Requiring manual Docker commands for basic connectivity
413
472
  - ❌ Mandatory configuration files for local dev
414
473
  - ❌ Exposing infrastructure complexity in the primary API
415
474
 
416
475
  **Example**:
476
+
417
477
  ```python
418
478
  # THE NORTH STAR EXPERIENCE
419
479
  from iris_devtester import IRISContainer
@@ -427,25 +487,28 @@ conn = IRISContainer.community().get_connection()
427
487
  **The Principle**: Failed approaches must be documented to prevent repetition.
428
488
 
429
489
  **Why It Matters**:
490
+
430
491
  - Developers waste time rediscovering "why not"
431
492
  - Institutional knowledge must be preserved
432
493
  - Context for design decisions is valuable
433
494
  - Prevents regression to worse solutions
434
495
 
435
496
  **Documented Blind Alleys**:
497
+
436
498
  - **Why not JDBC-only?** → DBAPI is 3x faster, see benchmark
437
499
  - **Why not shared test databases?** → Data pollution, see case study
438
500
  - **Why not manual password resets?** → CI/CD breaks, see incident report
439
501
  - **Why not port 1972 hardcoded?** → Conflicts in parallel tests, see issue #42
440
502
  - **Why not prefer `iris.connect()`?** → In `intersystems-irispython` 5.3.1+, `iris.connect` uses the `_elsdk_` driver which has a critical `SIGSEGV` bug with `FETCH FIRST N ROWS ONLY` syntax. Prefer `intersystems_iris.connect` (the stable `_IRISNative` path) instead.
441
503
 
442
-
443
504
  **Example Documentation**:
505
+
444
506
  ```markdown
445
507
  ## Why Not Use Docker Compose for Tests?
446
508
 
447
509
  **What we tried**: Using docker-compose.yml for test database
448
510
  **Why it didn't work**:
511
+
449
512
  - Parallel tests conflicted on ports
450
513
  - Cleanup required manual intervention
451
514
  - CI/CD required docker-compose installation
@@ -462,12 +525,14 @@ conn = IRISContainer.community().get_connection()
462
525
  ### Amendment Process
463
526
 
464
527
  Principles may be amended when:
528
+
465
529
  1. New evidence contradicts existing principle
466
530
  2. Technology landscape changes materially
467
531
  3. Production experience reveals gap
468
532
  4. Community consensus emerges
469
533
 
470
534
  **Amendment requires**:
535
+
471
536
  - Concrete evidence (benchmarks, case studies, incident reports)
472
537
  - Backwards compatibility analysis
473
538
  - Migration guide for existing code
@@ -477,18 +542,21 @@ Principles may be amended when:
477
542
  ### Enforcement
478
543
 
479
544
  **Pre-commit hooks** validate:
545
+
480
546
  - [ ] No hardcoded passwords or credentials
481
547
  - [ ] All database operations are idempotent
482
548
  - [ ] Error messages include remediation
483
549
  - [ ] Test isolation via testcontainers or unique namespaces
484
550
 
485
551
  **CI/CD validates**:
552
+
486
553
  - [ ] 95%+ test coverage
487
554
  - [ ] All platforms (Linux, Mac, Windows)
488
555
  - [ ] Both Community and Enterprise editions
489
556
  - [ ] Performance benchmarks (no regressions)
490
557
 
491
558
  **Code review checklist**:
559
+
492
560
  - [ ] Follows DBAPI-first principle
493
561
  - [ ] Automatic remediation implemented
494
562
  - [ ] Comprehensive error handling
@@ -498,12 +566,14 @@ Principles may be amended when:
498
566
  ## Version History
499
567
 
500
568
  ### v1.2.0 (2026-02-06)
569
+
501
570
  - Added Principle 9: SQLite-Level Ergonomics (The North Star)
502
571
  - Renumbered Principle 9 "Document the Blind Alleys" → Principle 10
503
572
  - Standardized on `_SYSTEM` as the default toolkit username
504
573
  - Renamed project to `iris-devtester` consistently across documentation
505
574
 
506
575
  ### v1.1.0 (2025-11-23)
576
+
507
577
  - Added Principle 8: Use Official IRIS Python API (No Private Attributes)
508
578
  - Documents empirically tested fact that `_DBAPI` does NOT exist in intersystems-irispython v5.1.2 or v5.3.0
509
579
  - Mandates use of official `iris.connect()` API per InterSystems documentation
@@ -511,6 +581,7 @@ Principles may be amended when:
511
581
  - Based on critical findings from hipporag2-pipeline project testing
512
582
 
513
583
  ### v1.0.0 (2025-10-05)
584
+
514
585
  - Initial constitution
515
586
  - 8 core principles established
516
587
  - Based on rag-templates production experience
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: iris-devtester
3
- Version: 1.19.2
3
+ Version: 1.19.4
4
4
  Summary: Battle-tested InterSystems IRIS infrastructure utilities for Python development
5
5
  Author-email: InterSystems Community <community@intersystems.com>
6
6
  Maintainer-email: Thomas Dyar <thomas.dyar@intersystems.com>
@@ -26,7 +26,7 @@ LangChain Integration:
26
26
  ... # Build your RAG app...
27
27
  """
28
28
 
29
- __version__ = "1.19.2"
29
+ __version__ = "1.19.4"
30
30
  __author__ = "InterSystems Community"
31
31
  __license__ = "MIT"
32
32
 
@@ -40,15 +40,18 @@ class TempCPFManager:
40
40
  file_path = os.path.abspath(tmp.name)
41
41
  self._temp_files.add(file_path)
42
42
 
43
- # Register for automatic cleanup
44
- weakref.finalize(self, self._delete_file, file_path)
43
+ # Register for automatic cleanup.
44
+ # Must use the unbound method — passing self._delete_file would hold
45
+ # a strong reference to self through the bound method, preventing GC.
46
+ weakref.finalize(self, TempCPFManager._delete_file, file_path)
45
47
 
46
48
  logger.debug(f"Created temporary CPF file: {file_path}")
47
49
  return file_path
48
50
  finally:
49
51
  tmp.close()
50
52
 
51
- def _delete_file(self, file_path: str):
53
+ @staticmethod
54
+ def _delete_file(file_path: str):
52
55
  """Internal helper to safely delete a file."""
53
56
  try:
54
57
  if os.path.exists(file_path):
@@ -7,6 +7,7 @@ from iris_devtester.config import IRISConfig
7
7
  from iris_devtester.connections import get_connection
8
8
  from iris_devtester.containers.connection_info import IRISConnectionInfo
9
9
  from iris_devtester.containers.models import ContainerHealth, ContainerHealthStatus, HealthCheckLevel, ValidationResult
10
+ from iris_devtester.utils.password import reset_password, unexpire_all_passwords
10
11
 
11
12
  logger = logging.getLogger(__name__)
12
13
 
@@ -350,7 +351,13 @@ class IRISContainer(IRISBase):
350
351
  return container
351
352
 
352
353
  @classmethod
353
- def attach(cls, container_name: str, **kwargs) -> "IRISContainer":
354
+ def attach(
355
+ cls,
356
+ container_name: str,
357
+ port: Optional[int] = None,
358
+ unexpire_passwords: bool = True,
359
+ **kwargs,
360
+ ) -> "IRISContainer":
354
361
  """
355
362
  Attach to an existing IRIS container by name.
356
363
 
@@ -360,18 +367,47 @@ class IRISContainer(IRISBase):
360
367
 
361
368
  Args:
362
369
  container_name: Name of the existing Docker container.
370
+ port: Host-side SuperServer port to use instead of the Docker-inspected
371
+ binding. Required when Docker Desktop NAT rewrites the source IP and
372
+ IRIS rejects the connection (macOS Docker Desktop with irishealth
373
+ enterprise). When supplied, the docker port lookup is skipped entirely.
374
+ Also honoured via ``IVG_PORT`` or ``IRIS_PORT`` environment variables
375
+ when ``port`` is not provided explicitly.
376
+ unexpire_passwords: When True (default), call unexpire_all_passwords() after
377
+ attaching. Enterprise images often ship with expired passwords; this
378
+ prevents silent login failures. Set to False when the container is known
379
+ to have valid credentials and you want to skip the overhead.
363
380
  **kwargs: Additional configuration (username, password, namespace).
364
381
 
365
382
  Returns:
366
383
  An IRISContainer instance attached to the existing container.
367
384
  """
385
+ import os
386
+
368
387
  if not container_name:
369
388
  raise ValueError("container_name must be a non-empty string")
370
389
 
390
+ # Resolve explicit port override: parameter > IVG_PORT > IRIS_PORT
391
+ if port is None:
392
+ for env_var in ("IVG_PORT", "IRIS_PORT"):
393
+ env_val = os.environ.get(env_var)
394
+ if env_val:
395
+ try:
396
+ port = int(env_val)
397
+ except ValueError:
398
+ pass
399
+ else:
400
+ break
401
+
371
402
  instance = cls(image="", name=container_name, **kwargs)
372
403
  instance._is_attached = True
373
404
  instance._container_name = container_name
374
405
 
406
+ # Pin the mapped port immediately so get_mapped_port(1972) never falls
407
+ # through to the docker port lookup, regardless of which code path below runs.
408
+ if port is not None:
409
+ instance._mapped_port = port
410
+
375
411
  try:
376
412
  import docker
377
413
 
@@ -379,15 +415,16 @@ class IRISContainer(IRISBase):
379
415
  container = client.containers.get(container_name)
380
416
  instance._container = container
381
417
 
382
- # If it's a mock container, we need to set these manually
383
418
  if not HAS_TESTCONTAINERS:
384
419
  instance.host = "localhost"
385
- # Try to find port from container ports
386
- ports = container.attrs.get("NetworkSettings", {}).get("Ports", {})
387
- if "1972/tcp" in ports and ports["1972/tcp"]:
388
- instance._mapped_port = int(ports["1972/tcp"][0]["HostPort"])
420
+ # Only read docker port binding when no explicit port was given
421
+ if port is None:
422
+ ports = container.attrs.get("NetworkSettings", {}).get("Ports", {})
423
+ if "1972/tcp" in ports and ports["1972/tcp"]:
424
+ instance._mapped_port = int(ports["1972/tcp"][0]["HostPort"])
389
425
  else:
390
- # testcontainers will handle discovery via get_config()
426
+ # testcontainers will handle host/port discovery via get_config(),
427
+ # but _mapped_port is already pinned above if port was supplied.
391
428
  instance.get_config()
392
429
 
393
430
  except Exception as e:
@@ -404,6 +441,15 @@ class IRISContainer(IRISBase):
404
441
  " 3. Check if your user has permission to access the Docker socket"
405
442
  )
406
443
 
444
+ if unexpire_passwords:
445
+ # Enterprise images often ship with expired passwords; wildcard unexpire
446
+ # prevents silent login failures. Failure is non-fatal — the container
447
+ # is already attached and the caller may supply valid credentials.
448
+ try:
449
+ unexpire_all_passwords(container_name)
450
+ except Exception:
451
+ pass
452
+
407
453
  return instance
408
454
 
409
455
  def with_name(self, name: str) -> "IRISContainer":
@@ -837,17 +883,14 @@ class IRISContainer(IRISBase):
837
883
  from iris_devtester.config.presets import CPFPreset
838
884
 
839
885
  if not hasattr(self, "_cpf_temp_files") or not self._cpf_temp_files:
840
- if self._preconfigure_password:
841
- cpf = (
842
- "[Actions]\n"
843
- "ModifyService:Name=%Service_CallIn,Enabled=1,AutheEnabled=48\n"
844
- f"ModifyUser:Name=SuperUser,PasswordHash={self._preconfigure_password},"
845
- "ChangePassword=0,PasswordNeverExpires=1\n"
846
- "ModifyUser:Name=_SYSTEM,ChangePassword=0,PasswordNeverExpires=1"
847
- )
848
- else:
849
- cpf = CPFPreset.SECURE_DEFAULTS
850
- self.with_cpf_merge(cpf)
886
+ # Always use SECURE_DEFAULTS for the CPF merge — it handles CallIn
887
+ # and clears expiry flags for SuperUser/_SYSTEM via proper CPF fields.
888
+ # When a custom password is requested we reset it post-start via
889
+ # PasswordExternal (plaintext accepted) rather than injecting it into
890
+ # PasswordHash= here, which requires a pre-computed PBKDF2 hash,salt
891
+ # string — passing plaintext into PasswordHash= sets a corrupt hash
892
+ # and makes every subsequent login fail silently.
893
+ self.with_cpf_merge(CPFPreset.SECURE_DEFAULTS)
851
894
 
852
895
  self._password_handled = True
853
896
 
@@ -866,6 +909,21 @@ class IRISContainer(IRISBase):
866
909
 
867
910
  super().start()
868
911
  self.get_config()
912
+
913
+ # Post-start: apply custom password via PasswordExternal (accepts plaintext).
914
+ # Must happen after super().start() so the container is ready.
915
+ if self._preconfigure_password:
916
+ config = self.get_config()
917
+ username = self._preconfigure_username or "SuperUser"
918
+ reset_password(
919
+ container_name=self.get_container_name(),
920
+ username=username,
921
+ new_password=self._preconfigure_password,
922
+ hostname=config.host,
923
+ port=config.port,
924
+ verify=False,
925
+ )
926
+
869
927
  self._password_preconfigured = True
870
928
  return self
871
929
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: iris-devtester
3
- Version: 1.19.2
3
+ Version: 1.19.4
4
4
  Summary: Battle-tested InterSystems IRIS infrastructure utilities for Python development
5
5
  Author-email: InterSystems Community <community@intersystems.com>
6
6
  Maintainer-email: Thomas Dyar <thomas.dyar@intersystems.com>