universalisos/tools/doorstop-integration/doorstop_config.py

219 lines
6.2 KiB
Python

#!/usr/bin/env python3
"""
Doorstop configuration for UniversalisOS requirements management.
Defines document types, custom attributes, and YAML schema for the
traceability pipeline linking requirements → code → tests.
"""
from __future__ import annotations
from dataclasses import dataclass, field
@dataclass
class DoorstopAttribute:
"""A custom attribute definition for a Doorstop document type."""
name: str
type: str # "string", "integer", "list"
required: bool = False
default: str = ""
@dataclass
class DocumentType:
"""Configuration for a Doorstop document type."""
prefix: str
template: str = ""
attribute_defaults: dict = field(default_factory=dict)
# ─── Document type definitions ──────────────────────────────────────────────
DOCUMENT_TYPES = {
"req": DocumentType(
prefix="REQ",
template="requirement",
attribute_defaults={
"dal_level": "B",
"priority": "high",
"category": "functional",
},
),
"spec": DocumentType(
prefix="SPEC",
template="specification",
attribute_defaults={
"dal_level": "B",
"revision": "1.0",
},
),
"test": DocumentType(
prefix="TC",
template="test_case",
attribute_defaults={
"method": "unit",
"result": "pending",
},
),
}
# ─── Custom attributes ─────────────────────────────────────────────────────
CUSTOM_ATTRIBUTES = {
# Traceability links (lists of identifiers)
"linked_files": DoorstopAttribute(
name="linked_files",
type="list",
required=False,
),
"linked_functions": DoorstopAttribute(
name="linked_functions",
type="list",
required=False,
),
"linked_tests": DoorstopAttribute(
name="linked_tests",
type="list",
required=False,
),
# DO-178C / certification attributes
"dal_level": DoorstopAttribute(
name="dal_level",
type="string",
required=True,
default="B",
),
"priority": DoorstopAttribute(
name="priority",
type="string",
required=True,
default="high",
),
"category": DoorstopAttribute(
name="category",
type="string",
required=True,
default="functional",
),
"revision": DoorstopAttribute(
name="revision",
type="string",
required=False,
default="1.0",
),
"method": DoorstopAttribute(
name="method",
type="string",
required=False,
default="unit",
),
"result": DoorstopAttribute(
name="result",
type="string",
required=False,
default="pending",
),
}
# ─── YAML schema for requirements ──────────────────────────────────────────
YAML_SCHEMA = {
"type": "object",
"properties": {
"uid": {
"type": "string",
"description": "Unique requirement identifier (e.g. REQ-001)",
},
"level": {
"type": "integer",
"description": "Hierarchy level (0 = top-level)",
},
"active": {
"type": "boolean",
"description": "Whether this requirement is active",
},
"derived": {
"type": "boolean",
"description": "Whether this is derived from a parent",
},
"normative": {
"type": "boolean",
"description": "Whether this is normative (must be satisfied)",
},
"text": {
"type": "string",
"description": "Requirement description text",
},
"parent": {
"type": "string",
"description": "Parent requirement UID",
},
"ref": {
"type": "string",
"description": "External reference (e.g. DO-178C section)",
},
"attrs": {
"type": "object",
"properties": {
"dal_level": {"type": "string"},
"priority": {"type": "string"},
"category": {"type": "string"},
"linked_files": {
"type": "array",
"items": {"type": "string"},
},
"linked_functions": {
"type": "array",
"items": {"type": "string"},
},
"linked_tests": {
"type": "array",
"items": {"type": "string"},
},
},
},
},
"required": ["uid", "text"],
}
def get_document_type(prefix: str) -> DocumentType | None:
"""Look up a document type by its prefix."""
for doc_type in DOCUMENT_TYPES.values():
if doc_type.prefix == prefix:
return doc_type
return None
def validate_requirement(data: dict) -> list[str]:
"""Validate a requirement dict against the schema. Returns list of errors."""
errors = []
if "uid" not in data:
errors.append("Missing required field: uid")
if "text" not in data:
errors.append("Missing required field: text")
attrs = data.get("attrs", {})
for attr_name, attr_def in CUSTOM_ATTRIBUTES.items():
if attr_def.required and attr_name not in attrs:
if attr_def.default:
continue # has default, OK
errors.append(f"Missing required attribute: {attr_name}")
return errors
if __name__ == "__main__":
import json
print("UniversalisOS Doorstop Configuration")
print("Document types:")
for name, dt in DOCUMENT_TYPES.items():
print(f" {name}: prefix={dt.prefix}, defaults={dt.attribute_defaults}")
print("Custom attributes:")
for name, attr in CUSTOM_ATTRIBUTES.items():
print(f" {name}: type={attr.type}, required={attr.required}")
print(f"\nYAML schema: {json.dumps(YAML_SCHEMA, indent=2)}")