@hunter-harness/workflow-harness 0.2.34 → 0.2.36

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 (226) 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 +147 -0
  3. package/harness/bundles/general/claude-code/contracts/doctor-capability.schema.json +43 -0
  4. package/harness/bundles/general/claude-code/contracts/external-platform-observation.schema.json +62 -0
  5. package/harness/bundles/general/claude-code/harness-archive/SKILL.md +1 -1
  6. package/harness/bundles/general/claude-code/harness-codebase-map/SKILL.md +1 -1
  7. package/harness/bundles/general/claude-code/harness-knowledge-ingest/SKILL.md +1 -1
  8. package/harness/bundles/general/claude-code/harness-knowledge-ingest/scripts/harness_knowledge.py +24 -1
  9. package/harness/bundles/general/claude-code/harness-knowledge-query/SKILL.md +1 -1
  10. package/harness/bundles/general/claude-code/harness-plan/SKILL.md +1 -1
  11. package/harness/bundles/general/claude-code/harness-review/SKILL.md +1 -1
  12. package/harness/bundles/general/claude-code/harness-run/SKILL.md +1 -1
  13. package/harness/bundles/general/claude-code/harness-submit/SKILL.md +1 -1
  14. package/harness/bundles/general/claude-code/harness-sync/SKILL.md +1 -1
  15. package/harness/bundles/general/claude-code/harness-sync/reference.md +2 -2
  16. package/harness/bundles/general/claude-code/harness-test/SKILL.md +1 -1
  17. package/harness/bundles/general/claude-code/harness-test/pitfalls.md +1 -1
  18. package/harness/bundles/general/claude-code/protocols/ci-layering-protocol.md +22 -0
  19. package/harness/bundles/general/claude-code/protocols/external-platform-protocol.md +27 -0
  20. package/harness/bundles/general/claude-code/protocols/memory-closeout-protocol.md +21 -0
  21. package/harness/bundles/general/claude-code/protocols/registry-governance-protocol.md +31 -0
  22. package/harness/bundles/general/claude-code/protocols/verification-graph-protocol.md +26 -0
  23. package/harness/bundles/general/claude-code/scripts/harness_archive.py +24 -12
  24. package/harness/bundles/general/claude-code/scripts/harness_ledger.py +121 -12
  25. package/harness/bundles/general/claude-code/scripts/harness_preflight.py +2 -2
  26. package/harness/bundles/general/claude-code/scripts/harness_profile.py +308 -23
  27. package/harness/bundles/general/codebuddy/.harness-build.json +1 -1
  28. package/harness/bundles/general/codebuddy/contracts/build-profile-v3.schema.json +147 -0
  29. package/harness/bundles/general/codebuddy/contracts/doctor-capability.schema.json +43 -0
  30. package/harness/bundles/general/codebuddy/contracts/external-platform-observation.schema.json +62 -0
  31. package/harness/bundles/general/codebuddy/harness-archive/SKILL.md +1 -1
  32. package/harness/bundles/general/codebuddy/harness-codebase-map/SKILL.md +1 -1
  33. package/harness/bundles/general/codebuddy/harness-knowledge-ingest/SKILL.md +1 -1
  34. package/harness/bundles/general/codebuddy/harness-knowledge-ingest/scripts/harness_knowledge.py +24 -1
  35. package/harness/bundles/general/codebuddy/harness-knowledge-query/SKILL.md +1 -1
  36. package/harness/bundles/general/codebuddy/harness-plan/SKILL.md +1 -1
  37. package/harness/bundles/general/codebuddy/harness-review/SKILL.md +1 -1
  38. package/harness/bundles/general/codebuddy/harness-run/SKILL.md +1 -1
  39. package/harness/bundles/general/codebuddy/harness-submit/SKILL.md +1 -1
  40. package/harness/bundles/general/codebuddy/harness-sync/SKILL.md +1 -1
  41. package/harness/bundles/general/codebuddy/harness-sync/reference.md +2 -2
  42. package/harness/bundles/general/codebuddy/harness-test/SKILL.md +1 -1
  43. package/harness/bundles/general/codebuddy/harness-test/pitfalls.md +1 -1
  44. package/harness/bundles/general/codebuddy/protocols/ci-layering-protocol.md +22 -0
  45. package/harness/bundles/general/codebuddy/protocols/external-platform-protocol.md +27 -0
  46. package/harness/bundles/general/codebuddy/protocols/memory-closeout-protocol.md +21 -0
  47. package/harness/bundles/general/codebuddy/protocols/registry-governance-protocol.md +31 -0
  48. package/harness/bundles/general/codebuddy/protocols/verification-graph-protocol.md +26 -0
  49. package/harness/bundles/general/codebuddy/scripts/harness_archive.py +24 -12
  50. package/harness/bundles/general/codebuddy/scripts/harness_ledger.py +121 -12
  51. package/harness/bundles/general/codebuddy/scripts/harness_preflight.py +2 -2
  52. package/harness/bundles/general/codebuddy/scripts/harness_profile.py +308 -23
  53. package/harness/bundles/general/codex/.harness-build.json +1 -1
  54. package/harness/bundles/general/codex/contracts/build-profile-v3.schema.json +147 -0
  55. package/harness/bundles/general/codex/contracts/doctor-capability.schema.json +43 -0
  56. package/harness/bundles/general/codex/contracts/external-platform-observation.schema.json +62 -0
  57. package/harness/bundles/general/codex/harness-archive/SKILL.md +1 -1
  58. package/harness/bundles/general/codex/harness-codebase-map/SKILL.md +1 -1
  59. package/harness/bundles/general/codex/harness-knowledge-ingest/SKILL.md +1 -1
  60. package/harness/bundles/general/codex/harness-knowledge-ingest/scripts/harness_knowledge.py +24 -1
  61. package/harness/bundles/general/codex/harness-knowledge-query/SKILL.md +1 -1
  62. package/harness/bundles/general/codex/harness-plan/SKILL.md +1 -1
  63. package/harness/bundles/general/codex/harness-review/SKILL.md +1 -1
  64. package/harness/bundles/general/codex/harness-run/SKILL.md +1 -1
  65. package/harness/bundles/general/codex/harness-submit/SKILL.md +1 -1
  66. package/harness/bundles/general/codex/harness-sync/SKILL.md +1 -1
  67. package/harness/bundles/general/codex/harness-sync/reference.md +2 -2
  68. package/harness/bundles/general/codex/harness-test/SKILL.md +1 -1
  69. package/harness/bundles/general/codex/harness-test/pitfalls.md +1 -1
  70. package/harness/bundles/general/codex/protocols/ci-layering-protocol.md +22 -0
  71. package/harness/bundles/general/codex/protocols/external-platform-protocol.md +27 -0
  72. package/harness/bundles/general/codex/protocols/memory-closeout-protocol.md +21 -0
  73. package/harness/bundles/general/codex/protocols/registry-governance-protocol.md +31 -0
  74. package/harness/bundles/general/codex/protocols/verification-graph-protocol.md +26 -0
  75. package/harness/bundles/general/codex/scripts/harness_archive.py +24 -12
  76. package/harness/bundles/general/codex/scripts/harness_ledger.py +121 -12
  77. package/harness/bundles/general/codex/scripts/harness_preflight.py +2 -2
  78. package/harness/bundles/general/codex/scripts/harness_profile.py +308 -23
  79. package/harness/bundles/general/cursor/.harness-build.json +1 -1
  80. package/harness/bundles/general/cursor/contracts/build-profile-v3.schema.json +147 -0
  81. package/harness/bundles/general/cursor/contracts/doctor-capability.schema.json +43 -0
  82. package/harness/bundles/general/cursor/contracts/external-platform-observation.schema.json +62 -0
  83. package/harness/bundles/general/cursor/harness-archive/SKILL.md +1 -1
  84. package/harness/bundles/general/cursor/harness-codebase-map/SKILL.md +1 -1
  85. package/harness/bundles/general/cursor/harness-knowledge-ingest/SKILL.md +1 -1
  86. package/harness/bundles/general/cursor/harness-knowledge-ingest/scripts/harness_knowledge.py +24 -1
  87. package/harness/bundles/general/cursor/harness-knowledge-query/SKILL.md +1 -1
  88. package/harness/bundles/general/cursor/harness-plan/SKILL.md +1 -1
  89. package/harness/bundles/general/cursor/harness-review/SKILL.md +1 -1
  90. package/harness/bundles/general/cursor/harness-run/SKILL.md +1 -1
  91. package/harness/bundles/general/cursor/harness-submit/SKILL.md +1 -1
  92. package/harness/bundles/general/cursor/harness-sync/SKILL.md +1 -1
  93. package/harness/bundles/general/cursor/harness-sync/reference.md +2 -2
  94. package/harness/bundles/general/cursor/harness-test/SKILL.md +1 -1
  95. package/harness/bundles/general/cursor/harness-test/pitfalls.md +1 -1
  96. package/harness/bundles/general/cursor/protocols/ci-layering-protocol.md +22 -0
  97. package/harness/bundles/general/cursor/protocols/external-platform-protocol.md +27 -0
  98. package/harness/bundles/general/cursor/protocols/memory-closeout-protocol.md +21 -0
  99. package/harness/bundles/general/cursor/protocols/registry-governance-protocol.md +31 -0
  100. package/harness/bundles/general/cursor/protocols/verification-graph-protocol.md +26 -0
  101. package/harness/bundles/general/cursor/scripts/harness_archive.py +24 -12
  102. package/harness/bundles/general/cursor/scripts/harness_ledger.py +121 -12
  103. package/harness/bundles/general/cursor/scripts/harness_preflight.py +2 -2
  104. package/harness/bundles/general/cursor/scripts/harness_profile.py +308 -23
  105. package/harness/bundles/java/claude-code/.harness-build.json +1 -1
  106. package/harness/bundles/java/claude-code/contracts/build-profile-v3.schema.json +147 -0
  107. package/harness/bundles/java/claude-code/contracts/doctor-capability.schema.json +43 -0
  108. package/harness/bundles/java/claude-code/contracts/external-platform-observation.schema.json +62 -0
  109. package/harness/bundles/java/claude-code/harness-apidoc/SKILL.md +1 -1
  110. package/harness/bundles/java/claude-code/harness-archive/SKILL.md +1 -1
  111. package/harness/bundles/java/claude-code/harness-codebase-map/SKILL.md +1 -1
  112. package/harness/bundles/java/claude-code/harness-knowledge-ingest/SKILL.md +1 -1
  113. package/harness/bundles/java/claude-code/harness-knowledge-ingest/scripts/harness_knowledge.py +24 -1
  114. package/harness/bundles/java/claude-code/harness-knowledge-query/SKILL.md +1 -1
  115. package/harness/bundles/java/claude-code/harness-package/SKILL.md +1 -1
  116. package/harness/bundles/java/claude-code/harness-plan/SKILL.md +1 -1
  117. package/harness/bundles/java/claude-code/harness-review/SKILL.md +1 -1
  118. package/harness/bundles/java/claude-code/harness-run/SKILL.md +1 -1
  119. package/harness/bundles/java/claude-code/harness-submit/SKILL.md +1 -1
  120. package/harness/bundles/java/claude-code/harness-sync/SKILL.md +1 -1
  121. package/harness/bundles/java/claude-code/harness-sync/reference.md +2 -2
  122. package/harness/bundles/java/claude-code/harness-test/SKILL.md +1 -1
  123. package/harness/bundles/java/claude-code/harness-test/pitfalls.md +1 -1
  124. package/harness/bundles/java/claude-code/protocols/ci-layering-protocol.md +22 -0
  125. package/harness/bundles/java/claude-code/protocols/external-platform-protocol.md +27 -0
  126. package/harness/bundles/java/claude-code/protocols/memory-closeout-protocol.md +21 -0
  127. package/harness/bundles/java/claude-code/protocols/registry-governance-protocol.md +31 -0
  128. package/harness/bundles/java/claude-code/protocols/verification-graph-protocol.md +26 -0
  129. package/harness/bundles/java/claude-code/scripts/harness_archive.py +24 -12
  130. package/harness/bundles/java/claude-code/scripts/harness_ledger.py +121 -12
  131. package/harness/bundles/java/claude-code/scripts/harness_preflight.py +2 -2
  132. package/harness/bundles/java/claude-code/scripts/harness_profile.py +308 -23
  133. package/harness/bundles/java/codebuddy/.harness-build.json +1 -1
  134. package/harness/bundles/java/codebuddy/contracts/build-profile-v3.schema.json +147 -0
  135. package/harness/bundles/java/codebuddy/contracts/doctor-capability.schema.json +43 -0
  136. package/harness/bundles/java/codebuddy/contracts/external-platform-observation.schema.json +62 -0
  137. package/harness/bundles/java/codebuddy/harness-apidoc/SKILL.md +1 -1
  138. package/harness/bundles/java/codebuddy/harness-archive/SKILL.md +1 -1
  139. package/harness/bundles/java/codebuddy/harness-codebase-map/SKILL.md +1 -1
  140. package/harness/bundles/java/codebuddy/harness-knowledge-ingest/SKILL.md +1 -1
  141. package/harness/bundles/java/codebuddy/harness-knowledge-ingest/scripts/harness_knowledge.py +24 -1
  142. package/harness/bundles/java/codebuddy/harness-knowledge-query/SKILL.md +1 -1
  143. package/harness/bundles/java/codebuddy/harness-package/SKILL.md +1 -1
  144. package/harness/bundles/java/codebuddy/harness-plan/SKILL.md +1 -1
  145. package/harness/bundles/java/codebuddy/harness-review/SKILL.md +1 -1
  146. package/harness/bundles/java/codebuddy/harness-run/SKILL.md +1 -1
  147. package/harness/bundles/java/codebuddy/harness-submit/SKILL.md +1 -1
  148. package/harness/bundles/java/codebuddy/harness-sync/SKILL.md +1 -1
  149. package/harness/bundles/java/codebuddy/harness-sync/reference.md +2 -2
  150. package/harness/bundles/java/codebuddy/harness-test/SKILL.md +1 -1
  151. package/harness/bundles/java/codebuddy/harness-test/pitfalls.md +1 -1
  152. package/harness/bundles/java/codebuddy/protocols/ci-layering-protocol.md +22 -0
  153. package/harness/bundles/java/codebuddy/protocols/external-platform-protocol.md +27 -0
  154. package/harness/bundles/java/codebuddy/protocols/memory-closeout-protocol.md +21 -0
  155. package/harness/bundles/java/codebuddy/protocols/registry-governance-protocol.md +31 -0
  156. package/harness/bundles/java/codebuddy/protocols/verification-graph-protocol.md +26 -0
  157. package/harness/bundles/java/codebuddy/scripts/harness_archive.py +24 -12
  158. package/harness/bundles/java/codebuddy/scripts/harness_ledger.py +121 -12
  159. package/harness/bundles/java/codebuddy/scripts/harness_preflight.py +2 -2
  160. package/harness/bundles/java/codebuddy/scripts/harness_profile.py +308 -23
  161. package/harness/bundles/java/codex/.harness-build.json +1 -1
  162. package/harness/bundles/java/codex/contracts/build-profile-v3.schema.json +147 -0
  163. package/harness/bundles/java/codex/contracts/doctor-capability.schema.json +43 -0
  164. package/harness/bundles/java/codex/contracts/external-platform-observation.schema.json +62 -0
  165. package/harness/bundles/java/codex/harness-apidoc/SKILL.md +1 -1
  166. package/harness/bundles/java/codex/harness-archive/SKILL.md +1 -1
  167. package/harness/bundles/java/codex/harness-codebase-map/SKILL.md +1 -1
  168. package/harness/bundles/java/codex/harness-knowledge-ingest/SKILL.md +1 -1
  169. package/harness/bundles/java/codex/harness-knowledge-ingest/scripts/harness_knowledge.py +24 -1
  170. package/harness/bundles/java/codex/harness-knowledge-query/SKILL.md +1 -1
  171. package/harness/bundles/java/codex/harness-package/SKILL.md +1 -1
  172. package/harness/bundles/java/codex/harness-plan/SKILL.md +1 -1
  173. package/harness/bundles/java/codex/harness-review/SKILL.md +1 -1
  174. package/harness/bundles/java/codex/harness-run/SKILL.md +1 -1
  175. package/harness/bundles/java/codex/harness-submit/SKILL.md +1 -1
  176. package/harness/bundles/java/codex/harness-sync/SKILL.md +1 -1
  177. package/harness/bundles/java/codex/harness-sync/reference.md +2 -2
  178. package/harness/bundles/java/codex/harness-test/SKILL.md +1 -1
  179. package/harness/bundles/java/codex/harness-test/pitfalls.md +1 -1
  180. package/harness/bundles/java/codex/protocols/ci-layering-protocol.md +22 -0
  181. package/harness/bundles/java/codex/protocols/external-platform-protocol.md +27 -0
  182. package/harness/bundles/java/codex/protocols/memory-closeout-protocol.md +21 -0
  183. package/harness/bundles/java/codex/protocols/registry-governance-protocol.md +31 -0
  184. package/harness/bundles/java/codex/protocols/verification-graph-protocol.md +26 -0
  185. package/harness/bundles/java/codex/scripts/harness_archive.py +24 -12
  186. package/harness/bundles/java/codex/scripts/harness_ledger.py +121 -12
  187. package/harness/bundles/java/codex/scripts/harness_preflight.py +2 -2
  188. package/harness/bundles/java/codex/scripts/harness_profile.py +308 -23
  189. package/harness/bundles/java/cursor/.harness-build.json +1 -1
  190. package/harness/bundles/java/cursor/contracts/build-profile-v3.schema.json +147 -0
  191. package/harness/bundles/java/cursor/contracts/doctor-capability.schema.json +43 -0
  192. package/harness/bundles/java/cursor/contracts/external-platform-observation.schema.json +62 -0
  193. package/harness/bundles/java/cursor/harness-apidoc/SKILL.md +1 -1
  194. package/harness/bundles/java/cursor/harness-archive/SKILL.md +1 -1
  195. package/harness/bundles/java/cursor/harness-codebase-map/SKILL.md +1 -1
  196. package/harness/bundles/java/cursor/harness-knowledge-ingest/SKILL.md +1 -1
  197. package/harness/bundles/java/cursor/harness-knowledge-ingest/scripts/harness_knowledge.py +24 -1
  198. package/harness/bundles/java/cursor/harness-knowledge-query/SKILL.md +1 -1
  199. package/harness/bundles/java/cursor/harness-package/SKILL.md +1 -1
  200. package/harness/bundles/java/cursor/harness-plan/SKILL.md +1 -1
  201. package/harness/bundles/java/cursor/harness-review/SKILL.md +1 -1
  202. package/harness/bundles/java/cursor/harness-run/SKILL.md +1 -1
  203. package/harness/bundles/java/cursor/harness-submit/SKILL.md +1 -1
  204. package/harness/bundles/java/cursor/harness-sync/SKILL.md +1 -1
  205. package/harness/bundles/java/cursor/harness-sync/reference.md +2 -2
  206. package/harness/bundles/java/cursor/harness-test/SKILL.md +1 -1
  207. package/harness/bundles/java/cursor/harness-test/pitfalls.md +1 -1
  208. package/harness/bundles/java/cursor/protocols/ci-layering-protocol.md +22 -0
  209. package/harness/bundles/java/cursor/protocols/external-platform-protocol.md +27 -0
  210. package/harness/bundles/java/cursor/protocols/memory-closeout-protocol.md +21 -0
  211. package/harness/bundles/java/cursor/protocols/registry-governance-protocol.md +31 -0
  212. package/harness/bundles/java/cursor/protocols/verification-graph-protocol.md +26 -0
  213. package/harness/bundles/java/cursor/scripts/harness_archive.py +24 -12
  214. package/harness/bundles/java/cursor/scripts/harness_ledger.py +121 -12
  215. package/harness/bundles/java/cursor/scripts/harness_preflight.py +2 -2
  216. package/harness/bundles/java/cursor/scripts/harness_profile.py +308 -23
  217. package/harness/manifests/general/claude-code.json +58 -20
  218. package/harness/manifests/general/codebuddy.json +58 -20
  219. package/harness/manifests/general/codex.json +58 -20
  220. package/harness/manifests/general/cursor.json +58 -20
  221. package/harness/manifests/java/claude-code.json +60 -22
  222. package/harness/manifests/java/codebuddy.json +60 -22
  223. package/harness/manifests/java/codex.json +60 -22
  224. package/harness/manifests/java/cursor.json +60 -22
  225. package/hunter-workflow-family.json +17 -5
  226. package/package.json +1 -1
@@ -621,6 +621,7 @@ def evaluate_product_ci_gate(change_dir: Path) -> dict[str, Any]:
621
621
 
622
622
  if assurance == "local-reproducible" and provider == "local-harness":
623
623
  local_required = {
624
+ "subject.environmentHash": environment_hash,
624
625
  "verification.commandSetHash": command_set_hash,
625
626
  "verification.ledgerHash": ledger_hash,
626
627
  "verification.toolchainHashes": verification.get("toolchainHashes"),
@@ -1047,6 +1048,14 @@ def certify_local_candidate(
1047
1048
  f"ledger={recorded_tree_hash} current={current_tree_hash}"
1048
1049
  )
1049
1050
  product_tree_hash = current_tree_hash
1051
+ environment_hashes = sorted(
1052
+ {str(item["environmentHash"]) for item in selected}
1053
+ )
1054
+ subject_environment_hash = (
1055
+ environment_hashes[0]
1056
+ if len(environment_hashes) == 1
1057
+ else _candidate_value_hash(environment_hashes)
1058
+ )
1050
1059
 
1051
1060
  receipt = {
1052
1061
  "schemaVersion": 2,
@@ -1057,6 +1066,7 @@ def certify_local_candidate(
1057
1066
  "subject": {
1058
1067
  "productCommit": product_commit,
1059
1068
  "productTreeHash": product_tree_hash,
1069
+ "environmentHash": subject_environment_hash,
1060
1070
  },
1061
1071
  "verification": {
1062
1072
  "requiredValidations": required,
@@ -1068,9 +1078,7 @@ def certify_local_candidate(
1068
1078
  "toolchainHashes": sorted(
1069
1079
  {str(item["toolchainHash"]) for item in selected}
1070
1080
  ),
1071
- "environmentHashes": sorted(
1072
- {str(item["environmentHash"]) for item in selected}
1073
- ),
1081
+ "environmentHashes": environment_hashes,
1074
1082
  "dependencyHashes": sorted(
1075
1083
  {str(item["inputsHash"]) for item in selected}
1076
1084
  ),
@@ -1495,6 +1503,14 @@ def build_workflow_timing(
1495
1503
  started = started_dt.isoformat() if started_dt else None
1496
1504
  cutoff = cutoff_dt.isoformat() if cutoff_dt else cutoff_text
1497
1505
 
1506
+ def elapsed_ms(start: Any, end: Any) -> int:
1507
+ delta = end - start
1508
+ total_microseconds = (
1509
+ (delta.days * 86_400 + delta.seconds) * 1_000_000
1510
+ + delta.microseconds
1511
+ )
1512
+ return max(0, total_microseconds // 1_000)
1513
+
1498
1514
  def clipped_interval(
1499
1515
  start_value: Any,
1500
1516
  end_value: Any,
@@ -1527,7 +1543,7 @@ def build_workflow_timing(
1527
1543
  return sum(
1528
1544
  max(
1529
1545
  0,
1530
- int((interval_end - interval_start).total_seconds() * 1000),
1546
+ elapsed_ms(interval_start, interval_end),
1531
1547
  )
1532
1548
  for interval_start, interval_end in merged
1533
1549
  )
@@ -1628,7 +1644,7 @@ def build_workflow_timing(
1628
1644
  for segment_start, segment_end in zip(boundaries, boundaries[1:]):
1629
1645
  milliseconds = max(
1630
1646
  0,
1631
- int((segment_end - segment_start).total_seconds() * 1000),
1647
+ elapsed_ms(segment_start, segment_end),
1632
1648
  )
1633
1649
  if covered(segment_start, segment_end, active_intervals):
1634
1650
  categories["attributed"] += milliseconds
@@ -1640,7 +1656,7 @@ def build_workflow_timing(
1640
1656
  categories["unattributed"] += milliseconds
1641
1657
 
1642
1658
  workflow_wall = (
1643
- max(0, int((cutoff_dt - started_dt).total_seconds() * 1000))
1659
+ elapsed_ms(started_dt, cutoff_dt)
1644
1660
  if started_dt is not None and cutoff_dt is not None
1645
1661
  else 0
1646
1662
  )
@@ -1660,9 +1676,7 @@ def build_workflow_timing(
1660
1676
  "session": len(sessions) + 1,
1661
1677
  "startedAt": session_start.isoformat(),
1662
1678
  "endedAt": pause_start_dt.isoformat(),
1663
- "wallClockMs": int(
1664
- (pause_start_dt - session_start).total_seconds() * 1000
1665
- ),
1679
+ "wallClockMs": elapsed_ms(session_start, pause_start_dt),
1666
1680
  "terminalStatus": "PAUSED",
1667
1681
  }
1668
1682
  )
@@ -1677,9 +1691,7 @@ def build_workflow_timing(
1677
1691
  "session": len(sessions) + 1,
1678
1692
  "startedAt": session_start.isoformat(),
1679
1693
  "endedAt": cutoff_dt.isoformat(),
1680
- "wallClockMs": int(
1681
- (cutoff_dt - session_start).total_seconds() * 1000
1682
- ),
1694
+ "wallClockMs": elapsed_ms(session_start, cutoff_dt),
1683
1695
  "terminalStatus": "CUTOFF",
1684
1696
  }
1685
1697
  )
@@ -82,6 +82,63 @@ REQUIRED_COVERAGE = {
82
82
  _EMPTY_TREE = "4b825dc642cb6eb9a060e54bf8d69288fbee4904"
83
83
 
84
84
 
85
+ def resolve_verification_target(
86
+ verification: str,
87
+ project_root: Path | None,
88
+ ) -> dict[str, Any] | None:
89
+ """Resolve a built-in or project-declared profile-v3 verification target."""
90
+ if not isinstance(verification, str) or not re.fullmatch(
91
+ r"[A-Za-z][A-Za-z0-9_-]{0,63}",
92
+ verification,
93
+ ):
94
+ return None
95
+ if project_root is not None:
96
+ profile = harness_profile.load_profile(project_root)
97
+ graph = (
98
+ profile.get("verificationGraph")
99
+ if isinstance(profile, dict)
100
+ else None
101
+ )
102
+ targets = graph.get("targets") if isinstance(graph, dict) else None
103
+ target = targets.get(verification) if isinstance(targets, dict) else None
104
+ if isinstance(target, dict):
105
+ required = str(target.get("requiredCoverage") or "module")
106
+ if required not in COVERAGE_RANK:
107
+ return None
108
+ return {**target, "requiredCoverage": required}
109
+ if verification in VERIFICATIONS:
110
+ required_rank = REQUIRED_COVERAGE.get(verification, 1)
111
+ required = next(
112
+ (
113
+ name
114
+ for name, rank in COVERAGE_RANK.items()
115
+ if rank == required_rank
116
+ ),
117
+ "module",
118
+ )
119
+ return {
120
+ "commandKey": verification,
121
+ "dependsOn": [],
122
+ "requiredCoverage": required,
123
+ "candidate": verification == "unitTestFull",
124
+ "requiredCapabilities": [],
125
+ }
126
+ return None
127
+
128
+
129
+ def verification_target_identity(
130
+ verification: str,
131
+ target: dict[str, Any],
132
+ ) -> str:
133
+ payload = json.dumps(
134
+ {"verification": verification, "target": target},
135
+ ensure_ascii=False,
136
+ sort_keys=True,
137
+ separators=(",", ":"),
138
+ ).encode("utf-8")
139
+ return "sha256:" + hashlib.sha256(payload).hexdigest()
140
+
141
+
85
142
  def now_iso() -> str:
86
143
  return dt.datetime.now().astimezone().isoformat(timespec="milliseconds")
87
144
 
@@ -1075,6 +1132,8 @@ def decide_can_reuse(
1075
1132
  requested_profile_hash: str | None = None,
1076
1133
  requested_environment_hash: str | None = None,
1077
1134
  requested_db_schema_hash: str | None = None,
1135
+ requested_target_identity: str | None = None,
1136
+ required_coverage: str | None = None,
1078
1137
  project_root: Path | str | None = None,
1079
1138
  ) -> dict[str, Any]:
1080
1139
  ledger, ledger_path = load_ledger(change_dir)
@@ -1136,6 +1195,7 @@ def decide_can_reuse(
1136
1195
  scope = entry.get("scope")
1137
1196
  algorithm_version = entry.get("algorithmVersion")
1138
1197
  coverage = entry.get("coverage")
1198
+ stored_target_identity = entry.get("targetIdentity")
1139
1199
 
1140
1200
  if entry.get("reusable") is False or isinstance(entry.get("invalidation"), dict):
1141
1201
  return {
@@ -1198,7 +1258,11 @@ def decide_can_reuse(
1198
1258
  # Coverage lattice: recorded coverage rank must meet the verification's
1199
1259
  # required rank. Stops incremental evidence satisfying a module/full gate
1200
1260
  # (UT-015 / API-005).
1201
- required = REQUIRED_COVERAGE.get(verification, 1)
1261
+ required = (
1262
+ COVERAGE_RANK.get(required_coverage, 1)
1263
+ if required_coverage is not None
1264
+ else REQUIRED_COVERAGE.get(verification, 1)
1265
+ )
1202
1266
  if COVERAGE_RANK.get(str(coverage).strip(), -1) < required:
1203
1267
  return {
1204
1268
  "ok": True,
@@ -1211,6 +1275,31 @@ def decide_can_reuse(
1211
1275
  "stored_coverage": coverage,
1212
1276
  }
1213
1277
 
1278
+ if _nonempty_str(requested_target_identity):
1279
+ if not _nonempty_str(stored_target_identity):
1280
+ return {
1281
+ "ok": True,
1282
+ "reuse": False,
1283
+ "reason": "insufficient-evidence",
1284
+ "code": "MISSING_TARGET_IDENTITY",
1285
+ "verification": verification,
1286
+ "detail": "recorded evidence predates the current verification target identity",
1287
+ "ledger_path": str(ledger_path) if ledger_path else None,
1288
+ "requested_target_identity": requested_target_identity,
1289
+ }
1290
+ if str(stored_target_identity).strip() != str(requested_target_identity).strip():
1291
+ return {
1292
+ "ok": True,
1293
+ "reuse": False,
1294
+ "reason": "rerun",
1295
+ "code": "TARGET_IDENTITY_CHANGED",
1296
+ "verification": verification,
1297
+ "detail": "verification target definition changed",
1298
+ "ledger_path": str(ledger_path) if ledger_path else None,
1299
+ "stored_target_identity": stored_target_identity,
1300
+ "requested_target_identity": requested_target_identity,
1301
+ }
1302
+
1214
1303
  if requested_command is not None and str(requested_command).strip():
1215
1304
  if str(command).strip() != str(requested_command).strip():
1216
1305
  return {
@@ -1352,6 +1441,7 @@ def upsert_verification_graph_node(
1352
1441
  environment_hash: str | None = None,
1353
1442
  db_schema_hash: str | None = None,
1354
1443
  diff_hash: str | None = None,
1444
+ target_identity: str | None = None,
1355
1445
  ) -> dict[str, Any]:
1356
1446
  """Persist a verification graph node keyed by canonical identity (H-9)."""
1357
1447
  path = verification_graph_path(change_dir)
@@ -1376,6 +1466,7 @@ def upsert_verification_graph_node(
1376
1466
  "environmentHash": environment_hash,
1377
1467
  "dbSchemaHash": db_schema_hash,
1378
1468
  "diffHash": diff_hash,
1469
+ "targetIdentity": target_identity,
1379
1470
  }
1380
1471
  node = {
1381
1472
  "identity": identity,
@@ -1427,16 +1518,19 @@ def cmd_can_reuse(args: argparse.Namespace) -> int:
1427
1518
  as_json = bool(args.json)
1428
1519
  verbose = bool(getattr(args, "verbose", False))
1429
1520
  verification = args.verification
1430
- if verification not in VERIFICATIONS:
1431
- return emit_error(
1432
- f"unsupported verification: {verification}; expected one of {sorted(VERIFICATIONS)}",
1433
- as_json=as_json,
1434
- )
1435
1521
  change_dir = resolve_path(args.change_dir)
1436
1522
  try:
1437
1523
  files, project_root = input_files_from_args(args)
1438
1524
  except (OSError, ValueError) as exc:
1439
1525
  return emit_error(f"can-reuse failed: {exc}", as_json=as_json)
1526
+ target = resolve_verification_target(verification, project_root)
1527
+ if target is None:
1528
+ return emit_error(
1529
+ "unsupported verification: "
1530
+ f"{verification}; declare it in build-profile.json "
1531
+ "verificationGraph.targets",
1532
+ as_json=as_json,
1533
+ )
1440
1534
  profile_input = getattr(args, "profile_input", None)
1441
1535
  project_raw = getattr(args, "project", None)
1442
1536
  if profile_input:
@@ -1474,6 +1568,10 @@ def cmd_can_reuse(args: argparse.Namespace) -> int:
1474
1568
  requested_profile_hash=getattr(args, "profile_hash", None),
1475
1569
  requested_environment_hash=getattr(args, "environment_hash", None),
1476
1570
  requested_db_schema_hash=getattr(args, "db_schema_hash", None),
1571
+ requested_target_identity=verification_target_identity(
1572
+ verification, target
1573
+ ),
1574
+ required_coverage=str(target["requiredCoverage"]),
1477
1575
  project_root=project_root,
1478
1576
  )
1479
1577
  except (OSError, ValueError, json.JSONDecodeError) as exc:
@@ -1492,11 +1590,6 @@ def cmd_record(args: argparse.Namespace) -> int:
1492
1590
  verbose = bool(getattr(args, "verbose", False))
1493
1591
  change_dir = resolve_path(args.change_dir)
1494
1592
  verification = args.verification
1495
- if verification not in VERIFICATIONS:
1496
- return emit_error(
1497
- f"unsupported verification: {verification}; expected one of {sorted(VERIFICATIONS)}",
1498
- as_json=as_json,
1499
- )
1500
1593
  contract_v2 = _contract_is_v2(change_dir)
1501
1594
  if not contract_v2 and any(
1502
1595
  _nonempty_str(getattr(args, field, None))
@@ -1510,6 +1603,14 @@ def cmd_record(args: argparse.Namespace) -> int:
1510
1603
  files, project_root = input_files_from_args(args)
1511
1604
  except (OSError, ValueError) as exc:
1512
1605
  return emit_error(f"record failed: {exc}", as_json=as_json)
1606
+ target = resolve_verification_target(verification, project_root)
1607
+ if target is None:
1608
+ return emit_error(
1609
+ "unsupported verification: "
1610
+ f"{verification}; declare it in build-profile.json "
1611
+ "verificationGraph.targets",
1612
+ as_json=as_json,
1613
+ )
1513
1614
  profile_input = getattr(args, "profile_input", None)
1514
1615
  project_raw = getattr(args, "project", None)
1515
1616
  if profile_input:
@@ -1610,6 +1711,9 @@ def cmd_record(args: argparse.Namespace) -> int:
1610
1711
  # toolchain/profile/environment hashes. v1 entries missing these are
1611
1712
  # conservatively invalidated by can-reuse (COM-002).
1612
1713
  entry["algorithmVersion"] = LEDGER_VERSION
1714
+ entry["targetIdentity"] = verification_target_identity(
1715
+ verification, target
1716
+ )
1613
1717
  coverage = getattr(args, "coverage", None)
1614
1718
  if not (_nonempty_str(coverage) and str(coverage).strip() in COVERAGE_RANK):
1615
1719
  coverage = derive_coverage(verification, args.scope)
@@ -1757,6 +1861,11 @@ def cmd_record(args: argparse.Namespace) -> int:
1757
1861
  if _nonempty_str(ledger.get("diffHash"))
1758
1862
  else None
1759
1863
  ),
1864
+ target_identity=(
1865
+ str(entry.get("targetIdentity")).strip()
1866
+ if _nonempty_str(entry.get("targetIdentity"))
1867
+ else None
1868
+ ),
1760
1869
  )
1761
1870
  except (OSError, ValueError, TypeError):
1762
1871
  # Graph is advisory persistence; ledger write already succeeded.
@@ -1832,7 +1941,7 @@ def build_parser() -> argparse.ArgumentParser:
1832
1941
  p_reuse.add_argument(
1833
1942
  "--verification",
1834
1943
  required=True,
1835
- choices=sorted(VERIFICATIONS),
1944
+ help="built-in or build-profile v3 verificationGraph target",
1836
1945
  )
1837
1946
  p_reuse.add_argument(
1838
1947
  "--files",
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env python3
2
2
  """Harness preflight: build-profile detect/check + quirk recording + agent precheck.
3
3
 
4
- 变更簇 4:detect/check 委托 ``harness_profile``(profile v2 公共内核),不再保留
4
+ 变更簇 4:detect/check 委托 ``harness_profile``(Build Profile v3 公共内核),不再保留
5
5
  v1 重复探测逻辑与 ``buildCommands`` 扁平字符串。record-quirk 适配 v2 ``commands``
6
6
  结构:``fix-command`` 写 ``source=user`` override(spec §3.1 显式保留),跨 detect
7
7
  保留;``skip-not-block`` 仍写 ``knownPreexistingErrors``。check-agents 与 pitfalls
@@ -51,7 +51,7 @@ def emit_json(payload: dict[str, Any], *, ok: bool = True) -> int:
51
51
 
52
52
 
53
53
  # ---------------------------------------------------------------------------
54
- # detect / check — 委托 harness_profile(profile v2 公共内核)
54
+ # detect / check — 委托 harness_profile(Build Profile v3 公共内核)
55
55
  # ---------------------------------------------------------------------------
56
56
 
57
57
  def cmd_detect(project: Path) -> dict[str, Any]: