@hunter-harness/workflow-harness 0.2.42 → 0.2.44

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 (334) hide show
  1. package/harness/bundles/general/claude-code/.harness-build.json +1 -1
  2. package/harness/bundles/general/claude-code/contracts/build-profile-v3.schema.json +156 -1
  3. package/harness/bundles/general/claude-code/contracts/fixtures/managed-execution.json +341 -0
  4. package/harness/bundles/general/claude-code/contracts/process-identity.schema.json +132 -0
  5. package/harness/bundles/general/claude-code/contracts/run-session.schema.json +142 -0
  6. package/harness/bundles/general/claude-code/contracts/service-retirement-receipt.schema.json +85 -0
  7. package/harness/bundles/general/claude-code/contracts/service-session.schema.json +147 -0
  8. package/harness/bundles/general/claude-code/harness-archive/SKILL.md +1 -1
  9. package/harness/bundles/general/claude-code/harness-archive/reference.md +1 -0
  10. package/harness/bundles/general/claude-code/harness-archive/templates/render-summary.mjs +8 -0
  11. package/harness/bundles/general/claude-code/harness-archive/templates/summary-data-template.json +28 -0
  12. package/harness/bundles/general/claude-code/harness-codebase-map/SKILL.md +1 -1
  13. package/harness/bundles/general/claude-code/harness-knowledge-ingest/SKILL.md +1 -1
  14. package/harness/bundles/general/claude-code/harness-knowledge-query/SKILL.md +1 -1
  15. package/harness/bundles/general/claude-code/harness-plan/SKILL.md +1 -1
  16. package/harness/bundles/general/claude-code/harness-review/SKILL.md +1 -1
  17. package/harness/bundles/general/claude-code/harness-run/SKILL.md +1 -1
  18. package/harness/bundles/general/claude-code/harness-run/reference.md +6 -1
  19. package/harness/bundles/general/claude-code/harness-submit/SKILL.md +1 -1
  20. package/harness/bundles/general/claude-code/harness-submit/checklist.md +12 -0
  21. package/harness/bundles/general/claude-code/harness-sync/SKILL.md +1 -1
  22. package/harness/bundles/general/claude-code/harness-test/SKILL.md +8 -2
  23. package/harness/bundles/general/claude-code/protocols/execution-session-protocol.md +52 -0
  24. package/harness/bundles/general/claude-code/protocols/state-layout-protocol.md +24 -1
  25. package/harness/bundles/general/claude-code/protocols/verification-graph-protocol.md +6 -0
  26. package/harness/bundles/general/claude-code/scripts/harness_archive.py +271 -22
  27. package/harness/bundles/general/claude-code/scripts/harness_change.py +137 -2
  28. package/harness/bundles/general/claude-code/scripts/harness_efficiency.py +443 -0
  29. package/harness/bundles/general/claude-code/scripts/harness_environment.py +760 -46
  30. package/harness/bundles/general/claude-code/scripts/harness_execution_contracts.py +596 -0
  31. package/harness/bundles/general/claude-code/scripts/harness_fixback.py +742 -0
  32. package/harness/bundles/general/claude-code/scripts/harness_integration.py +289 -52
  33. package/harness/bundles/general/claude-code/scripts/harness_ledger.py +5 -0
  34. package/harness/bundles/general/claude-code/scripts/harness_orchestration.py +344 -0
  35. package/harness/bundles/general/claude-code/scripts/harness_process.py +1065 -0
  36. package/harness/bundles/general/claude-code/scripts/harness_profile.py +278 -4
  37. package/harness/bundles/general/claude-code/scripts/harness_runtime.py +1576 -3
  38. package/harness/bundles/general/claude-code/scripts/harness_service.py +1528 -177
  39. package/harness/bundles/general/claude-code/scripts/harness_test_runner.py +224 -146
  40. package/harness/bundles/general/claude-code/scripts/harness_verification.py +487 -8
  41. package/harness/bundles/general/codebuddy/.harness-build.json +1 -1
  42. package/harness/bundles/general/codebuddy/contracts/build-profile-v3.schema.json +156 -1
  43. package/harness/bundles/general/codebuddy/contracts/fixtures/managed-execution.json +341 -0
  44. package/harness/bundles/general/codebuddy/contracts/process-identity.schema.json +132 -0
  45. package/harness/bundles/general/codebuddy/contracts/run-session.schema.json +142 -0
  46. package/harness/bundles/general/codebuddy/contracts/service-retirement-receipt.schema.json +85 -0
  47. package/harness/bundles/general/codebuddy/contracts/service-session.schema.json +147 -0
  48. package/harness/bundles/general/codebuddy/harness-archive/SKILL.md +1 -1
  49. package/harness/bundles/general/codebuddy/harness-archive/reference.md +1 -0
  50. package/harness/bundles/general/codebuddy/harness-archive/templates/render-summary.mjs +8 -0
  51. package/harness/bundles/general/codebuddy/harness-archive/templates/summary-data-template.json +28 -0
  52. package/harness/bundles/general/codebuddy/harness-codebase-map/SKILL.md +1 -1
  53. package/harness/bundles/general/codebuddy/harness-knowledge-ingest/SKILL.md +1 -1
  54. package/harness/bundles/general/codebuddy/harness-knowledge-query/SKILL.md +1 -1
  55. package/harness/bundles/general/codebuddy/harness-plan/SKILL.md +1 -1
  56. package/harness/bundles/general/codebuddy/harness-review/SKILL.md +1 -1
  57. package/harness/bundles/general/codebuddy/harness-run/SKILL.md +1 -1
  58. package/harness/bundles/general/codebuddy/harness-run/reference.md +6 -1
  59. package/harness/bundles/general/codebuddy/harness-submit/SKILL.md +1 -1
  60. package/harness/bundles/general/codebuddy/harness-submit/checklist.md +12 -0
  61. package/harness/bundles/general/codebuddy/harness-sync/SKILL.md +1 -1
  62. package/harness/bundles/general/codebuddy/harness-test/SKILL.md +8 -2
  63. package/harness/bundles/general/codebuddy/protocols/execution-session-protocol.md +52 -0
  64. package/harness/bundles/general/codebuddy/protocols/state-layout-protocol.md +24 -1
  65. package/harness/bundles/general/codebuddy/protocols/verification-graph-protocol.md +6 -0
  66. package/harness/bundles/general/codebuddy/scripts/harness_archive.py +271 -22
  67. package/harness/bundles/general/codebuddy/scripts/harness_change.py +137 -2
  68. package/harness/bundles/general/codebuddy/scripts/harness_efficiency.py +443 -0
  69. package/harness/bundles/general/codebuddy/scripts/harness_environment.py +760 -46
  70. package/harness/bundles/general/codebuddy/scripts/harness_execution_contracts.py +596 -0
  71. package/harness/bundles/general/codebuddy/scripts/harness_fixback.py +742 -0
  72. package/harness/bundles/general/codebuddy/scripts/harness_integration.py +289 -52
  73. package/harness/bundles/general/codebuddy/scripts/harness_ledger.py +5 -0
  74. package/harness/bundles/general/codebuddy/scripts/harness_orchestration.py +344 -0
  75. package/harness/bundles/general/codebuddy/scripts/harness_process.py +1065 -0
  76. package/harness/bundles/general/codebuddy/scripts/harness_profile.py +278 -4
  77. package/harness/bundles/general/codebuddy/scripts/harness_runtime.py +1576 -3
  78. package/harness/bundles/general/codebuddy/scripts/harness_service.py +1528 -177
  79. package/harness/bundles/general/codebuddy/scripts/harness_test_runner.py +224 -146
  80. package/harness/bundles/general/codebuddy/scripts/harness_verification.py +487 -8
  81. package/harness/bundles/general/codex/.harness-build.json +1 -1
  82. package/harness/bundles/general/codex/contracts/build-profile-v3.schema.json +156 -1
  83. package/harness/bundles/general/codex/contracts/fixtures/managed-execution.json +341 -0
  84. package/harness/bundles/general/codex/contracts/process-identity.schema.json +132 -0
  85. package/harness/bundles/general/codex/contracts/run-session.schema.json +142 -0
  86. package/harness/bundles/general/codex/contracts/service-retirement-receipt.schema.json +85 -0
  87. package/harness/bundles/general/codex/contracts/service-session.schema.json +147 -0
  88. package/harness/bundles/general/codex/harness-archive/SKILL.md +1 -1
  89. package/harness/bundles/general/codex/harness-archive/reference.md +1 -0
  90. package/harness/bundles/general/codex/harness-archive/templates/render-summary.mjs +8 -0
  91. package/harness/bundles/general/codex/harness-archive/templates/summary-data-template.json +28 -0
  92. package/harness/bundles/general/codex/harness-codebase-map/SKILL.md +1 -1
  93. package/harness/bundles/general/codex/harness-knowledge-ingest/SKILL.md +1 -1
  94. package/harness/bundles/general/codex/harness-knowledge-query/SKILL.md +1 -1
  95. package/harness/bundles/general/codex/harness-plan/SKILL.md +1 -1
  96. package/harness/bundles/general/codex/harness-review/SKILL.md +1 -1
  97. package/harness/bundles/general/codex/harness-run/SKILL.md +1 -1
  98. package/harness/bundles/general/codex/harness-run/reference.md +6 -1
  99. package/harness/bundles/general/codex/harness-submit/SKILL.md +1 -1
  100. package/harness/bundles/general/codex/harness-submit/checklist.md +12 -0
  101. package/harness/bundles/general/codex/harness-sync/SKILL.md +1 -1
  102. package/harness/bundles/general/codex/harness-test/SKILL.md +8 -2
  103. package/harness/bundles/general/codex/protocols/execution-session-protocol.md +52 -0
  104. package/harness/bundles/general/codex/protocols/state-layout-protocol.md +24 -1
  105. package/harness/bundles/general/codex/protocols/verification-graph-protocol.md +6 -0
  106. package/harness/bundles/general/codex/scripts/harness_archive.py +271 -22
  107. package/harness/bundles/general/codex/scripts/harness_change.py +137 -2
  108. package/harness/bundles/general/codex/scripts/harness_efficiency.py +443 -0
  109. package/harness/bundles/general/codex/scripts/harness_environment.py +760 -46
  110. package/harness/bundles/general/codex/scripts/harness_execution_contracts.py +596 -0
  111. package/harness/bundles/general/codex/scripts/harness_fixback.py +742 -0
  112. package/harness/bundles/general/codex/scripts/harness_integration.py +289 -52
  113. package/harness/bundles/general/codex/scripts/harness_ledger.py +5 -0
  114. package/harness/bundles/general/codex/scripts/harness_orchestration.py +344 -0
  115. package/harness/bundles/general/codex/scripts/harness_process.py +1065 -0
  116. package/harness/bundles/general/codex/scripts/harness_profile.py +278 -4
  117. package/harness/bundles/general/codex/scripts/harness_runtime.py +1576 -3
  118. package/harness/bundles/general/codex/scripts/harness_service.py +1528 -177
  119. package/harness/bundles/general/codex/scripts/harness_test_runner.py +224 -146
  120. package/harness/bundles/general/codex/scripts/harness_verification.py +487 -8
  121. package/harness/bundles/general/cursor/.harness-build.json +1 -1
  122. package/harness/bundles/general/cursor/contracts/build-profile-v3.schema.json +156 -1
  123. package/harness/bundles/general/cursor/contracts/fixtures/managed-execution.json +341 -0
  124. package/harness/bundles/general/cursor/contracts/process-identity.schema.json +132 -0
  125. package/harness/bundles/general/cursor/contracts/run-session.schema.json +142 -0
  126. package/harness/bundles/general/cursor/contracts/service-retirement-receipt.schema.json +85 -0
  127. package/harness/bundles/general/cursor/contracts/service-session.schema.json +147 -0
  128. package/harness/bundles/general/cursor/harness-archive/SKILL.md +1 -1
  129. package/harness/bundles/general/cursor/harness-archive/reference.md +1 -0
  130. package/harness/bundles/general/cursor/harness-archive/templates/render-summary.mjs +8 -0
  131. package/harness/bundles/general/cursor/harness-archive/templates/summary-data-template.json +28 -0
  132. package/harness/bundles/general/cursor/harness-codebase-map/SKILL.md +1 -1
  133. package/harness/bundles/general/cursor/harness-knowledge-ingest/SKILL.md +1 -1
  134. package/harness/bundles/general/cursor/harness-knowledge-query/SKILL.md +1 -1
  135. package/harness/bundles/general/cursor/harness-plan/SKILL.md +1 -1
  136. package/harness/bundles/general/cursor/harness-review/SKILL.md +1 -1
  137. package/harness/bundles/general/cursor/harness-run/SKILL.md +1 -1
  138. package/harness/bundles/general/cursor/harness-run/reference.md +6 -1
  139. package/harness/bundles/general/cursor/harness-submit/SKILL.md +1 -1
  140. package/harness/bundles/general/cursor/harness-submit/checklist.md +12 -0
  141. package/harness/bundles/general/cursor/harness-sync/SKILL.md +1 -1
  142. package/harness/bundles/general/cursor/harness-test/SKILL.md +8 -2
  143. package/harness/bundles/general/cursor/protocols/execution-session-protocol.md +52 -0
  144. package/harness/bundles/general/cursor/protocols/state-layout-protocol.md +24 -1
  145. package/harness/bundles/general/cursor/protocols/verification-graph-protocol.md +6 -0
  146. package/harness/bundles/general/cursor/scripts/harness_archive.py +271 -22
  147. package/harness/bundles/general/cursor/scripts/harness_change.py +137 -2
  148. package/harness/bundles/general/cursor/scripts/harness_efficiency.py +443 -0
  149. package/harness/bundles/general/cursor/scripts/harness_environment.py +760 -46
  150. package/harness/bundles/general/cursor/scripts/harness_execution_contracts.py +596 -0
  151. package/harness/bundles/general/cursor/scripts/harness_fixback.py +742 -0
  152. package/harness/bundles/general/cursor/scripts/harness_integration.py +289 -52
  153. package/harness/bundles/general/cursor/scripts/harness_ledger.py +5 -0
  154. package/harness/bundles/general/cursor/scripts/harness_orchestration.py +344 -0
  155. package/harness/bundles/general/cursor/scripts/harness_process.py +1065 -0
  156. package/harness/bundles/general/cursor/scripts/harness_profile.py +278 -4
  157. package/harness/bundles/general/cursor/scripts/harness_runtime.py +1576 -3
  158. package/harness/bundles/general/cursor/scripts/harness_service.py +1528 -177
  159. package/harness/bundles/general/cursor/scripts/harness_test_runner.py +224 -146
  160. package/harness/bundles/general/cursor/scripts/harness_verification.py +487 -8
  161. package/harness/bundles/java/claude-code/.harness-build.json +1 -1
  162. package/harness/bundles/java/claude-code/contracts/build-profile-v3.schema.json +156 -1
  163. package/harness/bundles/java/claude-code/contracts/fixtures/managed-execution.json +341 -0
  164. package/harness/bundles/java/claude-code/contracts/process-identity.schema.json +132 -0
  165. package/harness/bundles/java/claude-code/contracts/run-session.schema.json +142 -0
  166. package/harness/bundles/java/claude-code/contracts/service-retirement-receipt.schema.json +85 -0
  167. package/harness/bundles/java/claude-code/contracts/service-session.schema.json +147 -0
  168. package/harness/bundles/java/claude-code/harness-apidoc/SKILL.md +1 -1
  169. package/harness/bundles/java/claude-code/harness-archive/SKILL.md +1 -1
  170. package/harness/bundles/java/claude-code/harness-archive/reference.md +1 -0
  171. package/harness/bundles/java/claude-code/harness-archive/templates/render-summary.mjs +8 -0
  172. package/harness/bundles/java/claude-code/harness-archive/templates/summary-data-template.json +28 -0
  173. package/harness/bundles/java/claude-code/harness-codebase-map/SKILL.md +1 -1
  174. package/harness/bundles/java/claude-code/harness-knowledge-ingest/SKILL.md +1 -1
  175. package/harness/bundles/java/claude-code/harness-knowledge-query/SKILL.md +1 -1
  176. package/harness/bundles/java/claude-code/harness-package/SKILL.md +1 -1
  177. package/harness/bundles/java/claude-code/harness-plan/SKILL.md +1 -1
  178. package/harness/bundles/java/claude-code/harness-review/SKILL.md +1 -1
  179. package/harness/bundles/java/claude-code/harness-run/SKILL.md +1 -1
  180. package/harness/bundles/java/claude-code/harness-submit/SKILL.md +1 -1
  181. package/harness/bundles/java/claude-code/harness-submit/checklist.md +12 -0
  182. package/harness/bundles/java/claude-code/harness-sync/SKILL.md +1 -1
  183. package/harness/bundles/java/claude-code/harness-test/SKILL.md +8 -2
  184. package/harness/bundles/java/claude-code/protocols/execution-session-protocol.md +52 -0
  185. package/harness/bundles/java/claude-code/protocols/state-layout-protocol.md +24 -1
  186. package/harness/bundles/java/claude-code/protocols/verification-graph-protocol.md +6 -0
  187. package/harness/bundles/java/claude-code/scripts/harness_archive.py +271 -22
  188. package/harness/bundles/java/claude-code/scripts/harness_change.py +137 -2
  189. package/harness/bundles/java/claude-code/scripts/harness_efficiency.py +443 -0
  190. package/harness/bundles/java/claude-code/scripts/harness_environment.py +760 -46
  191. package/harness/bundles/java/claude-code/scripts/harness_execution_contracts.py +596 -0
  192. package/harness/bundles/java/claude-code/scripts/harness_fixback.py +742 -0
  193. package/harness/bundles/java/claude-code/scripts/harness_integration.py +289 -52
  194. package/harness/bundles/java/claude-code/scripts/harness_ledger.py +5 -0
  195. package/harness/bundles/java/claude-code/scripts/harness_orchestration.py +344 -0
  196. package/harness/bundles/java/claude-code/scripts/harness_process.py +1065 -0
  197. package/harness/bundles/java/claude-code/scripts/harness_profile.py +278 -4
  198. package/harness/bundles/java/claude-code/scripts/harness_runtime.py +1576 -3
  199. package/harness/bundles/java/claude-code/scripts/harness_service.py +1528 -177
  200. package/harness/bundles/java/claude-code/scripts/harness_test_runner.py +224 -146
  201. package/harness/bundles/java/claude-code/scripts/harness_verification.py +487 -8
  202. package/harness/bundles/java/codebuddy/.harness-build.json +1 -1
  203. package/harness/bundles/java/codebuddy/contracts/build-profile-v3.schema.json +156 -1
  204. package/harness/bundles/java/codebuddy/contracts/fixtures/managed-execution.json +341 -0
  205. package/harness/bundles/java/codebuddy/contracts/process-identity.schema.json +132 -0
  206. package/harness/bundles/java/codebuddy/contracts/run-session.schema.json +142 -0
  207. package/harness/bundles/java/codebuddy/contracts/service-retirement-receipt.schema.json +85 -0
  208. package/harness/bundles/java/codebuddy/contracts/service-session.schema.json +147 -0
  209. package/harness/bundles/java/codebuddy/harness-apidoc/SKILL.md +1 -1
  210. package/harness/bundles/java/codebuddy/harness-archive/SKILL.md +1 -1
  211. package/harness/bundles/java/codebuddy/harness-archive/reference.md +1 -0
  212. package/harness/bundles/java/codebuddy/harness-archive/templates/render-summary.mjs +8 -0
  213. package/harness/bundles/java/codebuddy/harness-archive/templates/summary-data-template.json +28 -0
  214. package/harness/bundles/java/codebuddy/harness-codebase-map/SKILL.md +1 -1
  215. package/harness/bundles/java/codebuddy/harness-knowledge-ingest/SKILL.md +1 -1
  216. package/harness/bundles/java/codebuddy/harness-knowledge-query/SKILL.md +1 -1
  217. package/harness/bundles/java/codebuddy/harness-package/SKILL.md +1 -1
  218. package/harness/bundles/java/codebuddy/harness-plan/SKILL.md +1 -1
  219. package/harness/bundles/java/codebuddy/harness-review/SKILL.md +1 -1
  220. package/harness/bundles/java/codebuddy/harness-run/SKILL.md +1 -1
  221. package/harness/bundles/java/codebuddy/harness-submit/SKILL.md +1 -1
  222. package/harness/bundles/java/codebuddy/harness-submit/checklist.md +12 -0
  223. package/harness/bundles/java/codebuddy/harness-sync/SKILL.md +1 -1
  224. package/harness/bundles/java/codebuddy/harness-test/SKILL.md +8 -2
  225. package/harness/bundles/java/codebuddy/protocols/execution-session-protocol.md +52 -0
  226. package/harness/bundles/java/codebuddy/protocols/state-layout-protocol.md +24 -1
  227. package/harness/bundles/java/codebuddy/protocols/verification-graph-protocol.md +6 -0
  228. package/harness/bundles/java/codebuddy/scripts/harness_archive.py +271 -22
  229. package/harness/bundles/java/codebuddy/scripts/harness_change.py +137 -2
  230. package/harness/bundles/java/codebuddy/scripts/harness_efficiency.py +443 -0
  231. package/harness/bundles/java/codebuddy/scripts/harness_environment.py +760 -46
  232. package/harness/bundles/java/codebuddy/scripts/harness_execution_contracts.py +596 -0
  233. package/harness/bundles/java/codebuddy/scripts/harness_fixback.py +742 -0
  234. package/harness/bundles/java/codebuddy/scripts/harness_integration.py +289 -52
  235. package/harness/bundles/java/codebuddy/scripts/harness_ledger.py +5 -0
  236. package/harness/bundles/java/codebuddy/scripts/harness_orchestration.py +344 -0
  237. package/harness/bundles/java/codebuddy/scripts/harness_process.py +1065 -0
  238. package/harness/bundles/java/codebuddy/scripts/harness_profile.py +278 -4
  239. package/harness/bundles/java/codebuddy/scripts/harness_runtime.py +1576 -3
  240. package/harness/bundles/java/codebuddy/scripts/harness_service.py +1528 -177
  241. package/harness/bundles/java/codebuddy/scripts/harness_test_runner.py +224 -146
  242. package/harness/bundles/java/codebuddy/scripts/harness_verification.py +487 -8
  243. package/harness/bundles/java/codex/.harness-build.json +1 -1
  244. package/harness/bundles/java/codex/contracts/build-profile-v3.schema.json +156 -1
  245. package/harness/bundles/java/codex/contracts/fixtures/managed-execution.json +341 -0
  246. package/harness/bundles/java/codex/contracts/process-identity.schema.json +132 -0
  247. package/harness/bundles/java/codex/contracts/run-session.schema.json +142 -0
  248. package/harness/bundles/java/codex/contracts/service-retirement-receipt.schema.json +85 -0
  249. package/harness/bundles/java/codex/contracts/service-session.schema.json +147 -0
  250. package/harness/bundles/java/codex/harness-apidoc/SKILL.md +1 -1
  251. package/harness/bundles/java/codex/harness-archive/SKILL.md +1 -1
  252. package/harness/bundles/java/codex/harness-archive/reference.md +1 -0
  253. package/harness/bundles/java/codex/harness-archive/templates/render-summary.mjs +8 -0
  254. package/harness/bundles/java/codex/harness-archive/templates/summary-data-template.json +28 -0
  255. package/harness/bundles/java/codex/harness-codebase-map/SKILL.md +1 -1
  256. package/harness/bundles/java/codex/harness-knowledge-ingest/SKILL.md +1 -1
  257. package/harness/bundles/java/codex/harness-knowledge-query/SKILL.md +1 -1
  258. package/harness/bundles/java/codex/harness-package/SKILL.md +1 -1
  259. package/harness/bundles/java/codex/harness-plan/SKILL.md +1 -1
  260. package/harness/bundles/java/codex/harness-review/SKILL.md +1 -1
  261. package/harness/bundles/java/codex/harness-run/SKILL.md +1 -1
  262. package/harness/bundles/java/codex/harness-submit/SKILL.md +1 -1
  263. package/harness/bundles/java/codex/harness-submit/checklist.md +12 -0
  264. package/harness/bundles/java/codex/harness-sync/SKILL.md +1 -1
  265. package/harness/bundles/java/codex/harness-test/SKILL.md +8 -2
  266. package/harness/bundles/java/codex/protocols/execution-session-protocol.md +52 -0
  267. package/harness/bundles/java/codex/protocols/state-layout-protocol.md +24 -1
  268. package/harness/bundles/java/codex/protocols/verification-graph-protocol.md +6 -0
  269. package/harness/bundles/java/codex/scripts/harness_archive.py +271 -22
  270. package/harness/bundles/java/codex/scripts/harness_change.py +137 -2
  271. package/harness/bundles/java/codex/scripts/harness_efficiency.py +443 -0
  272. package/harness/bundles/java/codex/scripts/harness_environment.py +760 -46
  273. package/harness/bundles/java/codex/scripts/harness_execution_contracts.py +596 -0
  274. package/harness/bundles/java/codex/scripts/harness_fixback.py +742 -0
  275. package/harness/bundles/java/codex/scripts/harness_integration.py +289 -52
  276. package/harness/bundles/java/codex/scripts/harness_ledger.py +5 -0
  277. package/harness/bundles/java/codex/scripts/harness_orchestration.py +344 -0
  278. package/harness/bundles/java/codex/scripts/harness_process.py +1065 -0
  279. package/harness/bundles/java/codex/scripts/harness_profile.py +278 -4
  280. package/harness/bundles/java/codex/scripts/harness_runtime.py +1576 -3
  281. package/harness/bundles/java/codex/scripts/harness_service.py +1528 -177
  282. package/harness/bundles/java/codex/scripts/harness_test_runner.py +224 -146
  283. package/harness/bundles/java/codex/scripts/harness_verification.py +487 -8
  284. package/harness/bundles/java/cursor/.harness-build.json +1 -1
  285. package/harness/bundles/java/cursor/contracts/build-profile-v3.schema.json +156 -1
  286. package/harness/bundles/java/cursor/contracts/fixtures/managed-execution.json +341 -0
  287. package/harness/bundles/java/cursor/contracts/process-identity.schema.json +132 -0
  288. package/harness/bundles/java/cursor/contracts/run-session.schema.json +142 -0
  289. package/harness/bundles/java/cursor/contracts/service-retirement-receipt.schema.json +85 -0
  290. package/harness/bundles/java/cursor/contracts/service-session.schema.json +147 -0
  291. package/harness/bundles/java/cursor/harness-apidoc/SKILL.md +1 -1
  292. package/harness/bundles/java/cursor/harness-archive/SKILL.md +1 -1
  293. package/harness/bundles/java/cursor/harness-archive/reference.md +1 -0
  294. package/harness/bundles/java/cursor/harness-archive/templates/render-summary.mjs +8 -0
  295. package/harness/bundles/java/cursor/harness-archive/templates/summary-data-template.json +28 -0
  296. package/harness/bundles/java/cursor/harness-codebase-map/SKILL.md +1 -1
  297. package/harness/bundles/java/cursor/harness-knowledge-ingest/SKILL.md +1 -1
  298. package/harness/bundles/java/cursor/harness-knowledge-query/SKILL.md +1 -1
  299. package/harness/bundles/java/cursor/harness-package/SKILL.md +1 -1
  300. package/harness/bundles/java/cursor/harness-plan/SKILL.md +1 -1
  301. package/harness/bundles/java/cursor/harness-review/SKILL.md +1 -1
  302. package/harness/bundles/java/cursor/harness-run/SKILL.md +1 -1
  303. package/harness/bundles/java/cursor/harness-submit/SKILL.md +1 -1
  304. package/harness/bundles/java/cursor/harness-submit/checklist.md +12 -0
  305. package/harness/bundles/java/cursor/harness-sync/SKILL.md +1 -1
  306. package/harness/bundles/java/cursor/harness-test/SKILL.md +8 -2
  307. package/harness/bundles/java/cursor/protocols/execution-session-protocol.md +52 -0
  308. package/harness/bundles/java/cursor/protocols/state-layout-protocol.md +24 -1
  309. package/harness/bundles/java/cursor/protocols/verification-graph-protocol.md +6 -0
  310. package/harness/bundles/java/cursor/scripts/harness_archive.py +271 -22
  311. package/harness/bundles/java/cursor/scripts/harness_change.py +137 -2
  312. package/harness/bundles/java/cursor/scripts/harness_efficiency.py +443 -0
  313. package/harness/bundles/java/cursor/scripts/harness_environment.py +760 -46
  314. package/harness/bundles/java/cursor/scripts/harness_execution_contracts.py +596 -0
  315. package/harness/bundles/java/cursor/scripts/harness_fixback.py +742 -0
  316. package/harness/bundles/java/cursor/scripts/harness_integration.py +289 -52
  317. package/harness/bundles/java/cursor/scripts/harness_ledger.py +5 -0
  318. package/harness/bundles/java/cursor/scripts/harness_orchestration.py +344 -0
  319. package/harness/bundles/java/cursor/scripts/harness_process.py +1065 -0
  320. package/harness/bundles/java/cursor/scripts/harness_profile.py +278 -4
  321. package/harness/bundles/java/cursor/scripts/harness_runtime.py +1576 -3
  322. package/harness/bundles/java/cursor/scripts/harness_service.py +1528 -177
  323. package/harness/bundles/java/cursor/scripts/harness_test_runner.py +224 -146
  324. package/harness/bundles/java/cursor/scripts/harness_verification.py +487 -8
  325. package/harness/manifests/general/claude-code.json +76 -31
  326. package/harness/manifests/general/codebuddy.json +76 -31
  327. package/harness/manifests/general/codex.json +76 -31
  328. package/harness/manifests/general/cursor.json +76 -31
  329. package/harness/manifests/java/claude-code.json +77 -32
  330. package/harness/manifests/java/codebuddy.json +77 -32
  331. package/harness/manifests/java/codex.json +77 -32
  332. package/harness/manifests/java/cursor.json +77 -32
  333. package/hunter-workflow-family.json +6 -4
  334. package/package.json +1 -1
@@ -13,11 +13,39 @@ import datetime as dt
13
13
  import hashlib
14
14
  import json
15
15
  import os
16
+ import shutil
17
+ import subprocess
16
18
  import sys
19
+ import tempfile
20
+ import time
21
+ import uuid
22
+ from contextlib import contextmanager
17
23
  from pathlib import Path
18
- from typing import Any
24
+ from typing import Any, Iterator
19
25
 
20
- import harness_service as hservice
26
+ import harness_process as hprocess
27
+ from harness_paths import resolve_state_dir_for_contract
28
+
29
+
30
+ def _verify_recorded_owner(owner: dict[str, Any]) -> bool | None:
31
+ try:
32
+ pid = int(owner.get("pid") or 0)
33
+ except (TypeError, ValueError):
34
+ return None
35
+ observed = hprocess.observe_process_identity(pid)
36
+ decision = hprocess.verify_process_identity(
37
+ {
38
+ "pid": pid,
39
+ "createdAt": owner.get("startedAt"),
40
+ "executable": owner.get("executable"),
41
+ },
42
+ observed,
43
+ )
44
+ if decision.get("ok") is True:
45
+ return True
46
+ if decision.get("reasonCode") == "IDENTITY_UNVERIFIABLE":
47
+ return None
48
+ return False
21
49
 
22
50
 
23
51
  if hasattr(sys.stdout, "reconfigure"):
@@ -87,11 +115,20 @@ def _read_json(path: Path) -> Any:
87
115
 
88
116
  def _write_json(path: Path, data: Any) -> None:
89
117
  path.parent.mkdir(parents=True, exist_ok=True)
90
- path.write_text(
91
- json.dumps(data, ensure_ascii=False, indent=2) + "\n",
92
- encoding="utf-8",
93
- newline="\n",
118
+ fd, raw_tmp = tempfile.mkstemp(
119
+ prefix=f".{path.name}.",
120
+ suffix=".tmp",
121
+ dir=str(path.parent),
94
122
  )
123
+ tmp = Path(raw_tmp)
124
+ try:
125
+ with os.fdopen(fd, "w", encoding="utf-8", newline="\n") as handle:
126
+ handle.write(json.dumps(data, ensure_ascii=False, indent=2) + "\n")
127
+ handle.flush()
128
+ os.fsync(handle.fileno())
129
+ os.replace(tmp, path)
130
+ finally:
131
+ tmp.unlink(missing_ok=True)
95
132
 
96
133
 
97
134
  def default_lease_root(project: Path) -> Path:
@@ -231,6 +268,45 @@ def create_environment_receipt(
231
268
  return receipt
232
269
 
233
270
 
271
+ def _validate_environment_receipt(
272
+ receipt: Any,
273
+ *,
274
+ stack_id: str,
275
+ environment_hash: str,
276
+ ) -> bool:
277
+ if not isinstance(receipt, dict) or receipt.get("schemaVersion") != 1:
278
+ return False
279
+ if (
280
+ receipt.get("stackId") != stack_id
281
+ or receipt.get("environmentHash") != environment_hash
282
+ or not isinstance(receipt.get("content"), dict)
283
+ ):
284
+ return False
285
+ try:
286
+ canonical_content = json.dumps(
287
+ _canonical_content_evidence(receipt["content"]),
288
+ ensure_ascii=False,
289
+ sort_keys=True,
290
+ separators=(",", ":"),
291
+ ).encode("utf-8")
292
+ except ValueError:
293
+ return False
294
+ expected_fingerprint = "sha256:" + _sha256_bytes(canonical_content)
295
+ if receipt.get("contentFingerprint") != expected_fingerprint:
296
+ return False
297
+ identity_payload = json.dumps(
298
+ {
299
+ key: value
300
+ for key, value in receipt.items()
301
+ if key not in {"preparedAt", "receiptId"}
302
+ },
303
+ ensure_ascii=False,
304
+ sort_keys=True,
305
+ separators=(",", ":"),
306
+ ).encode("utf-8")
307
+ return receipt.get("receiptId") == "sha256:" + _sha256_bytes(identity_payload)
308
+
309
+
234
310
  def resolve_verification_environment(
235
311
  receipt: dict[str, Any],
236
312
  *,
@@ -279,9 +355,321 @@ def resolve_verification_environment(
279
355
 
280
356
  def _lease_path(lease_root: Path, stack_id: str) -> Path:
281
357
  safe = "".join(ch if ch.isalnum() or ch in "-_." else "_" for ch in stack_id)
358
+ if safe != stack_id:
359
+ safe = f"{safe}-{hashlib.sha256(stack_id.encode('utf-8')).hexdigest()[:12]}"
282
360
  return lease_root / f"{safe}.json"
283
361
 
284
362
 
363
+ @contextmanager
364
+ def _lease_registry_guard(lease_root: Path) -> Iterator[None]:
365
+ lease_root.mkdir(parents=True, exist_ok=True)
366
+ lock_path = lease_root / ".acquire.lock"
367
+ deadline = time.monotonic() + 5.0
368
+ token = uuid.uuid4().hex
369
+ while True:
370
+ try:
371
+ fd = os.open(
372
+ lock_path,
373
+ os.O_WRONLY | os.O_CREAT | os.O_EXCL,
374
+ 0o600,
375
+ )
376
+ except FileExistsError:
377
+ try:
378
+ holder = _read_json(lock_path)
379
+ except (OSError, json.JSONDecodeError):
380
+ holder = {}
381
+ pid = holder.get("pid") if isinstance(holder, dict) else None
382
+ if isinstance(pid, int) and pid > 0 and not hprocess.is_pid_alive(pid):
383
+ try:
384
+ lock_path.unlink()
385
+ continue
386
+ except OSError:
387
+ pass
388
+ if time.monotonic() >= deadline:
389
+ raise RuntimeError("ENVIRONMENT_LEASE_BUSY")
390
+ time.sleep(0.02)
391
+ continue
392
+ with os.fdopen(fd, "w", encoding="utf-8", newline="\n") as handle:
393
+ json.dump(
394
+ {
395
+ "schemaVersion": 1,
396
+ "pid": os.getpid(),
397
+ "token": token,
398
+ "acquiredAt": now_iso(),
399
+ },
400
+ handle,
401
+ ensure_ascii=False,
402
+ )
403
+ handle.flush()
404
+ os.fsync(handle.fileno())
405
+ break
406
+ try:
407
+ yield
408
+ finally:
409
+ try:
410
+ holder = _read_json(lock_path)
411
+ except (OSError, json.JSONDecodeError):
412
+ holder = {}
413
+ if isinstance(holder, dict) and holder.get("token") == token:
414
+ lock_path.unlink(missing_ok=True)
415
+
416
+
417
+ def _provider_operation_root(lease_root: Path) -> Path:
418
+ return lease_root / "provider-operations"
419
+
420
+
421
+ def _provider_receipt_digest(receipt: dict[str, Any]) -> str:
422
+ payload = {
423
+ key: value
424
+ for key, value in receipt.items()
425
+ if key != "receiptDigest"
426
+ }
427
+ return "sha256:" + _sha256_bytes(
428
+ json.dumps(
429
+ payload,
430
+ ensure_ascii=False,
431
+ sort_keys=True,
432
+ separators=(",", ":"),
433
+ ).encode("utf-8")
434
+ )
435
+
436
+
437
+ def execute_provider_operation(
438
+ project: Path,
439
+ *,
440
+ change_id: str,
441
+ stack_id: str,
442
+ operation: str,
443
+ argv: list[str],
444
+ lease_root: Path | None = None,
445
+ timeout_seconds: float = 900.0,
446
+ ) -> dict[str, Any]:
447
+ """Execute a reset/cleanup provider and emit a bound, durable receipt."""
448
+ if operation not in {"reset", "cleanup"}:
449
+ raise ValueError("ENVIRONMENT_PROVIDER_OPERATION_INVALID")
450
+ if (
451
+ not argv
452
+ or not all(isinstance(item, str) and item for item in argv)
453
+ or timeout_seconds <= 0
454
+ ):
455
+ raise ValueError("ENVIRONMENT_PROVIDER_ARGV_INVALID")
456
+ root = lease_root or default_lease_root(project)
457
+ lease_path = _lease_path(root, stack_id)
458
+ lease = _read_json(lease_path)
459
+ if (
460
+ not isinstance(lease, dict)
461
+ or lease.get("changeId") != change_id
462
+ or lease.get("stackId") != stack_id
463
+ or lease.get("status") != "ACTIVE"
464
+ ):
465
+ raise ValueError("ENVIRONMENT_PROVIDER_LEASE_INVALID")
466
+ executable = shutil.which(argv[0]) or str(Path(argv[0]).expanduser().resolve())
467
+ started_at = now_iso()
468
+ spawned = hprocess.spawn_structured_argv(
469
+ argv,
470
+ cwd=project.resolve(),
471
+ environment={},
472
+ owner_token=f"environment:{change_id}:{stack_id}:{uuid.uuid4().hex}",
473
+ stdout=subprocess.PIPE,
474
+ stderr=subprocess.PIPE,
475
+ )
476
+ process = spawned.process
477
+ created = hprocess.get_process_create_time(process.pid)
478
+ cleanup_complete = False
479
+ try:
480
+ stdout, stderr = process.communicate(timeout=timeout_seconds)
481
+ timed_out = False
482
+ except subprocess.TimeoutExpired:
483
+ timed_out = True
484
+ hprocess.capture_owned_members(spawned)
485
+ cleanup = hprocess.terminate_owned_tree(
486
+ spawned.attestation,
487
+ spawned.ownershipProof,
488
+ {"graceSeconds": 3.0},
489
+ )
490
+ cleanup_complete = bool(cleanup.get("cleanupComplete"))
491
+ stdout, stderr = process.communicate()
492
+ finally:
493
+ if process.poll() is None:
494
+ hprocess.capture_owned_members(spawned)
495
+ cleanup = hprocess.terminate_owned_tree(
496
+ spawned.attestation,
497
+ spawned.ownershipProof,
498
+ {"graceSeconds": 3.0},
499
+ )
500
+ cleanup_complete = bool(cleanup.get("cleanupComplete"))
501
+ spawned.close()
502
+ operation_id = uuid.uuid4().hex
503
+ receipt_path = _provider_operation_root(root) / f"{operation}-{operation_id}.json"
504
+ receipt: dict[str, Any] = {
505
+ "schemaVersion": 2,
506
+ "operation": operation,
507
+ "status": "OK" if process.returncode == 0 and not timed_out else "FAIL",
508
+ "operationId": operation_id,
509
+ "changeId": change_id,
510
+ "stackId": stack_id,
511
+ "environmentHash": str(lease.get("environmentHash") or ""),
512
+ "environmentSessionId": str(lease.get("sessionId") or ""),
513
+ "contentFingerprint": str(
514
+ lease.get("contentFingerprint") or "sha256:none"
515
+ ),
516
+ "performedAt": started_at,
517
+ "completedAt": now_iso(),
518
+ "provider": {
519
+ "pid": process.pid,
520
+ "executable": executable,
521
+ "startedAt": (
522
+ created.isoformat(timespec="seconds")
523
+ if created is not None
524
+ else started_at
525
+ ),
526
+ "argvHash": "sha256:"
527
+ + _sha256_bytes(
528
+ json.dumps(argv, ensure_ascii=False, separators=(",", ":")).encode(
529
+ "utf-8"
530
+ )
531
+ ),
532
+ "processAttestation": spawned.attestation,
533
+ "ownershipProof": spawned.ownershipProof,
534
+ "cleanupComplete": cleanup_complete or process.poll() is not None,
535
+ },
536
+ "exitCode": process.returncode,
537
+ "timedOut": timed_out,
538
+ "stdoutDigest": "sha256:" + _sha256_bytes(stdout),
539
+ "stderrDigest": "sha256:" + _sha256_bytes(stderr),
540
+ "receiptPath": str(receipt_path.resolve()),
541
+ }
542
+ receipt["receiptDigest"] = _provider_receipt_digest(receipt)
543
+ _write_json(receipt_path, receipt)
544
+ return receipt
545
+
546
+
547
+ def _operation_evidence(
548
+ evidence: dict[str, Any] | None,
549
+ *,
550
+ operation: str,
551
+ lease_root: Path,
552
+ lease: dict[str, Any],
553
+ ) -> dict[str, Any] | None:
554
+ if not isinstance(evidence, dict):
555
+ return None
556
+ raw_path = str(evidence.get("receiptPath") or "").strip()
557
+ if not raw_path:
558
+ return None
559
+ receipt_path = Path(raw_path).expanduser().resolve()
560
+ provider_root = _provider_operation_root(lease_root).resolve()
561
+ try:
562
+ receipt_path.relative_to(provider_root)
563
+ except ValueError:
564
+ return None
565
+ try:
566
+ stored = _read_json(receipt_path)
567
+ except (OSError, json.JSONDecodeError):
568
+ return None
569
+ if stored != evidence or not isinstance(stored, dict):
570
+ return None
571
+ performed_at = _parse_expiry(stored.get("performedAt"))
572
+ age_seconds = (
573
+ (dt.datetime.now(dt.timezone.utc) - performed_at).total_seconds()
574
+ if performed_at is not None
575
+ else 1_000_000
576
+ )
577
+ if (
578
+ stored.get("schemaVersion") != 2
579
+ or stored.get("operation") != operation
580
+ or stored.get("status") != "OK"
581
+ or stored.get("changeId") != lease.get("changeId")
582
+ or stored.get("stackId") != lease.get("stackId")
583
+ or stored.get("environmentHash") != lease.get("environmentHash")
584
+ or stored.get("environmentSessionId") != lease.get("sessionId")
585
+ or stored.get("contentFingerprint")
586
+ != str(lease.get("contentFingerprint") or "sha256:none")
587
+ or not str(stored.get("operationId") or "").strip()
588
+ or not isinstance(stored.get("provider"), dict)
589
+ or not str(stored["provider"].get("executable") or "").strip()
590
+ or not str(stored["provider"].get("argvHash") or "").startswith("sha256:")
591
+ or stored.get("exitCode") != 0
592
+ or stored.get("timedOut") is not False
593
+ or age_seconds < -5
594
+ or age_seconds > 900
595
+ or stored.get("receiptDigest") != _provider_receipt_digest(stored)
596
+ ):
597
+ return None
598
+ consumed = lease_root / ".consumed-provider-operations" / (
599
+ hashlib.sha256(str(stored["operationId"]).encode("utf-8")).hexdigest()
600
+ + ".json"
601
+ )
602
+ if consumed.exists():
603
+ return None
604
+ return stored
605
+
606
+
607
+ def _consume_operation_evidence(
608
+ lease_root: Path,
609
+ evidence: dict[str, Any],
610
+ ) -> bool:
611
+ consumed = lease_root / ".consumed-provider-operations" / (
612
+ hashlib.sha256(str(evidence["operationId"]).encode("utf-8")).hexdigest()
613
+ + ".json"
614
+ )
615
+ consumed.parent.mkdir(parents=True, exist_ok=True)
616
+ try:
617
+ descriptor = os.open(
618
+ consumed,
619
+ os.O_WRONLY | os.O_CREAT | os.O_EXCL,
620
+ 0o600,
621
+ )
622
+ except FileExistsError:
623
+ return False
624
+ with os.fdopen(descriptor, "w", encoding="utf-8", newline="\n") as stream:
625
+ json.dump(
626
+ {
627
+ "schemaVersion": 1,
628
+ "operationId": evidence["operationId"],
629
+ "receiptDigest": evidence["receiptDigest"],
630
+ "consumedAt": now_iso(),
631
+ },
632
+ stream,
633
+ ensure_ascii=False,
634
+ )
635
+ stream.flush()
636
+ os.fsync(stream.fileno())
637
+ return True
638
+
639
+
640
+ def _write_environment_action_receipt(
641
+ lease_root: Path,
642
+ *,
643
+ action: str,
644
+ lease: dict[str, Any],
645
+ previous_session_id: str | None = None,
646
+ receipt_dir: Path | None = None,
647
+ operation_evidence: dict[str, Any] | None = None,
648
+ ) -> Path:
649
+ receipt = {
650
+ "schemaVersion": 1,
651
+ "action": action,
652
+ "recordedAt": now_iso(),
653
+ "changeId": lease.get("changeId"),
654
+ "stackId": lease.get("stackId"),
655
+ "sessionId": lease.get("sessionId"),
656
+ "mode": lease.get("mode"),
657
+ "environmentHash": lease.get("environmentHash"),
658
+ "contentFingerprint": lease.get("contentFingerprint"),
659
+ }
660
+ if previous_session_id:
661
+ receipt["previousSessionId"] = previous_session_id
662
+ if operation_evidence is not None:
663
+ receipt["operationEvidence"] = operation_evidence
664
+ receipt_dir = receipt_dir or lease_root.parent / "environment-receipts"
665
+ stack_digest = hashlib.sha256(
666
+ str(lease.get("stackId") or "").encode("utf-8")
667
+ ).hexdigest()[:12]
668
+ target = receipt_dir / f"{action}-{stack_digest}-{uuid.uuid4().hex}.json"
669
+ _write_json(target, receipt)
670
+ return target
671
+
672
+
285
673
  def list_leases(lease_root: Path) -> list[dict[str, Any]]:
286
674
  if not lease_root.is_dir():
287
675
  return []
@@ -340,38 +728,49 @@ def classify_leases(lease_root: Path) -> list[dict[str, Any]]:
340
728
  item["classification"] = "LEASE_EXPIRY_INVALID"
341
729
  results.append(item)
342
730
  continue
343
- if now < expires_at:
344
- item["classification"] = (
345
- "STALE_CONTENT"
346
- if lease.get("status") == "STALE_CONTENT"
347
- else "ACTIVE"
348
- )
349
- results.append(item)
350
- continue
731
+ unexpired = now < expires_at
351
732
  owner = lease.get("owner")
352
733
  if not isinstance(owner, dict) or any(
353
734
  not owner.get(field) for field in ("pid", "executable", "startedAt")
354
735
  ):
736
+ if unexpired:
737
+ item["classification"] = (
738
+ "STALE_CONTENT"
739
+ if lease.get("status") == "STALE_CONTENT"
740
+ else "ACTIVE"
741
+ )
742
+ results.append(item)
743
+ continue
355
744
  item["classification"] = "OWNER_IDENTITY_INCOMPLETE"
356
745
  results.append(item)
357
746
  continue
358
747
  try:
359
748
  owner_pid = int(owner["pid"])
360
749
  except (TypeError, ValueError):
750
+ if unexpired:
751
+ item["classification"] = (
752
+ "STALE_CONTENT"
753
+ if lease.get("status") == "STALE_CONTENT"
754
+ else "ACTIVE"
755
+ )
756
+ results.append(item)
757
+ continue
361
758
  item["classification"] = "OWNER_IDENTITY_INCOMPLETE"
362
759
  results.append(item)
363
760
  continue
364
- if not hservice.is_pid_alive(owner_pid):
761
+ if not hprocess.is_pid_alive(owner_pid):
365
762
  item["classification"] = "RECLAIMABLE"
366
763
  results.append(item)
367
764
  continue
368
- identity = hservice.verify_process_identity(
369
- {
370
- "pid": owner_pid,
371
- "startedAt": owner["startedAt"],
372
- "processIdentity": {"executable": owner["executable"]},
373
- }
374
- )
765
+ if unexpired:
766
+ item["classification"] = (
767
+ "STALE_CONTENT"
768
+ if lease.get("status") == "STALE_CONTENT"
769
+ else "ACTIVE"
770
+ )
771
+ results.append(item)
772
+ continue
773
+ identity = _verify_recorded_owner(owner)
375
774
  item["classification"] = (
376
775
  "OWNER_ACTIVE"
377
776
  if identity is True
@@ -386,7 +785,7 @@ def classify_leases(lease_root: Path) -> list[dict[str, Any]]:
386
785
 
387
786
 
388
787
  def reap_expired_leases(lease_root: Path) -> dict[str, Any]:
389
- """Remove only expired leases whose recorded owner is definitely gone."""
788
+ """Remove only leases whose recorded owner is definitely gone."""
390
789
  leases = classify_leases(lease_root)
391
790
  reaped: list[str] = []
392
791
  for item in leases:
@@ -502,6 +901,16 @@ def require_writable_lease(
502
901
  "changeId": change_id,
503
902
  "stackId": stack_id,
504
903
  }
904
+ if not _validate_environment_receipt(
905
+ environment_receipt,
906
+ stack_id=stack_id,
907
+ environment_hash=str(lease.get("environmentHash") or ""),
908
+ ):
909
+ return {
910
+ "ok": False,
911
+ "code": "ENVIRONMENT_RECEIPT_INVALID",
912
+ "message": "environment receipt digest or identity is invalid",
913
+ }
505
914
  observed_content = str(
506
915
  environment_receipt.get("contentFingerprint") or ""
507
916
  ).strip()
@@ -532,7 +941,7 @@ def require_writable_lease(
532
941
  return {"ok": True, "code": "LEASE_HELD", "lease": lease}
533
942
 
534
943
 
535
- def acquire_lease(
944
+ def _acquire_lease_locked(
536
945
  project: Path,
537
946
  *,
538
947
  change_id: str,
@@ -542,8 +951,17 @@ def acquire_lease(
542
951
  writable_volumes: list[str] | None = None,
543
952
  ttl_seconds: int = 3600,
544
953
  environment_receipt: dict[str, Any] | None = None,
954
+ mode: str = "change-session",
955
+ reset_evidence: dict[str, Any] | None = None,
956
+ receipt_dir: Path | None = None,
545
957
  ) -> dict[str, Any]:
546
- """Acquire a lease; reject cross-change sharing of the same writable volume."""
958
+ """Acquire, reuse, or reset an isolated environment session."""
959
+ if mode not in {"change-session", "ephemeral"}:
960
+ return {
961
+ "ok": False,
962
+ "code": "ENVIRONMENT_SESSION_MODE_INVALID",
963
+ "message": f"unsupported environment session mode: {mode}",
964
+ }
547
965
  root = lease_root or default_lease_root(project)
548
966
  root.mkdir(parents=True, exist_ok=True)
549
967
  volumes = [str(v).replace("\\", "/") for v in (writable_volumes or [])]
@@ -577,11 +995,16 @@ def acquire_lease(
577
995
  }
578
996
 
579
997
  path = _lease_path(root, stack_id)
998
+ current: dict[str, Any] = {}
580
999
  if path.is_file():
581
1000
  try:
582
1001
  current = _read_json(path)
583
- except (OSError, json.JSONDecodeError):
584
- current = {}
1002
+ except (OSError, json.JSONDecodeError) as exc:
1003
+ return {
1004
+ "ok": False,
1005
+ "code": "ENVIRONMENT_LEASE_UNREADABLE",
1006
+ "message": str(exc),
1007
+ }
585
1008
  if str(current.get("changeId") or "") not in {"", change_id}:
586
1009
  return {
587
1010
  "ok": False,
@@ -596,15 +1019,15 @@ def acquire_lease(
596
1019
  started = dt.datetime.now().astimezone()
597
1020
  expires = started + dt.timedelta(seconds=max(60, int(ttl_seconds)))
598
1021
  if environment_receipt is not None:
599
- if (
600
- environment_receipt.get("schemaVersion") != 1
601
- or environment_receipt.get("stackId") != stack_id
602
- or not str(environment_receipt.get("contentFingerprint") or "").strip()
1022
+ if not _validate_environment_receipt(
1023
+ environment_receipt,
1024
+ stack_id=stack_id,
1025
+ environment_hash=environment_hash,
603
1026
  ):
604
1027
  return {
605
1028
  "ok": False,
606
1029
  "code": "ENVIRONMENT_RECEIPT_INVALID",
607
- "message": "environment receipt identity does not match requested stack",
1030
+ "message": "environment receipt digest or identity does not match",
608
1031
  }
609
1032
  canaries = (
610
1033
  environment_receipt.get("content", {}).get("canaries")
@@ -620,10 +1043,88 @@ def acquire_lease(
620
1043
  "code": "ENVIRONMENT_CONTENT_NOT_READY",
621
1044
  "message": "all environment content canaries must pass before acquire",
622
1045
  }
1046
+ current_fingerprint = str(current.get("contentFingerprint") or "").strip()
1047
+ requested_fingerprint = str(
1048
+ (environment_receipt or {}).get("contentFingerprint") or ""
1049
+ ).strip()
1050
+ same_content = bool(current_fingerprint) and (
1051
+ current_fingerprint == requested_fingerprint
1052
+ )
1053
+ owner = current.get("owner")
1054
+ owner_verified = False
1055
+ if isinstance(owner, dict):
1056
+ try:
1057
+ owner_pid = int(owner.get("pid"))
1058
+ except (TypeError, ValueError):
1059
+ owner_pid = 0
1060
+ owner_verified = _verify_recorded_owner(owner) is True
1061
+ expires_at = _parse_expiry(current.get("expiresAt"))
1062
+ unexpired = (
1063
+ expires_at is not None
1064
+ and dt.datetime.now(dt.timezone.utc) < expires_at
1065
+ )
1066
+ if (
1067
+ current
1068
+ and str(current.get("changeId") or "") == change_id
1069
+ and mode == "change-session"
1070
+ and str(current.get("mode") or "change-session") == "change-session"
1071
+ and current.get("status") == "ACTIVE"
1072
+ and current.get("environmentHash") == environment_hash
1073
+ and current.get("writableVolumes") == volumes
1074
+ and owner_verified
1075
+ and unexpired
1076
+ and same_content
1077
+ ):
1078
+ current["heartbeatAt"] = started.isoformat(timespec="seconds")
1079
+ current["expiresAt"] = expires.isoformat(timespec="seconds")
1080
+ current["reuseCount"] = int(current.get("reuseCount") or 0) + 1
1081
+ _write_json(path, current)
1082
+ action_receipt = _write_environment_action_receipt(
1083
+ root,
1084
+ action="reuse",
1085
+ lease=current,
1086
+ receipt_dir=receipt_dir,
1087
+ )
1088
+ return {
1089
+ "ok": True,
1090
+ "code": "LEASE_REUSED",
1091
+ "lease": current,
1092
+ "path": str(path),
1093
+ "receiptPath": str(action_receipt),
1094
+ }
1095
+ previous_session_id = str(current.get("sessionId") or "").strip() or None
1096
+ reset_count = int(current.get("resetCount") or 0)
1097
+ is_reset = bool(current)
1098
+ reset_operation = _operation_evidence(
1099
+ reset_evidence,
1100
+ operation="reset",
1101
+ lease_root=root,
1102
+ lease=current,
1103
+ )
1104
+ if is_reset and reset_operation is None:
1105
+ return {
1106
+ "ok": False,
1107
+ "code": "ENVIRONMENT_RESET_REQUIRED",
1108
+ "message": (
1109
+ "existing environment cannot be replaced until a matching "
1110
+ "provider reset receipt is supplied"
1111
+ ),
1112
+ "changeId": change_id,
1113
+ "stackId": stack_id,
1114
+ "previousSessionId": previous_session_id,
1115
+ }
1116
+ if is_reset and not _consume_operation_evidence(root, reset_operation):
1117
+ return {
1118
+ "ok": False,
1119
+ "code": "ENVIRONMENT_RESET_EVIDENCE_REPLAYED",
1120
+ "message": "provider reset evidence was already consumed",
1121
+ }
623
1122
  lease = {
624
- "schemaVersion": 2 if environment_receipt is not None else 1,
1123
+ "schemaVersion": 3,
625
1124
  "changeId": change_id,
626
1125
  "stackId": stack_id,
1126
+ "sessionId": uuid.uuid4().hex,
1127
+ "mode": mode,
627
1128
  "environmentHash": environment_hash,
628
1129
  "writableVolumes": volumes,
629
1130
  "acquiredAt": started.isoformat(timespec="seconds"),
@@ -631,12 +1132,14 @@ def acquire_lease(
631
1132
  "expiresAt": expires.isoformat(timespec="seconds"),
632
1133
  "projectRoot": str(project.resolve()),
633
1134
  "status": "ACTIVE",
1135
+ "reuseCount": 0,
1136
+ "resetCount": reset_count + (1 if is_reset else 0),
634
1137
  "owner": {
635
1138
  "pid": os.getpid(),
636
- "executable": hservice.get_process_executable(os.getpid())
1139
+ "executable": hprocess.get_process_executable(os.getpid())
637
1140
  or str(Path(sys.executable).resolve()),
638
1141
  "startedAt": (
639
- hservice.get_process_create_time(os.getpid())
1142
+ hprocess.get_process_create_time(os.getpid())
640
1143
  or started
641
1144
  ).isoformat(timespec="seconds"),
642
1145
  },
@@ -645,15 +1148,79 @@ def acquire_lease(
645
1148
  lease["contentFingerprint"] = environment_receipt["contentFingerprint"]
646
1149
  lease["environmentReceiptId"] = environment_receipt.get("receiptId")
647
1150
  _write_json(path, lease)
648
- return {"ok": True, "code": "LEASE_ACQUIRED", "lease": lease, "path": str(path)}
1151
+ action = "reset" if is_reset else "prepare"
1152
+ action_receipt = _write_environment_action_receipt(
1153
+ root,
1154
+ action=action,
1155
+ lease=lease,
1156
+ previous_session_id=previous_session_id,
1157
+ receipt_dir=receipt_dir,
1158
+ operation_evidence=reset_operation,
1159
+ )
1160
+ return {
1161
+ "ok": True,
1162
+ "code": "LEASE_RESET" if is_reset else "LEASE_ACQUIRED",
1163
+ "lease": lease,
1164
+ "path": str(path),
1165
+ "receiptPath": str(action_receipt),
1166
+ }
649
1167
 
650
1168
 
651
- def release_lease(
1169
+ def acquire_lease(
1170
+ project: Path,
1171
+ *,
1172
+ change_id: str,
1173
+ stack_id: str,
1174
+ environment_hash: str,
1175
+ lease_root: Path | None = None,
1176
+ writable_volumes: list[str] | None = None,
1177
+ ttl_seconds: int = 3600,
1178
+ environment_receipt: dict[str, Any] | None = None,
1179
+ mode: str = "change-session",
1180
+ reset_evidence: dict[str, Any] | None = None,
1181
+ ) -> dict[str, Any]:
1182
+ root = lease_root or default_lease_root(project)
1183
+ receipt_dir: Path | None = None
1184
+ if lease_root is None:
1185
+ contract_dir = project / ".harness" / "changes" / change_id
1186
+ receipt_dir = (
1187
+ resolve_state_dir_for_contract(contract_dir, project)
1188
+ / "runtime"
1189
+ / "environment-receipts"
1190
+ )
1191
+ try:
1192
+ with _lease_registry_guard(root):
1193
+ return _acquire_lease_locked(
1194
+ project,
1195
+ change_id=change_id,
1196
+ stack_id=stack_id,
1197
+ environment_hash=environment_hash,
1198
+ lease_root=root,
1199
+ writable_volumes=writable_volumes,
1200
+ ttl_seconds=ttl_seconds,
1201
+ environment_receipt=environment_receipt,
1202
+ mode=mode,
1203
+ reset_evidence=reset_evidence,
1204
+ receipt_dir=receipt_dir,
1205
+ )
1206
+ except RuntimeError as exc:
1207
+ if str(exc) != "ENVIRONMENT_LEASE_BUSY":
1208
+ raise
1209
+ return {
1210
+ "ok": False,
1211
+ "code": "ENVIRONMENT_LEASE_BUSY",
1212
+ "message": "another lease acquisition is still in progress",
1213
+ }
1214
+
1215
+
1216
+ def _release_lease_locked(
652
1217
  project: Path,
653
1218
  *,
654
1219
  change_id: str,
655
1220
  stack_id: str,
656
1221
  lease_root: Path | None = None,
1222
+ cleanup_evidence: dict[str, Any] | None = None,
1223
+ receipt_dir: Path | None = None,
657
1224
  ) -> dict[str, Any]:
658
1225
  root = lease_root or default_lease_root(project)
659
1226
  path = _lease_path(root, stack_id)
@@ -677,8 +1244,78 @@ def release_lease(
677
1244
  "holder": lease.get("changeId"),
678
1245
  "changeId": change_id,
679
1246
  }
1247
+ cleanup_operation = _operation_evidence(
1248
+ cleanup_evidence,
1249
+ operation="cleanup",
1250
+ lease_root=root,
1251
+ lease=lease,
1252
+ )
1253
+ if cleanup_evidence is not None and cleanup_operation is None:
1254
+ return {
1255
+ "ok": False,
1256
+ "code": "ENVIRONMENT_CLEANUP_EVIDENCE_INVALID",
1257
+ "message": "provider cleanup evidence does not match the held lease",
1258
+ }
1259
+ if cleanup_operation is not None and not _consume_operation_evidence(
1260
+ root, cleanup_operation
1261
+ ):
1262
+ return {
1263
+ "ok": False,
1264
+ "code": "ENVIRONMENT_CLEANUP_EVIDENCE_REPLAYED",
1265
+ "message": "provider cleanup evidence was already consumed",
1266
+ }
1267
+ action_receipt = _write_environment_action_receipt(
1268
+ root,
1269
+ action="cleanup" if cleanup_operation is not None else "release",
1270
+ lease=lease,
1271
+ receipt_dir=receipt_dir,
1272
+ operation_evidence=cleanup_operation,
1273
+ )
680
1274
  path.unlink(missing_ok=True)
681
- return {"ok": True, "code": "LEASE_RELEASED", "changeId": change_id, "stackId": stack_id}
1275
+ return {
1276
+ "ok": True,
1277
+ "code": "LEASE_RELEASED",
1278
+ "changeId": change_id,
1279
+ "stackId": stack_id,
1280
+ "receiptPath": str(action_receipt),
1281
+ }
1282
+
1283
+
1284
+ def release_lease(
1285
+ project: Path,
1286
+ *,
1287
+ change_id: str,
1288
+ stack_id: str,
1289
+ lease_root: Path | None = None,
1290
+ cleanup_evidence: dict[str, Any] | None = None,
1291
+ ) -> dict[str, Any]:
1292
+ root = lease_root or default_lease_root(project)
1293
+ receipt_dir: Path | None = None
1294
+ if lease_root is None:
1295
+ contract_dir = project / ".harness" / "changes" / change_id
1296
+ receipt_dir = (
1297
+ resolve_state_dir_for_contract(contract_dir, project)
1298
+ / "runtime"
1299
+ / "environment-receipts"
1300
+ )
1301
+ try:
1302
+ with _lease_registry_guard(root):
1303
+ return _release_lease_locked(
1304
+ project,
1305
+ change_id=change_id,
1306
+ stack_id=stack_id,
1307
+ lease_root=root,
1308
+ cleanup_evidence=cleanup_evidence,
1309
+ receipt_dir=receipt_dir,
1310
+ )
1311
+ except RuntimeError as exc:
1312
+ if str(exc) != "ENVIRONMENT_LEASE_BUSY":
1313
+ raise
1314
+ return {
1315
+ "ok": False,
1316
+ "code": "ENVIRONMENT_LEASE_BUSY",
1317
+ "message": "another lease operation is still in progress",
1318
+ }
682
1319
 
683
1320
 
684
1321
  def cmd_fingerprint(args: argparse.Namespace) -> int:
@@ -822,11 +1459,12 @@ def cmd_prepare(args: argparse.Namespace) -> int:
822
1459
 
823
1460
  def cmd_acquire(args: argparse.Namespace) -> int:
824
1461
  project = Path(args.project).resolve()
825
- root = Path(args.lease_root).resolve() if args.lease_root else default_lease_root(project)
1462
+ root = Path(args.lease_root).resolve() if args.lease_root else None
826
1463
  env_hash = args.environment_hash or compute_environment_hash(project)
827
1464
  volumes = [v for v in (args.writable_volume or []) if v]
828
1465
  try:
829
1466
  receipt = _load_receipt_arg(args.environment_receipt)
1467
+ reset_evidence = _load_receipt_arg(args.reset_evidence)
830
1468
  result = acquire_lease(
831
1469
  project,
832
1470
  change_id=args.change,
@@ -836,6 +1474,8 @@ def cmd_acquire(args: argparse.Namespace) -> int:
836
1474
  writable_volumes=volumes,
837
1475
  ttl_seconds=int(args.ttl_seconds or 3600),
838
1476
  environment_receipt=receipt,
1477
+ mode=args.mode,
1478
+ reset_evidence=reset_evidence,
839
1479
  )
840
1480
  except (OSError, json.JSONDecodeError, ValueError) as exc:
841
1481
  result = {
@@ -848,16 +1488,72 @@ def cmd_acquire(args: argparse.Namespace) -> int:
848
1488
 
849
1489
  def cmd_release(args: argparse.Namespace) -> int:
850
1490
  project = Path(args.project).resolve()
851
- root = Path(args.lease_root).resolve() if args.lease_root else default_lease_root(project)
852
- result = release_lease(
853
- project,
854
- change_id=args.change,
855
- stack_id=args.stack_id,
856
- lease_root=root,
857
- )
1491
+ root = Path(args.lease_root).resolve() if args.lease_root else None
1492
+ try:
1493
+ cleanup_evidence = _load_receipt_arg(args.cleanup_evidence)
1494
+ result = release_lease(
1495
+ project,
1496
+ change_id=args.change,
1497
+ stack_id=args.stack_id,
1498
+ lease_root=root,
1499
+ cleanup_evidence=cleanup_evidence,
1500
+ )
1501
+ except (OSError, json.JSONDecodeError, ValueError) as exc:
1502
+ result = {
1503
+ "ok": False,
1504
+ "code": "ENVIRONMENT_CLEANUP_EVIDENCE_INVALID",
1505
+ "message": str(exc),
1506
+ }
858
1507
  return emit_json(result, ok=bool(result.get("ok")))
859
1508
 
860
1509
 
1510
+ def cmd_provider_operation(args: argparse.Namespace) -> int:
1511
+ project = Path(args.project).resolve()
1512
+ root = Path(args.lease_root).resolve() if args.lease_root else None
1513
+ try:
1514
+ argv = json.loads(args.argv_json)
1515
+ if not isinstance(argv, list):
1516
+ raise ValueError("provider argv must be a JSON array")
1517
+ result = execute_provider_operation(
1518
+ project,
1519
+ change_id=args.change,
1520
+ stack_id=args.stack_id,
1521
+ operation=args.operation,
1522
+ argv=argv,
1523
+ lease_root=root,
1524
+ timeout_seconds=float(args.timeout_seconds),
1525
+ )
1526
+ except (
1527
+ OSError,
1528
+ json.JSONDecodeError,
1529
+ ValueError,
1530
+ subprocess.SubprocessError,
1531
+ ) as exc:
1532
+ return emit_json(
1533
+ {
1534
+ "ok": False,
1535
+ "code": "ENVIRONMENT_PROVIDER_OPERATION_FAILED",
1536
+ "message": str(exc),
1537
+ },
1538
+ ok=False,
1539
+ )
1540
+ if args.output:
1541
+ _write_json(Path(args.output).resolve(), result)
1542
+ return emit_json(
1543
+ {
1544
+ "ok": result.get("status") == "OK",
1545
+ "code": (
1546
+ "ENVIRONMENT_PROVIDER_OPERATION_OK"
1547
+ if result.get("status") == "OK"
1548
+ else "ENVIRONMENT_PROVIDER_OPERATION_FAILED"
1549
+ ),
1550
+ "receiptPath": result.get("receiptPath"),
1551
+ "receiptDigest": result.get("receiptDigest"),
1552
+ },
1553
+ ok=result.get("status") == "OK",
1554
+ )
1555
+
1556
+
861
1557
  def cmd_require(args: argparse.Namespace) -> int:
862
1558
  project = Path(args.project).resolve()
863
1559
  root = Path(args.lease_root).resolve() if args.lease_root else default_lease_root(project)
@@ -933,9 +1629,15 @@ def build_parser() -> argparse.ArgumentParser:
933
1629
  ac.add_argument("--stack-id", required=True)
934
1630
  ac.add_argument("--environment-hash")
935
1631
  ac.add_argument("--environment-receipt")
1632
+ ac.add_argument("--reset-evidence")
936
1633
  ac.add_argument("--lease-root")
937
1634
  ac.add_argument("--writable-volume", action="append", default=[])
938
1635
  ac.add_argument("--ttl-seconds", type=int, default=3600)
1636
+ ac.add_argument(
1637
+ "--mode",
1638
+ choices=("change-session", "ephemeral"),
1639
+ default="change-session",
1640
+ )
939
1641
  ac.set_defaults(func=cmd_acquire)
940
1642
 
941
1643
  rel = sub.add_parser("release")
@@ -943,8 +1645,20 @@ def build_parser() -> argparse.ArgumentParser:
943
1645
  rel.add_argument("--change", required=True)
944
1646
  rel.add_argument("--stack-id", required=True)
945
1647
  rel.add_argument("--lease-root")
1648
+ rel.add_argument("--cleanup-evidence")
946
1649
  rel.set_defaults(func=cmd_release)
947
1650
 
1651
+ provider = sub.add_parser("provider-operation")
1652
+ provider.add_argument("--project", required=True)
1653
+ provider.add_argument("--change", required=True)
1654
+ provider.add_argument("--stack-id", required=True)
1655
+ provider.add_argument("--operation", choices=("reset", "cleanup"), required=True)
1656
+ provider.add_argument("--argv-json", required=True)
1657
+ provider.add_argument("--lease-root")
1658
+ provider.add_argument("--timeout-seconds", type=float, default=900.0)
1659
+ provider.add_argument("--output")
1660
+ provider.set_defaults(func=cmd_provider_operation)
1661
+
948
1662
  req = sub.add_parser("require")
949
1663
  req.add_argument("--project", required=True)
950
1664
  req.add_argument("--change", required=True)