harness-everything 0.33.0 → 0.35.0

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 (297) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +48 -0
  4. package/ci/fixtures/doc-lifecycle/prd-revisions/config.json +9 -0
  5. package/ci/fixtures/doc-lifecycle/prd-revisions/packaged/config.json +20 -0
  6. package/ci/fixtures/doc-lifecycle/prd-revisions/packaged/expected.json +12 -0
  7. package/ci/fixtures/doc-lifecycle/prd-revisions/packaged/invalid-fork/docs/prd/graph/r001.md +20 -0
  8. package/ci/fixtures/doc-lifecycle/prd-revisions/packaged/invalid-fork/docs/prd/graph/r002.md +26 -0
  9. package/ci/fixtures/doc-lifecycle/prd-revisions/packaged/invalid-fork/docs/prd/graph/r003.md +26 -0
  10. package/ci/fixtures/doc-lifecycle/prd-revisions/packaged/valid/docs/prd/graph/r001.md +20 -0
  11. package/ci/fixtures/doc-lifecycle/prd-revisions/packaged/valid/docs/prd/graph/r002.md +26 -0
  12. package/ci/fixtures/doc-lifecycle/prd-revisions/ten-reqs/r001.body.md +39 -0
  13. package/ci/fixtures/doc-lifecycle/prd-revisions/ten-reqs/r002-dropped.body.md +35 -0
  14. package/ci/fixtures/doc-lifecycle/prd-revisions/ten-reqs/r002-incomplete.body.md +37 -0
  15. package/ci/fixtures/doc-lifecycle/prd-revisions/ten-reqs/r002-one-replaced.body.md +39 -0
  16. package/ci/fixtures/doc-lifecycle/prd-revisions/ten-reqs/r002-overlap.body.md +39 -0
  17. package/ci/fixtures/doc-lifecycle/scenarios/active-to-active/config.json +1 -0
  18. package/ci/fixtures/doc-lifecycle/scenarios/active-to-active/docs/adr/GRPH-001.md +8 -0
  19. package/ci/fixtures/doc-lifecycle/scenarios/active-to-active/docs/adr/GRPH-002.md +9 -0
  20. package/ci/fixtures/doc-lifecycle/scenarios/active-to-active/expected.json +1 -0
  21. package/ci/fixtures/doc-lifecycle/scenarios/active-to-active-mismatch/config.json +1 -0
  22. package/ci/fixtures/doc-lifecycle/scenarios/active-to-active-mismatch/docs/adr/GRPH-001.md +8 -0
  23. package/ci/fixtures/doc-lifecycle/scenarios/active-to-active-mismatch/docs/adr/GRPH-002.md +9 -0
  24. package/ci/fixtures/doc-lifecycle/scenarios/active-to-active-mismatch/expected.json +1 -0
  25. package/ci/fixtures/doc-lifecycle/scenarios/active-to-legacy/config.json +1 -0
  26. package/ci/fixtures/doc-lifecycle/scenarios/active-to-legacy/docs/adr/GRPH-010.md +9 -0
  27. package/ci/fixtures/doc-lifecycle/scenarios/active-to-legacy/docs/adr/legacy/GRPH-007.md +8 -0
  28. package/ci/fixtures/doc-lifecycle/scenarios/active-to-legacy/expected.json +1 -0
  29. package/ci/fixtures/doc-lifecycle/scenarios/alias-rename/config.json +1 -0
  30. package/ci/fixtures/doc-lifecycle/scenarios/alias-rename/docs/adr/GRAPH-007.md +9 -0
  31. package/ci/fixtures/doc-lifecycle/scenarios/alias-rename/docs/adr/legacy/GRPH-007.md +8 -0
  32. package/ci/fixtures/doc-lifecycle/scenarios/alias-rename/expected.json +1 -0
  33. package/ci/fixtures/doc-lifecycle/scenarios/duplicate-legacy/config.json +1 -0
  34. package/ci/fixtures/doc-lifecycle/scenarios/duplicate-legacy/docs/adr/legacy/v1/GRPH-007.md +8 -0
  35. package/ci/fixtures/doc-lifecycle/scenarios/duplicate-legacy/docs/adr/legacy/v2/GRPH-007.md +8 -0
  36. package/ci/fixtures/doc-lifecycle/scenarios/duplicate-legacy/expected.json +1 -0
  37. package/ci/fixtures/doc-lifecycle/scenarios/invalid-refs/config.json +1 -0
  38. package/ci/fixtures/doc-lifecycle/scenarios/invalid-refs/docs/adr/GRPH-003.md +9 -0
  39. package/ci/fixtures/doc-lifecycle/scenarios/invalid-refs/docs/adr/GRPH-004.md +9 -0
  40. package/ci/fixtures/doc-lifecycle/scenarios/invalid-refs/docs/adr/GRPH-005.md +9 -0
  41. package/ci/fixtures/doc-lifecycle/scenarios/invalid-refs/docs/adr/GRPH-006.md +9 -0
  42. package/ci/fixtures/doc-lifecycle/scenarios/invalid-refs/docs/adr/GRPH-007.md +9 -0
  43. package/ci/fixtures/doc-lifecycle/scenarios/invalid-refs/expected.json +1 -0
  44. package/ci/fixtures/doc-lifecycle/scenarios/pending-edge/config.json +1 -0
  45. package/ci/fixtures/doc-lifecycle/scenarios/pending-edge/docs/adr/GRPH-020.md +8 -0
  46. package/ci/fixtures/doc-lifecycle/scenarios/pending-edge/docs/adr/GRPH-021.md +9 -0
  47. package/ci/fixtures/doc-lifecycle/scenarios/pending-edge/expected.json +1 -0
  48. package/ci/fixtures/doc-lifecycle/scenarios/recorded-conflict/config.json +1 -0
  49. package/ci/fixtures/doc-lifecycle/scenarios/recorded-conflict/docs/adr/GRPH-003.md +8 -0
  50. package/ci/fixtures/doc-lifecycle/scenarios/recorded-conflict/docs/prd/graph.md +10 -0
  51. package/ci/fixtures/doc-lifecycle/scenarios/recorded-conflict/expected.json +1 -0
  52. package/ci/fixtures/doc-obligations/derive/acceptance-only/base/docs/prd/graph.md +22 -0
  53. package/ci/fixtures/doc-obligations/derive/acceptance-only/candidate/docs/prd/graph.md +22 -0
  54. package/ci/fixtures/doc-obligations/derive/alias-only/base/docs/prd/graph.md +24 -0
  55. package/ci/fixtures/doc-obligations/derive/alias-only/candidate/docs/prd/graph.md +24 -0
  56. package/ci/fixtures/doc-obligations/derive/assertion-no-revision/base/ci/mechanism-lint.test.js +2 -0
  57. package/ci/fixtures/doc-obligations/derive/assertion-no-revision/base/docs/prd/graph.md +20 -0
  58. package/ci/fixtures/doc-obligations/derive/assertion-no-revision/candidate/ci/mechanism-lint.test.js +2 -0
  59. package/ci/fixtures/doc-obligations/derive/assertion-no-revision/candidate/docs/prd/graph.md +20 -0
  60. package/ci/fixtures/doc-obligations/derive/assertion-no-revision-attested/base/ci/mechanism-lint.test.js +2 -0
  61. package/ci/fixtures/doc-obligations/derive/assertion-no-revision-attested/base/docs/prd/graph.md +20 -0
  62. package/ci/fixtures/doc-obligations/derive/assertion-no-revision-attested/candidate/ci/mechanism-lint.test.js +2 -0
  63. package/ci/fixtures/doc-obligations/derive/assertion-no-revision-attested/candidate/docs/prd/graph.md +20 -0
  64. package/ci/fixtures/doc-obligations/derive/assertion-no-revision-attested/inventory.json +7 -0
  65. package/ci/fixtures/doc-obligations/derive/attribution-impossible/base/ci/mechanism-open.test.js +3 -0
  66. package/ci/fixtures/doc-obligations/derive/attribution-impossible/candidate/ci/mechanism-open.test.js.fixture +2 -0
  67. package/ci/fixtures/doc-obligations/derive/clause-context-typo/base/docs/prd/graph.md +24 -0
  68. package/ci/fixtures/doc-obligations/derive/clause-context-typo/candidate/docs/prd/graph.md +24 -0
  69. package/ci/fixtures/doc-obligations/derive/code-only/base/src/lint.js +1 -0
  70. package/ci/fixtures/doc-obligations/derive/code-only/candidate/src/lint.js +1 -0
  71. package/ci/fixtures/doc-obligations/derive/comment-only-test/base/ci/mechanism-lint.test.js +2 -0
  72. package/ci/fixtures/doc-obligations/derive/comment-only-test/candidate/ci/mechanism-lint.test.js +2 -0
  73. package/ci/fixtures/doc-obligations/derive/computed-member-assert/base/ci/mechanism-c1.test.js +2 -0
  74. package/ci/fixtures/doc-obligations/derive/computed-member-assert/candidate/ci/mechanism-c1.test.js +2 -0
  75. package/ci/fixtures/doc-obligations/derive/computed-member-variable/base/ci/mechanism-c1.test.js +2 -0
  76. package/ci/fixtures/doc-obligations/derive/computed-member-variable/candidate/ci/mechanism-c1.test.js +2 -0
  77. package/ci/fixtures/doc-obligations/derive/config.json +11 -0
  78. package/ci/fixtures/doc-obligations/derive/decision-fence-backtick/base/docs/adr/GRPH-010.md +20 -0
  79. package/ci/fixtures/doc-obligations/derive/decision-fence-backtick/candidate/docs/adr/GRPH-010.md +20 -0
  80. package/ci/fixtures/doc-obligations/derive/decision-fence-tilde/base/docs/adr/GRPH-010.md +20 -0
  81. package/ci/fixtures/doc-obligations/derive/decision-fence-tilde/candidate/docs/adr/GRPH-010.md +20 -0
  82. package/ci/fixtures/doc-obligations/derive/deep-strict-equal/base/ci/mechanism-s1.test.js +2 -0
  83. package/ci/fixtures/doc-obligations/derive/deep-strict-equal/candidate/ci/mechanism-s1.test.js +2 -0
  84. package/ci/fixtures/doc-obligations/derive/deleted-adr/base/docs/adr/GRPH-008.md +14 -0
  85. package/ci/fixtures/doc-obligations/derive/deleted-prd/base/ci/mechanism-lint.test.js +2 -0
  86. package/ci/fixtures/doc-obligations/derive/deleted-prd/base/docs/prd/graph.md +20 -0
  87. package/ci/fixtures/doc-obligations/derive/deleted-prd/candidate/ci/mechanism-lint.test.js +2 -0
  88. package/ci/fixtures/doc-obligations/derive/derived-rename/base/docs/index.md +3 -0
  89. package/ci/fixtures/doc-obligations/derive/derived-rename/base/src/model/a.js +1 -0
  90. package/ci/fixtures/doc-obligations/derive/derived-rename/candidate/docs/index.md +3 -0
  91. package/ci/fixtures/doc-obligations/derive/derived-rename/candidate/src/model/b.js +1 -0
  92. package/ci/fixtures/doc-obligations/derive/derived-rename/config.json +7 -0
  93. package/ci/fixtures/doc-obligations/derive/derived-two-outputs/base/docs/index.md +3 -0
  94. package/ci/fixtures/doc-obligations/derive/derived-two-outputs/base/docs/model-summary.md +3 -0
  95. package/ci/fixtures/doc-obligations/derive/derived-two-outputs/base/src/model.js +1 -0
  96. package/ci/fixtures/doc-obligations/derive/derived-two-outputs/candidate/docs/index.md +3 -0
  97. package/ci/fixtures/doc-obligations/derive/derived-two-outputs/candidate/docs/model-summary.md +3 -0
  98. package/ci/fixtures/doc-obligations/derive/derived-two-outputs/candidate/src/model.js +1 -0
  99. package/ci/fixtures/doc-obligations/derive/derived-two-outputs/config.json +10 -0
  100. package/ci/fixtures/doc-obligations/derive/distinct-logical-prds/base/ci/mechanism-lint.test.js +2 -0
  101. package/ci/fixtures/doc-obligations/derive/distinct-logical-prds/base/docs/prd/graph-one/r001.md +19 -0
  102. package/ci/fixtures/doc-obligations/derive/distinct-logical-prds/base/docs/prd/graph-two/r001.md +19 -0
  103. package/ci/fixtures/doc-obligations/derive/distinct-logical-prds/candidate/ci/mechanism-lint.test.js +2 -0
  104. package/ci/fixtures/doc-obligations/derive/distinct-logical-prds/candidate/docs/prd/graph-one/r001.md +19 -0
  105. package/ci/fixtures/doc-obligations/derive/distinct-logical-prds/candidate/docs/prd/graph-two/r001.md +19 -0
  106. package/ci/fixtures/doc-obligations/derive/docs-only/base/docs/reference/guide.md +3 -0
  107. package/ci/fixtures/doc-obligations/derive/docs-only/candidate/docs/reference/guide.md +3 -0
  108. package/ci/fixtures/doc-obligations/derive/dup-req-id/base/ci/mechanism-lint.test.js +2 -0
  109. package/ci/fixtures/doc-obligations/derive/dup-req-id/base/docs/prd/graph-a.md +20 -0
  110. package/ci/fixtures/doc-obligations/derive/dup-req-id/base/docs/prd/graph-b.md +20 -0
  111. package/ci/fixtures/doc-obligations/derive/dup-req-id/candidate/ci/mechanism-lint.test.js +2 -0
  112. package/ci/fixtures/doc-obligations/derive/dup-req-id/candidate/docs/prd/graph-a.md +20 -0
  113. package/ci/fixtures/doc-obligations/derive/dup-req-id/candidate/docs/prd/graph-b.md +20 -0
  114. package/ci/fixtures/doc-obligations/derive/expect-computed-literal/base/ci/mechanism-e.test.js +2 -0
  115. package/ci/fixtures/doc-obligations/derive/expect-computed-literal/candidate/ci/mechanism-e.test.js +2 -0
  116. package/ci/fixtures/doc-obligations/derive/expect-computed-matcher/base/ci/mechanism-e.test.js +2 -0
  117. package/ci/fixtures/doc-obligations/derive/expect-computed-matcher/candidate/ci/mechanism-e.test.js +2 -0
  118. package/ci/fixtures/doc-obligations/derive/expect-not-to-equal/base/ci/mechanism-s1.test.js +2 -0
  119. package/ci/fixtures/doc-obligations/derive/expect-not-to-equal/candidate/ci/mechanism-s1.test.js +2 -0
  120. package/ci/fixtures/doc-obligations/derive/formatting-only-test/base/ci/mechanism-lint.test.js +2 -0
  121. package/ci/fixtures/doc-obligations/derive/formatting-only-test/candidate/ci/mechanism-lint.test.js +2 -0
  122. package/ci/fixtures/doc-obligations/derive/helper-delegated-test/base/docs/prd/graph.md +18 -0
  123. package/ci/fixtures/doc-obligations/derive/helper-delegated-test/candidate/ci/mechanism-helper.test.js +1 -0
  124. package/ci/fixtures/doc-obligations/derive/helper-delegated-test/candidate/docs/prd/graph.md +18 -0
  125. package/ci/fixtures/doc-obligations/derive/historical-only-req/base/ci/mechanism-lint.test.js +2 -0
  126. package/ci/fixtures/doc-obligations/derive/historical-only-req/base/docs/prd/graph/r001.md +25 -0
  127. package/ci/fixtures/doc-obligations/derive/historical-only-req/candidate/ci/mechanism-lint.test.js +2 -0
  128. package/ci/fixtures/doc-obligations/derive/historical-only-req/candidate/docs/prd/graph/r001.md +25 -0
  129. package/ci/fixtures/doc-obligations/derive/historical-only-req/candidate/docs/prd/graph/r002.md +23 -0
  130. package/ci/fixtures/doc-obligations/derive/in-place-current/base/ci/mechanism-inplace.test.js +2 -0
  131. package/ci/fixtures/doc-obligations/derive/in-place-current/base/docs/prd/graph/r001.md +20 -0
  132. package/ci/fixtures/doc-obligations/derive/in-place-current/base/docs/prd/graph/r002.md +26 -0
  133. package/ci/fixtures/doc-obligations/derive/in-place-current/candidate/ci/mechanism-inplace.test.js +2 -0
  134. package/ci/fixtures/doc-obligations/derive/in-place-current/candidate/docs/prd/graph/r001.md +20 -0
  135. package/ci/fixtures/doc-obligations/derive/in-place-current/candidate/docs/prd/graph/r002.md +28 -0
  136. package/ci/fixtures/doc-obligations/derive/in-place-historical/base/ci/mechanism-inplace.test.js +2 -0
  137. package/ci/fixtures/doc-obligations/derive/in-place-historical/base/docs/prd/graph/r001.md +20 -0
  138. package/ci/fixtures/doc-obligations/derive/in-place-historical/base/docs/prd/graph/r002.md +26 -0
  139. package/ci/fixtures/doc-obligations/derive/in-place-historical/candidate/ci/mechanism-inplace.test.js +2 -0
  140. package/ci/fixtures/doc-obligations/derive/in-place-historical/candidate/docs/prd/graph/r001.md +22 -0
  141. package/ci/fixtures/doc-obligations/derive/in-place-historical/candidate/docs/prd/graph/r002.md +26 -0
  142. package/ci/fixtures/doc-obligations/derive/invalid-clause/base/docs/prd/graph.md +24 -0
  143. package/ci/fixtures/doc-obligations/derive/invalid-clause/candidate/docs/prd/graph.md +24 -0
  144. package/ci/fixtures/doc-obligations/derive/legacy-untagged-fallback/base/ci/mechanism-legacy.test.js +6 -0
  145. package/ci/fixtures/doc-obligations/derive/legacy-untagged-fallback/candidate/ci/mechanism-legacy.test.js +6 -0
  146. package/ci/fixtures/doc-obligations/derive/literal-whitespace-assert/base/ci/mechanism-lint.test.js +2 -0
  147. package/ci/fixtures/doc-obligations/derive/literal-whitespace-assert/candidate/ci/mechanism-lint.test.js +2 -0
  148. package/ci/fixtures/doc-obligations/derive/malformed-citation-attribution/base/ci/mechanism-bad.test.js +6 -0
  149. package/ci/fixtures/doc-obligations/derive/malformed-citation-attribution/candidate/ci/mechanism-bad.test.js.fixture +6 -0
  150. package/ci/fixtures/doc-obligations/derive/malformed-prd/base/ci/mechanism-lint.test.js +2 -0
  151. package/ci/fixtures/doc-obligations/derive/malformed-prd/base/docs/prd/graph.md +20 -0
  152. package/ci/fixtures/doc-obligations/derive/malformed-prd/candidate/ci/mechanism-lint.test.js +2 -0
  153. package/ci/fixtures/doc-obligations/derive/malformed-prd/candidate/docs/prd/graph.md +11 -0
  154. package/ci/fixtures/doc-obligations/derive/missing-approval/base/docs/prd/graph.md +20 -0
  155. package/ci/fixtures/doc-obligations/derive/missing-approval/candidate/docs/prd/graph.md +20 -0
  156. package/ci/fixtures/doc-obligations/derive/mixed-attribution/base/ci/mechanism-mixed.test.js +6 -0
  157. package/ci/fixtures/doc-obligations/derive/mixed-attribution/base/docs/prd/graph.md +24 -0
  158. package/ci/fixtures/doc-obligations/derive/mixed-attribution/candidate/ci/mechanism-mixed.test.js.fixture +5 -0
  159. package/ci/fixtures/doc-obligations/derive/mixed-attribution/candidate/docs/prd/graph.md +24 -0
  160. package/ci/fixtures/doc-obligations/derive/mixed-docs-code/base/ci/mechanism-lint.test.js +2 -0
  161. package/ci/fixtures/doc-obligations/derive/mixed-docs-code/base/docs/reference/guide.md +3 -0
  162. package/ci/fixtures/doc-obligations/derive/mixed-docs-code/base/src/lint.js +1 -0
  163. package/ci/fixtures/doc-obligations/derive/mixed-docs-code/candidate/ci/mechanism-lint.test.js +2 -0
  164. package/ci/fixtures/doc-obligations/derive/mixed-docs-code/candidate/docs/reference/guide.md +3 -0
  165. package/ci/fixtures/doc-obligations/derive/mixed-docs-code/candidate/src/lint.js +1 -0
  166. package/ci/fixtures/doc-obligations/derive/multiline-comment-only/base/ci/mechanism-multi.test.js +5 -0
  167. package/ci/fixtures/doc-obligations/derive/multiline-comment-only/candidate/ci/mechanism-multi.test.js +5 -0
  168. package/ci/fixtures/doc-obligations/derive/multiline-expect/base/ci/mechanism-multi.test.js +6 -0
  169. package/ci/fixtures/doc-obligations/derive/multiline-expect/candidate/ci/mechanism-multi.test.js +6 -0
  170. package/ci/fixtures/doc-obligations/derive/multiline-formatting-only/base/ci/mechanism-multi.test.js +5 -0
  171. package/ci/fixtures/doc-obligations/derive/multiline-formatting-only/candidate/ci/mechanism-multi.test.js +5 -0
  172. package/ci/fixtures/doc-obligations/derive/multiline-numeric-prd/base/ci/mechanism-multi.test.js +5 -0
  173. package/ci/fixtures/doc-obligations/derive/multiline-numeric-prd/base/docs/prd/graph.md +18 -0
  174. package/ci/fixtures/doc-obligations/derive/multiline-numeric-prd/candidate/ci/mechanism-multi.test.js +5 -0
  175. package/ci/fixtures/doc-obligations/derive/multiline-numeric-prd/candidate/docs/prd/graph.md +18 -0
  176. package/ci/fixtures/doc-obligations/derive/multiline-strictequal/base/ci/mechanism-multi.test.js +5 -0
  177. package/ci/fixtures/doc-obligations/derive/multiline-strictequal/candidate/ci/mechanism-multi.test.js +5 -0
  178. package/ci/fixtures/doc-obligations/derive/new-prd/candidate/docs/prd/graph-new.md +20 -0
  179. package/ci/fixtures/doc-obligations/derive/new-reference/candidate/docs/reference/new-note.md +3 -0
  180. package/ci/fixtures/doc-obligations/derive/new-req-test/candidate/ci/mechanism-new.test.js +2 -0
  181. package/ci/fixtures/doc-obligations/derive/orphan-req-test/base/ci/mechanism-lint.test.js +2 -0
  182. package/ci/fixtures/doc-obligations/derive/orphan-req-test/base/docs/prd/graph.md +20 -0
  183. package/ci/fixtures/doc-obligations/derive/orphan-req-test/candidate/ci/mechanism-lint.test.js +2 -0
  184. package/ci/fixtures/doc-obligations/derive/orphan-req-test/candidate/docs/prd/graph.md +20 -0
  185. package/ci/fixtures/doc-obligations/derive/partial-revision/base/ci/mechanism-partial.test.js +5 -0
  186. package/ci/fixtures/doc-obligations/derive/partial-revision/base/docs/prd/graph/r001.md +24 -0
  187. package/ci/fixtures/doc-obligations/derive/partial-revision/candidate/ci/mechanism-partial.test.js +5 -0
  188. package/ci/fixtures/doc-obligations/derive/partial-revision/candidate/docs/prd/graph/r001.md +24 -0
  189. package/ci/fixtures/doc-obligations/derive/partial-revision/candidate/docs/prd/graph/r002.md +26 -0
  190. package/ci/fixtures/doc-obligations/derive/regex-literal-whitespace/base/ci/mechanism-lint.test.js +2 -0
  191. package/ci/fixtures/doc-obligations/derive/regex-literal-whitespace/candidate/ci/mechanism-lint.test.js +2 -0
  192. package/ci/fixtures/doc-obligations/derive/rename/base/docs/reference/old-guide.md +3 -0
  193. package/ci/fixtures/doc-obligations/derive/rename/candidate/docs/reference/guide.md +3 -0
  194. package/ci/fixtures/doc-obligations/derive/replacement-target/base/docs/prd/graph.md +24 -0
  195. package/ci/fixtures/doc-obligations/derive/replacement-target/candidate/docs/prd/graph.md +24 -0
  196. package/ci/fixtures/doc-obligations/derive/req-context-typo/base/docs/prd/graph.md +26 -0
  197. package/ci/fixtures/doc-obligations/derive/req-context-typo/candidate/docs/prd/graph.md +26 -0
  198. package/ci/fixtures/doc-obligations/derive/req-revision/base/ci/mechanism-lint.test.js +2 -0
  199. package/ci/fixtures/doc-obligations/derive/req-revision/base/docs/prd/graph/r001.md +20 -0
  200. package/ci/fixtures/doc-obligations/derive/req-revision/candidate/ci/mechanism-lint.test.js +2 -0
  201. package/ci/fixtures/doc-obligations/derive/req-revision/candidate/docs/prd/graph/r001.md +20 -0
  202. package/ci/fixtures/doc-obligations/derive/req-revision/candidate/docs/prd/graph/r002.md +28 -0
  203. package/ci/fixtures/doc-obligations/derive/req-revision-in-place/base/docs/prd/graph/r001.md +20 -0
  204. package/ci/fixtures/doc-obligations/derive/req-revision-in-place/candidate/docs/prd/graph/r001.md +22 -0
  205. package/ci/fixtures/doc-obligations/derive/retained-revision-chain/base/ci/mechanism-lint.test.js +2 -0
  206. package/ci/fixtures/doc-obligations/derive/retained-revision-chain/base/docs/prd/graph/r001.md +20 -0
  207. package/ci/fixtures/doc-obligations/derive/retained-revision-chain/base/docs/prd/graph/r002.md +26 -0
  208. package/ci/fixtures/doc-obligations/derive/retained-revision-chain/candidate/ci/mechanism-lint.test.js +2 -0
  209. package/ci/fixtures/doc-obligations/derive/retained-revision-chain/candidate/docs/prd/graph/r001.md +20 -0
  210. package/ci/fixtures/doc-obligations/derive/retained-revision-chain/candidate/docs/prd/graph/r002.md +26 -0
  211. package/ci/fixtures/doc-obligations/derive/string-literal-tag/base/ci/mechanism-str.test.js +2 -0
  212. package/ci/fixtures/doc-obligations/derive/string-literal-tag/candidate/ci/mechanism-str.test.js +2 -0
  213. package/ci/fixtures/doc-obligations/derive/tag-inside-test-call/base/ci/mechanism-inside.test.js +5 -0
  214. package/ci/fixtures/doc-obligations/derive/tag-inside-test-call/candidate/ci/mechanism-inside.test.js +5 -0
  215. package/ci/fixtures/doc-obligations/derive/tagged-test/base/ci/mechanism-lint.test.js +2 -0
  216. package/ci/fixtures/doc-obligations/derive/tagged-test/candidate/ci/mechanism-lint.test.js +2 -0
  217. package/ci/fixtures/doc-obligations/derive/tagged-test-prd/base/ci/mechanism-lint.test.js +2 -0
  218. package/ci/fixtures/doc-obligations/derive/tagged-test-prd/base/docs/prd/graph.md +20 -0
  219. package/ci/fixtures/doc-obligations/derive/tagged-test-prd/candidate/ci/mechanism-lint.test.js +2 -0
  220. package/ci/fixtures/doc-obligations/derive/tagged-test-prd/candidate/docs/prd/graph.md +20 -0
  221. package/ci/fixtures/doc-obligations/derive/template-literal-tag/base/ci/mechanism-tpl.test.js +4 -0
  222. package/ci/fixtures/doc-obligations/derive/template-literal-tag/base/docs/prd/graph.md +18 -0
  223. package/ci/fixtures/doc-obligations/derive/template-literal-tag/candidate/ci/mechanism-tpl.test.js +4 -0
  224. package/ci/fixtures/doc-obligations/derive/template-literal-tag/candidate/docs/prd/graph.md +18 -0
  225. package/ci/fixtures/doc-obligations/derive/template-literal-whitespace/base/ci/mechanism-lint.test.js +2 -0
  226. package/ci/fixtures/doc-obligations/derive/template-literal-whitespace/candidate/ci/mechanism-lint.test.js +2 -0
  227. package/ci/fixtures/doc-obligations/derive/title-only-rename/base/ci/mechanism-title.test.js +3 -0
  228. package/ci/fixtures/doc-obligations/derive/title-only-rename/candidate/ci/mechanism-title.test.js +3 -0
  229. package/ci/fixtures/doc-obligations/derive/title-only-unrelated-computed/base/ci/mechanism-u.test.js +4 -0
  230. package/ci/fixtures/doc-obligations/derive/title-only-unrelated-computed/candidate/ci/mechanism-u.test.js +4 -0
  231. package/ci/fixtures/doc-obligations/derive/top-level-no-file-tag/base/ci/mechanism-top.test.js +4 -0
  232. package/ci/fixtures/doc-obligations/derive/top-level-no-file-tag/candidate/ci/mechanism-top.test.js +4 -0
  233. package/ci/fixtures/doc-obligations/derive/trailing-comment-test/base/ci/mechanism-lint.test.js +2 -0
  234. package/ci/fixtures/doc-obligations/derive/trailing-comment-test/base/docs/prd/graph.md +20 -0
  235. package/ci/fixtures/doc-obligations/derive/trailing-comment-test/candidate/ci/mechanism-lint.test.js +2 -0
  236. package/ci/fixtures/doc-obligations/derive/trailing-comment-test/candidate/docs/prd/graph.md +20 -0
  237. package/ci/fixtures/doc-obligations/derive/two-tests-one-file/base/ci/mechanism-two.test.js +6 -0
  238. package/ci/fixtures/doc-obligations/derive/two-tests-one-file/base/docs/prd/graph.md +24 -0
  239. package/ci/fixtures/doc-obligations/derive/two-tests-one-file/candidate/ci/mechanism-two.test.js +6 -0
  240. package/ci/fixtures/doc-obligations/derive/two-tests-one-file/candidate/docs/prd/graph.md +24 -0
  241. package/ci/fixtures/doc-obligations/derive/typo-context/base/docs/adr/GRPH-007.md +14 -0
  242. package/ci/fixtures/doc-obligations/derive/typo-context/candidate/docs/adr/GRPH-007.md +14 -0
  243. package/ci/fixtures/doc-obligations/derive/typo-decision/base/docs/adr/GRPH-007.md +14 -0
  244. package/ci/fixtures/doc-obligations/derive/typo-decision/candidate/docs/adr/GRPH-007.md +14 -0
  245. package/ci/fixtures/doc-obligations/derive/unbalanced-statement/base/ci/mechanism-multi.test.js +5 -0
  246. package/ci/fixtures/doc-obligations/derive/unbalanced-statement/candidate/ci/mechanism-multi.test.js.fixture +4 -0
  247. package/ci/fixtures/doc-obligations/derive/unknown-adr/base/docs/adr/GRPH-009.md +14 -0
  248. package/ci/fixtures/doc-obligations/derive/unknown-adr/candidate/docs/adr/GRPH-009.md +12 -0
  249. package/ci/fixtures/doc-obligations/derive/unsupported-assertion-syntax/base/ci/mechanism-s1.test.js +2 -0
  250. package/ci/fixtures/doc-obligations/derive/unsupported-assertion-syntax/candidate/ci/mechanism-s1.test.js +2 -0
  251. package/ci/fixtures/doc-obligations/derive/whitespace-only-test/base/ci/mechanism-lint.test.js +2 -0
  252. package/ci/fixtures/doc-obligations/derive/whitespace-only-test/candidate/ci/mechanism-lint.test.js +2 -0
  253. package/ci/fixtures/doc-obligations/evidence/base/docs/adr/GRPH-007.md +15 -0
  254. package/ci/fixtures/doc-obligations/evidence/base/docs/prd/PRD-GRPH-001.md +25 -0
  255. package/ci/fixtures/doc-obligations/evidence/candidate/docs/adr/GRPH-007.md +15 -0
  256. package/ci/fixtures/doc-obligations/evidence/candidate/docs/prd/PRD-GRPH-001.md +25 -0
  257. package/ci/mechanism-2aw-project-docs-config.test.js +759 -0
  258. package/ci/mechanism-2ax-doc-lifecycle.test.js +1061 -0
  259. package/ci/mechanism-2ay-prd-revisions.test.js +2891 -0
  260. package/ci/mechanism-325-3a-cross.test.js +795 -0
  261. package/ci/mechanism-325-3a-doc-evidence.test.js +1172 -0
  262. package/ci/mechanism-325-3a-doc-obligations.test.js +742 -0
  263. package/ci/mechanism-pr334-haiku.test.js +298 -0
  264. package/ci/mechanism-pr334-luna.test.js +192 -0
  265. package/ci/mechanism-pr334-opencode.test.js +313 -0
  266. package/ci/mechanism-pr334-r2-boundary.test.js +239 -0
  267. package/ci/mechanism-pr334-r2-resolution.test.js +243 -0
  268. package/ci/mechanism-pr334-r2-scope.test.js +127 -0
  269. package/ci/mechanism-pr334-r3-boundary.test.js +356 -0
  270. package/ci/mechanism-pr334-r3-citations.test.js +205 -0
  271. package/ci/mechanism-pr334-r3-headings.test.js +268 -0
  272. package/docs/document-lifecycle.md +262 -0
  273. package/harness-everything/SKILL.md +1 -1
  274. package/harness-everything/scripts/doc-evidence.js +471 -0
  275. package/harness-everything/scripts/doc-lifecycle.js +2401 -0
  276. package/harness-everything/scripts/doc-obligations.js +1571 -0
  277. package/harness-everything/scripts/project-docs-resolver.js +764 -0
  278. package/multi-agent-workspace/SKILL.md +1 -1
  279. package/multi-agent-workspace/scripts/project-docs-resolver.js +437 -30
  280. package/opencode-plugin/plugin.json +1 -1
  281. package/package.json +1 -1
  282. package/plugins/harness-everything/.codex-plugin/plugin.json +1 -1
  283. package/plugins/harness-everything/plugin.json +1 -1
  284. package/plugins/harness-everything/skills/harness-everything/SKILL.md +1 -1
  285. package/plugins/harness-everything/skills/harness-everything/scripts/doc-evidence.js +471 -0
  286. package/plugins/harness-everything/skills/harness-everything/scripts/doc-lifecycle.js +2401 -0
  287. package/plugins/harness-everything/skills/harness-everything/scripts/doc-obligations.js +1571 -0
  288. package/plugins/harness-everything/skills/harness-everything/scripts/project-docs-resolver.js +764 -0
  289. package/plugins/harness-everything/skills/multi-agent-workspace/SKILL.md +1 -1
  290. package/plugins/harness-everything/skills/multi-agent-workspace/scripts/project-docs-resolver.js +437 -30
  291. package/plugins/harness-everything/skills/to-spec/SKILL.md +1 -1
  292. package/plugins/harness-everything/skills/to-spec/scripts/check-project-docs.js +168 -54
  293. package/plugins/harness-everything/skills/to-spec/scripts/project-docs-resolver.js +749 -96
  294. package/sbom.cdx.json +7 -7
  295. package/to-spec/SKILL.md +1 -1
  296. package/to-spec/scripts/check-project-docs.js +168 -54
  297. package/to-spec/scripts/project-docs-resolver.js +749 -96
@@ -0,0 +1,471 @@
1
+ 'use strict';
2
+
3
+ const crypto = require('crypto');
4
+
5
+ function isPlainObject(value) {
6
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
7
+ }
8
+
9
+ function isRecord(value) {
10
+ return isPlainObject(value)
11
+ && typeof value.id === 'string' && value.id.length > 0
12
+ && typeof value.obligationId === 'string' && value.obligationId.length > 0;
13
+ }
14
+
15
+ function nonEmptyString(value) {
16
+ return typeof value === 'string' && value.trim().length > 0;
17
+ }
18
+
19
+ function compareText(left, right) {
20
+ return left < right ? -1 : left > right ? 1 : 0;
21
+ }
22
+
23
+ function uniqueSorted(values) {
24
+ return [...new Set(values.filter(value => typeof value === 'string'))].sort(compareText);
25
+ }
26
+
27
+ function scopePathsOf(obligation) {
28
+ if (isPlainObject(obligation.scope) && Array.isArray(obligation.scope.paths)) {
29
+ return uniqueSorted(obligation.scope.paths);
30
+ }
31
+ if (typeof obligation.scope === 'string') return [obligation.scope];
32
+ return [];
33
+ }
34
+
35
+ function artifact(entry) {
36
+ return entry && typeof entry.digest === 'string' && entry.digest.length > 0 ? entry.digest : '';
37
+ }
38
+
39
+ function provenanceText(record) {
40
+ return nonEmptyString(record.provenance) ? record.provenance : `record ${record.id}`;
41
+ }
42
+
43
+
44
+ function provenanceJoin(records, fallback) {
45
+ return records.length ? uniqueSorted(records.map(provenanceText)).join('; ') : fallback;
46
+ }
47
+
48
+ function reqIdsOf(value) {
49
+ if (!Array.isArray(value)) return null;
50
+ return uniqueSorted(value.map(item => {
51
+ if (typeof item === 'string') return item;
52
+ if (isPlainObject(item) && typeof item.id === 'string') return item.id;
53
+ return null;
54
+ }).filter(Boolean));
55
+ }
56
+
57
+ // R4 (§11): an execution record counts as evidence only with a non-empty command, a non-empty
58
+ // provenance, and an integer exitCode. Otherwise it is diagnosed (`execution-record-incomplete`)
59
+ // and never passes.
60
+ function completeExecution(item) {
61
+ return nonEmptyString(item.command)
62
+ && nonEmptyString(item.provenance)
63
+ && typeof item.exitCode === 'number' && Number.isInteger(item.exitCode);
64
+ }
65
+
66
+ // R4 diagnostics name exactly which required fields the record is missing.
67
+ function incompleteFields(item) {
68
+ const missing = [];
69
+ if (!nonEmptyString(item.command)) missing.push('command');
70
+ if (!nonEmptyString(item.provenance)) missing.push('provenance');
71
+ if (typeof item.exitCode !== 'number' || !Number.isInteger(item.exitCode)) missing.push('integer exitCode');
72
+ return missing.join(', ');
73
+ }
74
+
75
+ // F2 (§11): the candidate PRD-set identity is the digest over the sorted [path, digest] pairs
76
+ // of the artifacts under docs/prd/. The envelope must carry this exact digest to attest that
77
+ // the inventory describes the candidate being checked.
78
+ function prdSetDigest(artifactMap) {
79
+ const pairs = Object.keys(artifactMap)
80
+ .filter(path => path.startsWith('docs/prd/'))
81
+ .sort()
82
+ .map(path => [path, artifact(artifactMap[path])]);
83
+ return crypto.createHash('sha256').update(JSON.stringify(pairs)).digest('hex');
84
+ }
85
+
86
+ function checkDocObligations({ obligations, artifacts, baseline, executionRecords, reviewRecords } = {}) {
87
+ const diagnostics = [];
88
+ const results = [];
89
+ const artifactMap = isPlainObject(artifacts) ? artifacts : {};
90
+ const baselineDigest = isPlainObject(baseline) && nonEmptyString(baseline.digest) ? baseline.digest : '';
91
+ const executionList = Array.isArray(executionRecords) ? executionRecords.filter(isRecord) : [];
92
+ const reviewList = Array.isArray(reviewRecords) ? reviewRecords.filter(isRecord) : [];
93
+
94
+ const obligationList = Array.isArray(obligations) ? obligations.filter(isPlainObject) : [];
95
+ const knownIds = new Set(obligationList.map(item => nonEmptyString(item.id) ? item.id : item.capability)
96
+ .filter(value => typeof value === 'string' && value.length > 0));
97
+
98
+ for (const waiver of reviewList.filter(item => item.kind === 'waiver' && !knownIds.has(item.obligationId))) {
99
+ diagnostics.push({
100
+ code: 'waiver-unmatched',
101
+ obligationId: waiver.obligationId,
102
+ message: `waiver ${waiver.id} references obligation ${JSON.stringify(waiver.obligationId)} which is not in the input; it attaches to nothing and satisfies no obligation`
103
+ });
104
+ }
105
+
106
+ for (const obligation of obligationList) {
107
+ results.push(evaluateObligation(obligation, {
108
+ artifactMap, baselineDigest, executionList, reviewList, diagnostics
109
+ }));
110
+ }
111
+ return { diagnostics, results };
112
+ }
113
+
114
+ function evaluateObligation(obligation, context) {
115
+ const { artifactMap, baselineDigest, executionList, reviewList, diagnostics } = context;
116
+ const capability = typeof obligation.capability === 'string' ? obligation.capability : '';
117
+ const obligationId = nonEmptyString(obligation.id) ? obligation.id : capability;
118
+ const scopePaths = scopePathsOf(obligation);
119
+ const applicability = obligation.applicability;
120
+
121
+ const artifactDigestKey = scopePaths.length
122
+ ? scopePaths.map(path => `${path}=${artifact(artifactMap[path]) || 'missing'}`).join('|')
123
+ : (baselineDigest || 'no-scope-paths');
124
+ const baseDigestKey = baselineDigest || 'no-baseline';
125
+ const result = (observation, outcome, provenance, recordRefs) => ({
126
+ obligationId,
127
+ observation,
128
+ outcome,
129
+ artifactDigest: artifactDigestKey,
130
+ baseDigest: baseDigestKey,
131
+ provenance,
132
+ recordRefs: [...recordRefs]
133
+ });
134
+
135
+ if (applicability === 'N/A') {
136
+ return result('not-observed', 'none', 'obligation is not applicable to this change set', []);
137
+ }
138
+ if (applicability === 'unknown') {
139
+ return result('not-observed', 'none',
140
+ 'applicability unknown; fail-closed, pilot 3a defines no completion policy for this state', []);
141
+ }
142
+
143
+ const matching = [...executionList, ...reviewList].filter(item => item.obligationId === obligationId);
144
+ const bound = (item, path) => nonEmptyString(item.baseDigest) && item.baseDigest === baselineDigest
145
+ && isPlainObject(item.artifactDigests) && artifact(artifactMap[path]).length > 0
146
+ && item.artifactDigests[path] === artifactMap[path].digest
147
+ && Array.isArray(item.paths) && item.paths.includes(path);
148
+
149
+ for (const item of matching) {
150
+ if (executionList.includes(item) && !completeExecution(item)) {
151
+ diagnostics.push({
152
+ code: 'execution-record-incomplete',
153
+ obligationId,
154
+ message: `execution record ${item.id} is incomplete (missing: ${incompleteFields(item)}); a minimal record needs a non-empty command, a non-empty provenance, and an integer exitCode, and it therefore counts as no evidence`
155
+ });
156
+ }
157
+ }
158
+
159
+ for (const item of matching) {
160
+ if (nonEmptyString(item.baseDigest) && item.baseDigest !== baselineDigest) {
161
+ diagnostics.push({
162
+ code: 'stale-base-digest',
163
+ obligationId,
164
+ message: `record ${item.id} was made against a different baseline and cannot match`
165
+ });
166
+ continue;
167
+ }
168
+ if (Array.isArray(item.paths) && isPlainObject(item.artifactDigests)) {
169
+ const mismatch = item.paths.find(path => artifact(artifactMap[path]).length > 0
170
+ && nonEmptyString(item.artifactDigests[path]) && item.artifactDigests[path] !== artifactMap[path].digest);
171
+ if (mismatch) {
172
+ diagnostics.push({
173
+ code: 'stale-artifact-digest',
174
+ obligationId,
175
+ message: `record ${item.id} was made for a previous candidate; digest differs for path ${mismatch}`
176
+ });
177
+ }
178
+ }
179
+ }
180
+
181
+ const usedExec = matching.filter(item => executionList.includes(item)
182
+ && completeExecution(item)
183
+ && scopePaths.some(path => bound(item, path)));
184
+ const failingExec = usedExec.filter(item => item.exitCode !== 0);
185
+ const boundReviews = matching.filter(item => reviewList.includes(item) && item.kind === 'review'
186
+ && scopePaths.some(path => bound(item, path)));
187
+ const validReviews = boundReviews.filter(item => item.verdict === 'approve' && nonEmptyString(item.summary));
188
+ const invalidReviews = boundReviews.filter(item => !validReviews.includes(item));
189
+ const oddKindRecords = matching.filter(item => reviewList.includes(item)
190
+ && item.kind !== 'review' && item.kind !== 'waiver');
191
+
192
+ for (const item of invalidReviews) {
193
+ diagnostics.push({
194
+ code: item.verdict === 'approve' ? 'review-summary-missing' : 'review-not-approved',
195
+ obligationId,
196
+ message: `review record ${item.id} does not carry an approve verdict with a non-empty summary`
197
+ });
198
+ }
199
+ for (const item of oddKindRecords) {
200
+ diagnostics.push({
201
+ code: 'unsupported-review-kind',
202
+ obligationId,
203
+ message: `record ${item.id} has kind ${JSON.stringify(item.kind)} and cannot act as review evidence`
204
+ });
205
+ }
206
+
207
+ const boundAny = (path) => matching.some(item => bound(item, path));
208
+ const unmetPaths = scopePaths.filter(path => !boundAny(path));
209
+ for (const path of unmetPaths) {
210
+ diagnostics.push({
211
+ code: 'missing-path-coverage',
212
+ obligationId,
213
+ message: `no observed record is bound to path ${path} with the current candidate and baseline digests`
214
+ });
215
+ }
216
+
217
+ const recordIndex = new Map(matching.map(item => [item.id, item]));
218
+ const citedResolves = item => {
219
+ const target = recordIndex.get(item);
220
+ return Boolean(target) && target.obligationId === obligationId
221
+ && executionList.includes(target) && completeExecution(target) && target.exitCode === 0
222
+ && scopePaths.some(path => bound(target, path));
223
+ };
224
+ const waiverCandidates = reviewList.filter(item => item.kind === 'waiver'
225
+ && item.obligationId === obligationId);
226
+ const waiverQualifies = item => nonEmptyString(item.reason)
227
+ && Array.isArray(item.citedRecordIds) && item.citedRecordIds.length > 0
228
+ && item.citedRecordIds.every(citedResolves);
229
+ const validWaivers = waiverCandidates.filter(waiverQualifies);
230
+ const brokenWaivers = waiverCandidates.filter(item => !waiverQualifies(item));
231
+ for (const item of brokenWaivers) {
232
+ const message = !nonEmptyString(item.reason)
233
+ ? `waiver ${item.id} has no non-empty reason`
234
+ : !Array.isArray(item.citedRecordIds) || item.citedRecordIds.length === 0
235
+ ? `waiver ${item.id} cites no observed record`
236
+ : `waiver ${item.id} cites records that do not resolve to observed evidence for this obligation`;
237
+ diagnostics.push({ code: 'unsupported-waiver', obligationId, message });
238
+ }
239
+
240
+ const observation = () => {
241
+ if (brokenWaivers.length) return 'unresolved';
242
+ if (!matching.length) return 'not-observed';
243
+ return scopePaths.some(boundAny) ? 'unresolved' : 'unavailable';
244
+ };
245
+
246
+ // F1 (round 10): for requirement-sync, an explicitly unknown requirement scope is completed
247
+ // by nothing and attributes nothing — the check runs before ANY outcome branch, including
248
+ // the shared bound-failing-execution lane: outcome is none whether the bound records pass,
249
+ // fail, or contradict each other. Other capabilities keep observed fail on a bound failing
250
+ // execution.
251
+ const requirementScopeUnknown = capability === 'requirement-sync'
252
+ && (obligation.requirementScopeUnknown === true
253
+ || (isPlainObject(obligation.scope) && obligation.scope.requirementIds === 'unknown'));
254
+ if (requirementScopeUnknown) {
255
+ return result('not-observed', 'none',
256
+ 'requirement scope unknown; evidence for a known subset neither passes nor attributes absence', []);
257
+ }
258
+
259
+ // F1 (§11): resolution authority, hoisted ahead of the shared failing-execution lane (PR #334
260
+ // round 2, finding 4307059310). A cited REQ is resolved only when the entry is an object with a
261
+ // non-null PRD file and no overriding resolution label ('ambiguous', 'analysis-error', 'missing'
262
+ // are unresolved), and the id is declared by exactly one entry (duplicates are ambiguous).
263
+ // Unresolved cited REQs never pass and are never an observed absence or an observed fail.
264
+ const citedEntries = Array.isArray(obligation.citedReqs) ? obligation.citedReqs : [];
265
+ const idCounts = new Map();
266
+ for (const item of citedEntries) {
267
+ if (isPlainObject(item) && nonEmptyString(item.id)) {
268
+ idCounts.set(item.id, (idCounts.get(item.id) || 0) + 1);
269
+ }
270
+ }
271
+ const entryResolved = item => isPlainObject(item)
272
+ && nonEmptyString(item.id) && nonEmptyString(item.file)
273
+ && (item.resolution === undefined || item.resolution === 'resolved')
274
+ && idCounts.get(item.id) === 1;
275
+ const unresolvedReqs = citedEntries.filter(item => !entryResolved(item));
276
+ // An absent, null, or empty cited identity is unknown rather than resolved, so it never reaches
277
+ // the observed-fail lane for requirement-sync; other capabilities are unaffected.
278
+ const identityKnown = citedEntries.length > 0 && unresolvedReqs.length === 0;
279
+
280
+ if (usedExec.length && failingExec.length && (capability !== 'requirement-sync' || identityKnown)) {
281
+ return result('observed', 'fail', provenanceJoin(failingExec, 'observed failing evidence'),
282
+ uniqueSorted([...failingExec.map(item => item.id)]));
283
+ }
284
+
285
+ if (capability === 'requirement-sync') {
286
+ const citedIds = reqIdsOf(obligation.citedReqs);
287
+ const updatedIds = reqIdsOf(obligation.reqUpdates);
288
+ const revisionDataKnown = Array.isArray(citedIds) && Array.isArray(updatedIds);
289
+ const revisionSatisfied = revisionDataKnown && citedIds.length > 0
290
+ && citedIds.every(id => updatedIds.includes(id));
291
+ if (!revisionDataKnown) {
292
+ diagnostics.push({
293
+ code: 'req-sync-unverified',
294
+ obligationId,
295
+ message: 'the obligation carries no citedReqs/reqUpdates status; the REQ revision lane is unavailable (fail-closed)'
296
+ });
297
+ } else if (!revisionSatisfied) {
298
+ diagnostics.push({
299
+ code: 'req-sync-unverified',
300
+ obligationId,
301
+ message: `cited REQ ids ${citedIds.join(', ') || '(none)'} are not all updated in this change; the REQ revision lane is unavailable`
302
+ });
303
+ }
304
+
305
+ if (unresolvedReqs.length) {
306
+ for (const item of unresolvedReqs) {
307
+ const id = isPlainObject(item) ? item.id : item;
308
+ const reason = isPlainObject(item) && idCounts.get(item.id) > 1
309
+ ? 'the id is declared by more than one current PRD'
310
+ : isPlainObject(item) && typeof item.resolution === 'string' && item.resolution !== 'resolved'
311
+ ? `resolution is ${JSON.stringify(item.resolution)}`
312
+ : 'it has no non-null PRD file';
313
+ diagnostics.push({
314
+ code: 'req-unresolved',
315
+ obligationId,
316
+ message: `cited requirement ${JSON.stringify(id)} is unresolved: ${reason}; no waiver alternative and no absence fail can rest on it`
317
+ });
318
+ }
319
+ return result('unresolved', 'none',
320
+ 'requirement-sync cites an unresolved REQ (ambiguous, analysis error, or missing declaration); a no-REQ-change waiver cannot satisfy it',
321
+ []);
322
+ }
323
+
324
+ const waiverSelfBound = item => scopePaths.length > 0 && scopePaths.every(path => bound(item, path));
325
+ const citationsCoverScope = item => {
326
+ const covered = new Set();
327
+ for (const id of Array.isArray(item.citedRecordIds) ? item.citedRecordIds : []) {
328
+ const target = recordIndex.get(id);
329
+ if (target && target.obligationId === obligationId && target.exitCode === 0
330
+ && completeExecution(target) && !failingExec.includes(target)) {
331
+ for (const path of scopePaths) if (bound(target, path)) covered.add(path);
332
+ }
333
+ }
334
+ return covered.size === scopePaths.length;
335
+ };
336
+ const syncWaivers = waiverCandidates.filter(item => waiverQualifies(item)
337
+ && Array.isArray(item.requirementIds) && item.requirementIds.length > 0
338
+ && citationsCoverScope(item) && waiverSelfBound(item));
339
+ for (const item of waiverCandidates.filter(entry => waiverQualifies(entry) && !syncWaivers.includes(entry))) {
340
+ const listsRequirements = Array.isArray(item.requirementIds) && item.requirementIds.length > 0;
341
+ diagnostics.push({
342
+ code: 'unsupported-waiver',
343
+ obligationId,
344
+ message: !listsRequirements
345
+ ? `waiver ${item.id} carries no requirementIds and covers no requirement; a no-REQ-change waiver must list the REQ ids it covers`
346
+ : waiverSelfBound(item)
347
+ ? `waiver ${item.id}'s cited evidence does not bind every scope path`
348
+ : `waiver ${item.id} is not bound itself to the current candidate digests and baseline (self-binding fails)`
349
+ });
350
+ }
351
+
352
+ // T3 (§11): a no-REQ-change waiver must carry requirementIds (a non-empty list) and covers
353
+ // only those REQs. A waiver without requirementIds covers nothing. Every cited REQ that is
354
+ // not revised must be covered by a valid current-bound waiver that lists it; path, digest,
355
+ // and execution checks stay unchanged (waiverQualifies/citationsCoverScope/waiverSelfBound).
356
+ const missingIds = (Array.isArray(citedIds) && Array.isArray(updatedIds))
357
+ ? citedIds.filter(id => !updatedIds.includes(id))
358
+ : [];
359
+ const waiverCoversId = (item, id) => Array.isArray(item.requirementIds)
360
+ && item.requirementIds.length > 0 && item.requirementIds.includes(id);
361
+ const uncoveredMissing = missingIds.filter(id => !syncWaivers.some(item => waiverCoversId(item, id)));
362
+
363
+ const passingExec = usedExec.filter(item => item.exitCode === 0);
364
+ const execCoveredAll = scopePaths.length > 0
365
+ && scopePaths.every(path => passingExec.some(item => bound(item, path)));
366
+ // S3 (§11): identity data gates every per-REQ coverage claim. A waiver lane pass requires a
367
+ // known non-empty cited identity list, known update data, and coverage of every uncovered
368
+ // missing REQ by a listing valid waiver. Absent or empty cited data, or an absent
369
+ // reqUpdates field, leaves the obligation unknown: no pass and no absence fail.
370
+ const waiverPass = syncWaivers.length > 0
371
+ && Array.isArray(citedIds) && citedIds.length > 0
372
+ && Array.isArray(updatedIds)
373
+ && uncoveredMissing.length === 0;
374
+ if (usedExec.length && execCoveredAll && (revisionSatisfied || waiverPass)) {
375
+ const contributors = [...usedExec, ...syncWaivers];
376
+ return result('observed', 'pass', provenanceJoin(contributors, 'observed requirement-sync evidence'),
377
+ uniqueSorted(contributors.map(item => item.id)));
378
+ }
379
+
380
+ // F2 (§11): the absence-fail lane requires an attested envelope on the obligation.
381
+ // Envelope: obligation.inventory = { complete, waiverInventoryComplete, baseDigest,
382
+ // candidateDigest, configDigest }; baseDigest must equal the selected baseline digest and
383
+ // candidateDigest must equal the current candidate PRD-set digest. Any missing or
384
+ // mismatched field yields none with req-sync-envelope-unattested, never a fail. An empty
385
+ // cited list is never an observed absence (every() on [] is vacuous).
386
+ const envelope = isPlainObject(obligation.inventory) ? obligation.inventory : null;
387
+ const envelopeAttested = Boolean(envelope)
388
+ && envelope.complete === true
389
+ && envelope.waiverInventoryComplete === true
390
+ && nonEmptyString(envelope.baseDigest) && envelope.baseDigest === baselineDigest
391
+ && nonEmptyString(envelope.candidateDigest) && envelope.candidateDigest === prdSetDigest(artifactMap)
392
+ && nonEmptyString(envelope.configDigest);
393
+ // Q2 (§11): the absence fail is per cited REQ. A REQ that is neither revised in this
394
+ // change nor covered by a valid waiver that lists it (T3 per-REQ coverage; a waiver
395
+ // without requirementIds covers nothing) is missing, so uncovering any missing REQ keeps
396
+ // the fail lane open.
397
+ // P2 (round 9): an explicit trigger is authoritative. When the obligation carries the field
398
+ // at all, only 'new-test' and 'assertion-change' open the fail lane; null, 'unknown', or any
399
+ // other value never fails, even with assertionChanged true. An ABSENT field (distinguished
400
+ // with a property check) keeps the legacy behavior: assertionChanged true may fail.
401
+ const triggerPresent = Object.prototype.hasOwnProperty.call(obligation, 'requirementSyncTrigger');
402
+ const activeTrigger = obligation.requirementSyncTrigger === 'new-test'
403
+ || obligation.requirementSyncTrigger === 'assertion-change';
404
+ const failTrigger = triggerPresent
405
+ ? activeTrigger
406
+ : (obligation.assertionChanged === true || activeTrigger);
407
+ const failCandidate = revisionDataKnown && citedIds.length > 0
408
+ && failTrigger
409
+ && obligation.inventoryComplete === true
410
+ && citedEntries.every(entryResolved)
411
+ && missingIds.length > 0;
412
+ if (failCandidate && !envelopeAttested) {
413
+ diagnostics.push({
414
+ code: 'req-sync-envelope-unattested',
415
+ obligationId,
416
+ message: 'the absence-fail lane is unavailable: the obligation carries no attested inventory envelope (complete, waiverInventoryComplete, baseDigest equal to the selected baseline, candidateDigest equal to the current candidate PRD-set digest, and a present configDigest)'
417
+ });
418
+ } else if (failCandidate && envelopeAttested && uncoveredMissing.length > 0) {
419
+ diagnostics.push({
420
+ code: 'req-sync-no-revision-or-waiver',
421
+ obligationId,
422
+ message: `requirement-sync is unmet: the trigger is ${obligation.requirementSyncTrigger === 'new-test' ? 'a new REQ-citing test' : 'a changed assertion'} and the inventory is complete, and cited requirement${uncoveredMissing.length === 1 ? '' : 's'} ${uncoveredMissing.map(id => {
423
+ const entry = citedEntries.find(item => isPlainObject(item) && item.id === id);
424
+ return `${JSON.stringify(id)} (${entry.file})`;
425
+ }).join(', ')} are not revised in this change and not covered by a valid waiver listing them`
426
+ });
427
+ return result('observed', 'fail',
428
+ 'requirement-sync unmet: attested inventory, requirement-sync trigger fired, cited REQ(s) not revised and not covered',
429
+ []);
430
+ }
431
+
432
+ const contributors = [...usedExec, ...validReviews, ...syncWaivers];
433
+ return result(observation(), 'none',
434
+ provenanceJoin(contributors,
435
+ 'requirement-sync demands a revised cited REQ with matching evidence or a current-bound supported waiver; a passing command alone is insufficient'),
436
+ uniqueSorted(contributors.map(item => item.id)));
437
+ }
438
+
439
+ const reviewsRequired = capability === 'quality-review';
440
+ if (reviewsRequired) {
441
+ const reviewCovers = (path) => matching.some(item => reviewList.includes(item) && item.kind === 'review'
442
+ && item.verdict === 'approve' && nonEmptyString(item.summary) && bound(item, path));
443
+ if (!invalidReviews.length && !oddKindRecords.length
444
+ && validReviews.length && scopePaths.every(reviewCovers)) {
445
+ const contributors = [...validReviews, ...usedExec.filter(item => !failingExec.includes(item))];
446
+ return result('observed', 'pass', provenanceJoin(contributors, 'observed review evidence'),
447
+ uniqueSorted(contributors.map(item => item.id)));
448
+ }
449
+ const contributors = [...validReviews, ...usedExec];
450
+ return result(observation(), 'none',
451
+ provenanceJoin(contributors, 'review obligation is unresolved without an approving, path-bound review record'),
452
+ uniqueSorted(contributors.map(item => item.id)));
453
+ }
454
+
455
+ const execCovers = (path) => matching.some(item => executionList.includes(item)
456
+ && completeExecution(item)
457
+ && !failingExec.includes(item) && bound(item, path));
458
+ const execCovered = scopePaths.every(execCovers);
459
+ if (usedExec.length && execCovered) {
460
+ const contributors = [...usedExec.filter(item => !failingExec.includes(item)), ...validWaivers];
461
+ return result('observed', 'pass', provenanceJoin(contributors, 'observed passing evidence'),
462
+ uniqueSorted(contributors.map(item => item.id)));
463
+ }
464
+
465
+ const contributors = [...usedExec, ...validReviews, ...validWaivers];
466
+ return result(observation(), 'none',
467
+ provenanceJoin(contributors, 'no qualifying observed evidence; fail-closed outcome'),
468
+ uniqueSorted(contributors.map(item => item.id)));
469
+ }
470
+
471
+ module.exports = { checkDocObligations };