docassert 0.6.0__tar.gz → 0.7.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. {docassert-0.6.0/docassert.egg-info → docassert-0.7.0}/PKG-INFO +4 -1
  2. {docassert-0.6.0 → docassert-0.7.0}/README.md +3 -0
  3. {docassert-0.6.0 → docassert-0.7.0}/docassert/__init__.py +1 -1
  4. {docassert-0.6.0 → docassert-0.7.0}/docassert/_data/criteria/adr.criteria.yaml +6 -2
  5. {docassert-0.6.0 → docassert-0.7.0}/docassert/_data/criteria/benefits-realization.criteria.yaml +6 -2
  6. {docassert-0.6.0 → docassert-0.7.0}/docassert/_data/criteria/brd.criteria.yaml +5 -1
  7. {docassert-0.6.0 → docassert-0.7.0}/docassert/_data/criteria/business-case.criteria.yaml +5 -1
  8. {docassert-0.6.0 → docassert-0.7.0}/docassert/_data/criteria/charter.criteria.yaml +7 -3
  9. {docassert-0.6.0 → docassert-0.7.0}/docassert/_data/criteria/data-migration-plan.criteria.yaml +6 -2
  10. {docassert-0.6.0 → docassert-0.7.0}/docassert/_data/criteria/frnfr.criteria.yaml +5 -1
  11. {docassert-0.6.0 → docassert-0.7.0}/docassert/_data/criteria/hypercare-plan.criteria.yaml +6 -2
  12. {docassert-0.6.0 → docassert-0.7.0}/docassert/_data/criteria/post-implementation-review.criteria.yaml +5 -1
  13. {docassert-0.6.0 → docassert-0.7.0}/docassert/_data/criteria/prd.criteria.yaml +5 -1
  14. {docassert-0.6.0 → docassert-0.7.0}/docassert/_data/criteria/project.criteria.yaml +5 -1
  15. {docassert-0.6.0 → docassert-0.7.0}/docassert/_data/criteria/qa-test-plan.criteria.yaml +6 -2
  16. {docassert-0.6.0 → docassert-0.7.0}/docassert/_data/criteria/raci-stakeholder.criteria.yaml +6 -2
  17. {docassert-0.6.0 → docassert-0.7.0}/docassert/_data/criteria/release-cutover-plan.criteria.yaml +6 -2
  18. {docassert-0.6.0 → docassert-0.7.0}/docassert/_data/criteria/risk-register.criteria.yaml +6 -2
  19. {docassert-0.6.0 → docassert-0.7.0}/docassert/_data/criteria/rollback-plan.criteria.yaml +6 -2
  20. {docassert-0.6.0 → docassert-0.7.0}/docassert/_data/criteria/runbook.criteria.yaml +6 -2
  21. {docassert-0.6.0 → docassert-0.7.0}/docassert/_data/criteria/status-report.criteria.yaml +6 -2
  22. {docassert-0.6.0 → docassert-0.7.0}/docassert/_data/criteria/test-cases.criteria.yaml +5 -1
  23. {docassert-0.6.0 → docassert-0.7.0}/docassert/_data/criteria/user-story.criteria.yaml +6 -2
  24. {docassert-0.6.0 → docassert-0.7.0}/docassert/structural.py +51 -9
  25. {docassert-0.6.0 → docassert-0.7.0/docassert.egg-info}/PKG-INFO +4 -1
  26. {docassert-0.6.0 → docassert-0.7.0}/docassert.egg-info/SOURCES.txt +1 -0
  27. docassert-0.7.0/tests/test_draft_relaxation.py +138 -0
  28. {docassert-0.6.0 → docassert-0.7.0}/LICENSE +0 -0
  29. {docassert-0.6.0 → docassert-0.7.0}/NOTICE +0 -0
  30. {docassert-0.6.0 → docassert-0.7.0}/docassert/__main__.py +0 -0
  31. {docassert-0.6.0 → docassert-0.7.0}/docassert/_data/consistency.yaml +0 -0
  32. {docassert-0.6.0 → docassert-0.7.0}/docassert/_data/profiles/agile-delivery.yaml +0 -0
  33. {docassert-0.6.0 → docassert-0.7.0}/docassert/_data/profiles/lean-startup.yaml +0 -0
  34. {docassert-0.6.0 → docassert-0.7.0}/docassert/_data/profiles/regulated-industry.yaml +0 -0
  35. {docassert-0.6.0 → docassert-0.7.0}/docassert/_data/schema/adr.schema.json +0 -0
  36. {docassert-0.6.0 → docassert-0.7.0}/docassert/_data/schema/benefits-realization.schema.json +0 -0
  37. {docassert-0.6.0 → docassert-0.7.0}/docassert/_data/schema/brd.schema.json +0 -0
  38. {docassert-0.6.0 → docassert-0.7.0}/docassert/_data/schema/business-case.schema.json +0 -0
  39. {docassert-0.6.0 → docassert-0.7.0}/docassert/_data/schema/charter.schema.json +0 -0
  40. {docassert-0.6.0 → docassert-0.7.0}/docassert/_data/schema/data-migration-plan.schema.json +0 -0
  41. {docassert-0.6.0 → docassert-0.7.0}/docassert/_data/schema/frnfr.schema.json +0 -0
  42. {docassert-0.6.0 → docassert-0.7.0}/docassert/_data/schema/hypercare-plan.schema.json +0 -0
  43. {docassert-0.6.0 → docassert-0.7.0}/docassert/_data/schema/post-implementation-review.schema.json +0 -0
  44. {docassert-0.6.0 → docassert-0.7.0}/docassert/_data/schema/prd.schema.json +0 -0
  45. {docassert-0.6.0 → docassert-0.7.0}/docassert/_data/schema/project.schema.json +0 -0
  46. {docassert-0.6.0 → docassert-0.7.0}/docassert/_data/schema/qa-test-plan.schema.json +0 -0
  47. {docassert-0.6.0 → docassert-0.7.0}/docassert/_data/schema/raci-stakeholder.schema.json +0 -0
  48. {docassert-0.6.0 → docassert-0.7.0}/docassert/_data/schema/release-cutover-plan.schema.json +0 -0
  49. {docassert-0.6.0 → docassert-0.7.0}/docassert/_data/schema/risk-register.schema.json +0 -0
  50. {docassert-0.6.0 → docassert-0.7.0}/docassert/_data/schema/rollback-plan.schema.json +0 -0
  51. {docassert-0.6.0 → docassert-0.7.0}/docassert/_data/schema/runbook.schema.json +0 -0
  52. {docassert-0.6.0 → docassert-0.7.0}/docassert/_data/schema/status-report.schema.json +0 -0
  53. {docassert-0.6.0 → docassert-0.7.0}/docassert/_data/schema/test-cases.schema.json +0 -0
  54. {docassert-0.6.0 → docassert-0.7.0}/docassert/_data/schema/user-story.schema.json +0 -0
  55. {docassert-0.6.0 → docassert-0.7.0}/docassert/_data/skills/doc-to-pmo/SKILL.md +0 -0
  56. {docassert-0.6.0 → docassert-0.7.0}/docassert/_data/templates/adr.template.md +0 -0
  57. {docassert-0.6.0 → docassert-0.7.0}/docassert/_data/templates/benefits-realization.template.md +0 -0
  58. {docassert-0.6.0 → docassert-0.7.0}/docassert/_data/templates/brd.template.md +0 -0
  59. {docassert-0.6.0 → docassert-0.7.0}/docassert/_data/templates/business-case.template.md +0 -0
  60. {docassert-0.6.0 → docassert-0.7.0}/docassert/_data/templates/charter.template.md +0 -0
  61. {docassert-0.6.0 → docassert-0.7.0}/docassert/_data/templates/data-migration-plan.template.md +0 -0
  62. {docassert-0.6.0 → docassert-0.7.0}/docassert/_data/templates/frnfr.template.md +0 -0
  63. {docassert-0.6.0 → docassert-0.7.0}/docassert/_data/templates/hypercare-plan.template.md +0 -0
  64. {docassert-0.6.0 → docassert-0.7.0}/docassert/_data/templates/post-implementation-review.template.md +0 -0
  65. {docassert-0.6.0 → docassert-0.7.0}/docassert/_data/templates/prd.template.md +0 -0
  66. {docassert-0.6.0 → docassert-0.7.0}/docassert/_data/templates/project.template.md +0 -0
  67. {docassert-0.6.0 → docassert-0.7.0}/docassert/_data/templates/qa-test-plan.template.md +0 -0
  68. {docassert-0.6.0 → docassert-0.7.0}/docassert/_data/templates/raci-stakeholder.template.md +0 -0
  69. {docassert-0.6.0 → docassert-0.7.0}/docassert/_data/templates/release-cutover-plan.template.md +0 -0
  70. {docassert-0.6.0 → docassert-0.7.0}/docassert/_data/templates/risk-register.template.md +0 -0
  71. {docassert-0.6.0 → docassert-0.7.0}/docassert/_data/templates/rollback-plan.template.md +0 -0
  72. {docassert-0.6.0 → docassert-0.7.0}/docassert/_data/templates/runbook.template.md +0 -0
  73. {docassert-0.6.0 → docassert-0.7.0}/docassert/_data/templates/status-report.template.md +0 -0
  74. {docassert-0.6.0 → docassert-0.7.0}/docassert/_data/templates/test-cases.template.md +0 -0
  75. {docassert-0.6.0 → docassert-0.7.0}/docassert/_data/templates/user-story.template.md +0 -0
  76. {docassert-0.6.0 → docassert-0.7.0}/docassert/cli.py +0 -0
  77. {docassert-0.6.0 → docassert-0.7.0}/docassert/config.py +0 -0
  78. {docassert-0.6.0 → docassert-0.7.0}/docassert/consistency.py +0 -0
  79. {docassert-0.6.0 → docassert-0.7.0}/docassert/extract.py +0 -0
  80. {docassert-0.6.0 → docassert-0.7.0}/docassert/graph.py +0 -0
  81. {docassert-0.6.0 → docassert-0.7.0}/docassert/loader.py +0 -0
  82. {docassert-0.6.0 → docassert-0.7.0}/docassert/models.py +0 -0
  83. {docassert-0.6.0 → docassert-0.7.0}/docassert/profiles.py +0 -0
  84. {docassert-0.6.0 → docassert-0.7.0}/docassert/projects.py +0 -0
  85. {docassert-0.6.0 → docassert-0.7.0}/docassert/report.py +0 -0
  86. {docassert-0.6.0 → docassert-0.7.0}/docassert/rtm.py +0 -0
  87. {docassert-0.6.0 → docassert-0.7.0}/docassert/scaffold.py +0 -0
  88. {docassert-0.6.0 → docassert-0.7.0}/docassert/semantic.py +0 -0
  89. {docassert-0.6.0 → docassert-0.7.0}/docassert/status.py +0 -0
  90. {docassert-0.6.0 → docassert-0.7.0}/docassert.egg-info/dependency_links.txt +0 -0
  91. {docassert-0.6.0 → docassert-0.7.0}/docassert.egg-info/entry_points.txt +0 -0
  92. {docassert-0.6.0 → docassert-0.7.0}/docassert.egg-info/requires.txt +0 -0
  93. {docassert-0.6.0 → docassert-0.7.0}/docassert.egg-info/top_level.txt +0 -0
  94. {docassert-0.6.0 → docassert-0.7.0}/pyproject.toml +0 -0
  95. {docassert-0.6.0 → docassert-0.7.0}/setup.cfg +0 -0
  96. {docassert-0.6.0 → docassert-0.7.0}/tests/test_badge.py +0 -0
  97. {docassert-0.6.0 → docassert-0.7.0}/tests/test_config.py +0 -0
  98. {docassert-0.6.0 → docassert-0.7.0}/tests/test_consistency.py +0 -0
  99. {docassert-0.6.0 → docassert-0.7.0}/tests/test_defects.py +0 -0
  100. {docassert-0.6.0 → docassert-0.7.0}/tests/test_extract.py +0 -0
  101. {docassert-0.6.0 → docassert-0.7.0}/tests/test_graph.py +0 -0
  102. {docassert-0.6.0 → docassert-0.7.0}/tests/test_json_report.py +0 -0
  103. {docassert-0.6.0 → docassert-0.7.0}/tests/test_kinds_delivery.py +0 -0
  104. {docassert-0.6.0 → docassert-0.7.0}/tests/test_kinds_governance.py +0 -0
  105. {docassert-0.6.0 → docassert-0.7.0}/tests/test_kinds_operate.py +0 -0
  106. {docassert-0.6.0 → docassert-0.7.0}/tests/test_kinds_reporting.py +0 -0
  107. {docassert-0.6.0 → docassert-0.7.0}/tests/test_profiles.py +0 -0
  108. {docassert-0.6.0 → docassert-0.7.0}/tests/test_projects.py +0 -0
  109. {docassert-0.6.0 → docassert-0.7.0}/tests/test_scaffold.py +0 -0
  110. {docassert-0.6.0 → docassert-0.7.0}/tests/test_status.py +0 -0
  111. {docassert-0.6.0 → docassert-0.7.0}/tests/test_structural.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: docassert
3
- Version: 0.6.0
3
+ Version: 0.7.0
4
4
  Summary: Unit testing for business documents — validate structured Markdown docs against a configurable audit standard.
5
5
  Author: C4G Enterprises Inc.
6
6
  License: Apache-2.0
@@ -117,6 +117,9 @@ kind is adding a trio — no code for the common cases.
117
117
  - **Structural — deterministic, blocking.** Required fields and sections,
118
118
  measurable success criteria, risks with owner + mitigation, resolving
119
119
  references, unique ids. Plain Python, reliable enough to gate a merge.
120
+ Within this tier, *integrity* checks (malformed items, bad types, duplicate
121
+ ids) block at any status, while *completeness* checks relax to advisory on
122
+ `status: draft` and gate once a document is proposed — WIP is never punished.
120
123
  - **Semantic — AI-graded, advisory.** Scored via the Anthropic API and posted to
121
124
  the PR — never blocking. Set `ANTHROPIC_API_KEY` to enable; skipped otherwise.
122
125
 
@@ -77,6 +77,9 @@ kind is adding a trio — no code for the common cases.
77
77
  - **Structural — deterministic, blocking.** Required fields and sections,
78
78
  measurable success criteria, risks with owner + mitigation, resolving
79
79
  references, unique ids. Plain Python, reliable enough to gate a merge.
80
+ Within this tier, *integrity* checks (malformed items, bad types, duplicate
81
+ ids) block at any status, while *completeness* checks relax to advisory on
82
+ `status: draft` and gate once a document is proposed — WIP is never punished.
80
83
  - **Semantic — AI-graded, advisory.** Scored via the Anthropic API and posted to
81
84
  the PR — never blocking. Set `ANTHROPIC_API_KEY` to enable; skipped otherwise.
82
85
 
@@ -5,4 +5,4 @@ standard: deterministic structural checks that gate a merge, plus optional
5
5
  AI-graded semantic checks that advise.
6
6
  """
7
7
 
8
- __version__ = "0.6.0"
8
+ __version__ = "0.7.0"
@@ -18,9 +18,13 @@ checks:
18
18
  type: structural
19
19
  blocking: true
20
20
  description: Frontmatter is valid against schema/adr.schema.json.
21
+ - id: frontmatter-complete
22
+ type: structural
23
+ blocking: once-proposed
24
+ description: Every schema-required frontmatter field is present (advisory while draft).
21
25
  - id: required-sections
22
26
  type: structural
23
- blocking: true
27
+ blocking: once-proposed
24
28
  description: Every required section is present and non-empty.
25
29
  - id: items-well-formed
26
30
  type: structural
@@ -28,7 +32,7 @@ checks:
28
32
  description: Every decision is a valid **ADR-###** item.
29
33
  - id: adr-items-have-status
30
34
  type: structural
31
- blocking: true
35
+ blocking: once-proposed
32
36
  description: Every decision declares a valid Status (proposed | accepted | superseded | deprecated | rejected).
33
37
  - id: unique-id
34
38
  type: structural
@@ -16,13 +16,17 @@ checks:
16
16
  type: structural
17
17
  blocking: true
18
18
  description: Frontmatter is valid against schema/benefits-realization.schema.json.
19
+ - id: frontmatter-complete
20
+ type: structural
21
+ blocking: once-proposed
22
+ description: Every schema-required frontmatter field is present (advisory while draft).
19
23
  - id: required-sections
20
24
  type: structural
21
- blocking: true
25
+ blocking: once-proposed
22
26
  description: Every required section is present and non-empty.
23
27
  - id: measurable-items
24
28
  type: structural
25
- blocking: true
29
+ blocking: once-proposed
26
30
  description: Every benefit states a measurable target.
27
31
  - id: unique-id
28
32
  type: structural
@@ -16,9 +16,13 @@ checks:
16
16
  type: structural
17
17
  blocking: true
18
18
  description: Frontmatter is valid against schema/brd.schema.json.
19
+ - id: frontmatter-complete
20
+ type: structural
21
+ blocking: once-proposed
22
+ description: Every schema-required frontmatter field is present (advisory while draft).
19
23
  - id: required-sections
20
24
  type: structural
21
- blocking: true
25
+ blocking: once-proposed
22
26
  description: Every required section is present and non-empty.
23
27
  - id: items-well-formed
24
28
  type: structural
@@ -13,9 +13,13 @@ checks:
13
13
  type: structural
14
14
  blocking: true
15
15
  description: Frontmatter is valid against schema/business-case.schema.json.
16
+ - id: frontmatter-complete
17
+ type: structural
18
+ blocking: once-proposed
19
+ description: Every schema-required frontmatter field is present (advisory while draft).
16
20
  - id: required-sections
17
21
  type: structural
18
- blocking: true
22
+ blocking: once-proposed
19
23
  description: Every required section is present and non-empty.
20
24
  - id: unique-id
21
25
  type: structural
@@ -26,19 +26,23 @@ checks:
26
26
  blocking: true
27
27
  description: Frontmatter is present and valid against schema/charter.schema.json.
28
28
 
29
+ - id: frontmatter-complete
30
+ type: structural
31
+ blocking: once-proposed
32
+ description: Every schema-required frontmatter field is present (advisory while draft).
29
33
  - id: required-sections
30
34
  type: structural
31
- blocking: true
35
+ blocking: once-proposed
32
36
  description: Every required section is present and non-empty.
33
37
 
34
38
  - id: measurable-success-criteria
35
39
  type: structural
36
- blocking: true
40
+ blocking: once-proposed
37
41
  description: Every Success Criteria bullet states a measurable threshold (number, %, currency, or date).
38
42
 
39
43
  - id: risks-have-owner-and-mitigation
40
44
  type: structural
41
- blocking: true
45
+ blocking: once-proposed
42
46
  description: Every Risks bullet names an Owner and a Mitigation.
43
47
 
44
48
  - id: dates-consistent
@@ -14,13 +14,17 @@ checks:
14
14
  type: structural
15
15
  blocking: true
16
16
  description: Frontmatter is valid against schema/data-migration-plan.schema.json.
17
+ - id: frontmatter-complete
18
+ type: structural
19
+ blocking: once-proposed
20
+ description: Every schema-required frontmatter field is present (advisory while draft).
17
21
  - id: required-sections
18
22
  type: structural
19
- blocking: true
23
+ blocking: once-proposed
20
24
  description: Every required section is present and non-empty.
21
25
  - id: mapping-table
22
26
  type: structural
23
- blocking: true
27
+ blocking: once-proposed
24
28
  description: The Field Mapping section contains a mapping table.
25
29
  - id: unique-id
26
30
  type: structural
@@ -17,9 +17,13 @@ checks:
17
17
  type: structural
18
18
  blocking: true
19
19
  description: Frontmatter is valid against schema/frnfr.schema.json.
20
+ - id: frontmatter-complete
21
+ type: structural
22
+ blocking: once-proposed
23
+ description: Every schema-required frontmatter field is present (advisory while draft).
20
24
  - id: required-sections
21
25
  type: structural
22
- blocking: true
26
+ blocking: once-proposed
23
27
  description: Every required section is present and non-empty.
24
28
  - id: items-well-formed
25
29
  type: structural
@@ -13,13 +13,17 @@ checks:
13
13
  type: structural
14
14
  blocking: true
15
15
  description: Frontmatter is valid against schema/hypercare-plan.schema.json.
16
+ - id: frontmatter-complete
17
+ type: structural
18
+ blocking: once-proposed
19
+ description: Every schema-required frontmatter field is present (advisory while draft).
16
20
  - id: required-sections
17
21
  type: structural
18
- blocking: true
22
+ blocking: once-proposed
19
23
  description: Every required section is present and non-empty.
20
24
  - id: measurable-exit-criteria
21
25
  type: structural
22
- blocking: true
26
+ blocking: once-proposed
23
27
  description: Every hypercare exit criterion states a measurable threshold.
24
28
  - id: unique-id
25
29
  type: structural
@@ -14,9 +14,13 @@ checks:
14
14
  type: structural
15
15
  blocking: true
16
16
  description: Frontmatter is valid against schema/post-implementation-review.schema.json.
17
+ - id: frontmatter-complete
18
+ type: structural
19
+ blocking: once-proposed
20
+ description: Every schema-required frontmatter field is present (advisory while draft).
17
21
  - id: required-sections
18
22
  type: structural
19
- blocking: true
23
+ blocking: once-proposed
20
24
  description: Every required section is present and non-empty.
21
25
  - id: unique-id
22
26
  type: structural
@@ -17,9 +17,13 @@ checks:
17
17
  type: structural
18
18
  blocking: true
19
19
  description: Frontmatter is valid against schema/prd.schema.json.
20
+ - id: frontmatter-complete
21
+ type: structural
22
+ blocking: once-proposed
23
+ description: Every schema-required frontmatter field is present (advisory while draft).
20
24
  - id: required-sections
21
25
  type: structural
22
- blocking: true
26
+ blocking: once-proposed
23
27
  description: Every required section is present and non-empty.
24
28
  - id: items-well-formed
25
29
  type: structural
@@ -16,6 +16,10 @@ checks:
16
16
  blocking: true
17
17
  description: Frontmatter is present and valid against schema/project.schema.json.
18
18
 
19
+ - id: frontmatter-complete
20
+ type: structural
21
+ blocking: once-proposed
22
+ description: Every schema-required frontmatter field is present (advisory while draft).
19
23
  - id: project-id-format
20
24
  type: structural
21
25
  blocking: true
@@ -23,7 +27,7 @@ checks:
23
27
 
24
28
  - id: required-sections
25
29
  type: structural
26
- blocking: true
30
+ blocking: once-proposed
27
31
  description: Every required section is present and non-empty.
28
32
 
29
33
  - id: unique-id
@@ -13,13 +13,17 @@ checks:
13
13
  type: structural
14
14
  blocking: true
15
15
  description: Frontmatter is valid against schema/qa-test-plan.schema.json.
16
+ - id: frontmatter-complete
17
+ type: structural
18
+ blocking: once-proposed
19
+ description: Every schema-required frontmatter field is present (advisory while draft).
16
20
  - id: required-sections
17
21
  type: structural
18
- blocking: true
22
+ blocking: once-proposed
19
23
  description: Every required section is present and non-empty.
20
24
  - id: measurable-exit-criteria
21
25
  type: structural
22
- blocking: true
26
+ blocking: once-proposed
23
27
  description: Every exit criterion states a measurable threshold.
24
28
  - id: unique-id
25
29
  type: structural
@@ -10,13 +10,17 @@ checks:
10
10
  type: structural
11
11
  blocking: true
12
12
  description: Frontmatter is valid against schema/raci-stakeholder.schema.json.
13
+ - id: frontmatter-complete
14
+ type: structural
15
+ blocking: once-proposed
16
+ description: Every schema-required frontmatter field is present (advisory while draft).
13
17
  - id: required-sections
14
18
  type: structural
15
- blocking: true
19
+ blocking: once-proposed
16
20
  description: Every required section is present and non-empty.
17
21
  - id: raci-one-accountable
18
22
  type: structural
19
- blocking: true
23
+ blocking: once-proposed
20
24
  description: Every activity in the RACI Matrix has exactly one Accountable (A) role.
21
25
  - id: unique-id
22
26
  type: structural
@@ -16,13 +16,17 @@ checks:
16
16
  type: structural
17
17
  blocking: true
18
18
  description: Frontmatter is valid against schema/release-cutover-plan.schema.json.
19
+ - id: frontmatter-complete
20
+ type: structural
21
+ blocking: once-proposed
22
+ description: Every schema-required frontmatter field is present (advisory while draft).
19
23
  - id: required-sections
20
24
  type: structural
21
- blocking: true
25
+ blocking: once-proposed
22
26
  description: Every required section is present and non-empty.
23
27
  - id: numbered-steps
24
28
  type: structural
25
- blocking: true
29
+ blocking: once-proposed
26
30
  description: Cutover Steps is an ordered list of at least two numbered steps.
27
31
  - id: unique-id
28
32
  type: structural
@@ -14,9 +14,13 @@ checks:
14
14
  type: structural
15
15
  blocking: true
16
16
  description: Frontmatter is valid against schema/risk-register.schema.json.
17
+ - id: frontmatter-complete
18
+ type: structural
19
+ blocking: once-proposed
20
+ description: Every schema-required frontmatter field is present (advisory while draft).
17
21
  - id: required-sections
18
22
  type: structural
19
- blocking: true
23
+ blocking: once-proposed
20
24
  description: Every required section is present and non-empty.
21
25
  - id: items-well-formed
22
26
  type: structural
@@ -24,7 +28,7 @@ checks:
24
28
  description: Every risk is a valid **RISK-###** item.
25
29
  - id: risk-items-complete
26
30
  type: structural
27
- blocking: true
31
+ blocking: once-proposed
28
32
  description: Every risk states a Probability, Impact, Owner, and Response.
29
33
  - id: unique-id
30
34
  type: structural
@@ -15,13 +15,17 @@ checks:
15
15
  type: structural
16
16
  blocking: true
17
17
  description: Frontmatter is valid against schema/rollback-plan.schema.json.
18
+ - id: frontmatter-complete
19
+ type: structural
20
+ blocking: once-proposed
21
+ description: Every schema-required frontmatter field is present (advisory while draft).
18
22
  - id: required-sections
19
23
  type: structural
20
- blocking: true
24
+ blocking: once-proposed
21
25
  description: Every required section is present and non-empty.
22
26
  - id: numbered-steps
23
27
  type: structural
24
- blocking: true
28
+ blocking: once-proposed
25
29
  description: Rollback Steps is an ordered list of at least two numbered steps.
26
30
  - id: unique-id
27
31
  type: structural
@@ -16,13 +16,17 @@ checks:
16
16
  type: structural
17
17
  blocking: true
18
18
  description: Frontmatter is valid against schema/runbook.schema.json.
19
+ - id: frontmatter-complete
20
+ type: structural
21
+ blocking: once-proposed
22
+ description: Every schema-required frontmatter field is present (advisory while draft).
19
23
  - id: required-sections
20
24
  type: structural
21
- blocking: true
25
+ blocking: once-proposed
22
26
  description: Every required section is present and non-empty.
23
27
  - id: numbered-steps
24
28
  type: structural
25
- blocking: true
29
+ blocking: once-proposed
26
30
  description: Procedures is an ordered list of at least two numbered steps.
27
31
  - id: unique-id
28
32
  type: structural
@@ -12,13 +12,17 @@ checks:
12
12
  type: structural
13
13
  blocking: true
14
14
  description: Frontmatter is valid (includes a period date and a green/amber/red rag).
15
+ - id: frontmatter-complete
16
+ type: structural
17
+ blocking: once-proposed
18
+ description: Every schema-required frontmatter field is present (advisory while draft).
15
19
  - id: required-sections
16
20
  type: structural
17
- blocking: true
21
+ blocking: once-proposed
18
22
  description: Every required section is present and non-empty.
19
23
  - id: references-risk
20
24
  type: structural
21
- blocking: true
25
+ blocking: once-proposed
22
26
  description: The Risks & Issues section cites at least one RISK-### from the register.
23
27
  - id: unique-id
24
28
  type: structural
@@ -14,9 +14,13 @@ checks:
14
14
  type: structural
15
15
  blocking: true
16
16
  description: Frontmatter is valid against schema/test-cases.schema.json.
17
+ - id: frontmatter-complete
18
+ type: structural
19
+ blocking: once-proposed
20
+ description: Every schema-required frontmatter field is present (advisory while draft).
17
21
  - id: required-sections
18
22
  type: structural
19
- blocking: true
23
+ blocking: once-proposed
20
24
  description: Every required section is present and non-empty.
21
25
  - id: items-well-formed
22
26
  type: structural
@@ -14,9 +14,13 @@ checks:
14
14
  type: structural
15
15
  blocking: true
16
16
  description: Frontmatter is valid against schema/user-story.schema.json.
17
+ - id: frontmatter-complete
18
+ type: structural
19
+ blocking: once-proposed
20
+ description: Every schema-required frontmatter field is present (advisory while draft).
17
21
  - id: required-sections
18
22
  type: structural
19
- blocking: true
23
+ blocking: once-proposed
20
24
  description: Every required section is present and non-empty.
21
25
  - id: items-well-formed
22
26
  type: structural
@@ -24,7 +28,7 @@ checks:
24
28
  description: Every story is a valid **US-###** item.
25
29
  - id: story-format
26
30
  type: structural
27
- blocking: true
31
+ blocking: once-proposed
28
32
  description: Every story follows "As a … I want … so that …".
29
33
  - id: unique-id
30
34
  type: structural
@@ -4,6 +4,7 @@ from __future__ import annotations
4
4
  import datetime as dt
5
5
  import re
6
6
  from collections.abc import Callable
7
+ from pathlib import Path
7
8
 
8
9
  from jsonschema import Draft7Validator, FormatChecker
9
10
 
@@ -73,15 +74,31 @@ def _as_date(value) -> dt.date | None:
73
74
 
74
75
 
75
76
  # ── individual checks ──────────────────────────────────────────────────────
77
+ def _schema_errors(doc: Document, ctx: dict) -> list:
78
+ validator = Draft7Validator(ctx["schema"], format_checker=FormatChecker())
79
+ return sorted(validator.iter_errors(_jsonify(doc.frontmatter)), key=str)
80
+
81
+
82
+ def _format_errors(errors) -> str:
83
+ return "; ".join(f"{'/'.join(str(p) for p in e.path) or '(root)'}: {e.message}"
84
+ for e in errors)
85
+
86
+
76
87
  def check_frontmatter_schema(doc: Document, ctx: dict) -> tuple[bool, str]:
77
- schema = ctx["schema"]
78
- validator = Draft7Validator(schema, format_checker=FormatChecker())
79
- errors = sorted(validator.iter_errors(_jsonify(doc.frontmatter)), key=str)
88
+ """Frontmatter *wellformedness*: type, format, pattern, and enum errors.
89
+ Missing required fields are completeness, checked by frontmatter-complete."""
90
+ errors = [e for e in _schema_errors(doc, ctx) if e.validator != "required"]
80
91
  if not errors:
81
92
  return True, "Frontmatter is valid against the schema."
82
- msgs = "; ".join(f"{'/'.join(str(p) for p in e.path) or '(root)'}: {e.message}"
83
- for e in errors)
84
- return False, f"Frontmatter schema errors: {msgs}"
93
+ return False, f"Frontmatter schema errors: {_format_errors(errors)}"
94
+
95
+
96
+ def check_frontmatter_complete(doc: Document, ctx: dict) -> tuple[bool, str]:
97
+ """Frontmatter *completeness*: every schema-required field is present."""
98
+ errors = [e for e in _schema_errors(doc, ctx) if e.validator == "required"]
99
+ if not errors:
100
+ return True, "All required frontmatter fields are present."
101
+ return False, f"Missing required frontmatter: {_format_errors(errors)}"
85
102
 
86
103
 
87
104
  def check_required_sections(doc: Document, ctx: dict) -> tuple[bool, str]:
@@ -133,8 +150,12 @@ def check_dates_consistent(doc: Document, ctx: dict) -> tuple[bool, str]:
133
150
  dates = doc.frontmatter.get("dates") or {}
134
151
  created = _as_date(dates.get("created"))
135
152
  target = _as_date(dates.get("target"))
153
+ if created is None and dates.get("created") is not None:
154
+ return False, f"dates.created is not a valid ISO date: {dates.get('created')!r}"
155
+ if target is None and dates.get("target") is not None:
156
+ return False, f"dates.target is not a valid ISO date: {dates.get('target')!r}"
136
157
  if created is None or target is None:
137
- return False, "dates.created and dates.target must be valid ISO dates."
158
+ return True, "Date(s) not set yet (presence is checked by frontmatter-complete)."
138
159
  if target < created:
139
160
  return False, f"target ({target}) is before created ({created})."
140
161
  return True, f"Dates consistent (created {created} → target {target})."
@@ -143,7 +164,9 @@ def check_dates_consistent(doc: Document, ctx: dict) -> tuple[bool, str]:
143
164
  def check_unique_id(doc: Document, ctx: dict) -> tuple[bool, str]:
144
165
  if not doc.id:
145
166
  return False, "Document has no id."
146
- others = [p for p in ctx.get("id_index", {}).get(doc.id, []) if p != doc.path]
167
+ me = Path(doc.path).resolve()
168
+ others = [p for p in ctx.get("id_index", {}).get(doc.id, [])
169
+ if Path(p).resolve() != me]
147
170
  if others:
148
171
  return False, f"id '{doc.id}' also used by: {', '.join(others)}"
149
172
  return True, f"id '{doc.id}' is unique."
@@ -371,6 +394,7 @@ def check_references_risk(doc: Document, ctx: dict) -> tuple[bool, str]:
371
394
 
372
395
  CHECKS: dict[str, Callable[[Document, dict], tuple[bool, str]]] = {
373
396
  "frontmatter-schema": check_frontmatter_schema,
397
+ "frontmatter-complete": check_frontmatter_complete,
374
398
  "required-sections": check_required_sections,
375
399
  "measurable-success-criteria": check_measurable_success_criteria,
376
400
  "risks-have-owner-and-mitigation": check_risks_owner_mitigation,
@@ -390,11 +414,29 @@ CHECKS: dict[str, Callable[[Document, dict], tuple[bool, str]]] = {
390
414
  }
391
415
 
392
416
 
417
+ def _effective_blocking(spec: dict, doc: Document) -> bool:
418
+ """Interpret the criteria `blocking` value.
419
+
420
+ true/"always" -> blocks at any status (integrity: malformed data)
421
+ "once-proposed" -> blocks once status is proposed or beyond; advisory for
422
+ drafts (completeness: WIP is never punished)
423
+ false/"never" -> advisory
424
+ """
425
+ b = spec.get("blocking", True)
426
+ if b in (True, "always"):
427
+ return True
428
+ if b in (False, "never"):
429
+ return False
430
+ if b == "once-proposed":
431
+ return str(doc.frontmatter.get("status", "draft")).lower() != "draft"
432
+ return bool(b)
433
+
434
+
393
435
  def run_structural(doc: Document, spec: dict, ctx: dict) -> CheckResult:
394
436
  """Run one structural check described by a criteria `spec` dict."""
395
437
  check_id = spec["id"]
396
438
  fn = CHECKS.get(check_id)
397
- blocking = bool(spec.get("blocking", True))
439
+ blocking = _effective_blocking(spec, doc)
398
440
  if fn is None:
399
441
  return CheckResult(check_id, False, blocking,
400
442
  f"Unknown structural check '{check_id}'.", kind="structural")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: docassert
3
- Version: 0.6.0
3
+ Version: 0.7.0
4
4
  Summary: Unit testing for business documents — validate structured Markdown docs against a configurable audit standard.
5
5
  Author: C4G Enterprises Inc.
6
6
  License: Apache-2.0
@@ -117,6 +117,9 @@ kind is adding a trio — no code for the common cases.
117
117
  - **Structural — deterministic, blocking.** Required fields and sections,
118
118
  measurable success criteria, risks with owner + mitigation, resolving
119
119
  references, unique ids. Plain Python, reliable enough to gate a merge.
120
+ Within this tier, *integrity* checks (malformed items, bad types, duplicate
121
+ ids) block at any status, while *completeness* checks relax to advisory on
122
+ `status: draft` and gate once a document is proposed — WIP is never punished.
120
123
  - **Semantic — AI-graded, advisory.** Scored via the Anthropic API and posted to
121
124
  the PR — never blocking. Set `ANTHROPIC_API_KEY` to enable; skipped otherwise.
122
125
 
@@ -94,6 +94,7 @@ tests/test_badge.py
94
94
  tests/test_config.py
95
95
  tests/test_consistency.py
96
96
  tests/test_defects.py
97
+ tests/test_draft_relaxation.py
97
98
  tests/test_extract.py
98
99
  tests/test_graph.py
99
100
  tests/test_json_report.py
@@ -0,0 +1,138 @@
1
+ """Tests for check severities: integrity always blocks; completeness relaxes
2
+ to advisory while a document is a draft ("WIP is never punished")."""
3
+ from pathlib import Path
4
+
5
+ from docassert.cli import main
6
+ from docassert.loader import parse_sections
7
+ from docassert.models import Document
8
+ from docassert.structural import (
9
+ _effective_blocking,
10
+ check_dates_consistent,
11
+ check_frontmatter_complete,
12
+ check_frontmatter_schema,
13
+ )
14
+
15
+ ROOT = Path(__file__).resolve().parent.parent
16
+
17
+ DRAFT_CHARTER = """---
18
+ kind: charter
19
+ project: PRJ-001-TST
20
+ id: TST-charter
21
+ title: T — Draft Charter
22
+ sponsor: jane.doe
23
+ dates:
24
+ created: 2026-01-01
25
+ status: {status}
26
+ ---
27
+
28
+ ## Objective
29
+ Cut cycle time from 10 days to under 2 days.
30
+
31
+ ## Success Criteria
32
+ - Median cycle time drops below 48 hours.
33
+
34
+ ## Scope
35
+ In scope: the thing.
36
+
37
+ ## Milestones
38
+ - TODO: none yet.
39
+
40
+ ## Risks
41
+ - Might slip. Owner: jane.doe. Mitigation: buffer.
42
+
43
+ ## Approval
44
+ Pending.
45
+ """
46
+
47
+
48
+ def _write(tmp_path, status):
49
+ d = tmp_path / "documents" / "PRJ-001-TST"
50
+ d.mkdir(parents=True, exist_ok=True)
51
+ f = d / "charter.md"
52
+ f.write_text(DRAFT_CHARTER.format(status=status), encoding="utf-8")
53
+ return f
54
+
55
+
56
+ # ── the Refuge scenario: an incomplete DRAFT merges (advisory only) ──────────
57
+ def test_draft_missing_budget_is_advisory(tmp_path, monkeypatch, capsys):
58
+ f = _write(tmp_path, "draft")
59
+ monkeypatch.chdir(tmp_path)
60
+ assert main(["validate", str(f)]) == 0 # no blocking failures
61
+ out = capsys.readouterr().out
62
+ assert "Missing required frontmatter" in out # ...but the gap is reported
63
+ assert "budget" in out
64
+
65
+
66
+ def test_proposed_missing_budget_blocks(tmp_path, monkeypatch):
67
+ f = _write(tmp_path, "proposed")
68
+ monkeypatch.chdir(tmp_path)
69
+ assert main(["validate", str(f)]) >= 1 # completeness now gates
70
+
71
+
72
+ def test_type_error_blocks_even_on_draft(tmp_path, monkeypatch):
73
+ f = _write(tmp_path, "draft")
74
+ f.write_text(f.read_text().replace("sponsor: jane.doe", "sponsor: j"),
75
+ encoding="utf-8") # minLength violation = malformed
76
+ monkeypatch.chdir(tmp_path)
77
+ assert main(["validate", str(f)]) >= 1
78
+
79
+
80
+ def test_malformed_item_blocks_even_on_draft(tmp_path, monkeypatch):
81
+ d = tmp_path / "documents" / "PRJ-001-TST"
82
+ d.mkdir(parents=True)
83
+ (d / "brd.md").write_text("""---
84
+ kind: brd
85
+ project: PRJ-001-TST
86
+ id: TST-brd
87
+ title: T
88
+ owner: jane.doe
89
+ status: draft
90
+ ---
91
+
92
+ ## Purpose
93
+ p.
94
+
95
+ ## Business Requirements
96
+ - **broken bullet** without a valid item id
97
+
98
+ ## Out of Scope
99
+ n/a
100
+ """, encoding="utf-8")
101
+ monkeypatch.chdir(tmp_path)
102
+ assert main(["validate", str(d / "brd.md")]) >= 1 # integrity: always blocks
103
+
104
+
105
+ # ── the split checks ─────────────────────────────────────────────────────────
106
+ def _doc(fm):
107
+ return Document("x.md", fm, parse_sections(""), "")
108
+
109
+
110
+ def test_schema_vs_complete_split():
111
+ import json
112
+ schema = json.loads((ROOT / "schema" / "charter.schema.json").read_text())
113
+ ctx = {"schema": schema}
114
+ doc = _doc({"kind": "charter", "id": "TST-charter", "project": "PRJ-001-TST",
115
+ "title": "Test Charter", "sponsor": "jane.doe", "status": "draft"}) # no budget/dates
116
+ ok_schema, _ = check_frontmatter_schema(doc, ctx)
117
+ ok_complete, detail = check_frontmatter_complete(doc, ctx)
118
+ assert ok_schema # nothing malformed
119
+ assert not ok_complete and "budget" in detail
120
+
121
+
122
+ def test_dates_absent_pass_invalid_fail():
123
+ assert check_dates_consistent(_doc({"dates": {}}), {})[0]
124
+ assert check_dates_consistent(_doc({}), {})[0]
125
+ ok, detail = check_dates_consistent(_doc({"dates": {"created": "soonish"}}), {})
126
+ assert not ok and "soonish" in detail
127
+
128
+
129
+ # ── blocking interpretation ──────────────────────────────────────────────────
130
+ def test_effective_blocking_modes():
131
+ draft, proposed = _doc({"status": "draft"}), _doc({"status": "proposed"})
132
+ assert _effective_blocking({"blocking": True}, draft)
133
+ assert _effective_blocking({"blocking": "always"}, draft)
134
+ assert not _effective_blocking({"blocking": False}, proposed)
135
+ assert not _effective_blocking({"blocking": "never"}, proposed)
136
+ assert not _effective_blocking({"blocking": "once-proposed"}, draft)
137
+ assert _effective_blocking({"blocking": "once-proposed"}, proposed)
138
+ assert not _effective_blocking({"blocking": "once-proposed"}, _doc({})) # no status = draft
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes