bantamkit 0.31.0__tar.gz → 0.32.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (217) hide show
  1. {bantamkit-0.31.0 → bantamkit-0.32.0}/PKG-INFO +1 -1
  2. {bantamkit-0.31.0 → bantamkit-0.32.0}/src/bantamkit/__init__.py +1 -1
  3. {bantamkit-0.31.0 → bantamkit-0.32.0}/src/bantamkit/memory/component.py +27 -1
  4. {bantamkit-0.31.0 → bantamkit-0.32.0}/src/bantamkit/shiftwork.py +27 -2
  5. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/test_memory_dream.py +61 -0
  6. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/test_shiftwork.py +142 -0
  7. {bantamkit-0.31.0 → bantamkit-0.32.0}/.gitignore +0 -0
  8. {bantamkit-0.31.0 → bantamkit-0.32.0}/README.md +0 -0
  9. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/contracts/default.yaml +0 -0
  10. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/evals/devteam/manifest.yaml +0 -0
  11. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/evals/devteam/repo/HISTORY.md +0 -0
  12. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/evals/devteam/repo/README.md +0 -0
  13. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/evals/devteam/repo/docs/architecture.md +0 -0
  14. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/evals/devteam/repo/docs/runbook.md +0 -0
  15. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/evals/devteam/repo/issues/142-settlement-timeout.md +0 -0
  16. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/evals/devteam/repo/patches/0009-retry-budget.patch +0 -0
  17. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/evals/devteam/repo/src/ledger/__init__.py +0 -0
  18. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/evals/devteam/repo/src/ledger/config.py +0 -0
  19. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/evals/devteam/repo/src/ledger/errors.py +0 -0
  20. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/evals/devteam/repo/src/ledger/posting.py +0 -0
  21. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/evals/devteam/repo/src/ledger/registry.py +0 -0
  22. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/evals/devteam/repo/src/ledger/report.py +0 -0
  23. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/evals/devteam/repo/src/ledger/retry.py +0 -0
  24. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/evals/devteam/repo/src/ledger/settle.py +0 -0
  25. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/evals/devteam/repo/src/ledger/validate.py +0 -0
  26. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/evals/devteam/repo/tests/test_posting.py +0 -0
  27. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/evals/devteam/repo/tests/test_settle.py +0 -0
  28. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/evals/devteam/tasks/dt-error-contract.yaml +0 -0
  29. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/evals/devteam/tasks/dt-handler-map.yaml +0 -0
  30. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/evals/devteam/tasks/dt-patch-before-after.yaml +0 -0
  31. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/evals/devteam/tasks/dt-retry-attempts.yaml +0 -0
  32. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/evals/devteam/tasks/dt-settlement-config.yaml +0 -0
  33. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/evals/devteam/tasks/dt-symbol-home.yaml +0 -0
  34. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/evals/devteam/tasks/dt-trace-blame.yaml +0 -0
  35. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/evals/devteam/tasks/dt-unread-key.yaml +0 -0
  36. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/evals/document/tasks/doc-large-in-137.yaml +0 -0
  37. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/evals/document/tasks/doc-large-in-359.yaml +0 -0
  38. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/evals/document/tasks/doc-large-in-372.yaml +0 -0
  39. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/evals/document/tasks/doc-large-out-11764.yaml +0 -0
  40. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/evals/document/tasks/doc-large-out-4137.yaml +0 -0
  41. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/evals/document/tasks/doc-large-out-8022.yaml +0 -0
  42. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/evals/document/tasks/doc-small-137.yaml +0 -0
  43. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/evals/document/tasks/doc-small-261.yaml +0 -0
  44. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/evals/document/tasks/doc-small-388.yaml +0 -0
  45. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/evals/fixtures/.gitkeep +0 -0
  46. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/evals/fixtures/catalog.json +0 -0
  47. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/evals/perturbations/task-completion.yaml +0 -0
  48. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/evals/tasks/.gitkeep +0 -0
  49. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/evals/tasks/extract-contact.yaml +0 -0
  50. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/evals/tasks/extract-invoice.yaml +0 -0
  51. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/evals/tasks/extract-order.yaml +0 -0
  52. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/evals/tasks/extract-schedule.yaml +0 -0
  53. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/evals/tasks/extract-versions.yaml +0 -0
  54. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/evals/tasks/nav-prod-port.yaml +0 -0
  55. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/evals/tasks/nav-release-bundle.yaml +0 -0
  56. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/evals/tasks/recall-audit-retention.yaml +0 -0
  57. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/evals/tasks/recall-cache-ttl.yaml +0 -0
  58. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/evals/tasks/recall-db-port.yaml +0 -0
  59. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/evals/tasks/recall-deploy.yaml +0 -0
  60. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/evals/tasks/recall-env-endpoint.yaml +0 -0
  61. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/evals/tasks/recall-oncall-rotation.yaml +0 -0
  62. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/evals/tasks/recall-oncall.yaml +0 -0
  63. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/evals/tasks/recall-org-quota.yaml +0 -0
  64. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/evals/tasks/recall-owner.yaml +0 -0
  65. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/evals/tasks/shop-basket-total.yaml +0 -0
  66. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/evals/tasks/shop-cheapest.yaml +0 -0
  67. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/evals/tasks/shop-compare.yaml +0 -0
  68. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/evals/tasks/shop-gadget-value.yaml +0 -0
  69. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/evals/tasks/shop-stock-total.yaml +0 -0
  70. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/evals/tasks/shop-total.yaml +0 -0
  71. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/pricing/default.json +0 -0
  72. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/profiles/default.yaml +0 -0
  73. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/profiles/patient.yaml +0 -0
  74. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/rubrics/.gitkeep +0 -0
  75. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/rubrics/code-quality.yaml +0 -0
  76. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/rubrics/grounded-completion.yaml +0 -0
  77. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/rubrics/task-completion.yaml +0 -0
  78. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/schemas/shiftwork-checkpoint.json +0 -0
  79. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/skills/.gitkeep +0 -0
  80. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/skills/file-graph.md +0 -0
  81. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/skills/memory.md +0 -0
  82. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/tools/.gitkeep +0 -0
  83. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/tools/bantamkit_read.json +0 -0
  84. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/tools/bantamkit_status.json +0 -0
  85. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/tools/build_identity.json +0 -0
  86. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/tools/document_list.json +0 -0
  87. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/tools/document_read.json +0 -0
  88. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/tools/file_graph.json +0 -0
  89. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/tools/memory_compact.json +0 -0
  90. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/tools/memory_dream.json +0 -0
  91. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/tools/memory_recall.json +0 -0
  92. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/tools/memory_save.json +0 -0
  93. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/tools/repo_map.json +0 -0
  94. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/tools/shiftwork_clock_in.json +0 -0
  95. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/tools/shiftwork_clock_out.json +0 -0
  96. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/tools/shiftwork_status.json +0 -0
  97. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/tools/skill_audit.json +0 -0
  98. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/tools/token_ledger.json +0 -0
  99. {bantamkit-0.31.0 → bantamkit-0.32.0}/_assets/tools/validate_json.json +0 -0
  100. {bantamkit-0.31.0 → bantamkit-0.32.0}/hatch_build.py +0 -0
  101. {bantamkit-0.31.0 → bantamkit-0.32.0}/pyproject.toml +0 -0
  102. {bantamkit-0.31.0 → bantamkit-0.32.0}/src/bantamkit/agent.py +0 -0
  103. {bantamkit-0.31.0 → bantamkit-0.32.0}/src/bantamkit/assets.py +0 -0
  104. {bantamkit-0.31.0 → bantamkit-0.32.0}/src/bantamkit/budget.py +0 -0
  105. {bantamkit-0.31.0 → bantamkit-0.32.0}/src/bantamkit/client.py +0 -0
  106. {bantamkit-0.31.0 → bantamkit-0.32.0}/src/bantamkit/contract.py +0 -0
  107. {bantamkit-0.31.0 → bantamkit-0.32.0}/src/bantamkit/criticreplay.py +0 -0
  108. {bantamkit-0.31.0 → bantamkit-0.32.0}/src/bantamkit/critique.py +0 -0
  109. {bantamkit-0.31.0 → bantamkit-0.32.0}/src/bantamkit/docmanifest.py +0 -0
  110. {bantamkit-0.31.0 → bantamkit-0.32.0}/src/bantamkit/docread.py +0 -0
  111. {bantamkit-0.31.0 → bantamkit-0.32.0}/src/bantamkit/evalrun.py +0 -0
  112. {bantamkit-0.31.0 → bantamkit-0.32.0}/src/bantamkit/eventlog.py +0 -0
  113. {bantamkit-0.31.0 → bantamkit-0.32.0}/src/bantamkit/filegraph.py +0 -0
  114. {bantamkit-0.31.0 → bantamkit-0.32.0}/src/bantamkit/hostinstall.py +0 -0
  115. {bantamkit-0.31.0 → bantamkit-0.32.0}/src/bantamkit/loopguard.py +0 -0
  116. {bantamkit-0.31.0 → bantamkit-0.32.0}/src/bantamkit/mcpreport.py +0 -0
  117. {bantamkit-0.31.0 → bantamkit-0.32.0}/src/bantamkit/mcpserver.py +0 -0
  118. {bantamkit-0.31.0 → bantamkit-0.32.0}/src/bantamkit/memory/__init__.py +0 -0
  119. {bantamkit-0.31.0 → bantamkit-0.32.0}/src/bantamkit/memory/__main__.py +0 -0
  120. {bantamkit-0.31.0 → bantamkit-0.32.0}/src/bantamkit/memory/divergence.py +0 -0
  121. {bantamkit-0.31.0 → bantamkit-0.32.0}/src/bantamkit/memory/dream.py +0 -0
  122. {bantamkit-0.31.0 → bantamkit-0.32.0}/src/bantamkit/memory/layers.py +0 -0
  123. {bantamkit-0.31.0 → bantamkit-0.32.0}/src/bantamkit/memory/store.py +0 -0
  124. {bantamkit-0.31.0 → bantamkit-0.32.0}/src/bantamkit/pdfread.py +0 -0
  125. {bantamkit-0.31.0 → bantamkit-0.32.0}/src/bantamkit/pricing.py +0 -0
  126. {bantamkit-0.31.0 → bantamkit-0.32.0}/src/bantamkit/profile.py +0 -0
  127. {bantamkit-0.31.0 → bantamkit-0.32.0}/src/bantamkit/repomap.py +0 -0
  128. {bantamkit-0.31.0 → bantamkit-0.32.0}/src/bantamkit/selfupdate.py +0 -0
  129. {bantamkit-0.31.0 → bantamkit-0.32.0}/src/bantamkit/skillaudit.py +0 -0
  130. {bantamkit-0.31.0 → bantamkit-0.32.0}/src/bantamkit/statusline.py +0 -0
  131. {bantamkit-0.31.0 → bantamkit-0.32.0}/src/bantamkit/structured.py +0 -0
  132. {bantamkit-0.31.0 → bantamkit-0.32.0}/src/bantamkit/textutil.py +0 -0
  133. {bantamkit-0.31.0 → bantamkit-0.32.0}/src/bantamkit/tokenledger.py +0 -0
  134. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/cli_exit_status_probe.py +0 -0
  135. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/conftest.py +0 -0
  136. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/data/docread/bad-crc.docx +0 -0
  137. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/data/docread/charref-4301-digits.html +0 -0
  138. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/data/docread/charset-table.json +0 -0
  139. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/data/docread/compression-method-9.docx +0 -0
  140. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/data/docread/corrupt-deflate.docx +0 -0
  141. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/data/docread/encrypted-member.docx +0 -0
  142. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/data/docread/encrypted-mimetype.odt +0 -0
  143. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/data/docread/eszett-cell-ref.xlsx +0 -0
  144. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/data/docread/internal-dtd-entity.docx +0 -0
  145. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/data/docread/rfc2231-charset.eml +0 -0
  146. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/data/docread/rfc822-nested-twice.eml +0 -0
  147. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/data/docread/unicode-digit-shared-string.xlsx +0 -0
  148. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/data/docread/x-uuencode.eml +0 -0
  149. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/data/f8404ab-perturbation-baseline.json +0 -0
  150. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/data/platform-assumption-baseline.json +0 -0
  151. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/data/served-tool-surface.json +0 -0
  152. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/docread_fixtures.py +0 -0
  153. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/perturbation_baseline_harness.py +0 -0
  154. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/rbp16_effect_probe.py +0 -0
  155. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/rbp18_payload_probe.py +0 -0
  156. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/test_adapter.py +0 -0
  157. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/test_agent.py +0 -0
  158. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/test_amendguard.py +0 -0
  159. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/test_bantamkit_read_tool.py +0 -0
  160. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/test_budget.py +0 -0
  161. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/test_build_identity.py +0 -0
  162. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/test_client.py +0 -0
  163. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/test_compaction_corpus_survey.py +0 -0
  164. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/test_conformance.py +0 -0
  165. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/test_conformance_harness_resilience.py +0 -0
  166. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/test_conformance_suite_table_gate.py +0 -0
  167. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/test_contract_fanout.py +0 -0
  168. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/test_criticreplay.py +0 -0
  169. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/test_critique.py +0 -0
  170. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/test_doc_commands_gate.py +0 -0
  171. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/test_docread.py +0 -0
  172. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/test_docread_ceilings.py +0 -0
  173. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/test_document_manifest_parity.py +0 -0
  174. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/test_document_setup.py +0 -0
  175. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/test_document_tasks.py +0 -0
  176. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/test_document_tools.py +0 -0
  177. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/test_encoding_gate.py +0 -0
  178. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/test_evalrun.py +0 -0
  179. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/test_eventlog.py +0 -0
  180. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/test_field_program_gates.py +0 -0
  181. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/test_field_programs.py +0 -0
  182. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/test_filegraph.py +0 -0
  183. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/test_hostinstall.py +0 -0
  184. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/test_install_shape.py +0 -0
  185. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/test_ladder_statistics.py +0 -0
  186. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/test_launcher_which.py +0 -0
  187. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/test_layers.py +0 -0
  188. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/test_loopguard.py +0 -0
  189. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/test_mcp_endpoint.py +0 -0
  190. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/test_mcpdrift.py +0 -0
  191. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/test_mcpreport.py +0 -0
  192. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/test_mcpserver.py +0 -0
  193. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/test_memory.py +0 -0
  194. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/test_memory_compact_tool.py +0 -0
  195. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/test_memory_component.py +0 -0
  196. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/test_memory_divergence.py +0 -0
  197. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/test_memory_layers.py +0 -0
  198. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/test_memory_store_tripwire.py +0 -0
  199. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/test_mutmatrix.py +0 -0
  200. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/test_newline_gate.py +0 -0
  201. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/test_packaging.py +0 -0
  202. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/test_pdfread.py +0 -0
  203. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/test_pinharness_ledger.py +0 -0
  204. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/test_platform_assumption_gate.py +0 -0
  205. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/test_pricing.py +0 -0
  206. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/test_repo_map_tool.py +0 -0
  207. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/test_repomap.py +0 -0
  208. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/test_selfupdate.py +0 -0
  209. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/test_served_tool_count_records.py +0 -0
  210. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/test_skillaudit.py +0 -0
  211. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/test_status_surface.py +0 -0
  212. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/test_statusline.py +0 -0
  213. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/test_structured.py +0 -0
  214. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/test_thread_exception_gate.py +0 -0
  215. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/test_tokenledger.py +0 -0
  216. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/test_tool_manifest.py +0 -0
  217. {bantamkit-0.31.0 → bantamkit-0.32.0}/tests/test_version_agreement.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: bantamkit
3
- Version: 0.31.0
3
+ Version: 0.32.0
4
4
  Summary: bantamweight tooling — harness primitives that lift small-model agents
5
5
  License-Expression: MIT
6
6
  Requires-Python: >=3.11
@@ -29,4 +29,4 @@ from bantamkit.structured import StructuredOutputError, extract_json, structured
29
29
  # file as its dynamic version source, so the wheel's metadata and the string the MCP
30
30
  # server advertises are the same committed bytes, and neither is a function of when
31
31
  # someone last ran `pip`.
32
- __version__ = "0.31.0"
32
+ __version__ = "0.32.0"
@@ -2,6 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import os
5
6
  from collections.abc import Iterator
6
7
  from contextlib import ExitStack, contextmanager
7
8
  from dataclasses import dataclass
@@ -153,6 +154,22 @@ def _profile_store() -> Path:
153
154
  return Path.home() / ".bantamkit" / "memory"
154
155
 
155
156
 
157
+ def _same_directory(a: Path, b: Path) -> bool:
158
+ """Whether two paths name ONE directory, symlinks and `/var` vs `/private/var` included.
159
+
160
+ Realpath, not string equality, and that distinction is measured rather than tidy: on
161
+ macOS the walk up from a cwd under `/var` returns `/private/var/...` while `Path.home()`
162
+ returns `/var/...`, so two spellings of one directory compare unequal as strings. The
163
+ Stop hook already compares the same two roots the same way and for the same reason
164
+ (`tools/hooks/bantamkit-hook.mjs`). When the resolution itself fails, an absolute-path
165
+ comparison is the honest fallback: it can only under-report a match, never invent one.
166
+ """
167
+ try:
168
+ return os.path.realpath(a) == os.path.realpath(b)
169
+ except OSError:
170
+ return a.absolute() == b.absolute()
171
+
172
+
156
173
  def _layer_label(root: Path) -> str:
157
174
  if root.parent.name == ".bantamkit":
158
175
  return root.parent.parent.name
@@ -201,7 +218,16 @@ class Memory:
201
218
  mem._layers.append(
202
219
  (f"extra:{_layer_label(grant)}", MemoryStore(grant, k=k, create=False), False)
203
220
  )
204
- mem._layers.append(("profile", MemoryStore(_profile_store(), k=k, create=False), False))
221
+ # ONE DIRECTORY IS ONE LAYER. The walk above starts at the cwd and climbs, so a
222
+ # session with no `.bantamkit` anywhere above it resolves `~/.bantamkit/memory` —
223
+ # the profile store — as its PROJECT store. Binding that directory a second time
224
+ # gave `dream` the same store twice: every fact collided with itself, was merged
225
+ # into itself, and the "profile copy" that was archived was the same file. It
226
+ # archived 20 of 20 of the user's real facts on 2026-09-10. Such a session has one
227
+ # layer, and `dream_outcome` already has a true thing to say about that.
228
+ profile_root = _profile_store()
229
+ if not _same_directory(profile_root, project_root):
230
+ mem._layers.append(("profile", MemoryStore(profile_root, k=k, create=False), False))
205
231
  return mem
206
232
 
207
233
  def setup(self, agent: Agent) -> None:
@@ -19,7 +19,10 @@ asset, same discipline:
19
19
  allowed, when `job.roles` names that role (AS-2) — a validation-time refusal
20
20
  taken BEFORE the log line is appended, so a rejected model never leaves an
21
21
  orphan accounting line behind; a role the map omits, or a checkpoint with no
22
- map, is unconstrained and behaves exactly as it did before the map existed;
22
+ map, is unconstrained and behaves exactly as it did before the map existed.
23
+ J47-4: a role the map DOES name whose value is not a list of model identifiers
24
+ is refused there too — an unreadable declaration allows no model, and the
25
+ refusal is the same structured one, taken in the same place;
23
26
  * every clock-out appends one line to `<checkpoint>.log.jsonl` beside the
24
27
  checkpoint (unit, role, status, ts, plus orchestrator-reported accounting)
25
28
  — the driver-log shape, so the history ring's 5-entry cap never loses
@@ -154,12 +157,31 @@ def _model_refusal(document: dict, unit_id: str, unit: dict, accounting: dict |
154
157
  the sentence says so. Measured, not asserted: the conformance corpus and
155
158
  both runtimes' tests drive this through a `BANTAMKIT_ASSETS` pack whose
156
159
  schema has no `minItems`, which is the only way to reach the branch at all.
160
+
161
+ AND A DECLARATION THIS CODE CANNOT READ IS NOT A LICENCE (J47-4). The same
162
+ ruling, one step further out: a role the map names is constrained by what it
163
+ NAMES, and a value that is not a list of model identifiers names nothing, so
164
+ it allows nothing. What made this a second case rather than a corollary is
165
+ that `", ".join(allowed)` answered for every shape without ever deciding one
166
+ — over a string it iterated CHARACTERS and refused while misquoting the
167
+ checkpoint back at its author, and over a number, a null, a bool or a list
168
+ holding a non-string it raised `TypeError` straight out of `clock_out`, which
169
+ is the one exit the ruling forbids: not a structured refusal, so not an
170
+ answer at all. Fail CLOSED and say only what is true — the declaration cannot
171
+ be read, therefore no model is allowed — without rendering the unreadable
172
+ value into the sentence. `[]` is untouched by this: an empty list IS a list
173
+ of model identifiers, and it keeps the sentence J46-10 pinned.
157
174
  """
158
175
  role = unit["role"]
159
176
  roles = document["job"].get("roles", {})
160
177
  if role not in roles:
161
178
  return None
162
179
  allowed = roles[role]
180
+ if not isinstance(allowed, list) or not all(isinstance(m, str) for m in allowed):
181
+ return (
182
+ f"unit {unit_id} in role {role} cannot clock out: job.roles.{role} "
183
+ f"is not a list of model identifiers, so it allows no model"
184
+ )
163
185
  names = ", ".join(allowed)
164
186
  offered = (accounting or {}).get("model")
165
187
  if offered is None:
@@ -189,7 +211,10 @@ def clock_out(
189
211
  (driver parity); anything else is a structured error. When `job.roles` names
190
212
  the unit's role, `accounting["model"]` must be one of that role's models,
191
213
  spelled exactly; a wrong or missing model is refused here, before any
192
- mutation and before the accounting line. Mutations: set the
214
+ mutation and before the accounting line. Extended 2026-09-11 (job47): so is a
215
+ `job.roles` value for that role that is not a list of model identifiers —
216
+ an unreadable declaration allows no model, and it is refused in the same
217
+ place, by the same structured return, writing nothing. Mutations: set the
193
218
  unit's status, advance `plan.cursor` to the first non-terminal unit
194
219
  (v1-linear, `depends_on` is ignored), shallow-merge `handoff_patch` into
195
220
  `handoff`, push `history_entry` onto the 5-entry ring. The mutated
@@ -776,6 +776,67 @@ def test_a_memory_with_no_profile_layer_says_so_rather_than_failing(tmp_path: Pa
776
776
  assert "no profile layer is bound" in outcome.reply
777
777
 
778
778
 
779
+ def test_one_directory_is_one_layer_when_the_walk_lands_on_the_profile_store(
780
+ tmp_path: Path, monkeypatch: pytest.MonkeyPatch
781
+ ) -> None:
782
+ """The 2026-09-10 incident, as a node. A cwd with no `.bantamkit` anywhere above it
783
+ walks up to `~/.bantamkit/memory`, so the PROJECT store resolves to the profile store
784
+ and `dream` is handed the same directory twice: every fact collides with itself, is
785
+ merged into itself, and the "profile copy" — the same file — is archived. It archived
786
+ 20 of 20 of the user's real facts. The property is that one directory is one layer, so
787
+ such a session has a single layer and the existing `no-profile-layer` outcome is the
788
+ true thing to say about it.
789
+ """
790
+ home = tmp_path / "home"
791
+ (home / "work").mkdir(parents=True)
792
+ monkeypatch.setattr(Path, "home", classmethod(lambda cls: home))
793
+ monkeypatch.setenv("HOME", str(home))
794
+ monkeypatch.delenv("BANTAMKIT_MEMORY_DIR", raising=False)
795
+ profile = MemoryStore(home / ".bantamkit" / "memory")
796
+ profile.save(
797
+ "project", "alpha-routing-rule", "how the alpha router picks a shard", "hash by tenant"
798
+ )
799
+
800
+ memory = Memory.layered(start=home / "work")
801
+ outcome = memory.dream_outcome(dry_run=False)
802
+
803
+ assert memory.store.root.resolve() == profile.root.resolve() # the walk landed on it
804
+ assert outcome.status == "no-profile-layer"
805
+ assert (outcome.merged, outcome.consumed) == (0, 0)
806
+ assert [fact.name for fact in profile._facts()] == ["alpha-routing-rule"]
807
+ assert list((profile.root / "archive").glob("*.md")) == []
808
+ assert [label for label, _, _ in memory._layers] == ["project"]
809
+
810
+
811
+ def test_one_directory_is_one_layer_through_a_symlinked_home(
812
+ tmp_path: Path, monkeypatch: pytest.MonkeyPatch
813
+ ) -> None:
814
+ """Two spellings of one directory are still one directory, so the comparison resolves.
815
+
816
+ Without this node a string comparison passes the node above: `tmp_path` hands back an
817
+ already-resolved path and both spellings agree. The case that forced the Stop hook to
818
+ use `fs.realpathSync` is exactly this one — macOS `/var` vs `/private/var`, where the
819
+ walk up from the cwd returns the resolved spelling and `Path.home()` returns the link.
820
+ """
821
+ real = tmp_path / "real-home"
822
+ (real / "work").mkdir(parents=True)
823
+ link = tmp_path / "linked-home"
824
+ link.symlink_to(real, target_is_directory=True)
825
+ monkeypatch.setattr(Path, "home", classmethod(lambda cls: link))
826
+ monkeypatch.setenv("HOME", str(link))
827
+ monkeypatch.delenv("BANTAMKIT_MEMORY_DIR", raising=False)
828
+ profile = MemoryStore(link / ".bantamkit" / "memory")
829
+ profile.save("user", "beta-timezone", "which timezone the operator works in", "Asia/Bangkok")
830
+
831
+ memory = Memory.layered(start=real / "work")
832
+ outcome = memory.dream_outcome(dry_run=False)
833
+
834
+ assert str(memory.store.root) != str(profile.root) # the two spellings really differ
835
+ assert outcome.status == "no-profile-layer"
836
+ assert [fact.name for fact in profile._facts()] == ["beta-timezone"]
837
+ assert list((profile.root / "archive").glob("*.md")) == []
838
+
839
+
779
840
  def test_the_outcome_status_is_read_off_the_decision_not_the_reply(tmp_path: Path) -> None:
780
841
  """The same seam `SaveOutcome`, `RecallOutcome` and `CompactOutcome` have: a caller that
781
842
  wants to tell the outcomes apart must never have to match prose."""
@@ -713,6 +713,148 @@ def test_an_absent_role_and_an_empty_list_are_different_inputs(
713
713
  assert ops.clock_out(str(empty), *args)["result"] == "error"
714
714
 
715
715
 
716
+ # --- J47-4: a roles value this code cannot read is not a licence -------------
717
+
718
+
719
+ def _pack_with_unconstrained_roles_values(tmp_path, schema):
720
+ """An asset pack whose checkpoint schema no longer says what a roles value IS.
721
+
722
+ `additionalProperties: true` on `job.roles` is the loosest the shared asset
723
+ could ever drift to, so it is the instrument that reaches every non-list
724
+ shape at once. Same `BANTAMKIT_ASSETS` override the empty-list ruling uses,
725
+ for the same reason: the check must hold without the schema's help.
726
+ """
727
+ mutated = copy.deepcopy(schema)
728
+ mutated["properties"]["job"]["properties"]["roles"]["additionalProperties"] = True
729
+ root = tmp_path / "anyroles"
730
+ (root / "schemas").mkdir(parents=True)
731
+ (root / "schemas" / "shiftwork-checkpoint.json").write_text(
732
+ json.dumps(mutated), encoding="utf-8"
733
+ )
734
+ return root
735
+
736
+
737
+ NOT_A_MODEL_LIST = [
738
+ pytest.param("claude-opus-5", id="str"),
739
+ pytest.param({"a": 1}, id="dict"),
740
+ pytest.param(5, id="int"),
741
+ pytest.param(None, id="null"),
742
+ pytest.param(True, id="bool"),
743
+ pytest.param([5], id="list-of-int"),
744
+ pytest.param(["ok", None], id="list-with-a-non-string"),
745
+ ]
746
+
747
+
748
+ @pytest.mark.parametrize("value", NOT_A_MODEL_LIST)
749
+ def test_a_roles_value_that_is_not_a_list_of_model_names_refuses_closed(
750
+ tmp_path, monkeypatch, schema, example, value
751
+ ):
752
+ """The J46-10 ruling's other half. A role the map NAMES is constrained by what
753
+ it names, and a value this code cannot read as a list of model identifiers
754
+ names nothing -- so it allows nothing.
755
+
756
+ Measured before the fix, with `accounting['model'] = 'haiku'`: `str` and
757
+ `dict` refused with the declared value mangled into single characters
758
+ (`', '.join` over a string iterates CHARACTERS), and `int`, `null`, `bool`
759
+ and a list holding a non-string all died with an uncaught `TypeError` out of
760
+ `clock_out` -- no structured answer at all, which is the one exit shape the
761
+ ruling forbids.
762
+ """
763
+ monkeypatch.setenv(
764
+ "BANTAMKIT_ASSETS", str(_pack_with_unconstrained_roles_values(tmp_path, schema))
765
+ )
766
+ example["job"]["roles"] = {"implementer": value}
767
+ path = write_checkpoint(tmp_path, example)
768
+ before = path.read_bytes()
769
+ assert ops.clock_out(
770
+ str(path), "U3", "done", {}, {"unit": "U3", "outcome": "done"}, ACCOUNTING
771
+ ) == {
772
+ "result": "error",
773
+ "reason": (
774
+ "unit U3 in role implementer cannot clock out: job.roles.implementer "
775
+ "is not a list of model identifiers, so it allows no model"
776
+ ),
777
+ }
778
+ # Offering no model is the same refusal: which model was reported cannot
779
+ # matter when the declaration that would judge it is unreadable.
780
+ assert ops.clock_out(
781
+ str(path), "U3", "done", {}, {"unit": "U3", "outcome": "done"}, {"tokens": 1}
782
+ ) == {
783
+ "result": "error",
784
+ "reason": (
785
+ "unit U3 in role implementer cannot clock out: job.roles.implementer "
786
+ "is not a list of model identifiers, so it allows no model"
787
+ ),
788
+ }
789
+ assert path.read_bytes() == before
790
+ assert read_log(path) == []
791
+
792
+
793
+ def test_an_unreadable_roles_value_never_mangles_the_declaration_into_characters(
794
+ tmp_path, monkeypatch, schema, example
795
+ ):
796
+ """The specific pre-fix defect, pinned so it cannot come back: a string value
797
+ refused, but rendered `claude-opus-5` as `c, l, a, u, d, e, -, o, p, u, s, -, 5`
798
+ -- a refusal sentence that misdescribes the checkpoint it is refusing.
799
+ """
800
+ monkeypatch.setenv(
801
+ "BANTAMKIT_ASSETS", str(_pack_with_unconstrained_roles_values(tmp_path, schema))
802
+ )
803
+ example["job"]["roles"] = {"implementer": "claude-opus-5"}
804
+ path = write_checkpoint(tmp_path, example)
805
+ reason = ops.clock_out(
806
+ str(path), "U3", "done", {}, {"unit": "U3", "outcome": "done"}, ACCOUNTING
807
+ )["reason"]
808
+ assert "c, l, a, u" not in reason
809
+
810
+
811
+ def test_the_relaxed_pack_is_what_lets_a_non_list_roles_value_through(
812
+ tmp_path, monkeypatch, schema, example
813
+ ):
814
+ """The companion that keeps the tests above honest, the empty-list ruling's own
815
+ companion by name: with the SHIPPED schema the same document is refused during
816
+ the read and never reaches the model check, so the two refusals are different
817
+ sentences and the gate is confirmed to reach the thing it checks.
818
+ """
819
+ example["job"]["roles"] = {"implementer": 5}
820
+ path = write_checkpoint(tmp_path, example)
821
+ assert ops.clock_out(
822
+ str(path), "U3", "done", {}, {"unit": "U3", "outcome": "done"}, ACCOUNTING
823
+ ) == {
824
+ "result": "error",
825
+ "reason": (
826
+ "checkpoint invalid: JSON does not match schema at "
827
+ "'job/roles/implementer': 5 is not of type 'array'"
828
+ ),
829
+ }
830
+ monkeypatch.setenv(
831
+ "BANTAMKIT_ASSETS", str(_pack_with_unconstrained_roles_values(tmp_path, schema))
832
+ )
833
+ assert "is not a list of model identifiers" in ops.clock_out(
834
+ str(path), "U3", "done", {}, {"unit": "U3", "outcome": "done"}, ACCOUNTING
835
+ )["reason"]
836
+
837
+
838
+ def test_an_empty_list_is_still_the_j46_10_sentence_not_the_unreadable_one(
839
+ tmp_path, monkeypatch, schema, example
840
+ ):
841
+ """`[]` IS a list of model identifiers -- an empty one. It stays on the J46-10
842
+ branch with the sentence that ruling pinned, and does not fall into the
843
+ unreadable-declaration branch added here.
844
+ """
845
+ monkeypatch.setenv(
846
+ "BANTAMKIT_ASSETS", str(_pack_with_unconstrained_roles_values(tmp_path, schema))
847
+ )
848
+ example["job"]["roles"] = {"implementer": []}
849
+ path = write_checkpoint(tmp_path, example)
850
+ assert ops.clock_out(
851
+ str(path), "U3", "done", {}, {"unit": "U3", "outcome": "done"}, ACCOUNTING
852
+ )["reason"] == (
853
+ "unit U3 in role implementer reported model haiku, "
854
+ "which job.roles.implementer does not allow: "
855
+ )
856
+
857
+
716
858
  # --- MCP flavor: status -----------------------------------------------------
717
859
 
718
860
 
File without changes
File without changes
File without changes
File without changes