|
1 | 1 | = How it Works |
2 | 2 |
|
3 | | -This page covers the internal architecture of the reqstool client. For general concepts like annotations, parsing, and validation, see xref:reqstool::concepts.adoc[Concepts]. |
| 3 | +This page covers the internal architecture of the reqstool client. For general concepts like annotations, parsing, and validation, see xref:reqstool::concepts.adoc[Concepts]. For detailed architectural decisions, see the link:https://github.com/reqstool/reqstool-client/blob/main/docs/DESIGN.md[DESIGN.md] in the repository. |
4 | 4 |
|
5 | | -== Template generation |
| 5 | +== Pipeline overview |
6 | 6 |
|
7 | | -The CombinedIndexedDatasetGenerator prepares the data provided from the CombinedRawDatasetsGenerator for rendering with the Jinja2 templates and is used by the ReportCommand and the StatusCommand components. |
| 7 | +---- |
| 8 | +Location → parse → RawDataset → INSERT into SQLite → Repository/Services → Command output |
| 9 | +---- |
8 | 10 |
|
9 | | -== Overview of central components |
| 11 | +Each command calls `build_database()` which: |
| 12 | + |
| 13 | +1. Parses all sources into the in-memory SQLite database (two-phase traversal, see below) |
| 14 | +2. Applies filters (`DatabaseFilterProcessor`) — removes out-of-scope requirements and applies user-defined `filters:` blocks |
| 15 | +3. Runs lifecycle validation (warns on DEPRECATED/OBSOLETE references) |
| 16 | +4. Commands then query via `RequirementsRepository` and service layer |
| 17 | + |
| 18 | +== Two-phase graph traversal |
| 19 | + |
| 20 | +The requirement graph is a directed graph of URNs connected by two edge types: |
| 21 | + |
| 22 | +* **`import`** — "I reference requirements from this URN" (upward, toward requirement definitions) |
| 23 | +* **`implementation`** — "this URN provides evidence for my requirements" (downward, toward code/tests) |
| 24 | + |
| 25 | +`CombinedRawDatasetsGenerator` traverses this graph in two phases: |
| 26 | + |
| 27 | +=== Phase 1 — import chain (recursive DFS, full insert) |
| 28 | + |
| 29 | +Follows `imports:` sections recursively, depth-first. For each node, all five data types are fully inserted into SQLite: requirements, SVCs, MVRs, annotations, and test results. Cycle detection raises `CircularImportError`. |
| 30 | + |
| 31 | +=== Phase 2 — implementation chain (recursive, metadata-only insert) |
10 | 32 |
|
11 | | -Below is a breakdown of the central components of reqstool: |
| 33 | +Follows `implementations:` sections recursively. Think library-uses-library — lib-a can itself have implementations (lib-b → lib-c), all of which may contribute test evidence for the initial URN's requirements. |
| 34 | + |
| 35 | +For each implementation node, `requirements.yml` is parsed (validation runs) but only the URN metadata is inserted — the requirement rows are excluded. All other files (SVCs, MVRs, annotations, test results) are inserted normally; SQLite FK constraints automatically discard rows that reference requirements outside the current scope. Cycle detection raises `CircularImplementationError`. |
| 36 | + |
| 37 | +NOTE: `imports:` sections of implementation nodes are not followed — an implementation's own imports belong to a different scope. |
| 38 | + |
| 39 | +== The `variant` field |
| 40 | + |
| 41 | +`variant: system/microservice/external` in `requirements.yml` is optional advisory metadata. It is not a behavioral gate — parsing is entirely presence-based. If a file exists, it is read. If a section exists in YAML, it is parsed. |
| 42 | + |
| 43 | +== Overview of central components |
12 | 44 |
|
13 | 45 | [plantuml,format=svg] |
14 | 46 | .... |
15 | 47 | @startuml |
16 | 48 | !include <C4/C4_Component> |
17 | 49 |
|
18 | | -Component(StatusCommand, "StatusCommand", "Processes status command") |
19 | | -Component(GenerateJsonCommand, "GenerateJsonCommand", "Generates JSON from imported Models") |
20 | | -Component(ReportCommand, "ReportCommand", "Generates reports") |
21 | | -Component(SemanticValidator, "SemanticValidator", "Validates data read from source") |
22 | | -Component(CombinedRawDatasetsGenerator, "CombinedRawDatasetsGenerator", "Generates imported models") |
23 | | -Component(reqstoolConfig, "reqstoolConfig", "Resolves paths to yaml files") |
24 | | -Component(CombinedIndexedDatasetGenerator, "CombinedIndexedDatasetGenerator", "Prepares data for rendering of Jinja2 templates") |
25 | | -Component(Command, "Command", "Handles user commands") |
26 | | -
|
27 | | -Rel(Command, StatusCommand, "Uses") |
28 | | -Rel(Command, GenerateJsonCommand, "Uses") |
29 | | -Rel(Command, ReportCommand, "Uses") |
30 | | -Rel(CombinedRawDatasetsGenerator, SemanticValidator, "Depends on") |
31 | | -Rel_Right(CombinedRawDatasetsGenerator, reqstoolConfig, "Uses") |
32 | | -Rel(StatusCommand, CombinedRawDatasetsGenerator, "Uses") |
33 | | -Rel(GenerateJsonCommand, CombinedRawDatasetsGenerator, "Uses") |
34 | | -Rel(ReportCommand, CombinedRawDatasetsGenerator, "Uses") |
35 | | -Rel(ReportCommand, CombinedIndexedDatasetGenerator, "Uses") |
36 | | -Rel(StatusCommand, CombinedIndexedDatasetGenerator, "Uses") |
37 | | -
|
38 | | -Rel_Down(CombinedRawDatasetsGenerator, CombinedIndexedDatasetGenerator, "Provides data to") |
| 50 | +Component(Command, "Command", "Handles user commands (status, report, export)") |
| 51 | +Component(CombinedRawDatasetsGenerator, "CombinedRawDatasetsGenerator", "Two-phase graph traversal and SQLite population") |
| 52 | +Component(DatabaseFilterProcessor, "DatabaseFilterProcessor", "Post-parse requirement/SVC filtering") |
| 53 | +Component(RequirementsRepository, "RequirementsRepository", "Data access layer over SQLite") |
| 54 | +Component(StatisticsService, "StatisticsService", "Computes per-requirement status and totals") |
| 55 | +Component(SemanticValidator, "SemanticValidator", "Cross-reference validation") |
| 56 | +Component(SQLiteDB, "SQLite (in-memory)", "Single source of truth after parsing") |
| 57 | +
|
| 58 | +Rel(Command, CombinedRawDatasetsGenerator, "build_database()") |
| 59 | +Rel(CombinedRawDatasetsGenerator, SQLiteDB, "INSERT") |
| 60 | +Rel(CombinedRawDatasetsGenerator, SemanticValidator, "validate_post_parsing()") |
| 61 | +Rel(DatabaseFilterProcessor, SQLiteDB, "DELETE (filters)") |
| 62 | +Rel(RequirementsRepository, SQLiteDB, "SELECT") |
| 63 | +Rel(StatisticsService, RequirementsRepository, "queries") |
| 64 | +Rel(Command, StatisticsService, "Uses") |
| 65 | +Rel(Command, RequirementsRepository, "Uses") |
39 | 66 |
|
40 | 67 | @enduml |
41 | 68 | .... |
42 | 69 |
|
43 | | -== Sequence diagram of the program execution |
| 70 | +== Sequence diagram |
44 | 71 |
|
45 | | -Below is an example to illustrate how reqstool parses data from the initial source. |
| 72 | +Below illustrates how reqstool processes the `status` command against an initial source that imports a parent system. |
46 | 73 |
|
47 | 74 | [plantuml,format=svg] |
48 | 75 | .... |
49 | 76 | @startuml |
50 | 77 | !include <C4/C4_Sequence> |
51 | 78 |
|
52 | | -Person(user, "User", "", "") |
53 | | -
|
| 79 | +Person(user, "User") |
54 | 80 | Container(reqsTool, "reqstool") |
55 | 81 |
|
56 | | -Container_Boundary(b, "Requirement files") |
57 | | - Container_Boundary(b1, "MS-001") |
58 | | - Component(reqs, "Requirements", "Requirements.yml") |
59 | | - Component(svcs, "SVCS", "software_verification_cases.yml") |
60 | | - Component(mvrs, "MVRS", "manual_verification_results.yml") |
61 | | - Component(annot_impls,"Implementations", "requirements_annotations.yml") |
62 | | - Component(annot_tests,"Automated tests", "svcs_annotations.yml") |
63 | | - Boundary_End() |
64 | | - Container_Boundary(b2, "Ext-001") |
65 | | - Component(reqs_ext, "Requirements", "Requirements.yml") |
66 | | - Boundary_End() |
| 82 | +Container_Boundary(phase1, "Phase 1 — import chain") |
| 83 | + Component(initial_reqs, "initial/requirements.yml") |
| 84 | + Component(initial_svcs, "initial/svcs.yml") |
| 85 | + Component(parent_reqs, "parent/requirements.yml") |
| 86 | +Boundary_End() |
| 87 | +
|
| 88 | +Container_Boundary(phase2, "Phase 2 — implementation chain") |
| 89 | + Component(impl_reqs, "lib-a/requirements.yml") |
| 90 | + Component(impl_svcs, "lib-a/svcs.yml") |
| 91 | + Component(impl_tests, "lib-a/test results") |
67 | 92 | Boundary_End() |
68 | 93 |
|
69 | | -Rel(user, reqsTool, "Submit command", "bash") |
70 | | -Rel(reqsTool, reqs, "Reads requirements") |
71 | | -Rel(reqsTool, svcs, "Reads svcs") |
72 | | -Rel(reqsTool, mvrs, "Reads mvrs") |
73 | | -Rel(reqsTool, annot_impls, "Reads impls annotations") |
74 | | -Rel(reqsTool, annot_tests, "Reads test annotations") |
75 | | -Rel(reqsTool, reqsTool, "Create imported model") |
76 | | -Rel(reqsTool, reqs_ext, "Reads imported requirements") |
77 | | -Rel(reqsTool, reqsTool, "Create imported model") |
78 | | -Rel(reqsTool, user, "Returns combined data based on imported") |
| 94 | +Rel(user, reqsTool, "reqstool status local -p ./initial") |
| 95 | +Rel(reqsTool, initial_reqs, "parse + full insert") |
| 96 | +Rel(reqsTool, initial_svcs, "parse + full insert") |
| 97 | +Rel(reqsTool, parent_reqs, "parse + full insert (recursive)") |
| 98 | +Rel(reqsTool, impl_reqs, "parse + metadata only") |
| 99 | +Rel(reqsTool, impl_svcs, "parse + FK-scoped insert") |
| 100 | +Rel(reqsTool, impl_tests, "parse + scoped insert") |
| 101 | +Rel(reqsTool, reqsTool, "post-parse: delete impl-child requirements") |
| 102 | +Rel(reqsTool, user, "status table (exit code = unmet requirements)") |
79 | 103 |
|
80 | 104 | @enduml |
81 | 105 | .... |
0 commit comments