nene2-python 1.8.49__tar.gz → 1.8.51__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 (402) hide show
  1. {nene2_python-1.8.49 → nene2_python-1.8.51}/PKG-INFO +1 -1
  2. nene2_python-1.8.51/docs/field-trials/2026-05-field-trial-179.md +242 -0
  3. nene2_python-1.8.51/docs/field-trials/2026-05-field-trial-180.md +475 -0
  4. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/INDEX.md +7 -4
  5. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/todo/current.md +11 -6
  6. {nene2_python-1.8.49 → nene2_python-1.8.51}/pyproject.toml +1 -1
  7. {nene2_python-1.8.49 → nene2_python-1.8.51}/.env.example +0 -0
  8. {nene2_python-1.8.49 → nene2_python-1.8.51}/.github/workflows/ci.yml +0 -0
  9. {nene2_python-1.8.49 → nene2_python-1.8.51}/.github/workflows/docs.yml +0 -0
  10. {nene2_python-1.8.49 → nene2_python-1.8.51}/.github/workflows/publish.yml +0 -0
  11. {nene2_python-1.8.49 → nene2_python-1.8.51}/.gitignore +0 -0
  12. {nene2_python-1.8.49 → nene2_python-1.8.51}/.vitepress/config.mts +0 -0
  13. {nene2_python-1.8.49 → nene2_python-1.8.51}/.vitepress/theme/custom.css +0 -0
  14. {nene2_python-1.8.49 → nene2_python-1.8.51}/.vitepress/theme/index.ts +0 -0
  15. {nene2_python-1.8.49 → nene2_python-1.8.51}/AGENTS.md +0 -0
  16. {nene2_python-1.8.49 → nene2_python-1.8.51}/CHANGELOG.md +0 -0
  17. {nene2_python-1.8.49 → nene2_python-1.8.51}/CLAUDE.md +0 -0
  18. {nene2_python-1.8.49 → nene2_python-1.8.51}/Dockerfile +0 -0
  19. {nene2_python-1.8.49 → nene2_python-1.8.51}/LICENSE +0 -0
  20. {nene2_python-1.8.49 → nene2_python-1.8.51}/README.md +0 -0
  21. {nene2_python-1.8.49 → nene2_python-1.8.51}/alembic/README +0 -0
  22. {nene2_python-1.8.49 → nene2_python-1.8.51}/alembic/env.py +0 -0
  23. {nene2_python-1.8.49 → nene2_python-1.8.51}/alembic/script.py.mako +0 -0
  24. {nene2_python-1.8.49 → nene2_python-1.8.51}/alembic/versions/001_create_notes_and_tags_tables.py +0 -0
  25. {nene2_python-1.8.49 → nene2_python-1.8.51}/alembic.ini +0 -0
  26. {nene2_python-1.8.49 → nene2_python-1.8.51}/compose.yaml +0 -0
  27. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/adr/0001-toolchain.md +0 -0
  28. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/adr/0002-clean-architecture.md +0 -0
  29. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/adr/0003-security-first.md +0 -0
  30. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/adr/0004-ai-first-design.md +0 -0
  31. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/adr/0005-logging.md +0 -0
  32. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/adr/0006-rate-limiting.md +0 -0
  33. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/adr/0009-mcp-design.md +0 -0
  34. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/adr/0010-async-use-case.md +0 -0
  35. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/adr/0011-mcp-as-core-dependency.md +0 -0
  36. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/de/index.md +0 -0
  37. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/de/tutorials/getting-started.md +0 -0
  38. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/explanation/architecture.md +0 -0
  39. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/explanation/design-philosophy.md +0 -0
  40. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-1.md +0 -0
  41. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-10.md +0 -0
  42. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-100.md +0 -0
  43. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-101.md +0 -0
  44. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-102.md +0 -0
  45. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-103.md +0 -0
  46. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-104.md +0 -0
  47. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-105.md +0 -0
  48. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-106.md +0 -0
  49. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-107.md +0 -0
  50. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-108.md +0 -0
  51. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-109.md +0 -0
  52. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-11.md +0 -0
  53. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-110.md +0 -0
  54. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-111.md +0 -0
  55. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-112.md +0 -0
  56. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-113.md +0 -0
  57. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-114.md +0 -0
  58. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-115.md +0 -0
  59. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-116.md +0 -0
  60. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-117.md +0 -0
  61. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-118.md +0 -0
  62. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-119.md +0 -0
  63. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-12.md +0 -0
  64. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-120.md +0 -0
  65. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-121.md +0 -0
  66. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-122.md +0 -0
  67. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-123.md +0 -0
  68. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-124.md +0 -0
  69. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-125.md +0 -0
  70. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-126.md +0 -0
  71. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-127.md +0 -0
  72. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-128.md +0 -0
  73. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-129.md +0 -0
  74. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-13.md +0 -0
  75. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-130.md +0 -0
  76. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-131.md +0 -0
  77. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-132.md +0 -0
  78. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-133.md +0 -0
  79. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-134.md +0 -0
  80. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-135.md +0 -0
  81. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-136.md +0 -0
  82. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-137.md +0 -0
  83. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-138.md +0 -0
  84. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-139.md +0 -0
  85. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-14.md +0 -0
  86. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-140.md +0 -0
  87. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-141.md +0 -0
  88. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-142.md +0 -0
  89. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-143.md +0 -0
  90. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-144.md +0 -0
  91. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-145.md +0 -0
  92. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-146.md +0 -0
  93. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-147.md +0 -0
  94. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-148.md +0 -0
  95. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-149.md +0 -0
  96. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-15.md +0 -0
  97. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-150.md +0 -0
  98. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-151.md +0 -0
  99. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-152.md +0 -0
  100. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-153.md +0 -0
  101. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-154.md +0 -0
  102. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-155.md +0 -0
  103. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-156.md +0 -0
  104. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-157.md +0 -0
  105. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-158.md +0 -0
  106. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-159.md +0 -0
  107. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-16.md +0 -0
  108. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-160.md +0 -0
  109. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-161.md +0 -0
  110. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-162.md +0 -0
  111. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-163.md +0 -0
  112. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-164.md +0 -0
  113. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-165.md +0 -0
  114. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-166.md +0 -0
  115. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-167.md +0 -0
  116. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-168.md +0 -0
  117. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-169.md +0 -0
  118. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-17.md +0 -0
  119. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-170.md +0 -0
  120. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-171.md +0 -0
  121. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-172.md +0 -0
  122. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-173.md +0 -0
  123. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-174.md +0 -0
  124. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-175.md +0 -0
  125. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-176.md +0 -0
  126. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-177.md +0 -0
  127. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-178.md +0 -0
  128. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-18.md +0 -0
  129. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-19.md +0 -0
  130. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-2.md +0 -0
  131. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-20.md +0 -0
  132. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-21.md +0 -0
  133. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-22.md +0 -0
  134. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-23.md +0 -0
  135. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-24.md +0 -0
  136. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-25.md +0 -0
  137. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-26.md +0 -0
  138. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-27.md +0 -0
  139. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-28.md +0 -0
  140. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-29.md +0 -0
  141. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-3.md +0 -0
  142. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-30.md +0 -0
  143. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-31.md +0 -0
  144. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-32.md +0 -0
  145. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-33.md +0 -0
  146. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-34.md +0 -0
  147. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-35.md +0 -0
  148. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-36.md +0 -0
  149. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-37.md +0 -0
  150. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-38.md +0 -0
  151. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-39.md +0 -0
  152. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-4.md +0 -0
  153. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-40.md +0 -0
  154. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-41.md +0 -0
  155. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-42.md +0 -0
  156. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-43.md +0 -0
  157. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-44.md +0 -0
  158. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-45.md +0 -0
  159. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-46.md +0 -0
  160. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-47.md +0 -0
  161. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-48.md +0 -0
  162. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-49.md +0 -0
  163. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-5.md +0 -0
  164. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-50.md +0 -0
  165. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-51.md +0 -0
  166. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-52.md +0 -0
  167. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-53.md +0 -0
  168. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-54.md +0 -0
  169. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-55.md +0 -0
  170. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-56.md +0 -0
  171. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-57.md +0 -0
  172. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-58.md +0 -0
  173. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-59.md +0 -0
  174. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-6.md +0 -0
  175. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-60.md +0 -0
  176. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-61.md +0 -0
  177. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-62.md +0 -0
  178. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-63.md +0 -0
  179. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-64.md +0 -0
  180. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-65.md +0 -0
  181. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-66.md +0 -0
  182. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-67.md +0 -0
  183. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-68.md +0 -0
  184. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-69.md +0 -0
  185. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-7.md +0 -0
  186. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-70.md +0 -0
  187. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-71.md +0 -0
  188. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-72.md +0 -0
  189. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-73.md +0 -0
  190. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-74.md +0 -0
  191. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-75.md +0 -0
  192. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-76.md +0 -0
  193. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-77.md +0 -0
  194. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-78.md +0 -0
  195. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-79.md +0 -0
  196. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-8.md +0 -0
  197. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-80.md +0 -0
  198. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-81.md +0 -0
  199. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-82.md +0 -0
  200. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-83.md +0 -0
  201. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-84.md +0 -0
  202. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-85.md +0 -0
  203. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-86.md +0 -0
  204. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-87.md +0 -0
  205. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-88.md +0 -0
  206. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-89.md +0 -0
  207. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-9.md +0 -0
  208. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-90.md +0 -0
  209. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-91.md +0 -0
  210. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-92.md +0 -0
  211. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-93.md +0 -0
  212. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-94.md +0 -0
  213. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-95.md +0 -0
  214. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-96.md +0 -0
  215. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-97.md +0 -0
  216. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-98.md +0 -0
  217. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/field-trials/2026-05-field-trial-99.md +0 -0
  218. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/fr/index.md +0 -0
  219. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/fr/tutorials/getting-started.md +0 -0
  220. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/how-to/add-new-domain.md +0 -0
  221. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/how-to/api-versioning.md +0 -0
  222. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/how-to/async-use-case.md +0 -0
  223. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/how-to/background-tasks.md +0 -0
  224. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/how-to/configure-auth.md +0 -0
  225. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/how-to/cors.md +0 -0
  226. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/how-to/custom-auth-middleware.md +0 -0
  227. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/how-to/dependency-injection.md +0 -0
  228. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/how-to/domain-events.md +0 -0
  229. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/how-to/file-upload.md +0 -0
  230. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/how-to/lifespan-and-app-state.md +0 -0
  231. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/how-to/middleware-stack.md +0 -0
  232. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/how-to/new-project.md +0 -0
  233. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/how-to/problem-details.md +0 -0
  234. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/how-to/response-patterns.md +0 -0
  235. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/how-to/run-tests.md +0 -0
  236. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/how-to/soft-delete.md +0 -0
  237. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/how-to/sqlalchemy-repository.md +0 -0
  238. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/how-to/streaming.md +0 -0
  239. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/how-to/structured-logging.md +0 -0
  240. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/how-to/validation.md +0 -0
  241. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/how-to/webhook.md +0 -0
  242. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/howto/mcp-setup.md +0 -0
  243. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/index.md +0 -0
  244. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/ja/explanation/architecture.md +0 -0
  245. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/ja/explanation/design-philosophy.md +0 -0
  246. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/ja/how-to/add-new-domain.md +0 -0
  247. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/ja/how-to/configure-auth.md +0 -0
  248. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/ja/how-to/new-project.md +0 -0
  249. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/ja/how-to/run-tests.md +0 -0
  250. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/ja/how-to/sqlalchemy-repository.md +0 -0
  251. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/ja/howto/mcp-setup.md +0 -0
  252. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/ja/index.md +0 -0
  253. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/ja/reference/api.md +0 -0
  254. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/ja/reference/configuration.md +0 -0
  255. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/ja/reference/framework-modules.md +0 -0
  256. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/ja/tutorials/first-domain.md +0 -0
  257. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/ja/tutorials/getting-started.md +0 -0
  258. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/pt-br/index.md +0 -0
  259. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/pt-br/tutorials/getting-started.md +0 -0
  260. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/reference/api.md +0 -0
  261. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/reference/configuration.md +0 -0
  262. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/reference/framework-modules.md +0 -0
  263. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/roadmap.md +0 -0
  264. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/templates/field-trial-report.md +0 -0
  265. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/tutorials/first-domain.md +0 -0
  266. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/tutorials/getting-started.md +0 -0
  267. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/zh/index.md +0 -0
  268. {nene2_python-1.8.49 → nene2_python-1.8.51}/docs/zh/tutorials/getting-started.md +0 -0
  269. {nene2_python-1.8.49 → nene2_python-1.8.51}/package-lock.json +0 -0
  270. {nene2_python-1.8.49 → nene2_python-1.8.51}/package.json +0 -0
  271. {nene2_python-1.8.49 → nene2_python-1.8.51}/src/example/__init__.py +0 -0
  272. {nene2_python-1.8.49 → nene2_python-1.8.51}/src/example/__main__.py +0 -0
  273. {nene2_python-1.8.49 → nene2_python-1.8.51}/src/example/app.py +0 -0
  274. {nene2_python-1.8.49 → nene2_python-1.8.51}/src/example/comment/__init__.py +0 -0
  275. {nene2_python-1.8.49 → nene2_python-1.8.51}/src/example/comment/entity.py +0 -0
  276. {nene2_python-1.8.49 → nene2_python-1.8.51}/src/example/comment/exceptions.py +0 -0
  277. {nene2_python-1.8.49 → nene2_python-1.8.51}/src/example/comment/handler.py +0 -0
  278. {nene2_python-1.8.49 → nene2_python-1.8.51}/src/example/comment/repository.py +0 -0
  279. {nene2_python-1.8.49 → nene2_python-1.8.51}/src/example/comment/sqlalchemy_repository.py +0 -0
  280. {nene2_python-1.8.49 → nene2_python-1.8.51}/src/example/comment/use_case.py +0 -0
  281. {nene2_python-1.8.49 → nene2_python-1.8.51}/src/example/mcp.py +0 -0
  282. {nene2_python-1.8.49 → nene2_python-1.8.51}/src/example/note/__init__.py +0 -0
  283. {nene2_python-1.8.49 → nene2_python-1.8.51}/src/example/note/async_use_case.py +0 -0
  284. {nene2_python-1.8.49 → nene2_python-1.8.51}/src/example/note/entity.py +0 -0
  285. {nene2_python-1.8.49 → nene2_python-1.8.51}/src/example/note/exceptions.py +0 -0
  286. {nene2_python-1.8.49 → nene2_python-1.8.51}/src/example/note/handler.py +0 -0
  287. {nene2_python-1.8.49 → nene2_python-1.8.51}/src/example/note/repository.py +0 -0
  288. {nene2_python-1.8.49 → nene2_python-1.8.51}/src/example/note/sqlalchemy_repository.py +0 -0
  289. {nene2_python-1.8.49 → nene2_python-1.8.51}/src/example/note/use_case.py +0 -0
  290. {nene2_python-1.8.49 → nene2_python-1.8.51}/src/example/schema.py +0 -0
  291. {nene2_python-1.8.49 → nene2_python-1.8.51}/src/example/tag/__init__.py +0 -0
  292. {nene2_python-1.8.49 → nene2_python-1.8.51}/src/example/tag/entity.py +0 -0
  293. {nene2_python-1.8.49 → nene2_python-1.8.51}/src/example/tag/exceptions.py +0 -0
  294. {nene2_python-1.8.49 → nene2_python-1.8.51}/src/example/tag/handler.py +0 -0
  295. {nene2_python-1.8.49 → nene2_python-1.8.51}/src/example/tag/repository.py +0 -0
  296. {nene2_python-1.8.49 → nene2_python-1.8.51}/src/example/tag/sqlalchemy_repository.py +0 -0
  297. {nene2_python-1.8.49 → nene2_python-1.8.51}/src/example/tag/use_case.py +0 -0
  298. {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/__init__.py +0 -0
  299. {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/auth/__init__.py +0 -0
  300. {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/auth/api_key.py +0 -0
  301. {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/auth/bearer_token.py +0 -0
  302. {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/auth/deps.py +0 -0
  303. {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/auth/exceptions.py +0 -0
  304. {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/auth/interfaces.py +0 -0
  305. {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/auth/local_verifier.py +0 -0
  306. {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/cache/__init__.py +0 -0
  307. {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/cache/ttl.py +0 -0
  308. {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/config/__init__.py +0 -0
  309. {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/config/settings.py +0 -0
  310. {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/database/__init__.py +0 -0
  311. {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/database/exceptions.py +0 -0
  312. {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/database/health.py +0 -0
  313. {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/database/interfaces.py +0 -0
  314. {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/database/sqlalchemy_executor.py +0 -0
  315. {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/database/utils.py +0 -0
  316. {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/http/__init__.py +0 -0
  317. {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/http/etag.py +0 -0
  318. {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/http/health.py +0 -0
  319. {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/http/pagination.py +0 -0
  320. {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/http/problem_details.py +0 -0
  321. {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/log/__init__.py +0 -0
  322. {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/log/setup.py +0 -0
  323. {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/mcp/__init__.py +0 -0
  324. {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/mcp/http_client.py +0 -0
  325. {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/mcp/server.py +0 -0
  326. {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/middleware/__init__.py +0 -0
  327. {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/middleware/domain_exception.py +0 -0
  328. {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/middleware/error_handler.py +0 -0
  329. {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/middleware/request_id.py +0 -0
  330. {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/middleware/request_logging.py +0 -0
  331. {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/middleware/request_size_limit.py +0 -0
  332. {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/middleware/security_headers.py +0 -0
  333. {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/middleware/setup.py +0 -0
  334. {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/middleware/throttle.py +0 -0
  335. {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/py.typed +0 -0
  336. {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/security/__init__.py +0 -0
  337. {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/security/webhook.py +0 -0
  338. {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/use_case/__init__.py +0 -0
  339. {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/use_case/protocols.py +0 -0
  340. {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/validation/__init__.py +0 -0
  341. {nene2_python-1.8.49 → nene2_python-1.8.51}/src/nene2/validation/exceptions.py +0 -0
  342. {nene2_python-1.8.49 → nene2_python-1.8.51}/src/scripts/__init__.py +0 -0
  343. {nene2_python-1.8.49 → nene2_python-1.8.51}/src/scripts/export_openapi.py +0 -0
  344. {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/__init__.py +0 -0
  345. {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/conftest.py +0 -0
  346. {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/example/__init__.py +0 -0
  347. {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/example/comment/__init__.py +0 -0
  348. {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/example/comment/test_comment_http.py +0 -0
  349. {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/example/comment/test_comment_repository.py +0 -0
  350. {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/example/comment/test_comment_use_case.py +0 -0
  351. {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/example/conftest.py +0 -0
  352. {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/example/note/__init__.py +0 -0
  353. {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/example/note/test_async_note_use_case.py +0 -0
  354. {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/example/note/test_list_notes.py +0 -0
  355. {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/example/note/test_note_repository.py +0 -0
  356. {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/example/tag/__init__.py +0 -0
  357. {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/example/tag/test_tag_repository.py +0 -0
  358. {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/example/tag/test_tags.py +0 -0
  359. {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/example/test_cors.py +0 -0
  360. {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/example/test_mcp.py +0 -0
  361. {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/__init__.py +0 -0
  362. {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/auth/__init__.py +0 -0
  363. {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/auth/test_api_key.py +0 -0
  364. {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/auth/test_bearer_token.py +0 -0
  365. {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/auth/test_make_require_auth.py +0 -0
  366. {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/auth/test_token_issuer.py +0 -0
  367. {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/cache/__init__.py +0 -0
  368. {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/cache/test_ttl.py +0 -0
  369. {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/config/__init__.py +0 -0
  370. {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/config/test_settings.py +0 -0
  371. {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/database/__init__.py +0 -0
  372. {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/database/test_transaction.py +0 -0
  373. {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/database/test_utils.py +0 -0
  374. {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/http/__init__.py +0 -0
  375. {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/http/test_etag.py +0 -0
  376. {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/http/test_health.py +0 -0
  377. {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/http/test_pagination.py +0 -0
  378. {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/http/test_problem_details.py +0 -0
  379. {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/log/__init__.py +0 -0
  380. {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/log/test_setup.py +0 -0
  381. {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/mcp/__init__.py +0 -0
  382. {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/mcp/test_http_client.py +0 -0
  383. {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/mcp/test_server.py +0 -0
  384. {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/middleware/__init__.py +0 -0
  385. {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/middleware/test_error_handler.py +0 -0
  386. {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/middleware/test_request_id.py +0 -0
  387. {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/middleware/test_request_logging.py +0 -0
  388. {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/middleware/test_request_size_limit.py +0 -0
  389. {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/middleware/test_security_headers.py +0 -0
  390. {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/middleware/test_setup_middlewares.py +0 -0
  391. {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/middleware/test_simple_domain_handler.py +0 -0
  392. {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/middleware/test_throttle.py +0 -0
  393. {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/security/__init__.py +0 -0
  394. {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/security/test_webhook.py +0 -0
  395. {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/use_case/__init__.py +0 -0
  396. {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/use_case/test_protocols.py +0 -0
  397. {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/use_case/test_run_in_threadpool.py +0 -0
  398. {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/validation/__init__.py +0 -0
  399. {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/nene2/validation/test_exceptions.py +0 -0
  400. {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/scripts/__init__.py +0 -0
  401. {nene2_python-1.8.49 → nene2_python-1.8.51}/tests/scripts/test_export_openapi.py +0 -0
  402. {nene2_python-1.8.49 → nene2_python-1.8.51}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nene2-python
3
- Version: 1.8.49
3
+ Version: 1.8.51
4
4
  Summary: NENE2 Python — minimal API framework following NENE2's design philosophy
5
5
  Project-URL: Homepage, https://github.com/hideyukiMORI/nene2-python
6
6
  Project-URL: Repository, https://github.com/hideyukiMORI/nene2-python
@@ -0,0 +1,242 @@
1
+ # FT179: zlib モジュール
2
+
3
+ **日付**: 2026-05-21
4
+ **テーマ**: データ圧縮・解凍・CRC32/Adler-32 整合性検証・展開爆弾対策
5
+ **セキュリティ診断**: なし(179 % 3 = 2)
6
+
7
+ ---
8
+
9
+ ## 概要
10
+
11
+ Python 標準ライブラリの `zlib` モジュールを検証する。
12
+ 単純な圧縮・解凍にとどまらず、展開爆弾(decompression bomb)対策のストリーミング解凍、
13
+ CRC32 と Adler-32 の両チェックサムアルゴリズム、圧縮レベル 1〜9 の比較、
14
+ チャンク単位のストリーミング圧縮まで網羅し、Web API での実用的な使い方を検証する。
15
+
16
+ ---
17
+
18
+ ## 実装したサンプルアプリ
19
+
20
+ **場所**: `/home/xi/docker/nene2-python-FT/ft179-zlib/`
21
+
22
+ ### 主要機能
23
+
24
+ | 関数/クラス | 概要 |
25
+ |---|---|
26
+ | `compress(data, level)` | zlib 圧縮(入力 10MB 上限、`CompressResult` 返却) |
27
+ | `decompress(compressed_hex)` | hex 文字列から解凍(展開爆弾対策付き) |
28
+ | `_decompress_bytes(data)` | ストリーミング解凍コア(50MB 上限をチャンクごとに監視) |
29
+ | `decompress_streaming(data)` | raw bytes を受け取る解凍インターフェース |
30
+ | `compute_crc32(data)` | CRC32 チェックサム計算(`ChecksumResult` 返却) |
31
+ | `compute_adler32(data)` | Adler-32 チェックサム計算(`ChecksumResult` 返却) |
32
+ | `verify_crc32(data, expected_hex)` | CRC32 検証 |
33
+ | `verify_adler32(data, expected_hex)` | Adler-32 検証 |
34
+ | `compare_compression_levels(data)` | レベル 1〜9 の圧縮結果比較(`LevelComparison` リスト) |
35
+ | `compress_streaming(chunks, level)` | チャンクリストのストリーミング圧縮 |
36
+
37
+ ### HTTP エンドポイント
38
+
39
+ | メソッド | パス | 概要 |
40
+ |---|---|---|
41
+ | POST | `/compress` | データを zlib 圧縮(`level` 指定可) |
42
+ | POST | `/decompress` | zlib 圧縮データを解凍 |
43
+ | POST | `/checksum/crc32` | CRC32 チェックサム計算 |
44
+ | POST | `/checksum/adler32` | Adler-32 チェックサム計算 |
45
+ | POST | `/verify` | チェックサム検証(`algorithm` で切り替え) |
46
+ | POST | `/compress/levels` | レベル 1〜9 の圧縮比較 |
47
+
48
+ ---
49
+
50
+ ## テスト結果
51
+
52
+ **39 passed**(初回から全通過)
53
+
54
+ ```
55
+ 39 passed in 0.67s
56
+ ```
57
+
58
+ mypy: Success / ruff: All checks passed / pip-audit: PYSEC-2025-183(継続監視)
59
+
60
+ ---
61
+
62
+ ## 摩擦ポイント
63
+
64
+ **今回の FT では実装上の摩擦はゼロだった。**
65
+
66
+ APIRouter パターン(FT177 F-1 対応)を最初から適用し、テストが一発で全通過した。
67
+
68
+ ---
69
+
70
+ ## 観察点
71
+
72
+ ### 観察1: 展開爆弾(Decompression Bomb)対策にはストリーミング解凍が必須
73
+
74
+ `zlib.decompress()` は解凍後サイズをチェックする前に全データをメモリに展開する。
75
+ ゼロバイト 50MB を level=9 で圧縮すると数百バイトになり、解凍すると 50MB になる。
76
+ 悪意ある入力をそのまま `decompress()` すると OOM になりえる。
77
+
78
+ ```python
79
+ # 危険: 上限チェック前に全解凍
80
+ zlib.decompress(huge_compressed_data) # → OOM の危険
81
+
82
+ # 安全: ストリーミングでチャンクごとに上限チェック
83
+ decompressor = zlib.decompressobj()
84
+ total = 0
85
+ for offset in range(0, len(data), CHUNK_SIZE):
86
+ chunk = decompressor.decompress(data[offset : offset + CHUNK_SIZE])
87
+ total += len(chunk)
88
+ if total > MAX_OUTPUT_BYTES:
89
+ return None # 上限超過で早期終了
90
+ ```
91
+
92
+ `zlib.decompressobj()` を使うストリーミング方式により、
93
+ 解凍途中で上限(50MB)を超えたと判断できる。
94
+
95
+ ### 観察2: CRC32 と Adler-32 の使い分け
96
+
97
+ 両者とも `zlib` モジュールに含まれるが特性が異なる。
98
+
99
+ ```python
100
+ zlib.crc32(b"hello") & 0xFFFFFFFF # → 907060870
101
+ zlib.adler32(b"hello") & 0xFFFFFFFF # → 103547413
102
+ ```
103
+
104
+ | 特性 | CRC32 | Adler-32 |
105
+ |---|---|---|
106
+ | 用途 | ファイル整合性(PNG, ZIP, gzip) | zlib ストリームヘッダー |
107
+ | 計算速度 | やや遅い | 高速(加算のみ) |
108
+ | 小データ衝突耐性 | 高い | 低い(短文字列で衝突しやすい) |
109
+ | & 0xFFFFFFFF の必要性 | あり(符号ありの場合がある) | あり |
110
+
111
+ Web API で「ファイルのダウンロード整合性確認」には CRC32、
112
+ 「zlib ストリームの内部チェックサム」には Adler-32 が適している。
113
+
114
+ ### 観察3: 圧縮レベルの実効差
115
+
116
+ 繰り返しデータ(`b"hello world! " * 100 = 1300 bytes`)では、
117
+ レベル 1〜9 の差は小さいが高圧縮データでは差が出る。
118
+
119
+ ```python
120
+ results = compare_compression_levels(b"hello world! " * 100)
121
+ # level=1: 34 bytes (ratio=0.0262)
122
+ # level=9: 22 bytes (ratio=0.0169)
123
+ ```
124
+
125
+ 一般的な API ペイロード(JSON 等)ではレベル 6(デフォルト)が
126
+ 速度と圧縮率のバランス点として適切。
127
+
128
+ ### 観察4: ストリーミング圧縮の結果は oneshot と roundtrip 互換
129
+
130
+ `zlib.compressobj()` によるストリーミング圧縮の出力は、
131
+ `zlib.decompress()` や `zlib.decompressobj()` で正常に解凍できる。
132
+ チャンク境界に関係なくストリーム形式は同一なので、
133
+ ネットワーク越しの分割送信データをチャンク単位で圧縮してもラウンドトリップが保証される。
134
+
135
+ ```python
136
+ chunks = [data[i : i + 64] for i in range(0, len(data), 64)]
137
+ streamed = compress_streaming(chunks)
138
+ assert decompress_streaming(streamed) == data # ✅ 常に成立
139
+ ```
140
+
141
+ ---
142
+
143
+ ## nene2-python フレームワークとの統合
144
+
145
+ - `compress` / `decompress` エンドポイントは Content-Encoding 圧縮 API の基盤として使える
146
+ - `MAX_INPUT_BYTES = 10MB` + Pydantic `max_length=20_971_520`(hex 換算)で DoS 対策済み
147
+ - `MAX_OUTPUT_BYTES = 50MB` の展開爆弾対策は、ファイルアップロード API のメモリ安全性に直結
148
+ - `verify_crc32` / `verify_adler32` は `hmac.compare_digest` 相当の定数時間比較ではない点に注意
149
+ (チェックサム比較はタイミング攻撃対象にはならないため問題なし)
150
+ - `APIRouter` + `create_app()` パターン(FT177 F-1 対応)を最初から適用済み
151
+
152
+ ---
153
+
154
+ ## Developer Experience (DX) Review
155
+
156
+ ### ペルソナ1: 初心者(Python 歴1年・独学中・女性・バックエンド志望)
157
+
158
+ ファイルアップロード API で圧縮ストレージを実装しようとしている。
159
+
160
+ **ドキュメント理解**: `zlib.compress()` / `zlib.decompress()` のペアは直感的。
161
+ 圧縮レベルのデフォルト値(6)がなぜ最適なのかは公式ドキュメントに書いていない。
162
+ **事故リスク**: 高。`zlib.decompress()` に信頼できないデータを渡すと OOM になりうる。
163
+ `MAX_OUTPUT_BYTES` によるガードを知らずに実装すると本番で問題になる。
164
+ **規約の使いやすさ**: `hex()` / `bytes.fromhex()` の往復は Python 固有概念として最初の壁になる。
165
+
166
+ ### ペルソナ2: ロースキル経験者(Python 歴3-4年・スクリプト系・男性・SES)
167
+
168
+ 既存の zlib 圧縮コードをコピーして API に組み込もうとしている。
169
+
170
+ **コピペ可能性**: `compress()` / `decompress()` のラッパーは分かりやすい。
171
+ `_decompress_bytes()` の展開爆弾対策ロジックは読んでも「なぜ必要か」が分かりにくい。
172
+ **拡張時の罠**: `MAX_OUTPUT_BYTES` の定数を削除または増やすと展開爆弾に脆弱になる。
173
+ 「動いているから削ってもいいか」と判断する人がいる。
174
+ **セキュリティ的な事故リスク**: 高。展開爆弾対策なしの実装はサービス停止に直結する。
175
+
176
+ ### ペルソナ3: フロントエンド寄り経験者(React/TS 歴4年・バックエンド転向中・ノンバイナリ)
177
+
178
+ TypeScript の `pako`(zlib の JS 実装)に慣れており、Python で同じことをしようとしている。
179
+
180
+ **エラーレスポンスの質**: 400 Bad Request に具体的なメッセージが返るのは良い。
181
+ 圧縮爆弾で `None` が返ったときの 400 レスポンスがなぜ「Invalid compressed data」なのかは
182
+ クライアント実装側からは分かりにくい(「サイズ上限超過」と区別できない)。
183
+ **Python 固有概念の学習コスト**: `bytes.hex()` / `bytes.fromhex()` の往復は JS にない概念。
184
+ `zlib.decompressobj()` のストリーミング API は `pako` の `Inflate` に相当するが設計が異なる。
185
+ **事故リスク**: 低。HTTP 境界での Pydantic バリデーションが充実。
186
+
187
+ ### ペルソナ4: バックエンド経験者(Django/FastAPI 歴5-6年・男性・リードエンジニア)
188
+
189
+ zlib を直接使うより、HTTP レスポンスの Content-Encoding や S3 のサーバー側圧縮を使うことが多い。
190
+
191
+ **他フレームワークとの差異**: Django では `GZipMiddleware` が透過的に圧縮するため、
192
+ zlib を直接操作するコードは書かない。nene2-python では zlib 操作がアプリコードに露出しており、
193
+ ユースケースが明確(ファイルストレージ等)でなければ設計レビューで指摘される。
194
+ **nene2-python の薄さへの評価**: `_decompress_bytes()` の展開爆弾対策ロジックは再利用可能な
195
+ ミドルウェア候補。フレームワーク側に `DecompressionSizeLimitMiddleware` として組み込む価値がある。
196
+ **本番投入可能性**: 展開爆弾対策が明示的に実装されており、本番品質として評価できる。
197
+
198
+ ### ペルソナ5: シニアエンジニア(設計・コードレビュー担当・女性・10-12年)
199
+
200
+ **コードレビューチェックポイント**:
201
+ - [x] `zlib.decompress()` を直接呼ばず、ストリーミング解凍で上限チェックをしているか
202
+ - [x] `MAX_OUTPUT_BYTES` が `MAX_INPUT_BYTES` より大きいことを確認(圧縮率を考慮)
203
+ - [x] `verify_crc32` / `verify_adler32` の比較が文字列の `==` であることの妥当性
204
+ (チェックサム比較はタイミング攻撃対象外なので OK)
205
+
206
+ **チームでの安全な共有パターン**: `_decompress_bytes()` を内部 API として隠蔽し、
207
+ 公開 API は `decompress()` と `decompress_streaming()` の 2 つのみに絞った設計が良い。
208
+ **ツール追加の必要性**: `bandit` (ruff S ルール相当) の B322(`zlib.decompress` 直接使用)は
209
+ ruff にはないが、コードレビューチェックリストに追加すべき。
210
+
211
+ ### ペルソナ6: 設計者・ポリシー照合(nene2-python 設計ポリシー目線)
212
+
213
+ **ポリシー達成度**: 高
214
+ **「初心者でも安全な API」達成度**: 中
215
+ — `zlib.decompress()` 直接使用の罠は `_decompress_bytes()` の命名(アンダースコアで内部実装を示す)で
216
+ ある程度ガードできているが、stdlib の `zlib.decompress()` を直接呼ぶと再発する。
217
+ **設計上の負債**: 展開爆弾対策を nene2-python フレームワークの共通ユーティリティとして
218
+ `nene2.io.SafeDecompressor` 等に昇格させる価値がある。
219
+ **Follow-up Issue 候補**: なし(現状の実装で十分。フレームワーク統合は別 Issue で検討)
220
+
221
+ ---
222
+
223
+ ## Follow-up Issues
224
+
225
+ | 優先度 | タイトル | 種別 |
226
+ |---|---|---|
227
+ | 低 | `decompress` の 400 エラーメッセージをサイズ超過と不正データで分離する | feat |
228
+
229
+ ---
230
+
231
+ ## まとめ
232
+
233
+ FT179 では `zlib` モジュールを中心に、データ圧縮・解凍・チェックサム計算を実装した。
234
+ 39 テストが全通過し、mypy/ruff も問題なし。
235
+
236
+ 最大の発見は展開爆弾(Decompression Bomb)対策の必要性。
237
+ `zlib.decompress()` は解凍後サイズを事前チェックできないため、
238
+ `zlib.decompressobj()` によるストリーミング解凍でチャンクごとに上限(50MB)を監視する実装が必須。
239
+
240
+ APIRouter パターン(FT177 F-1 の改善)を最初から適用し、テストが一発で全通過した。
241
+
242
+ v1.8.50 としてリリース。