docassert 0.15.0__tar.gz → 0.16.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.
- {docassert-0.15.0/docassert.egg-info → docassert-0.16.0}/PKG-INFO +1 -1
- {docassert-0.15.0 → docassert-0.16.0}/docassert/__init__.py +1 -1
- docassert-0.16.0/docassert/_data/criteria/operations.criteria.yaml +40 -0
- docassert-0.16.0/docassert/_data/profiles/operations.yaml +18 -0
- docassert-0.16.0/docassert/_data/schema/operations.schema.json +50 -0
- docassert-0.16.0/docassert/_data/templates/operations.template.md +17 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/status/derive.py +23 -1
- {docassert-0.15.0 → docassert-0.16.0}/docassert/status/render.py +13 -1
- {docassert-0.15.0 → docassert-0.16.0}/docassert/structural.py +31 -0
- {docassert-0.15.0 → docassert-0.16.0/docassert.egg-info}/PKG-INFO +1 -1
- {docassert-0.15.0 → docassert-0.16.0}/docassert.egg-info/SOURCES.txt +4 -0
- {docassert-0.15.0 → docassert-0.16.0}/tests/test_profiles.py +3 -2
- {docassert-0.15.0 → docassert-0.16.0}/tests/test_status.py +59 -0
- {docassert-0.15.0 → docassert-0.16.0}/LICENSE +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/NOTICE +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/README.md +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/__main__.py +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/_data/consistency.yaml +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/_data/criteria/adr.criteria.yaml +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/_data/criteria/benefits-realization.criteria.yaml +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/_data/criteria/brd.criteria.yaml +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/_data/criteria/business-case.criteria.yaml +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/_data/criteria/charter.criteria.yaml +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/_data/criteria/data-migration-plan.criteria.yaml +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/_data/criteria/frnfr.criteria.yaml +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/_data/criteria/hypercare-plan.criteria.yaml +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/_data/criteria/post-implementation-review.criteria.yaml +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/_data/criteria/prd.criteria.yaml +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/_data/criteria/project.criteria.yaml +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/_data/criteria/qa-test-plan.criteria.yaml +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/_data/criteria/raci-stakeholder.criteria.yaml +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/_data/criteria/release-cutover-plan.criteria.yaml +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/_data/criteria/risk-register.criteria.yaml +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/_data/criteria/rollback-plan.criteria.yaml +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/_data/criteria/runbook.criteria.yaml +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/_data/criteria/status-report.criteria.yaml +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/_data/criteria/test-cases.criteria.yaml +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/_data/criteria/user-story.criteria.yaml +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/_data/profiles/agile-delivery.yaml +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/_data/profiles/lean-startup.yaml +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/_data/profiles/regulated-industry.yaml +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/_data/schema/adr.schema.json +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/_data/schema/benefits-realization.schema.json +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/_data/schema/brd.schema.json +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/_data/schema/business-case.schema.json +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/_data/schema/charter.schema.json +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/_data/schema/data-migration-plan.schema.json +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/_data/schema/frnfr.schema.json +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/_data/schema/hypercare-plan.schema.json +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/_data/schema/post-implementation-review.schema.json +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/_data/schema/prd.schema.json +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/_data/schema/project.schema.json +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/_data/schema/qa-test-plan.schema.json +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/_data/schema/raci-stakeholder.schema.json +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/_data/schema/release-cutover-plan.schema.json +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/_data/schema/risk-register.schema.json +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/_data/schema/rollback-plan.schema.json +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/_data/schema/runbook.schema.json +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/_data/schema/status-report.schema.json +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/_data/schema/test-cases.schema.json +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/_data/schema/user-story.schema.json +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/_data/skills/doc-to-pmo/SKILL.md +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/_data/templates/adr.template.md +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/_data/templates/benefits-realization.template.md +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/_data/templates/brd.template.md +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/_data/templates/business-case.template.md +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/_data/templates/charter.template.md +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/_data/templates/data-migration-plan.template.md +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/_data/templates/frnfr.template.md +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/_data/templates/hypercare-plan.template.md +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/_data/templates/post-implementation-review.template.md +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/_data/templates/prd.template.md +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/_data/templates/project.template.md +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/_data/templates/qa-test-plan.template.md +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/_data/templates/raci-stakeholder.template.md +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/_data/templates/release-cutover-plan.template.md +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/_data/templates/risk-register.template.md +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/_data/templates/rollback-plan.template.md +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/_data/templates/runbook.template.md +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/_data/templates/status-report.template.md +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/_data/templates/test-cases.template.md +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/_data/templates/user-story.template.md +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/bridge/__init__.py +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/bridge/board.py +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/bridge/gh.py +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/bridge/ops.py +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/bridge/plan.py +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/cli.py +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/config.py +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/consistency.py +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/extract.py +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/graph.py +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/loader.py +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/models.py +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/profiles.py +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/projects.py +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/report.py +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/rtm.py +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/scaffold.py +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/semantic.py +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert/status/__init__.py +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert.egg-info/dependency_links.txt +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert.egg-info/entry_points.txt +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert.egg-info/requires.txt +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/docassert.egg-info/top_level.txt +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/pyproject.toml +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/setup.cfg +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/tests/test_badge.py +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/tests/test_bridge.py +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/tests/test_config.py +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/tests/test_consistency.py +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/tests/test_defects.py +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/tests/test_draft_relaxation.py +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/tests/test_extract.py +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/tests/test_graph.py +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/tests/test_json_report.py +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/tests/test_kinds_delivery.py +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/tests/test_kinds_governance.py +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/tests/test_kinds_operate.py +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/tests/test_kinds_reporting.py +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/tests/test_projects.py +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/tests/test_scaffold.py +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/tests/test_semantic.py +0 -0
- {docassert-0.15.0 → docassert-0.16.0}/tests/test_structural.py +0 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Audit criteria for the Operations kind (spec §13.21).
|
|
2
|
+
kind: operations
|
|
3
|
+
|
|
4
|
+
required_sections:
|
|
5
|
+
- Overview
|
|
6
|
+
- Services
|
|
7
|
+
|
|
8
|
+
item_sections:
|
|
9
|
+
- section: Services
|
|
10
|
+
prefix: SVC
|
|
11
|
+
|
|
12
|
+
checks:
|
|
13
|
+
- id: frontmatter-schema
|
|
14
|
+
type: structural
|
|
15
|
+
blocking: true
|
|
16
|
+
description: Frontmatter is valid against schema/operations.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).
|
|
21
|
+
- id: required-sections
|
|
22
|
+
type: structural
|
|
23
|
+
blocking: once-proposed
|
|
24
|
+
description: Every required section is present and non-empty.
|
|
25
|
+
- id: items-well-formed
|
|
26
|
+
type: structural
|
|
27
|
+
blocking: true
|
|
28
|
+
description: Every SVC item follows the item grammar in its declared section.
|
|
29
|
+
- id: svc-items-complete
|
|
30
|
+
type: structural
|
|
31
|
+
blocking: once-proposed
|
|
32
|
+
description: Every SVC item states a Level (the commitment) and a Measure (how attainment is read).
|
|
33
|
+
- id: ops-review-fresh
|
|
34
|
+
type: structural
|
|
35
|
+
blocking: false
|
|
36
|
+
description: review_by is today or later; a past date is an amber input to derived status, never a merge blocker.
|
|
37
|
+
- id: unique-id
|
|
38
|
+
type: structural
|
|
39
|
+
blocking: true
|
|
40
|
+
description: The document id is unique across the repository.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Profile: operations
|
|
2
|
+
#
|
|
3
|
+
# For a repository (or project) whose work is a standing service catalog
|
|
4
|
+
# rather than a delivery arc. The catalog itself is the one required kind;
|
|
5
|
+
# the register and runbook are strongly recommended companions.
|
|
6
|
+
name: operations
|
|
7
|
+
description: >
|
|
8
|
+
A governed service catalog: the operations document is required, with the
|
|
9
|
+
risk register and runbook recommended alongside it.
|
|
10
|
+
|
|
11
|
+
enforce_when: active
|
|
12
|
+
|
|
13
|
+
expects:
|
|
14
|
+
required:
|
|
15
|
+
- operations
|
|
16
|
+
recommended:
|
|
17
|
+
- risk-register
|
|
18
|
+
- runbook
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"title": "Operations frontmatter",
|
|
4
|
+
"type": "object",
|
|
5
|
+
"required": [
|
|
6
|
+
"kind",
|
|
7
|
+
"id",
|
|
8
|
+
"project",
|
|
9
|
+
"title",
|
|
10
|
+
"owner",
|
|
11
|
+
"status",
|
|
12
|
+
"review_by"
|
|
13
|
+
],
|
|
14
|
+
"additionalProperties": true,
|
|
15
|
+
"properties": {
|
|
16
|
+
"kind": {
|
|
17
|
+
"const": "operations"
|
|
18
|
+
},
|
|
19
|
+
"id": {
|
|
20
|
+
"type": "string",
|
|
21
|
+
"pattern": "^[A-Z]{2,6}-[a-z0-9][a-z0-9-]*$",
|
|
22
|
+
"description": "Stable id, <CODE>-<slug>. Unique across documents/."
|
|
23
|
+
},
|
|
24
|
+
"title": {
|
|
25
|
+
"type": "string",
|
|
26
|
+
"minLength": 3
|
|
27
|
+
},
|
|
28
|
+
"owner": {
|
|
29
|
+
"type": "string",
|
|
30
|
+
"minLength": 2
|
|
31
|
+
},
|
|
32
|
+
"status": {
|
|
33
|
+
"enum": [
|
|
34
|
+
"draft",
|
|
35
|
+
"proposed",
|
|
36
|
+
"approved",
|
|
37
|
+
"baselined"
|
|
38
|
+
]
|
|
39
|
+
},
|
|
40
|
+
"project": {
|
|
41
|
+
"type": "string",
|
|
42
|
+
"pattern": "^PRJ-\\d{3,}-[A-Z]{2,6}$"
|
|
43
|
+
},
|
|
44
|
+
"review_by": {
|
|
45
|
+
"type": "string",
|
|
46
|
+
"format": "date",
|
|
47
|
+
"description": "The next operations review; a past date is an amber input to derived status."
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
kind: operations
|
|
3
|
+
project: PRJ-000-XXX # the owning project's id
|
|
4
|
+
id: XXX-operations # <CODE>-<slug>; the project code namespaces it
|
|
5
|
+
title: My Operations
|
|
6
|
+
owner: jane.doe
|
|
7
|
+
status: draft
|
|
8
|
+
review_by: 2026-12-31 # the next operations review; stale = amber
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Overview
|
|
12
|
+
|
|
13
|
+
<!-- The operating period, the envelope, and what this catalog covers. -->
|
|
14
|
+
|
|
15
|
+
## Services
|
|
16
|
+
|
|
17
|
+
<!-- - **XXX-SVC-001**: What the service is. Level: the commitment, measurably stated. Measure: how attainment is read. -->
|
|
@@ -113,6 +113,25 @@ def _broken_references(graph, code=None):
|
|
|
113
113
|
return broken
|
|
114
114
|
|
|
115
115
|
|
|
116
|
+
def _operations(docs):
|
|
117
|
+
"""Operations documents with their review state."""
|
|
118
|
+
import datetime
|
|
119
|
+
out = []
|
|
120
|
+
for d in docs:
|
|
121
|
+
if d.kind != "operations":
|
|
122
|
+
continue
|
|
123
|
+
raw = d.frontmatter.get("review_by")
|
|
124
|
+
try:
|
|
125
|
+
due = (raw if isinstance(raw, datetime.date)
|
|
126
|
+
else datetime.date.fromisoformat(str(raw)))
|
|
127
|
+
fresh = due >= datetime.date.today()
|
|
128
|
+
due_s = due.isoformat()
|
|
129
|
+
except (TypeError, ValueError):
|
|
130
|
+
fresh, due_s = False, str(raw)
|
|
131
|
+
out.append({"id": d.id, "review_by": due_s, "fresh": fresh})
|
|
132
|
+
return out
|
|
133
|
+
|
|
134
|
+
|
|
116
135
|
def _latest_report(docs):
|
|
117
136
|
reports = [d for d in docs if d.kind == "status-report"]
|
|
118
137
|
if not reports:
|
|
@@ -179,6 +198,7 @@ def build_status(documents_dir=DOCUMENTS_DIR, project: str | None = None) -> dic
|
|
|
179
198
|
},
|
|
180
199
|
"coverage": _coverage(graph, cfg, code),
|
|
181
200
|
"risks": _risks(graph, code),
|
|
201
|
+
"operations": _operations(docs),
|
|
182
202
|
"broken_references": _broken_references(graph, code),
|
|
183
203
|
"latest_report": _latest_report(docs),
|
|
184
204
|
"completeness": completeness,
|
|
@@ -245,7 +265,9 @@ def derive_rag(model) -> str:
|
|
|
245
265
|
completeness_gap = bool(comp and (comp["missing_required"] or comp["incomplete_required"]
|
|
246
266
|
or comp["recommended_gaps"] or comp.get("unknown")))
|
|
247
267
|
open_risks = [r for r in model["risks"] if r.get("disposition", "open") == "open"]
|
|
248
|
-
|
|
268
|
+
stale_ops = [o for o in model.get("operations", []) if not o["fresh"]]
|
|
269
|
+
if (coverage_gap or open_risks or stale_ops
|
|
270
|
+
or reported in {"amber", "red"} or completeness_gap):
|
|
249
271
|
return "amber"
|
|
250
272
|
return "green"
|
|
251
273
|
|
|
@@ -54,6 +54,10 @@ def render_markdown(model, summary: bool = False) -> str:
|
|
|
54
54
|
if failing:
|
|
55
55
|
out.append(f"- 🔴 **Documents failing audit**: {', '.join(failing)}")
|
|
56
56
|
|
|
57
|
+
for o in model.get("operations", []):
|
|
58
|
+
mark = "🟢" if o["fresh"] else "🟠"
|
|
59
|
+
verb = "next" if o["fresh"] else "OVERDUE since"
|
|
60
|
+
out.append(f"- {mark} **Operations review** (`{o['id']}`): {verb} {o['review_by']}")
|
|
57
61
|
if model["risks"]:
|
|
58
62
|
open_r = [r for r in model["risks"] if r.get("disposition", "open") == "open"]
|
|
59
63
|
rest = len(model["risks"]) - len(open_r)
|
|
@@ -176,6 +180,14 @@ def render_html(model) -> str:
|
|
|
176
180
|
else:
|
|
177
181
|
risks = "<p>None open.</p>"
|
|
178
182
|
|
|
183
|
+
ops_html = "".join(
|
|
184
|
+
f'<div class="sig"><div class="sig-h"><span>Operations review · '
|
|
185
|
+
f'<code>{esc(o["id"])}</code></span><span style="color:'
|
|
186
|
+
+ ("var(--ok)" if o["fresh"] else "var(--amber)") + ';font-weight:600;">'
|
|
187
|
+
+ ("next " if o["fresh"] else "overdue since ") + f'{esc(o["review_by"])}'
|
|
188
|
+
'</span></div></div>'
|
|
189
|
+
for o in model.get("operations", []))
|
|
190
|
+
|
|
179
191
|
problems = ""
|
|
180
192
|
if model["broken_references"]:
|
|
181
193
|
problems += ("<section><h2>Broken references</h2><ul>"
|
|
@@ -287,7 +299,7 @@ def render_html(model) -> str:
|
|
|
287
299
|
</header>
|
|
288
300
|
<p class="meta">Derived from {c['total']} documents · {c['kinds']} kinds ·
|
|
289
301
|
{c['approved']} approved{scope} · generated {gen}. Do not edit — regenerated from the documents.</p>
|
|
290
|
-
<section><h2>Traceability coverage</h2>{coverage}</section>
|
|
302
|
+
<section><h2>Traceability coverage</h2>{coverage}{ops_html}</section>
|
|
291
303
|
{document_set}
|
|
292
304
|
{execution_html}
|
|
293
305
|
{problems}
|
|
@@ -270,6 +270,35 @@ def check_risk_disposition_valid(doc: Document, ctx: dict) -> tuple[bool, str]:
|
|
|
270
270
|
return True, f"All {total} risk disposition(s) valid (absent means open)."
|
|
271
271
|
|
|
272
272
|
|
|
273
|
+
def check_svc_items_complete(doc: Document, ctx: dict) -> tuple[bool, str]:
|
|
274
|
+
"""Every SVC item states a Level and a Measure."""
|
|
275
|
+
incomplete, total = [], 0
|
|
276
|
+
for iid, text in _items_of_prefix(doc, ctx, "SVC"):
|
|
277
|
+
total += 1
|
|
278
|
+
missing = [f for f in ("Level", "Measure") if _field_value(text, f) is None]
|
|
279
|
+
if missing:
|
|
280
|
+
incomplete.append(f'{iid} missing {", ".join(missing)}')
|
|
281
|
+
if incomplete:
|
|
282
|
+
return False, "; ".join(incomplete)
|
|
283
|
+
return True, f"All {total} service(s) state a level and a measure."
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def check_ops_review_fresh(doc: Document, ctx: dict) -> tuple[bool, str]:
|
|
287
|
+
"""review_by is today or later (advisory; staleness ambers derived status)."""
|
|
288
|
+
import datetime
|
|
289
|
+
raw = doc.frontmatter.get("review_by")
|
|
290
|
+
if raw is None:
|
|
291
|
+
return False, "No review_by date."
|
|
292
|
+
try:
|
|
293
|
+
due = raw if isinstance(raw, datetime.date) else datetime.date.fromisoformat(str(raw))
|
|
294
|
+
except ValueError:
|
|
295
|
+
return False, f"review_by is not a date: {raw!r}"
|
|
296
|
+
today = datetime.date.today()
|
|
297
|
+
if due < today:
|
|
298
|
+
return False, f"operations review overdue since {due.isoformat()}"
|
|
299
|
+
return True, f"Next operations review {due.isoformat()}."
|
|
300
|
+
|
|
301
|
+
|
|
273
302
|
def check_adr_items_have_status(doc: Document, ctx: dict) -> tuple[bool, str]:
|
|
274
303
|
"""Every ADR item declares a valid Status."""
|
|
275
304
|
bad, total = [], 0
|
|
@@ -418,6 +447,8 @@ CHECKS: dict[str, Callable[[Document, dict], tuple[bool, str]]] = {
|
|
|
418
447
|
"items-well-formed": check_items_well_formed,
|
|
419
448
|
"risk-items-complete": check_risk_items_complete,
|
|
420
449
|
"risk-disposition-valid": check_risk_disposition_valid,
|
|
450
|
+
"svc-items-complete": check_svc_items_complete,
|
|
451
|
+
"ops-review-fresh": check_ops_review_fresh,
|
|
421
452
|
"adr-items-have-status": check_adr_items_have_status,
|
|
422
453
|
"raci-one-accountable": check_raci_one_accountable,
|
|
423
454
|
"story-format": check_story_format,
|
|
@@ -33,6 +33,7 @@ docassert/_data/criteria/charter.criteria.yaml
|
|
|
33
33
|
docassert/_data/criteria/data-migration-plan.criteria.yaml
|
|
34
34
|
docassert/_data/criteria/frnfr.criteria.yaml
|
|
35
35
|
docassert/_data/criteria/hypercare-plan.criteria.yaml
|
|
36
|
+
docassert/_data/criteria/operations.criteria.yaml
|
|
36
37
|
docassert/_data/criteria/post-implementation-review.criteria.yaml
|
|
37
38
|
docassert/_data/criteria/prd.criteria.yaml
|
|
38
39
|
docassert/_data/criteria/project.criteria.yaml
|
|
@@ -47,6 +48,7 @@ docassert/_data/criteria/test-cases.criteria.yaml
|
|
|
47
48
|
docassert/_data/criteria/user-story.criteria.yaml
|
|
48
49
|
docassert/_data/profiles/agile-delivery.yaml
|
|
49
50
|
docassert/_data/profiles/lean-startup.yaml
|
|
51
|
+
docassert/_data/profiles/operations.yaml
|
|
50
52
|
docassert/_data/profiles/regulated-industry.yaml
|
|
51
53
|
docassert/_data/schema/adr.schema.json
|
|
52
54
|
docassert/_data/schema/benefits-realization.schema.json
|
|
@@ -56,6 +58,7 @@ docassert/_data/schema/charter.schema.json
|
|
|
56
58
|
docassert/_data/schema/data-migration-plan.schema.json
|
|
57
59
|
docassert/_data/schema/frnfr.schema.json
|
|
58
60
|
docassert/_data/schema/hypercare-plan.schema.json
|
|
61
|
+
docassert/_data/schema/operations.schema.json
|
|
59
62
|
docassert/_data/schema/post-implementation-review.schema.json
|
|
60
63
|
docassert/_data/schema/prd.schema.json
|
|
61
64
|
docassert/_data/schema/project.schema.json
|
|
@@ -77,6 +80,7 @@ docassert/_data/templates/charter.template.md
|
|
|
77
80
|
docassert/_data/templates/data-migration-plan.template.md
|
|
78
81
|
docassert/_data/templates/frnfr.template.md
|
|
79
82
|
docassert/_data/templates/hypercare-plan.template.md
|
|
83
|
+
docassert/_data/templates/operations.template.md
|
|
80
84
|
docassert/_data/templates/post-implementation-review.template.md
|
|
81
85
|
docassert/_data/templates/prd.template.md
|
|
82
86
|
docassert/_data/templates/project.template.md
|
|
@@ -60,8 +60,9 @@ def test_load_profile_and_unknown():
|
|
|
60
60
|
assert P.load_profile("does-not-exist", PROFILES) is None
|
|
61
61
|
|
|
62
62
|
|
|
63
|
-
def
|
|
64
|
-
assert set(P.available(PROFILES)) == {"regulated-industry", "lean-startup",
|
|
63
|
+
def test_available_lists_the_shipped_profiles():
|
|
64
|
+
assert set(P.available(PROFILES)) == {"regulated-industry", "lean-startup",
|
|
65
|
+
"agile-delivery", "operations"}
|
|
65
66
|
|
|
66
67
|
|
|
67
68
|
# ── integration with the real repo ──────────────────────────────────────────
|
|
@@ -214,3 +214,62 @@ def test_render_html_shows_disposition_column():
|
|
|
214
214
|
_cd_root()
|
|
215
215
|
out = S.render_html(S.build_status(ROOT / "documents"))
|
|
216
216
|
assert "<th>Status</th>" in out and "open of" in out
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
# ── operations kind (ENG-PR-003) ─────────────────────────────────────────────
|
|
220
|
+
OPS_DOC = """---
|
|
221
|
+
kind: operations
|
|
222
|
+
id: XX-operations
|
|
223
|
+
project: PRJ-001-XX
|
|
224
|
+
title: Ops fixture
|
|
225
|
+
owner: ops.owner
|
|
226
|
+
status: approved
|
|
227
|
+
review_by: {due}
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## Overview
|
|
231
|
+
x
|
|
232
|
+
|
|
233
|
+
## Services
|
|
234
|
+
|
|
235
|
+
- **XX-SVC-001**: Triage. Level: within 14 days. Measure: monthly report.
|
|
236
|
+
"""
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def _ops_tree(tmp_path, due):
|
|
240
|
+
d = tmp_path / "documents" / "PRJ-001-XX"
|
|
241
|
+
d.mkdir(parents=True)
|
|
242
|
+
(d / "project.md").write_text(
|
|
243
|
+
"---\nkind: project\nid: PRJ-001-XX\ncode: XX\nname: Xy Ops\n"
|
|
244
|
+
"sponsor: sp.owner\nstatus: active\n---\n\n## Overview\nx\n\n## Scope\nx\n",
|
|
245
|
+
encoding="utf-8")
|
|
246
|
+
(d / "operations.md").write_text(OPS_DOC.format(due=due), encoding="utf-8")
|
|
247
|
+
return tmp_path / "documents"
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def test_stale_operations_review_ambers_status(tmp_path):
|
|
251
|
+
_cd_root()
|
|
252
|
+
m = S.build_status(_ops_tree(tmp_path, "2020-01-01"), project="PRJ-001-XX")
|
|
253
|
+
assert m["operations"][0]["fresh"] is False
|
|
254
|
+
assert m["rag"] == "amber"
|
|
255
|
+
assert "OVERDUE" in S.render_markdown(m)
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def test_fresh_operations_review_stays_green(tmp_path):
|
|
259
|
+
_cd_root()
|
|
260
|
+
m = S.build_status(_ops_tree(tmp_path, "2099-12-31"), project="PRJ-001-XX")
|
|
261
|
+
assert m["operations"][0]["fresh"] is True
|
|
262
|
+
assert m["rag"] == "green"
|
|
263
|
+
assert "Operations review" in S.render_html(m)
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def test_svc_and_freshness_checks(tmp_path):
|
|
267
|
+
from docassert.loader import load
|
|
268
|
+
from docassert.structural import check_ops_review_fresh, check_svc_items_complete
|
|
269
|
+
docs = _ops_tree(tmp_path, "2020-01-01")
|
|
270
|
+
doc = load(docs / "PRJ-001-XX" / "operations.md")
|
|
271
|
+
ctx = {"item_sections": [{"section": "Services", "prefix": "SVC"}]}
|
|
272
|
+
ok, _ = check_svc_items_complete(doc, ctx)
|
|
273
|
+
assert ok
|
|
274
|
+
fresh_ok, detail = check_ops_review_fresh(doc, ctx)
|
|
275
|
+
assert not fresh_ok and "overdue" in detail
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{docassert-0.15.0 → docassert-0.16.0}/docassert/_data/criteria/benefits-realization.criteria.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{docassert-0.15.0 → docassert-0.16.0}/docassert/_data/criteria/data-migration-plan.criteria.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{docassert-0.15.0 → docassert-0.16.0}/docassert/_data/criteria/raci-stakeholder.criteria.yaml
RENAMED
|
File without changes
|
{docassert-0.15.0 → docassert-0.16.0}/docassert/_data/criteria/release-cutover-plan.criteria.yaml
RENAMED
|
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
|
{docassert-0.15.0 → docassert-0.16.0}/docassert/_data/schema/benefits-realization.schema.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{docassert-0.15.0 → docassert-0.16.0}/docassert/_data/schema/data-migration-plan.schema.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{docassert-0.15.0 → docassert-0.16.0}/docassert/_data/schema/post-implementation-review.schema.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{docassert-0.15.0 → docassert-0.16.0}/docassert/_data/schema/release-cutover-plan.schema.json
RENAMED
|
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
|
{docassert-0.15.0 → docassert-0.16.0}/docassert/_data/templates/benefits-realization.template.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{docassert-0.15.0 → docassert-0.16.0}/docassert/_data/templates/data-migration-plan.template.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{docassert-0.15.0 → docassert-0.16.0}/docassert/_data/templates/raci-stakeholder.template.md
RENAMED
|
File without changes
|
{docassert-0.15.0 → docassert-0.16.0}/docassert/_data/templates/release-cutover-plan.template.md
RENAMED
|
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
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|