harness-everything 0.34.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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +25 -0
- package/ci/fixtures/doc-obligations/derive/acceptance-only/base/docs/prd/graph.md +22 -0
- package/ci/fixtures/doc-obligations/derive/acceptance-only/candidate/docs/prd/graph.md +22 -0
- package/ci/fixtures/doc-obligations/derive/alias-only/base/docs/prd/graph.md +24 -0
- package/ci/fixtures/doc-obligations/derive/alias-only/candidate/docs/prd/graph.md +24 -0
- package/ci/fixtures/doc-obligations/derive/assertion-no-revision/base/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/assertion-no-revision/base/docs/prd/graph.md +20 -0
- package/ci/fixtures/doc-obligations/derive/assertion-no-revision/candidate/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/assertion-no-revision/candidate/docs/prd/graph.md +20 -0
- package/ci/fixtures/doc-obligations/derive/assertion-no-revision-attested/base/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/assertion-no-revision-attested/base/docs/prd/graph.md +20 -0
- package/ci/fixtures/doc-obligations/derive/assertion-no-revision-attested/candidate/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/assertion-no-revision-attested/candidate/docs/prd/graph.md +20 -0
- package/ci/fixtures/doc-obligations/derive/assertion-no-revision-attested/inventory.json +7 -0
- package/ci/fixtures/doc-obligations/derive/attribution-impossible/base/ci/mechanism-open.test.js +3 -0
- package/ci/fixtures/doc-obligations/derive/attribution-impossible/candidate/ci/mechanism-open.test.js.fixture +2 -0
- package/ci/fixtures/doc-obligations/derive/clause-context-typo/base/docs/prd/graph.md +24 -0
- package/ci/fixtures/doc-obligations/derive/clause-context-typo/candidate/docs/prd/graph.md +24 -0
- package/ci/fixtures/doc-obligations/derive/code-only/base/src/lint.js +1 -0
- package/ci/fixtures/doc-obligations/derive/code-only/candidate/src/lint.js +1 -0
- package/ci/fixtures/doc-obligations/derive/comment-only-test/base/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/comment-only-test/candidate/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/computed-member-assert/base/ci/mechanism-c1.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/computed-member-assert/candidate/ci/mechanism-c1.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/computed-member-variable/base/ci/mechanism-c1.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/computed-member-variable/candidate/ci/mechanism-c1.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/config.json +11 -0
- package/ci/fixtures/doc-obligations/derive/decision-fence-backtick/base/docs/adr/GRPH-010.md +20 -0
- package/ci/fixtures/doc-obligations/derive/decision-fence-backtick/candidate/docs/adr/GRPH-010.md +20 -0
- package/ci/fixtures/doc-obligations/derive/decision-fence-tilde/base/docs/adr/GRPH-010.md +20 -0
- package/ci/fixtures/doc-obligations/derive/decision-fence-tilde/candidate/docs/adr/GRPH-010.md +20 -0
- package/ci/fixtures/doc-obligations/derive/deep-strict-equal/base/ci/mechanism-s1.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/deep-strict-equal/candidate/ci/mechanism-s1.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/deleted-adr/base/docs/adr/GRPH-008.md +14 -0
- package/ci/fixtures/doc-obligations/derive/deleted-prd/base/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/deleted-prd/base/docs/prd/graph.md +20 -0
- package/ci/fixtures/doc-obligations/derive/deleted-prd/candidate/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/derived-rename/base/docs/index.md +3 -0
- package/ci/fixtures/doc-obligations/derive/derived-rename/base/src/model/a.js +1 -0
- package/ci/fixtures/doc-obligations/derive/derived-rename/candidate/docs/index.md +3 -0
- package/ci/fixtures/doc-obligations/derive/derived-rename/candidate/src/model/b.js +1 -0
- package/ci/fixtures/doc-obligations/derive/derived-rename/config.json +7 -0
- package/ci/fixtures/doc-obligations/derive/derived-two-outputs/base/docs/index.md +3 -0
- package/ci/fixtures/doc-obligations/derive/derived-two-outputs/base/docs/model-summary.md +3 -0
- package/ci/fixtures/doc-obligations/derive/derived-two-outputs/base/src/model.js +1 -0
- package/ci/fixtures/doc-obligations/derive/derived-two-outputs/candidate/docs/index.md +3 -0
- package/ci/fixtures/doc-obligations/derive/derived-two-outputs/candidate/docs/model-summary.md +3 -0
- package/ci/fixtures/doc-obligations/derive/derived-two-outputs/candidate/src/model.js +1 -0
- package/ci/fixtures/doc-obligations/derive/derived-two-outputs/config.json +10 -0
- package/ci/fixtures/doc-obligations/derive/distinct-logical-prds/base/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/distinct-logical-prds/base/docs/prd/graph-one/r001.md +19 -0
- package/ci/fixtures/doc-obligations/derive/distinct-logical-prds/base/docs/prd/graph-two/r001.md +19 -0
- package/ci/fixtures/doc-obligations/derive/distinct-logical-prds/candidate/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/distinct-logical-prds/candidate/docs/prd/graph-one/r001.md +19 -0
- package/ci/fixtures/doc-obligations/derive/distinct-logical-prds/candidate/docs/prd/graph-two/r001.md +19 -0
- package/ci/fixtures/doc-obligations/derive/docs-only/base/docs/reference/guide.md +3 -0
- package/ci/fixtures/doc-obligations/derive/docs-only/candidate/docs/reference/guide.md +3 -0
- package/ci/fixtures/doc-obligations/derive/dup-req-id/base/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/dup-req-id/base/docs/prd/graph-a.md +20 -0
- package/ci/fixtures/doc-obligations/derive/dup-req-id/base/docs/prd/graph-b.md +20 -0
- package/ci/fixtures/doc-obligations/derive/dup-req-id/candidate/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/dup-req-id/candidate/docs/prd/graph-a.md +20 -0
- package/ci/fixtures/doc-obligations/derive/dup-req-id/candidate/docs/prd/graph-b.md +20 -0
- package/ci/fixtures/doc-obligations/derive/expect-computed-literal/base/ci/mechanism-e.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/expect-computed-literal/candidate/ci/mechanism-e.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/expect-computed-matcher/base/ci/mechanism-e.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/expect-computed-matcher/candidate/ci/mechanism-e.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/expect-not-to-equal/base/ci/mechanism-s1.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/expect-not-to-equal/candidate/ci/mechanism-s1.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/formatting-only-test/base/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/formatting-only-test/candidate/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/helper-delegated-test/base/docs/prd/graph.md +18 -0
- package/ci/fixtures/doc-obligations/derive/helper-delegated-test/candidate/ci/mechanism-helper.test.js +1 -0
- package/ci/fixtures/doc-obligations/derive/helper-delegated-test/candidate/docs/prd/graph.md +18 -0
- package/ci/fixtures/doc-obligations/derive/historical-only-req/base/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/historical-only-req/base/docs/prd/graph/r001.md +25 -0
- package/ci/fixtures/doc-obligations/derive/historical-only-req/candidate/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/historical-only-req/candidate/docs/prd/graph/r001.md +25 -0
- package/ci/fixtures/doc-obligations/derive/historical-only-req/candidate/docs/prd/graph/r002.md +23 -0
- package/ci/fixtures/doc-obligations/derive/in-place-current/base/ci/mechanism-inplace.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/in-place-current/base/docs/prd/graph/r001.md +20 -0
- package/ci/fixtures/doc-obligations/derive/in-place-current/base/docs/prd/graph/r002.md +26 -0
- package/ci/fixtures/doc-obligations/derive/in-place-current/candidate/ci/mechanism-inplace.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/in-place-current/candidate/docs/prd/graph/r001.md +20 -0
- package/ci/fixtures/doc-obligations/derive/in-place-current/candidate/docs/prd/graph/r002.md +28 -0
- package/ci/fixtures/doc-obligations/derive/in-place-historical/base/ci/mechanism-inplace.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/in-place-historical/base/docs/prd/graph/r001.md +20 -0
- package/ci/fixtures/doc-obligations/derive/in-place-historical/base/docs/prd/graph/r002.md +26 -0
- package/ci/fixtures/doc-obligations/derive/in-place-historical/candidate/ci/mechanism-inplace.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/in-place-historical/candidate/docs/prd/graph/r001.md +22 -0
- package/ci/fixtures/doc-obligations/derive/in-place-historical/candidate/docs/prd/graph/r002.md +26 -0
- package/ci/fixtures/doc-obligations/derive/invalid-clause/base/docs/prd/graph.md +24 -0
- package/ci/fixtures/doc-obligations/derive/invalid-clause/candidate/docs/prd/graph.md +24 -0
- package/ci/fixtures/doc-obligations/derive/legacy-untagged-fallback/base/ci/mechanism-legacy.test.js +6 -0
- package/ci/fixtures/doc-obligations/derive/legacy-untagged-fallback/candidate/ci/mechanism-legacy.test.js +6 -0
- package/ci/fixtures/doc-obligations/derive/literal-whitespace-assert/base/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/literal-whitespace-assert/candidate/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/malformed-citation-attribution/base/ci/mechanism-bad.test.js +6 -0
- package/ci/fixtures/doc-obligations/derive/malformed-citation-attribution/candidate/ci/mechanism-bad.test.js.fixture +6 -0
- package/ci/fixtures/doc-obligations/derive/malformed-prd/base/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/malformed-prd/base/docs/prd/graph.md +20 -0
- package/ci/fixtures/doc-obligations/derive/malformed-prd/candidate/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/malformed-prd/candidate/docs/prd/graph.md +11 -0
- package/ci/fixtures/doc-obligations/derive/missing-approval/base/docs/prd/graph.md +20 -0
- package/ci/fixtures/doc-obligations/derive/missing-approval/candidate/docs/prd/graph.md +20 -0
- package/ci/fixtures/doc-obligations/derive/mixed-attribution/base/ci/mechanism-mixed.test.js +6 -0
- package/ci/fixtures/doc-obligations/derive/mixed-attribution/base/docs/prd/graph.md +24 -0
- package/ci/fixtures/doc-obligations/derive/mixed-attribution/candidate/ci/mechanism-mixed.test.js.fixture +5 -0
- package/ci/fixtures/doc-obligations/derive/mixed-attribution/candidate/docs/prd/graph.md +24 -0
- package/ci/fixtures/doc-obligations/derive/mixed-docs-code/base/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/mixed-docs-code/base/docs/reference/guide.md +3 -0
- package/ci/fixtures/doc-obligations/derive/mixed-docs-code/base/src/lint.js +1 -0
- package/ci/fixtures/doc-obligations/derive/mixed-docs-code/candidate/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/mixed-docs-code/candidate/docs/reference/guide.md +3 -0
- package/ci/fixtures/doc-obligations/derive/mixed-docs-code/candidate/src/lint.js +1 -0
- package/ci/fixtures/doc-obligations/derive/multiline-comment-only/base/ci/mechanism-multi.test.js +5 -0
- package/ci/fixtures/doc-obligations/derive/multiline-comment-only/candidate/ci/mechanism-multi.test.js +5 -0
- package/ci/fixtures/doc-obligations/derive/multiline-expect/base/ci/mechanism-multi.test.js +6 -0
- package/ci/fixtures/doc-obligations/derive/multiline-expect/candidate/ci/mechanism-multi.test.js +6 -0
- package/ci/fixtures/doc-obligations/derive/multiline-formatting-only/base/ci/mechanism-multi.test.js +5 -0
- package/ci/fixtures/doc-obligations/derive/multiline-formatting-only/candidate/ci/mechanism-multi.test.js +5 -0
- package/ci/fixtures/doc-obligations/derive/multiline-numeric-prd/base/ci/mechanism-multi.test.js +5 -0
- package/ci/fixtures/doc-obligations/derive/multiline-numeric-prd/base/docs/prd/graph.md +18 -0
- package/ci/fixtures/doc-obligations/derive/multiline-numeric-prd/candidate/ci/mechanism-multi.test.js +5 -0
- package/ci/fixtures/doc-obligations/derive/multiline-numeric-prd/candidate/docs/prd/graph.md +18 -0
- package/ci/fixtures/doc-obligations/derive/multiline-strictequal/base/ci/mechanism-multi.test.js +5 -0
- package/ci/fixtures/doc-obligations/derive/multiline-strictequal/candidate/ci/mechanism-multi.test.js +5 -0
- package/ci/fixtures/doc-obligations/derive/new-prd/candidate/docs/prd/graph-new.md +20 -0
- package/ci/fixtures/doc-obligations/derive/new-reference/candidate/docs/reference/new-note.md +3 -0
- package/ci/fixtures/doc-obligations/derive/new-req-test/candidate/ci/mechanism-new.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/orphan-req-test/base/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/orphan-req-test/base/docs/prd/graph.md +20 -0
- package/ci/fixtures/doc-obligations/derive/orphan-req-test/candidate/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/orphan-req-test/candidate/docs/prd/graph.md +20 -0
- package/ci/fixtures/doc-obligations/derive/partial-revision/base/ci/mechanism-partial.test.js +5 -0
- package/ci/fixtures/doc-obligations/derive/partial-revision/base/docs/prd/graph/r001.md +24 -0
- package/ci/fixtures/doc-obligations/derive/partial-revision/candidate/ci/mechanism-partial.test.js +5 -0
- package/ci/fixtures/doc-obligations/derive/partial-revision/candidate/docs/prd/graph/r001.md +24 -0
- package/ci/fixtures/doc-obligations/derive/partial-revision/candidate/docs/prd/graph/r002.md +26 -0
- package/ci/fixtures/doc-obligations/derive/regex-literal-whitespace/base/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/regex-literal-whitespace/candidate/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/rename/base/docs/reference/old-guide.md +3 -0
- package/ci/fixtures/doc-obligations/derive/rename/candidate/docs/reference/guide.md +3 -0
- package/ci/fixtures/doc-obligations/derive/replacement-target/base/docs/prd/graph.md +24 -0
- package/ci/fixtures/doc-obligations/derive/replacement-target/candidate/docs/prd/graph.md +24 -0
- package/ci/fixtures/doc-obligations/derive/req-context-typo/base/docs/prd/graph.md +26 -0
- package/ci/fixtures/doc-obligations/derive/req-context-typo/candidate/docs/prd/graph.md +26 -0
- package/ci/fixtures/doc-obligations/derive/req-revision/base/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/req-revision/base/docs/prd/graph/r001.md +20 -0
- package/ci/fixtures/doc-obligations/derive/req-revision/candidate/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/req-revision/candidate/docs/prd/graph/r001.md +20 -0
- package/ci/fixtures/doc-obligations/derive/req-revision/candidate/docs/prd/graph/r002.md +28 -0
- package/ci/fixtures/doc-obligations/derive/req-revision-in-place/base/docs/prd/graph/r001.md +20 -0
- package/ci/fixtures/doc-obligations/derive/req-revision-in-place/candidate/docs/prd/graph/r001.md +22 -0
- package/ci/fixtures/doc-obligations/derive/retained-revision-chain/base/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/retained-revision-chain/base/docs/prd/graph/r001.md +20 -0
- package/ci/fixtures/doc-obligations/derive/retained-revision-chain/base/docs/prd/graph/r002.md +26 -0
- package/ci/fixtures/doc-obligations/derive/retained-revision-chain/candidate/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/retained-revision-chain/candidate/docs/prd/graph/r001.md +20 -0
- package/ci/fixtures/doc-obligations/derive/retained-revision-chain/candidate/docs/prd/graph/r002.md +26 -0
- package/ci/fixtures/doc-obligations/derive/string-literal-tag/base/ci/mechanism-str.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/string-literal-tag/candidate/ci/mechanism-str.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/tag-inside-test-call/base/ci/mechanism-inside.test.js +5 -0
- package/ci/fixtures/doc-obligations/derive/tag-inside-test-call/candidate/ci/mechanism-inside.test.js +5 -0
- package/ci/fixtures/doc-obligations/derive/tagged-test/base/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/tagged-test/candidate/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/tagged-test-prd/base/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/tagged-test-prd/base/docs/prd/graph.md +20 -0
- package/ci/fixtures/doc-obligations/derive/tagged-test-prd/candidate/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/tagged-test-prd/candidate/docs/prd/graph.md +20 -0
- package/ci/fixtures/doc-obligations/derive/template-literal-tag/base/ci/mechanism-tpl.test.js +4 -0
- package/ci/fixtures/doc-obligations/derive/template-literal-tag/base/docs/prd/graph.md +18 -0
- package/ci/fixtures/doc-obligations/derive/template-literal-tag/candidate/ci/mechanism-tpl.test.js +4 -0
- package/ci/fixtures/doc-obligations/derive/template-literal-tag/candidate/docs/prd/graph.md +18 -0
- package/ci/fixtures/doc-obligations/derive/template-literal-whitespace/base/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/template-literal-whitespace/candidate/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/title-only-rename/base/ci/mechanism-title.test.js +3 -0
- package/ci/fixtures/doc-obligations/derive/title-only-rename/candidate/ci/mechanism-title.test.js +3 -0
- package/ci/fixtures/doc-obligations/derive/title-only-unrelated-computed/base/ci/mechanism-u.test.js +4 -0
- package/ci/fixtures/doc-obligations/derive/title-only-unrelated-computed/candidate/ci/mechanism-u.test.js +4 -0
- package/ci/fixtures/doc-obligations/derive/top-level-no-file-tag/base/ci/mechanism-top.test.js +4 -0
- package/ci/fixtures/doc-obligations/derive/top-level-no-file-tag/candidate/ci/mechanism-top.test.js +4 -0
- package/ci/fixtures/doc-obligations/derive/trailing-comment-test/base/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/trailing-comment-test/base/docs/prd/graph.md +20 -0
- package/ci/fixtures/doc-obligations/derive/trailing-comment-test/candidate/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/trailing-comment-test/candidate/docs/prd/graph.md +20 -0
- package/ci/fixtures/doc-obligations/derive/two-tests-one-file/base/ci/mechanism-two.test.js +6 -0
- package/ci/fixtures/doc-obligations/derive/two-tests-one-file/base/docs/prd/graph.md +24 -0
- package/ci/fixtures/doc-obligations/derive/two-tests-one-file/candidate/ci/mechanism-two.test.js +6 -0
- package/ci/fixtures/doc-obligations/derive/two-tests-one-file/candidate/docs/prd/graph.md +24 -0
- package/ci/fixtures/doc-obligations/derive/typo-context/base/docs/adr/GRPH-007.md +14 -0
- package/ci/fixtures/doc-obligations/derive/typo-context/candidate/docs/adr/GRPH-007.md +14 -0
- package/ci/fixtures/doc-obligations/derive/typo-decision/base/docs/adr/GRPH-007.md +14 -0
- package/ci/fixtures/doc-obligations/derive/typo-decision/candidate/docs/adr/GRPH-007.md +14 -0
- package/ci/fixtures/doc-obligations/derive/unbalanced-statement/base/ci/mechanism-multi.test.js +5 -0
- package/ci/fixtures/doc-obligations/derive/unbalanced-statement/candidate/ci/mechanism-multi.test.js.fixture +4 -0
- package/ci/fixtures/doc-obligations/derive/unknown-adr/base/docs/adr/GRPH-009.md +14 -0
- package/ci/fixtures/doc-obligations/derive/unknown-adr/candidate/docs/adr/GRPH-009.md +12 -0
- package/ci/fixtures/doc-obligations/derive/unsupported-assertion-syntax/base/ci/mechanism-s1.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/unsupported-assertion-syntax/candidate/ci/mechanism-s1.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/whitespace-only-test/base/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/whitespace-only-test/candidate/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/evidence/base/docs/adr/GRPH-007.md +15 -0
- package/ci/fixtures/doc-obligations/evidence/base/docs/prd/PRD-GRPH-001.md +25 -0
- package/ci/fixtures/doc-obligations/evidence/candidate/docs/adr/GRPH-007.md +15 -0
- package/ci/fixtures/doc-obligations/evidence/candidate/docs/prd/PRD-GRPH-001.md +25 -0
- package/ci/mechanism-325-3a-cross.test.js +795 -0
- package/ci/mechanism-325-3a-doc-evidence.test.js +1172 -0
- package/ci/mechanism-325-3a-doc-obligations.test.js +742 -0
- package/ci/mechanism-pr334-haiku.test.js +298 -0
- package/ci/mechanism-pr334-luna.test.js +192 -0
- package/ci/mechanism-pr334-opencode.test.js +313 -0
- package/ci/mechanism-pr334-r2-boundary.test.js +239 -0
- package/ci/mechanism-pr334-r2-resolution.test.js +243 -0
- package/ci/mechanism-pr334-r2-scope.test.js +127 -0
- package/ci/mechanism-pr334-r3-boundary.test.js +356 -0
- package/ci/mechanism-pr334-r3-citations.test.js +205 -0
- package/ci/mechanism-pr334-r3-headings.test.js +268 -0
- package/docs/document-lifecycle.md +57 -1
- package/harness-everything/SKILL.md +1 -1
- package/harness-everything/scripts/doc-evidence.js +471 -0
- package/harness-everything/scripts/doc-lifecycle.js +1 -1
- package/harness-everything/scripts/doc-obligations.js +1571 -0
- package/opencode-plugin/plugin.json +1 -1
- package/package.json +1 -1
- package/plugins/harness-everything/.codex-plugin/plugin.json +1 -1
- package/plugins/harness-everything/plugin.json +1 -1
- package/plugins/harness-everything/skills/harness-everything/SKILL.md +1 -1
- package/plugins/harness-everything/skills/harness-everything/scripts/doc-evidence.js +471 -0
- package/plugins/harness-everything/skills/harness-everything/scripts/doc-lifecycle.js +1 -1
- package/plugins/harness-everything/skills/harness-everything/scripts/doc-obligations.js +1571 -0
- package/sbom.cdx.json +7 -7
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const assert = require('assert');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const crypto = require('crypto');
|
|
6
|
+
const helper = require('./test-helper');
|
|
7
|
+
|
|
8
|
+
const modulePath = path.join(__dirname, '..', 'harness-everything', 'scripts', 'doc-obligations.js');
|
|
9
|
+
const evidencePath = path.join(__dirname, '..', 'harness-everything', 'scripts', 'doc-evidence.js');
|
|
10
|
+
const lifecyclePath = path.join(__dirname, '..', 'harness-everything', 'scripts', 'doc-lifecycle.js');
|
|
11
|
+
let deriveDocFacts, deriveDocObligations, checkDocObligations, lifecycle;
|
|
12
|
+
try {
|
|
13
|
+
({ deriveDocFacts, deriveDocObligations } = require(modulePath));
|
|
14
|
+
({ checkDocObligations } = require(evidencePath));
|
|
15
|
+
lifecycle = require(lifecyclePath);
|
|
16
|
+
} catch (error) {
|
|
17
|
+
helper.check('pr334-opencode. revision mechanism modules are available', false, error.message);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
console.log('\n[pr334-opencode] doc-obligations requirement revision / lifecycle lane...');
|
|
21
|
+
|
|
22
|
+
const CONFIG = {
|
|
23
|
+
governed: [
|
|
24
|
+
{ path: 'docs/prd/**', role: 'prd' },
|
|
25
|
+
{ path: 'docs/adr/**', role: 'adr' },
|
|
26
|
+
{ path: 'docs/reference/**', role: 'reference' }
|
|
27
|
+
],
|
|
28
|
+
domains: ['GRPH'],
|
|
29
|
+
legacy: ['docs/adr/legacy/**'],
|
|
30
|
+
derived: [{ path: 'docs/roadmap/index.md', generator: 'npm run docs:index' }],
|
|
31
|
+
tickets: 'tracker'
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const sha256 = bytes => `sha256:${crypto.createHash('sha256').update(bytes).digest('hex')}`;
|
|
35
|
+
const sortedJson = value => Array.isArray(value)
|
|
36
|
+
? `[${value.map(sortedJson).join(',')}]`
|
|
37
|
+
: value && typeof value === 'object'
|
|
38
|
+
? `{${Object.keys(value).sort().map(key => `${JSON.stringify(key)}:${sortedJson(value[key])}`).join(',')}}`
|
|
39
|
+
: JSON.stringify(value);
|
|
40
|
+
|
|
41
|
+
function prdDoc({ id, revision, previous, previousDigest, acceptance, approvalBy = 'Bob Independent' }) {
|
|
42
|
+
const data = { id, revision, drafted_by: 'Alice Example' };
|
|
43
|
+
if (previous !== undefined) {
|
|
44
|
+
data.previous_revision = previous;
|
|
45
|
+
data.previous_digest = previousDigest;
|
|
46
|
+
}
|
|
47
|
+
const body = `### REQ-GRPH-011: Stable requirement\n\nAcceptance: ${acceptance}\n`;
|
|
48
|
+
const walk = value => {
|
|
49
|
+
const lines = [];
|
|
50
|
+
for (const key of Object.keys(value)) {
|
|
51
|
+
const item = value[key];
|
|
52
|
+
if (item && typeof item === 'object' && !Array.isArray(item)) {
|
|
53
|
+
lines.push(`${key}:`);
|
|
54
|
+
walk(item).forEach(inner => lines.push(` ${inner}`));
|
|
55
|
+
} else if (key === 'revision' || key === 'previous_revision') {
|
|
56
|
+
lines.push(`${key}: ${item}`);
|
|
57
|
+
} else {
|
|
58
|
+
lines.push(`${key}: ${JSON.stringify(item)}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return lines;
|
|
62
|
+
};
|
|
63
|
+
const contentDigest = sha256(Buffer.concat([
|
|
64
|
+
Buffer.from(`${sortedJson(data)}\n`, 'utf8'), Buffer.from(body, 'utf8')
|
|
65
|
+
]));
|
|
66
|
+
const approval = { by: approvalBy, at: '2026-10-17T00:00:00Z', ref: 'https://approvals.example.invalid/PRD-GRPH-001', role: 'human', content_digest: contentDigest };
|
|
67
|
+
const front = walk({ ...data, status: 'effective', effective_at: '2026-10-17T00:02:00Z', approval }).join('\n');
|
|
68
|
+
return `---\n${front}\n---\n${body}`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const r001Text = prdDoc({ id: 'PRD-GRPH-001', revision: 1, acceptance: 'stable v1 behavior.' });
|
|
72
|
+
const r002Descendant = acceptance => prdDoc({ id: 'PRD-GRPH-001', revision: 2, previous: 1, previousDigest: sha256(Buffer.from(r001Text, 'utf8')), acceptance });
|
|
73
|
+
const plainPrd = acceptance => `---\nstatus: "effective"\n---\n### REQ-GRPH-011: Plain requirement\n\nAcceptance: ${acceptance}\n`;
|
|
74
|
+
const renameTest = value => `// REQ-GRPH-011\nassert.equal(run(), ${value});\n`;
|
|
75
|
+
|
|
76
|
+
const BASELINE = 'sha256:pr334-opencode-baseline';
|
|
77
|
+
|
|
78
|
+
function derive(input) {
|
|
79
|
+
return deriveDocFacts({ base: { files: input.base }, candidate: { files: input.candidate }, config: CONFIG, inventory: { complete: true } });
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function factAssertions(setup, run) {
|
|
83
|
+
const { facts } = derive(setup);
|
|
84
|
+
run(facts);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function syncObligation(facts) {
|
|
88
|
+
const obligations = deriveDocObligations(facts);
|
|
89
|
+
const item = obligations.find(obligation => obligation.capability === 'requirement-sync');
|
|
90
|
+
assert.ok(item, 'requirement-sync is in the catalog');
|
|
91
|
+
return item;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function boundPassingEvidence(obligation, candidateFiles) {
|
|
95
|
+
const artifacts = {};
|
|
96
|
+
const records = obligation.scope.paths.map((targetPath, index) => {
|
|
97
|
+
const text = candidateFiles[targetPath];
|
|
98
|
+
assert.ok(typeof text === 'string', `scope path ${targetPath} exists in the candidate for evidence binding`);
|
|
99
|
+
const digest = { digest: sha256(Buffer.from(text, 'utf8')) };
|
|
100
|
+
artifacts[targetPath] = digest;
|
|
101
|
+
return {
|
|
102
|
+
id: `exec-${index}`,
|
|
103
|
+
obligationId: obligation.id,
|
|
104
|
+
command: `npm run verify -- ${targetPath}`,
|
|
105
|
+
provenance: `bound execution record for ${targetPath}`,
|
|
106
|
+
exitCode: 0,
|
|
107
|
+
baseDigest: BASELINE,
|
|
108
|
+
artifactDigests: { [targetPath]: digest.digest },
|
|
109
|
+
paths: [targetPath]
|
|
110
|
+
};
|
|
111
|
+
});
|
|
112
|
+
const outcome = checkDocObligations({
|
|
113
|
+
obligations: [obligation],
|
|
114
|
+
artifacts,
|
|
115
|
+
baseline: { digest: BASELINE },
|
|
116
|
+
executionRecords: records,
|
|
117
|
+
reviewRecords: []
|
|
118
|
+
});
|
|
119
|
+
assert.deepStrictEqual(outcome.diagnostics.filter(item => item.code.endsWith('digest') || item.code === 'missing-path-coverage'), [],
|
|
120
|
+
`evidence binding is accepted for every scope path`);
|
|
121
|
+
return outcome.results[0];
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function checkCase(name, run) {
|
|
125
|
+
try {
|
|
126
|
+
run();
|
|
127
|
+
helper.check(name, true);
|
|
128
|
+
} catch (error) {
|
|
129
|
+
helper.check(name, false, error && error.message ? error.message : String(error));
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function citedEntry(facts, id) {
|
|
134
|
+
const entries = facts.citedReqs.filter(entry => entry && entry.id === id);
|
|
135
|
+
assert.ok(entries.length > 0, `citedReqs reports ${id}: ${JSON.stringify(facts.citedReqs)}`);
|
|
136
|
+
assert.strictEqual(entries.length, 1, `${id} is reported once: ${JSON.stringify(facts.citedReqs)}`);
|
|
137
|
+
return entries[0];
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function mutationEntry(facts, id, file) {
|
|
141
|
+
const entries = (facts.reqMutationNotRevision || []).filter(entry => entry && entry.id === id);
|
|
142
|
+
assert.ok(entries.length > 0, `${id} is flagged as a mutation, not a revision: ${JSON.stringify(facts.reqMutationNotRevision)}`);
|
|
143
|
+
const targeted = entries.filter(entry => !file || entry.file === file);
|
|
144
|
+
assert.ok(targeted.length > 0, `${id} is flagged at ${file || 'some file'}: ${JSON.stringify(facts.reqMutationNotRevision)}`);
|
|
145
|
+
return targeted[0];
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
checkCase('pr334-opencode. control: generated lifecycle docs are accepted as valid revisions with no error diagnostics', () => {
|
|
149
|
+
const docs = [
|
|
150
|
+
docForParse('docs/prd/graph/r001.md', r001Text),
|
|
151
|
+
docForParse('docs/prd/graph/r002.md', r002Descendant('stable v2 behavior.'))
|
|
152
|
+
];
|
|
153
|
+
const analysis = lifecycle.analyzeDocs(docs, CONFIG, { all: true });
|
|
154
|
+
assert.deepStrictEqual(analysis.diagnostics.filter(item => item.severity === 'error'), [],
|
|
155
|
+
`the generated r001/r002 revision chain is lifecycle-valid: ${JSON.stringify(analysis.diagnostics.filter(item => item.severity === 'error'))}`);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
function docForParse(parsedPath, text) {
|
|
159
|
+
const parsed = lifecycle.parseFrontmatter(text);
|
|
160
|
+
return { path: parsedPath, body: parsed.body, frontmatter: parsed.data, error: parsed.error, storedBytes: Buffer.from(text, 'utf8'), legacy: false, role: 'prd' };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
checkCase('pr334-opencode. positive legal successor r001 -> r002: REQ-GRPH-011 is a reqUpdate at r002 and resolves there (fix scope control)', () => {
|
|
164
|
+
const candidate = 'docs/prd/graph/r002.md';
|
|
165
|
+
factAssertions(legalSuccessor(), facts => {
|
|
166
|
+
assert.ok(facts.reqUpdates.some(entry => entry.id === 'REQ-GRPH-011' && entry.file === candidate),
|
|
167
|
+
`the lifecycle successor is an update: ${JSON.stringify(facts.reqUpdates)}`);
|
|
168
|
+
assert.deepStrictEqual(facts.reqMutationNotRevision, [], 'a legal successor is not flagged as a mutation');
|
|
169
|
+
const cited = citedEntry(facts, 'REQ-GRPH-011');
|
|
170
|
+
assert.strictEqual(cited.file, candidate, `the cited request resolves to the successor: ${JSON.stringify(facts.citedReqs)}`);
|
|
171
|
+
assert.strictEqual(cited.resolution, 'resolved', 'the successor is current authority');
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
checkCase('pr334-opencode. positive legal successor with matching bound passing evidence: requirement-sync passes (no over-block from the fix)', () => {
|
|
176
|
+
const setup = legalSuccessor();
|
|
177
|
+
const { facts } = derive(setup);
|
|
178
|
+
const obligation = syncObligation(facts);
|
|
179
|
+
assert.strictEqual(obligation.applicability, 'applicable', 'an assertion change triggers requirement-sync');
|
|
180
|
+
const outcome = boundPassingEvidence(obligation, setup.candidate);
|
|
181
|
+
assert.strictEqual(outcome.outcome, 'pass', `matching evidence over a legal successor passes: ${JSON.stringify(outcome)}`);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
checkCase('pr334-opencode. negative in-place edit of the current effective r002: not a reqUpdate, flagged as req-mutation-not-revision, cited REQ mutated with file null (T2/S2 lane)', () => {
|
|
185
|
+
factAssertions(inPlaceCurrent(), facts => {
|
|
186
|
+
assert.ok(!facts.reqUpdates.some(entry => entry.id === 'REQ-GRPH-011'),
|
|
187
|
+
`an in-place edit is not a legal revision: ${JSON.stringify(facts.reqUpdates)}`);
|
|
188
|
+
mutationEntry(facts, 'REQ-GRPH-011', 'docs/prd/graph/r002.md');
|
|
189
|
+
const cited = citedEntry(facts, 'REQ-GRPH-011');
|
|
190
|
+
assert.strictEqual(cited.file, null, `the mutated requirement has no resolution file: ${JSON.stringify(facts.citedReqs)}`);
|
|
191
|
+
assert.strictEqual(cited.resolution, 'mutated', 'the mutation is unresolved, never resolved');
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
checkCase('pr334-opencode. negative in-place edit of the current effective r002 with matching bound passing evidence: requirement-sync must not pass', () => {
|
|
196
|
+
const setup = inPlaceCurrent();
|
|
197
|
+
const { facts } = derive(setup);
|
|
198
|
+
const obligation = syncObligation(facts);
|
|
199
|
+
assert.strictEqual(obligation.applicability, 'applicable', 'the accompanying assertion change triggers requirement-sync');
|
|
200
|
+
const outcome = boundPassingEvidence(obligation, setup.candidate);
|
|
201
|
+
assert.notStrictEqual(outcome.outcome, 'pass',
|
|
202
|
+
`matching evidence cannot turn an in-place mutation into a legal revision: ${JSON.stringify(outcome)}`);
|
|
203
|
+
assert.ok(outcome.observation === 'unresolved' && outcome.outcome === 'none',
|
|
204
|
+
'the mutation lane stays unresolved with no observed pass');
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
checkCase('pr334-opencode. negative edit to a retained historical r001: not a reqUpdate, cited REQ mutated with file null (historical lane)', () => {
|
|
208
|
+
factAssertions(inPlaceHistorical(), facts => {
|
|
209
|
+
assert.ok(!facts.reqUpdates.some(entry => entry.id === 'REQ-GRPH-011'),
|
|
210
|
+
`a historical edit is not a successor revision: ${JSON.stringify(facts.reqUpdates)}`);
|
|
211
|
+
mutationEntry(facts, 'REQ-GRPH-011', 'docs/prd/graph/r001.md');
|
|
212
|
+
const cited = citedEntry(facts, 'REQ-GRPH-011');
|
|
213
|
+
assert.strictEqual(cited.file, null, `the historical mutation is not current authority: ${JSON.stringify(facts.citedReqs)}`);
|
|
214
|
+
assert.strictEqual(cited.resolution, 'mutated', 'a historical edit is an unresolved mutation');
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
checkCase('pr334-opencode. negative edit to a retained historical r001 with matching bound passing evidence: requirement-sync must not pass', () => {
|
|
219
|
+
const setup = inPlaceHistorical();
|
|
220
|
+
const { facts } = derive(setup);
|
|
221
|
+
const obligation = syncObligation(facts);
|
|
222
|
+
assert.strictEqual(obligation.applicability, 'applicable', 'the accompanying assertion change triggers requirement-sync');
|
|
223
|
+
const outcome = boundPassingEvidence(obligation, setup.candidate);
|
|
224
|
+
assert.notStrictEqual(outcome.outcome, 'pass',
|
|
225
|
+
`matching evidence cannot launder a historical edit: ${JSON.stringify(outcome)}`);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
checkCase('pr334-opencode. negative in-place rewrite of an unnumbered governed PRD that mints a new effective PRD identity: not a reqUpdate and unresolved (finding 4305942766 round: identity laundering)', () => {
|
|
229
|
+
const plainCandidate = prdDoc({ id: 'PRD-GRPH-001', revision: 2, acceptance: 'MUTATED plain behavior.' });
|
|
230
|
+
const setup = {
|
|
231
|
+
base: { 'docs/prd/graph/plain.md': plainPrd('plain v1 behavior.'), 'ci/mechanism-inplace.test.js': renameTest(1) },
|
|
232
|
+
candidate: { 'docs/prd/graph/plain.md': plainCandidate, 'ci/mechanism-inplace.test.js': renameTest(2) }
|
|
233
|
+
};
|
|
234
|
+
factAssertions(setup, facts => {
|
|
235
|
+
assert.ok(!facts.reqUpdates.some(entry => entry.id === 'REQ-GRPH-011'),
|
|
236
|
+
`a freshly minted identity is not a lifecycle successor of the base declaration: ${JSON.stringify(facts.reqUpdates)}`);
|
|
237
|
+
mutationEntry(facts, 'REQ-GRPH-011', 'docs/prd/graph/plain.md');
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
checkCase('pr334-opencode. negative identity laundering with matching bound passing evidence: requirement-sync must not pass and the cited REQ is unresolved', () => {
|
|
242
|
+
const plainCandidate = prdDoc({ id: 'PRD-GRPH-001', revision: 2, acceptance: 'MUTATED plain behavior.' });
|
|
243
|
+
const setup = {
|
|
244
|
+
base: { 'docs/prd/graph/plain.md': plainPrd('plain v1 behavior.'), 'ci/mechanism-inplace.test.js': renameTest(1) },
|
|
245
|
+
candidate: { 'docs/prd/graph/plain.md': plainCandidate, 'ci/mechanism-inplace.test.js': renameTest(2) }
|
|
246
|
+
};
|
|
247
|
+
const { facts } = derive(setup);
|
|
248
|
+
const obligation = syncObligation(facts);
|
|
249
|
+
assert.strictEqual(obligation.applicability, 'applicable', 'the accompanying assertion change triggers requirement-sync');
|
|
250
|
+
const cited = citedEntry(facts, 'REQ-GRPH-011');
|
|
251
|
+
assert.strictEqual(cited.file, null, `a laundered mutation resolves unresolved (previously it resolved): ${JSON.stringify(facts.citedReqs)}`);
|
|
252
|
+
assert.strictEqual(cited.resolution, 'mutated', 'the laundering lane reports the mutation');
|
|
253
|
+
const outcome = boundPassingEvidence(obligation, setup.candidate);
|
|
254
|
+
assert.notStrictEqual(outcome.outcome, 'pass',
|
|
255
|
+
`matching evidence cannot launder an identity-granting in-place rewrite: ${JSON.stringify(outcome)}`);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
checkCase('pr334-opencode. negative duplicate-revision fork of r002: no reqUpdate and the cited REQ stays unresolved (malformed lifecycle lane)', () => {
|
|
259
|
+
const forkText = prdDoc({ id: 'PRD-GRPH-001', revision: 2, previous: 1, previousDigest: sha256(Buffer.from(r001Text, 'utf8')), acceptance: 'forked behavior.' });
|
|
260
|
+
const mainCandidate = r002Descendant('MUTATED main fork behavior.');
|
|
261
|
+
const setup = {
|
|
262
|
+
base: { 'docs/prd/graph/r001.md': r001Text, 'docs/prd/graph/r002.md': r002Descendant('stable v2 behavior.'), 'ci/mechanism-inplace.test.js': renameTest(1) },
|
|
263
|
+
candidate: { 'docs/prd/graph/r001.md': r001Text, 'docs/prd/graph/r002.md': mainCandidate, 'docs/prd/graph/r002-fork.md': forkText, 'ci/mechanism-inplace.test.js': renameTest(2) }
|
|
264
|
+
};
|
|
265
|
+
factAssertions(setup, facts => {
|
|
266
|
+
assert.ok(!facts.reqUpdates.some(entry => entry.id === 'REQ-GRPH-011'),
|
|
267
|
+
`an ambiguous revision chain is not a legal update: ${JSON.stringify(facts.reqUpdates)}`);
|
|
268
|
+
const cited = citedEntry(facts, 'REQ-GRPH-011');
|
|
269
|
+
assert.strictEqual(cited.file, null, `an ambiguous chain has no resolution file: ${JSON.stringify(facts.citedReqs)}`);
|
|
270
|
+
assert.ok(['mutated', 'ambiguous', 'analysis-error'].includes(cited.resolution),
|
|
271
|
+
`an ambiguous chain is explicit unresolved, never resolved: ${cited.resolution}`);
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
checkCase('pr334-opencode. negative r003 successor with a broken previous_digest: not a reqUpdate, requirement unresolved, not a pass (malformed lifecycle lane)', () => {
|
|
276
|
+
const r003Broken = prdDoc({ id: 'PRD-GRPH-001', revision: 3, previous: 2, previousDigest: 'sha256:0000000000000000000000000000000000000000000000000000000000000000', acceptance: 'third generation behavior.' });
|
|
277
|
+
const validR003 = prdDoc({ id: 'PRD-GRPH-001', revision: 3, previous: 2, previousDigest: sha256(Buffer.from(r002Descendant('stable v2 behavior.'), 'utf8')), acceptance: 'stable v3 behavior.' });
|
|
278
|
+
assert.notStrictEqual(r003Broken, validR003, 'the malformed case really carries a broken previous_digest');
|
|
279
|
+
const setup = {
|
|
280
|
+
base: { 'docs/prd/graph/r001.md': r001Text, 'ci/mechanism-inplace.test.js': renameTest(1) },
|
|
281
|
+
candidate: { 'docs/prd/graph/r001.md': r001Text, 'docs/prd/graph/r002.md': r002Descendant('stable v2 behavior.'), 'docs/prd/graph/r003.md': r003Broken, 'ci/mechanism-inplace.test.js': renameTest(2) }
|
|
282
|
+
};
|
|
283
|
+
factAssertions(setup, facts => {
|
|
284
|
+
assert.ok(!facts.reqUpdates.some(entry => entry.id === 'REQ-GRPH-011'),
|
|
285
|
+
`a malformed successor is not a legal update: ${JSON.stringify(facts.reqUpdates)}`);
|
|
286
|
+
const cited = citedEntry(facts, 'REQ-GRPH-011');
|
|
287
|
+
assert.strictEqual(cited.file, null, `a malformed chain has no resolution file: ${JSON.stringify(facts.citedReqs)}`);
|
|
288
|
+
});
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
function legalSuccessor() {
|
|
292
|
+
return {
|
|
293
|
+
base: { 'docs/prd/graph/r001.md': r001Text, 'ci/mechanism-inplace.test.js': renameTest(1) },
|
|
294
|
+
candidate: { 'docs/prd/graph/r001.md': r001Text, 'docs/prd/graph/r002.md': r002Descendant('stable v2 behavior.'), 'ci/mechanism-inplace.test.js': renameTest(2) }
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function inPlaceCurrent() {
|
|
299
|
+
return {
|
|
300
|
+
base: { 'docs/prd/graph/r001.md': r001Text, 'docs/prd/graph/r002.md': r002Descendant('stable v2 behavior.'), 'ci/mechanism-inplace.test.js': renameTest(1) },
|
|
301
|
+
candidate: { 'docs/prd/graph/r001.md': r001Text, 'docs/prd/graph/r002.md': r002Descendant('MUTATED stable behavior.'), 'ci/mechanism-inplace.test.js': renameTest(2) }
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function inPlaceHistorical() {
|
|
306
|
+
const mutatedR001 = prdDoc({ id: 'PRD-GRPH-001', revision: 1, acceptance: 'MUTATED historical behavior.' });
|
|
307
|
+
return {
|
|
308
|
+
base: { 'docs/prd/graph/r001.md': r001Text, 'docs/prd/graph/r002.md': r002Descendant('stable v2 behavior.'), 'ci/mechanism-inplace.test.js': renameTest(1) },
|
|
309
|
+
candidate: { 'docs/prd/graph/r001.md': mutatedR001, 'docs/prd/graph/r002.md': r002Descendant('stable v2 behavior.'), 'ci/mechanism-inplace.test.js': renameTest(2) }
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
helper.finish();
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// PR #334 round 2 — cross-module boundary regression.
|
|
4
|
+
//
|
|
5
|
+
// This suite integrates the two round-2 findings across the module boundary:
|
|
6
|
+
// * finding 4307059315 (doc-obligations requirement-sync scope): an unrelated
|
|
7
|
+
// REQ-citing B test edited comment-only / formatting-only / title-only must
|
|
8
|
+
// neither enter the requirement-sync scope nor block a legitimate pass for a
|
|
9
|
+
// legal A revision + A assertion change. A genuine B assertion edit still
|
|
10
|
+
// joins the scope.
|
|
11
|
+
// * finding 4307059310 (doc-evidence resolution guard): a derived missing /
|
|
12
|
+
// unresolved (duplicate) cited REQ plus a bound, complete, nonzero execution
|
|
13
|
+
// must be unresolved/none with a req-unresolved diagnostic, never
|
|
14
|
+
// observed/fail; a resolved cited REQ plus a bound nonzero execution keeps
|
|
15
|
+
// the observed/fail lane.
|
|
16
|
+
//
|
|
17
|
+
// Unlike the per-module sibling suites, every case here drives the full
|
|
18
|
+
// deriveDocFacts -> deriveDocObligations -> checkDocObligations pipeline using the
|
|
19
|
+
// existing req-revision fixture/config plus the existing unresolved fixtures, so
|
|
20
|
+
// it catches regressions that only appear when the two modules interact.
|
|
21
|
+
|
|
22
|
+
const assert = require('assert');
|
|
23
|
+
const fs = require('fs');
|
|
24
|
+
const path = require('path');
|
|
25
|
+
const crypto = require('crypto');
|
|
26
|
+
const helper = require('./test-helper');
|
|
27
|
+
|
|
28
|
+
const obligationsPath = path.join(__dirname, '..', 'harness-everything', 'scripts', 'doc-obligations.js');
|
|
29
|
+
const evidencePath = path.join(__dirname, '..', 'harness-everything', 'scripts', 'doc-evidence.js');
|
|
30
|
+
const fixtureRoot = path.join(__dirname, 'fixtures', 'doc-obligations', 'derive');
|
|
31
|
+
const CONFIG = JSON.parse(fs.readFileSync(path.join(fixtureRoot, 'config.json'), 'utf8'));
|
|
32
|
+
|
|
33
|
+
let deriveDocFacts, deriveDocObligations, checkDocObligations;
|
|
34
|
+
try {
|
|
35
|
+
({ deriveDocFacts, deriveDocObligations } = require(obligationsPath));
|
|
36
|
+
({ checkDocObligations } = require(evidencePath));
|
|
37
|
+
} catch (error) {
|
|
38
|
+
helper.check('pr334-r2-boundary. derive/evidence modules are available', false, error.message);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
console.log('\n[pr334-r2-boundary] cross-module derive -> obligations -> evidence regression...');
|
|
42
|
+
|
|
43
|
+
const BASELINE = 'sha256:pr334-r2-boundary-baseline';
|
|
44
|
+
const TEST_A = 'ci/mechanism-lint.test.js'; // legal-revision fixture test citing REQ-GRPH-011
|
|
45
|
+
const TEST_B = 'ci/mechanism-pr334-r2-b.test.js'; // unrelated B test citing REQ-GRPH-010
|
|
46
|
+
const REVISION_PRD = 'docs/prd/graph/r002.md';
|
|
47
|
+
|
|
48
|
+
function sha256(text) {
|
|
49
|
+
return `sha256:${crypto.createHash('sha256').update(text, 'utf8').digest('hex')}`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function readTree(dir) {
|
|
53
|
+
const files = {};
|
|
54
|
+
const walk = current => {
|
|
55
|
+
if (!fs.existsSync(current)) return;
|
|
56
|
+
for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
|
|
57
|
+
const full = path.join(current, entry.name);
|
|
58
|
+
if (entry.isDirectory()) walk(full);
|
|
59
|
+
else files[path.relative(dir, full).split(path.sep).join('/')] = fs.readFileSync(full, 'utf8');
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
walk(dir);
|
|
63
|
+
return files;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function fixtureTree(name, side) {
|
|
67
|
+
return readTree(path.join(fixtureRoot, name, side));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Builds the legal-revision fixture tree with an explicit unrelated B test.
|
|
71
|
+
function withB(beforeB, afterB) {
|
|
72
|
+
const base = fixtureTree('req-revision', 'base');
|
|
73
|
+
const candidate = fixtureTree('req-revision', 'candidate');
|
|
74
|
+
base[TEST_B] = beforeB;
|
|
75
|
+
candidate[TEST_B] = afterB;
|
|
76
|
+
return { base, candidate };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function derivePipeline(baseFiles, candidateFiles) {
|
|
80
|
+
const { facts } = deriveDocFacts({
|
|
81
|
+
base: { files: baseFiles },
|
|
82
|
+
candidate: { files: candidateFiles },
|
|
83
|
+
config: CONFIG,
|
|
84
|
+
inventory: { complete: true }
|
|
85
|
+
});
|
|
86
|
+
const obligation = deriveDocObligations(facts).find(item => item.capability === 'requirement-sync');
|
|
87
|
+
assert.ok(obligation, 'requirement-sync is in the derived obligation catalog');
|
|
88
|
+
return { facts, obligation };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Binds a bound, complete execution record to every requested candidate path and
|
|
92
|
+
// runs the derived obligation through the evidence checker.
|
|
93
|
+
function checkWithEvidence(obligation, candidateFiles, bindPaths, exitCode) {
|
|
94
|
+
const artifacts = {};
|
|
95
|
+
const executionRecords = bindPaths.map((targetPath, index) => {
|
|
96
|
+
const text = candidateFiles[targetPath];
|
|
97
|
+
assert.strictEqual(typeof text, 'string', `candidate text exists for evidence binding to ${targetPath}`);
|
|
98
|
+
const digest = sha256(text);
|
|
99
|
+
artifacts[targetPath] = { digest };
|
|
100
|
+
return {
|
|
101
|
+
id: `exec-${index}`,
|
|
102
|
+
obligationId: obligation.id,
|
|
103
|
+
command: `npm run verify -- ${targetPath}`,
|
|
104
|
+
provenance: `bound execution record for ${targetPath}`,
|
|
105
|
+
exitCode,
|
|
106
|
+
baseDigest: BASELINE,
|
|
107
|
+
artifactDigests: { [targetPath]: digest },
|
|
108
|
+
paths: [targetPath]
|
|
109
|
+
};
|
|
110
|
+
});
|
|
111
|
+
return checkDocObligations({
|
|
112
|
+
obligations: [obligation],
|
|
113
|
+
artifacts,
|
|
114
|
+
baseline: { digest: BASELINE },
|
|
115
|
+
executionRecords,
|
|
116
|
+
reviewRecords: []
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function resultOf(output) {
|
|
121
|
+
return output.results[0];
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function hasDiagnostic(output, code) {
|
|
125
|
+
return output.diagnostics.some(item => item.code === code);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function checkCase(name, body) {
|
|
129
|
+
try {
|
|
130
|
+
body();
|
|
131
|
+
helper.check(name, true);
|
|
132
|
+
} catch (error) {
|
|
133
|
+
helper.check(name, false, error && error.message ? error.message : String(error));
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// A legitimate A revision + assertion change must pass on A evidence alone; a
|
|
138
|
+
// non-substantive B edit must not add B to the scope or force B-only coverage.
|
|
139
|
+
function assertNonSubstantiveBKeepsAEvidencePassing(label, beforeB, afterB) {
|
|
140
|
+
const { base, candidate } = withB(beforeB, afterB);
|
|
141
|
+
const { facts, obligation } = derivePipeline(base, candidate);
|
|
142
|
+
|
|
143
|
+
assert.strictEqual(facts.requirementSyncTrigger, 'assertion-change', 'the A assertion change triggers requirement-sync');
|
|
144
|
+
assert.ok(facts.reqUpdates.some(item => item.id === 'REQ-GRPH-011' && item.file === REVISION_PRD),
|
|
145
|
+
`the legal A successor is delivered: ${JSON.stringify(facts.reqUpdates)}`);
|
|
146
|
+
assert.deepStrictEqual(facts.citedReqs.map(item => item.id), ['REQ-GRPH-011'], 'only A contributes a cited REQ');
|
|
147
|
+
assert.ok(!facts.reqCitingTests.includes(TEST_B), `${label} B is not a contributing test path`);
|
|
148
|
+
assert.ok(!obligation.scope.paths.includes(TEST_B), `${label} B is excluded from the requirement-sync scope`);
|
|
149
|
+
|
|
150
|
+
const output = checkWithEvidence(obligation, candidate, [TEST_A, REVISION_PRD], 0);
|
|
151
|
+
assert.strictEqual(resultOf(output).outcome, 'pass',
|
|
152
|
+
`${label} B leaves the legal A revision pass intact: ${JSON.stringify(resultOf(output))}`);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
checkCase('pr334-r2-boundary. A assertion + legal A revision with a comment-only unrelated B edit: B stays out of scope and A evidence passes', () => {
|
|
156
|
+
assertNonSubstantiveBKeepsAEvidencePassing(
|
|
157
|
+
'comment-only',
|
|
158
|
+
'// REQ-GRPH-010\n// old note\nassert.equal(runB(), 1);\n',
|
|
159
|
+
'// REQ-GRPH-010\n// revised note\nassert.equal(runB(), 1);\n'
|
|
160
|
+
);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
checkCase('pr334-r2-boundary. title-only unrelated B edit stays out of scope and keeps the legal A revision pass', () => {
|
|
164
|
+
assertNonSubstantiveBKeepsAEvidencePassing(
|
|
165
|
+
'title-only',
|
|
166
|
+
"test('REQ-GRPH-010 old title', () => { assert.equal(runB(), 1); });\n",
|
|
167
|
+
"test('REQ-GRPH-010 renamed title', () => { assert.equal(runB(), 1); });\n"
|
|
168
|
+
);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
checkCase('pr334-r2-boundary. formatting-only unrelated B edit stays out of scope and keeps the legal A revision pass', () => {
|
|
172
|
+
assertNonSubstantiveBKeepsAEvidencePassing(
|
|
173
|
+
'formatting-only',
|
|
174
|
+
'// REQ-GRPH-010\nassert.equal(runB(), 1);\n',
|
|
175
|
+
'// REQ-GRPH-010\nassert.equal( runB( ), 1 );\n'
|
|
176
|
+
);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
checkCase('pr334-r2-boundary. a genuine unrelated B assertion edit joins the scope and needs B-bound evidence (positive control)', () => {
|
|
180
|
+
const { base, candidate } = withB(
|
|
181
|
+
'// REQ-GRPH-010\nassert.equal(runB(), 1);\n',
|
|
182
|
+
'// REQ-GRPH-010\nassert.equal(runB(), 2);\n'
|
|
183
|
+
);
|
|
184
|
+
const { facts, obligation } = derivePipeline(base, candidate);
|
|
185
|
+
|
|
186
|
+
assert.deepStrictEqual(facts.citedReqs.map(item => item.id), ['REQ-GRPH-010', 'REQ-GRPH-011'],
|
|
187
|
+
'both changed assertions contribute their own requirement IDs');
|
|
188
|
+
assert.ok(facts.reqCitingTests.includes(TEST_B), 'a genuine B assertion edit is a contributing test path');
|
|
189
|
+
assert.ok(obligation.scope.paths.includes(TEST_B), `a genuine B assertion edit is scoped: ${obligation.scope.paths}`);
|
|
190
|
+
|
|
191
|
+
const aOnly = checkWithEvidence(obligation, candidate, [TEST_A, REVISION_PRD], 0);
|
|
192
|
+
assert.notStrictEqual(resultOf(aOnly).outcome, 'pass', 'A-only evidence is insufficient once B genuinely joins the scope');
|
|
193
|
+
|
|
194
|
+
const full = checkWithEvidence(obligation, candidate, obligation.scope.paths, 0);
|
|
195
|
+
assert.strictEqual(resultOf(full).outcome, 'pass', `full scope evidence passes: ${JSON.stringify(resultOf(full))}`);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
checkCase('pr334-r2-boundary. derived missing cited REQ with a bound complete nonzero execution: unresolved/none, never observed fail', () => {
|
|
199
|
+
const { facts, obligation } = derivePipeline(fixtureTree('orphan-req-test', 'base'), fixtureTree('orphan-req-test', 'candidate'));
|
|
200
|
+
assert.strictEqual(obligation.applicability, 'applicable', 'the assertion change triggers requirement-sync');
|
|
201
|
+
assert.deepStrictEqual(facts.citedReqs.map(item => item.id), ['REQ-GRPH-099'], 'the orphan citation is derived');
|
|
202
|
+
assert.strictEqual(facts.citedReqs[0].resolution, 'missing', 'the orphan REQ is missing, not resolved');
|
|
203
|
+
|
|
204
|
+
const output = checkWithEvidence(obligation, fixtureTree('orphan-req-test', 'candidate'), obligation.scope.paths, 1);
|
|
205
|
+
const result = resultOf(output);
|
|
206
|
+
assert.strictEqual(result.outcome, 'none', 'a missing cited REQ with a bound failing execution is never observed fail');
|
|
207
|
+
assert.strictEqual(result.observation, 'unresolved', 'the missing REQ leaves the obligation unresolved');
|
|
208
|
+
assert.ok(hasDiagnostic(output, 'req-unresolved'), 'the missing REQ is named in a req-unresolved diagnostic');
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
checkCase('pr334-r2-boundary. derived duplicate/unresolved cited REQ with a bound complete nonzero execution: unresolved/none, never observed fail', () => {
|
|
212
|
+
const { facts, obligation } = derivePipeline(fixtureTree('dup-req-id', 'base'), fixtureTree('dup-req-id', 'candidate'));
|
|
213
|
+
assert.strictEqual(obligation.applicability, 'applicable', 'the assertion change triggers requirement-sync');
|
|
214
|
+
assert.deepStrictEqual(facts.citedReqs.map(item => item.id), ['REQ-GRPH-001'], 'the duplicated citation is derived once');
|
|
215
|
+
assert.notStrictEqual(facts.citedReqs[0].resolution, 'resolved', 'a duplicate declaration never resolves');
|
|
216
|
+
assert.strictEqual(facts.citedReqs[0].file, null, 'a duplicate declaration has no resolution file');
|
|
217
|
+
|
|
218
|
+
const output = checkWithEvidence(obligation, fixtureTree('dup-req-id', 'candidate'), obligation.scope.paths, 1);
|
|
219
|
+
const result = resultOf(output);
|
|
220
|
+
assert.strictEqual(result.outcome, 'none', 'a duplicate/unresolved cited REQ with a bound failing execution is never observed fail');
|
|
221
|
+
assert.strictEqual(result.observation, 'unresolved', 'the unresolved REQ leaves the obligation unresolved');
|
|
222
|
+
assert.ok(hasDiagnostic(output, 'req-unresolved'), 'the unresolved REQ is named in a req-unresolved diagnostic');
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
checkCase('pr334-r2-boundary. resolved cited REQ with a bound complete nonzero execution keeps observed fail (positive control)', () => {
|
|
226
|
+
const base = fixtureTree('req-revision', 'base');
|
|
227
|
+
const candidate = fixtureTree('req-revision', 'candidate');
|
|
228
|
+
const { facts, obligation } = derivePipeline(base, candidate);
|
|
229
|
+
assert.strictEqual(facts.citedReqs[0].resolution, 'resolved', 'the legal successor resolves the cited REQ');
|
|
230
|
+
assert.strictEqual(facts.citedReqs[0].file, REVISION_PRD, 'the cited REQ resolves to the successor');
|
|
231
|
+
|
|
232
|
+
const output = checkWithEvidence(obligation, candidate, obligation.scope.paths, 1);
|
|
233
|
+
const result = resultOf(output);
|
|
234
|
+
assert.strictEqual(result.observation, 'observed', 'a resolved known identity keeps the failing-execution lane');
|
|
235
|
+
assert.strictEqual(result.outcome, 'fail', 'a resolved cited REQ with a bound nonzero execution stays observed fail');
|
|
236
|
+
assert.ok(!hasDiagnostic(output, 'req-unresolved'), 'a resolved REQ is never diagnosed unresolved');
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
helper.finish();
|